VideoSeal Embed Watermark
Hide a recoverable ID inside your frames — invisible video watermarking, fully local
- model
- images
- audio
- watermarked
- message
- audio
If you generate video for clients, contests, or just to keep track of your own stuff, you eventually ask the same question: how do I prove I made this after it's been recompressed, re-uploaded, and stripped of every scrap of metadata? PNG metadata dies in the first re-encode. Pixels are the only thing that survive. VideoSeal Embed Watermark is the node that bakes an arbitrary ID into the frames themselves - invisibly, locally, with no cloud service and no API key - so you can read it back out later with the Detect node.
It's a ComfyUI wrapper around Meta's VideoSeal (facebookresearch/videoseal, MIT-licensed), vendored in by the aperire/ComfyUI-VideoSeal pack. Nothing leaves your machine: the weights auto-download on first use, the model runs in your ComfyUI Python env, and it doesn't touch your Torch install.
What it actually does
Drop it between your VAE decode and your save node - feed it the IMAGE frames of your finished video, type an ID into watermark_id, and out the other side you get watermarked_images that look identical to the originals. The embedder writes a binary message into every frame (chunked across time for temporal consistency), so even a heavily compressed or cropped clip usually still carries enough of the message to decode.
The message itself is just your UTF-8 text ID packed into bits. The default videoseal model holds 256 bits, and the node is smart about it: IDs up to 27 bytes get a CRC32 checksum tacked on (so corruption gets caught on decode), anything up to 31 bytes works without the checksum, and chunkyseal (1024 bits) swallows much longer strings. A 128-bit ID in the canonical wm_v1_ form gets full Reed–Solomon error correction instead - more on that in the PixelSeal article.
The inputs that matter
Most of these have sane defaults, so you really set three:
watermark_id- the string you'll search for later. Make it unique and meaningful; you can't rename it after the fact.model-videoseal(robust, 256-bit, the default),pixelseal(best imperceptibility), orchunkyseal(4× capacity). Use the same one to embed and detect.scaling_w- the strength-vs-invisibility knob, 0.01–1.0. Higher = survives more abuse but risks visible noise; 0.2 is the pack's recommended starting point.
chunk_size/step_size control how frames are processed per pass (the pack defaults of 16/4 are fine - leave them). video_mode decides how the message repeats across frames; repeat is the safe default. lowres_attenuation lets the model hide the signal in detail your eye can't track, using JND perception masking - keep it on. device is auto, and it correctly falls back to CPU.
Outputs: watermarked_images (feed to SaveAnimatedWEBP, SaveVideo, or whatever you use), plus watermark_id echoed back and message_bits showing the model's capacity.
Install
ComfyUI Manager, search ComfyUI-VideoSeal, install, restart. Or manually:
cd ComfyUI/custom_nodes
git clone https://github.com/aperire/ComfyUI-VideoSeal
# restart ComfyUI
The pack vendors the upstream source and a small Reed–Solomon library, and installs no pip packages itself. What it does do on first run is download the model checkpoint (a few hundred MB) into a ckpts/ folder it creates in your ComfyUI root.
Where people get burned
- First run looks frozen. The model downloads on first embed, not at startup. Give it a minute; check for a
ckpts/folder in the ComfyUI directory. ModuleNotFoundErroron first embed. The pack lazy-imports the vendored upstream when you run the node, and that import pulls inomegaconf,timm,PyWavelets,scikit-image, and friends. A stock ComfyUI env often lacks some. If you see it,pip install omegaconf timm PyWavelets scikit-imageand retry.- Overly long IDs. On the 256-bit model, keep the ID to 31 bytes or less - the node raises a clear error telling you exactly what fits.
- Wrong model on detect. The extractor is model-specific. Embed with
videoseal, detect withvideoseal, or you'll get garbage out.
One honest caveat: this is not C2PA and writes no provenance metadata - it just changes pixels slightly. Someone determined to scrub it can, and no watermark survives every transform. For everyday attribution against casual re-upload, it's genuinely hard to beat.
Inputs (8)
| Name | Type | Default | Description |
|---|---|---|---|
| model | VIDEOSEAL_MODEL | — | |
| images | IMAGE | — | |
| is_video | COMBO | true | 2 options: true, false |
| messageopt | STRING | — | |
| scaling_wopt | FLOAT | 0.200–1 | — |
| lowres_attenuationopt | COMBO | true | 2 options: true, false |
| batch_sizeopt | INT | 321–256 | — |
| audioopt | AUDIO | — |
Outputs (3)
| Name | Type | Description |
|---|---|---|
| watermarked | IMAGE | — |
| message | STRING | — |
| audio | AUDIO | — |