Javid JamaeOriginally published at ffmpeg-micro.com FFmpeg's subtitles filter burns SRT or ASS captions...
Originally published at ffmpeg-micro.com
FFmpeg's subtitles filter burns SRT or ASS captions directly into the video pixels. Unlike soft subtitles (which viewers can toggle off), burned-in subtitles are permanent. They show up everywhere: social media players that strip subtitle tracks, messaging apps, and platforms that don't support external caption files.
The basic command is one line, but the filter's syntax for styling, positioning, and encoding has a few traps that waste hours if you don't know about them.
ffmpeg -i input.mp4 -vf "subtitles=captions.srt" -c:v libx264 -crf 23 -c:a copy output.mp4
FFmpeg reads captions.srt, renders each subtitle at its timestamp, and encodes the result into a new MP4. The -c:a copy flag passes audio through untouched so you don't re-encode it for no reason.
Two things to know up front:
subtitles filter requires FFmpeg compiled with libass. If you get No such filter: 'subtitles', your build doesn't have it. Most package managers include it by default (brew install ffmpeg on macOS, apt install ffmpeg on Ubuntu), but minimal Docker images often skip it.The default appearance (white text, thin black outline) works, but you'll almost always want to customize it. The force_style parameter accepts ASS override tags:
ffmpeg -i input.mp4 \
-vf "subtitles=captions.srt:force_style='FontSize=28,PrimaryColour=&H00FFFFFF,OutlineColour=&H00000000,Outline=2,Shadow=1'" \
-c:v libx264 -crf 23 -c:a copy output.mp4
| Parameter | What it does | Example value |
|---|---|---|
| FontSize | Text size in pixels | 28 |
| FontName | Font family | Arial |
| PrimaryColour | Text fill color (ASS hex: &HAABBGGRR) |
&H00FFFFFF (white) |
| OutlineColour | Outline color |
&H00000000 (black) |
| BackColour | Shadow/background color |
&H80000000 (50% black) |
| Outline | Outline thickness in pixels | 2 |
| Shadow | Shadow distance in pixels | 1 |
| BorderStyle | 1 = outline + shadow, 4 = opaque box | 4 |
| Alignment | Position on screen (numpad layout) |
2 (bottom center) |
| MarginV | Vertical margin from the edge | 30 |
| Bold | Bold text (1 = on, 0 = off) | 1 |
The color format is tricky. ASS uses &HAABBGGRR, not the #RRGGBB you're used to from CSS. White is &H00FFFFFF. Yellow is &H0000FFFF. Red is &H000000FF. The AA prefix is transparency (00 = fully opaque, FF = fully transparent).
Three escaping rules that catch people off guard:
Colons in file paths. On Windows, paths like C:\Users\me\captions.srt break the filter because : is a parameter separator. Escape them with backslashes.
Single quotes inside force_style. The force_style value is wrapped in single quotes. If your font name contains an apostrophe, escape it with a backslash.
UTF-8 encoding. The SRT file must be UTF-8. If you get garbled characters or blank subtitles, check with file captions.srt. Convert if needed:
iconv -f UTF-16 -t UTF-8 captions-utf16.srt > captions.srt
"No such filter: subtitles" means your FFmpeg build doesn't include libass. Reinstall with subtitle support: brew install ffmpeg (macOS), sudo apt install ffmpeg libass-dev (Ubuntu).
Timing is off after trimming. If you use -ss (seek) to trim the video, subtitle timestamps don't shift automatically. Put -ss before -i as an input option.
Output file is bigger than the input. Burning subtitles forces a full re-encode. Use -crf 23 or lower for quality parity. Don't use -c:v copy because the filter can't modify copied frames.
Text renders at wrong size after scaling. Put subtitles AFTER the scale filter: -vf "scale=1280:720,subtitles=captions.srt". Chain order matters.
Yes. The filter burns text into video frames, so it works with any output container.
No. The subtitles filter only reads local files. Download your SRT first with curl or wget before passing it to the filter.
You can't. The filter modifies video frames, which requires a full decode-and-encode cycle. To minimize quality loss, match the original codec and use a CRF value equal to or lower than the source.
Read the full guide with more examples on ffmpeg-micro.com.