"""Tests for the video processing API and CLI.""" from __future__ import annotations import hashlib import io import json import shutil import subprocess from pathlib import Path from typing import TYPE_CHECKING import cv2 import numpy as np import pytest from click.testing import CliRunner from PIL import Image, ImageDraw, ImageFont from remove_ai_watermarks.cli import main from remove_ai_watermarks.metadata import C2PA_UUID if TYPE_CHECKING: from collections.abc import Callable _MP4_FTYP = b"\x00\x00\x00\x18ftypmp42\x00\x00\x00\x00mp42isom" _VIDEO_PAYLOAD = b"synthetic-video-payload" _TC260_AIGC = ( b'{"Label":"1","ContentProducer":"00119144030008867405X210002",' b'"ProduceID":"sample-001","ReservedCode1":"","ContentPropagator":"",' b'"PropagateID":"","ReservedCode2":""}' ) def _box(box_type: bytes, payload: bytes) -> bytes: return (8 + len(payload)).to_bytes(4, "big") + box_type + payload def _video_with_c2pa(path: Path) -> Path: manifest = C2PA_UUID + b"OpenAI trainedAlgorithmicMedia c2pa.watermarked.unbound" path.write_bytes(_MP4_FTYP + _box(b"uuid", manifest) + _box(b"mdat", _VIDEO_PAYLOAD)) return path def _metadata_key(name: bytes) -> bytes: return (8 + len(name)).to_bytes(4, "big") + b"mdta" + name def _metadata_value(index: int, value: bytes) -> bytes: data = _box(b"data", b"\x00\x00\x00\x01\x00\x00\x00\x00" + value) return _box(index.to_bytes(4, "big"), data) def _video_with_tc260(path: Path, *, media_payload: bytes = _VIDEO_PAYLOAD) -> Path: keys = _box( b"keys", b"\x00\x00\x00\x00" + (2).to_bytes(4, "big") + _metadata_key(b"AIGC") + _metadata_key(b"title"), ) ilst = _box( b"ilst", _metadata_value(1, _TC260_AIGC) + _metadata_value(2, b"standard title"), ) meta = _box(b"meta", b"\x00\x00\x00\x00" + keys + ilst) path.write_bytes(_MP4_FTYP + _box(b"mdat", media_payload) + _box(b"moov", _box(b"udta", meta))) return path def _video_with_comfyui_metadata(path: Path, *, media_payload: bytes = _VIDEO_PAYLOAD) -> Path: workflow = b'{"nodes":[{"type":"KSampler"}],"app":"ComfyUI"}' prompt = b'{"1":{"class_type":"KSampler"}}' keys = _box( b"keys", b"\x00\x00\x00\x00" + (4).to_bytes(4, "big") + _metadata_key(b"workflow") + _metadata_key(b"prompt") + _metadata_key(b"title") + _metadata_key(b"software"), ) ilst = _box( b"ilst", _metadata_value(1, workflow) + _metadata_value(2, prompt) + _metadata_value(3, b"standard title") + _metadata_value(4, b"ordinary editor"), ) meta = _box(b"meta", b"\x00\x00\x00\x00" + keys + ilst) path.write_bytes(_MP4_FTYP + _box(b"mdat", media_payload) + _box(b"moov", _box(b"udta", meta))) return path def _hdlr(handler: bytes) -> bytes: # version/flags + pre_defined + handler_type + reserved[3]; the TC260 walker # never parses it, but a real-shaped hdlr keeps the fixture honest. return _box(b"hdlr", b"\x00\x00\x00\x00" + b"\x00\x00\x00\x00" + handler + b"\x00" * 12) def _quicktime_meta(*children: bytes) -> bytes: # QuickTime form: no 4-byte FullBox header before the children. return _box(b"meta", b"".join(children)) def _video_with_tc260_moov_meta(path: Path, *, media_payload: bytes = _VIDEO_PAYLOAD) -> Path: """Doubao's iOS export form: a QuickTime ``meta`` box as a direct ``moov`` child.""" keys = _box( b"keys", b"\x00\x00\x00\x00" + (2).to_bytes(4, "big") + _metadata_key(b"com.apple.quicktime.artwork") + _metadata_key(b"AIGC"), ) ilst = _box( b"ilst", _metadata_value(1, b'{"source_type":"","data":{"product":"doubao"}}') + _metadata_value(2, _TC260_AIGC), ) meta = _quicktime_meta(_hdlr(b"mdta"), keys, ilst) path.write_bytes(_MP4_FTYP + _box(b"mdat", media_payload) + _box(b"moov", meta)) return path def _video_with_tc260_mdir_list(path: Path, *, media_payload: bytes = _VIDEO_PAYLOAD) -> Path: """Doubao's iOS QuickTime metadata list: ``udta.meta(hdlr=mdir)/ilst`` data items with numeric indices and no ``keys`` box at all.""" ilst = _box( b"ilst", _metadata_value(0, b"vid:standard-video-id") + _metadata_value(0, _TC260_AIGC), ) meta = _quicktime_meta(_hdlr(b"mdir"), ilst) path.write_bytes(_MP4_FTYP + _box(b"mdat", media_payload) + _box(b"moov", _box(b"udta", meta))) return path def _ebml_size(value: int) -> bytes: for length in range(1, 9): if value < (1 << (7 * length)) - 1: return ((1 << (7 * length)) | value).to_bytes(length, "big") raise ValueError("EBML test value is too large") def _ebml_element(element_id: bytes, payload: bytes) -> bytes: return element_id + _ebml_size(len(payload)) + payload def _video_with_tc260_ebml(path: Path, *, value: bytes = _TC260_AIGC) -> Path: simple_tag = _ebml_element( b"\x67\xc8", _ebml_element(b"\x45\xa3", b"AIGC") + _ebml_element(b"\x44\x87", value), ) tags = _ebml_element(b"\x12\x54\xc3\x67", _ebml_element(b"\x73\x73", simple_tag)) segment = _ebml_element(b"\x18\x53\x80\x67", tags) path.write_bytes(_ebml_element(b"\x1a\x45\xdf\xa3", b"") + segment) return path def _riff_chunk(chunk_id: bytes, payload: bytes) -> bytes: return chunk_id + len(payload).to_bytes(4, "little") + payload + (b"\x00" if len(payload) & 1 else b"") def _video_with_tc260_avi(path: Path, *, value: bytes = _TC260_AIGC) -> Path: info = _riff_chunk(b"AIGC", value) + _riff_chunk(b"INAM", b"standard title\x00") body = b"AVI " + _riff_chunk(b"LIST", b"INFO" + info) + _riff_chunk(b"JUNK", _VIDEO_PAYLOAD) path.write_bytes(b"RIFF" + len(body).to_bytes(4, "little") + body) return path def _amf0_string(value: bytes) -> bytes: return b"\x02" + len(value).to_bytes(2, "big") + value def _video_with_tc260_flv(path: Path, *, value: bytes = _TC260_AIGC) -> Path: payload = ( _amf0_string(b"onMetaData") + b"\x08\x00\x00\x00\x02" + len(b"AIGC").to_bytes(2, "big") + b"AIGC" + _amf0_string(value) + len(b"duration").to_bytes(2, "big") + b"duration" + b"\x00" + b"\x00\x00\x00\x00\x00\x00\x00\x00" + b"\x00\x00\x09" ) tag_header = b"\x12" + len(payload).to_bytes(3, "big") + b"\x00" * 7 path.write_bytes( b"FLV\x01\x05\x00\x00\x00\x09" + b"\x00\x00\x00\x00" + tag_header + payload + (11 + len(payload)).to_bytes(4, "big") ) return path _LEGACY_VIDEO_CASES = ( (".avi", _video_with_tc260_avi), (".flv", _video_with_tc260_flv), ) def _stamp_gray_mark( frame: np.ndarray, mark: Image.Image | np.ndarray, *, x: int, y: int, opacity: float, ) -> None: """Alpha-composite a grayscale synthetic mark onto a BGR test frame.""" mark_array = np.asarray(mark, dtype=np.float32) height, width = mark_array.shape alpha = mark_array[:, :, None] / 255 * opacity crop = frame[y : y + height, x : x + width].astype(np.float32) frame[y : y + height, x : x + width] = np.clip( crop * (1 - alpha) + 255 * alpha, 0, 255, ).astype(np.uint8) def _independent_sora_mark() -> tuple[np.ndarray, tuple[int, int, int, int]]: """Render a Sora-like mark without using the detector's template.""" mark = Image.new("L", (180, 64), 0) draw = ImageDraw.Draw(mark) draw.ellipse((1, 14, 32, 54), fill=255) draw.ellipse((25, 8, 62, 58), fill=255) draw.ellipse((15, 20, 28, 44), fill=0) draw.ellipse((37, 18, 50, 43), fill=0) try: font = ImageFont.load_default(size=49) except TypeError: font = ImageFont.load_default() draw.text((68, 1), "Sora", font=font, fill=255, stroke_width=1) mark_array = cv2.resize(np.asarray(mark), (124, 44), interpolation=cv2.INTER_AREA) x, y = 620, 398 return mark_array, (x, y, 124, 44) def _independent_sora_frame() -> tuple[np.ndarray, tuple[int, int, int, int]]: """Stamp the independent Sora-like mark onto a flat test frame.""" frame = np.full((480, 840, 3), 36, dtype=np.uint8) mark_array, region = _independent_sora_mark() x, y, _width, _height = region _stamp_gray_mark(frame, mark_array, x=x, y=y, opacity=0.78) return frame, region def _moving_video_background(frame_index: int) -> np.ndarray: """Return a smooth moving background with known clean pixels.""" x = np.arange(840, dtype=np.float32)[None, :] y = np.arange(480, dtype=np.float32)[:, None] luma = 42 + 10 * np.sin((x + frame_index * 5) / 38) + 5 * np.cos((y - frame_index * 2) / 54) frame = np.stack( ( np.clip(luma - 5, 0, 255), np.clip(luma + 1, 0, 255), np.clip(luma + 7, 0, 255), ), axis=2, ).astype(np.uint8) moving_x = 30 + frame_index * 8 cv2.rectangle(frame, (moving_x, 70), (moving_x + 72, 132), (80, 140, 210), -1) cv2.line(frame, (0, 220 + frame_index), (839, 250 + frame_index), (110, 70, 45), 3) return frame def _ffmpeg_test_tools() -> tuple[str, str]: """Return real ffmpeg tools or skip the integration test.""" ffmpeg = shutil.which("ffmpeg") ffprobe = shutil.which("ffprobe") if ffmpeg is None or ffprobe is None: pytest.skip("full-clip video integration test requires ffmpeg and ffprobe") return ffmpeg, ffprobe def _write_synthetic_sora_clip( path: Path, frames: list[np.ndarray], *, fps: float, ffmpeg: str, start_offset: float = 0.0, ) -> None: """Encode a synthetic marked MP4 with AAC audio and C2PA provenance.""" height, width = frames[0].shape[:2] duration = len(frames) / fps command = [ ffmpeg, "-y", "-loglevel", "error", "-f", "rawvideo", "-pix_fmt", "bgr24", "-s:v", f"{width}x{height}", "-r", f"{fps:.12g}", "-i", "pipe:0", "-f", "lavfi", "-i", f"sine=frequency=880:sample_rate=48000:duration={duration:.12g}", "-map", "0:v:0", "-map", "1:a:0", "-c:v", "libx264", "-preset", "ultrafast", "-crf", "8", "-x264-params", "colorprim=bt709:transfer=bt709:colormatrix=bt709:range=limited", "-pix_fmt", "yuv420p", "-color_range", "tv", "-colorspace", "bt709", "-color_trc", "bt709", "-color_primaries", "bt709", "-c:a", "aac", "-shortest", "-video_track_timescale", "90000", "-movflags", "+faststart", ] if start_offset: command.extend(["-output_ts_offset", f"{start_offset:.12g}"]) command.append(str(path)) subprocess.run( # noqa: S603 command, input=b"".join(frame.tobytes() for frame in frames), capture_output=True, check=True, ) with path.open("ab") as stream: stream.write( _box( b"uuid", C2PA_UUID + b"OpenAI Sora trainedAlgorithmicMedia c2pa.watermarked.unbound", ) ) def _write_vfr_sora_clip( path: Path, frames: list[np.ndarray], *, durations: list[float], ffmpeg: str, start_offset: float = 0.0, ) -> None: """Encode marked stills at deliberately irregular presentation times.""" assert len(frames) == len(durations) frame_paths: list[Path] = [] for index, frame in enumerate(frames): frame_path = path.with_name(f"{path.stem}-frame-{index:03d}.png") assert cv2.imwrite(str(frame_path), frame) frame_paths.append(frame_path) manifest = path.with_suffix(".ffconcat") lines = ["ffconcat version 1.0"] for frame_path, duration in zip(frame_paths, durations, strict=True): lines.extend((f"file '{frame_path.as_posix()}'", f"duration {duration:.12g}")) lines.append(f"file '{frame_paths[-1].as_posix()}'") manifest.write_text("\n".join(lines) + "\n", encoding="utf-8") total_duration = sum(durations) command = [ ffmpeg, "-y", "-loglevel", "error", "-f", "concat", "-safe", "0", "-i", str(manifest), "-f", "lavfi", "-i", f"sine=frequency=880:sample_rate=48000:duration={total_duration:.12g}", "-map", "0:v:0", "-map", "1:a:0", "-frames:v", str(len(frames)), "-fps_mode", "vfr", "-c:v", "libx264", "-preset", "ultrafast", "-crf", "8", "-bf", "0", "-pix_fmt", "yuv420p", "-c:a", "aac", "-video_track_timescale", "90000", ] if start_offset: command.extend(["-output_ts_offset", f"{start_offset:.12g}"]) command.append(str(path)) subprocess.run(command, capture_output=True, check=True) # noqa: S603 with path.open("ab") as stream: stream.write( _box( b"uuid", C2PA_UUID + b"OpenAI Sora trainedAlgorithmicMedia c2pa.watermarked.unbound", ) ) def _decode_video(path: Path) -> tuple[list[np.ndarray], float]: """Decode every frame through the same OpenCV boundary as production.""" capture = cv2.VideoCapture(str(path)) assert capture.isOpened() fps = float(capture.get(cv2.CAP_PROP_FPS)) frames: list[np.ndarray] = [] try: while True: ok, frame = capture.read() if not ok: break frames.append(frame) finally: capture.release() return frames, fps def _video_frame_timestamps(path: Path, *, ffprobe: str) -> list[float]: """Read display timestamps from the first video stream.""" result = subprocess.run( # noqa: S603 [ ffprobe, "-v", "error", "-select_streams", "v:0", "-show_frames", "-show_entries", "frame=best_effort_timestamp_time", "-of", "json", str(path), ], capture_output=True, check=True, text=True, ) frames = json.loads(result.stdout)["frames"] return [float(frame["best_effort_timestamp_time"]) for frame in frames] def _audio_bitstream(path: Path, *, ffmpeg: str) -> bytes: """Extract copied AAC packets in a container-independent form.""" result = subprocess.run( # noqa: S603 [ ffmpeg, "-loglevel", "error", "-i", str(path), "-map", "0:a:0", "-c:a", "copy", "-f", "adts", "pipe:1", ], capture_output=True, check=True, ) return result.stdout def _container_duration(path: Path, *, ffprobe: str) -> float: """Read the container duration from the real ffprobe boundary.""" result = subprocess.run( # noqa: S603 [ ffprobe, "-v", "error", "-show_entries", "format=duration", "-of", "default=noprint_wrappers=1:nokey=1", str(path), ], capture_output=True, check=True, text=True, ) return float(result.stdout.strip()) def _video_stream_info(path: Path, *, ffprobe: str) -> dict[str, str]: """Read source-sensitive video stream properties through ffprobe.""" result = subprocess.run( # noqa: S603 [ ffprobe, "-v", "error", "-select_streams", "v:0", "-show_entries", "stream=pix_fmt,color_range,color_space,color_transfer,color_primaries,time_base", "-of", "json", str(path), ], capture_output=True, check=True, text=True, ) streams = json.loads(result.stdout)["streams"] assert len(streams) == 1 return streams[0] def _stream_start_times(path: Path, *, ffprobe: str) -> dict[str, float]: """Read the first video and audio stream start times.""" result = subprocess.run( # noqa: S603 [ ffprobe, "-v", "error", "-show_entries", "stream=codec_type,start_time", "-of", "json", str(path), ], capture_output=True, check=True, text=True, ) return { stream["codec_type"]: float(stream["start_time"]) for stream in json.loads(result.stdout)["streams"] if stream["codec_type"] in {"video", "audio"} } def _regeneration_metrics( *, frames: int = 24, fps: float = 12.0, width: int = 512, height: int = 288, psnr_db: float = 22.0, temporal_residual_ratio: float = 1.2, ): from remove_ai_watermarks.video_invisible import RegenerationMetrics return RegenerationMetrics( frames=frames, fps=fps, width=width, height=height, psnr_db=psnr_db, temporal_residual_ratio=temporal_residual_ratio, ) class TestVideoDependencies: def test_visible_runtime_reports_video_extra(self, monkeypatch): from remove_ai_watermarks import optional_deps from remove_ai_watermarks.video import _require_video_runtime monkeypatch.setattr(optional_deps, "module_available", lambda *_names: False) with pytest.raises(RuntimeError, match=r"remove-ai-watermarks\[video\]"): _require_video_runtime() class TestVideoMetadataApi: def test_inspects_and_removes_comfyui_mp4_metadata(self, tmp_path: Path): from remove_ai_watermarks.video import inspect_video_metadata, remove_video_metadata source = _video_with_comfyui_metadata(tmp_path / "source.mp4") output = tmp_path / "clean.mp4" report = inspect_video_metadata(source) assert set(report.markers) >= {"workflow", "prompt"} result = remove_video_metadata(source, output) cleaned = output.read_bytes() assert result.remaining == {} assert len(cleaned) == source.stat().st_size assert _VIDEO_PAYLOAD in cleaned assert b"standard title" in cleaned assert b"ordinary editor" in cleaned assert b"ComfyUI" not in cleaned def test_top_level_api_is_lazy_exported(self): import remove_ai_watermarks as raiw assert raiw.identify_video is not None assert raiw.inspect_video_metadata is not None assert raiw.remove_video_all is not None assert raiw.remove_video_batch is not None assert raiw.remove_video_invisible is not None assert raiw.remove_video_metadata is not None assert raiw.remove_video_visible is not None def test_inspects_video_metadata(self, tmp_path: Path): from remove_ai_watermarks.video import inspect_video_metadata source = _video_with_c2pa(tmp_path / "source.mp4") report = inspect_video_metadata(source) assert report.source == source assert report.has_ai_metadata is True assert report.markers def test_removes_metadata_without_touching_video_payload(self, tmp_path: Path): from remove_ai_watermarks.video import remove_video_metadata source = _video_with_c2pa(tmp_path / "source.mp4") output = tmp_path / "clean.mp4" result = remove_video_metadata(source, output) assert result.output == output assert result.detected assert result.remaining == {} assert result.audio.stream_action == "copied_if_present" assert result.audio.watermark_status == "unverified" assert _VIDEO_PAYLOAD in output.read_bytes() assert C2PA_UUID not in output.read_bytes() def test_default_output_preserves_source(self, tmp_path: Path): from remove_ai_watermarks.video import remove_video_metadata source = _video_with_c2pa(tmp_path / "source.mp4") original = source.read_bytes() result = remove_video_metadata(source) assert result.output == tmp_path / "source_clean.mp4" assert result.output.exists() assert source.read_bytes() == original @pytest.mark.parametrize("suffix", [".mp4", ".mov"]) def test_inspects_native_tc260_metadata(self, tmp_path: Path, suffix: str): from remove_ai_watermarks.video import inspect_video_metadata source = _video_with_tc260(tmp_path / f"source{suffix}") report = inspect_video_metadata(source) assert report.has_ai_metadata is True assert report.markers["aigc_label"].endswith("producer 00119144030008867405X210002") def test_inspects_native_tc260_metadata_after_large_media_payload(self, tmp_path: Path): from remove_ai_watermarks.video import inspect_video_metadata source = _video_with_tc260( tmp_path / "source.mp4", media_payload=b"x" * (1024 * 1024), ) report = inspect_video_metadata(source) assert report.has_ai_metadata is True assert "aigc_label" in report.markers def test_removes_native_tc260_metadata_without_touching_media_or_standard_tag(self, tmp_path: Path): from remove_ai_watermarks.video import remove_video_metadata source = _video_with_tc260(tmp_path / "source.mp4") output = tmp_path / "clean.mp4" result = remove_video_metadata(source, output) cleaned = output.read_bytes() assert result.detected["aigc_label"].startswith("China AIGC label") assert result.remaining == {} assert len(cleaned) == source.stat().st_size assert _VIDEO_PAYLOAD in cleaned assert b"standard title" in cleaned assert b"AIGC" not in cleaned assert _TC260_AIGC not in cleaned @pytest.mark.parametrize("suffix", [".mp4", ".mov"]) def test_inspects_native_tc260_in_quicktime_moov_meta(self, tmp_path: Path, suffix: str): # Doubao's iOS export stores the label in a QuickTime-form meta box # (no FullBox header) hanging directly off moov, not under udta. from remove_ai_watermarks.video import inspect_video_metadata source = _video_with_tc260_moov_meta(tmp_path / f"source{suffix}") report = inspect_video_metadata(source) assert report.has_ai_metadata is True assert report.markers["aigc_label"].endswith("producer 00119144030008867405X210002") def test_removes_native_tc260_in_quicktime_moov_meta(self, tmp_path: Path): from remove_ai_watermarks.video import remove_video_metadata source = _video_with_tc260_moov_meta(tmp_path / "source.mov") output = tmp_path / "clean.mov" result = remove_video_metadata(source, output) cleaned = output.read_bytes() assert result.detected["aigc_label"].startswith("China AIGC label") # The artwork item's Doubao product JSON is app-export provenance, a # separate signal the TC260 blanker does not touch -- as on the real # Doubao iOS export this fixture mirrors. assert result.remaining == {"app_provenance": "App export provenance (ByteDance Doubao)"} assert len(cleaned) == source.stat().st_size assert _VIDEO_PAYLOAD in cleaned assert _TC260_AIGC not in cleaned def test_inspects_native_tc260_in_quicktime_mdir_metadata_list(self, tmp_path: Path): # The second Doubao iOS variant: a QuickTime metadata list under # udta.meta with hdlr=mdir and ilst data items but no keys box. from remove_ai_watermarks.video import inspect_video_metadata source = _video_with_tc260_mdir_list(tmp_path / "source.mov") report = inspect_video_metadata(source) assert report.has_ai_metadata is True assert "aigc_label" in report.markers def test_removes_native_tc260_in_quicktime_mdir_metadata_list(self, tmp_path: Path): from remove_ai_watermarks.video import remove_video_metadata source = _video_with_tc260_mdir_list(tmp_path / "source.mov") output = tmp_path / "clean.mov" result = remove_video_metadata(source, output) cleaned = output.read_bytes() assert result.detected["aigc_label"].startswith("China AIGC label") assert result.remaining == {} assert len(cleaned) == source.stat().st_size assert _VIDEO_PAYLOAD in cleaned # The keyless entry has no key name to blank; only the JSON is spaced # out, and the neighboring standard ilst item survives untouched. assert _TC260_AIGC not in cleaned assert b"vid:standard-video-id" in cleaned def test_streams_large_isobmff_without_full_file_read( self, tmp_path: Path, monkeypatch: pytest.MonkeyPatch, ): from remove_ai_watermarks.video import inspect_video_metadata, remove_video_metadata media_payload = b"x" * (8 * 1024 * 1024) source = _video_with_tc260(tmp_path / "source.mp4", media_payload=media_payload) with source.open("ab") as stream: stream.write( _box( b"uuid", C2PA_UUID + b"OpenAI trainedAlgorithmicMedia c2pa.watermarked.unbound", ) ) output = tmp_path / "clean.mp4" source_size = source.stat().st_size source_media_digest = hashlib.sha256(media_payload).digest() original_read_bytes = type(source).read_bytes source_report = inspect_video_metadata(source) assert source_report.has_ai_metadata is True assert "synthid_watermark" in source_report.markers assert "aigc_label" in source_report.markers def reject_source_read_bytes(path: Path) -> bytes: if path.resolve() == source.resolve(): raise AssertionError("video metadata removal must not read the complete source") return original_read_bytes(path) monkeypatch.setattr(type(source), "read_bytes", reject_source_read_bytes) result = remove_video_metadata(source, output) cleaned = output.read_bytes() mdat_start = cleaned.index(b"mdat") + 4 mdat_end = mdat_start + len(media_payload) assert result.remaining == {} assert output.stat().st_size == source_size assert hashlib.sha256(cleaned[mdat_start:mdat_end]).digest() == source_media_digest assert b"standard title" in cleaned assert C2PA_UUID not in cleaned assert _TC260_AIGC not in cleaned def test_ignores_generic_mp4_aigc_tag_without_tc260_fields(self, tmp_path: Path): from remove_ai_watermarks.video import inspect_video_metadata source = _video_with_tc260(tmp_path / "source.mp4") source.write_bytes(source.read_bytes().replace(_TC260_AIGC, b'{"description":"' + b"x" * 146 + b'"}')) report = inspect_video_metadata(source) assert report.has_ai_metadata is False assert report.markers == {} @pytest.mark.parametrize("suffix", [".mkv", ".webm"]) def test_inspects_native_tc260_ebml_metadata(self, tmp_path: Path, suffix: str): from remove_ai_watermarks.video import inspect_video_metadata source = _video_with_tc260_ebml(tmp_path / f"source{suffix}") report = inspect_video_metadata(source) assert report.has_ai_metadata is True assert report.markers["aigc_label"].endswith("producer 00119144030008867405X210002") def test_ignores_generic_ebml_aigc_tag_without_tc260_fields(self, tmp_path: Path): from remove_ai_watermarks.video import inspect_video_metadata source = _video_with_tc260_ebml( tmp_path / "source.mkv", value=b'{"description":"ordinary application metadata"}', ) report = inspect_video_metadata(source) assert report.has_ai_metadata is False assert report.markers == {} @pytest.mark.parametrize( ("suffix", "factory"), _LEGACY_VIDEO_CASES, ) def test_inspects_native_tc260_legacy_video_metadata( self, tmp_path: Path, suffix: str, factory: Callable[..., Path], ): from remove_ai_watermarks.video import inspect_video_metadata source = factory(tmp_path / f"source{suffix}") report = inspect_video_metadata(source) assert report.has_ai_metadata is True assert report.markers["aigc_label"].endswith("producer 00119144030008867405X210002") @pytest.mark.parametrize( ("suffix", "factory"), _LEGACY_VIDEO_CASES, ) def test_ignores_generic_legacy_video_aigc_tag( self, tmp_path: Path, suffix: str, factory: Callable[..., Path], ): from remove_ai_watermarks.video import inspect_video_metadata source = factory( tmp_path / f"source{suffix}", value=b'{"description":"ordinary application metadata"}', ) report = inspect_video_metadata(source) assert report.has_ai_metadata is False assert report.markers == {} def test_rejects_image_input(self, tmp_clean_png: Path): from remove_ai_watermarks.video import inspect_video_metadata with pytest.raises(ValueError, match="Unsupported video format"): inspect_video_metadata(tmp_clean_png) def test_rejects_image_with_video_extension(self, tmp_clean_png: Path, tmp_path: Path): from remove_ai_watermarks.video import inspect_video_metadata disguised = tmp_path / "image.mp4" disguised.write_bytes(tmp_clean_png.read_bytes()) with pytest.raises(ValueError, match="does not match"): inspect_video_metadata(disguised) def test_rejects_output_container_change(self, tmp_path: Path): from remove_ai_watermarks.video import remove_video_metadata source = _video_with_c2pa(tmp_path / "source.mp4") with pytest.raises(ValueError, match="must match"): remove_video_metadata(source, tmp_path / "clean.mov") class TestVideoProvenanceApi: @pytest.mark.parametrize( ("generator", "issuer", "expected"), [ ( "Google C2PA Core Generator Library", "Google LLC, OpenAI", "Google (Gemini / Imagen)", ), ( "Dreamina/2.0", "OpenAI", "ByteDance Dreamina", ), ("Higgsfield AI", "BytePlus (ByteDance)", "Higgsfield AI"), ("Higgsfield AI", "", "Higgsfield AI"), ("FastVid", "", "FastVid"), ], ) def test_claim_generator_precedes_mixed_certificate_issuers(self, generator: str, issuer: str, expected: str): from remove_ai_watermarks.video import _platform_from_video_metadata assert _platform_from_video_metadata({"claim_generator": generator, "issuer": issuer}) == expected def test_c2pa_platform_keeps_the_bytedance_surface_name(self): from remove_ai_watermarks.video import _platform_from_video_metadata assert _platform_from_video_metadata({"issuer": "BytePlus (ByteDance)"}) == "BytePlus (ByteDance)" assert _platform_from_video_metadata({"issuer": "ByteDance (Volcano Engine)"}) == "ByteDance Volcano Engine" def test_metadata_only_identification_prefers_generator_to_mixed_issuer( self, tmp_path: Path, monkeypatch: pytest.MonkeyPatch ): from remove_ai_watermarks.video import identify_video source = _video_with_c2pa(tmp_path / "source.mp4") monkeypatch.setattr( "remove_ai_watermarks.metadata.get_ai_metadata", lambda _path: { "claim_generator": "Google C2PA Core Generator Library", "issuer": "Google LLC, OpenAI", "source_type": "trainedAlgorithmicMedia (AI-generated)", }, ) report = identify_video(source, check_visible=False) assert report.platform == "Google (Gemini / Imagen)" assert report.visible_mark is None assert report.has_ai_metadata is True def test_identifies_metadata_without_pixel_scan(self, tmp_path: Path): from remove_ai_watermarks.video import identify_video source = _video_with_c2pa(tmp_path / "source.mp4") report = identify_video(source, check_visible=False) assert report.source == source assert report.is_ai_generated is True assert report.confidence == "high" assert report.platform == "OpenAI (ChatGPT / GPT Image / DALLĀ·E / Sora)" assert report.visible_mark is None assert report.total_frames is None assert report.has_ai_metadata is True assert report.metadata_markers assert "Visible video-mark detection was skipped." in report.caveats def test_identifies_stable_visible_mark_with_shared_arbiter( self, tmp_path: Path, monkeypatch: pytest.MonkeyPatch, ): from remove_ai_watermarks import video_visible from remove_ai_watermarks.video import VIDEO_VISIBLE_MARKS, identify_video from remove_ai_watermarks.video_visible import FrameLocalization, VideoScan source = _video_with_c2pa(tmp_path / "source.mp4") box = (4, 4, 20, 8) stable = VideoScan( width=64, height=64, fps=24.0, detections=tuple(FrameLocalization(index, 0.66, box) for index in range(5)), ) empty = VideoScan( width=64, height=64, fps=24.0, detections=tuple(FrameLocalization(index, 0.0, None) for index in range(5)), ) def fake_scan( _source: Path, marks: tuple[str, ...], *, collect_timestamps: bool, ) -> dict[str, VideoScan]: assert collect_timestamps is False return {candidate: stable if candidate == "sora" else empty for candidate in marks} monkeypatch.setattr(video_visible, "scan_video_marks", fake_scan) report = identify_video(source) assert report.platform == "OpenAI Sora" assert report.visible_mark == "sora" assert report.visible_detected_frames == 5 assert report.total_frames == 5 assert tuple(VIDEO_VISIBLE_MARKS) == ("sora", "veo", "seedance", "doubao", "dola", "hailuo", "vidu", "kling") def test_reports_unknown_instead_of_clean(self, tmp_path: Path): from remove_ai_watermarks.video import identify_video source = tmp_path / "source.mp4" source.write_bytes(_MP4_FTYP + _box(b"mdat", _VIDEO_PAYLOAD)) report = identify_video(source, check_visible=False) assert report.is_ai_generated is None assert report.confidence == "unknown" assert report.has_ai_metadata is False assert any("not proof" in caveat for caveat in report.caveats) class TestVideoMetadataCli: def test_help(self): runner = CliRunner() result = runner.invoke(main, ["video", "metadata", "--help"]) assert result.exit_code == 0, result.output assert "AI metadata" in result.output def test_check_reports_metadata(self, tmp_path: Path): runner = CliRunner() source = _video_with_c2pa(tmp_path / "source.mp4") result = runner.invoke(main, ["video", "metadata", str(source), "--check"]) assert result.exit_code == 0, result.output assert "AI metadata detected" in result.output assert "not the same as 'clean'" not in result.output def test_remove_reports_output(self, tmp_path: Path): runner = CliRunner() source = _video_with_c2pa(tmp_path / "source.mp4") output = tmp_path / "clean.mp4" result = runner.invoke(main, ["video", "metadata", str(source), "--remove", "-o", str(output)]) assert result.exit_code == 0, result.output assert "AI metadata stripped" in result.output assert "not the same as 'clean'" in result.output assert C2PA_UUID not in output.read_bytes() def test_rejects_image_input(self, tmp_clean_png: Path): runner = CliRunner() result = runner.invoke(main, ["video", "metadata", str(tmp_clean_png), "--check"]) assert result.exit_code != 0 assert "Unsupported video format" in result.output class TestVideoProvenanceCli: def test_json_metadata_report(self, tmp_path: Path): source = _video_with_c2pa(tmp_path / "source.mp4") result = CliRunner().invoke(main, ["video", "identify", str(source), "--no-visible", "--json"]) assert result.exit_code == 0, result.output payload = json.loads(result.output) assert payload["is_ai_generated"] is True assert payload["visible_mark"] is None assert payload["has_ai_metadata"] is True class TestVideoAllApi: def test_no_visible_mark_still_strips_metadata_and_writes_output( self, tmp_path: Path, monkeypatch: pytest.MonkeyPatch, ): from remove_ai_watermarks import video from remove_ai_watermarks.video import VideoVisibleResult, remove_video_all source = _video_with_c2pa(tmp_path / "source.mp4") output = tmp_path / "clean.mp4" monkeypatch.setattr( video, "remove_video_visible", lambda *_args, **_kwargs: VideoVisibleResult( source=source, output=None, mark="auto", total_frames=3, detected_frames=0, removed_frames=0, remaining_metadata={"c2pa_manifest": "present"}, ), ) result = remove_video_all(source, output) assert result.output == output assert result.visible_mark is None assert result.detected_metadata assert result.remaining_metadata == {} assert result.invisible_removed is False assert result.visual_invisible_action == "not_run" assert result.audio.stream_action == "copied_if_present" assert result.audio.watermark_status == "unverified" assert C2PA_UUID not in output.read_bytes() assert _VIDEO_PAYLOAD in output.read_bytes() def test_visible_output_is_the_final_locally_verified_result( self, tmp_path: Path, monkeypatch: pytest.MonkeyPatch, ): from remove_ai_watermarks import video from remove_ai_watermarks.video import VideoVisibleResult, remove_video_all source = _video_with_c2pa(tmp_path / "source.mp4") output = tmp_path / "clean.mp4" def fake_visible(_source: Path, target: Path, **_kwargs: object) -> VideoVisibleResult: target.write_bytes(_MP4_FTYP + _box(b"mdat", _VIDEO_PAYLOAD)) return VideoVisibleResult( source=source, output=target, mark="sora", total_frames=12, detected_frames=12, removed_frames=12, remaining_metadata={}, ) monkeypatch.setattr(video, "remove_video_visible", fake_visible) monkeypatch.setattr( video, "remove_video_metadata", lambda *_args, **_kwargs: pytest.fail("metadata must not reprocess an already stripped visible output"), ) result = remove_video_all(source, output) assert result.visible_mark == "sora" assert result.visible_removed_frames == 12 assert result.remaining_metadata == {} assert output.exists() def test_invisible_stage_is_explicit_and_uses_an_intermediate_visible_output( self, tmp_path: Path, monkeypatch: pytest.MonkeyPatch, ): from remove_ai_watermarks import video from remove_ai_watermarks.video import ( VideoInvisibleResult, VideoVisibleResult, remove_video_all, ) source = _video_with_c2pa(tmp_path / "source.mp4") output = tmp_path / "clean.mp4" intermediate_sources: list[Path] = [] def fake_visible(_source: Path, target: Path, **_kwargs: object) -> VideoVisibleResult: target.write_bytes(_MP4_FTYP + _box(b"mdat", _VIDEO_PAYLOAD)) return VideoVisibleResult( source=source, output=target, mark="sora", total_frames=12, detected_frames=12, removed_frames=12, remaining_metadata={}, ) def fake_invisible( candidate_source: Path, target: Path, **_kwargs: object, ) -> VideoInvisibleResult: assert candidate_source != source assert candidate_source.exists() intermediate_sources.append(candidate_source) target.write_bytes(_MP4_FTYP + _box(b"mdat", _VIDEO_PAYLOAD)) return VideoInvisibleResult( source=candidate_source, output=target, noise_std=0.1, metrics=_regeneration_metrics(frames=12), remaining_metadata={}, ) monkeypatch.setattr(video, "remove_video_visible", fake_visible) monkeypatch.setattr(video, "remove_video_invisible", fake_invisible) result = remove_video_all(source, output, include_invisible=True) assert result.invisible_removed is True assert result.visual_invisible_action == "regenerated" assert result.audio.stream_action == "copied_if_present" assert result.audio.watermark_status == "unverified" assert output.exists() assert len(intermediate_sources) == 1 assert not intermediate_sources[0].exists() def test_rejects_unsupported_invisible_container_before_visible_scan( self, tmp_path: Path, monkeypatch: pytest.MonkeyPatch, ): from remove_ai_watermarks import video from remove_ai_watermarks.video import remove_video_all source = _video_with_tc260_ebml(tmp_path / "source.webm") monkeypatch.setattr( video, "remove_video_visible", lambda *_args, **_kwargs: pytest.fail("visible scan must not start"), ) with pytest.raises(ValueError, match="requires one of"): remove_video_all(source, include_invisible=True) class TestVideoAllCli: def test_help_keeps_invisible_stage_opt_in(self): result = CliRunner().invoke(main, ["video", "all", "--help"]) assert result.exit_code == 0, result.output assert "--invisible" in result.output assert "calibrated lossy regeneration" in result.output assert "video pixels" in result.output def test_reports_locally_verified_result(self, tmp_path: Path, monkeypatch: pytest.MonkeyPatch): from remove_ai_watermarks import video from remove_ai_watermarks.video import VideoAllResult source = _video_with_c2pa(tmp_path / "source.mp4") output = tmp_path / "clean.mp4" monkeypatch.setattr( video, "remove_video_all", lambda *_args, **_kwargs: VideoAllResult( source=source, output=output, visible_mark="sora", total_frames=12, visible_detected_frames=12, visible_removed_frames=12, detected_metadata={"c2pa_manifest": "present"}, remaining_metadata={}, invisible_removed=False, ), ) result = CliRunner().invoke(main, ["video", "all", str(source), "-o", str(output)]) assert result.exit_code == 0, result.output assert "removed sora from 12/12 frames" in result.output assert "Audio watermark: UNVERIFIED" in result.output def test_reports_video_regeneration_and_unverified_audio( self, tmp_path: Path, monkeypatch: pytest.MonkeyPatch, ): from remove_ai_watermarks import video from remove_ai_watermarks.video import VideoAllResult source = _video_with_c2pa(tmp_path / "source.mp4") output = tmp_path / "clean.mp4" monkeypatch.setattr( video, "remove_video_all", lambda *_args, **_kwargs: VideoAllResult( source=source, output=output, visible_mark=None, total_frames=12, visible_detected_frames=0, visible_removed_frames=0, detected_metadata={}, remaining_metadata={}, invisible_removed=True, ), ) result = CliRunner().invoke(main, ["video", "all", str(source), "--invisible"]) assert result.exit_code == 0, result.output assert "Video pixels: regenerated with the calibrated SynthID-removal profile" in result.output assert "not a per-file verification" in result.output assert "Audio watermark: UNVERIFIED" in result.output class TestVideoBatchApi: def test_visible_mode_copies_noop_and_collects_per_file_error( self, tmp_path: Path, monkeypatch: pytest.MonkeyPatch, ): from remove_ai_watermarks import video from remove_ai_watermarks.video import VideoVisibleResult, remove_video_batch directory = tmp_path / "videos" directory.mkdir() clean = directory / "clean.mp4" clean.write_bytes(_MP4_FTYP + _box(b"mdat", _VIDEO_PAYLOAD)) broken = directory / "broken.mp4" broken.write_bytes(b"not a video") (directory / "notes.txt").write_text("ignored", encoding="utf-8") def fake_visible(source: Path, *_args: object, **_kwargs: object) -> VideoVisibleResult: if source == broken: raise ValueError("invalid container") return VideoVisibleResult( source=source, output=None, mark="auto", total_frames=3, detected_frames=0, removed_frames=0, remaining_metadata={}, ) monkeypatch.setattr(video, "remove_video_visible", fake_visible) result = remove_video_batch(directory, mode="visible") assert result.processed == 1 assert result.failed == 1 assert len(result.items) == 2 assert (result.output_directory / clean.name).read_bytes() == clean.read_bytes() assert result.items[0].source.name == "broken.mp4" assert result.items[0].error == "invalid container" assert result.items[0].audio.stream_action == "not_written" assert result.items[1].changed is False assert result.items[1].audio.stream_action == "copied_if_present" def test_metadata_mode_processes_every_supported_video(self, tmp_path: Path): from remove_ai_watermarks.video import remove_video_batch directory = tmp_path / "videos" directory.mkdir() marked = _video_with_c2pa(directory / "marked.mp4") clean = directory / "clean.mp4" clean.write_bytes(_MP4_FTYP + _box(b"mdat", _VIDEO_PAYLOAD)) result = remove_video_batch(directory, mode="metadata") assert result.processed == 2 assert result.failed == 0 assert C2PA_UUID not in (result.output_directory / marked.name).read_bytes() assert (result.output_directory / clean.name).read_bytes() == clean.read_bytes() assert {item.changed for item in result.items} == {False, True} def test_rejects_invisible_stage_outside_all_mode(self, tmp_path: Path): from remove_ai_watermarks.video import remove_video_batch directory = tmp_path / "videos" directory.mkdir() with pytest.raises(ValueError, match="only in all mode"): remove_video_batch(directory, mode="visible", include_invisible=True) def test_invisible_mode_reuses_one_vae_runtime_for_the_batch( self, tmp_path: Path, monkeypatch: pytest.MonkeyPatch, ): from remove_ai_watermarks import video, video_invisible from remove_ai_watermarks.video import VideoAllResult, remove_video_batch directory = tmp_path / "videos" directory.mkdir() for name in ("one.mp4", "two.mp4"): (directory / name).write_bytes(_MP4_FTYP + _box(b"mdat", _VIDEO_PAYLOAD)) runtime = object() load_calls: list[tuple[str, str]] = [] used_runtimes: list[object] = [] def fake_load(*, model: str, device: str) -> object: load_calls.append((model, device)) return runtime def fake_all(source: Path, output: Path, **kwargs: object) -> VideoAllResult: used_runtimes.append(kwargs["_invisible_runtime"]) return VideoAllResult( source=source, output=output, visible_mark=None, total_frames=2, visible_detected_frames=0, visible_removed_frames=0, detected_metadata={}, remaining_metadata={}, invisible_removed=True, ) monkeypatch.setattr(video_invisible, "load_video_vae_runtime", fake_load) monkeypatch.setattr(video, "remove_video_all", fake_all) result = remove_video_batch(directory, include_invisible=True) assert result.processed == 2 assert load_calls == [("stabilityai/sd-vae-ft-mse", "auto")] assert used_runtimes == [runtime, runtime] def test_invisible_runtime_failure_is_not_retried_for_every_file( self, tmp_path: Path, monkeypatch: pytest.MonkeyPatch, ): from remove_ai_watermarks import video_invisible from remove_ai_watermarks.video import remove_video_batch directory = tmp_path / "videos" directory.mkdir() for name in ("one.mp4", "two.mp4"): (directory / name).write_bytes(_MP4_FTYP + _box(b"mdat", _VIDEO_PAYLOAD)) load_calls = 0 def fail_load(**_kwargs: object) -> object: nonlocal load_calls load_calls += 1 raise RuntimeError("model unavailable") monkeypatch.setattr(video_invisible, "load_video_vae_runtime", fail_load) result = remove_video_batch(directory, include_invisible=True) assert result.failed == 2 assert load_calls == 1 assert {item.error for item in result.items} == {"model unavailable"} def test_rejects_source_as_output_directory(self, tmp_path: Path): from remove_ai_watermarks.video import remove_video_batch with pytest.raises(ValueError, match="must differ"): remove_video_batch(tmp_path, tmp_path) class TestVideoBatchCli: def test_help(self): result = CliRunner().invoke(main, ["video", "batch", "--help"]) assert result.exit_code == 0, result.output assert "all|visible|metadata" in result.output assert "--invisible" in result.output def test_returns_nonzero_when_any_file_fails( self, tmp_path: Path, monkeypatch: pytest.MonkeyPatch, ): from remove_ai_watermarks import video from remove_ai_watermarks.video import VideoBatchItem, VideoBatchResult directory = tmp_path / "videos" directory.mkdir() output = tmp_path / "clean" failed_source = directory / "broken.mp4" monkeypatch.setattr( video, "remove_video_batch", lambda *_args, **_kwargs: VideoBatchResult( directory=directory, output_directory=output, items=( VideoBatchItem( source=failed_source, output=None, mode="all", changed=False, visible_mark=None, invisible_removed=False, error="invalid container", ), ), ), ) result = CliRunner().invoke(main, ["video", "batch", str(directory)]) assert result.exit_code == 1 assert "FAILED broken.mp4: invalid container" in result.output assert "1 failed" in result.output def test_reports_visual_regeneration_without_claiming_audio_removal( self, tmp_path: Path, monkeypatch: pytest.MonkeyPatch, ): from remove_ai_watermarks import video from remove_ai_watermarks.video import VideoBatchItem, VideoBatchResult directory = tmp_path / "videos" directory.mkdir() output = tmp_path / "clean" source = directory / "source.mp4" monkeypatch.setattr( video, "remove_video_batch", lambda *_args, **_kwargs: VideoBatchResult( directory=directory, output_directory=output, items=( VideoBatchItem( source=source, output=output / source.name, mode="all", changed=True, visible_mark=None, invisible_removed=True, ), ), ), ) result = CliRunner().invoke(main, ["video", "batch", str(directory), "--invisible"]) assert result.exit_code == 0, result.output assert "Video pixel regeneration: applied to 1 file(s)" in result.output assert "SynthID: removed" not in result.output assert "Audio watermark: UNVERIFIED" in result.output class TestVideoInvisibleApi: def test_removes_synthid_and_strips_metadata( self, tmp_path: Path, monkeypatch: pytest.MonkeyPatch, ): from remove_ai_watermarks import video_invisible from remove_ai_watermarks.video import remove_video_invisible source = _video_with_c2pa(tmp_path / "source.mp4") output = tmp_path / "clean.mp4" def fake_regenerate(_source: Path, target: Path, **_kwargs: object): target.write_bytes(_MP4_FTYP + _box(b"mdat", _VIDEO_PAYLOAD)) return _regeneration_metrics() monkeypatch.setattr(video_invisible, "regenerate_video_candidate", fake_regenerate) result = remove_video_invisible(source, output) assert result.output == output assert result.total_frames == 24 assert result.remaining_metadata == {} assert result.visual_invisible_action == "regenerated" assert result.audio.stream_action == "copied_if_present" assert result.audio.watermark_status == "unverified" def test_default_output_is_named_as_clean( self, tmp_path: Path, monkeypatch: pytest.MonkeyPatch, ): from remove_ai_watermarks import video_invisible from remove_ai_watermarks.video import remove_video_invisible source = _video_with_c2pa(tmp_path / "source.mp4") def fake_regenerate(_source: Path, target: Path, **_kwargs: object): target.write_bytes(_MP4_FTYP + _box(b"mdat", _VIDEO_PAYLOAD)) return _regeneration_metrics( frames=2, fps=2.0, width=16, height=16, psnr_db=20.0, temporal_residual_ratio=1.0, ) monkeypatch.setattr(video_invisible, "regenerate_video_candidate", fake_regenerate) result = remove_video_invisible(source) assert result.output == tmp_path / "source_clean.mp4" def test_rejects_webm_regeneration(self, tmp_path: Path): from remove_ai_watermarks.video import remove_video_invisible source = _video_with_tc260_ebml(tmp_path / "source.webm") with pytest.raises(ValueError, match="requires one of"): remove_video_invisible(source) class TestVideoInvisibleCli: def test_help_describes_calibrated_video_pixel_action(self): runner = CliRunner() result = runner.invoke(main, ["video", "invisible", "--help"]) assert result.exit_code == 0, result.output assert "calibrated SynthID-removal profile to video pixels" in result.output def test_reports_completed_removal( self, tmp_path: Path, monkeypatch: pytest.MonkeyPatch, ): from remove_ai_watermarks import video runner = CliRunner() source = _video_with_c2pa(tmp_path / "source.mp4") output = tmp_path / "clean.mp4" def fake_remove(_source: Path, target: Path, **_kwargs: object): target.write_bytes(_MP4_FTYP + _box(b"mdat", _VIDEO_PAYLOAD)) return video.VideoInvisibleResult( source=_source, output=target, noise_std=0.1, metrics=_regeneration_metrics(), remaining_metadata={}, ) monkeypatch.setattr(video, "remove_video_invisible", fake_remove) result = runner.invoke(main, ["video", "invisible", str(source), "-o", str(output)]) assert result.exit_code == 0, result.output assert "SynthID removal complete" not in result.output assert "Video pixels: regenerated with the calibrated SynthID-removal profile" in result.output assert "not a per-file verification" in result.output assert "Audio watermark: UNVERIFIED" in result.output class TestSoraFrameLocalization: def test_localizes_independently_rendered_sora_like_mark(self): from remove_ai_watermarks.video_visible import _region_iou, detect_sora_frame frame, expected = _independent_sora_frame() detection = detect_sora_frame(frame) assert detection.region is not None assert detection.confidence >= 0.58 assert _region_iou(detection.region, expected) >= 0.45 def test_empty_frame_is_not_localized(self): from remove_ai_watermarks.video_visible import detect_sora_frame detection = detect_sora_frame(np.empty((0, 0, 3), dtype=np.uint8)) assert detection.confidence == 0.0 assert detection.region is None def test_localizes_independently_rendered_mascot_without_wordmark(self): from remove_ai_watermarks.video_visible import detect_sora_frame mark, _region = _independent_sora_mark() mascot = cv2.resize(mark[:, :45], (55, 55), interpolation=cv2.INTER_AREA) frame = np.full((720, 1280, 3), 36, dtype=np.uint8) _stamp_gray_mark(frame, mascot, x=900, y=500, opacity=0.78) detection = detect_sora_frame(frame) assert detection.confidence >= 0.60 assert detection.region is not None assert abs(detection.region[0] - 900) <= 10 assert abs(detection.region[1] - 500) <= 10 class TestVeoFrameLocalization: def test_localizes_independently_rendered_diamond_at_relocated_position(self): from remove_ai_watermarks.video_visible import _region_iou, detect_sora_frame, detect_veo_frame frame = np.full((720, 1280, 3), 28, dtype=np.uint8) size = 55 x, y = 1080, 570 mark = Image.new("L", (size, size), 0) points = ( (size // 2, 1), (round(size * 0.61), round(size * 0.38)), (size - 2, size // 2), (round(size * 0.61), round(size * 0.62)), (size // 2, size - 2), (round(size * 0.39), round(size * 0.62)), (1, size // 2), (round(size * 0.39), round(size * 0.38)), ) ImageDraw.Draw(mark).polygon(points, fill=255) _stamp_gray_mark(frame, mark, x=x, y=y, opacity=0.72) detection = detect_veo_frame(frame) assert detection.region is not None assert detection.confidence >= 0.70 assert _region_iou(detection.region, (x, y, size, size)) >= 0.70 assert detect_sora_frame(frame).confidence < 0.60 def test_localizes_independently_rendered_legacy_text(self): from remove_ai_watermarks.video_visible import _region_iou, detect_veo_frame frame = np.full((720, 1280, 3), 42, dtype=np.uint8) mark = Image.new("L", (60, 24), 0) try: font = ImageFont.load_default(size=19) except TypeError: font = ImageFont.load_default() ImageDraw.Draw(mark).text((1, 0), "Veo", font=font, fill=255) mark_array = np.asarray(mark) ys, xs = np.where(mark_array > 0) mark_array = mark_array[ys.min() : ys.max() + 1, xs.min() : xs.max() + 1] mark_height, mark_width = mark_array.shape x = frame.shape[1] - mark_width - 20 y = frame.shape[0] - mark_height - 18 _stamp_gray_mark(frame, mark_array, x=x, y=y, opacity=0.66) detection = detect_veo_frame(frame) assert detection.region is not None assert detection.confidence >= 0.55 assert _region_iou(detection.region, (x, y, mark_width, mark_height)) >= 0.65 def test_empty_frame_is_not_localized(self): from remove_ai_watermarks.video_visible import detect_veo_frame detection = detect_veo_frame(np.empty((0, 0, 3), dtype=np.uint8)) assert detection.confidence == 0.0 assert detection.region is None def test_diamond_mask_preserves_transparent_box_corners(self): from remove_ai_watermarks.video_visible import _mask_for_region mask = _mask_for_region( np.zeros((100, 100, 3), dtype=np.uint8), (20, 20, 48, 48), padding_fraction=0.18, mask_style="veo", ) assert mask[44, 44] == 255 assert mask[20, 20] == 0 assert mask[67, 67] == 0 class TestByteDanceFrameLocalization: def test_localizes_independently_rendered_seedance_box(self): from remove_ai_watermarks.video_visible import _region_iou, detect_seedance_frame frame = np.full((720, 1280, 3), 30, dtype=np.uint8) mark = Image.new("L", (80, 60), 0) draw = ImageDraw.Draw(mark) draw.rounded_rectangle((2, 2, 70, 53), radius=14, outline=255, width=4) try: font = ImageFont.load_default(size=35) except TypeError: font = ImageFont.load_default() draw.text((18, 8), "AI", font=font, fill=255) x, y = 1130, 620 _stamp_gray_mark(frame, mark, x=x, y=y, opacity=0.65) detection = detect_seedance_frame(frame) assert detection.region is not None assert detection.confidence >= 0.43 assert _region_iou(detection.region, (x, y, 80, 60)) >= 0.75 def test_localizes_independently_rendered_dola_text(self): from pathlib import Path from remove_ai_watermarks.video_visible import _region_iou, detect_dola_frame frame = np.full((720, 1280, 3), 35, dtype=np.uint8) # A frozen OpenCV 4 render in the other Hershey face: OpenCV 5 draws the same # putText call smaller and heavier (scripts/render_video_hershey_templates.py). fixture = Path(__file__).resolve().parents[1] / "data/fixtures/synthetic/dola_hershey_simplex.png" mark = cv2.imread(str(fixture), cv2.IMREAD_GRAYSCALE) assert mark is not None mark_height, mark_width = mark.shape x = frame.shape[1] - mark_width - 18 y = frame.shape[0] - mark_height - 14 _stamp_gray_mark(frame, mark, x=x, y=y, opacity=0.75) detection = detect_dola_frame(frame) assert detection.region is not None assert detection.confidence >= 0.52 assert _region_iou(detection.region, (x, y, mark_width, mark_height)) >= 0.75 def test_seedance_box_mask_covers_the_full_localized_mark(self): from remove_ai_watermarks.video_visible import _mask_for_region mask = _mask_for_region( np.zeros((120, 160, 3), dtype=np.uint8), (20, 20, 80, 60), padding_fraction=0.0, mask_style="box", ) assert mask[15, 15] == 0 assert mask[16, 16] == 255 assert mask[83, 103] == 255 assert mask[84, 104] == 0 class TestAdditionalProviderFrameLocalization: def test_localizes_independently_rendered_hailuo_label(self): from remove_ai_watermarks.video_visible import _region_iou, detect_hailuo_frame frame = np.full((720, 1280, 3), 32, dtype=np.uint8) mark = Image.new("L", (330, 54), 0) draw = ImageDraw.Draw(mark) try: font = ImageFont.load_default(size=28) except TypeError: font = ImageFont.load_default() for index, height in enumerate((20, 34, 46, 34, 20)): x = 4 + index * 6 draw.rounded_rectangle((x, 27 - height // 2, x + 2, 27 + height // 2), radius=1, fill=255) draw.text((39, 8), "MINIMAX", font=font, fill=255) draw.rectangle((164, 7, 166, 47), fill=255) draw.ellipse((178, 8, 224, 50), outline=255, width=5) draw.text((228, 8), "hailuo AI", font=font, fill=255) x, y = 930, 650 _stamp_gray_mark(frame, mark, x=x, y=y, opacity=0.75) detection = detect_hailuo_frame(frame) assert detection.region is not None assert detection.confidence >= 0.24 assert _region_iou(detection.region, (x, y, 330, 54)) >= 0.45 def test_localizes_kling_core_and_covers_version_suffix(self): from remove_ai_watermarks.video_visible import detect_kling_frame frame = np.full((720, 1280, 3), 28, dtype=np.uint8) mark = np.zeros((42, 245), dtype=np.uint8) cv2.ellipse(mark, (20, 21), (15, 15), 0, 20, 330, 255, 4, cv2.LINE_AA) cv2.putText( mark, "KLING AI 1.6", (43, 31), cv2.FONT_HERSHEY_SIMPLEX, 0.9, 255, 2, cv2.LINE_AA, ) x, y = 1018, 664 _stamp_gray_mark(frame, mark, x=x, y=y, opacity=0.72) detection = detect_kling_frame(frame) glyph_ys, glyph_xs = np.where(mark > 0) glyph_box = ( x + int(glyph_xs.min()), y + int(glyph_ys.min()), int(glyph_xs.max() - glyph_xs.min() + 1), int(glyph_ys.max() - glyph_ys.min() + 1), ) assert detection.region is not None assert detection.confidence >= 0.24 detected_x, detected_y, detected_width, detected_height = detection.region glyph_x, glyph_y, glyph_width, glyph_height = glyph_box assert detected_x <= glyph_x assert detected_y <= glyph_y assert detected_x + detected_width >= glyph_x + glyph_width assert detected_y + detected_height >= glyph_y + glyph_height def test_rejects_a_saturated_fixed_kling_shape(self): from remove_ai_watermarks.video_visible import detect_kling_frame frame = np.full((720, 1280, 3), 24, dtype=np.uint8) cv2.circle(frame, (1040, 670), 15, (0, 220, 0), 5, cv2.LINE_AA) cv2.putText( frame, "KLING AI 1.6", (1065, 681), cv2.FONT_HERSHEY_SIMPLEX, 0.9, (0, 220, 0), 2, cv2.LINE_AA, ) detection = detect_kling_frame(frame) assert detection.confidence == 0.0 assert detection.region is None def test_ignores_a_logo_candidate_left_of_the_wordmark(self, monkeypatch: pytest.MonkeyPatch): from remove_ai_watermarks import video_visible from remove_ai_watermarks.video_visible import FrameLocalization, detect_kling_frame frame = np.zeros((720, 1280, 3), dtype=np.uint8) frame[670:706, 1120:1280] = 255 def fixed_mark(_frame, template_key, *, frame_index, **_kwargs): if template_key == "kling-3": return FrameLocalization(frame_index, 0.63, (1156, 678, 60, 20)) if template_key == "kling-logo": return FrameLocalization(frame_index, 0.55, (900, 680, 20, 20)) return FrameLocalization(frame_index, 0.0, None) monkeypatch.setattr(video_visible, "_detect_fixed_mark", fixed_mark) detection = detect_kling_frame(frame) assert detection.region == (1120, 670, 160, 36) assert detection.confidence == 0.63 class TestSoraTemporalArbiter: _BOX = (40, 60, 150, 54) def test_four_frame_lookalike_run_is_too_short(self): from remove_ai_watermarks.video_visible import FrameLocalization, stabilize_localizations detections = [FrameLocalization(index, 0.70, self._BOX) for index in range(4)] assert stabilize_localizations("sora", detections, provenance=False) == [None] * 4 def test_provenance_accepts_recurring_low_contrast_visual_match(self): from remove_ai_watermarks.video_visible import FrameLocalization, stabilize_localizations detections = [ FrameLocalization(0, 0.59, self._BOX), FrameLocalization(1, 0.61, self._BOX), FrameLocalization(2, 0.62, self._BOX), FrameLocalization(3, 0.60, self._BOX), FrameLocalization(4, 0.61, self._BOX), ] assert stabilize_localizations("sora", detections, provenance=True) == [self._BOX] * 5 def test_confirmed_provenance_run_covers_transition_frames(self): from remove_ai_watermarks.video_visible import FrameLocalization, stabilize_localizations detections = [ FrameLocalization(0, 0.30, (500, 300, 54, 54)), FrameLocalization(1, 0.59, self._BOX), FrameLocalization(2, 0.61, self._BOX), FrameLocalization(3, 0.62, self._BOX), FrameLocalization(4, 0.60, self._BOX), FrameLocalization(5, 0.61, self._BOX), FrameLocalization(6, 0.30, (300, 100, 54, 54)), ] assert stabilize_localizations("sora", detections, provenance=True) == [self._BOX] * 7 def test_transition_prefers_low_score_match_at_a_confirmed_position(self): from remove_ai_watermarks.video_visible import FrameLocalization, stabilize_localizations other_box = (500, 300, 150, 54) detections = [ FrameLocalization(0, 0.61, self._BOX), FrameLocalization(1, 0.62, self._BOX), FrameLocalization(2, 0.63, self._BOX), FrameLocalization(3, 0.61, self._BOX), FrameLocalization(4, 0.62, self._BOX), FrameLocalization(5, 0.20, (250, 180, 54, 54)), FrameLocalization(6, 0.52, self._BOX), FrameLocalization(7, 0.61, other_box), FrameLocalization(8, 0.62, other_box), FrameLocalization(9, 0.63, other_box), FrameLocalization(10, 0.61, other_box), FrameLocalization(11, 0.62, other_box), ] stabilized = stabilize_localizations("sora", detections, provenance=True) assert stabilized[6] == self._BOX assert stabilized[7:] == [other_box] * 5 def test_transition_without_a_match_keeps_previous_stable_position(self): from remove_ai_watermarks.video_visible import FrameLocalization, stabilize_localizations other_box = (500, 300, 150, 54) detections = [ FrameLocalization(0, 0.61, self._BOX), FrameLocalization(1, 0.62, self._BOX), FrameLocalization(2, 0.63, self._BOX), FrameLocalization(3, 0.61, self._BOX), FrameLocalization(4, 0.62, self._BOX), FrameLocalization(5, 0.20, (250, 180, 54, 54)), FrameLocalization(6, 0.20, (300, 200, 54, 54)), FrameLocalization(7, 0.61, other_box), FrameLocalization(8, 0.62, other_box), FrameLocalization(9, 0.63, other_box), FrameLocalization(10, 0.61, other_box), FrameLocalization(11, 0.62, other_box), ] stabilized = stabilize_localizations("sora", detections, provenance=True) assert stabilized[5:7] == [self._BOX, self._BOX] def test_unproven_weak_run_is_rejected(self): from remove_ai_watermarks.video_visible import FrameLocalization, stabilize_localizations detections = [ FrameLocalization(0, 0.61, self._BOX), FrameLocalization(1, 0.62, self._BOX), FrameLocalization(2, 0.63, self._BOX), FrameLocalization(3, 0.62, self._BOX), FrameLocalization(4, 0.61, self._BOX), ] assert stabilize_localizations("sora", detections, provenance=False) == [None] * 5 def test_strong_recurring_visual_run_needs_no_metadata(self): from remove_ai_watermarks.video_visible import FrameLocalization, stabilize_localizations detections = [ FrameLocalization(0, 0.61, self._BOX), FrameLocalization(1, 0.66, self._BOX), FrameLocalization(2, 0.62, self._BOX), FrameLocalization(3, 0.61, self._BOX), FrameLocalization(4, 0.62, self._BOX), ] assert stabilize_localizations("sora", detections, provenance=False) == [self._BOX] * 5 def test_isolated_lookalikes_at_different_positions_are_rejected(self): from remove_ai_watermarks.video_visible import FrameLocalization, stabilize_localizations detections = [ FrameLocalization(0, 0.70, (10, 10, 150, 54)), FrameLocalization(1, 0.70, (400, 200, 150, 54)), FrameLocalization(2, 0.70, (650, 400, 150, 54)), ] assert stabilize_localizations("sora", detections, provenance=True) == [None, None, None] def test_short_dropout_between_matching_boxes_is_filled(self): from remove_ai_watermarks.video_visible import FrameLocalization, stabilize_localizations detections = [ FrameLocalization(0, 0.66, self._BOX), FrameLocalization(1, 0.20, (500, 300, 54, 54)), FrameLocalization(2, 0.67, self._BOX), FrameLocalization(3, 0.66, self._BOX), FrameLocalization(4, 0.66, self._BOX), FrameLocalization(5, 0.66, self._BOX), ] assert stabilize_localizations("sora", detections, provenance=False) == [self._BOX] * 6 class TestVeoTemporalArbiter: _BOX = (1132, 572, 56, 56) def test_eleven_frame_lookalike_run_is_too_short(self): from remove_ai_watermarks.video_visible import FrameLocalization, stabilize_localizations detections = [FrameLocalization(index, 0.70, self._BOX) for index in range(11)] assert stabilize_localizations("veo", detections, provenance=False) == [None] * 11 def test_strong_fixed_run_covers_video_without_metadata(self): from remove_ai_watermarks.video_visible import FrameLocalization, stabilize_localizations detections = [FrameLocalization(index, 0.60, self._BOX) for index in range(12)] detections.extend(FrameLocalization(index, 0.20, (300, 200, 48, 48)) for index in range(12, 15)) assert stabilize_localizations("veo", detections, provenance=False) == [self._BOX] * 15 def test_google_provenance_accepts_recurring_low_contrast_diamond(self): from remove_ai_watermarks.video_visible import FrameLocalization, stabilize_localizations detections = [FrameLocalization(index, 0.47, self._BOX) for index in range(12)] assert stabilize_localizations("veo", detections, provenance=True) == [self._BOX] * 12 def test_unproven_weak_run_is_rejected(self): from remove_ai_watermarks.video_visible import FrameLocalization, stabilize_localizations detections = [FrameLocalization(index, 0.52, self._BOX) for index in range(12)] assert stabilize_localizations("veo", detections, provenance=False) == [None] * 12 class TestByteDanceTemporalArbiter: _SEEDANCE_BOX = (1110, 610, 90, 66) _DOLA_BOX = (1160, 680, 96, 22) def test_seedance_requires_twelve_recurring_frames(self): from remove_ai_watermarks.video_visible import FrameLocalization, stabilize_localizations detections = [FrameLocalization(index, 0.50, self._SEEDANCE_BOX) for index in range(11)] assert stabilize_localizations("seedance", detections, provenance=False) == [None] * 11 def test_seedance_strong_run_covers_low_contrast_frames(self): from remove_ai_watermarks.video_visible import FrameLocalization, stabilize_localizations detections = [FrameLocalization(index, 0.45, self._SEEDANCE_BOX) for index in range(12)] detections.extend(FrameLocalization(index, 0.20, (200, 100, 80, 60)) for index in range(12, 15)) assert stabilize_localizations("seedance", detections, provenance=False) == [self._SEEDANCE_BOX] * 15 def test_seedance_rejects_a_slowly_drifting_scene_detail(self): from remove_ai_watermarks.video_visible import FrameLocalization, stabilize_localizations detections = [FrameLocalization(index, 0.46, (1110 - index * 3, 610, 90, 66)) for index in range(14)] assert stabilize_localizations("seedance", detections, provenance=False) == [None] * 14 def test_dola_requires_twelve_recurring_frames(self): from remove_ai_watermarks.video_visible import FrameLocalization, stabilize_localizations detections = [FrameLocalization(index, 0.60, self._DOLA_BOX) for index in range(11)] assert stabilize_localizations("dola", detections, provenance=True) == [None] * 11 def test_dola_provenance_accepts_recurring_low_contrast_text(self): from remove_ai_watermarks.video_visible import FrameLocalization, stabilize_localizations detections = [FrameLocalization(index, 0.49, self._DOLA_BOX) for index in range(12)] assert stabilize_localizations("dola", detections, provenance=True) == [self._DOLA_BOX] * 12 def test_dola_without_provenance_needs_a_strong_frame(self): from remove_ai_watermarks.video_visible import FrameLocalization, stabilize_localizations detections = [FrameLocalization(index, 0.51, self._DOLA_BOX) for index in range(12)] assert stabilize_localizations("dola", detections, provenance=False) == [None] * 12 def test_bytedance_provenance_requires_ai_source_type(self): from remove_ai_watermarks.video_visible import has_bytedance_video_provenance assert has_bytedance_video_provenance( { "issuer": "BytePlus (ByteDance)", "source_type": "trainedAlgorithmicMedia (AI-generated)", } ) assert not has_bytedance_video_provenance({"issuer": "BytePlus (ByteDance)"}) class TestAdditionalProviderTemporalArbiter: _HAILUO_BOX = (930, 650, 330, 54) _KLING_BOX = (1018, 664, 245, 42) @pytest.mark.parametrize( ("mark", "box", "weak_score", "strong_score"), [ ("hailuo", _HAILUO_BOX, 0.31, 0.35), ("kling", _KLING_BOX, 0.21, 0.25), ], ) def test_requires_a_strong_anchored_twelve_frame_run( self, mark: str, box: tuple[int, int, int, int], weak_score: float, strong_score: float, ): from remove_ai_watermarks.video_visible import FrameLocalization, stabilize_localizations def stabilize(dets): return stabilize_localizations(mark, dets) weak = [FrameLocalization(index, weak_score, box) for index in range(12)] strong = [FrameLocalization(index, strong_score, box) for index in range(12)] assert stabilize(weak) == [None] * 12 assert stabilize(strong) == [box] * 12 # Producer codes as they appear on real exports: Vidu (measured on a Vidu Q3 # export, 2026-09-23) and Kling (the registered image producer). _VIDU_PRODUCER = "001191110108MACC4D63XF10306" _KLING_PRODUCER = "001191110108335469089C10100" def test_non_kling_tc260_producer_vetoes_kling_video_mark(self): # A real Vidu "Vidu AI" logo plus wordmark formed a stable 193-frame kling run # and was reported as Kling. The TC260 producer contradicts it. from remove_ai_watermarks.video import _visible_removal_plan from remove_ai_watermarks.video_visible import FrameLocalization, VideoScan scan = VideoScan( width=1920, height=1080, fps=24.0, detections=tuple(FrameLocalization(index, 0.56, self._KLING_BOX) for index in range(12)), ) vetoed, _, _ = _visible_removal_plan("kling", scan, {"aigc_producer": self._VIDU_PRODUCER}) kept, _, _ = _visible_removal_plan("kling", scan, {"aigc_producer": self._KLING_PRODUCER}) unlabeled, _, _ = _visible_removal_plan("kling", scan, {}) assert vetoed == [None] * 12 assert kept == [self._KLING_BOX] * 12 assert unlabeled == [self._KLING_BOX] * 12 def test_shengshu_tc260_label_confirms_vidu(self): from remove_ai_watermarks.video_visible import has_vidu_video_provenance assert has_vidu_video_provenance({"aigc_producer": self._VIDU_PRODUCER}) assert not has_vidu_video_provenance({"aigc_producer": self._KLING_PRODUCER}) assert not has_vidu_video_provenance({}) @pytest.mark.parametrize( ("producer", "platform"), [ # Producer values as measured on Higgsfield video downloads, 2026-09-24. ("kling", "Kuaishou Kling AI"), ("001191330106MA2CFLDG4R10001", "Alibaba Tongyi (Wan, HappyHorse)"), ("MiniMax", "MiniMax Hailuo AI"), ("91110108MACC4D63XF", "ShengShu Vidu"), ("91110000000000000X", "China AIGC-labeled content (TC260 standard)"), ], ) def test_tc260_producer_names_the_video_platform(self, producer: str, platform: str): from remove_ai_watermarks.video import _platform_from_video_metadata markers = {"aigc_label": "China AIGC label (TC260)", "aigc_producer": producer} assert _platform_from_video_metadata(markers) == platform def test_bare_kling_tc260_producer_keeps_the_kling_mark(self): from remove_ai_watermarks.video_visible import contradicts_video_provenance assert not contradicts_video_provenance("kling", {"aigc_producer": "kling"}) def test_non_kling_c2pa_ai_claim_vetoes_kling_video_mark(self): # Veo 3.1 Lite from Higgsfield carries Google's C2PA and scored above the # Kling floor on wood texture on all 192 frames (2026-09-24). from remove_ai_watermarks.video_visible import contradicts_video_provenance ai = "http://cv.iptc.org/newscodes/digitalsourcetype/trainedAlgorithmicMedia" assert contradicts_video_provenance("kling", {"issuer": "Google LLC", "source_type": ai}) assert not contradicts_video_provenance("kling", {"issuer": "Kuaishou", "source_type": ai}) # A re-signing platform records no AI source type and vetoes nothing. assert not contradicts_video_provenance("kling", {"issuer": "YouTube"}) assert not contradicts_video_provenance("kling", {"source_type": ai}) @pytest.mark.parametrize( ("mark", "own_issuer"), [ ("sora", "OpenAI"), ("veo", "Google LLC"), ("seedance", "BytePlus"), ("doubao", "ByteDance"), ("dola", "ByteDance"), ("hailuo", "MiniMax"), ("vidu", "ShengShu"), ("kling", "Kuaishou"), ], ) def test_another_vendor_c2pa_ai_claim_vetoes_every_video_mark(self, mark: str, own_issuer: str): # Gemini Omni 1.1 Flash via Runway carries Google's C2PA and scored above the # strict Sora floor on a steam wisp for five frames (2026-09-25). from remove_ai_watermarks.video import VIDEO_VISIBLE_MARKS from remove_ai_watermarks.video_visible import contradicts_video_provenance ai = "http://cv.iptc.org/newscodes/digitalsourcetype/trainedAlgorithmicMedia" foreign = "RUNWAY AI, INC." if mark != "sora" else "Google LLC" assert mark in VIDEO_VISIBLE_MARKS assert contradicts_video_provenance(mark, {"issuer": foreign, "source_type": ai}) assert not contradicts_video_provenance(mark, {"issuer": own_issuer, "source_type": ai}) assert not contradicts_video_provenance(mark, {"issuer": foreign}) @pytest.mark.parametrize("mark", ["veo", "seedance", "doubao", "dola", "hailuo", "vidu", "kling"]) def test_opening_only_label_is_not_carried_over_the_clean_rest(self, mark: str): # China's labeling rules require a video label only on the opening frames. A # 2 s label at 24 fps used to be carried over every later, clean frame. from remove_ai_watermarks.video_visible import ( VISIBLE_MARK_POLICIES, FrameLocalization, stabilize_localizations, ) policy = VISIBLE_MARK_POLICIES[mark] score = max(policy.strong_floor, policy.weak_floor) + 0.05 box = (1700, 980, 200, 60) detections = [FrameLocalization(index, score, box) for index in range(48)] detections += [FrameLocalization(index, 0.0, None) for index in range(48, 240)] accepted = stabilize_localizations(mark, detections) assert accepted[:48] == [box] * 48 assert accepted[48 + policy.min_stable_frames + 1 :] == [None] * (240 - 48 - policy.min_stable_frames - 1) def test_hailuo_provenance_accepts_sub_strong_stable_run(self): # A TC260 label naming MiniMax relaxes only the strong-frame requirement: # the entry bar stays the measured weak floor (0.30), so a stable run # between weak and strong (0.34) passes with provenance, fails without. from remove_ai_watermarks.video_visible import FrameLocalization, stabilize_localizations detections = [FrameLocalization(index, 0.31, self._HAILUO_BOX) for index in range(12)] assert stabilize_localizations("hailuo", detections, provenance=False) == [None] * 12 assert stabilize_localizations("hailuo", detections, provenance=True) == [self._HAILUO_BOX] * 12 def test_hailuo_provenance_requires_a_minimax_producer_label(self): from remove_ai_watermarks.video_visible import has_hailuo_video_provenance assert has_hailuo_video_provenance({"aigc_producer": "MiniMax"}) assert not has_hailuo_video_provenance({"aigc_producer": "001191110102MACQD9K64010000"}) assert not has_hailuo_video_provenance({"aigc_label": "China AIGC label (TC260); producer MiniMax"}) assert not has_hailuo_video_provenance({"issuer": "Some other vendor"}) class TestVideoVisibleScan: def test_auto_prepares_each_frame_once_for_every_detector( self, tmp_path: Path, monkeypatch: pytest.MonkeyPatch, ): from remove_ai_watermarks import video_visible from remove_ai_watermarks.video_visible import FrameLocalization, scan_video_marks frame = np.zeros((8, 12, 3), dtype=np.uint8) class FakeCapture: def __init__(self) -> None: self._read = False def isOpened(self) -> bool: return True def get(self, property_id: int) -> float: return { video_visible.cv2.CAP_PROP_FRAME_WIDTH: 12.0, video_visible.cv2.CAP_PROP_FRAME_HEIGHT: 8.0, video_visible.cv2.CAP_PROP_FPS: 24.0, video_visible.cv2.CAP_PROP_POS_MSEC: 0.0, }[property_id] def read(self) -> tuple[bool, np.ndarray | None]: if self._read: return False, None self._read = True return True, frame def release(self) -> None: pass prepared_ids: list[int] = [] def fake_detector( _frame: np.ndarray, *, frame_index: int, prepared: object, ) -> FrameLocalization: assert prepared is not None prepared_ids.append(id(prepared)) return FrameLocalization(frame_index, 0.0, None) monkeypatch.setattr(video_visible.cv2, "VideoCapture", lambda _path: FakeCapture()) monkeypatch.setattr(video_visible, "probe_video_timestamps", lambda _path: (0.25,)) for detector_name in ( "detect_sora_frame", "detect_veo_frame", "detect_seedance_frame", "detect_dola_frame", "detect_hailuo_frame", "detect_vidu_frame", "detect_kling_frame", ): monkeypatch.setattr(video_visible, detector_name, fake_detector) scans = scan_video_marks( tmp_path / "synthetic.mp4", ("sora", "veo", "seedance", "doubao", "dola", "hailuo", "vidu", "kling"), ) assert set(scans) == {"sora", "veo", "seedance", "doubao", "dola", "hailuo", "vidu", "kling"} assert all(scan.timestamps == (0.25,) for scan in scans.values()) assert len(prepared_ids) == 7 assert len(set(prepared_ids)) == 1 class TestVideoVisibleEncoding: @staticmethod def _patch_single_frame_encode( monkeypatch: pytest.MonkeyPatch, *, encoded_bytes: bytes, fail: bool, ) -> tuple[object, list[Path]]: from remove_ai_watermarks import video_visible, watermark_registry frame = np.full((8, 8, 3), 32, dtype=np.uint8) class FakeCapture: def __init__(self) -> None: self._read = False def isOpened(self) -> bool: return True def read(self) -> tuple[bool, np.ndarray | None]: if self._read: return False, None self._read = True return True, frame.copy() def release(self) -> None: pass class FakeProcess: def __init__(self) -> None: self.stdin = io.BytesIO() def poll(self) -> int: return 1 def wait(self) -> int: return 1 def discard_stderr(self) -> None: pass targets: list[Path] = [] def fake_command(target: Path, **_kwargs: object) -> list[str]: targets.append(target) return ["ffmpeg", str(target)] def fake_finish(_process: object, target: Path, **_kwargs: object) -> None: target.write_bytes(encoded_bytes) if fail: raise RuntimeError("synthetic encode failure") def fake_mux(encoded: Path, _source: Path, target: Path, **_kwargs: object) -> None: target.write_bytes(encoded.read_bytes()) process = FakeProcess() monkeypatch.setattr(video_visible.cv2, "VideoCapture", lambda _path: FakeCapture()) monkeypatch.setattr(video_visible, "raw_video_command", fake_command) monkeypatch.setattr(video_visible, "start_raw_video_encoder", lambda _command: process) monkeypatch.setattr(video_visible, "finish_raw_video_encoder", fake_finish) monkeypatch.setattr(video_visible, "mux_encoded_video", fake_mux) monkeypatch.setattr(watermark_registry, "resolve_backend", lambda _backend: "cv2") return process, targets def test_publishes_completed_encode_atomically( self, tmp_path: Path, monkeypatch: pytest.MonkeyPatch, ): from remove_ai_watermarks.video_visible import FrameLocalization, VideoScan, encode_clean_video source = tmp_path / "source.mp4" source.write_bytes(b"source") output = tmp_path / "clean.mp4" scan = VideoScan(8, 8, 24.0, (FrameLocalization(0, 0.0, None),)) _process, targets = self._patch_single_frame_encode( monkeypatch, encoded_bytes=b"complete", fail=False, ) encode_clean_video( source, output, scan, [None], backend="cv2", strip_metadata=True, ) assert output.read_bytes() == b"complete" assert targets[0] != output assert targets[0].suffix == output.suffix assert not targets[0].exists() def test_failed_encode_preserves_existing_output( self, tmp_path: Path, monkeypatch: pytest.MonkeyPatch, ): from remove_ai_watermarks.video_visible import FrameLocalization, VideoScan, encode_clean_video source = tmp_path / "source.mp4" source.write_bytes(b"source") output = tmp_path / "clean.mp4" output.write_bytes(b"previous") scan = VideoScan(8, 8, 24.0, (FrameLocalization(0, 0.0, None),)) _process, targets = self._patch_single_frame_encode( monkeypatch, encoded_bytes=b"partial", fail=True, ) with pytest.raises(RuntimeError, match="synthetic encode failure"): encode_clean_video( source, output, scan, [None], backend="cv2", strip_metadata=True, ) assert output.read_bytes() == b"previous" assert not targets[0].exists() def test_failed_mux_preserves_existing_output( self, tmp_path: Path, monkeypatch: pytest.MonkeyPatch, ): from remove_ai_watermarks import video_visible from remove_ai_watermarks.video_visible import FrameLocalization, VideoScan, encode_clean_video source = tmp_path / "source.mp4" source.write_bytes(b"source") output = tmp_path / "clean.mp4" output.write_bytes(b"previous") scan = VideoScan(8, 8, 24.0, (FrameLocalization(0, 0.0, None),)) _process, targets = self._patch_single_frame_encode( monkeypatch, encoded_bytes=b"complete video stream", fail=False, ) def fail_mux(*_args: object, **_kwargs: object) -> None: raise RuntimeError("synthetic mux failure") monkeypatch.setattr(video_visible, "mux_encoded_video", fail_mux) with pytest.raises(RuntimeError, match="synthetic mux failure"): encode_clean_video( source, output, scan, [None], backend="cv2", strip_metadata=True, ) assert output.read_bytes() == b"previous" assert not targets[0].exists() def test_rejects_high_bit_depth_before_silent_downconversion( self, tmp_path: Path, monkeypatch: pytest.MonkeyPatch, ): from remove_ai_watermarks import video_visible from remove_ai_watermarks.video_encoding import VideoEncodeProfile from remove_ai_watermarks.video_visible import FrameLocalization, VideoScan, encode_clean_video source = tmp_path / "source.mp4" source.write_bytes(b"source") output = tmp_path / "clean.mp4" output.write_bytes(b"previous") scan = VideoScan( 8, 8, 24.0, (FrameLocalization(0, 1.0, (1, 1, 2, 2)),), ) monkeypatch.setattr( video_visible, "probe_video_encode_profile", lambda _path: VideoEncodeProfile( pixel_format="yuv420p", color_transfer="smpte2084", source_pixel_format="yuv420p10le", component_depth=10, ), ) monkeypatch.setattr( video_visible, "start_raw_video_encoder", lambda _command: pytest.fail("encoder must not start for a high-bit-depth source"), ) with pytest.raises(RuntimeError, match="refusing to silently reduce yuv420p10le"): encode_clean_video( source, output, scan, [(1, 1, 2, 2)], backend="cv2", strip_metadata=True, ) assert output.read_bytes() == b"previous" assert list(tmp_path.glob(".clean-*")) == [] class TestVideoVisibleFullClip: def test_removes_complete_clip_and_preserves_sequence_and_audio(self, tmp_path: Path): from remove_ai_watermarks.metadata import get_ai_metadata from remove_ai_watermarks.video import remove_video_all, remove_video_metadata, remove_video_visible from remove_ai_watermarks.video_visible import scan_video_marks, stabilize_localizations ffmpeg, ffprobe = _ffmpeg_test_tools() frame_count = 18 fps = 12.0 clean_frames: list[np.ndarray] = [] source_frames: list[np.ndarray] = [] mark, mark_region = _independent_sora_mark() mark_x, mark_y, _mark_width, _mark_height = mark_region for frame_index in range(frame_count): clean_frame = _moving_video_background(frame_index) marked_frame = clean_frame.copy() _stamp_gray_mark(marked_frame, mark, x=mark_x, y=mark_y, opacity=0.78) clean_frames.append(clean_frame) source_frames.append(marked_frame) source = tmp_path / "marked.mp4" clean_control = tmp_path / "control.mp4" metadata_clean = tmp_path / "metadata-clean.mp4" output = tmp_path / "clean.mp4" all_output = tmp_path / "clean-all.mp4" independent_output = tmp_path / "clean-independent.mp4" _write_synthetic_sora_clip(source, source_frames, fps=fps, ffmpeg=ffmpeg) _write_synthetic_sora_clip(clean_control, clean_frames, fps=fps, ffmpeg=ffmpeg) assert get_ai_metadata(source) metadata_result = remove_video_metadata(source, metadata_clean) result = remove_video_visible(source, output, mark="sora", backend="cv2") all_result = remove_video_all(source, all_output, mark="sora", backend="cv2") independent_result = remove_video_visible( source, independent_output, mark="sora", backend="cv2", temporal_consistency=False, ) assert metadata_result.remaining == {} assert metadata_clean.stat().st_size == source.stat().st_size assert get_ai_metadata(metadata_clean) == {} assert result.output == output assert result.total_frames == frame_count assert result.detected_frames == frame_count assert result.removed_frames == frame_count assert result.remaining_metadata == {} assert get_ai_metadata(output) == {} assert all_result.output == all_output assert all_result.visible_mark == "sora" assert all_result.visible_removed_frames == frame_count assert all_result.detected_metadata assert all_result.remaining_metadata == {} assert all_result.invisible_removed is False assert get_ai_metadata(all_output) == {} assert independent_result.removed_frames == frame_count decoded_source, source_fps = _decode_video(source) decoded_control, control_fps = _decode_video(clean_control) decoded_metadata_clean, metadata_clean_fps = _decode_video(metadata_clean) decoded_output, output_fps = _decode_video(output) decoded_all, all_fps = _decode_video(all_output) decoded_independent, independent_fps = _decode_video(independent_output) assert ( len(decoded_source) == len(decoded_control) == len(decoded_metadata_clean) == len(decoded_output) == len(decoded_all) == len(decoded_independent) == frame_count ) assert source_fps == pytest.approx(fps, abs=0.01) assert control_fps == pytest.approx(source_fps, abs=0.01) assert metadata_clean_fps == pytest.approx(source_fps, abs=0.01) assert output_fps == pytest.approx(source_fps, abs=0.01) assert all_fps == pytest.approx(source_fps, abs=0.01) assert independent_fps == pytest.approx(source_fps, abs=0.01) assert all( np.array_equal(original, metadata_cleaned) for original, metadata_cleaned in zip(decoded_source, decoded_metadata_clean, strict=True) ) assert _container_duration(output, ffprobe=ffprobe) == pytest.approx( _container_duration(source, ffprobe=ffprobe), abs=1 / fps, ) source_stream = _video_stream_info(source, ffprobe=ffprobe) output_stream = _video_stream_info(output, ffprobe=ffprobe) expected_stream_properties = { "pix_fmt": "yuv420p", "color_range": "tv", "color_space": "bt709", "color_transfer": "bt709", "color_primaries": "bt709", "time_base": "1/90000", } assert source_stream == expected_stream_properties assert _video_stream_info(metadata_clean, ffprobe=ffprobe) == source_stream assert output_stream == expected_stream_properties assert _video_stream_info(all_output, ffprobe=ffprobe) == expected_stream_properties source_audio = _audio_bitstream(source, ffmpeg=ffmpeg) assert source_audio assert _audio_bitstream(metadata_clean, ffmpeg=ffmpeg) == source_audio assert _audio_bitstream(output, ffmpeg=ffmpeg) == source_audio assert _audio_bitstream(all_output, ffmpeg=ffmpeg) == source_audio output_scan = scan_video_marks(output, ("sora",))["sora"] all_output_scan = scan_video_marks(all_output, ("sora",))["sora"] assert all( region is None for region in stabilize_localizations( "sora", output_scan.detections, provenance=False, ) ) assert all( region is None for region in stabilize_localizations( "sora", all_output_scan.detections, provenance=False, ) ) x, y, width, height = mark_region untouched = np.ones(decoded_source[0].shape[:2], dtype=bool) padding = 24 untouched[ max(0, y - padding) : min(untouched.shape[0], y + height + padding), max(0, x - padding) : min(untouched.shape[1], x + width + padding), ] = False untouched_psnr: list[float] = [] for original, cleaned in zip(decoded_source, decoded_output, strict=True): squared_error = (original.astype(np.float32) - cleaned.astype(np.float32)) ** 2 mean_squared_error = float(np.mean(squared_error[untouched])) untouched_psnr.append(float(10 * np.log10((255**2) / mean_squared_error))) assert min(untouched_psnr) >= 35.0 filled_region = np.logical_not(untouched) temporal_errors: list[float] = [] independent_temporal_errors: list[float] = [] for frame_index in range(1, frame_count): expected_delta = decoded_control[frame_index].astype(np.float32) - decoded_control[frame_index - 1].astype( np.float32 ) cleaned_delta = decoded_output[frame_index].astype(np.float32) - decoded_output[frame_index - 1].astype( np.float32 ) independent_delta = decoded_independent[frame_index].astype(np.float32) - decoded_independent[ frame_index - 1 ].astype(np.float32) temporal_errors.append(float(np.mean(np.abs(cleaned_delta[filled_region] - expected_delta[filled_region])))) independent_temporal_errors.append( float(np.mean(np.abs(independent_delta[filled_region] - expected_delta[filled_region]))) ) assert float(np.median(temporal_errors)) <= 1.5 assert float(np.percentile(temporal_errors, 95)) <= 2.0 assert float(np.median(temporal_errors)) < float(np.median(independent_temporal_errors)) assert float(np.percentile(temporal_errors, 95)) <= float(np.percentile(independent_temporal_errors, 95)) def test_preserves_variable_frame_timestamps(self, tmp_path: Path): from remove_ai_watermarks.video import remove_video_visible ffmpeg, ffprobe = _ffmpeg_test_tools() durations = [value for _ in range(6) for value in (1 / 30, 1 / 12, 1 / 20)] mark, mark_region = _independent_sora_mark() mark_x, mark_y, _mark_width, _mark_height = mark_region frames: list[np.ndarray] = [] for frame_index in range(len(durations)): frame = _moving_video_background(frame_index) _stamp_gray_mark(frame, mark, x=mark_x, y=mark_y, opacity=0.78) frames.append(frame) source = tmp_path / "marked-vfr.mp4" output = tmp_path / "clean-vfr.mp4" _write_vfr_sora_clip( source, frames, durations=durations, ffmpeg=ffmpeg, start_offset=2.0, ) source_timestamps = _video_frame_timestamps(source, ffprobe=ffprobe) source_intervals = np.diff(source_timestamps) assert len(source_timestamps) == len(frames) assert source_timestamps[0] == pytest.approx(2.0, abs=1 / 90000) assert float(np.ptp(source_intervals)) >= 0.03 result = remove_video_visible(source, output, mark="sora", backend="cv2") output_timestamps = _video_frame_timestamps(output, ffprobe=ffprobe) assert result.removed_frames == len(frames) assert len(output_timestamps) == len(source_timestamps) assert output_timestamps == pytest.approx(source_timestamps, abs=1 / 90000) assert _stream_start_times(output, ffprobe=ffprobe) == pytest.approx( _stream_start_times(source, ffprobe=ffprobe), abs=1 / 90000, ) assert _container_duration(output, ffprobe=ffprobe) == pytest.approx( _container_duration(source, ffprobe=ffprobe), abs=max(durations), ) assert _audio_bitstream(output, ffmpeg=ffmpeg) == _audio_bitstream(source, ffmpeg=ffmpeg) def test_preserves_nonzero_start_on_constant_rate_clip(self, tmp_path: Path): from remove_ai_watermarks.video import remove_video_visible ffmpeg, ffprobe = _ffmpeg_test_tools() mark, mark_region = _independent_sora_mark() mark_x, mark_y, _mark_width, _mark_height = mark_region frames = [] for frame_index in range(18): frame = _moving_video_background(frame_index) _stamp_gray_mark(frame, mark, x=mark_x, y=mark_y, opacity=0.78) frames.append(frame) source = tmp_path / "marked-offset.mp4" output = tmp_path / "clean-offset.mp4" _write_synthetic_sora_clip( source, frames, fps=12.0, ffmpeg=ffmpeg, start_offset=2.0, ) result = remove_video_visible(source, output, mark="sora", backend="cv2") assert result.removed_frames == len(frames) assert _video_frame_timestamps(output, ffprobe=ffprobe) == pytest.approx( _video_frame_timestamps(source, ffprobe=ffprobe), abs=1 / 90000, ) assert _stream_start_times(output, ffprobe=ffprobe) == pytest.approx( _stream_start_times(source, ffprobe=ffprobe), abs=1 / 90000, ) assert _audio_bitstream(output, ffmpeg=ffmpeg) == _audio_bitstream(source, ffmpeg=ffmpeg) class TestVideoVisibleApi: @pytest.mark.parametrize( ("sora_box", "sora_frames", "integrity", "signature", "signer", "generator", "expected_mark"), [ ((1125, 584, 155, 69), 15, "valid", "valid", "unknown", "Google C2PA Core Generator Library", "veo"), # Valid Google AI provenance vetoes Sora wherever its box sits. ((300, 200, 155, 69), 15, "valid", "valid", "unknown", "Google C2PA Core Generator Library", "veo"), ((300, 200, 155, 69), 8, "valid", "valid", "unknown", "Google C2PA Core Generator Library", "veo"), ((1125, 584, 155, 69), 15, "invalid", "valid", "unknown", "Google C2PA Core Generator Library", "sora"), ((1125, 584, 155, 69), 15, "valid", "invalid", "unknown", "Google C2PA Core Generator Library", "sora"), # A revoked or expired signing credential is "invalid" to identify, so it # cannot veto either. ((1125, 584, 155, 69), 15, "valid", "valid", "invalid", "Google C2PA Core Generator Library", "sora"), # Provenance that also names Sora confirms the mark instead of vetoing it. ((1125, 584, 155, 69), 15, "valid", "valid", "unknown", "Sora export", "sora"), ], ) def test_google_ai_provenance_vetoes_sora_unless_invalid_or_sora_confirmed( self, sora_box: tuple[int, int, int, int], sora_frames: int, integrity: str, signature: str, signer: str, generator: str, expected_mark: str, ): from remove_ai_watermarks.video import _select_stable_visible_mark from remove_ai_watermarks.video_visible import FrameLocalization, VideoScan veo_box = (1132, 572, 56, 56) scans = { "sora": VideoScan( width=1280, height=720, fps=24.0, detections=tuple( FrameLocalization( index, 0.66 if index < sora_frames else 0.0, sora_box if index < sora_frames else None, ) for index in range(15) ), ), "veo": VideoScan( width=1280, height=720, fps=24.0, detections=tuple(FrameLocalization(index, 0.82, veo_box) for index in range(15)), ), } google_markers = { "claim_generator": generator, "issuer": "Google LLC", "source_type": "trainedAlgorithmicMedia (AI-generated)", "c2pa_integrity": integrity, "c2pa_signature": signature, "c2pa_signer_validity": signer, } selected = _select_stable_visible_mark(scans, google_markers, ("sora", "veo")) assert selected is not None assert selected[0] == expected_mark assert selected[2] == [veo_box if expected_mark == "veo" else sora_box] * 15 assert selected[4] == ("veo" if expected_mark == "veo" else "box") def test_auto_prefers_specific_sora_run_over_hailuo_cross_match( self, tmp_path: Path, monkeypatch: pytest.MonkeyPatch, ): from remove_ai_watermarks import video_visible from remove_ai_watermarks.video import remove_video_visible from remove_ai_watermarks.video_visible import FrameLocalization, VideoScan source = _video_with_c2pa(tmp_path / "source.mp4") output = tmp_path / "clean.mp4" sora_box = (4, 4, 20, 8) hailuo_box = (30, 40, 28, 12) sora_scan = VideoScan( width=64, height=64, fps=24.0, detections=tuple(FrameLocalization(index, 0.66, sora_box) for index in range(12)), ) hailuo_scan = VideoScan( width=64, height=64, fps=24.0, detections=tuple(FrameLocalization(index, 0.35, hailuo_box) for index in range(12)), ) def fake_scan(_source: Path, marks: tuple[str, ...]): assert marks == ("sora", "veo", "seedance", "doubao", "dola", "hailuo", "vidu", "kling") return { "sora": sora_scan, "veo": sora_scan, "seedance": sora_scan, "dola": sora_scan, "hailuo": hailuo_scan, "kling": sora_scan, } monkeypatch.setattr(video_visible, "scan_video_marks", fake_scan) def fake_encode( _source: Path, target: Path, _scan: VideoScan, regions: list[tuple[int, int, int, int] | None], **kwargs: object, ) -> int: assert regions == [sora_box] * 12 assert kwargs["padding_fraction"] == 0.28 assert kwargs["temporal_consistency"] is True target.write_bytes(_MP4_FTYP + _box(b"mdat", _VIDEO_PAYLOAD)) return 12 monkeypatch.setattr(video_visible, "encode_clean_video", fake_encode) result = remove_video_visible(source, output) assert result.output == output assert result.mark == "sora" def test_removes_stable_sora_run_and_writes_output(self, tmp_path: Path, monkeypatch: pytest.MonkeyPatch): from remove_ai_watermarks import video_visible from remove_ai_watermarks.video import remove_video_visible from remove_ai_watermarks.video_visible import FrameLocalization, VideoScan source = _video_with_c2pa(tmp_path / "source.mp4") output = tmp_path / "clean.mp4" box = (4, 4, 20, 8) scan = VideoScan( width=64, height=64, fps=24.0, detections=tuple(FrameLocalization(index, 0.66, box) for index in range(5)), ) monkeypatch.setattr( video_visible, "scan_video_marks", lambda _source, marks: {"sora": scan} if marks == ("sora",) else {}, ) def fake_encode( _source: Path, target: Path, _scan: VideoScan, regions: list[tuple[int, int, int, int] | None], **kwargs: object, ) -> int: assert regions == [box] * 5 assert kwargs["temporal_consistency"] is False target.write_bytes(_MP4_FTYP + _box(b"mdat", _VIDEO_PAYLOAD)) return 5 monkeypatch.setattr(video_visible, "encode_clean_video", fake_encode) result = remove_video_visible( source, output, mark="sora", temporal_consistency=False, ) assert result.output == output assert result.detected_frames == 5 assert result.removed_frames == 5 assert result.remaining_metadata == {} assert result.audio.stream_action == "copied_if_present" assert result.audio.watermark_status == "unverified" def test_no_stable_mark_writes_no_output(self, tmp_path: Path, monkeypatch: pytest.MonkeyPatch): from remove_ai_watermarks import video_visible from remove_ai_watermarks.video import remove_video_visible from remove_ai_watermarks.video_visible import FrameLocalization, VideoScan source = _video_with_c2pa(tmp_path / "source.mp4") output = tmp_path / "clean.mp4" scan = VideoScan( width=64, height=64, fps=24.0, detections=( FrameLocalization(0, 0.70, (1, 1, 20, 8)), FrameLocalization(1, 0.70, (30, 30, 20, 8)), FrameLocalization(2, 0.70, (1, 30, 20, 8)), ), ) monkeypatch.setattr( video_visible, "scan_video_marks", lambda _source, marks: {"sora": scan} if marks == ("sora",) else {}, ) result = remove_video_visible(source, output, mark="sora") assert result.output is None assert result.removed_frames == 0 assert result.audio.stream_action == "not_written" assert result.audio.watermark_status == "unverified" assert not output.exists() def test_dispatches_veo_detector_and_uses_tighter_mask( self, tmp_path: Path, monkeypatch: pytest.MonkeyPatch, ): from remove_ai_watermarks import video_visible from remove_ai_watermarks.video import remove_video_visible from remove_ai_watermarks.video_visible import FrameLocalization, VideoScan source = _video_with_c2pa(tmp_path / "source.mp4") output = tmp_path / "clean.mp4" box = (4, 4, 20, 20) scan = VideoScan( width=64, height=64, fps=24.0, detections=tuple(FrameLocalization(index, 0.60, box) for index in range(12)), ) monkeypatch.setattr( video_visible, "scan_video_marks", lambda _source, marks: {"veo": scan} if marks == ("veo",) else {}, ) def fake_encode( _source: Path, target: Path, _scan: VideoScan, regions: list[tuple[int, int, int, int] | None], **kwargs: object, ) -> int: assert regions == [box] * 12 assert kwargs["padding_fraction"] == 0.18 assert kwargs["mask_style"] == "veo" target.write_bytes(_MP4_FTYP + _box(b"mdat", _VIDEO_PAYLOAD)) return 12 monkeypatch.setattr(video_visible, "encode_clean_video", fake_encode) result = remove_video_visible(source, output, mark="veo") assert result.output == output assert result.mark == "veo" assert result.detected_frames == 12 assert result.removed_frames == 12 @pytest.mark.parametrize( ("mark", "mask_style"), [ ("seedance", "box"), ("dola", "box"), ("hailuo", "box"), ("kling", "box"), ], ) def test_dispatches_fixed_mark_detectors( self, tmp_path: Path, monkeypatch: pytest.MonkeyPatch, mark: str, mask_style: str, ): from remove_ai_watermarks import video_visible from remove_ai_watermarks.video import remove_video_visible from remove_ai_watermarks.video_visible import FrameLocalization, VideoScan source = _video_with_c2pa(tmp_path / "source.mp4") output = tmp_path / "clean.mp4" box = (40, 40, 20, 12) scan = VideoScan( width=64, height=64, fps=24.0, detections=tuple(FrameLocalization(index, 0.60, box) for index in range(12)), ) monkeypatch.setattr( video_visible, "scan_video_marks", lambda _source, marks: {mark: scan} if marks == (mark,) else {}, ) def fake_encode( _source: Path, target: Path, _scan: VideoScan, regions: list[tuple[int, int, int, int] | None], **kwargs: object, ) -> int: assert regions == [box] * 12 assert kwargs["mask_style"] == mask_style target.write_bytes(_MP4_FTYP + _box(b"mdat", _VIDEO_PAYLOAD)) return 12 monkeypatch.setattr(video_visible, "encode_clean_video", fake_encode) result = remove_video_visible(source, output, mark=mark) assert result.output == output assert result.mark == mark assert result.detected_frames == 12 assert result.removed_frames == 12 class TestVideoVisibleCli: def test_help(self): result = CliRunner().invoke(main, ["video", "visible", "--help"]) assert result.exit_code == 0, result.output assert "temporally stable" in result.output assert "auto|sora|veo|seedance|doubao|dola|hailuo|vidu|kling" in result.output assert "--temporal-consistency" in result.output def test_reports_removed_frames(self, tmp_path: Path, monkeypatch: pytest.MonkeyPatch): from remove_ai_watermarks import video from remove_ai_watermarks.video import VideoVisibleResult source = _video_with_c2pa(tmp_path / "source.mp4") output = tmp_path / "clean.mp4" monkeypatch.setattr( video, "remove_video_visible", lambda *_args, **_kwargs: VideoVisibleResult( source=source, output=output, mark="sora", total_frames=12, detected_frames=10, removed_frames=10, remaining_metadata={}, ), ) result = CliRunner().invoke(main, ["video", "visible", str(source), "-o", str(output)]) assert result.exit_code == 0, result.output assert "10/12 frames" in result.output assert "Audio watermark: UNVERIFIED" in result.output class TestGeminiOmniRealSamples: """Real Gemini Omni outputs: app export with the visible diamond, and API output without it.""" _DIR = Path(__file__).resolve().parents[1] / "data" / "fixtures" / "provenance" def test_app_export_carries_the_veo_diamond_and_google_synthid(self): from remove_ai_watermarks.video import identify_video report = identify_video(self._DIR / "google-gemini-omni-app.mp4") assert report.visible_mark == "veo" assert report.visible_detected_frames == report.total_frames == 240 assert report.metadata_markers["c2pa_validation_state"] == "Valid" assert report.metadata_markers["synthid_watermark"] == "present according to Google LLC provenance" def test_api_output_has_google_synthid_and_no_visible_mark(self): from remove_ai_watermarks.video import identify_video report = identify_video(self._DIR / "google-gemini-omni-flash-api.mp4") assert report.visible_mark is None assert report.visible_detected_frames == 0 assert report.platform == "Google (Gemini / Imagen)" assert report.metadata_markers["c2pa_validation_state"] == "Valid" assert report.metadata_markers["synthid_watermark"] == "present according to Google LLC provenance" class TestYouTubeReencodedSamples: """YouTube re-signs every upload as Google LLC ("YouTube Video Processing Services"). Real Studio downloads (2026-09-24) of two uploads: an xAI Grok video, whose own self-signed manifest the reader drops, and a Gemini Omni Flash video, whose manifest survives as the ingredient with its SynthID action. """ _DIR = Path(__file__).resolve().parents[1] / "data" / "fixtures" / "provenance" def test_reencoded_grok_is_not_read_as_google_ai(self): from remove_ai_watermarks.video import identify_video report = identify_video(self._DIR / "youtube-reencode-grok.mp4", check_visible=False) assert report.is_ai_generated is None assert report.confidence == "unknown" assert report.platform is None assert "synthid_watermark" not in report.metadata_markers assert report.metadata_markers["actions"] == "opened, transcoded" assert any("no AI claim" in caveat for caveat in report.caveats) def test_reencoded_gemini_keeps_its_ingredient_synthid(self): from remove_ai_watermarks.video import identify_video report = identify_video(self._DIR / "youtube-reencode-gemini-omni-flash.mp4", check_visible=False) assert report.is_ai_generated is True assert report.platform == "Google (Gemini / Imagen)" assert report.metadata_markers["synthid_watermark"] == "present according to Google LLC provenance" def test_runway_gen45_video_is_attributed_to_runway(self): from remove_ai_watermarks.video import identify_video report = identify_video(self._DIR / "runway-gen45-video.mp4", check_visible=False) assert report.is_ai_generated is True assert report.platform == "Runway" assert report.metadata_markers["issuer"] == "Runway" assert "synthid_watermark" not in report.metadata_markers @pytest.mark.parametrize( ("name", "platform"), [ ("higgsfield-kling-3-0-turbo.mp4", "Kuaishou Kling AI"), ("higgsfield-happyhorse.mp4", "Alibaba Tongyi (Wan, HappyHorse)"), ("higgsfield-minimax-h3.mp4", "MiniMax Hailuo AI"), ("dreamina-seedance-2-mini.mp4", "ByteDance Dreamina"), ], ) def test_aggregator_video_names_its_generator(self, name: str, platform: str): from remove_ai_watermarks.video import identify_video report = identify_video(self._DIR / name, check_visible=False) assert report.is_ai_generated is True assert report.platform == platform @staticmethod def _frames(path: Path, indices: tuple[int, ...]): import cv2 cap = cv2.VideoCapture(str(path)) frames = {} index = 0 while len(frames) < len(indices): ok, frame = cap.read() if not ok: break if index in indices: frames[index] = frame index += 1 cap.release() assert sorted(frames) == sorted(indices) return frames def test_kling_swirl_on_wood_short_of_the_edge_is_rejected(self): # Higgsfield Cinema Studio v2 (2026-09-25): the swirl arm matched wood # grain at 0.52-0.56 on these frames, 11% short of the right edge, and # the run covered every frame. Its provenance is Kling, so no veto applies. from remove_ai_watermarks.video_visible import _KLING_WEAK_CONFIDENCE, detect_kling_frame for index, frame in self._frames(self._DIR / "higgsfield-cinema-studio-v2.mp4", (0, 3, 4, 7)).items(): assert detect_kling_frame(frame, frame_index=index).confidence < _KLING_WEAK_CONFIDENCE def test_hailuo_candidate_without_white_label_pixels_is_rejected(self): # Hailuo 2.3 via Higgsfield (2026-09-25) scored 0.34-0.36 on wood grain, # above the real label, on every frame; the box holds no white pixel. from remove_ai_watermarks.video_visible import detect_hailuo_frame for index, frame in self._frames(self._DIR / "higgsfield-hailuo-2-3.mp4", (0, 1, 2, 3)).items(): assert detect_hailuo_frame(frame, frame_index=index).region is None def test_drifting_kling_match_on_texture_is_not_a_run(self): # Higgsfield Genjutsu motion control (2026-09-25): the wordmark arm matched # wood grain at 0.22-0.32 on every frame, reaching the frame edge with white # highlights, while its box drifted by a few pixels. A real Kling overlay # keeps an identical box, so the run must stay anchored within 0.95 IoU. from remove_ai_watermarks.video_visible import scan_video_marks, stabilize_localizations texture = scan_video_marks( self._DIR / "higgsfield-genjutsu-motion-control.mp4", ("kling",), collect_timestamps=False ) real = scan_video_marks( self._DIR.parent / "visible" / "kling" / "provider-original.mp4", ("kling",), collect_timestamps=False ) assert not any(stabilize_localizations("kling", texture["kling"].detections)) assert all(stabilize_localizations("kling", real["kling"].detections)) def test_google_c2pa_vetoes_a_sora_match_on_steam(self): # Gemini Omni 1.1 Flash via Runway (2026-09-25): a steam wisp scored 0.61-0.67 # on the Sora template for five frames, a stable run above the strict floor, # and the report named OpenAI Sora over the file's valid Google C2PA. The # mascot-only shape check now rejects the wisp too; the veto stays as the # independent second guard. from remove_ai_watermarks.video import identify_video from remove_ai_watermarks.video_visible import contradicts_video_provenance clip = self._DIR.parents[1] / "captures" / "2026-09" / "runway" / "gemini-omni-1-1-flash.mp4" report = identify_video(clip) assert report.visible_mark != "sora" assert contradicts_video_provenance("sora", report.metadata_markers) def test_real_kling_and_hailuo_labels_still_pass_the_new_gates(self): from remove_ai_watermarks.video_visible import ( _HAILUO_WEAK_CONFIDENCE, _KLING_WEAK_CONFIDENCE, detect_hailuo_frame, detect_kling_frame, ) visible = self._DIR.parent / "visible" kling = self._frames(visible / "kling" / "provider-original.mp4", (30,))[30] hailuo = self._frames(visible / "hailuo" / "provider-original.mp4", (30,))[30] assert detect_kling_frame(kling, frame_index=30).confidence >= _KLING_WEAK_CONFIDENCE assert detect_hailuo_frame(hailuo, frame_index=30).confidence >= _HAILUO_WEAK_CONFIDENCE def test_veo_provenance_vetoes_a_kling_match(self): # Veo 3.1 Lite via Higgsfield: the Kling detector matched wood texture on all # 192 frames. Its Google C2PA must veto Kling; a real bare "kling" producer # must not. from remove_ai_watermarks.video import identify_video from remove_ai_watermarks.video_visible import contradicts_video_provenance veo = identify_video(self._DIR / "higgsfield-veo-3-1-lite.mp4", check_visible=False) kling = identify_video(self._DIR / "higgsfield-kling-3-0-turbo.mp4", check_visible=False) assert veo.platform == "Google (Gemini / Imagen)" assert contradicts_video_provenance("kling", veo.metadata_markers) assert not contradicts_video_provenance("kling", kling.metadata_markers) def test_amazon_bedrock_nova_reel_is_attributed(self): # aws-samples Nova Reel 1.1 output: C2PA from Amazon Web Services with # claim generator "Amazon Bedrock"; before the vendor row the platform was None. from remove_ai_watermarks.video import identify_video report = identify_video(self._DIR / "amazon-bedrock-nova-reel.mp4", check_visible=False) assert report.is_ai_generated is True assert report.platform == "Amazon Bedrock (Nova)" def test_original_grok_video_is_xai_without_synthid(self): from remove_ai_watermarks.video import identify_video report = identify_video(self._DIR / "xai-grok-imagine-video.mp4", check_visible=False) assert report.is_ai_generated is True assert report.platform == "xAI Grok Imagine" assert "synthid_watermark" not in report.metadata_markers @pytest.mark.parametrize( ("markers", "expected"), [ ({"c2pa_manifest": "store", "issuer": "Google LLC", "actions": "opened, transcoded"}, False), ({"c2pa_manifest": "store", "issuer": "Google LLC", "source_type": "algorithmicMedia"}, False), ({"c2pa_manifest": "store", "issuer": "Google LLC", "source_type": "trainedAlgorithmicMedia"}, True), ( {"c2pa_manifest": "store", "issuer": "TikTok", "source_type": "compositeWithTrainedAlgorithmicMedia"}, True, ), ({"c2pa_manifest": "store", "issuer": "xAI Grok Imagine"}, True), ({"c2pa_manifest": "store", "claim_generator": "Grok Imagine"}, True), ({"c2pa_manifest": "store", "aigc_label": "China AIGC label (TC260)"}, True), ], ) def test_only_an_ai_claim_makes_c2pa_markers_an_ai_signal(self, markers: dict[str, str], expected: bool): from remove_ai_watermarks.video import _video_markers_claim_ai assert _video_markers_claim_ai(markers) is expected