当它是唯一正在进行的进程时,我能够优雅地终止 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'
出现异常是因为FFmpeg在
progress_text = procs.stdout.readline()
期间关闭。
因为
readline()
是“阻塞”命令,所以没有避免异常的选项。如果我们想避免异常,我们必须避免在
readline()
等待数据时关闭 FFmpeg。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')