diff --git a/BatchFuncs.py b/BatchFuncs.py index 555777a..2039b1b 100644 --- a/BatchFuncs.py +++ b/BatchFuncs.py @@ -299,9 +299,6 @@ def interpolate_prompt_series(animation_prompts, max_frames, start_frame, pre_te # if it is an in-between frame and the prompts differ, composable diffusion will be performed. return (cur_prompt_series, nxt_prompt_series, weight_series) -def encode_and_pad(cur_prompt_series, nxt_prompt_series,clip): - - return clip def BatchPoolAnimConditioning(cur_prompt_series, nxt_prompt_series, weight_series, clip): pooled_out = [] cond_out = [] @@ -310,8 +307,7 @@ def BatchPoolAnimConditioning(cur_prompt_series, nxt_prompt_series, weight_serie for i in range(len(cur_prompt_series)): tokens = clip.tokenize(str(cur_prompt_series[i])) cond_to, pooled_to = clip.encode_from_tokens(tokens, return_pooled=True) - tensor_size = cond_to.shape[1] - max_size = max(max_size, tensor_size) + max_size = max(max_size, cond_to.shape[1]) for i in range(len(cur_prompt_series)): tokens = clip.tokenize(str(cur_prompt_series[i])) cond_to, pooled_to = clip.encode_from_tokens(tokens, return_pooled=True) @@ -335,10 +331,8 @@ def BatchPoolAnimConditioning(cur_prompt_series, nxt_prompt_series, weight_serie final_pooled_output = torch.cat(pooled_out, dim=0) final_conditioning = torch.cat(cond_out, dim=0) - return [[final_conditioning, {"pooled_output": final_pooled_output}]] - def BatchGLIGENConditioning(cur_prompt_series, nxt_prompt_series, weight_series, clip): pooled_out = [] cond_out = [] @@ -349,6 +343,7 @@ def BatchGLIGENConditioning(cur_prompt_series, nxt_prompt_series, weight_series, cond_to, pooled_to = clip.encode_from_tokens(tokens, return_pooled=True) tensor_size = cond_to.shape[1] max_size = max(max_size, tensor_size) + for i in range(len(cur_prompt_series)): tokens = clip.tokenize(str(cur_prompt_series[i])) cond_to, pooled_to = clip.encode_from_tokens(tokens, return_pooled=True) diff --git a/ScheduleFuncs.py b/ScheduleFuncs.py index 044c3c6..416dbf8 100644 --- a/ScheduleFuncs.py +++ b/ScheduleFuncs.py @@ -3,6 +3,7 @@ import numexpr import torch +import torch.nn.functional as F import numpy as np import pandas as pd import re @@ -63,7 +64,7 @@ class ScheduleSettings: self.sync_context_to_pe = sync_option #Addweighted function from Comfyui -def addWeighted(conditioning_to, conditioning_from, conditioning_to_strength, max_size = 0): +def addWeighted(conditioning_to, conditioning_from, conditioning_to_strength, max_size=0): out = [] if len(conditioning_from) > 1: @@ -77,87 +78,53 @@ def addWeighted(conditioning_to, conditioning_from, conditioning_to_strength, ma pooled_output_to = conditioning_to[i][1].get("pooled_output", pooled_output_from) if max_size == 0: max_size = max(t1.shape[1], cond_from.shape[1]) - t0 = pad_with_zeros(cond_from, max_size) - t1 = pad_with_zeros(t1, max_size) + t0, max_size = pad_with_zeros(cond_from, max_size) + t1, max_size = pad_with_zeros(t1, t0.shape[1]) # Padding t1 to match max_size + t0, max_size = pad_with_zeros(t0, t1.shape[1]) tw = torch.mul(t1, conditioning_to_strength) + torch.mul(t0, (1.0 - conditioning_to_strength)) t_to = conditioning_to[i][1].copy() t_to["pooled_output"] = pooled_output_from - - if pooled_output_from is not None and pooled_output_to is not None: - # Pad pooled outputs if available - pooled_output_to = pad_with_zeros(pooled_output_to, max_size) - pooled_output_from = pad_with_zeros(pooled_output_from, max_size) - t_to["pooled_output"] = torch.mul(pooled_output_to, conditioning_to_strength) + torch.mul(pooled_output_from, (1.0 - conditioning_to_strength)) - elif pooled_output_from is not None: - t_to["pooled_output"] = pooled_output_from - n = [tw, t_to] out.append(n) return out + +def pad_with_zeros(tensor, target_length): + current_length = tensor.shape[1] + + if current_length < target_length: + # Calculate the required padding length + pad_length = target_length - current_length + + # Calculate padding on both sides to maintain the tensor's original shape + left_pad = pad_length // 2 + right_pad = pad_length - left_pad + + # Pad the tensor along the second dimension + tensor = F.pad(tensor, (0, 0, left_pad, right_pad)) + + return tensor, target_length + def process_input_text(text: str) -> dict: input_text = "{" + text + "}" input_text = re.sub(r',\s*}', '}', input_text) animation_prompts = json.loads(input_text.strip()) return animation_prompts -def pad_with_zeros(tensor, target_length): - current_length = tensor.shape[1] - if current_length < target_length: - padding = torch.zeros(tensor.shape[0], target_length - current_length, tensor.shape[2]).to(tensor.device) - tensor = torch.cat([tensor, padding], dim=1) - return tensor +#def pad_with_zeros(tensor, target_length): +# current_length = tensor.shape[1] +# if current_length < target_length: +# padding = torch.zeros(tensor.shape[0], target_length - current_length, tensor.shape[2]).to(tensor.device) +# tensor = torch.cat([tensor, padding], dim=1) +# return tensor def check_is_number(value): float_pattern = r'^(?=.)([+-]?([0-9]*)(\.([0-9]+))?)$' return re.match(float_pattern, value) -def split_weighted_subprompts(text, frame=0, pre_text='', app_text=''): - pre_text = str(pre_text) - app_text = str(app_text) - - if "--neg" in pre_text: - pre_pos, pre_neg = pre_text.split("--neg") - else: - pre_pos, pre_neg = pre_text, "" - - if "--neg" in app_text: - app_pos, app_neg = app_text.split("--neg") - else: - app_pos, app_neg = app_text, "" - - # Check if the text is a string; if not, convert it to a string - if not isinstance(text, str): - text = str(text) - - math_parser = re.compile("(?P(`[\S\s]*?`))", re.VERBOSE) - - parsed_prompt = re.sub(math_parser, lambda m: str(parse_weight(m, frame)), text) - - negative_prompts = "" - positive_prompts = "" - - # Check if the last character is '0' and remove it - prompt_split = parsed_prompt.split("--neg") - if len(prompt_split) > 1: - positive_prompts, negative_prompts = prompt_split[0], prompt_split[1] - else: - positive_prompts = prompt_split[0] - - pos = {} - neg = {} - pos[frame] = (str(pre_pos) + " " + str(positive_prompts) + " " + str(app_pos)) - neg[frame] = (str(pre_neg) + " " + str(negative_prompts) + " " + str(app_neg)) - if pos[frame].endswith('0'): - pos[frame] = pos[frame][:-1] - if neg[frame].endswith('0'): - neg[frame] = neg[frame][:-1] - - return pos, neg - def parse_weight(match, frame=0, max_frames=0) -> float: #calculate weight steps for in-betweens w_raw = match.group("weight") max_f = max_frames # this line has to be left intact as it's in use by numexpr even though it looks like it doesn't @@ -172,91 +139,6 @@ def parse_weight(match, frame=0, max_frames=0) -> float: #calculate weight steps return 1 return float(numexpr.evaluate(w_raw[1:-1])) -def prepare_prompt(prompt_series, max_frames, frame_idx, prompt_weight_1 = 0, prompt_weight_2 = 0, prompt_weight_3 = 0, prompt_weight_4 = 0): #calculate expressions from the text input and return a string - max_f = max_frames - 1 - pattern = r'`.*?`' #set so the expression will be read between two backticks (``) - regex = re.compile(pattern) - prompt_parsed = str(prompt_series) - for match in regex.finditer(prompt_parsed): - matched_string = match.group(0) - parsed_string = matched_string.replace('t', f'{frame_idx}').replace("pw_a", f"prompt_weight_1").replace("pw_b", f"prompt_weight_2").replace("pw_c", f"prompt_weight_3").replace("pw_d", f"prompt_weight_4").replace("max_f", f"{max_f}").replace('`', '') #replace t, max_f and `` respectively - parsed_value = numexpr.evaluate(parsed_string) - prompt_parsed = prompt_parsed.replace(matched_string, str(parsed_value)) - return prompt_parsed.strip() - -def interpolate_string(animation_prompts, max_frames, current_frame, pre_text, app_text, prompt_weight_1, - prompt_weight_2, prompt_weight_3, - prompt_weight_4): # parse the conditioning strength and determine in-betweens. - # Get prompts sorted by keyframe - max_f = max_frames # needed for numexpr even though it doesn't look like it's in use. - parsed_animation_prompts = {} - for key, value in animation_prompts.items(): - if check_is_number(key): # default case 0:(1 + t %5), 30:(5-t%2) - parsed_animation_prompts[key] = value - else: # math on the left hand side case 0:(1 + t %5), maxKeyframes/2:(5-t%2) - parsed_animation_prompts[int(numexpr.evaluate(key))] = value - - sorted_prompts = sorted(parsed_animation_prompts.items(), key=lambda item: int(item[0])) - - # Setup containers for interpolated prompts - cur_prompt_series = pd.Series([np.nan for a in range(max_frames)]) - - # simple array for strength values - weight_series = [np.nan] * max_frames - - # in case there is only one keyed promt, set all prompts to that prompt - if len(sorted_prompts) - 1 == 0: - for i in range(0, len(cur_prompt_series) - 1): - current_prompt = sorted_prompts[0][1] - cur_prompt_series[i] = str(pre_text) + " " + str(current_prompt) + " " + str(app_text) - - # Initialized outside of loop for nan check - current_key = 0 - next_key = 0 - - # For every keyframe prompt except the last - for i in range(0, len(sorted_prompts) - 1): - # Get current and next keyframe - current_key = int(sorted_prompts[i][0]) - next_key = int(sorted_prompts[i + 1][0]) - - # Ensure there's no weird ordering issues or duplication in the animation prompts - # (unlikely because we sort above, and the json parser will strip dupes) - if current_key >= next_key: - print( - f"WARNING: Sequential prompt keyframes {i}:{current_key} and {i + 1}:{next_key} are not monotonously increasing; skipping interpolation.") - continue - - # Get current and next keyframes' positive and negative prompts (if any) - current_prompt = sorted_prompts[i][1] - - for f in range(current_key, next_key): - # add the appropriate prompts and weights to their respective containers. - cur_prompt_series[f] = '' - weight_series[f] = 0.0 - - cur_prompt_series[f] += (str(pre_text) + " " + str(current_prompt) + " " + str(app_text)) - - current_key = next_key - next_key = max_frames - # second loop to catch any nan runoff - - for f in range(current_key, next_key): - # add the appropriate prompts and weights to their respective containers. - cur_prompt_series[f] = '' - cur_prompt_series[f] += (str(pre_text) + " " + str(current_prompt) + " " + str(app_text)) - - # Evaluate the current and next prompt's expressions - cur_prompt_series[current_frame] = prepare_prompt(cur_prompt_series[current_frame], max_frames, current_frame, - prompt_weight_1, prompt_weight_2, prompt_weight_3, - prompt_weight_4) - - # Show the to/from prompts with evaluated expressions for transparency. - print("\n", "Max Frames: ", max_frames, "\n", "Current Prompt: ", cur_prompt_series[current_frame], "\n") - - # Output methods depending if the prompts are the same or if the current frame is a keyframe. - # if it is an in-between frame and the prompts differ, composable diffusion will be performed. - return (cur_prompt_series[current_frame]) def PoolAnimConditioning(cur_prompt, nxt_prompt, weight, clip): if str(cur_prompt) == str(nxt_prompt): tokens = clip.tokenize(str(cur_prompt)) @@ -297,182 +179,4 @@ def SDXLencode(g, l, settings:ScheduleSettings, clip): "crop_h": settings.crop_h, "target_width": settings.target_width, "target_height": settings.target_height - }]] - -def interpolate_prompts_SDXL(animation_promptsG, animation_promptsL, max_frames, current_frame, clip, app_text_G, app_text_L, pre_text_G, pre_text_L, pw_a, pw_b, pw_c, pw_d, width, height, crop_w, crop_h, target_width, target_height, print_output): #parse the conditioning strength and determine in-betweens. - #Get prompts sorted by keyframe - max_f = max_frames #needed for numexpr even though it doesn't look like it's in use. - parsed_animation_promptsG = {} - parsed_animation_promptsL = {} - for key, value in animation_promptsG.items(): - if check_is_number(key): #default case 0:(1 + t %5), 30:(5-t%2) - parsed_animation_promptsG[key] = value - else: #math on the left hand side case 0:(1 + t %5), maxKeyframes/2:(5-t%2) - parsed_animation_promptsG[int(numexpr.evaluate(key))] = value - - sorted_prompts_G = sorted(parsed_animation_promptsG.items(), key=lambda item: int(item[0])) - - for key, value in animation_promptsL.items(): - if check_is_number(key): #default case 0:(1 + t %5), 30:(5-t%2) - parsed_animation_promptsL[key] = value - else: #math on the left hand side case 0:(1 + t %5), maxKeyframes/2:(5-t%2) - parsed_animation_promptsL[int(numexpr.evaluate(key))] = value - - sorted_prompts_L = sorted(parsed_animation_promptsL.items(), key=lambda item: int(item[0])) - - #Setup containers for interpolated prompts - cur_prompt_series_G = pd.Series([np.nan for a in range(max_frames)]) - nxt_prompt_series_G = pd.Series([np.nan for a in range(max_frames)]) - - cur_prompt_series_L = pd.Series([np.nan for a in range(max_frames)]) - nxt_prompt_series_L = pd.Series([np.nan for a in range(max_frames)]) - - #simple array for strength values - weight_series = [np.nan] * max_frames - - #in case there is only one keyed promt, set all prompts to that prompt - if len(sorted_prompts_G) - 1 == 0: - for i in range(0, len(cur_prompt_series_G)-1): - current_prompt_G = sorted_prompts_G[0][1] - cur_prompt_series_G[i] = str(pre_text_G) + " " + str(current_prompt_G) + " " + str(app_text_G) - nxt_prompt_series_G[i] = str(pre_text_G) + " " + str(current_prompt_G) + " " + str(app_text_G) - - if len(sorted_prompts_L) - 1 == 0: - for i in range(0, len(cur_prompt_series_L)-1): - current_prompt_L = sorted_prompts_L[0][1] - cur_prompt_series_L[i] = str(pre_text_L) + " " + str(current_prompt_L) + " " + str(app_text_L) - nxt_prompt_series_L[i] = str(pre_text_L) + " " + str(current_prompt_L) + " " + str(app_text_L) - - #Initialized outside of loop for nan check - current_key = 0 - next_key = 0 - - # For every keyframe prompt except the last - for i in range(0, len(sorted_prompts_G) - 1): - # Get current and next keyframe - current_key = int(sorted_prompts_G[i][0]) - next_key = int(sorted_prompts_G[i + 1][0]) - - # Ensure there's no weird ordering issues or duplication in the animation prompts - # (unlikely because we sort above, and the json parser will strip dupes) - if current_key >= next_key: - print(f"WARNING: Sequential prompt keyframes {i}:{current_key} and {i + 1}:{next_key} are not monotonously increasing; skipping interpolation.") - continue - - # Get current and next keyframes' positive and negative prompts (if any) - current_prompt_G = sorted_prompts_G[i][1] - next_prompt_G = sorted_prompts_G[i + 1][1] - - # Calculate how much to shift the weight from current to next prompt at each frame. - weight_step = 1 / (next_key - current_key) - - for f in range(current_key, next_key): - next_weight = weight_step * (f - current_key) - current_weight = 1 - next_weight - - #add the appropriate prompts and weights to their respective containers. - cur_prompt_series_G[f] = '' - nxt_prompt_series_G[f] = '' - weight_series[f] = 0.0 - - cur_prompt_series_G[f] += (str(pre_text_G) + " " + str(current_prompt_G) + " " + str(app_text_G)) - nxt_prompt_series_G[f] += (str(pre_text_G) + " " + str(next_prompt_G) + " " + str(app_text_G)) - - weight_series[f] += current_weight - - current_key = next_key - next_key = max_frames - current_weight = 0.0 - #second loop to catch any nan runoff - for f in range(current_key, next_key): - next_weight = weight_step * (f - current_key) - - #add the appropriate prompts and weights to their respective containers. - cur_prompt_series_G[f] = '' - nxt_prompt_series_G[f] = '' - weight_series[f] = current_weight - - cur_prompt_series_G[f] += (str(pre_text_G) + " " + str(current_prompt_G) + " " + str(app_text_G)) - nxt_prompt_series_G[f] += (str(pre_text_G) + " " + str(next_prompt_G) + " " + str(app_text_G)) - - - #Reset outside of loop for nan check - current_key = 0 - next_key = 0 - - # For every keyframe prompt except the last - for i in range(0, len(sorted_prompts_L) - 1): - # Get current and next keyframe - current_key = int(sorted_prompts_L[i][0]) - next_key = int(sorted_prompts_L[i + 1][0]) - - # Ensure there's no weird ordering issues or duplication in the animation prompts - # (unlikely because we sort above, and the json parser will strip dupes) - if current_key >= next_key: - print(f"WARNING: Sequential prompt keyframes {i}:{current_key} and {i + 1}:{next_key} are not monotonously increasing; skipping interpolation.") - continue - - # Get current and next keyframes' positive and negative prompts (if any) - current_prompt_L = sorted_prompts_L[i][1] - next_prompt_L = sorted_prompts_L[i + 1][1] - - # Calculate how much to shift the weight from current to next prompt at each frame. - weight_step = 1 / (next_key - current_key) - - for f in range(current_key, next_key): - next_weight = weight_step * (f - current_key) - current_weight = 1 - next_weight - - #add the appropriate prompts and weights to their respective containers. - cur_prompt_series_L[f] = '' - nxt_prompt_series_L[f] = '' - weight_series[f] = 0.0 - - cur_prompt_series_L[f] += (str(pre_text_L) + " " + str(current_prompt_L) + " " + str(app_text_L)) - nxt_prompt_series_L[f] += (str(pre_text_L) + " " + str(next_prompt_L) + " " + str(app_text_L)) - - weight_series[f] += current_weight - - current_key = next_key - next_key = max_frames - current_weight = 0.0 - #second loop to catch any nan runoff - for f in range(current_key, next_key): - next_weight = weight_step * (f - current_key) - - #add the appropriate prompts and weights to their respective containers. - cur_prompt_series_L[f] = '' - nxt_prompt_series_L[f] = '' - weight_series[f] = current_weight - - cur_prompt_series_L[f] += (str(pre_text_L) + " " + str(current_prompt_L) + " " + str(app_text_L)) - nxt_prompt_series_L[f] += (str(pre_text_L) + " " + str(next_prompt_L) + " " + str(app_text_L)) - - #Evaluate the current and next prompt's expressions - cur_prompt_series_G[current_frame] = prepare_prompt(cur_prompt_series_G[current_frame], max_frames, current_frame, pw_a, pw_b, pw_c, pw_d) - nxt_prompt_series_G[current_frame] = prepare_prompt(nxt_prompt_series_G[current_frame], max_frames, current_frame, pw_a, pw_b, pw_c, pw_d) - cur_prompt_series_L[current_frame] = prepare_prompt(cur_prompt_series_L[current_frame], max_frames, current_frame, pw_a, pw_b, pw_c, pw_d) - nxt_prompt_series_L[current_frame] = prepare_prompt(nxt_prompt_series_L[current_frame], max_frames, current_frame, pw_a, pw_b, pw_c, pw_d) - if print_output == True: - #Show the to/from prompts with evaluated expressions for transparency. - print("\n", "G_Clip:", "\n", "Max Frames: ", max_frames, "\n", "Current Prompt: ", cur_prompt_series_G[current_frame], "\n", "Next Prompt: ", nxt_prompt_series_G[current_frame], "\n", "Strength : ", weight_series[current_frame], "\n") - - print("\n", "L_Clip:", "\n", "Max Frames: ", max_frames, "\n", "Current Prompt: ", cur_prompt_series_L[current_frame], "\n", "Next Prompt: ", nxt_prompt_series_L[current_frame], "\n", "Strength : ", weight_series[current_frame], "\n") - - #Output methods depending if the prompts are the same or if the current frame is a keyframe. - #if it is an in-between frame and the prompts differ, composable diffusion will be performed. - current_cond = SDXLencode(settings, clip, cur_prompt_series_G[current_frame], cur_prompt_series_L[current_frame]) - - if str(cur_prompt_series_G[current_frame]) == str(nxt_prompt_series_G[current_frame]) and str(cur_prompt_series_L[current_frame]) == str(nxt_prompt_series_L[current_frame]): - return current_cond - - if weight_series[current_frame] == 1: - return current_cond - - if weight_series[current_frame] == 0: - next_cond = SDXLencode(settings, clip, nxt_prompt_series_G[current_frame], nxt_prompt_series_L[current_frame]) - return next_cond - - else: - next_cond = SDXLencode(settings, clip, nxt_prompt_series_G[current_frame], nxt_prompt_series_L[current_frame]) - return addWeighted(current_cond, next_cond, weight_series[current_frame]) \ No newline at end of file + }]] \ No newline at end of file diff --git a/ScheduleTypes.py b/ScheduleTypes.py index bc21c0e..a5020b0 100644 --- a/ScheduleTypes.py +++ b/ScheduleTypes.py @@ -24,8 +24,6 @@ def prompt_schedule(settings:ScheduleSettings,clip): pos_cur_prompt, pos_nxt_prompt, weight = interpolate_prompt_seriesA(pos, settings) neg_cur_prompt, neg_nxt_prompt, weight = interpolate_prompt_seriesA(neg, settings) - #encode prompts - # Apply composable diffusion across the batch p = PoolAnimConditioning(pos_cur_prompt[settings.current_frame], pos_nxt_prompt[settings.current_frame], weight[settings.current_frame], clip) @@ -47,11 +45,6 @@ def batch_prompt_schedule(settings:ScheduleSettings,clip): pos_cur_prompt, pos_nxt_prompt, weight = interpolate_prompt_seriesA(pos, settings) neg_cur_prompt, neg_nxt_prompt, weight = interpolate_prompt_seriesA(neg, settings) - # encode prompts - - # pad conditionings to largest tensor size - - # Apply composable diffusion across the batch p = BatchPoolAnimConditioning(pos_cur_prompt, pos_nxt_prompt, weight, clip, ) n = BatchPoolAnimConditioning(neg_cur_prompt, neg_nxt_prompt, weight, clip, ) diff --git a/ScheduledNodes.py b/ScheduledNodes.py index 522733c..cd45ad6 100644 --- a/ScheduledNodes.py +++ b/ScheduledNodes.py @@ -214,6 +214,9 @@ class BatchPromptScheduleLatentInput: ) return batch_prompt_schedule_latentInput(settings,clip, num_latents) +# This node prepares the strings and calculates +# the numexpr expressions. It returns a single +# string at the current_frame input. class StringSchedule: @classmethod def INPUT_TYPES(s): @@ -261,6 +264,10 @@ class StringSchedule: ) return string_schedule(settings) + +# This node prepares the strings and calculates +# the numexpr expressions. It returns a batch of +# strings. class BatchStringSchedule: @classmethod def INPUT_TYPES(s): @@ -311,7 +318,75 @@ class BatchStringSchedule: ) return batch_string_schedule(settings) +# Same as the regular node just for SDXL +# clips instead. the G and L clip can be +# scheduled separately before tokenization, +# goes through the same add_weighted process +# and returns the current, next or averaged +# conditioning. +class PromptScheduleEncodeSDXL: + @classmethod + def INPUT_TYPES(s): + return {"required": { + "width": ("INT", {"default": 1024.0, "min": 0, "max": MAX_RESOLUTION}), + "height": ("INT", {"default": 1024.0, "min": 0, "max": MAX_RESOLUTION}), + "crop_w": ("INT", {"default": 0, "min": 0, "max": MAX_RESOLUTION}), + "crop_h": ("INT", {"default": 0, "min": 0, "max": MAX_RESOLUTION}), + "target_width": ("INT", {"default": 1024.0, "min": 0, "max": MAX_RESOLUTION}), + "target_height": ("INT", {"default": 1024.0, "min": 0, "max": MAX_RESOLUTION}), + "text_g": ("STRING", {"multiline": True, }), "clip": ("CLIP", ), + "text_l": ("STRING", {"multiline": True, }), "clip": ("CLIP", ), + "max_frames": ("INT", {"default": 120.0, "min": 1.0, "max": 999999.0, "step": 1.0}), + "current_frame": ("INT", {"default": 0.0, "min": 0.0, "max": 999999.0, "step": 1.0}), + "print_output":("BOOLEAN", {"default": False}) + }, + "optional": { + "pre_text_G": ("STRING", {"multiline": True, "forceInput": True}), + "app_text_G": ("STRING", {"multiline": True, "forceInput": True}), + "pre_text_L": ("STRING", {"multiline": True, "forceInput": True}), + "app_text_L": ("STRING", {"multiline": True, "forceInput": True}), + "pw_a": ("FLOAT", {"default": 0.0, "min": -9999.0, "max": 9999.0, "step": 0.1, "forceInput": True }), + "pw_b": ("FLOAT", {"default": 0.0, "min": -9999.0, "max": 9999.0, "step": 0.1, "forceInput": True }), + "pw_c": ("FLOAT", {"default": 0.0, "min": -9999.0, "max": 9999.0, "step": 0.1, "forceInput": True }), + "pw_d": ("FLOAT", {"default": 0.0, "min": -9999.0, "max": 9999.0, "step": 0.1, "forceInput": True }), + } + } + RETURN_TYPES = ("CONDITIONING","CONDITIONING",) + RETURN_NAMES = ("POS", "NEG",) + FUNCTION = "animate" + CATEGORY = "FizzNodes 📅🅕🅝/ScheduleNodes" + + def animate(self, clip, width, height, crop_w, crop_h, target_width, target_height, text_g, text_l, app_text_G, app_text_L, pre_text_G, pre_text_L, max_frames, current_frame, print_output, pw_a, pw_b, pw_c, pw_d): + settings = ScheduleSettings( + text_g=text_g, + pre_text_G=pre_text_G, + app_text_G=app_text_G, + text_L=text_l, + pre_text_L=pre_text_L, + app_text_L=app_text_L, + max_frames=max_frames, + current_frame=current_frame, + print_output=print_output, + pw_a=pw_a, + pw_b=pw_b, + pw_c=pw_c, + pw_d=pw_d, + start_frame=0, + width=width, + height=height, + crop_w=crop_w, + crop_h=crop_h, + target_width=target_width, + target_height=target_height, + ) + return prompt_schedule_SDXL(settings,clip) + +# Same as the regular node just for SDXL +# clips instead. the G and L clip can be +# scheduled separately before tokenization, +# goes through the same add_weighted process +# and returns a batch of conditionings. class BatchPromptScheduleEncodeSDXL: @classmethod def INPUT_TYPES(s): @@ -370,6 +445,13 @@ class BatchPromptScheduleEncodeSDXL: ) return batch_prompt_schedule_SDXL(settings, clip) +# Same as the regular node just for SDXL +# clips instead. the G and L clip can be +# scheduled separately before tokenization, +# goes through the same add_weighted process +# and returns a batch of conditionings. The +# max_size is input by the number of latents +# in the input. class BatchPromptScheduleEncodeSDXLLatentInput: @classmethod def INPUT_TYPES(s): @@ -427,63 +509,7 @@ class BatchPromptScheduleEncodeSDXLLatentInput: ) return batch_prompt_schedule_SDXL_latentInput(settings, clip, num_latents) -class PromptScheduleEncodeSDXL: - @classmethod - def INPUT_TYPES(s): - return {"required": { - "width": ("INT", {"default": 1024.0, "min": 0, "max": MAX_RESOLUTION}), - "height": ("INT", {"default": 1024.0, "min": 0, "max": MAX_RESOLUTION}), - "crop_w": ("INT", {"default": 0, "min": 0, "max": MAX_RESOLUTION}), - "crop_h": ("INT", {"default": 0, "min": 0, "max": MAX_RESOLUTION}), - "target_width": ("INT", {"default": 1024.0, "min": 0, "max": MAX_RESOLUTION}), - "target_height": ("INT", {"default": 1024.0, "min": 0, "max": MAX_RESOLUTION}), - "text_g": ("STRING", {"multiline": True, }), "clip": ("CLIP", ), - "text_l": ("STRING", {"multiline": True, }), "clip": ("CLIP", ), - "max_frames": ("INT", {"default": 120.0, "min": 1.0, "max": 999999.0, "step": 1.0}), - "current_frame": ("INT", {"default": 0.0, "min": 0.0, "max": 999999.0, "step": 1.0}), - "print_output":("BOOLEAN", {"default": False}) - }, - "optional": { - "pre_text_G": ("STRING", {"multiline": True, "forceInput": True}), - "app_text_G": ("STRING", {"multiline": True, "forceInput": True}), - "pre_text_L": ("STRING", {"multiline": True, "forceInput": True}), - "app_text_L": ("STRING", {"multiline": True, "forceInput": True}), - "pw_a": ("FLOAT", {"default": 0.0, "min": -9999.0, "max": 9999.0, "step": 0.1, "forceInput": True }), - "pw_b": ("FLOAT", {"default": 0.0, "min": -9999.0, "max": 9999.0, "step": 0.1, "forceInput": True }), - "pw_c": ("FLOAT", {"default": 0.0, "min": -9999.0, "max": 9999.0, "step": 0.1, "forceInput": True }), - "pw_d": ("FLOAT", {"default": 0.0, "min": -9999.0, "max": 9999.0, "step": 0.1, "forceInput": True }), - } - } - RETURN_TYPES = ("CONDITIONING","CONDITIONING",) - RETURN_NAMES = ("POS", "NEG",) - FUNCTION = "animate" - CATEGORY = "FizzNodes 📅🅕🅝/ScheduleNodes" - - def animate(self, clip, width, height, crop_w, crop_h, target_width, target_height, text_g, text_l, app_text_G, app_text_L, pre_text_G, pre_text_L, max_frames, current_frame, print_output, pw_a, pw_b, pw_c, pw_d): - settings = ScheduleSettings( - text_g=text_g, - pre_text_G=pre_text_G, - app_text_G=app_text_G, - text_L=text_l, - pre_text_L=pre_text_L, - app_text_L=app_text_L, - max_frames=max_frames, - current_frame=current_frame, - print_output=print_output, - pw_a=pw_a, - pw_b=pw_b, - pw_c=pw_c, - pw_d=pw_d, - start_frame=0, - width=width, - height=height, - crop_w=crop_w, - crop_h=crop_h, - target_width=target_width, - target_height=target_height, - ) - return prompt_schedule_SDXL(settings,clip) # This node schedules the prompt using separate nodes as the keyframes. # The values in the prompt are evaluated in NodeFlowEnd. @@ -569,6 +595,7 @@ class PromptScheduleNodeFlowEnd: ) return prompt_schedule(settings, clip) +#same as the other node end except it returns a batch class BatchPromptScheduleNodeFlowEnd: @classmethod def INPUT_TYPES(s): @@ -622,6 +649,10 @@ class BatchPromptScheduleNodeFlowEnd: ) return batch_prompt_schedule(settings, clip) +# WIP, requires some hijacking but otherwise +# applies every scheduled gligen bound box to +# a batch of latents with the scheduled +# conditionings class BatchGLIGENSchedule: @classmethod def INPUT_TYPES(s): @@ -744,7 +775,13 @@ class BatchValueScheduleLatentInput: print("ValueSchedule: ", t) return (t, list(map(int,t)), num_latents, ) -# Expects a Batch Value Schedule list input, it exports an image batch with images taken from an input image batch +# Expects a Batch Value Schedule list input, +# it exports an image batch with images taken +# from an input image batch. +# Original code is from: +# ComfyUI-Image-Selector by SLAPaper +# https://github.com/SLAPaper/ComfyUI-Image-Selector +# licensed under Apache-2.0 class ImagesFromBatchSchedule: @classmethod def INPUT_TYPES(s): @@ -773,7 +810,6 @@ class ImagesFromBatchSchedule: selImages = selectImages(images,pos_cur_prompt[current_frame]) return selImages - def selectImages(images: torch.Tensor, selected_indexes: str): shape = images.shape len_first_dim = shape[0]