plesty.lib.ui.qt.recorder
Recording a shell: one captured frame per refresh, encoded on stop.
A measurement that took two hours is shown to a colleague, a supervisor, or a paper reviewer as a video of the run building up — the same arrangement of panels the operator watched, at whatever speed makes it readable. Recording therefore belongs to the window, not to any one view: whatever is docked gets captured, including panels added halfway through.
Capture is a window grab on the GUI thread, which is cheap and, more importantly, needs no cooperation from the panels. Frames are size-locked to the first capture, so resizing or floating a panel mid-recording cannot corrupt the stream — the resized window is letterboxed into the frame it started with.
MP4 is written through imageio (with imageio-ffmpeg) when both are
installed, and an animated GIF through Pillow otherwise, so a recording is
always produced:
uv add "plesty-lib[record]"
Attributes
Classes
Capture a window while active; encode a video file on stop. |
Functions
|
Grab widget as an RGB array. |
|
Letterbox frame into shape, so every frame of a video matches. |
Module Contents
- plesty.lib.ui.qt.recorder.DEFAULT_FPS = 10
- plesty.lib.ui.qt.recorder._to_array(widget: PySide6.QtWidgets.QWidget) numpy.ndarray
Grab widget as an RGB array.
- Parameters:
widget (PySide6.QtWidgets.QWidget) – The window (or panel) to capture.
- Returns:
The pixels as a
(height, width, 3)uint8 array.- Return type:
numpy.ndarray
- plesty.lib.ui.qt.recorder._fit(frame: numpy.ndarray, shape: tuple[int, int]) numpy.ndarray
Letterbox frame into shape, so every frame of a video matches.
- Parameters:
frame (numpy.ndarray)
shape (tuple[int, int])
- Return type:
numpy.ndarray
- class plesty.lib.ui.qt.recorder.Recorder(widget: PySide6.QtWidgets.QWidget, path: str | pathlib.Path, *, fps: int = DEFAULT_FPS, every: int = 1)
Capture a window while active; encode a video file on stop.
shell.start_recording("run.mp4", fps=10) ... # the shell captures on every tick path = shell.stop_recording() # → run.mp4, or run.gif without ffmpeg
Prepare a recording of widget.
- Parameters:
widget (PySide6.QtWidgets.QWidget) – The window to capture — normally the shell itself.
path (str | pathlib.Path) – Destination file; the suffix is corrected to what the available encoder can actually write.
fps (int) – Frame rate of the encoded video.
every (int) – Capture one frame per every refreshes, for a run whose rows arrive faster than a video needs them.
- Raises:
ValueError – If fps or every is not positive.
- widget
- path
- fps = 10
- every = 1
- active = False
- frames: list[numpy.ndarray] = []
- _shape: tuple[int, int] | None = None
- _seen = 0
- start() None
Begin capturing; a restart discards what was captured before.
- Return type:
None
- stop() pathlib.Path | None
Stop capturing and encode what was captured.
- Returns:
The written file, or
Nonewhen nothing was captured — a recording that never saw a frame writes no file rather than an empty one.- Return type:
Optional[pathlib.Path]
- _encode() pathlib.Path
Write the captured frames, preferring MP4 and falling back to GIF.
- Return type:
pathlib.Path
- _encode_gif() pathlib.Path
Write an animated GIF, the fallback that needs only Pillow.
- Return type:
pathlib.Path
- describe() dict[str, Any]
Return what has been captured so far.
- Returns:
Whether the recorder is active, the frame count, and the target.
- Return type:
dict[str, Any]