Remove segment outputs and fix mode type for cross-node wiring
- Remove segment_1-4 from both MaskGenerator and SourcePrep outputs - Define shared VACE_MODES list so SourcePrep's mode output type matches MaskGenerator's combo input type (fixes inability to connect mode wire) - Remove dead helpers: _placeholder, _ensure_nonempty, ph(), safe() - Clean up dead variables (mid_seg, part_1, part_4, image_b, segment_1) - Remove segment tables from README mode reference sections Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
58
README.md
58
README.md
@@ -83,7 +83,6 @@ Builds mask and control_frames sequences for all VACE generation modes. Works st
|
||||
|---|---|
|
||||
| `mask` | Black/white frame sequence (`target_frames` long). Black = keep, White = generate. |
|
||||
| `control_frames` | Source frames composited with grey (`#7f7f7f`) fill (`target_frames` long). Fed to VACE as visual reference. |
|
||||
| `segment_1`–`segment_4` | Clip segments whose contents depend on the mode (see below). Unused segments are 1-frame black placeholders. |
|
||||
| `frames_to_generate` | INT — number of new frames the model needs to produce (the white/grey region). |
|
||||
|
||||
## Mode Reference
|
||||
@@ -104,11 +103,6 @@ mask: [ BLACK × source ][ WHITE × generated ]
|
||||
control_frames: [ source clip ][ GREY × generated ]
|
||||
```
|
||||
|
||||
| Segment | Content |
|
||||
|---|---|
|
||||
| `segment_1` | Source frames (trimmed if `split_index ≠ 0`) |
|
||||
| `segment_2`–`4` | Placeholder |
|
||||
|
||||
---
|
||||
|
||||
### Pre Extend
|
||||
@@ -123,11 +117,6 @@ mask: [ WHITE × generated ][ BLACK × reference ]
|
||||
control_frames: [ GREY × generated ][ reference frames ]
|
||||
```
|
||||
|
||||
| Segment | Content |
|
||||
|---|---|
|
||||
| `segment_1` | Remaining frames after the reference (source[split_index:]) |
|
||||
| `segment_2`–`4` | Placeholder |
|
||||
|
||||
---
|
||||
|
||||
### Middle Extend
|
||||
@@ -142,12 +131,6 @@ mask: [ BLACK × part_a ][ WHITE × generated ][ BLACK × part_b ]
|
||||
control_frames: [ part_a ][ GREY × generated ][ part_b ]
|
||||
```
|
||||
|
||||
| Segment | Content |
|
||||
|---|---|
|
||||
| `segment_1` | Part A — source[:split_index] |
|
||||
| `segment_2` | Part B — source[split_index:] |
|
||||
| `segment_3`–`4` | Placeholder |
|
||||
|
||||
---
|
||||
|
||||
### Edge Extend
|
||||
@@ -165,13 +148,6 @@ mask: [ BLACK × end_seg ][ WHITE × generated ][ BLACK × start_seg ]
|
||||
control_frames: [ end_seg ][ GREY × generated ][ start_seg ]
|
||||
```
|
||||
|
||||
| Segment | Content |
|
||||
|---|---|
|
||||
| `segment_1` | Start edge — source[:edge_frames] |
|
||||
| `segment_2` | Middle remainder — source[edge_frames:−edge_frames] |
|
||||
| `segment_3` | End edge — source[−edge_frames:] |
|
||||
| `segment_4` | Placeholder |
|
||||
|
||||
---
|
||||
|
||||
### Join Extend
|
||||
@@ -190,13 +166,6 @@ mask: [ BLACK × part_2 ][ WHITE × generated ][ BLACK × part_3 ]
|
||||
control_frames: [ part_2 ][ GREY × generated ][ part_3 ]
|
||||
```
|
||||
|
||||
| Segment | Content |
|
||||
|---|---|
|
||||
| `segment_1` | Part 1 — first half minus its trailing edge |
|
||||
| `segment_2` | Part 2 — trailing edge of first half |
|
||||
| `segment_3` | Part 3 — leading edge of second half |
|
||||
| `segment_4` | Part 4 — second half minus its leading edge |
|
||||
|
||||
---
|
||||
|
||||
### Bidirectional Extend
|
||||
@@ -212,11 +181,6 @@ mask: [ WHITE × pre ][ BLACK × source ][ WHITE × post ]
|
||||
control_frames: [ GREY × pre ][ source clip ][ GREY × post ]
|
||||
```
|
||||
|
||||
| Segment | Content |
|
||||
|---|---|
|
||||
| `segment_1` | Full source clip |
|
||||
| `segment_2`–`4` | Placeholder |
|
||||
|
||||
---
|
||||
|
||||
### Frame Interpolation
|
||||
@@ -232,11 +196,6 @@ mask: [ B ][ W×step ][ B ][ W×step ][ B ] ...
|
||||
control_frames: [ f0][ GREY ][ f1][ GREY ][ f2] ...
|
||||
```
|
||||
|
||||
| Segment | Content |
|
||||
|---|---|
|
||||
| `segment_1` | Full source clip |
|
||||
| `segment_2`–`4` | Placeholder |
|
||||
|
||||
---
|
||||
|
||||
### Replace/Inpaint
|
||||
@@ -253,13 +212,6 @@ mask: [ BLACK × before ][ WHITE × replace ][ BLACK × after ]
|
||||
control_frames: [ before frames ][ GREY × replace ][ after frames ]
|
||||
```
|
||||
|
||||
| Segment | Content |
|
||||
|---|---|
|
||||
| `segment_1` | Before — source[:start] |
|
||||
| `segment_2` | Original replaced frames — source[start:start+length] |
|
||||
| `segment_3` | After — source[start+length:] |
|
||||
| `segment_4` | Placeholder |
|
||||
|
||||
---
|
||||
|
||||
### Video Inpaint
|
||||
@@ -282,11 +234,6 @@ mask: [ per-pixel mask broadcast to (B, H, W, 3) ]
|
||||
control_frames: [ source pixels where mask=0, grey where mask=1 ]
|
||||
```
|
||||
|
||||
| Segment | Content |
|
||||
|---|---|
|
||||
| `segment_1` | Full source clip |
|
||||
| `segment_2`–`4` | Placeholder |
|
||||
|
||||
---
|
||||
|
||||
### Keyframe
|
||||
@@ -307,11 +254,6 @@ mask: [ B ][ W×26 ][ B ][ W×25 ][ B ][ W×26 ][ B ]
|
||||
control_frames: [ k0][ GREY ][ k1][ GREY ][ k2][ GREY ][ k3]
|
||||
```
|
||||
|
||||
| Segment | Content |
|
||||
|---|---|
|
||||
| `segment_1` | Full source clip (keyframe images) |
|
||||
| `segment_2`–`4` | Placeholder |
|
||||
|
||||
---
|
||||
|
||||
## Node: VACE Merge Back
|
||||
|
||||
106
nodes.py
106
nodes.py
@@ -1,6 +1,20 @@
|
||||
import torch
|
||||
|
||||
|
||||
VACE_MODES = [
|
||||
"End Extend",
|
||||
"Pre Extend",
|
||||
"Middle Extend",
|
||||
"Edge Extend",
|
||||
"Join Extend",
|
||||
"Bidirectional Extend",
|
||||
"Frame Interpolation",
|
||||
"Replace/Inpaint",
|
||||
"Video Inpaint",
|
||||
"Keyframe",
|
||||
]
|
||||
|
||||
|
||||
def _create_solid_batch(count, height, width, color_value, device="cpu"):
|
||||
"""Create a batch of solid-color frames (B, H, W, 3). Returns empty tensor if count <= 0."""
|
||||
if count <= 0:
|
||||
@@ -8,38 +22,14 @@ def _create_solid_batch(count, height, width, color_value, device="cpu"):
|
||||
return torch.full((count, height, width, 3), color_value, dtype=torch.float32, device=device)
|
||||
|
||||
|
||||
def _placeholder(height, width, device="cpu"):
|
||||
"""Create a single-frame black placeholder (1, H, W, 3)."""
|
||||
return torch.zeros((1, height, width, 3), dtype=torch.float32, device=device)
|
||||
|
||||
|
||||
def _ensure_nonempty(tensor, height, width, device="cpu"):
|
||||
"""Replace a 0-frame tensor with a 1-frame black placeholder."""
|
||||
if tensor.shape[0] == 0:
|
||||
return _placeholder(height, width, device)
|
||||
return tensor
|
||||
|
||||
|
||||
class VACEMaskGenerator:
|
||||
CATEGORY = "VACE Tools"
|
||||
FUNCTION = "generate"
|
||||
RETURN_TYPES = ("IMAGE", "IMAGE", "IMAGE", "IMAGE", "IMAGE", "IMAGE", "INT")
|
||||
RETURN_NAMES = (
|
||||
"mask",
|
||||
"control_frames",
|
||||
"segment_1",
|
||||
"segment_2",
|
||||
"segment_3",
|
||||
"segment_4",
|
||||
"frames_to_generate",
|
||||
)
|
||||
RETURN_TYPES = ("IMAGE", "IMAGE", "INT")
|
||||
RETURN_NAMES = ("mask", "control_frames", "frames_to_generate")
|
||||
OUTPUT_TOOLTIPS = (
|
||||
"Mask sequence — black (0) = keep original, white (1) = generate. Per-frame for most modes; per-pixel for Video Inpaint.",
|
||||
"Visual reference for VACE — source pixels where mask is black, grey (#7f7f7f) fill where mask is white.",
|
||||
"Segment 1: source/context frames. End/Pre/Bidirectional/Frame Interpolation/Video Inpaint/Keyframe: full clip. Middle: part A. Edge: start edge. Join: part 1. Replace/Inpaint: frames before replaced region.",
|
||||
"Segment 2: secondary context. Middle: part B. Edge: middle remainder. Join: part 2. Replace/Inpaint: original replaced frames. Others: placeholder.",
|
||||
"Segment 3: Edge: end edge. Join: part 3. Replace/Inpaint: frames after replaced region. Others: placeholder.",
|
||||
"Segment 4: Join: part 4. Others: placeholder.",
|
||||
"Number of new frames to generate (white/grey frames added).",
|
||||
)
|
||||
DESCRIPTION = """VACE Mask Generator — builds mask + control_frames sequences for all VACE generation modes.
|
||||
@@ -75,18 +65,7 @@ If your source is longer, use VACE Source Prep upstream to trim it first."""
|
||||
"required": {
|
||||
"source_clip": ("IMAGE", {"description": "Source video frames (B,H,W,C tensor)."}),
|
||||
"mode": (
|
||||
[
|
||||
"End Extend",
|
||||
"Pre Extend",
|
||||
"Middle Extend",
|
||||
"Edge Extend",
|
||||
"Join Extend",
|
||||
"Bidirectional Extend",
|
||||
"Frame Interpolation",
|
||||
"Replace/Inpaint",
|
||||
"Video Inpaint",
|
||||
"Keyframe",
|
||||
],
|
||||
VACE_MODES,
|
||||
{
|
||||
"default": "End Extend",
|
||||
"description": "End: generate after clip. Pre: generate before clip. Middle: generate at split point. Edge: generate between reversed edges (looping). Join: generate to heal two halves. Bidirectional: generate before AND after clip. Frame Interpolation: insert generated frames between each source pair. Replace/Inpaint: regenerate a range of frames in-place. Video Inpaint: regenerate masked spatial regions across all frames (requires inpaint_mask). Keyframe: place keyframe images at positions within target_frames, generate between them (optional keyframe_positions for manual placement).",
|
||||
@@ -158,27 +137,19 @@ If your source is longer, use VACE Source Prep upstream to trim it first."""
|
||||
def solid(count, color):
|
||||
return _create_solid_batch(count, H, W, color, dev)
|
||||
|
||||
def ph():
|
||||
return _placeholder(H, W, dev)
|
||||
|
||||
def safe(t):
|
||||
return _ensure_nonempty(t, H, W, dev)
|
||||
|
||||
if mode == "End Extend":
|
||||
frames_to_generate = target_frames - B
|
||||
mask = torch.cat([solid(B, BLACK), solid(frames_to_generate, WHITE)], dim=0)
|
||||
control_frames = torch.cat([source_clip, solid(frames_to_generate, GREY)], dim=0)
|
||||
segment_1 = source_clip[:split_index] if split_index != 0 else source_clip
|
||||
return (mask, control_frames, safe(segment_1), ph(), ph(), ph(), frames_to_generate)
|
||||
return (mask, control_frames, frames_to_generate)
|
||||
|
||||
elif mode == "Pre Extend":
|
||||
image_a = source_clip[:split_index]
|
||||
image_b = source_clip[split_index:]
|
||||
a_count = image_a.shape[0]
|
||||
frames_to_generate = target_frames - a_count
|
||||
mask = torch.cat([solid(frames_to_generate, WHITE), solid(a_count, BLACK)], dim=0)
|
||||
control_frames = torch.cat([solid(frames_to_generate, GREY), image_a], dim=0)
|
||||
return (mask, control_frames, safe(image_b), ph(), ph(), ph(), frames_to_generate)
|
||||
return (mask, control_frames, frames_to_generate)
|
||||
|
||||
elif mode == "Middle Extend":
|
||||
image_a = source_clip[:split_index]
|
||||
@@ -188,33 +159,30 @@ If your source is longer, use VACE Source Prep upstream to trim it first."""
|
||||
frames_to_generate = target_frames - (a_count + b_count)
|
||||
mask = torch.cat([solid(a_count, BLACK), solid(frames_to_generate, WHITE), solid(b_count, BLACK)], dim=0)
|
||||
control_frames = torch.cat([image_a, solid(frames_to_generate, GREY), image_b], dim=0)
|
||||
return (mask, control_frames, safe(image_a), safe(image_b), ph(), ph(), frames_to_generate)
|
||||
return (mask, control_frames, frames_to_generate)
|
||||
|
||||
elif mode == "Edge Extend":
|
||||
start_seg = source_clip[:edge_frames]
|
||||
end_seg = source_clip[-edge_frames:]
|
||||
mid_seg = source_clip[edge_frames:-edge_frames]
|
||||
start_count = start_seg.shape[0]
|
||||
end_count = end_seg.shape[0]
|
||||
frames_to_generate = target_frames - (start_count + end_count)
|
||||
mask = torch.cat([solid(end_count, BLACK), solid(frames_to_generate, WHITE), solid(start_count, BLACK)], dim=0)
|
||||
control_frames = torch.cat([end_seg, solid(frames_to_generate, GREY), start_seg], dim=0)
|
||||
return (mask, control_frames, start_seg, safe(mid_seg), end_seg, ph(), frames_to_generate)
|
||||
return (mask, control_frames, frames_to_generate)
|
||||
|
||||
elif mode == "Join Extend":
|
||||
half = B // 2
|
||||
first_half = source_clip[:half]
|
||||
second_half = source_clip[half:]
|
||||
part_1 = first_half[:-edge_frames]
|
||||
part_2 = first_half[-edge_frames:]
|
||||
part_3 = second_half[:edge_frames]
|
||||
part_4 = second_half[edge_frames:]
|
||||
p2_count = part_2.shape[0]
|
||||
p3_count = part_3.shape[0]
|
||||
frames_to_generate = target_frames - (p2_count + p3_count)
|
||||
mask = torch.cat([solid(p2_count, BLACK), solid(frames_to_generate, WHITE), solid(p3_count, BLACK)], dim=0)
|
||||
control_frames = torch.cat([part_2, solid(frames_to_generate, GREY), part_3], dim=0)
|
||||
return (mask, control_frames, safe(part_1), safe(part_2), safe(part_3), safe(part_4), frames_to_generate)
|
||||
return (mask, control_frames, frames_to_generate)
|
||||
|
||||
elif mode == "Bidirectional Extend":
|
||||
frames_to_generate = max(0, target_frames - B)
|
||||
@@ -225,7 +193,7 @@ If your source is longer, use VACE Source Prep upstream to trim it first."""
|
||||
post_count = frames_to_generate - pre_count
|
||||
mask = torch.cat([solid(pre_count, WHITE), solid(B, BLACK), solid(post_count, WHITE)], dim=0)
|
||||
control_frames = torch.cat([solid(pre_count, GREY), source_clip, solid(post_count, GREY)], dim=0)
|
||||
return (mask, control_frames, source_clip, ph(), ph(), ph(), frames_to_generate)
|
||||
return (mask, control_frames, frames_to_generate)
|
||||
|
||||
elif mode == "Frame Interpolation":
|
||||
step = max(split_index, 1)
|
||||
@@ -240,7 +208,7 @@ If your source is longer, use VACE Source Prep upstream to trim it first."""
|
||||
ctrl_parts.append(solid(step, GREY))
|
||||
mask = torch.cat(mask_parts, dim=0)
|
||||
control_frames = torch.cat(ctrl_parts, dim=0)
|
||||
return (mask, control_frames, source_clip, ph(), ph(), ph(), frames_to_generate)
|
||||
return (mask, control_frames, frames_to_generate)
|
||||
|
||||
elif mode == "Replace/Inpaint":
|
||||
start = max(0, min(split_index, B))
|
||||
@@ -251,7 +219,7 @@ If your source is longer, use VACE Source Prep upstream to trim it first."""
|
||||
after = source_clip[end:]
|
||||
mask = torch.cat([solid(before.shape[0], BLACK), solid(length, WHITE), solid(after.shape[0], BLACK)], dim=0)
|
||||
control_frames = torch.cat([before, solid(length, GREY), after], dim=0)
|
||||
return (mask, control_frames, safe(before), safe(source_clip[start:end]), safe(after), ph(), frames_to_generate)
|
||||
return (mask, control_frames, frames_to_generate)
|
||||
|
||||
elif mode == "Video Inpaint":
|
||||
if inpaint_mask is None:
|
||||
@@ -275,7 +243,7 @@ If your source is longer, use VACE Source Prep upstream to trim it first."""
|
||||
grey = torch.full_like(source_clip, GREY)
|
||||
control_frames = source_clip * (1.0 - m3) + grey * m3
|
||||
frames_to_generate = B
|
||||
return (mask, control_frames, source_clip, ph(), ph(), ph(), frames_to_generate)
|
||||
return (mask, control_frames, frames_to_generate)
|
||||
|
||||
elif mode == "Keyframe":
|
||||
if B > target_frames:
|
||||
@@ -322,7 +290,7 @@ If your source is longer, use VACE Source Prep upstream to trim it first."""
|
||||
mask = torch.cat(mask_parts, dim=0)
|
||||
control_frames = torch.cat(ctrl_parts, dim=0)
|
||||
frames_to_generate = target_frames - B
|
||||
return (mask, control_frames, source_clip, ph(), ph(), ph(), frames_to_generate)
|
||||
return (mask, control_frames, frames_to_generate)
|
||||
|
||||
raise ValueError(f"Unknown mode: {mode}")
|
||||
|
||||
@@ -330,14 +298,14 @@ If your source is longer, use VACE Source Prep upstream to trim it first."""
|
||||
class VACESourcePrep:
|
||||
CATEGORY = "VACE Tools"
|
||||
FUNCTION = "prepare"
|
||||
RETURN_TYPES = ("IMAGE", "STRING", "INT", "INT", "MASK", "STRING", "VACE_PIPE")
|
||||
RETURN_TYPES = ("IMAGE", VACE_MODES, "INT", "INT", "MASK", "STRING", "VACE_PIPE")
|
||||
RETURN_NAMES = (
|
||||
"source_clip", "mode", "split_index", "edge_frames",
|
||||
"inpaint_mask", "keyframe_positions", "vace_pipe",
|
||||
)
|
||||
OUTPUT_TOOLTIPS = (
|
||||
"Trimmed source frames — wire to VACE Mask Generator's source_clip.",
|
||||
"Selected mode — wire to VACE Mask Generator's mode (convert widget to input).",
|
||||
"Selected mode — wire to VACE Mask Generator's mode.",
|
||||
"Adjusted split_index for the trimmed clip — wire to VACE Mask Generator.",
|
||||
"Adjusted edge_frames — wire to VACE Mask Generator.",
|
||||
"Inpaint mask trimmed to match output — wire to VACE Mask Generator.",
|
||||
@@ -368,18 +336,7 @@ input_left / input_right (0 = use all available):
|
||||
"required": {
|
||||
"source_clip": ("IMAGE", {"description": "Full source video frames (B,H,W,C tensor)."}),
|
||||
"mode": (
|
||||
[
|
||||
"End Extend",
|
||||
"Pre Extend",
|
||||
"Middle Extend",
|
||||
"Edge Extend",
|
||||
"Join Extend",
|
||||
"Bidirectional Extend",
|
||||
"Frame Interpolation",
|
||||
"Replace/Inpaint",
|
||||
"Video Inpaint",
|
||||
"Keyframe",
|
||||
],
|
||||
VACE_MODES,
|
||||
{
|
||||
"default": "End Extend",
|
||||
"description": "Generation mode — must match VACE Mask Generator's mode.",
|
||||
@@ -502,7 +459,6 @@ input_left / input_right (0 = use all available):
|
||||
sym = min(eff_left, eff_right)
|
||||
start_seg = source_clip[:sym]
|
||||
end_seg = source_clip[-sym:] if sym > 0 else source_clip[:0]
|
||||
mid_seg = source_clip[sym:B - sym] if 2 * sym < B else source_clip[:0]
|
||||
output = torch.cat([start_seg, end_seg], dim=0)
|
||||
pipe = {"mode": mode, "trim_start": 0, "trim_end": B, "left_ctx": 0, "right_ctx": 0}
|
||||
return (output, mode, 0, sym, mask_ph(), kp_out, pipe)
|
||||
@@ -516,10 +472,8 @@ input_left / input_right (0 = use all available):
|
||||
eff_left = min(eff_left, first_half.shape[0])
|
||||
eff_right = min(eff_right, second_half.shape[0])
|
||||
sym = min(eff_left, eff_right)
|
||||
part_1 = first_half[:-sym] if sym < first_half.shape[0] else first_half[:0]
|
||||
part_2 = first_half[-sym:]
|
||||
part_3 = second_half[:sym]
|
||||
part_4 = second_half[sym:]
|
||||
output = torch.cat([part_2, part_3], dim=0)
|
||||
pipe = {"mode": mode, "trim_start": half - sym, "trim_end": half + sym, "left_ctx": sym, "right_ctx": sym}
|
||||
return (output, mode, 0, sym, mask_ph(), kp_out, pipe)
|
||||
|
||||
Reference in New Issue
Block a user