mirror of
https://github.com/daveallie/crosspoint-reader.git
synced 2026-02-06 23:57:39 +03:00
feat: Extract author from XTC/XTCH files
This commit is contained in:
parent
a4b9a43ca1
commit
1f669b9880
@ -7,7 +7,6 @@
|
|||||||
|
|
||||||
#include "Xtc.h"
|
#include "Xtc.h"
|
||||||
|
|
||||||
#include <FsHelpers.h>
|
|
||||||
#include <HardwareSerial.h>
|
#include <HardwareSerial.h>
|
||||||
#include <SDCardManager.h>
|
#include <SDCardManager.h>
|
||||||
|
|
||||||
@ -87,6 +86,15 @@ std::string Xtc::getTitle() const {
|
|||||||
return filepath.substr(lastSlash, lastDot - lastSlash);
|
return filepath.substr(lastSlash, lastDot - lastSlash);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::string Xtc::getAuthor() const {
|
||||||
|
if (!loaded || !parser) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
// Try to get author from XTC metadata
|
||||||
|
return parser->getAuthor();
|
||||||
|
}
|
||||||
|
|
||||||
bool Xtc::hasChapters() const {
|
bool Xtc::hasChapters() const {
|
||||||
if (!loaded || !parser) {
|
if (!loaded || !parser) {
|
||||||
return false;
|
return false;
|
||||||
|
|||||||
@ -56,6 +56,7 @@ class Xtc {
|
|||||||
|
|
||||||
// Metadata
|
// Metadata
|
||||||
std::string getTitle() const;
|
std::string getTitle() const;
|
||||||
|
std::string getAuthor() const;
|
||||||
bool hasChapters() const;
|
bool hasChapters() const;
|
||||||
const std::vector<xtc::ChapterInfo>& getChapters() const;
|
const std::vector<xtc::ChapterInfo>& getChapters() const;
|
||||||
|
|
||||||
|
|||||||
@ -47,8 +47,21 @@ XtcError XtcParser::open(const char* filepath) {
|
|||||||
return m_lastError;
|
return m_lastError;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Read title if available
|
// Read title & author if available
|
||||||
readTitle();
|
if (m_header.hasMetadata) {
|
||||||
|
m_lastError = readTitle();
|
||||||
|
if (m_lastError != XtcError::OK) {
|
||||||
|
Serial.printf("[%lu] [XTC] Failed to read title: %s\n", millis(), errorToString(m_lastError));
|
||||||
|
m_file.close();
|
||||||
|
return m_lastError;
|
||||||
|
}
|
||||||
|
m_lastError = readAuthor();
|
||||||
|
if (m_lastError != XtcError::OK) {
|
||||||
|
Serial.printf("[%lu] [XTC] Failed to read author: %s\n", millis(), errorToString(m_lastError));
|
||||||
|
m_file.close();
|
||||||
|
return m_lastError;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Read page table
|
// Read page table
|
||||||
m_lastError = readPageTable();
|
m_lastError = readPageTable();
|
||||||
@ -124,24 +137,34 @@ XtcError XtcParser::readHeader() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
XtcError XtcParser::readTitle() {
|
XtcError XtcParser::readTitle() {
|
||||||
// Title is usually at offset 0x38 (56) for 88-byte headers
|
constexpr auto titleOffset = 0x38;
|
||||||
// Read title as null-terminated UTF-8 string
|
if (!m_file.seek(titleOffset)) {
|
||||||
if (m_header.titleOffset == 0) {
|
|
||||||
m_header.titleOffset = 0x38; // Default offset
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!m_file.seek(m_header.titleOffset)) {
|
|
||||||
return XtcError::READ_ERROR;
|
return XtcError::READ_ERROR;
|
||||||
}
|
}
|
||||||
|
|
||||||
char titleBuf[128] = {0};
|
char titleBuf[128] = {0};
|
||||||
m_file.read(reinterpret_cast<uint8_t*>(titleBuf), sizeof(titleBuf) - 1);
|
m_file.read(titleBuf, sizeof(titleBuf) - 1);
|
||||||
m_title = titleBuf;
|
m_title = titleBuf;
|
||||||
|
|
||||||
Serial.printf("[%lu] [XTC] Title: %s\n", millis(), m_title.c_str());
|
Serial.printf("[%lu] [XTC] Title: %s\n", millis(), m_title.c_str());
|
||||||
return XtcError::OK;
|
return XtcError::OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
XtcError XtcParser::readAuthor() {
|
||||||
|
// Read author as null-terminated UTF-8 string with max length 64, directly following title
|
||||||
|
constexpr auto authorOffset = 0xB8;
|
||||||
|
if (!m_file.seek(authorOffset)) {
|
||||||
|
return XtcError::READ_ERROR;
|
||||||
|
}
|
||||||
|
|
||||||
|
char authorBuf[64] = {0};
|
||||||
|
m_file.read(authorBuf, sizeof(authorBuf) - 1);
|
||||||
|
m_author = authorBuf;
|
||||||
|
|
||||||
|
Serial.printf("[%lu] [XTC] Author: %s\n", millis(), m_author.c_str());
|
||||||
|
return XtcError::OK;
|
||||||
|
}
|
||||||
|
|
||||||
XtcError XtcParser::readPageTable() {
|
XtcError XtcParser::readPageTable() {
|
||||||
if (m_header.pageTableOffset == 0) {
|
if (m_header.pageTableOffset == 0) {
|
||||||
Serial.printf("[%lu] [XTC] Page table offset is 0, cannot read\n", millis());
|
Serial.printf("[%lu] [XTC] Page table offset is 0, cannot read\n", millis());
|
||||||
|
|||||||
@ -67,8 +67,9 @@ class XtcParser {
|
|||||||
std::function<void(const uint8_t* data, size_t size, size_t offset)> callback,
|
std::function<void(const uint8_t* data, size_t size, size_t offset)> callback,
|
||||||
size_t chunkSize = 1024);
|
size_t chunkSize = 1024);
|
||||||
|
|
||||||
// Get title from metadata
|
// Get title/author from metadata
|
||||||
std::string getTitle() const { return m_title; }
|
std::string getTitle() const { return m_title; }
|
||||||
|
std::string getAuthor() const { return m_author; }
|
||||||
|
|
||||||
bool hasChapters() const { return m_hasChapters; }
|
bool hasChapters() const { return m_hasChapters; }
|
||||||
const std::vector<ChapterInfo>& getChapters() const { return m_chapters; }
|
const std::vector<ChapterInfo>& getChapters() const { return m_chapters; }
|
||||||
@ -86,6 +87,7 @@ class XtcParser {
|
|||||||
std::vector<PageInfo> m_pageTable;
|
std::vector<PageInfo> m_pageTable;
|
||||||
std::vector<ChapterInfo> m_chapters;
|
std::vector<ChapterInfo> m_chapters;
|
||||||
std::string m_title;
|
std::string m_title;
|
||||||
|
std::string m_author;
|
||||||
uint16_t m_defaultWidth;
|
uint16_t m_defaultWidth;
|
||||||
uint16_t m_defaultHeight;
|
uint16_t m_defaultHeight;
|
||||||
uint8_t m_bitDepth; // 1 = XTC/XTG (1-bit), 2 = XTCH/XTH (2-bit)
|
uint8_t m_bitDepth; // 1 = XTC/XTG (1-bit), 2 = XTCH/XTH (2-bit)
|
||||||
@ -96,6 +98,7 @@ class XtcParser {
|
|||||||
XtcError readHeader();
|
XtcError readHeader();
|
||||||
XtcError readPageTable();
|
XtcError readPageTable();
|
||||||
XtcError readTitle();
|
XtcError readTitle();
|
||||||
|
XtcError readAuthor();
|
||||||
XtcError readChapters();
|
XtcError readChapters();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@ -38,14 +38,16 @@ struct XtcHeader {
|
|||||||
uint8_t versionMajor; // 0x04: Format version major (typically 1) (together with minor = 1.0)
|
uint8_t versionMajor; // 0x04: Format version major (typically 1) (together with minor = 1.0)
|
||||||
uint8_t versionMinor; // 0x05: Format version minor (typically 0)
|
uint8_t versionMinor; // 0x05: Format version minor (typically 0)
|
||||||
uint16_t pageCount; // 0x06: Total page count
|
uint16_t pageCount; // 0x06: Total page count
|
||||||
uint32_t flags; // 0x08: Flags/reserved
|
uint8_t readDirection; // 0x08: Reading direction (0-2)
|
||||||
uint32_t headerSize; // 0x0C: Size of header section (typically 88)
|
uint8_t hasMetadata; // 0x09: Has metadata (0-1)
|
||||||
uint32_t reserved1; // 0x10: Reserved
|
uint8_t hasThumbnails; // 0x0A: Has thumbnails (0-1)
|
||||||
uint32_t tocOffset; // 0x14: TOC offset (0 if unused) - 4 bytes, not 8!
|
uint8_t hasChapters; // 0x0B: Has chapters (0-1)
|
||||||
|
uint32_t currentPage; // 0x0C: Current page (1-based) (0-65535)
|
||||||
|
uint64_t metadataOffset; // 0x10: Metadata offset (0 if unused)
|
||||||
uint64_t pageTableOffset; // 0x18: Page table offset
|
uint64_t pageTableOffset; // 0x18: Page table offset
|
||||||
uint64_t dataOffset; // 0x20: First page data offset
|
uint64_t dataOffset; // 0x20: First page data offset
|
||||||
uint64_t reserved2; // 0x28: Reserved
|
uint64_t thumbOffset; // 0x28: Thumbnail offset
|
||||||
uint32_t titleOffset; // 0x30: Title string offset
|
uint32_t chapterOffset; // 0x30: Chapter data offset
|
||||||
uint32_t padding; // 0x34: Padding to 56 bytes
|
uint32_t padding; // 0x34: Padding to 56 bytes
|
||||||
};
|
};
|
||||||
#pragma pack(pop)
|
#pragma pack(pop)
|
||||||
|
|||||||
@ -71,6 +71,9 @@ void HomeActivity::onEnter() {
|
|||||||
if (!xtc.getTitle().empty()) {
|
if (!xtc.getTitle().empty()) {
|
||||||
lastBookTitle = std::string(xtc.getTitle());
|
lastBookTitle = std::string(xtc.getTitle());
|
||||||
}
|
}
|
||||||
|
if (!xtc.getAuthor().empty()) {
|
||||||
|
lastBookAuthor = std::string(xtc.getAuthor());
|
||||||
|
}
|
||||||
// Try to generate thumbnail image for Continue Reading card
|
// Try to generate thumbnail image for Continue Reading card
|
||||||
if (xtc.generateThumbBmp()) {
|
if (xtc.generateThumbBmp()) {
|
||||||
coverBmpPath = xtc.getThumbBmpPath();
|
coverBmpPath = xtc.getThumbBmpPath();
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user