web-dev-qa-db-ja.com

プログレスバー(PyQt4、stdout)で使用するffmpegからのリアルタイム出力の取得

私はいくつかの質問を見てきましたが、それでもこれを完全に理解することはできません。私は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 /出力バッファリングと関係があると思いますが、次のような行を読み取る方法については行き詰まっています

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

進捗状況を把握するために使用できます。

PyQtを使用して、または使用せずに、この情報をffmpegからpythonに取得する方法の例を誰かに教えてもらえますか(可能な場合)


編集:最終的に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

完璧!

22
Jason O'Neil

子プロセスから動的なフィードバック/出力を取得するために私が見つけた唯一の方法は、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からのバッファーなしの出力が取得されます。

15
jlp

Ffmpegのステータス出力(STDERRに送られる)をキャプチャするためのこの特定のケースでは、このSO質問は私のためにそれを解決しました: FFMPEGおよびPythonsサブプロセス

Ffmpegの出力は実際にはバッファリングされていませんが、改行文字が含まれているため、トリックは_universal_newlines=True_をsubprocess.Popen()呼び出しに追加することです。

_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_にリダイレクトされることに注意してください。

16
dequid
  1. 通常、シェルからの呼び出しは必要ありません。
  2. 私は経験から、ffmpeg出力の一部がstderrではなくstdoutに来ることを知っています。

上記の例のように、出力行を印刷するだけの場合は、次のようにします。

import subprocess

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

p = subprocess.Popen(args)

Ffmpegチャットの行は\rで終了しているため、同じ行で上書きされることに注意してください。これは、rsyncの例のように、p.stderrの行を繰り返すことができないことを意味すると思います。独自のプログレスバーを作成するには、自分で読み取りを処理する必要がある場合があります。これで開始できます。

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

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

この答えは私にはうまくいきませんでした:/これが私がやった方法です。

それは私のプロジェクトから 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)

これがお役に立てば幸いです。

2
binarytrails

また、QProcessからQTextEditなどにスロットを接続することにより、PyQt4のQProcess(元の質問で尋ねられた)を使用して非常に明確に行うことができます。私はまだ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
Spencer

期間(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)
0
JayLev