Получение результата в реальном времени из ffmpeg для использования в индикаторе выполнения (PyQt4, stdout)
Я рассмотрел ряд вопросов, но до сих пор не могу понять это. Я использую PyQt, и я надеюсь запустить ffmpeg -i file.mp4 file.avi
и получить вывод по мере его потока, чтобы я мог создать индикатор выполнения.
Я рассмотрел следующие вопросы:
Может ли ffmpeg показать индикатор выполнения?
ловить stdout в реальном времени из подпроцесса
Я могу видеть вывод команды 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 (если возможно)
EDIT:
Я закончил работу с 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
Perfect!
Ответы
Ответ 1
Единственный способ получить динамическую обратную связь/выход из дочернего процесса - использовать что-то вроде 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.
Ответ 2
В этом конкретном случае для получения вывода статуса ffmpeg (который идет в STDERR) этот вопрос SO решил для меня: подпроцесс FFMPEG и Pythons
Трюк заключается в том, чтобы добавить 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
Ответ 3
- Вызов из оболочки обычно не требуется.
- Из опыта я знаю, что часть выхода ffmpeg появляется
stderr
, а не stdout
.
Если все, что вы хотите сделать, это распечатать выходную строку, как в приведенном выше примере, просто выполните следующее:
import subprocess
cmd = 'ffmpeg -i file.mp4 file.avi'
args = cmd.split()
p = subprocess.Popen(args)
Обратите внимание, что строка чата ffmpeg завершается с помощью \r
, поэтому она будет перезаписана в одной строке! Я думаю, это означает, что вы не можете перебирать строки в p.stderr
, как это делается с вашим примером rsync. Чтобы создать свой собственный индикатор выполнения, вам, возможно, потребуется обработать чтение самостоятельно, это должно начать:
p = subprocess.Popen(args, stderr=subprocess.PIPE)
while True:
chatter = p.stderr.read(1024)
print("OUTPUT>>> " + chatter.rstrip())
Ответ 4
Эти ответы не сработали для меня:/Вот как я это сделал.
Из моего проекта 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)
Надеюсь, это поможет!
Ответ 5
Если у вас есть продолжительность (которую вы также можете получить с выхода 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
Вы также можете сделать это довольно четко с помощью PyQt4 QProcess (как задано в исходном вопросе), подключив слот из QProcess к QTextEdit или тому подобное. Я все еще довольно новичок в 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()