Problem
If there isn't a window handle associated with a process, it's unable to catch the WM_CLOSE event that taskkill sends when it's used without the /F flag.
Send WM_CLOSE message to a process with no window
taskkill, when used without the /F flag, sends a WM_CLOSE or WM_QUIT message to the window handle associated with the pid you use when calling taskkill
Difference between taskkill and taskkill /f
Note that I'm on Windows 10 so I have to catch WM_CLOSE instead of WM_QUIT
You can test the problem yourself by opening up a command prompt and running ffmpeg, then trying to taskkill /PID {pid} or taskkill /IM ffmpeg.exe. ffmpeg will continue to encode/record and taskkill will tell you it successfully killed the process.
ffmpeg.exe -rtbufsize 150M -f gdigrab -framerate 30 -offset_x 448 -offset_y 240 -video_size 1024x600 -draw_mouse 1 -show_region 1 -i desktop -r 30 -preset ultrafast -tune zerolatency -movflags +faststart output.mp4
Here is a demo of trying to shut down an ffmpeg process with taskkill. The default behavior for taskkill is unhandled because ffmpeg does not create a window handle that can have the WM_CLOSE event sent to it.
https://i.stack.imgur.com/d5v6l.jpg
^ I used my custom ffmpeg wrapper to catch the WM_CLOSE event and shut down ffmpeg gracefully to record this clip.
Solutions
One way to associate a window with any executable on Windows is to use the start command:
start "Your window title" program.exe -option1 -option2
You can then use taskkill to send the WM_CLOSE event, which can be intercepted, but ffmpeg doesn't actually catch the event because it wasn't designed to catch it in the first place.
To solve this in nim, you can use a combination of 3 additional modules to create a window handle, start/monitor a process, and intercept the WM_CLOSE event and write "q" to the ffmpeg process's stdout.
wNim
winim
nimpy
In other versions of windows taskkill sends the WM_QUIT event, so you may need to handle that as well.
Create the window handle with wNim; winim is only used to get access to the WM_CLOSE constant that's used for the wNim event handler.
import os
import wnim
import winim
import nimpy
# Put python3.8.dll into following folder. It is required by nimpy
# C:/Users/username/AppData/Local/Microsoft/WindowsApps
let app = App()
let frame = Frame(title="ffmpeg", size=(400, 300)) # Size doesn't matter because we never show the frame.
Load a python3.8.dll file using nimpy so you can use the subprocess module from python. (easier to use than the builtin nim multiprocessing library imho)
# This is used to check if ffmpeg has been launched
var running_process = false
# The process must be a PyObject, not a Process object from nim
var the_proc: PyObject
# Get reference to the subprocess module
let subprocess = pyImport("subprocess")
# Get reference to python builtin modules
let py = pyBuiltinsModule()
# Get a reference to the subprocess PIPE
let pipe = subprocess.PIPE
Create event handlers. These will handle the app first starting and the WM_CLOSE event in windows.
# We catch the WM_MOVE event and start the process.
# We force the event to trigger when we center
# the frame and start the app.
frame.connect(WM_MOVE) do (event: wEvent):
# Make sure we only start 1 process.
if running_process == false:
running_process = true
# Create command and start the process.
var command_str: string = paramStr(1)
for i in 2..paramCount():
command_str = command_str & " " & paramStr(i)
# Use python subprocess module to execute command and set the stdin to the pipe.
the_proc = subprocess.Popen(command_str, stdin=pipe)
# When WM_CLOSE is triggered from taskkill, write the utf-8 encoded "q"
# to the ffmpeg process
frame.connect(WM_CLOSE) do (event: wEvent):
var command = "q"
# Make sure objects are all safe to use
# They should by python objects. Call the
# standard library to create the python objects.
var pycommand = py.str.encode(py.str(command), "utf-8")
# With a python process you call `communicate` on
# the process when you want it to wait to complete.
# Since ffmpeg just needs the "q" character we send
# the utf-8 encoded "q" with the `input` keyword
discard the_proc.communicate(input=pycommand)
sleep(1000)
# Get rid of process and quit.
discard the_proc.terminate()
quit(0)
Then all that needs to be done is centering the frame and starting the app.
frame.center()
app.mainLoop()
Remember that this uses the python standard library, so you need to put a python dll into the following folder for nimpy to get access.
C:/Users/username/AppData/Local/Microsoft/WindowsApps
Here is a repository with everything you need if you happen to run into the same issue:
https://github.com/Wykleph/ffmpeg_wrapper