B.D. Murphy e810b49d2a Add a voice-regeneration queue with inline per-speaker state
Reviewing speaker exceptions meant clicking Regenerate, watching a modal that
blocked the page, then navigating back to find out what happened. Worse, the
route refused a second request with a 409 while one was running, and
/api/generation/status exposed only a single global `active` flag - so there
was nowhere to put a second speaker and nothing to render per row.

Now: POST returns as soon as the task is captured, the row shows a status tag
(queued / working / done / failed), and further speakers can be queued while one
generates. No navigation, no modal.

umbraaudio/voices/voice_queue.py
- Ordered, serial queue. TTS is strictly serial here, so items run one at a
  time in enqueue order; the second speaker waits rather than contending.
- Per-item state, persisted to voice_queue.json. An item left `running` by a
  crash is marked `error` on load - the honest outcome, since silently
  re-running could write audio nobody asked for twice.
- Enqueueing a speaker that is already pending is a no-op returning the
  existing state, so a double-click cannot queue the same work twice.
- `cancel` withdraws a queued item but refuses one already running.
- Status vocabulary (queued/running/done/error) is deliberately small and
  stable: it is what the controller will map onto node task states.

umbraaudio/web/voice_queue_routes.py - POST /api/voices/queue (capture the task),
GET /api/voices/queue (report per-item state), POST .../{speaker}/cancel. Kept
out of the hot routes.py, following name_token_routes.

/api/speakers/regenerate-voice is now a thin shim onto the queue so a
bookmarked call still works and the two paths cannot race. Net -24 lines in
routes.py; the blocking implementation is gone, not commented out.

The UI's modal, its 409 handler and the page-load resume handshake were removed
(67 lines) rather than left dormant - queue state is re-read from the snapshot
on load and after each HTMX row swap, so it survives navigation.

Unknown speaker names are rejected with a 404 at enqueue time. Previously they
reached the profile builder and surfaced as a bare KeyError('primary_name') in
the failed tag.

Verified live: two speakers enqueued back to back, first ran while the second
stayed queued, then the second started automatically on completion; both wrote
real ref WAVs. 16 new tests. Full suite 665 passed, 8 pre-existing failures.
2026-10-09 19:45:46 -07:00

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 checkout on 2026-09-20 wiped out a month of changes to umbraaudio/web/routes.py. See AGENTS.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.

  1. Models: weights are not committed (see .gitignore). Download them:

    bash scripts/download_models.sh   # ~9 GB, targets ./models
    
  2. Qwen3-TTS is a git submodule pinned to upstream commit 022e286:

    git submodule update --init --recursive
    

    The tts_server/web-demo backend includes a local UI customization to qwen_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
    
  3. Environment: copy/keep .env (contains secrets) and set UMBRA_TTS__* model paths under models/.

Book projects (e.g. DealHunterShip_...) are committed separately under their own project directories and are not part of this program repository.

Description
Quality-first multi-pass audiobook generation pipeline (5-pass: Analysis -> Voice Design -> Mapping -> Test -> Generate). FastAPI+HTMX review UI, Edge-TTS/Qwen3-TTS multi-engine voice, SQLite state.
Readme Apache-2.0 37 MiB
Languages
Python 84.8%
HTML 11.4%
Shell 3%
PLpgSQL 0.7%