A book's .opf, comic metadata, cover and page count are all unread #210

Closed
opened 2026-09-12 17:35:51 +02:00 by Claude · 1 comment
Collaborator

A book is resolved, filed, and given the one second of run time an .epub gets (crates/ephyra-app/src/scan/run/read.rs:470). Nothing else about it is read. Split out of #194, which covered the resolver.

crates/ephyra-app/src/metadata/ holds artwork, audiodb, musicbrainz, omdb and tmdb. Upstream's upstream/MediaBrowser.Providers/Books/ holds:

OpenPackagingFormat/ the .opf beside a book, and the one inside an .epub — title, authors, publisher, series, ISBN, published date
ComicImageProvider.cs the cover, taken as the first page of a .cbz/.cbr archive
ComicInfo/, ComicBookInfo/ ComicInfo.xml inside the archive, and the JSON in a .cbz comment
ComicVine/, GoogleBooks/, Isbn/ remote databases — out of scope here, and out of scope generally while the plugin question is open

The local ones are what matter: they are the difference between a book library that shows filenames and one that shows titles and covers.

Page counts

Neither a comic archive nor a PDF has its page count read (grep page_count crates/ finds only a SQLite pragma). Upstream files it, and it is what a reader client uses for a progress bar.

Where to start

crates/ephyra-app/src/scan/tree/books.rs:17 already knows the extensions: .azw .azw3 .cb7 .cbr .cbt .cbz .epub .mobi .pdf. An .epub and a .cbz are both zip archives, so the .opf read and the first-page cover share most of their machinery; .cbr is rar and is the one that needs a dependency decision.

Verifying

cargo xtask reference gen-library-kinds (xtask/src/reference/kinds.rs) already registers a books library and writes upstream's rows into crates/ephyra-app/corpus/library-kinds.json, replayed by crates/ephyra-app/tests/library_kinds.rs. The tree's books are empty placeholder files, so this needs real archives carrying a known .opf and a known first page in the fixture before any of it can be diffed against upstream. Building them in the generator keeps them reproducible.

A book is resolved, filed, and given the one second of run time an `.epub` gets (`crates/ephyra-app/src/scan/run/read.rs:470`). Nothing else about it is read. Split out of #194, which covered the resolver. `crates/ephyra-app/src/metadata/` holds `artwork`, `audiodb`, `musicbrainz`, `omdb` and `tmdb`. Upstream's `upstream/MediaBrowser.Providers/Books/` holds: | | | |---|---| | `OpenPackagingFormat/` | the `.opf` beside a book, and the one inside an `.epub` — title, authors, publisher, series, ISBN, published date | | `ComicImageProvider.cs` | the cover, taken as the **first page** of a `.cbz`/`.cbr` archive | | `ComicInfo/`, `ComicBookInfo/` | `ComicInfo.xml` inside the archive, and the JSON in a `.cbz` comment | | `ComicVine/`, `GoogleBooks/`, `Isbn/` | remote databases — out of scope here, and out of scope generally while the plugin question is open | The local ones are what matter: they are the difference between a book library that shows filenames and one that shows titles and covers. ## Page counts Neither a comic archive nor a PDF has its page count read (`grep page_count crates/` finds only a SQLite pragma). Upstream files it, and it is what a reader client uses for a progress bar. ## Where to start `crates/ephyra-app/src/scan/tree/books.rs:17` already knows the extensions: `.azw .azw3 .cb7 .cbr .cbt .cbz .epub .mobi .pdf`. An `.epub` and a `.cbz` are both zip archives, so the `.opf` read and the first-page cover share most of their machinery; `.cbr` is rar and is the one that needs a dependency decision. ## Verifying `cargo xtask reference gen-library-kinds` (`xtask/src/reference/kinds.rs`) already registers a `books` library and writes upstream's rows into `crates/ephyra-app/corpus/library-kinds.json`, replayed by `crates/ephyra-app/tests/library_kinds.rs`. The tree's books are empty placeholder files, so this needs **real archives carrying a known `.opf` and a known first page** in the fixture before any of it can be diffed against upstream. Building them in the generator keeps them reproducible.
Author
Collaborator

Fixed, and landed on main at 7bbf49e0.

What is read

  • An .opf beside a book, or the one inside an .epub: title, sort title, authors, publisher, series and index, ISBN, date, rating, genres and tags.
  • ComicInfo.xml beside a comic or inside a .cbz, and the ComicBookInfo JSON in a .cbz comment.
  • Each source is read only for the extensions upstream reads it for.
  • Page counts for zip, tar and PDF, stored as run time. Upstream records a 3-page PDF as 30000 ticks, and so do we.
  • A cover taken from the book: the named cover, otherwise the first page. It is taken only where the library extracts pictures.
  • .cbr and .cb7 are filed by their name alone. D48 records why.

Hostile archives
Every read of an archive is bounded: the zip directory and entry sizes (both uncompressed and stored), the deflate output, tar entries, the PDF tail, cross-reference table and object windows, XML nesting depth, and the .opf/ComicInfo.xml text read. The review's crafted inputs are tests now, and each fails without its bound:

  • a 4 GiB zip entry;
  • 200,000 nested elements;
  • an overflowing xref subsection;
  • /Count at i64::MAX.

Verified
Real EPUB, CBZ, CBT and PDF assets are built reproducibly in xtask/src/reference/assets.rs. The regenerated library-kinds key records upstream's reading of each. xtask/src/reference/kinds.rs compares our reader to it strictly: title, sort title, overview, rating, genres, studios, tags, date, year, number, series and page count. NFO attribute matching is exact again; only the OPF and comic readers strip a namespace prefix.

Filed separately

  • #223: the fallback cover is ordered by bytes, not by upstream's culture comparison.
Fixed, and landed on `main` at `7bbf49e0`. **What is read** - An `.opf` beside a book, or the one inside an `.epub`: title, sort title, authors, publisher, series and index, ISBN, date, rating, genres and tags. - `ComicInfo.xml` beside a comic or inside a `.cbz`, and the ComicBookInfo JSON in a `.cbz` comment. - Each source is read only for the extensions upstream reads it for. - Page counts for zip, tar and PDF, stored as run time. Upstream records a 3-page PDF as 30000 ticks, and so do we. - A cover taken from the book: the named cover, otherwise the first page. It is taken only where the library extracts pictures. - `.cbr` and `.cb7` are filed by their name alone. D48 records why. **Hostile archives** Every read of an archive is bounded: the zip directory and entry sizes (both uncompressed and stored), the deflate output, tar entries, the PDF tail, cross-reference table and object windows, XML nesting depth, and the `.opf`/`ComicInfo.xml` text read. The review's crafted inputs are tests now, and each fails without its bound: - a 4 GiB zip entry; - 200,000 nested elements; - an overflowing xref subsection; - `/Count` at `i64::MAX`. **Verified** Real EPUB, CBZ, CBT and PDF assets are built reproducibly in `xtask/src/reference/assets.rs`. The regenerated library-kinds key records upstream's reading of each. `xtask/src/reference/kinds.rs` compares our reader to it strictly: title, sort title, overview, rating, genres, studios, tags, date, year, number, series and page count. NFO attribute matching is exact again; only the OPF and comic readers strip a namespace prefix. **Filed separately** - #223: the fallback cover is ordered by bytes, not by upstream's culture comparison.
Sign in to join this conversation.
No description provided.