ffmpeg可以显示进度条吗?

83

我正在使用ffmpeg将.avi文件转换为.flv文件。由于转换文件需要很长时间,因此我想显示进度条。请问有人能指导我如何做到这一点吗?

我知道ffmpeg必须以某种方式将进度输出到文本文件,并且我必须使用ajax调用来读取它。但是我该如何让ffmpeg将进度输出到文本文件中呢?

18个回答

36

我已经玩了几天。那个“ffmpegprogress”很有帮助,但是在我的设置中很难工作,并且代码难以阅读。

为了显示ffmpeg的进度,您需要执行以下操作:

  1. 从PHP运行ffmpeg命令,而不等待响应(对我来说,这是最困难的部分)
  2. 告诉ffmpeg将其输出发送到文件
  3. 从前端(AJAX、Flash或其他方式)直接击中该文件或能够从ffmpeg的输出中提取进度的php文件。

这是我解决每个部分的方法:

1. 我从"ffmpegprogress"得到了以下想法。他所做的是:一个PHP文件通过http套接字调用另一个PHP文件,第二个文件实际上运行“exec”,而第一个文件只是在它上面挂起。对我来说,他的实现过于复杂。他使用的是"fsockopen"。我喜欢CURL。所以我做了以下事情:

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

将CURLOPT_TIMEOUT设置为1表示它将等待1秒钟以获取响应,最好将其设置更低。还有CURLOPT_TIMEOUT_MS选项,它以毫秒为单位计算时间,但对我无效。

1秒后,CURL会挂起,但exec命令仍在运行。第一部分已解决。

顺便说一句 - 有些人建议使用“nohup”命令来解决此问题,但对我来说似乎不起作用。

*另外!在服务器上拥有一个可以直接在命令行上执行代码的php文件显然存在安全风险。您应该设置密码或以某种方式编码post数据。

2. 上述"exec.php"脚本还必须告诉ffmpeg输出到文件。以下是相应的代码:

exec("ffmpeg -i path/to/input.mov path/to/output.flv 1> path/to/output.txt 2>&1");
注意"1> path/to/output.txt 2>&1"。我不是命令行专家,但据我所知,该行表示“将正常输出发送到此文件,并将错误发送到相同位置”。查看此网址以获取更多信息:http://tldp.org/LDP/abs/html/io-redirection.html 从前端调用一个给定输出.txt文件位置的php脚本。那个php文件会从文本文件中提取进度。下面是我这样做的方法:
// # 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;
希望这能帮助到某些人。

1
我知道这是一个旧的帖子,但我想添加一些今天发现的东西。似乎随着ffmpeg一起提供了ffprobe,可以用它来获取标准输出中的信息("ffmpeg -i {file}"实际上会退出错误代码,因此您必须使用2>1来管道输出以获取信息)。所以,尽情使用吧: ffprobe -v quiet -print_format json -show_format -i file.mp4 -show_streams - thorne51

35

有一篇俄文的文章详细介绍了如何解决你的问题。

关键是在编码之前捕获Duration值,并在编码过程中捕获time=...值。

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

19
我打开了这篇文章,本以为会因为俄语而感到有些吃力,但出乎意料的阅读起来非常容易。 - Camilo Martin
我认为文章中的命令应该是这样的:ffmpeg -y -i input.avi -vcodec xvid -acodec mp3 -ab 96 output.avi > output.txt - Salem

34

使用pipeview命令非常简单。为此,请转换

ffmpeg -i input.avi {arguments}

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

不需要涉及编码!


我喜欢这个答案,非常优雅。pv的一个相关参数是-n,只获取百分比的数字进度。 - Basti
15
注意,ffmpeg无法对不可流式传输的视频进行stdin编码,例如元数据位于结尾的*.mov文件。原因是ffmpeg无法在管道上执行元数据的寻址操作。在将其传送至ffmpeg之前,您必须先使用qt-faststart处理每个输入文件。 - Basti
我最初想使用pv,但后来发现我的ffmpeg由于使用了特定的视频过滤器而没有写入任何有用的vstats,因此我更仔细地研究了pv,并发现它正好符合我的要求。我只是在格式字符串中使用时间和附加文本 pv -F "这里有一些额外的信息 %t %e" "${video}",这就是我所需要的。 - Benjamin
你好,我一直在尝试将上述代码添加到我的程序中,但是一直没有成功。请问有什么建议吗?for /R %%f IN (*.mkv) DO ffmpeg -v warning -hide_banner -stats -i "%%f" -map 0 -c copy "%%~nf.mp4" - Arthor
使用-v quiet代替-v warning,这样就不会有ffmpeg输出,只显示pv输出。 - omides248

