I am using GLMakie.jl in a GUI application (RainbowAlga.jl) which is an interactive event display for neutrino interactions in water-based Cherenkov detectors. The application works really nicely but I am currently stuck on the most-requested features: video recording.
What I would like to implement is toggling video recording with a keypress, e.g. pressing V to start recording and pressing V again to stop and save the video. During these two events, the user should be able to interact with the display.
OK, long story short, the following MWE resembles the structure of my application, creates a window, a basegrid and rotates the camera in the renderloop. There are two keyboard events registered:
Pto save a screenshot (works fine, just like in the original app)Vto start/top video recording → immediately crashing when creatingVideoStream(scene)and after a while it shows aStackOverflow
How can I achieve this functionality? Starting/stopping live video recording while viewing the scene…
using GLMakie
"""
A non-evel global state to manage the video recording.
"""
mutable struct VideoRecordingState
isrecording::Bool
videostream::Union{VideoStream, Nothing}
end
const GLOBAL_VIDEO_RECORDING_STATE = Ref{Union{VideoRecordingState, Nothing}}(nothing)
function global_vrs()
if isnothing(GLOBAL_VIDEO_RECORDING_STATE[])
GLOBAL_VIDEO_RECORDING_STATE[] = VideoRecordingState(false, nothing)
end
GLOBAL_VIDEO_RECORDING_STATE[]
end
function main()
scene = Scene(backgroundcolor=RGBf(1.0))
cam = cam3d!(scene, rotation_center = :lookat)
screen = display(GLMakie.Screen(start_renderloop=true, focus_on_show=true), scene)
basegrid!(scene) # against whiteout
register_events(scene)
on(screen.render_tick) do tick
rotate_cam!(scene, Vec3f(0, 0.001, 0))
if global_vrs().isrecording
# can't even reach this block due to the stackoverflow caused by VideoStream(scene)
recordframe!(scene)
end
end
wait(screen)
end
"""
Registers callbacks for user inputs.
"""
function register_events(scene)
on(events(scene).keyboardbutton) do event
if ispressed(scene, Makie.Keyboard.p)
fname = "screenshot.png"
save(fname, scene)
println("Screenshot saved as '$(fname)'")
return Consume()
end
if ispressed(scene, Makie.Keyboard.v)
vrs = global_vrs()
if !vrs.isrecording
println("Video recording started")
vrs.isrecording = true
vrs.videostream = VideoStream(scene) # stackoverflow!
else
println("Video recording stopped")
vrs.isrecording = false
# not sure how to close properly, for now just GC-feeding it
save("movie.mp4", vrs.videostream)
vrs.videostream = nothing
end
end
end
end
"""
Draws a basegrid into the scene's xy-plane.
"""
function basegrid!(scene; center=(0, 0, 0), span=(-1, 1), spacing=0.1, linewidth=1, color=(:grey, 0.3))
min, max = span
center = Point3f(center)
for q ∈ range(min, max; step=spacing)
lines!(scene, [Point3f(q, min, 0) + center, Point3f(q, max, 0) + center], color=color, linewidth=linewidth)
lines!(scene, [Point3f(min, q, 0) + center, Point3f(max, q, 0) + center], color=color, linewidth=linewidth)
end
scene
end
main()
Btw. I already created scripts which programmatically handle the camera movement and export videos (see https://www.youtube.com/watch?v=5pOz4qdnS1s) but for that, I had to juggle around with the scene creation since the GUI was not required.
Instead of using screen = display(GLMakie.Screen(start_rendeloop=true, ...), I had to create a Figure with the desired dimensions, create an LScene and then replacing it’s scene with my own renderer, like
fig = Figure(size = (3840, 2160), figure_padding = 0, backgroundcolor = bgcolor)
lscene = LScene(fig[1, 1]; show_axis = false, scenekw = (; size = (3840, 2160)))
scene = lscene.scene
This worked prefectly fine with a record(fig, ...) do t block:
record(fig, "uhe_animation.mp4", time_iterator; framerate = framerate) do t
cam_lookat =
cam_lookat_start + frame_idx / nframes * (cam_lookat_end - cam_lookat_start)
cam_pos = cam_pos_start + frame_idx / nframes * (cam_pos_end - cam_pos_start)
update_cam!(scene, cam, cam_pos, cam_lookat, Vec3f(0, 0, 1))
# some additional screen manipulation logic...
frame_idx += 1
next!(p)
end

