Nodes/Teskor's Utils/TS Half Mask Video Layout
ComfyUI Node

TS Half Mask Video Layout

The Wan Animate Canvas Everyone Keeps Building by Hand

By teskor-hub·Created 8 months ago·Updated 10 days ago· 5
TS Half Mask Video Layout
  • source_video
  • wide_canvas
  • right_noise_mask
  • layout
  • canvas_width
  • canvas_height
  • frame_count
  • right_width
  • right_height
◄left_quality350►
◄right_aspect_ratio9:16 (Portrait Widescreen)►
◄right_quality350►
◄crop_modefit►

Half-mask video is the trick behind a lot of Wan motion transfer: put the reference material and the footage you want generated into one wide canvas, mask the reference half to zero noise, and let the sampler only work on the other half. Wan Animate is the most used of Alibaba's open task models and the workflow everyone actually runs, which means a lot of people are assembling that canvas out of ImageConcatMulti, a resize node and a manually built mask. It works until your source aspect ratio isn't the one you assumed, at which point your dimensions stop being divisible by 32 and the model stops being happy.

TS Half Mask Video Layout builds the whole thing in one node: the canvas, the noise mask, the layout metadata the rest of the chain needs, and the exact numbers.

The resolution math is the node

Every dimension it produces is snapped to a 32-pixel grid, which is the part worth understanding. Video diffusion models want it, and it also means the boundary between the two panels lands between latent columns instead of smearing halfway across one when ComfyUI resamples your mask down to latent size.

left_quality and right_quality are short-edge targets, not widths: 200 through 2048, plus max on the left, both defaulting to 350. The left panel is fitted from your source at that short edge and never upscaled - pick 350 on a 1080p clip and you get a 352px-ish short edge, pick max and you get the source's native size snapped down to the grid. The right panel is built fresh from right_aspect_ratio (1:1, 2:3, 3:2, 3:4, 4:3, 9:16, 16:9, 21:9; default 9:16 portrait) at right_quality.

Take a 1920x1080 source, 350 on the left, 9:16 at 350 on the right. Left becomes 640x352. Right becomes 352x640. Panels are vertically centred, the canvas is 992x640, and you get 144 rows of black above and below the reference. That's fit.

crop_mode is the real decision

  • fit (default) - keep the entire source frame, add black bars if the left panel is shorter than the canvas. Nothing of your reference gets thrown away.
  • fill_height_center_crop - left panel is widened to the canvas height and cover-cropped, excess removed equally from both sides. In the example above that's a 640x640 crop out of a 1920-wide frame, so you lose the outer two-thirds of every frame but get zero bars and no distortion.
  • match_right_center_crop - left panel exactly equals the right panel's size, cover-cropped. Two equal halves, and on a landscape source that's a brutal crop. Reach for it when you specifically want both panels on the same latent footprint, not as a default.

Outputs, and where each one goes

  • wide_canvas (IMAGE) → VAE Encode → Set Latent Noise Mask.
  • right_noise_mask (MASK) → the mask input. Zeros over the left panel and the bars, ones over the right panel's rectangle.
  • layout (TS_HALF_MASK_LAYOUT) → keep it alive through the sampler and hand it to TS Extract Generated Video after the decode, which is what gets your single-panel video back out of the wide render.
  • canvas_width, canvas_height, frame_count, right_width, right_height (INT) - cheap sanity checks, and frame_count is what your sampler length has to match. The mask covers exactly that many frames; if you generate a different count, the two won't line up.
Source frames → TS Half Mask Video Layout → VAE Encode → Set Latent Noise Mask → sampler
decoded wide frames + layout → TS Extract Generated Video → video combine

Install

ComfyUI Manager, search Teskor's Utils, or:

cd ComfyUI/custom_nodes
git clone https://github.com/teskor-hub/comfyui-teskors-utils
pip install -r comfyui-teskors-utils/requirements.txt

Restart ComfyUI, then find the nodes under Teskor's Utils/Video. Requirements are numpy and opencv-python; the heavy lifting here is pure PyTorch, which ComfyUI already provides. Don't let pip reinstall torch for you.

Where people get burned

The canvas is a full float tensor of every frame at once. Push right_quality to 1024 with 21:9 and a few hundred source frames and you've got a very large allocation before the sampler has drawn a single step. The README's advice is the right one: run a short test clip at your target numbers before committing to the full sequence. It's also more than a memory question - the masked half is still in the sequence, so the model pays attention cost for it. Half-masking isn't a free crop, it's roughly the price of a canvas that size.

Second: this node does not round your frame count for you. The Wan VAE compresses time 4x, so valid lengths are 4n+1, and everything else comes back short or weird. frame_count is reported, not corrected.

Third, purely practical: there's basically no community discussion of this pack yet. The code is clean and the README is honest, but if something breaks you're reading GitHub issues from scratch. Keep the layout node visible - its numbers are the whole contract between the two halves of the workflow, and eyeballing canvas_width and right_height explains a bad render faster than re-running the sampler does.

CategoryTeskor's Utils/Video

Inputs (5)

NameTypeDefaultDescription
source_videoIMAGE—
left_qualityCOMBO35027 options: max, 200, 256, 320, 350, 400, +21
right_aspect_ratioCOMBO9:16 (Portrait Widescreen)8 options: 1:1 (Square), 2:3 (Portrait Photo), 3:2 (Photo), 3:4 (Portrait Standard), 4:3 (Standard), 9:16 (Portrait Widescreen), +2
right_qualityCOMBO35026 options: 200, 256, 320, 350, 400, 448, +20
crop_modeoptCOMBOfit3 options: fit, fill_height_center_crop, match_right_center_crop

Outputs (8)

NameTypeDescription
wide_canvasIMAGE—
right_noise_maskMASK—
layoutTS_HALF_MASK_LAYOUT—
canvas_widthINT—
canvas_heightINT—
frame_countINT—
right_widthINT—
right_heightINT—