encodingffmpegio-redirectionpowershell-coreredirectstandardoutput

In Powershell, how do you select different parts of a string and output them to variable separately and display them in line?


I am trying to create a progress bar for FFmpeg, much like python's FFpb and got far enough that I can capture FFmpeg's output to variable and save the needed info in variables, but only after it ends.

This command saves each line outputted by FFmpeg into the $RedirectOutput variable as objects:

$RedirectOutput = ffmpeg.exe -y -v quiet -stats -hide_banner -hwaccel cuda -i 'input' -c:v hevc_nvenc -preset slow -crf 18 -an -t 300 'output' 2>&1

$RedirectOutput then becomes:

frame=  124 fps=0.0 q=14.0 size=     768kB time=00:00:04.03 bitrate=1559.9kbits/s speed=8.04x
frame=  480 fps=479 q=14.0 size=    3840kB time=00:00:15.90 bitrate=1978.5kbits/s speed=15.9x
frame=  865 fps=576 q=17.0 size=    7424kB time=00:00:28.73 bitrate=2116.6kbits/s speed=19.1x
frame= 1256 fps=627 q=17.0 size=   10496kB time=00:00:41.76 bitrate=2058.7kbits/s speed=20.9x
frame= 1698 fps=678 q=16.0 size=   14080kB time=00:00:56.50 bitrate=2041.5kbits/s speed=22.6x
frame= 2074 fps=691 q=16.0 size=   17152kB time=00:01:09.03 bitrate=2035.4kbits/s speed=  23x
frame= 2515 fps=718 q=16.0 size=   20992kB time=00:01:23.73 bitrate=2053.7kbits/s speed=23.9x
frame= 2972 fps=742 q=18.0 size=   25088kB time=00:01:38.96 bitrate=2076.7kbits/s speed=24.7x
frame= 3404 fps=755 q=18.0 size=   28928kB time=00:01:53.36 bitrate=2090.4kbits/s speed=25.2x
...

Here I save each piece of necessary information into their respective variables:

$videoFps     = $RedirectOutput -replace "(frame=)(\s+)?(\d+)(.*)", "$3"
$videoFps     = $RedirectOutput -replace "(.*)(fps=)(\d+)(.*)", "$3"
$videoSpeed   = $RedirectOutput -replace "(.*)(speed=)(\d+.*)x", "$3"
$videoBitrate = $RedirectOutput -replace "(.*)(bitrate=)(\d+)(.*)", "$3"

Now that the arrays were created, I'd need to write these to the console periodically, in a formatted fashion (that I'll figure out eventually), as the FFmpeg conversion is running (instead of when it finished) and in a singular line, but I don't know how to proceed.

Do I save FFmpeg's output to a file instead of a variable and try to read from there? If so, how do I read it after each write? With a while loop?

Wouldn't that be a performance issue? I have to say, that isn't a great concern of mine, for now. Just want it to work.


Solution

  • … how do I read it after each write? With a ForEach-Object loop:

    ffmpeg.exe -y -v quiet -stats -hide_banner -hwaccel cuda -i 'input' -c:v hevc_nvenc -preset slow -crf 18 -an -t 300 'output' 2>&1 |
    ForEach-Object {
      $lineData = [psCustomObject]@{
        videoFrame   = $_ -replace "(frame=)(\s+)?(\d+)(.*)", '$3'
        videoFps     = $_ -replace "(.*)(fps=)(\d+)(.*)", '$3'
        videoSpeed   = $_ -replace "(.*)(speed=\s*?)(\d+.*)x", '$3'
        videoBitrate = $_ -replace "(.*)(bitrate=)(\d+)(.*)", '$3'
      }
      $lineData | Select-Object -Property video*
      Write-Progress -Activity "ffmpeg in progress: Bitrate=$($lineData.videoBitrate), Fps=$($lineData.videoFps)" -CurrentOperation "" -PercentComplete $lineData.videoSpeed -Status "Frame $($lineData.videoFrame)"
    }
    

    Output (derived from given $RedirectOutput data, progressbar example omitted:):

    D:\PShell\SO\61971642.ps1
    
    videoFrame videoFps videoSpeed videoBitrate
    ---------- -------- ---------- ------------
    124        0        8.04       1559        
    480        479      15.9       1978        
    865        576      19.1       2116        
    1256       627      20.9       2058        
    1698       678      22.6       2041        
    …