spacr.qt.tutorial.engine

Tutorial rendering engine — narration, cursor overlay, capture, mux.

The pipeline is intentionally linear and easy to reason about:

  1. Synthesize each Step’s narration through Piper → WAV, know duration

  2. Spin up the MainWindow (on real DISPLAY or under Xvfb) at 1920x1080

  3. Walk through the Steps, capturing frames at 30 fps into a scratch dir. Each step gets ceil(narration_s * 30) + hold frames budget. A synthesized cursor (little arrow drawn onto each frame) animates to each step’s target widget before the step’s action fires.

  4. Concatenate all step WAVs → one audio track

  5. ffmpeg -framerate 30 -i frames/%06d.png -i audio.wav … out.mp4

  6. Emit matching .srt sidecar

The engine has no Qt-specific business logic — that lives in per-app scripts.py functions that return a list of Steps.

Module Contents

spacr.qt.tutorial.engine.LOG[source]
spacr.qt.tutorial.engine.FRAME_RATE = 30[source]
spacr.qt.tutorial.engine.VIDEO_SIZE = (1920, 1080)[source]
spacr.qt.tutorial.engine.CURSOR_MOVE_FRAMES = 12[source]
spacr.qt.tutorial.engine.DEFAULT_HOLD_MS = 500[source]
spacr.qt.tutorial.engine.DEFAULT_VOICE[source]
class spacr.qt.tutorial.engine.Step[source]

A single narrated beat of a tutorial.

Fields:

narration: what the narrator says (also becomes the subtitle) action: optional callable that mutates the UI. Runs AFTER

the cursor animation completes but BEFORE the narration finishes playing.

target: optional (widget, point-in-widget) the cursor

animates to before the action fires. Point is relative to widget. Pass a QWidget with point omitted to target its center.

hold_ms: extra silence at the end of the step, in ms.

Useful to let a UI change settle before the next step begins.

highlight: optional widget to draw a soft highlight ring

around while this step runs.

narration: str[source]
action: Callable[[], None] | None = None[source]
target: Tuple[Any, Tuple[int, int] | None] | None = None[source]
hold_ms: int = 500[source]
highlight: Any | None = None[source]
class spacr.qt.tutorial.engine.Narrator(voice_model: pathlib.Path | None = None, length_scale: float = 1.0)[source]

Synthesize step narration WAVs using Piper.

Uses the Piper CLI (already installed via pip install piper-tts). Voice model defaults to ~/.spacr/piper/en_US-lessac-medium.onnx but any Piper .onnx can be passed via voice_model=.

voice_model[source]
length_scale = 1.0[source]
synth(text: str, out_wav: pathlib.Path) float[source]

Synthesize text into out_wav. Returns duration in seconds.

class spacr.qt.tutorial.engine.Recorder(window, frames_dir: pathlib.Path, fps: int = FRAME_RATE, size: Tuple[int, int] = VIDEO_SIZE)[source]

Grab the MainWindow’s rendered pixmap N times a second, compositing a synthetic cursor onto each frame.

Parameters:
  • window – source Qt window to grab.

  • frames_dir – destination folder for numbered PNG frames.

  • fps – capture frame rate.

  • size – fixed output frame size (width, height) in px.

window[source]
frames_dir[source]
fps = 30[source]
size = (1920, 1080)[source]
frame_idx = 0[source]
cursor_pos: Tuple[float, float][source]
snap(cursor_pos: Tuple[float, float] | None = None, highlight_rect: Tuple[int, int, int, int] | None = None) pathlib.Path[source]

Grab one frame, save as PNG, return its path.

class spacr.qt.tutorial.engine.RenderResult[source]

Output paths and metadata for a completed tutorial render.

Variables:
  • mp4 – absolute path to the produced MP4.

  • srt – absolute path to the produced SRT sidecar.

  • frames – total number of frames captured.

  • duration_s – total narration duration in seconds.

mp4: pathlib.Path[source]
srt: pathlib.Path[source]
frames: int[source]
duration_s: float[source]
class spacr.qt.tutorial.engine.Director(window, steps: List[Step], out_dir: pathlib.Path, narrator: Narrator | None = None, fps: int = FRAME_RATE)[source]

Orchestrates narration, capture, and mux into a final MP4 + SRT.

Parameters:
  • window – live MainWindow the tutorial drives.

  • steps – ordered list of Step beats.

  • out_dir – destination folder for the rendered mp4/srt.

  • narrator – optional Narrator; a default is built if omitted.

  • fps – capture frame rate.

window[source]
steps[source]
out_dir[source]
narrator[source]
fps = 30[source]
render(name: str) RenderResult[source]

Run the full narrate → capture → mux pipeline for this director.

Parameters:

name – base filename for the produced <name>.mp4 and .srt.

Returns:

RenderResult with paths and duration metadata.

spacr.qt.tutorial.engine.render_tutorial(app_key: str, out_dir: pathlib.Path | None = None, voice_model: pathlib.Path | None = None, length_scale: float = 1.0) RenderResult[source]

Boot MainWindow, run the per-app tutorial script, render MP4.

Returns a RenderResult describing where the MP4 and SRT ended up.