From 99ddc2e42586da11bea8949ed30c83eaae64024c Mon Sep 17 00:00:00 2001 From: jenstandstad Date: Sun, 19 Jul 2026 20:45:40 +0200 Subject: [PATCH] Adding fix --- gnommo/cli.py | 55 ++++++++++++++++++++++++++++++++++++++------------- 1 file changed, 41 insertions(+), 14 deletions(-) diff --git a/gnommo/cli.py b/gnommo/cli.py index c11c4de..4e46f49 100644 --- a/gnommo/cli.py +++ b/gnommo/cli.py @@ -4772,11 +4772,29 @@ def _measure_suit(ref: Path, filters: "list[dict]") -> "tuple[int, int, int]": return (sr // n, sg // n, sb // n) if n else (0, 0, 0) +def _png_dims(path: Path) -> "tuple[int, int]": + """Return (width, height) of an image/frame, or (1920, 1080) on failure.""" + r = subprocess.run( + ["ffprobe", "-v", "error", "-select_streams", "v:0", + "-show_entries", "stream=width,height", "-of", "csv=p=0:s=x", str(path)], + capture_output=True, text=True, + ) + try: + w, h = r.stdout.strip().split("x")[:2] + return int(w), int(h) + except ValueError: + return (1920, 1080) + + def _render_preview(ref: Path, filters: "list[dict]", out_png: Path) -> None: - """Save the filtered still composited over magenta (to judge the matte).""" + """Save the filtered still composited over magenta (to judge the matte). + + The magenta layer is sized to the frame so the full frame shows at any + source resolution — a fixed-size backdrop would crop the overlay.""" vf = _grade_video_filter(filters) + w, h = _png_dims(ref) cmd = ["ffmpeg", "-y", "-v", "error", - "-f", "lavfi", "-i", "color=c=magenta:s=1280x720", + "-f", "lavfi", "-i", f"color=c=magenta:s={w}x{h}", "-i", str(ref), "-filter_complex", f"[1]{vf},format=yuva444p10le[fg];" f"[0][fg]overlay=shortest=1,format=rgb24", @@ -4861,24 +4879,32 @@ def _stage_key(project_path, filters, ref, out_dir, source_name, ss) -> int: def _stage_despill(project_path, filters, ref, out_dir, source_name, ss) -> int: - """Deterministic spill_suppress sweep 0.7–1.5 → candidates for a visual pick.""" + """Deterministic spill_suppress sweep 0.5–1.5 → candidates for a visual pick. + + Reports both sides of the tradeoff: scalp green_cast (want ~0) AND the + costume's orange-ness (suit R−G, want low). Over-despilling clears the + scalp but pushes a yellow costume orange, so the balance point — not the + maximum — is usually right.""" base = _grade_step(filters, "gnommokey") - values = [round(0.7 + (1.5 - 0.7) * i / 6, 2) for i in range(7)] # 0.7 .. 1.5 - print(" Sweeping spill_suppress 0.7 → 1.5 (yellow_protect held fixed)") + values = [round(0.5 + (1.5 - 0.5) * i / 8, 3) for i in range(9)] # 0.5 .. 1.5 + print(" Sweeping spill_suppress 0.5 → 1.5 (yellow_protect held fixed)") + suit_raw = _measure_suit(ref, _mask_and(filters, dict(base, spill_suppress=0))) candidates = [] for i, v in enumerate(values, 1): cfg = dict(base) cfg["spill_suppress"] = v chain = _mask_and(filters, cfg) cast = _measure_green_cast(ref, chain) + suit = _measure_suit(ref, chain) png = out_dir / f"despill_{i}.png" _render_preview(ref, chain, png) candidates.append({ "id": f"despill_{i}", "file": png.name, - "params": {"spill_suppress": v}, "hint": {"green_cast": cast}, + "params": {"spill_suppress": v}, + "hint": {"green_cast": cast, "suit_orange": suit[0] - suit[1]}, }) - # Advisory only: the value nearest neutral green cast. Localized bald-head - # spill means the eye is the real judge, hence a full sweep to pick from. + # Advisory: nearest-neutral scalp cast — the balance point that clears the + # scalp without needlessly orange-ing the costume (over-despill goes magenta). rec = min(candidates, key=lambda c: abs(c["hint"]["green_cast"]))["id"] manifest = { "stage": "despill", "target_step": "gnommokey", @@ -4886,15 +4912,16 @@ def _stage_despill(project_path, filters, ref, out_dir, source_name, ss) -> int: "recommended": rec, "candidates": candidates, } mpath = _grade_write_manifest(out_dir, manifest) - print(f" {'id':>12} {'spill':>7} {'green_cast':>10} png") - print(f" {'-'*12} {'-'*7} {'-'*10} {'-'*3}") + print(f" raw costume R−G (target for suit_orange): {suit_raw[0] - suit_raw[1]}") + print(f" {'id':>12} {'spill':>6} {'scalp cast':>10} {'suit_orange':>11} png") + print(f" {'-'*12} {'-'*6} {'-'*10} {'-'*11} {'-'*3}") for c in candidates: star = " ◀ suggested" if c["id"] == rec else "" - print(f" {c['id']:>12} {c['params']['spill_suppress']:>7} " - f"{c['hint']['green_cast']:>10} {c['file']}{star}") - print("\n green_cast: >0 residual green · ~0 neutral · <0 over-despilled (magenta)") + print(f" {c['id']:>12} {c['params']['spill_suppress']:>6} " + f"{c['hint']['green_cast']:>10} {c['hint']['suit_orange']:>11} {c['file']}{star}") + print("\n scalp cast: >0 green · ~0 neutral · <0 magenta | suit_orange: lower = more yellow") print(f" Manifest: {mpath}") - print(" Pick the one with a clean crown and no magenta skin:") + print(" Pick the balance — scalp near 0 without the suit going orange:") print(f" gnommo -p {project_path.name} grade --pick despill_5") return 0