intitial commit

This commit is contained in:
stuttlepress
2026-01-03 17:55:53 -06:00
commit a879dfd29e
6 changed files with 229 additions and 0 deletions
+46
View File
@@ -0,0 +1,46 @@
# Python
__pycache__/
*.py[cod]
*$py.class
*.so
.Python
*.egg-info/
dist/
build/
*.egg
# Virtual environments
venv/
env/
ENV/
# IDE
.vscode/
.idea/
*.swp
*.swo
*~
.DS_Store
# OS
Thumbs.db
.DS_Store
# ComfyUI specific
*.safetensors
*.ckpt
*.pth
*.pt
models/
input/
output/
temp/
# Logs
*.log
# Test files
test_*.py
tests/
TODO.md
+21
View File
@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2025
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
+48
View File
@@ -0,0 +1,48 @@
# Wan VACE Prep
A ComfyUI node for smooth video transitions with Wan VACE - generates control video and masks
## What It Does
Takes two video clips and prepares them for smooth VACE-based transition
- Extracts context frames from both clips
- Builds VACE control video and mask
- Output video segments for final assembly
- Optionally include context frames in output segments for crossfade or other artifact-reduction processing
## Installation
Clone this repository into your custom_nodes directory.
```bash
cd /path/to/comfyui/custom_nodes
git clone https://github.com/stuttlepress/ComfyUI-Wan-VACE-Prep
```
Restart ComfyUI.
## Usage
The node appears as **"Wan VACE Prep"** in the `video/VACE` category.
![Wan VACE Prep Node](assets/comfyui-wan-vace-prep.png)
## Parameters
### context_frames
Reference frames from each video edge that VACE uses for interpolation. These frames guide the model and are preserved in the output. Must be a multiple of 4.
### replace_frames
Number of frames at each transition edge to discard and regenerate. These create the actual transition blend zone. Must be a multiple of 4.
### add_frames
Number of completely new frames to generate between the two clips, extending the transition duration. Must be a multiple of 4.
Note: Wan likes to generate 4n+1 frames at a time. If you ask it for some other amount, it will silently round your request down to the nearest 4n+1. For this reason, parameters are restricted to multiples of 4, and the node adds +1 to the number of generated frames.
### crossfade_mode
- **False (default)**: Clean output - start_images and end_images exclude context frames. Use this for simple concatenation.
- **True**: Cross-fade mode - includes context frames in start_images and end_images so they can be blended with the context frames included in the VACE generated clip. This can help mitigate color or brightness artifacts at the transition.
## License
MIT License - feel free to use, modify, and distribute.
+7
View File
@@ -0,0 +1,7 @@
"""
Wan VACE Prep - Custom node for preparing videos for Wan VACE generation
"""
from .wan_vace_prep import NODE_CLASS_MAPPINGS, NODE_DISPLAY_NAME_MAPPINGS
__all__ = ['NODE_CLASS_MAPPINGS', 'NODE_DISPLAY_NAME_MAPPINGS']
Binary file not shown.

After

Width:  |  Height:  |  Size: 195 KiB

+107
View File
@@ -0,0 +1,107 @@
import torch
class WanVACEPrep:
"""Generates VACE control video and mask for smooth transitions between two videos using context frames and frame replacement."""
@classmethod
def INPUT_TYPES(cls):
return {
"required": {
"video_1": ("IMAGE",),
"video_2": ("IMAGE",),
"context_frames": ("INT", {
"default": 8,
"min": 4,
"max": 120,
"step": 4,
"tooltip": "Reference frames from each video edge for VACE interpolation (multiple of 4)."
}),
"replace_frames": ("INT", {
"default": 8,
"min": 0,
"max": 120,
"step": 4,
"tooltip": "Number of frames to regenerate at each transition edge (multiple of 4)."
}),
"add_frames": ("INT", {
"default": 0,
"min": 0,
"max": 240,
"step": 4,
"tooltip": "Number of new transition frames to generate, in addition to the replace_frames (multiple of 4)."
}),
"crossfade_mode": ("BOOLEAN", {
"default": False,
"tooltip": "Include context frames in start_images and end_images for cross-fade or other use cases."
}),
}
}
RETURN_TYPES = ("IMAGE", "MASK", "INT", "INT", "INT", "IMAGE", "IMAGE")
RETURN_NAMES = ("control_video", "control_mask", "width", "height", "length", "start_images", "end_images")
FUNCTION = "vace_prep"
CATEGORY = "video/VACE"
def vace_prep(self, video_1, video_2, context_frames, replace_frames, add_frames, crossfade_mode):
v1_len = video_1.shape[0]
v2_len = video_2.shape[0]
required_frames = context_frames + replace_frames
if v1_len < required_frames:
raise ValueError(
f"context_frames ({context_frames}) + replace_frames ({replace_frames}) = {required_frames}, "
f"which is too large for video_1 length of {v1_len}. "
f"Reduce context_frames or replace_frames."
)
if v2_len < required_frames:
raise ValueError(
f"context_frames ({context_frames}) + replace_frames ({replace_frames}) = {required_frames}, "
f"which is too large for video_2 length of {v2_len}. "
f"Reduce context_frames or replace_frames."
)
if replace_frames > 0:
v1_context = video_1[-(context_frames + replace_frames):-replace_frames]
v2_context = video_2[replace_frames:context_frames + replace_frames]
else:
v1_context = video_1[-context_frames:]
v2_context = video_2[:context_frames]
height = int(video_1.shape[1])
width = int(video_1.shape[2])
channels = video_1.shape[3]
# Wan wants to generate 4n+1 frames. If we don't provide that,
# it will quietly round down to the nearest 4n+1. So we add 1 here.
vace_count = replace_frames * 2 + add_frames + 1
vace_frames = torch.full((vace_count, height, width, channels), 0.5, dtype=video_1.dtype, device=video_1.device)
control_video = torch.cat([v1_context, vace_frames, v2_context], dim=0)
total_frames = context_frames * 2 + vace_count
mask = torch.zeros((total_frames, height, width), dtype=torch.float32, device=video_1.device)
mask[context_frames:context_frames + vace_count] = 1.0
if crossfade_mode:
if replace_frames > 0:
start_images = video_1[:-replace_frames]
else:
start_images = video_1
end_images = video_2[replace_frames:]
else:
start_images = video_1[:-(context_frames + replace_frames)]
end_images = video_2[context_frames + replace_frames:]
length = int(control_video.shape[0])
return (control_video, mask, width, height, length, start_images, end_images)
NODE_CLASS_MAPPINGS = {
"WanVACEPrep": WanVACEPrep
}
NODE_DISPLAY_NAME_MAPPINGS = {
"WanVACEPrep": "Wan VACE Prep"
}