A pre-flight check that fails loudly if your voiceover audio or word-level timestamp files are missing, malformed, or out of sync. Run this before you build the CapCut draft. If it fails, fix the VO step rather than guessing offsets later in the timeline.
Chinese translation pending — see
README.zh-CN.mdat the repo root.
If you drive cut points from word-level timestamps — the standard way to do faceless / documentary edits — every shot boundary depends on the alignment data being trustworthy. One missing per-act file or one non-monotonic stitch point silently corrupts the entire edit, and you only notice when a clip lands on the wrong word three minutes in.
This script catches the four failure modes that actually happen:
- Wrong endpoint. Someone called
/v1/text-to-speech/{voice_id}instead of/v1/text-to-speech/{voice_id}/with-timestamps— there's no alignment to drive cuts from. - Truncated audio. A
.mp3exists but is < 1 KB because the API call timed out. - Length mismatch. ElevenLabs returned
characters,character_start_times_seconds, andcharacter_end_times_secondsarrays of unequal length. - Stitch missing offsets. A master
words.jsonwas concatenated from per-act files but the per-actstartMsvalues were never shifted forward — so the timeline rewinds halfway through.
<vo-dir>/
<slug>-act1.mp3
<slug>-act1-words.json ← raw ElevenLabs response
<slug>-act2.mp3
<slug>-act2-words.json
…
<slug>-vo.mp3 ← stitched master
words.json ← stitched word-level array with cumulative offsets
Each entry in words.json is {"word": "...", "startMs": int, "endMs": int}, monotonically increasing, total duration ≥ 60s.
- ElevenLabs voiceover already generated using the
with-timestampsendpoint, one file per act. - A stitched master
<slug>-vo.mp3and awords.jsonwith cumulative offsets applied. - Python 3.9+. No external deps.
python3 scripts/verify-vo-timestamps.py my-video \
--vo-dir ./projects/my-video/voiceover \
--acts 5Exit code 0 only if every check passes. On failure, the diagnostic on stderr names the file and the specific problem.
Verifying VO timestamps for 'my-video' (5 acts) in ./projects/my-video/voiceover
act1: OK (my-video-act1.mp3, my-video-act1-words.json)
act2: OK (my-video-act2.mp3, my-video-act2-words.json)
act3: OK (my-video-act3.mp3, my-video-act3-words.json)
act4: OK (my-video-act4.mp3, my-video-act4-words.json)
act5: OK (my-video-act5.mp3, my-video-act5-words.json)
master mp3: OK (my-video-vo.mp3)
master words.json: OK
PASS: all 5 acts have audio + alignment, master VO + words.json present and well-formed.
The filename convention (<slug>-act{N}.mp3, <slug>-act{N}-words.json) is wired into the script. If you stitch differently, change the file patterns inside main() — the validation logic per file is independent and reusable.