ffmpeg可以显示进度条吗?

时间:2009-04-14 14:51:30

标签: ffmpeg progress-bar

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

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

非常感谢。

12 个答案:

答案 0 :(得分:32)

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

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

  1. 从php运行ffmpeg命令而不等待响应(对我来说,这是最难的部分)
  2. 告诉ffmpeg将其输出发送到文件
  3. 来自前端的
  4. (AJAX,Flash,无论如何)直接命中该文件或者可以从ffmpeg的输出中取出进度的php文件。
  5. 以下是我解决每个部分的方法:

    1。 我从“ffmpegprogress”得到了以下想法。这就是他所做的:一个PHP文件通过http套接字调用另一个。第二个实际运行“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命令仍然运行。第1部分解决了。

    BTW - 有些人建议使用“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>路径/到/ 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;
    

    希望这有助于某人。

答案 1 :(得分:25)

俄语中有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--

答案 2 :(得分:19)

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

使用unix shell就像:

ffmpeg {ffmpeg arguments} 2> logFile

ffmpeg {ffmpeg arguments} 2| processFFmpegLog

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

答案 3 :(得分:13)

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

ffmpeg -i input.avi {arguments}

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

无需编码!

答案 4 :(得分:9)

您可以使用ffmpeg的{​​{1}}参数和-progress

来执行此操作
nc

答案 5 :(得分: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或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);

答案 6 :(得分:3)

可悲的是,ffmpeg本身仍然无法显示进度条-而且,许多上述基于bash或python的权宜之计解决方案已经过时且无法正常工作。

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

ffmpeg-progressbar-cli screencast

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

它也是基于Node.js的开源软件,并且是主动开发的,可以处理大多数提到的怪癖(完整披露:我是其当前的主要开发人员)。

答案 7 :(得分:1)

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

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

答案 8 :(得分:1)

第二个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&gt;路径”至“ 1&gt;路径”或“ 2&gt;路径”至“ 2&gt;路径< /强>“

我花了一些时间来搞清楚。 FFMPEG一直在失败。当我改为无空间时工作。

答案 9 :(得分:1)

我发现ffpb Python程序包(pip install ffpb)将参数透明地传递给FFmpeg。由于其坚固性,因此不需要太多维护。最新版本是2019年4月29日。

https://github.com/althonos/ffpb

答案 10 :(得分:0)

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

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

答案 11 :(得分:0)

这是我的解决方案:

我使用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...')