This commit is contained in:
cottongin
2026-01-24 02:01:53 -05:00
parent 2952d7554c
commit 5c3828efe8
14 changed files with 908 additions and 24 deletions

View File

@@ -169,6 +169,189 @@ bool Txt::generateCoverBmp() const {
return false;
}
std::string Txt::getThumbBmpPath() const { return cachePath + "/thumb.bmp"; }
bool Txt::generateThumbBmp() const {
// Already generated, return true
if (SdMan.exists(getThumbBmpPath().c_str())) {
return true;
}
std::string coverImagePath = findCoverImage();
if (coverImagePath.empty()) {
Serial.printf("[%lu] [TXT] No cover image found for thumbnail\n", millis());
return false;
}
// Setup cache directory
setupCacheDir();
// Get file extension
const size_t len = coverImagePath.length();
const bool isJpg =
(len >= 4 && (coverImagePath.substr(len - 4) == ".jpg" || coverImagePath.substr(len - 4) == ".JPG")) ||
(len >= 5 && (coverImagePath.substr(len - 5) == ".jpeg" || coverImagePath.substr(len - 5) == ".JPEG"));
if (isJpg) {
// Convert JPG to 1-bit BMP thumbnail
Serial.printf("[%lu] [TXT] Generating thumb BMP from JPG cover image\n", millis());
FsFile coverJpg, thumbBmp;
if (!SdMan.openFileForRead("TXT", coverImagePath, coverJpg)) {
return false;
}
if (!SdMan.openFileForWrite("TXT", getThumbBmpPath(), thumbBmp)) {
coverJpg.close();
return false;
}
constexpr int THUMB_TARGET_WIDTH = 240;
constexpr int THUMB_TARGET_HEIGHT = 400;
const bool success =
JpegToBmpConverter::jpegFileTo1BitBmpStreamWithSize(coverJpg, thumbBmp, THUMB_TARGET_WIDTH, THUMB_TARGET_HEIGHT);
coverJpg.close();
thumbBmp.close();
if (!success) {
Serial.printf("[%lu] [TXT] Failed to generate thumb BMP from JPG cover image\n", millis());
SdMan.remove(getThumbBmpPath().c_str());
} else {
Serial.printf("[%lu] [TXT] Generated thumb BMP from JPG cover image\n", millis());
}
return success;
}
// For BMP files, just copy cover.bmp to thumb.bmp (no scaling for BMP)
if (generateCoverBmp() && SdMan.exists(getCoverBmpPath().c_str())) {
FsFile src, dst;
if (SdMan.openFileForRead("TXT", getCoverBmpPath(), src)) {
if (SdMan.openFileForWrite("TXT", getThumbBmpPath(), dst)) {
uint8_t buffer[512];
while (src.available()) {
size_t bytesRead = src.read(buffer, sizeof(buffer));
dst.write(buffer, bytesRead);
}
dst.close();
}
src.close();
}
Serial.printf("[%lu] [TXT] Copied cover to thumb\n", millis());
return SdMan.exists(getThumbBmpPath().c_str());
}
return false;
}
std::string Txt::getMicroThumbBmpPath() const { return cachePath + "/micro_thumb.bmp"; }
bool Txt::generateMicroThumbBmp() const {
// Already generated, return true
if (SdMan.exists(getMicroThumbBmpPath().c_str())) {
return true;
}
std::string coverImagePath = findCoverImage();
if (coverImagePath.empty()) {
Serial.printf("[%lu] [TXT] No cover image found for micro thumbnail\n", millis());
return false;
}
// Setup cache directory
setupCacheDir();
// Get file extension
const size_t len = coverImagePath.length();
const bool isJpg =
(len >= 4 && (coverImagePath.substr(len - 4) == ".jpg" || coverImagePath.substr(len - 4) == ".JPG")) ||
(len >= 5 && (coverImagePath.substr(len - 5) == ".jpeg" || coverImagePath.substr(len - 5) == ".JPEG"));
if (isJpg) {
// Convert JPG to 1-bit BMP micro thumbnail
Serial.printf("[%lu] [TXT] Generating micro thumb BMP from JPG cover image\n", millis());
FsFile coverJpg, microThumbBmp;
if (!SdMan.openFileForRead("TXT", coverImagePath, coverJpg)) {
return false;
}
if (!SdMan.openFileForWrite("TXT", getMicroThumbBmpPath(), microThumbBmp)) {
coverJpg.close();
return false;
}
constexpr int MICRO_THUMB_TARGET_WIDTH = 45;
constexpr int MICRO_THUMB_TARGET_HEIGHT = 60;
const bool success = JpegToBmpConverter::jpegFileTo1BitBmpStreamWithSize(coverJpg, microThumbBmp,
MICRO_THUMB_TARGET_WIDTH, MICRO_THUMB_TARGET_HEIGHT);
coverJpg.close();
microThumbBmp.close();
if (!success) {
Serial.printf("[%lu] [TXT] Failed to generate micro thumb BMP from JPG cover image\n", millis());
SdMan.remove(getMicroThumbBmpPath().c_str());
} else {
Serial.printf("[%lu] [TXT] Generated micro thumb BMP from JPG cover image\n", millis());
}
return success;
}
// For BMP files, just copy cover.bmp to micro_thumb.bmp (no scaling for BMP)
if (generateCoverBmp() && SdMan.exists(getCoverBmpPath().c_str())) {
FsFile src, dst;
if (SdMan.openFileForRead("TXT", getCoverBmpPath(), src)) {
if (SdMan.openFileForWrite("TXT", getMicroThumbBmpPath(), dst)) {
uint8_t buffer[512];
while (src.available()) {
size_t bytesRead = src.read(buffer, sizeof(buffer));
dst.write(buffer, bytesRead);
}
dst.close();
}
src.close();
}
Serial.printf("[%lu] [TXT] Copied cover to micro thumb\n", millis());
return SdMan.exists(getMicroThumbBmpPath().c_str());
}
return false;
}
bool Txt::generateAllCovers(const std::function<void(int)>& progressCallback) const {
// Check if all covers already exist
const bool hasCover = SdMan.exists(getCoverBmpPath().c_str());
const bool hasThumb = SdMan.exists(getThumbBmpPath().c_str());
const bool hasMicroThumb = SdMan.exists(getMicroThumbBmpPath().c_str());
if (hasCover && hasThumb && hasMicroThumb) {
Serial.printf("[%lu] [TXT] All covers already cached\n", millis());
if (progressCallback) progressCallback(100);
return true;
}
std::string coverImagePath = findCoverImage();
if (coverImagePath.empty()) {
Serial.printf("[%lu] [TXT] No cover image found, skipping cover generation\n", millis());
return false;
}
Serial.printf("[%lu] [TXT] Generating all covers (cover:%d, thumb:%d, micro:%d)\n", millis(), !hasCover, !hasThumb,
!hasMicroThumb);
// Generate each cover type that's missing with progress updates
if (!hasCover) {
(void)generateCoverBmp();
}
if (progressCallback) progressCallback(33);
if (!hasThumb) {
(void)generateThumbBmp();
}
if (progressCallback) progressCallback(66);
if (!hasMicroThumb) {
(void)generateMicroThumbBmp();
}
if (progressCallback) progressCallback(100);
Serial.printf("[%lu] [TXT] All cover generation complete\n", millis());
return true;
}
bool Txt::readContent(uint8_t* buffer, size_t offset, size_t length) const {
if (!loaded) {
return false;

View File

@@ -2,6 +2,7 @@
#include <SDCardManager.h>
#include <functional>
#include <memory>
#include <string>
@@ -27,6 +28,14 @@ class Txt {
[[nodiscard]] std::string getCoverBmpPath() const;
[[nodiscard]] bool generateCoverBmp() const;
[[nodiscard]] std::string findCoverImage() const;
// Thumbnail support (for Continue Reading card)
[[nodiscard]] std::string getThumbBmpPath() const;
[[nodiscard]] bool generateThumbBmp() const;
// Micro thumbnail support (for Recent Books list)
[[nodiscard]] std::string getMicroThumbBmpPath() const;
[[nodiscard]] bool generateMicroThumbBmp() const;
// Generate all covers at once (for pre-generation on book open)
[[nodiscard]] bool generateAllCovers(const std::function<void(int)>& progressCallback = nullptr) const;
// Read content from file
[[nodiscard]] bool readContent(uint8_t* buffer, size_t offset, size_t length) const;