mirror of
https://github.com/calesthio/OpenMontage.git
synced 2026-08-12 03:33:44 +08:00
fix(green_screen): make chromakey compositing portable across FFmpeg builds
The CI Linux FFmpeg build carried the keyed frame forward without an alpha plane, so overlay drew opaque green over the background (corner stayed green) instead of compositing — the E2E test failed there even though it passed on macOS/Windows. Force `format=yuva420p` immediately after chromakey so the keyed transparency always has an explicit alpha plane, and size the background to the frame up front (color=...:size=WxH, passing the probed width/height into _process_chromakey) instead of scaling a 1x1 source with scale2ref — dropping scale2ref also removes the format negotiation that discarded the alpha on some builds. Output is flattened to yuv420p after the overlay.
This commit is contained in:
@@ -50,16 +50,20 @@ def test_chromakey_filter_scales_background_to_frame():
|
||||
fd.mkdir()
|
||||
pd.mkdir()
|
||||
(fd / "frame_0000.png").write_bytes(b"stub")
|
||||
tool._process_chromakey(fd, pd, "#0E172A", 1)
|
||||
tool._process_chromakey(fd, pd, "#0E172A", 1, 320, 240)
|
||||
finally:
|
||||
GreenScreenProcessor.run_command = orig
|
||||
|
||||
ffmpeg_cmds = [c for c in captured if c and c[0] == "ffmpeg"]
|
||||
assert ffmpeg_cmds, "no ffmpeg command built"
|
||||
fc_idx = ffmpeg_cmds[0].index("-filter_complex")
|
||||
fc = ffmpeg_cmds[0][fc_idx + 1]
|
||||
assert "scale2ref" in fc, f"background not scaled to frame: {fc}"
|
||||
cmd = ffmpeg_cmds[0]
|
||||
fc = cmd[cmd.index("-filter_complex") + 1]
|
||||
# Background must be sized to the frame, not the old 1x1 no-op.
|
||||
assert "size=320x240" in " ".join(cmd), "background not sized to frame"
|
||||
assert "size=1x1" not in " ".join(cmd), "still using the 1x1 background"
|
||||
assert "[0:v]scale=iw:ih[bg]" not in fc, "still using the 1x1 no-op scale"
|
||||
# Alpha must be forced so keyed transparency survives on every FFmpeg build.
|
||||
assert "format=yuva420p" in fc, f"keyed alpha not forced: {fc}"
|
||||
|
||||
|
||||
@pytest.mark.skipif(shutil.which("ffmpeg") is None or shutil.which("ffprobe") is None,
|
||||
@@ -79,7 +83,9 @@ def test_chromakey_preserves_frame_size_and_keys(tmp_path):
|
||||
capture_output=True, check=True, timeout=60,
|
||||
)
|
||||
|
||||
ok = GreenScreenProcessor()._process_chromakey(frames_dir, processed_dir, "#0E172A", 1)
|
||||
ok = GreenScreenProcessor()._process_chromakey(
|
||||
frames_dir, processed_dir, "#0E172A", 1, 320, 240
|
||||
)
|
||||
assert ok
|
||||
|
||||
out = processed_dir / "frame_0000.png"
|
||||
|
||||
@@ -161,7 +161,7 @@ class GreenScreenProcessor(BaseTool):
|
||||
|
||||
if method == "chromakey":
|
||||
ok = self._process_chromakey(
|
||||
frames_dir, processed_dir, bg_color, frame_count
|
||||
frames_dir, processed_dir, bg_color, frame_count, width, height
|
||||
)
|
||||
else:
|
||||
ok = self._process_rembg(
|
||||
@@ -445,6 +445,8 @@ class GreenScreenProcessor(BaseTool):
|
||||
processed_dir: Path,
|
||||
bg_color: str,
|
||||
frame_count: int,
|
||||
width: int,
|
||||
height: int,
|
||||
) -> bool:
|
||||
"""Process frames using FFmpeg chromakey filter.
|
||||
|
||||
@@ -461,20 +463,23 @@ class GreenScreenProcessor(BaseTool):
|
||||
out_path = processed_dir / frame.name
|
||||
cmd = [
|
||||
"ffmpeg", "-y",
|
||||
"-f", "lavfi", "-i", f"color=c={ffmpeg_bg}:size=1x1",
|
||||
# Background sized to the frame up front. The old code used a 1x1
|
||||
# color source and `[0:v]scale=iw:ih` — a no-op (iw/ih were the
|
||||
# 1x1 source's own size) — and overlay takes the size of its
|
||||
# FIRST input, so every frame was clipped to a single pixel and
|
||||
# the whole video came out a solid color with the subject gone.
|
||||
"-f", "lavfi", "-i", f"color=c={ffmpeg_bg}:size={width}x{height}",
|
||||
"-i", str(frame),
|
||||
"-filter_complex",
|
||||
(
|
||||
# The background is a 1x1 color source; it MUST be scaled up
|
||||
# to the frame's dimensions. `scale=iw:ih` on [0:v] alone is a
|
||||
# no-op (iw/ih are the 1x1 source's own size), and overlay
|
||||
# takes the size of its FIRST input — so that leaves a 1x1
|
||||
# canvas and the keyed frame gets clipped to a single pixel,
|
||||
# producing a solid-color video with the subject gone.
|
||||
# scale2ref resizes [0:v] to match the frame [1:v] first.
|
||||
f"[1:v]chromakey=color=0x00FF00:similarity=0.3:blend=0.08[fg];"
|
||||
f"[0:v][fg]scale2ref[bg][fg2];"
|
||||
f"[bg][fg2]overlay=0:0"
|
||||
# Force an explicit alpha format after chromakey so the keyed
|
||||
# transparency survives filter negotiation on every FFmpeg
|
||||
# build — without it, some Linux builds carry the keyed frame
|
||||
# forward without an alpha plane and overlay draws opaque
|
||||
# green over the background instead of compositing.
|
||||
f"[1:v]chromakey=color=0x00FF00:similarity=0.3:blend=0.08,"
|
||||
f"format=yuva420p[fg];"
|
||||
f"[0:v][fg]overlay=0:0:format=auto,format=yuv420p"
|
||||
),
|
||||
"-frames:v", "1",
|
||||
str(out_path),
|
||||
|
||||
Reference in New Issue
Block a user