UmbraTrans delivers one row per utterance per target language, in the shape translations.import_translations already accepts. There was no script to run that ingest, so the delivery side of the contract existed only as a function. scripts/import_translations.py runs it: report-only by default, --write to store, --confirm to store as confirmed rather than draft. It prints the coverage before and after, so a delivery can be checked without writing it. Two bugs in the ingest, both found by running a real delivery: * import_translations folded the target language with lang.normalize, which does not know script subtags. A delivery labelled "zh-Hans" - which is what UmbraTrans writes - matched nothing, so every row was dropped and reported as a problem. It now folds with canonical_code, and an unknown language is still stored: the text is reviewable even when the render will be refused. * coverage and not_voiced_rows compared the stored code to the requested code as strings, so a row stored as "zh-Hans" read as "nothing delivered" when the reviewer asked about "zh". The coverage report was wrong in the direction that makes a reviewer distrust it. Measured on the real Deal Hunter Chinese delivery: 4,300 rows ingested, the review UI's resolver then renders Chinese for 12/12 utterances of chapter 1 and 415/416 of chapter 6 - the missing one being the spoken chapter announcement, which is deliberately not part of the reading edition. spec 27 records what the shipped samples actually are: single-pass drafts made against a hand-cleaned paraphrase of the English, matching the speaker-verified database text in 0 of 12 utterances. The production Chinese edition on oldbox does use the intent-based process; what it lacked was the alignment back onto the utterances, which is now implemented in UmbraTrans. 7 new tests. Suite: 890 passed, 8 failed (the same 8 pre-existing failures).
UmbraAudio
Quality-first multi-pass audiobook generation from English manuscripts.
Features
- 5-pass pipeline: Analysis → Voice Design → Mapping → Test → Generate
- Multi-engine TTS: Chatterbox (primary), Qwen3-TTS (secondary)
- LLM-enhanced: BookNLP for NLP, LLM for profiling/emotion/pronunciation
- Web review: FastAPI + HTMX at checkpoints for human approval
Quick Start
python -m umbraaudio.cli init <manuscript.epub> --name 'My Book'
python -m umbraaudio.cli analyze
python -m umbraaudio.cli voices
python -m umbraaudio.cli review
Requirements
- Python 3.11+
- NVIDIA GPU with CUDA support (RTX 3060 or better recommended)
- ffmpeg
Development
pip install -e ".[dev]"
ruff check umbraaudio/
pytest tests/ -v
Commit cadence: commit at least once every Sunday. Never leave a week (let alone a month) of work uncommitted — a single accidental
git checkouton 2026-09-20 wiped out a month of changes toumbraaudio/web/routes.py. SeeAGENTS.md→ Commit Cadence.
scripts/commit_check.sh enforces the nudge: it exits non-zero once the tree has been dirty
longer than a week. The LaunchAgent com.umbraaudio.commitcheck runs it every Sunday at
18:00 and posts a macOS notification when there is uncommitted work.
scripts/commit_check.sh # warn if dirty > 7 days
scripts/commit_check.sh --days 3 # tighter threshold
scripts/commit_check.sh --any # warn on any uncommitted change
Rebuilding from source
The repo tracks source only; large runtime data is reconstructed as needed.
-
Models: weights are not committed (see
.gitignore). Download them:bash scripts/download_models.sh # ~9 GB, targets ./models -
Qwen3-TTS is a git submodule pinned to upstream commit
022e286:git submodule update --init --recursiveThe
tts_server/web-demo backend includes a local UI customization toqwen_tts/cli/demo.py. It is preserved as a patch — reapply it after checking out the submodule:cd Qwen3-TTS && git apply ../scripts/qwen3_tts_demo_umbra_patch.patch -
Environment: copy/keep
.env(contains secrets) and setUMBRA_TTS__*model paths undermodels/.
Book projects (e.g. DealHunterShip_...) are committed separately under their
own project directories and are not part of this program repository.