Captions & subtitles
Two outputs, one transcription engine (mlx-whisper, same as transcribe):
- Sidecar file (
.srt / .vtt) — for YouTube/Vimeo upload, re-editing, or other tools.
- Burned-in — pixels baked into the video (social clips where the player won't show a
subtitle track). Rendered via PIL → ffmpeg overlay (mise ffmpeg lacks
drawtext).
CRITICAL: use --clean for captions
This is the opposite of filler-removal. Whisper is verbatim by default here (keeps
um/uh) because filler-removal needs them. Subtitles do not — viewers don't want "um, uh"
on screen. Always pass --clean when transcribing for captions:
uv run video-agent transcribe video.mp4 --clean --srt -o subs.srt
Export a subtitle file (no burn-in)
uv run video-agent transcribe video.mp4 --clean --srt -o subs.srt # SubRip
uv run video-agent transcribe video.mp4 --clean --vtt -o subs.vtt # WebVTT
Upload subs.srt alongside the video, or hand-edit it (fix names/typos, retime) and burn
that edited file — see below.
Burn captions into the video
# Auto-transcribe and burn in one step (uses --clean transcription internally? NO — pass it)
uv run video-agent captions video.mp4 -o captioned.mp4 --clean
# Best workflow for accuracy: transcribe → hand-edit the .srt → burn the edited file
uv run video-agent transcribe video.mp4 --clean --srt -o subs.srt
# ...fix typos, proper nouns, retime any off lines in subs.srt...
uv run video-agent captions video.mp4 --srt subs.srt -o captioned.mp4
--srt accepts both .srt and .vtt. When --srt is given, no transcription runs — it
just burns the file you pass, so this is also how you re-burn after editing.
Styling
uv run video-agent captions video.mp4 -o out.mp4 --srt subs.srt \
--size 48 --color yellow --position bottom --no-box
--size px (default ~5% of frame height) · --color white|yellow|red|black
--position bottom (default) | top | center
--no-box removes the semi-transparent legibility box (keep it for busy backgrounds)
- Text auto-wraps to ~90% of frame width, centered.
Gotchas
- Caption count = encode passes? No — one pass. Every segment becomes a full-frame RGBA
PNG overlaid with an
enable='between(t,…)' gate, all in a single ffmpeg encode. Fine for
clips and typical talks. For a very long video (many hundreds of segments) the command
line gets large; split the video, caption each part, then concat.
- Timing comes from whisper segment boundaries, which read fine but can be ~0.3s loose.
For tight sync, edit the times in the
.srt and re-burn with --srt.
- Word-level "karaoke" captions (one or two words popping per beat) aren't built in here.
Approximate by transcribing
--words, grouping into short segments, and writing your own
.srt with tight start/end per group, then captions --srt. For animated per-word pops
(eased, scaled), use the optional remotion-graphics skill's KineticCaption — fed the same
--words timestamps. Either way the timing is grepped from the --words transcript, never
scrubbed.
- Burn on the final-resolution video. If you're also reframing for social
(
reframe skill), reframe first, then caption — so font size and wrapping match the
output frame, not the original.
1---2name: captions3description: Add subtitles/captions to a video — burn them into the picture, or export an .srt/.vtt file (for YouTube etc.). Use whenever the user wants captions, subtitles, on-screen text of what's being said, an SRT/VTT file, or accessibility text. Covers auto-transcription, editing for accuracy, styling, and the verbatim-vs-clean gotcha.4---56# Captions & subtitles78Two outputs, one transcription engine (mlx-whisper, same as `transcribe`):9- **Sidecar file** (`.srt` / `.vtt`) — for YouTube/Vimeo upload, re-editing, or other tools.10- **Burned-in** — pixels baked into the video (social clips where the player won't show a11 subtitle track). Rendered via PIL → ffmpeg overlay (mise ffmpeg lacks `drawtext`).1213## CRITICAL: use `--clean` for captions1415This is the opposite of `filler-removal`. Whisper is **verbatim by default** here (keeps16um/uh) because filler-removal needs them. Subtitles do **not** — viewers don't want "um, uh"17on screen. Always pass `--clean` when transcribing for captions:1819```bash20uv run video-agent transcribe video.mp4 --clean --srt -o subs.srt21```2223## Export a subtitle file (no burn-in)2425```bash26uv run video-agent transcribe video.mp4 --clean --srt -o subs.srt # SubRip27uv run video-agent transcribe video.mp4 --clean --vtt -o subs.vtt # WebVTT28```2930Upload `subs.srt` alongside the video, or hand-edit it (fix names/typos, retime) and burn31that edited file — see below.3233## Burn captions into the video3435```bash36# Auto-transcribe and burn in one step (uses --clean transcription internally? NO — pass it)37uv run video-agent captions video.mp4 -o captioned.mp4 --clean3839# Best workflow for accuracy: transcribe → hand-edit the .srt → burn the edited file40uv run video-agent transcribe video.mp4 --clean --srt -o subs.srt41# ...fix typos, proper nouns, retime any off lines in subs.srt...42uv run video-agent captions video.mp4 --srt subs.srt -o captioned.mp443```4445`--srt` accepts both `.srt` and `.vtt`. When `--srt` is given, no transcription runs — it46just burns the file you pass, so this is also how you re-burn after editing.4748### Styling4950```bash51uv run video-agent captions video.mp4 -o out.mp4 --srt subs.srt \52 --size 48 --color yellow --position bottom --no-box53```5455- `--size` px (default ~5% of frame height) · `--color` white|yellow|red|black56- `--position` bottom (default) | top | center57- `--no-box` removes the semi-transparent legibility box (keep it for busy backgrounds)58- Text auto-wraps to ~90% of frame width, centered.5960## Gotchas6162- **Caption count = encode passes? No — one pass.** Every segment becomes a full-frame RGBA63 PNG overlaid with an `enable='between(t,…)'` gate, all in a single ffmpeg encode. Fine for64 clips and typical talks. For a very long video (many hundreds of segments) the command65 line gets large; split the video, caption each part, then concat.66- **Timing comes from whisper segment boundaries**, which read fine but can be ~0.3s loose.67 For tight sync, edit the times in the `.srt` and re-burn with `--srt`.68- **Word-level "karaoke" captions** (one or two words popping per beat) aren't built in here.69 Approximate by transcribing `--words`, grouping into short segments, and writing your own70 `.srt` with tight start/end per group, then `captions --srt`. For *animated* per-word pops71 (eased, scaled), use the optional `remotion-graphics` skill's `KineticCaption` — fed the same72 `--words` timestamps. Either way the timing is **grepped from the `--words` transcript, never73 scrubbed.**74- **Burn on the final-resolution video.** If you're also reframing for social75 (`reframe` skill), reframe **first**, then caption — so font size and wrapping match the76 output frame, not the original.