2011-10-03 13 views
16

Ho esaminato una serie di domande ma non riesco ancora a capirlo. Sto usando PyQt e spero di eseguire ffmpeg -i file.mp4 file.avi e ottenere l'output mentre scorre, così posso creare una barra di avanzamento.Ottenere l'output in tempo reale da ffmpeg da utilizzare nella barra di avanzamento (PyQt4, stdout)

Ho guardato queste domande: Can ffmpeg show a progress bar? catching stdout in realtime from subprocess

Sono in grado di vedere l'output di un comando rsync, utilizzando questo codice:

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

Ma quando cambio il comando a ffmpeg -i file.mp4 file.avi Non ricevo output. Sto indovinando questo ha qualcosa a che fare con il buffering stdout/uscita, ma io sono bloccato su come leggere la riga che assomiglia

frame= 51 fps= 27 q=31.0 Lsize=  769kB time=2.04 bitrate=3092.8kbits/s 

quale ho potuto usare per capire i progressi.

Qualcuno mi può mostrare un esempio di come ottenere queste informazioni da ffmpeg in pitone, con o senza l'uso di PyQt (se possibile)


EDIT: ho finito per andare con il JLP di soluzione, il mio codice si presentava così:

#!/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 

che dà questo output:

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 

Perfetto!

+1

il codice in ** ** di modifica non guardare a destra (e non funziona per me). .. Non penso che tu voglia catturare un pattern jolly e non fare nulla (devi solo catturare i pattern che ti interessano) e, cosa più importante, vuoi che il 'thread.close 'sia ** al di fuori ** del ciclo while piuttosto che chiamare la prima volta si cattura il tuo modello di interesse. Il codice di @jlp sembra più corretto e funziona per me una volta adattato all'output di ffmpeg. – Anentropic

risposta

13

L'unico modo che ho trovato per ottenere un feedback dinamico/uscita da un processo figlio è quello di usare qualcosa come 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() 

chiamato processo di sub foo.sh solo attende un tempo casuale tra 10 e 20 secondi, ecco il codice per esso:

#! /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 

Ti consigliamo di utilizzare qualche espressione regolare che corrisponde l'uscita che stai ricevendo da ffmpeg e fa un certo genere di calcolo su di esso per mostrare la barra di avanzamento, ma questo almeno ti porterà l'output senza buffer da ffmpeg.

+0

Esattamente quello che volevo, grazie Ho visto pexpect ma non avevo capito come usarlo, il tuo esempio lo ha dimostrato molto chiaramente. un principiante e non ho punti per votare la tua risposta! –

+0

Nessun problema, sono anch'io un novizio.Alcuni voti lo voteranno a un certo punto. Sono contento che lo abbia aiutato. – jlp

+0

dovrebbe 'thread.close' essere' thread.close() '? – Anentropic

2
  1. La chiamata dalla shell non è in genere richiesta.
  2. So per esperienza che parte dell'output ffmpeg si trova stderr, non stdout.

Se tutto quello che vogliamo fare è stampare la linea di uscita, come nel tuo esempio di cui sopra, quindi è sufficiente questo farà:

import subprocess 

cmd = 'ffmpeg -i file.mp4 file.avi' 
args = cmd.split() 

p = subprocess.Popen(args) 

nota che la linea di chat di ffmpeg è terminato con \r, così sovrascriverà nella stessa riga! Penso che questo significhi che non puoi scorrere le righe in p.stderr, come fai con il tuo esempio rsync. Per costruire il proprio barra di avanzamento, quindi, potrebbe essere necessario per gestire la lettura da soli, questo dovrebbe iniziare:

p = subprocess.Popen(args, stderr=subprocess.PIPE) 

while True: 
    chatter = p.stderr.read(1024) 
    print("OUTPUT>>> " + chatter.rstrip()) 
0

Se avete la durata (che si può anche ottenere dall'output FFMPEG) è possibile calcolare il progresso leggendo l'output di tempo (tempo) trascorso durante la codifica.

Un semplice esempio:

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) 
+0

Selezionare non funzionerà su file non creati da WinSock su Windows sfortunatamente :( – chaz

2

Queste risposte non hanno funzionato per me:/Ecco come l'ho fatto.

Dal mio progetto KoalaBeatzHunter.

Divertiti!

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 = '' 

Successivamente, sono necessarie altre 3 funzioni per 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) 

E infine lo fai:

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

Spero che questo vi aiuterà!

6

In questo caso specifico per la cattura all'output di stato di ffmpeg (che va a STDERR), questa domanda SO risolto per me: FFMPEG and Pythons subprocess

Il trucco è quello di aggiungere universal_newlines=True alla chiamata subprocess.Popen(), perché l'uscita di ffmpeg è infatti senza buffer ma con caratteri di nuova riga.

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) 

Si noti inoltre che in questo esempio di codice l'uscita di stato STDERR è direttamente reindirizzato alla subprocess.STDOUT

+0

È così bello vedere qualcuno fornire un collegamento semplicemente per riferimento. Così tante persone li usano come l'intera risposta. – zondo

0

È anche possibile farlo abbastanza chiaramente con del PyQt4 QProcess (come richiesto nella domanda originale) collegando una fessura da il QProcess in un QTextEdit o qualsiasi altra cosa. Sono ancora abbastanza nuovo per Python e PyQt, ma ecco come ho appena riuscito a farlo:

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