Python で何か外部コマンド(ffmpeg, vlc,...)を実行するとき、標準モジュールである subprocess を使うのが一般的かと思います。この時、標準出力をPythonプログラム内でリアルタイムに取得したいときは subprocess.Popen で非同期で実行し、その結果をキューなどでメインプロセス側に渡せばOKかと思いますが、ここでハマったので自分の備忘録がてら記事にしたいと思います。
環境
Ubuntu:20.04
Python:3.8
やりたいこと
あるPythonメインプロセスを実行させながら、外部コマンド(ffmpeg, vlc, ..)の標準出力される結果をリアルタイムでPythonメインプロセスで読み込みたい。
一般的な方法
コード例
import subprocess
import multiprocessing
import time
def sub(queue):
"""サブプロセスで実行する処理"""
cmd = ['ffmpeg', '-i', 'input.mp4', 'output.mp4']
proc = subprocess.Popen(cmd, stdout=subprocess.PIPE)
while True:
# 標準出力を取り出し
start = time.time()
line = proc.stdout.readline()
elapsed_time = time.time() - start
print("stdout parse fps: ", 1/elapsed_time)
queue.put(line)
def main():
# プロセス間でやり取りするキュー
queue = multiprocessing.Queue()
p = multiprocessing.Process(target=sub, args=(queue, ))
# サブプロセスを実行
p.start()
while True:
line = queue.get()
# 以下、何か処理を記述
main()
問題
上のコードを実行すると、例えば自分の環境では以下のような出力になります。
stdout parse fps: 0.3043587...
stdout parse fps: 2087535.7856...
stdout parse fps: 2038475.8047...
stdout parse fps: 2083567.7802...
stdout parse fps: 2384956.6805...
stdout parse fps: 0.3043587...
....
標準出力を行ごとに取得しているのですが、一定間隔である行の取得に時間がかかってしまいます。
原因
調べても同じような問題で困っている人は少なかったのですが、挙動的に内部のどこか標準出力の結果がバッファリングされているような感じでした。しかし、subprocess.Popenの引数でbufsizeというオプションがあったため、この数を調整しても変化なし。
さらに調べてみると、linuxコマンドで stdbuf という標準出力のバッファリングの長さを指定できるコマンドがあることを知りました。実行したいコマンドの前に stdbuf を付けるようです。
結論
subprocess で以下のようにして実行する
# 標準出力を行バッファリングとする
stdbuf_cmd = ['stdbuf', '-oL']
cmd = ['ffmpeg', '-i', 'input.mp4', 'output.mp4']
proc = subprocess.Popen(stdbuf_cmd + cmd, stdout=subprocess.PIPE)