Can ffmpeg show a progress bar?
Asked Answered
F

20

85

I am converting a .avi file to .flv file using ffmpeg. As it takes a long time to convert a file I would like to display a progress bar. Can someone please guide me on how to go about the same.

I know that ffmpeg somehow has to output the progress in a text file and I have to read it using ajax calls. But how do I get ffmpeg to output the progress to the text file?

Fluorspar answered 14/4, 2009 at 14:51 Comment(0)
F
37

I've been playing around with this for a few days. That "ffmpegprogress" thing helped, but it was very hard to get to work with my set up, and hard to read the code.

In order to show the progress of ffmpeg you need to do the following:

  1. run the ffmpeg command from php without it waiting for a response (for me, this was the hardest part)
  2. tell ffmpeg to send it's output to a file
  3. from the front end (AJAX, Flash, whatever) hit either that file directly or a php file that can pull out the progress from ffmpeg's output.

Here's how I solved each part:

1. I got the following idea from "ffmpegprogress". This is what he did: one PHP file calls another through an http socket. The 2nd one actually runs the "exec" and the first file just hangs up on it. For me his implementation was too complex. He was using "fsockopen". I like CURL. So here's what I did:

$url = "http://".$_SERVER["HTTP_HOST"]."/path/to/exec/exec.php";
curl_setopt($curlH, CURLOPT_URL, $url);
$postData = "&cmd=".urlencode($cmd);
$postData .= "&outFile=".urlencode("path/to/output.txt");
curl_setopt($curlH, CURLOPT_POST, TRUE);
curl_setopt($curlH, CURLOPT_POSTFIELDS, $postData);

curl_setopt($curlH, CURLOPT_RETURNTRANSFER, TRUE);

// # this is the key!
curl_setopt($curlH, CURLOPT_TIMEOUT, 1);
$result = curl_exec($curlH);

Setting CURLOPT_TIMEOUT to 1 means it will wait 1 second for a response. Preferably that would be lower. There is also the CURLOPT_TIMEOUT_MS which takes milliseconds, but it didn't work for me.

After 1 second, CURL hangs up, but the exec command still runs. Part 1 solved.

BTW - A few people were suggesting using the "nohup" command for this. But that didn't seem to work for me.

*ALSO! Having a php file on your server that can execute code directly on the command line is an obvious security risk. You should have a password, or encode the post data in some way.

2. The "exec.php" script above must also tell ffmpeg to output to a file. Here's code for that:

exec("ffmpeg -i path/to/input.mov path/to/output.flv 1> path/to/output.txt 2>&1");

Note the "1> path/to/output.txt 2>&1". I'm no command line expert, but from what I can tell this line says "send normal output to this file, AND send errors to the same place". Check out this url for more info: http://tldp.org/LDP/abs/html/io-redirection.html

3. From the front end call a php script giving it the location of the output.txt file. That php file will then pull out the progress from the text file. Here's how I did that:

// # get duration of source
preg_match("/Duration: (.*?), start:/", $content, $matches);

$rawDuration = $matches[1];

// # rawDuration is in 00:00:00.00 format. This converts it to seconds.
$ar = array_reverse(explode(":", $rawDuration));
$duration = floatval($ar[0]);
if (!empty($ar[1])) $duration += intval($ar[1]) * 60;
if (!empty($ar[2])) $duration += intval($ar[2]) * 60 * 60;


// # get the current time
preg_match_all("/time=(.*?) bitrate/", $content, $matches); 

$last = array_pop($matches);
// # this is needed if there is more than one match
if (is_array($last)) {
    $last = array_pop($last);
}

$curTime = floatval($last);


// # finally, progress is easy
$progress = $curTime/$duration;

Hope this helps someone.

Fatimafatimah answered 5/6, 2011 at 19:54 Comment(1)
I know this is an old thread but I'd like to add something I discovered today. It seems along with ffmpeg comes ffprobe which can be used to get info in std output ("ffmpeg -i {file}" actually exits with an error code so you'll have to pipe output using 2>1 to get the info). so, have at it: ffprobe -v quiet -print_format json -show_format -i file.mp4 -show_streamsBouldin
N
35

There is an article in Russian which describes how to solve your problem.

