unease
v0.0.1
Published
Infer easing/spring curves from animation video via algorithmic tracking + MCP
Downloads
166
Maintainers
Readme
unease
Infer an easing curve or spring config from an animation video (.mp4).
unease is a Rust CLI and MCP server. A model identifies the item once; unease
then tracks it across every frame algorithmically and fits the curve — deterministic,
precise, reproducible. The model does perception (naming/pointing at the item); the
algorithm does inference (tracking + fitting).
open_video → [model marks item with a bbox] → track_item → emit
│
color-mask · connected-blob · fitWhy this shape
The naive approach — hand every frame to a vision model and have it read the item's size by eye — is non-deterministic, imprecise (can't read sub-pixel scale), slow, and breaks when scale, translation, and camera-zoom combine. So perception is minimized to a single seed; everything measurable is a plain pixel algorithm:
- seed — model marks the item with one bbox; unease samples its hue
- track — per frame: hue-mask (survives the brightness/glow ramp UI elements undergo), label connected components, follow the item's blob by centroid continuity (so a same-colored label elsewhere can't steal the track)
- signal — the blob's geometry over time, normalized
(metric−start)/(settle−start)so start=0, settle=1, overshoot>1 - fit — cubic-bezier and damped-spring models, dependency-free Nelder–Mead
- classify — spring only on real overshoot and a clearly better fit
- emit — CSS, framer-motion, reanimated, SwiftUI
- verify — optional
--demo: a blue pill the item's size replays the inferred curve on white; if it matches the source, the fit is good
A manual sample_frames/observe loop remains as a fallback for items color-masking
can't isolate.
Install
Needs ffmpeg on PATH.
cargo build --releaseCLI
unease probe anim.mp4 # fps, duration, dims (+ low-fps warning)
unease frames anim.mp4 --count 12 # dump evenly spaced frames
unease track anim.mp4 --bbox x,y,w,h # algorithmic track + fit (seed color from a bbox)
unease track anim.mp4 --color R,G,B # …or seed the color directly
unease fit observations.json # fit from a samples file (pure math, no video)
unease mcp # run the MCP server on stdiotrack — the main path
# seed the item with a bbox on frame 0, track its scale, render a verification demo
unease track anim.mp4 --bbox 590,530,55,55 --property height --demo--bbox x,y,w,h(on--ref-frame N) or--color R,G,B— seed the target--property height|width|area|cx|cy— what to track; omit for auto (largest change)--hue-tol,--min-value,--min-sat— mask tuning (defaults handle glow)--demo [--demo-out path]— write<video>.demo.mp4: a blue pill the item's size replaying the inferred curve on white--dump-metrics— per-frame bbox metrics to stderr
observations.json:
{ "duration": 0.5, "samples": [ {"t":0.0,"v":0.0}, {"t":0.5,"v":1.18}, {"t":1.0,"v":1.0} ] }t— normalized time[0,1]over the animation windowv— normalized value of the tracked property (0start,1settle,>1overshoot)
MCP tools
| tool | purpose |
|------|---------|
| open_video(path) | metadata + a session handle; warns on low fps |
| sample_frames(handle, count?\|timestamps?) | returns frames as images (to see the item before marking it) |
| track_item(handle, bbox, property?, demo?) | mark item once → algorithmic track + fit in one call |
| observe(handle, samples, window_seconds?) | fallback: model submits tracked {t,v} per frame |
| fit(handle) | fallback: fit observed samples + classify |
| emit(handle) | render the fitted curve into framework snippets |
Primary flow: open_video → sample_frames (see the item) → track_item(bbox) → done.
Register with Claude Code
claude mcp add unease -- /path/to/unease mcpThen: "infer the easing of the blue button in demo.mp4" — the model walks the loop.
Output example (underdamped spring)
{
"kind": "spring",
"confidence": 0.77,
"framer_motion": "transition={{ type: 'spring', stiffness: 681.3, damping: 24.52, mass: 1.00 }}",
"swiftui": ".spring(response: 0.241, dampingFraction: 0.470)",
"notes": ["Overshoot peak 18.0% — underdamped spring."]
}Limits
- Frame rate — 30 fps undersamples fast springs;
uneasewarns. 60 fps preferred. - Single property — fits one value channel (position or scale or opacity) at a time.
- Window — spring stiffness/damping scale with the real animation duration; report it
via
window_secondsfor physical params.
License
MIT
