Motion metrics
Measure how much rendered frames move - holds, jumps, motion density - to check motion graphics automatically.
Coming in the next release
Motion metrics ship in the next @miraiclip/renderer release.
Motion graphics fail in two typical ways. Frames hold still too long, and the film reads like slides. Or something jumps with calm on both sides, which reads as a glitch or a cut nobody hid. Motion metrics measure both on small greyscale frames, so a tool (or an AI assistant) can check a piece and say where to fix it.
The measure is the mean absolute change in luma between consecutive frames, on a 0–255 scale, at 192 px wide and 10 frames per second. Thresholds were calibrated on films measured that way.
Measure frames
import { changeSeries, lumaFrame, motionFindings, motionMetrics, MOTION_SAMPLE, MOTION_TARGETS, rasterizeHtml } from "@miraiclip/renderer";
const frames = [];
for (let i = 0; i < durationS * MOTION_SAMPLE.fps; i++) {
const canvas = await rasterizeHtml({ template, params, widthPx: 1920, heightPx: 1080, timeS: i / MOTION_SAMPLE.fps, durationS, density: MOTION_SAMPLE.width / 1920, scripted: true, software: true });
const pixels = canvas.getContext("2d")!.getImageData(0, 0, canvas.width, canvas.height).data;
frames.push(lumaFrame(pixels));
}
const report = motionMetrics(changeSeries(frames), MOTION_SAMPLE.fps, { endHoldSeconds: 1.5 });
// { seconds, density: 0.95, medianChange: 1.6, holds: [{ from, to }], jumps: [t] }
const findings = motionFindings(report, MOTION_TARGETS.motion);
// [{ kind: "hold", from: 4.8, to: 5.7, message: "nothing moves 4.8–5.7 s" }, …]lumaFrame(rgba)converts pixels to luma in video range (16–235), so rendered frames match the calibration made on decoded video.software: truerasterizes on the CPU. It's cheap at 192 px, and exact: some GPU rasterizers add tile artifacts that would read as motion.endHoldSecondsallows an end card to hold, andoffsetSecondsreports times within a longer film.
Targets
| Target | Motion graphics | Documentary pace |
|---|---|---|
| Density: the share of frames with visible change | ≥ 0.85 | ≥ 0.6 |
| Median change per frame | ≥ 1.5 | ≥ 0.6 |
| Longest hold | 0.8 s | 2 s |
For reference, a 13 s motion-graphics launch film measured density 0.99 with a median change of 3.5. Slide-like films with dissolves measured 0.3–0.7 with many holds.
In the React template
The template's assistant measures every act it composes and the whole film at the end. Each finding comes back with what to change (keep a camera drift running, overlap beats, move further, hide the cut), so the model fixes the act before moving on.