Adding fix
This commit is contained in:
+41
-14
@@ -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
|
||||
|
||||
|
||||
Reference in New Issue
Block a user