The point is to catch Duration value before encoding and to catch time=... values during encoding.

--skipped--
Duration: 00:00:24.9, start: 0.000000, bitrate: 331 kb/s
--skipped--
frame=   41 q=7.0 size=     116kB time=1.6 bitrate= 579.7kbits/s
frame=   78 q=12.0 size=     189kB time=3.1 bitrate= 497.2kbits/s
frame=  115 q=13.0 size=     254kB time=4.6 bitrate= 452.3kbits/s
--skipped--
Neurosis answered 10/6, 2010 at 13:35 Comment(2)
I went to the article expecting to struggle a little with the Russian but it was surprisingly easy to read.Misadvise
I think the command on the article is like this ffmpeg -y -i input.avi -vcodec xvid -acodec mp3 -ab 96 output.avi > output.txtSusceptive
G
35

It’s very simple if you use the pipeview command. To do this, transform

ffmpeg -i input.avi {arguments}

to

pv input.avi | ffmpeg -i pipe:0 -v warning {arguments}

No need to get into coding!

Gobbledegook answered 27/5, 2015 at 12:17 Comment(5)
I like this answer. It's very elegant. A relevant argument for pv is -n to only get the numeric progress in percent.Seaver
Beware that ffmpeg will fail to encode from stdin for video that are not streamable like a *.mov file where the meta data is at the end. The reason is that ffmpeg cannot seek to the meta data on the pipe. You'd have to qt-faststart every inputfile before you can pipe it into ffmpeg.Seaver
I thought of using pv first, then after realizing that my ffmpeg doesn't write any useful vstats because of a particular video filter being used I looked closer at pv and found that it does exactly what I want. I'm just using times in the format string and additional text pv -F "some additional info here %t %e" "${video}", that's all I needed.Kamilahkamillah
Hi, I have been trying to add the above code to mine, however, no success. Andy advice please for /R %%f IN (*.mkv) DO ffmpeg -v warning -hide_banner -stats -i "%%f" -map 0 -c copy "%%~nf.mp4"Bonaparte
Using -v quiet instead of -v warning so that there is no ffmpeg output and only the pv output is displayedBloodstone
H
23

You can do it with ffmpeg's -progress argument and nc

WATCHER_PORT=9998

DURATION= $(ffprobe -select_streams v:0 -show_entries "stream=duration" \
    -of compact $INPUT_FILE | sed 's!.*=\(.*\)!\1!g')

nc -l $WATCHER_PORT | while read; do
    sed -n 's/out_time=\(.*\)/\1 of $DURATION/p')
done &

ffmpeg -y -i $INPUT_FILE -progress localhost:$WATCHER_PORT $OUTPUT_ARGS
Heterosexuality answered 11/7, 2015 at 5:7 Comment(3)
This looks like it leads to the best solution. You can pipe the progress information via ffmpeg -v warning -progress /dev/stdout -i in.mp4 out.mp4 to stdout in order to get progress updates once per second. If you only need a percentage you'd ffprobe -v quiet -print_format json -show_format in.mp4 for the duration in seconds before starting the encoding and then | awk -F"=" '/out_time_ms/{print $2}' to only get the current duration in ms where progress = total_duration * 100 * 1000 / current_duration.Seaver
I didn't know you could route progress to a file; if that's the case you could just make a fifo out of sed and be on your way.Heterosexuality
It might also be a good idea to create a named pipe and call ffmpeg and sed | grep | awk | whatever separately to get ffmpeg's return code. Furthermore I send ffmpeg's stdout to a logfile so that in case of an error (code > 0) you have something to look at. My wip command currently looks something like ffmpeg -v warning -progress /dev/stdout -y -i $source $encodingOptions $target >>$logFile | grep --line-buffered out_time_ms.Seaver
D
20

FFmpeg uses stdout for outputing media data and stderr for logging/progress information. You just have to redirect stderr to a file or to stdin of a process able to handle it.

With a unix shell this is something like:

ffmpeg {ffmpeg arguments} 2> logFile

or

ffmpeg {ffmpeg arguments} 2| processFFmpegLog

Anyway, you have to run ffmpeg as a separate thread or process.

