ffmpeg Progress Bar - Encoding Percentage in PHP

32,440

Solution 1

Okay, I've found what I needed - and hopefully this helps someone else as well!

First and foremost, you want to output the ffmpeg data to a text file on the server.

ffmpeg -i path/to/input.mov -vcodec videocodec -acodec audiocodec path/to/output.flv 1> block.txt 2>&1

So, the ffmpeg output is block.txt. Now in PHP, let's do this!

$content = @file_get_contents('../block.txt');

if($content){
    //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 time in the file that is already encoded
    preg_match_all("/time=(.*?) bitrate/", $content, $matches);

    $rawTime = array_pop($matches);

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

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

    //calculate the progress
    $progress = round(($time/$duration) * 100);

    echo "Duration: " . $duration . "<br>";
    echo "Current Time: " . $time . "<br>";
    echo "Progress: " . $progress . "%";

}

This outputs the percentage of time left.

You can have this as the only piece of text echoed out to a page, and from another page you can perform an AJAX request using jQuery to grab this piece of text and output it into a div, for example, to update on your page every 10 seconds. :)

Solution 2

ffmpeg now has a progress option, which gives output more easily parsed.

ffmpeg -progress block.txt -i path/to/input.mov -vcodec videocodec -acodec audiocodec path/to/output.flv 2>&1

