Voice a script
Turn a script.json and a cast (a voice and a performance guide per speaking
character) into one dialogue track, with every line's span on it. The audio comes
first: lip sync, scene renders and title cues are all timed from this track.
First question: is there more than one speaker in a scene? Then perform each scene
in one take so the turns play off each other. A single presenter or narrator is read
line by line. If a character has no voice yet, cast it first (guide-cast-a-voice);
if there is no script.json, make one (guide-make-a-script-from-source).
Routes
| Route | When | Quality | Leaf |
|---|---|---|---|
| TTS, one voice at a time (eleven_v4) | One presenter or narrator; single lines; auditions | One voice per call; tags direct it | elevenlabs-tts-v4 |
| Text-to-dialogue, a scene in one take (eleven_v4) | Two or more speakers in a scene | Turns react to each other; each line's span returned; pauses approximate | elevenlabs-text-to-dialogue |
| The person's own recordings (a voice they cloned themselves) | They bring the audio of each line | As recorded; timings come from speech-to-text, like any recorded track | elevenlabs-speech-to-text, python-read-word-timings |
| Assemble the passages into one track | After TTS or text-to-dialogue | Every line and shot timed (track.json) | python-assemble-dialogue-track |
| Normalize loudness | Once, on the assembled track | −16 LUFS, two-pass linear; timings unchanged | ffmpeg-normalize-loudness |
| Transcribe the track | Every take | Words, speech rate per character, pauses | elevenlabs-speech-to-text, python-read-word-timings |
Cost is per character of text spoken; a fix re-speaks only the passages it touches. On v4, takes came back word-perfect, at about 2.9 words/s for both presenters with no speed-up.
Process
- Cast arguments. Each speaking character gets its voice id and guide (the casting
voice.md, or a few sentences for a given voice). Director notes that override the guide go at the top. Keep them inAGENTS.mdso every take runs with the same ones. - Group the script into passages: a scene each, merged where a line runs across a cut.
- Direct. Write each line's audio tags from its parenthetical, emotion, the beats
around it and the guide (
[warmly], and free-form ones like[lower, knowing]). The words must stay the script's: check them word for word before speaking. - Perform every passage at once, one run each with
wait: false. Follow them withtasks(wait: true, calls: [...], until: "any")and start each take's check (step 5) as it lands, calling again with the ids still running. Once every take is in, assemble: each passage is preceded by the script's silence before it, at least 0.4 s and at most 2.5 s. Plan silence into the track wherever the picture needs action time (an exit, a transition) before any video is rendered. - Check every take by transcribing it: words, words per second per character, the pauses the notes mention.
- Go on, no gate. The person approved each voice when they picked it at casting and ElevenLabs takes are reliable, so a take that passes the check goes straight to the next step; don't ask about the voice or the track again. The person hears it with the pictures, and notes they give then are fixed below. Keep two presenters in two folders (two variants); take letters are never reused.
Fixes, in this order
- Notes on specific lines: write those lines' tags by hand into the new take and re-speak only their passages; copy the rest. Re-prompting the LLM loses the notes.
- A pause too long or short: pause tags land only roughly. Measure it from the transcript, then insert or cut silence midway between the two words; every later line moves, so re-transcribe.
- A voice that reads too slowly: speed that character's lines up with pitch kept (atempo, at most 1.2). One v3 voice read 2.4 words/s however it was directed.
- More expression: stability 0 (the most expressive setting) on that passage only.
Audio quality
- Ask for
pcm_48000(lossless, written as WAV) instead of the defaultmp3_44100_128, and normalize the assembled track to −16 LUFS, keeping the raw one. Normalize after assembling, never per line. - A voice that sounds "like a telephone" is usually band-limited at the source: measure
the library preview's spectrum before casting (Voice A had nothing above ~12 kHz and
99% of its energy under 5 kHz). No output format adds what isn't there, and
denoisers (
veed/clean-audio,deepfilternet3) don't help. - Optional: when a voice sounds a little tinny, an EQ plus ffmpeg
aexciter(a synthesized top end) can make it a bit more vibrant. No fixed settings: try two or three side by side with the plain track and let the person pick by ear (elevenlabs-tts-v4). - Pronunciation: spell out what the model misreads ("H-R" for HR) and check it in the transcript.
Avoid
- The
speedvoice setting: v3 and v4 ignore it (same length at 1.0 and 1.2). previous_text/future_texton v3: rejected.- Library professional clones not trained for v4: they render but can drift. Prefer voices verified for eleven_v4.
- Re-prompting the LLM to fix a director's line notes: hand-write the tags.
- Laying this track under clips a video model re-voiced: see
guide-lipsync-video.
Related
guide-cast-a-voice,guide-make-a-script-from-source: before this.guide-lipsync-video,guide-add-titles-and-motion-graphics: they readtrack.jsonand the transcript's word times; re-cue from the new transcript after every re-take.