Diminution answered 14/4, 2009 at 14:57 Comment(3)
Were I to ask the question, I would gladly accept your answer.Hagio
@mouviciel, do you know how I can achieve this in .NET? I tried what you said and it doesn't print out a file, also How can I get notified for when the file is changed?Rosariarosario
Thank you for specifying the output with relation to stdout and stderrDismuke
O
17

Sadly, ffmpeg itself still cannot show a progress bar – also, many of the aforementioned bash- or python-based stop-gap solutions have become dated and nonfunctional.

Thus, i recommend giving the brand-new ffmpeg-progressbar-cli a try:

ffmpeg-progressbar-cli screencast

It's a wrapper for the ffmpeg executable, showing a colored, centered progress bar and the remaining time.

Also, it's open-source, based on Node.js and actively developed, handling most of the mentioned quirks (full disclosure: i'm its current lead developer).

Ovation answered 11/8, 2018 at 1:13 Comment(4)
2020 update, the longer considered active. last commit to master in Nov 2018, 9 open issues, 3 open pull requests.Traditionalism
Also now ffmpeg has its own progress barConsalve
@TanishqBanyal what is the correct flag to pass for ffmpeg's progress bar? I can't find the option in the documentation/internet?Metonym
Not a progress bar, but you can pipe -progress - -y before the output. e.g. ffmpeg -i ..... -progress - -y output.mp4Maladjusted
D
13

If you just need hide all info and show default progress like ffmpeg in last line, you can use -stats option:

ffmpeg -v warning -hide_banner -stats ${your_params}
Diencephalon answered 25/4, 2019 at 16:25 Comment(0)
T
6

I found ffpb Python package (pip install ffpb) that passes arguments transparently to FFmpeg. Due to it's robustness, it doesn't need much maintenance. The last release is from Apr 29, 2019.

https://github.com/althonos/ffpb

Traditionalism answered 24/3, 2020 at 21:19 Comment(1)
I found this to be the easiest and least complex solution to show a prograss bar for ffmpeg (confirmed to work absolutely great With MacOS Ventura!)Palanquin
H
3

javascript should tell php to start converting [1] and then do [2] ...


[1] php: start conversion and write status to file (see above):

exec("ffmpeg -i path/to/input.mov path/to/output.flv 1>path/to/output.txt 2>&1");

For the second part we need just javascript to read the file. The following example uses dojo.request for AJAX, but you could use jQuery or vanilla or whatever as well :

[2] js: grab the progress from the file:

var _progress = function(i){
    i++;
    // THIS MUST BE THE PATH OF THE .txt FILE SPECIFIED IN [1] : 
    var logfile = 'path/to/output.txt';

/* (example requires dojo) */

request.post(logfile).then( function(content){
// AJAX success
    var duration = 0, time = 0, progress = 0;
    var resArr = [];

    // get duration of source
    var matches = (content) ? content.match(/Duration: (.*?), start:/) : [];
    if( matches.length>0 ){
        var rawDuration = matches[1];
        // convert rawDuration from 00:00:00.00 to seconds.
        var ar = rawDuration.split(":").reverse();
        duration = parseFloat(ar[0]);
        if (ar[1]) duration += parseInt(ar[1]) * 60;
        if (ar[2]) duration += parseInt(ar[2]) * 60 * 60;

        // get the time 
        matches = content.match(/time=(.*?) bitrate/g);
        console.log( matches );

        if( matches.length>0 ){
            var rawTime = matches.pop();
            // needed if there is more than one match
            if (lang.isArray(rawTime)){ 
                rawTime = rawTime.pop().replace('time=','').replace(' bitrate',''); 
            } else {
                rawTime = rawTime.replace('time=','').replace(' bitrate','');
            }

            // convert rawTime from 00:00:00.00 to seconds.
            ar = rawTime.split(":").reverse();
            time = parseFloat(ar[0]);
            if (ar[1]) time += parseInt(ar[1]) * 60;
            if (ar[2]) time += parseInt(ar[2]) * 60 * 60;

            //calculate the progress
            progress = Math.round((time/duration) * 100);
        }

        resArr['status'] = 200;
        resArr['duration'] = duration;
        resArr['current']  = time;
        resArr['progress'] = progress;

        console.log(resArr);

        /* UPDATE YOUR PROGRESSBAR HERE with above values ... */

        if(progress==0 && i>20){
            // TODO err - giving up after 8 sec. no progress - handle progress errors here
            console.log('{"status":-400, "error":"there is no progress while we tried to encode the video" }'); 
            return;
        } else if(progress<100){ 
            setTimeout(function(){ _progress(i); }, 400);
        }
    } else if( content.indexOf('Permission denied') > -1) {
        // TODO - err - ffmpeg is not executable ...
        console.log('{"status":-400, "error":"ffmpeg : Permission denied, either for ffmpeg or upload location ..." }');    
    } 
},
function(err){
// AJAX error
    if(i<20){
        // retry
        setTimeout(function(){ _progress(0); }, 400);
    } else {
        console.log('{"status":-400, "error":"there is no progress while we tried to encode the video" }');
        console.log( err ); 
    }
    return; 
});

}
setTimeout(function(){ _progress(0); }, 800);
Harrumph answered 26/6, 2013 at 7:53 Comment(2)
You above code is giving below error. Please reply if possible. Parse error: syntax error, unexpected T_VARMaim
@Harrumph correct your code because it brings errorsCozmo
A
2

