问题描述
我的问题基于 https://github.com/kkroening/ffmpeg-python/blob/master/examples/show_progress.py 理想情况下,我想要的只是跟踪例如转码过程的 0 - 100,向我的 gRPC 客户端发出流产率响应。从技术上讲,我不需要进度条。 如何向 ffmpeg 子进程提供自己的套接字并处理写入事件?
解决方法
您可以使用 stdout
将“进度”重定向到 -progress pipe:1
。
见How to redirect -progress option output of ffmpeg to stderr?
难点在于实际获得进度百分比。
- 首先使用 FFprobe 计算帧总数,如here 所述。
- 将 FFmpeg 作为子进程执行,将
-progress
重定向到stdout
。 - 发起一个从
stdout
读取文本行的线程。
该线程查找frame=xx
,获取框架,并将其放入一个列表(1 个元素的列表)中。 - 执行“主循环”以演示进度读数。
循环休眠 1 秒,从队列中读取最后一个元素,并打印进度。
代码首先构建合成视频文件 input.mp4
- 用作输入。
这是一个“自包含”的代码示例:
import subprocess as sp
import shlex
import json
from threading import Thread
import time
def progress_reader(procs,q):
while True:
if procs.poll() is not None:
break # Break if FFmpeg sun-process is closed
progress_text = procs.stdout.readline() # Read line from the pipe
# Break the loop if progress_text is None (when pipe is closed).
if progress_text is None:
break
progress_text = progress_text.decode("utf-8") # Convert bytes array to strings
# Look for "frame=xx"
if progress_text.startswith("frame="):
frame = int(progress_text.partition('=')[-1]) # Get the frame number
q[0] = frame # Store the last sample
# Build synthetic video for testing:
################################################################################
sp.run(shlex.split('ffmpeg -y -f lavfi -i testsrc=size=320x240:rate=30 -f lavfi -i sine=frequency=400 -f lavfi -i sine=frequency=1000 -filter_complex amerge -vcodec libx265 -crf 17 -pix_fmt yuv420p -acodec aac -ar 22050 -t 30 input.mp4'))
################################################################################
# Use FFprobe for counting the total number of frames
################################################################################
# Execute ffprobe (to show streams),and get the output in JSON format
# Actually counts packets instead of frames but it is much faster
# https://stackoverflow.com/questions/2017843/fetch-frame-count-with-ffmpeg/28376817#28376817
data = sp.run(shlex.split('ffprobe -v error -select_streams v:0 -count_packets -show_entries stream=nb_read_packets -of csv=p=0 -of json input.mp4'),stdout=sp.PIPE).stdout
dict = json.loads(data) # Convert data from JSON string to dictionary
tot_n_frames = float(dict['streams'][0]['nb_read_packets']) # Get the total number of frames.
################################################################################
# Execute FFmpeg as sub-process with stdout as a pipe
# Redirect progress to stdout using -progress pipe:1 arguments
process = sp.Popen(shlex.split('ffmpeg -y -loglevel error -i input.mp4 -acodec libvorbis -vcodec libvpx-vp9 -crf 20 -pix_fmt yuv420p -progress pipe:1 output.webm'),stdout=sp.PIPE)
q = [0] # We don't really need to use a Queue - use a list of of size 1
progress_reader_thread = Thread(target=progress_reader,args=(process,q)) # Initialize progress reader thread
progress_reader_thread.start() # Start the thread
while True:
if process.poll() is not None:
break # Break if FFmpeg sun-process is closed
time.sleep(1) # Sleep 1 second (do some work...)
n_frame = q[0] # Read last element from progress_reader - current encoded frame
progress_percent = (n_frame/tot_n_frames)*100 # Convert to percentage.
print(f'Progress [%]: {progress_percent:.2f}') # Print the progress
process.stdout.close() # Close stdin pipe.
progress_reader_thread.join() # Join thread
process.wait() # Wait for FFmpeg sub-process to finish
注意:
- 代码示例假定
ffmpeg
和ffprobe
在可执行路径中。
示例输出:
Progress [%]: 7.33
Progress [%]: 16.00
Progress [%]: 24.67
Progress [%]: 33.33
Progress [%]: 42.13
Progress [%]: 50.40
Progress [%]: 58.80
Progress [%]: 67.20
Progress [%]: 75.60
Progress [%]: 84.00
Progress [%]: 92.40
Progress [%]: 100.00