ffmpeg Progress Bar - Encoding Percentage in PHP

后端 未结 3 1892
忘掉有多难
忘掉有多难 2020-11-27 03:30

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 conten

相关标签:
3条回答
  • 2020-11-27 03:40

    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

    0 讨论(0)
  • 2020-11-27 03:44

    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. :)

    0 讨论(0)
  • 2020-11-27 04:04

    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);
    
    0 讨论(0)
提交回复
热议问题