From a49e824bd6a4528b155cd8949873a065e5216953 Mon Sep 17 00:00:00 2001 From: yolain Date: Mon, 6 Oct 2025 14:22:54 +0800 Subject: [PATCH] first commit --- .gitignore | 24 ++ README.md | 38 ++ SaveVideoRGBA.py | 287 ++++++++++++++ __init__.py | 1 + wan_alpha_t2v_14B.json | 833 +++++++++++++++++++++++++++++++++++++++++ 5 files changed, 1183 insertions(+) create mode 100644 .gitignore create mode 100644 README.md create mode 100644 SaveVideoRGBA.py create mode 100644 __init__.py create mode 100644 wan_alpha_t2v_14B.json diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..0cdcc01 --- /dev/null +++ b/.gitignore @@ -0,0 +1,24 @@ +__pycache__ +.DS_Store +*.cache +*.ini +*.bak +.vscode/ +.idea/ +uni/ +custom_presets/** +workflows/** +node.tar.gz +py/ +front/ +prompts/ +web/dev/** +web/stable/** +web/wass/** +dist/ +.pyarmor/ +config.yaml +*.bat +*.log +*.bak +build_cloud.sh diff --git a/README.md b/README.md new file mode 100644 index 0000000..54da67c --- /dev/null +++ b/README.md @@ -0,0 +1,38 @@ + +Enhanced the comfyui savevideo node to support previewing and saving videos containing alpha channels. + +Image + +### πŸ”¨Wan-Alpha Video Usage + +1. Download models +- The Wan DiT base model: [wan2.1_t2v_14B_fp16.safetensors](https://huggingface.co/Comfy-Org/Wan_2.1_ComfyUI_repackaged/blob/main/split_files/diffusion_models/wan2.1_t2v_14B_fp16.safetensors) +- The Wan text encoder: [umt5_xxl_fp8_e4m3fn_scaled.safetensors](https://huggingface.co/Comfy-Org/Wan_2.1_ComfyUI_repackaged/blob/main/split_files/text_encoders/umt5_xxl_fp8_e4m3fn_scaled.safetensors) +- The LightX2V model: [lightx2v_T2V_14B_cfg_step_distill_v2_lora_rank64_bf16.safetensors](https://huggingface.co/Kijai/WanVideo_comfy/blob/main/Lightx2v/lightx2v_T2V_14B_cfg_step_distill_v2_lora_rank64_bf16.safetensors) +- RGBA Dora: [epoch-13-1500_changed.safetensors](https://huggingface.co/htdong/Wan-Alpha_ComfyUI/blob/main/epoch-13-1500_changed.safetensors) +- RGB VAE Decoder: [wan_alpha_2.1_vae_rgb_channel.safetensors.safetensors](https://huggingface.co/htdong/Wan-Alpha_ComfyUI/blob/main/wan_alpha_2.1_vae_rgb_channel.safetensors.safetensors) +- Alpha VAE Decoder: [wan_alpha_2.1_vae_alpha_channel.safetensors.safetensors](https://huggingface.co/htdong/Wan-Alpha_ComfyUI/blob/main/wan_alpha_2.1_vae_alpha_channel.safetensors.safetensors) + +2. Copy the files into the `ComfyUI/models` folder and organize them as follows: + +``` +ComfyUI/models +β”œβ”€β”€ diffusion_models +β”‚ └── wan2.1_t2v_14B_fp16.safetensors +β”œβ”€β”€ loras +β”‚ β”œβ”€β”€ epoch-13-1500_changed.safetensors +β”‚ └── lightx2v_T2V_14B_cfg_step_distill_v2_lora_rank64_bf16.safetensors +β”œβ”€β”€ text_encoders +β”‚ └── umt5_xxl_fp8_e4m3fn_scaled.safetensors +β”œβ”€β”€ vae +β”‚ β”œβ”€β”€ wan_alpha_2.1_vae_alpha_channel.safetensors.safetensors +β”‚ └── wan_alpha_2.1_vae_rgb_channel.safetensors.safetensors +``` + +3. Copy this repo or [SaveVideoRGBA.py](SaveVideoRGBA.py) into the `ComfyUI/custom_nodes` folder. + + +## 🀝 Acknowledgements + +- [Wan-Alpha](https://github.com/WeChatCV/Wan-Alpha) for the alpha video generate +- [ComfyUI](https://github.com/comfyanonymous/ComfyUI) for the node workflow framework \ No newline at end of file diff --git a/SaveVideoRGBA.py b/SaveVideoRGBA.py new file mode 100644 index 0000000..176e747 --- /dev/null +++ b/SaveVideoRGBA.py @@ -0,0 +1,287 @@ +from __future__ import annotations +from typing_extensions import override +from typing import Optional +from fractions import Fraction +from enum import Enum +import io +import json +import math +import av +import os +import random +import folder_paths +from comfy_api.latest._input import AudioInput, VideoInput +from comfy_api.latest import ComfyExtension, io, ui +from comfy_api.util import VideoComponents + +class SaveVideoRGBA(io.ComfyNode): + + @classmethod + def define_schema(cls): + return io.Schema( + node_id="SaveVideoRGBA", + display_name="Save Video (RGBA)", + category="image/animation", + inputs=[ + io.Image.Input("images"), + io.Float.Input("fps", default=24.0, min=1.0, max=120.0, step=1.0), + io.String.Input("filename_prefix", default="video/ComfyUI", tooltip="The prefix for the file to save. This may include formatting information such as %date:yyyy-MM-dd% or %Empty Latent Image.width% to include values from nodes."), + io.Boolean.Input("only_preview", default=False), + io.Audio.Input("audio", optional=True), + ], + outputs=[ + # io.Video.Output("VIDEO") + ], + hidden=[io.Hidden.unique_id], + is_output_node=True + ) + + @classmethod + def execute(self, images, fps, filename_prefix, only_preview, audio=None, **kwargs): + + B, H, W, C = images.shape + has_alpha = C == 4 + + video = RGBAVideoFromComponents( + VideoComponents( + images=images, + audio=audio, + frame_rate=Fraction(fps), + ) + ) + + width, height = video.get_dimensions() + results = list() + + # ι’„θ§ˆ + if only_preview or has_alpha: + output_dir = folder_paths.get_temp_directory() + prefix_append = "ComfyUI_temp_" + ''.join(random.choice("abcdefghijklmnopqrstupvxyz") for x in range(5)) + full_output_folder, filename, counter, subfolder, filename_prefix = folder_paths.get_save_image_path( + prefix_append, + output_dir, + width, + height + ) + format = 'webm' if has_alpha else 'auto' + file = f"{filename}_{counter:05}_.{RGBAVideoContainer.get_extension(format)}" + video.save_to( + path=os.path.join(full_output_folder, file), + format=format, + codec='auto', + metadata=None, + ) + + results.append(ui.SavedResult(file, subfolder, io.FolderType.temp)) + counter += 1 + + # 保存 + if not only_preview: + output_dir = folder_paths.get_output_directory() + + full_output_folder, filename, counter, subfolder, filename_prefix = folder_paths.get_save_image_path( + filename_prefix, + output_dir, + width, + height + ) + format = 'mov' if has_alpha else 'auto' + codec = 'prores_ks' if has_alpha else 'auto' + file = f"{filename}_{counter:05}_.{RGBAVideoContainer.get_extension(format)}" + video.save_to( + path=os.path.join(full_output_folder, file), + format=format, + codec=codec, + metadata=None, + ) + + if not has_alpha: + results.append(ui.SavedResult(file, subfolder, io.FolderType.output)) + counter += 1 + + return io.NodeOutput(ui=ui.PreviewVideo(results)) + +class NodeExtension(ComfyExtension): + @override + async def get_node_list(self) -> list[type[io.ComfyNode]]: + return [ + SaveVideoRGBA + ] + +async def comfy_entrypoint() -> NodeExtension: + return NodeExtension() + + + +class RGBAVideoCodec(str, Enum): + AUTO = "auto" + H264 = "h264" # H.264 codec, no alpha channel + VP9 = "libvpx-vp9" # VP9 codec, supports alpha channel + PRORES = "prores_ks" # ProRes 4444, supports alpha channel + + @classmethod + def as_input(cls) -> list[str]: + """ + Returns a list of codec names that can be used as node input. + """ + return [member.value for member in cls] + +class RGBAVideoContainer(str, Enum): + AUTO = "auto" + MP4 = "mp4" # MP4 container + WEBM = "webm" # WebM container, supports VP9 with alpha channel + MOV = "mov" # QuickTime MOV, supports ProRes with alpha channel + + @classmethod + def as_input(cls) -> list[str]: + """ + Returns a list of codec names that can be used as node input. + """ + return [member.value for member in cls] + + @classmethod + def get_extension(cls, value) -> str: + """ + Returns the file extension for the container. + """ + if isinstance(value, str): + value = cls(value) + if value == RGBAVideoContainer.AUTO: + return "mp4" + elif value == RGBAVideoContainer.MP4: + return "mp4" + elif value == RGBAVideoContainer.WEBM: + return "webm" + elif value == RGBAVideoContainer.MOV: + return "mov" + return "" + +class RGBAVideoFromComponents(VideoInput): + + def __init__(self, components: VideoComponents): + self.__components = components + + def get_components(self) -> VideoComponents: + return VideoComponents( + images=self.__components.images, + audio=self.__components.audio, + frame_rate=self.__components.frame_rate + ) + + def save_to( + self, + path: str, + format: RGBAVideoContainer = RGBAVideoContainer.AUTO, + codec: RGBAVideoCodec = RGBAVideoCodec.AUTO, + metadata: Optional[dict] = None + ): + # Check if images have alpha channel (4 channels) + has_alpha = self.__components.images.shape[-1] == 4 if len(self.__components.images.shape) == 4 else False + + # Determine format and codec based on alpha channel + if has_alpha: + # For alpha channel support, use webm container with vp9 codec + if format == RGBAVideoContainer.AUTO: + format = RGBAVideoContainer.WEBM.value + if codec == RGBAVideoCodec.AUTO: + codec = RGBAVideoCodec.VP9.value + # Ensure format is string for comparison + format_str = format.value if isinstance(format, RGBAVideoContainer) else format + codec_str = codec.value if isinstance(codec, RGBAVideoCodec) else codec + if format_str not in ['webm', 'mov']: + raise ValueError("Only WEBM and MOV formats support alpha channel") + else: + # Ensure format and codec are strings for comparison + format_str = format.value if isinstance(format, RGBAVideoContainer) else format + codec_str = codec.value if isinstance(codec, RGBAVideoCodec) else codec + if format != RGBAVideoContainer.AUTO and format_str not in ['mp4', 'webm', 'mov']: + raise ValueError("Supported formats: MP4, WEBM, MOV") + if codec != RGBAVideoCodec.AUTO and codec_str not in ['h264', 'libvpx-vp9', 'prores_ks']: + raise ValueError("Supported codecs: H264, VP9, ProRes") + + # Set default codec if AUTO + if codec == RGBAVideoCodec.AUTO: + codec = RGBAVideoCodec.H264.value + codec_str = codec + + # Ensure codec_str is defined + if 'codec_str' not in locals(): + codec_str = codec.value if isinstance(codec, RGBAVideoCodec) else codec + + # Prepare options based on format + options = {} + if format_str in ['mp4', 'mov']: + options['movflags'] = 'use_metadata_tags' + + # Determine the format string for av.open + output_format = None if format == RGBAVideoContainer.AUTO else format_str + + with av.open(path, mode='w', format=output_format, options=options) as output: + # Add metadata before writing any streams + if metadata is not None: + for key, value in metadata.items(): + output.metadata[key] = json.dumps(value) + + frame_rate = Fraction(round(self.__components.frame_rate * 1000), 1000) + + # Create a video stream + video_stream = output.add_stream(codec_str, rate=frame_rate) + video_stream.width = self.__components.images.shape[2] + video_stream.height = self.__components.images.shape[1] + + # Set pixel format based on codec and alpha channel + if has_alpha: + if codec_str in ['libvpx-vp9']: + video_stream.pix_fmt = 'yuva420p' # VP9 with alpha + elif codec_str in ['prores_ks']: + video_stream.pix_fmt = 'yuva444p10le' # ProRes 4444 + else: + video_stream.pix_fmt = 'yuva420p' # Default alpha format + else: + if codec_str in ['h264']: + video_stream.pix_fmt = 'yuv420p' + elif codec_str in ['libvpx-vp9']: + video_stream.pix_fmt = 'yuv420p' + else: + video_stream.pix_fmt = 'yuv420p' + + # Create an audio stream + audio_sample_rate = 1 + audio_stream: Optional[av.AudioStream] = None + if self.__components.audio: + audio_sample_rate = int(self.__components.audio['sample_rate']) + audio_codec = 'libopus' if format_str == 'webm' else 'aac' + audio_stream = output.add_stream(audio_codec, rate=audio_sample_rate) + + # Encode video + for i, frame in enumerate(self.__components.images): + img = (frame * 255).clamp(0, 255).byte().cpu().numpy() + + if has_alpha: + # Create frame with alpha channel (H, W, 4) + frame = av.VideoFrame.from_ndarray(img, format='rgba') + frame = frame.reformat(format=video_stream.pix_fmt) + else: + # Create frame without alpha channel (H, W, 3) + frame = av.VideoFrame.from_ndarray(img, format='rgb24') + frame = frame.reformat(format=video_stream.pix_fmt) + + packet = video_stream.encode(frame) + output.mux(packet) + + # Flush video + packet = video_stream.encode(None) + output.mux(packet) + + if audio_stream and self.__components.audio: + waveform = self.__components.audio['waveform'] + waveform = waveform[:, :, + :math.ceil((audio_sample_rate / frame_rate) * self.__components.images.shape[0])] + frame = av.AudioFrame.from_ndarray(waveform.movedim(2, 1).reshape(1, -1).float().numpy(), format='flt', + layout='mono' if waveform.shape[1] == 1 else 'stereo') + frame.sample_rate = audio_sample_rate + frame.pts = 0 + output.mux(audio_stream.encode(frame)) + + # Flush encoder + output.mux(audio_stream.encode(None)) \ No newline at end of file diff --git a/__init__.py b/__init__.py new file mode 100644 index 0000000..e102a7e --- /dev/null +++ b/__init__.py @@ -0,0 +1 @@ +from .SaveVideoRGBA import * \ No newline at end of file diff --git a/wan_alpha_t2v_14B.json b/wan_alpha_t2v_14B.json new file mode 100644 index 0000000..a2e44d9 --- /dev/null +++ b/wan_alpha_t2v_14B.json @@ -0,0 +1,833 @@ +{ + "id": "c265fdcc-11cd-4a1d-9a0d-bc37c45667d6", + "revision": 0, + "last_node_id": 79, + "last_link_id": 164, + "nodes": [ + { + "id": 38, + "type": "CLIPLoader", + "pos": [ + -117.83563995361328, + 294.0386047363281 + ], + "size": [ + 390, + 106 + ], + "flags": {}, + "order": 0, + "mode": 0, + "inputs": [], + "outputs": [ + { + "name": "CLIP", + "type": "CLIP", + "slot_index": 0, + "links": [ + 117, + 118 + ] + } + ], + "title": "Load Text Encoder", + "properties": { + "Node name for S&R": "CLIPLoader" + }, + "widgets_values": [ + "umt5_xxl_fp8_e4m3fn_scaled.safetensors", + "wan", + "default" + ] + }, + { + "id": 40, + "type": "EmptyHunyuanLatentVideo", + "pos": [ + 463.4875793457031, + 657.9263305664062 + ], + "size": [ + 315, + 130 + ], + "flags": {}, + "order": 1, + "mode": 0, + "inputs": [], + "outputs": [ + { + "name": "LATENT", + "type": "LATENT", + "slot_index": 0, + "links": [ + 91 + ] + } + ], + "properties": { + "Node name for S&R": "EmptyHunyuanLatentVideo" + }, + "widgets_values": [ + 832, + 480, + 33, + 1 + ] + }, + { + "id": 3, + "type": "KSampler", + "pos": [ + 863, + 187 + ], + "size": [ + 315, + 262 + ], + "flags": {}, + "order": 10, + "mode": 0, + "inputs": [ + { + "name": "model", + "type": "MODEL", + "link": 95 + }, + { + "name": "positive", + "type": "CONDITIONING", + "link": 46 + }, + { + "name": "negative", + "type": "CONDITIONING", + "link": 52 + }, + { + "name": "latent_image", + "type": "LATENT", + "link": 91 + } + ], + "outputs": [ + { + "name": "LATENT", + "type": "LATENT", + "slot_index": 0, + "links": [ + 35, + 108 + ] + } + ], + "properties": { + "Node name for S&R": "KSampler" + }, + "widgets_values": [ + 129212191626761, + "randomize", + 4, + 1, + "uni_pc", + "simple", + 1 + ] + }, + { + "id": 48, + "type": "ModelSamplingSD3", + "pos": [ + 572.943359375, + 51.24325180053711 + ], + "size": [ + 210, + 58 + ], + "flags": {}, + "order": 9, + "mode": 0, + "inputs": [ + { + "name": "model", + "type": "MODEL", + "link": 144 + } + ], + "outputs": [ + { + "name": "MODEL", + "type": "MODEL", + "slot_index": 0, + "links": [ + 95 + ] + } + ], + "properties": { + "Node name for S&R": "ModelSamplingSD3" + }, + "widgets_values": [ + 5 + ] + }, + { + "id": 6, + "type": "CLIPTextEncode", + "pos": [ + 365.5584411621094, + 198.10687255859375 + ], + "size": [ + 422.84503173828125, + 164.31304931640625 + ], + "flags": {}, + "order": 6, + "mode": 0, + "inputs": [ + { + "name": "clip", + "type": "CLIP", + "link": 118 + } + ], + "outputs": [ + { + "name": "CONDITIONING", + "type": "CONDITIONING", + "slot_index": 0, + "links": [ + 46 + ] + } + ], + "title": "CLIP Text Encode (Positive Prompt)", + "properties": { + "Node name for S&R": "CLIPTextEncode" + }, + "widgets_values": [ + "Medium shot. A little girl holds a bubble wand and blows out colorful bubbles that float and pop in the air. The background of this video is transparent. Realistic style." + ], + "color": "#232", + "bgcolor": "#353" + }, + { + "id": 51, + "type": "VAELoader", + "pos": [ + 867.8726196289062, + 522.9005126953125 + ], + "size": [ + 306.36004638671875, + 58 + ], + "flags": {}, + "order": 2, + "mode": 0, + "inputs": [], + "outputs": [ + { + "name": "VAE", + "type": "VAE", + "slot_index": 0, + "links": [ + 107 + ] + } + ], + "properties": { + "Node name for S&R": "VAELoader" + }, + "widgets_values": [ + "wan_alpha_2.1_vae_alpha_channel.safetensors.safetensors" + ] + }, + { + "id": 39, + "type": "VAELoader", + "pos": [ + 873.0031127929688, + 51.24325180053711 + ], + "size": [ + 306.36004638671875, + 58 + ], + "flags": {}, + "order": 3, + "mode": 0, + "inputs": [], + "outputs": [ + { + "name": "VAE", + "type": "VAE", + "slot_index": 0, + "links": [ + 76 + ] + } + ], + "properties": { + "Node name for S&R": "VAELoader" + }, + "widgets_values": [ + "wan_alpha_2.1_vae_rgb_channel.safetensors.safetensors" + ] + }, + { + "id": 37, + "type": "UNETLoader", + "pos": [ + -415.37969970703125, + 51.24325180053711 + ], + "size": [ + 346.7470703125, + 82 + ], + "flags": {}, + "order": 4, + "mode": 0, + "inputs": [], + "outputs": [ + { + "name": "MODEL", + "type": "MODEL", + "slot_index": 0, + "links": [ + 142 + ] + } + ], + "title": "Load Wan 2.1 t2v 14B", + "properties": { + "Node name for S&R": "UNETLoader" + }, + "widgets_values": [ + "wan2.1_t2v_14B_fp16.safetensors", + "default" + ] + }, + { + "id": 65, + "type": "LoraLoaderModelOnly", + "pos": [ + 267.7607421875, + 51.24325180053711 + ], + "size": [ + 270, + 82 + ], + "flags": {}, + "order": 8, + "mode": 0, + "inputs": [ + { + "name": "model", + "type": "MODEL", + "link": 143 + } + ], + "outputs": [ + { + "name": "MODEL", + "type": "MODEL", + "links": [ + 144 + ] + } + ], + "properties": { + "Node name for S&R": "LoraLoaderModelOnly" + }, + "widgets_values": [ + "epoch-13-1500_changed.safetensors", + 1 + ] + }, + { + "id": 59, + "type": "LoraLoaderModelOnly", + "pos": [ + -38.43362045288086, + 51.24325180053711 + ], + "size": [ + 270, + 82 + ], + "flags": {}, + "order": 7, + "mode": 0, + "inputs": [ + { + "name": "model", + "type": "MODEL", + "link": 142 + } + ], + "outputs": [ + { + "name": "MODEL", + "type": "MODEL", + "links": [ + 143 + ] + } + ], + "properties": { + "Node name for S&R": "LoraLoaderModelOnly" + }, + "widgets_values": [ + "lightx2v_T2V_14B_cfg_step_distill_v2_lora_rank64_bf16.safetensors", + 1 + ] + }, + { + "id": 7, + "type": "CLIPTextEncode", + "pos": [ + 359.2072448730469, + 406.81744384765625 + ], + "size": [ + 425.27801513671875, + 180.6060791015625 + ], + "flags": {}, + "order": 5, + "mode": 0, + "inputs": [ + { + "name": "clip", + "type": "CLIP", + "link": 117 + } + ], + "outputs": [ + { + "name": "CONDITIONING", + "type": "CONDITIONING", + "slot_index": 0, + "links": [ + 52 + ] + } + ], + "title": "CLIP Text Encode (Negative Prompt) Useless s", + "properties": { + "Node name for S&R": "CLIPTextEncode" + }, + "widgets_values": [ + "" + ], + "color": "#322", + "bgcolor": "#533" + }, + { + "id": 52, + "type": "VAEDecode", + "pos": [ + 1220.6512451171875, + 530.7353515625 + ], + "size": [ + 210, + 46 + ], + "flags": {}, + "order": 12, + "mode": 0, + "inputs": [ + { + "name": "samples", + "type": "LATENT", + "link": 108 + }, + { + "name": "vae", + "type": "VAE", + "link": 107 + } + ], + "outputs": [ + { + "name": "IMAGE", + "type": "IMAGE", + "slot_index": 0, + "links": [ + 163 + ] + } + ], + "title": "Alpha VAE Decode", + "properties": { + "Node name for S&R": "VAEDecode" + }, + "widgets_values": [] + }, + { + "id": 8, + "type": "VAEDecode", + "pos": [ + 1220.984130859375, + 51.24325180053711 + ], + "size": [ + 210, + 46 + ], + "flags": {}, + "order": 11, + "mode": 0, + "inputs": [ + { + "name": "samples", + "type": "LATENT", + "link": 35 + }, + { + "name": "vae", + "type": "VAE", + "link": 76 + } + ], + "outputs": [ + { + "name": "IMAGE", + "type": "IMAGE", + "slot_index": 0, + "links": [ + 164 + ] + } + ], + "title": "RGB VAE Decode", + "properties": { + "Node name for S&R": "VAEDecode" + }, + "widgets_values": [] + }, + { + "id": 74, + "type": "JoinImageWithAlpha", + "pos": [ + 1464.0049568109648, + 205.78294262667404 + ], + "size": [ + 180.52341319729067, + 46 + ], + "flags": {}, + "order": 15, + "mode": 0, + "inputs": [ + { + "name": "image", + "type": "IMAGE", + "link": 164 + }, + { + "name": "alpha", + "type": "MASK", + "link": 158 + } + ], + "outputs": [ + { + "name": "IMAGE", + "type": "IMAGE", + "links": [ + 162 + ] + } + ], + "properties": { + "Node name for S&R": "JoinImageWithAlpha" + } + }, + { + "id": 75, + "type": "InvertMask", + "pos": [ + 1488, + 374.66456757897765 + ], + "size": [ + 140, + 26 + ], + "flags": {}, + "order": 14, + "mode": 0, + "inputs": [ + { + "name": "mask", + "type": "MASK", + "link": 159 + } + ], + "outputs": [ + { + "name": "MASK", + "type": "MASK", + "links": [ + 158 + ] + } + ], + "properties": { + "Node name for S&R": "InvertMask" + } + }, + { + "id": 76, + "type": "ImageToMask", + "pos": [ + 1465.4226047466786, + 482.26601727075456 + ], + "size": [ + 210, + 58 + ], + "flags": {}, + "order": 13, + "mode": 0, + "inputs": [ + { + "name": "image", + "type": "IMAGE", + "link": 163 + } + ], + "outputs": [ + { + "name": "MASK", + "type": "MASK", + "links": [ + 159 + ] + } + ], + "properties": { + "Node name for S&R": "ImageToMask" + }, + "widgets_values": [ + "red" + ] + }, + { + "id": 79, + "type": "SaveVideoRGBA", + "pos": [ + 1720.5411964998127, + 51.24325180053711 + ], + "size": [ + 441.01432163205754, + 896.260567585984 + ], + "flags": {}, + "order": 16, + "mode": 0, + "inputs": [ + { + "name": "images", + "type": "IMAGE", + "link": 162 + }, + { + "name": "audio", + "shape": 7, + "type": "AUDIO", + "link": null + } + ], + "outputs": [ + { + "name": "VIDEO", + "type": "VIDEO", + "links": null + } + ], + "properties": { + "Node name for S&R": "SaveVideoRGBA" + }, + "widgets_values": [ + 24, + "video/ComfyUI", + false + ] + } + ], + "links": [ + [ + 35, + 3, + 0, + 8, + 0, + "LATENT" + ], + [ + 46, + 6, + 0, + 3, + 1, + "CONDITIONING" + ], + [ + 52, + 7, + 0, + 3, + 2, + "CONDITIONING" + ], + [ + 76, + 39, + 0, + 8, + 1, + "VAE" + ], + [ + 91, + 40, + 0, + 3, + 3, + "LATENT" + ], + [ + 95, + 48, + 0, + 3, + 0, + "MODEL" + ], + [ + 107, + 51, + 0, + 52, + 1, + "VAE" + ], + [ + 108, + 3, + 0, + 52, + 0, + "LATENT" + ], + [ + 117, + 38, + 0, + 7, + 0, + "CLIP" + ], + [ + 118, + 38, + 0, + 6, + 0, + "CLIP" + ], + [ + 142, + 37, + 0, + 59, + 0, + "MODEL" + ], + [ + 143, + 59, + 0, + 65, + 0, + "MODEL" + ], + [ + 144, + 65, + 0, + 48, + 0, + "MODEL" + ], + [ + 158, + 75, + 0, + 74, + 1, + "MASK" + ], + [ + 159, + 76, + 0, + 75, + 0, + "MASK" + ], + [ + 162, + 74, + 0, + 79, + 0, + "IMAGE" + ], + [ + 163, + 52, + 0, + 76, + 0, + "IMAGE" + ], + [ + 164, + 8, + 0, + 74, + 0, + "IMAGE" + ] + ], + "groups": [], + "config": {}, + "extra": { + "ds": { + "scale": 0.5949345628810089, + "offset": [ + 657.1153588041857, + 464.13758186017674 + ] + }, + "frontendVersion": "1.28.6", + "VHS_latentpreview": false, + "VHS_latentpreviewrate": 0, + "VHS_MetadataImage": true, + "VHS_KeepIntermediate": true + }, + "version": 0.4 +} \ No newline at end of file