Skip to content

Audio and subtitles

current

A film or episode usually carries more than one video and one audio stream: several languages, commentary, full and forced subtitles, chapters and attached fonts. This guide covers keeping the ones you want, re-encoding audio, labelling tracks, getting subtitles into MP4, burning subtitles in, and fixing audio delay.

Run ffprobe -hide_banner input.mkv to list the streams. FFmpeg’s stream specifiers count in two ways:

  • 0:3 is stream 3 of input 0, counting all streams.
  • 0:a:1 is the second audio stream of input 0, counting only that type from zero.

Options that act on output streams, such as -c:a:1 or -metadata:s:a:1, use the numbering of the output file. If you map the source’s second audio track first, it becomes output a:0.

Once you use -map, FFmpeg writes only the streams you list. Without it, it keeps one video, one audio and one subtitle stream and drops the rest.

Terminal window
ffmpeg -i input.mkv -map 0 -c copy output.mkv

-map 0 takes every stream, including attachments such as fonts; -c copy copies them without re-encoding. Chapters and global metadata are copied by default. This is how you change container or drop tracks without any quality loss. For detailed Matroska editing (track order, flags, header edits without remuxing) MKVToolNix is often more convenient.

To drop tracks, map selectively. This keeps the video, the first audio track and every subtitle stream (? makes the subtitle map optional; the quotes stop zsh from treating ? as a wildcard):

Terminal window
ffmpeg -i input.mkv -map 0:v:0 -map 0:a:0 -map '0:s?' -map '0:t?' -c copy output.mkv
Terminal window
ffmpeg -i input.mkv -map 0 -c copy -c:a libopus -b:a:0 128k -b:a:1 256k output.mkv

-c copy applies to everything and -c:a libopus then overrides it for audio. Set bitrates per output stream: about 128 kbit/s for stereo and 256 kbit/s for 5.1 are good Opus starting points.

libopus rejects the common 5.1(side) layout with Invalid channel layout 5.1(side) for specified mapping family, and -mapping_family 1 on its own does not change that. Convert the surround track to plain 5.1 with a filter that targets only that output stream:

Terminal window
ffmpeg -i input.mkv -map 0 -c copy -c:a libopus -b:a:0 128k -b:a:1 256k \
-filter:a:1 aformat=channel_layouts=5.1 -mapping_family:a:1 1 output.mkv

aformat moves the side channels to the back positions without downmixing. A bare -af would apply to every audio stream and upmix the stereo track to 5.1 as well. -mapping_family 1 switches libopus to its surround encoder, which uses masking between channels to spend the bits better.

Copy audio that already plays on your targets. Re-encoding a lossy track (AC-3, DTS, AAC) adds another generation of loss; re-encoding lossless tracks (FLAC, PCM, TrueHD, DTS-HD MA) to Opus saves most of their size.

Terminal window
ffmpeg -i input.mkv -map 0:v:0 -map 0:a:0 -map 0:a:1 -map 0:s:0 -c copy \
-metadata:s:a:0 language=eng -metadata:s:a:0 title="Stereo" \
-metadata:s:a:1 language=jpn \
-disposition:a:0 default -disposition:a:1 0 -disposition:s:0 0 \
output.mkv

Languages are ISO 639-2 codes (eng, jpn, ger…). A disposition value replaces all of that stream’s flags, so 0 clears them. Players use the default flag when the viewer has no language preference; they may ignore it when one is set.

A forced subtitle track holds only what should appear when full subtitles are off, such as translated signs and foreign-language lines. Mark such a track with -disposition:s:0 forced. The flag does not extract forced lines from a full subtitle track.

TargetSubtitle formatNotes
MKVCopy anything: SRT, ASS, PGS, VobSubKeep ASS fonts as attachments.
MP4mov_text (converted from SRT or ASS)ASS styling and positioning are lost. Bitmap subtitles cannot be converted.
HTML <video>External WebVTT file in a <track> elementMost browsers ignore subtitle tracks embedded in the video file.
Players without subtitle supportBurn in (below)Cannot be turned off.

An MP4 with AAC audio and text subtitles converted to mov_text:

Terminal window
ffmpeg -i input.mkv -map 0:v:0 -map 0:a:0 -map 0:s:0 \
-c:v copy -c:a aac -b:a 192k -c:s mov_text -movflags +faststart output.mp4

-movflags +faststart moves the MP4 index to the front so web playback can start before the file has fully downloaded. Turning PGS or VobSub (bitmap) subtitles into text needs OCR, for example with Subtitle Edit.

ASS styles refer to fonts by family name, so the fonts have to travel with the file. In MKV they are stored as attachments; -map 0 (or -map '0:t?') keeps them. To add a font, use mkvmerge:

Terminal window
mkvmerge -o output.mkv input.mkv --attach-file MyFont-Regular.ttf

Include the bold and italic faces the script uses, and only attach fonts whose licence allows it.

Burning in renders the subtitles onto the video, so the video must be re-encoded. For text subtitles (SRT, ASS) use the subtitles filter. It needs an FFmpeg build with libass, and it loads fonts attached to the file:

Terminal window
ffmpeg -i input.mkv -map 0:v:0 -map 0:a:0 -vf "subtitles=input.mkv:si=0" \
-c:v libx264 -preset slow -crf 20 -c:a copy output.mkv

si=0 picks the first subtitle stream in the file. For an external file use subtitles=captions.ass, and add :fontsdir=fonts if its fonts are in a separate directory.

For bitmap subtitles (PGS, VobSub, DVB), overlay them instead:

Terminal window
ffmpeg -i input.mkv -filter_complex "[0:v][0:s:0]overlay=shortest=1" -map 0:a:0 \
-c:v libx264 -preset slow -crf 20 -c:a copy output.mkv

shortest=1 ends the output with the video; without it the last subtitle event can stretch the output far past the end. The subtitle canvas must match the video size; if it does not (for example after cropping or scaling), scale or crop the video after the overlay rather than before.

Check sync near the start, middle and end. A constant offset is a delay you can fix with timestamps. Drift that grows over time comes from a frame-rate or speed mismatch (for example a 25 fps PAL track against a 23.976 fps video) and needs the audio to be time-stretched or resampled instead.

To delay an audio track by 250 ms, offset the input it comes from:

Terminal window
ffmpeg -i video.mkv -itsoffset 0.250 -i audio.mka -map 0:v:0 -map 1:a:0 -c copy output.mkv

-itsoffset applies to the input that follows it; use a negative value to make the audio earlier. The same input can be opened twice (-i input.mkv -itsoffset 0.250 -i input.mkv) to shift a track within one file. In MKVToolNix the equivalent is the track’s delay setting (--sync TID:250).