Harden automatic video watermark removal for product use

This commit is contained in:
Victor Kuznetsov
2026-07-30 18:36:04 -07:00
parent 07b678fa4c
commit 8975916b11
14 changed files with 737 additions and 232 deletions
+11 -2
View File
@@ -210,7 +210,15 @@ must also remain anchored instead of drifting with a scene object. Matching
provider provenance may relax the visual score only for registered
provenance-aware marks; metadata alone never creates a detection.
The video stream is transcoded and the original audio stream is copied.
`--mark auto` is the default. It evaluates all providers in one decode pass and
selects the first stable match in specificity order: Sora, Veo, Seedance, Dola,
Hailuo, then Kling. Their confidence scores are independently calibrated and
are not compared across providers. Pass an explicit `--mark` to scan only that
provider.
The video stream is transcoded and the complete original audio stream is
copied without truncating an audio tail that extends beyond the final video
frame.
Supported input and output containers are MP4, MOV, M4V, WebM, MKV, AVI, and
FLV; the output extension must match the input. The default `cv2` backend is
fast but can smear structured backgrounds. Select `--backend migan` or
@@ -220,7 +228,8 @@ installed backend.
AI metadata is stripped from the encoded output by default. Use
`--keep-metadata` to retain mapped container metadata. When no temporally stable
mark is found, the command writes no output and exits with the no-visible-mark
status.
status. The final path is replaced atomically only after ffmpeg completes, so a
failed encode does not overwrite an existing result.
## Remove invisible watermarks
+18 -11
View File
@@ -215,6 +215,11 @@ drifting with a scene object. Kling also requires a bright low-saturation
candidate near the expected frame edge. Provider provenance can recover
low-contrast runs only after visual evidence exists for the marks that define a
provenance prior, so metadata alone does not erase a clean API export.
The default auto-router evaluates all detectors in one decode pass but does not
rank their raw confidence values. Those scores are provider-specific and known
to cross-match in some layouts, so the router applies the independent temporal
policies and selects the first stable result in specificity order. Use an
explicit mark when the provider is already known.
Historical Sora Turbo exports use a small OpenAI swirl in the corner rather
than the moving mascot-and-wordmark design; that earlier variant is not
detected by the `sora` video mark. Hailuo and Kling coverage is specific to the
@@ -223,17 +228,19 @@ silhouette. Other provider video labels are not supported yet. Google video
SynthID has a candidate-producing VAE path, while other proprietary invisible
video watermarks have no registered attack.
Visible removal transcodes the video stream and copies audio. Its frame-local
fill is not a motion-aware video inpainting model. OpenCV can leave a visible
smear where the mark overlaps a hard edge or structured texture, and the smear
can vary over time. MI-GAN and LaMa improve individual frames but do not
guarantee temporal coherence. The Veo diamond uses a shape mask to limit damage
outside the symbol. Seedance fills the full localized box because a synthetic
outline mask left part of the real translucent border visible in an end-to-end
check. OpenCV may therefore soften texture inside that small box; use MI-GAN or
LaMa when reconstruction quality matters. The current encoder also emits a
constant-frame-rate output at the decoded stream rate, so variable-frame-rate
preservation is not yet guaranteed.
Visible removal transcodes the video stream and copies the complete audio
stream without shortening an audio tail. Completed visible and invisible
encodes are published atomically, so an encode failure preserves an existing
output. Its frame-local fill is not a motion-aware video inpainting model.
OpenCV can leave a visible smear where the mark overlaps a hard edge or
structured texture, and the smear can vary over time. MI-GAN and LaMa improve
individual frames but do not guarantee temporal coherence. The Veo diamond
uses a shape mask to limit damage outside the symbol. Seedance fills the full
localized box because a synthetic outline mask left part of the real
translucent border visible in an end-to-end check. OpenCV may therefore soften
texture inside that small box; use MI-GAN or LaMa when reconstruction quality
matters. The current encoder also emits a constant-frame-rate output at the
decoded stream rate, so variable-frame-rate preservation is not yet guaranteed.
Native TC260 metadata in MP4/MOV is supported at its normative
`moov.udta.meta.keys/ilst` placement, including non-faststart files whose
+11 -1
View File
@@ -125,7 +125,9 @@ use the verified ffmpeg stream-copy path for removal.
[`video_encoding.py`](../src/remove_ai_watermarks/video_encoding.py) owns the
raw-BGR ffmpeg command and pipe lifecycle shared by visible removal and
invisible regeneration. It centralizes container codecs, optional audio stream
copying, metadata/chapter policy, and encode-failure reporting.
copying, metadata/chapter policy, encode-failure reporting, and atomic
same-directory publication. Each mapped stream is allowed to reach its own end,
so a copied audio tail is not shortened to the raw-video input duration.
[`video_invisible.py`](../src/remove_ai_watermarks/video_invisible.py)
implements the oracle-gated video SynthID candidate engine. It samples frames
@@ -161,6 +163,14 @@ expected lower-frame area and calibrated independently. A strong relocated Veo
diamond may bypass the known layout anchors, but weak free-corner matches never
enter the temporal arbiter.
The default `auto` route decodes each frame once, shares its grayscale and
normalized representations across all detectors, and caches resized synthetic
template features for the fixed stream geometry. Provider confidence scales
are not comparable: selection applies each provider's temporal arbiter and
takes the first stable result in specificity order (`sora`, `veo`, `seedance`,
`dola`, `hailuo`, `kling`). An explicit mark uses the same scan path with one
candidate.
Every per-frame result is untrusted. The provider-specific stabilization
wrappers share one recurrence implementation, while retaining separate visual
floors and minimum-run policy. Provenance can relax a low-contrast run only
+20 -12
View File
@@ -174,8 +174,8 @@ if result.remaining_metadata:
`remove_video_invisible` supports MP4, MOV, and M4V. It regenerates the complete
video through a VAE in bounded batches, shares one seeded latent-noise field
across all frames, streams pixels to ffmpeg, copies audio, and strips source
metadata. The default output is
across all frames, streams pixels to ffmpeg, copies complete audio, strips
source metadata, and publishes atomically. The default output is
`input_synthid_candidate.mp4`; a distinct same-container output is required.
The returned `VideoInvisibleResult` includes output geometry, frame rate, frame
@@ -191,13 +191,15 @@ Flash's built-in content verification before treating it as watermark-negative.
import remove_ai_watermarks as raiw
result = raiw.remove_video_visible(
"sora.mp4",
"sora_clean.mp4",
"input.mp4",
"clean.mp4",
backend="cv2",
strip_metadata=True,
)
if result.output is None:
print("No temporally stable Sora mark was found")
print("No temporally stable supported mark was found")
else:
print(result.mark)
veo_result = raiw.remove_video_visible(
"veo.mp4",
@@ -228,18 +230,24 @@ kling_result = raiw.remove_video_visible(
`remove_video_visible` scans the complete video before writing output. It
combines synthetic multi-scale visual matching with temporal consistency, so an
isolated lookalike in one frame is not enough to authorize inpainting. The
supported `mark` values are `sora`, `veo`, `seedance`, `dola`, `hailuo`, and
`kling`. The Veo detector recognizes the current four-point diamond and the
isolated lookalike in one frame is not enough to authorize inpainting.
`mark="auto"` is the default: it evaluates all providers in one decode pass and
selects the first stable match in specificity order (`sora`, `veo`, `seedance`,
`dola`, `hailuo`, `kling`). Provider confidence values are calibrated
independently and are not compared across detectors. Pass one of those explicit
values to restrict the scan to a single provider. The Veo detector recognizes
the current four-point diamond and the
legacy `Veo` text. Seedance recognizes the boxed `AI` label, Dola recognizes
its compact text label, Hailuo recognizes the composite MINIMAX/Hailuo label,
and Kling recognizes its bottom-right logo, wordmark, and version suffix. Each
variant has an independent synthetic silhouette and calibrated temporal policy.
The returned `VideoVisibleResult` records the total, detected, and removed frame
counts plus any AI metadata that survived the output encode. The function
returns `output=None` and writes no file when no stable mark is selected. Video
pixels are transcoded through ffmpeg while the source audio stream is copied.
The returned `VideoVisibleResult` records the selected `mark`, the total,
detected, and removed frame counts, plus any AI metadata that survived the
output encode. The function returns `output=None` and writes no file when no
stable mark is selected. Video pixels are transcoded through ffmpeg while the
complete source audio stream is copied. A failed encode preserves any existing
output; only a completed result is published atomically.
## Remove invisible watermarks
+4 -1
View File
@@ -41,7 +41,10 @@ when you can select the affected area yourself.
| `kling` | Kling swirl, `KLING AI`, version, and optional `PRO` suffix | Fixed bottom-right edge | Combines a synthetic logo rescue with font variants, an edge gate, a white-label gate, and anchored temporal recurrence. |
Use `video visible` for this registry. It is separate from the image `visible`
command because selection is made over a sequence rather than one raster.
command because selection is made over a sequence rather than one raster. Its
default `auto` mode scans all six entries in one decode pass and selects the
first temporally stable match in table order; an explicit mark restricts the
scan to that row.
## Fill backends