I'm working on a cancel button that cancels the ffmpeg video & audio merging process when pressed. But while loop doesn't keep looping once ffmpeg starts execution, while loop continues to loop after ffmpeg finished the process. I couldn't really figure it out, sorry if it's duplicate.
I know the code looks really silly but I'm kinda doomed, any help will be greatly appreciated. Thanks in advance.
from tkinter import *
import ffmpeg
import threading
def start_ffmpeg_thread(audio_part, video_part, path):
threading.Thread(target=start_ffmpeg, args=(audio_part, video_part, path)).start()
def start_ffmpeg(audio_part, video_part, path):
while True:
if is_cancelled:
break
threading.Thread(target=ffmpeg_func, args=(audio_part, video_part, path)).start()
def ffmpeg_func(audio_part, video_part, path):
ffmpeg.output(audio_part, video_part, path).run(overwrite_output=True)
def cancel_ffmpeg():
global is_cancelled
is_cancelled = True
is_cancelled = False
root = Tk()
video_part = ffmpeg.input("<path_video_part>")
audio_part = ffmpeg.input("<path_audio_part>")
path = "<path>"
button_1 = Button(root, text="Start", command=lambda: start_ffmpeg_thread(audio_part, video_part, path))
button_1.pack(pady=30, padx=30)
button_2 = Button(root, text="Stop", command=cancel_ffmpeg)
button_2.pack(pady=30, padx=30)
root.mainloop()
CodePudding user response:
For gracefully closing FFmpeg sub-process, we may write 'q'
to stdin
pipe of FFmpeg sub-process as described in my following answer.
In order to have it possible to write 'q'
, we may execute FFmpeg sub-process as follows:
ffmpeg_process = ffmpeg.output(audio_part, video_part, path).overwrite_output().run_async(pipe_stdin=True)
ffmpeg_process
allows us to access the sub-process and terminate it.
For keeping the code simple, we may declare ffmpeg_process
as global
variable.
Updated ffmpeg_func
method:
def ffmpeg_func(audio_part, video_part, path):
global ffmpeg_process
ffmpeg_process = ffmpeg.output(audio_part, video_part, path).overwrite_output().run_async(pipe_stdin=True)
Updated start_ffmpeg
method:
def start_ffmpeg(audio_part, video_part, path):
global ffmpeg_process
# Allow only one instance of FFmpeg to be executed - ffmpeg_process = None in the first time, and ffmpeg_process.poll() is not None when FFmpeg is not running
if (ffmpeg_process is None) or ffmpeg_process.poll():
threading.Thread(target=ffmpeg_func, args=(audio_part, video_part, path)).start()
The above code allows executing FFmpeg only if it's not running.
Updated cancel_ffmpeg
method:
def cancel_ffmpeg():
global ffmpeg_process
#Check if FFmpeg sub-process is running
if (ffmpeg_process is not None) and (ffmpeg_process.poll() is None):
# Terminate FFmpeg gracefully
ffmpeg_process.stdin.write('q'.encode("GBK")) # Simulate user pressing 'q' key
ffmpeg_process.communicate()
ffmpeg_process.wait()
ffmpeg_process = None
Note:
Your implementation of start_ffmpeg
executes threading.Thread
in a loop, and that executes FFmpeg sub-process in a loop.
def start_ffmpeg(audio_part, video_part, path):
while True:
if is_cancelled:
break
threading.Thread(target=ffmpeg_func, args=(audio_part, video_part, path)).start()
We have to remove the while True
from the above method.
Updated code sample:
from tkinter import *
import ffmpeg
import threading
def start_ffmpeg_thread(audio_part, video_part, path):
threading.Thread(target=start_ffmpeg, args=(audio_part, video_part, path)).start()
def start_ffmpeg(audio_part, video_part, path):
#while True:
# if is_cancelled:
# break
global ffmpeg_process
# Allow only one instance of FFmpeg to be executed - ffmpeg_process = None in the first time, and ffmpeg_process.poll() is not None when FFmpeg is not running
if (ffmpeg_process is None) or ffmpeg_process.poll():
threading.Thread(target=ffmpeg_func, args=(audio_part, video_part, path)).start()
def ffmpeg_func(audio_part, video_part, path):
global ffmpeg_process
#ffmpeg.output(audio_part, video_part, path).run(overwrite_output=True)
ffmpeg_process = ffmpeg.output(audio_part, video_part, path).overwrite_output().run_async(pipe_stdin=True)
def cancel_ffmpeg():
#global is_cancelled
#is_cancelled = True
global ffmpeg_process
#Check if FFmpeg sub-process is running
if (ffmpeg_process is not None) and (ffmpeg_process.poll() is None):
# Terminate FFmpeg gracefully
ffmpeg_process.stdin.write('q'.encode("GBK")) # Simulate user pressing 'q' key
ffmpeg_process.communicate()
ffmpeg_process.wait()
ffmpeg_process = None
#is_cancelled = False
ffmpeg_process = None
root = Tk()
video_part = ffmpeg.input("bunny_1080p_60fps.mp4")
audio_part = ffmpeg.input("bunny_1080p_60fps.mp4")
path = "output.mp4"
button_1 = Button(root, text="Start", command=lambda: start_ffmpeg_thread(audio_part, video_part, path))
button_1.pack(pady=30, padx=30)
button_2 = Button(root, text="Stop", command=cancel_ffmpeg)
button_2.pack(pady=30, padx=30)
root.mainloop()
In case you are planning to allow multiple instances of FFmpeg sub-processes, you may insert the ffmpeg_process
into a list, for keeping track over all the sub-processes.
For closing all the sub-processes at once, just iterate the list.