Before you start encoding you can get the total frames, and a lot of other info with this (this is what would be done with bash. I'm a Perl programmer so I don't know how you'd get the info into your PHP script).

eval $(ffprobe -of flat=s=_ -show_entries stream=height,width,nb_frames,duration,codec_name path/to/input.mov);
width=${streams_stream_0_width};
height=${streams_stream_0_height};
frames=${streams_stream_0_nb_frames};
videoduration=${streams_stream_0_duration};
audioduration=${streams_stream_1_duration};
codec=${streams_stream_0_codec_name};
echo $width,$height,$frames,$videoduration,$audioduration,$codec;

-of flate=s=_ says to put each name=value on a separate line. -show_entries tells it to show the entries from what follows (stream for -show_streams, format for -show_format, etc.) stream=... says to show those items from the -show_streams output. Try the following to see what is available:

ffprobe -show_streams path/to/input.mov

The output to the progress file is added to approximately once a second. Content, after the encoding is finished, looks like the following. In my script, once a second I am putting the file into an array, and traversing the array in reverse order, using only what is between the first [last before reversal] two "progress" lines I find, so that I am using the most recent info from the end of the file. There may be better ways. This is from an mp4 with no audio so there is only one stream.

frame=86
fps=0.0
stream_0_0_q=23.0
total_size=103173
out_time_ms=1120000
out_time=00:00:01.120000
dup_frames=0
drop_frames=0
progress=continue
frame=142
fps=140.9
stream_0_0_q=23.0
total_size=415861
out_time_ms=3360000
out_time=00:00:03.360000
dup_frames=0
drop_frames=0
progress=continue
frame=185
fps=121.1
stream_0_0_q=23.0
total_size=1268982
out_time_ms=5080000
out_time=00:00:05.080000
dup_frames=0
drop_frames=0
progress=continue
frame=225
fps=110.9
stream_0_0_q=23.0
total_size=2366000
out_time_ms=6680000
out_time=00:00:06.680000
dup_frames=0
drop_frames=0
progress=continue
frame=262
fps=103.4
stream_0_0_q=23.0
total_size=3810570
out_time_ms=8160000
out_time=00:00:08.160000
dup_frames=0
drop_frames=0
progress=continue
frame=299
fps=84.9
stream_0_0_q=-1.0
total_size=6710373
out_time_ms=11880000
out_time=00:00:11.880000
dup_frames=0
drop_frames=0
progress=end

Solution 3

if javascript updates your progress bar, javascript could perform step 2 "directly" :

[this example requires dojo ]


1 php: start conversion and write status to a textfile - example syntax:

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 result = {};

    // 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);
        }

        result.status = 200;
        result.duration = duration;
        result.current  = time;
        result.progress = progress;

        console.log(result);

        /* 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);
Share:
32,440

Related videos on Youtube

Jimbo
Author by

Jimbo

Currently not open to new roles. Happy to chat about interesting leadership topics, though! LinkedIn: https://www.linkedin.com/in/james-mallison-a647694b/ Twitter: http://www.twitter.com/j7mbo Blog: https://blog.j7mbo.com Github: http://github.com/j7mbo

Updated on March 25, 2020

Comments

  • Jimbo
    Jimbo over 4 years

    I've written a whole system in PHP and bash on the server to convert and stream videos in HTML5 on my VPS. The conversion is done by ffmpeg in the background and the contents is output to block.txt.

    Having looked at the following posts:

    Can ffmpeg show a progress bar?

    and

    ffmpeg video encoding progress bar

    amongst others, I can't find a working example.

    I need to grab the currently encoded progress as a percentage.

    The first post I linked above gives:

    $log = @file_get_contents('block.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)));
    
    echo $percent_extracted;
    

    The $percent_extracted variable echoes zero, and as maths is not my strong point, I really don't know how to progress here.

    Here's one line from the ffmpeg output from block.txt (if it's helpful)

    time=00:19:25.16 bitrate= 823.0kbits/s frame=27963 fps= 7 q=0.0 size= 117085kB time=00:19:25.33 bitrate= 823.1kbits/s frame=27967 fps= 7 q=0.0 size= 117085kB time=00:19:25.49 bitrate= 823.0kbits/s frame=27971 fps= 7 q=0.0 size= 117126kB

    Please help me output this percentage, once done I can create my own progress bar. Thanks.

  • Benz Bumroungruksa
    Benz Bumroungruksa almost 12 years
    Thanks! I'm developing a site which streams videos in HTML5 too. Your solution saved my day.
  • lewiguez
    lewiguez over 11 years
    As a note, you should probably specify stdin to /dev/null or something so the process doesnt anticipate user input. You can do this with </dev/null This is good stuff though! Helped me figure out a solution to my problem!
  • vertigoelectric
    vertigoelectric about 11 years
    Where would you put </dev/null ?
  • SamIAm
    SamIAm about 11 years
    What is the purpose of the 2>&1?
  • Jimbo
    Jimbo about 11 years
    @SamIAm From a two second google search: Here's what 2>&1 means
  • Jimbo
    Jimbo about 11 years
    Cool, so mine is a PHP solution and yours is a JavaScript only solution then?
  • sebilasse
    sebilasse about 11 years
    well - both would work but the js saves php some work... If you run a long video encoding process in php you would need a second process to read the logfiles (for each encoding). But if you read the logfile directly with javascript, you don't have to care about php processes.
  • Jimbo
    Jimbo about 11 years
    Well I like it, I think you should state clearly at the top that it requires Dojo, but here's a +1! :)
  • JoeR
    JoeR about 10 years
    How are you getting ffmpeg to output block.txt in the style you show it? Have you done any other processing to make it look like that? Have you changed this at all for recent ffmpeg versions?
  • Jimbo
    Jimbo about 10 years
    @JoeR No, I haven't altered the output at all nor have I changed this for recent ffmpeg versions. Are you getting a different output?
  • Jimbo
    Jimbo over 9 years
    Thanks for this addition! eval() is inherently insecure in PHP, but if you could format your code (see format faqs) and add what version of ffmpeg is required, I'd happily up-vote you.
  • Mick Jack
    Mick Jack over 8 years
    thank you. this is exactly what i am looking for. however how do i convert the sec to mins?
  • M a m a D
    M a m a D almost 8 years
    What is the $matches value?
  • Jimbo
    Jimbo almost 8 years
    @Drupalist If you look at the documentation for preg_match, you'll see it has a third optional (see the [ square brackets?) array parameter, $matches. If you pass it an array, because it's passed by reference (&), this array will get overwritten with the result. If you put in a variable that doesn't exist yet, it'll get created with that value. Another idiosyncrasy with PHP, why can't the return value be what we want? Oh well...
  • M a m a D
    M a m a D almost 8 years
    @Jimbo thank you very much. I am building an online video editor (cut piece of video), and I was in need of a progress bar. Your code worked well.
  • A Sahra
    A Sahra over 7 years
    @Jimbo Great Job .Here i am stacked with creating flock or pid for ffmpeg process is it possible to get pid for processing each file in a folder by running bash script to convert the video files
  • Jorge Alonso
    Jorge Alonso over 4 years
    @sebilasse You still need to explain some things more, for novice people on this topic it will not be easy for them to understand the example you are setting, for example how to add the dojo library, in this line I get an error: request.post (logfile) .then (function (content) {.... Tell me that request is not defined, how do I solve that?