Screenstudio alt skill
screenstudio-alt — an open-source headless Screen Studio alternative (Claude Code skill): auto-zoom, idle speed-up, vertical export.
npx -y skills add connerkward/screenstudio-alt-skillAssembled from the repository path, not quoted from the project. Check it against their README if it does not work.
One thing to look at
- 8 stars8 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.
What its author says it does
Copied from the file, not written here
Screen-Studio-style post-production for screen recordings, headless — auto speed-up of idle, auto-zoom on click clusters, keystroke overlay chips, smoothed synthetic cursor, and 9:16 vertical export that follows the action. Use when polishing a screen recording / demo video for sharing, when the user mentions Screen Studio, auto-zoom, idle speed-up, or vertical/social video from a screen capture, and for any social-facing demo (vertical output is the default for those).
SKILL.md
8.0 KB, as published. Nobody here has run it
screenstudio-alt
The skill's code lives in this directory (polish.py, render.py, studio.py,
events-log.swift, test fixtures, etc.). Published publicly as
connerkward/screenstudio-alternative-skill.
Two components:
events-log(Swift) — capture-side input logger (cursor 60Hz, clicks, keys; drops keys during macOS secure input). Runs ONLY while recording. Needs Accessibility/Input Monitoring for the terminal. Auto-zoom/keys/cursor need this data at capture time — it cannot be recovered from pixels later.polish.py(Python, ffmpeg + PIL) — the post-production pass:
python3 src/polish.py in.mp4 --events in.events.jsonl \
--speedup # compress idle (input-gap ∩ frozen-pixels; animations stay 1x)
--zoom # eased auto-zoom on click clusters (zoompan)
--keys # accumulating keystroke chips (PIL overlays, no drawtext dep)
--smooth-cursor # synthetic eased cursor (best with sck-record --no-cursor)
--vertical # ALSO emit 1080x1920 following the action
--speedup works WITHOUT events (freezedetect only) — usable on the whole
existing dailies corpus.
render.py— high-quality non-destructive renderer (preferred): single-pass spring-physics camera over the original high-res frames, LANCZOS into a smaller target (crisp zoom, ~1.3× sharper than the ffmpeg upscale path), 60fps, H + 9:16 V. Tunable--freq/--zeta(spring),--fps,--target-w. Takes explicit--regions [{t0,t1,z,cx,cy}]— plus optionalcx1,cy1,z1end-keyframe per region: when present the camera pans (eases start→end across the held span,ease_traj).polish.pyis the older ffmpeg-filter fallback.studio.py [recording.mp4]— local web UI, NLE-style fixed-ruler timeline (bar = source duration, never rescales → upstream always planted): zoom regions are draggable blocks (move / retime edges / click to add / double-click delete). Selecting a zoom shows a draggable crop-rect overlay on the preview (reframe where the zoom looks: drag body = move cx/cy, corner handle = zoom level). + pan adds a second END keyframe → a two-keyframe camera move: start rect (solid) + end rect (dashed) + a path arrow on the preview, a[ Start | End ]toggle picks which the drag edits, and scrubbing interpolates the pan live (sameease_trajas the export). Idle spans are speed blocks with rate-only editing — source range locked, rate set via inspector slider on select or right-edge rate-stretch drag (FCP retime / Premiere Rate Stretch); rate changes ripple downstream only. Camera easing is smootherstep ease-in-out by default (full ease, no constant-velocity middle — cinematic slow-start/smooth-middle/gentle-stop on zoom AND pan); Advanced section exposes an easing curve selector (Smooth · Snappy · Linear) + smoothness/ramp slider (--curve/--ramp, persisted in session.json, preview == export). Tunable default zoom, aspect, frame styling. Always-smooth synthetic cursor + click ripple + real recorded click sound (CC0 #735771). Export uses render.py. Free port, local. (Keystroke overlay exists in the engine but is off by default.)
Easy path
screencast.sh --demo (screencast skill) does the whole chain: starts the event
logger, records, then polishes + emits the 9:16 vertical automatically. Vertical
is the DEFAULT for social-facing demos.
Gotchas (learned the hard way, kept here so they're not relearned)
- ffmpeg CANNOT do animated
scale=eval=frame→crop(link reinit wedges crop's per-frame exprs). That's why zoom useszoompan(notvar there — useon/FPS). - This machine's ffmpeg lacks
drawtext; all text/cursor overlays are PIL-rendered PNGs +overlay. - Test rig:
make-fixture.pysynthesizes a fake screen recording + ground-truth events.jsonl — validate any change against it before trusting real footage. - Do NOT use Playwright
recordVideoto capture the studio demo — its VFR→CFR conversion judders on the auto-zoom (a frozen frame wedged mid-motion); usetest/record-demo.jsinstead.
Recording the demo
test/record-demo.jsis the canonical smooth demo recorder: it steps the studio's logical timeline frame-by-frame against the live page (notrecordVideo), so every output frame is captured exactly once → perfect CFR, no judder. See docs/ARCHITECTURE.md for mechanism + usage.
Text callouts / keystroke chips — NEVER overlap or crowd
Callouts are read one at a time. Two text callouts must never overlap in output time, and must not be closely spaced — only one on screen at a time, with a clear gap (~0.3s+) between them so each is legible before the next appears. Rules:
- Anchor callouts CLEAR of the content they describe — don't park them on top of the search bar, nav, or the result/image being shown. A callout covering the thing it points at is worse than none.
- On long enough to read — ≥~1s of output time at the playback rate. Over a sped-up span, extend the callout's window so it survives the compression (a callout inside a 5× block is gone in a blink otherwise).
- Time the callout to lead or coincide with the moment it labels, never trail it.
(Burn 2026-06-18: five callouts set with overlapping windows, sitting on top of the results, too brief over a sped-up idle — read as clutter; the features didn't register.)
Get an objective critique — send the rendered demo to a VIDEO model (don't iterate blind)
Before handing a demo over, stop judging it from stills — send the actual rendered mp4 to a video-understanding model and act on its critique. Repeatedly, demos shipped with broken framing/pacing/loop because they were judged frame-by-frame, not as motion. Standing practice:
- Render the cut (
render.py). - Pick the model dynamically — do NOT hardcode one. At this moment, list the current
video-understanding (video-to-text) models with
fal search_models category="video-to-text"(use this, NOTrecommend_model— that one is popularity-ranked and returns video generators for a "video" task), look at what's actually available right now, and choose the strongest one for this job (a critique needs temporal/motion reasoning, so prefer a reasoning-capable VLM; e.g.openrouter/router/videopointed at the latest Gemini, or a native reasoning VLM likenvidia/nemotron-3-nano-omni/video). The right model changes over time — decide from the live list, not from a name written here. Bias slightly toward SOTA: when options are close, prefer the newest-generation / most-capable model (cost here is pennies, so favor capability over the cheap/older pick). - Upload the mp4 and run the chosen model with a harsh-critique
promptthat states the demo's PURPOSE + the 4 features + the region/speed/callout spec and asks for a blunt, timestamped, prioritized fix list covering: each zoom's framing (on the right thing? on dead UI?), pan smoothness (eased vs hard-cut), speed-up legibility (feature vs glitch), callout placement/timing/overlap, overall pacing, and clean loop (last frame ≈ first). A stills-only fallback (image VQA on a labeled contact sheet) cannot judge motion, so it's a weak substitute, not the check.
This loop catches what self-review misses (e.g. Gemini flagged a broken loop + a hard-cut "pan" + a zoom landing on the Search button, all of which had passed self-inspection). Don't skip it for a demo that's going public.