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

31 投票
9 回答
33908 浏览
提问于 2025-04-17 03:34

我看了很多问题,但还是搞不明白。我在用PyQt,想要运行 ffmpeg -i file.mp4 file.avi 这个命令,并且希望能实时看到输出,这样我就可以做一个进度条。

我查看了这些问题:

ffmpeg能显示进度条吗? 如何实时捕获子进程的输出?

我能看到rsync命令的输出,使用的代码是:

import subprocess, time, os, sys

cmd = "rsync -vaz -P source/ dest/"
p, line = True, 'start'


p = subprocess.Popen(cmd,
                     shell=True,
                     bufsize=64,
                     stdin=subprocess.PIPE,
                     stderr=subprocess.PIPE,
                     stdout=subprocess.PIPE)

for line in p.stdout:
    print("OUTPUT>>> " + str(line.rstrip()))
    p.stdout.flush()

但是当我把命令改成 ffmpeg -i file.mp4 file.avi 时,我就收不到任何输出。我猜这可能和输出缓冲有关,但我不知道怎么读取像这样的行:

frame=   51 fps= 27 q=31.0 Lsize=     769kB time=2.04 bitrate=3092.8kbits/s

我可以用它来计算进度。

有没有人能给我一个例子,教我怎么把ffmpeg的信息获取到python里,使用或不使用PyQt都可以(如果可能的话)


编辑: 最后我选择了jlp的解决方案,我的代码看起来是这样的:

#!/usr/bin/python
import pexpect

cmd = 'ffmpeg -i file.MTS file.avi'
thread = pexpect.spawn(cmd)
print "started %s" % cmd
cpl = thread.compile_pattern_list([
    pexpect.EOF,
    "frame= *\d+",
    '(.+)'
])
while True:
    i = thread.expect_list(cpl, timeout=None)
    if i == 0: # EOF
        print "the sub process exited"
        break
    elif i == 1:
        frame_number = thread.match.group(0)
        print frame_number
        thread.close
    elif i == 2:
        #unknown_line = thread.match.group(0)
        #print unknown_line
        pass

这给出了这样的输出:

started ffmpeg -i file.MTS file.avi
frame=   13
frame=   31
frame=   48
frame=   64
frame=   80
frame=   97
frame=  115
frame=  133
frame=  152
frame=  170
frame=  188
frame=  205
frame=  220
frame=  226
the sub process exited

太好了!

9 个回答

3
  1. 从命令行调用通常是不必要的。
  2. 我知道根据经验,ffmpeg的输出有一部分是通过stderr而不是stdout输出的。

如果你只是想打印输出的内容,就像你上面举的例子一样,那么只需要这样做:

import subprocess

cmd = 'ffmpeg -i file.mp4 file.avi'
args = cmd.split()

p = subprocess.Popen(args)

注意,ffmpeg的输出行是以\r结尾的,这样会在同一行上覆盖之前的内容!我觉得这意味着你不能像处理rsync的例子那样遍历p.stderr中的行。因此,如果你想自己制作一个进度条,你可能需要自己处理读取的部分,这里有个开始的代码:

p = subprocess.Popen(args, stderr=subprocess.PIPE)

while True:
  chatter = p.stderr.read(1024)
  print("OUTPUT>>> " + chatter.rstrip())
23

在这个特定的情况下,我想捕获ffmpeg的状态输出(这个输出会发送到错误输出流),这个StackOverflow上的问题帮我解决了这个问题:FFMPEG和Python的subprocess

关键是要在调用subprocess.Popen()时加上universal_newlines=True,因为ffmpeg的输出实际上是没有缓存的,但它是带有换行符的。

cmd = "ffmpeg -i in.mp4 -y out.avi"
process = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT,universal_newlines=True)
for line in process.stdout:
    print(line)

另外要注意,在这个代码示例中,错误输出流的状态信息是直接重定向到subprocess.STDOUT的。

17

我找到的唯一方法,可以从一个子进程中获取动态反馈或输出,就是使用像 pexpect 这样的工具:

#! /usr/bin/python

import pexpect

cmd = "foo.sh"
thread = pexpect.spawn(cmd)
print "started %s" % cmd
cpl = thread.compile_pattern_list([pexpect.EOF,
                                   'waited (\d+)'])
while True:
    i = thread.expect_list(cpl, timeout=None)
    if i == 0: # EOF
        print "the sub process exited"
        break
    elif i == 1:
        waited_time = thread.match.group(1)
        print "the sub process waited %d seconds" % int(waited_time)
thread.close()

被调用的子进程 foo.sh 会随机等待 10 到 20 秒之间的时间,下面是它的代码:

#! /bin/sh

n=5
while [ $n -gt 0 ]; do
    ns=`date +%N`
    p=`expr $ns % 10 + 10`
    sleep $p
    echo waited $p
    n=`expr $n - 1`
done

你需要使用一些正则表达式,来匹配你从 ffmpeg 得到的输出,并对其进行一些计算,以显示进度条。不过,这样至少可以让你获得 ffmpeg 的实时输出。

撰写回答