Storyboard TTS
Take a storyboard deliverable and batch-synthesize Chinese + English voice-over with IndexTTS2 (shared setup with ai-text-to-speech).
| Output | Path |
|---|---|
| Chinese VO | <storyboard-dir>/<voice-stem>/Chinese/<shot-id>.wav |
| English VO | <storyboard-dir>/<voice-stem>/English/<shot-id>.wav |
| Duration doc | <storyboard-dir>/<voice-stem>/speech-timeline.md |
| Chinese subs | <storyboard-dir>/<voice-stem>/Chinese.srt |
| English subs | <storyboard-dir>/<voice-stem>/English.srt |
Shot id from headers (### Shot 01 — … → 01.wav).
Subtitles: one SRT per language. Shots are laid end-to-end on the VO timeline (shot N starts when N−1 ends). Inside a shot, text is split on sentence punctuation (.!?;… and CJK equivalents) into multiple cues; cue lengths share that shot’s WAV duration by non-whitespace character weight. Skip (no VO) / missing audio.
Rules
When this skill applies, read and follow skill-dependency-manager — run scripts as documented, install missing tools into .dependency/.
- Batch synthesis only via
.ai/storyboard-tts/synthesize.pywith theindex-ttsinterpreter. Do not hand-write IndexTTS loops, temporary batch drivers, or N× singletts.pycalls for a full storyboard. - Trial / single-line checks may use ai-text-to-speech
tts.py, orsynthesize.py --limit 1. - Parse-only / report-only / subtitle-only steps use stdlib
python(.dependency/python/python). - Never overwrite the storyboard source. Write only under
<audio-dir>/. - Skip
(no VO)/ empty lines — no empty WAVs or empty subtitle cues. - Confirm voice reference (and output dir if unclear) before a full batch.
- The top-level output directory uses the reference voice filename stem. Subdirectory names stay fixed. Default
<audio-dir>is<storyboard-dir>/<voice-stem>/. Do not use<storyboard-stem>-speech.
Inputs
| Required | Notes |
|---|---|
Storyboard .md |
### Shot NN — title with - **Chinese:** / - **English:** |
| Reference voice | WAV/MP3 for IndexTTS (--voice, or --voice-zh / --voice-en) |
| Optional | Default |
|---|---|
| Output dir | <storyboard-dir>/<voice-stem>/ (reference audio filename, no extension) |
| Language | both (--lang chinese / english) |
--fp16 / emotion / --device |
same meaning as ai-text-to-speech |
--force |
off (skip existing WAVs) |
--limit N |
0 = all jobs (use 1 for trial) |
--report |
write speech-timeline.md + Chinese.srt / English.srt after synth |
--no-subtitles |
with --report, skip SRT files |
| Edge pad | on (0.4 s); --no-pad / --pad-duration / --pad-threshold |
Layout
<storyboard-dir>/<voice-stem>/ # e.g. narrator-self-fast/ from narrator-self-fast.wav
Chinese/
01.wav
…
English/
01.wav
…
shots.json
speech-timeline.md
Chinese.srt # all Chinese cues, continuous timeline
English.srt # all English cues, continuous timeline
_text/ # only with --write-text
Quick Start
From project root:
.dependency/index-tts/.venv/Scripts/python.exe .ai/storyboard-tts/synthesize.py --storyboard path/to/storyboard.md --voice path/to/ref.wav --fp16 --report
Trial run (first line only):
.dependency/index-tts/.venv/Scripts/python.exe .ai/storyboard-tts/synthesize.py --storyboard path/to/storyboard.md --voice path/to/ref.wav --fp16 --limit 1
Writes under <storyboard-dir>/<voice-stem>/ (override with --audio-dir only when needed).
This will:
- Parse the storyboard →
<audio-dir>/shots.json - Load IndexTTS2 once
- Write
Chinese/<id>.wavandEnglish/<id>.wav(skip existing unless--force) - Pad each WAV in place to 0.4 s leading/trailing silence (
--no-padto skip) - Write
speech-timeline.mdandChinese.srt/English.srtwhen--report
Separate voices / language
.dependency/index-tts/.venv/Scripts/python.exe .ai/storyboard-tts/synthesize.py --storyboard path/to/storyboard.md --voice-zh path/to/zh_ref.wav --voice-en path/to/en_ref.wav --fp16 --report
.dependency/index-tts/.venv/Scripts/python.exe .ai/storyboard-tts/synthesize.py --storyboard path/to/storyboard.md --voice path/to/ref.wav --lang chinese --fp16 --report
Parse, report, or subtitles alone (stdlib python)
.dependency/python/python .ai/storyboard-tts/parse_storyboard.py path/to/storyboard.md -o path/to/<audio-dir>/shots.json
.dependency/python/python .ai/storyboard-tts/duration_report.py --storyboard path/to/storyboard.md --audio-dir path/to/<audio-dir> --shots path/to/<audio-dir>/shots.json -o path/to/<audio-dir>/speech-timeline.md
.dependency/python/python .ai/storyboard-tts/write_subtitles.py --audio-dir path/to/<audio-dir> --shots path/to/<audio-dir>/shots.json
Resume from an existing shots.json:
.dependency/index-tts/.venv/Scripts/python.exe .ai/storyboard-tts/synthesize.py --shots path/to/<audio-dir>/shots.json --voice path/to/ref.wav --fp16 --report
Common Flags (synthesize.py)
| Flag | Notes |
|---|---|
--storyboard / --shots |
Source (one required) |
--audio-dir |
Output root (default: <storyboard-dir>/<voice-stem>/) |
--voice |
Shared speaker ref |
--voice-zh / --voice-en |
Per-language refs |
--lang |
both (default), chinese, english |
--limit N |
First N pending jobs only |
--force |
Overwrite existing WAVs |
--report |
Write timeline + SRT subtitles |
--report-out |
Custom timeline path |
--no-subtitles |
Skip SRT when using --report |
--write-text |
Dump lines under _text/ |
--no-pad |
Skip in-place 0.4 s edge padding (off by default — padding is on) |
--pad-duration |
Target silence per edge in seconds (default: 0.4) |
--pad-threshold |
Silence detect threshold in dB (default: -50) |
--fp16 / --device |
Runtime |
--emotion-* / --random / --verbose |
Same role as tts.py |
On partial failure: script continues remaining jobs, prints Failed jobs: …, exit code 1. Fix install/voice per ai-text-to-speech troubleshooting, re-run (existing OK files are skipped).
Agent Notes
- Do not invent narration — use storyboard Chinese/English fields as-is (audio and subtitles).
- Prefer one
synthesize.pyinvocation for a full board; model reload cost is the reason. - Chat summary:
audio-dir(<storyboard-dir>/<voice-stem>/), counts, path tospeech-timeline.md,Chinese.srt/English.srt, Chinese/English total seconds — no full transcripts unless asked. Omit--audio-dirunless overriding. - IndexTTS install lives in ai-text-to-speech; do not duplicate Setup here beyond “populate index-tts if missing”.
- Edge padding is built in (default 0.4 s, in place via
pad.py). Use--no-padonly when they want raw TTS with no extra silence;--pad-durationif they want a different length. - Loudnorm / OGG / trim remain separate skills after this one.
- If audio already exists and only subtitles are needed, run
write_subtitles.pyalone (stdlib python). Re-runningsynthesize.py --reportwithout--forcestill pads existing WAVs, then rewrites the timeline/SRT.
Tests
Stdlib scripts (from repo root):
.dependency/python/python .ai/storyboard-tts/test_parse_storyboard.py
.dependency/python/python .ai/storyboard-tts/test_write_subtitles.py
IndexTTS batch driver (requires populated index-tts; see cli/storyboard-tts.md):
.dependency/index-tts/.venv/Scripts/python.exe .ai/storyboard-tts/synthesize.py --storyboard path/to/storyboard.md --voice .ai/test/audio/han.wav --fp16 --limit 1 --report
Related
- storyboard — source markdown
- ai-text-to-speech — single-line TTS + IndexTTS setup
- storyboard-av-mix — mux VO with per-shot video
- Optional after: audio-loudness-normalization