From motiscope
Analyzes screen recordings of animations to produce a motion spec with timing, easing, transforms, and sequencing for web code recreation.
How this skill is triggered — by the user, by Claude, or both
Slash command
/motiscope:analyze [path-to-video] [notes][path-to-video] [notes]This skill is limited to the following tools:
The summary Claude sees in its skill listing — used to decide when to auto-load this skill
Turn a screen recording of an animation into a precise, target-agnostic **animation spec** you can hand to `/motiscope:recreate`. A bundled Python pipeline measures the motion (a dense per-frame motion-energy curve + ffmpeg signal analysis — this is the source of truth for timing and easing) and extracts a small set of curated PNG keyframes for you to *see*. You combine the two into the spec.
Turn a screen recording of an animation into a precise, target-agnostic animation spec you can hand to /motiscope:recreate. A bundled Python pipeline measures the motion (a dense per-frame motion-energy curve + ffmpeg signal analysis — this is the source of truth for timing and easing) and extracts a small set of curated PNG keyframes for you to see. You combine the two into the spec.
Every command below runs a bundled script. Set SCRIPTS:
SCRIPTS="${CLAUDE_PLUGIN_ROOT:-}/scripts"
# Fallback for harnesses that don't export CLAUDE_PLUGIN_ROOT: this SKILL.md lives at
# <plugin>/skills/analyze/SKILL.md, so scripts are two levels up. Use the absolute
# path of the directory containing the SKILL.md you just Read.
if [ ! -f "$SCRIPTS/ingest.py" ]; then
SCRIPTS="<absolute dir of this SKILL.md>/../../scripts"
fi
if [ ! -f "$SCRIPTS/ingest.py" ]; then
echo "ERROR: cannot find ingest.py — check the plugin install." >&2; exit 1
fi
On Windows use python instead of python3 in every command below.
python3 "$SCRIPTS/mvsetup.py" --check
Exit 0 → proceed silently. Non-zero → ffmpeg/ffprobe are missing; hand off to /motiscope:doctor (don't try to analyze without them).
$1 or their message), use it.ls -t animations/*.{mp4,mov,webm,mkv,m4v,avi,gif} *.{mp4,mov,webm,mkv,m4v,avi,gif} 2>/dev/null | head
AskUserQuestion) which one.animations/ (or pass a path) and stop.Local files only. motiscope does not download URLs. If the user pastes a URL, ask them to screen-record it and drop the file in.
python3 "$SCRIPTS/ingest.py" "<video>" --preset balanced
It writes to .motiscope/<slug>/ and prints a summary, the report.md path, and the ordered curated frame list.
The number of frames you Read is what costs tokens (~300–400 tokens/frame); the numeric analysis is free. Frame count tracks motion complexity, capped by the preset — it does not grow with video length.
| Preset | Frame cap | Resolution | Use when |
|---|---|---|---|
--preset draft | 12 | 512px | quick look, tight token budget |
--preset balanced (default) | 32 (usually lands 8–20 after dedup) | 640px | most cases |
--preset detailed | 48 | 960px | dense multi-beat sequences, or reading on-screen text |
--preset landing | 44 | 1280px | web/landing walkthroughs — cover each section's design at readable resolution + its in-section motion |
Start with balanced. Only reach for detailed if the animation is intricate or you couldn't read a label; use draft for a fast first pass. If the user hasn't said, pick balanced and mention they can ask for more detail.
--start / --end)For anything longer than ~15s, or when the user points at a specific moment ("the part around 0:12", "the last second"), analyze just that window instead of a sparse whole-clip scan. Times accept SS, MM:SS, or HH:MM:SS; frame/segment timestamps come back in absolute source time.
python3 "$SCRIPTS/ingest.py" "<video>" --preset detailed --start 0:12 --end 0:15
--fps)Within a (short) focus window you can force a uniform sample rate so nothing between keyposes is missed — e.g. --fps 20 gives up to a frame every ~50ms. Near-identical frames are still collapsed unless you pass --no-dedup. Combine with a short window so the budget isn't blown:
python3 "$SCRIPTS/ingest.py" "<video>" --start 1.0 --end 3.0 --fps 20 --frame-budget 48
For clips ≥8s with two or more motion beats, ingest.py auto-decomposes by default: it finds the beats, then concentrates frames on each motion segment (drilling densely) and gives each hold just one representative frame — so the budget is spent on motion, not dead air. A 10s clip with a 5s hold in the middle spends ~0 frames on the hold. The report notes when this happened.
Control it with --decompose (force on) / --no-decompose (force the flat single-pass). Auto is the right default; force it off only if you specifically want even coverage across the whole timeline.
Other flags: --frame-budget N / --resolution W override the preset; --format jpg for gradient-heavy recordings that bloat as PNG; --no-dedup to keep every sampled frame; --out DIR (default .motiscope/).
Small elements register now. The primary motion signal is localized (built from the most-active regions of the frame), so a small button/card/icon moving on a large page reads as real motion instead of washing out. The stagger direction in the report tells you the sequencing (e.g.
left-to-right, ~200ms each) — use it when building the spec'sstagger.
The division of labor is the whole point:
report.md measured the timing you can't see in a still: exact durations, the per-segment easing curve (a real cubic-bezier fitted from the velocity profile), the beat/segment boundaries, the stagger timing (~ms between items), and any loop period. Trust these — a screenshot has no time axis, so this is the one thing that isn't guessable.Steps:
Read the printed report.md for the measured timing (energy sparkline, segment/beat table with the fitted cubic-bezier, stagger timing, loop).Read every curated frame in one message (parallel Reads) so you see them in order. Filenames encode t=<seconds> + why each was picked, so you can line frames up against the measured timeline.Read timing from the numbers; read everything else from the frames.
Frames are chosen by the motion-energy signal. Anything small, low-contrast, briefly visible, or mostly occluded barely moves that signal and may not appear in a single curated frame — while still being a headline feature of the animation. A train crossing a bridge was missed exactly this way.
Before you commit to a spec:
A failed automated measurement does not crash. It returns a confident float.
-0.03 .. 0.63 into 0.30
invents a fact. Reporting the range is the finding.The full list, with the real failures behind each one, is in
references/measurement-traps.md. Read it before reporting any velocity, easing or ratio you
derived yourself rather than reading out of report.md.
Emit a target-agnostic spec: a timing skeleton (measured) + your visual reading (what each element is and does). Schema:
{
"duration_ms": 2000,
"canvas": { "w": 800, "h": 600, "bg": "#ffffff" },
"loop": false,
"scroll_driven": false,
"elements": [{ "id": "card", "role": "card" }],
"timeline": [
{ "target": "card", "start_ms": 0, "dur_ms": 400,
"ease": "ease-out", "bezier": [0.16, 1, 0.3, 1],
"animation": "fade + slide up",
"props": { "opacity": [0, 1], "y": [24, 0] },
"source": { "timing": "measured", "everything_else": "seen" } }
],
"stagger": { "children": "card", "each_ms": 80 },
"notes": "e.g. 'headline does a per-word mask reveal'; 'logo path draws in'"
}
Rules:
start_ms, dur_ms, ease, bezier, stagger each_ms, loop period_ms are MEASURED — copy them from report.md/motion.json verbatim. Especially: use the fitted cubic-bezier directly (CSS cubic-bezier(...), Framer [..], GSAP CustomEase) — it's the real curve, not a guess.animation, elements, props (magnitudes/direction/colors), and the animation type are YOURS from the frames — describe the actual effect in the free-text animation field (open vocabulary), and put your best-estimate property values in props. Direction, scale, rotation, opacity, morph, draw, etc. all come from what you see, not from the numbers.hold: a gentle ease-in's slow start can read as a short hold (sub-pixel motion is invisible in the analysis thumbnails). Check the first frames — if the element already moved slightly, treat it as the start of the ease, not a delay."loop": true with period_ms. It can be a half-cycle for back-and-forth motion — decide loop-vs-yoyo from the frames.props (a complex morph, a particle system, a shader), say so in animation/notes and let recreate build it directly from your description — don't force it into simple props."scroll_driven": true and do not copy duration_ms into the spec as if it were the design's. Report ratios (layer speed ÷ page-scroll speed) and note which quantities belong to the recorder. A clip can be scroll-driven and contain a time-driven object (a train, a spinner, a looping badge) — measure that object's speed separately.Summarize the animation in one or two sentences, then offer:
Recreate this as GSAP, CSS, Framer Motion, or Lottie/SVG? Run
/motiscope:recreate <target>.
The spec you just built is saved in .motiscope/<slug>/manifest.json; recreate will reload it (plus motion.json) if you don't hand it over directly.
npx claudepluginhub kumarsashank/motiscope --plugin motiscopeRecreates analyzed animations as runnable web code in GSAP, CSS/Web Animations, Framer Motion, or Lottie/SVG. Use after /motiscope:analyze or when asked to build an animation in a specific framework.
Analyzes reference videos to deconstruct layout, motion, and timing, then builds original motion replicas with reusable templates and MP4 QC for React Native projects.
Generates detailed animation prompts from videos or screenshots that can be fed to LLMs to recreate the motion. Focuses on understanding movement dynamics and aligning intent, not just frame extraction.