2026-10-29 · 5 min read
AI Voiceover Plus Captions: Closing the Loop on Generated Audio
Text-to-speech voiceovers have become a standard layer in faceless channels, product explainers, and localization workflows. The audio is cheap to regenerate, but it has a failure mode human narration rarely does: the model occasionally mangles a name, a number, or a word it has never seen. Captions are how a viewer catches that. If the words on screen match the audio, a slightly odd pronunciation reads as style; without them it reads as an error, or worse, as a different word entirely.
caption.sh burns word-timed, styled captions into video through one API. For a voiceover pipeline the fit is direct: render the TTS audio, cut it against your visuals, then hand the finished video to the captioning job as the last automated step before publish.
A pipeline that stays in sync
The ordering matters. Caption the final cut, not a draft, because burned-in captions cannot be re-timed after the fact. If your edit changes, re-run the caption job on the new cut. At $0.10 per minute prepaid, re-captioning a revised two-minute video costs twenty cents, so treating captions as disposable is cheaper than trying to preserve them across edits.
# after your TTS + edit pipeline emits the final cut
KEY=$CAPTION_API_KEY
resp=$(curl -s -X POST https://api.caption.sh/v1/videos \
-H "Authorization: Bearer $KEY" \
-H "Content-Type: application/json" \
-d '{"source_url":"https://cdn.example.com/final/explainer-v3.mp4","options":{"font":"fredoka"}}')
job=$(echo "$resp" | jq -r .job_id)
if [ -z "$job" ] || [ "$job" = "null" ]; then echo "submit failed: $resp"; exit 1; fi
deadline=$((SECONDS + 900))
status=""
while [ $SECONDS -lt $deadline ]; do
resp=$(curl -s https://api.caption.sh/v1/jobs/$job -H "Authorization: Bearer $KEY")
status=$(echo "$resp" | jq -r .status)
[ "$status" = "done" ] && break
[ "$status" = "error" ] && { echo "job failed: $resp"; exit 1; }
sleep 5
done
[ "$status" = "done" ] || { echo "gave up waiting for job $job"; exit 1; }
curl -sL -H "Authorization: Bearer $KEY" \
https://api.caption.sh/v1/jobs/$job/result -o explainer-v3-captioned.mp4Practical notes for generated audio
- Word-level timing follows the audio, not your script. If the TTS engine ad-libs a contraction or swallows a syllable, the captions track what was actually spoken.
- Keep the caption style in your brand kit: font (fredoka, inter, roboto and more), colors, position, and word highlighting are per-job options, so every regenerated voiceover still comes out looking like your channel.
- The API documents no webhook surface, so the pipeline polls the job endpoint with its own deadline, as in the sample. A job that outlives its window is an error to retry, not a state to wait in forever.
- Results are stored for 24 hours and then deleted. Download the captioned render into your own storage as part of the same run.
The pairing works because both steps are APIs. A script that can regenerate a voiceover can also regenerate its captions, and the whole loop stays unattended from text to publishable video.