mirror of
https://github.com/wiltodelta/remove-ai-watermarks.git
synced 2026-08-10 08:00:32 +02:00
Add streaming video SynthID regeneration
This commit is contained in:
+32
@@ -153,6 +153,38 @@ different container extension.
|
||||
Visible video labels and invisible video watermarks are not handled by this
|
||||
command.
|
||||
|
||||
## Generate a video SynthID candidate
|
||||
|
||||
```bash
|
||||
uv tool install --force "remove-ai-watermarks[gpu]"
|
||||
remove-ai-watermarks video invisible input.mp4 -o candidate.mp4
|
||||
```
|
||||
|
||||
The experimental command supports MP4, MOV, and M4V. It samples the complete
|
||||
sequence at the configured frame rate, resizes frames to the configured long
|
||||
side, regenerates them through a VAE, and applies one deterministic latent-noise
|
||||
field to every frame. Reusing one spatial field avoids the unnecessary flicker
|
||||
caused by independent per-frame noise. Frames are regenerated in bounded
|
||||
batches and streamed directly to ffmpeg, which encodes the result, copies
|
||||
audio, and drops source metadata.
|
||||
|
||||
The output is always an unverified candidate. The project has no local video
|
||||
SynthID decoder, and PSNR or temporal-residual metrics cannot prove watermark
|
||||
absence. After generation, upload the candidate to Gemini Flash and ask:
|
||||
|
||||
> Was this uploaded video created or edited by Google AI? Use the built-in
|
||||
> content verification result.
|
||||
|
||||
Only an explicit built-in verification result is an oracle verdict. A response
|
||||
based on the visible logo, content appearance, or metadata is not. The command
|
||||
prints `UNVERIFIED` even when generation succeeds.
|
||||
|
||||
The default output is `<source>_synthid_candidate` in the same container. The
|
||||
source is never overwritten. Use `--noise-std`, `--long-side`, `--fps`,
|
||||
`--batch-size`, `--seed`, and `--device` to control the regeneration. The
|
||||
defaults are calibrated operating points, not a guarantee for every carrier or
|
||||
future verifier version.
|
||||
|
||||
## Remove a supported visible video mark
|
||||
|
||||
```bash
|
||||
|
||||
+26
-11
@@ -79,17 +79,31 @@ For important outputs:
|
||||
Provider systems can change, so a result verified on one file, seed, or version
|
||||
is not a permanent certification.
|
||||
|
||||
### Video regeneration is research only
|
||||
### Video regeneration produces an unverified candidate
|
||||
|
||||
The package does not expose a `video invisible` command. The separate
|
||||
`scripts/video_synthid_sweep.py` harness generates a matched transcode control
|
||||
and VAE-regenerated candidates for external verification. It deliberately
|
||||
leaves verdicts empty because neither the metadata proxy nor a visual quality
|
||||
metric can prove that the pixel watermark is gone.
|
||||
The `video invisible` command and `remove_video_invisible` API regenerate video
|
||||
pixels through a VAE, but cannot verify the proprietary SynthID payload locally.
|
||||
Every successful result remains explicitly unverified and requires Google's
|
||||
matching content-verification flow. A quiet metadata scan, paired PSNR, and the
|
||||
temporal-residual metric are not substitutes for that oracle.
|
||||
|
||||
The control must use the same clip, frame rate, dimensions, and final codec as
|
||||
the candidates. If the control is not detected by the matching provider oracle,
|
||||
the experiment cannot attribute a quiet candidate to regeneration.
|
||||
the candidates. The separate `scripts/video_synthid_sweep.py` harness produces
|
||||
that matched control. If the control is not detected by the matching provider
|
||||
oracle, the experiment cannot attribute a quiet candidate to regeneration.
|
||||
|
||||
The 2026-07-29 calibration used two public Veo clips. Both matched controls were
|
||||
SynthID-positive. The default stronger VAE candidate was negative on both, while
|
||||
a weaker candidate remained positive on one. This small calibration validates
|
||||
the mechanism and exposes its content dependence; it does not certify all
|
||||
videos, full-length sequences, alternate settings, or future verifier versions.
|
||||
|
||||
The shipped engine streams sampled frames in bounded batches, computes its
|
||||
fidelity metrics incrementally, and pipes regenerated pixels directly to
|
||||
ffmpeg. Its frame and latent memory is therefore bounded by `--batch-size`
|
||||
rather than clip duration. Runtime still grows linearly with duration, and the
|
||||
separate multi-candidate research sweep deliberately retains its short sampled
|
||||
prefix so it can reuse identical latents across candidate strengths.
|
||||
|
||||
### Strength is content and seed dependent
|
||||
|
||||
@@ -186,7 +200,7 @@ WebM, Matroska, MP3, WAV, FLAC, OGG, Opus, and AAC container metadata is strippe
|
||||
through ffmpeg with stream copying. The operation fails if ffmpeg is absent or
|
||||
cannot parse the input.
|
||||
|
||||
### Visible video removal is provider-specific and still experimental
|
||||
### Video pixel removal is provider-specific and still experimental
|
||||
|
||||
The experimental `video metadata` command and high level video API inspect and
|
||||
strip supported AI provenance metadata without transcoding streams.
|
||||
@@ -201,8 +215,9 @@ after visual evidence exists, so metadata alone does not erase a clean API
|
||||
export.
|
||||
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. Other provider video labels and proprietary
|
||||
invisible video watermarks are not supported yet.
|
||||
detected by the `sora` video mark. 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
|
||||
|
||||
@@ -90,6 +90,7 @@ Regression coverage:
|
||||
video entry point:
|
||||
|
||||
- `inspect_video_metadata`
|
||||
- `remove_video_invisible`
|
||||
- `remove_video_metadata`
|
||||
- `remove_video_visible`
|
||||
|
||||
@@ -97,7 +98,7 @@ The video API validates both the supported extension and container signature,
|
||||
then delegates all metadata detection and stripping to `metadata.py`. It
|
||||
requires a separate same-container output, defaulting to `<source>_clean`, so
|
||||
the experimental path does not overwrite an original. The package root exposes
|
||||
both functions lazily.
|
||||
all four functions lazily.
|
||||
|
||||
Native MP4/MOV TC260 labels follow TC260-PG-20257A:
|
||||
`moov.udta.meta.keys` maps an `AIGC` key to a raw JSON value in `ilst`.
|
||||
@@ -114,6 +115,30 @@ only a `Segment.Tags.Tag.SimpleTag` pairing `TagName=AIGC` with a JSON
|
||||
`TagString` carrying a TC260 field. The existing ffmpeg stream-copy path removes
|
||||
those container tags without transcoding the encoded streams.
|
||||
|
||||
[`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.
|
||||
|
||||
[`video_invisible.py`](../src/remove_ai_watermarks/video_invisible.py)
|
||||
implements the oracle-gated video SynthID candidate engine. It samples frames
|
||||
uniformly, resizes to a VAE-aligned geometry, encodes each frame to latent
|
||||
space, applies one seeded spatial-noise field across the entire sequence, and
|
||||
decodes fresh pixels. Reusing a single noise field avoids independent
|
||||
frame-to-frame noise. The shipped path retains only one configured frame batch,
|
||||
updates PSNR and temporal residuals incrementally, and streams BGR frames
|
||||
directly to ffmpeg. ffmpeg encodes H.264 video, maps optional source audio, and
|
||||
drops all source metadata. The result is written through a same-directory
|
||||
temporary file and atomically replaced only after a successful encode.
|
||||
|
||||
The engine returns PSNR and a motion-compensated temporal-residual ratio as
|
||||
quality measurements. Neither is a watermark detector. The high-level
|
||||
`VideoInvisibleResult.requires_external_verification` flag is always true, and
|
||||
the CLI prints an `UNVERIFIED` warning plus the Gemini Flash verification
|
||||
prompt. The companion `scripts/video_synthid_sweep.py` imports the same engine
|
||||
helpers to build a matched control and candidate grid, preventing research and
|
||||
shipped regeneration paths from drifting.
|
||||
|
||||
[`video_visible.py`](../src/remove_ai_watermarks/video_visible.py) implements
|
||||
the first pixel stages for Sora, Veo, Seedance, and Dola. The Sora detector
|
||||
searches a normalized frame with a fully synthetic mascot-and-text silhouette
|
||||
|
||||
@@ -154,6 +154,34 @@ stream bytes. MKV/WebM inspection recognizes the corresponding
|
||||
`Segment.Tags.Tag.SimpleTag` representation; its removal requires ffmpeg for a
|
||||
stream-copy remux.
|
||||
|
||||
## Generate a video SynthID candidate
|
||||
|
||||
```python
|
||||
import remove_ai_watermarks as raiw
|
||||
|
||||
result = raiw.remove_video_invisible(
|
||||
"input.mp4",
|
||||
"candidate.mp4",
|
||||
device="auto",
|
||||
)
|
||||
assert result.requires_external_verification
|
||||
if result.remaining_metadata:
|
||||
raise RuntimeError(f"AI metadata remains: {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
|
||||
`input_synthid_candidate.mp4`; a distinct same-container output is required.
|
||||
|
||||
The returned `VideoInvisibleResult` includes output geometry, frame rate, frame
|
||||
count, paired PSNR, and the motion-compensated temporal-residual ratio. Those
|
||||
fields measure fidelity and flicker only. They are not a SynthID detector.
|
||||
`requires_external_verification` is always true because Google does not publish
|
||||
a local decoder for this video payload. Verify the candidate with Gemini
|
||||
Flash's built-in content verification before treating it as watermark-negative.
|
||||
|
||||
## Remove a supported visible video mark
|
||||
|
||||
```python
|
||||
|
||||
@@ -113,6 +113,10 @@ SynthID does not have a public local pixel decoder in this project. The tool can
|
||||
infer likely presence from supported provenance metadata, but after that
|
||||
metadata is removed a local negative result is inconclusive.
|
||||
|
||||
For MP4, MOV, and M4V, `video invisible` can regenerate the video through a VAE
|
||||
and strip source metadata. This is a candidate-producing attack, not a local
|
||||
decoder. Every result still requires Gemini Flash's built-in verification.
|
||||
|
||||
The optional `detect` extra is different: it provides a local decoder for the
|
||||
open DWT-DCT watermark used by some Stable Diffusion, SDXL, and FLUX workflows.
|
||||
That signal is carrier and transformation sensitive, so a negative is still
|
||||
@@ -123,6 +127,7 @@ not a universal clean verdict.
|
||||
| Provider or family | Visible | Invisible path | Metadata or provenance |
|
||||
| --- | --- | --- | --- |
|
||||
| Google Gemini | Sparkle | Diffusion regeneration for SynthID | C2PA and related source signals |
|
||||
| Google Veo video | Veo diamond and legacy text | VAE regeneration candidate for SynthID | C2PA and related source signals |
|
||||
| OpenAI image generators | None registered | Diffusion regeneration for supported invisible signals | C2PA and generator provenance |
|
||||
| Stable Diffusion and SDXL | None registered | Diffusion regeneration; optional open decoder | Embedded parameters and text metadata |
|
||||
| FLUX | None registered | Diffusion regeneration; optional open decoder | C2PA for supported sources |
|
||||
|
||||
+13
-2
@@ -331,8 +331,11 @@ framework.
|
||||
Gemini's verification flow can report the portions of a video where it detects
|
||||
Google SynthID. This is still a proprietary oracle: a normal Gemini answer that
|
||||
describes visual clues, metadata, or an unavailable decoder is not a pixel
|
||||
verdict. Use the dedicated verification flow offered to an eligible signed-in
|
||||
account; some versions expose an explicit `@synthid` trigger.
|
||||
verdict. Google's current support flow is to upload the file to an eligible
|
||||
signed-in Gemini account and ask whether it was created or edited by Google AI.
|
||||
In the 2026-07-29 calibration, Flash invoked the built-in verifier while Pro
|
||||
first answered from the visible Veo logo; the model mode is therefore part of
|
||||
the recorded procedure, not an interchangeable chat preference.
|
||||
|
||||
The research harness `scripts/video_synthid_sweep.py` tests a VAE regeneration
|
||||
attack without pretending to detect success locally. It emits:
|
||||
@@ -350,6 +353,14 @@ control-positive, candidate-negative pair is evidence about the regeneration
|
||||
attack. PSNR and temporal residual measure fidelity and flicker, never watermark
|
||||
presence.
|
||||
|
||||
The shipped `video invisible` command and `remove_video_invisible` API reuse the
|
||||
same VAE regeneration mechanism for a complete input sequence. They always
|
||||
label the output as requiring external verification. Calibration on 2026-07-29
|
||||
used two public Veo clips: both matched controls were positive, the stronger
|
||||
default candidate was negative on both, and a weaker candidate was negative on
|
||||
only one. This establishes a content-dependent operating point, not a universal
|
||||
clean verdict.
|
||||
|
||||
The VAE perturbation follows the general regeneration-attack construction from
|
||||
Zhao et al. The video-specific control and temporal metric are local additions.
|
||||
VideoMarkBench motivates testing frame aggregation and matched perturbations,
|
||||
|
||||
@@ -232,6 +232,14 @@ The harness shares one latent-noise field across the sequence to avoid adding
|
||||
independent frame noise. Its temporal-residual metric is a fidelity check, not a
|
||||
watermark detector.
|
||||
|
||||
The 2026-07-29 run completed the first two-carrier calibration through Gemini
|
||||
Flash's built-in content verification. Both matched Veo controls were positive.
|
||||
The stronger default VAE candidate was negative on both carriers; a weaker
|
||||
candidate was negative on only one. The implementation is exposed as
|
||||
`video invisible` and `remove_video_invisible`, but every result remains
|
||||
explicitly oracle-required because the small calibration does not establish
|
||||
full-corpus or future-verifier coverage.
|
||||
|
||||
## Tier E -- robustness and adversarial inputs
|
||||
|
||||
Malformed and hostile inputs, including truncated files:
|
||||
|
||||
Reference in New Issue
Block a user