12

I'm trying to re-encode video streams from a Matroska file to save space, while keeping all the subtitles as-is, using ffmpeg. I want to write a generic command that works without me having to specify exact stream numbers. Now I can't figure out how to let ffmpeg pick its default video stream and default audio stream and then all subtitles.

The current input file I'm working with has these streams, but other files will have different streams.

    [lavf] stream 0: video (mpeg2video), -vid 0
    [lavf] stream 1: audio (ac3), -aid 0, -alang eng, Surround 5.1
    [lavf] stream 2: audio (ac3), -aid 1, -alang fre, Surround 5.1
    [lavf] stream 3: audio (ac3), -aid 2, -alang ita, Surround 5.1
    [lavf] stream 4: audio (ac3), -aid 3, -alang spa, Surround 5.1
    [lavf] stream 5: audio (ac3), -aid 4, -alang eng, Stereo
    [lavf] stream 6: subtitle (dvdsub), -sid 0, -slang eng
    [lavf] stream 7: subtitle (dvdsub), -sid 1, -slang fre
    [lavf] stream 8: subtitle (dvdsub), -sid 2, -slang ita
    [lavf] stream 9: subtitle (dvdsub), -sid 3, -slang spa
    [lavf] stream 10: subtitle (dvdsub), -sid 4, -slang ara
    [lavf] stream 11: subtitle (dvdsub), -sid 5, -slang dan
    [lavf] stream 12: subtitle (dvdsub), -sid 6, -slang dut
    [lavf] stream 13: subtitle (dvdsub), -sid 7, -slang fin
    [lavf] stream 14: subtitle (dvdsub), -sid 8, -slang ice
    [lavf] stream 15: subtitle (dvdsub), -sid 9, -slang nor
    [lavf] stream 16: subtitle (dvdsub), -sid 10, -slang por
    [lavf] stream 17: subtitle (dvdsub), -sid 11, -slang swe
    [lavf] stream 18: subtitle (dvdsub), -sid 12, -slang fre
    [lavf] stream 19: subtitle (dvdsub), -sid 13, -slang ita
    [lavf] stream 20: subtitle (dvdsub), -sid 14, -slang spa

Commands I have tried:

ffmpeg -i IN.mkv -c:v libx264 -threads 4 -speed 1 -f matroska OUT.mkv

Result: One video stream, one audio stream, no subtitle streams.

ffmpeg -i IN.mkv -c:v libx264 -threads 4 -speed 1 -f matroska -c:s copy OUT.mkv

Result: One video stream, one audio stream, one subtitle stream.

ffmpeg -i IN.mkv -c:v libx264 -threads 4 -speed 1 -f matroska -map 0 OUT.mkv

Result: All video, all audio, all subtitles.

ffmpeg -i IN.mkv -c:v libx264 -threads 4 -speed 1 -f matroska -c:s copy -map 0:s OUT.mkv

Result: No video, no audio, all subtitles.

As far as I can tell from the manual, -c:s copy is supposed to copy all the streams, not just the default one, but it won't. Perhaps it's a bug?

To clarify, what I'm after is the result: one video, one audio and all subtitles.

2 Answers 2

29

The stream selection default behavior only selects one stream per type of stream, so inputs with multiple audio streams will create an output with one audio stream. To disable this behavior and manually choose desired streams use the -map option.

These examples use -c copy to stream copy (re-mux) from the input to to the output. No re-encoding occurs.

Stream copy all streams

ffmpeg -i input -map 0 -c copy output

1st video stream, 2nd audio stream, all subtitles

ffmpeg -i input -map 0:v:0 -map 0:a:1 -map 0:s -c copy output

3rd video stream, all audio streams, no subtitles

This example uses negative mapping to exclude the subtitles.

ffmpeg -i input -map 0:v:2 -map 0:a -map -0:s -c copy output

Choosing streams from multiple inputs

All video from input 0, all audio from input 1:

