我看了很多问题,但仍然无法弄清楚这一点。我正在使用PyQt,我希望运行ffmpeg -i file.mp4 file.avi
并获取输出,因为我可以创建一个进度条。
我看过这些问题: Can ffmpeg show a progress bar? catching stdout in realtime from subprocess
我可以使用以下代码查看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
时,我没有收到任何输出。我猜这与stdout / output缓冲有关,但我不知道如何读取看起来像
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
完美!
答案 0 :(得分:14)
我发现从子进程获得动态反馈/输出的唯一方法是使用像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的无缓冲输出。 / p>
答案 1 :(得分:13)
在捕获ffmpeg状态输出(转到STDERR)的特定情况下,这个SO问题为我解决了这个问题:FFMPEG and Pythons subprocess
诀窍是将universal_newlines=True
添加到subprocess.Popen()
调用,因为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)
另请注意,在此代码示例中,STDERR状态输出直接重定向到subprocess.STDOUT
答案 2 :(得分:3)
stderr
,而不是stdout
。 如果您只想打印输出线,就像上面的示例一样,那么这样就可以了:
import subprocess
cmd = 'ffmpeg -i file.mp4 file.avi'
args = cmd.split()
p = subprocess.Popen(args)
请注意,ffmpeg聊天行以\r
终止,因此它会覆盖同一行!我认为这意味着您不能像{rs}示例那样迭代p.stderr
中的行。要建立自己的进度条,那么,您可能需要自己处理阅读,这应该让您开始:
p = subprocess.Popen(args, stderr=subprocess.PIPE)
while True:
chatter = p.stderr.read(1024)
print("OUTPUT>>> " + chatter.rstrip())
答案 3 :(得分:2)
这个答案对我没用:/这就是我做的方式。
来自我的项目KoalaBeatzHunter。
享受!
def convertMp4ToMp3(mp4f, mp3f, odir, kbps, callback=None, efsize=None):
"""
mp4f: mp4 file
mp3f: mp3 file
odir: output directory
kbps: quality in kbps, ex: 320000
callback: callback() to recieve progress
efsize: estimated file size, if there is will callback() with %
Important:
communicate() blocks until the child process returns, so the rest of the lines
in your loop will only get executed after the child process has finished running.
Reading from stderr will block too, unless you read character by character like here.
"""
cmdf = "ffmpeg -i "+ odir+mp4f +" -f mp3 -ab "+ str(kbps) +" -vn "+ odir+mp3f
lineAfterCarriage = ''
print deleteFile(odir + mp3f)
child = subprocess.Popen(cmdf, shell=True, stderr=subprocess.PIPE)
while True:
char = child.stderr.read(1)
if char == '' and child.poll() != None:
break
if char != '':
# simple print to console
# sys.stdout.write(char)
# sys.stdout.flush()
lineAfterCarriage += char
if char == '\r':
if callback:
size = int(extractFFmpegFileSize(lineAfterCarriage)[0])
# kb to bytes
size *= 1024
if efsize:
callback(size, efsize)
lineAfterCarriage = ''
接下来,您还需要3个函数来实现它。
def executeShellCommand(cmd):
p = Popen(cmd , shell=True, stdout=PIPE, stderr=PIPE)
out, err = p.communicate()
return out.rstrip(), err.rstrip(), p.returncode
def getFFmpegFileDurationInSeconds(filename):
cmd = "ffmpeg -i "+ filename +" 2>&1 | grep 'Duration' | cut -d ' ' -f 4 | sed s/,//"
time = executeShellCommand(cmd)[0]
h = int(time[0:2])
m = int(time[3:5])
s = int(time[6:8])
ms = int(time[9:11])
ts = (h * 60 * 60) + (m * 60) + s + (ms/60)
return ts
def estimateFFmpegMp4toMp3NewFileSizeInBytes(duration, kbps):
"""
* Very close but not exact.
duration: current file duration in seconds
kbps: quality in kbps, ex: 320000
Ex:
estim.: 12,200,000
real: 12,215,118
"""
return ((kbps * duration) / 8)
最后你做了:
# get new mp3 estimated size
secs = utls.getFFmpegFileDurationInSeconds(filename)
efsize = utls.estimateFFmpegMp4toMp3NewFileSizeInBytes(secs, 320000)
print efsize
utls.convertMp4ToMp3("AwesomeKoalaBeat.mp4", "AwesomeKoalaBeat.mp3",
"../../tmp/", 320000, utls.callbackPrint, efsize)
希望这会有所帮助!
答案 4 :(得分:1)
这里是一个dedicated function,以百分比显示进度,它可以与您可能已经拥有的任何ffmpeg命令一起使用(作为字符串列表):
for progress in run_ffmpeg_command(["ffmpeg", "-i", "test.mp4", "test2.mp4"])
print(progress)
这将打印0到100。
这个想法是启用-progress
选项,从stderr输出解析持续时间,然后,在获得进度时间后,只需对其进行划分即可。该代码是从this Gist借来的。
import subprocess
import re
from typing import Iterator
DUR_REGEX = re.compile(
r"Duration: (?P<hour>\d{2}):(?P<min>\d{2}):(?P<sec>\d{2})\.(?P<ms>\d{2})"
)
TIME_REGEX = re.compile(
r"out_time=(?P<hour>\d{2}):(?P<min>\d{2}):(?P<sec>\d{2})\.(?P<ms>\d{2})"
)
def to_ms(s=None, des=None, **kwargs) -> float:
if s:
hour = int(s[0:2])
minute = int(s[3:5])
sec = int(s[6:8])
ms = int(s[10:11])
else:
hour = int(kwargs.get("hour", 0))
minute = int(kwargs.get("min", 0))
sec = int(kwargs.get("sec", 0))
ms = int(kwargs.get("ms", 0))
result = (hour * 60 * 60 * 1000) + (minute * 60 * 1000) + (sec * 1000) + ms
if des and isinstance(des, int):
return round(result, des)
return result
def run_ffmpeg_command(cmd: "list[str]") -> Iterator[int]:
"""
Run an ffmpeg command, trying to capture the process output and calculate
the duration / progress.
Yields the progress in percent.
"""
total_dur = None
cmd_with_progress = [cmd[0]] + ["-progress", "-", "-nostats"] + cmd[1:]
stderr = []
p = subprocess.Popen(
cmd_with_progress,
stdout=subprocess.PIPE,
stderr=subprocess.STDOUT,
universal_newlines=False,
)
while True:
line = p.stdout.readline().decode("utf8", errors="replace").strip()
if line == "" and p.poll() is not None:
break
stderr.append(line.strip())
if not total_dur and DUR_REGEX.search(line):
total_dur = DUR_REGEX.search(line).groupdict()
total_dur = to_ms(**total_dur)
continue
if total_dur:
result = TIME_REGEX.search(line)
if result:
elapsed_time = to_ms(**result.groupdict())
yield int(elapsed_time / total_dur * 100)
if p.returncode != 0:
raise RuntimeError(
"Error running command {}: {}".format(cmd, str("\n".join(stderr)))
)
yield 100
答案 5 :(得分:0)
如果您有持续时间(您也可以从FFMPEG输出获得),您可以通过读取编码时经过的时间(时间)输出来计算进度。
一个简单的例子:
pipe = subprocess.Popen(
cmd,
stderr=subprocess.PIPE,
close_fds=True
)
fcntl.fcntl(
pipe.stderr.fileno(),
fcntl.F_SETFL,
fcntl.fcntl(pipe.stderr.fileno(), fcntl.F_GETFL) | os.O_NONBLOCK,
)
while True:
readx = select.select([pipe.stderr.fileno()], [], [])[0]
if readx:
chunk = pipe.stderr.read()
if not chunk:
break
result = re.search(r'\stime=(?P<time>\S+) ', chunk)
elapsed_time = float(result.groupdict()['time'])
# Assuming you have the duration in seconds
progress = (elapsed_time / duration) * 100
# Do something with progress here
callback(progress)
time.sleep(10)
答案 6 :(得分:0)
您还可以通过将QProcess中的插槽连接到QTextEdit或其他任何内容,使用PyQt4的QProcess(如原始问题中所述)清楚地完成此操作。我对python和pyqt还很陌生,但这就是我如何设法做到的:
import sys
from PyQt4 import QtCore, QtGui
class ffmpegBatch(QtGui.QWidget):
def __init__(self):
super(ffmpegBatch, self).__init__()
self.initUI()
def initUI(self):
layout = QtGui.QVBoxLayout()
self.edit = QtGui.QTextEdit()
self.edit.setGeometry(300, 300, 300, 300)
run = QtGui.QPushButton("Run process")
layout.addWidget(self.edit)
layout.addWidget(run)
self.setLayout(layout)
run.clicked.connect(self.run)
def run(self):
# your commandline whatnot here, I just used this for demonstration
cmd = "systeminfo"
proc = QtCore.QProcess(self)
proc.setProcessChannelMode(proc.MergedChannels)
proc.start(cmd)
proc.readyReadStandardOutput.connect(lambda: self.readStdOutput(proc))
def readStdOutput(self, proc):
self.edit.append(QtCore.QString(proc.readAllStandardOutput()))
def main():
app = QtGui.QApplication(sys.argv)
ex = ffmpegBatch()
ex.show()
sys.exit(app.exec_())
if __name__ == '__main__':
main()