462 lines
23 KiB
Python
462 lines
23 KiB
Python
from comfy_api.latest import io
|
|
from typing import Union
|
|
import numpy as np
|
|
from collections.abc import Iterable
|
|
|
|
from .utils import ControlWeights, TimestepKeyframe, TimestepKeyframeGroup, LatentKeyframe, LatentKeyframeGroup
|
|
from .utils import StrengthInterpolation as SI
|
|
from .logger import logger
|
|
|
|
class TimestepKeyframeNode(io.ComfyNode):
|
|
OUTDATED_DUMMY = -39
|
|
|
|
@classmethod
|
|
def define_schema(cls) -> io.Schema:
|
|
return io.Schema(
|
|
node_id='TimestepKeyframe',
|
|
display_name='Timestep Keyframe 🛂🅐🅒🅝',
|
|
category='Adv-ControlNet 🛂🅐🅒🅝/keyframes',
|
|
inputs=[
|
|
io.Float.Input('start_percent', default=0.0, max=1.0, min=0.0, step=0.001),
|
|
io.Custom('TIMESTEP_KEYFRAME').Input('prev_timestep_kf', optional=True),
|
|
io.Float.Input('strength', optional=True, default=1.0, max=10.0, min=0.0, step=0.001),
|
|
io.Custom('CONTROL_NET_WEIGHTS').Input('cn_weights', optional=True),
|
|
io.Custom('LATENT_KEYFRAME').Input('latent_keyframe', optional=True),
|
|
io.Float.Input('null_latent_kf_strength', optional=True, default=0.0, max=10.0, min=0.0, step=0.001),
|
|
io.Boolean.Input('inherit_missing', optional=True, default=True),
|
|
io.Int.Input('guarantee_steps', optional=True, default=1, max=9007199254740991, min=0),
|
|
io.Mask.Input('mask_optional', optional=True)
|
|
],
|
|
outputs=[
|
|
io.Custom('TIMESTEP_KEYFRAME').Output('TIMESTEP_KF', is_output_list=False)
|
|
]
|
|
)
|
|
|
|
|
|
@classmethod
|
|
def execute(cls,
|
|
start_percent: float,
|
|
strength: float=1.0,
|
|
cn_weights: ControlWeights=None, control_net_weights: ControlWeights=None, # old name
|
|
latent_keyframe: LatentKeyframeGroup=None,
|
|
prev_timestep_kf: TimestepKeyframeGroup=None, prev_timestep_keyframe: TimestepKeyframeGroup=None, # old name
|
|
null_latent_kf_strength: float=0.0,
|
|
inherit_missing=True,
|
|
guarantee_steps=OUTDATED_DUMMY,
|
|
guarantee_usage=True, # old input
|
|
mask_optional=None,):
|
|
# if using outdated dummy value, means node on workflow is outdated and should appropriately convert behavior
|
|
if guarantee_steps == cls.OUTDATED_DUMMY:
|
|
guarantee_steps = int(guarantee_usage)
|
|
control_net_weights = control_net_weights if control_net_weights else cn_weights
|
|
prev_timestep_keyframe = prev_timestep_keyframe if prev_timestep_keyframe else prev_timestep_kf
|
|
if not prev_timestep_keyframe:
|
|
prev_timestep_keyframe = TimestepKeyframeGroup()
|
|
else:
|
|
prev_timestep_keyframe = prev_timestep_keyframe.clone()
|
|
keyframe = TimestepKeyframe(start_percent=start_percent, strength=strength, null_latent_kf_strength=null_latent_kf_strength,
|
|
control_weights=control_net_weights, latent_keyframes=latent_keyframe, inherit_missing=inherit_missing,
|
|
guarantee_steps=guarantee_steps, mask_hint_orig=mask_optional)
|
|
prev_timestep_keyframe.add(keyframe)
|
|
return io.NodeOutput(prev_timestep_keyframe,)
|
|
|
|
|
|
class TimestepKeyframeInterpolationNode(io.ComfyNode):
|
|
@classmethod
|
|
def define_schema(cls) -> io.Schema:
|
|
return io.Schema(
|
|
node_id='ACN_TimestepKeyframeInterpolation',
|
|
display_name='Timestep Keyframe Interp. 🛂🅐🅒🅝',
|
|
category='Adv-ControlNet 🛂🅐🅒🅝/keyframes',
|
|
inputs=[
|
|
io.Float.Input('start_percent', default=0.0, max=1.0, min=0.0, step=0.001),
|
|
io.Float.Input('end_percent', default=1.0, max=1.0, min=0.0, step=0.001),
|
|
io.Float.Input('strength_start', default=1.0, max=10.0, min=0.0, step=0.001),
|
|
io.Float.Input('strength_end', default=1.0, max=10.0, min=0.0, step=0.001),
|
|
io.Combo.Input('interpolation', options=['linear', 'ease-in', 'ease-out', 'ease-in-out']),
|
|
io.Int.Input('intervals', default=50, max=100, min=2, step=1),
|
|
io.Custom('TIMESTEP_KEYFRAME').Input('prev_timestep_kf', optional=True),
|
|
io.Custom('CONTROL_NET_WEIGHTS').Input('cn_weights', optional=True),
|
|
io.Custom('LATENT_KEYFRAME').Input('latent_keyframe', optional=True),
|
|
io.Float.Input('null_latent_kf_strength', optional=True, default=0.0, max=10.0, min=0.0, step=0.001),
|
|
io.Boolean.Input('inherit_missing', optional=True, default=True),
|
|
io.Mask.Input('mask_optional', optional=True),
|
|
io.Boolean.Input('print_keyframes', optional=True, default=False)
|
|
],
|
|
outputs=[
|
|
io.Custom('TIMESTEP_KEYFRAME').Output('TIMESTEP_KF', is_output_list=False)
|
|
]
|
|
)
|
|
|
|
|
|
@classmethod
|
|
def execute(cls,
|
|
start_percent: float, end_percent: float,
|
|
strength_start: float, strength_end: float, interpolation: str, intervals: int,
|
|
cn_weights: ControlWeights=None,
|
|
latent_keyframe: LatentKeyframeGroup=None,
|
|
prev_timestep_kf: TimestepKeyframeGroup=None,
|
|
null_latent_kf_strength: float=0.0,
|
|
inherit_missing=True,
|
|
guarantee_steps=1,
|
|
mask_optional=None, print_keyframes=False):
|
|
if not prev_timestep_kf:
|
|
prev_timestep_kf = TimestepKeyframeGroup()
|
|
else:
|
|
prev_timestep_kf = prev_timestep_kf.clone()
|
|
|
|
percents = SI.get_weights(num_from=start_percent, num_to=end_percent, length=intervals, method=SI.LINEAR)
|
|
strengths = SI.get_weights(num_from=strength_start, num_to=strength_end, length=intervals, method=interpolation)
|
|
|
|
is_first = True
|
|
for percent, strength in zip(percents, strengths):
|
|
guarantee_steps = 0
|
|
if is_first:
|
|
guarantee_steps = 1
|
|
is_first = False
|
|
prev_timestep_kf.add(TimestepKeyframe(start_percent=percent, strength=strength, null_latent_kf_strength=null_latent_kf_strength,
|
|
control_weights=cn_weights, latent_keyframes=latent_keyframe, inherit_missing=inherit_missing,
|
|
guarantee_steps=guarantee_steps, mask_hint_orig=mask_optional))
|
|
if print_keyframes:
|
|
logger.info(f"TimestepKeyframe - start_percent:{percent} = {strength}")
|
|
return io.NodeOutput(prev_timestep_kf,)
|
|
|
|
class TimestepKeyframeFromStrengthListNode(io.ComfyNode):
|
|
@classmethod
|
|
def define_schema(cls) -> io.Schema:
|
|
return io.Schema(
|
|
node_id='ACN_TimestepKeyframeFromStrengthList',
|
|
display_name='Timestep Keyframe From List 🛂🅐🅒🅝',
|
|
category='Adv-ControlNet 🛂🅐🅒🅝/keyframes',
|
|
inputs=[
|
|
io.Float.Input('float_strengths', default=-1, force_input=True, min=-1, step=0.001),
|
|
io.Float.Input('start_percent', default=0.0, max=1.0, min=0.0, step=0.001),
|
|
io.Float.Input('end_percent', default=1.0, max=1.0, min=0.0, step=0.001),
|
|
io.Custom('TIMESTEP_KEYFRAME').Input('prev_timestep_kf', optional=True),
|
|
io.Custom('CONTROL_NET_WEIGHTS').Input('cn_weights', optional=True),
|
|
io.Custom('LATENT_KEYFRAME').Input('latent_keyframe', optional=True),
|
|
io.Float.Input('null_latent_kf_strength', optional=True, default=0.0, max=10.0, min=0.0, step=0.001),
|
|
io.Boolean.Input('inherit_missing', optional=True, default=True),
|
|
io.Mask.Input('mask_optional', optional=True),
|
|
io.Boolean.Input('print_keyframes', optional=True, default=False)
|
|
],
|
|
outputs=[
|
|
io.Custom('TIMESTEP_KEYFRAME').Output('TIMESTEP_KF', is_output_list=False)
|
|
]
|
|
)
|
|
|
|
|
|
@classmethod
|
|
def execute(cls,
|
|
start_percent: float, end_percent: float,
|
|
float_strengths: float,
|
|
cn_weights: ControlWeights=None,
|
|
latent_keyframe: LatentKeyframeGroup=None,
|
|
prev_timestep_kf: TimestepKeyframeGroup=None,
|
|
null_latent_kf_strength: float=0.0,
|
|
inherit_missing=True,
|
|
guarantee_steps=1,
|
|
mask_optional=None, print_keyframes=False):
|
|
if not prev_timestep_kf:
|
|
prev_timestep_kf = TimestepKeyframeGroup()
|
|
else:
|
|
prev_timestep_kf = prev_timestep_kf.clone()
|
|
|
|
if type(float_strengths) in (float, int):
|
|
float_strengths = [float(float_strengths)]
|
|
elif isinstance(float_strengths, Iterable):
|
|
pass
|
|
else:
|
|
raise Exception(f"strengths_float must be either an iterable input or a float, but was {type(float_strengths).__repr__}.")
|
|
percents = SI.get_weights(num_from=start_percent, num_to=end_percent, length=len(float_strengths), method=SI.LINEAR)
|
|
|
|
is_first = True
|
|
for percent, strength in zip(percents, float_strengths):
|
|
guarantee_steps = 0
|
|
if is_first:
|
|
guarantee_steps = 1
|
|
is_first = False
|
|
prev_timestep_kf.add(TimestepKeyframe(start_percent=percent, strength=strength, null_latent_kf_strength=null_latent_kf_strength,
|
|
control_weights=cn_weights, latent_keyframes=latent_keyframe, inherit_missing=inherit_missing,
|
|
guarantee_steps=guarantee_steps, mask_hint_orig=mask_optional))
|
|
if print_keyframes:
|
|
logger.info(f"TimestepKeyframe - start_percent:{percent} = {strength}")
|
|
return io.NodeOutput(prev_timestep_kf,)
|
|
|
|
class LatentKeyframeNode(io.ComfyNode):
|
|
@classmethod
|
|
def define_schema(cls) -> io.Schema:
|
|
return io.Schema(
|
|
node_id='LatentKeyframe',
|
|
display_name='Latent Keyframe 🛂🅐🅒🅝',
|
|
category='Adv-ControlNet 🛂🅐🅒🅝/keyframes',
|
|
inputs=[
|
|
io.Int.Input('batch_index', default=0, max=9007199254740991, min=-9007199254740991, step=1),
|
|
io.Float.Input('strength', default=1.0, max=10.0, min=0.0, step=0.001),
|
|
io.Custom('LATENT_KEYFRAME').Input('prev_latent_kf', optional=True)
|
|
],
|
|
outputs=[
|
|
io.Custom('LATENT_KEYFRAME').Output('LATENT_KF', is_output_list=False)
|
|
]
|
|
)
|
|
|
|
@classmethod
|
|
def execute(cls,
|
|
batch_index: int,
|
|
strength: float,
|
|
prev_latent_kf: LatentKeyframeGroup=None,
|
|
prev_latent_keyframe: LatentKeyframeGroup=None, # old name
|
|
):
|
|
prev_latent_keyframe = prev_latent_keyframe if prev_latent_keyframe else prev_latent_kf
|
|
if not prev_latent_keyframe:
|
|
prev_latent_keyframe = LatentKeyframeGroup()
|
|
else:
|
|
prev_latent_keyframe = prev_latent_keyframe.clone()
|
|
keyframe = LatentKeyframe(batch_index, strength)
|
|
prev_latent_keyframe.add(keyframe)
|
|
return io.NodeOutput(prev_latent_keyframe,)
|
|
|
|
class LatentKeyframeGroupNode(io.ComfyNode):
|
|
@classmethod
|
|
def define_schema(cls) -> io.Schema:
|
|
return io.Schema(
|
|
node_id='LatentKeyframeGroup',
|
|
display_name='Latent Keyframe Group 🛂🅐🅒🅝',
|
|
category='Adv-ControlNet 🛂🅐🅒🅝/keyframes',
|
|
inputs=[
|
|
io.String.Input('index_strengths', default='', multiline=True),
|
|
io.Custom('LATENT_KEYFRAME').Input('prev_latent_kf', optional=True),
|
|
io.Latent.Input('latent_optional', optional=True),
|
|
io.Boolean.Input('print_keyframes', optional=True, default=False)
|
|
],
|
|
outputs=[
|
|
io.Custom('LATENT_KEYFRAME').Output('LATENT_KF', is_output_list=False)
|
|
]
|
|
)
|
|
|
|
|
|
@staticmethod
|
|
def validate_index(index: int, latent_count: int = 0, is_range: bool = False, allow_negative = False) -> int:
|
|
# if part of range, do nothing
|
|
if is_range:
|
|
return index
|
|
# otherwise, validate index
|
|
# validate not out of range - only when latent_count is passed in
|
|
if latent_count > 0 and index > latent_count-1:
|
|
raise IndexError(f"Index '{index}' out of range for the total {latent_count} latents.")
|
|
# if negative, validate not out of range
|
|
if index < 0:
|
|
if not allow_negative:
|
|
raise IndexError(f"Negative indeces not allowed, but was {index}.")
|
|
conv_index = latent_count+index
|
|
if conv_index < 0:
|
|
raise IndexError(f"Index '{index}', converted to '{conv_index}' out of range for the total {latent_count} latents.")
|
|
index = conv_index
|
|
return index
|
|
|
|
@classmethod
|
|
def convert_to_index_int(cls, raw_index: str, latent_count: int = 0, is_range: bool = False, allow_negative = False) -> int:
|
|
try:
|
|
return cls.validate_index(int(raw_index), latent_count=latent_count, is_range=is_range, allow_negative=allow_negative)
|
|
except ValueError as e:
|
|
raise ValueError(f"index '{raw_index}' must be an integer.", e)
|
|
|
|
@classmethod
|
|
def convert_to_latent_keyframes(cls, latent_indeces: str, latent_count: int) -> set[LatentKeyframe]:
|
|
if not latent_indeces:
|
|
return set()
|
|
int_latent_indeces = [i for i in range(0, latent_count)]
|
|
allow_negative = latent_count > 0
|
|
chosen_indeces = set()
|
|
# parse string - allow positive ints, negative ints, and ranges separated by ':'
|
|
groups = latent_indeces.split(",")
|
|
groups = [g.strip() for g in groups]
|
|
for g in groups:
|
|
# parse strengths - default to 1.0 if no strength given
|
|
strength = 1.0
|
|
if '=' in g:
|
|
g, strength_str = g.split("=", 1)
|
|
g = g.strip()
|
|
try:
|
|
strength = float(strength_str.strip())
|
|
except ValueError as e:
|
|
raise ValueError(f"strength '{strength_str}' must be a float.", e)
|
|
if strength < 0:
|
|
raise ValueError(f"Strength '{strength}' cannot be negative.")
|
|
# parse range of indeces (e.g. 2:16)
|
|
if ':' in g:
|
|
index_range = g.split(":", 1)
|
|
index_range = [r.strip() for r in index_range]
|
|
start_index = cls.convert_to_index_int(index_range[0], latent_count=latent_count, is_range=True, allow_negative=allow_negative)
|
|
end_index = cls.convert_to_index_int(index_range[1], latent_count=latent_count, is_range=True, allow_negative=allow_negative)
|
|
# if latents were passed in, base indeces on known latent count
|
|
if len(int_latent_indeces) > 0:
|
|
for i in int_latent_indeces[start_index:end_index]:
|
|
chosen_indeces.add(LatentKeyframe(i, strength))
|
|
# otherwise, assume indeces are valid
|
|
else:
|
|
for i in range(start_index, end_index):
|
|
chosen_indeces.add(LatentKeyframe(i, strength))
|
|
# parse individual indeces
|
|
else:
|
|
chosen_indeces.add(LatentKeyframe(cls.convert_to_index_int(g, latent_count=latent_count, allow_negative=allow_negative), strength))
|
|
return chosen_indeces
|
|
|
|
@classmethod
|
|
def execute(cls,
|
|
index_strengths: str,
|
|
prev_latent_kf: LatentKeyframeGroup=None,
|
|
prev_latent_keyframe: LatentKeyframeGroup=None, # old name
|
|
latent_optional=None,
|
|
latent_image_opt=None, # old name
|
|
print_keyframes=False):
|
|
prev_latent_keyframe = prev_latent_keyframe if prev_latent_keyframe else prev_latent_kf
|
|
if not prev_latent_keyframe:
|
|
prev_latent_keyframe = LatentKeyframeGroup()
|
|
else:
|
|
prev_latent_keyframe = prev_latent_keyframe.clone()
|
|
curr_latent_keyframe = LatentKeyframeGroup()
|
|
|
|
latent_image_opt = latent_image_opt if latent_image_opt is not None else latent_optional
|
|
latent_count = -1
|
|
if latent_image_opt:
|
|
latent_count = latent_image_opt['samples'].size()[0]
|
|
latent_keyframes = cls.convert_to_latent_keyframes(index_strengths, latent_count=latent_count)
|
|
|
|
for latent_keyframe in latent_keyframes:
|
|
curr_latent_keyframe.add(latent_keyframe)
|
|
|
|
if print_keyframes:
|
|
for keyframe in curr_latent_keyframe.keyframes:
|
|
logger.info(f"LatentKeyframe {keyframe.batch_index}={keyframe.strength}")
|
|
|
|
# replace values with prev_latent_keyframes
|
|
for latent_keyframe in prev_latent_keyframe.keyframes:
|
|
curr_latent_keyframe.add(latent_keyframe)
|
|
|
|
return io.NodeOutput(curr_latent_keyframe,)
|
|
|
|
|
|
class LatentKeyframeInterpolationNode(io.ComfyNode):
|
|
@classmethod
|
|
def define_schema(cls) -> io.Schema:
|
|
return io.Schema(
|
|
node_id='LatentKeyframeTiming',
|
|
display_name='Latent Keyframe Interp. 🛂🅐🅒🅝',
|
|
category='Adv-ControlNet 🛂🅐🅒🅝/keyframes',
|
|
inputs=[
|
|
io.Int.Input('batch_index_from', default=0, max=9007199254740991, min=-9007199254740991, step=1),
|
|
io.Int.Input('batch_index_to_excl', default=0, max=9007199254740991, min=-9007199254740991, step=1),
|
|
io.Float.Input('strength_from', default=1.0, max=10.0, min=0.0, step=0.001),
|
|
io.Float.Input('strength_to', default=1.0, max=10.0, min=0.0, step=0.001),
|
|
io.Combo.Input('interpolation', options=['linear', 'ease-in', 'ease-out', 'ease-in-out']),
|
|
io.Custom('LATENT_KEYFRAME').Input('prev_latent_kf', optional=True),
|
|
io.Boolean.Input('print_keyframes', optional=True, default=False)
|
|
],
|
|
outputs=[
|
|
io.Custom('LATENT_KEYFRAME').Output('LATENT_KF', is_output_list=False)
|
|
]
|
|
)
|
|
|
|
@classmethod
|
|
def execute(cls,
|
|
batch_index_from: int,
|
|
strength_from: float,
|
|
batch_index_to_excl: int,
|
|
strength_to: float,
|
|
interpolation: str,
|
|
prev_latent_kf: LatentKeyframeGroup=None,
|
|
prev_latent_keyframe: LatentKeyframeGroup=None, # old name
|
|
print_keyframes=False):
|
|
|
|
if (batch_index_from > batch_index_to_excl):
|
|
raise ValueError("batch_index_from must be less than or equal to batch_index_to.")
|
|
|
|
if (batch_index_from < 0 and batch_index_to_excl >= 0):
|
|
raise ValueError("batch_index_from and batch_index_to must be either both positive or both negative.")
|
|
|
|
prev_latent_keyframe = prev_latent_keyframe if prev_latent_keyframe else prev_latent_kf
|
|
if not prev_latent_keyframe:
|
|
prev_latent_keyframe = LatentKeyframeGroup()
|
|
else:
|
|
prev_latent_keyframe = prev_latent_keyframe.clone()
|
|
curr_latent_keyframe = LatentKeyframeGroup()
|
|
|
|
steps = batch_index_to_excl - batch_index_from
|
|
diff = strength_to - strength_from
|
|
if interpolation == SI.LINEAR:
|
|
weights = np.linspace(strength_from, strength_to, steps)
|
|
elif interpolation == SI.EASE_IN:
|
|
index = np.linspace(0, 1, steps)
|
|
weights = diff * np.power(index, 2) + strength_from
|
|
elif interpolation == SI.EASE_OUT:
|
|
index = np.linspace(0, 1, steps)
|
|
weights = diff * (1 - np.power(1 - index, 2)) + strength_from
|
|
elif interpolation == SI.EASE_IN_OUT:
|
|
index = np.linspace(0, 1, steps)
|
|
weights = diff * ((1 - np.cos(index * np.pi)) / 2) + strength_from
|
|
|
|
for i in range(steps):
|
|
keyframe = LatentKeyframe(batch_index_from + i, float(weights[i]))
|
|
curr_latent_keyframe.add(keyframe)
|
|
|
|
if print_keyframes:
|
|
for keyframe in curr_latent_keyframe.keyframes:
|
|
logger.info(f"LatentKeyframe {keyframe.batch_index}={keyframe.strength}")
|
|
|
|
# replace values with prev_latent_keyframes
|
|
for latent_keyframe in prev_latent_keyframe.keyframes:
|
|
curr_latent_keyframe.add(latent_keyframe)
|
|
|
|
return io.NodeOutput(curr_latent_keyframe,)
|
|
|
|
class LatentKeyframeBatchedGroupNode(io.ComfyNode):
|
|
@classmethod
|
|
def define_schema(cls) -> io.Schema:
|
|
return io.Schema(
|
|
node_id='LatentKeyframeBatchedGroup',
|
|
display_name='Latent Keyframe From List 🛂🅐🅒🅝',
|
|
category='Adv-ControlNet 🛂🅐🅒🅝/keyframes',
|
|
inputs=[
|
|
io.Float.Input('float_strengths', default=-1, force_input=True, min=-1, step=0.001),
|
|
io.Custom('LATENT_KEYFRAME').Input('prev_latent_kf', optional=True),
|
|
io.Boolean.Input('print_keyframes', optional=True, default=False)
|
|
],
|
|
outputs=[
|
|
io.Custom('LATENT_KEYFRAME').Output('LATENT_KF', is_output_list=False)
|
|
]
|
|
)
|
|
|
|
@classmethod
|
|
def execute(cls, float_strengths: Union[float, list[float]],
|
|
prev_latent_kf: LatentKeyframeGroup=None,
|
|
prev_latent_keyframe: LatentKeyframeGroup=None, # old name
|
|
print_keyframes=False):
|
|
prev_latent_keyframe = prev_latent_keyframe if prev_latent_keyframe else prev_latent_kf
|
|
if not prev_latent_keyframe:
|
|
prev_latent_keyframe = LatentKeyframeGroup()
|
|
else:
|
|
prev_latent_keyframe = prev_latent_keyframe.clone()
|
|
curr_latent_keyframe = LatentKeyframeGroup()
|
|
|
|
# if received a normal float input, do nothing
|
|
if type(float_strengths) in (float, int):
|
|
logger.info("No batched float_strengths passed into Latent Keyframe Batch Group node; will not create any new keyframes.")
|
|
# if iterable, attempt to create LatentKeyframes with chosen strengths
|
|
elif isinstance(float_strengths, Iterable):
|
|
for idx, strength in enumerate(float_strengths):
|
|
keyframe = LatentKeyframe(idx, strength)
|
|
curr_latent_keyframe.add(keyframe)
|
|
else:
|
|
raise ValueError(f"Expected strengths to be an iterable input, but was {type(float_strengths).__repr__}.")
|
|
|
|
if print_keyframes:
|
|
for keyframe in curr_latent_keyframe.keyframes:
|
|
logger.info(f"LatentKeyframe {keyframe.batch_index}={keyframe.strength}")
|
|
|
|
# replace values with prev_latent_keyframes
|
|
for latent_keyframe in prev_latent_keyframe.keyframes:
|
|
curr_latent_keyframe.add(latent_keyframe)
|
|
|
|
return io.NodeOutput(curr_latent_keyframe,)
|