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 };

/** 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
};

/** 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.

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).

Coming from another tool? Shotstack's highlightkaraoke, Shotstack's karaokefill; Creatomate's colorkaraoke and enlargepop.

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

bounce
fade
fill
highlight
karaoke
normal
one-word
pop
progressive
slide
typewriter

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.
  • The background box cannot have rounded corners (a limitation of the subtitle burn-in engine).
  • Subtitles are not available on image renders.

Legacy schema

The previous shape — { "captions": [...], "styles": { "mode": ..., "isBold": ..., "marginV": ... } } — is still fully supported and renders identically; see SubtitleStyles. It cannot be mixed with the flat style fields above in the same subtitle object.