python - and - Obtención de la salida en tiempo real de ffmpeg para ser utilizada en la barra de progreso(PyQt4, stdout)
python execute shell command and get output (6)
- Por lo general, no es necesario llamar desde el shell.
- Sé por experiencia que parte de la salida ffmpeg viene en
stderr
, no enstdout
.
Si todo lo que desea hacer es imprimir la línea de salida, como en el ejemplo anterior, simplemente esto hará:
import subprocess
cmd = ''ffmpeg -i file.mp4 file.avi''
args = cmd.split()
p = subprocess.Popen(args)
Tenga en cuenta que la línea de chat de ffmpeg termina con /r
, por lo que se sobrescribirá en la misma línea. Creo que esto significa que no puedes p.stderr
las líneas en p.stderr
, como lo haces con tu ejemplo rsync. Para crear su propia barra de progreso, entonces, es posible que deba manejar la lectura usted mismo, esto debería comenzar:
p = subprocess.Popen(args, stderr=subprocess.PIPE)
while True:
chatter = p.stderr.read(1024)
print("OUTPUT>>> " + chatter.rstrip())
He mirado una serie de preguntas, pero todavía no puedo resolver esto. Estoy usando PyQt, y espero ejecutar ffmpeg -i file.mp4 file.avi
y obtener la salida a medida que se transmite para que pueda crear una barra de progreso.
He visto estas preguntas: ¿Puede ffmpeg mostrar una barra de progreso? captura de stdout en tiempo real desde subproceso
Puedo ver la salida de un comando rsync, usando este código:
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()
Pero cuando cambio el comando a ffmpeg -i file.mp4 file.avi
no recibo salida. Supongo que esto tiene algo que ver con el búfer de salida / salida estándar, pero estoy atascado en cuanto a cómo leer la línea que se ve
frame= 51 fps= 27 q=31.0 Lsize= 769kB time=2.04 bitrate=3092.8kbits/s
Que podría utilizar para averiguar el progreso.
Alguien me puede mostrar un ejemplo de cómo obtener esta información de ffmpeg en python, con o sin el uso de PyQt (si es posible)
EDIT: Terminé yendo con la solución de jlp, mi código se veía así:
#!/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
Lo que da esta salida:
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
¡Perfecto!
En este caso específico para capturar la salida de estado de ffmpeg (que va a STDERR), esta pregunta SO lo resolvió para mí: FFMPEG y subproceso Pythons
El truco es agregar universal_newlines=True
a la llamada subprocess.Popen()
, porque la salida de ffmpeg en realidad no tiene buffer pero viene con caracteres de nueva línea.
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)
También tenga en cuenta que en este ejemplo de código la salida de estado STDERR se redirige directamente a subprocess.STDOUT
Estas respuestas no funcionaron para mí: / Así es como lo hice.
Es de mi proyecto KoalaBeatzHunter .
¡Disfrutar!
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 = ''''
A continuación, necesitas 3 funciones más para implementarlo.
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)
Y finalmente lo haces:
# 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)
Espero que esto ayude!
La única manera que he encontrado para obtener retroalimentación / salida dinámica de un proceso secundario es usar algo como 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()
el subproceso llamado foo.sh solo espera un tiempo aleatorio entre 10 y 20 segundos, aquí está el código para ello:
#! /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
Querrá usar alguna expresión regular que coincida con la salida que está obteniendo de ffmpeg y haga algún tipo de cálculo para mostrar la barra de progreso, pero al menos obtendrá la salida sin buffer de ffmpeg.
Si tiene la duración (que también puede obtener de la salida FFMPEG), puede calcular el progreso leyendo la salida del tiempo transcurrido (tiempo) durante la codificación.
Un ejemplo simple:
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)
También puede hacerlo de manera bastante clara con el QProcess de PyQt4 (como se pregunta en la pregunta original) conectando una ranura del QProcess a un QTextEdit o lo que sea. Todavía soy bastante nuevo en python y pyqt pero aquí es cómo me las arreglé para hacerlo:
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()