Read word timings
Give it the JSON a speech-to-text run returned (elevenlabs-speech-to-text, diarized);
get the timing facts an edit runs on, as JSON: words, phrase times, speaker turns,
pauses, lip-sync chunks, and where a clip's own speech ends.
Inputs
- The STT JSON, in ElevenLabs' shape:
wordswithtext,start,end,typeandspeaker_id. Times are on the clock of the audio that was transcribed: transcribe the exact file whose timeline you will cut or cue against. - For
chunks, that audio file too (its length ends the last chunk).
Run it
Write references/word_timings.md's script into the project folder as
word_timings.py, unchanged, and run one command per question:
exec("python", ["word_timings.py", "chunks", "--stt", "audio/track.stt.json", "--track", "audio/track.wav"], cwd="<job>")
| Command | Gives | Use it to |
|---|---|---|
words --stt S | [{text, start, end, speaker_id}], spacing and sound events dropped | see the transcript with times |
phrase --stt S [--after T] "P1" "P2" … | [{phrase, start, end}], each found after the one before; null if not spoken | cue a title, a slide build or a graphic on the word |
turns --stt S [--max-gap 0.7] [--name speaker_0=Nora] | [{speaker, start, end, text}]: one speaker's consecutive words, split at pauses over 0.7 s | find who speaks when; cut a shot's dialogue window |
pauses --stt S [--start A] [--end B] [--min-gap 0.2] | [{at, gap}]: midpoint and length of each silence of 0.2 s or more | find safe places to cut a track |
chunks --stt S --track W [--min-seconds 5] [--max-seconds 14] | [{chunk, start, end, seconds, sentence_end, text}] | cut a long track for H3 Max lip sync |
speech-end --stt S [--window W] [--tail 0.25] | {last_word_end, plays_for} | how long a clip's own re-voiced audio plays |
Reading the results
- Phrases match case- and punctuation-blind, whole words in sequence. Pass a few
words that occur once ("Finally, report"), and pass several phrases in reading order
so each is found after the last.
nullmeans the voice didn't say it as written: readwordsand fix the phrase, never guess a time. - Speakers are voice clusters, not people.
speaker_0,speaker_1are numbered in order of first appearance; the ids follow how the speech sounds, not who is on screen. Listen, then map ids to names with--name. - Pauses: cut at
at, the midpoint of the silence, never at a word's edge. - Chunks: greedy from the start, each cut at the longest pause (0.25 s or more)
that leaves a chunk of 5 to 14 s, and any pause after
.?!beats any pause inside a sentence.sentence_end: falseon a chunk other than the last means no sentence ended in range: the person returns to the pinned pose mid-sentence there, which reads as a hitch, so look at that seam first on review. "no pause between …" means a span with no usable pause: widen--max-secondsup to 15 or re-voice it with a pause. Each chunk'stextis the words whose midpoint falls inside it; it goes into the H3 prompt. Cut the audio of each chunk withffmpeg-cut-audio-window, back to back. - speech-end: run on the STT of a clip's own soundtrack (a reference-audio render
re-voices the line, and its timing drifts from the track).
plays_foris the longer of the planned window and the last word plus 0.25 s, so a line that runs past its window isn't cut off. Room tone fools a silence detector; the transcript doesn't.
Tips and traps
- Word times are good for cueing and for chunking, not for the exact frame where one
speaker ends and the next begins: there they were about 0.1 s off. Pick that cut by
ear from a ladder of trims (
ffmpeg-cut-audio-window). - After a re-voice or a retimed track, transcribe again; old times are wrong by however much the audio moved.
phrasematches the transcript's spelling, not the script's: readwordsfirst when a cue word is a number, an acronym or a name.


