VideoSeal Detect Watermark
Read the invisible watermark back out — the other half of VideoSeal
- model
- images
- message
- confidence
- detection_map
Embedding a watermark is only half the job - at some point you need to prove it's there. VideoSeal Detect Watermark is the output node that does the reading: feed it video frames, and it decodes whatever VideoSeal message is buried in the pixels and hands you back the ID, a pass/fail verdict, and a plain-text report you can read in the UI. It's the verification half of the aperire/ComfyUI-VideoSeal pack, the counterpart to VideoSeal Embed Watermark.
This is the node you run when someone re-uploads your render, or when a client swears they never touched a file, or just when you want to check your own pipeline is actually stamping frames. Load the suspect video into ComfyUI, extract the frames, wire them in, and read the answer off the report output without even opening a console.
How it works
The model's extractor runs over every frame and produces a soft estimate of each message bit - a per-pixel logit that says "this bit is probably a 1." A single frame is noisy, so the node merges across all frames using an aggregation method (default avg; the squared and L1/L2-norm variants weight high-confidence frames more heavily). More frames, cleaner result.
Then it decodes the merged bits, trying formats in order:
- a Reed–Solomon-protected
wm_v1_ID (this is what PixelSeal's canonical IDs use - it can correct up to eight corrupted bytes), - a plain text ID with its CRC32 checksum,
- and, if you gave it an
expected_watermark_idstarting withwm_v1_, the legacywm_v1_format for backwards compatibility.
Whatever it lands on becomes decoded_watermark_id, and format_valid tells you whether the bits even formed a recognizable watermark at all. frames_checked is just the number of frames you fed it.
The input that makes it useful
Everything except two things has a sensible default:
expected_watermark_id- the interesting one. Leave it empty and you get whatever ID decodes (great for checking an unknown video). Fill it in with the ID you embedded and the node also computesbit_accuracy_percent- the exact bit-by-bit match between what's decoded and what you expected, which is the honest "is this really mine, and how badly is it damaged" number. You also get aconfidence_percentin the report.model- must match the model used to embed.videoseal-embedded content needsvideoseal; PixelSeal content needspixelseal. This is the single most common mistake, and it produces garbage instead of a clean ID.
chunk_size (default 16) sets frames per pass, device is auto. Outputs: decoded_watermark_id, format_valid, bit_accuracy_percent, frames_checked, and the report string that bundles all of it (plus payload encoding and confidence) into the UI. It's an output node, so it runs last in the graph - that's expected.
Install
Same pack as the embed node. ComfyUI Manager, search ComfyUI-VideoSeal, install, restart. Or:
cd ComfyUI/custom_nodes
git clone https://github.com/aperire/ComfyUI-VideoSeal
# restart ComfyUI
No pip packages are installed by the pack; the model checkpoint downloads on first use into a ckpts/ folder in your ComfyUI root.
Where people get burned
- Model mismatch. Embed with
pixelseal, detect withpixelseal. Crossing models gives you a confident-sounding wrong answer. - Expected ID left empty. You still get
decoded_watermark_id, butbit_accuracy_percentcomes back as not checked. Set the expected ID if you want the pass/fail number. - Too few frames. Aggregation is what makes detection reliable - one or two frames decode poorly, especially after compression. Feed it a meaningful chunk of the video.
- First-run download and missing imports. The checkpoint downloads on first detect, and if the env lacks the vendored upstream's imports (
omegaconf,timm,PyWavelets,scikit-image) you'll hitModuleNotFoundError;pip installthem and retry.
Reasonable expectations: this is not magic. Invisible watermarks survive ordinary recompression and re-uploads, but a deliberate scrub with crops, resizes, and re-encodes can kill them. Treat detection as strong evidence of provenance, not a forensic guarantee.
Inputs (5)
| Name | Type | Default | Description |
|---|---|---|---|
| model | VIDEOSEAL_MODEL | — | |
| images | IMAGE | — | |
| is_video | COMBO | true | 2 options: true, false |
| aggregationopt | COMBO | avg | 4 options: avg, squared_avg, l1norm_avg, l2norm_avg |
| batch_sizeopt | INT | 321–256 | — |
Outputs (3)
| Name | Type | Description |
|---|---|---|
| message | STRING | — |
| confidence | FLOAT | — |
| detection_map | IMAGE | — |