Skip to main content

Subtitle

The root-level subtitle property burns word-timed captions into the video. Adding captions can be as simple as:

{
"subtitle": {
"captions": [{ "start": 0, "end": 2, "text": "Hello world" }]
}
}

Word timings are generated automatically from the text (proportionally to word length), so every animation works out of the box. You can still provide exact per-word timings when you have them (e.g. from a transcription model).

Interface​

interface Subtitle {
/** content — provide exactly ONE of src / captions */
src?: string; // public http(s) URL of an SRT or VTT file
captions?: Caption[];

/** animation */
animation?: SubtitleAnimation; // default "normal"
direction?: "up" | "down" | "left" | "right"; // slide only, default "up"
activeWord?: { color?: string; background?: string; radius?: number };

/** typography */
font?: {
family?: string; // Google Fonts name, default "Poppins"
size?: number; // px, default 50
color?: string; // hex (+ optional alpha), default "#FFFFFF"
bold?: boolean;
italic?: boolean;
transform?: "uppercase" | "lowercase" | "capitalize";
};
stroke?: { color: string; width: number };

/** background box */
background?: {
color?: string; // hex (+ optional alpha)
opacity?: number; // 0–1, multiplies the color's alpha
padding?: number; // px around the text
radius?: number; // corner radius in px, 0–200, default 0
};

/** placement */
position?: // default "bottom"
| "top"
| "center"
| "bottom" // shorthands for *-center
| "top-left"
| "top-center"
| "top-right"
| "center-left"
| "center-center"
| "center-right"
| "bottom-left"
| "bottom-center"
| "bottom-right";
margin?: { x?: number; y?: number }; // px from edge, default 5% of size

/** layout */
maxWordsPerLine?: number; // split captions into lines of ≤ N words
}

interface Caption {
start: number; // seconds
end: number; // seconds
text?: string; // words auto-timed when `words` is omitted
words?: Word[]; // optional exact per-word timing
}

Each caption needs text and/or words. See Caption and Word.

Caption and word timestamps are absolute seconds on the project timeline, not offsets from the caption start. Subtitles are project-level, including when visuals use scenes. Use increasing start/end values and keep each word inside its caption's interval. maxWordsPerLine is an integer from 1 to 20; omitted, no word-count limit is imposed by this field.

Loading captions from a file (src)​

Instead of inlining captions, point src at a public SRT or VTT file. The file is fetched at render time, parsed, and word timings are distributed automatically:

{
"subtitle": {
"src": "https://cdn.example.com/captions.srt",
"animation": "highlight",
"activeWord": { "color": "#0b0d12", "background": "#7CFFB2" }
}
}

src and captions are mutually exclusive. The URL must be a public http(s) address (private hosts and non-standard ports are rejected).

Animations​

animationBehavior
normalStatic captions (default). none is an alias.
one-wordOnly the word being spoken is shown.
karaokeFull text; the spoken word switches to activeWord.color.
highlightKaraoke plus a box behind the spoken word (activeWord.background).
progressiveWords appear as they are spoken and stay.
fillColor sweeps across each word while it is spoken (true karaoke).
popThe spoken word scales up with a punchy two-stage animation.
bounceThe spoken word bounces in with a spring overshoot.
fadeWords fade in as they are spoken.
typewriterCharacters type on at the spoken pace.
slideEach word slides into its slot (direction: up/down/left/right).

There are 11 distinct modes; none is an alias for normal.

The same caption rendered in every animation mode — hover to play:

Examples​

Karaoke captions with a background box​

{
"subtitle": {
"captions": [
{ "start": 0, "end": 3, "text": "Let's create amazing videos" }
],
"animation": "karaoke",
"activeWord": { "color": "#FFD700" },
"font": { "family": "Montserrat", "size": 50, "bold": true },
"background": { "color": "#000000", "opacity": 0.8, "padding": 12 },
"position": "center"
}
}

Short lines, top of frame​

{
"subtitle": {
"captions": [
{
"start": 0,
"end": 5,
"text": "This caption is split into short lines automatically"
}
],
"maxWordsPerLine": 4,
"position": "top",
"margin": { "x": 40, "y": 60 },
"font": { "size": 40, "transform": "uppercase" },
"stroke": { "color": "#000000", "width": 3 }
}
}

Exact word timings (e.g. from Whisper)​

{
"subtitle": {
"captions": [
{
"start": 0.5,
"end": 2,
"text": "Welcome to our video",
"words": [
{ "start": 0.5, "end": 0.9, "text": "Welcome" },
{ "start": 0.9, "end": 1.1, "text": "to" },
{ "start": 1.1, "end": 1.4, "text": "our" },
{ "start": 1.4, "end": 2, "text": "video" }
]
}
],
"animation": "fill",
"activeWord": { "color": "#7CFFB2" }
}
}

Limits and notes​

  • stroke and background can be combined — the text keeps its outline on top of the caption box.
  • Captions are counted against your plan's caption limit; src files are capped at 5,000 cues and 2 MB.
  • Set background.radius for rounded caption boxes and activeWord.radius for rounded active-word boxes. Both use pixels, accept 0–200, and default to square corners (0). A radius needs a corresponding background color to be visible. Active-word boxes apply to modes that display them.
  • Subtitles are not available on image renders.

Legacy schema​

The previous shape — { "captions": [...], "styles": { "mode": ..., "isBold": ..., "marginV": ... } } — remains accepted and uses the same rendering pipeline; see SubtitleStyles. It cannot be mixed with the flat style fields above in the same subtitle object.