nice
This commit is contained in:
183
lib/Txt/Txt.cpp
183
lib/Txt/Txt.cpp
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user