Add all project files
This commit is contained in:
+1
-1
@@ -7,4 +7,4 @@
|
||||
# Denote all files that are truly binary and should not be modified.
|
||||
*.jpg binary
|
||||
*.png binary
|
||||
*.gif binary
|
||||
*.gif binary
|
||||
File diff suppressed because one or more lines are too long
@@ -0,0 +1,156 @@
|
||||
import subprocess
|
||||
from time import gmtime, strftime
|
||||
|
||||
# Live Prompt Interpolation
|
||||
# A new node to execute python with text interpolation and character stability check.
|
||||
# The resulting print() statement ends up as the output, if any.
|
||||
# (c) 2024 Atom, ChatGPT, and mgfxer.
|
||||
|
||||
# Internal code to handle the interpolation and prompt logic
|
||||
def internal_code():
|
||||
return """
|
||||
def lerp(a: float, b: float, t: float) -> float:
|
||||
return (1 - t) * a + t * b
|
||||
|
||||
def generate_interpolated_prompt(prev_prompt: str, curr_prompt: str, t: float) -> str:
|
||||
if t == 0:
|
||||
return f"{prev_prompt}"
|
||||
elif t == 1:
|
||||
return f"{curr_prompt}"
|
||||
else:
|
||||
weighted_prev = f"({prev_prompt}:{1 - t:.2f})"
|
||||
weighted_curr = f"({curr_prompt}:{t:.2f})"
|
||||
return f"{weighted_prev} {weighted_curr}"
|
||||
|
||||
current_frame = PYTHON_CODE_BOX_SEED % (TOTAL_FRAMES + 1)
|
||||
|
||||
# Example previous and current prompts
|
||||
previous_prompt = 'PREVIOUS_PROMPT'
|
||||
current_prompt = 'CURRENT_PROMPT'
|
||||
|
||||
# Interpolation factor based on frame count
|
||||
if current_frame > TOTAL_FRAMES:
|
||||
current_frame = TOTAL_FRAMES
|
||||
|
||||
t = current_frame / TOTAL_FRAMES
|
||||
|
||||
# Generate the interpolated prompt
|
||||
interpolated_prompt = generate_interpolated_prompt(previous_prompt, current_prompt, t)
|
||||
|
||||
# Print the interpolated prompt
|
||||
print(interpolated_prompt)
|
||||
"""
|
||||
|
||||
class LivePromptInterpolation:
|
||||
RETURN_TYPES = ("STRING", "FLOAT", "FLOAT", "INT",)
|
||||
RETURN_NAMES = ("text", "current_strength", "previous_strength", "toggle_state",)
|
||||
|
||||
FUNCTION = "node_update_with_text_v3"
|
||||
CATEGORY = "Scripting"
|
||||
|
||||
def __init__(self):
|
||||
self.previous_prompt = ""
|
||||
self.current_prompt = ""
|
||||
self.current_frame = 0
|
||||
self.total_frames = 100
|
||||
self.update_cycle = 10
|
||||
self.char_stability_frames = 5
|
||||
self.stable_char_count_frame = 0
|
||||
self.last_char_count = 0
|
||||
self.toggle_state = 0 # Initialize the toggle state
|
||||
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
return {
|
||||
"required": {
|
||||
"new_prompt": ("STRING", {"default": "Enter your prompt here..."}),
|
||||
"seed": ("INT", {"default": 311, "step": 1, "display": "number"}),
|
||||
"total_frames": ("INT", {"default": 100, "step": 1, "display": "number"}),
|
||||
"update_cycle": ("INT", {"default": 10, "step": 1, "display": "number"}),
|
||||
"min_char_count": ("INT", {"default": 30, "step": 1, "display": "number"}),
|
||||
"char_stability_frames": ("INT", {"default": 5, "step": 1, "display": "number"})
|
||||
},
|
||||
}
|
||||
|
||||
def node_update_with_text_v3(self, new_prompt, seed, total_frames, update_cycle, min_char_count, char_stability_frames):
|
||||
# Check if the new prompt is significant
|
||||
def is_significant_change(prev_prompt, new_prompt, min_char_count):
|
||||
char_count = len(new_prompt)
|
||||
return char_count >= min_char_count
|
||||
|
||||
# Debug logging
|
||||
print(f"Current Frame: {self.current_frame}")
|
||||
print(f"Previous Prompt: {self.previous_prompt}")
|
||||
print(f"Current Prompt: {self.current_prompt}")
|
||||
print(f"New Prompt: {new_prompt}")
|
||||
print(f"Update Cycle: {update_cycle}")
|
||||
print(f"Character Stability Frames: {char_stability_frames}")
|
||||
|
||||
self.update_cycle = update_cycle # Update cycle dynamically
|
||||
self.char_stability_frames = char_stability_frames # Update char stability frames dynamically
|
||||
|
||||
# Check if character count has been stable
|
||||
if len(new_prompt) == self.last_char_count:
|
||||
self.stable_char_count_frame += 1
|
||||
else:
|
||||
self.stable_char_count_frame = 0
|
||||
self.last_char_count = len(new_prompt)
|
||||
|
||||
# Check if it's time to update the prompt
|
||||
if (self.stable_char_count_frame >= self.char_stability_frames and
|
||||
new_prompt != self.current_prompt and
|
||||
is_significant_change(self.current_prompt, new_prompt, min_char_count)):
|
||||
|
||||
self.previous_prompt = self.current_prompt
|
||||
self.current_prompt = new_prompt
|
||||
self.current_frame = 1 # Reset frame count for new interpolation
|
||||
self.stable_char_count_frame = 0 # Reset stability counter
|
||||
self.toggle_state = 1 - self.toggle_state # Toggle the state between 0 and 1
|
||||
print("New prompt detected, resetting frames and toggling state.")
|
||||
elif self.current_frame < self.total_frames:
|
||||
self.current_frame += 1
|
||||
print("Update cycle not reached yet or prompt not stable long enough.")
|
||||
|
||||
# Ensure current_frame doesn't exceed total_frames
|
||||
if self.current_frame > self.total_frames:
|
||||
self.current_frame = self.total_frames
|
||||
|
||||
# Update total_frames dynamically
|
||||
self.total_frames = total_frames
|
||||
|
||||
# Interpolation calculation
|
||||
t = self.current_frame / self.total_frames
|
||||
current_strength = t if self.toggle_state == 0 else 1 - t
|
||||
previous_strength = 1 - current_strength
|
||||
|
||||
# Modify the code to include the previous and current prompts
|
||||
modified_code = internal_code().replace('PREVIOUS_PROMPT', self.previous_prompt).replace('CURRENT_PROMPT', self.current_prompt).replace('TOTAL_FRAMES', str(self.total_frames))
|
||||
code = f"import random; random.seed({seed}); PYTHON_CODE_BOX_SEED={self.current_frame}; {modified_code}"
|
||||
|
||||
try:
|
||||
proc = subprocess.Popen(["python", "-c", code], stdout=subprocess.PIPE)
|
||||
code_result = proc.communicate(timeout=10)[0] # Add timeout here
|
||||
# Fix up result.
|
||||
convert_result = code_result.decode().strip()
|
||||
except subprocess.TimeoutExpired:
|
||||
proc.kill()
|
||||
print("Subprocess timed out and was killed.")
|
||||
convert_result = ""
|
||||
except Exception as e:
|
||||
print(f"Error during code execution: {e}")
|
||||
convert_result = ""
|
||||
|
||||
t = strftime("%m-%d-%Y %H:%M:%S", gmtime())
|
||||
print(f"\033[36m[{t}] PCBv3--> {convert_result}\033[0m")
|
||||
|
||||
return (convert_result, current_strength, previous_strength, self.toggle_state,)
|
||||
|
||||
@classmethod
|
||||
def IS_CHANGED(cls, new_prompt, seed):
|
||||
return True
|
||||
|
||||
# NOTE: names should be globally unique
|
||||
NODE_CLASS_MAPPINGS = {"LivePromptInterpolation": LivePromptInterpolation}
|
||||
|
||||
# A dictionary that contains the friendly/humanly readable titles for the nodes
|
||||
NODE_DISPLAY_NAME_MAPPINGS = {"LivePromptInterpolation": "Live Prompt Interpolation"}
|
||||
@@ -0,0 +1,49 @@
|
||||
class PromptStackManager:
|
||||
def __init__(self):
|
||||
self.prompts = []
|
||||
self.current_index = 0
|
||||
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
return {
|
||||
"required": {
|
||||
"prompt_stack": ("STRING", {"multiline": True, "dynamicPrompts": False, "default": "Prompt 1\nPrompt 2\nPrompt 3"}),
|
||||
"frames_per_prompt": ("INT", {"default": 1, "min": 1, "max": 1000, "step": 1}),
|
||||
"seed": ("INT", {"default": 0, "min": 0, "max": 1000000, "step": 1}),
|
||||
}
|
||||
}
|
||||
|
||||
RETURN_TYPES = ("STRING", "STRING", "INT")
|
||||
RETURN_NAMES = ("current_prompt", "previous_prompt", "current_frame")
|
||||
FUNCTION = "manage_prompts"
|
||||
CATEGORY = "Prompt Management"
|
||||
DESCRIPTION = """
|
||||
Manages a stack of prompts from a multiline text box and outputs the current and previous prompts.
|
||||
The node cycles through the prompts based on the frame count derived from the seed input.
|
||||
"""
|
||||
|
||||
def manage_prompts(self, prompt_stack, frames_per_prompt, seed):
|
||||
self.prompts = prompt_stack.strip().split("\n")
|
||||
num_prompts = len(self.prompts)
|
||||
|
||||
if num_prompts == 0:
|
||||
return ("", "", 0)
|
||||
|
||||
# Calculate the current frame based on the seed
|
||||
current_frame = seed % (frames_per_prompt * num_prompts)
|
||||
|
||||
# Determine the current and previous prompt indices
|
||||
self.current_index = current_frame // frames_per_prompt
|
||||
previous_index = (self.current_index - 1) if self.current_index > 0 else (num_prompts - 1)
|
||||
|
||||
current_prompt = self.prompts[self.current_index]
|
||||
previous_prompt = self.prompts[previous_index] if self.current_index > 0 else current_prompt
|
||||
|
||||
return (current_prompt, previous_prompt, current_frame)
|
||||
|
||||
@classmethod
|
||||
def IS_CHANGED(cls, prompt_stack, frames_per_prompt, seed):
|
||||
return True
|
||||
|
||||
NODE_CLASS_MAPPINGS = {"PromptStackManager": PromptStackManager}
|
||||
NODE_DISPLAY_NAME_MAPPINGS = {"PromptStackManager": "Prompt Stack Manager"}
|
||||
@@ -0,0 +1,602 @@
|
||||
import os
|
||||
from PIL import Image, ImageDraw, ImageFilter, ImageOps, ImageChops, ImageEnhance
|
||||
import numpy as np
|
||||
import torch
|
||||
import random
|
||||
|
||||
class DynamicAnimatedWeightsHelper:
|
||||
def __init__(self):
|
||||
pass
|
||||
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
base_animation_types = ["LeftToRight", "RightToLeft", "TopDown", "BottomToTop", "GrowingCircle", "ShrinkingCircle",
|
||||
"DiagonalTopLeft-BottomRight", "DiagonalBottomRight-TopLeft", "DiagonalTopRight-BottomLeft",
|
||||
"DiagonalBottomLeft-TopRight", "Fade", "SqSpinCw", "SqSpinCCW", "VenetianBlindsHorizontal",
|
||||
"VenetianBlindsVertical", "DiagonalVenetianBlinds1", "DiagonalVenetianBlinds2"]
|
||||
animation_types = base_animation_types + ["Random", "RandomNoVenetian"]
|
||||
easing_options = ["ease_in", "ease_out", "ease_in_out", "false"]
|
||||
generation_options = ["Only Transitions", "Generate QR", "Generate Edge-FX", "Generate All"]
|
||||
return {
|
||||
"required": {
|
||||
"animation_type_1": (animation_types, {"default": cls.random_animation()}),
|
||||
"animation_type_2": (animation_types, {"default": cls.random_animation()}),
|
||||
"animation_type_3": (animation_types, {"default": cls.random_animation()}),
|
||||
"animation_type_4": (animation_types, {"default": cls.random_animation()}),
|
||||
"animation_type_5": (animation_types, {"default": cls.random_animation()}),
|
||||
"animation_type_6": (animation_types, {"default": cls.random_animation()}),
|
||||
"animation_type_7": (animation_types, {"default": cls.random_animation()}),
|
||||
"animation_type_8": (animation_types, {"default": cls.random_animation()}),
|
||||
"animation_type_9": (animation_types, {"default": cls.random_animation()}),
|
||||
"animation_type_10": (animation_types, {"default": cls.random_animation()}),
|
||||
"animation_type_11": (animation_types, {"default": cls.random_animation()}),
|
||||
"animation_type_12": (animation_types, {"default": cls.random_animation()}),
|
||||
"transition_easing": (easing_options, {"default": "false"}),
|
||||
"blur_easing": (easing_options, {"default": "false"}),
|
||||
"frame_width": ("INT", {"default": 512, "min": 1, "step": 1, "display": "number"}),
|
||||
"frame_height": ("INT", {"default": 512, "min": 1, "step": 1, "display": "number"}),
|
||||
"hold_frames": ("INT", {"default": 8, "min": 1, "step": 1, "display": "number"}),
|
||||
"transition_frames": ("INT", {"default": 20, "min": 1, "step": 1, "display": "number"}),
|
||||
"padding_frames": ("INT", {"default": 6, "min": 0, "step": 1, "display": "number"}),
|
||||
"input_frames": ("INT", {"default": 5, "min": 1, "step": 1, "display": "number"}),
|
||||
"gaussian_blur_amount": ("FLOAT", {"default": 0.0, "min": 0.0, "max": 10.0, "step": 0.1, "display": "slider"}),
|
||||
"edge_fx_thickness": ("INT", {"default": 1, "min": 1, "max": 10, "step": 1, "display": "number"}),
|
||||
"push_fx": ("INT", {"default": 0, "min": 0, "max": 30, "step": 1, "display": "number"}),
|
||||
"retract_fx": ("INT", {"default": 0, "min": 0, "max": 30, "step": 1, "display": "number"}),
|
||||
"fx_cull_white_frames": ("FLOAT", {"default": 10.0, "min": 0.0, "max": 100.0, "step": 0.1, "display": "slider"}),
|
||||
"qr_greyness": ("FLOAT", {"default": 0.5, "min": 0.0, "max": 1.0, "step": 0.01, "display": "slider"}),
|
||||
"random_seed": ("FLOAT", {"default": 0.0, "min": 0.0, "max": 10.0, "step": 0.1, "display": "slider"}),
|
||||
"edgeFade_contrast": ("FLOAT", {"default": 1.0, "min": 0.5, "max": 6.0, "step": 0.1, "display": "slider"}), # Increased contrast effectiveness
|
||||
"edgeFade_blur": ("FLOAT", {"default": 0.0, "min": 0.0, "max": 10.0, "step": 0.1, "display": "slider"}), # Final blur adjustment for Edge FX Fade
|
||||
"generation_mode": (generation_options, {"default": "Only Transitions"}),
|
||||
"edge_fx_fade_balance": ("FLOAT", {"default": 0.5, "min": 0.1, "max": 0.9, "step": 0.05, "display": "slider"}),
|
||||
"venetian_bars": ("INT", {"default": 4, "min": 1, "step": 1, "display": "number"})
|
||||
}
|
||||
}
|
||||
|
||||
@classmethod
|
||||
def random_animation(cls, exclude_venetian=False):
|
||||
venetian_types = ["VenetianBlindsHorizontal", "VenetianBlindsVertical", "DiagonalVenetianBlinds1", "DiagonalVenetianBlinds2"]
|
||||
base_animation_types = ["LeftToRight", "RightToLeft", "TopDown", "BottomToTop", "GrowingCircle", "ShrinkingCircle",
|
||||
"DiagonalTopLeft-BottomRight", "DiagonalBottomRight-TopLeft", "DiagonalTopRight-BottomLeft",
|
||||
"DiagonalBottomLeft-TopRight", "Fade", "SqSpinCw", "SqSpinCCW", "VenetianBlindsHorizontal",
|
||||
"VenetianBlindsVertical", "DiagonalVenetianBlinds1", "DiagonalVenetianBlinds2"]
|
||||
available_types = base_animation_types
|
||||
if exclude_venetian:
|
||||
available_types = [t for t in available_types if t not in venetian_types]
|
||||
return random.choice(available_types)
|
||||
|
||||
RETURN_TYPES = ("IMAGE", "IMAGE", "IMAGE", "IMAGE", "INT")
|
||||
RETURN_NAMES = ("transitions", "qr_mode", "edge_fx", "edge_fx_fade", "total_frames")
|
||||
FUNCTION = "run"
|
||||
CATEGORY = "mgfxer"
|
||||
|
||||
def run(self, **kwargs):
|
||||
animation_types = [kwargs[f'animation_type_{i}'] for i in range(1, 13)]
|
||||
frame_width = kwargs['frame_width']
|
||||
frame_height = kwargs['frame_height']
|
||||
hold_frames = kwargs['hold_frames']
|
||||
transition_frames = kwargs['transition_frames']
|
||||
padding_frames = kwargs['padding_frames']
|
||||
input_frames = kwargs['input_frames']
|
||||
gaussian_blur_amount = kwargs['gaussian_blur_amount'] * 20 # Amplify the blur amount here
|
||||
edgeFade_blur = kwargs['edgeFade_blur'] * 10 # Amplify the final blur amount by 10
|
||||
edgeFade_contrast = kwargs['edgeFade_contrast']
|
||||
transition_easing = kwargs['transition_easing']
|
||||
blur_easing = kwargs['blur_easing']
|
||||
qr_greyness = kwargs['qr_greyness']
|
||||
random_seed = int(kwargs['random_seed'] * 10)
|
||||
edge_fx_thickness = kwargs['edge_fx_thickness']
|
||||
push_fx = kwargs['push_fx']
|
||||
retract_fx = kwargs['retract_fx']
|
||||
fx_cull_white_frames = kwargs['fx_cull_white_frames']
|
||||
generation_mode = kwargs['generation_mode']
|
||||
edge_fx_fade_balance = kwargs['edge_fx_fade_balance']
|
||||
venetian_bars = kwargs['venetian_bars']
|
||||
|
||||
random.seed(random_seed)
|
||||
|
||||
images = []
|
||||
qr_images = []
|
||||
edge_fx_frames = []
|
||||
edge_fx_fade_frames = []
|
||||
total_frames = 0
|
||||
for i in range(input_frames):
|
||||
animation_type = animation_types[i % 12]
|
||||
if animation_type == "Random":
|
||||
animation_type = self.random_animation()
|
||||
elif animation_type == "RandomNoVenetian":
|
||||
animation_type = self.random_animation(exclude_venetian=True)
|
||||
|
||||
frames, frame_count = self.generate_animation(animation_type, frame_width, frame_height, hold_frames, transition_frames, gaussian_blur_amount, transition_easing, blur_easing, venetian_bars)
|
||||
|
||||
if generation_mode in ["Generate QR", "Generate All"]:
|
||||
qr_frames = self.apply_qr_mode(frames, i, input_frames, hold_frames + transition_frames)
|
||||
qr_images.extend(qr_frames)
|
||||
|
||||
images.extend(frames)
|
||||
total_frames += frame_count
|
||||
|
||||
final_padding_color_qr = 'black' if input_frames % 2 == 0 else 'white'
|
||||
for _ in range(padding_frames):
|
||||
frame = self.create_frame(frame_width, frame_height, is_black=False)
|
||||
images.append(frame)
|
||||
if generation_mode in ["Generate QR", "Generate All"]:
|
||||
qr_frame = self.create_frame(frame_width, frame_height, is_black=(final_padding_color_qr == 'black'))
|
||||
qr_images.append(qr_frame)
|
||||
total_frames += 1
|
||||
|
||||
if generation_mode in ["Generate QR", "Generate All"]:
|
||||
qr_images = self.apply_qr_greyness(qr_images, qr_greyness, frame_width, frame_height)
|
||||
|
||||
if generation_mode in ["Generate Edge-FX", "Generate All"]:
|
||||
edge_fx_frames = self.generate_edge_fx(images, edge_fx_thickness, frame_width, frame_height, fx_cull_white_frames)
|
||||
edge_fx_frames = self.apply_push_retract_fx(edge_fx_frames, push_fx, retract_fx, frame_width, frame_height)
|
||||
edge_fx_frames = self.check_and_correct_white_frames(edge_fx_frames, frame_width, frame_height, fx_cull_white_frames)
|
||||
|
||||
if generation_mode == "Generate All":
|
||||
unblurred_edge_fx = edge_fx_frames.copy()
|
||||
final_edge_fx = self.composite_edge_fx(unblurred_edge_fx, edge_fx_frames)
|
||||
edge_fx_fade_frames = self.apply_fade_to_edge_fx(final_edge_fx, transition_frames, hold_frames, edge_fx_fade_balance)
|
||||
edge_fx_fade_frames = self.apply_contrast_to_frames(edge_fx_fade_frames, edgeFade_contrast)
|
||||
edge_fx_fade_frames = self.apply_blur_to_frames(edge_fx_fade_frames, edgeFade_blur)
|
||||
|
||||
image_batch = torch.cat([self.process_image_for_output(frame) for frame in images], dim=0)
|
||||
qr_image_batch = torch.cat([self.process_image_for_output(frame) for frame in (qr_images if qr_images else images)], dim=0)
|
||||
edge_fx_batch = torch.cat([self.process_image_for_output(frame) for frame in (edge_fx_frames if edge_fx_frames else images)], dim=0)
|
||||
edge_fx_fade_batch = torch.cat([self.process_image_for_output(frame) for frame in (edge_fx_fade_frames if edge_fx_fade_frames else images)], dim=0)
|
||||
|
||||
return (image_batch, qr_image_batch, edge_fx_batch, edge_fx_fade_batch, total_frames)
|
||||
|
||||
def generate_animation(self, animation_type, frame_width, frame_height, hold_frames, transition_frames, gaussian_blur_amount, transition_easing, blur_easing, venetian_bars):
|
||||
if animation_type == "LeftToRight":
|
||||
return self.generate_left_to_right_animation(frame_width, frame_height, hold_frames, transition_frames, gaussian_blur_amount, transition_easing, blur_easing)
|
||||
elif animation_type == "RightToLeft":
|
||||
return self.generate_rotated_animation(self.generate_left_to_right_animation, frame_width, frame_height, hold_frames, transition_frames, gaussian_blur_amount, transition_easing, blur_easing, 180)
|
||||
elif animation_type == "TopDown":
|
||||
return self.generate_rotated_animation(self.generate_left_to_right_animation, frame_width, frame_height, hold_frames, transition_frames, gaussian_blur_amount, transition_easing, blur_easing, 90, resize=True)
|
||||
elif animation_type == "BottomToTop":
|
||||
return self.generate_rotated_animation(self.generate_left_to_right_animation, frame_width, frame_height, hold_frames, transition_frames, gaussian_blur_amount, transition_easing, blur_easing, 270, resize=True)
|
||||
elif animation_type == "GrowingCircle":
|
||||
return self.generate_growing_circle_animation(frame_width, frame_height, hold_frames, transition_frames, gaussian_blur_amount, transition_easing, blur_easing)
|
||||
elif animation_type == "ShrinkingCircle":
|
||||
return self.generate_shrinking_circle_animation(frame_width, frame_height, hold_frames, transition_frames, gaussian_blur_amount, transition_easing, blur_easing)
|
||||
elif animation_type in ["DiagonalTopLeft-BottomRight", "DiagonalBottomRight-TopLeft", "DiagonalTopRight-BottomLeft", "DiagonalBottomLeft-TopRight"]:
|
||||
return self.generate_diagonal_animation(animation_type, frame_width, frame_height, hold_frames, transition_frames, gaussian_blur_amount, transition_easing, blur_easing)
|
||||
elif animation_type == "Fade":
|
||||
return self.generate_fade_animation(frame_width, frame_height, hold_frames, transition_frames, transition_easing, blur_easing)
|
||||
elif animation_type == "SqSpinCw":
|
||||
return self.generate_sq_spin_animation(frame_width, frame_height, hold_frames, transition_frames, gaussian_blur_amount, transition_easing, blur_easing, clockwise=True)
|
||||
elif animation_type == "SqSpinCCW":
|
||||
return self.generate_sq_spin_animation(frame_width, frame_height, hold_frames, transition_frames, gaussian_blur_amount, transition_easing, blur_easing, clockwise=False)
|
||||
elif animation_type == "VenetianBlindsHorizontal":
|
||||
return self.generate_venetian_blinds_animation(frame_width, frame_height, hold_frames, transition_frames, gaussian_blur_amount, transition_easing, blur_easing, vertical=False, venetian_bars=venetian_bars)
|
||||
elif animation_type == "VenetianBlindsVertical":
|
||||
return self.generate_venetian_blinds_animation(frame_width, frame_height, hold_frames, transition_frames, gaussian_blur_amount, transition_easing, blur_easing, vertical=True, venetian_bars=venetian_bars)
|
||||
elif animation_type == "DiagonalVenetianBlinds1":
|
||||
return self.generate_diagonal_venetian_blinds_animation(frame_width, frame_height, hold_frames, transition_frames, gaussian_blur_amount, transition_easing, blur_easing, angle=45, venetian_bars=venetian_bars)
|
||||
elif animation_type == "DiagonalVenetianBlinds2":
|
||||
return self.generate_diagonal_venetian_blinds_animation(frame_width, frame_height, hold_frames, transition_frames, gaussian_blur_amount, transition_easing, blur_easing, angle=135, venetian_bars=venetian_bars)
|
||||
|
||||
def generate_edge_fx(self, frames, offset, frame_width, frame_height, fx_cull_white_frames):
|
||||
edge_fx_frames = []
|
||||
for i in range(len(frames)):
|
||||
current_frame = frames[i]
|
||||
if i + offset < len(frames):
|
||||
next_frame = frames[i + offset]
|
||||
else:
|
||||
next_frame = Image.new('RGB', (frame_width, frame_height), color='white')
|
||||
|
||||
mask = ImageChops.difference(current_frame, next_frame)
|
||||
mask = mask.convert('L').point(lambda x: 255 if x > 0 else 0, mode='1')
|
||||
|
||||
edge_fx_frame = Image.new('RGB', (frame_width, frame_height), color='black')
|
||||
edge_fx_frame.paste(Image.new('RGB', (frame_width, frame_height), color='white'), mask=mask)
|
||||
|
||||
if ImageChops.invert(edge_fx_frame).getbbox() is None:
|
||||
edge_fx_frame = Image.new('RGB', (frame_width, frame_height), color='black')
|
||||
|
||||
edge_fx_frames.append(edge_fx_frame)
|
||||
|
||||
edge_fx_frames = self.check_and_correct_white_frames(edge_fx_frames, frame_width, frame_height, fx_cull_white_frames)
|
||||
|
||||
return edge_fx_frames
|
||||
|
||||
def check_and_correct_white_frames(self, frames, frame_width, frame_height, fx_cull_white_frames):
|
||||
corrected_frames = []
|
||||
for frame in frames:
|
||||
white_pixels = np.sum(np.array(frame) == 255)
|
||||
total_pixels = frame_width * frame_height * 3
|
||||
white_percentage = (white_pixels / total_pixels) * 100
|
||||
|
||||
if white_percentage > fx_cull_white_frames:
|
||||
corrected_frame = Image.new('RGB', (frame_width, frame_height), color='black')
|
||||
else:
|
||||
corrected_frame = frame
|
||||
corrected_frames.append(corrected_frame)
|
||||
return corrected_frames
|
||||
|
||||
def apply_push_retract_fx(self, frames, push_fx, retract_fx, frame_width, frame_height):
|
||||
black_frame = Image.new('RGB', (frame_width, frame_height), color='black')
|
||||
|
||||
if push_fx > 0:
|
||||
frames = [black_frame] * push_fx + frames[:-push_fx]
|
||||
elif retract_fx > 0:
|
||||
frames = frames[retract_fx:] + [black_frame] * retract_fx
|
||||
|
||||
return frames
|
||||
|
||||
def apply_qr_greyness(self, frames, qr_greyness, frame_width, frame_height):
|
||||
grey_image_white = Image.new('RGB', (frame_width, frame_height), color=(255, 255, 255))
|
||||
grey_image_black = Image.new('RGB', (frame_width, frame_height), color=(235, 235, 235))
|
||||
grey_blend_white = lambda img: Image.blend(img, grey_image_white, qr_greyness)
|
||||
grey_blend_black = lambda img: Image.blend(img, grey_image_black, qr_greyness)
|
||||
frames = [grey_blend_black(grey_blend_white(frame)) for frame in frames]
|
||||
return frames
|
||||
|
||||
def generate_diagonal_venetian_blinds_animation(self, frame_width, frame_height, hold_frames, transition_frames, gaussian_blur_amount, transition_easing, blur_easing, angle, venetian_bars):
|
||||
frames, _ = self.generate_venetian_blinds_animation(frame_width, frame_height, hold_frames, transition_frames, gaussian_blur_amount, transition_easing, blur_easing, vertical=True, venetian_bars=venetian_bars)
|
||||
|
||||
enlarged_frames = []
|
||||
for frame in frames:
|
||||
large_canvas = Image.new('RGB', (frame_width * 2, frame_height * 2), 'black')
|
||||
enlarged_frame = frame.resize((int(frame_width * 1.5), int(frame_height * 1.5)), Image.LANCZOS)
|
||||
large_canvas.paste(enlarged_frame, ((large_canvas.width - enlarged_frame.width) // 2, (large_canvas.height - enlarged_frame.height) // 2))
|
||||
|
||||
rotated_frame = large_canvas.rotate(angle, expand=True)
|
||||
|
||||
cropped_frame = rotated_frame.crop(((rotated_frame.width - frame_width) // 2, (rotated_frame.height - frame_height) // 2,
|
||||
(rotated_frame.width + frame_width) // 2, (rotated_frame.height + frame_height) // 2))
|
||||
|
||||
enlarged_frames.append(cropped_frame)
|
||||
|
||||
total_frames = hold_frames + transition_frames
|
||||
return enlarged_frames, total_frames
|
||||
|
||||
def generate_venetian_blinds_animation(self, frame_width, frame_height, hold_frames, transition_frames, gaussian_blur_amount, transition_easing, blur_easing, vertical=True, venetian_bars=4):
|
||||
images = []
|
||||
bar_size = frame_height // venetian_bars if vertical else frame_width // venetian_bars
|
||||
|
||||
for _ in range(hold_frames):
|
||||
frame = self.create_frame(frame_width, frame_height, is_black=True)
|
||||
images.append(frame)
|
||||
|
||||
for i in range(transition_frames):
|
||||
ease_factor = self.calculate_ease_factor(i, transition_frames, transition_easing)
|
||||
blur_factor = self.calculate_blur_factor(i, transition_frames, blur_easing, gaussian_blur_amount)
|
||||
|
||||
frame = self.create_venetian_blinds_transition_frame(frame_width, frame_height, venetian_bars, bar_size, ease_factor, blur_factor['gaussian'], vertical)
|
||||
images.append(frame)
|
||||
|
||||
total_frames = hold_frames + transition_frames
|
||||
return images, total_frames
|
||||
|
||||
def create_venetian_blinds_transition_frame(self, frame_width, frame_height, num_bars, bar_size, ease_factor, gaussian_blur_amount, vertical=True):
|
||||
frame = Image.new('RGB', (frame_width, frame_height), color='black')
|
||||
draw = ImageDraw.Draw(frame)
|
||||
|
||||
for j in range(num_bars):
|
||||
if vertical:
|
||||
left = j * bar_size
|
||||
right = left + int(bar_size * ease_factor)
|
||||
draw.rectangle([left, 0, right, frame_height], fill='white')
|
||||
else:
|
||||
top = j * bar_size
|
||||
bottom = top + int(bar_size * ease_factor)
|
||||
draw.rectangle([0, top, frame_width, bottom], fill='white')
|
||||
|
||||
if gaussian_blur_amount > 0:
|
||||
frame = frame.filter(ImageFilter.GaussianBlur(gaussian_blur_amount))
|
||||
|
||||
return frame
|
||||
|
||||
def generate_sq_spin_animation(self, frame_width, frame_height, hold_frames, transition_frames, gaussian_blur_amount, transition_easing, blur_easing, clockwise=True):
|
||||
images = []
|
||||
initial_square_size = 2
|
||||
max_square_size = int(frame_width * 1.65)
|
||||
canvas_size = int(frame_width * 2.5)
|
||||
|
||||
for _ in range(hold_frames):
|
||||
frame = self.create_frame(frame_width, frame_height, is_black=True)
|
||||
images.append(frame)
|
||||
|
||||
for i in range(transition_frames):
|
||||
ease_factor = self.calculate_ease_factor(i, transition_frames, transition_easing)
|
||||
blur_factor = self.calculate_blur_factor(i, transition_frames, blur_easing, gaussian_blur_amount)
|
||||
|
||||
square_size = initial_square_size + int((max_square_size - initial_square_size) * ease_factor)
|
||||
rotation_angle = (235 * ease_factor) if clockwise else (-235 * ease_factor)
|
||||
|
||||
frame = self.create_sq_spin_transition_frame(canvas_size, frame_width, frame_height, square_size, rotation_angle, blur_factor['gaussian'])
|
||||
images.append(frame)
|
||||
|
||||
total_frames = hold_frames + transition_frames
|
||||
return images, total_frames
|
||||
|
||||
def create_sq_spin_transition_frame(self, canvas_size, frame_width, frame_height, square_size, rotation_angle, gaussian_blur_amount):
|
||||
frame = Image.new('RGB', (canvas_size, canvas_size), color='black')
|
||||
draw = ImageDraw.Draw(frame)
|
||||
|
||||
top_left = ((canvas_size - square_size) // 2, (canvas_size - square_size) // 2)
|
||||
bottom_right = (top_left[0] + square_size, top_left[1] + square_size)
|
||||
draw.rectangle([top_left, bottom_right], fill='white')
|
||||
|
||||
frame = frame.rotate(rotation_angle, expand=True)
|
||||
|
||||
left = (frame.width - frame_width) // 2
|
||||
top = (frame.height - frame_height) // 2
|
||||
frame = frame.crop((left, top, left + frame_width, top + frame_height))
|
||||
|
||||
if gaussian_blur_amount > 0:
|
||||
frame = frame.filter(ImageFilter.GaussianBlur(gaussian_blur_amount))
|
||||
|
||||
return frame
|
||||
|
||||
def generate_rotated_animation(self, animation_func, frame_width, frame_height, hold_frames, transition_frames, gaussian_blur_amount, transition_easing, blur_easing, rotation_angle, resize=False):
|
||||
images, total_frames = animation_func(frame_width, frame_height, hold_frames, transition_frames, gaussian_blur_amount, transition_easing, blur_easing)
|
||||
rotated_images = [img.rotate(rotation_angle, expand=True) for img in images]
|
||||
if resize:
|
||||
rotated_images = [img.resize((frame_width, frame_height), Image.LANCZOS) for img in rotated_images]
|
||||
return rotated_images, total_frames
|
||||
|
||||
def generate_left_to_right_animation(self, frame_width, frame_height, hold_frames, transition_frames, gaussian_blur_amount, transition_easing, blur_easing):
|
||||
images = []
|
||||
|
||||
for _ in range(hold_frames):
|
||||
frame = self.create_frame(frame_width, frame_height, is_black=True)
|
||||
images.append(frame)
|
||||
|
||||
for i in range(transition_frames):
|
||||
ease_factor = self.calculate_ease_factor(i, transition_frames, transition_easing)
|
||||
blur_factor = self.calculate_blur_factor(i, transition_frames, blur_easing, gaussian_blur_amount)
|
||||
frame = self.create_left_to_right_transition_frame(frame_width, frame_height, blur_factor['gaussian'], transition_frames, frame_index=i, ease_factor=ease_factor)
|
||||
images.append(frame)
|
||||
|
||||
total_frames = hold_frames + transition_frames
|
||||
return images, total_frames
|
||||
|
||||
def generate_growing_circle_animation(self, frame_width, frame_height, hold_frames, transition_frames, gaussian_blur_amount, transition_easing, blur_easing):
|
||||
images = []
|
||||
circle_size = int(frame_width * 1.5)
|
||||
|
||||
for _ in range(hold_frames):
|
||||
frame = self.create_frame(frame_width, frame_height, is_black=True)
|
||||
images.append(frame)
|
||||
|
||||
for i in range(transition_frames):
|
||||
ease_factor = self.calculate_ease_factor(i, transition_frames, transition_easing)
|
||||
blur_factor = self.calculate_blur_factor(i, transition_frames, blur_easing, gaussian_blur_amount)
|
||||
frame = self.create_growing_circle_transition_frame(frame_width, frame_height, circle_size, blur_factor['gaussian'], transition_frames, frame_index=i, ease_factor=ease_factor)
|
||||
images.append(frame)
|
||||
|
||||
total_frames = hold_frames + transition_frames
|
||||
return images, total_frames
|
||||
|
||||
def generate_shrinking_circle_animation(self, frame_width, frame_height, hold_frames, transition_frames, gaussian_blur_amount, transition_easing, blur_easing):
|
||||
images = []
|
||||
circle_size = int(frame_width * 1.5)
|
||||
|
||||
for _ in range(hold_frames):
|
||||
frame = self.create_frame(frame_width, frame_height, is_black=True)
|
||||
images.append(frame)
|
||||
|
||||
for i in range(transition_frames):
|
||||
ease_factor = self.calculate_ease_factor(i, transition_frames, transition_easing)
|
||||
blur_factor = self.calculate_blur_factor(i, transition_frames, blur_easing, gaussian_blur_amount)
|
||||
frame = self.create_shrinking_circle_transition_frame(frame_width, frame_height, circle_size, blur_factor['gaussian'], transition_frames, frame_index=i, ease_factor=ease_factor)
|
||||
images.append(frame)
|
||||
|
||||
total_frames = hold_frames + transition_frames
|
||||
return images, total_frames
|
||||
|
||||
def generate_diagonal_animation(self, animation_type, frame_width, frame_height, hold_frames, transition_frames, gaussian_blur_amount, transition_easing, blur_easing):
|
||||
frames, _ = self.generate_left_to_right_animation(frame_width, frame_height, hold_frames, transition_frames, gaussian_blur_amount, transition_easing, blur_easing)
|
||||
|
||||
enlarged_frames = []
|
||||
for frame in frames:
|
||||
large_canvas = Image.new('RGB', (frame_width * 2, frame_height * 2), 'black')
|
||||
enlarged_frame = frame.resize((int(frame_width * 1.5), int(frame_height * 1.5)), Image.LANCZOS)
|
||||
large_canvas.paste(enlarged_frame, ((large_canvas.width - enlarged_frame.width) // 2, (large_canvas.height - enlarged_frame.height) // 2))
|
||||
|
||||
if animation_type == "DiagonalTopLeft-BottomRight":
|
||||
rotated_frame = large_canvas.rotate(45, expand=True)
|
||||
elif animation_type == "DiagonalBottomRight-TopLeft":
|
||||
rotated_frame = large_canvas.rotate(225, expand=True)
|
||||
elif animation_type == "DiagonalTopRight-BottomLeft":
|
||||
rotated_frame = large_canvas.rotate(135, expand=True)
|
||||
elif animation_type == "DiagonalBottomLeft-TopRight":
|
||||
rotated_frame = large_canvas.rotate(315, expand=True)
|
||||
|
||||
cropped_frame = rotated_frame.crop(((rotated_frame.width - frame_width) // 2, (rotated_frame.height - frame_height) // 2,
|
||||
(rotated_frame.width + frame_width) // 2, (rotated_frame.height + frame_height) // 2))
|
||||
|
||||
enlarged_frames.append(cropped_frame)
|
||||
|
||||
total_frames = hold_frames + transition_frames
|
||||
return enlarged_frames, total_frames
|
||||
|
||||
def generate_fade_animation(self, frame_width, frame_height, hold_frames, transition_frames, transition_easing, blur_easing):
|
||||
images = []
|
||||
|
||||
for _ in range(hold_frames):
|
||||
frame = self.create_frame(frame_width, frame_height, is_black=True)
|
||||
images.append(frame)
|
||||
|
||||
for i in range(transition_frames):
|
||||
ease_factor = self.calculate_ease_factor(i, transition_frames, transition_easing)
|
||||
blur_factor = self.calculate_blur_factor(i, transition_frames, blur_easing, 0)
|
||||
gray_value = int(255 * (i / transition_frames) * ease_factor)
|
||||
frame = Image.new('RGB', (frame_width, frame_height), color=(gray_value, gray_value, gray_value))
|
||||
images.append(frame)
|
||||
|
||||
total_frames = hold_frames + transition_frames
|
||||
return images, total_frames
|
||||
|
||||
def create_frame(self, frame_width, frame_height, is_black=True):
|
||||
color = 'black' if is_black else 'white'
|
||||
frame = Image.new('RGB', (frame_width, frame_height), color=color)
|
||||
return frame
|
||||
|
||||
def create_left_to_right_transition_frame(self, frame_width, frame_height, blur_factor, transition_frames, frame_index, ease_factor):
|
||||
frame = Image.new('RGB', (frame_width, frame_height), color='black')
|
||||
draw = ImageDraw.Draw(frame)
|
||||
|
||||
box_width = int(frame_width * ease_factor)
|
||||
draw.rectangle([0, 0, box_width, frame_height], fill='white')
|
||||
|
||||
if blur_factor > 0:
|
||||
frame = frame.filter(ImageFilter.GaussianBlur(blur_factor))
|
||||
|
||||
return frame
|
||||
|
||||
def create_growing_circle_transition_frame(self, frame_width, frame_height, circle_size, blur_factor, transition_frames, frame_index, ease_factor):
|
||||
frame = Image.new('RGB', (circle_size, circle_size), color='black')
|
||||
draw = ImageDraw.Draw(frame)
|
||||
|
||||
max_radius = circle_size // 2
|
||||
radius = int(max_radius * ease_factor)
|
||||
|
||||
draw.ellipse([(circle_size // 2 - radius, circle_size // 2 - radius),
|
||||
(circle_size // 2 + radius, circle_size // 2 + radius)], fill='white')
|
||||
|
||||
frame = frame.crop((circle_size//2-frame_width//2, circle_size//2-frame_height//2, circle_size//2+frame_width//2, circle_size//2+frame_height//2))
|
||||
|
||||
if blur_factor > 0:
|
||||
frame = frame.filter(ImageFilter.GaussianBlur(blur_factor))
|
||||
|
||||
return frame
|
||||
|
||||
def create_shrinking_circle_transition_frame(self, frame_width, frame_height, circle_size, blur_factor, transition_frames, frame_index, ease_factor):
|
||||
frame = Image.new('RGB', (circle_size, circle_size), color='white')
|
||||
draw = ImageDraw.Draw(frame)
|
||||
|
||||
max_radius = circle_size // 2
|
||||
radius = int(max_radius * (1 - ease_factor))
|
||||
|
||||
draw.ellipse([(circle_size // 2 - radius, circle_size // 2 - radius),
|
||||
(circle_size // 2 + radius, circle_size // 2 + radius)], fill='black')
|
||||
|
||||
frame = frame.crop((circle_size//2-frame_width//2, circle_size//2-frame_height//2, circle_size//2+frame_width//2, circle_size//2+frame_height//2))
|
||||
|
||||
if blur_factor > 0:
|
||||
frame = frame.filter(ImageFilter.GaussianBlur(blur_factor))
|
||||
|
||||
return frame
|
||||
|
||||
def calculate_ease_factor(self, frame_index, transition_frames, transition_easing):
|
||||
t = frame_index / transition_frames
|
||||
if transition_easing == "ease_in":
|
||||
return self.ease_in_quad(t)
|
||||
elif transition_easing == "ease_out":
|
||||
return self.ease_out_quad(t)
|
||||
elif transition_easing == "ease_in_out":
|
||||
return self.ease_in_out_quad(t)
|
||||
else: # "false" or any other value
|
||||
return t # Linear easing
|
||||
|
||||
def calculate_blur_factor(self, frame_index, transition_frames, blur_easing, gaussian_blur_amount):
|
||||
t = frame_index / transition_frames
|
||||
if blur_easing == "ease_in":
|
||||
blur_factor = self.ease_in_quad(t)
|
||||
elif blur_easing == "ease_out":
|
||||
blur_factor = 1 - self.ease_out_quad(1 - t)
|
||||
elif blur_easing == "ease_in_out":
|
||||
blur_factor = self.ease_in_out_quad_for_blur(t)
|
||||
else: # "false" or any other value
|
||||
blur_factor = 1 # Constant blur amount for linear
|
||||
|
||||
return {
|
||||
'gaussian': gaussian_blur_amount * blur_factor
|
||||
}
|
||||
|
||||
def ease_in_quad(self, t):
|
||||
return t * t
|
||||
|
||||
def ease_out_quad(self, t):
|
||||
return t * (2 - t)
|
||||
|
||||
def ease_in_out_quad(self, t):
|
||||
return 2 * t * t if t < 0.5 else -1 + (4 - 2 * t) * t
|
||||
|
||||
def ease_in_out_quad_for_blur(self, t):
|
||||
if t < 0.5:
|
||||
return 2 * t * t
|
||||
else:
|
||||
return 1 - pow(-2 * t + 2, 2) / 2
|
||||
|
||||
def apply_qr_mode(self, frames, cycle_index, input_frames, cycle_length):
|
||||
if cycle_index % 2 == 1: # Apply inversion for even cycles
|
||||
frames = [ImageOps.invert(frame) for frame in frames]
|
||||
return frames
|
||||
|
||||
def process_image_for_output(self, image) -> torch.Tensor:
|
||||
i = ImageOps.exif_transpose(image)
|
||||
if i.mode == 'I':
|
||||
i = i.point(lambda i: i * (1 / 255))
|
||||
image = i.convert("RGB")
|
||||
image_np = np.array(image).astype(np.float32) / 255.0
|
||||
return torch.from_numpy(image_np)[None,]
|
||||
|
||||
def apply_blur_to_frames(self, frames, gaussian_blur_amount):
|
||||
return [frame.filter(ImageFilter.GaussianBlur(gaussian_blur_amount)) for frame in frames]
|
||||
|
||||
def apply_contrast_to_frames(self, frames, contrast_factor):
|
||||
return [ImageEnhance.Contrast(frame).enhance(contrast_factor * 2) for frame in frames] # Apply 3x contrast factor
|
||||
|
||||
def composite_edge_fx(self, unblurred_frames, blurred_frames):
|
||||
final_frames = []
|
||||
for unblurred, blurred in zip(unblurred_frames, blurred_frames):
|
||||
# Create a black background
|
||||
final_frame = Image.new('RGB', unblurred.size, color='black')
|
||||
|
||||
# Check if the unblurred frame has an alpha channel
|
||||
if unblurred.mode == 'RGBA':
|
||||
mask = unblurred.split()[3]
|
||||
else:
|
||||
# If no alpha channel, use the image itself as a mask
|
||||
mask = unblurred.convert('L')
|
||||
|
||||
# Use unblurred frame as mask to cut out blurred frame
|
||||
final_frame.paste(blurred, mask=mask)
|
||||
|
||||
final_frames.append(final_frame)
|
||||
|
||||
return final_frames
|
||||
|
||||
def apply_fade_to_edge_fx(self, edge_fx_frames, transition_frames, hold_frames, fade_balance):
|
||||
faded_frames = []
|
||||
frames_per_cycle = transition_frames + hold_frames
|
||||
|
||||
# Calculate fade-up and fade-down frames, ensuring they sum to transition_frames
|
||||
fade_up_frames = max(1, min(transition_frames - 1, round(transition_frames * fade_balance)))
|
||||
fade_down_frames = transition_frames - fade_up_frames
|
||||
|
||||
for i, frame in enumerate(edge_fx_frames):
|
||||
cycle_position = i % frames_per_cycle
|
||||
|
||||
if cycle_position < fade_up_frames:
|
||||
# Fade in
|
||||
fade_factor = cycle_position / fade_up_frames
|
||||
elif cycle_position >= frames_per_cycle - fade_down_frames:
|
||||
# Fade out
|
||||
fade_factor = (frames_per_cycle - cycle_position) / fade_down_frames
|
||||
else:
|
||||
# Hold at full opacity
|
||||
fade_factor = 1.0
|
||||
|
||||
# Apply fade effect
|
||||
faded_frame = Image.new('RGB', frame.size, (0, 0, 0))
|
||||
faded_frame = Image.blend(faded_frame, frame, fade_factor)
|
||||
faded_frames.append(faded_frame)
|
||||
|
||||
return faded_frames
|
||||
|
||||
# Mapping the class to its name for the node system
|
||||
NODE_CLASS_MAPPINGS = {
|
||||
"DynamicAnimatedWeightsHelper": DynamicAnimatedWeightsHelper
|
||||
}
|
||||
|
||||
# Display name mappings for the node system
|
||||
NODE_DISPLAY_NAME_MAPPINGS = {
|
||||
"DynamicAnimatedWeightsHelper": "Dynamic Animated Weights"
|
||||
}
|
||||
@@ -0,0 +1,124 @@
|
||||
import torch
|
||||
|
||||
class EdgeFXSourceImages:
|
||||
def __init__(self):
|
||||
pass # This constructor does nothing but is required for class instantiation
|
||||
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
# Define the input types for the node
|
||||
return {
|
||||
"required": {
|
||||
"image_stream": ("IMAGE",), # Single image input for the stream of images
|
||||
"num_images": ("INT", {"default": 4, "min": 1}), # Integer input for the number of images
|
||||
"hold_length": ("INT", {"default": 5, "min": 1}), # Integer input for hold length with default 5 and minimum 1
|
||||
"transition_length": ("INT", {"default": 20, "min": 1}), # Integer input for transition length with default 20 and minimum 1
|
||||
"padding_frames": ("INT", {"default": 0, "min": 0}), # Integer input for padding frames with default 0 and minimum 0
|
||||
"push": ("INT", {"default": 0, "min": 0}),
|
||||
"retract": ("INT", {"default": 0, "min": 0}),
|
||||
}
|
||||
}
|
||||
|
||||
# Define the return types for the function outputs
|
||||
RETURN_TYPES = ("IMAGE", "IMAGE", "STRING", "STRING", "INT") # The function will return two image tensors, two strings, and an integer
|
||||
# Define the return names for the function outputs
|
||||
RETURN_NAMES = ("first_timeline", "second_timeline", "first_text_output", "second_text_output", "total_frames")
|
||||
# Name of the function to be executed
|
||||
FUNCTION = "generate_mask_definitions"
|
||||
# Category of the node
|
||||
CATEGORY = "advanced"
|
||||
|
||||
def generate_mask_definitions(self, image_stream, num_images, hold_length, transition_length, padding_frames, push, retract):
|
||||
# Initialize lists to hold the timelines and mask definitions
|
||||
first_timeline = []
|
||||
second_timeline = []
|
||||
first_text_output = ""
|
||||
second_text_output = ""
|
||||
|
||||
# Calculate the total number of frames for each image and the total number of frames for the entire animation
|
||||
frame_distance = hold_length + transition_length
|
||||
total_frames = num_images * frame_distance + padding_frames
|
||||
|
||||
# Get the actual number of images provided
|
||||
num_images_provided = image_stream.shape[0]
|
||||
|
||||
# Adjust hold value for the first cycle
|
||||
adjusted_first_hold = hold_length - retract + push
|
||||
|
||||
# Generate the first timeline with adjusted first hold value
|
||||
for i in range(num_images):
|
||||
index = i % num_images_provided
|
||||
current_hold = adjusted_first_hold if i == 0 else hold_length
|
||||
frame_distance = current_hold + transition_length
|
||||
repeated_images = image_stream[index:index+1].repeat(frame_distance, 1, 1, 1)
|
||||
first_timeline.append(repeated_images)
|
||||
|
||||
# Concatenate the list of tensors into a single tensor
|
||||
first_timeline = torch.cat(first_timeline, dim=0)
|
||||
|
||||
# Adjust padding length
|
||||
adjusted_padding_length = padding_frames + retract - push
|
||||
|
||||
# Generate the second timeline
|
||||
for i in range(1, num_images):
|
||||
index = i % num_images_provided
|
||||
repeated_images = image_stream[index:index+1].repeat(frame_distance, 1, 1, 1)
|
||||
second_timeline.append(repeated_images)
|
||||
repeated_images = image_stream[0:1].repeat(frame_distance, 1, 1, 1)
|
||||
second_timeline.append(repeated_images)
|
||||
|
||||
# Concatenate the list of tensors into a single tensor
|
||||
second_timeline = torch.cat(second_timeline, dim=0)
|
||||
|
||||
# Initialize frame number and create mask text outputs
|
||||
frame = 0
|
||||
while frame < num_images * frame_distance:
|
||||
frame_end_hold = frame + hold_length - 1
|
||||
frame_start_transition = frame_end_hold + 1
|
||||
frame_end_transition = frame_start_transition + transition_length - 1
|
||||
|
||||
first_text_output += f"{frame}:(1.0),\n"
|
||||
first_text_output += f"{frame_end_hold}:(1.0),\n"
|
||||
first_text_output += f"{frame_end_transition}:(0.0),\n"
|
||||
|
||||
second_text_output += f"{frame}:(0.0),\n"
|
||||
second_text_output += f"{frame_end_hold}:(0.0),\n"
|
||||
second_text_output += f"{frame_end_transition}:(1.0),\n"
|
||||
|
||||
frame = frame_end_transition + 1
|
||||
|
||||
# Add padding frames if specified
|
||||
if adjusted_padding_length > 0:
|
||||
padding_start_frame = frame
|
||||
last_image_first_timeline = first_timeline[-1:]
|
||||
last_image_second_timeline = second_timeline[-1:]
|
||||
|
||||
# Add padding frames to the timelines
|
||||
padding_images_first = last_image_first_timeline.repeat(adjusted_padding_length, 1, 1, 1)
|
||||
first_timeline = torch.cat((first_timeline, padding_images_first), dim=0)
|
||||
|
||||
padding_images_second = last_image_second_timeline.repeat(adjusted_padding_length, 1, 1, 1)
|
||||
second_timeline = torch.cat((second_timeline, padding_images_second), dim=0)
|
||||
|
||||
# Append padding frames to the text outputs
|
||||
for i in range(adjusted_padding_length):
|
||||
frame = padding_start_frame + i
|
||||
first_text_output += f"{frame}:(0.0),\n"
|
||||
second_text_output += f"{frame}:(1.0),\n" # Set to 1.0 for the second timeline
|
||||
|
||||
# Ensure the text output ends correctly
|
||||
first_text_output = first_text_output.strip().rstrip(',')
|
||||
second_text_output = second_text_output.strip().rstrip(',')
|
||||
|
||||
# Return the generated timelines, text outputs, and total frames
|
||||
return first_timeline, second_timeline, first_text_output, second_text_output, total_frames
|
||||
|
||||
# Mapping the class to its name for the node system
|
||||
NODE_CLASS_MAPPINGS = {
|
||||
"EdgeFXSourceImages": EdgeFXSourceImages # Map the class name to the node system
|
||||
}
|
||||
|
||||
# Display name mappings for the node system
|
||||
NODE_DISPLAY_NAME_MAPPINGS = {
|
||||
"EdgeFXSourceImages": "EdgeFX Source Images" # Map the display name to the node system
|
||||
}
|
||||
@@ -0,0 +1,86 @@
|
||||
# Florence Prompt Travel Helper
|
||||
# Version: 2.5
|
||||
import re
|
||||
from collections import defaultdict
|
||||
|
||||
class FlorencePromptTravelHelper:
|
||||
def __init__(self):
|
||||
pass
|
||||
|
||||
@classmethod
|
||||
def INPUT_TYPES(s):
|
||||
return {
|
||||
"required": {
|
||||
"bulk_text_input": ("STRING", {
|
||||
"multiline": True,
|
||||
"default": "",
|
||||
}),
|
||||
"hold_length": ("INT", {
|
||||
"default": 5,
|
||||
"min": 0
|
||||
}),
|
||||
"transition_length": ("INT", {
|
||||
"default": 5,
|
||||
"min": 0
|
||||
}),
|
||||
"end_padding_frames": ("INT", {
|
||||
"default": 10,
|
||||
"min": 0
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
RETURN_TYPES = ("STRING",)
|
||||
RETURN_NAMES = ("formatted_prompts",)
|
||||
FUNCTION = "process_bulk_text"
|
||||
CATEGORY = "advanced"
|
||||
|
||||
def process_bulk_text(self, bulk_text_input="", hold_length=5, transition_length=5, end_padding_frames=10):
|
||||
# Handle both string and list inputs
|
||||
if isinstance(bulk_text_input, list):
|
||||
bulk_text_input = ".,".join(bulk_text_input)
|
||||
|
||||
if not bulk_text_input or not bulk_text_input.strip():
|
||||
return ("", )
|
||||
|
||||
# Split the bulk input into individual prompts
|
||||
prompts = bulk_text_input.strip().split('.,')
|
||||
# Strip whitespace from all prompts except the last one
|
||||
prompts = [prompt.strip() for prompt in prompts[:-1]] + [prompts[-1].strip()]
|
||||
|
||||
frames = []
|
||||
current_frame = 0
|
||||
|
||||
# Create frames based on hold and transition lengths
|
||||
for prompt in prompts:
|
||||
prompt = prompt.replace('"', '\\"') # Escape quotes
|
||||
frames.append((current_frame, prompt))
|
||||
current_frame += hold_length
|
||||
frames.append((current_frame, prompt))
|
||||
current_frame += transition_length
|
||||
|
||||
# Add the first prompt again at the end to create a loop
|
||||
if prompts:
|
||||
first_prompt = prompts[0].replace('"', '\\"')
|
||||
frames.append((current_frame, first_prompt))
|
||||
current_frame += end_padding_frames
|
||||
# Add the final piece of the travel schedule
|
||||
frames.append((current_frame, first_prompt))
|
||||
|
||||
frame_dict = defaultdict(tuple)
|
||||
for frame, text in frames:
|
||||
frame_dict[frame] += (text,)
|
||||
|
||||
formatted_prompts = []
|
||||
for frame in sorted(frame_dict.keys()):
|
||||
formatted_prompts.append(f'"{frame}": "{", ".join(frame_dict[frame])}"')
|
||||
|
||||
return (',\n'.join(formatted_prompts), )
|
||||
|
||||
NODE_CLASS_MAPPINGS = {
|
||||
"FlorencePromptTravelHelper": FlorencePromptTravelHelper
|
||||
}
|
||||
|
||||
NODE_DISPLAY_NAME_MAPPINGS = {
|
||||
"FlorencePromptTravelHelper": "Florence Prompt Travel Helper"
|
||||
}
|
||||
@@ -0,0 +1,114 @@
|
||||
import torch
|
||||
|
||||
class MaskSequenceHelper:
|
||||
def __init__(self):
|
||||
pass # This constructor does nothing but is required for class instantiation
|
||||
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
# Define the input types for the node
|
||||
return {
|
||||
"required": {
|
||||
"image_stream": ("IMAGE",), # Single image input for the stream of images
|
||||
"num_images": ("INT", {"default": 4, "min": 1}), # Integer input for the number of images
|
||||
"hold_length": ("INT", {"default": 5, "min": 1}), # Integer input for hold length with default 5 and minimum 1
|
||||
"transition_length": ("INT", {"default": 20, "min": 1}), # Integer input for transition length with default 20 and minimum 1
|
||||
"padding_frames": ("INT", {"default": 0, "min": 0}), # Integer input for padding frames with default 0 and minimum 0
|
||||
}
|
||||
}
|
||||
|
||||
# Define the return types for the function outputs
|
||||
RETURN_TYPES = ("IMAGE", "IMAGE", "STRING", "STRING", "INT") # The function will return two image tensors, two strings, and an integer
|
||||
# Define the return names for the function outputs
|
||||
RETURN_NAMES = ("first_timeline", "second_timeline", "first_text_output", "second_text_output", "total_frames")
|
||||
# Name of the function to be executed
|
||||
FUNCTION = "generate_mask_definitions"
|
||||
# Category of the node
|
||||
CATEGORY = "advanced"
|
||||
|
||||
def generate_mask_definitions(self, image_stream, num_images, hold_length, transition_length, padding_frames):
|
||||
# Initialize lists to hold the timelines and mask definitions
|
||||
first_timeline = []
|
||||
second_timeline = []
|
||||
first_text_output = ""
|
||||
second_text_output = ""
|
||||
|
||||
# Calculate the total number of frames for each image and the total number of frames for the entire animation
|
||||
frame_distance = hold_length + transition_length
|
||||
total_frames = num_images * frame_distance + padding_frames
|
||||
|
||||
# Get the actual number of images provided
|
||||
num_images_provided = image_stream.shape[0]
|
||||
|
||||
# Generate the first timeline
|
||||
for i in range(num_images):
|
||||
index = i % num_images_provided
|
||||
repeated_images = image_stream[index:index+1].repeat(frame_distance, 1, 1, 1)
|
||||
first_timeline.append(repeated_images)
|
||||
|
||||
# Concatenate the list of tensors into a single tensor
|
||||
first_timeline = torch.cat(first_timeline, dim=0)
|
||||
|
||||
# Generate the second timeline
|
||||
for i in range(1, num_images):
|
||||
index = i % num_images_provided
|
||||
repeated_images = image_stream[index:index+1].repeat(frame_distance, 1, 1, 1)
|
||||
second_timeline.append(repeated_images)
|
||||
repeated_images = image_stream[0:1].repeat(frame_distance, 1, 1, 1)
|
||||
second_timeline.append(repeated_images)
|
||||
|
||||
# Concatenate the list of tensors into a single tensor
|
||||
second_timeline = torch.cat(second_timeline, dim=0)
|
||||
|
||||
# Initialize frame number and create mask text outputs
|
||||
frame = 0
|
||||
while frame < num_images * frame_distance:
|
||||
frame_end_hold = frame + hold_length - 1
|
||||
frame_start_transition = frame_end_hold + 1
|
||||
frame_end_transition = frame_start_transition + transition_length - 1
|
||||
|
||||
first_text_output += f"{frame}:(1.0),\n"
|
||||
first_text_output += f"{frame_end_hold}:(1.0),\n"
|
||||
first_text_output += f"{frame_end_transition}:(0.0),\n"
|
||||
|
||||
second_text_output += f"{frame}:(0.0),\n"
|
||||
second_text_output += f"{frame_end_hold}:(0.0),\n"
|
||||
second_text_output += f"{frame_end_transition}:(1.0),\n"
|
||||
|
||||
frame = frame_end_transition + 1
|
||||
|
||||
# Add padding frames if specified
|
||||
if padding_frames > 0:
|
||||
padding_start_frame = frame
|
||||
last_image_first_timeline = first_timeline[-1:]
|
||||
last_image_second_timeline = second_timeline[-1:]
|
||||
|
||||
# Add padding frames to the timelines
|
||||
padding_images_first = last_image_first_timeline.repeat(padding_frames, 1, 1, 1)
|
||||
first_timeline = torch.cat((first_timeline, padding_images_first), dim=0)
|
||||
|
||||
padding_images_second = last_image_second_timeline.repeat(padding_frames, 1, 1, 1)
|
||||
second_timeline = torch.cat((second_timeline, padding_images_second), dim=0)
|
||||
|
||||
# Append padding frames to the text outputs
|
||||
for i in range(padding_frames):
|
||||
frame = padding_start_frame + i
|
||||
first_text_output += f"{frame}:(0.0),\n"
|
||||
second_text_output += f"{frame}:(1.0),\n" # Set to 1.0 for the second timeline
|
||||
|
||||
# Ensure the text output ends correctly
|
||||
first_text_output = first_text_output.strip().rstrip(',')
|
||||
second_text_output = second_text_output.strip().rstrip(',')
|
||||
|
||||
# Return the generated timelines, text outputs, and total frames
|
||||
return first_timeline, second_timeline, first_text_output, second_text_output, total_frames
|
||||
|
||||
# Mapping the class to its name for the node system
|
||||
NODE_CLASS_MAPPINGS = {
|
||||
"MaskSequenceHelper": MaskSequenceHelper # Map the class name to the node system
|
||||
}
|
||||
|
||||
# Display name mappings for the node system
|
||||
NODE_DISPLAY_NAME_MAPPINGS = {
|
||||
"MaskSequenceHelper": "Mask Sequence Helper" # Map the display name to the node system
|
||||
}
|
||||
@@ -0,0 +1,83 @@
|
||||
# Prompt Travel Helper
|
||||
# Version: 2.2
|
||||
|
||||
import re
|
||||
from collections import defaultdict
|
||||
|
||||
class promptTravelHelper:
|
||||
def __init__(self):
|
||||
pass
|
||||
|
||||
@classmethod
|
||||
def INPUT_TYPES(s):
|
||||
return {
|
||||
"required": {
|
||||
"bulk_text_input": ("STRING", {
|
||||
"multiline": True,
|
||||
"default": "",
|
||||
}),
|
||||
"hold_length": ("INT", {
|
||||
"default": 5,
|
||||
"min": 0
|
||||
}),
|
||||
"transition_length": ("INT", {
|
||||
"default": 5,
|
||||
"min": 0
|
||||
}),
|
||||
"end_padding_frames": ("INT", {
|
||||
"default": 10,
|
||||
"min": 0
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
RETURN_TYPES = ("STRING",)
|
||||
RETURN_NAMES = ("formatted_prompts",)
|
||||
FUNCTION = "process_bulk_text"
|
||||
CATEGORY = "advanced"
|
||||
|
||||
def process_bulk_text(self, bulk_text_input="", hold_length=5, transition_length=5, end_padding_frames=10):
|
||||
if not bulk_text_input.strip():
|
||||
return ("", )
|
||||
|
||||
# Split the bulk input into individual prompts
|
||||
prompts = [prompt.strip() for prompt in bulk_text_input.strip().split('\n') if prompt.strip()]
|
||||
|
||||
frames = []
|
||||
current_frame = 0
|
||||
|
||||
# Create frames based on hold and transition lengths
|
||||
for prompt in prompts:
|
||||
prompt = prompt.replace('"', '\\"') # Escape quotes
|
||||
frames.append((current_frame, prompt))
|
||||
current_frame += hold_length
|
||||
frames.append((current_frame, prompt))
|
||||
current_frame += transition_length
|
||||
|
||||
# Add the first prompt again at the end to create a loop
|
||||
if prompts:
|
||||
first_prompt = prompts[0].replace('"', '\\"')
|
||||
frames.append((current_frame, first_prompt))
|
||||
current_frame += end_padding_frames
|
||||
|
||||
# Add the final piece of the travel schedule
|
||||
frames.append((current_frame, first_prompt))
|
||||
|
||||
frame_dict = defaultdict(tuple)
|
||||
|
||||
for frame, text in frames:
|
||||
frame_dict[frame] += (text,)
|
||||
|
||||
formatted_prompts = []
|
||||
for frame in sorted(frame_dict.keys()):
|
||||
formatted_prompts.append(f'"{frame}": "{", ".join(frame_dict[frame])}"')
|
||||
|
||||
return (',\n'.join(formatted_prompts), )
|
||||
|
||||
NODE_CLASS_MAPPINGS = {
|
||||
"PromptTravelHelper": promptTravelHelper
|
||||
}
|
||||
|
||||
NODE_DISPLAY_NAME_MAPPINGS = {
|
||||
"PromptTravelHelper": "Prompt Travel Helper"
|
||||
}
|
||||
+26
@@ -0,0 +1,26 @@
|
||||
# init.py
|
||||
from .ComfyUI_Live_Prompt_Interpolation import LivePromptInterpolation
|
||||
from .ComfyUI_Prompt_Stack_Manager import PromptStackManager
|
||||
from .DynamicAnimatedWeights import DynamicAnimatedWeightsHelper
|
||||
from .FlorenceTravelHelper import FlorencePromptTravelHelper
|
||||
from .MaskSequenceHelper import MaskSequenceHelper
|
||||
from .PromptTravelHelper import promptTravelHelper
|
||||
# Add any other necessary imports or initialization code here
|
||||
# Mapping the class to its name for the node system
|
||||
NODE_CLASS_MAPPINGS = {
|
||||
"LivePromptInterpolation": LivePromptInterpolation,
|
||||
"PromptStackManager": PromptStackManager,
|
||||
"DynamicAnimatedWeightsHelper": DynamicAnimatedWeightsHelper,
|
||||
"FlorencePromptTravelHelper": FlorencePromptTravelHelper,
|
||||
"MaskSequenceHelper": MaskSequenceHelper,
|
||||
"PromptTravelHelper": promptTravelHelper
|
||||
}
|
||||
# Display name mappings for the node system
|
||||
NODE_DISPLAY_NAME_MAPPINGS = {
|
||||
"LivePromptInterpolation": "Live Prompt Interpolation",
|
||||
"PromptStackManager": "Prompt Stack Manager",
|
||||
"DynamicAnimatedWeightsHelper": "Dynamic Animated Weights",
|
||||
"FlorencePromptTravelHelper": "Florence Prompt Travel Helper",
|
||||
"MaskSequenceHelper": "Mask Sequence Helper",
|
||||
"PromptTravelHelper": "Prompt Travel Helper"
|
||||
}
|
||||
Reference in New Issue
Block a user