EPUB handling
EPUB support uses two external libraries through booktx adapters:
epub2textfor structured extractiontext2epubfor rebuild
The adapter code lives mainly in:
booktx.epub_iobooktx.epub_manifestbooktx.build
Extraction flow
booktx extract calls extract_epub().
The extraction policy requests:
raw documents
character offsets
inline runs
no duplicate-title removal
no nav document text as translatable prose
no pre-segmentation by
epub2text
booktx then:
maps structured blocks back to raw XHTML offsets
protects configured names
stores ordered span references
converts structured extraction data into a
text2epubmanifestwrites
.booktx/source-manifest.json
Fresh EPUB chunk rule
New EPUB chunks should contain clean block text and __NAME_NNN__ placeholders only.
Fresh EPUB extraction must not emit:
__TAG_NNN__
__SPANTX_NNNN__
If these appear in fresh EPUB chunks, extraction is considered defective. Legacy EPUB projects should be re-extracted after upgrading.
Manifest v2
EPUB rebuild uses .booktx/source-manifest.json version 2.
The manifest stores:
source filename
source format
source and target languages
source SHA256
chunk count
record count
EPUB template data
text2epubextraction manifestspan references
navigation references
Build rejects legacy EPUB manifests and asks the user to rerun extraction.
Source checksum
EPUB build verifies that the current source EPUB matches the checksum recorded at extraction time.
If the source changed, rebuild fails. Re-run extraction after intentional source changes.
Identity build
The intended gold standard is that pass-through EPUB builds preserve the extracted source EPUB bytes by default. Pass-through profiles resolve to a fully preserving EPUB output policy and so remain byte-identical.
Translation profiles, by contrast, apply an EPUB output policy that rewrites publication/content language and injects a deterministic hyphenation style sheet, so a translated build is not expected to be byte-identical to the source. See EPUB output-language and hyphenation policy below.
EPUB output-language and hyphenation policy
A translated EPUB build writes the resolved target language to the primary
OPF dc:language and to the lang/xml:lang of targeted XHTML root
elements, and injects one deterministic best-effort hyphenation/word-break
style sheet into eligible reflowable documents. Descendant language
annotations (for example a quoted passage in another language) are preserved.
This is a metadata and author-style correctness contract, not a promise
of identical rendering across reading systems. Automatic hyphenation depends
on the reader and its installed dictionaries; booktx cannot control computed
style. CSS cascade conflicts (source !important, higher-specificity rules,
or reader styles) are reported as warnings because they can override the
injected policy.
Defaults depend on profile kind:
translation and legacy translation projects default to
targetlanguage andautohyphenation;pass-through profiles default to
preserve/preserve(byte-identical output).
Override the policy explicitly under [epub_output] in the profile (or
legacy) config:
[epub_output]
language_policy = "target"
language = "de-DE" # required only when language_policy = "explicit"
hyphenation = "auto" # auto | manual | none | preserve
inject_css = true
patch_body_language = false
hyphenation = "none" is the compatibility escape hatch: it disables the
generated automatic hyphenation when a reader keeps producing bad breaks.
The build is transactional: a failed policy resolution, rebuild, or audit
leaves the last successful output untouched. The build report keeps the
existing top-level keys (changed_entries, replacement_count,
unresolved_token_count) and adds an epub_output_policy object with the
resolved language, hyphenation mode, patched XHTML count, and warning count.
Reconstruction validation
To verify that extraction and reconstruction include all content, create a pass-through profile that rebuilds the EPUB from source-as-target chunks:
booktx extract ./book
booktx pass-through ./book --profile passthrough_en --create
Then compare the source and rebuilt output byte-for-byte (for the fixture) or with an EPUB diff viewer (for real books):
source/book.epub
translations/passthrough_en/output/book.en.epub
Changed block tradeoff
The current EPUB rebuild path replaces changed blocks with escaped translated text for the whole block body.
This preserves identity builds, but changed blocks can lose inner inline markup such as <strong> or <em> until a future text-run-preserving replacement mode exists.
Chapter detection
EPUB chapter detection combines several signals rather than trusting a single source:
upstream
epub2textblock chapter annotations — the authoritative source for manifests markedchapter_mapping="epub2text-block-v1". New extractions persistTextBlock.chapter_id/chapter_titleonto each span ref and use them even when the set is empty (an authoritative “no assignment” result).heading tags (
h1throughh6) that extend a numbered sequenceTOC-derived document starts: when a boundary source is partial and a visible contents page links to an extracted XHTML document, the first span of that document becomes a chapter boundary
a single chapter covering the whole record stream (last resort)
Boundaries are resolved through canonical Record.span_index metadata, so a
multi-sentence block never shifts a chapter start. Old manifests without
block annotations (chapter_mapping="legacy") fall back to a conservative
navigation mapper that ignores fallback/unresolved navigation entries and
offsets beyond extracted spans; re-extraction is required to gain upstream
annotations. Detection no longer trusts partial navigation blindly: when a
boundary source is a strict subset of a strongly chapter-like heading
sequence, headings complete the map. TOC-derived boundaries are only used
for documents that were actually extracted, so a truncated/preview EPUB
never produces empty chapter entries. Relative visible-TOC hrefs are resolved
against their containing XHTML document (no same-basename collapse).
Chapter completeness audit
A visible contents page can promise more chapters than were extracted or detected (for example a preview EPUB, a skipped spine document, or partial navigation). The audit compares the visible TOC against extracted spans, navigation, and the chapter map:
booktx chapters ./book --audit
booktx chapters ./book --audit --json
The audit writes .booktx/reports/chapter-audit.json and is also surfaced by
booktx validate and booktx check for unscoped EPUB runs. Deterministic
finding codes and severities:
error epub_toc_href_extracted_but_unmapped: the TOC target has extracted spans but no chapter boundary covers it.warning epub_toc_chapter_missing_from_map: a numbered TOC entry is not in the chapter map.warning epub_toc_href_missing_from_extracted_spans: the TOC target has no extracted span (truncated/preview EPUB or extraction skip).warning epub_navigation_partial: navigation covers fewer numbered chapters than visible chapter signals.warning epub_chapter_sequence_gap: numbered TOC chapters have gaps.
The chapter map and audit are generated automatically during booktx extract:
extract writes .booktx/chapter-map.json, runs the audit, writes
.booktx/reports/chapter-audit.json, and prints a one-line warning with the
booktx chapters . --audit hint when findings exist. Extraction stays
successful for warning-only preview/truncation cases (it is a completeness
signal, not a policy gate). The chapter-map algorithm is versioned
(ChapterMap.version); a cached map whose version is older than the current
algorithm is regenerated even when the source SHA is unchanged.
booktx status recomputes the audit summary for the current source rather
than trusting the persisted report, and shows it when findings exist. New
chapter/task/todo selection (next, next-chapter, translate next --chapter, todo creation) refuses to create new work only on error audit
findings (for example epub_toc_href_extracted_but_unmapped); warning-only
findings remain visible but non-blocking. This keeps the three counts distinct:
visible-TOC count — chapters promised by the contents page (audit only).
extracted-spine documents — XHTML documents actually present in the spine.
chapter-map count — chapters booktx will translate.
If the chapter-map count is lower than the visible-TOC count, inspect the source rather than trusting the contents page:
booktx chapters . --audit
booktx epub inspect .
Re-extraction for upstream annotations
Projects extracted before chapter_mapping="epub2text-block-v1" use the
conservative legacy navigation fallback and do not carry upstream block
annotations. Re-extract to gain them:
booktx extract .
Common EPUB errors
Legacy manifest
Message:
This project uses the legacy EPUB extraction format. Re-run `booktx extract` after upgrading.
Fix:
booktx extract .
Source checksum mismatch
The source EPUB bytes differ from the extraction manifest. Restore the original source or re-extract.
Unresolved placeholder in output EPUB
A target likely omitted or changed a placeholder token. Run validation, repair the translated chunk, and rebuild.
Inline XHTML semantics
EPUB extraction exposes inline XHTML fragments in record source values when the source block contains inline semantics. EPUB records use source_markup="epub-inline-xhtml:v1". Legacy plain records continue to load as plain:v1.
During rebuild, changed EPUB targets are parsed and sanitized as constrained inline XHTML before text2epub receives allow_inline_xhtml=True. Identity/pass-through output uses the plain expected source so reconstruction checks remain useful.