ffmpeg-python跟踪转码过程

Gwe*_*n J 1 python video ffmpeg transcoding

我的问题基于https://github.com/kkroening/ffmpeg-python/blob/master/examples/show_progress.py 理想情况下,我想要的只是跟踪转码过程,例如 0 - 100,发出流媒体产量对我的 gRPC 客户端的响应。从技术上讲,我不需要进度条。如何向 ffmpeg 子进程提供自己的套接字并处理对其的写入事件?

Rot*_*tem 5

您可以将“进度”重定向到stdout使用-progress pipe:1.
请参阅如何将 ffmpeg 的 -progress 选项输出重定向到 stderr?

困难的部分是实际获得百分比进度。

  • 首先使用 FFprobe 计算帧总数,如下所述
  • 作为子进程执行 FFmpeg,重定向-progressstdout.
  • 启动一个从 读取文本行的线程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
# /sf/ask/141249041/#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
Run Code Online (Sandbox Code Playgroud)

笔记:

  • 代码示例假定ffmpegffprobe位于可执行路径中。

示例输出:

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
Run Code Online (Sandbox Code Playgroud)