ffmpeg可以显示进度条吗?

Posted

技术标签:

【中文标题】ffmpeg可以显示进度条吗?【英文标题】:Can ffmpeg show a progress bar? 【发布时间】:2010-10-19 09:05:16 【问题描述】:

我正在使用 ffmpeg 将 .avi 文件转换为 .flv 文件。由于转换文件需要很长时间,我想显示一个进度条。有人可以指导我如何去做。

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

【问题讨论】:

【参考方案1】:

我已经玩了几天了。那个“ffmpegprogress”的东西有帮助,但是很难使用我的设置,而且很难阅读代码。

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

    php 运行 ffmpeg 命令而不等待响应(对我来说,这是最难的部分) 告诉 ffmpeg 将其输出发送到文件 从前端(AJAX、Flash 等)直接点击该文件或可以从 ffmpeg 输出中提取进度的 php 文件。

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

1。 我从“ffmpegprogress”中得到了以下想法。这就是他所做的:一个 PHP 文件通过 http 套接字调用另一个文件。第二个实际上运行“exec”,第一个文件就挂断了。对我来说,他的实现太复杂了。他正在使用“fsockopen”。我喜欢卷曲。所以这就是我所做的:

$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 命令仍在运行。第 1 部分已解决。

顺便说一句 - 一些人建议为此使用“nohup”命令。但这似乎对我不起作用。

*还有!在您的服务器上拥有一个可以直接在命令行上执行代码的 php 文件是一个明显的安全风险。您应该有密码,或者以某种方式对发布数据进行编码。

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

3。 从前端调用一个 php 脚本,为其提供 output.txt 文件的位置。然后,该 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;

希望这对某人有所帮助。

【讨论】:

我知道这是一个旧线程,但我想添加一些我今天发现的内容。似乎与 ffmpeg 一起出现了 ffprobe,它可用于在 std 输出中获取信息(“ffmpeg -i file”实际上以错误代码退出,因此您必须使用 2>1 管道输出以获取信息)。所以,就这样吧:ffprobe -v quiet -print_format json -show_format -i file.mp4 -show_streams【参考方案2】:

俄语中有一个article,它描述了如何解决您的问题。

重点是在编码前捕捉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--

【讨论】:

我去看了这篇文章,原以为会在俄语上有点挣扎,但读起来却出奇的容易。 我觉得文章上的命令是这样的ffmpeg -y -i input.avi -vcodec xvid -acodec mp3 -ab 96 output.avi > output.txt【参考方案3】:

如果使用 pipeview 命令就很简单了。为此,请转换

ffmpeg -i input.avi arguments

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

无需编码!

【讨论】:

我喜欢这个答案。它非常优雅。 pv 的一个相关参数是 -n,仅以百分比表示数字进度。 请注意,ffmpeg 将无法从 stdin 编码无法流式传输的视频,例如元数据位于末尾的 *.mov 文件。原因是ffmpeg 无法查找管道上的元数据。您必须先 qt-faststart 每个输入文件才能将其通过管道传输到 ffmpeg 我想先使用 pv,然后在意识到我的 ffmpeg 由于使用了特定的视频过滤器而没有编写任何有用的 vstats 后,我仔细查看了 pv,发现它完全符合我的要求.我只是在格式字符串中使用时间和附加文本pv -F "some additional info here %t %e" "$video",这就是我所需要的。 嗨,我一直在尝试将上面的代码添加到我的,但是没有成功。安迪建议请for /R %%f IN (*.mkv) DO ffmpeg -v warning -hide_banner -stats -i "%%f" -map 0 -c copy "%%~nf.mp4"【参考方案4】:

FFmpeg 使用 stdout 输出媒体数据,使用 stderr 记录/进度信息。您只需要将 stderr 重定向到文件或能够处理它的进程的标准输入。

对于 unix shell,这类似于:

ffmpeg ffmpeg arguments 2> logFile

ffmpeg ffmpeg arguments 2| processFFmpegLog

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

