Author SHA1 Message Date
Tung Nguyen 91a286cdf6 add new example & fix ImageSizeAndBatchSize node 2023-09-18 18:18:00 +07:00
Tung Nguyen 07f8b8d2a9 fix typos 2023-09-18 17:53:25 +07:00
Tung Nguyen d849f6c7d6 add video upload node and improve video preview 2023-09-18 17:49:35 +07:00
Tung Nguyen 12ea0093e3 add more example workflows 2023-09-18 17:48:23 +07:00
ArtVenture 4e881671aa Merge pull request #27 from AustinMroz/upstream_video_format
ffmpeg improvements: webm quality, and additional video formats
2023-09-18 14:31:52 +07:00
Austin Mroz 414c5d3bb8 Add additional video formats and config system
This ports the video format code written for the upstream changes to the
ffmpeg implementation. It improves the quality of webm outputs and adds
support for additional codecs (h264, h265, av1)

It also improves the logging by passing errors and more selectively
blocking the logging of encoders.

While h265 has been included, most browsers will be unable to display the
resulting video.
2023-09-17 19:45:35 -05:00
ArtVenture 78e04fcdc6 Merge pull request #25 from ArtVentureX/feat/gif_preview
Improve GIF preview and support video output
2023-09-17 11:43:51 +07:00
Tung Nguyen 60d14a9840 update README 2023-09-17 11:41:55 +07:00
Tung Nguyen 427cf04893 improve gif preview 2023-09-17 11:08:55 +07:00
Tung Nguyen 87815b7aae add gif preview & support pingping gif 2023-09-16 17:46:24 +07:00
Tung Nguyen 9ae375fbd8 fix: cannot change frame_number 2023-09-16 17:16:16 +07:00
ArtVenture d4f5328a47 Merge pull request #23 from ArtVentureX/code-refactor
code refactor
2023-09-16 06:28:31 +07:00
16 changed files with 4212 additions and 79 deletions
+107 -45
View File
@@ -5,33 +5,120 @@
## How to Use
1. Clone this repo into `custom_nodes` folder.
2. Download motion modules from [Google Drive](https://drive.google.com/drive/folders/1EqLC65eR1-W-sGD0Im7fkED6c8GkiNFI) | [HuggingFace](https://huggingface.co/guoyww/animatediff) | [CivitAI](https://civitai.com/models/108836) | [Baidu NetDisk](https://pan.baidu.com/s/18ZpcSM6poBqxWNHtnyMcxg?pwd=et8y). You only need to download one of `mm_sd_v14.ckpt` | `mm_sd_v15.ckpt`. Put the model weights under `comfyui-animatediff/models/`. DO NOT change model filename.
2. Download motion modules and put them under `comfyui-animatediff/models/`.
#### Update 2023/09/15
- Original modules: [Google Drive](https://drive.google.com/drive/folders/1EqLC65eR1-W-sGD0Im7fkED6c8GkiNFI) | [HuggingFace](https://huggingface.co/guoyww/animatediff) | [CivitAI](https://civitai.com/models/108836) | [Baidu NetDisk](https://pan.baidu.com/s/18ZpcSM6poBqxWNHtnyMcxg?pwd=et8y)
- Community modules: [manshoety/AD_Stabilized_Motion](https://huggingface.co/manshoety/AD_Stabilized_Motion) | [CiaraRowles/TemporalDiff](https://huggingface.co/CiaraRowles/TemporalDiff)
- AnimateDiff v2 [mm_sd_v15_v2.ckpt](https://huggingface.co/guoyww/animatediff/blob/main/mm_sd_v15_v2.ckpt)
- You can now use community models from [manshoety/AD_Stabilized_Motion](https://huggingface.co/manshoety/AD_Stabilized_Motion) or [CiaraRowles/TemporalDiff](https://huggingface.co/CiaraRowles/TemporalDiff)
- Supports AnimateDiff v2 [mm_sd_v15_v2.ckpt](https://huggingface.co/guoyww/animatediff/blob/main/mm_sd_v15_v2.ckpt) model
- Fix image is grayed out.
- New node: **AnimateDiffSampler** and **AnimateDiffLoader**
- Mostly the same with `KSampler`
- Use `AnimateDiffLoader` to load the motion module
- `inject_method`: should left default. See [this issue](https://github.com/ArtVentureX/comfyui-animatediff#gif-has-wartermark-after-update-to-the-latest-version) for more details.
- `frame_number`: animation length
## Nodes
<img width="506" alt="image" src="https://github.com/ArtVentureX/comfyui-animatediff/assets/133728487/f22d6b36-ce36-44cc-80e8-dffe6f77b296">
#### AnimateDiffLoader
#### Example Workflow
<img width="370" alt="image" src="https://github.com/ArtVentureX/comfyui-animatediff/assets/133728487/9d756d01-ea45-4d1c-8e48-56f2725c7ca1">
<img width="1311" alt="image" src="https://github.com/ArtVentureX/comfyui-animatediff/assets/133728487/b7164539-bc58-4ef9-b178-d914e833805e">
#### AnimateDiffSampler
- Mostly the same with `KSampler`
- Use `AnimateDiffLoader` to load the motion module
- `inject_method`: should left default
- `frame_number`: animation length
- `latent_image`: You can pass an `EmptyLatentImage`
Workflow file: https://github.com/ArtVentureX/comfyui-animatediff/blob/main/workflow.json
<img width="370" alt="image" src="https://github.com/ArtVentureX/comfyui-animatediff/assets/133728487/f22d6b36-ce36-44cc-80e8-dffe6f77b296">
## Samples
#### AnimateDiffCombine
![23b44c29-29e8-4f48-ab3c-4df87c90c13f](https://github.com/ArtVentureX/comfyui-animatediff/assets/133728487/97efb96f-3d3d-4976-8789-78b88f89b2eb)
- Combine GIF frames and produce the GIF image
- `frame_rate`: number of frame per second
- `loop_count`: use 0 for infinite loop
- `save_image`: should GIF be saved to disk
- `format`: supports `image/gif`, `image/webp` (better compression) or `video/webm` (need `ffmpeg` installed and available in PATH)
![25f6c60c-f8ac-4abe-984f-1559c355d7f6](https://github.com/ArtVentureX/comfyui-animatediff/assets/133728487/c39b26f7-a2af-4dc4-902f-c363e2e6f39a)
<img width="370" alt="image" src="https://github.com/ArtVentureX/comfyui-animatediff/assets/133728487/381c5acc-06ef-43da-ada0-3dc76f37a3e4">
## Workflows
### Simple txt2gif
<img width="1280" alt="image" src="https://github.com/ArtVentureX/comfyui-animatediff/assets/133728487/b7164539-bc58-4ef9-b178-d914e833805e">
Workflow: [simple.json](https://github.com/ArtVentureX/comfyui-animatediff/blob/main/workflows/simple.json)
Samples:
![animate_diff_01](https://github.com/ArtVentureX/comfyui-animatediff/assets/133728487/97efb96f-3d3d-4976-8789-78b88f89b2eb)
![animate_diff_02](https://github.com/ArtVentureX/comfyui-animatediff/assets/133728487/c39b26f7-a2af-4dc4-902f-c363e2e6f39a)
### Latent upscale
Upscale latent output using `LatentUpscale` then do a 2nd pass with `AnimateDiffSampler`.
<img width="1280" alt="image" src="https://github.com/ArtVentureX/comfyui-animatediff/assets/133728487/987a1c5a-c1f8-4b24-8c62-f14496261d6c">
Workflow: [latent-upscale.json](https://github.com/ArtVentureX/comfyui-animatediff/blob/main/workflows/latent-upscale.json)
Samples:
![animate_diff_upscale](https://github.com/ArtVentureX/comfyui-animatediff/assets/133728487/f363f6f8-3117-4fa8-bca9-62f6a6e38ce7)
### Using with ControlNet
You will need following additional nodes:
- [Kosinkadink/ComfyUI-Advanced-ControlNet](https://github.com/Kosinkadink/ComfyUI-Advanced-ControlNet): Apply different weight for each latent in batch
- [Fannovel16/comfyui_controlnet_aux](https://github.com/Fannovel16/comfyui_controlnet_aux): ControlNet preprocessors
#### Animate with starting and ending images
- Use `LatentKeyframe` and `TimestampKeyframe` from [ComfyUI-Advanced-ControlNet](https://github.com/Kosinkadink/ComfyUI-Advanced-ControlNet) to apply diffrent weights for each latent index.
- Use 2 controlnet modules for two images with weights reverted.
![image](https://github.com/ArtVentureX/comfyui-animatediff/assets/133728487/bcca1070-e4a1-4698-a2af-aadf9723d015)
Workflow: [cn-2images.json](https://github.com/ArtVentureX/comfyui-animatediff/blob/main/workflows/cn-2images.json)
Samples:
<table>
<tr>
<td>
<img src="https://github.com/ArtVentureX/comfyui-animatediff/assets/133728487/e73fc3cd-a590-40a9-8b33-11358b54f0cd">
</td>
<td>
<img src="https://github.com/ArtVentureX/comfyui-animatediff/assets/133728487/96c2ee92-d457-4862-94d3-d675b7fa2d1f">
</td>
</tr>
<tr>
<td>
<img src="https://github.com/ArtVentureX/comfyui-animatediff/assets/133728487/46338853-1ae0-433e-925c-2a41e0382e68">
</td>
<td>
<img src="https://github.com/ArtVentureX/comfyui-animatediff/assets/133728487/707e4ce3-3594-4ff5-9a5f-f9596eb2bcf4">
</td>
</tr>
</table>
#### Using GIF as ControlNet input
Using a GIF (or video, or a list of images) as ControlNet input.
![image](https://github.com/ArtVentureX/comfyui-animatediff/assets/133728487/cfeed634-e683-4797-b2fd-dbe0926a449e)
Workflow: [cn-vid2vid.json](https://github.com/ArtVentureX/comfyui-animatediff/blob/main/workflows/cn-vid2vid.json)
Samples:
<table>
<tr>
<td>
<img src="https://github.com/ArtVentureX/comfyui-animatediff/assets/133728487/bf926f52-da97-4fb4-b86a-8b26ef5fab04">
</td>
<td>
<img src="https://github.com/ArtVentureX/comfyui-animatediff/assets/133728487/f6472c8c-9b92-47c2-8f28-638726f21be7">
</td>
</tr>
</table>
## Known Issues
@@ -39,39 +126,14 @@ Workflow file: https://github.com/ArtVentureX/comfyui-animatediff/blob/main/work
![AnimateDiff_00007_](https://github.com/ArtVentureX/comfyui-animatediff/assets/8894763/e6cd53cb-9878-45da-a58a-a15851882386)
See: https://github.com/continue-revolution/sd-webui-animatediff/issues/38
Main reasons:
- Promt are too long (more than 75 tokens)
- Resolution are too high
- Number of frame too high
Work around:
- Shorter your prompt and negative prompt
- Reduce resolution. AnimateDiff is trained on 512x512 images so it works best with 512x512 output.
- Shouldn't generate longer than 16 frames. AnimateDiff is trained to output the best results with 16 frames.
- Disable xformers with `--disable-xformers`
### GIF has Wartermark after update to the latest version
### GIF has Wartermark (especially when using mm_sd_v15)
See: https://github.com/continue-revolution/sd-webui-animatediff/issues/31
As mentioned in the issue thread, it seems to be due to the training dataset. The new version is the correct implementation and produces smoother GIFs compared to the older version.
<table class="center">
<tr>
<td>Old revision</td>
<td>New revision</td>
</tr>
<tr>
<td><img src="https://github.com/ArtVentureX/comfyui-animatediff/assets/133728487/8f1a6233-875f-4f0c-aa60-ba93e73b7d64" /></td>
<td><img src="https://github.com/ArtVentureX/comfyui-animatediff/assets/133728487/a2029eba-f519-437c-a0b5-1f881e099a20" /></td>
</tr>
<tr>
<td><img src="https://github.com/ArtVentureX/comfyui-animatediff/assets/133728487/41ec449f-1955-466c-bd38-6f2a55d654f8" /></td>
<td><img src="https://github.com/ArtVentureX/comfyui-animatediff/assets/133728487/766c2891-5d27-4052-99f9-be9862620919" /></td>
</tr>
</table>
I played around with both version and found that the watermark only present in some models, not always. To use the **old (legacy)** method, change `injection_method` to `legacy` in the `AnimateDiffSampler` node.
Training data used by the authors of the AnimateDiff paper contained Shutterstock watermarks. Since mm_sd_v15 was finetuned on finer, less drastic movement, the motion module attempts to replicate the transparency of that watermark and does not get blurred away like mm_sd_v14. Try other community finetuned modules.
+3 -1
View File
@@ -5,4 +5,6 @@ from .animatediff.model_utils import get_available_models
if len(get_available_models()) == 0:
logger.error("No models available. Please download one and put it in models folder")
__all__ = ["NODE_CLASS_MAPPINGS", "NODE_DISPLAY_NAME_MAPPINGS"]
WEB_DIRECTORY = "./web"
__all__ = ["NODE_CLASS_MAPPINGS", "NODE_DISPLAY_NAME_MAPPINGS", "WEB_DIRECTORY"]
+6
View File
@@ -11,6 +11,12 @@ folder_paths.folder_names_and_paths["AnimateDiff"] = (
],
folder_paths.supported_pt_extensions,
)
folder_paths.folder_names_and_paths["video_formats"] = (
[
os.path.join(os.path.dirname(os.path.abspath(__file__)), "..", "video_formats"),
],
[".json"]
)
def get_available_models():
+2 -5
View File
@@ -5,7 +5,6 @@ from torch import Tensor, nn
import math
from einops import rearrange, repeat
from comfy.utils import load_torch_file
from comfy.ldm.modules.attention import FeedForward, CrossAttention
@@ -57,14 +56,12 @@ class MotionWrapper(nn.Module):
)
@classmethod
def from_pretrained(cls, checkpoint_path: str):
mm_state_dict = load_torch_file(checkpoint_path)
mm_type = os.path.basename(checkpoint_path)
def from_pretrained(cls, mm_state_dict: dict[str, Tensor], mm_type: str):
encoding_max_len = get_encoding_max_len(mm_state_dict)
is_v2 = has_mid_block(mm_state_dict)
mm = cls(mm_type, encoding_max_len=encoding_max_len, is_v2=is_v2)
mm.load_state_dict(mm_state_dict)
mm.load_state_dict(mm_state_dict, strict=False)
return mm
def set_video_length(self, video_length: int):
+218 -28
View File
@@ -2,10 +2,11 @@ import os
import json
import torch
import numpy as np
import hashlib
from typing import Dict, List
from torch import Tensor
from torch.nn.functional import group_norm
from PIL import Image
from PIL import Image, ImageSequence
from PIL.PngImagePlugin import PngInfo
from einops import rearrange
@@ -14,12 +15,13 @@ import comfy.ldm.modules.diffusionmodules.openaimodel as openaimodel
import comfy.model_management as model_management
from comfy.model_base import BaseModel
from comfy.ldm.modules.attention import SpatialTransformer
from comfy.cli_args import args as cli_args
from comfy.utils import load_torch_file, calculate_parameters
from nodes import KSampler
from .logger import logger
from .motion_module import MotionWrapper, VanillaTemporalModule
from .model_utils import get_available_models, get_model_path, get_model_hash
from .utils import pil2tensor
def forward_timestep_embed(
@@ -47,7 +49,8 @@ def groupnorm_mm_factory(video_length: int):
axes_factor = input.size(0) // video_length
input = rearrange(input, "(b f) c h w -> b c f h w", b=axes_factor)
input = group_norm(input, self.num_groups, self.weight, self.bias, self.eps)
input = group_norm(input, self.num_groups,
self.weight, self.bias, self.eps)
input = rearrange(input, "b c f h w -> (b f) c h w", b=axes_factor)
return input
@@ -67,10 +70,16 @@ def load_motion_module(model_name: str):
model_hash = get_model_hash(model_path)
if model_hash not in motion_modules:
logger.info(f"Loading motion module {model_name}")
motion_module = MotionWrapper.from_pretrained(model_path)
if not cli_args.force_fp32:
mm_state_dict = load_torch_file(model_path)
motion_module = MotionWrapper.from_pretrained(
mm_state_dict, model_name)
params = calculate_parameters(mm_state_dict, "")
if model_management.should_use_fp16(model_params=params):
logger.info(f"Converting motion module to fp16.")
motion_module.half()
offload_device = model_management.unet_offload_device()
motion_module = motion_module.to(offload_device)
motion_modules[model_hash] = motion_module
@@ -215,7 +224,7 @@ class AnimateDiffSampler(KSampler):
def override_beta_schedule(self, model: BaseModel):
logger.info(f"Override beta schedule.")
self.prev_beta = model.get_buffer("betas")
self.prev_beta = model.get_buffer("betas").cpu().clone()
self.prev_linear_start = model.linear_start
self.prev_linear_end = model.linear_end
model.register_schedule(
@@ -245,6 +254,7 @@ class AnimateDiffSampler(KSampler):
unet = model.model.diffusion_model
logger.info(f"Injecting motion module with method {inject_method}.")
motion_module.set_video_length(frame_number)
injectors[inject_method](unet, motion_module)
self.override_beta_schedule(model.model)
if not motion_module.is_v2:
@@ -258,7 +268,7 @@ class AnimateDiffSampler(KSampler):
self.restore_beta_schedule(model.model)
if not unet.motion_module.is_v2:
logger.info(f"Restore GroupNorm32 forward function.")
logger.info(f"Restore GroupNorm.forward function.")
torch.nn.GroupNorm.forward = orig_groupnorm_forward
logger.info(f"Ejecting motion module with method {inject_method}.")
@@ -326,8 +336,11 @@ class AnimateDiffCombine:
{"default": 8, "min": 1, "max": 24, "step": 1},
),
"loop_count": ("INT", {"default": 0, "min": 0, "max": 100, "step": 1}),
"save_image": (["Enabled", "Disabled"],),
"filename_prefix": ("STRING", {"default": "AnimateDiff"}),
"save_image": ([True, False],),
"filename_prefix": ("STRING", {"default": "animate_diff"}),
"format": (["image/gif", "image/webp"] +
["video/"+x[:-5] for x in folder_paths.get_filename_list("video_formats")],),
"pingpong": ([False, True],),
},
"hidden": {
"prompt": "PROMPT",
@@ -345,22 +358,24 @@ class AnimateDiffCombine:
images,
frame_rate: int,
loop_count: int,
save_image="Enabled",
save_image=True,
filename_prefix="AnimateDiff",
format="image/gif",
pingpong=False,
prompt=None,
extra_pnginfo=None,
):
# convert images to numpy
pil_images: List[Image.Image] = []
frames: List[Image.Image] = []
for image in images:
img = 255.0 * image.cpu().numpy()
img = Image.fromarray(np.clip(img, 0, 255).astype(np.uint8))
pil_images.append(img)
frames.append(img)
# save image
output_dir = (
folder_paths.get_output_directory()
if save_image == "Enabled"
if save_image
else folder_paths.get_temp_directory()
)
(
@@ -381,43 +396,218 @@ class AnimateDiffCombine:
# save first frame as png to keep metadata
file = f"{filename}_{counter:05}_.png"
file_path = os.path.join(full_output_folder, file)
pil_images[0].save(
frames[0].save(
file_path,
pnginfo=metadata,
compress_level=4,
)
if pingpong:
frames = frames + frames[-2:0:-1]
# save gif
file = f"{filename}_{counter:05}_.gif"
file_path = os.path.join(full_output_folder, file)
pil_images[0].save(
file_path,
save_all=True,
append_images=pil_images[1:],
duration=round(1000 / frame_rate),
loop=loop_count,
compress_level=4,
)
format_type, format_ext = format.split("/")
print("Saved gif to", file_path, os.path.exists(file_path))
if format_type == "image":
file = f"{filename}_{counter:05}_.{format_ext}"
file_path = os.path.join(full_output_folder, file)
frames[0].save(
file_path,
format=format_ext.upper(),
save_all=True,
append_images=frames[1:],
duration=round(1000 / frame_rate),
loop=loop_count,
compress_level=4,
)
else:
# save webm
import shutil
import subprocess
ffmpeg_path = shutil.which("ffmpeg")
if ffmpeg_path is None:
raise ProcessLookupError("Could not find ffmpeg")
video_format_path = folder_paths.get_full_path(
"video_formats", format_ext + ".json")
with open(video_format_path, 'r') as stream:
video_format = json.load(stream)
file = f"{filename}_{counter:05}_.{video_format['extension']}"
file_path = os.path.join(full_output_folder, file)
dimensions = f"{frames[0].width}x{frames[0].height}"
args = [ffmpeg_path, "-v", "error", "-f", "rawvideo", "-pix_fmt", "rgb24",
"-s", dimensions, "-r", str(frame_rate), "-i", "-"] \
+ video_format['main_pass'] + [file_path]
env = os.environ
if "environment" in video_format:
env.update(video_format["environment"])
with subprocess.Popen(args, stdin=subprocess.PIPE, env=env) as proc:
for frame in frames:
proc.stdin.write(frame.tobytes())
previews = [
{
"filename": file,
"subfolder": subfolder,
"type": "output" if save_image == "Enabled" else "temp",
"type": "output" if save_image else "temp",
"format": format,
}
]
return {"ui": {"images": previews}}
return {"ui": {"videos": previews}}
class LoadVideo:
@classmethod
def INPUT_TYPES(s):
input_dir = os.path.join(folder_paths.get_input_directory(), "video")
if not os.path.exists(input_dir):
os.makedirs(input_dir, exist_ok=True)
files = [f"video/{f}" for f in os.listdir(input_dir) if os.path.isfile(
os.path.join(input_dir, f))]
return {
"required": {
"video": (sorted(files), {"video_upload": True}),
},
"optional": {
"frame_start": ("INT", {"default": 0, "min": 0, "max": 0xffffffff, "step": 1}),
"frame_limit": ("INT", {"default": 16, "min": 1, "max": 10240, "step": 1}),
}
}
CATEGORY = "Animate Diff/Utils"
RETURN_TYPES = ("IMAGE", "INT")
RETURN_NAMES = ("frames", "frame_count")
FUNCTION = "load"
def load_gif(self, gif_path: str, frame_start: int, frame_limit: int):
image = Image.open(gif_path)
frames = []
for i, frame in enumerate(ImageSequence.Iterator(image)):
if i < frame_start:
continue
elif i >= frame_start + frame_limit:
break
else:
frames.append(pil2tensor(frame.copy().convert("RGB")))
return frames
def load_video(self, video_path, frame_start: int, frame_limit: int):
import cv2
video = cv2.VideoCapture(video_path)
video.set(cv2.CAP_PROP_POS_FRAMES, frame_start)
frames = []
for i in range(frame_limit):
# Read the next frame
ret, frame = video.read()
if ret:
# Convert the frame to RGB (OpenCV uses BGR)
frame = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
# Convert the NumPy array to a PIL image and append to list
frames.append(pil2tensor(Image.fromarray(frame)))
else:
break
video.release()
return frames
def load(self, video: str, frame_start=0, frame_limit=16):
print("path", video)
video_path = folder_paths.get_annotated_filepath(video)
(_, ext) = os.path.splitext(video_path)
if ext.lower() in {".gif", ".webp"}:
frames = self.load_gif(video_path, frame_start, frame_limit)
elif ext.lower() in {".webp", ".mp4", ".mov", ".avi"}:
frames = self.load_video(video_path, frame_start, frame_limit)
else:
raise ValueError(f"Unsupported video format: {ext}")
return (torch.cat(frames, dim=0),)
@classmethod
def IS_CHANGED(s, image, *args, **kwargs):
image_path = folder_paths.get_annotated_filepath(image)
m = hashlib.sha256()
with open(image_path, 'rb') as f:
m.update(f.read())
return m.digest().hex()
@classmethod
def VALIDATE_INPUTS(s, video, *args, **kwargs):
if not folder_paths.exists_annotated_filepath(video):
return "Invalid video file: {}".format(video)
return True
class ImageSizeAndBatchSize:
@classmethod
def INPUT_TYPES(s):
return {
"required": {
"image": ("IMAGE",),
},
}
CATEGORY = "Animate Diff/Utils"
RETURN_TYPES = ("INT", "INT", "INT")
RETURN_NAMES = ("width", "height", "batch_size")
FUNCTION = "batch_size"
def batch_size(self, image: Tensor):
(batch_size, height, width) = image.shape[0:3]
return (width, height, batch_size)
class ImageChunking:
@classmethod
def INPUT_TYPES(s):
return {
"required": {
"images": ("IMAGE",),
"chunk_size": ("INT", {"default": 16, "min": 1, "max": 1024, "step": 1}),
"allow_remainder": ([True, False],),
},
}
CATEGORY = "Animate Diff/Utils"
RETURN_TYPES = ("IMAGE",)
OUTPUT_IS_LIST = (True,)
FUNCTION = "chunk"
def chunk(self, images: Tensor, chunk_size: int, allow_remainder: bool):
# Check if tensor is divisible into chunks of chunk_size
if images.shape[0] % chunk_size != 0 and not allow_remainder:
raise ValueError(
"Tensor's first dimension is not divisible by chunk size")
# Use torch.chunk to divide the tensor
chunk_count = images.shape[0] // chunk_size + \
images.shape[0] % chunk_size
print("chunk_count", chunk_count)
chunks = torch.chunk(images, chunk_count, dim=0)
return (list(chunks), )
NODE_CLASS_MAPPINGS = {
"AnimateDiffModuleLoader": AnimateDiffModuleLoader,
"AnimateDiffCombine": AnimateDiffCombine,
"AnimateDiffSampler": AnimateDiffSampler,
"LoadVideo": LoadVideo,
"ImageSizeAndBatchSize": ImageSizeAndBatchSize,
}
NODE_DISPLAY_NAME_MAPPINGS = {
"AnimateDiffModuleLoader": "Animate Diff Module Loader",
"AnimateDiffSampler": "Animate Diff Sampler",
"AnimateDiffCombine": "Animate Diff Combine",
"LoadVideo": "Load Video",
"ImageSizeAndBatchSize": "Get Image Size + Batch Size",
}
+13
View File
@@ -0,0 +1,13 @@
import torch
import numpy as np
from PIL import Image
# Tensor to PIL
def tensor2pil(image):
return Image.fromarray(
np.clip(255.0 * image.cpu().numpy().squeeze(), 0, 255).astype(np.uint8)
)
# Convert PIL to Tensor
def pil2tensor(image):
return torch.from_numpy(np.array(image).astype(np.float32) / 255.0).unsqueeze(0)
+10
View File
@@ -0,0 +1,10 @@
{
"main_pass":
[
"-n", "-c:v", "libsvtav1",
"-pix_fmt", "yuv420p10le",
"-crf", "23"
],
"extension": "webm",
"environment": {"SVT_LOG": "1"}
}
+9
View File
@@ -0,0 +1,9 @@
{
"main_pass":
[
"-n", "-c:v", "libx264",
"-pix_fmt", "yuv420p",
"-crf", "19"
],
"extension": "mp4"
}
+11
View File
@@ -0,0 +1,11 @@
{
"main_pass":
[
"-n", "-c:v", "libx265",
"-pix_fmt", "yuv420p10le",
"-preset", "medium",
"-crf", "22",
"-x265-params", "log-level=quiet"
],
"extension": "mp4"
}
+9
View File
@@ -0,0 +1,9 @@
{
"main_pass":
[
"-n",
"-pix_fmt", "yuv420p",
"-crf", "23"
],
"extension": "webm"
}
+162
View File
@@ -0,0 +1,162 @@
import { app } from "../../../scripts/app.js";
import { api } from "../../../scripts/api.js";
function offsetDOMWidget(widget, ctx, node, widgetWidth, widgetY, height) {
const margin = 10;
const elRect = ctx.canvas.getBoundingClientRect();
const transform = new DOMMatrix()
.scaleSelf(
elRect.width / ctx.canvas.width,
elRect.height / ctx.canvas.height
)
.multiplySelf(ctx.getTransform())
.translateSelf(0, widgetY + margin);
const scale = new DOMMatrix().scaleSelf(transform.a, transform.d);
Object.assign(widget.inputEl.style, {
transformOrigin: "0 0",
transform: scale,
left: `${transform.e}px`,
top: `${transform.d + transform.f}px`,
width: `${widgetWidth}px`,
height: `${(height || widget.parent?.inputHeight || 32) - margin}px`,
position: "absolute",
background: !node.color ? "" : node.color,
color: !node.color ? "" : "white",
zIndex: 5, //app.graph._nodes.indexOf(node),
});
}
export const hasWidgets = (node) => {
if (!node.widgets || !node.widgets?.[Symbol.iterator]) {
return false;
}
return true;
};
export const cleanupNode = (node) => {
if (!hasWidgets(node)) {
return;
}
for (const w of node.widgets) {
if (w.canvas) {
w.canvas.remove();
}
if (w.inputEl) {
w.inputEl.remove();
}
// calls the widget remove callback
w.onRemoved?.();
}
};
export const CreatePreviewElement = (name, val, format, callback) => {
const [type] = format.split("/");
const w = {
name,
type,
value: val,
draw: function (ctx, node, widgetWidth, widgetY, height) {
const [cw, ch] = this.computeSize(widgetWidth);
offsetDOMWidget(this, ctx, node, widgetWidth, widgetY, ch);
},
computeSize: function (_) {
const ratio = this.inputRatio || 1;
const width = Math.max(220, this.parent.size[0]);
return [width, width / ratio + 10];
},
onRemoved: function () {
if (this.inputEl) {
this.inputEl.remove();
}
},
};
w.inputEl = document.createElement(type === "video" ? "video" : "img");
w.inputEl.src = w.value;
if (type === "video") {
w.inputEl.setAttribute("type", "video/webm");
w.inputEl.autoplay = true;
w.inputEl.loop = true;
w.inputEl.controls = false;
}
w.inputEl.onload = function () {
w.inputRatio = w.inputEl.naturalWidth / w.inputEl.naturalHeight;
callback?.();
};
document.body.appendChild(w.inputEl);
return w;
};
const videoPreview = {
name: "AnimateDiff.VideoPreview",
async beforeRegisterNodeDef(nodeType, nodeData, app) {
const onExecuted = nodeType.prototype.onExecuted;
nodeType.prototype.onExecuted = function (message) {
const r = onExecuted ? onExecuted.apply(this, message) : undefined;
if (message?.videos) {
this.videos = message.videos;
}
return r;
};
const onDrawBackground = nodeType.prototype.onDrawBackground;
nodeType.prototype.onDrawBackground = function (ctx) {
const r = onDrawBackground ? onDrawBackground.apply(this, arguments) : undefined;
const node = this;
const prefix = "ad_video_preview_";
if (node.videos_rendered === node.videos) {
return r;
}
if (node.widgets) {
const pos = node.widgets.findIndex((w) => w.name === `${prefix}_0`);
if (pos !== -1) {
for (let i = pos; i < node.widgets.length; i++) {
node.widgets[i].onRemoved?.();
}
node.widgets.length = pos;
}
}
if (node.videos) {
node.videos.forEach((params, i) => {
const previewUrl = api.apiURL(
"/view?" + new URLSearchParams(params).toString()
);
const w = node.addCustomWidget(
CreatePreviewElement(
`${prefix}_${i}`,
previewUrl,
params.format || "image/gif",
node.computeSizeKeepWidth.bind(node)
)
);
w.parent = node;
});
node.videos_rendered = node.videos;
}
return r;
};
const onRemoved = nodeType.prototype.onRemoved;
nodeType.prototype.onRemoved = function () {
cleanupNode(this);
return onRemoved ? onRemoved.apply(this, arguments) : undefined;
};
nodeType.prototype.computeSizeKeepWidth = function () {
this.setSize([
this.size[0],
this.computeSize([this.size[0], this.size[1]])[1],
]);
};
},
};
app.registerExtension(videoPreview);
+188
View File
@@ -0,0 +1,188 @@
import { app } from "../../../scripts/app.js";
import { api } from "../../../scripts/api.js";
import { ComfyWidgets } from "../../../scripts/widgets.js";
const supportedVideoTypes = [
"image/gif",
"video/webm",
"video/mp4",
"video/mov",
];
const VIDEOUPLOAD = (node, inputName, inputData, app) => {
const previewWidget = "ad_video_preview";
const videoWidget = node.widgets.find((w) => w.name === "video");
let uploadWidget;
const showVideo = (name) => {
let folder_separator = name.lastIndexOf("/");
let subfolder = "";
if (folder_separator > -1) {
subfolder = name.substring(0, folder_separator);
name = name.substring(folder_separator + 1);
}
const ext = name.substring(name.lastIndexOf(".") + 1);
const format = supportedVideoTypes.find((t) => t.endsWith(ext));
node.videos = [
{
filename: name,
type: "input",
subfolder: subfolder,
format,
},
];
};
var default_value = videoWidget.value;
Object.defineProperty(videoWidget, "value", {
set: function (value) {
this._real_value = value;
},
get: function () {
let value = "";
if (this._real_value) {
value = this._real_value;
} else {
return default_value;
}
if (value.filename) {
let real_value = value;
value = "";
if (real_value.subfolder) {
value = real_value.subfolder + "/";
}
value += real_value.filename;
if (real_value.type && real_value.type !== "input")
value += ` [${real_value.type}]`;
}
return value;
},
});
// Add our own callback to the combo widget to render an image when it changes
const cb = node.callback;
videoWidget.callback = function () {
showVideo(videoWidget.value);
if (cb) {
return cb.apply(this, arguments);
}
};
// On load if we have a value then render the image
// The value isnt set immediately so we need to wait a moment
// No change callbacks seem to be fired on initial setting of the value
requestAnimationFrame(() => {
if (videoWidget.value) {
showVideo(videoWidget.value);
}
});
async function uploadFile(file, updateNode, pasted = false) {
try {
// Wrap file in formdata so it includes filename
const body = new FormData();
body.append("image", file);
body.append("subfolder", "video");
const resp = await api.fetchApi("/upload/image", {
method: "POST",
body,
});
if (resp.status === 200) {
const data = await resp.json();
// Add the file to the dropdown list and update the widget value
let path = data.name;
if (data.subfolder) path = data.subfolder + "/" + path;
if (!videoWidget.options.values.includes(path)) {
videoWidget.options.values.push(path);
}
if (updateNode) {
showVideo(path);
videoWidget.value = path;
}
} else {
alert(resp.status + " - " + resp.statusText);
}
} catch (error) {
alert(error);
}
}
const fileInput = document.createElement("input");
Object.assign(fileInput, {
type: "file",
accept: supportedVideoTypes.join(","),
style: "display: none",
onchange: async () => {
if (fileInput.files.length) {
await uploadFile(fileInput.files[0], true);
}
},
});
document.body.append(fileInput);
// Create the button widget for selecting the files
uploadWidget = node.addWidget(
"button",
"choose file to upload",
"image",
() => {
fileInput.click();
}
);
uploadWidget.serialize = false;
// Add handler to check if an image is being dragged over our node
node.onDragOver = function (e) {
if (e.dataTransfer && e.dataTransfer.items) {
const image = [...e.dataTransfer.items].find((f) => f.kind === "file");
return !!image;
}
return false;
};
// On drop upload files
node.onDragDrop = function (e) {
console.log("onDragDrop called");
let handled = false;
for (const file of e.dataTransfer.files) {
if (file.type.startsWith("image/")) {
uploadFile(file, !handled); // Dont await these, any order is fine, only update on first one
handled = true;
}
}
return handled;
};
node.pasteFile = function (file) {
if (supportedVideoTypes.indexOf(file.type) > -1) {
const is_pasted =
file.name === "image.png" && file.lastModified - Date.now() < 2000;
uploadFile(file, true, is_pasted);
return true;
}
return false;
};
return { widget: uploadWidget };
};
ComfyWidgets["VIDEOUPLOAD"] = VIDEOUPLOAD;
// Adds an upload button to the nodes
app.registerExtension({
name: "AnimateDiff.UploadVideo",
async beforeRegisterNodeDef(nodeType, nodeData, app) {
if (nodeData?.input?.required?.video?.[1]?.video_upload === true) {
nodeData.input.required.upload = ["VIDEOUPLOAD"];
}
},
});
File diff suppressed because it is too large Load Diff
+877
View File
@@ -0,0 +1,877 @@
{
"last_node_id": 106,
"last_link_id": 189,
"nodes": [
{
"id": 16,
"type": "AnimateDiffModuleLoader",
"pos": [
-280,
140
],
"size": {
"0": 310,
"1": 60
},
"flags": {},
"order": 0,
"mode": 0,
"outputs": [
{
"name": "MOTION_MODULE",
"type": "MOTION_MODULE",
"links": [
78
],
"shape": 3,
"slot_index": 0
}
],
"properties": {
"Node name for S&R": "AnimateDiffModuleLoader"
},
"widgets_values": [
"mm-Stabilized_mid.pth"
],
"color": "#571a1a",
"bgcolor": "#6b2e2e"
},
{
"id": 13,
"type": "VAELoader",
"pos": [
-280,
400
],
"size": {
"0": 310,
"1": 60
},
"flags": {},
"order": 1,
"mode": 0,
"outputs": [
{
"name": "VAE",
"type": "VAE",
"links": [
82
],
"shape": 3,
"slot_index": 0
}
],
"properties": {
"Node name for S&R": "VAELoader"
},
"widgets_values": [
"vae-ft-mse-840000-ema-pruned.safetensors"
],
"color": "#571a1a",
"bgcolor": "#6b2e2e"
},
{
"id": 45,
"type": "AnimateDiffCombine",
"pos": [
1240,
140
],
"size": [
360,
732
],
"flags": {},
"order": 13,
"mode": 0,
"inputs": [
{
"name": "images",
"type": "IMAGE",
"link": 172
}
],
"outputs": [
{
"name": "GIF",
"type": "GIF",
"links": null,
"shape": 3
}
],
"properties": {
"Node name for S&R": "AnimateDiffCombine"
},
"widgets_values": [
8,
0,
true,
"AnimateDiff",
"image/gif",
true,
"/view?filename=AnimateDiff_00092_.gif&subfolder=&type=output&format=image%2Fgif"
]
},
{
"id": 4,
"type": "CheckpointLoaderSimple",
"pos": [
-280,
250
],
"size": {
"0": 310,
"1": 100
},
"flags": {},
"order": 2,
"mode": 0,
"outputs": [
{
"name": "MODEL",
"type": "MODEL",
"links": [
79
],
"slot_index": 0
},
{
"name": "CLIP",
"type": "CLIP",
"links": [
3,
5
],
"slot_index": 1
},
{
"name": "VAE",
"type": "VAE",
"links": [],
"slot_index": 2
}
],
"properties": {
"Node name for S&R": "CheckpointLoaderSimple"
},
"widgets_values": [
"SDHK_v4.safetensors"
],
"color": "#571a1a",
"bgcolor": "#6b2e2e"
},
{
"id": 7,
"type": "CLIPTextEncode",
"pos": [
60,
300
],
"size": [
310,
100
],
"flags": {},
"order": 6,
"mode": 0,
"inputs": [
{
"name": "clip",
"type": "CLIP",
"link": 5
}
],
"outputs": [
{
"name": "CONDITIONING",
"type": "CONDITIONING",
"links": [
70
],
"slot_index": 0
}
],
"properties": {
"Node name for S&R": "CLIPTextEncode"
},
"widgets_values": [
"embedding:easynegative, embedding:badhandv4, nsfw"
],
"color": "#572e1a",
"bgcolor": "#6b422e"
},
{
"id": 6,
"type": "CLIPTextEncode",
"pos": [
60,
140
],
"size": [
310,
110
],
"flags": {},
"order": 5,
"mode": 0,
"inputs": [
{
"name": "clip",
"type": "CLIP",
"link": 3
}
],
"outputs": [
{
"name": "CONDITIONING",
"type": "CONDITIONING",
"links": [
69
],
"slot_index": 0
}
],
"properties": {
"Node name for S&R": "CLIPTextEncode"
},
"widgets_values": [
"(best quality, masterpiece), 1girl, short hair, blue eyes, dancing, city, cloudy"
],
"color": "#572e1a",
"bgcolor": "#6b422e"
},
{
"id": 41,
"type": "AnimateDiffSampler",
"pos": [
900,
140
],
"size": [
310,
330
],
"flags": {},
"order": 11,
"mode": 0,
"inputs": [
{
"name": "motion_module",
"type": "MOTION_MODULE",
"link": 78,
"slot_index": 0
},
{
"name": "model",
"type": "MODEL",
"link": 79,
"slot_index": 1
},
{
"name": "positive",
"type": "CONDITIONING",
"link": 176
},
{
"name": "negative",
"type": "CONDITIONING",
"link": 180
},
{
"name": "latent_image",
"type": "LATENT",
"link": 80
},
{
"name": "frame_number",
"type": "INT",
"link": 185,
"widget": {
"name": "frame_number",
"config": [
"INT",
{
"default": 16,
"min": 2,
"max": 32,
"step": 1
}
]
}
}
],
"outputs": [
{
"name": "LATENT",
"type": "LATENT",
"links": [
81
],
"shape": 3,
"slot_index": 0
}
],
"properties": {
"Node name for S&R": "AnimateDiffSampler"
},
"widgets_values": [
"default",
16,
345029849956754,
"fixed",
20,
8,
"euler",
"normal",
1
],
"color": "#57571a",
"bgcolor": "#6b6b2e"
},
{
"id": 39,
"type": "ControlNetApplyAdvanced",
"pos": [
471,
275
],
"size": [
300,
170
],
"flags": {},
"order": 9,
"mode": 0,
"inputs": [
{
"name": "positive",
"type": "CONDITIONING",
"link": 69
},
{
"name": "negative",
"type": "CONDITIONING",
"link": 70
},
{
"name": "control_net",
"type": "CONTROL_NET",
"link": 68
},
{
"name": "image",
"type": "IMAGE",
"link": 181
}
],
"outputs": [
{
"name": "positive",
"type": "CONDITIONING",
"links": [
176
],
"shape": 3,
"slot_index": 0
},
{
"name": "negative",
"type": "CONDITIONING",
"links": [
180
],
"shape": 3,
"slot_index": 1
}
],
"properties": {
"Node name for S&R": "ControlNetApplyAdvanced"
},
"widgets_values": [
1,
0,
1
],
"color": "#43571a",
"bgcolor": "#576b2e"
},
{
"id": 44,
"type": "VAEDecode",
"pos": [
1000,
520
],
"size": {
"0": 210,
"1": 46
},
"flags": {},
"order": 12,
"mode": 0,
"inputs": [
{
"name": "samples",
"type": "LATENT",
"link": 81
},
{
"name": "vae",
"type": "VAE",
"link": 82
}
],
"outputs": [
{
"name": "IMAGE",
"type": "IMAGE",
"links": [
172,
187
],
"shape": 3,
"slot_index": 0
}
],
"properties": {
"Node name for S&R": "VAEDecode"
},
"color": "#2e571a",
"bgcolor": "#426b2e"
},
{
"id": 103,
"type": "LoadVideo",
"pos": [
-280,
650
],
"size": [
310,
629
],
"flags": {},
"order": 3,
"mode": 0,
"outputs": [
{
"name": "frames",
"type": "IMAGE",
"links": [
181,
182,
186
],
"shape": 3,
"slot_index": 0
},
{
"name": "frame_count",
"type": "INT",
"links": null,
"shape": 3
}
],
"properties": {
"Node name for S&R": "LoadVideo"
},
"widgets_values": [
"video/265043418-23291941-864d-495a-8ba8-d02e05756396.gif",
"image",
0,
16,
"/view?filename=265043418-23291941-864d-495a-8ba8-d02e05756396.gif&type=input&subfolder=video&format=image%2Fgif"
]
},
{
"id": 20,
"type": "EmptyLatentImage",
"pos": [
520,
630
],
"size": [
210,
80
],
"flags": {},
"order": 10,
"mode": 0,
"inputs": [
{
"name": "width",
"type": "INT",
"link": 189,
"widget": {
"name": "width",
"config": [
"INT",
{
"default": 512,
"min": 64,
"max": 8192,
"step": 8
}
]
}
},
{
"name": "height",
"type": "INT",
"link": 188,
"widget": {
"name": "height",
"config": [
"INT",
{
"default": 512,
"min": 64,
"max": 8192,
"step": 8
}
]
}
}
],
"outputs": [
{
"name": "LATENT",
"type": "LATENT",
"links": [
80
],
"shape": 3,
"slot_index": 0
}
],
"properties": {
"Node name for S&R": "EmptyLatentImage"
},
"widgets_values": [
512,
512,
1
],
"color": "#1a572e",
"bgcolor": "#2e6b42"
},
{
"id": 104,
"type": "ImageSizeAndBatchSize",
"pos": [
300,
630
],
"size": [
190,
80
],
"flags": {},
"order": 7,
"mode": 0,
"inputs": [
{
"name": "image",
"type": "IMAGE",
"link": 182
}
],
"outputs": [
{
"name": "width",
"type": "INT",
"links": [
188
],
"shape": 3,
"slot_index": 0
},
{
"name": "height",
"type": "INT",
"links": [
189
],
"shape": 3,
"slot_index": 1
},
{
"name": "batch_size",
"type": "INT",
"links": [
185
],
"shape": 3,
"slot_index": 2
}
],
"properties": {
"Node name for S&R": "ImageSizeAndBatchSize"
},
"color": "#1a5757",
"bgcolor": "#2e6b6b"
},
{
"id": 36,
"type": "ControlNetLoaderAdvanced",
"pos": [
-280,
540
],
"size": [
310,
60
],
"flags": {},
"order": 4,
"mode": 0,
"inputs": [
{
"name": "timestep_keyframe",
"type": "TIMESTEP_KEYFRAME",
"link": null,
"slot_index": 0
}
],
"outputs": [
{
"name": "CONTROL_NET",
"type": "CONTROL_NET",
"links": [
68
],
"shape": 3,
"slot_index": 0
}
],
"properties": {
"Node name for S&R": "ControlNetLoaderAdvanced"
},
"widgets_values": [
"control_v11p_sd15_openpose.pth"
],
"color": "#571a1a",
"bgcolor": "#6b2e2e"
},
{
"id": 105,
"type": "PreviewImage",
"pos": [
70,
830
],
"size": [
530,
420
],
"flags": {},
"order": 8,
"mode": 0,
"inputs": [
{
"name": "images",
"type": "IMAGE",
"link": 186
}
],
"properties": {
"Node name for S&R": "PreviewImage"
},
"color": "#1a5757",
"bgcolor": "#2e6b6b"
},
{
"id": 106,
"type": "PreviewImage",
"pos": [
670,
830
],
"size": [
530,
420
],
"flags": {},
"order": 14,
"mode": 0,
"inputs": [
{
"name": "images",
"type": "IMAGE",
"link": 187
}
],
"properties": {
"Node name for S&R": "PreviewImage"
},
"color": "#1a5757",
"bgcolor": "#2e6b6b"
}
],
"links": [
[
3,
4,
1,
6,
0,
"CLIP"
],
[
5,
4,
1,
7,
0,
"CLIP"
],
[
68,
36,
0,
39,
2,
"CONTROL_NET"
],
[
69,
6,
0,
39,
0,
"CONDITIONING"
],
[
70,
7,
0,
39,
1,
"CONDITIONING"
],
[
78,
16,
0,
41,
0,
"MOTION_MODULE"
],
[
79,
4,
0,
41,
1,
"MODEL"
],
[
80,
20,
0,
41,
4,
"LATENT"
],
[
81,
41,
0,
44,
0,
"LATENT"
],
[
82,
13,
0,
44,
1,
"VAE"
],
[
172,
44,
0,
45,
0,
"IMAGE"
],
[
176,
39,
0,
41,
2,
"CONDITIONING"
],
[
180,
39,
1,
41,
3,
"CONDITIONING"
],
[
181,
103,
0,
39,
3,
"IMAGE"
],
[
182,
103,
0,
104,
0,
"IMAGE"
],
[
185,
104,
2,
41,
5,
"INT"
],
[
186,
103,
0,
105,
0,
"IMAGE"
],
[
187,
44,
0,
106,
0,
"IMAGE"
],
[
188,
104,
0,
20,
1,
"INT"
],
[
189,
104,
1,
20,
0,
"INT"
]
],
"groups": [],
"config": {},
"extra": {},
"version": 0.4
}
+820
View File
@@ -0,0 +1,820 @@
{
"last_node_id": 28,
"last_link_id": 56,
"nodes": [
{
"id": 20,
"type": "EmptyLatentImage",
"pos": [
520,
20
],
"size": {
"0": 315,
"1": 106
},
"flags": {},
"order": 0,
"mode": 0,
"outputs": [
{
"name": "LATENT",
"type": "LATENT",
"links": [
35
],
"shape": 3,
"slot_index": 0
}
],
"properties": {
"Node name for S&R": "EmptyLatentImage"
},
"widgets_values": [
512,
512,
1
]
},
{
"id": 25,
"type": "Reroute",
"pos": [
440,
611
],
"size": [
75,
26
],
"flags": {},
"order": 9,
"mode": 0,
"inputs": [
{
"name": "",
"type": "*",
"link": 45
}
],
"outputs": [
{
"name": "",
"type": "LATENT",
"links": [
46
],
"slot_index": 0
}
],
"properties": {
"showOutputText": false,
"horizontal": false
}
},
{
"id": 24,
"type": "Reroute",
"pos": [
1224,
604
],
"size": [
75,
26
],
"flags": {},
"order": 7,
"mode": 0,
"inputs": [
{
"name": "",
"type": "*",
"link": 44
}
],
"outputs": [
{
"name": "",
"type": "LATENT",
"links": [
45
],
"slot_index": 0
}
],
"properties": {
"showOutputText": false,
"horizontal": false
}
},
{
"id": 16,
"type": "AnimateDiffModuleLoader",
"pos": [
27,
345
],
"size": {
"0": 315,
"1": 58
},
"flags": {},
"order": 1,
"mode": 0,
"outputs": [
{
"name": "MOTION_MODULE",
"type": "MOTION_MODULE",
"links": [
24,
48
],
"shape": 3,
"slot_index": 0
}
],
"properties": {
"Node name for S&R": "AnimateDiffModuleLoader"
},
"widgets_values": [
"mm-Stabilized_mid.pth"
]
},
{
"id": 4,
"type": "CheckpointLoaderSimple",
"pos": [
26,
474
],
"size": {
"0": 315,
"1": 98
},
"flags": {},
"order": 2,
"mode": 0,
"outputs": [
{
"name": "MODEL",
"type": "MODEL",
"links": [
25,
49
],
"slot_index": 0
},
{
"name": "CLIP",
"type": "CLIP",
"links": [
3,
5
],
"slot_index": 1
},
{
"name": "VAE",
"type": "VAE",
"links": [],
"slot_index": 2
}
],
"properties": {
"Node name for S&R": "CheckpointLoaderSimple"
},
"widgets_values": [
"AnimeLike25D_v11.safetensors"
]
},
{
"id": 22,
"type": "LatentUpscaleBy",
"pos": [
571,
712
],
"size": [
275.35137939453125,
82
],
"flags": {},
"order": 11,
"mode": 0,
"inputs": [
{
"name": "samples",
"type": "LATENT",
"link": 46
}
],
"outputs": [
{
"name": "LATENT",
"type": "LATENT",
"links": [
47
],
"shape": 3,
"slot_index": 0
}
],
"properties": {
"Node name for S&R": "LatentUpscaleBy"
},
"widgets_values": [
"nearest-exact",
1.5
]
},
{
"id": 6,
"type": "CLIPTextEncode",
"pos": [
415,
186
],
"size": {
"0": 422.84503173828125,
"1": 164.31304931640625
},
"flags": {},
"order": 4,
"mode": 0,
"inputs": [
{
"name": "clip",
"type": "CLIP",
"link": 3
}
],
"outputs": [
{
"name": "CONDITIONING",
"type": "CONDITIONING",
"links": [
29,
50
],
"slot_index": 0
}
],
"properties": {
"Node name for S&R": "CLIPTextEncode"
},
"widgets_values": [
"masterpiece, best quality, 1girl, solo, cherry blossoms, hanami, pink flower, white flower, spring season, wisteria, petals, flower, plum blossoms, outdoors, falling petals, white hair, black eyes"
]
},
{
"id": 7,
"type": "CLIPTextEncode",
"pos": [
413,
389
],
"size": {
"0": 425.27801513671875,
"1": 180.6060791015625
},
"flags": {},
"order": 5,
"mode": 0,
"inputs": [
{
"name": "clip",
"type": "CLIP",
"link": 5
}
],
"outputs": [
{
"name": "CONDITIONING",
"type": "CONDITIONING",
"links": [
30,
51
],
"slot_index": 0
}
],
"properties": {
"Node name for S&R": "CLIPTextEncode"
},
"widgets_values": [
"embedding:easynegative, embedding:badhandv4, "
]
},
{
"id": 26,
"type": "AnimateDiffSampler",
"pos": [
893,
712
],
"size": {
"0": 315,
"1": 330
},
"flags": {},
"order": 12,
"mode": 0,
"inputs": [
{
"name": "motion_module",
"type": "MOTION_MODULE",
"link": 48,
"slot_index": 0
},
{
"name": "model",
"type": "MODEL",
"link": 49,
"slot_index": 1
},
{
"name": "positive",
"type": "CONDITIONING",
"link": 50
},
{
"name": "negative",
"type": "CONDITIONING",
"link": 51
},
{
"name": "latent_image",
"type": "LATENT",
"link": 47
}
],
"outputs": [
{
"name": "LATENT",
"type": "LATENT",
"links": [
52
],
"shape": 3,
"slot_index": 0
}
],
"properties": {
"Node name for S&R": "AnimateDiffSampler"
},
"widgets_values": [
"default",
16,
345029849956687,
"increment",
20,
8,
"euler",
"normal",
0.4
]
},
{
"id": 8,
"type": "VAEDecode",
"pos": [
1239,
712
],
"size": {
"0": 210,
"1": 46
},
"flags": {},
"order": 13,
"mode": 0,
"inputs": [
{
"name": "samples",
"type": "LATENT",
"link": 52
},
{
"name": "vae",
"type": "VAE",
"link": 20
}
],
"outputs": [
{
"name": "IMAGE",
"type": "IMAGE",
"links": [
19
],
"slot_index": 0
}
],
"properties": {
"Node name for S&R": "VAEDecode"
}
},
{
"id": 15,
"type": "AnimateDiffSampler",
"pos": [
882,
192
],
"size": {
"0": 315,
"1": 330
},
"flags": {},
"order": 6,
"mode": 0,
"inputs": [
{
"name": "motion_module",
"type": "MOTION_MODULE",
"link": 24,
"slot_index": 0
},
{
"name": "model",
"type": "MODEL",
"link": 25,
"slot_index": 1
},
{
"name": "positive",
"type": "CONDITIONING",
"link": 29
},
{
"name": "negative",
"type": "CONDITIONING",
"link": 30
},
{
"name": "latent_image",
"type": "LATENT",
"link": 35
}
],
"outputs": [
{
"name": "LATENT",
"type": "LATENT",
"links": [
44,
53
],
"shape": 3,
"slot_index": 0
}
],
"properties": {
"Node name for S&R": "AnimateDiffSampler"
},
"widgets_values": [
"default",
16,
345029849956687,
"increment",
20,
8,
"euler",
"normal",
1
]
},
{
"id": 13,
"type": "VAELoader",
"pos": [
27,
631
],
"size": {
"0": 315,
"1": 58
},
"flags": {},
"order": 3,
"mode": 0,
"outputs": [
{
"name": "VAE",
"type": "VAE",
"links": [
20,
54
],
"shape": 3,
"slot_index": 0
}
],
"properties": {
"Node name for S&R": "VAELoader"
},
"widgets_values": [
"klF8Anime2.ckpt"
]
},
{
"id": 12,
"type": "AnimateDiffCombine",
"pos": [
1504,
481
],
"size": [
325.7265625,
517.7265625
],
"flags": {},
"order": 14,
"mode": 0,
"inputs": [
{
"name": "images",
"type": "IMAGE",
"link": 19
}
],
"outputs": [
{
"name": "GIF",
"type": "GIF",
"links": null,
"shape": 3
}
],
"properties": {
"Node name for S&R": "AnimateDiffCombine"
},
"widgets_values": [
8,
0,
true,
"AnimateDiff",
"image/webp",
false,
"/view?filename=AnimateDiff_00033_.webp&subfolder=&type=output&format=image%2Fwebp"
]
},
{
"id": 27,
"type": "VAEDecode",
"pos": [
1258,
187
],
"size": {
"0": 210,
"1": 46
},
"flags": {},
"order": 8,
"mode": 0,
"inputs": [
{
"name": "samples",
"type": "LATENT",
"link": 53
},
{
"name": "vae",
"type": "VAE",
"link": 54
}
],
"outputs": [
{
"name": "IMAGE",
"type": "IMAGE",
"links": [
56
],
"slot_index": 0
}
],
"properties": {
"Node name for S&R": "VAEDecode"
}
},
{
"id": 28,
"type": "AnimateDiffCombine",
"pos": [
1506,
-60
],
"size": [
321.19170831298857,
513.0408935546875
],
"flags": {},
"order": 10,
"mode": 0,
"inputs": [
{
"name": "images",
"type": "IMAGE",
"link": 56
}
],
"outputs": [
{
"name": "GIF",
"type": "GIF",
"links": null,
"shape": 3
}
],
"properties": {
"Node name for S&R": "AnimateDiffCombine"
},
"widgets_values": [
8,
0,
true,
"AnimateDiff",
"image/webp",
false,
"/view?filename=AnimateDiff_00032_.webp&subfolder=&type=output&format=image%2Fwebp"
]
}
],
"links": [
[
3,
4,
1,
6,
0,
"CLIP"
],
[
5,
4,
1,
7,
0,
"CLIP"
],
[
19,
8,
0,
12,
0,
"IMAGE"
],
[
20,
13,
0,
8,
1,
"VAE"
],
[
24,
16,
0,
15,
0,
"MOTION_MODULE"
],
[
25,
4,
0,
15,
1,
"MODEL"
],
[
29,
6,
0,
15,
2,
"CONDITIONING"
],
[
30,
7,
0,
15,
3,
"CONDITIONING"
],
[
35,
20,
0,
15,
4,
"LATENT"
],
[
44,
15,
0,
24,
0,
"*"
],
[
45,
24,
0,
25,
0,
"*"
],
[
46,
25,
0,
22,
0,
"LATENT"
],
[
47,
22,
0,
26,
4,
"LATENT"
],
[
48,
16,
0,
26,
0,
"MOTION_MODULE"
],
[
49,
4,
0,
26,
1,
"MODEL"
],
[
50,
6,
0,
26,
2,
"CONDITIONING"
],
[
51,
7,
0,
26,
3,
"CONDITIONING"
],
[
52,
26,
0,
8,
0,
"LATENT"
],
[
53,
15,
0,
27,
0,
"LATENT"
],
[
54,
13,
0,
27,
1,
"VAE"
],
[
56,
27,
0,
28,
0,
"IMAGE"
]
],
"groups": [],
"config": {},
"extra": {},
"version": 0.4
}