ffmpeg -i input0 -i input1 -map 0:v -map 1:a -c copy output
0
-2
#!/bin/bash
# Extract old format video tracks from each MKV file in the given directories and transcode them to e.g. x265
# extend for your purposes ;-) I've run this script for over a year. 
echo MKV:migrate
echo   "|  no args    : scan directory tree for candidates"
echo   "|  dir        : scan that directory"
echo   "|  . -c       : convert the video track in those candidates to H.265 (x265)"
echo   "|  . -c -r    : convert and remux into new resulting video"
echo   "\  . -c -r -k : and keep the intermediate video files (not by default)"
echo   " "
date
echo Reading all directories for MKV files, and verify whether they have Mpeg-4p2, a H264 or XVid or DivX or a H263 track in them.
echo If so, it will be extracted with mkvextract and converted to H265 with ffmpeg.
echo If remuxed, the original will be named to .old.mkv, and the new one will get the original name with suffix .x265.mkv.
echo With remuxing with mkvmerge, the new video track will be integrated, whilst the original old format track will not.
echo "(See: ffmpeg bla bla bla   -d \!0 trackno       if track 0 was the old videoformat, it will be skipped by -d)"
echo .
vidxt="mp4"
vidco="libx265"
H264=" "
H264="(H.264)|H264|(MPEG-4p10)|"
CRF="25"
VIDRATE=" 1500k "
#If 4 cores, you have 400% available. Limit your process to 65% e.g.
CPULIM=240
#SCALE=" -vf scale=1280:720 "
#RATE=" -r 23.976216 "

# If no directory is given, work in local dir
if [ "$1" == "" ]; then
  DIR="."
  doconvert=""
  doremux=""
  keep=""
else
  DIR="$1"
  doconvert=$2
  doremux=$3
  keep=$4
fi
# I ran this script first to get rid of really old formats...
if [ "$H264" == " " ]; then
  echo "Not scanning for H.264 videotracks."
else
  echo "Scanning also for H.264 videotracks: "$H264
fi

# Get all the MKV files in this dir and its subdirs
find "$DIR" -type f -name '*.mkv' | sort | while read filename
do
  # Find out which tracks contain the subtitles
  # echo Checking: $filename
  cand=0
  
  mkvmerge -i "$filename" | grep ' video ' | while read subline
  do
    candidate=`echo $subline | egrep -o $H264"(avc1)|(XVID)|(DX50)|MPEG-4p2"`
    #echo $filename.$subline
    if [ "$candidate" != "" ]; then
        # Grep the number of the video/audio track
        tracknumber=`echo $subline | egrep -o "[0-9]{1,2}" | head -1`
        #echo " EVAL : " $candidate " trk:" $tracknumber " cand: " $cand

        if [ $cand != 1 ]; then
        echo "|"
        echo $filename " : " $subline " : " $tracknumber
        MYDAT=$(ls -l --full-time "${filename}" |gawk -F ' '  '{ print $6" "$7 }'  | gawk -F.  '{ print $1 }')
        echo "  | Original date: "$MYDAT
        cand=1
        else
        echo "  /--Next track"
        fi
        echo "  |" `date`
        echo "  | Candidate: [$candidate]"

        # Get base name for subtitle
        basefilename=${filename%.*}
        echo "  \-- Video: "$subline" ($basefilename)"
        
        if [ "$doconvert" == "-c" ]; then
        # Extract the track to a .tmp file
        echo "   -- extracting $tracknumber:$basefilename.vid.tmp (avg 2 min) "
            `mkvextract tracks "$filename" $tracknumber:"$basefilename.vid.tmp" >>/dev/null 2>&1`
        `chmod g+rw "$basefilename.vid.tmp"`
        # DO THE FFMPEG CONVERSION
        echo "   -- "`date` 
        echo "   -- FFMpeg conversion of $basefilename.vid.tmp via $vidco to $vidxt (avg 2 hour)"
#       echo `   ffmpeg -i "$basefilename.vid.tmp" $RATE -c:v:0 $vidco -crf $CRF -b:v:0 $VIDRATE $SCALE "$basefilename.$tracknumber.$vidxt"`  >/dev/null 2>&1
        `cpulimit -f --limit=$CPULIM -- ffmpeg -i "$basefilename.vid.tmp" $RATE -c:v:0 $vidco -crf $CRF -b:v:0 $VIDRATE $SCALE "$basefilename.$tracknumber.$vidxt"  >/dev/null 2>&1`
        if [ "$keep" == "" ]; then
            `rm "$basefilename.vid.tmp" >/dev/null 2>&1`
        fi
        if [ "$doremux" == "-r" ]; then
            echo "   -- "`date` 
            `mv "$filename" "$basefilename".old.mkv >/dev/null 2>&1`
            echo "   -- Muxing $filename without track $tracknumber but with $vidxt. (avg 4 min) "
            mkvmerge -o "$basefilename.x265.mkv" -d \!$tracknumber "$basefilename.old.mkv" "$basefilename.$tracknumber.$vidxt"  >>/dev/null 2>&1
            if [ "$keep" == "" ]; then
                `rm "$basefilename.$tracknumber.$vidxt" >/dev/null 2>&1`
            fi
            touch --date="$MYDAT" "$basefilename.x265.mkv"  >>/dev/null 2>&1
        fi
        fi
    fi
  done
done

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .