如何优雅地终止 ffmpeg 进程和 ffprobe 进程?

问题描述 投票:0回答:1

当它是唯一正在进行的进程时,我能够优雅地终止 ffmpeg 进程。现在我还有 ffprobe 进程和跟踪 ffmpeg 进程进度的 ffmpeg 进程。当我尝试取消该过程时,它会抛出以下异常。我试图将语句放在 try & except 块中,但我认为这不是一个好的解决方案。完成这份工作的正确方法是什么?

附言代码可能有点混乱,但我试图从我的实际代码中创建一个小的可执行形式,对此感到抱歉。

import subprocess as sp
import shlex
import json
import time
import threading

def start_ffmpeg_thread(audio_part, video_part, path):

    global ffmpeg_process
    if (ffmpeg_process is None) or ffmpeg_process.poll():
        data = sp.run(shlex.split(f'ffprobe -v error -select_streams v:0 -count_packets -show_entries stream=nb_read_packets -of csv=p=0 -of json "{video_part}"'), stdout=sp.PIPE).stdout
        dict = json.loads(data)
        tot_n_frames = float(dict['streams'][0]['nb_read_packets'])

        ffmpeg_process = sp.Popen(shlex.split(f'ffmpeg -y -loglevel error -i "{video_part}" -i "{audio_part}" -progress pipe:1 "{path}"'), stdout=sp.PIPE, stdin=sp.PIPE)

        q = [0]

        ffmpeg_progress_reader_thread = threading.Thread(target=ffmpeg_progress_reader, args=(ffmpeg_process, q))
        ffmpeg_progress_reader_thread.start()

        while True:
            if ffmpeg_process.poll() is not None:
                break

            n_frame = q[0]
            progress_percent = (n_frame/tot_n_frames)*100
            print(f"Progress: [%] {progress_percent}", end="\r")
        ffmpeg_progress_reader_thread.join()

def ffmpeg_progress_reader(procs, q):

    while True:
        if procs.poll() is not None:
            break

        progress_text = procs.stdout.readline()
        progress_text = progress_text.decode("utf-8")
        if progress_text.startswith("frame="):
            frame = int(progress_text.partition('=')[-1])
            q[0] = frame

def cancel_ffmpeg():

    time.sleep(10)
    global ffmpeg_process
    if (ffmpeg_process is not None) and (ffmpeg_process.poll() is None):
            ffmpeg_process.stdin.write('q'.encode("GBK"))
            ffmpeg_process.communicate()
            ffmpeg_process.wait()
            ffmpeg_process = None


ffmpeg_process = None

threading.Thread(target=cancel_ffmpeg).start()
start_ffmpeg_thread(<<AUDIO_FILE_FULL_PATH>>, <<VIDEO_FILE_FULL_PATH>>, <<OUTPUT_FULL_PATH>>)
Exception in thread Thread-2 (ffmpeg_progress_reader):
Traceback (most recent call last):
  File "D:\Python311\Lib\threading.py", line 1038, in _bootstrap_inner
    self.run()9.354796147248976
  File "D:\Python311\Lib\threading.py", line 975, in run
    self._target(*self._args, **self._kwargs)
  File "d:\Python Projects\main.py", line 82, in ffmpeg_progress_reader
    progress_text = procs.stdout.readline()
                    ^^^^^^^^^^^^^^^^^^^^^^^
ValueError: PyMemoryView_FromBuffer(): info->buf must not be NULL
Traceback (most recent call last):
  File "d:\Python Projects\main.py", line 101, in <module>
    start_ffmpeg_thread("aud.mp3", "vid.mp4", "output.mp4")
  File "d:\Python Projects\main.py", line 69, in start_ffmpeg_thread
    if ffmpeg_process.poll() is not None:
       ^^^^^^^^^^^^^^^^^^^
AttributeError: 'NoneType' object has no attribute 'poll'
python ffmpeg subprocess ffprobe ffmpeg-python
1个回答
0
投票

出现异常是因为FFmpeg在

progress_text = procs.stdout.readline()
期间关闭。

因为

readline()
是“阻塞”命令,所以没有避免异常的选项。
在这种情况下,使用 try & except 是一个有效的解决方案。


如果我们想避免异常,我们必须避免在

readline()
等待数据时关闭 FFmpeg。
“代价”是closing不会立即响应,而是等到
readline()
返回。

为了优雅地关闭,我们可以使用

threading.Event()
对象。

  • 在启动线程之前设置事件:
    event.set()
    .
  • ffmpeg_progress_reader
    线程检查
    if not event.is_set()
    每次迭代,如果事件被“清除”则中断循环。
  • ffmpeg_progress_reader
    线程之前的最后一个命令是
    event.set()
    .
  • 取消时:
    • 清除事件:
      event.clear()
      .
    • 等待事件设置:
      event.wait()
      .
      event.wait()
      正在等待
      ffmpeg_progress_reader
      线程完成(记住 - 最后一个命令是
      event.set()
      )。
      event.wait()
      返回时,我们可以关闭 FFmpeg 子进程。

代码示例:

import subprocess as sp
import shlex
import json
import time
import threading

def start_ffmpeg_thread(audio_part, video_part, path):

    global ffmpeg_process
    if (ffmpeg_process is None) or ffmpeg_process.poll():
        data = sp.run(shlex.split(f'ffprobe -v error -select_streams v:0 -count_packets -show_entries stream=nb_read_packets -of csv=p=0 -of json "{video_part}"'), stdout=sp.PIPE).stdout
        dict = json.loads(data)
        tot_n_frames = float(dict['streams'][0]['nb_read_packets'])

        ffmpeg_process = sp.Popen(shlex.split(f'ffmpeg -y -loglevel error -i "{video_part}" -i "{audio_part}" -progress pipe:1 "{path}"'), stdout=sp.PIPE, stdin=sp.PIPE)

        q = [0]

        ffmpeg_progress_reader_thread = threading.Thread(target=ffmpeg_progress_reader, args=(ffmpeg_process, q))
        ffmpeg_progress_reader_thread.start()

        while True:
            if (ffmpeg_process is None) or (ffmpeg_process.poll() is not None):
                break

            n_frame = q[0]
            progress_percent = (n_frame/tot_n_frames)*100
            print(f"Progress: [%] {progress_percent}", end="\r")
        ffmpeg_progress_reader_thread.join()


def ffmpeg_progress_reader(procs, q):
    while True:
        if procs.poll() is not None:
            break

        progress_text = procs.stdout.readline()
        progress_text = progress_text.decode("utf-8")
        if progress_text.startswith("frame="):
            frame = int(progress_text.partition('=')[-1])
            q[0] = frame

        if not event.is_set():
            break  # Break the loop when event is clear

    event.set()  # Marks that the thread is finished.


def cancel_ffmpeg():
    time.sleep(10)

    # Clear event - causes ffmpeg_progress_reader loop to break
    event.clear()

    # Wait for ffmpeg_progress_reader thread to end (the last command of ffmpeg_progress_reader is event.set()).
    event.wait(3)  # Wait with timeout of 3 seconds (just in case...).

    # Close FFmpeg only after ffmpeg_progress_reader is ended
    global ffmpeg_process
    if (ffmpeg_process is not None) and (ffmpeg_process.poll() is None):
            ffmpeg_process.stdin.write('q'.encode("GBK"))
            ffmpeg_process.communicate()
            ffmpeg_process.wait()
            ffmpeg_process = None


event = threading.Event()
event.set()  # Initialize event to "set" state.
ffmpeg_process = None

threading.Thread(target=cancel_ffmpeg).start()
start_ffmpeg_thread('BigBuckBunny.mp4', 'BigBuckBunny.mp4', 'tmp0.mp4')
© www.soinside.com 2019 - 2024. All rights reserved.