Captions
Word-synced captions, from a transcript to a burned-in style.
Captions are the difference between a short that works on mute and one that does not. reelcn ships one engine and eight styles on top of it.
1. Get a transcript
Install the tool, then run it on your clip:
npx shadcn@latest add https://www.reelcn.dev/r/transcribe.json
node scripts/reelcn-transcribe.ts talk.mp4It writes public/captions/talk.json: an array of words with start and end times. The first run downloads whisper.cpp and a model, then works offline. An existing .srt works too, and --openai uses the OpenAI API instead when OPENAI_API_KEY is set.
2. Put them on screen
import captions from "../../public/captions/talk.json";
import { Captions } from "./reelcn/captions";
export const Short = () => (
<AbsoluteFill>
<Video src={staticFile("talk.mp4")} />
<Captions captions={captions} emphasize={["free", "today"]} />
</AbsoluteFill>
);The engine pages the words, highlights the one being spoken, and pops anything in emphasize in the theme's highlight color.
Styles
Each style is its own installable component: captions-bold-pop, captions-karaoke, captions-boxed, captions-minimal, captions-neon, captions-word-stack, captions-subtitle-bar and captions-highlight-box. They all take the same props as captions minus variant.
Props worth knowing
pageMs(default 1200): how much speech goes on one page. Lower means fewer words at a time.maxWords(default 6): a hard cap; long pages split.position(defaultauto):autokeeps captions above the platform UI in vertical video, and in the lower third elsewhere.size(default 64): the starting size in design units. Long pages shrink from there, so captions never run past the safe area.
Timing
Captions are data-driven: their length comes from the transcript, not from a durationInFrames. Put them next to your footage and they line up on their own, in every format.