【讨论】:

如果我问这个问题,我很乐意接受你的回答。 @mouviciel,你知道我如何在 .NET 中实现这一点吗?我试过你说的,它没有打印出文件,还有当文件更改时我怎样才能得到通知? 感谢您指定与 stdout 和 stderr 相关的输出【参考方案5】:

您可以使用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

【讨论】:

这看起来是最好的解决方案。您可以通过ffmpeg -v warning -progress /dev/stdout -i in.mp4 out.mp4 将进度信息传送到stdout,以便每秒获得一次进度更新。如果您只需要一个百分比,您可以在开始编码之前以秒为单位为duration 输入ffprobe -v quiet -print_format json -show_format in.mp4,然后在| awk -F"=" '/out_time_ms/print $2' 中仅获得以毫秒为单位的当前持续时间,其中progress = total_duration * 100 * 1000 / current_duration 我不知道您可以将进度路由到文件;如果是这种情况,您可以使用sed 制作一个先进先出并继续前进。 创建命名管道并分别调用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【参考方案6】:

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

因此,我建议尝试全新的ffmpeg-progressbar-cli:

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

此外,它是开源的,基于 Node.js 并积极开发,处理了大部分提到的怪癖(完全披露:我是它目前的首席开发人员)。

【讨论】:

2020 年更新,更长的被认为是有效的。最后一次提交给 master 是在 2018 年 11 月,9 个未解决的问题,3 个未解决的拉取请求。 现在ffmpeg也有了自己的进度条 @TanishqBanyal 传递 ffmpeg 进度条的正确标志是什么?我在文档/互联网中找不到该选项? 不是进度条,但您可以在输出之前使用管道-progress - -y。例如ffmpeg -i ..... -progress - -y output.mp4【参考方案7】:

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

ffmpeg -v warning -hide_banner -stats $your_params

【讨论】:

【参考方案8】:

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 或 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 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 @sebilasse 更正您的代码,因为它会带来错误【参考方案9】:

我发现 ffpb Python 包 (pip install ffpb) 将参数透明地传递给 FFmpeg。由于它的鲁棒性,它不需要太多的维护。最后一个版本是 2019 年 4 月 29 日。

https://github.com/althonos/ffpb

【讨论】:

【参考方案10】:

调用 php 的系统函数会阻塞该线程,因此您需要产生 1 个 HTTP 请求来执行转换,并产生另一个轮询一个来读取正在生成的 txt 文件。

或者,更好的是,客户提交视频进行转换,然后由另一个进程负责执行转换。这样客户端的连接在等待系统调用终止时不会超时。轮询以与上述相同的方式完成。

【讨论】:

但是为什么要使用文本文件呢?必须有更好的方法。您不必为每次上传创建一个新的文本文件吗?【参考方案11】:

第二个 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>路径”到“1>路径”或“2>路径”到“2>路径"

我花了一段时间才弄明白。 FFMPEG一直失败。当我改为无空间时工作。

【讨论】:

【参考方案12】:

这是我的解决方案:

我使用ffpb 和 python 子进程来跟踪 ffmpeg 进度。然后我将状态推送到数据库(例如:Redis)以在网站上显示进度条。

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

【讨论】:

【参考方案13】:

这些使用多个工具/控制台的答案太复杂了。pv 是一个不错的选择,但具有缺少非序列数据的明显缺点。 只需使用progress 实用程序:正常运行ffmpeg,然后在另一个控制台监视器中使用progress -m -c ffmpeg

【讨论】:

以上是关于ffmpeg可以显示进度条吗?的主要内容,如果未能解决你的问题,请参考以下文章

Python开发技巧-教你制作Python进度条

如何从 java 读取 ffmpeg 响应并使用它来创建进度条?

FFmpeg+Qt视频进度条控制——点击跳转和拖动跳转

从 ffmpeg 获取实时输出以在进度条中使用(PyQt4,stdout)

VB如何实现进度条

如何显示ProgressBar的进度条,给分100