Menu
Sign up

Add subtitles with FFmpeg: soft subtitles, burned-in captions, and the silent MP4 trap

Measured with FFmpeg 9.0.2: soft subtitles in 0.03 s, burned-in captions in 1.4 s, and the -c copy command that silently dropped the subtitles from an MP4.

Published November 7, 2026 · 4 min read

There are two ways to put subtitles on a video with FFmpeg. Soft subtitles are a separate text track inside the file: players can turn them on and off, and nothing is re-encoded. Burned-in subtitles are drawn into the picture: they show in every player, including social feeds, but the video has to be re-encoded and nobody can turn them off. This page measures both and points out a trap that leaves you with no subtitles and no error.

Measured on 2026-11-07 with FFmpeg 9.0.2 (linuxserver/ffmpeg image, built with libass) on a 10-second 1280×720, 30 fps H.264 video with AAC audio, and a two-line SRT file with Polish accents.

The SRT file

1
00:00:00,500 --> 00:00:02,500
Hello and welcome.

2
00:00:03,000 --> 00:00:05,500
Zażółć gęślą jaźń — subtitles with accents.

SRT is a numbered list of cues: a time range (with a comma before the milliseconds) and one or two lines of text, separated by blank lines. Save it as UTF-8, or accented letters break. Most transcription tools export SRT; WebVTT (.vtt) works the same way in FFmpeg.

Soft subtitles

# MP4: text subtitles must be mov_text
ffmpeg -i in.mp4 -i subs.srt -c copy -c:s mov_text -metadata:s:s:0 language=eng out.mp4

# MKV: keeps SRT as it is
ffmpeg -i in.mp4 -i subs.srt -c copy out.mkv

-c copy copies the video and audio untouched; -c:s mov_text converts the subtitle stream to the text format MP4 supports; the language tag lets players show "English" in their subtitle menu. In our test this took 0.03 seconds and the output had three streams: H.264, AAC and mov_text. The MKV command kept the subtitles as SubRip (subrip).

The trap

Leave out -c:s mov_text:

ffmpeg -i in.mp4 -i subs.srt -c copy out.mp4   # no subtitles in the result

FFmpeg 9.0.2 finished without an error, and the MP4 had only video and audio — the subtitle stream was simply not included. Always check the result with ffprobe -v error -show_entries stream=codec_name -of csv=p=0 out.mp4; a file with subtitles lists mov_text.

Soft subtitles are the right format for YouTube and Vimeo (both also accept the SRT file uploaded on its own), for video on your website (a <track> element with a .vtt file, which also lets you offer several languages), and for archives, where the text stays searchable and editable.

Converting and extracting

FFmpeg also converts between subtitle formats: ffmpeg -i subs.srt subs.vtt turned our SRT into WebVTT for a web <track> in one step, and ffmpeg -i soft.mp4 -map 0:s:0 back.srt pulled the mov_text track out of the MP4 again as an SRT file with the original timings — handy when you need to correct a typo and re-mux.

Burned-in subtitles

ffmpeg -i in.mp4 -vf "subtitles=subs.srt:force_style='FontSize=28,Outline=2'" -c:a copy out.mp4

The subtitles filter uses libass to draw each cue onto the frames at its time; -c:a copy keeps the audio as it is. This re-encodes the video: 1.38 seconds for our 10-second clip, against 0.03 for the soft track — about 45 times longer, and it scales with the length of your video.

Styling

force_style takes ASS style fields, separated by commas:

Field Example Effect
FontName FontName=DejaVu Sans A font installed on the machine running FFmpeg
FontSize FontSize=28 Size, relative to the script resolution
PrimaryColour PrimaryColour=&H00FFFFFF Text colour as &HAABBGGRR (alpha, blue, green, red)
Outline / Shadow Outline=2,Shadow=0 Outline and shadow width
BorderStyle BorderStyle=3 An opaque box behind the text instead of an outline
MarginV MarginV=40 Distance from the bottom edge

Two surprises. First, sizes are not in video pixels: SRT has no resolution, so libass uses a small default script resolution and scales it to the frame — FontSize=28 drew letters about 60 pixels tall on our 720p video. Start around 20–24 and look at a frame. Second, colours are written blue-green-red: yellow is &H0000FFFF, not &H00FFFF00.

To check a burned frame without watching the whole video: ffmpeg -ss 1 -i out.mp4 -frames:v 1 check.png.

Fonts

The filter can only use fonts installed where FFmpeg runs. In a slim Docker image, text may fall back to another font or show boxes for missing characters — in our image the Polish accents rendered correctly with its installed fonts. If you need a specific font, install it, or point libass at a folder with subtitles=subs.srt:fontsdir=./fonts.

Which to use

Soft subtitles Burned in
Time (10 s clip) 0.03 s 1.38 s
Re-encodes video No Yes
Viewer can turn off Yes No
Shows in social feeds Often not Always
Several languages Yes, one track each One per video file
Searchable text Yes No

For a short clip that will autoplay muted in a feed, burn it in. For long videos, YouTube and your own site, keep subtitles soft or upload the SRT separately.

Quotes from your transcript, as images

A subtitle file is also a ready list of your best lines. We do not caption or edit video — FFmpeg is the tool for that — but the same text makes good images: a quote card for each memorable line of an interview or podcast episode, posted next to the clip. With Shareglint that is one request per line, with the guest's name and the show's cover:

curl -X POST "https://shareglint.com/v1/images?wait=true" \
  -H "Authorization: Bearer sk_live_..." -H "Content-Type: application/json" \
  -d '{ "template": "podcast-quote", "size": "square",
        "modifications": { "quote": "Hello and welcome.", "guest": "Ada Lovelace" } }'

See quote cards for pulling lines from transcripts and the templates that fit.

FAQ

How do I add an SRT file to an MP4 with FFmpeg?

ffmpeg -i in.mp4 -i subs.srt -c copy -c:s mov_text out.mp4. MP4 stores text subtitles as mov_text, so the subtitle stream must be converted while video and audio are copied. In our test, the same command without -c:s mov_text produced an MP4 with no subtitle track and no error.

How do I burn subtitles into a video with FFmpeg?

Use the subtitles filter: ffmpeg -i in.mp4 -vf "subtitles=subs.srt" -c:a copy out.mp4. The text becomes part of the picture, so it shows everywhere, but the video is re-encoded and viewers cannot turn it off.

How do I change the subtitle font size in FFmpeg?

Add force_style to the subtitles filter, for example subtitles=subs.srt:force_style='FontSize=28,Outline=2'. Sizes are relative to the subtitle script's own resolution, not your video's pixels: FontSize=28 came out about 60 pixels tall on a 720p frame in our test.

Should I burn in subtitles for social media?

For feeds that autoplay without sound — Instagram, TikTok, LinkedIn — burned-in captions are the safe choice, because they show in every player. For YouTube or your own site, upload the subtitle file separately, so viewers can switch it off and search engines can read it.