I know that this post is now 7+ years old, but it still shows up near the top of searches for "ffmpeg display progress bar powershell" and such, so I thought I'd contribute a working example.
Environment
Show me the code
$theFile = "path\to\file\video.mp4"
$frames = Invoke-Expression "ffprobe.exe -v error -select_streams v:0 -count_packets -show_entries stream=nb_read_packets -of csv=p=0 ""$($theFile)"""
$ffmpeg_params = "-i ""$($theFile)"" ""$($theFile)-processed.mkv"""
Write-Progress -Activity "ffmpeg" -Status "$theFile" -PercentComplete 0
$p = New-Object System.Diagnostics.Process
$p.StartInfo.Filename = "ffmpeg.exe"
$p.StartInfo.Arguments = "$ffmpeg_params"
$p.StartInfo.UseShellExecute = $false
$p.StartInfo.RedirectStandardError = $true
$p.StartInfo.RedirectStandardOutput = $true
$p.StartInfo.CreateNoWindow = $true
$p.Start() | Out-Null
while (-not $p.HasExited) {
if ($p.StandardError.Peek()) {
$line = $p.StandardError.ReadLineAsync().Result
if ($line) {
$info = $line.Trim();
if ($info.StartsWith("frame=")) {
$progperc = ([int]($info.split(" fps")[0]).split("=")[1] / $frames) * 100;
if ($progperc -gt $PrevProgress) {
Write-Progress "ffmpeg" -Status "$theFile" -PercentComplete $progperc
$PrevProgress = $progperc
}
}
}
}
}
$p.WaitForExit();
Write-Progress "ffmpeg" -Status "$theFile" -PercentComplete 100 -Completed
Let's tear this apart
An important note first
By default, ffmpeg sends its text output to stderr
and not the expected stdout
. This is because ffmpeg is capable of outputting streams on stdout
for other consumption. This is non-obvious since most shells redirect stderr
to stdout
silently, but when trying to capture output from a process this is important. Note that you could redirect stderr
to stdout
with 2>&1
, but the method presented here works just as well (e.g. capturing and reading stderr
). Note that ffprobe does not, which also makes sense when you consider what the utility does.
Here we go
I won't explain the first line, which brings us to:
$frames = Invoke-Expression "ffprobe.exe -v error -select_streams v:0 -count_packets -show_entries stream=nb_read_packets -of csv=p=0 ""$($theFile)"""
Since displaying a progress bar depends on knowing the final value to count to, we get the number of frames in the video. ffmpeg's output during processing meshes well with this (it's also possible to deal with duration in time since the output has that too and calculate that way, but it's significantly more annoying.)
$ffmpeg_params = "-i ""$($theFile)"" ""$($theFile)-processed.mkv"""
This is just setting up the parameters to pass to ffmpeg. Note that this particular set of parameters basically doesn't do anything except cross-encode from whatever your .mp4 is to a .mkv format with defaults, which is mostly useless except as a demo. Replace this with whatever parameters you need to pass to ffmpeg.
Write-Progress -Activity "ffmpeg" -Status "$theFile" -PercentComplete 0
This throws up the actual progress bar. Play with the -Activity
and -Status
parameters however you see fit.
$p = New-Object System.Diagnostics.Process
$p.StartInfo.Filename = "ffmpeg.exe"
$p.StartInfo.Arguments = "$ffmpeg_params"
$p.StartInfo.UseShellExecute = $false
$p.StartInfo.RedirectStandardError = $true
$p.StartInfo.RedirectStandardOutput = $true
$p.StartInfo.CreateNoWindow = $true
$p.Start() | Out-Null
This block is fairly boilerplate; it creates a child process for ffmpeg, passes its arguments, and (here's the important part) sets the parent process (PowerShell) up to read both stderr
and stdout
from the child process it will create (ffmpeg). It then starts that child process with the passed in arguments and command line.
Important
A common problem with the above block is that your command, in this case ffmpeg.exe
, isn't in your path. You should do more work to ensure that ffmpeg.exe
is present and probably pass it a fully-qualified path.
while (-not $p.HasExited) {
if ($p.StandardError.Peek()) {
$line = $p.StandardError.ReadLineAsync().Result
if ($line) {
$info = $line.Trim();
if ($info.StartsWith("frame=")) {
$progperc = ([int]($info.split(" fps")[0]).split("=")[1] / $frames) * 100;
if ($progperc -gt $PrevProgress) {
Write-Progress "ffmpeg" -Status "$theFile" -PercentComplete $progperc
$PrevProgress = $progperc
}
}
}
}
}
This while
loop is checking if the process, running ffmpeg, has exited. If it hasn't, then it "Peeks" (that is, returns the next available character in the stream but doesn't consume it) at the StandardError stream to see if there's something there. If there is, it reads the line in in an asynchronous fashion (non-blocking to the process), trims it, and then the real magic starts.
For completeness, it's important to know that ffmpeg's process output (once it's done giving you a ton of information on what you told it to do) looks a lot like this:
frame= 1 fps=0.0 q=0.0 size= 5kB time=00:00:00.00 bitrate=N/A speed= 0x
frame= 1 fps=0.0 q=0.0 size= 5kB time=00:00:00.00 bitrate=N/A speed= 0x
frame= 2 fps=0.0 q=0.0 size= 5kB time=00:00:00.00 bitrate=N/A speed= 0x
frame= 2 fps=0.0 q=0.0 size= 5kB time=00:00:00.00 bitrate=N/A speed= 0x
frame= 2 fps=1.9 q=0.0 size= 5kB time=00:00:00.00 bitrate=N/A speed= 0x
frame= 2 fps=1.9 q=0.0 size= 5kB time=00:00:00.00 bitrate=N/A speed= 0x
frame= 16 fps= 10 q=0.0 size= 5kB time=00:00:00.59 bitrate= 63.5kbits/s speed=0.379x
frame= 16 fps= 10 q=0.0 size= 5kB time=00:00:00.59 bitrate= 63.5kbits/s speed=0.379x
frame= 31 fps= 15 q=0.0 size= 5kB time=00:00:01.09 bitrate= 34.6kbits/s speed=0.528x
frame= 31 fps= 15 q=0.0 size= 5kB time=00:00:01.09 bitrate= 34.6kbits/s speed=0.528x
frame= 46 fps= 18 q=0.0 size= 5kB time=00:00:01.59 bitrate= 23.8kbits/s speed=0.616x
frame= 46 fps= 18 q=0.0 size= 5kB time=00:00:01.59 bitrate= 23.8kbits/s speed=0.616x
frame= 61 fps= 20 q=0.0 size= 5kB time=00:00:02.09 bitrate= 18.1kbits/s speed=0.678x
frame= 61 fps= 20 q=0.0 size= 5kB time=00:00:02.09 bitrate= 18.1kbits/s speed=0.678x
frame= 76 fps= 21 q=29.0 size= 5kB time=00:00:02.59 bitrate= 14.7kbits/s speed=0.718x
frame= 76 fps= 21 q=29.0 size= 5kB time=00:00:02.59 bitrate= 14.7kbits/s speed=0.718x
frame= 91 fps= 22 q=29.0 size= 5kB time=00:00:03.09 bitrate= 12.3kbits/s speed=0.75x
frame= 91 fps= 22 q=29.0 size= 5kB time=00:00:03.09 bitrate= 12.3kbits/s speed=0.75x
frame= 106 fps= 23 q=29.0 size= 5kB time=00:00:03.59 bitrate= 10.6kbits/s speed=0.775x
frame= 106 fps= 23 q=29.0 size= 5kB time=00:00:03.59 bitrate= 10.6kbits/s speed=0.775x
... which, in recent versions of ffmpeg at least, only shows up on a continuously changing single line about once per second. From the perspective of our child process though, it streams an entire line each time it updates, which is what we'll be using to calculate where our progress is on the progress bar. You can probably see the important information right at the start of the line: frame= 2
and so on.
Therefore, back to our code; so long as the line out output starts with frame=
, we grab that line, do some splitting on it (this is probably inefficient and is definitely prone to breaking should ffmpeg's output format change; I'm certain someone out there could do this much more elegantly than I have, but what I have works), and compare that number to the total number of frames we have to process. Multiply that number by 100 to get a percent, and we have our progress, so, as long as the current progress is greater than the last one, we update the progress bar.
$p.WaitForExit();
Again, this is boilerplate child process stuff and just waits for the process to exit before anything else happens in the script.
You should also do some validation here to see if it exited with a regular exit code or not (if ($p.ExitCode -ne 0)
) and so something valuable with that.
The last line just completes the progress bar and makes it go away from the UI.
That's it! Modify as needed.
Unimportant to the question--but relevant to the answer--information
ffmpeg and its siblings (e.g. ffprobe) require double-quoted ("
) filenames if the filename or path has spaces in it, which causes a lot of script developers heartburn. To deal with this in PowerShell, we double-double quote, so the double-double quotes you see in the commands are to protect those quotations in the actual commands which ensures that if your filename has spaces in it that it'll get processed properly.
Sources
https://stackoverflow.com/a/28376817/13902318 for the ffprobe command.
Most of the progress bar code has been adapted from https://github.com/JoJoBond/Powershell-YouTube-Upload.