Media Conversion Scripts

August 15, 2022 ยท View on GitHub

Purpose

  • To learn PowerShell 7+ (pwsh)
  • Learn HandBrakeCLI
  • Learn FFMPEG (CLI)
  • Learn how to create multi-stream videos
    • Optimize media library for a wide range of mid-to-low bandwidth Roku devices

Language

  • PowerShell v7.2.6 - I'm already a "Power User" with Linux/BASH ... can do some basic things in Python, but I fall short on PowerShell, so I thought this would be a great project to introduce me to the language. I appreciate any feedback on the code and/or tips/tricks! Thank you!

Tools

Documentation

RTFM

Process

The Bit Ladder

As outlined here (https://developer.roku.com/docs/specs/media/streaming-specifications.md), the Roku developer platform talks about how the devices can intelligently switch between the available streams within the file to best serve content. I had always assumed that my Plex server would do this with various versions of the media I had stored in the "Plex Versions" folder. Perhaps my mistake was manually re-encoding the videos and just dropping them there, perhaps I had to use the Plex method so the media could be "Tagged" somehow -- I'm not sure.

So, I decided I would follow the Roku recommendation and try to create the "Bit Ladder." Roku Bit Ladder

The Code

With this information in hand, I set out to create a script that would automatically convert a movie into the requisite 8 parts:

'%HANDBRAKECLI%' `
--preset-import-file '%HANDBRAKEPRESETS%\BITLADDER.json' `
--preset BITLADDER/STEP$_ `
--input $SOURCE `
--output $OUTPUT `
--verbose | Tee-Object -FilePath "$LOGFILE" -Append

The preset file I'm passing to HandBrake is what's actually doing all the hard work, and it's cool to watch if you're into that kind of thing.

The PowerShell script "BitLadder.ps1" uses the new PowerShell 7+ ForEach-Object -Parallel which allows me to encdode all 8 Bitladder Steps at once. It definitely hit my system like a freight train, but it handled it quite well: Windows Task Manager getting absolutely murdered

In (roughly) 18 minutes, I had 8 versions of the same file! BitLadder Steps Output

FFMPEG

Last Step

I got learned up on using FFMPEG and learned about "Complex Filter Maps" and how I could "Map" the audio and video of my eight (8) input files into my single output file. I wrote the automation for it with a loop so that the end command would be formatted properly:

# Mux the 8 Bitrate Ladder steps into a single file with a common audio stream
$STEPS = @(Get-ChildItem -Path $OUTPUTDIR -Name)
$COUNT = 0
ForEach ($STEP in $STEPS){
    $INPUT_FILES += ("-i $STEP ")
    $VIDEO += ("[" + $COUNT + ":v]" + "[v" + $COUNT + "],")
    $V_ALIAS += ("[v" + $COUNT + "]")
    $AUDIO += ("[" + $COUNT + ":a]" + "[a" + $COUNT + "],")
    $A_ALIAS += ("[a" + $COUNT + "]")
    $COUNT++
}

$V_ALIAS += ("[video]")
$A_ALIAS += ("[audio]")
$FLATTEN = ('-' + "$INPUT_FILES" + "-filter_complex " + '"' + "$VIDEO" + ";" + "$AUDIO" + '"' + ';' + "$V_ALIAS" + ';' + "$A_ALIAS" + '"' + " -map [video][audio] -c:v h264 -c:a copy").Replace(',;', ';').Replace(',";', ';').Replace(',[video]', '[video]').Replace(',[audio]', '[audio]').TrimStart(' ')

D:\Applications\ffmpeg\bin\ffmpeg.exe $FLATTEN -f matroska "E:\MediaConversions\White.Boy.Rick.2018_MultiStreamTest_AAC.H264.mkv"

If I comment out the executable, this is the output I get and which I would correctly expect/want:

ffmpeg.exe \
-i White.Boy.Rick.2018.1080p.WEB-DL.H264.AC3-EVO_STEP1.mp4 \
-i White.Boy.Rick.2018.1080p.WEB-DL.H264.AC3-EVO_STEP2.mp4 \
-i White.Boy.Rick.2018.1080p.WEB-DL.H264.AC3-EVO_STEP3.mp4 \
-i White.Boy.Rick.2018.1080p.WEB-DL.H264.AC3-EVO_STEP4.mp4 \
-i White.Boy.Rick.2018.1080p.WEB-DL.H264.AC3-EVO_STEP5.mp4 \
-i White.Boy.Rick.2018.1080p.WEB-DL.H264.AC3-EVO_STEP6.mp4 \
-i White.Boy.Rick.2018.1080p.WEB-DL.H264.AC3-EVO_STEP7.mp4 \
-i White.Boy.Rick.2018.1080p.WEB-DL.H264.AC3-EVO_STEP8.mp4 \
-filter_complex "[0:v][v0],[1:v][v1],[2:v][v2],[3:v][v3],[4:v][v4],[5:v][v5],[6:v][v6],[7:v][v7];[0:a][a0],[1:a][a1],[2:a][a2],[3:a][a3],[4:a][a4],[5:a][a5],[6:a][a6],[7:a][a7];[v0][v1][v2][v3][v4][v5][v6][v7][video];[a0][a1][a2][a3][a4][a5][a6][a7][audio]" \
-map [video][audio] \
-c:v h264 \
-c:a copy \
-f matroska \
"E:\MediaConversions\White.Boy.Rick.2018_MultiStreamTest_AAC.H264.mkv"

Errors

  • It doesn't work
    • Because, of course, it doesn't.
  • Running the code above will generate this absolutely general and unhelpful error:
Unrecognized option '-i White.Boy.Rick.2018.1080p.WEB-DL.H264.AC3-EVO_STEP1.mp4 -i White.Boy.Rick.2018.1080p.WEB-DL.H264.AC3-EVO_STEP2.mp4 -i White.Boy.Rick.2018.1080p.WEB-DL.H264.AC3-EVO_STEP3.mp4 -i White.Boy.Rick.2018.1080p.WEB-DL.H264.AC3-EVO_STEP4.mp4 -i White.Boy.Rick.2018.1080p.WEB-DL.H264.AC3-EVO_STEP5.mp4 -i White.Boy.Rick.2018.1080p.WEB-DL.H264.AC3-EVO_STEP6.mp4 -i White.Boy.Rick.2018.1080p.WEB-DL.H264.AC3-EVO_STEP7.mp4 -i White.Boy.Rick.2018.1080p.WEB-DL.H264.AC3-EVO_STEP8.mp4 -filter_complex [0:v][v0],[1:v][v1],[2:v][v2],[3:v][v3],[4:v][v4],[5:v][v5],[6:v][v6],[7:v][v7];[0:a][a0],[1:a][a1],[2:a][a2],[3:a][a3],[4:a][a4],[5:a][a5],[6:a][a6],[7:a][a7];[v0][v1][v2][v3][v4][v5][v6][v7][video];[a0][a1][a2][a3][a4][a5][a6][a7][audio] -map [video][audio] -c:v h264 -c:a copy'.
Error splitting the argument list: Option not found.

What option? What's wrong? Cool, thanks -- good talk FFMPEG.