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
animation | Behavior |
|---|---|
normal | Static captions (default). none is an alias. |
one-word | Only the word being spoken is shown. |
karaoke | Full text; the spoken word switches to activeWord.color. |
highlight | Karaoke plus a box behind the spoken word (activeWord.background). |
progressive | Words appear as they are spoken and stay. |
fill | Color sweeps across each word while it is spoken (true karaoke). |
pop | The spoken word scales up with a punchy two-stage animation. |
bounce | The spoken word bounces in with a spring overshoot. |
fade | Words fade in as they are spoken. |
typewriter | Characters type on at the spoken pace. |
slide | Each word slides into its slot (direction: up/down/left/right). |
Coming from another tool? Shotstack's highlight ≈ karaoke, Shotstack's
karaoke ≈ fill; Creatomate's color ≈ karaoke and enlarge ≈ pop.
Mode Gallery
The same caption rendered in every animation mode — hover to play:
bouncefadefillhighlightkaraokenormalone-wordpopprogressiveslidetypewriterExamples
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
strokeandbackgroundcan be combined — the text keeps its outline on top of the caption box.- Captions are counted against your plan's caption limit;
srcfiles 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.