Magazines break the audiobook pipeline's assumptions: several unrelated
articles share a page, columns run in parallel, adverts and sidebars
interrupt body copy, and a feature routinely jumps twenty pages to its
tail. Narrating page-by-page walks out of an article mid-sentence and
into an advert, so the canonical object is the article, not the page.
core/magazine.py reconstructs articles from a flat PDF: PyMuPDF extracts
blocks with bboxes and font metrics, tesseract supplies a TSV fallback so
scanned pages join the same column grouping, blocks are classified and
ordered column-major, and articles are anchored by the parsed table of
contents with "continued on page N" tails linked back.
routes/magazine.py adds 13 endpoints. Ingestion is a restart-safe
background job. Nothing is narrated until a human approves the
boundaries; podcast generation refuses unapproved articles.
Podcast mode renders an approved article as a 2-3 host discussion using
the configured LLM and voices the user picks, alongside narration rather
than instead of it.
Verified end-to-end over HTTP on a synthetic 8-page issue: the feature
came back as pages [3, 8] with its jump tail absorbed and the advert page
excluded, and an image-only rendering recovered all 47 blocks via OCR.
44 new tests; full suite 90 passing.
The Studio UI is not built — this is reachable only via the API.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
- Deterministic hash-based character portraits and reference sheets
- Implemented strict early Renaissance aesthetic controls and role-specific headwear rules
- Added audiobook batch export scripts and progress tooling
- Verified book profiles with a new 'confirmed' state tracking mechanism
- Updated Pillow dependency for robust image handling
- Configured .gitignore for audiobook and audio cache exports
Fish-Speech clones from the app's own saved WAV library but was missing from
the set of backends populated with those voices, so the UI probed the Fish
server for a voice-listing endpoint it does not have, showed "Fetched 0
voices" and left the engine unusable despite being healthy. It now reports
the full library (218 voices).
Also adds a Fish-only panel listing all 49 documented emotion tags with
click-to-insert and a multi-emotion example, and corrects the backend's
advertised capabilities: it was flagged style_aware with "emotion markers are
honoured per request", but measurement shows a reference clip's in-context
prosody overwhelms inline tags (10x loudness spread across emotions without a
reference, 1.2x with one). It is now described by what it does well:
deterministic, byte-reproducible cloning.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Fish-Speech emotion tags were silently ignored on non-English books: per-line
emotions are LLM-generated in the book's own language, but Fish-Speech only
recognizes English [tag] markers, and a double-tagging bug was stacking a
broken server-derived tag on top of the client's own. Added a DE->EN
translation table and removed the double-tagging. Also wires the existing
book-profile context and race_species field into character portrait prompts
(previously only used for voice design), adds a recast-until-threshold loop
for casting, and adds backend-aware emotion quick-picks to Read Aloud, Try a
Voice, and Conversation.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
For a designed voice the instruct prompt is the voice's identity — the TTS
engine reproduces the voice from that text alone. The only copy saved was the
`note` display summary, clipped to 240 characters, which left 43 of 73 voices
cut off mid-sentence. Save the complete prompt in its own field so the engine
can register a voice from the whole description.
Existing voices keep working from the clipped copy (it still carries gender,
accent and timbre) and pick up the full text when next redesigned.
Pairs with the engine-side fix in tts-dgx-spark-faster-qwen3-tts.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>