Zura · Prepare H3 Control Pixels
Getting your source, depth and mask onto H3's grid
- source_video
- control_video
- mask
- target_latent
- model_patch
- fitted_control
- masked_source
- visibility
Every H3 control pass starts here, and almost every H3 control error traces back to skipping something this node does. It's the unglamorous step: it takes your source video, your depth guide and your edit mask, and forces all three onto the exact grid the target latent implies - same raster, same frame count, same multiples of 32 that H3's patch structure needs.
What it's really solving
H3's video VAE compresses 16× spatially and its DiT works in patches on top of that, so a latent carrying H × W spatial dims corresponds to H×16 × W×16 pixels, and both need to be even at the latent level. Get that wrong and you're not "slightly off" - you get a hard error at sampling time, or control that's subtly misaligned frame by frame.
So this node works backwards from the target latent. It reads target_latent to derive the native pixel raster and the frame count, then:
- resizes the depth guide to that raster and frame count (bilinear, center crop/pad),
- resizes a down-mixed version of the source the same way,
- thresholds your
maskat 0.5 and inverts it - the outputvisibilityis 1 where the model may repaint and 0 where the source is frozen, - multiplies the fitted source by that visibility.
Hence masked_source: the performer's region knocked out to black, everything else intact.
The bit nobody expects
If the loaded H3 control patch uses inpaint_post_norm, the node adds the ImageNet mean back into the masked region after zeroing it. That's the native H3 inpaint preprocessing convention, and skipping it is why hand-rolled H3 control sometimes produces a grey halo around the subject - the VAE was trained to see normalised pixels there, not zeros. You don't have to think about it, but it's the reason this node reads model_patch at all.
Two more things worth knowing: the tail is padded by repeating the last frame, and the visibility channel uses max-pooling-style fitting rather than smooth interpolation, so a partially-covered pixel counts as visible. Conservative on both counts - you get real data rather than invented motion, and the model gets slightly more room to edit than you drew.
Inputs and outputs
source_video-IMAGE, the original frames.control_video-IMAGE, the depth guide (or whatever structure you're conditioning on).mask-MASK,[H, W]or[frames, H, W], values in 0–1. This is the region you want changed.target_latent-LATENT, the H3 target the whole thing is measured against.model_patch-MODEL_PATCH, the loaded H3 fun-control patch. Needed because of that normalisation branch.fitted_control-IMAGE, into a stockVAEEncode.masked_source-IMAGE, into a second stockVAEEncode.visibility-MASK, into Zura · Pack H3 Control Latents.
Install
cd ComfyUI/custom_nodes && git clone https://github.com/ZURAVFX/ComfyUI_zura_nodes
pip install -r ComfyUI_zura_nodes/requirements.txt
Restart. Weights are the usual H3 set plus the fun-control union patch in models/model_patches/. Nothing this node does requires a GPU - it's resize and threshold arithmetic - but the downloads around it are measured in gigabytes, and they're under H3's territory-restricted community licence.
Common issues
"Prepared H3 raster exceeds the dimension limit." The target latent implies something bigger than 4096 on a side. You're controlling at a resolution H3 wasn't asked to render; drop the target resolution.
"Edit mask must lie in [0, 1]." A mask that came out of a matte or an upscaler in 0–255 land. Normalise it, or check the upstream node's output range.
"Use the actual loaded native H3 ControlNet MODEL_PATCH." Same trap as the apply node: model_patch wants the loaded patch, not a patched MODEL. The node introspects the patch to check it really is the H3 control patch with injection layers, so a wrong object type fails loudly.
Mask and video out of step. If your mask has a different frame count than the source, the fit silently repeats the last frame of whichever is shorter. Check the counts before you blame the sampler - a mask that's one frame short produces a single wrong frame at the end, which is easy to miss in a preview and obvious in a render.
Everything is black. You probably want the inverse of what you drew. mask is the area to change; visibility is derived from it. If your mask covers the performer and you expected the background to stay, that's correct behaviour - just confirm which one you meant before the sampler tells you the hard way.
Inputs (5)
| Name | Type | Default | Description |
|---|---|---|---|
| source_video | IMAGE | — | |
| control_video | IMAGE | — | |
| mask | MASK | — | |
| target_latent | LATENT | — | |
| model_patch | MODEL_PATCH | — |
Outputs (3)
| Name | Type | Description |
|---|---|---|
| fitted_control | IMAGE | — |
| masked_source | IMAGE | — |
| visibility | MASK | — |