burn-subtitles
Word-timed subtitle burn-in for shorts. Highlights the current word, no manual sync.
Invoke
.claude/skills/burn-subtitles/burn-subtitles.sh <input> <transcript.json> <out> [style] [font_size]
input: video pathtranscript: path to JSON. Forstyle=chunks(default) this is achunks.jsonfrom thechunk-captionsskill. For other styles it's atranscribeoutput JSON.out: output video pathstyle(optional):chunks(default) |line|word-karaoke|selectivefont_size(optional): default72
Output
mp4 with subtitles burned in. Same dimensions, audio stream-copied. Prints the
output path to stdout. Idempotent: skips work if out is newer than both inputs.
Styles
chunks(default): consumes achunks.jsonfrom thechunk-captionsskill. Each chunk is shown as a complete phrase for its[t0, t1]window; the currently-spoken word is rendered cyan, all other words white. Chunks hard-cut between each other — no scrolling, no fade. Kills the per-word slide-up that made the legacyword-karaokestyle hard to read.line: one plain line per transcript segment.word-karaoke(legacy): rolling window of up to 4 words, tied to word timestamps. Each word slides up 8px over 80ms at its ownt0and hard-cuts off when bumped or after a >0.6s speech gap. Deprecated for shorts — feedback showed the constant scroll was hard to follow.selective: only burns segments overlapping high-RMS-energy seconds.
Brand preset (deliberate, not default)
This is the project's caption identity — keep it consistent:
- Font: Impact (
/System/Library/Fonts/Supplemental/Impact.ttf) — chunky condensed sans, distinct from the CapCut/Submagic/Opus default of Arial. - Accent (active word): Sapphire Glow
#2E6BFF— the C0BALT CUT brand accent (brand/BRAND.md), chosen against the ubiquitous MrBeast-yellow#FFD633. - Context (earlier visible words): off-white
#F5F5F0. - Position: upper-third (top of first line at ~22% of frame height) — not the default lower-third.
- Animation: 8px slide-up over 80ms (linear) on entry; hard-cut on exit. No ease-out-back pop, no scale animation.
- Stroke: thick black outline (font_size / 10), for legibility over any background without falling back to white-on-black bars.
How
The local ffmpeg build ships without libass, subtitles, or drawtext
filters. So burn_subtitles.py renders a transparent PNG per video frame with
PIL using the brand preset above, hardlinking duplicate frames for speed.
burn-subtitles.sh then composites the sequence onto the source with ffmpeg's
overlay filter and re-encodes video (libx264 veryfast crf18); audio is copied.
Status
Works. Tested on a 6.7s 720x1280 clip (say-synthesized speech, 19 words):
- word-karaoke: 20 unique states, frame at t=3.2s shows 16130 white + 5616 accent (highlighted-word) pixels in the lower third.
- line: 27049 white pixels, 0 accent. Dimensions and duration preserved, audio stream-copied (aac).