💡 In MP4, ffmpeg expects ISO 639-2 three-letter codes for subtitle language metadata: use
-metadata:s:s:0 language=viefor Vietnamese, notvi. In MKV, mkvmerge writes both the legacy ISO 639-2 element and the BCP 47LanguageIETFelement from a single flag, letting you usezh-Hansfor Simplified Chinese, something the three-letter codezhoalone cannot express.
Key takeaways
- ffmpeg's
-metadata:s:s:N language=takes ISO 639-2 three-letter codes:vie(Vietnamese),eng(English),fra(French),zho(any Chinese). - Using the two-letter BCP 47 form (
vi) in ffmpeg is unreliable across players; always use the three-letter ISO 639-2 form for MP4. - MKV stores two parallel language fields: legacy
Language(ISO 639-2) and modernLanguageIETF(BCP 47); mkvmerge writes both from a single--languagevalue. - For Chinese, BCP 47 subtags (
zh-Hans/zh-Hant) are the only way to distinguish Simplified from Traditional in a subtitle track. - Verify every tag after muxing:
ffprobe -v error -select_streams s -show_entries stream=index,codec_name:stream_tags=language,title -of default=noprint_wrappers=1 output.mp4
Why does the subtitle language tag matter, and which codes should I use?
Subtitle language tags tell media players, streaming manifests and accessibility tools which track to load automatically. Set the wrong code or leave it as und (undefined) and the player may skip the track, or load the wrong language for a viewer whose system locale is set to Vietnamese or Simplified Chinese.
I have had a client's Plex library skip a Vietnamese subtitle track because the muxer wrote vi into an MP4 field that expected the three-letter ISO 639-2 code vie. The file played fine in VLC, which is more lenient, but the delivery failed QC on the client's platform. A wrong tag is a quick fix when caught early and an expensive re-delivery when caught later.
Two standards govern language tagging in media containers:
| Language | BCP 47 tag | ISO 639-2 code | Notes |
|---|---|---|---|
| Vietnamese | vi | vie | Use vie in ffmpeg MP4 |
| English | en | eng | |
| French | fr | fra | |
| Simplified Chinese | zh-Hans | zho | zho alone is ambiguous |
| Traditional Chinese | zh-Hant | zho | same ISO 639-2 as Simplified |
| Japanese | ja | jpn | |
| Korean | ko | kor | |
| Spanish | es | spa |
ISO 639-2 uses three-letter codes and was the standard for media containers before BCP 47 (IETF RFC 5646) was widely adopted. BCP 47 adds optional subtags for script (Hans, Hant) and region. The critical mismatch: zho covers every Chinese variant in ISO 639-2, so an ISO-639-2-only container cannot tell Simplified from Traditional script.
Setting the correct language tag in MP4 with ffmpeg
MP4 stores subtitle metadata in a track-level language box. ffmpeg maps this to ISO 639-2. This is the command I use to embed a Vietnamese SRT with the right tag and mark it as the default track:
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
Flags relevant to the language tag:
-metadata:s:s:0 language=vie: the specifiers:s:0means stream-type subtitle, index 0. The value must be an ISO 639-2 three-letter code, not the two-letter BCP 47 form.-metadata:s:s:0 title="Vietnamese": the human-readable label shown in player menus - independent of the language code.-disposition:s:0 default: marks this track as the default for players that auto-load one subtitle.
For two subtitle tracks, the second gets -metadata:s:s:1 language=eng and -disposition:s:1 0 to clear the default flag. The index after s:s: is the subtitle stream index starting from 0, not the absolute stream number in the file. For the full codec and mux workflow, see the post on embedding soft subtitles in MP4 and MKV.
How do MKV files handle BCP 47 tags and zh-Hans?
Matroska (MKV) introduced a second language field in its v4 spec: the legacy Language (ISO 639-2) and the modern LanguageIETF (BCP 47), both on every track. mkvmerge (from MKVToolNix) writes both from a single --language value, automatically extracting the ISO 639-2 portion for the legacy field:
# Add a Vietnamese SRT to an MKV
mkvmerge -o output.mkv \
video.mkv \
--language 0:vi \
--track-name "0:Vietnamese" \
vi.srt
# Add Simplified Chinese subtitles with script differentiation
mkvmerge -o output.mkv \
video.mkv \
--language 0:zh-Hans \
--track-name "0:Chinese (Simplified)" \
zh-hans.srt
# Both Simplified and Traditional as separate selectable tracks
mkvmerge -o output.mkv \
video.mkv \
--language 0:zh-Hans --track-name "0:Chinese (Simplified)" zh-hans.srt \
--language 0:zh-Hant --track-name "0:Chinese (Traditional)" zh-hant.srt
--language 0:zh-Hans: mkvmerge writeszhoto the legacyLanguageelement andzh-HanstoLanguageIETF. Players that readLanguageIETF(VLC 3.0+, mpv, Infuse) show "Chinese (Simplified)" as a named option.- The
0:in--language 0:viis the track index within the NEXT input file listed, not an absolute stream number in the output.
The script differentiation matters most when a file carries both Simplified and Traditional Chinese tracks. Without distinct BCP 47 subtags, a player menu shows two entries both labeled "Chinese" and the viewer cannot tell them apart. For MP4 output with two Chinese tracks, the standard language field does not carry the script subtag the same way. When a deliverable requires both scripts as distinguishable named options, MKV + mkvmerge is the more reliable container choice.
How do I verify the language tags with ffprobe?
I run this ffprobe command immediately after every mux to confirm the tag landed before the file leaves my machine:
ffprobe -v error \
-select_streams s \
-show_entries stream=index,codec_name:stream_tags=language,title \
-of default=noprint_wrappers=1 \
output.mp4
Expected output for a correctly tagged Vietnamese track in MP4:
index=2
codec_name=mov_text
TAG:language=vie
TAG:title=Vietnamese
What the output tells you:
TAG:language=und: the ISO 639-2 code was not recognised; check the three-letter spelling against the table above.TAG:languageline missing: the-metadata:s:s:Nindex was off - count subtitle streams from 0.codec_namenotmov_textin an MP4: the subtitle codec is wrong; most players will silently skip the track.
For MKV files, use mkvinfo output.mkv (included in MKVToolNix) instead - it prints both Language and LanguageIETF explicitly for each track, so you can confirm the BCP 47 subtag was written correctly.
When is a subtitle translator worth it, and when can I do this myself?
The tagging and muxing commands above are within reach of anyone with ffmpeg or MKVToolNix installed. Once you have the subtitle file, the technical work is a few minutes.
What is not automatable is the translation itself. My role in a subtitle delivery is the Vietnamese reading of the source, the line-break judgement on each cue, and the CPS check against the target platform's style guide. The mux step with the correct language tag is the last five minutes of a project, not what justifies the rate.
If you have a finished SRT in the right language and need it tagged and muxed correctly, the commands above handle it. If you need the subtitle file itself translated, timed and checked by a native speaker, that is what I do on the subtitle translation page.
FAQ
What is the correct language code for Vietnamese in ffmpeg?
Use vie, the ISO 639-2 three-letter code for Vietnamese. Avoid vi (the BCP 47 form) for ffmpeg's -metadata:s:s:N language= field. Some ffmpeg builds auto-convert vi to vie, but the behaviour is not guaranteed across all versions. Set vie explicitly and the tag is portable across VLC, Plex, Apple TV, Infuse and streaming platform QC tools.
Can I use BCP 47 tags like zh-Hans in an MP4?
ffmpeg's standard -metadata:s:s:N language= field only carries a three-letter ISO 639-2 code, not a full BCP 47 subtag like zh-Hans. The MP4 container does not have a standard slot for the script subtag the same way MKV does via LanguageIETF. For subtitle tracks that must distinguish Simplified from Traditional Chinese as separate named options, MKV with mkvmerge is the more reliable container choice.
Does the container language code affect web search indexing?
Not directly. For web delivery, the HTML <track> element's srclang attribute uses BCP 47 (e.g., srclang="vi"), and browsers and search engines use that for accessibility and indexing. The container-level language tag is for local players, streaming platform QC and media library software, not for web crawlers.
What happens if I deliver a file with the wrong language tag to a streaming platform?
Automated QC tools at platforms like Netflix or Amazon check subtitle track metadata against the expected delivery spec. A wrong or missing ISO 639-2 code on a subtitle track typically generates a flag in the QC report. Depending on the platform, this may block ingest or require a correction and re-delivery. It is a quick fix when caught early and a costly delay when caught during release QC.
How do I find the ISO 639-2 code for a language not in this table?
The authoritative lookup is the Library of Congress ISO 639-2 registry (loc.gov/standards/iso639-2/). It lists both the terminological form (which ffmpeg uses) and the bibliographic form for each language. For BCP 47 base codes and subtags, the IANA Language Subtag Registry (iana.org) is the official source. Look up both when working with a new language to confirm the three-letter form before muxing.
Official Sources
- FFmpeg documentation: -metadata option and stream specifiers - authoritative reference for
-metadata:s:s:N language=syntax and stream specifier format. Verified November 2026. - MKVToolNix mkvmerge documentation: --language option - documents BCP 47 support, automatic ISO 639-2 extraction and dual element writing. Verified November 2026.
- Library of Congress: ISO 639-2 code list - authoritative lookup for three-letter codes including
vie,eng,fra,zho. Verified November 2026. - IETF RFC 5646: BCP 47 language tags - defines the BCP 47 syntax including script subtags like
zh-Hansandzh-Hant. Verified November 2026.
作者:Đào Huy(Lucas),越南语译员与本地化专家(英、中、法译越)。 See translation services →

