captions.js (captionsjs) — burn animated word-level captions/subtitles into video with FFmpeg
648
Burn captions.js animated word-level captions
into a video file. It runs the same renderFrame as the browser overlay, drawn with
skia-canvas and piped into FFmpeg. No headless browser.
npx captions.js burn talk.mp4 words.json --preset Karaoke
# → talk.captions.mp4
words.json is any of:
[{ "word": "Hello", "start": 0.12, "end": 0.48 }, ...] // plain word timings
{ "segments": [{ "words": [...] }] } // OpenAI Whisper verbose_json
{ "results": { "channels": [...] } } // Deepgram response
| Option | |
|---|---|
-p, --preset <name> | Style preset, case-insensitive (default Karaoke). npx captions.js presets lists them. |
-o, --output <file> | Output path (default <video>.captions.mp4). |
--fps <n> | Overlay frame rate (default: source fps, max 60). |
--fonts-dir <dir> | Folder with TTFs in Google Fonts layout (Family_Name/FamilyName-Bold.ttf). |
--crf <n> | x264 quality (default 20). |
Requires ffmpeg and ffprobe on PATH (or FFMPEG_PATH / FFPROBE_PATH).
Preset fonts are downloaded from Google Fonts on first use and cached in
~/.cache/captionsjs/fonts (override with CAPTIONSJS_CACHE_DIR).
import { burnCaptions } from "@captionsjs/server";
const { output } = await burnCaptions({
video: "talk.mp4",
captions: "words.json", // path, URL, JSON string or parsed array
preset: "Focus Box",
onProgress: ({ frame, totalFrames }) => console.log(frame / totalFrames),
});
Font size follows the browser overlay rule (videoHeight / 480), so what you tune in
the preview is what you get in the file. Pass scale to override.
Multi-arch image (linux/amd64, linux/arm64) with FFmpeg and all preset fonts baked in —
works offline.
# CLI: mount a folder, burn, done
docker run --rm -v "$PWD:/data" maskin25/captions.js-render \
burn /data/talk.mp4 /data/words.json --preset Karaoke -o /data/out.mp4
# HTTP service on :4000 (GET /health, POST /burnCaptions)
docker run -p 4000:4000 maskin25/captions.js-render
The HTTP endpoint currently takes the Pub/Sub push envelope used by Shorty.plus
({ message: { data: base64({ preset, video_uri, captions_uri, output_uri }) } }).
A simpler upload-and-download API is planned.
MIT © maskin25
Content type
Image
Digest
sha256:1da507879…
Size
285.1 MB
Last updated
10 days ago
docker pull maskin25/captions.js-render