Had problems with the second php part. So I am using this instead:

$log = @file_get_contents($txt);
preg_match("/Duration:([^,]+)/", $log, $matches);
list($hours,$minutes,$seconds,$mili) = split(":",$matches[1]);
$seconds = (($hours * 3600) + ($minutes * 60) + $seconds);
$seconds = round($seconds);

$page = join("",file("$txt"));
$kw = explode("time=", $page);
$last = array_pop($kw);
$values = explode(' ', $last);
$curTime = round($values[0]);
$percent_extracted = round((($curTime * 100)/($seconds)));

Outputs perfectly.

Would like to see something for multiple uploads for another progress bar. This passing for the current file for one percentage. Then an overall progress bar. Almost there.

Also, if people are having a hard time getting:

exec("ffmpeg -i path/to/input.mov path/to/output.flv 1> path/to/output.txt 2>&1");

To work.

Try:

exec("ffmpeg -i path/to/input.mov path/to/output.flv 1>path/to/output.txt 2>&1");

"1> path" to "1>path" OR "2> path" to "2>path"

Took me awhile to figure it out. FFMPEG kept failing. Worked when I changed to no space.

Apartheid answered 24/8, 2011 at 1:30 Comment(0)
O
2

These answers that use multiple tools/consoles are complicating things too much.
pv is a good option but has the noted drawbacks of missing non-senquential data.
Just use the progress utility: Run ffmpeg as normal then in another console monitor with progress -m -c ffmpeg

Ousel answered 17/2, 2022 at 21:41 Comment(1)
It shows 100% no matter what the real progress is.Hackathorn
L
2

By taking the progress bar from this link, I create a simple script that shows the actual frame of the video being encoded and shows a progress bar at the bottom of it... at the same time that ffmpeg encodes the video, of course.

enter image description here

First, we have to get duration, width and height from the video, to create the bar. But, as color filter can't get this information from the file, we have to get them first with ffprobe. Then, we use them with ffmpeg and a filter_complex. There are 2 groups of arguments: one is after the file (called $xv1), the other at the end of the command (called $xv2).

Inside ffencoder_with_bar file:

#!/bin/bash

video_duration=`ffprobe -v quiet -show_entries format=duration -of default=noprint_wrappers=1:nokey=1 "$2"`
video_width=`ffprobe -v quiet -select_streams v -show_entries stream=width    -of csv=p=0:s=x "$2"`
video_height=`ffprobe -v quiet -select_streams v -show_entries stream=height   -of csv=p=0:s=x "$2"`
video_width="${video_width//x/}"
video_height="${video_height//x/}"
five_percent=`expr $video_height / 20`

xv1='-filter_complex "color=c=red:s='"$video_width"x"$five_percent"'[bar];[0][bar]overlay=-w+(w/'"$video_duration"')*t:H-h:shortest=1,format=yuv420p[o0]"'
xv2='-map [o0] -f xv display'

video_in=$2
shift 2  # shift arguments to make $@ to be the rest of the arguments
command="ffmpeg -i $video_in $xv1 $@ $xv2"
echo $command
eval $command

For example, you can use script as:

