27

I have multiple audio tracks and subtitles to extract in a single .mkv file. I'm new to ffmpeg commands, this is what I've tried (audio):

ffmpeg -i VIDEO.mkv -vn -acodec copy AUDIO.aac

It just extract 1 audio. What I want is tell ffmpeg to extract every single audio files and subtitle files to a destination, and keep the original name of each files and extensions. (Because I don't know which extension does the audio files are, sometimes maybe .flac or .aac).

I'm not sure about the solutions I'd found online, because it's quite complicated, and I need explanations to know how it's works, so that I can manipulate the command in the future. By the way, I planned to run the code from Windows CMD.

Thanks.

Ronald S. Bultje
  • 10,828
  • 26
  • 47
WhatWhereWhen
  • 473
  • 1
  • 5
  • 6
  • For Matroska (MKV) format, this is easy to do with [MKVToolNix](https://mkvtoolnix.download/downloads.html). – Geremia Jun 26 '20 at 17:26

5 Answers5

64

There is no option yet in ffmpeg to automatically extract all streams into an appropriate container, but it is certainly possible to do manually.

You only need to know the appropriate containers for the formats you want to extract.

Default stream selection only chooses one stream per stream type, so you have to manually map each stream with the -map option.

1. Get input info

Using ffmpeg or ffprobe you can get the info in each individual stream, and there is a wide variety of formats (xml, json, cvs, etc) available to fit your needs.

ffmpeg example

ffmpeg -i input.mkv

The resulting output (I cut out some extra stuff, the stream numbers and format info are what is important):

Input #0, matroska,webm, from 'input.mkv':
  Metadata:
  Duration: 00:00:05.00, start: 0.000000, bitrate: 106 kb/s
    Stream #0:0: Video: h264 (High 4:4:4 Predictive), yuv444p, 320x240 [SAR 1:1 DAR 4:3], 25 fps, 25 tbr, 1k tbn, 50 tbc (default)
    Stream #0:1: Audio: vorbis, 44100 Hz, mono, fltp (default)
    Stream #0:2: Audio: aac, 44100 Hz, mono, fltp (default)
    Stream #0:3: Audio: flac, 44100 Hz, mono, fltp (default)
    Stream #0:4: Subtitle: ass (default)

ffprobe example

ffprobe -v error -show_entries stream=index,codec_name,codec_type input.mkv

The resulting output:

[STREAM]
index=0
codec_name=h264
codec_type=video
[/STREAM]
[STREAM]
index=1
codec_name=vorbis
codec_type=audio
[/STREAM]
[STREAM]
index=2
codec_name=aac
codec_type=audio
[/STREAM]
[STREAM]
index=3
codec_name=flac
codec_type=audio
[/STREAM]
[STREAM]
index=4
codec_name=ass
codec_type=subtitle
[/STREAM]

2. Extract the streams

Using the info from one of the commands above:

ffmpeg -i input.mkv \
-map 0:v -c copy video_h264.mkv \
-map 0:a:0 -c copy audio0_vorbis.oga \
-map 0:a:1 -c copy audio1_aac.m4a \
-map 0:a:2 -c copy audio2.flac \
-map 0:s -c copy subtitles.ass

In this case, the example above is the same as:

ffmpeg -i input.mkv \
-map 0:0 -c copy video_h264.mkv \
-map 0:1 -c copy audio0_vorbis.oga \
-map 0:2 -c copy audio1_aac.m4a \
-map 0:3 -c copy audio2.flac \
-map 0:4 -c copy subtitles.ass

Container formats

A partial list to match the stream with the output extension for some common formats:

Video Format Extensions
H.264 .mp4, .m4v, .mov, .h264, .264
H.265/HEVC .mp4, .h265, .265
VP8/VP9 .webm
AV1 .mp4
MPEG-4 .mp4, .avi
MPEG-2 .mpg, .vob, .ts
DV .dv, .avi, .mov
Theora .ogv/.ogg
FFV1 .mkv
Almost anything .mkv, .nut
Audio Format Extensions
AAC .m4a, .aac
MP3 .mp3
PCM .wav
Vorbis .oga/.ogg
Opus .opus, .oga/.ogg, .mp4
FLAC .flac, .oga/.ogg
Almost anything .mka, .nut
Subtitle Format Extensions
Subrip/SRT .srt
SubStation Alpha/ASS .ass
llogan
  • 121,796
  • 28
  • 232
  • 243
  • 1
    What's the zero prefix for in `-map 0:a:0` ? Can we have multiple "indexes" (e.g. `0:a:0`, `1:a:0`) ? I thought the suffix(`0`) is actually the stream index. Is there any documentation for these "super" indexes? Sorry for being a bit off-topic. – themihai Apr 22 '17 at 22:29
  • 6
    @themihai These values represent `input id:stream specifier:stream id`. For example, `2:a:5` would be the 6th *a*udio stream from the 3rd input (`ffmpeg` starts counting from 0). It's more flexible than simply declaring something like `2:5` because the *stream index* value may not always represent the desired stream. It lets you be more lazy by letting you just choose the something like the 8th video stream from the 2nd input: `1:v:8`. See [`-map` docs](http://ffmpeg.org/ffmpeg.html#Advanced-options) and [this answer](http://stackoverflow.com/a/12943003/1109017) for more info. – llogan Apr 22 '17 at 22:35
  • thanks for answering! Makes sense, I've read the -map docs but I don't know why I missed that. – themihai Apr 22 '17 at 22:39
  • @themihai Main advantage is that you don't have to necessarily know ahead of time the exact numbers for a particular stream. – llogan Apr 22 '17 at 22:41
  • 3
    It is worth noting that prior to version 57.82.100 of libavformat it was not possible to extract Bluray subtitles (hdmv_pgs_subtitle) in *.sup format using this command so if you need to do that make sure you are using recent version of ffmpeg executable. – Igor Levicki Jan 01 '18 at 14:44
5

I solved it like this:

ffprobe -show_entries stream=index,codec_type:stream_tags=language -of compact $video1 2>&1 | { while read line; do if $(echo "$line" | grep -q -i "stream #"); then echo "$line"; fi; done; while read -d $'\x0D' line; do if $(echo "$line" | grep -q "time="); then echo "$line" | awk '{ printf "%s\r", $8 }'; fi; done; }

Output:

result-ffprobre-list-streams

Only set $video1 var before command.

Enjoy it!.

EzLo
  • 13,780
  • 10
  • 33
  • 38
arcmop
  • 61
  • 1
  • 2
  • Looks useful, but I am guessing this is a linux (only) command... Any chance you can share a version of this script that will work on Window please? – Martin Jun 25 '23 at 15:39
  • @Martin This is a bash command and windows can run bash already. – Ipor Sircer Jul 06 '23 at 15:41
  • @Igor - This doesn't work natively (e.g. in the Commend Prompt) so I guess you mean by using some third party app or optional Windows feature? – Martin Jul 28 '23 at 22:52
  • @Martin: It should also work on Mac which also has Bash. If you only have Windows, you can [Install Linux on Windows with WSL](https://learn.microsoft.com/en-us/windows/wsl/install). It's probably much easier than trying to figure out a Powershell equivalent. – mivk Aug 28 '23 at 08:09
4

You would first list all the audio streams:

ffmpeg -i VIDEO.mkv

and then based on the output you can compile the command to extract the audio tracks individually.

Using some shell script you can then potentially automate this in a script file so that you can do it generically for any mkv file.

Subtitles are pretty much the same. The subtitles will be printed in the info and then you can extract them, similar to:

ffmpeg -threads 4 -i VIDEO.mkv -vn -an -codec:s:0.2 srt myLangSubtitle.srt

0.2 is the identifier that you have to read from the info.

seba.wagner
  • 3,800
  • 4
  • 28
  • 52
1

If someone steps in this question with a modern version of ffmpeg, it looks like they added the option there. I needed to convert a file by maintaining all tracks:

ffmpeg -i "${input_file}" -vcodec hevc -crf 28 -map 0 "${output_file}"

To achieve what the original question asked, probably this could be used:

mappings="`ffmpeg -i \"${filein}\" |& awk 'BEGIN { i = 1 }; /Stream.*Audio/ {gsub(/^ *Stream #/, \"-map \"); gsub(/\(.*$/, \" -acodec mp3 audio\"i\".mp3\"); print; i +=1}'`"
ffmpeg -i "${input_file}" ${mappings}

The 1st line (mappings=...) extracts the existing audio streams and converts them in "-map X:Y -acodec mp3 FILENAME", while the 2nd one executes the extraction

Nicola
  • 81
  • 3
0

The following script extracts all audio streams from files in current directory

ls |parallel "ffmpeg -i {} 2>&1 |\
 sed -n 's/.*Stream \#\(.\+\)\:\(.\+\)\: Audio\: \([a-zA-Z0-9]\+\).*$/-map \1:\2 -c copy \"{.}.\1\2.\3\"/p' |\
 xargs -n5 ffmpeg -i {} "
ivan2kh
  • 36
  • 2
  • 4
    The stderr output from `ffmpeg` is not meant for machine parsing. That's what `ffprobe` is for. – llogan Jan 16 '19 at 23:07
  • Surely I could devise a better solution if only I have a better understanding of ffmpeg. So it is early solution that just works. – ivan2kh Jan 18 '19 at 08:40