What does reanimate-video do?
reanimate films a live page, rebuilds it in your code, films the copy and compares, round after round, until the numbers match. Its own path for a VIDEO reference works, but a first real run (6 Nov 2026, a free stock countdown) hit seven traps, each silent or slow. This kit removes them and adds the measuring a video needs, because a video has no page to inspect.
Requires reanimate: npx skills add NiazMorshed2007/reanimate -g, then once bash <reanimate>/scripts/setup.sh.
Then rv setup once (the measuring tools' numpy and opencv, in a Python of the kit's own: the python3 most Macs have refuses
a global pip install). Check with rv doctor. (rv is this skill's rv file.)
The traps, and what the kit does
| Trap (reanimate as it ships, from a video) | The kit | |
|---|---|---|
| 1 | a 10-bit video (ProRes, most stock footage) becomes 16-bit PNGs, 10 MB a frame | rv ref writes 8-bit frames |
| 2 | sheets inline every frame in one page and time out on 1080p frames | sheets come from 480-px copies |
| 3 | a copy opened as file:// cannot load its ES modules: reanimate records a blank page without a word | rv serve + rv round refuses file://, and every recording is checked: BLANK or FROZEN stops the round |
| 4 | the copy's first frame is recorded before its first animation frame | the template draws at load |
| 5 | comparing 300 frames at 1080p takes minutes a round | rounds compare at half size, the same way every round, sharp and blurred 4 px |
| 6 | a video has no element tracks or keyframes to read | measure/: centre, float, spin, arcs, fade, size, segments, typeface, from the pixels |
| 7 | the virtual clock sums its ticks in floating point and can skip a frame's last tick: that frame shows the page one tick early (17 ms at its default 60 Hz) | a copy on the template is recorded by calling its draw at exactly t = k / fps; other pages at a 240 Hz tick |
| 8 | (the kit's own tools) numpy and opencv cannot be pip-installed into the python3 most Macs have (Homebrew's, PEP 668) | rv setup makes the kit's own Python (.venv); rv measure <tool> runs each tool with it |
tests/run.sh reproduces every trap on reanimate as it ships (RED), then runs the kit's way (GREEN); tests/e2e.sh runs
the whole path on a small case (a copy that IS the reference reads 0 %; a planted wrong colour reads worse). Run both after
any change to the kit.
Before anything: may this video be copied?
Copy the LOOK only of a video the user may copy: their own, or a free licence that allows changes (Videezy's Standard
License: free for personal and commercial use with the credit "Videezy.com"; Mixkit's free licence; Pexels and Pixabay's
licences). Write the licence, its source link and the date into <work>/LICENSE.md, and put the credit on screen wherever the
video is shown (<work>/film.json "credit"). A game's footage is copied for its MOTION only (reanimate's default): the
look - the words, the font, the colours, the art - stays the user's own.
The steps
rv doctorrv ref <video> <work>(add--start/--endfor a part,--crop x,y,w,hfor one element). READ EVERY SHEET in<work>/ref/sheetsand the full-rate ones in<work>/ref/sheets_full_rate. Write what moves, and when, before measuring.- Measure each moving element with the tool for its kind of motion (
measure/README.md; run each asrv measure <tool> ...), on<work>/ref/frames. Prove each tool on the element before trusting it (its--sheet, or a second frame that must agree). <work>/spec.md: one row per element per property: start, duration, from -> to, easing or the measured table, and the file the number came from. A row with a "?" is not finished.- Build: copy
template/into the project.animation.jsexportsdraw(ctx, t)(a pure function of time) andDURATION; put every measured number in a module with the file it came from. The canvas is the reference's size (<work>/run.json). rv serve <copy dir>thenrv round 1 <work> --url http://127.0.0.1:8765/index.html. Read<work>/compare-1/report.mdand its sheets (compare-1/sheets): the numbers say how far off, the sheets say what. Fix the BIGGEST difference only, thenrv round 2 .... Stop at 5 rounds; what still fails is reported with its number and its reason.rv rounds <work>(the table) andrv film <work>(the process clips, 1080x1920: the video, its frames, every round with what differs in red and its number, the numbers falling, the final in sync).- Report: what was copied and at which fidelity, the files, the rounds table, what stayed off and why.
What copies from a video, and what does not
Copies well, by measurement: shapes, text, rings and arcs that turn, ticks and segments that light, fades and flashes,
growth and slides, the order and the timing of all of it. Measure them with measure/ and rebuild them as shapes.
Does NOT copy by measuring: a 3D-rendered scene, footage, particles placed at random, a background whose pieces flash and
change. measure/spin.py says so with its AGREEMENT (under about 0.3 the band does more than turn). On 6 Nov 2026 the
countdown's tunnel read 0.16-0.6: three rounds of the copy left it a smear (76 % -> 48 % of pixels off) while its HUD matched.
Then: copy the element in front, give the background the user's own scene (a game's, a page's), and compare only where the
element is, or pick a reference whose background is plain. Never call such a copy pixel-perfect.
Pass bars (reanimate's own, read for a video)
- Timing: every event (an element's 10/50/90 % rise and fall,
measure/curve.py) within 0.08 s of the reference's. - Easing: the copy's measured curve within 5 % of the travel (mean absolute difference) of the reference's.
- Pixels: mean mismatch at most 2 % for a pixel-faithful copy (only on a reference the user may copy the look of, and only where the background is part of what was copied); report the sharp number beside the blurred one.
- Every round's recording passed the guard (no blank or frozen frames).
Pitfalls learned
-
One float for a whole design is a hypothesis: fit EACH ring's centre on the original frames (
polar.py fit) before steadying anything (stabilize.py). On 6 Nov 2026 (Videezy 40314) the first fit locked onto the brightest ring, the white one, which drifts 8.5 px; the ring at 319 moves 0.1 px. Three rounds floated the whole HUD and measured it on frames steadied by the wrong layer; round 4, with only the white ring floating and everything measured again around the still centre, went 12.6 % -> 9.3 %. -
Try a change on a few frames before spending a round:
rv still <work> --url ... --frames 0,60,150and compare those frames with compare.ts against the last round's code. On 6 Nov two of three candidate changes helped (10.0 -> 9.5 -> 8.9 %) and one made it worse (13.3 %): it never became a round. -
measure/radial_diff.pyshows WHERE a round design is off, radius by radius; it reads still parts only - a floating ring smears in its medians, so judge by compare.ts when they disagree. -
A still ring is bright at EVERY angle: the 30th percentile over angle keeps it and drops what moves - the way to read the rings' true radii and widths.
-
rv filmcrops to the subject (film.json"crop"): a showcase shows its subject big. -
A centre found from "the sharpest rings" wanders when a big white shape is in the middle: use a small bright landmark on several frames, and trust it only when the frames agree (
measure/polar.py landmark). -
A glow thickens every edge: choose the typeface by measuring (
measure/fontmatch.py), not by eye. -
A measured zoom or turn is a hypothesis until a second view agrees: on 6 Nov a log-polar correlation reported a 3 %/s zoom that a radius-by-time strip showed did not exist, and the copy shrank its tunnel by it for a round.
-
An area grows as size squared: a number's size is sqrt(white area above the frames before it), or its box.
-
Free stock footage is often 4K ProRes, 1-2 GB a clip: read it at 1920 wide (
rv refdoes), keep the original for the record, and mind the disk.