ffencode_with_bar -i video_in.mkv -s 1280x720 video_out.mp4

Important: you can pass arguments like you do with ffmpeg... but it has its limitations. The code ignores the first argument ("-i"), takes the second argument as the input video file ("video_in.mkv"), adds $xv1, adds the rest of the passed arguments and finally adds $xv2. If you want to use "-ss" before the input file... it won't work. Also if you want to input more than one file. In these cases, you would have to change the code.

Performance: the filter used is very simple... but everything added consumes additional CPU. Testing a 10MB video file in my computer, this is the difference:

  • Without script: 14.46 seconds
  • With script: 17.05 seconds (18% more)

Yes, almost 20% more. For short videos, it's nice. For larger files, probably it's not a good idea 🤷.

Lecher answered 25/4, 2022 at 18:55 Comment(0)
W
1

Calling php's system function blocks that thread, so you'll need to spawn off 1 HTTP request for performing the conversion, and another polling one for reading the txt file, that's being generated.

Or, better yet, clients submit the video for conversion and then another process is made responsible for performing the conversion. That way the client's connection won't timeout while waiting for the system call to terminate. Polling is done in the same way as above.

Wellestablished answered 14/4, 2009 at 14:56 Comment(1)
But why use a text file? there has got to be a better way. Wouldn't you have to make a new text file for every upload?Superimpose
M
1

This is my solution:

I use ffpb and python subprocess to tracking ffmpeg progress. Then I push status to a database (ex: Redis) for display a progress bar on website.

import subprocess

cmd = 'ffpb -i 400MB.mp4 400MB.avi'

process = subprocess.Popen(
    cmd,
    stdout=subprocess.PIPE,
    stderr=subprocess.STDOUT,
    shell=True,
    encoding='utf-8',
    errors='replace'
)

while True:
    realtime_output = process.stdout.readline()

    if realtime_output == '' and process.poll() is not None:
        break

    if realtime_output:
        print(realtime_output.strip(), flush=True)
        print('Push status to Redis...')
Mudra answered 3/6, 2020 at 10:21 Comment(0)
P
0

After some investigation of this library I see that implementation should follow these steps:

  1. Calc result duration. E.g. get initial duration (substract any trim seconds if required):
ffprobe -v error -show_entries format=duration -of csv=p=0 input.mkv

Result must be parsed as float and stored, e.g. in JavaScript:

const duration = parseFloat(result);
  1. In task - specify additional flag, so ffmpeg will output processed duration:
ffmpeg -i input.mkv -progress pipe:1 ... 

Or the same but pipe to stderr:

ffmpeg -i input.mkv -progress pipe:2 ... 
  1. Extract processed duration (from stdout or stderr), e.g. in JavaScript:
const matchedResult = chunkResult.match(/out_time_ms=(\d+)/);
if (Array.isArray(matchedResult)) {
  const currentTimeMs = Math.round(Number(matchedResult[1]) / 1000000);
}
  1. Compare processed duration with total duration
currentTimeMs/duration
Pikestaff answered 26/9, 2022 at 11:27 Comment(0)
A
0

Keeping it simple &… Here's a brief summary of the possibilities for using a progress bar.

No problem with pv (istefani's suggestion) for converting, e.g. YouTube videos. Not working for videos downloaded from Odysee though. I've got this error message:

[mov,mp4,m4a,3gp,3g2,mj2 @ 0x7fee39005400] stream 0, offset 0x30: partial file [mov,mp4,m4a,3gp,3g2,mj2 @ 0x7fee39005400] Could not find codec parameters for stream 0 (Video: h264 (avc1 / 0x31637661), none(tv, bt709), 1280x720, 604 kb/s): unspecified pixel format Consider increasing the value for the 'analyzeduration' (0) and 'probesize' (5000000) options

I ended up using ffpb, which still works great for me; no issues so far.

Using progress -m -c ffmpeg … is interesting, but one needs to open another console to run it after executing the normal ffmpeg command in the first console (not convenient if running from a shell script).

ffmpeg-progress-yield seems to be an excellent alternative to ffpb, but does not show (at least, for me) the name of the video being converted; it shows “test” instead of the actual filename.

Finally, ffmpeg-progressbar-cli is very similar to ffpb and ffmpeg-progress-yield, but apparently, is no longer maintained; I haven't tried it.

Arella answered 20/11, 2022 at 22:56 Comment(0)
W
0

Docker

To convert WMA/MP3 files to static movie (to upload them to youtube) I try to use docker jrottenberg/ffmpeg. At the begining it seems that none of above solutions works - but when I run docker compose using run instead up then Geograph solution works :)

