Check unwanted motion in AI video before you publish.
A local Python CLI and optional agent skill for creators checking image-to-video clips. Measure a global feature-motion proxy, flag clips above your threshold, and send intentional camera moves for review. Runs on your own video files; no API key, GPU, or Hermes installation is needed for the CLI.
繁體中文 · Agent skill · Synthetic examples · Automation contract · Evaluation results · Release candidate
| Your intent | What the CLI does | Next step |
|---|---|---|
| Keep the shot static | PASS or FAIL against an 8% default threshold | Review the actual subject and framing before publishing |
| Make an intentional pan / tilt / dolly | NEEDS_REVIEW, with the measured motion | Confirm the move matches your shot plan |
| Video cannot be measured reliably | NEEDS_REVIEW, exit 2 | Inspect the file or use a visual review |
The metric tracks global feature motion, so a moving subject can trigger a false alarm. It does not detect faces, prove subject containment, stabilize footage, or generate video. The 8% default is a project policy, not a validated universal quality threshold.
Install Python 3.10 or newer and Git. From a terminal:
git clone https://github.com/leonininder/camera-motion-language.git
cd camera-motion-language
python -m venv .venvWindows PowerShell:
.\.venv\Scripts\python.exe -m pip install -r requirements.txt
.\.venv\Scripts\python.exe scripts/measure_frame_drift.py --video golden_clips/static_hold.mp4 --intent staticmacOS / Linux:
.venv/bin/python -m pip install -r requirements.txt
.venv/bin/python scripts/measure_frame_drift.py --video golden_clips/static_hold.mp4 --intent staticExpected key lines:
drift_pct_of_width=0.000
status=PASS
gate=ENFORCE
The included clips are synthetic OpenCV fixtures, not real AI-video outputs. The sparse legacy golden_clips/pan_authorized.mp4 now returns NEEDS_REVIEW (exit 2) because its matches fail geometric consistency. Run python scripts/evaluate_regressions.py --output regression-results.json for reproducible textured pan/return-pan FAIL cases. Replace the fixture path with your own MP4 or MOV.
| Exit code | Status | Meaning |
|---|---|---|
| 0 | PASS | All decoded frames satisfy the measured proxy policy for static intent |
| 0 | NEEDS_REVIEW | Intentional motion or an opt-in sampled preview is reported, never auto-approved |
| 1 | FAIL | Static intent exceeds the selected threshold |
| 2 | NEEDS_REVIEW or error | Insufficient features, unreadable video, invalid options, or missing dependencies |
Do not treat exit 0 alone as approval. Check both status and gate when integrating with automation. Low texture, failed frame decoding, or insufficient tracked features cannot produce PASS. Each sampled frame after the first needs at least eight geometrically consistent ORB inliers to the first frame (at least 50% of the best 50 matches), and each adjacent pair needs at least eight flow tracks with forward/backward disagreement of at most one pixel.
The proxy takes the larger of sampled adjacent-frame median optical flow and maximum first-to-sampled-frame ORB displacement, normalized by frame width. Comparing every sample against the first catches movement that returns to its start. ORB mismatches can still inflate results; geometric checks do not identify background versus subject. A PASS is a screening result, not proof of camera lock or identity preservation. Every frame is decoded by default (--all-frames is an explicit alias). --sample-frames N opts into a faster preview that cannot auto-approve; inspect the clip visually. The default full-frame mode reads frames sequentially without retaining the entire video in memory.
python scripts/measure_frame_drift.py --video golden_clips/static_hold.mp4 --format json --require-pass --all-frames--require-pass gives exit 0 only for PASS/ENFORCE. JSON includes a boolean
approved, schema version, actual sample indices and evidence counts; errors
cannot silently become zero drift. Contract and Python example.
In the stress suite, 24-frame sampling misses an isolated 20% displacement.
The preview therefore returns NEEDS_REVIEW even when its sampled metric is low;
the default full decode detects the excursion and returns FAIL. The raw
sampled_proxy_status is diagnostic only and never grants approval.
Four CC BY 3.0 open-animation excerpts all required review under strict geometry;
this is a current usability limitation, not a successful real-video benchmark.
- For authored animation, key exact camera values in your animation tool.
- For AI-generated video, start with a static camera unless you want one named move.
- Keep the subject visible in both endpoint stills; review the entire output afterward.
- Run the gate, then inspect flagged clips. Rework the prompt or endpoint images when framing is wrong.
A starter camera line: Static Shot. Stationary camera. Only the person moves. Set stays fixed. This is prompt guidance, not a guarantee that a video model will obey it. See the shot-card schema and example.
The self-contained skill explains keyed versus sampled camera motion and the review workflow. Read it in any file-capable agent. For Hermes, copy this repository to %LOCALAPPDATA%/hermes/profiles/<profile>/skills/creative/camera-motion-language/ and start a new session. Private operator notes are optional historical context; the CLI and examples work without them.
Using the Python interpreter from your virtual environment, run:
python -m unittest discover -s tests -vSubstitute .venv/bin/python or .\.venv\Scripts\python.exe if you have not activated the environment. Tests cover included pass/fail/report-only clips, featureless input, missing files, and invalid CLI settings.
Useful contributions: a redistributable real video with your camera intent, observed framing, CLI output, and permission to publish; a reproducible false positive or false negative; or clearer onboarding. Open an issue with the command, Python/OpenCV versions, and expected result. Never upload private footage without permission.
Early public tool with synthetic regression fixtures. Real AI-video accuracy and subject-retention benchmarks remain open work. An authored open-animation probe and synthetic stress results are available in benchmarks. Earlier internal SOP review scores do not establish public adoption, benchmark accuracy, or independent endorsement. See release-readiness work.
MIT for this repository's original text and scripts. Referenced animation libraries have their own licenses; see sources. The historical brightness-centroid implementation remains in scripts/measure_frame_drift_brightness_legacy.py for comparison.