How to Embed Soft Subtitles in MP4 and MKV with ffmpeg
← 博客
🎬 字幕与隐藏字幕7 min read

How to Embed Soft Subtitles in MP4 and MKV with ffmpeg

💡 To add a soft subtitle track to MP4, use -c:s mov_text: it is the only text codec the MP4 container accepts. For MKV, use -c:s srt or -c:s ass. Tag the track with ISO 639-2 code language=vie for Vietnamese and add -disposition:s:0 default to pre-select it. Video and audio copy without re-encoding.

Key takeaways

  • MP4 requires -c:s mov_text for text subtitle tracks; any other text codec will fail or produce an unreadable stream.
  • MKV accepts -c:s srt (plain timing) or -c:s ass (styled); both mux cleanly with no format conversion.
  • The ISO 639-2 language tag for Vietnamese is vie; set it with -metadata:s:s:0 language=vie.
  • Mark the default track with -disposition:s:0 default; clear other tracks with -disposition:s:1 0.
  • Use -c:v copy -c:a copy so only the subtitle stream is converted; video and audio pass through unchanged.

Soft subtitles vs burned-in captions: what is actually different?

Burned-in (hardcoded) captions are rendered into the video pixels permanently. They cannot be toggled off, and every frame must be re-encoded. That takes time and costs quality unless you set a generous CRF and bitrate.

Soft subtitles live in a separate stream inside the container. The player renders them at runtime, so the viewer can switch them on or off, change language, or resize them. The video stream stays untouched: no quality loss, and the mux completes in seconds even for a feature-length file because you are only packaging streams, not transcoding.

I use burned-in captions mainly for social exports where a platform gives me no styling control: a vertical short-form video with a styled .ass file burned in. For everything else, soft tracks are cleaner and more reusable. A soft track in the delivery MP4 or MKV, alongside a standalone .srt sidecar, covers most client requirements.

The core ffmpeg command for MP4

Here is the command I use to embed a Vietnamese SRT as a soft subtitle track in an existing MP4:

ffmpeg -i video.mp4 -i vi.srt \
  -map 0 -map 1 \
  -c:v copy -c:a copy \
  -c:s mov_text \
  -metadata:s:s:0 language=vie \
  -metadata:s:s:0 title="Vietnamese" \
  -disposition:s:0 default \
  output.mp4

What each flag does:

  • -map 0: include all streams from the first input (video, audio, any existing tracks).
  • -map 1: include the first stream from the second input (the .srt file).
  • -c:v copy -c:a copy: pass video and audio through without re-encoding.
  • -c:s mov_text: encode the subtitle as MOV text, the 3GPP timed text format MP4 requires.
  • -metadata:s:s:0 language=vie: tag the first subtitle stream with the ISO 639-2 code for Vietnamese. The specifier s:s:0 means: stream type subtitle, index 0.
  • -metadata:s:s:0 title="Vietnamese": the track label shown in player menus.
  • -disposition:s:0 default: marks this track as the default selection for players that auto-load one.

Why does MP4 require mov_text?

MP4 is built on the ISO Base Media File Format (ISOBMFF). For timed text tracks, the spec defines 3GPP Timed Text, which ffmpeg exposes as the mov_text codec. It is the only text-based subtitle type defined for the MP4 and MOV container family.

If you try -c:s srt or -c:s copy with an SRT source into an MP4 output, ffmpeg will error or write a track that players silently skip. The container has no slot for raw SubRip bytes. The same applies to ASS/SSA: do not use -c:s ass in an MP4 output.

The practical limit of mov_text: it supports basic inline styling (bold, italic, colour) but not the full positioning, margins, and animations that ASS provides. When you need rich on-screen positioning, burn the styled track in with -vf ass=styled.ass and optionally mux a plain soft track separately for accessibility.

Embedding in MKV and adding multiple language tracks

Matroska (MKV) is more flexible. It natively stores SubRip (srt), ASS/SSA (ass), WebVTT, and more. You can mux an SRT without any conversion, or preserve a full ASS style sheet:

# MKV: embed a Vietnamese SRT track
ffmpeg -i video.mkv -i vi.srt \
  -map 0 -map 1 \
  -c:v copy -c:a copy \
  -c:s srt \
  -metadata:s:s:0 language=vie \
  -metadata:s:s:0 title="Vietnamese" \
  -disposition:s:0 default \
  output.mkv

