Quality-first multi-pass audiobook generation from English manuscripts. Includes: - umbraaudio/ core package (pipeline, config, voices, web review UI) - scripts/ CLI utilities + model download script - tts_server/ remote TTS server - specs/ design docs, tests/, pyproject.toml + uv.lock - Qwen3-TTS git submodule (pinned 022e286) with local demo.py UI patch saved at scripts/qwen3_tts_demo_umbra_patch.patch - .env with runtime config/secrets Excluded (see .gitignore): models/ (downloaded via scripts/download_models.sh), output/, logs/, .venv, .umbra runtime data, and book projects (DealHunterShip_*), which are committed separately.
6.3 KiB
6.3 KiB
Spec: CLI & Orchestrator
Overview
The CLI provides Click-based commands for each pipeline pass. The orchestrator coordinates the full pipeline with LM Studio lifecycle management.
CLI Commands
Global Options
umbraaudio [--project DIR] COMMAND
| Option | Description |
|---|---|
--project, -p |
Project directory (default: .) |
Command: init
umbraaudio init MANUSCRIPT [--name NAME]
| Argument/Option | Type | Description |
|---|---|---|
MANUSCRIPT |
Path (required) | Source manuscript file |
--name, -n |
str (optional) | Project name |
Flow:
- Detect format via
detect_format() - Parse manuscript via
parse_manuscript() - Create project in DB
- Insert chapters into DB
- Update status to "parsed"
Command: analyze
umbraaudio analyze
Flow:
- BookNLP analysis per chapter
- Character merging and deduplication
- LLM profiling for each character (up to 30)
- Pronunciation guide for unusual terms
- Update status to "analyzed"
Command: voices
umbraaudio voices
Flow:
- Load non-narrator characters with
voice_description - Load Chatterbox TTS
- Generate voice reference WAV per character
- Extract speaker embedding
- Update DB with paths and embeddings
Command: map-audio
umbraaudio map-audio
Flow:
- Segment each chapter via
Segmenter.segment() - Build generation manifests
- Save to
.umbra/manifests/ch{id}.json
Command: test
umbraaudio test [--chapter N]
| Option | Type | Description |
|---|---|---|
--chapter, -c |
int (optional) | Chapter to test (default: 1) |
Flow:
- Load manifest for specified chapter
- Load Chatterbox TTS
- Generate audio for the chapter
- Report results
Command: generate
umbraaudio generate [--chapters RANGE]
| Option | Type | Description |
|---|---|---|
--chapters, -c |
str | Chapter range: "all", "3", "1-5", "3,7" |
Flow:
- Load Chatterbox TTS
- Load manifest per chapter
- Generate audio with caching, retry, normalization, watermark
- Report progress per chapter
Command: export
umbraaudio export [--format FORMAT]
| Option | Type | Description |
|---|---|---|
--format, -f |
str (multiple) | Export formats: "m4b", "mp3", "wav" |
Flow:
- Load normalized audio (fallback to raw)
- Stitch chapter audio
- Export in requested formats
Command: pipeline
umbraaudio pipeline [--chapters RANGE] [--author NAME]
| Option | Type | Default | Description |
|---|---|---|---|
--chapters, -c |
str | "all" | Chapter range |
--author, -a |
str | "B.D. Murphy" | Author name for watermark |
Flow: Calls orchestrator.run_pipeline(). See Orchestrator section below.
Command: status
umbraaudio status
Displays project status, chapter count, character count, and progress in a Rich table.
Command: review
umbraaudio review [--host HOST] [--port PORT]
| Option | Type | Default | Description |
|---|---|---|---|
--host |
str | "127.0.0.1" | Server bind address |
--port |
int | 8420 | Server port |
Launches the web review UI.
Chapter Range Parsing
def _parse_chapter_range(chapters_str: str, total: int) -> range
| Input | Result |
|---|---|
"all" |
range(0, total) |
"3" |
range(2, 3) |
"1-5" |
range(0, 5) |
"3,7" |
range(2, 3) ∪ range(6, 7) (union) |
Orchestrator (orchestrator.py)
Function: run_pipeline()
async def run_pipeline(
project_dir: Path,
chapters: str | None,
author_name: str,
) -> dict[str, Any]
Returns:
{
"stages": list[dict], # Per-stage results
"defects": list[dict], # Quality defects
"summary": dict, # Final statistics
}
Full Pipeline Stages
1. init
└── Load project and chapters from DB
2. lms_unload
└── Run: lms unload --all (free VRAM)
3. chatterbox_load
└── Load ChatterboxTTS (~3.2 GB VRAM)
4. Pass 1: Analysis (conditional: status == "initialized" or "parsed")
├── BookNLP analysis per chapter
├── _merge_characters() (deduplicate, filter noise)
├── CharacterProfiler.profile_character() per character
├── PronunciationGenerator.find_unusual_terms()
└── Update DB + status → "analyzed"
5. Pass 2: Voice references
├── VoiceDesigner.generate_voice_reference() per character
├── Skip if voice_ref_path exists on disk
├── ChatterboxEngine.extract_embedding()
└── Update DB
6. Pass 3: Segment & manifest
├── Segmenter.segment() per chapter
├── build_generation_manifest()
└── save_manifest() to .umbra/manifests/
7. Pass 5: Generate audio
├── ChapterGenerator per chapter
├── Per-chapter: gc.collect() + torch.cuda.empty_cache()
└── AudioNormalizer per file
8. Quality check
└── check_acx_compliance() per generated file
9. Export
├── export_mp3()
└── export_wav()
10. chatterbox_unload
└── Unload Chatterbox from VRAM
11. lms_restart
└── Run: lms server stop && lms server start
LM Studio Lifecycle
The orchestrator manages LM Studio to prevent VRAM conflicts:
def unload_lm_studio() -> bool:
result = subprocess.run(["lms", "unload", "--all"], ...)
return result.returncode == 0
def stop_lm_studio() -> bool:
result = subprocess.run(["lms", "server", "stop"], ...)
return result.returncode == 0
def start_lm_studio() -> bool:
result = subprocess.run(["lms", "server", "start"], ...)
return result.returncode == 0
Order: Unload → Stop → (generate) → Unload Chatterbox → Start
Error Handling
- Entire pipeline wrapped in try/except
- On failure: unload Chatterbox, restart LM Studio, log CRITICAL defect
- Individual utterance failures don't abort the pipeline
- Defects collected and written to
.umbra/defects.json
Progress Reporting
Rich progress bars per stage:
- Pass 1: Per-chapter analysis
- Pass 2: Per-character voice design
- Pass 3: Per-chapter segmentation
- Pass 5: Per-utterance generation with ETA