我正在使用 ffmpeg 将 .avi 文件转换为 .flv 文件。由于转换文件需要很长时间,我想显示一个进度条。有人可以指导我如何去做。
我知道 ffmpeg 必须以某种方式在文本文件中输出进度,我必须使用 ajax 调用来读取它。但是如何让 ffmpeg 将进度输出到文本文件?
我正在使用 ffmpeg 将 .avi 文件转换为 .flv 文件。由于转换文件需要很长时间,我想显示一个进度条。有人可以指导我如何去做。
我知道 ffmpeg 必须以某种方式在文本文件中输出进度,我必须使用 ajax 调用来读取它。但是如何让 ffmpeg 将进度输出到文本文件?
这几天我一直在玩这个。那个“ffmpegprogress”的东西有帮助,但是很难使用我的设置,也很难阅读代码。
为了显示 ffmpeg 的进度,您需要执行以下操作:
这是我解决每个部分的方法:
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;
希望这可以帮助某人。
有一篇俄语文章描述了如何解决您的问题。
关键是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--
如果使用 pipeview 命令,这非常简单。为此,变换
ffmpeg -i input.avi {arguments}
至
pv input.avi | ffmpeg -i pipe:0 -v warning {arguments}
无需进入编码!
FFmpeg 使用 stdout 输出媒体数据,使用 stderr 记录/进度信息。您只需要将 stderr 重定向到文件或能够处理它的进程的标准输入。
对于 unix shell,这类似于:
ffmpeg {ffmpeg arguments} 2> logFile
或者
ffmpeg {ffmpeg arguments} 2| processFFmpegLog
无论如何,您必须将 ffmpeg 作为单独的线程或进程运行。
你可以用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
它本身仍然无法显示进度条——此外,许多上述基于 bash 或 python 的权宜之计解决方案已经过时且无法使用。
因此,我建议尝试全新的ffmpeg-progressbar-cli :
它是可执行文件的包装ffmpeg
,显示彩色居中进度条和剩余时间。
此外,它是开源的,基于 Node.js 并积极开发,处理了大部分提到的怪癖(完全披露:我是它目前的首席开发人员)。
如果您只需要隐藏所有信息并在最后一行显示像 ffmpeg 这样的默认进度,您可以使用-stats
选项:
ffmpeg -v warning -hide_banner -stats ${your_params}
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);
我找到了将参数透明地传递给 FFmpeg 的ffpb
Python 包 ( pip install ffpb
)。由于它的鲁棒性,它不需要太多的维护。最后一个版本是 2019 年 4 月 29 日。
调用 php 的系统函数会阻塞该线程,因此您需要产生 1 个 HTTP 请求来执行转换,并产生另一个轮询一个来读取正在生成的 txt 文件。
或者,更好的是,客户提交视频进行转换,然后另一个进程负责执行转换。这样客户端的连接在等待系统调用终止时不会超时。轮询以与上述相同的方式完成。
第二个 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一直失败。当我改为没有空间时工作。
这是我的解决方案:
我使用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...')
These answers that use multiple tools/consoles are complicating things too much.
pv
is a good option but has the noted drawbacks of missing non-senquential data.
Just use the progress
utility: Run ffmpeg as normal then in another console monitor with progress -m -c ffmpeg