# MKV: embed a styled ASS subtitle (preserves all style data)
ffmpeg -i video.mkv -i vi.ass \
  -map 0 -map 1 \
  -c:v copy -c:a copy \
  -c:s ass \
  -metadata:s:s:0 language=vie \
  output.mkv

For two language tracks in one file (English default, Vietnamese second):

ffmpeg -i video.mp4 -i en.srt -i vi.srt \
  -map 0 -map 1 -map 2 \
  -c:v copy -c:a copy \
  -c:s mov_text \
  -metadata:s:s:0 language=eng \
  -metadata:s:s:0 title="English" \
  -metadata:s:s:1 language=vie \
  -metadata:s:s:1 title="Vietnamese" \
  -disposition:s:0 default \
  -disposition:s:1 0 \
  output.mp4
  • -map 2: adds the third input (the second .srt file) as another stream.
  • -metadata:s:s:1: targets the second subtitle stream (index 1).
  • -disposition:s:1 0: clears the default flag on the Vietnamese track.

Verify both tracks landed with ffprobe:

ffprobe -v error \
  -select_streams s \
  -show_entries stream=index,codec_name:stream_tags=language,title \
  -of default=noprint_wrappers=1 \
  output.mp4

A clean two-track result:

index=2
codec_name=mov_text
TAG:language=eng
TAG:title=English

index=3
codec_name=mov_text
TAG:language=vie
TAG:title=Vietnamese

If language=und appears, the -metadata:s:s: index was off. If codec_name is not mov_text in an MP4, most players will skip the track silently.

When is professional subtitle translation worth it?

The ffmpeg commands above handle the container work perfectly. What they cannot do is translate the text. My workflow when a client needs an English video with a Vietnamese subtitle track: translate in Subtitle Edit with CPS warnings live, export the finished .srt, then embed it with the command above.

A common trap: auto-translated .srt files embedded without a CPS or line-break check. The container muxes cleanly, but the subtitles run too fast because the translation expanded the text. Vietnamese lines typically run longer than their English originals, so a reading-speed check before the mux step saves a revision round.

If you need an accurate, professionally timed Vietnamese subtitle track, or delivery to a streamer with a Timed Text Style Guide spec, that is where a native subtitler earns their rate. I cover the full translation and QC process on the subtitle translation page.

FAQ

Can I embed WebVTT as a soft track in an MP4?

No. MP4 does not support WebVTT as a container subtitle track. WebVTT is delivered alongside a video as a sidecar file loaded via an HTML <track> element or an HLS playlist. To get a self-contained soft track inside an MP4, convert the VTT to SRT first with ffmpeg -i in.vtt out.srt, then mux it with -c:s mov_text.

What is the difference between -c:s copy and -c:s srt?

With -c:s copy, ffmpeg passes subtitle bytes through unchanged. This works when the source codec is already compatible with the output container, for example an SRT source into MKV. For MP4 output, copy will fail because MP4 needs mov_text, not raw SubRip bytes. Always specify -c:s mov_text explicitly for any MP4 output.

Why does my player show "und" or "undefined" for the subtitle language?

The language tag uses the ISO 639-2 three-letter code. For Vietnamese the correct value is vie, not vi or Vietnamese. Some ffmpeg builds accept vi and normalise it silently; others write it verbatim as vi, which some players do not recognise. I always use the three-letter form: vie for Vietnamese, eng for English, fra for French.

Can I add a soft subtitle track without re-encoding the video?

Yes. With -c:v copy -c:a copy, the video and audio pass through byte-for-byte. Only the subtitle is converted (from SRT text to mov_text for MP4). The operation takes seconds even for a long file, and there is no quality loss to the video. The output file grows slightly because it now contains the subtitle stream.

My subtitle text is garbled in the MP4. What happened?

The most common cause is character encoding. The source SRT file must be UTF-8. If it was saved in Windows-1252 or another legacy encoding, Vietnamese diacritics and other special characters will corrupt after mov_text encoding. Open the file in Subtitle Edit, check File > Character encoding, re-save as UTF-8 without BOM, then re-run ffmpeg. A second cause is using -c:s copy on an SRT into MP4 output, which writes raw SubRip bytes into a mov_text slot and produces garbled text.

Official Sources

Written by Dao Huy (Lucas), Vietnamese translator & localization specialist (EN · ZH · FR → Vietnamese). See translation services →

报价WhatsApp