FFmpeg进度条 - 在PHP中编码百分比

27

我在服务器上使用PHP和Bash编写了一个整个系统,用于将视频转换成HTML5并进行流式传输。转换是由后台的FFmpeg完成的,内容输出到 block.txt

在查看了以下帖子之后:

Can ffmpeg show a progress bar?

以及

ffmpeg video encoding progress bar

还有其他一些帖子后,我找不到一个可行的例子。

我需要获取当前编码进度的百分比。

我上面引用的第一个帖子给出了:

$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;
$percent_extracted 变量输出为零,由于数学不是我的强项,我真的不知道如何继续下去。以下是来自 block.txt 的 ffmpeg 输出中的一行(如果有帮助的话)。 请帮我输出这个百分比,一旦完成,我就可以创建自己的进度条。谢谢。
3个回答

50

好的,我已经找到了我需要的东西-希望这也能帮助别人!

首先,您想将ffmpeg数据输出到服务器上的文本文件中。

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

因此,ffmpeg的输出为block.txt。现在在PHP中,让我们来做这件事!

$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 . "%";

}

这会输出剩余时间的百分比。

您可以将此作为页面上唯一的文本输出,然后从另一个页面使用jQuery执行AJAX请求,以获取此文本并将其输出到一个div中,例如每10秒更新您的页面。 :)


1
@SamIAm 从两秒钟的谷歌搜索中得知:这就是2>&1的含义 - Jimbo
你是如何让FFmpeg以你所展示的方式输出block.txt的? 你是否进行了其他处理使其看起来像那样? 你最近是否更改了此设置以适应新版FFmpeg? - JoeR
谢谢。这正是我在寻找的。不过如何将秒转换为分钟? - Mick Jack
1
@Drupalist 如果你查看preg_match的文档,你会发现它有一个第三个可选参数(看到[方括号了吗?)数组 $matches。如果你传递一个数组,因为它是通过引用(&)传递的,这个数组将被覆盖为结果。如果你放入一个尚不存在的变量,它将被创建并赋予该值。PHP的另一个怪癖,为什么返回值不能是我们想要的呢?哦,算了... - Jimbo
1
@Jimbo 非常感谢你。我正在构建一个在线视频编辑器(剪切视频片段),我需要一个进度条。你的代码很有效。 - M a m a D
显示剩余9条评论

8

现在ffmpeg有一个进度选项,可以更轻松地输出解析。

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

在开始编码之前,您可以使用以下命令获取总帧数和许多其他信息(这是在bash中执行的操作。我是一名Perl程序员,因此不知道如何将信息传递到PHP脚本中)。

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=_指示将每个name=value放在单独的行上。-show_entries告诉它从后面的内容(stream用于-show_streams,format用于-show_format等)中显示条目。stream = ...表示从-show_streams输出中显示这些项目。尝试以下内容以查看可用内容:

ffprobe -show_streams path/to/input.mov

进度文件的输出大约每秒添加一次。编码完成后的内容如下所示。在我的脚本中,我每秒将文件放入一个数组中,并以相反的顺序遍历数组,仅使用我找到的第一个“progress”行之间的内容(在反转之前的最后一个),以便使用来自文件末尾的最新信息。可能有更好的方法。这是来自没有音频的mp4的输出,因此只有一个流。

帧数=86
帧率=0.0
流_0_0_q=23.0
总大小=103173
输出时间毫秒=1120000
输出时间=00:00:01.120000
重复帧=0
丢帧=0
进度=继续
帧数=142
帧率=140.9
流_0_0_q=23.0
总大小=415861
输出时间毫秒=3360000
输出时间=00:00:03.360000
重复帧=0
丢帧=0
进度=继续
帧数=185
帧率=121.1
流_0_0_q=23.0
总大小=1268982
输出时间毫秒=5080000
输出时间=00:00:05.080000
重复帧=0
丢帧=0
进度=继续
帧数=225
帧率=110.9
流_0_0_q=23.0
总大小=2366000
输出时间毫秒=6680000
输出时间=00:00:06.680000
重复帧=0
丢帧=0
进度=继续
帧数=262
帧率=103.4
流_0_0_q=23.0
总大小=3810570
输出时间毫秒=8160000
输出时间=00:00:08.160000
重复帧=0
丢帧=0
进度=继续
帧数=299
帧率=84.9
流_0_0_q=-1.0
总大小=6710373
输出时间毫秒=11880000
输出时间=00:00:11.880000
重复帧=0
丢帧=0
进度=结束


感谢您的补充!在PHP中,eval()本质上是不安全的,但如果您能格式化您的代码(请参阅格式化FAQ)并添加所需的ffmpeg版本,我将很高兴为您点赞。 - Jimbo

1
如果JavaScript更新进度条,JavaScript可以直接执行步骤2: [此示例需要 dojo]

1 php: 开始转换并将状态写入文本文件 - 示例语法:

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

对于第二部分,我们只需要使用JavaScript来读取文件。 以下示例使用dojo.request进行AJAX,但您也可以使用jQuery或vanilla或其他库:

[2] js:从文件中获取进度:

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

很酷,那么我的解决方案是 PHP,而你的只是 JavaScript 解决方案? - Jimbo
两种方法都可以,但是使用 JavaScript 可以减轻 PHP 的工作量。如果你在 PHP 中运行一个长时间的视频编码过程,你需要一个第二个进程来读取日志文件(每个编码都需要)。但是如果你直接用 JavaScript 读取日志文件,就不必担心 PHP 进程了。 - sebilasse
我很喜欢它,我认为你应该在顶部清楚地说明它需要Dojo,但这里是一个+1! :) - Jimbo
@sebilasse,您仍需更详细地解释一些事情。对于初学者而言,他们可能无法理解您所设置的示例。例如如何添加Dojo库,在此行中我遇到了错误:request.post(logfile).then(function(content){....告诉我request未定义,我该如何解决? - Jorge Alonso

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接