diff --git a/specs/16-audiobook-production.md b/specs/16-audiobook-production.md index 712bc95..65a1411 100644 --- a/specs/16-audiobook-production.md +++ b/specs/16-audiobook-production.md @@ -108,6 +108,26 @@ voices/narrator_ref.wav (voice for new narration) ─▶ 1. synth opening/annou | `umbraaudio/web/templates/index.html` | "Build Final Audiobook (MP3)" card + stats table + download links | | `tests/test_ar_export.py` | Unit tests with mocked TTS + ffmpeg | +## 5a. Scene breaks (`<<<>>>`) + +A scene break is a segmentation row whose `text` is exactly `<<<>>>`. It renders as +**2.000 s of silence**, never as speech. + +- **Author rule:** every scene-break utterance must carry `speaker_name = 'Narrator'`. + The system requires a speaker on every utterance row, and `Narrator` is the correct + value. A `speaker_name` of `scene_break` is **not** valid data — it was a transient + artifact of the generator, not an authorial choice. Normalized book-wide on + 2026-09-26 (74 rows changed; 81 total). +- **The speaker value is inert for rendering.** `is_scene_break(text, utt_type)` + (`umbraaudio/generation/scene_break.py:31`) keys off the `<<<>>>` text and the + `scene_break` type only, never `speaker_name`. Both generation paths short-circuit + before voice resolution — `generate_all_audio.py:167` and + `chapter_builder.py:514` (`write_scene_break_audio(out)` then `continue`, ahead of + `_resolve_voice_ref` at `:520`). So the value is required by the schema but cannot + affect the audio. +- **Duration constant:** `SCENE_BREAK_SILENCE_MS = 2000` (`scene_break.py:26`). + Verified 2026-09-26: 62/62 existing scene-break WAVs are exactly 2.000 s. + ## 6. Quality gates before upload - All tracks: 44.1 kHz, mono, 192 kbps CBR MP3 (ffprobe report in manifest).