23
你可以使用 ffmpeg-progress 参数和 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

4
看起来这会导致最佳解决方案。您可以通过使用ffmpeg -v warning -progress /dev/stdout -i in.mp4 out.mp4将进度信息传输到 stdout,以便每秒获得一次进度更新。如果您只需要百分比,您可以在开始编码之前使用ffprobe -v quiet -print_format json -show_format in.mp4获取以秒为单位的 duration,然后使用 | awk -F"=" '/out_time_ms/{print $2}' 仅获取当前持续时间(以毫秒为单位),其中 progress = total_duration * 100 * 1000 / current_duration - Basti
2
我不知道你可以将进度路由到文件中;如果是这样,你可以只需使用 sed 制作一个 fifo 并继续进行。 - iluvcapra
创建一个命名管道并单独调用ffmpegsed | grep | awk | whatever可能也是个好主意,以获取ffmpeg的返回代码。此外,我将ffmpeg的标准输出发送到日志文件中,以便在出现错误(代码> 0)时有东西可以查看。我的wip命令目前看起来像ffmpeg -v warning -progress /dev/stdout -y -i $source $encodingOptions $target >>$logFile | grep --line-buffered out_time_ms - Basti

20

FFmpeg使用标准输出输出媒体数据,使用标准错误输出日志/进度信息。您只需将标准错误重定向到文件或能够处理它的进程的标准输入即可。

在Unix shell中,可以这样做:

ffmpeg {ffmpeg arguments} 2> logFile
或者
ffmpeg {ffmpeg arguments} 2| processFFmpegLog

无论如何,你都必须将ffmpeg作为单独的线程或进程运行。


3
如果我问这个问题,我会很愿意接受你的答案。 - Riduidel
@mouviciel,你知道我怎样在.NET中实现这个吗?我尝试了你说的方法,但它没有打印出文件。另外,当文件被更改时,我该如何得到通知? - Shimmy Weitzhandler
感谢您指定与stdout和stderr相关的输出。 - Darrel Holt

17

不幸的是,ffmpeg 本身仍然无法显示进度条 - 此外,许多上述基于bash或python的临时解决方案已经过时且无法使用。

因此,我推荐尝试全新的ffmpeg-progressbar-cli

ffmpeg-progressbar-cli screencast

它是 ffmpeg 可执行文件的包装器,显示一个彩色、居中的进度条和剩余时间。

此外,它是开源的,基于Node.js并在积极开发中,处理大多数提到的小问题(完全披露:我是其当前的主要开发人员)。


5
2020更新,长期被认为是活跃状态。最后一次提交于2018年11月,有9个未解决的问题和3个未解决的拉取请求。 - glen
1
现在ffmpeg也有自己的进度条。 - 0xB00B
3
ffmpeg 的进度条需要传递哪个正确的标志?我在文档/互联网上找不到选项? - Thomas Nyberg
不是进度条,但你可以在输出前使用管道-progress - -y。例如:ffmpeg -i ..... -progress - -y output.mp4 - Benji

12

如果您只需要隐藏所有信息并像ffmpeg的最后一行一样显示默认进度,则可以使用 -stats 选项:

ffmpeg -v warning -hide_banner -stats ${your_params}

6
我发现了一个名为ffpb的Python软件包(pip install ffpb),它可以透明地传递参数到FFmpeg。由于其稳定性很高,因此不需要太多的维护。最新版本发布于2019年4月29日。
以下是相关演示图: 了解更多请参阅链接:https://github.com/althonos/ffpb

我发现这是显示FFmpeg进度条的最简单和最不复杂的解决方案(经过确认,在MacOS Ventura上工作非常出色!) - Peterv88

3

JavaScript 应该告诉 PHP 开始转换 [1],然后执行 [2] ...


[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 或原生 JavaScript :

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

你上面的代码出现了以下错误。如果可能的话,请回复。 解析错误:语法错误,意外的T_VAR - KRA
@sebilasse,请纠正你的代码,因为它会引起错误。 - Jorge Alonso

2

第二个 PHP 部分出了问题,所以我改用这个:

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

输出完美。

希望看到用于多个上传的其他进度条。当前文件的传递为一个百分比。然后是整体进度条。快要完成了。

此外,如果有人难以理解:

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

开始工作。

尝试:

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

将"1> path"翻译为"1>path"或"2> path"翻译为"2>path"。

我花了一段时间才想明白。FFMPEG一直失败,但当我把路径中的空格去掉后就可以运行了。


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