Burn Subtitles into Video with FFmpeg
Burn SRT or ASS subtitles into video with FFmpeg, change the font, size and position, or add soft subtitles that viewers can turn off. With an API option.
To burn subtitles into a video with FFmpeg, use the subtitles filter and re-encode: ffmpeg -i input.mp4 -vf subtitles=subs.srt -c:v libx264 -crf 20 -c:a copy output.mp4. The text becomes part of the picture, so it shows on every player and platform; if viewers must be able to turn it off, add soft subtitles in place of burning them.
Below are the commands with each part explained, how to style the text, the same jobs as Xora API calls, and the common errors. For other common commands, see the FFmpeg cheat sheet.
The local command
ffmpeg -i input.mp4 -vf subtitles=subs.srt -c:v libx264 -crf 20 -preset medium -c:a copy -movflags +faststart output.mp4| Part | What it does |
|---|---|
-vf subtitles=subs.srt | Draw the subtitles from the file onto each frame. Works with SRT, ASS and VTT files. Needs FFmpeg with libass. |
-c:v libx264 -crf 20 | The picture changes, so the video must be re-encoded. CRF 20 keeps it close to the source. |
-c:a copy | Keep the audio as it is. |
If the subtitles are already a stream in the input file (for example in an MKV), use subtitles=input.mkv:si=0 to burn the first subtitle stream.
Style the text
ffmpeg -i input.mp4 \ -vf "subtitles=subs.srt:force_style='FontName=Arial,FontSize=24,PrimaryColour=&H00FFFFFF,OutlineColour=&H00000000,BorderStyle=1,Outline=2,MarginV=40'" \ -c:v libx264 -crf 20 -c:a copy styled.mp4| Style key | What it sets |
|---|---|
FontName, FontSize | The font and its size. The font must be installed where FFmpeg runs. |
PrimaryColour, OutlineColour | Text and outline colours, written &HAABBGGRR (alpha, blue, green, red). |
BorderStyle=1, Outline=2 | An outline 2 pixels wide. BorderStyle=3 draws a box behind the text. |
MarginV=40 | Distance from the bottom edge, in pixels. |
Alignment=8 | Put the text at the top centre (2 is bottom centre, the default). |
For full control of every line, write the subtitles as an ASS file and use -vf ass=subs.ass.
Soft subtitles: no re-encode
ffmpeg -i input.mp4 -i subs.srt -map 0 -map 1 -c copy -c:s mov_text -metadata:s:s:0 language=eng output.mp4-c copy keeps the video and audio as they are, and -c:s mov_text stores the SRT as an MP4 subtitle stream. Viewers can turn it on and off in players that support it. For MKV, use -c:s srt.
| Type | Re-encode | Viewers can turn it off | Shows on social platforms |
|---|---|---|---|
| Burned-in | Yes | No | Yes |
| Soft | No | Yes | Often not |
The same job with the Xora API
Send the video and the subtitle file as two inputs in mode: "ffmpeg". Xora replaces {{in_2}} inside the filter value with the path of the downloaded subtitle file:
{ "mode": "ffmpeg", "input_files": { "in_1": "https://example.com/talk.mp4", "in_2": "https://example.com/talk_en.srt" }, "output_files": { "out_1": "talk_burned.mp4" }, "ffmpeg": { "args": [ "-i", "{{in_1}}", "-vf", "subtitles={{in_2}}:force_style='FontName=DejaVu Sans,FontSize=24,Outline=2'", "-c:v", "libx264", "-crf", "20", "-c:a", "copy", "{{out_1}}" ] }}Use a subtitle URL that ends in .srt, .ass or .vtt, so the downloaded file keeps its extension, and do not set stageInput: false for this job: the filter needs a local file. The Xora workers have the DejaVu Sans font; a font name that is not installed falls back to it.
For soft subtitles, send both files as normal inputs:
{ "mode": "ffmpeg", "input_files": { "in_1": "https://example.com/talk.mp4", "in_2": "https://example.com/talk_en.srt" }, "output_files": { "out_1": "talk_subtitled.mp4" }, "ffmpeg": { "args": [ "-i", "{{in_1}}", "-i", "{{in_2}}", "-map", "0", "-map", "1", "-c", "copy", "-c:s", "mov_text", "-metadata:s:s:0", "language=eng", "{{out_1}}" ] }}Send the body to https://api.xora.sh/v1/jobs with Authorization: Bearer YOUR_API_KEY, then poll GET /v1/jobs/{id} or pass webhookUrl. See custom FFmpeg commands.
Common errors
| Error | Cause | Fix |
|---|---|---|
No such filter: 'subtitles' | The FFmpeg build has no libass. | Install a full build. |
Unable to open subs.srt | The path is wrong, or it has characters that the filter reads as options, such as : or '. | Use a simple file name, or escape the characters. |
| The text shows in the wrong font, or as boxes | The font is not installed, or it has no glyphs for the language. | Install the font, and name it in FontName. |
| Subtitles are early or late | The SRT times do not match this cut of the video. | Shift them with -itsoffset on the SRT input (soft), or fix the SRT file. |
VALIDATION_ERROR (Xora) | The output name has a hyphen or a space. | Use letters, numbers and underscores, for example talk_subtitled.mp4. |
Failed and rejected Xora jobs cost nothing. See error handling.
Related
- Custom FFmpeg commands.
- FFmpeg cheat sheet: the common commands, each as an API call.
- Add a watermark to video programmatically.
Frequently asked questions
How do I burn subtitles into a video with FFmpeg?
Use the subtitles filter: ffmpeg -i input.mp4 -vf subtitles=subs.srt -c:v libx264 -crf 20 -c:a copy output.mp4. The text becomes part of the picture, so it shows in every player and cannot be turned off.
How do I change the subtitle font size and colour in FFmpeg?
Add force_style to the subtitles filter, for example subtitles=subs.srt:force_style='FontName=Arial,FontSize=24,PrimaryColour=&H00FFFFFF,OutlineColour=&H00000000,Outline=2'. The colours are in ASS format, &HAABBGGRR.
What is the difference between burned-in and soft subtitles?
Burned-in (hard) subtitles are drawn into the video frames, which needs a re-encode. Soft subtitles are a separate stream in the file, which needs no re-encode and can be turned off, but not every player or social platform shows them.
Why does FFmpeg say No such filter: subtitles?
Your FFmpeg build has no libass. Install a full build, or use a build from your package manager that lists --enable-libass in ffmpeg -version.