Purpose
AI-generated scenes drift in color/contrast/saturation across cuts. This atom samples a reference clip, computes mean Y/U/V stats on every other clip, and applies an ffmpeg eq (+ optional curves) filter to each to bring its mean closer to the reference. The result is a graded set of clips with significantly reduced inter-clip variance.
Mirrors the manual color-grade logic in coca-cola/ad-runs/video-01-museum-painting/scripts/polish_v9.py but generalized and per-clip.
Inputs
--clips-dir <path>— directory containingscene-*.mp4(required)--output-dir <path>— destination (default:<clips-dir>/graded/)--reference <name|path>— clip used as the grade reference. Default: lexicographically first clip in<clips-dir>. Can be a scene filename (scene-03.mp4) or an absolute path.--match <y,u,v>— comma-separated channels to match (defaulty,u,v). Dropu,vto keep original tint per clip.--max-delta <0–1>— clamp the contrast/saturation/gamma adjustments so a single bad reference can't blow out everything (default0.30)--archive <path>— if set, copy original clips here before overwriting (default:<clips-dir>/_archive/pre-grade-<ts>/)
Workflow
- List
scene-*.mp4in<clips-dir>; resolve the reference clip. - For each clip including reference, compute mean YAVG, UAVG, VAVG via
ffmpeg signalstatsover a 1-fps frame sample. - Derive an adjustment per clip:
- Contrast =
clamp(ref_yavg / clip_yavg, 1 - max_delta, 1 + max_delta)(applied as ffmpegeq=contrast=around 1.0) - Saturation =
clamp(ref_uvspread / clip_uvspread, ...), whereuvspread = sqrt((uavg-128)^2 + (vavg-128)^2) - Gamma = chosen so the corrected clip's mean Y matches reference Y, capped by
max-delta.
- Contrast =
- Apply per-clip via
ffmpeg -i scene.mp4 -vf "eq=contrast=<C>:saturation=<S>:gamma=<G>" -c:a copy graded/scene.mp4. - Archive originals before overwrite (if
--archiveresolves to the same path as--output-dir). - Re-measure each graded clip and report deltas.
Output
<output-dir>/scene-*.mp4— graded clips<output-dir>/manifest.json:{ "reference": "scene-01.mp4", "reference_stats": {"y": 128.4, "u": 127.1, "v": 130.5}, "clips": [ {"name": "scene-02.mp4", "before": {...}, "after": {...}, "params": {"contrast": 0.96, "saturation": 1.08, "gamma": 1.02}}, ... ], "variance_before": {"y": 14.2, "u": 6.0, "v": 8.1}, "variance_after": {"y": 3.1, "u": 2.4, "v": 3.0}, "status": "ok" }<output-dir>/verification.md— variance reduction table.
Quality checks
- Variance of
yacross graded clips < variance before, by ≥ 50%. - No single clip's contrast/saturation/gamma adjustment exceeds
max-delta. - All originals archived before overwrite.
- Audio streams byte-identical to source.
Failure modes
- Reference clip is itself an outlier (e.g. very dark) — grading everything to it darkens the whole cut. Mitigation: use
--referenceto point at a known-good scene, or compute reference as the median rather than first clip (future flag). - Black frames in a clip skew mean Y — current impl samples at 1 fps, which still includes them. For clips with intentional fades, pass
--match u,vonly. - Audio re-encoded inadvertently — explicit
-c:a copy.
Example
scripts/grade.py \
--clips-dir coca-cola/ad-runs/video-01-museum-painting/clips/ \
--reference scene-03.mp4
Consumed by /polish (color-consistency branch).