FFMPEG COMMAND

Add subtitles to a video with FFmpeg

Two different jobs: hardcoding draws the text into the pixels (works everywhere, needs a re-encode and a build with libass), a soft track keeps the subtitles as a separate stream the player can toggle (instant, stream copy). Both are below, with what ffprobe saw.

Verified with FFmpeg 9.0.1 on 26 August 2026. Search intent: ffmpeg burn subtitles.

the command
ffmpeg -i input.mp4 -vf "subtitles=captions.srt" -c:v libx264 -crf 23 -preset medium -c:a copy subtitled.mp4

Renders each cue of captions.srt onto the frames at the right time with libass default styling (white text, black outline, bottom centre) and re-encodes the video; the audio is copied. The bottom band of the frame at 1.5 s went from a peak luma of 228 to 248, i.e. the white text is there.

Flags, explained

subtitles=captions.srt
Render the subtitle file onto the video with libass. SRT, ASS, SSA, WebVTT and subtitle streams inside media files are accepted.
force_style=...
Override the ASS style: FontName, FontSize, PrimaryColour, OutlineColour, Outline, Shadow, Alignment, MarginV and the other [V4+ Styles] fields.
-c:s mov_text
Encode the subtitle stream in the MP4-native text format. MKV would take -c:s srt or copy instead.
-map 0 -map 1
Take every stream of the video and the subtitle file. Without explicit maps FFmpeg keeps one stream per type and may drop the subtitles.
-metadata:s:s:0 language=eng
Tag the first subtitle stream with a language so players list it properly.
ass=captions.ass
Same renderer, but the file is read as ASS with its own styles, positions and effects.

Variants

Style the burned-in text (font, size, colours, margin)

shell
ffmpeg -i input.mp4 -vf "subtitles=captions.srt:force_style='FontName=DejaVu Sans,FontSize=28,PrimaryColour=&H00FFFFFF,OutlineColour=&H00000000,Outline=2,MarginV=40'" -c:v libx264 -crf 23 -preset medium -c:a copy subtitled.mp4

force_style takes ASS style fields. Colours are &HAABBGGRR (blue-green-red, not RGB), MarginV is the distance from the bottom in pixels of the 384x288 ASS reference frame. Use a font that exists on the machine (Helvetica on macOS, DejaVu Sans on Debian).

Soft subtitles the viewer can switch off (no re-encode)

shell
ffmpeg -i input.mp4 -i captions.srt -map 0 -map 1 -c copy -c:s mov_text -metadata:s:s:0 language=eng subtitled.mp4

The SRT becomes a mov_text stream inside the MP4, video and audio are copied. Finished instantly; the output was 686 bytes larger than the input. Players that ignore mov_text (some web players, most social platforms) show nothing, which is why burning in still exists.

Use an ASS file for full control

shell
ffmpeg -i input.mp4 -vf "ass=captions.ass" -c:v libx264 -crf 23 -preset medium -c:a copy subtitled.mp4

Convert first with ffmpeg -i captions.srt captions.ass, edit the [V4+ Styles] block, then burn with the ass filter. With the untouched conversion the output was byte-for-byte the same size as the subtitles filter result.

Path with spaces

shell
ffmpeg -i input.mp4 -vf "subtitles='sub dir/my captions.srt'" -c:v libx264 -crf 23 -preset medium -c:a copy subtitled.mp4

Single quotes inside the double-quoted filter string keep the path together. Characters that mean something to the filter parser (colon, comma, brackets) must be escaped with a backslash, for example C\:/subs/file.srt on Windows.

Pitfalls we hit

  • A build without libass gives a misleading error: the Homebrew FFmpeg 9.0.1 on macOS answered "No option name near 'captions.srt'" to the exact same command that works on a build with --enable-libass. Check ffmpeg -buildconf before debugging the syntax.
  • Hardcoding always re-encodes the video; -c:v copy with the subtitles filter is an error. Expect the full encode time of the file.
  • Timing comes from the subtitle file, not from -ss. If you trim with -ss before -i and burn in the same command, the cues shift: cut first, then burn, or use the setpts-aware ass filter with a shifted file.
  • mov_text tracks are ignored by most social platforms and by many HTML5 players; when the subtitles must be seen, burn them.
  • ASS colours are &HAABBGGRR. &H0000FF is red, not blue, and the leading AA is alpha where 00 means opaque.

How we verified it

Burn-in, force_style and ass ran on 26 August 2026 on the KinoPipe worker build of FFmpeg 9.0.1 (Debian, libass enabled) against a 12 s, 1280x720 H.264/AAC file with a three-cue SRT. Primary command: 360 frames, 4,734,912 bytes; signalstats on the bottom 90 px at 1.5 s: YMAX 228 in the source, 248 with the subtitles. Styled variant: 4,221,327 bytes. ass variant: 4,734,912 bytes, identical to the SRT burn. Soft track with -c copy: 3 streams (h264, aac, mov_text), 4,896,780 bytes against 4,896,094 for the input, ran on the Homebrew build in 0.1 s. The quoted path variant produced 60 frames for -t 2.

The same edit as a typed operation

add_subtitles_to_video takes the video and the SRT as two named inputs and burns the cues in on workers that ship libass and fonts, so there is no build to check and no path to escape. The caller gets a signed MP4 URL back.

MCP tool: add_subtitles_to_videoPOST /api/v1/tools/add-subtitles-to-video
request body
{
  "inputs": [
    { "id": "main", "url": "https://example.com/video.mp4" },
    { "id": "captions", "url": "https://example.com/captions.srt" }
  ],
  "options": {}
}

FAQ

What is the difference between burned-in and soft subtitles?

Burned-in subtitles are drawn into the picture and cannot be turned off; they need a re-encode but display everywhere. Soft subtitles are a separate stream added with -c copy in seconds; the player decides whether to show them, and many web and social players do not.

Why does FFmpeg fail with "No option name near" on the subtitles filter?

Your build was compiled without libass, so the subtitles filter does not exist and the parser reports a syntax error instead. Run ffmpeg -buildconf and look for --enable-libass, or use a build that has it (the Homebrew default on macOS did not).

How do I change the font size or colour of burned-in subtitles?

Add force_style to the subtitles filter with ASS style fields, for example FontSize=28,PrimaryColour=&H00FFFFFF,Outline=2. For per-cue control convert the SRT to ASS and edit the styles there.

Can I burn subtitles without re-encoding?

No. Burning changes the pixels, which means decoding and encoding every frame. If you cannot afford the encode, add a soft mov_text track instead and accept that some players will not show it.

Related commands

All FFmpeg commands