Cómo usar
- Copia el comando y revisa el paquete antes de instalarlo en tu entorno de agente.
- Consulta las herramientas, credenciales y entornos necesarios en el documento original.
- Describe las entradas y el resultado deseado; contrasta la salida con las fuentes.
npx skills add https://github.com/moh4696/open-3d-skills --skill frame-captureAntes de empezar
- La instalación no incluye suscripción al modelo, cuota API ni permisos sobre tus archivos.
- Las estrellas e instalaciones reflejan actividad, no una prueba independiente de calidad o seguridad.
Contenido del repositorio de origen con títulos, ejemplos, código, tablas, enlaces e imágenes preservados.
Frame capture
Screen recorders capture whatever the GPU managed to draw in real time, so a heavy scene stutters and a scroll-through looks hand-held. This skill records differently. Before the page's own scripts run, its clock is replaced: performance.now, Date.now and requestAnimationFrame read a virtual time, CSS animations and transitions are pinned to it, and every <video> is paused and seeked to it. Each output frame advances the clock by exactly 1/60 s, runs one frame, and takes a screenshot. A frame can take two seconds to render and the video still plays at a perfect 60 fps.
Tools
| Script | What it does |
|---|---|
scripts/record.py | Records a URL to MP4 with scroll keys, pointer motion and a ready condition |
scripts/compare.py | Stacks two clips (or sets them side by side) with labels and rounded panels |
scripts/serve.py | Static server that answers HTTP Range requests, so videos can seek |
Needs Python 3.9+, pip install playwright pillow, python3 -m playwright install chromium webkit, and ffmpeg on PATH.
Run
- Serve the page with `scripts/serve.py`, never
python3 -m http.server. The built-in server ignores Range requests. The browser then cannot seek any video it serves (video.seekableis empty) and every recorded video holds its first frame.record.pywarns when it sees this.
python3 scripts/serve.py path/to/site --port 8000- Make a fast timing pass first. Ten frames a second shows the choreography in seconds instead of minutes:
python3 scripts/record.py http://127.0.0.1:8000/ /tmp/check.mp4 --seconds 20 --fps 8 \
--scroll "0:0 2.5:0 9:40% 12:40% 19:100%" --pointer driftWatch it, adjust the keys, repeat. Only then render the full clip.
- Render the final.
--sizeis the CSS viewport and--dprmultiplies it.--size 1920x1080 --dpr 1makes 1080p;--size 1280x720 --dpr 1.5makes the same 1920x1080 with a larger layout. Output is H.264, CRF 16, yuv420p, BT.709 tags, faststart, no audio.
python3 scripts/record.py http://127.0.0.1:8000/ out.mp4 --seconds 20 --fps 60 \
--scroll "0:0 2.5:0 9:40% 12:40% 19:100%" --pointer drift --ready "window.__ready === true"- Look at the result before handing it over. Pull frames at the moments that matter (
ffmpeg -ss 9 -i out.mp4 -frames:v 1 f.png) and check them: first frame, every scroll stop, the last frame.
Choreography
--scrollkeys areseconds:position. Positions are pixels (1200), a share of the scroll range (40%) or viewport heights (2.5vh). Segments ease (half linear, half smootherstep, so a long move never sprints in the middle). Add~after the time for a linear segment:9~:60%.- Hold on anything the viewer needs to read: give a pinned section or a counter its own key pair with the same position.
--pointer driftcircles the real mouse slowly round the centre, so hover states, parallax and cursor-following rigs come alive.--pointer 0.7,0.3parks it.- For choreography the page knows best (camera flights, a tour), let the page drive itself: define
window.__recordFrame = (t) => { ... }behind a?record=1flag. It is called with the virtual time in seconds before each frame.window.__recordingis true while recording. - Smooth-scroll libraries fight a recorder that sets the scroll position.
--kill-lenisstops Lenis from loading. For others, switch them off under?record=1.
Make the page recordable
- Time comes from `performance.now()` or the rAF timestamp, never from `setTimeout` or `setInterval`. Timers keep real time while the clock is virtual, so a
setTimeout(next, 3000)fires after three real seconds, which might be frame 20 or frame 400. Schedule on the page clock instead:
const pending = [];
const later = (secs, fn) => pending.push({ at: performance.now() / 1000 + secs, fn });
// in the frame loop:
const now = performance.now() / 1000;
for (let i = pending.length - 1; i >= 0; i--) if (now >= pending[i].at) pending.splice(i, 1)[0].fn();- Expose a ready flag (
window.__ready = trueafter the first rendered frame, textures decoded, fonts loaded) and pass it as--ready. Recording before assets land bakes a loading flash into frame 0. - In record mode, hide UI that would not be in a film: hints, cursors, loaders, cookie bars.
- If WebGL frames come out black or stale, create the renderer with
preserveDrawingBuffer: truewhen recording.
Browsers
- Chromium (default) runs WebGL on the real GPU headless. On macOS the script adds
--use-angle=metal, which renders at full speed; without it Chromium falls back to SwiftShader, which is correct but many times slower. - WebKit (
--browser webkit) shows how Safari renders: different font smoothing, colour handling and video decoding. Its screenshots occasionally stall at 1080p (about 1 in 100); the script retries, and because the clock is paused between steps the retry returns the identical frame. - If Playwright's own Chromium is missing (a version mismatch after an upgrade), the script finds the newest Chromium in the Playwright cache.
CHROME_PATHoverrides it. - Never record in a visible browser tab you are not looking at, and never in a hidden embedded preview pane. Background tabs throttle or freeze
requestAnimationFrame. Headless does not.
Comparisons
python3 scripts/compare.py a.mp4 b.mp4 versus.mp4 --labels "Model A" "Model B" # stacked, for phones
python3 scripts/compare.py a.mp4 b.mp4 versus.mp4 --labels "Before" "After" --layout side # side by sideA fair comparison holds everything equal except the thing being compared:
- Same viewport, same duration, same choreography (
--scrollkeys reused verbatim). - Crop each source to its content first. Strip watermarks, webcam overlays and UI chrome the other clip does not have.
- Silent output. Two soundtracks fight, and audio steals attention from the picture.
- The shorter clip holds its last frame, so both finish together.
- Name each side honestly in its label. Put the one the audience knows on top.
Delivery
- Social platforms re-encode everything. 1920x1080 or 1080x1920 at 60 fps, H.264, CRF 14 to 18 survives that best. Higher resolutions are often downscaled anyway.
- Keep the master. Make cut-downs (a 6 s loop, a vertical crop) from it with ffmpeg rather than re-recording.
- Add sound afterwards, timed to the frame numbers the recording printed. Never record audio through the virtual clock.

