Screenstudio alt skill
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).From its SKILL.md
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.
2 things 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.
- runs commandsInstructs the agent to run 7 commands, including `python3 src/polish.py in.mp4 --events in.events.jsonl --speedup --zoom --keys --smooth-cursor --vertical` and 6 more.
SKILL.md
8.0 KB, ~2.0k tokens by cl100k_base, 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.
What ships with it: 22 files
3661.3 KB alongside SKILL.md, 8 of them executable
.claude-plugin/
- marketplace.json779 B
- plugin.json733 B
assets/
- click.wav7.8 KB
- CREDITS.txt198 B
docs/
- ARCHITECTURE.md10.7 KB
- demo.gif1738.5 KB
- features.gif1628.9 KB
- logo.svg374 B
src/
- events-log.swift10.1 KB
- fcpxml.pyruns30.3 KB
- iteration.pyruns3.0 KB
- polish.pyruns30.8 KB
- render.pyruns34.9 KB
- sequence.pyruns43.5 KB
- studio.pyruns104.1 KB
test/
- make-fixture.pyruns6.1 KB
- record-demo.jsruns4.3 KB
- .gitignore105 B
- LICENSE1.0 KB
- llms.txt647 B
- README.md4.6 KB
- requirements.txt25 B