H3 · Apply Edit Mask
Repaint the person, keep every other pixel honest
- latent
- source_latent
- mask
- LATENT
Inpainting a still image is a solved, boring thing. Inpainting a video with an audio track welded into the latent is not, because the mask has to survive H3's temporal compression without turning into a flickering cutout.
H3 · Apply Edit Mask is the small, sharp node that does that conversion. Its whole job: take a pixel-space mask and a clean source latent, and hand the sampler a nested video+audio latent pair where only the masked region is noisy - and only in cells the model can actually express.
Why it needs its own node
H3's video latent is not a per-frame image latent. Its temporal axis packs frames in a repeating 1-4-4-4-4 span pattern, and its spatial cells are 32-pixel DiT patches. Feed it a mask that's the same shape as your frames and it has no idea what to do with it. Feed it a mask that's the right shape but sharp, and the boundary you get back won't line up with the source pixels you're trying to preserve - you get a halo of half-regenerated background.
So the node does two conversions, both conservative in the same direction. It unions mask values across each temporal span with a max, so a pixel that's masked in any frame of a span is treated as masked for the whole span. Then it max-pools down to the 32-px grid and dilates back up by 2 in latent space. The net effect: the mask you get is never smaller than the mask you drew. That's deliberate - it's cheaper to regenerate two extra pixels of background than to leave a seam through a face.
The inputs
Three, all required, no widgets. mask is your pixel-space mask - Window Inputs' edit_mask output is the intended source, which means it's already been sliced and padded to this window. latent is the output of Apply History. source_latent is a native VAE Encode of the clean source window: the same frames, unmasked, encoded through the video VAE.
The node swaps the video half of the nested latent for source_latent["samples"] and attaches a nested noise_mask - your pooled mask for video, all-ones for audio. Audio is always fully denoised; you're not preserving source audio through this path, and trying to partially mask audio latents isn't something this node attempts.
One LATENT output, straight into your guider.
Reading the errors
It validates hard because a bad mask here costs a full render to discover. Expected the native H3 video/audio latent pair means you gave it a plain LATENT - usually because something upstream re-encoded instead of passing the nested AV latent through. Source VAE shape does not match the H3 target means your source latent came from a different canvas size or a different window than the one you're sampling; re-encode from the same source_window. H3 edit masks require dimensions divisible by 32 and the latent-timeline mismatch error are the ones you'll see if you built your mask chain against the raw video instead of Prepare Source's prepared canvas. Channel count must be 24, which is just H3's video latent width - if that number is off, you're not holding an H3 latent at all.
Install
ComfyUI Manager → search H3 Relay → install → restart. Manual:
cd /path/to/ComfyUI/custom_nodes
git clone https://github.com/akatz-ai/h3-relay.git
This node needs the H3 video VAE and a native VAE Encode node; the mask tool side of the pack needs numpy, scipy and Pillow, which the pack does not declare as pip dependencies - they're usually already in a ComfyUI environment, but a minimal hand-built venv might not have scipy, and the mask nodes will fail on import. ComfyUI 0.37.0+.
And the usual caveat, since it applies to everything in this pack: the H3 weights are under a community licence that excludes the US, EU, UK and Korea from its territory. The node is free; running it isn't, everywhere.
Where people get burned
The mask comes from Window Inputs, not from your SAM chain directly. That's the thing people get wrong: SAM's mask is full-timeline, this node needs this window's padded mask, and only Window Inputs knows which frames those are. Wire your expanded mask into Window Inputs' optional mask input and take edit_mask from there.
The second mistake is expecting a pixel-exact composite. Masked H3 sampling still regenerates the whole frame - the mask steers denoising, it doesn't paste pixels. The pack's own docs report a mean absolute difference around 1.5/255 on continuation windows even in the qualified 4090 runs, so expect the background to move slightly rather than stay frozen. If you need truly untouched background, that's a compositing problem after the fact, not a sampling one.
Inputs (3)
| Name | Type | Default | Description |
|---|---|---|---|
| latent | LATENT | — | |
| source_latent | LATENT | — | |
| mask | MASK | — |
Outputs (1)
| Name | Type | Description |
|---|---|---|
| LATENT | LATENT | — |