Получение вывода в реальном времени из ffmpeg для использования в индикаторе выполнения (PyQt4, stdout) - PullRequest
20 голосов
/ 03 октября 2011

Я посмотрел на несколько вопросов, но до сих пор не могу понять это.Я использую 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 (если возможно)


РЕДАКТИРОВАТЬ: В итоге я решил использовать решение 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

Отлично!

Ответы [ 6 ]

14 голосов
/ 16 февраля 2016

В этом конкретном случае для захвата вывода статуса 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

14 голосов
/ 04 октября 2011

Единственный способ получить динамическую обратную связь / вывод от дочернего процесса - это использовать что-то вроде 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.

3 голосов
/ 03 октября 2011
  1. Вызов из оболочки обычно не требуется.
  2. Из опыта я знаю, что часть вывода ffmpeg появляется на stderr, а не stdout.

Если все, что вы хотите сделать, это напечатать строку вывода, как в вашем примере выше, то просто это будет делать:заканчивается на \r, поэтому он будет перезаписан в той же строке!Я думаю, это означает, что вы не можете перебирать строки в p.stderr, как вы делаете с вашим примером rsync.Чтобы построить свой собственный индикатор выполнения, вам, возможно, придется справиться с чтением самостоятельно, это должно помочь вам начать:

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

while True:
  chatter = p.stderr.read(1024)
  print("OUTPUT>>> " + chatter.rstrip())
2 голосов
/ 05 января 2014

Этот ответ не сработал для меня: / Вот как я это сделал.

Это из моего проекта 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)

Надеюсь, это поможет!

0 голосов
/ 08 марта 2017

Вы также можете сделать это довольно четко с QProcess PyQt4 (как задано в исходном вопросе), подключив слот из 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()
0 голосов
/ 03 октября 2011

Если у вас есть длительность (которую вы также можете получить из выходных данных 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)
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...