# 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 2026-11-07 · https://shareglint.com/en/guides/ffmpeg-add-subtitles

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

```text
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

```bash
# 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`:

```bash
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](https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Elements/track) 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

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

The [`subtitles` filter](https://ffmpeg.org/ffmpeg-filters.html#subtitles) 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:

```bash
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](https://shareglint.com/en/use-cases/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.
