Compare commits
6
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
14d40b1d57 | ||
|
|
192f2c3cb3 | ||
|
|
62f4d6e441 | ||
|
|
09ffef9e0f | ||
|
|
d9702d937b | ||
|
|
7307f4ea40 |
@@ -1,201 +0,0 @@
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
APPENDIX: How to apply the Apache License to your work.
|
||||
|
||||
To apply the Apache License to your work, attach the following
|
||||
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||
replaced with your own identifying information. (Don't include
|
||||
the brackets!) The text should be enclosed in the appropriate
|
||||
comment syntax for the file format. We also recommend that a
|
||||
file or class name and description of purpose be included on the
|
||||
same "printed page" as the copyright notice for easier
|
||||
identification within third-party archives.
|
||||
|
||||
Copyright [yyyy] [name of copyright owner]
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
@@ -11,53 +11,11 @@
|
||||
- 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)
|
||||
|
||||
## Update 2023/09/25
|
||||
|
||||
#### **Motion LoRA** is now supported!
|
||||
|
||||
Download [motion LoRAs](https://huggingface.co/guoyww/animatediff/tree/main) and put them under `comfyui-animatediff/loras/` folder.
|
||||
|
||||
Note: LoRAs only work with **AnimateDiff v2** [mm_sd_v15_v2.ckpt](https://huggingface.co/guoyww/animatediff/blob/main/mm_sd_v15_v2.ckpt) module.
|
||||
|
||||
#### New node: `AnimateDiffLoraLoader`
|
||||
|
||||
<img width="370" alt="image" src="https://github.com/ArtVentureX/comfyui-animatediff/assets/133728487/7a9f62f7-702e-48a4-934c-bbfe1e23aff2">
|
||||
|
||||
Example workflow:
|
||||
<img width="1280" alt="image" src="https://github.com/ArtVentureX/comfyui-animatediff/assets/133728487/93e7550f-4648-4482-9961-6cece5132dc9">
|
||||
|
||||
Workflow: [lora.json](https://github.com/ArtVentureX/comfyui-animatediff/blob/main/workflows/lora.json)
|
||||
|
||||
Samples:
|
||||
|
||||
<table>
|
||||
<tr>
|
||||
<td>
|
||||
<img width="512" alt="image" src="https://github.com/ArtVentureX/comfyui-animatediff/assets/133728487/2c5aa25e-0682-481f-8842-066c5b988864">
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<img width="512" alt="image" src="https://github.com/ArtVentureX/comfyui-animatediff/assets/133728487/adfbad45-3ba5-42e3-9bee-d2b83f43989c">
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<img width="512" alt="image" src="https://github.com/ArtVentureX/comfyui-animatediff/assets/133728487/8e484c74-c691-4d1c-9514-719dbfe3a0b5">
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<img width="512" alt="image" src="https://github.com/ArtVentureX/comfyui-animatediff/assets/133728487/4921a335-9207-4a7b-9d66-61a5d76e3179">
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
## Update 2023/09/21
|
||||
|
||||
#### **Sliding Window** is now available!
|
||||
|
||||
The sliding window feature enables you to generate GIFs without a frame length limit. It divides frames into smaller batches with a slight overlap. This feature is activated automatically when generating more than 16 frames. To modify the trigger number and other settings, utilize the `SlidingWindowOptions` node. See the [sample workflow](#long-duration-with-sliding-window) bellow.
|
||||
Sliding window is trigged automatically when generating more than 16 frames. To adjust the trigger number and other options, use `SlidingWindowOptions` node. See the sample workflow bellow.
|
||||
|
||||
## Nodes
|
||||
|
||||
@@ -91,13 +49,7 @@ The sliding window feature enables you to generate GIFs without a frame length l
|
||||
Custom sliding window options
|
||||
|
||||
- `context_length`: number of frame per _window_. Use **16** to get the best results. Reduce it if you have low VRAM.
|
||||
- `context_stride`:
|
||||
- 1: sampling every frame
|
||||
- 2: sampling every frame then every second frame
|
||||
- 3: sampling every frame then every second frame then every third frames
|
||||
- ...
|
||||
- `context_overlap`: overlap frames between each window slice
|
||||
- `closed_loop`: make the GIF a closed loop, will add more sampling step
|
||||
- `closed_loop`: try to make the GIF a closed loop. Will take longer to render.
|
||||
|
||||
<img width="370" alt="image" src="https://github.com/ArtVentureX/comfyui-animatediff/assets/133728487/6679a8dd-bf96-419f-8934-ea2b046dd23c">
|
||||
|
||||
@@ -131,19 +83,9 @@ Samples:
|
||||
Workflow: [sliding-window.json](https://github.com/ArtVentureX/comfyui-animatediff/blob/main/workflows/sliding-window.json)
|
||||
|
||||
Samples:
|
||||

|
||||
|
||||
<table>
|
||||
<tr>
|
||||
<td>
|
||||
<img width="512" alt="image" src="https://github.com/ArtVentureX/comfyui-animatediff/assets/133728487/e1da7a66-e615-475d-9400-41eff484ad49">
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<img width="768" alt="image" src="https://github.com/ArtVentureX/comfyui-animatediff/assets/133728487/4faa7e5e-cdaa-49da-8759-46d779c0e0b6">
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||

|
||||
|
||||
### Latent upscale
|
||||
|
||||
@@ -216,10 +158,6 @@ Samples:
|
||||
|
||||
## Known Issues
|
||||
|
||||
### CUDA error: invalid configuration argument
|
||||
|
||||
It's an `xformers` bug accidentally triggered by the way the original AnimateDiff CrossAttention is passed in. The current workaround is to disable xformers with `--disable-xformers` when booting ComfyUI.
|
||||
|
||||
### GIF split into multiple scenes
|
||||
|
||||

|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import os
|
||||
import hashlib
|
||||
import torch
|
||||
from typing import Dict
|
||||
|
||||
import folder_paths
|
||||
@@ -12,7 +11,6 @@ from .motion_module import MotionWrapper
|
||||
|
||||
|
||||
motion_modules: Dict[str, MotionWrapper] = {}
|
||||
motion_loras: Dict[str, Dict[str, torch.Tensor]] = {}
|
||||
|
||||
|
||||
folder_paths.folder_names_and_paths["AnimateDiff"] = (
|
||||
@@ -22,34 +20,19 @@ folder_paths.folder_names_and_paths["AnimateDiff"] = (
|
||||
],
|
||||
folder_paths.supported_pt_extensions,
|
||||
)
|
||||
folder_paths.folder_names_and_paths["AnimateDiffLora"] = (
|
||||
[
|
||||
os.path.join(folder_paths.models_dir, "AnimateDiffLora"),
|
||||
os.path.join(os.path.dirname(os.path.abspath(__file__)), "..", "loras"),
|
||||
],
|
||||
folder_paths.supported_pt_extensions,
|
||||
)
|
||||
|
||||
|
||||
def get_available_models():
|
||||
return folder_paths.get_filename_list("AnimateDiff")
|
||||
|
||||
|
||||
def get_available_loras():
|
||||
return folder_paths.get_filename_list("AnimateDiffLora")
|
||||
|
||||
|
||||
def get_model_path(model_name):
|
||||
return folder_paths.get_full_path("AnimateDiff", model_name)
|
||||
|
||||
|
||||
def get_lora_path(lora_name):
|
||||
return folder_paths.get_full_path("AnimateDiffLora", lora_name)
|
||||
|
||||
|
||||
def get_model_hash(file_path):
|
||||
with open(file_path, "rb") as f:
|
||||
bytes = f.read(1024 * 1024) # read entire file as bytes
|
||||
bytes = f.read() # read entire file as bytes
|
||||
return hashlib.sha256(bytes).hexdigest()
|
||||
|
||||
|
||||
@@ -71,30 +54,3 @@ def load_motion_module(model_name: str):
|
||||
motion_modules[model_hash] = motion_module
|
||||
|
||||
return motion_modules[model_hash]
|
||||
|
||||
|
||||
def load_lora(lora_name: str):
|
||||
lora_path = get_lora_path(lora_name)
|
||||
lora_hash = get_model_hash(lora_path)
|
||||
if lora_hash not in motion_modules:
|
||||
logger.info(f"Loading lora {lora_name}")
|
||||
state_dict = load_torch_file(lora_path)
|
||||
updated_state_dict: Dict[str, torch.Tensor] = {}
|
||||
|
||||
for key in state_dict:
|
||||
# only process lora down key
|
||||
if "up." in key:
|
||||
continue
|
||||
|
||||
up_key = key.replace(".down.", ".up.")
|
||||
model_key = key.replace("processor.", "").replace("_lora", "").replace("down.", "").replace("up.", "")
|
||||
model_key = model_key.replace("to_out.", "to_out.0.")
|
||||
combined_key = ".".join(model_key.split(".")[:-1])
|
||||
|
||||
weight_down = state_dict[key]
|
||||
weight_up = state_dict[up_key]
|
||||
updated_state_dict[combined_key] = torch.mm(weight_up, weight_down).to("cpu")
|
||||
|
||||
motion_loras[lora_hash] = updated_state_dict
|
||||
|
||||
return motion_loras[lora_hash]
|
||||
|
||||
@@ -1,35 +1,11 @@
|
||||
import os
|
||||
import torch
|
||||
from torch import Tensor, nn
|
||||
|
||||
import math
|
||||
from einops import rearrange, repeat
|
||||
|
||||
import comfy.model_management as model_management
|
||||
from comfy.ldm.modules.attention import (
|
||||
default,
|
||||
FeedForward,
|
||||
CrossAttention as ComfyCrossAttention,
|
||||
attention_basic,
|
||||
attention_pytorch,
|
||||
attention_split,
|
||||
attention_sub_quad,
|
||||
)
|
||||
from comfy.cli_args import args
|
||||
|
||||
from .logger import logger
|
||||
|
||||
attention = attention_basic
|
||||
|
||||
if model_management.xformers_enabled():
|
||||
logger.warn("xformers is enabled but it has a bug that can cause issue while using with AnimateDiff.")
|
||||
|
||||
if model_management.pytorch_attention_enabled():
|
||||
attention = attention_pytorch
|
||||
else:
|
||||
if args.use_split_cross_attention:
|
||||
attention = attention_split
|
||||
else:
|
||||
attention = attention_sub_quad
|
||||
from comfy.ldm.modules.attention import FeedForward, CrossAttention
|
||||
|
||||
|
||||
def zero_module(module):
|
||||
@@ -56,24 +32,6 @@ def has_mid_block(mm_state_dict: dict[str, Tensor]):
|
||||
return False
|
||||
|
||||
|
||||
class CrossAttention(ComfyCrossAttention):
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
|
||||
def forward(self, x, context=None, value=None, mask=None):
|
||||
q = self.to_q(x)
|
||||
context = default(context, x)
|
||||
k = self.to_k(context)
|
||||
if value is not None:
|
||||
v = self.to_v(value)
|
||||
del value
|
||||
else:
|
||||
v = self.to_v(context)
|
||||
|
||||
out = attention(q, k, v, self.heads, mask)
|
||||
return self.to_out(out)
|
||||
|
||||
|
||||
class MotionWrapper(nn.Module):
|
||||
def __init__(self, mm_type: str, encoding_max_len: int = 24, is_v2=False):
|
||||
super().__init__()
|
||||
@@ -86,11 +44,17 @@ class MotionWrapper(nn.Module):
|
||||
self.encoding_max_len = encoding_max_len
|
||||
|
||||
for c in (320, 640, 1280, 1280):
|
||||
self.down_blocks.append(MotionModule(c, BlockType.DOWN, encoding_max_len=encoding_max_len))
|
||||
self.down_blocks.append(
|
||||
MotionModule(c, BlockType.DOWN, encoding_max_len=encoding_max_len)
|
||||
)
|
||||
for c in (1280, 1280, 640, 320):
|
||||
self.up_blocks.append(MotionModule(c, BlockType.UP, encoding_max_len=encoding_max_len))
|
||||
self.up_blocks.append(
|
||||
MotionModule(c, BlockType.UP, encoding_max_len=encoding_max_len)
|
||||
)
|
||||
if is_v2:
|
||||
self.mid_block = MotionModule(1280, BlockType.MID, encoding_max_len=encoding_max_len)
|
||||
self.mid_block = MotionModule(
|
||||
1280, BlockType.MID, encoding_max_len=encoding_max_len
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def from_state_dict(cls, mm_state_dict: dict[str, Tensor], mm_type: str):
|
||||
@@ -127,7 +91,9 @@ class MotionModule(nn.Module):
|
||||
self.block_type = block_type
|
||||
|
||||
if block_type == BlockType.MID:
|
||||
self.motion_modules = nn.ModuleList([get_motion_module(in_channels, encoding_max_len)])
|
||||
self.motion_modules = nn.ModuleList(
|
||||
[get_motion_module(in_channels, encoding_max_len)]
|
||||
)
|
||||
else:
|
||||
self.motion_modules = nn.ModuleList(
|
||||
[
|
||||
@@ -136,7 +102,9 @@ class MotionModule(nn.Module):
|
||||
]
|
||||
)
|
||||
if block_type == BlockType.UP:
|
||||
self.motion_modules.append(get_motion_module(in_channels, encoding_max_len))
|
||||
self.motion_modules.append(
|
||||
get_motion_module(in_channels, encoding_max_len)
|
||||
)
|
||||
|
||||
def set_video_length(self, video_length: int):
|
||||
for motion_module in self.motion_modules:
|
||||
@@ -144,7 +112,9 @@ class MotionModule(nn.Module):
|
||||
|
||||
|
||||
def get_motion_module(in_channels, max_len):
|
||||
return VanillaTemporalModule(in_channels=in_channels, temporal_position_encoding_max_len=max_len)
|
||||
return VanillaTemporalModule(
|
||||
in_channels=in_channels, temporal_position_encoding_max_len=max_len
|
||||
)
|
||||
|
||||
|
||||
class VanillaTemporalModule(nn.Module):
|
||||
@@ -165,7 +135,9 @@ class VanillaTemporalModule(nn.Module):
|
||||
self.temporal_transformer = TemporalTransformer3DModel(
|
||||
in_channels=in_channels,
|
||||
num_attention_heads=num_attention_heads,
|
||||
attention_head_dim=in_channels // num_attention_heads // temporal_attention_dim_div,
|
||||
attention_head_dim=in_channels
|
||||
// num_attention_heads
|
||||
// temporal_attention_dim_div,
|
||||
num_layers=num_transformer_block,
|
||||
attention_block_types=attention_block_types,
|
||||
cross_frame_attention_mode=cross_frame_attention_mode,
|
||||
@@ -174,13 +146,17 @@ class VanillaTemporalModule(nn.Module):
|
||||
)
|
||||
|
||||
if zero_initialize:
|
||||
self.temporal_transformer.proj_out = zero_module(self.temporal_transformer.proj_out)
|
||||
self.temporal_transformer.proj_out = zero_module(
|
||||
self.temporal_transformer.proj_out
|
||||
)
|
||||
|
||||
def set_video_length(self, video_length: int):
|
||||
self.temporal_transformer.set_video_length(video_length)
|
||||
|
||||
def forward(self, input_tensor, encoder_hidden_states=None, attention_mask=None):
|
||||
return self.temporal_transformer(input_tensor, encoder_hidden_states, attention_mask)
|
||||
def forward(self, input_tensor, encoder_hidden_states, attention_mask=None):
|
||||
return self.temporal_transformer(
|
||||
input_tensor, encoder_hidden_states, attention_mask
|
||||
)
|
||||
|
||||
|
||||
class TemporalTransformer3DModel(nn.Module):
|
||||
@@ -208,7 +184,9 @@ class TemporalTransformer3DModel(nn.Module):
|
||||
|
||||
inner_dim = num_attention_heads * attention_head_dim
|
||||
|
||||
self.norm = torch.nn.GroupNorm(num_groups=norm_num_groups, num_channels=in_channels, eps=1e-6, affine=True)
|
||||
self.norm = torch.nn.GroupNorm(
|
||||
num_groups=norm_num_groups, num_channels=in_channels, eps=1e-6, affine=True
|
||||
)
|
||||
self.proj_in = nn.Linear(in_channels, inner_dim)
|
||||
|
||||
self.transformer_blocks = nn.ModuleList(
|
||||
@@ -243,7 +221,9 @@ class TemporalTransformer3DModel(nn.Module):
|
||||
|
||||
hidden_states = self.norm(hidden_states)
|
||||
inner_dim = hidden_states.shape[1]
|
||||
hidden_states = hidden_states.permute(0, 2, 3, 1).reshape(batch, height * weight, inner_dim)
|
||||
hidden_states = hidden_states.permute(0, 2, 3, 1).reshape(
|
||||
batch, height * weight, inner_dim
|
||||
)
|
||||
hidden_states = self.proj_in(hidden_states)
|
||||
|
||||
# Transformer Blocks
|
||||
@@ -256,7 +236,11 @@ class TemporalTransformer3DModel(nn.Module):
|
||||
|
||||
# output
|
||||
hidden_states = self.proj_out(hidden_states)
|
||||
hidden_states = hidden_states.reshape(batch, height, weight, inner_dim).permute(0, 3, 1, 2).contiguous()
|
||||
hidden_states = (
|
||||
hidden_states.reshape(batch, height, weight, inner_dim)
|
||||
.permute(0, 3, 1, 2)
|
||||
.contiguous()
|
||||
)
|
||||
|
||||
output = hidden_states + residual
|
||||
|
||||
@@ -292,7 +276,9 @@ class TemporalTransformerBlock(nn.Module):
|
||||
attention_blocks.append(
|
||||
VersatileAttention(
|
||||
attention_mode=block_name.split("_")[0],
|
||||
context_dim=cross_attention_dim if block_name.endswith("_Cross") else None,
|
||||
context_dim=cross_attention_dim
|
||||
if block_name.endswith("_Cross")
|
||||
else None,
|
||||
query_dim=dim,
|
||||
heads=num_attention_heads,
|
||||
dim_head=attention_head_dim,
|
||||
@@ -324,7 +310,9 @@ class TemporalTransformerBlock(nn.Module):
|
||||
hidden_states = (
|
||||
attention_block(
|
||||
norm_hidden_states,
|
||||
encoder_hidden_states=encoder_hidden_states if attention_block.is_cross_attention else None,
|
||||
encoder_hidden_states=encoder_hidden_states
|
||||
if attention_block.is_cross_attention
|
||||
else None,
|
||||
video_length=video_length,
|
||||
)
|
||||
+ hidden_states
|
||||
@@ -341,7 +329,9 @@ class PositionalEncoding(nn.Module):
|
||||
super().__init__()
|
||||
self.dropout = nn.Dropout(p=dropout)
|
||||
position = torch.arange(max_len).unsqueeze(1)
|
||||
div_term = torch.exp(torch.arange(0, d_model, 2) * (-math.log(10000.0) / d_model))
|
||||
div_term = torch.exp(
|
||||
torch.arange(0, d_model, 2) * (-math.log(10000.0) / d_model)
|
||||
)
|
||||
pe = torch.zeros(1, max_len, d_model)
|
||||
pe[0, :, 0::2] = torch.sin(position * div_term)
|
||||
pe[0, :, 1::2] = torch.cos(position * div_term)
|
||||
@@ -393,7 +383,9 @@ class VersatileAttention(CrossAttention):
|
||||
raise NotImplementedError
|
||||
|
||||
d = hidden_states.shape[1]
|
||||
hidden_states = rearrange(hidden_states, "(b f) d c -> (b d) f c", f=video_length)
|
||||
hidden_states = rearrange(
|
||||
hidden_states, "(b f) d c -> (b d) f c", f=video_length
|
||||
)
|
||||
|
||||
if self.pos_encoder is not None:
|
||||
hidden_states = self.pos_encoder(hidden_states)
|
||||
|
||||
+5
-88
@@ -3,18 +3,16 @@ import json
|
||||
import torch
|
||||
import numpy as np
|
||||
import hashlib
|
||||
from typing import List, Dict, Tuple
|
||||
from typing import List
|
||||
from torch import Tensor
|
||||
from PIL import Image, ImageSequence
|
||||
from PIL.PngImagePlugin import PngInfo
|
||||
|
||||
import folder_paths
|
||||
|
||||
from .motion_module import MotionWrapper
|
||||
from .model_utils import get_available_models, load_motion_module, get_available_loras, load_lora
|
||||
from .utils import pil2tensor, ensure_opencv
|
||||
from .model_utils import get_available_models, load_motion_module
|
||||
from .utils import pil2tensor
|
||||
from .sampler import AnimateDiffSampler, AnimateDiffSlidingWindowOptions
|
||||
from .logger import logger
|
||||
|
||||
|
||||
SLIDING_CONTEXT_LENGTH = 16
|
||||
@@ -30,99 +28,21 @@ class AnimateDiffModuleLoader:
|
||||
"required": {
|
||||
"model_name": (get_available_models(),),
|
||||
},
|
||||
"optional": {
|
||||
"lora_stack": ("MOTION_LORA_STACK",),
|
||||
},
|
||||
}
|
||||
|
||||
RETURN_TYPES = ("MOTION_MODULE",)
|
||||
CATEGORY = "Animate Diff"
|
||||
FUNCTION = "load_motion_module"
|
||||
|
||||
def inject_loras(self, motion_module: MotionWrapper, lora_stack: List[Tuple[Dict[str, Tensor], float]]):
|
||||
for lora in lora_stack:
|
||||
(state_dict, alpha) = lora
|
||||
|
||||
for key in state_dict:
|
||||
layer_infos = key.split(".")
|
||||
|
||||
curr_layer = motion_module
|
||||
while len(layer_infos) > 0:
|
||||
temp_name = layer_infos.pop(0)
|
||||
curr_layer = curr_layer.__getattr__(temp_name)
|
||||
|
||||
curr_layer.weight.data += alpha * state_dict[key].to(curr_layer.weight.data.device)
|
||||
|
||||
def eject_loras(self, motion_module: MotionWrapper, lora_stack: List[Tuple[float, Dict[str, Tensor]]]):
|
||||
lora_stack.reverse() # should not matter but just in case
|
||||
for lora in lora_stack:
|
||||
(state_dict, alpha) = lora
|
||||
|
||||
for key in state_dict:
|
||||
layer_infos = key.split(".")
|
||||
|
||||
curr_layer = motion_module
|
||||
while len(layer_infos) > 0:
|
||||
temp_name = layer_infos.pop(0)
|
||||
curr_layer = curr_layer.__getattr__(temp_name)
|
||||
|
||||
curr_layer.weight.data -= alpha * state_dict[key].to(curr_layer.weight.data.device)
|
||||
|
||||
def load_motion_module(
|
||||
self,
|
||||
model_name: str,
|
||||
lora_stack: List = None,
|
||||
):
|
||||
motion_module = load_motion_module(model_name)
|
||||
|
||||
# inject loras
|
||||
if motion_module.is_v2:
|
||||
if hasattr(motion_module, "lora_stack") and isinstance(motion_module.lora_stack, list):
|
||||
self.eject_loras(motion_module, motion_module.lora_stack)
|
||||
delattr(motion_module, "lora_stack")
|
||||
|
||||
if isinstance(lora_stack, list):
|
||||
self.inject_loras(motion_module, lora_stack)
|
||||
setattr(motion_module, "lora_stack", lora_stack)
|
||||
|
||||
elif isinstance(lora_stack, list):
|
||||
logger.warning("LoRA is provided but only motion module v2 is supported.")
|
||||
|
||||
return (motion_module,)
|
||||
|
||||
|
||||
class AnimateDiffLoraLoader:
|
||||
@classmethod
|
||||
def INPUT_TYPES(s):
|
||||
return {
|
||||
"required": {
|
||||
"lora_name": (get_available_loras(),),
|
||||
"alpha": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 10.0, "step": 0.001}),
|
||||
},
|
||||
"optional": {
|
||||
"lora_stack": ("MOTION_LORA_STACK",),
|
||||
},
|
||||
}
|
||||
|
||||
RETURN_TYPES = ("MOTION_LORA_STACK",)
|
||||
CATEGORY = "Animate Diff"
|
||||
FUNCTION = "load_lora"
|
||||
|
||||
def load_lora(
|
||||
self,
|
||||
lora_name: str,
|
||||
alpha: float,
|
||||
lora_stack: List = None,
|
||||
):
|
||||
if not lora_stack:
|
||||
lora_stack = []
|
||||
|
||||
lora = load_lora(lora_name)
|
||||
lora_stack.append((lora, alpha))
|
||||
|
||||
return (lora_stack,)
|
||||
|
||||
|
||||
class AnimateDiffCombine:
|
||||
@classmethod
|
||||
def INPUT_TYPES(s):
|
||||
@@ -302,7 +222,6 @@ class LoadVideo:
|
||||
return frames
|
||||
|
||||
def load_video(self, video_path, frame_start: int, frame_limit: int):
|
||||
ensure_opencv()
|
||||
import cv2
|
||||
|
||||
video = cv2.VideoCapture(video_path)
|
||||
@@ -330,12 +249,12 @@ class LoadVideo:
|
||||
|
||||
if ext.lower() in {".gif", ".webp"}:
|
||||
frames = self.load_gif(video_path, frame_start, frame_limit)
|
||||
elif ext.lower() in {".webp", ".mp4", ".mov", ".avi", ".webm"}:
|
||||
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), len(frames))
|
||||
return (torch.cat(frames, dim=0),)
|
||||
|
||||
@classmethod
|
||||
def IS_CHANGED(s, image, *args, **kwargs):
|
||||
@@ -404,7 +323,6 @@ class ImageChunking:
|
||||
|
||||
NODE_CLASS_MAPPINGS = {
|
||||
"AnimateDiffModuleLoader": AnimateDiffModuleLoader,
|
||||
"AnimateDiffLoraLoader": AnimateDiffLoraLoader,
|
||||
"AnimateDiffCombine": AnimateDiffCombine,
|
||||
"AnimateDiffSampler": AnimateDiffSampler,
|
||||
"AnimateDiffSlidingWindowOptions": AnimateDiffSlidingWindowOptions,
|
||||
@@ -413,7 +331,6 @@ NODE_CLASS_MAPPINGS = {
|
||||
}
|
||||
NODE_DISPLAY_NAME_MAPPINGS = {
|
||||
"AnimateDiffModuleLoader": "Animate Diff Module Loader",
|
||||
"AnimateDiffLoraLoader": "Animate Diff Lora Loader",
|
||||
"AnimateDiffSampler": "Animate Diff Sampler",
|
||||
"AnimateDiffSlidingWindowOptions": "Sliding Window Options",
|
||||
"AnimateDiffCombine": "Animate Diff Combine",
|
||||
|
||||
+36
-18
@@ -4,7 +4,9 @@ from torch.nn.functional import group_norm
|
||||
from einops import rearrange
|
||||
|
||||
import comfy.ldm.modules.diffusionmodules.openaimodel as openaimodel
|
||||
from comfy.model_base import BaseModel, model_sampling
|
||||
import comfy.model_management as model_management
|
||||
from comfy.model_base import BaseModel
|
||||
from comfy.ldm.modules.attention import SpatialTransformer
|
||||
from nodes import KSampler
|
||||
|
||||
from .logger import logger
|
||||
@@ -16,19 +18,19 @@ from .sliding_context_sampling import SlidingContext, inject_sampling_function,
|
||||
SLIDING_CONTEXT_LENGTH = 16
|
||||
|
||||
|
||||
class ModelSamplingConfig:
|
||||
def __init__(self, beta_schedule: str):
|
||||
self.sampling_settings = {}
|
||||
self.sampling_settings["beta_schedule"] = beta_schedule
|
||||
|
||||
|
||||
def forward_timestep_embed(ts, x, emb, context=None, *args, **kwargs):
|
||||
def forward_timestep_embed(ts, x, emb, context=None, transformer_options={}, output_shape=None):
|
||||
for layer in ts:
|
||||
if isinstance(layer, VanillaTemporalModule):
|
||||
if isinstance(layer, openaimodel.TimestepBlock):
|
||||
x = layer(x, emb)
|
||||
elif isinstance(layer, VanillaTemporalModule):
|
||||
x = layer(x, context)
|
||||
elif isinstance(layer, SpatialTransformer):
|
||||
x = layer(x, context, transformer_options)
|
||||
transformer_options["current_index"] += 1
|
||||
elif isinstance(layer, openaimodel.Upsample):
|
||||
x = layer(x, output_shape=output_shape)
|
||||
else:
|
||||
x = orig_forward_timestep_embed([layer], x, emb, context, *args, **kwargs)
|
||||
|
||||
x = layer(x)
|
||||
return x
|
||||
|
||||
|
||||
@@ -47,6 +49,7 @@ def groupnorm_mm_factory(video_length: int):
|
||||
|
||||
|
||||
orig_forward_timestep_embed = openaimodel.forward_timestep_embed
|
||||
orig_maximum_batch_area = model_management.maximum_batch_area
|
||||
orig_groupnorm_forward = torch.nn.GroupNorm.forward
|
||||
|
||||
|
||||
@@ -135,7 +138,7 @@ class AnimateDiffSlidingWindowOptions:
|
||||
"context_length": ("INT", {"default": SLIDING_CONTEXT_LENGTH, "min": 2, "max": 32}),
|
||||
"context_stride": ("INT", {"default": 1, "min": 1, "max": 32}),
|
||||
"context_overlap": ("INT", {"default": 4, "min": 0, "max": 32}),
|
||||
"context_schedule": (ContextSchedules.CONTEXT_SCHEDULE_LIST, {"default": ContextSchedules.UNIFORM}),
|
||||
"context_schedule": (ContextSchedules.CONTEXT_SCHEDULE_LIST,),
|
||||
"closed_loop": ("BOOLEAN", {"default": False}),
|
||||
}
|
||||
}
|
||||
@@ -178,17 +181,32 @@ class AnimateDiffSampler(KSampler):
|
||||
|
||||
def __init__(self) -> None:
|
||||
super().__init__()
|
||||
self.model_sampling = None
|
||||
self.prev_beta = None
|
||||
self.prev_linear_start = None
|
||||
self.prev_linear_end = None
|
||||
|
||||
def override_beta_schedule(self, model: BaseModel):
|
||||
self.model_sampling = model.model_sampling
|
||||
model.model_sampling = model_sampling(
|
||||
ModelSamplingConfig(beta_schedule="sqrt_linear"), model_type=model.model_type
|
||||
self.prev_beta = model.get_buffer("betas").cpu().clone().detach()
|
||||
self.prev_linear_start = model.linear_start
|
||||
self.prev_linear_end = model.linear_end
|
||||
model.register_schedule(
|
||||
given_betas=None,
|
||||
beta_schedule="sqrt_linear",
|
||||
timesteps=1000,
|
||||
linear_start=0.00085,
|
||||
linear_end=0.012,
|
||||
cosine_s=8e-3,
|
||||
)
|
||||
|
||||
def restore_beta_schedule(self, model: BaseModel):
|
||||
model.model_sampling = self.model_sampling
|
||||
self.model_sampling = None
|
||||
model.register_schedule(
|
||||
given_betas=self.prev_beta,
|
||||
linear_start=self.prev_linear_start,
|
||||
linear_end=self.prev_linear_end,
|
||||
)
|
||||
self.prev_beta = None
|
||||
self.prev_linear_start = None
|
||||
self.prev_linear_end = None
|
||||
|
||||
def inject_motion_module(self, model, motion_module: MotionWrapper, inject_method: str, frame_number: int):
|
||||
model = model.clone()
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import math
|
||||
import torch
|
||||
from torch import Tensor
|
||||
from typing import List, Dict
|
||||
import math
|
||||
|
||||
import comfy.utils
|
||||
import comfy.sample
|
||||
@@ -18,10 +17,6 @@ orig_comfy_sample = comfy.sample.sample
|
||||
orig_sampling_function = comfy_samplers.sampling_function
|
||||
|
||||
|
||||
def lcm(a, b):
|
||||
return abs(a * b) // math.gcd(a, b)
|
||||
|
||||
|
||||
class SlidingContext:
|
||||
def __init__(
|
||||
self,
|
||||
@@ -75,34 +70,47 @@ def __sliding_sample_factory(ctx: SlidingContext):
|
||||
|
||||
ctx.current_step = start_step + step + 1
|
||||
|
||||
return orig_comfy_sample(model, *args, **kwargs, callback=callback)
|
||||
try:
|
||||
return orig_comfy_sample(model, *args, **kwargs, callback=callback)
|
||||
except RuntimeError as e:
|
||||
if str(e).startswith("CUDA error: invalid configuration argument"):
|
||||
raise RuntimeError(
|
||||
f"An xformers bug was encountered in AnimateDiff - to run your workflow, \
|
||||
disable xformers in ComfyUI using '--disable-xformers' startup argument."
|
||||
)
|
||||
raise
|
||||
|
||||
def sampling_function(model, x, timestep, uncond, cond, cond_scale, model_options={}, seed=None):
|
||||
def get_area_and_mult(conds, x_in, timestep_in):
|
||||
def sampling_function(
|
||||
model_function, x, timestep, uncond, cond, cond_scale, cond_concat=None, model_options={}, seed=None
|
||||
):
|
||||
def get_area_and_mult(cond, x_in, cond_concat_in, timestep_in):
|
||||
area = (x_in.shape[2], x_in.shape[3], 0, 0)
|
||||
strength = 1.0
|
||||
|
||||
if "timestep_start" in conds:
|
||||
timestep_start = conds["timestep_start"]
|
||||
if "timestep_start" in cond[1]:
|
||||
timestep_start = cond[1]["timestep_start"]
|
||||
if timestep_in[0] > timestep_start:
|
||||
return None
|
||||
if "timestep_end" in conds:
|
||||
timestep_end = conds["timestep_end"]
|
||||
if "timestep_end" in cond[1]:
|
||||
timestep_end = cond[1]["timestep_end"]
|
||||
if timestep_in[0] < timestep_end:
|
||||
return None
|
||||
if "area" in conds:
|
||||
area = conds["area"]
|
||||
if "strength" in conds:
|
||||
strength = conds["strength"]
|
||||
if "area" in cond[1]:
|
||||
area = cond[1]["area"]
|
||||
if "strength" in cond[1]:
|
||||
strength = cond[1]["strength"]
|
||||
|
||||
adm_cond = None
|
||||
if "adm_encoded" in cond[1]:
|
||||
adm_cond = cond[1]["adm_encoded"]
|
||||
|
||||
input_x = x_in[:, :, area[2] : area[0] + area[2], area[3] : area[1] + area[3]]
|
||||
if "mask" in conds:
|
||||
if "mask" in cond[1]:
|
||||
# Scale the mask to the size of the input
|
||||
# The mask should have been resized as we began the sampling process
|
||||
mask_strength = 1.0
|
||||
if "mask_strength" in conds:
|
||||
mask_strength = conds["mask_strength"]
|
||||
mask = conds["mask"]
|
||||
if "mask_strength" in cond[1]:
|
||||
mask_strength = cond[1]["mask_strength"]
|
||||
mask = cond[1]["mask"]
|
||||
assert mask.shape[1] == x_in.shape[2]
|
||||
assert mask.shape[2] == x_in.shape[3]
|
||||
mask = mask[:, area[2] : area[0] + area[2], area[3] : area[1] + area[3]] * mask_strength
|
||||
@@ -111,7 +119,7 @@ def __sliding_sample_factory(ctx: SlidingContext):
|
||||
mask = torch.ones_like(input_x)
|
||||
mult = mask * strength
|
||||
|
||||
if "mask" not in conds:
|
||||
if "mask" not in cond[1]:
|
||||
rr = 8
|
||||
if area[2] != 0:
|
||||
for t in range(rr):
|
||||
@@ -127,17 +135,24 @@ def __sliding_sample_factory(ctx: SlidingContext):
|
||||
mult[:, :, :, area[1] - 1 - t : area[1] - t] *= (1.0 / rr) * (t + 1)
|
||||
|
||||
conditionning = {}
|
||||
model_conds = conds["model_conds"]
|
||||
for c in model_conds:
|
||||
conditionning[c] = model_conds[c].process_cond(batch_size=x_in.shape[0], device=x_in.device, area=area)
|
||||
conditionning["c_crossattn"] = cond[0]
|
||||
if cond_concat_in is not None and len(cond_concat_in) > 0:
|
||||
cropped = []
|
||||
for x in cond_concat_in:
|
||||
cr = x[:, :, area[2] : area[0] + area[2], area[3] : area[1] + area[3]]
|
||||
cropped.append(cr)
|
||||
conditionning["c_concat"] = torch.cat(cropped, dim=1)
|
||||
|
||||
if adm_cond is not None:
|
||||
conditionning["c_adm"] = adm_cond
|
||||
|
||||
control = None
|
||||
if "control" in conds:
|
||||
control = conds["control"]
|
||||
if "control" in cond[1]:
|
||||
control = cond[1]["control"]
|
||||
|
||||
patches = None
|
||||
if "gligen" in conds:
|
||||
gligen = conds["gligen"]
|
||||
if "gligen" in cond[1]:
|
||||
gligen = cond[1]["gligen"]
|
||||
patches = {}
|
||||
gligen_type = gligen[0]
|
||||
gligen_model = gligen[1]
|
||||
@@ -155,8 +170,24 @@ def __sliding_sample_factory(ctx: SlidingContext):
|
||||
return True
|
||||
if c1.keys() != c2.keys():
|
||||
return False
|
||||
for k in c1:
|
||||
if not c1[k].can_concat(c2[k]):
|
||||
if "c_crossattn" in c1:
|
||||
s1 = c1["c_crossattn"].shape
|
||||
s2 = c2["c_crossattn"].shape
|
||||
if s1 != s2:
|
||||
if s1[0] != s2[0] or s1[2] != s2[2]: # these 2 cases should not happen
|
||||
return False
|
||||
|
||||
mult_min = comfy_samplers.lcm(s1[1], s2[1])
|
||||
diff = mult_min // min(s1[1], s2[1])
|
||||
if (
|
||||
diff > 4
|
||||
): # arbitrary limit on the padding because it's probably going to impact performance negatively if it's too much
|
||||
return False
|
||||
if "c_concat" in c1:
|
||||
if c1["c_concat"].shape != c2["c_concat"].shape:
|
||||
return False
|
||||
if "c_adm" in c1:
|
||||
if c1["c_adm"].shape != c2["c_adm"].shape:
|
||||
return False
|
||||
return True
|
||||
|
||||
@@ -185,41 +216,55 @@ def __sliding_sample_factory(ctx: SlidingContext):
|
||||
c_concat = []
|
||||
c_adm = []
|
||||
crossattn_max_len = 0
|
||||
|
||||
temp = {}
|
||||
for x in c_list:
|
||||
for k in x:
|
||||
cur = temp.get(k, [])
|
||||
cur.append(x[k])
|
||||
temp[k] = cur
|
||||
|
||||
if "c_crossattn" in x:
|
||||
c = x["c_crossattn"]
|
||||
if crossattn_max_len == 0:
|
||||
crossattn_max_len = c.shape[1]
|
||||
else:
|
||||
crossattn_max_len = comfy_samplers.lcm(crossattn_max_len, c.shape[1])
|
||||
c_crossattn.append(c)
|
||||
if "c_concat" in x:
|
||||
c_concat.append(x["c_concat"])
|
||||
if "c_adm" in x:
|
||||
c_adm.append(x["c_adm"])
|
||||
out = {}
|
||||
for k in temp:
|
||||
conds = temp[k]
|
||||
out[k] = conds[0].concat(conds[1:])
|
||||
c_crossattn_out = []
|
||||
for c in c_crossattn:
|
||||
if c.shape[1] < crossattn_max_len:
|
||||
c = c.repeat(1, crossattn_max_len // c.shape[1], 1) # padding with repeat doesn't change result
|
||||
c_crossattn_out.append(c)
|
||||
|
||||
if len(c_crossattn_out) > 0:
|
||||
out["c_crossattn"] = torch.cat(c_crossattn_out)
|
||||
if len(c_concat) > 0:
|
||||
out["c_concat"] = torch.cat(c_concat)
|
||||
if len(c_adm) > 0:
|
||||
out["c_adm"] = torch.cat(c_adm)
|
||||
return out
|
||||
|
||||
def calc_cond_uncond_batch(model, cond, uncond, x_in, timestep, model_options):
|
||||
def calc_cond_uncond_batch(
|
||||
model_function, cond, uncond, x_in, timestep, max_total_area, cond_concat_in, model_options
|
||||
):
|
||||
out_cond = torch.zeros_like(x_in)
|
||||
out_count = torch.ones_like(x_in) * 1e-37
|
||||
out_count = torch.ones_like(x_in) / 100000.0
|
||||
|
||||
out_uncond = torch.zeros_like(x_in)
|
||||
out_uncond_count = torch.ones_like(x_in) * 1e-37
|
||||
out_uncond_count = torch.ones_like(x_in) / 100000.0
|
||||
|
||||
COND = 0
|
||||
UNCOND = 1
|
||||
|
||||
to_run = []
|
||||
for x in cond:
|
||||
p = get_area_and_mult(x, x_in, timestep)
|
||||
p = get_area_and_mult(x, x_in, cond_concat_in, timestep)
|
||||
if p is None:
|
||||
continue
|
||||
|
||||
to_run += [(p, COND)]
|
||||
if uncond is not None:
|
||||
for x in uncond:
|
||||
p = get_area_and_mult(x, x_in, timestep)
|
||||
p = get_area_and_mult(x, x_in, cond_concat_in, timestep)
|
||||
if p is None:
|
||||
continue
|
||||
|
||||
@@ -236,11 +281,9 @@ def __sliding_sample_factory(ctx: SlidingContext):
|
||||
to_batch_temp.reverse()
|
||||
to_batch = to_batch_temp[:1]
|
||||
|
||||
free_memory = model_management.get_free_memory(x_in.device)
|
||||
for i in range(1, len(to_batch_temp) + 1):
|
||||
batch_amount = to_batch_temp[: len(to_batch_temp) // i]
|
||||
input_shape = [len(batch_amount) * first_shape[0]] + list(first_shape)[1:]
|
||||
if model.memory_required(input_shape) < free_memory:
|
||||
if len(batch_amount) * first_shape[0] * first_shape[2] * first_shape[3] < max_total_area:
|
||||
to_batch = batch_amount
|
||||
break
|
||||
|
||||
@@ -290,11 +333,11 @@ def __sliding_sample_factory(ctx: SlidingContext):
|
||||
|
||||
if "model_function_wrapper" in model_options:
|
||||
output = model_options["model_function_wrapper"](
|
||||
model.apply_model,
|
||||
model_function,
|
||||
{"input": input_x, "timestep": timestep_, "c": c, "cond_or_uncond": cond_or_uncond},
|
||||
).chunk(batch_chunks)
|
||||
else:
|
||||
output = model.apply_model(input_x, timestep_, **c).chunk(batch_chunks)
|
||||
output = model_function(input_x, timestep_, **c).chunk(batch_chunks)
|
||||
del input_x
|
||||
|
||||
for o in range(batch_chunks):
|
||||
@@ -318,11 +361,14 @@ def __sliding_sample_factory(ctx: SlidingContext):
|
||||
del out_count
|
||||
out_uncond /= out_uncond_count
|
||||
del out_uncond_count
|
||||
|
||||
return out_cond, out_uncond
|
||||
|
||||
# sliding_calc_cond_uncond_batch inspired by ashen's initial hack for 16-frame sliding context:
|
||||
# https://github.com/comfyanonymous/ComfyUI/compare/master...ashen-sensored:ComfyUI:master
|
||||
def sliding_calc_cond_uncond_batch(model, cond, uncond, x_in, timestep, model_options):
|
||||
def sliding_calc_cond_uncond_batch(
|
||||
model_function, cond, uncond, x_in, timestep, max_total_area, cond_concat_in, model_options
|
||||
):
|
||||
# figure out how input is split
|
||||
axes_factor = x.size(0) // ctx.video_length
|
||||
|
||||
@@ -338,29 +384,37 @@ def __sliding_sample_factory(ctx: SlidingContext):
|
||||
control.full_latent_length = ctx.video_length
|
||||
control.context_length = ctx.context_length
|
||||
|
||||
def get_resized_cond(cond_in: List[Dict], full_idxs) -> list:
|
||||
def get_resized_cond(cond_in, full_idxs) -> list:
|
||||
# reuse or resize cond items to match context requirements
|
||||
resized_cond = []
|
||||
# cond object is a list containing a list - outer list is irrelevant, so just loop through it
|
||||
for actual_cond in cond_in:
|
||||
new_cond_item = actual_cond.copy()
|
||||
for key, cond_item in new_cond_item.items():
|
||||
resized_actual_cond = []
|
||||
# now we are in the inner list - index 0 is tensor, index 1 is dictionary
|
||||
for cond_idx, cond_item in enumerate(actual_cond):
|
||||
if isinstance(cond_item, Tensor):
|
||||
# check that tensor is the expected length - x.size(0)
|
||||
if cond_item.size(0) == x.size(0):
|
||||
pass
|
||||
# if so, it's subsetting time - tell controls the expected indeces so they can handle them
|
||||
actual_cond_item = cond_item[full_idxs]
|
||||
new_cond_item[key] = actual_cond_item
|
||||
elif key == "control":
|
||||
control_item = cond_item
|
||||
if hasattr(control_item, "sub_idxs"):
|
||||
prepare_control_objects(control_item, full_idxs)
|
||||
resized_actual_cond.append(actual_cond_item)
|
||||
else:
|
||||
raise ValueError(
|
||||
f"Control type {type(control_item).__name__} may not support required features for sliding context window; use Control objects from Kosinkadink/Advanced-ControlNet nodes."
|
||||
)
|
||||
new_cond_item[key] = cond_item
|
||||
resized_cond.append(new_cond_item)
|
||||
resized_actual_cond.append(cond_item)
|
||||
elif isinstance(cond_item, dict):
|
||||
# when in dictionary, look for control
|
||||
if "control" in cond_item:
|
||||
control_item = cond_item["control"]
|
||||
if hasattr(control_item, "sub_idxs"):
|
||||
prepare_control_objects(control_item, full_idxs)
|
||||
else:
|
||||
raise ValueError(
|
||||
f"Control type {type(control_item).__name__} may not support required features for sliding context window; use Control objects from Kosinkadink/Advanced-ControlNet nodes."
|
||||
)
|
||||
resized_actual_cond.append(cond_item)
|
||||
else:
|
||||
resized_actual_cond.append(cond_item)
|
||||
resized_cond.append(resized_actual_cond)
|
||||
return resized_cond
|
||||
|
||||
# perform calc_cond_uncond_batch per context window
|
||||
@@ -383,13 +437,16 @@ def __sliding_sample_factory(ctx: SlidingContext):
|
||||
sub_timestep = timestep[full_idxs]
|
||||
sub_cond = get_resized_cond(cond, full_idxs) if cond is not None else None
|
||||
sub_uncond = get_resized_cond(uncond, full_idxs) if uncond is not None else None
|
||||
sub_cond_concat = get_resized_cond(cond_concat, full_idxs) if cond_concat is not None else None
|
||||
|
||||
sub_cond_out, sub_uncond_out = calc_cond_uncond_batch(
|
||||
model,
|
||||
model_function,
|
||||
sub_cond,
|
||||
sub_uncond,
|
||||
sub_x,
|
||||
sub_timestep,
|
||||
max_total_area,
|
||||
sub_cond_concat,
|
||||
model_options,
|
||||
)
|
||||
|
||||
@@ -402,21 +459,17 @@ def __sliding_sample_factory(ctx: SlidingContext):
|
||||
uncond_final /= out_count_final
|
||||
return cond_final, uncond_final
|
||||
|
||||
max_total_area = model_management.maximum_batch_area()
|
||||
if math.isclose(cond_scale, 1.0):
|
||||
uncond = None
|
||||
|
||||
cond, uncond = sliding_calc_cond_uncond_batch(model, cond, uncond, x, timestep, model_options)
|
||||
cond, uncond = sliding_calc_cond_uncond_batch(
|
||||
model_function, cond, uncond, x, timestep, max_total_area, cond_concat, model_options
|
||||
)
|
||||
|
||||
if "sampler_cfg_function" in model_options:
|
||||
args = {
|
||||
"cond": x - cond,
|
||||
"uncond": x - uncond,
|
||||
"cond_scale": cond_scale,
|
||||
"timestep": timestep,
|
||||
"input": x,
|
||||
"sigma": timestep,
|
||||
}
|
||||
return x - model_options["sampler_cfg_function"](args)
|
||||
args = {"cond": cond, "uncond": uncond, "cond_scale": cond_scale, "timestep": timestep}
|
||||
return model_options["sampler_cfg_function"](args)
|
||||
else:
|
||||
return uncond + (cond - uncond) * cond_scale
|
||||
|
||||
@@ -424,10 +477,6 @@ def __sliding_sample_factory(ctx: SlidingContext):
|
||||
|
||||
|
||||
def inject_sampling_function(ctx: SlidingContext):
|
||||
global orig_comfy_sample, orig_sampling_function
|
||||
orig_comfy_sample = comfy.sample.sample
|
||||
orig_sampling_function = comfy_samplers.sampling_function
|
||||
|
||||
(sample, sampling_function) = __sliding_sample_factory(ctx)
|
||||
comfy.sample.sample = sample
|
||||
comfy_samplers.sampling_function = sampling_function
|
||||
|
||||
@@ -9,7 +9,7 @@ class ContextSchedules:
|
||||
UNIFORM_CONSTANT = "uniform_constant"
|
||||
UNIFORM_V2 = "uniform v2"
|
||||
|
||||
CONTEXT_SCHEDULE_LIST = [UNIFORM]
|
||||
CONTEXT_SCHEDULE_LIST = [UNIFORM, UNIFORM_V2]
|
||||
|
||||
|
||||
# Returns fraction that has denominator that is a power of 2
|
||||
@@ -142,12 +142,14 @@ def uniform_constant(
|
||||
# yield if not skipped
|
||||
yield to_yield
|
||||
|
||||
|
||||
def get_context_scheduler(name: str) -> Callable:
|
||||
if name == ContextSchedules.UNIFORM:
|
||||
return uniform
|
||||
elif name == ContextSchedules.UNIFORM_CONSTANT:
|
||||
return uniform_constant
|
||||
elif name == ContextSchedules.UNIFORM_V2:
|
||||
return uniform_v2
|
||||
else:
|
||||
raise ValueError(f"Unknown context_overlap policy {name}")
|
||||
match name:
|
||||
case ContextSchedules.UNIFORM:
|
||||
return uniform
|
||||
case ContextSchedules.UNIFORM_CONSTANT:
|
||||
return uniform_constant
|
||||
case ContextSchedules.UNIFORM_V2:
|
||||
return uniform_v2
|
||||
case _:
|
||||
raise ValueError(f"Unknown context_overlap policy {name}")
|
||||
|
||||
+3
-22
@@ -1,32 +1,13 @@
|
||||
import sys
|
||||
import torch
|
||||
import numpy as np
|
||||
import subprocess
|
||||
from PIL import Image
|
||||
|
||||
|
||||
from .logger import logger
|
||||
|
||||
# Tensor to PIL
|
||||
def tensor2pil(image):
|
||||
return Image.fromarray(np.clip(255.0 * image.cpu().numpy().squeeze(), 0, 255).astype(np.uint8))
|
||||
|
||||
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)
|
||||
|
||||
|
||||
def ensure_opencv():
|
||||
if "python_embeded" in sys.executable or "python_embedded" in sys.executable:
|
||||
pip_install = [sys.executable, "-s", "-m", "pip", "install"]
|
||||
else:
|
||||
pip_install = [sys.executable, "-m", "pip", "install"]
|
||||
|
||||
try:
|
||||
import cv2
|
||||
except Exception as e:
|
||||
try:
|
||||
subprocess.check_call(pip_install + ['opencv-python'])
|
||||
except:
|
||||
logger.error(f"Failed to install 'opencv-python'. Please, install manually.")
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
opencv-python
|
||||
+154
-237
@@ -1,245 +1,162 @@
|
||||
import { app, ANIM_PREVIEW_WIDGET } from '../../../scripts/app.js';
|
||||
import { app } from "../../../scripts/app.js";
|
||||
import { api } from "../../../scripts/api.js";
|
||||
import { $el } from '../../../scripts/ui.js';
|
||||
import { createImageHost } from "../../../scripts/ui/imagePreview.js"
|
||||
|
||||
const URL_REGEX = /^(https?:\/\/|\/view\?|data:image\/)/;
|
||||
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 style = `
|
||||
.comfy-img-preview video {
|
||||
object-fit: contain;
|
||||
width: var(--comfy-img-preview-width);
|
||||
height: var(--comfy-img-preview-height);
|
||||
}
|
||||
`;
|
||||
|
||||
export function chainCallback(object, property, callback) {
|
||||
if (object == undefined) {
|
||||
//This should not happen.
|
||||
console.error("Tried to add callback to non-existant object");
|
||||
return;
|
||||
}
|
||||
if (property in object) {
|
||||
const callback_orig = object[property];
|
||||
object[property] = function () {
|
||||
const r = callback_orig.apply(this, arguments);
|
||||
callback.apply(this, arguments);
|
||||
return r;
|
||||
};
|
||||
} else {
|
||||
object[property] = callback;
|
||||
}
|
||||
};
|
||||
|
||||
export function formatUploadedUrl(params) {
|
||||
if (params.url) {
|
||||
return params.url;
|
||||
}
|
||||
|
||||
params = { ...params };
|
||||
|
||||
if (!params.filename && params.name) {
|
||||
params.filename = params.name;
|
||||
delete params.name;
|
||||
}
|
||||
|
||||
return api.apiURL("/view?" + new URLSearchParams(params));
|
||||
};
|
||||
|
||||
export function addVideoPreview(nodeType, options = {}) {
|
||||
const createVideoNode = (url) => {
|
||||
return new Promise((cb) => {
|
||||
const videoEl = document.createElement('video');
|
||||
Object.defineProperty(videoEl, 'naturalWidth', {
|
||||
get: () => {
|
||||
return videoEl.videoWidth;
|
||||
},
|
||||
});
|
||||
Object.defineProperty(videoEl, 'naturalHeight', {
|
||||
get: () => {
|
||||
return videoEl.videoHeight;
|
||||
},
|
||||
});
|
||||
videoEl.addEventListener('loadedmetadata', () => {
|
||||
videoEl.controls = false;
|
||||
videoEl.loop = true;
|
||||
videoEl.muted = true;
|
||||
cb(videoEl);
|
||||
});
|
||||
videoEl.addEventListener('error', () => {
|
||||
cb();
|
||||
});
|
||||
videoEl.src = url;
|
||||
});
|
||||
};
|
||||
|
||||
const createImageNode = (url) => {
|
||||
return new Promise((cb) => {
|
||||
const imgEl = document.createElement('img');
|
||||
imgEl.onload = () => {
|
||||
cb(imgEl);
|
||||
};
|
||||
imgEl.addEventListener('error', () => {
|
||||
cb();
|
||||
});
|
||||
imgEl.src = url;
|
||||
});
|
||||
};
|
||||
|
||||
nodeType.prototype.onDrawBackground = function (ctx) {
|
||||
if (this.flags.collapsed) return;
|
||||
|
||||
let imageURLs = (this.images ?? []).map((i) =>
|
||||
typeof i === 'string' ? i : formatUploadedUrl(i),
|
||||
);
|
||||
let imagesChanged = false;
|
||||
|
||||
if (JSON.stringify(this.displayingImages) !== JSON.stringify(imageURLs)) {
|
||||
this.displayingImages = imageURLs;
|
||||
imagesChanged = true;
|
||||
}
|
||||
|
||||
if (!imagesChanged) return;
|
||||
if (!imageURLs.length) {
|
||||
this.imgs = null;
|
||||
this.animatedImages = false;
|
||||
return;
|
||||
}
|
||||
|
||||
const promises = imageURLs.map((url) => {
|
||||
if (url.startsWith('/view')) {
|
||||
url = window.location.origin + url;
|
||||
}
|
||||
|
||||
const u = new URL(url);
|
||||
const filename =
|
||||
u.searchParams.get('filename') || u.searchParams.get('name') || u.pathname.split('/').pop();
|
||||
const ext = filename.split('.').pop();
|
||||
const format = ['gif', 'webp', 'avif'].includes(ext) ? 'image' : 'video';
|
||||
if (format === 'video') {
|
||||
return createVideoNode(url);
|
||||
} else {
|
||||
return createImageNode(url);
|
||||
}
|
||||
});
|
||||
|
||||
Promise.all(promises)
|
||||
.then((imgs) => {
|
||||
this.imgs = imgs.filter(Boolean);
|
||||
})
|
||||
.then(() => {
|
||||
if (!this.imgs.length) return;
|
||||
|
||||
this.animatedImages = true;
|
||||
const widgetIdx = this.widgets?.findIndex((w) => w.name === ANIM_PREVIEW_WIDGET);
|
||||
|
||||
// Instead of using the canvas we'll use a IMG
|
||||
if (widgetIdx > -1) {
|
||||
// Replace content
|
||||
const widget = this.widgets[widgetIdx];
|
||||
widget.options.host.updateImages(this.imgs);
|
||||
} else {
|
||||
const host = createImageHost(this);
|
||||
this.setSizeForImage(true);
|
||||
const widget = this.addDOMWidget(ANIM_PREVIEW_WIDGET, 'img', host.el, {
|
||||
host,
|
||||
getHeight: host.getHeight,
|
||||
onDraw: host.onDraw,
|
||||
hideOnZoom: false,
|
||||
});
|
||||
widget.serializeValue = () => ({
|
||||
height: host.el.clientHeight,
|
||||
});
|
||||
// widget.computeSize = (w) => ([w, 220]);
|
||||
|
||||
widget.options.host.updateImages(this.imgs);
|
||||
}
|
||||
|
||||
this.imgs.forEach((img) => {
|
||||
if (img instanceof HTMLVideoElement) {
|
||||
img.muted = true;
|
||||
img.autoplay = true;
|
||||
img.play();
|
||||
}
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
const { textWidget, comboWidget } = options;
|
||||
|
||||
if (textWidget) {
|
||||
chainCallback(nodeType.prototype, 'onNodeCreated', function () {
|
||||
const pathWidget = this.widgets.find((w) => w.name === textWidget);
|
||||
pathWidget._value = pathWidget.value;
|
||||
Object.defineProperty(pathWidget, 'value', {
|
||||
set: (value) => {
|
||||
pathWidget._value = value;
|
||||
pathWidget.inputEl.value = value;
|
||||
this.images = (value ?? '').split('\n').filter((url) => URL_REGEX.test(url));
|
||||
},
|
||||
get: () => {
|
||||
return pathWidget._value;
|
||||
},
|
||||
});
|
||||
pathWidget.inputEl.addEventListener('change', (e) => {
|
||||
const value = e.target.value;
|
||||
pathWidget._value = value;
|
||||
this.images = (value ?? '').split('\n').filter((url) => URL_REGEX.test(url));
|
||||
});
|
||||
|
||||
// Set value to ensure preview displays on initial add.
|
||||
pathWidget.value = pathWidget._value;
|
||||
});
|
||||
}
|
||||
|
||||
if (comboWidget) {
|
||||
chainCallback(nodeType.prototype, 'onNodeCreated', function () {
|
||||
const pathWidget = this.widgets.find((w) => w.name === comboWidget);
|
||||
pathWidget._value = pathWidget.value;
|
||||
Object.defineProperty(pathWidget, 'value', {
|
||||
set: (value) => {
|
||||
pathWidget._value = value;
|
||||
if (!value) {
|
||||
return this.images = []
|
||||
}
|
||||
|
||||
const parts = value.split("/")
|
||||
const filename = parts.pop()
|
||||
const subfolder = parts.join("/")
|
||||
const extension = filename.split(".").pop();
|
||||
const format = (["gif", "webp", "avif"].includes(extension)) ? 'image' : 'video'
|
||||
this.images = [formatUploadedUrl({ filename, subfolder, type: "input", format: format })]
|
||||
},
|
||||
get: () => {
|
||||
return pathWidget._value;
|
||||
},
|
||||
});
|
||||
|
||||
// Set value to ensure preview displays on initial add.
|
||||
pathWidget.value = pathWidget._value;
|
||||
});
|
||||
}
|
||||
|
||||
chainCallback(nodeType.prototype, "onExecuted", function (message) {
|
||||
if (message?.videos) {
|
||||
this.images = message?.videos.map(formatUploadedUrl);
|
||||
}
|
||||
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),
|
||||
});
|
||||
}
|
||||
|
||||
app.registerExtension({
|
||||
name: "AnimateDiff.VideoPreview",
|
||||
init() {
|
||||
$el('style', {
|
||||
textContent: style,
|
||||
parent: document.head,
|
||||
});
|
||||
},
|
||||
async beforeRegisterNodeDef(nodeType, nodeData) {
|
||||
if (nodeData.name !== "AnimateDiffCombine") {
|
||||
return;
|
||||
}
|
||||
export const hasWidgets = (node) => {
|
||||
if (!node.widgets || !node.widgets?.[Symbol.iterator]) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
};
|
||||
|
||||
addVideoPreview(nodeType);
|
||||
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);
|
||||
|
||||
+169
-71
@@ -1,90 +1,188 @@
|
||||
import { app } from "../../../scripts/app.js";
|
||||
import { api } from "../../../scripts/api.js";
|
||||
import { ComfyWidgets } from "../../../scripts/widgets.js";
|
||||
|
||||
import {
|
||||
chainCallback,
|
||||
addVideoPreview,
|
||||
} from "./vid_preview.js";
|
||||
const supportedVideoTypes = [
|
||||
"image/gif",
|
||||
"video/webm",
|
||||
"video/mp4",
|
||||
"video/mov",
|
||||
];
|
||||
|
||||
async function uploadFile(file) {
|
||||
try {
|
||||
// Wrap file in formdata so it includes filename
|
||||
const body = new FormData();
|
||||
const new_file = new File([file], file.name, {
|
||||
type: file.type,
|
||||
lastModified: file.lastModified,
|
||||
});
|
||||
body.append("image", new_file);
|
||||
body.append("subfolder", "video");
|
||||
const resp = await api.fetchApi("/upload/image", {
|
||||
method: "POST",
|
||||
body,
|
||||
});
|
||||
const VIDEOUPLOAD = (node, inputName, inputData, app) => {
|
||||
const previewWidget = "ad_video_preview";
|
||||
const videoWidget = node.widgets.find((w) => w.name === "video");
|
||||
let uploadWidget;
|
||||
|
||||
if (resp.status === 200 || resp.status === 201) {
|
||||
return resp.json();
|
||||
} else {
|
||||
alert(`Upload failed: ${resp.statusText}`);
|
||||
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);
|
||||
}
|
||||
} catch (error) {
|
||||
alert(`Upload failed: ${error}`);
|
||||
}
|
||||
}
|
||||
|
||||
function addUploadWidget(nodeType, widgetName) {
|
||||
chainCallback(nodeType.prototype, "onNodeCreated", function () {
|
||||
const pathWidget = this.widgets.find((w) => w.name === widgetName);
|
||||
if (pathWidget.element) {
|
||||
pathWidget.options.getMinHeight = () => 50;
|
||||
pathWidget.options.getMaxHeight = () => 150;
|
||||
}
|
||||
|
||||
const fileInput = document.createElement("input");
|
||||
chainCallback(this, "onRemoved", () => {
|
||||
fileInput?.remove();
|
||||
});
|
||||
|
||||
Object.assign(fileInput, {
|
||||
type: "file",
|
||||
accept: "video/webm,video/mp4,video/mkv,image/gif,image/webp",
|
||||
style: "display: none",
|
||||
onchange: async () => {
|
||||
if (fileInput.files.length) {
|
||||
const params = await uploadFile(fileInput.files[0]);
|
||||
if (!params) {
|
||||
// upload failed and file can not be added to options
|
||||
return;
|
||||
}
|
||||
|
||||
fileInput.value = "";
|
||||
const filename = [params.subfolder, params.name || params.filename].filter(Boolean).join('/')
|
||||
pathWidget.value = filename;
|
||||
pathWidget.options.values.push(filename);
|
||||
}
|
||||
const ext = name.substring(name.lastIndexOf(".") + 1);
|
||||
const format = supportedVideoTypes.find((t) => t.endsWith(ext));
|
||||
node.videos = [
|
||||
{
|
||||
filename: name,
|
||||
type: "input",
|
||||
subfolder: subfolder,
|
||||
format,
|
||||
},
|
||||
});
|
||||
];
|
||||
};
|
||||
|
||||
document.body.append(fileInput);
|
||||
let uploadWidget = this.addWidget(
|
||||
"button",
|
||||
"choose video to upload",
|
||||
"image",
|
||||
() => {
|
||||
app.canvas.node_widget = null;
|
||||
fileInput.click();
|
||||
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;
|
||||
}
|
||||
);
|
||||
uploadWidget.options.serialize = false;
|
||||
|
||||
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) {
|
||||
addUploadWidget(nodeType, 'video');
|
||||
addVideoPreview(nodeType, { comboWidget: 'video' });
|
||||
nodeData.input.required.upload = ["VIDEOUPLOAD"];
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
+58
-62
@@ -738,10 +738,10 @@
|
||||
60,
|
||||
140
|
||||
],
|
||||
"size": {
|
||||
"0": 340,
|
||||
"1": 110
|
||||
},
|
||||
"size": [
|
||||
340,
|
||||
110
|
||||
],
|
||||
"flags": {},
|
||||
"order": 10,
|
||||
"mode": 0,
|
||||
@@ -780,7 +780,7 @@
|
||||
],
|
||||
"size": {
|
||||
"0": 310,
|
||||
"1": 350
|
||||
"1": 330
|
||||
},
|
||||
"flags": {},
|
||||
"order": 30,
|
||||
@@ -812,11 +812,6 @@
|
||||
"name": "latent_image",
|
||||
"type": "LATENT",
|
||||
"link": 80
|
||||
},
|
||||
{
|
||||
"name": "sliding_window_opts",
|
||||
"type": "SLIDING_WINDOW_OPTS",
|
||||
"link": null
|
||||
}
|
||||
],
|
||||
"outputs": [
|
||||
@@ -1044,10 +1039,10 @@
|
||||
2140,
|
||||
140
|
||||
],
|
||||
"size": {
|
||||
"0": 360,
|
||||
"1": 552
|
||||
},
|
||||
"size": [
|
||||
360,
|
||||
552
|
||||
],
|
||||
"flags": {},
|
||||
"order": 32,
|
||||
"mode": 0,
|
||||
@@ -1075,7 +1070,8 @@
|
||||
true,
|
||||
"AnimateDiff",
|
||||
"image/gif",
|
||||
true
|
||||
true,
|
||||
"/view?filename=AnimateDiff_00090_.gif&subfolder=&type=output&format=image%2Fgif"
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -1114,6 +1110,49 @@
|
||||
"color": "#1a572e",
|
||||
"bgcolor": "#2e6b42"
|
||||
},
|
||||
{
|
||||
"id": 44,
|
||||
"type": "VAEDecode",
|
||||
"pos": [
|
||||
1900,
|
||||
520
|
||||
],
|
||||
"size": {
|
||||
"0": 210,
|
||||
"1": 46
|
||||
},
|
||||
"flags": {},
|
||||
"order": 31,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "samples",
|
||||
"type": "LATENT",
|
||||
"link": 81
|
||||
},
|
||||
{
|
||||
"name": "vae",
|
||||
"type": "VAE",
|
||||
"link": 82
|
||||
}
|
||||
],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "IMAGE",
|
||||
"type": "IMAGE",
|
||||
"links": [
|
||||
172
|
||||
],
|
||||
"shape": 3,
|
||||
"slot_index": 0
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"Node name for S&R": "VAEDecode"
|
||||
},
|
||||
"color": "#2e571a",
|
||||
"bgcolor": "#426b2e"
|
||||
},
|
||||
{
|
||||
"id": 4,
|
||||
"type": "CheckpointLoaderSimple",
|
||||
@@ -1169,10 +1208,10 @@
|
||||
420,
|
||||
530
|
||||
],
|
||||
"size": {
|
||||
"0": 260,
|
||||
"1": 170
|
||||
},
|
||||
"size": [
|
||||
260,
|
||||
170
|
||||
],
|
||||
"flags": {},
|
||||
"order": 28,
|
||||
"mode": 0,
|
||||
@@ -1439,49 +1478,6 @@
|
||||
],
|
||||
"color": "#1a572e",
|
||||
"bgcolor": "#2e6b42"
|
||||
},
|
||||
{
|
||||
"id": 44,
|
||||
"type": "VAEDecode",
|
||||
"pos": [
|
||||
1898,
|
||||
541
|
||||
],
|
||||
"size": {
|
||||
"0": 210,
|
||||
"1": 46
|
||||
},
|
||||
"flags": {},
|
||||
"order": 31,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "samples",
|
||||
"type": "LATENT",
|
||||
"link": 81
|
||||
},
|
||||
{
|
||||
"name": "vae",
|
||||
"type": "VAE",
|
||||
"link": 82
|
||||
}
|
||||
],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "IMAGE",
|
||||
"type": "IMAGE",
|
||||
"links": [
|
||||
172
|
||||
],
|
||||
"shape": 3,
|
||||
"slot_index": 0
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"Node name for S&R": "VAEDecode"
|
||||
},
|
||||
"color": "#2e571a",
|
||||
"bgcolor": "#426b2e"
|
||||
}
|
||||
],
|
||||
"links": [
|
||||
|
||||
+290
-290
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"last_node_id": 107,
|
||||
"last_link_id": 199,
|
||||
"last_node_id": 106,
|
||||
"last_link_id": 189,
|
||||
"nodes": [
|
||||
{
|
||||
"id": 16,
|
||||
@@ -21,7 +21,7 @@
|
||||
"name": "MOTION_MODULE",
|
||||
"type": "MOTION_MODULE",
|
||||
"links": [
|
||||
193
|
||||
78
|
||||
],
|
||||
"shape": 3,
|
||||
"slot_index": 0
|
||||
@@ -77,10 +77,10 @@
|
||||
1240,
|
||||
140
|
||||
],
|
||||
"size": {
|
||||
"0": 360,
|
||||
"1": 732
|
||||
},
|
||||
"size": [
|
||||
360,
|
||||
732
|
||||
],
|
||||
"flags": {},
|
||||
"order": 13,
|
||||
"mode": 0,
|
||||
@@ -108,7 +108,8 @@
|
||||
true,
|
||||
"AnimateDiff",
|
||||
"image/gif",
|
||||
true
|
||||
true,
|
||||
"/view?filename=AnimateDiff_00092_.gif&subfolder=&type=output&format=image%2Fgif"
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -130,7 +131,7 @@
|
||||
"name": "MODEL",
|
||||
"type": "MODEL",
|
||||
"links": [
|
||||
194
|
||||
79
|
||||
],
|
||||
"slot_index": 0
|
||||
},
|
||||
@@ -166,10 +167,10 @@
|
||||
60,
|
||||
300
|
||||
],
|
||||
"size": {
|
||||
"0": 310,
|
||||
"1": 100
|
||||
},
|
||||
"size": [
|
||||
310,
|
||||
100
|
||||
],
|
||||
"flags": {},
|
||||
"order": 6,
|
||||
"mode": 0,
|
||||
@@ -206,10 +207,10 @@
|
||||
60,
|
||||
140
|
||||
],
|
||||
"size": {
|
||||
"0": 310,
|
||||
"1": 110
|
||||
},
|
||||
"size": [
|
||||
310,
|
||||
110
|
||||
],
|
||||
"flags": {},
|
||||
"order": 5,
|
||||
"mode": 0,
|
||||
@@ -239,6 +240,94 @@
|
||||
"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",
|
||||
@@ -246,10 +335,10 @@
|
||||
471,
|
||||
275
|
||||
],
|
||||
"size": {
|
||||
"0": 300,
|
||||
"1": 170
|
||||
},
|
||||
"size": [
|
||||
300,
|
||||
170
|
||||
],
|
||||
"flags": {},
|
||||
"order": 9,
|
||||
"mode": 0,
|
||||
@@ -280,7 +369,7 @@
|
||||
"name": "positive",
|
||||
"type": "CONDITIONING",
|
||||
"links": [
|
||||
195
|
||||
176
|
||||
],
|
||||
"shape": 3,
|
||||
"slot_index": 0
|
||||
@@ -289,7 +378,7 @@
|
||||
"name": "negative",
|
||||
"type": "CONDITIONING",
|
||||
"links": [
|
||||
196
|
||||
180
|
||||
],
|
||||
"shape": 3,
|
||||
"slot_index": 1
|
||||
@@ -306,6 +395,50 @@
|
||||
"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",
|
||||
@@ -357,10 +490,10 @@
|
||||
520,
|
||||
630
|
||||
],
|
||||
"size": {
|
||||
"0": 210,
|
||||
"1": 80
|
||||
},
|
||||
"size": [
|
||||
210,
|
||||
80
|
||||
],
|
||||
"flags": {},
|
||||
"order": 10,
|
||||
"mode": 0,
|
||||
@@ -368,7 +501,7 @@
|
||||
{
|
||||
"name": "width",
|
||||
"type": "INT",
|
||||
"link": 190,
|
||||
"link": 189,
|
||||
"widget": {
|
||||
"name": "width",
|
||||
"config": [
|
||||
@@ -385,7 +518,7 @@
|
||||
{
|
||||
"name": "height",
|
||||
"type": "INT",
|
||||
"link": 191,
|
||||
"link": 188,
|
||||
"widget": {
|
||||
"name": "height",
|
||||
"config": [
|
||||
@@ -405,7 +538,7 @@
|
||||
"name": "LATENT",
|
||||
"type": "LATENT",
|
||||
"links": [
|
||||
197
|
||||
80
|
||||
],
|
||||
"shape": 3,
|
||||
"slot_index": 0
|
||||
@@ -422,6 +555,62 @@
|
||||
"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",
|
||||
@@ -429,10 +618,10 @@
|
||||
-280,
|
||||
540
|
||||
],
|
||||
"size": {
|
||||
"0": 310,
|
||||
"1": 60
|
||||
},
|
||||
"size": [
|
||||
310,
|
||||
60
|
||||
],
|
||||
"flags": {},
|
||||
"order": 4,
|
||||
"mode": 0,
|
||||
@@ -471,10 +660,10 @@
|
||||
70,
|
||||
830
|
||||
],
|
||||
"size": {
|
||||
"0": 530,
|
||||
"1": 420
|
||||
},
|
||||
"size": [
|
||||
530,
|
||||
420
|
||||
],
|
||||
"flags": {},
|
||||
"order": 8,
|
||||
"mode": 0,
|
||||
@@ -498,10 +687,10 @@
|
||||
670,
|
||||
830
|
||||
],
|
||||
"size": {
|
||||
"0": 530,
|
||||
"1": 420
|
||||
},
|
||||
"size": [
|
||||
530,
|
||||
420
|
||||
],
|
||||
"flags": {},
|
||||
"order": 14,
|
||||
"mode": 0,
|
||||
@@ -517,195 +706,6 @@
|
||||
},
|
||||
"color": "#1a5757",
|
||||
"bgcolor": "#2e6b6b"
|
||||
},
|
||||
{
|
||||
"id": 104,
|
||||
"type": "ImageSizeAndBatchSize",
|
||||
"pos": [
|
||||
258,
|
||||
631
|
||||
],
|
||||
"size": {
|
||||
"0": 226.8000030517578,
|
||||
"1": 80
|
||||
},
|
||||
"flags": {},
|
||||
"order": 7,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "image",
|
||||
"type": "IMAGE",
|
||||
"link": 182
|
||||
}
|
||||
],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "width",
|
||||
"type": "INT",
|
||||
"links": [
|
||||
190
|
||||
],
|
||||
"shape": 3,
|
||||
"slot_index": 0
|
||||
},
|
||||
{
|
||||
"name": "height",
|
||||
"type": "INT",
|
||||
"links": [
|
||||
191
|
||||
],
|
||||
"shape": 3,
|
||||
"slot_index": 1
|
||||
},
|
||||
{
|
||||
"name": "batch_size",
|
||||
"type": "INT",
|
||||
"links": [
|
||||
198
|
||||
],
|
||||
"shape": 3,
|
||||
"slot_index": 2
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"Node name for S&R": "ImageSizeAndBatchSize"
|
||||
},
|
||||
"color": "#1a5757",
|
||||
"bgcolor": "#2e6b6b"
|
||||
},
|
||||
{
|
||||
"id": 44,
|
||||
"type": "VAEDecode",
|
||||
"pos": [
|
||||
1000,
|
||||
548
|
||||
],
|
||||
"size": {
|
||||
"0": 210,
|
||||
"1": 46
|
||||
},
|
||||
"flags": {},
|
||||
"order": 12,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "samples",
|
||||
"type": "LATENT",
|
||||
"link": 199
|
||||
},
|
||||
{
|
||||
"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": 107,
|
||||
"type": "AnimateDiffSampler",
|
||||
"pos": [
|
||||
881,
|
||||
141
|
||||
],
|
||||
"size": [
|
||||
330,
|
||||
350
|
||||
],
|
||||
"flags": {},
|
||||
"order": 11,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "motion_module",
|
||||
"type": "MOTION_MODULE",
|
||||
"link": 193
|
||||
},
|
||||
{
|
||||
"name": "model",
|
||||
"type": "MODEL",
|
||||
"link": 194
|
||||
},
|
||||
{
|
||||
"name": "positive",
|
||||
"type": "CONDITIONING",
|
||||
"link": 195
|
||||
},
|
||||
{
|
||||
"name": "negative",
|
||||
"type": "CONDITIONING",
|
||||
"link": 196
|
||||
},
|
||||
{
|
||||
"name": "latent_image",
|
||||
"type": "LATENT",
|
||||
"link": 197
|
||||
},
|
||||
{
|
||||
"name": "sliding_window_opts",
|
||||
"type": "SLIDING_WINDOW_OPTS",
|
||||
"link": null
|
||||
},
|
||||
{
|
||||
"name": "frame_number",
|
||||
"type": "INT",
|
||||
"link": 198,
|
||||
"widget": {
|
||||
"name": "frame_number",
|
||||
"config": [
|
||||
"INT",
|
||||
{
|
||||
"default": 16,
|
||||
"min": 2,
|
||||
"max": 10000,
|
||||
"step": 1
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "LATENT",
|
||||
"type": "LATENT",
|
||||
"links": [
|
||||
199
|
||||
],
|
||||
"shape": 3,
|
||||
"slot_index": 0
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"Node name for S&R": "AnimateDiffSampler"
|
||||
},
|
||||
"widgets_values": [
|
||||
"default",
|
||||
16,
|
||||
0,
|
||||
"randomize",
|
||||
20,
|
||||
8,
|
||||
"euler",
|
||||
"normal",
|
||||
1
|
||||
]
|
||||
}
|
||||
],
|
||||
"links": [
|
||||
@@ -749,6 +749,38 @@
|
||||
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,
|
||||
@@ -765,6 +797,22 @@
|
||||
0,
|
||||
"IMAGE"
|
||||
],
|
||||
[
|
||||
176,
|
||||
39,
|
||||
0,
|
||||
41,
|
||||
2,
|
||||
"CONDITIONING"
|
||||
],
|
||||
[
|
||||
180,
|
||||
39,
|
||||
1,
|
||||
41,
|
||||
3,
|
||||
"CONDITIONING"
|
||||
],
|
||||
[
|
||||
181,
|
||||
103,
|
||||
@@ -781,6 +829,14 @@
|
||||
0,
|
||||
"IMAGE"
|
||||
],
|
||||
[
|
||||
185,
|
||||
104,
|
||||
2,
|
||||
41,
|
||||
5,
|
||||
"INT"
|
||||
],
|
||||
[
|
||||
186,
|
||||
103,
|
||||
@@ -798,76 +854,20 @@
|
||||
"IMAGE"
|
||||
],
|
||||
[
|
||||
190,
|
||||
188,
|
||||
104,
|
||||
0,
|
||||
20,
|
||||
0,
|
||||
"INT"
|
||||
],
|
||||
[
|
||||
191,
|
||||
104,
|
||||
1,
|
||||
20,
|
||||
1,
|
||||
"INT"
|
||||
],
|
||||
[
|
||||
193,
|
||||
16,
|
||||
0,
|
||||
107,
|
||||
0,
|
||||
"MOTION_MODULE"
|
||||
],
|
||||
[
|
||||
194,
|
||||
4,
|
||||
0,
|
||||
107,
|
||||
189,
|
||||
104,
|
||||
1,
|
||||
"MODEL"
|
||||
],
|
||||
[
|
||||
195,
|
||||
39,
|
||||
0,
|
||||
107,
|
||||
2,
|
||||
"CONDITIONING"
|
||||
],
|
||||
[
|
||||
196,
|
||||
39,
|
||||
1,
|
||||
107,
|
||||
3,
|
||||
"CONDITIONING"
|
||||
],
|
||||
[
|
||||
197,
|
||||
20,
|
||||
0,
|
||||
107,
|
||||
4,
|
||||
"LATENT"
|
||||
],
|
||||
[
|
||||
198,
|
||||
104,
|
||||
2,
|
||||
107,
|
||||
6,
|
||||
"INT"
|
||||
],
|
||||
[
|
||||
199,
|
||||
107,
|
||||
0,
|
||||
44,
|
||||
0,
|
||||
"LATENT"
|
||||
]
|
||||
],
|
||||
"groups": [],
|
||||
|
||||
@@ -195,10 +195,10 @@
|
||||
571,
|
||||
712
|
||||
],
|
||||
"size": {
|
||||
"0": 275.35137939453125,
|
||||
"1": 82
|
||||
},
|
||||
"size": [
|
||||
275.35137939453125,
|
||||
82
|
||||
],
|
||||
"flags": {},
|
||||
"order": 11,
|
||||
"mode": 0,
|
||||
@@ -315,7 +315,7 @@
|
||||
],
|
||||
"size": {
|
||||
"0": 315,
|
||||
"1": 350
|
||||
"1": 330
|
||||
},
|
||||
"flags": {},
|
||||
"order": 12,
|
||||
@@ -347,11 +347,6 @@
|
||||
"name": "latent_image",
|
||||
"type": "LATENT",
|
||||
"link": 47
|
||||
},
|
||||
{
|
||||
"name": "sliding_window_opts",
|
||||
"type": "SLIDING_WINDOW_OPTS",
|
||||
"link": null
|
||||
}
|
||||
],
|
||||
"outputs": [
|
||||
@@ -429,7 +424,7 @@
|
||||
],
|
||||
"size": {
|
||||
"0": 315,
|
||||
"1": 350
|
||||
"1": 330
|
||||
},
|
||||
"flags": {},
|
||||
"order": 6,
|
||||
@@ -461,11 +456,6 @@
|
||||
"name": "latent_image",
|
||||
"type": "LATENT",
|
||||
"link": 35
|
||||
},
|
||||
{
|
||||
"name": "sliding_window_opts",
|
||||
"type": "SLIDING_WINDOW_OPTS",
|
||||
"link": null
|
||||
}
|
||||
],
|
||||
"outputs": [
|
||||
@@ -528,6 +518,48 @@
|
||||
"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",
|
||||
@@ -572,13 +604,13 @@
|
||||
"id": 28,
|
||||
"type": "AnimateDiffCombine",
|
||||
"pos": [
|
||||
1505,
|
||||
-72
|
||||
1506,
|
||||
-60
|
||||
],
|
||||
"size": [
|
||||
321.19170831298857,
|
||||
513.0408935546875
|
||||
],
|
||||
"size": {
|
||||
"0": 321.19171142578125,
|
||||
"1": 513.0408935546875
|
||||
},
|
||||
"flags": {},
|
||||
"order": 10,
|
||||
"mode": 0,
|
||||
@@ -605,49 +637,9 @@
|
||||
0,
|
||||
true,
|
||||
"AnimateDiff",
|
||||
"image/gif",
|
||||
false
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 12,
|
||||
"type": "AnimateDiffCombine",
|
||||
"pos": [
|
||||
1504,
|
||||
481
|
||||
],
|
||||
"size": {
|
||||
"0": 325.7265625,
|
||||
"1": 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/gif",
|
||||
false
|
||||
"image/webp",
|
||||
false,
|
||||
"/view?filename=AnimateDiff_00032_.webp&subfolder=&type=output&format=image%2Fwebp"
|
||||
]
|
||||
}
|
||||
],
|
||||
|
||||
@@ -1,515 +0,0 @@
|
||||
{
|
||||
"last_node_id": 21,
|
||||
"last_link_id": 38,
|
||||
"nodes": [
|
||||
{
|
||||
"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
|
||||
],
|
||||
"slot_index": 0
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"Node name for S&R": "CLIPTextEncode"
|
||||
},
|
||||
"widgets_values": [
|
||||
"photo of coastline, rocks, storm weather, wind, waves, lightning, 8k uhd, dslr, soft lighting, high quality, film grain, Fujifilm XT3"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 8,
|
||||
"type": "VAEDecode",
|
||||
"pos": [
|
||||
1253,
|
||||
191
|
||||
],
|
||||
"size": {
|
||||
"0": 210,
|
||||
"1": 46
|
||||
},
|
||||
"flags": {},
|
||||
"order": 8,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "samples",
|
||||
"type": "LATENT",
|
||||
"link": 28
|
||||
},
|
||||
{
|
||||
"name": "vae",
|
||||
"type": "VAE",
|
||||
"link": 20
|
||||
}
|
||||
],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "IMAGE",
|
||||
"type": "IMAGE",
|
||||
"links": [
|
||||
19
|
||||
],
|
||||
"slot_index": 0
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"Node name for S&R": "VAEDecode"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": 12,
|
||||
"type": "AnimateDiffCombine",
|
||||
"pos": [
|
||||
1254,
|
||||
290
|
||||
],
|
||||
"size": [
|
||||
315,
|
||||
507
|
||||
],
|
||||
"flags": {},
|
||||
"order": 9,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "images",
|
||||
"type": "IMAGE",
|
||||
"link": 19
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"Node name for S&R": "AnimateDiffCombine"
|
||||
},
|
||||
"widgets_values": [
|
||||
8,
|
||||
0,
|
||||
false,
|
||||
"AnimateDiff",
|
||||
"image/gif",
|
||||
false,
|
||||
"/view?filename=AnimateDiff_00003_.gif&subfolder=&type=temp&format=image%2Fgif"
|
||||
]
|
||||
},
|
||||
{
|
||||
"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
|
||||
],
|
||||
"slot_index": 0
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"Node name for S&R": "CLIPTextEncode"
|
||||
},
|
||||
"widgets_values": [
|
||||
"blur, haze, deformed iris, deformed pupils, semi-realistic, cgi, 3d, render, sketch, cartoon, drawing, anime, mutated hands and fingers, deformed, distorted, disfigured, poorly drawn, bad anatomy, wrong anatomy, extra limb, missing limb, floating limbs, disconnected limbs, mutation, mutated, ugly, disgusting, amputation"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 20,
|
||||
"type": "EmptyLatentImage",
|
||||
"pos": [
|
||||
522,
|
||||
621
|
||||
],
|
||||
"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": 13,
|
||||
"type": "VAELoader",
|
||||
"pos": [
|
||||
28,
|
||||
223
|
||||
],
|
||||
"size": {
|
||||
"0": 315,
|
||||
"1": 58
|
||||
},
|
||||
"flags": {},
|
||||
"order": 1,
|
||||
"mode": 0,
|
||||
"outputs": [
|
||||
{
|
||||
"name": "VAE",
|
||||
"type": "VAE",
|
||||
"links": [
|
||||
20
|
||||
],
|
||||
"shape": 3,
|
||||
"slot_index": 0
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"Node name for S&R": "VAELoader"
|
||||
},
|
||||
"widgets_values": [
|
||||
"vae-ft-mse-840000-ema-pruned.safetensors"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 15,
|
||||
"type": "AnimateDiffSampler",
|
||||
"pos": [
|
||||
882,
|
||||
192
|
||||
],
|
||||
"size": {
|
||||
"0": 315,
|
||||
"1": 350
|
||||
},
|
||||
"flags": {},
|
||||
"order": 7,
|
||||
"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
|
||||
},
|
||||
{
|
||||
"name": "sliding_window_opts",
|
||||
"type": "SLIDING_WINDOW_OPTS",
|
||||
"link": null
|
||||
}
|
||||
],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "LATENT",
|
||||
"type": "LATENT",
|
||||
"links": [
|
||||
28
|
||||
],
|
||||
"shape": 3,
|
||||
"slot_index": 0
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"Node name for S&R": "AnimateDiffSampler"
|
||||
},
|
||||
"widgets_values": [
|
||||
"default",
|
||||
14,
|
||||
45987230,
|
||||
"fixed",
|
||||
25,
|
||||
7.5,
|
||||
"ddim",
|
||||
"ddim_uniform",
|
||||
1
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 16,
|
||||
"type": "AnimateDiffModuleLoader",
|
||||
"pos": [
|
||||
27,
|
||||
345
|
||||
],
|
||||
"size": {
|
||||
"0": 315,
|
||||
"1": 58
|
||||
},
|
||||
"flags": {},
|
||||
"order": 6,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "lora_stack",
|
||||
"type": "MOTION_LORA_STACK",
|
||||
"link": 38,
|
||||
"slot_index": 0
|
||||
}
|
||||
],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "MOTION_MODULE",
|
||||
"type": "MOTION_MODULE",
|
||||
"links": [
|
||||
24
|
||||
],
|
||||
"shape": 3
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"Node name for S&R": "AnimateDiffModuleLoader"
|
||||
},
|
||||
"widgets_values": [
|
||||
"mm_sd_v15_v2.ckpt"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 21,
|
||||
"type": "AnimateDiffLoraLoader",
|
||||
"pos": [
|
||||
-317,
|
||||
350
|
||||
],
|
||||
"size": [
|
||||
310,
|
||||
80
|
||||
],
|
||||
"flags": {},
|
||||
"order": 3,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "lora_stack",
|
||||
"type": "MOTION_LORA_STACK",
|
||||
"link": null
|
||||
}
|
||||
],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "MOTION_LORA_STACK",
|
||||
"type": "MOTION_LORA_STACK",
|
||||
"links": [
|
||||
38
|
||||
],
|
||||
"shape": 3,
|
||||
"slot_index": 0
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"Node name for S&R": "AnimateDiffLoraLoader"
|
||||
},
|
||||
"widgets_values": [
|
||||
"v2_lora_ZoomIn.ckpt",
|
||||
1
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 4,
|
||||
"type": "CheckpointLoaderSimple",
|
||||
"pos": [
|
||||
28,
|
||||
457
|
||||
],
|
||||
"size": {
|
||||
"0": 315,
|
||||
"1": 98
|
||||
},
|
||||
"flags": {},
|
||||
"order": 2,
|
||||
"mode": 0,
|
||||
"outputs": [
|
||||
{
|
||||
"name": "MODEL",
|
||||
"type": "MODEL",
|
||||
"links": [
|
||||
25
|
||||
],
|
||||
"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": [
|
||||
"RealisticVision_v20.safetensors"
|
||||
]
|
||||
}
|
||||
],
|
||||
"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"
|
||||
],
|
||||
[
|
||||
28,
|
||||
15,
|
||||
0,
|
||||
8,
|
||||
0,
|
||||
"LATENT"
|
||||
],
|
||||
[
|
||||
29,
|
||||
6,
|
||||
0,
|
||||
15,
|
||||
2,
|
||||
"CONDITIONING"
|
||||
],
|
||||
[
|
||||
30,
|
||||
7,
|
||||
0,
|
||||
15,
|
||||
3,
|
||||
"CONDITIONING"
|
||||
],
|
||||
[
|
||||
35,
|
||||
20,
|
||||
0,
|
||||
15,
|
||||
4,
|
||||
"LATENT"
|
||||
],
|
||||
[
|
||||
38,
|
||||
21,
|
||||
0,
|
||||
16,
|
||||
0,
|
||||
"MOTION_LORA_STACK"
|
||||
]
|
||||
],
|
||||
"groups": [],
|
||||
"config": {},
|
||||
"extra": {},
|
||||
"version": 0.4
|
||||
}
|
||||
Reference in New Issue
Block a user