fix: Port upstream cover extraction fallback and outline improvements

Port PR #838 (epub cover fallback logic) and PR #907 (cover outlines):

- Add fallback cover filename probing when EPUB metadata lacks cover info
- Case-insensitive extension checking for cover images
- Detect and re-generate corrupt/empty thumbnail BMPs
- Always draw outline rect on cover tiles for legibility (PR #907)
- Upgrade Storage.exists() checks to Epub::isValidThumbnailBmp()
- Fallback chain: Real Cover → PlaceholderCoverGenerator → X-pattern marker
- Add epub.load retry logic (cache-only first, then full build)
- Adapt upstream Serial.printf calls to LOG_DBG/LOG_ERR macros

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
cottongin
2026-02-16 01:20:27 -05:00
parent 744d6160e8
commit b965ce9fb7
6 changed files with 400 additions and 77 deletions

View File

@@ -642,11 +642,14 @@ void SleepActivity::renderCoverSleepScreen() const {
if (!lastEpub.generateCoverBmp(cropped)) {
LOG_DBG("SLP", "EPUB cover generation failed, trying placeholder");
PlaceholderCoverGenerator::generate(lastEpub.getCoverBmpPath(cropped), lastEpub.getTitle(),
lastEpub.getAuthor(), 480, 800);
if (!PlaceholderCoverGenerator::generate(lastEpub.getCoverBmpPath(cropped), lastEpub.getTitle(),
lastEpub.getAuthor(), 480, 800)) {
LOG_DBG("SLP", "Placeholder generation failed, creating X-pattern marker");
lastEpub.generateInvalidFormatCoverBmp(cropped);
}
}
if (!Storage.exists(lastEpub.getCoverBmpPath(cropped).c_str())) {
if (!Epub::isValidThumbnailBmp(lastEpub.getCoverBmpPath(cropped))) {
LOG_ERR("SLP", "Failed to generate cover bmp");
return (this->*renderNoCoverSleepScreen)();
}

View File

@@ -65,7 +65,7 @@ void HomeActivity::loadRecentCovers(int coverHeight) {
for (RecentBook& book : recentBooks) {
if (!book.coverBmpPath.empty()) {
std::string coverPath = UITheme::getCoverThumbPath(book.coverBmpPath, coverHeight);
if (!Storage.exists(coverPath.c_str())) {
if (!Epub::isValidThumbnailBmp(coverPath)) {
if (!showingLoading) {
showingLoading = true;
popupRect = GUI.drawPopup(renderer, "Loading...");
@@ -74,21 +74,46 @@ void HomeActivity::loadRecentCovers(int coverHeight) {
bool success = false;
// Try format-specific thumbnail generation first
// Try format-specific thumbnail generation first (Real Cover)
if (StringUtils::checkFileExtension(book.path, ".epub")) {
Epub epub(book.path, "/.crosspoint");
epub.load(false, true);
// Try fast cache-only load first; only build cache if missing
if (!epub.load(false, true)) {
// Cache missing — build it (may take longer)
epub.load(true, true);
}
success = epub.generateThumbBmp(coverHeight);
if (success) {
const std::string thumbPath = epub.getThumbBmpPath(coverHeight);
RECENT_BOOKS.updateBook(book.path, book.title, book.author, thumbPath);
book.coverBmpPath = thumbPath;
} else {
// Fallback: generate a placeholder thumbnail with title/author
const int thumbWidth = static_cast<int>(coverHeight * 0.6);
success = PlaceholderCoverGenerator::generate(coverPath, book.title, book.author, thumbWidth, coverHeight);
if (!success) {
// Last resort: X-pattern marker to prevent repeated generation attempts
epub.generateInvalidFormatThumbBmp(coverHeight);
}
}
} else if (StringUtils::checkFileExtension(book.path, ".xtch") ||
StringUtils::checkFileExtension(book.path, ".xtc")) {
Xtc xtc(book.path, "/.crosspoint");
if (xtc.load()) {
success = xtc.generateThumbBmp(coverHeight);
if (success) {
const std::string thumbPath = xtc.getThumbBmpPath(coverHeight);
RECENT_BOOKS.updateBook(book.path, book.title, book.author, thumbPath);
book.coverBmpPath = thumbPath;
}
}
}
// Fallback: generate a placeholder thumbnail with title/author
if (!success) {
if (!success) {
// Fallback: generate a placeholder thumbnail with title/author
const int thumbWidth = static_cast<int>(coverHeight * 0.6);
PlaceholderCoverGenerator::generate(coverPath, book.title, book.author, thumbWidth, coverHeight);
}
} else {
// Unknown format: generate a placeholder thumbnail
const int thumbWidth = static_cast<int>(coverHeight * 0.6);
PlaceholderCoverGenerator::generate(coverPath, book.title, book.author, thumbWidth, coverHeight);
}

View File

@@ -131,32 +131,41 @@ void EpubReaderActivity::onEnter() {
GUI.fillPopupProgress(renderer, popupRect, completedSteps * 100 / totalSteps);
};
if (!Storage.exists(epub->getCoverBmpPath(false).c_str())) {
if (!Epub::isValidThumbnailBmp(epub->getCoverBmpPath(false))) {
epub->generateCoverBmp(false);
// Fallback: generate placeholder if real cover extraction failed
if (!Storage.exists(epub->getCoverBmpPath(false).c_str())) {
PlaceholderCoverGenerator::generate(epub->getCoverBmpPath(false), epub->getTitle(), epub->getAuthor(), 480,
800);
if (!Epub::isValidThumbnailBmp(epub->getCoverBmpPath(false))) {
if (!PlaceholderCoverGenerator::generate(epub->getCoverBmpPath(false), epub->getTitle(), epub->getAuthor(),
480, 800)) {
// Last resort: X-pattern marker
epub->generateInvalidFormatCoverBmp(false);
}
}
updateProgress();
}
if (!Storage.exists(epub->getCoverBmpPath(true).c_str())) {
if (!Epub::isValidThumbnailBmp(epub->getCoverBmpPath(true))) {
epub->generateCoverBmp(true);
if (!Storage.exists(epub->getCoverBmpPath(true).c_str())) {
PlaceholderCoverGenerator::generate(epub->getCoverBmpPath(true), epub->getTitle(), epub->getAuthor(), 480,
800);
if (!Epub::isValidThumbnailBmp(epub->getCoverBmpPath(true))) {
if (!PlaceholderCoverGenerator::generate(epub->getCoverBmpPath(true), epub->getTitle(), epub->getAuthor(),
480, 800)) {
// Last resort: X-pattern marker
epub->generateInvalidFormatCoverBmp(true);
}
}
updateProgress();
}
for (int i = 0; i < PRERENDER_THUMB_HEIGHTS_COUNT; i++) {
if (!Storage.exists(epub->getThumbBmpPath(PRERENDER_THUMB_HEIGHTS[i]).c_str())) {
if (!Epub::isValidThumbnailBmp(epub->getThumbBmpPath(PRERENDER_THUMB_HEIGHTS[i]))) {
epub->generateThumbBmp(PRERENDER_THUMB_HEIGHTS[i]);
// Fallback: generate placeholder thumbnail
if (!Storage.exists(epub->getThumbBmpPath(PRERENDER_THUMB_HEIGHTS[i]).c_str())) {
if (!Epub::isValidThumbnailBmp(epub->getThumbBmpPath(PRERENDER_THUMB_HEIGHTS[i]))) {
const int thumbHeight = PRERENDER_THUMB_HEIGHTS[i];
const int thumbWidth = static_cast<int>(thumbHeight * 0.6);
PlaceholderCoverGenerator::generate(epub->getThumbBmpPath(thumbHeight), epub->getTitle(),
epub->getAuthor(), thumbWidth, thumbHeight);
if (!PlaceholderCoverGenerator::generate(epub->getThumbBmpPath(thumbHeight), epub->getTitle(),
epub->getAuthor(), thumbWidth, thumbHeight)) {
// Last resort: X-pattern marker
epub->generateInvalidFormatThumbBmp(thumbHeight);
}
}
updateProgress();
}