Search code examples
ffmpeg

FFmpeg - How to choose video stream based on resolution


I want to choose the video stream based on it's quality, let's say I want to choose one video stream with certain resolution.

Manually selecting the video stream is not good enough for me because I want to process many files in bulk and they have the video streams in different order, so always going for certain position would make me end up with different resolutions.

I don't want to use filters as that would make me reencode which I don't need and would make it way slower.

I've tried using the -map with metadata but the only key that is different is "variant_bitrate" which has slightly different values everytime, so unless I can use some wildcard or conditionals, I guess it won't work either.

What I want to try now is to obtain the exact bitrate of the stream using ffmpeg or ffprobe and then pass it to the ffmpeg command so it ends in something like this:

ffmpeg -i <URL> -map m:variant_bitrate:1760000 ...

PD: I've been reading the FFmpeg documentation and browsing the whole internet without luck.

Edit: I managed to make it work by first using ffprobe to obtain stream info in json format (easier to parse), then I search for the string "height": 540 and extract next 50 lines (counted them manually so I'm sure I'll pick the value I need), then I search for the string variant_bitrate and then I use a regular expression to extract the bitrate. Once I have the bitrate I make use of the MacOS clipboard (with pbcopy and pbpaste) to pass the value to the final ffmpeg command through the -map option using a metadata selector.

ffprobe -v error -show_streams -of json "https://streamlink.com/master.m3u8?f=dash"
| grep -A 50 '"height": 540' 
| grep variant_bitrate
| grep -oe '\([0-9.]*\)' 
| pbcopy
&& ffmpeg -protocol_whitelist file,http,https,tcp,tls,crypto -i "https://streamlink.com/master.m3u8?f=dash" -map "0:a:0" -map "m:variant_bitrate:$(pbpaste)" -c copy "Output.mp4"

(added line breaks for readability)

I know it looks kinda dirty but I didn't find any other way to achieve my requirement.


Solution

  • Thanks for this. It was helpful getting started.

    You can use jq to select the stream indexes. In this case I select the highest bitrate audio and highest resolution video up to 720p:

    (This is fish shell syntax but bash is very similar I think. just add dollar signs everywhere)

    set streams (ffprobe -v error -show_streams -of json -headers 'User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:109.0) Gecko/20100101 Firefox/117.0' -headers 'Referer: https://emturbovid.com/' -i "https://ss216.livepie.com/stream/A/AA/WzblahblahblahzAk/master.m3u8")
    
    set video_index (echo $streams | jq '.streams | map(select(.codec_type == "video" and .height <= 720)) | max_by(.height) | .index')
    set audio_index (echo $streams | jq '.streams | map(select(.codec_type == "audio") | {index, bitrate: (.tags.variant_bitrate|tonumber)}) | max_by(.bitrate) | .index')
    
    ffmpeg -hide_banner -loglevel warning -stats -headers 'User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:109.0) Gecko/20100101 Firefox/117.0' -headers 'Referer: https://emturbovid.com/' -i "https://ss216.livepie.com/stream/A/AA/WzblahblahblahzAk/master.m3u8"  -map "0:$video_index" -map "0:$audio_index" out.mp4