example and python ffmpeg pyqt subprocess stdout

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)

  1. Por lo general, no es necesario llamar desde el shell.
  2. Sé por experiencia que parte de la salida ffmpeg viene en stderr , no en stdout .

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()