version: '3.7'

services:
    musicToVideo:
        image: jrottenberg/ffmpeg
        volumes:
            - ./music:/data
        command: ' -loop 1 -framerate 2 -i "/data/image.jpeg" -i "/data/myAudio.WMA" -preset medium -tune stillimage -crf 18 -c:a aac -b:a 128k -shortest -pix_fmt yuv420p "/data/myVideo.mp4" -stats'
        # command: ' -loop 1 -framerate 2 -i "/data/image.jpeg" -i "/data/myAudio.mp3" -c:v libx264 -preset medium -tune stillimage -crf 18 -c:a copy -shortest -pix_fmt yuv420p "/data/myVideo.mp4" -stats'


I left above my working docker-dompose.yml file for future readers who use docker - just run it in by docker compose run musicToVideo (I assume that directory ./music is in the same directory that above .yml file and it containss WMA/MP3 file and image.jpeg) (for mp3 comment 'command' with WMA and uncomment with mp3)

Wed answered 1/4, 2023 at 19:0 Comment(0)
L
0

Solution for FFMPEG.WASM

This code can be placed after loading ffmpeg

Source

ffmpeg.setProgress(({ ratio }) => {
//ratio is a float number between 0 to 1.

const progress = Math.round(ratio * 100);
// Update your progress indicator here
console.log(`Progress: ${progress}%`);
});
Longmire answered 5/7, 2023 at 13:28 Comment(0)
M
0

Coming at this late, but this is what I'm going to use, in the stderr handler:

import bytes from 'bytes'

function parseFfmpegError(text: string) {
  const lines = text.split(/\n/)
  if (lines[0]?.match(/File '(?:.+)' already exists\. Exiting/)) {
    return { type: 'error', text: `File already exists.` }
  } else if (
    lines[0]?.match(
      /^frame=\s*(.+)\s+fps=\s*(.+)\s+q=\s*(.+)\s+size=\s*(.+)\s+time=\s*(.+)\s+bitrate=\s*(.+)\s+speed=(.+)/,
    )
  ) {
    const { $1, $2, $3, $4, $5, $6, $7 } = RegExp
    const frame = parseInt($1, 10)
    const fps = parseFloat($2)
    const q = parseFloat($3)
    const size = bytes($4)
    const [t, ms] = $5.split('.')
    const [h, m, s] = t.split(':')
    const totalSeconds = parseInt(s, 10) * 1000
    const totalMinutes = parseInt(m, 10) * 60 * 1000
    const totalHours = parseInt(h, 10) * 60 * 60 * 1000
    const time = totalHours + totalMinutes + totalSeconds
    const bitrate = bytes($6.split('bits/s').shift() + 'b')
    const speed = parseFloat($7)
    return {
      type: 'progress',
      data: {
        frame,
        fps,
        q,
        size,
        time,
        bitrate,
        speed,
      },
    }
  }

  return text
}

Here is one frame, comes about once every second or so.

{
  type: 'progress',
  data: {
    frame: 14194,
    fps: 1491,
    q: 24.8,
    size: 103546880,
    time: 592000,
    bitrate: 1431552,
    speed: 62.3
  }
}
Mosley answered 13/1 at 1:1 Comment(0)
T
0

try ffmpeg-progress-yield
https://pypi.org/project/ffmpeg-progress-yield/
https://github.com/slhck/ffmpeg-progress-yield

install via pipx
pipx install ffmpeg-progress-yield
ffmpeg-progress-yield still works without tqdm and colorama
pipx inject ffmpeg-progress-yield tqdm colorama

After that, do whatever you want, starting with ffmpeg-progress-yield like this.
ffmpeg-progress-yield ffmpeg -i input.mp4 output.mp4
https://github.com/slhck/ffmpeg-progress-yield?tab=readme-ov-file#on-the-command-line

Typewritten answered 7/2 at 10:28 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.