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