All posts
ffmpeg burn subtitles

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

Terminal window
ffmpeg -i input.mp4 -vf subtitles=subs.srt -c:v libx264 -crf 20 -preset medium -c:a copy -movflags +faststart output.mp4
PartWhat it does
-vf subtitles=subs.srtDraw the subtitles from the file onto each frame. Works with SRT, ASS and VTT files. Needs FFmpeg with libass.
-c:v libx264 -crf 20The picture changes, so the video must be re-encoded. CRF 20 keeps it close to the source.
-c:a copyKeep 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

Terminal window
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 keyWhat it sets
FontName, FontSizeThe font and its size. The font must be installed where FFmpeg runs.
PrimaryColour, OutlineColourText and outline colours, written &HAABBGGRR (alpha, blue, green, red).
BorderStyle=1, Outline=2An outline 2 pixels wide. BorderStyle=3 draws a box behind the text.
MarginV=40Distance from the bottom edge, in pixels.
Alignment=8Put 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

Terminal window
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.

TypeRe-encodeViewers can turn it offShows on social platforms
Burned-inYesNoYes
SoftNoYesOften 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:

POST /v1/jobs — raw args, burn-in
{
"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:

POST /v1/jobs — raw args, soft subtitles
{
"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

ErrorCauseFix
No such filter: 'subtitles'The FFmpeg build has no libass.Install a full build.
Unable to open subs.srtThe 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 boxesThe font is not installed, or it has no glyphs for the language.Install the font, and name it in FontName.
Subtitles are early or lateThe 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.

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.

Ready to try Xora?

Replace your local FFmpeg binaries with a secure, high-performance cloud API.