mirror of
https://github.com/wiltodelta/remove-ai-watermarks.git
synced 2026-08-09 15:36:01 +02:00
The probe shipped with no tests while every sibling research script has them, and it needs neither a model nor a GPU, so nothing justified the gap. Its whole output is a ranking and a mispaired comparison still prints a plausible number, so the pairing tests carry more weight than the metric ones. The centerpiece builds a correctly phased and a wrong-phased delivery of the same clip. Both have six frames, so the frame-count check the probe used to rely on passes for both; the misaligned one scores near 9 dB against 40+. A separate assertion pins that the probe binds the engine's sampler rather than a copy, which is the contract a same-phase private copy would otherwise satisfy silently. This file now holds the only constraint on that sampler's phase. CI ran none of this. The test matrix installs no ffmpeg, and the job that does install it ran one class from another file, so five of these tests would have skipped on every runner. The video-e2e job now collects this file too. Recorded in .claude/rules/development.md: mutating the sampler's >= to > proves nothing, because the 1e-9 epsilon makes both branches agree on exact equality. The phase or the period is what has to move. That inert mutation passed a green suite here before the real one failed five tests. Dropped the downscale test's PSNR bound: solid colors survive both the downscale and the upscale exactly, so any score there pins the local ffmpeg's chroma rounding rather than the probe. Geometry is the assertion that means something. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
263 lines
9.7 KiB
Python
263 lines
9.7 KiB
Python
"""Regression tests for the end-to-end video fidelity probe.
|
|
|
|
The probe needs no model and no GPU, so every behavior below is covered without a
|
|
download. Its whole output is a ranking, and a mispaired comparison still prints a
|
|
plausible number, so the pairing tests matter more than the metric ones.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import importlib.util
|
|
import json
|
|
import shutil
|
|
import subprocess
|
|
import sys
|
|
from pathlib import Path
|
|
from typing import TYPE_CHECKING
|
|
|
|
import click
|
|
import click.testing
|
|
import numpy as np
|
|
import pytest
|
|
|
|
from remove_ai_watermarks import video_invisible
|
|
|
|
if TYPE_CHECKING:
|
|
from collections.abc import Iterator
|
|
from types import ModuleType
|
|
|
|
_SCRIPT = Path(__file__).parent.parent / "scripts" / "video_fidelity_probe.py"
|
|
|
|
|
|
@pytest.fixture(scope="module")
|
|
def probe() -> Iterator[ModuleType]:
|
|
# The script inserts scripts/ on sys.path to reach its shared SSIM helper and
|
|
# never removes it, which would otherwise outlive this module and defeat the
|
|
# restore that test_fidelity_matching.py performs for the same directory.
|
|
original_path = list(sys.path)
|
|
spec = importlib.util.spec_from_file_location("video_fidelity_probe", _SCRIPT)
|
|
assert spec is not None
|
|
assert spec.loader is not None
|
|
module = importlib.util.module_from_spec(spec)
|
|
spec.loader.exec_module(module)
|
|
try:
|
|
yield module
|
|
finally:
|
|
sys.path[:] = original_path
|
|
|
|
|
|
def _ffmpeg() -> str:
|
|
ffmpeg = shutil.which("ffmpeg")
|
|
if ffmpeg is None:
|
|
pytest.skip("the fidelity probe tests need ffmpeg to build their clips")
|
|
return ffmpeg
|
|
|
|
|
|
def _distinct_frames(count: int, *, width: int = 64, height: int = 48) -> list[np.ndarray]:
|
|
"""Solid-color frames whose index is recoverable from the pixels.
|
|
|
|
Distinguishable frames are the point: a pairing bug against a gradient or a
|
|
static scene still scores well, which is how a reordering drift hides.
|
|
"""
|
|
frames: list[np.ndarray] = []
|
|
for index in range(count):
|
|
# Odd multipliers, so each channel alone is injective for count <= 256. The
|
|
# per-step deltas are what set the misalignment penalty the thresholds below
|
|
# rely on; a constant offset would change neither and is left out.
|
|
color = ((index * 23) % 256, (index * 71) % 256, (index * 137) % 256)
|
|
frames.append(np.full((height, width, 3), color, dtype=np.uint8))
|
|
return frames
|
|
|
|
|
|
def _write_clip(path: Path, frames: list[np.ndarray], *, fps: float, ffmpeg: str) -> None:
|
|
height, width = frames[0].shape[:2]
|
|
command = [
|
|
ffmpeg,
|
|
"-y",
|
|
"-loglevel",
|
|
"error",
|
|
"-f",
|
|
"rawvideo",
|
|
"-pix_fmt",
|
|
"bgr24",
|
|
"-s:v",
|
|
f"{width}x{height}",
|
|
"-r",
|
|
f"{fps:.12g}",
|
|
"-i",
|
|
"pipe:0",
|
|
"-c:v",
|
|
"libx264",
|
|
"-preset",
|
|
"ultrafast",
|
|
"-crf",
|
|
"0",
|
|
"-pix_fmt",
|
|
"yuv444p",
|
|
str(path),
|
|
]
|
|
subprocess.run( # noqa: S603
|
|
command,
|
|
input=b"".join(frame.tobytes() for frame in frames),
|
|
capture_output=True,
|
|
check=True,
|
|
)
|
|
|
|
|
|
def test_the_probe_binds_the_engine_sampler_rather_than_its_own_copy(probe: ModuleType) -> None:
|
|
"""The "shared, not copied" contract, asserted at the seam that carries it.
|
|
|
|
The behavioral test below pins the phase the sampler happens to select, which a
|
|
private copy with the same phase would also satisfy. Identity is the only thing
|
|
that fails the moment someone reimplements the rule here. It needs no ffmpeg, so
|
|
it survives on runners where the rest of this file skips.
|
|
"""
|
|
assert probe._iter_sampled_frames is video_invisible._iter_sampled_frames
|
|
assert probe._probe_video is video_invisible._probe_video
|
|
|
|
|
|
def test_identical_source_and_delivery_score_as_untouched(probe: ModuleType, tmp_path: Path) -> None:
|
|
ffmpeg = _ffmpeg()
|
|
clip = tmp_path / "clip.mp4"
|
|
_write_clip(clip, _distinct_frames(8), fps=12.0, ffmpeg=ffmpeg)
|
|
|
|
row = probe._measure(clip, clip, source_geometry=probe._probe_video(clip), duration=None)
|
|
|
|
assert row["source_psnr_db"] == float("inf")
|
|
assert row["source_ssim"] == pytest.approx(1.0)
|
|
assert row["pixel_ratio"] == 1.0
|
|
assert row["frames"] == 8
|
|
|
|
|
|
def test_pairing_follows_the_engine_sampling_rule_not_just_the_frame_count(
|
|
probe: ModuleType,
|
|
tmp_path: Path,
|
|
) -> None:
|
|
"""The guard the frame-count check could not provide.
|
|
|
|
Decimating 12 fps to 6 fps selects source frames 0, 2, 4, ... A rule that
|
|
selected 1, 3, 5, ... instead returns exactly as many frames, so a count check
|
|
passes while every comparison is against the wrong frame. Starting the sampler's
|
|
accumulator half a period late produces exactly that, and this is the only test
|
|
in the suite that constrains the sampler's phase at all.
|
|
|
|
The clips encode losslessly, so the aligned pairing is exact and clears any
|
|
floor; the misaligned one compares frames a full colour step apart and lands
|
|
near 9 dB, which is what leaves the 25 dB ceiling a wide moat rather than a
|
|
tuned threshold.
|
|
"""
|
|
ffmpeg = _ffmpeg()
|
|
source = tmp_path / "source.mp4"
|
|
frames = _distinct_frames(12)
|
|
_write_clip(source, frames, fps=12.0, ffmpeg=ffmpeg)
|
|
|
|
aligned = tmp_path / "aligned.mp4"
|
|
_write_clip(aligned, frames[0::2], fps=6.0, ffmpeg=ffmpeg)
|
|
shifted = tmp_path / "shifted.mp4"
|
|
_write_clip(shifted, frames[1::2], fps=6.0, ffmpeg=ffmpeg)
|
|
|
|
geometry = probe._probe_video(source)
|
|
aligned_row = probe._measure(source, aligned, source_geometry=geometry, duration=None)
|
|
shifted_row = probe._measure(source, shifted, source_geometry=geometry, duration=None)
|
|
|
|
assert aligned_row["frames"] == shifted_row["frames"] == 6
|
|
assert aligned_row["source_psnr_db"] > 40.0
|
|
assert shifted_row["source_psnr_db"] < 25.0
|
|
|
|
|
|
def test_a_delivery_shorter_than_the_source_names_duration_rather_than_drift(
|
|
probe: ModuleType,
|
|
tmp_path: Path,
|
|
) -> None:
|
|
"""A short delivery is a duration prefix far more often than a real drift.
|
|
|
|
The sweep harness trims its candidates by default, so pointing the probe at one
|
|
is the common case rather than the pathological one. It has to refuse instead of
|
|
scoring a truncated pairing, and the refusal has to name the fix.
|
|
"""
|
|
ffmpeg = _ffmpeg()
|
|
source = tmp_path / "source.mp4"
|
|
frames = _distinct_frames(12)
|
|
_write_clip(source, frames, fps=12.0, ffmpeg=ffmpeg)
|
|
trimmed = tmp_path / "trimmed.mp4"
|
|
_write_clip(trimmed, frames[:6], fps=12.0, ffmpeg=ffmpeg)
|
|
|
|
geometry = probe._probe_video(source)
|
|
with pytest.raises(click.ClickException, match="--duration"):
|
|
probe._measure(source, trimmed, source_geometry=geometry, duration=None)
|
|
|
|
row = probe._measure(source, trimmed, source_geometry=geometry, duration=0.5)
|
|
|
|
assert row["frames"] == 6
|
|
assert row["source_psnr_db"] > 40.0
|
|
|
|
|
|
def test_a_downscaled_delivery_is_upscaled_back_to_the_source_geometry(
|
|
probe: ModuleType,
|
|
tmp_path: Path,
|
|
) -> None:
|
|
ffmpeg = _ffmpeg()
|
|
source = tmp_path / "source.mp4"
|
|
frames = _distinct_frames(6, width=64, height=48)
|
|
_write_clip(source, frames, fps=12.0, ffmpeg=ffmpeg)
|
|
delivered = tmp_path / "small.mp4"
|
|
_write_clip(delivered, _distinct_frames(6, width=32, height=24), fps=12.0, ffmpeg=ffmpeg)
|
|
|
|
row = probe._measure(source, delivered, source_geometry=probe._probe_video(source), duration=None)
|
|
|
|
assert (row["width"], row["height"]) == (32, 24)
|
|
assert row["pixel_ratio"] == 0.25
|
|
# Geometry is the whole assertion. Solid colors survive both the downscale and
|
|
# the upscale exactly, so any score here would pin the local ffmpeg's chroma
|
|
# rounding rather than probe behavior. A missing upscale fails this test through
|
|
# the shape mismatch in the metric, not through a number.
|
|
assert row["frames"] == 6
|
|
|
|
|
|
def test_geometry_read_rejects_a_file_that_is_not_a_video(probe: ModuleType, tmp_path: Path) -> None:
|
|
broken = tmp_path / "broken.mp4"
|
|
broken.write_bytes(b"not a video")
|
|
|
|
with pytest.raises(click.ClickException, match="Could not open video"):
|
|
probe._delivered_geometry(broken)
|
|
|
|
|
|
def test_a_single_frame_delivery_is_rejected_before_the_temporal_metric(
|
|
probe: ModuleType,
|
|
tmp_path: Path,
|
|
) -> None:
|
|
ffmpeg = _ffmpeg()
|
|
clip = tmp_path / "one.mp4"
|
|
_write_clip(clip, _distinct_frames(1), fps=12.0, ffmpeg=ffmpeg)
|
|
|
|
with pytest.raises(click.ClickException, match="fewer than two frames"):
|
|
probe._measure(clip, clip, source_geometry=probe._probe_video(clip), duration=None)
|
|
|
|
|
|
def test_the_command_writes_one_json_row_per_argument_in_order(probe: ModuleType, tmp_path: Path) -> None:
|
|
"""`main` is the only code the helper tests do not reach, and it has to round-trip.
|
|
|
|
The rows exist to be read by something else, so the file has to parse. An
|
|
identical source and delivery would serialize PSNR as bare ``Infinity``, which
|
|
`json.loads` accepts but a strict reader does not; distinct deliveries keep the
|
|
numbers finite and that stays out of the tool's normal output.
|
|
"""
|
|
ffmpeg = _ffmpeg()
|
|
source = tmp_path / "source.mp4"
|
|
_write_clip(source, _distinct_frames(6), fps=12.0, ffmpeg=ffmpeg)
|
|
small = tmp_path / "small.mp4"
|
|
_write_clip(small, _distinct_frames(6, width=32, height=24), fps=12.0, ffmpeg=ffmpeg)
|
|
smaller = tmp_path / "smaller.mp4"
|
|
_write_clip(smaller, _distinct_frames(6, width=16, height=16), fps=12.0, ffmpeg=ffmpeg)
|
|
report = tmp_path / "rows.json"
|
|
|
|
result = click.testing.CliRunner().invoke(
|
|
probe.main,
|
|
[str(source), str(small), str(smaller), "--json-out", str(report)],
|
|
)
|
|
|
|
assert result.exit_code == 0, result.output
|
|
rows = json.loads(report.read_text(encoding="utf-8"))
|
|
assert [row["file"] for row in rows] == ["small.mp4", "smaller.mp4"]
|
|
assert all(row["source"] == "source.mp4" for row in rows)
|