FFMPEG和Python的subprocess

11
我正在尝试为FFMPEG编写GUI界面。我使用Python的subprocess模块来创建每个转换所需的FFMPEG进程。这种方法运行良好,但我还想获得转换的进度以及是否失败等信息。我认为可以通过访问进程的标准输出来实现:

调用subprocess.Popen()函数。

# Convert - Calls FFMPEG with current settings. (in a seperate
# thread.)
def convert(self):
    # Check if options are valid
    if self.input == "" or self.output == "":
        return False

# Make the command string
ffmpegString = self.makeString()

# Try to open with these settings
try:
    self.ffmpeg = subprocess.Popen(ffmpegString, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
except OSError:
    self.error.append("OSError: ")
except ValueError:
    self.error.append("ValueError: Couldn't call FFMPEG with these parameters")

# Convert process should be running now.

阅读 stdout:

convert = Convert()
convert.input = "test.ogv"
convert.output = "test.mp4"
convert.output_size = (0, 0)

convert.convert()

while 1:
    print convert.ffmpeg.stdout.readline()

这个可以工作,不过ffmpeg的状态没有显示出来。我猜想这可能与ffmpeg刷新的方式有关。是否有一种方法可以访问它?

6个回答

11

只需在 subprocess.Popen 行中添加 universal_newlines=True。

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)

现在你的循环语句如下:

frame= 1900 fps=453 q=18.6 Lsize=    3473kB time=00:01:16.08 bitrate= 373.9kbits/s

使用 time= 值来确定百分比进度。

有趣的解决方案,但使用Popen会出现一些缓冲问题。如果你正在寻找实时编码监控,它不能直接使用。 - littlebridge
可以了!谢谢。但是为什么添加 universal_newlines=True 就能让它工作呢? - Qin Heyang

8

我经常注意到使用subprocess读取标准输出(甚至标准错误!)时存在缓冲问题,这些问题很难解决。当我确实需要从子进程中读取stdout/stderr时,我最喜欢的解决方案是改用pexpect(或在Windows上使用wexpect)而不是subprocess


你提供的链接都失效了,请修复它们。 - slhck

3

我认为你不能使用readline,因为ffmpeg从不输出一行,它通过写入\r(回车符)然后再次写入该行来更新状态。

size=      68kB time=0.39 bitrate=1412.1kbits/s    \rsize=    2786kB time=16.17 bitrate=1411.2kbits/s    \rsize=    5472kB time=31.76 bitrate=1411.2kbits/s    \r\n

如果您查看上面的行,您会注意到只有一个\n,并且在文件转换完成后打印。

1
你可以使用readline,但只有在完成输入后才能获取该行的最终版本... - Anentropic

3
自ffmpeg将数据未刷新地写入stderr,您需要使用fcntl将stderr文件描述符设置为非阻塞。
    fcntl.fcntl(
        pipe.stderr.fileno(),
        fcntl.F_SETFL,
        fcntl.fcntl(pipe.stderr.fileno(), fcntl.F_GETFL) | os.O_NONBLOCK,
    )
然后使用select循环读取数据。
    while True:
        readx = select.select([pipe.stderr.fileno()], [], [])[0]
        if readx:
            chunk = pipe.stderr.read()
完整示例请在此处查看。

我不确定这是否仍然正确...在py2.7上的当前版本的ffmpeg中,只需在stderr上执行readline即可获得渐进式输出:for line in proc.stderr: print line proc.stderr.flush() - Anentropic
啊,不太对。在编码过程中,我没有得到“frame= xxx”状态输出的渐进式输出(因为这是一行会被重复更新的内容),但我看到了元数据行,然后它就会阻塞直到编码完成,只显示最后一个状态更新,然后显示剩余的摘要行。 - Anentropic

-1

FFMPEG:

FFMPEG会在stderr接口上输出所有状态文本(当您在命令行上手动运行它时所看到的内容)。为了捕获ffmpeg的输出,您需要观察stderr接口 - 或者像示例一样重定向它。

检查stderr上的输出:

这是另一种尝试从stderr读取输出的方法,而不是在调用Popen时重定向它

Python中的Popen类有一个名为stderr的文件对象,您可以以与访问stdout相同的方式访问它。我认为您的循环应该看起来像这样:

while 1:
    print convert.ffmpeg.stdout.readline()
    print convert.ffmpeg.stderr.readline()

免责声明:我尚未在Python中测试过此代码,但我已使用Java创建了一个类似的应用程序。


Gorgapor,你确定他是吗? - Matthew Talbert
是的,在代码片段中,stderr 在 subprocess.Popen 的那一行被重定向了 -- 当然,如果您不使用代码片段下方的滚动条,它可以被截断... - Astra

-2
ffmpegCommand='''
ffmpeg
-f lavfi
-i anullsrc=channel_layout=1c:sample_rate=11025
-rtsp_transport tcp
-rtsp_transport udp
-rtsp_transport http
-thread_queue_size 32000
-i rtsp://xxx.xxx.xxx.xxx:554/user=admin&password=xxx&channel=1&stream=1.sdp?real_stream
-reconnect 1
-reconnect_at_eof 1
-reconnect_streamed 1
-reconnect_delay_max 4294
-tune zerolatency
-c:v copy
-c:a aac
-bufsize 6000k
-f flv rtmp://a.rtmp.youtube.com/live2/xxx-xxx-xxx-xxx'''
cmd=ffmpegCommand.split()
# "universal newline support" This will cause to interpret \n, \r\n and \r     equally, each as a newline.

p = subprocess.Popen(cmd, stderr=subprocess.PIPE, universal_newlines=True)
while True:    
        print(p.stderr.readline().rstrip('\r\n'))

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