Подтвердить что ты не робот

Получение результата в реальном времени из 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!

4b9b3361

Ответ 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()