Added some LTXV nodes, bugfixes, updated workflow & context nodes
This commit is contained in:
+9
-3
@@ -2,7 +2,7 @@
|
||||
|
||||
from typing import override
|
||||
|
||||
from comfy_api.latest import ComfyExtension, io
|
||||
from comfy_api.latest import ComfyAPI, ComfyExtension, io
|
||||
from .nodes_context import NODES as NODES_CONTEXT
|
||||
from .nodes_controlnet import NODES as NODES_CONTROLNET
|
||||
from .nodes_debug import NODES as NODES_DEBUG
|
||||
@@ -15,8 +15,12 @@ from .nodes_loaders_d import NODES as NODES_LOADERS_D
|
||||
from .nodes_postproduction import NODES as NODES_POSTPRODUCTION
|
||||
from .nodes_sampling import NODES as NODES_SAMPLING
|
||||
from .nodes_text_encoder import NODES as NODES_TEXT_ENCODER
|
||||
from .nodes_vae import NODES as NODES_VAE
|
||||
from .nodes_text_encoder_d import NODES as NODES_TEXT_ENCODER_D
|
||||
from .nodes_vae_d import NODES as NODES_VAE_D
|
||||
from .nodes_workflow import NODES as NODES_WORKFLOW
|
||||
from .nodes_workflow_d import NODES as NODES_WORKFLOW_D
|
||||
|
||||
api = ComfyAPI()
|
||||
|
||||
|
||||
class TenserTensorExtension(ComfyExtension):
|
||||
@@ -35,6 +39,8 @@ class TenserTensorExtension(ComfyExtension):
|
||||
*NODES_POSTPRODUCTION,
|
||||
*NODES_SAMPLING,
|
||||
*NODES_TEXT_ENCODER,
|
||||
*NODES_VAE,
|
||||
*NODES_TEXT_ENCODER_D,
|
||||
*NODES_VAE_D,
|
||||
*NODES_WORKFLOW,
|
||||
*NODES_WORKFLOW_D,
|
||||
]
|
||||
|
||||
+1
-1
@@ -10,7 +10,7 @@ from comfy import ldm, sd, utils, model_patcher, model_management, lora, float
|
||||
from .dequant import is_quantized, is_torch_compatible
|
||||
from .loader import gguf_clip_loader, gguf_sd_loader
|
||||
from .ops import GGMLOps, move_patch_to_device
|
||||
from ..utils import raise_unless, get_embedding_directory, apply_lora_stack
|
||||
from ..utils import raise_unless, get_embedding_directory
|
||||
|
||||
|
||||
def attention_override_pytorch(func, *args, **kwargs):
|
||||
|
||||
+51
-36
@@ -4,9 +4,7 @@ import dataclasses
|
||||
import re
|
||||
from dataclasses import dataclass
|
||||
|
||||
from comfy.clip_vision import Output
|
||||
from comfy_api.latest import IO, ComfyExtension, Input
|
||||
|
||||
from comfy_api.latest import io, Input
|
||||
from .nodes_workflow import TTWorkflowSettings, WorkflowSettings
|
||||
|
||||
CATEGORY = "TenserTensor/Context"
|
||||
@@ -23,29 +21,46 @@ class TTContext():
|
||||
Use create() to instantiate from kwargs, and set_attr/get_attr for safe field access.
|
||||
Workflow config fields are accessible via set_workflow_config_attr/get_workflow_config_attr.
|
||||
"""
|
||||
cfg: IO.Float | None = None
|
||||
clip: IO.Clip | None = None
|
||||
control_net: IO.ControlNet | None = None
|
||||
conditioning: IO.Conditioning | None = None
|
||||
guidance: IO.Float | None = None
|
||||
guider: IO.Guider | None = None
|
||||
image: IO.Image | None = None
|
||||
latent: IO.Latent | None = None
|
||||
lora_triggers: IO.String | None = None
|
||||
mask: IO.Mask | None = None
|
||||
model: IO.Model | None = None
|
||||
negative: IO.Conditioning | None = None
|
||||
positive: IO.Conditioning | None = None
|
||||
prompt: IO.String | None = None
|
||||
rnd_noise: IO.Noise | None = None
|
||||
sampler: IO.Sampler | None = None
|
||||
seed: IO.Int | None = None
|
||||
sigmas: IO.Sigmas | None = None
|
||||
steps: IO.Int | None = None
|
||||
vae: IO.Vae | None = None
|
||||
ascore_negative: io.Float | None = None
|
||||
ascore_positive: io.Float | None = None
|
||||
cfg: io.Float | None = None
|
||||
clip: io.Clip | None = None
|
||||
clip_g_negative: io.String | None = None
|
||||
clip_g_positive: io.String | None = None
|
||||
clip_l_negative: io.String | None = None
|
||||
clip_l_positive: io.String | None = None
|
||||
conditioning: io.Conditioning | None = None
|
||||
control_net: io.ControlNet | None = None
|
||||
frame_rate: io.String | None = None
|
||||
guidance: io.Float | None = None
|
||||
guider: io.Guider | None = None
|
||||
height: io.Int | None = None
|
||||
image: io.Image | None = None
|
||||
latent: io.Latent | None = None
|
||||
length_sec: io.Float | None = None
|
||||
lora_triggers: io.String | None = None
|
||||
mask: io.Mask | None = None
|
||||
model: io.Model | None = None
|
||||
negative: io.Conditioning | None = None
|
||||
negative_prompt: io.String | None = None
|
||||
noise_seed: io.Int | None = None
|
||||
positive: io.Conditioning | None = None
|
||||
positive_prompt: io.String | None = None
|
||||
prompt: io.String | None = None
|
||||
rnd_noise: io.Noise | None = None
|
||||
sampler: io.Sampler | None = None
|
||||
sampler_name: io.String | None = None
|
||||
scheduler: io.String | None = None
|
||||
seed: io.Int | None = None
|
||||
sigmas: io.Sigmas | None = None
|
||||
steps: io.Int | None = None
|
||||
t5xxl_negative: io.String | None = None
|
||||
t5xxl_positive: io.String | None = None
|
||||
target_height: io.Int | None = None
|
||||
target_width: io.Int | None = None
|
||||
vae: io.Vae | None = None
|
||||
width: io.Int | None = None
|
||||
workflow_config: WorkflowSettings | None = None
|
||||
sampler_name: IO.String | None = None
|
||||
scheduler: IO.String | None = None
|
||||
|
||||
@classmethod
|
||||
def create(cls, **kwargs):
|
||||
@@ -101,20 +116,20 @@ class TTContext():
|
||||
raise AttributeError(f"ERROR: Workflow Config is not set in context")
|
||||
|
||||
|
||||
@IO.comfytype(io_type="TT_CONTEXT")
|
||||
@io.comfytype(io_type="TT_CONTEXT")
|
||||
class Context:
|
||||
Type = TTContext
|
||||
|
||||
class Input(IO.Input):
|
||||
class Input(io.Input):
|
||||
def __init__(self, id: str, **kwargs):
|
||||
super().__init__(id, **kwargs)
|
||||
|
||||
class Output(IO.Output):
|
||||
class Output(io.Output):
|
||||
def __init__(self, id: str, **kwargs):
|
||||
super().__init__(id, **kwargs)
|
||||
|
||||
|
||||
class ContextNode(IO.ComfyNode):
|
||||
class ContextNode(io.ComfyNode):
|
||||
"""
|
||||
Abstract base class for all context nodes.
|
||||
Handles context creation, delegation to handle(), and building return tuple.
|
||||
@@ -139,7 +154,7 @@ class ContextNode(IO.ComfyNode):
|
||||
return Context.Input(input_name, optional=optional)
|
||||
case _:
|
||||
f_type = cls.F_TYPES[input_name]
|
||||
if f_type in (IO.Boolean, IO.Int, IO.Float, IO.String,):
|
||||
if f_type in (io.Boolean, io.Int, io.Float, io.String,):
|
||||
return f_type.Input(input_name, optional=optional, force_input=True)
|
||||
else:
|
||||
return f_type.Input(input_name, optional=optional)
|
||||
@@ -155,7 +170,7 @@ class ContextNode(IO.ComfyNode):
|
||||
return input_schema
|
||||
|
||||
@classmethod
|
||||
def outputs(cls) -> list[Output]:
|
||||
def outputs(cls) -> list[io.Output]:
|
||||
output_schema = []
|
||||
|
||||
for f_name in cls.RETURNS if len(cls.RETURNS) > 0 else cls.REQUIRED + cls.OPTIONAL:
|
||||
@@ -168,24 +183,24 @@ class ContextNode(IO.ComfyNode):
|
||||
return output_schema
|
||||
|
||||
@classmethod
|
||||
def node_output(cls, context) -> IO.NodeOutput:
|
||||
def node_output(cls, context) -> io.NodeOutput:
|
||||
args = [context]
|
||||
for f_name in cls.RETURNS if len(cls.RETURNS) > 0 else cls.REQUIRED + cls.OPTIONAL:
|
||||
if f_name != "context":
|
||||
args.append(context.get_attr(f_name))
|
||||
|
||||
return IO.NodeOutput(*args)
|
||||
return io.NodeOutput(*args)
|
||||
|
||||
@classmethod
|
||||
def display_name(cls):
|
||||
return re.sub(r'(?<=[a-z])(?=[A-Z])', ' ', cls.__name__.replace("TT_", "TT ").replace("Node", ""))
|
||||
|
||||
@classmethod
|
||||
def define_schema(cls) -> IO.Schema:
|
||||
def define_schema(cls) -> io.Schema:
|
||||
if len(cls.F_TYPES) == 0:
|
||||
cls.fill_field_types()
|
||||
|
||||
return IO.Schema(
|
||||
return io.Schema(
|
||||
node_id=cls.__name__,
|
||||
display_name=cls.display_name(),
|
||||
category=CATEGORY,
|
||||
@@ -206,7 +221,7 @@ class ContextNode(IO.ComfyNode):
|
||||
return (context, kwargs,)
|
||||
|
||||
@classmethod
|
||||
def execute(cls, context=None, **kwargs) -> IO.NodeOutput:
|
||||
def execute(cls, context=None, **kwargs) -> io.NodeOutput:
|
||||
if not context:
|
||||
context = TTContext.create(**kwargs)
|
||||
cls.handle(context, **kwargs)
|
||||
|
||||
+6
-6
@@ -2,9 +2,9 @@
|
||||
|
||||
import folder_paths
|
||||
from comfy import controlnet
|
||||
from comfy_api.latest import io, ui, ComfyExtension
|
||||
from comfy_api.latest import io, ui
|
||||
|
||||
from .nodes_image import SingleCondCFGGuider, get_image_files, load_image
|
||||
from .nodes_image import AdaptiveCFGGuider, get_image_files, load_image
|
||||
|
||||
CATEGORY = "TenserTensor/ControlNet"
|
||||
|
||||
@@ -92,8 +92,8 @@ class TT_Flux2ApplyControlNetNode(io.ComfyNode):
|
||||
kwargs.get("strength"),
|
||||
kwargs.get("control_net"))
|
||||
|
||||
if not isinstance(guider, SingleCondCFGGuider):
|
||||
guider = SingleCondCFGGuider.from_cfg_guider(guider)
|
||||
if not isinstance(guider, AdaptiveCFGGuider):
|
||||
guider = AdaptiveCFGGuider.from_cfg_guider(guider)
|
||||
|
||||
if strength == 0.0:
|
||||
return io.NodeOutput(guider)
|
||||
@@ -141,8 +141,8 @@ class TT_Flux2ApplyControlNetAdvancedNode(io.ComfyNode):
|
||||
kwargs.get("strength"),
|
||||
kwargs.get("control_net"))
|
||||
|
||||
if not isinstance(guider, SingleCondCFGGuider):
|
||||
guider = SingleCondCFGGuider.from_cfg_guider(guider)
|
||||
if not isinstance(guider, AdaptiveCFGGuider):
|
||||
guider = AdaptiveCFGGuider.from_cfg_guider(guider)
|
||||
|
||||
if strength == 0.0:
|
||||
return io.NodeOutput(guider)
|
||||
|
||||
@@ -193,10 +193,6 @@ class TT_JsonPreviewDebugNode(io.ComfyNode):
|
||||
return io.NodeOutput(markdown, ui=ui.PreviewText(markdown))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
NODES = [
|
||||
# TT_StableAudioOpenModelsLoaderNode,
|
||||
# TT_AudioLatentFactoryNode,
|
||||
|
||||
+8
-8
@@ -7,17 +7,17 @@ from datetime import datetime
|
||||
from pathlib import Path
|
||||
from typing import Any
|
||||
|
||||
import folder_paths
|
||||
import numpy
|
||||
import torch
|
||||
from PIL import Image, ImageOps, ImageSequence
|
||||
from comfy import model_management, utils
|
||||
from comfy_api.latest import ComfyExtension, io, ui
|
||||
from node_helpers import conditioning_set_values, pillow
|
||||
from spandrel import ModelLoader, ImageModelDescriptor
|
||||
|
||||
from .nodes_text_encoder import SingleCondCFGGuider
|
||||
from .nodes_vae import vae_encode
|
||||
import folder_paths
|
||||
from comfy import model_management, utils
|
||||
from comfy_api.latest import io, ui
|
||||
from node_helpers import conditioning_set_values, pillow
|
||||
from .nodes_text_encoder import AdaptiveCFGGuider
|
||||
from .nodes_vae_d import vae_encode
|
||||
|
||||
CATEGORY = "TenserTensor/Image"
|
||||
RESIZE_METHODS = ["nearest-exact", "bilinear", "area", "bicubic", "bislerp"]
|
||||
@@ -457,8 +457,8 @@ class TT_GuiderImageReferenceNode(io.ComfyNode):
|
||||
kwargs.get("dimension_step")
|
||||
)
|
||||
|
||||
if not isinstance(guider, SingleCondCFGGuider):
|
||||
guider = SingleCondCFGGuider.from_cfg_guider(guider)
|
||||
if not isinstance(guider, AdaptiveCFGGuider):
|
||||
guider = AdaptiveCFGGuider.from_cfg_guider(guider)
|
||||
|
||||
resized = resize_image_to_megapixels(timage, resize_method, megapixels, dimension_step)
|
||||
latent = vae_encode(resized, vae)
|
||||
|
||||
+5
-3
@@ -176,7 +176,7 @@ def prepare_multimodal_latents(**kwargs):
|
||||
kwargs.get("use_ref_frame"),
|
||||
kwargs.get("seed"),
|
||||
kwargs.get("noise_seed"),
|
||||
kwargs.get("megapixels")
|
||||
kwargs.get("megapixels"),
|
||||
)
|
||||
|
||||
video_latent, width, height, total_length, fps = create_empty_video_latent(**kwargs)
|
||||
@@ -196,6 +196,7 @@ def prepare_multimodal_latents(**kwargs):
|
||||
seed,
|
||||
noise_seed,
|
||||
megapixels,
|
||||
kwargs.get("length_sec"),
|
||||
total_length,
|
||||
fps,
|
||||
width,
|
||||
@@ -232,7 +233,7 @@ class TT_LtxvLatentFactoryNode(io.ComfyNode):
|
||||
io.Combo.Input("aspect_ratio", options=CommonTypes.ASPECT_RATIOS),
|
||||
io.Combo.Input("megapixels", options=CommonTypes.MEGAPIXELS),
|
||||
io.Combo.Input("orientation", options=CommonTypes.ORIENTATIONS),
|
||||
io.Int.Input("length_sec", default=5, min=1, max=20),
|
||||
io.Float.Input("length_sec", default=5.0, min=0.1, max=120.0, step=0.1),
|
||||
io.Combo.Input("frame_rate", options=CommonTypes.FRAME_RATES, default="24fps"),
|
||||
io.Int.Input("batch_size", default=1, min=1, max=64),
|
||||
],
|
||||
@@ -244,8 +245,9 @@ class TT_LtxvLatentFactoryNode(io.ComfyNode):
|
||||
io.Int.Output(display_name="SEED"),
|
||||
io.Int.Output(display_name="NOISE_SEED"),
|
||||
io.String.Output(display_name="MEGAPIXELS"),
|
||||
io.String.Output(display_name="LENGTH_SEC"),
|
||||
io.Int.Output(display_name="TOTAL_LENGTH"),
|
||||
io.Int.Output(display_name="FPS"),
|
||||
io.Int.Output(display_name="FRAME_RATE"),
|
||||
io.Int.Output(display_name="WIDTH"),
|
||||
io.Int.Output(display_name="HEIGHT"),
|
||||
]
|
||||
|
||||
+1
-1
@@ -9,7 +9,7 @@ from comfy.latent_formats import SDXL, Flux, Flux2, SD3
|
||||
from comfy_api.latest import io, ComfyExtension
|
||||
|
||||
from .nodes_image import MEGAPIXELS, resize_image, rotate_image, flip_image
|
||||
from .nodes_vae import vae_decode, vae_encode
|
||||
from .nodes_vae_d import vae_decode, vae_encode
|
||||
|
||||
CATEGORY = "TenserTensor/Latent"
|
||||
|
||||
|
||||
+9
-8
@@ -2,12 +2,13 @@
|
||||
|
||||
import latent_preview
|
||||
import torch
|
||||
from comfy import sample, model_management, samplers
|
||||
from comfy_api.latest import ComfyExtension, io
|
||||
|
||||
from comfy import sample, model_management, samplers
|
||||
from comfy_api.latest import io
|
||||
from .nodes_context import Context
|
||||
from .nodes_latent import SCALE_FACTORS, SCALE_METHODS, scale_latent
|
||||
from .nodes_text_encoder import SingleCondCFGGuider
|
||||
from .nodes_latent_d import scale_latent
|
||||
from .nodes_text_encoder import AdaptiveCFGGuider
|
||||
from .utils import CommonTypes
|
||||
|
||||
CATEGORY = "TenserTensor/Sampling"
|
||||
|
||||
@@ -262,8 +263,8 @@ def execute_guided_sampling(**kwargs):
|
||||
kwargs.get("preview_latent"),
|
||||
)
|
||||
|
||||
if not isinstance(guider, SingleCondCFGGuider):
|
||||
guider = SingleCondCFGGuider.from_cfg_guider(guider)
|
||||
if not isinstance(guider, AdaptiveCFGGuider):
|
||||
guider = AdaptiveCFGGuider.from_cfg_guider(guider)
|
||||
|
||||
model_patcher = guider.model_patcher
|
||||
latent_dict = latent.copy()
|
||||
@@ -343,8 +344,8 @@ class TT_GuidedUpscaleKSamplerNode(io.ComfyNode):
|
||||
io.Sampler.Input("sampler"),
|
||||
io.Noise.Input("random_noise"),
|
||||
io.Boolean.Input("scale_latent", default=False, label_on="Scale", label_off="Skip"),
|
||||
io.Combo.Input("scale_factor", options=SCALE_FACTORS, default="1x", advanced=True),
|
||||
io.Combo.Input("scale_method", options=SCALE_METHODS, default="bicubic", advanced=True),
|
||||
io.Combo.Input("scale_factor", options=CommonTypes.SCALE_FACTORS, default="1x", advanced=True),
|
||||
io.Combo.Input("scale_method", options=CommonTypes.SCALE_METHODS, default="bicubic", advanced=True),
|
||||
io.Boolean.Input("preview_latent", default=False, label_on="Show Preview", label_off="Only Sample"),
|
||||
],
|
||||
outputs=[
|
||||
|
||||
+4
-152
@@ -1,155 +1,10 @@
|
||||
# (c) TenserTensor <tenser.tensor@proton.me> || Apache-2.0 (apache.org/licenses/LICENSE-2.0)
|
||||
|
||||
from comfy_api.latest import io, ComfyExtension
|
||||
|
||||
from .nodes_context import Context
|
||||
|
||||
CATEGORY = "TenserTensor/VAE"
|
||||
|
||||
TILE_SIZE, OVERLAP = 512, 64
|
||||
from comfy_api.latest import io
|
||||
|
||||
|
||||
def vae_decode(latent, vae, tile_width=TILE_SIZE, tile_height=TILE_SIZE, overlap=OVERLAP):
|
||||
samples = latent["samples"]
|
||||
|
||||
compression = vae.spacial_compression_decode()
|
||||
image = vae.decode_tiled(
|
||||
samples,
|
||||
tile_x=tile_width // compression,
|
||||
tile_y=tile_height // compression,
|
||||
overlap=overlap // compression
|
||||
)
|
||||
|
||||
return image
|
||||
|
||||
|
||||
def vae_encode(image, vae, tile_width=TILE_SIZE, tile_height=TILE_SIZE, overlap=OVERLAP):
|
||||
samples = vae.encode_tiled(
|
||||
image,
|
||||
tile_x=tile_width,
|
||||
tile_y=tile_height,
|
||||
overlap=overlap
|
||||
)
|
||||
|
||||
return {"samples": samples}
|
||||
|
||||
|
||||
class TT_VaeDecodeTiledNode(io.ComfyNode):
|
||||
@classmethod
|
||||
def define_schema(cls) -> io.Schema:
|
||||
return io.Schema(
|
||||
node_id="TT_VaeDecodeTiledNode",
|
||||
display_name="TT Vae Decode (Tiled)",
|
||||
category=CATEGORY,
|
||||
description="",
|
||||
inputs=[
|
||||
io.Vae.Input("vae"),
|
||||
io.Latent.Input("latent"),
|
||||
io.Int.Input("tile_width", default=512, min=64, max=4096, step=64),
|
||||
io.Int.Input("tile_height", default=512, min=64, max=4096, step=64),
|
||||
io.Int.Input("overlap", default=64, min=8, max=256, step=8),
|
||||
],
|
||||
outputs=[
|
||||
io.Image.Output("IMAGE"),
|
||||
]
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def execute(cls, **kwargs) -> io.NodeOutput:
|
||||
image = vae_decode(**kwargs)
|
||||
|
||||
return io.NodeOutput(image)
|
||||
|
||||
|
||||
class TT_VaeDecodeContextNode(io.ComfyNode):
|
||||
@classmethod
|
||||
def define_schema(cls) -> io.Schema:
|
||||
return io.Schema(
|
||||
node_id="TT_VaeDecodeContextNode",
|
||||
display_name="TT Vae Decode (Context)",
|
||||
category=CATEGORY,
|
||||
description="",
|
||||
inputs=[
|
||||
Context.Input("context"),
|
||||
],
|
||||
outputs=[
|
||||
Context.Output("CONTEXT"),
|
||||
io.Image.Output("IMAGE"),
|
||||
]
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def execute(cls, context) -> io.NodeOutput:
|
||||
vae = context.get("vae")
|
||||
if vae is None:
|
||||
raise ValueError("ERROR: VAE is required for decode")
|
||||
|
||||
latent = context.get("latent")
|
||||
if latent is None:
|
||||
raise ValueError("ERROR: Latent image is required for decode")
|
||||
|
||||
image = vae_decode(latent, vae)
|
||||
|
||||
return io.NodeOutput(context, image)
|
||||
|
||||
|
||||
class TT_VaeEncodeTiledNode(io.ComfyNode):
|
||||
@classmethod
|
||||
def define_schema(cls) -> io.Schema:
|
||||
return io.Schema(
|
||||
node_id="TT_VaeEncodeTiledNode",
|
||||
display_name="TT Vae Encode (Tiled)",
|
||||
category=CATEGORY,
|
||||
description="",
|
||||
inputs=[
|
||||
io.Image.Input("image"),
|
||||
io.Vae.Input("vae"),
|
||||
io.Int.Input("tile_width", default=512, min=64, max=4096, step=64),
|
||||
io.Int.Input("tile_height", default=512, min=64, max=4096, step=64),
|
||||
io.Int.Input("overlap", default=64, min=8, max=256, step=8),
|
||||
],
|
||||
outputs=[
|
||||
io.Latent.Output("LATENT"),
|
||||
]
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def execute(cls, **kwargs) -> io.NodeOutput:
|
||||
latent = vae_encode(**kwargs)
|
||||
|
||||
return io.NodeOutput(latent)
|
||||
|
||||
|
||||
class TT_VaeEncodeContextNode(io.ComfyNode):
|
||||
@classmethod
|
||||
def define_schema(cls) -> io.Schema:
|
||||
return io.Schema(
|
||||
node_id="TT_VaeEncodeContextNode",
|
||||
display_name="TT Vae Encode (Context)",
|
||||
category=CATEGORY,
|
||||
description="",
|
||||
inputs=[
|
||||
Context.Input("context"),
|
||||
],
|
||||
outputs=[
|
||||
Context.Output("CONTEXT"),
|
||||
io.Latent.Output("LATENT"),
|
||||
]
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def execute(cls, context) -> io.NodeOutput:
|
||||
vae = context.get("vae")
|
||||
if vae is None:
|
||||
raise ValueError("ERROR: VAE is required for encode")
|
||||
|
||||
image = context.get("image")
|
||||
if image is None:
|
||||
raise ValueError("ERROR: Pixel image is required for encode")
|
||||
|
||||
latent = vae_encode(image, vae)
|
||||
|
||||
return io.NodeOutput(context, latent)
|
||||
class TT_GuidedUpscaleKSamplerNode(io.ComfyNode):
|
||||
pass
|
||||
|
||||
|
||||
# ==============================================================================
|
||||
@@ -157,8 +12,5 @@ class TT_VaeEncodeContextNode(io.ComfyNode):
|
||||
# ==============================================================================
|
||||
|
||||
NODES = [
|
||||
TT_VaeDecodeTiledNode,
|
||||
TT_VaeDecodeContextNode,
|
||||
TT_VaeEncodeTiledNode,
|
||||
TT_VaeEncodeContextNode,
|
||||
|
||||
]
|
||||
|
||||
+164
@@ -0,0 +1,164 @@
|
||||
# (c) TenserTensor <tenser.tensor@proton.me> || Apache-2.0 (apache.org/licenses/LICENSE-2.0)
|
||||
|
||||
from comfy_api.latest import io, ComfyExtension
|
||||
|
||||
from .nodes_context import Context
|
||||
|
||||
CATEGORY = "TenserTensor/VAE"
|
||||
|
||||
TILE_SIZE, OVERLAP = 512, 64
|
||||
|
||||
|
||||
def vae_decode(latent, vae, tile_width=TILE_SIZE, tile_height=TILE_SIZE, overlap=OVERLAP):
|
||||
samples = latent["samples"]
|
||||
|
||||
compression = vae.spacial_compression_decode()
|
||||
image = vae.decode_tiled(
|
||||
samples,
|
||||
tile_x=tile_width // compression,
|
||||
tile_y=tile_height // compression,
|
||||
overlap=overlap // compression
|
||||
)
|
||||
|
||||
return image
|
||||
|
||||
|
||||
def vae_encode(image, vae, tile_width=TILE_SIZE, tile_height=TILE_SIZE, overlap=OVERLAP):
|
||||
samples = vae.encode_tiled(
|
||||
image,
|
||||
tile_x=tile_width,
|
||||
tile_y=tile_height,
|
||||
overlap=overlap
|
||||
)
|
||||
|
||||
return {"samples": samples}
|
||||
|
||||
|
||||
class TT_VaeDecodeTiledNode(io.ComfyNode):
|
||||
@classmethod
|
||||
def define_schema(cls) -> io.Schema:
|
||||
return io.Schema(
|
||||
node_id="TT_VaeDecodeTiledNode",
|
||||
display_name="TT Vae Decode (Tiled)",
|
||||
category=CATEGORY,
|
||||
description="",
|
||||
inputs=[
|
||||
io.Vae.Input("vae"),
|
||||
io.Latent.Input("latent"),
|
||||
io.Int.Input("tile_width", default=512, min=64, max=4096, step=64),
|
||||
io.Int.Input("tile_height", default=512, min=64, max=4096, step=64),
|
||||
io.Int.Input("overlap", default=64, min=8, max=256, step=8),
|
||||
],
|
||||
outputs=[
|
||||
io.Image.Output("IMAGE"),
|
||||
]
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def execute(cls, **kwargs) -> io.NodeOutput:
|
||||
image = vae_decode(**kwargs)
|
||||
|
||||
return io.NodeOutput(image)
|
||||
|
||||
|
||||
class TT_VaeDecodeContextNode(io.ComfyNode):
|
||||
@classmethod
|
||||
def define_schema(cls) -> io.Schema:
|
||||
return io.Schema(
|
||||
node_id="TT_VaeDecodeContextNode",
|
||||
display_name="TT Vae Decode (Context)",
|
||||
category=CATEGORY,
|
||||
description="",
|
||||
inputs=[
|
||||
Context.Input("context"),
|
||||
],
|
||||
outputs=[
|
||||
Context.Output("CONTEXT"),
|
||||
io.Image.Output("IMAGE"),
|
||||
]
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def execute(cls, context) -> io.NodeOutput:
|
||||
vae = context.get("vae")
|
||||
if vae is None:
|
||||
raise ValueError("ERROR: VAE is required for decode")
|
||||
|
||||
latent = context.get("latent")
|
||||
if latent is None:
|
||||
raise ValueError("ERROR: Latent image is required for decode")
|
||||
|
||||
image = vae_decode(latent, vae)
|
||||
|
||||
return io.NodeOutput(context, image)
|
||||
|
||||
|
||||
class TT_VaeEncodeTiledNode(io.ComfyNode):
|
||||
@classmethod
|
||||
def define_schema(cls) -> io.Schema:
|
||||
return io.Schema(
|
||||
node_id="TT_VaeEncodeTiledNode",
|
||||
display_name="TT Vae Encode (Tiled)",
|
||||
category=CATEGORY,
|
||||
description="",
|
||||
inputs=[
|
||||
io.Image.Input("image"),
|
||||
io.Vae.Input("vae"),
|
||||
io.Int.Input("tile_width", default=512, min=64, max=4096, step=64),
|
||||
io.Int.Input("tile_height", default=512, min=64, max=4096, step=64),
|
||||
io.Int.Input("overlap", default=64, min=8, max=256, step=8),
|
||||
],
|
||||
outputs=[
|
||||
io.Latent.Output("LATENT"),
|
||||
]
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def execute(cls, **kwargs) -> io.NodeOutput:
|
||||
latent = vae_encode(**kwargs)
|
||||
|
||||
return io.NodeOutput(latent)
|
||||
|
||||
|
||||
class TT_VaeEncodeContextNode(io.ComfyNode):
|
||||
@classmethod
|
||||
def define_schema(cls) -> io.Schema:
|
||||
return io.Schema(
|
||||
node_id="TT_VaeEncodeContextNode",
|
||||
display_name="TT Vae Encode (Context)",
|
||||
category=CATEGORY,
|
||||
description="",
|
||||
inputs=[
|
||||
Context.Input("context"),
|
||||
],
|
||||
outputs=[
|
||||
Context.Output("CONTEXT"),
|
||||
io.Latent.Output("LATENT"),
|
||||
]
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def execute(cls, context) -> io.NodeOutput:
|
||||
vae = context.get("vae")
|
||||
if vae is None:
|
||||
raise ValueError("ERROR: VAE is required for encode")
|
||||
|
||||
image = context.get("image")
|
||||
if image is None:
|
||||
raise ValueError("ERROR: Pixel image is required for encode")
|
||||
|
||||
latent = vae_encode(image, vae)
|
||||
|
||||
return io.NodeOutput(context, latent)
|
||||
|
||||
|
||||
# ==============================================================================
|
||||
# V3 entrypoint — registers context nodes with ComfyUI
|
||||
# ==============================================================================
|
||||
|
||||
NODES = [
|
||||
TT_VaeDecodeTiledNode,
|
||||
TT_VaeDecodeContextNode,
|
||||
TT_VaeEncodeTiledNode,
|
||||
TT_VaeEncodeContextNode,
|
||||
]
|
||||
+85
-516
@@ -1,556 +1,132 @@
|
||||
# (c) TenserTensor <tenser.tensor@proton.me> || Apache-2.0 (apache.org/licenses/LICENSE-2.0)
|
||||
|
||||
import dataclasses
|
||||
import math
|
||||
from dataclasses import dataclass
|
||||
|
||||
import torch
|
||||
from comfy.samplers import KSampler, sampler_object, Sampler
|
||||
from comfy_api.latest import io, ComfyExtension
|
||||
from nodes import MAX_RESOLUTION
|
||||
|
||||
from comfy.samplers import KSampler, sampler_object
|
||||
from comfy_api.latest import io
|
||||
from .nodes_workflow_d import WorkflowSettings, TTWorkflowSettings
|
||||
from .utils import CommonTypes
|
||||
|
||||
CATEGORY = "TenserTensor/Workflow"
|
||||
|
||||
|
||||
@dataclass
|
||||
class TTWorkflowSettings():
|
||||
ascore_negative: float | None = None
|
||||
ascore_positive: float | None = None
|
||||
cfg: int | None = None
|
||||
clip_g_negative: str | None = None
|
||||
clip_l_negative: str | None = None
|
||||
clip_g_positive: str | None = None
|
||||
clip_l_positive: str | None = None
|
||||
guidance: float | None = None
|
||||
height: int | None = None
|
||||
lora_triggers: str | None = None
|
||||
prompt: str | None = None
|
||||
sampler: Sampler | None = None
|
||||
sampler_name: str | None = None
|
||||
scheduler: str | None = None
|
||||
seed: int | None = None
|
||||
sigmas: torch.Tensor | None = None
|
||||
steps: int | None = None
|
||||
target_height: int | None = None
|
||||
target_width: int | None = None
|
||||
t5xxl_negative: str | None = None
|
||||
t5xxl_positive: str | None = None
|
||||
width: int | None = None
|
||||
# ==============================================================================
|
||||
# Helper functions — pipeline utilities and loaders
|
||||
# ==============================================================================
|
||||
|
||||
@classmethod
|
||||
def create(cls, **kwargs) -> TTWorkflowSettings:
|
||||
wf = cls()
|
||||
for field in dataclasses.fields(wf):
|
||||
wf.set_attr(field.name, kwargs.get(field.name, None))
|
||||
def ltxv_sigmas(**kwargs):
|
||||
latent, scheduler_config = (
|
||||
kwargs.get("video_latent_opt"),
|
||||
kwargs.get("scheduler_config"),
|
||||
)
|
||||
|
||||
return wf
|
||||
base_shift, max_shift = 0.95, 2.05
|
||||
if scheduler_config.get("scheduler_config") == "Manual":
|
||||
base_shift = scheduler_config.get("schedule_base_shift", 0.95),
|
||||
max_shift = scheduler_config.get("schedule_max_shift", 2.05)
|
||||
|
||||
def update_attrs(self, **kwargs) -> TTWorkflowSettings:
|
||||
for key in kwargs.keys():
|
||||
if hasattr(self, key):
|
||||
val = kwargs.get(key)
|
||||
if val is not None:
|
||||
self.set_attr(key, val)
|
||||
token_count = math.prod(latent["samples"].shape[2:]) if latent else 4096
|
||||
sigmas = torch.linspace(1.0, 0.0, kwargs.get("steps") + 1)
|
||||
|
||||
return self
|
||||
MIN_STEPS, MAX_STEPS = 1024, 4096
|
||||
|
||||
def set_attr(self, key, attr) -> None:
|
||||
if hasattr(self, key):
|
||||
setattr(self, key, attr)
|
||||
slope = (max_shift - base_shift) / (MAX_STEPS - MIN_STEPS)
|
||||
intercept = base_shift - slope * MIN_STEPS
|
||||
sigma_shift = (token_count) * slope + intercept
|
||||
sigma = 1.0
|
||||
timesteps = math.exp(sigma_shift) / (math.exp(sigma_shift) + (1 / sigmas - 1) ** sigma)
|
||||
sigmas = torch.where(sigmas != 0, timesteps, 0)
|
||||
|
||||
def get_attr(self, key, default=None) -> None:
|
||||
if hasattr(self, key):
|
||||
val = getattr(self, key)
|
||||
return val if val is not None else default
|
||||
stretch_sigmas = kwargs.get("stretch_sigmas")
|
||||
if stretch_sigmas.get("stretch_sigmas") == "In Range":
|
||||
non_zero_mask = sigmas != 0
|
||||
non_zero_sigmas = sigmas[non_zero_mask]
|
||||
one_minus_z = 1.0 - non_zero_sigmas
|
||||
scale_factor = one_minus_z[-1] / (1.0 - stretch_sigmas.get("sigmas_terminal", 0.1))
|
||||
stretched = 1.0 - (one_minus_z / scale_factor)
|
||||
sigmas[non_zero_mask] = stretched
|
||||
|
||||
return sigmas
|
||||
|
||||
|
||||
@io.comfytype(io_type="TT_WORKFLOW_CONFIG")
|
||||
class WorkflowSettings:
|
||||
Type = TTWorkflowSettings
|
||||
|
||||
class Input(io.Input):
|
||||
def __init__(self, id: str, **kwargs):
|
||||
super().__init__(id, **kwargs)
|
||||
|
||||
class Output(io.Output):
|
||||
def __init__(self, id: str, **kwargs):
|
||||
super().__init__(id, **kwargs)
|
||||
|
||||
|
||||
class TT_SdxlWorkflowSettingsNode(io.ComfyNode):
|
||||
@classmethod
|
||||
def define_schema(cls) -> io.Schema:
|
||||
return io.Schema(
|
||||
node_id="TT_SdxlWorkflowSettingsNode",
|
||||
display_name="TT SDXL Workflow Settings",
|
||||
category=CATEGORY,
|
||||
description="",
|
||||
inputs=[
|
||||
io.Int.Input("seed", default=0, min=0, max=0xffffffffffffffff),
|
||||
io.Int.Input("steps", default=30, min=1, max=10_000),
|
||||
io.Float.Input("cfg", default=5.0, min=0.0, max=100.0, step=0.1),
|
||||
io.Combo.Input("sampler_name", options=KSampler.SAMPLERS),
|
||||
io.Combo.Input("scheduler", options=KSampler.SCHEDULERS),
|
||||
io.Float.Input("ascore_positive", default=9.0, min=0.0, max=100.0, step=0.1),
|
||||
io.Float.Input("ascore_negative", default=6.0, min=0.0, max=100.0, step=0.1),
|
||||
io.Int.Input("width", default=1024, min=16, max=MAX_RESOLUTION, step=8),
|
||||
io.Int.Input("height", default=1024, min=16, max=MAX_RESOLUTION, step=8),
|
||||
io.Int.Input("target_width", default=1024, min=16, max=MAX_RESOLUTION, step=8, advanced=True),
|
||||
io.Int.Input("target_height", default=1024, min=16, max=MAX_RESOLUTION, step=8, advanced=True),
|
||||
],
|
||||
outputs=[
|
||||
WorkflowSettings.Output("WORKFLOW_CONFIG"),
|
||||
io.Int.Output("SEED"),
|
||||
io.Int.Output("STEPS"),
|
||||
io.Float.Output("CFG"),
|
||||
io.String.Output("SAMPLER_NAME"),
|
||||
io.String.Output("SCHEDULER"),
|
||||
io.Float.Output("ASCORE_POSITIVE"),
|
||||
io.Float.Output("ASCORE_NEGATIVE"),
|
||||
io.Int.Output("WIDTH"),
|
||||
io.Int.Output("HEIGHT"),
|
||||
io.Int.Output("TARGET_WIDTH"),
|
||||
io.Int.Output("TARGET_HEIGHT"),
|
||||
]
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def execute(cls, **kwargs) -> io.NodeOutput:
|
||||
workflow_config = TTWorkflowSettings.create(**kwargs)
|
||||
args = {"workflow_config": workflow_config, **kwargs}
|
||||
|
||||
return io.NodeOutput(*args.values())
|
||||
|
||||
|
||||
class TT_SdxlWorkflowSettingsAdvancedNode(io.ComfyNode):
|
||||
@classmethod
|
||||
def define_schema(cls) -> io.Schema:
|
||||
return io.Schema(
|
||||
node_id="TT_SdxlWorkflowSettingsAdvancedNode",
|
||||
display_name="TT SDXL Workflow Settings (Advanced)",
|
||||
category=CATEGORY,
|
||||
description="",
|
||||
inputs=[
|
||||
io.Int.Input("seed", default=0, min=0, max=0xffffffffffffffff),
|
||||
io.Int.Input("steps", default=30, min=1, max=10_000),
|
||||
io.Float.Input("cfg", default=5.0, min=0.0, max=100.0, step=0.1),
|
||||
io.Combo.Input("sampler_name", options=KSampler.SAMPLERS),
|
||||
io.Combo.Input("scheduler", options=KSampler.SCHEDULERS),
|
||||
io.Float.Input("ascore_positive", default=9.0, min=0.0, max=100.0, step=0.1),
|
||||
io.Float.Input("ascore_negative", default=6.0, min=0.0, max=100.0, step=0.1),
|
||||
io.String.Input("clip_l_positive", multiline=True, dynamic_prompts=True),
|
||||
io.String.Input("clip_g_positive", multiline=True, dynamic_prompts=True),
|
||||
io.String.Input("clip_l_negative", multiline=True, dynamic_prompts=True),
|
||||
io.String.Input("clip_g_negative", multiline=True, dynamic_prompts=True),
|
||||
io.String.Input("lora_triggers", multiline=True, dynamic_prompts=True, advanced=True),
|
||||
io.Int.Input("width", default=1024, min=16, max=MAX_RESOLUTION, step=8),
|
||||
io.Int.Input("height", default=1024, min=16, max=MAX_RESOLUTION, step=8),
|
||||
io.Int.Input("target_width", default=1024, min=16, max=MAX_RESOLUTION, step=8, advanced=True),
|
||||
io.Int.Input("target_height", default=1024, min=16, max=MAX_RESOLUTION, step=8, advanced=True),
|
||||
],
|
||||
outputs=[
|
||||
WorkflowSettings.Output("WORKFLOW_CONFIG"),
|
||||
io.Int.Output("SEED"),
|
||||
io.Int.Output("STEPS"),
|
||||
io.Float.Output("CFG"),
|
||||
io.String.Output("SAMPLER_NAME"),
|
||||
io.String.Output("SCHEDULER"),
|
||||
io.Float.Output("ASCORE_POSITIVE"),
|
||||
io.Float.Output("ASCORE_NEGATIVE"),
|
||||
io.String.Output("CLIP_L_POSITIVE"),
|
||||
io.String.Output("CLIP_G_POSITIVE"),
|
||||
io.String.Output("CLIP_L_NEGATIVE"),
|
||||
io.String.Output("CLIP_G_NEGATIVE"),
|
||||
io.String.Output("LORA_TRIGGERS"),
|
||||
io.Int.Output("WIDTH"),
|
||||
io.Int.Output("HEIGHT"),
|
||||
io.Int.Output("TARGET_WIDTH"),
|
||||
io.Int.Output("TARGET_HEIGHT"),
|
||||
]
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def execute(cls, **kwargs) -> io.NodeOutput:
|
||||
workflow_config = TTWorkflowSettings.create(**kwargs)
|
||||
args = {"workflow_config": workflow_config, **kwargs}
|
||||
|
||||
return io.NodeOutput(*args.values())
|
||||
|
||||
|
||||
class TT_FluxWorkflowSettingsNode(io.ComfyNode):
|
||||
@classmethod
|
||||
def define_schema(cls) -> io.Schema:
|
||||
return io.Schema(
|
||||
node_id="TT_FluxWorkflowSettingsNode",
|
||||
display_name="TT FLUX Workflow Settings",
|
||||
category=CATEGORY,
|
||||
description="",
|
||||
inputs=[
|
||||
io.Int.Input("seed", default=0, min=0, max=0xffffffffffffffff),
|
||||
io.Int.Input("steps", default=30, min=1, max=10_000),
|
||||
io.Float.Input("cfg", default=5.0, min=0.0, max=100.0, step=0.1),
|
||||
io.Combo.Input("sampler_name", options=KSampler.SAMPLERS),
|
||||
io.Combo.Input("scheduler", options=KSampler.SCHEDULERS),
|
||||
io.Float.Input("guidance", default=3.0, min=1.0, max=10.0, step=0.1),
|
||||
],
|
||||
outputs=[
|
||||
WorkflowSettings.Output("WORKFLOW_CONFIG"),
|
||||
io.Int.Output("SEED"),
|
||||
io.Int.Output("STEPS"),
|
||||
io.Float.Output("CFG"),
|
||||
io.String.Output("SAMPLER_NAME"),
|
||||
io.String.Output("SCHEDULER"),
|
||||
io.Float.Output("GUIDANCE"),
|
||||
]
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def execute(cls, **kwargs) -> io.NodeOutput:
|
||||
workflow_config = TTWorkflowSettings.create(**kwargs)
|
||||
args = {"workflow_config": workflow_config, **kwargs}
|
||||
|
||||
return io.NodeOutput(*args.values())
|
||||
|
||||
|
||||
class TT_FluxWorkflowSettingsAdvancedNode(io.ComfyNode):
|
||||
@classmethod
|
||||
def define_schema(cls) -> io.Schema:
|
||||
return io.Schema(
|
||||
node_id="TT_FluxWorkflowSettingsAdvancedNode",
|
||||
display_name="TT FLUX Workflow Settings (Advanced)",
|
||||
category=CATEGORY,
|
||||
description="",
|
||||
inputs=[
|
||||
io.Int.Input("seed", default=0, min=0, max=0xffffffffffffffff),
|
||||
io.Int.Input("steps", default=30, min=1, max=10_000),
|
||||
io.Float.Input("cfg", default=5.0, min=0.0, max=100.0, step=0.1),
|
||||
io.Combo.Input("sampler_name", options=KSampler.SAMPLERS),
|
||||
io.Combo.Input("scheduler", options=KSampler.SCHEDULERS),
|
||||
io.String.Input("clip_l_positive", multiline=True, dynamic_prompts=True),
|
||||
io.String.Input("t5xxl_positive", multiline=True, dynamic_prompts=True),
|
||||
io.String.Input("clip_l_negative", multiline=True, dynamic_prompts=True),
|
||||
io.String.Input("t5xxl_negative", multiline=True, dynamic_prompts=True),
|
||||
io.String.Input("lora_triggers", multiline=True, dynamic_prompts=True, advanced=True),
|
||||
io.Float.Input("guidance", default=3.0, min=1.0, max=10.0, step=0.1),
|
||||
],
|
||||
outputs=[
|
||||
WorkflowSettings.Output("WORKFLOW_CONFIG"),
|
||||
io.Int.Output("SEED"),
|
||||
io.Int.Output("STEPS"),
|
||||
io.Float.Output("CFG"),
|
||||
io.String.Output("SAMPLER_NAME"),
|
||||
io.String.Output("SCHEDULER"),
|
||||
io.String.Output("CLIP_L_POSITIVE"),
|
||||
io.String.Output("T5XXL_POSITIVE"),
|
||||
io.String.Output("CLIP_L_NEGATIVE"),
|
||||
io.String.Output("T5XXL_NEGATIVE"),
|
||||
io.String.Output("LORA_TRIGGERS"),
|
||||
io.Float.Output("GUIDANCE"),
|
||||
]
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def execute(cls, **kwargs) -> io.NodeOutput:
|
||||
workflow_config = TTWorkflowSettings.create(**kwargs)
|
||||
args = {"workflow_config": workflow_config, **kwargs}
|
||||
|
||||
return io.NodeOutput(*args.values())
|
||||
|
||||
|
||||
def compute_empirical_mu(image_seq_len, num_steps):
|
||||
slope_high_res, intercept_high_res = 8.73809524e-05, 1.89833333
|
||||
slope_low_res, intercept_low_res = 0.00016927, 0.45666666
|
||||
HIGH_RES_THRESHOLD = 4300
|
||||
MIN_STEPS, MAX_STEPS = 10, 200
|
||||
mu_at_max_steps = slope_low_res * image_seq_len + intercept_low_res
|
||||
|
||||
if image_seq_len > HIGH_RES_THRESHOLD:
|
||||
return float(mu_at_max_steps)
|
||||
|
||||
mu_at_min_steps = slope_high_res * image_seq_len + intercept_high_res
|
||||
slope = (mu_at_max_steps - mu_at_min_steps) / (MAX_STEPS - MIN_STEPS)
|
||||
intercept = mu_at_max_steps - MAX_STEPS * slope
|
||||
mu = slope * num_steps + intercept
|
||||
|
||||
return float(mu)
|
||||
|
||||
|
||||
def generalized_time_snr_shift(t, mu, sigma):
|
||||
return math.exp(mu) / (math.exp(mu) + (1 / t - 1) ** sigma)
|
||||
|
||||
|
||||
def get_schedule(steps, width, height):
|
||||
image_seq_len = (width * height / (16 * 16))
|
||||
mu = compute_empirical_mu(image_seq_len, steps)
|
||||
timesteps = torch.linspace(1, 0, steps + 1)
|
||||
timesteps = generalized_time_snr_shift(timesteps, mu, 1.0)
|
||||
|
||||
return timesteps
|
||||
|
||||
|
||||
def build_sampler_sigmas(sampler_name, steps, width, height):
|
||||
sampler = sampler_object(sampler_name)
|
||||
sigmas = get_schedule(steps, width, height)
|
||||
def build_ltxv_sampler_sigmas(**kwargs):
|
||||
sampler = sampler_object(kwargs.get("sampler_name"))
|
||||
sigmas = ltxv_sigmas(**kwargs)
|
||||
|
||||
return sampler, sigmas
|
||||
|
||||
|
||||
class TT_Flux2WorkflowSettingsNode(io.ComfyNode):
|
||||
# ==============================================================================
|
||||
# Node classes — ComfyUI node definitions
|
||||
# ==============================================================================
|
||||
|
||||
|
||||
class TT_LtxvWorkflowSettingsNode(io.ComfyNode):
|
||||
@classmethod
|
||||
def define_schema(cls) -> io.Schema:
|
||||
return io.Schema(
|
||||
node_id="TT_Flux2WorkflowSettingsNode",
|
||||
display_name="TT FLUX2 Workflow Settings",
|
||||
node_id="TT_LtxvWorkflowSettingsNode",
|
||||
display_name="TT LTXV Workflow Settings",
|
||||
category=CATEGORY,
|
||||
description="",
|
||||
inputs=[
|
||||
io.Latent.Input("video_latent_opt", optional=True),
|
||||
io.Combo.Input("sampler_name", options=KSampler.SAMPLERS),
|
||||
io.DynamicCombo.Input("stretch_sigmas", options=[
|
||||
io.DynamicCombo.Option("In Range", [
|
||||
io.Float.Input("sigmas_terminal", default=0.1, min=0.0, max=0.99, step=0.01),
|
||||
]),
|
||||
io.DynamicCombo.Option("Bypass", []),
|
||||
]),
|
||||
io.DynamicCombo.Input("scheduler_config", options=[
|
||||
io.DynamicCombo.Option("Defaults", []),
|
||||
io.DynamicCombo.Option("Manual", [
|
||||
io.Float.Input("schedule_base_shift", default=0.95, min=0.0, max=10.0, step=0.01),
|
||||
io.Float.Input("schedule_max_shift", default=2.05, min=0.0, max=10.0, step=0.01),
|
||||
]),
|
||||
]),
|
||||
io.Int.Input("seed", default=0, min=0, max=0xffffffffffffffff),
|
||||
io.Int.Input("noise_seed", default=0, min=0, max=0xffffffffffffffff, control_after_generate=True),
|
||||
io.Int.Input("steps", default=30, min=1, max=10_000),
|
||||
io.Float.Input("cfg", default=3.0, min=0.0, max=100.0, step=0.1),
|
||||
io.Float.Input("guidance", default=3.0, min=1.0, max=10.0, step=0.1),
|
||||
io.Int.Input("width", default=1024, min=16, max=MAX_RESOLUTION, step=8),
|
||||
io.Int.Input("height", default=1024, min=16, max=MAX_RESOLUTION, step=8),
|
||||
io.Combo.Input("sampler_name", options=KSampler.SAMPLERS)
|
||||
io.Float.Input("length_sec", default=5.0, min=0.1, max=120.0, step=0.1),
|
||||
io.Combo.Input("frame_rate", options=CommonTypes.FRAME_RATES, default="24fps"),
|
||||
],
|
||||
outputs=[
|
||||
WorkflowSettings.Output("WORKFLOW_CONFIG"),
|
||||
io.Sampler.Output("SAMPLER"),
|
||||
io.Sigmas.Output("SIGMAS"),
|
||||
io.Int.Output("SEED"),
|
||||
io.Int.Output("NOISE_SEED"),
|
||||
io.Int.Output("STEPS"),
|
||||
io.Float.Output("CFG"),
|
||||
io.Float.Output("GUIDANCE"),
|
||||
io.Int.Output("WIDTH"),
|
||||
io.Int.Output("HEIGHT"),
|
||||
io.String.Output("LENGTH_SEC"),
|
||||
io.String.Output("FRAME_RATE"),
|
||||
]
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def execute(cls, **kwargs) -> io.NodeOutput:
|
||||
kwargs["sampler"], kwargs["sigmas"] = build_sampler_sigmas(
|
||||
kwargs.get("sampler_name"),
|
||||
workflow_config = TTWorkflowSettings.create(**kwargs)
|
||||
|
||||
sampler, sigmas = build_ltxv_sampler_sigmas(**kwargs)
|
||||
workflow_config.set_attr("sampler", sampler)
|
||||
workflow_config.set_attr("sigmas", sigmas)
|
||||
|
||||
args = (
|
||||
workflow_config,
|
||||
sampler,
|
||||
sigmas,
|
||||
kwargs.get("seed"),
|
||||
kwargs.get("noise_seed"),
|
||||
kwargs.get("steps"),
|
||||
kwargs.get("width"),
|
||||
kwargs.get("height"),
|
||||
kwargs.get("cfg"),
|
||||
kwargs.get("length_sec"),
|
||||
kwargs.get("frame_rate"),
|
||||
)
|
||||
|
||||
workflow_config = TTWorkflowSettings.create(**kwargs)
|
||||
args = {
|
||||
"workflow_config": workflow_config,
|
||||
"sampler": kwargs["sampler"],
|
||||
"sigmas": kwargs["sigmas"],
|
||||
"seed": kwargs.get("seed"),
|
||||
"steps": kwargs.get("steps"),
|
||||
"cfg": kwargs.get("cfg"),
|
||||
"guidance": kwargs.get("guidance"),
|
||||
"width": kwargs.get("width"),
|
||||
"height": kwargs.get("height"),
|
||||
}
|
||||
|
||||
return io.NodeOutput(*args.values())
|
||||
|
||||
|
||||
class TT_Flux2WorkflowSettingsAdvancedNode(io.ComfyNode):
|
||||
@classmethod
|
||||
def define_schema(cls) -> io.Schema:
|
||||
return io.Schema(
|
||||
node_id="TT_Flux2WorkflowSettingsAdvancedNode",
|
||||
display_name="TT FLUX2 Workflow Settings (Advanced)",
|
||||
category=CATEGORY,
|
||||
description="",
|
||||
inputs=[
|
||||
io.Int.Input("seed", default=0, min=0, max=0xffffffffffffffff),
|
||||
io.Int.Input("steps", default=30, min=1, max=10_000),
|
||||
io.Float.Input("cfg", default=3.0, min=0.0, max=100.0, step=0.1),
|
||||
io.Combo.Input("sampler_name", options=KSampler.SAMPLERS),
|
||||
io.Int.Input("width", default=1024, min=16, max=MAX_RESOLUTION, step=8),
|
||||
io.Int.Input("height", default=1024, min=16, max=MAX_RESOLUTION, step=8),
|
||||
io.String.Input("prompt", multiline=True, dynamic_prompts=True),
|
||||
io.String.Input("lora_triggers", multiline=True, dynamic_prompts=True),
|
||||
io.Float.Input("guidance", default=3.0, min=1.0, max=10.0, step=0.1),
|
||||
],
|
||||
outputs=[
|
||||
WorkflowSettings.Output("WORKFLOW_CONFIG"),
|
||||
io.Sampler.Output("SAMPLER"),
|
||||
io.Sigmas.Output("SIGMAS"),
|
||||
io.Int.Output("SEED"),
|
||||
io.Int.Output("STEPS"),
|
||||
io.Float.Output("CFG"),
|
||||
io.Int.Output("WIDTH"),
|
||||
io.Int.Output("HEIGHT"),
|
||||
io.String.Output("PROMPT"),
|
||||
io.String.Output("LORA_TRIGGERS"),
|
||||
io.Float.Output("GUIDANCE"),
|
||||
]
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def execute(cls, **kwargs) -> io.NodeOutput:
|
||||
kwargs["sampler"], kwargs["sigmas"] = build_sampler_sigmas(
|
||||
kwargs.get("sampler_name"), kwargs.get("steps"), kwargs.get("width"), kwargs.get("height"),
|
||||
)
|
||||
|
||||
workflow_config = TTWorkflowSettings.create(**kwargs)
|
||||
args = {
|
||||
"workflow_config": workflow_config,
|
||||
"sampler": kwargs["sampler"],
|
||||
"sigmas": kwargs["sigmas"],
|
||||
"seed": kwargs.get("seed"),
|
||||
"steps": kwargs.get("steps"),
|
||||
"cfg": kwargs.get("cfg"),
|
||||
"width": kwargs.get("width"),
|
||||
"height": kwargs.get("height"),
|
||||
"prompt": kwargs.get("prompt"),
|
||||
"lora_triggers": kwargs.get("lora_triggers"),
|
||||
"guidance": kwargs.get("guidance"),
|
||||
}
|
||||
|
||||
return io.NodeOutput(*args.values())
|
||||
|
||||
|
||||
def sd3_shift_sigma(sigma, schedule_shift):
|
||||
return schedule_shift * sigma / (1 + (schedule_shift - 1) * sigma)
|
||||
|
||||
|
||||
def build_sd3_sampler_sigmas(sampler_name, steps, schedule_shift=3.0):
|
||||
sampler = sampler_object(sampler_name)
|
||||
timesteps = torch.linspace(1, 0, steps + 1)
|
||||
sigmas = sd3_shift_sigma(timesteps, schedule_shift)
|
||||
|
||||
return sampler, sigmas
|
||||
|
||||
|
||||
class TT_Sd35GgufWorkflowSettingsNode(io.ComfyNode):
|
||||
@classmethod
|
||||
def define_schema(cls) -> io.Schema:
|
||||
return io.Schema(
|
||||
node_id="TT_Sd35GgufWorkflowSettingsNode",
|
||||
display_name="TT SD3.5 GGUF Workflow Settings",
|
||||
category=CATEGORY,
|
||||
description="",
|
||||
inputs=[
|
||||
io.Int.Input("seed", default=0, min=0, max=0xffffffffffffffff),
|
||||
io.Int.Input("steps", default=30, min=1, max=10_000),
|
||||
io.Float.Input("cfg", default=5.0, min=0.0, max=100.0, step=0.1),
|
||||
io.Combo.Input("sampler_name", options=KSampler.SAMPLERS),
|
||||
io.Float.Input("schedule_shift", default=3.0, min=0.0, max=20.0, step=0.1, advanced=True),
|
||||
io.Int.Input("width", default=1024, min=16, max=MAX_RESOLUTION, step=8),
|
||||
io.Int.Input("height", default=1024, min=16, max=MAX_RESOLUTION, step=8),
|
||||
io.Int.Input("target_width", default=1024, min=16, max=MAX_RESOLUTION, step=8, advanced=True),
|
||||
io.Int.Input("target_height", default=1024, min=16, max=MAX_RESOLUTION, step=8, advanced=True),
|
||||
],
|
||||
outputs=[
|
||||
WorkflowSettings.Output("WORKFLOW_CONFIG"),
|
||||
io.Sampler.Output("SAMPLER"),
|
||||
io.Sigmas.Output("SIGMAS"),
|
||||
io.Int.Output("SEED"),
|
||||
io.Int.Output("STEPS"),
|
||||
io.Float.Output("CFG"),
|
||||
io.Int.Output("WIDTH"),
|
||||
io.Int.Output("HEIGHT"),
|
||||
io.Int.Output("TARGET_WIDTH"),
|
||||
io.Int.Output("TARGET_HEIGHT"),
|
||||
]
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def execute(cls, **kwargs) -> io.NodeOutput:
|
||||
kwargs["sampler"], kwargs["sigmas"] = build_sd3_sampler_sigmas(
|
||||
kwargs.get("sampler_name"), kwargs.get("steps"), kwargs.get("schedule_shift"),
|
||||
)
|
||||
|
||||
workflow_config = TTWorkflowSettings.create(**kwargs)
|
||||
args = {
|
||||
"workflow_config": workflow_config,
|
||||
"sampler": kwargs["sampler"],
|
||||
"sigmas": kwargs["sigmas"],
|
||||
"seed": kwargs.get("seed"),
|
||||
"steps": kwargs.get("steps"),
|
||||
"cfg": kwargs.get("cfg"),
|
||||
"width": kwargs.get("width"),
|
||||
"height": kwargs.get("height"),
|
||||
"target_width": kwargs.get("target_width"),
|
||||
"target_height": kwargs.get("target_height"),
|
||||
}
|
||||
|
||||
return io.NodeOutput(*args.values())
|
||||
|
||||
|
||||
class TT_Sd35GgufWorkflowSettingsAdvancedNode(io.ComfyNode):
|
||||
@classmethod
|
||||
def define_schema(cls) -> io.Schema:
|
||||
return io.Schema(
|
||||
node_id="TT_Sd35GgufWorkflowSettingsAdvancedNode",
|
||||
display_name="TT SD3.5 GGUF Workflow Settings (Advanced)",
|
||||
category=CATEGORY,
|
||||
description="",
|
||||
inputs=[
|
||||
io.Int.Input("seed", default=0, min=0, max=0xffffffffffffffff),
|
||||
io.Int.Input("steps", default=30, min=1, max=10_000),
|
||||
io.Float.Input("cfg", default=5.0, min=0.0, max=100.0, step=0.1),
|
||||
io.Combo.Input("sampler_name", options=KSampler.SAMPLERS),
|
||||
io.Float.Input("schedule_shift", default=3.0, min=0.0, max=20.0, step=0.1, advanced=True),
|
||||
io.String.Input("clip_l_positive", multiline=True, dynamic_prompts=True),
|
||||
io.String.Input("clip_g_positive", multiline=True, dynamic_prompts=True),
|
||||
io.String.Input("t5xxl_positive", multiline=True, dynamic_prompts=True),
|
||||
io.String.Input("clip_l_negative", multiline=True, dynamic_prompts=True),
|
||||
io.String.Input("clip_g_negative", multiline=True, dynamic_prompts=True),
|
||||
io.String.Input("t5xxl_negative", multiline=True, dynamic_prompts=True),
|
||||
io.String.Input("lora_triggers", multiline=True, dynamic_prompts=True, advanced=True),
|
||||
io.Int.Input("width", default=1024, min=16, max=MAX_RESOLUTION, step=8),
|
||||
io.Int.Input("height", default=1024, min=16, max=MAX_RESOLUTION, step=8),
|
||||
io.Int.Input("target_width", default=1024, min=16, max=MAX_RESOLUTION, step=8, advanced=True),
|
||||
io.Int.Input("target_height", default=1024, min=16, max=MAX_RESOLUTION, step=8, advanced=True),
|
||||
],
|
||||
outputs=[
|
||||
WorkflowSettings.Output("WORKFLOW_CONFIG"),
|
||||
io.Sampler.Output("SAMPLER"),
|
||||
io.Sigmas.Output("SIGMAS"),
|
||||
io.Int.Output("SEED"),
|
||||
io.Int.Output("STEPS"),
|
||||
io.Float.Output("CFG"),
|
||||
io.Int.Output("WIDTH"),
|
||||
io.Int.Output("HEIGHT"),
|
||||
io.Int.Output("TARGET_WIDTH"),
|
||||
io.Int.Output("TARGET_HEIGHT"),
|
||||
io.String.Output("CLIP_L_POSITIVE"),
|
||||
io.String.Output("CLIP_G_POSITIVE"),
|
||||
io.String.Output("T5XXL_POSITIVE"),
|
||||
io.String.Output("CLIP_L_NEGATIVE"),
|
||||
io.String.Output("CLIP_G_NEGATIVE"),
|
||||
io.String.Output("T5XXL_NEGATIVE"),
|
||||
io.String.Output("LORA_TRIGGERS"),
|
||||
]
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def execute(cls, **kwargs) -> io.NodeOutput:
|
||||
kwargs["sampler"], kwargs["sigmas"] = build_sd3_sampler_sigmas(
|
||||
kwargs.get("sampler_name"), kwargs.get("steps"), kwargs.get("schedule_shift"),
|
||||
)
|
||||
|
||||
workflow_config = TTWorkflowSettings.create(**kwargs)
|
||||
args = {
|
||||
"workflow_config": workflow_config,
|
||||
"sampler": kwargs["sampler"],
|
||||
"sigmas": kwargs["sigmas"],
|
||||
"seed": kwargs.get("seed"),
|
||||
"steps": kwargs.get("steps"),
|
||||
"cfg": kwargs.get("cfg"),
|
||||
"width": kwargs.get("width"),
|
||||
"height": kwargs.get("height"),
|
||||
"target_width": kwargs.get("target_width"),
|
||||
"target_height": kwargs.get("target_height"),
|
||||
"clip_l_positive": kwargs.get("clip_l_positive"),
|
||||
"clip_g_positive": kwargs.get("clip_g_positive"),
|
||||
"t5xxl_positive": kwargs.get("t5xxl_positive"),
|
||||
"clip_l_negative": kwargs.get("clip_l_negative"),
|
||||
"clip_g_negative": kwargs.get("clip_g_negative"),
|
||||
"t5xxl_negative": kwargs.get("t5xxl_negative"),
|
||||
"lora_triggers": kwargs.get("lora_triggers"),
|
||||
}
|
||||
|
||||
return io.NodeOutput(*args.values())
|
||||
return io.NodeOutput(workflow_config, *args)
|
||||
|
||||
|
||||
# ==============================================================================
|
||||
@@ -558,12 +134,5 @@ class TT_Sd35GgufWorkflowSettingsAdvancedNode(io.ComfyNode):
|
||||
# ==============================================================================
|
||||
|
||||
NODES = [
|
||||
TT_SdxlWorkflowSettingsNode,
|
||||
TT_SdxlWorkflowSettingsAdvancedNode,
|
||||
TT_FluxWorkflowSettingsNode,
|
||||
TT_FluxWorkflowSettingsAdvancedNode,
|
||||
TT_Flux2WorkflowSettingsNode,
|
||||
TT_Flux2WorkflowSettingsAdvancedNode,
|
||||
TT_Sd35GgufWorkflowSettingsNode,
|
||||
TT_Sd35GgufWorkflowSettingsAdvancedNode,
|
||||
TT_LtxvWorkflowSettingsNode,
|
||||
]
|
||||
|
||||
@@ -0,0 +1,575 @@
|
||||
# (c) TenserTensor <tenser.tensor@proton.me> || Apache-2.0 (apache.org/licenses/LICENSE-2.0)
|
||||
|
||||
import dataclasses
|
||||
import math
|
||||
from dataclasses import dataclass
|
||||
|
||||
import torch
|
||||
|
||||
from comfy.samplers import KSampler, sampler_object, Sampler
|
||||
from comfy_api.latest import io
|
||||
from nodes import MAX_RESOLUTION
|
||||
|
||||
CATEGORY = "TenserTensor/Workflow"
|
||||
|
||||
|
||||
@dataclass
|
||||
class TTWorkflowSettings():
|
||||
ascore_negative: float | None = None
|
||||
ascore_positive: float | None = None
|
||||
cfg: int | None = None
|
||||
clip_g_negative: str | None = None
|
||||
clip_g_positive: str | None = None
|
||||
clip_l_negative: str | None = None
|
||||
clip_l_positive: str | None = None
|
||||
frame_rate: str | None = None
|
||||
guidance: float | None = None
|
||||
height: int | None = None
|
||||
length_sec: float | None = None
|
||||
lora_triggers: str | None = None
|
||||
negative_prompt: str | None = None
|
||||
noise_seed: int | None = None
|
||||
positive_prompt: str | None = None
|
||||
prompt: str | None = None
|
||||
sampler: Sampler | None = None
|
||||
sampler_name: str | None = None
|
||||
scheduler: str | None = None
|
||||
seed: int | None = None
|
||||
sigmas: torch.Tensor | None = None
|
||||
steps: int | None = None
|
||||
target_height: int | None = None
|
||||
target_width: int | None = None
|
||||
t5xxl_negative: str | None = None
|
||||
t5xxl_positive: str | None = None
|
||||
width: int | None = None
|
||||
|
||||
@classmethod
|
||||
def create(cls, **kwargs) -> TTWorkflowSettings:
|
||||
wf = cls()
|
||||
for field in dataclasses.fields(wf):
|
||||
wf.set_attr(field.name, kwargs.get(field.name, None))
|
||||
|
||||
return wf
|
||||
|
||||
def update_attrs(self, **kwargs) -> TTWorkflowSettings:
|
||||
for key in kwargs.keys():
|
||||
if hasattr(self, key):
|
||||
val = kwargs.get(key)
|
||||
if val is not None:
|
||||
self.set_attr(key, val)
|
||||
|
||||
return self
|
||||
|
||||
def set_attr(self, key, attr) -> None:
|
||||
if hasattr(self, key):
|
||||
setattr(self, key, attr)
|
||||
|
||||
def get_attr(self, key, default=None) -> None:
|
||||
if hasattr(self, key):
|
||||
val = getattr(self, key)
|
||||
return val if val is not None else default
|
||||
|
||||
|
||||
@io.comfytype(io_type="TT_WORKFLOW_CONFIG")
|
||||
class WorkflowSettings:
|
||||
Type = TTWorkflowSettings
|
||||
|
||||
class Input(io.Input):
|
||||
def __init__(self, id: str, **kwargs):
|
||||
super().__init__(id, **kwargs)
|
||||
|
||||
class Output(io.Output):
|
||||
def __init__(self, id: str, **kwargs):
|
||||
super().__init__(id, **kwargs)
|
||||
|
||||
|
||||
class TT_SdxlWorkflowSettingsNode(io.ComfyNode):
|
||||
@classmethod
|
||||
def define_schema(cls) -> io.Schema:
|
||||
return io.Schema(
|
||||
node_id="TT_SdxlWorkflowSettingsNode",
|
||||
display_name="TT SDXL Workflow Settings",
|
||||
category=CATEGORY,
|
||||
description="",
|
||||
inputs=[
|
||||
io.Int.Input("seed", default=0, min=0, max=0xffffffffffffffff),
|
||||
io.Int.Input("steps", default=30, min=1, max=10_000),
|
||||
io.Float.Input("cfg", default=5.0, min=0.0, max=100.0, step=0.1),
|
||||
io.Combo.Input("sampler_name", options=KSampler.SAMPLERS),
|
||||
io.Combo.Input("scheduler", options=KSampler.SCHEDULERS),
|
||||
io.Float.Input("ascore_positive", default=9.0, min=0.0, max=100.0, step=0.1),
|
||||
io.Float.Input("ascore_negative", default=6.0, min=0.0, max=100.0, step=0.1),
|
||||
io.Int.Input("width", default=1024, min=16, max=MAX_RESOLUTION, step=8),
|
||||
io.Int.Input("height", default=1024, min=16, max=MAX_RESOLUTION, step=8),
|
||||
io.Int.Input("target_width", default=1024, min=16, max=MAX_RESOLUTION, step=8, advanced=True),
|
||||
io.Int.Input("target_height", default=1024, min=16, max=MAX_RESOLUTION, step=8, advanced=True),
|
||||
],
|
||||
outputs=[
|
||||
WorkflowSettings.Output("WORKFLOW_CONFIG"),
|
||||
io.Int.Output("SEED"),
|
||||
io.Int.Output("STEPS"),
|
||||
io.Float.Output("CFG"),
|
||||
io.String.Output("SAMPLER_NAME"),
|
||||
io.String.Output("SCHEDULER"),
|
||||
io.Float.Output("ASCORE_POSITIVE"),
|
||||
io.Float.Output("ASCORE_NEGATIVE"),
|
||||
io.Int.Output("WIDTH"),
|
||||
io.Int.Output("HEIGHT"),
|
||||
io.Int.Output("TARGET_WIDTH"),
|
||||
io.Int.Output("TARGET_HEIGHT"),
|
||||
]
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def execute(cls, **kwargs) -> io.NodeOutput:
|
||||
workflow_config = TTWorkflowSettings.create(**kwargs)
|
||||
args = {"workflow_config": workflow_config, **kwargs}
|
||||
|
||||
return io.NodeOutput(*args.values())
|
||||
|
||||
|
||||
class TT_SdxlWorkflowSettingsAdvancedNode(io.ComfyNode):
|
||||
@classmethod
|
||||
def define_schema(cls) -> io.Schema:
|
||||
return io.Schema(
|
||||
node_id="TT_SdxlWorkflowSettingsAdvancedNode",
|
||||
display_name="TT SDXL Workflow Settings (Advanced)",
|
||||
category=CATEGORY,
|
||||
description="",
|
||||
inputs=[
|
||||
io.Int.Input("seed", default=0, min=0, max=0xffffffffffffffff),
|
||||
io.Int.Input("steps", default=30, min=1, max=10_000),
|
||||
io.Float.Input("cfg", default=5.0, min=0.0, max=100.0, step=0.1),
|
||||
io.Combo.Input("sampler_name", options=KSampler.SAMPLERS),
|
||||
io.Combo.Input("scheduler", options=KSampler.SCHEDULERS),
|
||||
io.Float.Input("ascore_positive", default=9.0, min=0.0, max=100.0, step=0.1),
|
||||
io.Float.Input("ascore_negative", default=6.0, min=0.0, max=100.0, step=0.1),
|
||||
io.String.Input("clip_l_positive", multiline=True, dynamic_prompts=True),
|
||||
io.String.Input("clip_g_positive", multiline=True, dynamic_prompts=True),
|
||||
io.String.Input("clip_l_negative", multiline=True, dynamic_prompts=True),
|
||||
io.String.Input("clip_g_negative", multiline=True, dynamic_prompts=True),
|
||||
io.String.Input("lora_triggers", multiline=True, dynamic_prompts=True, advanced=True),
|
||||
io.Int.Input("width", default=1024, min=16, max=MAX_RESOLUTION, step=8),
|
||||
io.Int.Input("height", default=1024, min=16, max=MAX_RESOLUTION, step=8),
|
||||
io.Int.Input("target_width", default=1024, min=16, max=MAX_RESOLUTION, step=8, advanced=True),
|
||||
io.Int.Input("target_height", default=1024, min=16, max=MAX_RESOLUTION, step=8, advanced=True),
|
||||
],
|
||||
outputs=[
|
||||
WorkflowSettings.Output("WORKFLOW_CONFIG"),
|
||||
io.Int.Output("SEED"),
|
||||
io.Int.Output("STEPS"),
|
||||
io.Float.Output("CFG"),
|
||||
io.String.Output("SAMPLER_NAME"),
|
||||
io.String.Output("SCHEDULER"),
|
||||
io.Float.Output("ASCORE_POSITIVE"),
|
||||
io.Float.Output("ASCORE_NEGATIVE"),
|
||||
io.String.Output("CLIP_L_POSITIVE"),
|
||||
io.String.Output("CLIP_G_POSITIVE"),
|
||||
io.String.Output("CLIP_L_NEGATIVE"),
|
||||
io.String.Output("CLIP_G_NEGATIVE"),
|
||||
io.String.Output("LORA_TRIGGERS"),
|
||||
io.Int.Output("WIDTH"),
|
||||
io.Int.Output("HEIGHT"),
|
||||
io.Int.Output("TARGET_WIDTH"),
|
||||
io.Int.Output("TARGET_HEIGHT"),
|
||||
]
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def execute(cls, **kwargs) -> io.NodeOutput:
|
||||
workflow_config = TTWorkflowSettings.create(**kwargs)
|
||||
args = {"workflow_config": workflow_config, **kwargs}
|
||||
|
||||
return io.NodeOutput(*args.values())
|
||||
|
||||
|
||||
class TT_FluxWorkflowSettingsNode(io.ComfyNode):
|
||||
@classmethod
|
||||
def define_schema(cls) -> io.Schema:
|
||||
return io.Schema(
|
||||
node_id="TT_FluxWorkflowSettingsNode",
|
||||
display_name="TT FLUX Workflow Settings",
|
||||
category=CATEGORY,
|
||||
description="",
|
||||
inputs=[
|
||||
io.Int.Input("seed", default=0, min=0, max=0xffffffffffffffff),
|
||||
io.Int.Input("steps", default=30, min=1, max=10_000),
|
||||
io.Float.Input("cfg", default=5.0, min=0.0, max=100.0, step=0.1),
|
||||
io.Combo.Input("sampler_name", options=KSampler.SAMPLERS),
|
||||
io.Combo.Input("scheduler", options=KSampler.SCHEDULERS),
|
||||
io.Float.Input("guidance", default=3.0, min=1.0, max=10.0, step=0.1),
|
||||
],
|
||||
outputs=[
|
||||
WorkflowSettings.Output("WORKFLOW_CONFIG"),
|
||||
io.Int.Output("SEED"),
|
||||
io.Int.Output("STEPS"),
|
||||
io.Float.Output("CFG"),
|
||||
io.String.Output("SAMPLER_NAME"),
|
||||
io.String.Output("SCHEDULER"),
|
||||
io.Float.Output("GUIDANCE"),
|
||||
]
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def execute(cls, **kwargs) -> io.NodeOutput:
|
||||
workflow_config = TTWorkflowSettings.create(**kwargs)
|
||||
args = {"workflow_config": workflow_config, **kwargs}
|
||||
|
||||
return io.NodeOutput(*args.values())
|
||||
|
||||
|
||||
class TT_FluxWorkflowSettingsAdvancedNode(io.ComfyNode):
|
||||
@classmethod
|
||||
def define_schema(cls) -> io.Schema:
|
||||
return io.Schema(
|
||||
node_id="TT_FluxWorkflowSettingsAdvancedNode",
|
||||
display_name="TT FLUX Workflow Settings (Advanced)",
|
||||
category=CATEGORY,
|
||||
description="",
|
||||
inputs=[
|
||||
io.Int.Input("seed", default=0, min=0, max=0xffffffffffffffff),
|
||||
io.Int.Input("steps", default=30, min=1, max=10_000),
|
||||
io.Float.Input("cfg", default=5.0, min=0.0, max=100.0, step=0.1),
|
||||
io.Combo.Input("sampler_name", options=KSampler.SAMPLERS),
|
||||
io.Combo.Input("scheduler", options=KSampler.SCHEDULERS),
|
||||
io.String.Input("clip_l_positive", multiline=True, dynamic_prompts=True),
|
||||
io.String.Input("t5xxl_positive", multiline=True, dynamic_prompts=True),
|
||||
io.String.Input("clip_l_negative", multiline=True, dynamic_prompts=True),
|
||||
io.String.Input("t5xxl_negative", multiline=True, dynamic_prompts=True),
|
||||
io.String.Input("lora_triggers", multiline=True, dynamic_prompts=True, advanced=True),
|
||||
io.Float.Input("guidance", default=3.0, min=1.0, max=10.0, step=0.1),
|
||||
],
|
||||
outputs=[
|
||||
WorkflowSettings.Output("WORKFLOW_CONFIG"),
|
||||
io.Int.Output("SEED"),
|
||||
io.Int.Output("STEPS"),
|
||||
io.Float.Output("CFG"),
|
||||
io.String.Output("SAMPLER_NAME"),
|
||||
io.String.Output("SCHEDULER"),
|
||||
io.String.Output("CLIP_L_POSITIVE"),
|
||||
io.String.Output("T5XXL_POSITIVE"),
|
||||
io.String.Output("CLIP_L_NEGATIVE"),
|
||||
io.String.Output("T5XXL_NEGATIVE"),
|
||||
io.String.Output("LORA_TRIGGERS"),
|
||||
io.Float.Output("GUIDANCE"),
|
||||
]
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def execute(cls, **kwargs) -> io.NodeOutput:
|
||||
workflow_config = TTWorkflowSettings.create(**kwargs)
|
||||
args = {"workflow_config": workflow_config, **kwargs}
|
||||
|
||||
return io.NodeOutput(*args.values())
|
||||
|
||||
|
||||
def compute_empirical_mu(image_seq_len, num_steps):
|
||||
slope_high_res, intercept_high_res = 8.73809524e-05, 1.89833333
|
||||
slope_low_res, intercept_low_res = 0.00016927, 0.45666666
|
||||
HIGH_RES_THRESHOLD = 4300
|
||||
MIN_STEPS, MAX_STEPS = 10, 200
|
||||
mu_at_max_steps = slope_low_res * image_seq_len + intercept_low_res
|
||||
|
||||
if image_seq_len > HIGH_RES_THRESHOLD:
|
||||
return float(mu_at_max_steps)
|
||||
|
||||
mu_at_min_steps = slope_high_res * image_seq_len + intercept_high_res
|
||||
slope = (mu_at_max_steps - mu_at_min_steps) / (MAX_STEPS - MIN_STEPS)
|
||||
intercept = mu_at_max_steps - MAX_STEPS * slope
|
||||
mu = slope * num_steps + intercept
|
||||
|
||||
return float(mu)
|
||||
|
||||
|
||||
def generalized_time_snr_shift(t, mu, sigma):
|
||||
return math.exp(mu) / (math.exp(mu) + (1 / t - 1) ** sigma)
|
||||
|
||||
|
||||
def get_schedule(steps, width, height):
|
||||
image_seq_len = (width * height / (16 * 16))
|
||||
mu = compute_empirical_mu(image_seq_len, steps)
|
||||
timesteps = torch.linspace(1, 0, steps + 1)
|
||||
timesteps = generalized_time_snr_shift(timesteps, mu, 1.0)
|
||||
|
||||
return timesteps
|
||||
|
||||
|
||||
def build_sampler_sigmas(sampler_name, steps, width, height):
|
||||
sampler = sampler_object(sampler_name)
|
||||
sigmas = get_schedule(steps, width, height)
|
||||
|
||||
return sampler, sigmas
|
||||
|
||||
|
||||
class TT_Flux2WorkflowSettingsNode(io.ComfyNode):
|
||||
@classmethod
|
||||
def define_schema(cls) -> io.Schema:
|
||||
return io.Schema(
|
||||
node_id="TT_Flux2WorkflowSettingsNode",
|
||||
display_name="TT FLUX2 Workflow Settings",
|
||||
category=CATEGORY,
|
||||
description="",
|
||||
inputs=[
|
||||
io.Int.Input("seed", default=0, min=0, max=0xffffffffffffffff),
|
||||
io.Int.Input("steps", default=30, min=1, max=10_000),
|
||||
io.Float.Input("cfg", default=3.0, min=0.0, max=100.0, step=0.1),
|
||||
io.Float.Input("guidance", default=3.0, min=1.0, max=10.0, step=0.1),
|
||||
io.Int.Input("width", default=1024, min=16, max=MAX_RESOLUTION, step=8),
|
||||
io.Int.Input("height", default=1024, min=16, max=MAX_RESOLUTION, step=8),
|
||||
io.Combo.Input("sampler_name", options=KSampler.SAMPLERS)
|
||||
],
|
||||
outputs=[
|
||||
WorkflowSettings.Output("WORKFLOW_CONFIG"),
|
||||
io.Sampler.Output("SAMPLER"),
|
||||
io.Sigmas.Output("SIGMAS"),
|
||||
io.Int.Output("SEED"),
|
||||
io.Int.Output("STEPS"),
|
||||
io.Float.Output("CFG"),
|
||||
io.Float.Output("GUIDANCE"),
|
||||
io.Int.Output("WIDTH"),
|
||||
io.Int.Output("HEIGHT"),
|
||||
]
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def execute(cls, **kwargs) -> io.NodeOutput:
|
||||
kwargs["sampler"], kwargs["sigmas"] = build_sampler_sigmas(
|
||||
kwargs.get("sampler_name"),
|
||||
kwargs.get("steps"),
|
||||
kwargs.get("width"),
|
||||
kwargs.get("height"),
|
||||
)
|
||||
|
||||
workflow_config = TTWorkflowSettings.create(**kwargs)
|
||||
args = {
|
||||
"workflow_config": workflow_config,
|
||||
"sampler": kwargs["sampler"],
|
||||
"sigmas": kwargs["sigmas"],
|
||||
"seed": kwargs.get("seed"),
|
||||
"steps": kwargs.get("steps"),
|
||||
"cfg": kwargs.get("cfg"),
|
||||
"guidance": kwargs.get("guidance"),
|
||||
"width": kwargs.get("width"),
|
||||
"height": kwargs.get("height"),
|
||||
}
|
||||
|
||||
return io.NodeOutput(*args.values())
|
||||
|
||||
|
||||
class TT_Flux2WorkflowSettingsAdvancedNode(io.ComfyNode):
|
||||
@classmethod
|
||||
def define_schema(cls) -> io.Schema:
|
||||
return io.Schema(
|
||||
node_id="TT_Flux2WorkflowSettingsAdvancedNode",
|
||||
display_name="TT FLUX2 Workflow Settings (Advanced)",
|
||||
category=CATEGORY,
|
||||
description="",
|
||||
inputs=[
|
||||
io.Int.Input("seed", default=0, min=0, max=0xffffffffffffffff),
|
||||
io.Int.Input("steps", default=30, min=1, max=10_000),
|
||||
io.Float.Input("cfg", default=3.0, min=0.0, max=100.0, step=0.1),
|
||||
io.Combo.Input("sampler_name", options=KSampler.SAMPLERS),
|
||||
io.Int.Input("width", default=1024, min=16, max=MAX_RESOLUTION, step=8),
|
||||
io.Int.Input("height", default=1024, min=16, max=MAX_RESOLUTION, step=8),
|
||||
io.String.Input("prompt", multiline=True, dynamic_prompts=True),
|
||||
io.String.Input("lora_triggers", multiline=True, dynamic_prompts=True),
|
||||
io.Float.Input("guidance", default=3.0, min=1.0, max=10.0, step=0.1),
|
||||
],
|
||||
outputs=[
|
||||
WorkflowSettings.Output("WORKFLOW_CONFIG"),
|
||||
io.Sampler.Output("SAMPLER"),
|
||||
io.Sigmas.Output("SIGMAS"),
|
||||
io.Int.Output("SEED"),
|
||||
io.Int.Output("STEPS"),
|
||||
io.Float.Output("CFG"),
|
||||
io.Int.Output("WIDTH"),
|
||||
io.Int.Output("HEIGHT"),
|
||||
io.String.Output("PROMPT"),
|
||||
io.String.Output("LORA_TRIGGERS"),
|
||||
io.Float.Output("GUIDANCE"),
|
||||
]
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def execute(cls, **kwargs) -> io.NodeOutput:
|
||||
kwargs["sampler"], kwargs["sigmas"] = build_sampler_sigmas(
|
||||
kwargs.get("sampler_name"), kwargs.get("steps"), kwargs.get("width"), kwargs.get("height"),
|
||||
)
|
||||
|
||||
workflow_config = TTWorkflowSettings.create(**kwargs)
|
||||
args = {
|
||||
"workflow_config": workflow_config,
|
||||
"sampler": kwargs["sampler"],
|
||||
"sigmas": kwargs["sigmas"],
|
||||
"seed": kwargs.get("seed"),
|
||||
"steps": kwargs.get("steps"),
|
||||
"cfg": kwargs.get("cfg"),
|
||||
"width": kwargs.get("width"),
|
||||
"height": kwargs.get("height"),
|
||||
"prompt": kwargs.get("prompt"),
|
||||
"lora_triggers": kwargs.get("lora_triggers"),
|
||||
"guidance": kwargs.get("guidance"),
|
||||
}
|
||||
|
||||
return io.NodeOutput(*args.values())
|
||||
|
||||
|
||||
def sd3_shift_sigma(sigma, schedule_shift):
|
||||
return schedule_shift * sigma / (1 + (schedule_shift - 1) * sigma)
|
||||
|
||||
|
||||
def build_sd3_sampler_sigmas(sampler_name, steps, schedule_shift=3.0):
|
||||
sampler = sampler_object(sampler_name)
|
||||
timesteps = torch.linspace(1, 0, steps + 1)
|
||||
sigmas = sd3_shift_sigma(timesteps, schedule_shift)
|
||||
|
||||
return sampler, sigmas
|
||||
|
||||
|
||||
class TT_Sd35GgufWorkflowSettingsNode(io.ComfyNode):
|
||||
@classmethod
|
||||
def define_schema(cls) -> io.Schema:
|
||||
return io.Schema(
|
||||
node_id="TT_Sd35GgufWorkflowSettingsNode",
|
||||
display_name="TT SD3.5 GGUF Workflow Settings",
|
||||
category=CATEGORY,
|
||||
description="",
|
||||
inputs=[
|
||||
io.Int.Input("seed", default=0, min=0, max=0xffffffffffffffff),
|
||||
io.Int.Input("steps", default=30, min=1, max=10_000),
|
||||
io.Float.Input("cfg", default=5.0, min=0.0, max=100.0, step=0.1),
|
||||
io.Combo.Input("sampler_name", options=KSampler.SAMPLERS),
|
||||
io.Float.Input("schedule_shift", default=3.0, min=0.0, max=20.0, step=0.1, advanced=True),
|
||||
io.Int.Input("width", default=1024, min=16, max=MAX_RESOLUTION, step=8),
|
||||
io.Int.Input("height", default=1024, min=16, max=MAX_RESOLUTION, step=8),
|
||||
io.Int.Input("target_width", default=1024, min=16, max=MAX_RESOLUTION, step=8, advanced=True),
|
||||
io.Int.Input("target_height", default=1024, min=16, max=MAX_RESOLUTION, step=8, advanced=True),
|
||||
],
|
||||
outputs=[
|
||||
WorkflowSettings.Output("WORKFLOW_CONFIG"),
|
||||
io.Sampler.Output("SAMPLER"),
|
||||
io.Sigmas.Output("SIGMAS"),
|
||||
io.Int.Output("SEED"),
|
||||
io.Int.Output("STEPS"),
|
||||
io.Float.Output("CFG"),
|
||||
io.Int.Output("WIDTH"),
|
||||
io.Int.Output("HEIGHT"),
|
||||
io.Int.Output("TARGET_WIDTH"),
|
||||
io.Int.Output("TARGET_HEIGHT"),
|
||||
]
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def execute(cls, **kwargs) -> io.NodeOutput:
|
||||
kwargs["sampler"], kwargs["sigmas"] = build_sd3_sampler_sigmas(
|
||||
kwargs.get("sampler_name"), kwargs.get("steps"), kwargs.get("schedule_shift"),
|
||||
)
|
||||
|
||||
workflow_config = TTWorkflowSettings.create(**kwargs)
|
||||
args = {
|
||||
"workflow_config": workflow_config,
|
||||
"sampler": kwargs["sampler"],
|
||||
"sigmas": kwargs["sigmas"],
|
||||
"seed": kwargs.get("seed"),
|
||||
"steps": kwargs.get("steps"),
|
||||
"cfg": kwargs.get("cfg"),
|
||||
"width": kwargs.get("width"),
|
||||
"height": kwargs.get("height"),
|
||||
"target_width": kwargs.get("target_width"),
|
||||
"target_height": kwargs.get("target_height"),
|
||||
}
|
||||
|
||||
return io.NodeOutput(*args.values())
|
||||
|
||||
|
||||
class TT_Sd35GgufWorkflowSettingsAdvancedNode(io.ComfyNode):
|
||||
@classmethod
|
||||
def define_schema(cls) -> io.Schema:
|
||||
return io.Schema(
|
||||
node_id="TT_Sd35GgufWorkflowSettingsAdvancedNode",
|
||||
display_name="TT SD3.5 GGUF Workflow Settings (Advanced)",
|
||||
category=CATEGORY,
|
||||
description="",
|
||||
inputs=[
|
||||
io.Int.Input("seed", default=0, min=0, max=0xffffffffffffffff),
|
||||
io.Int.Input("steps", default=30, min=1, max=10_000),
|
||||
io.Float.Input("cfg", default=5.0, min=0.0, max=100.0, step=0.1),
|
||||
io.Combo.Input("sampler_name", options=KSampler.SAMPLERS),
|
||||
io.Float.Input("schedule_shift", default=3.0, min=0.0, max=20.0, step=0.1, advanced=True),
|
||||
io.String.Input("clip_l_positive", multiline=True, dynamic_prompts=True),
|
||||
io.String.Input("clip_g_positive", multiline=True, dynamic_prompts=True),
|
||||
io.String.Input("t5xxl_positive", multiline=True, dynamic_prompts=True),
|
||||
io.String.Input("clip_l_negative", multiline=True, dynamic_prompts=True),
|
||||
io.String.Input("clip_g_negative", multiline=True, dynamic_prompts=True),
|
||||
io.String.Input("t5xxl_negative", multiline=True, dynamic_prompts=True),
|
||||
io.String.Input("lora_triggers", multiline=True, dynamic_prompts=True, advanced=True),
|
||||
io.Int.Input("width", default=1024, min=16, max=MAX_RESOLUTION, step=8),
|
||||
io.Int.Input("height", default=1024, min=16, max=MAX_RESOLUTION, step=8),
|
||||
io.Int.Input("target_width", default=1024, min=16, max=MAX_RESOLUTION, step=8, advanced=True),
|
||||
io.Int.Input("target_height", default=1024, min=16, max=MAX_RESOLUTION, step=8, advanced=True),
|
||||
],
|
||||
outputs=[
|
||||
WorkflowSettings.Output("WORKFLOW_CONFIG"),
|
||||
io.Sampler.Output("SAMPLER"),
|
||||
io.Sigmas.Output("SIGMAS"),
|
||||
io.Int.Output("SEED"),
|
||||
io.Int.Output("STEPS"),
|
||||
io.Float.Output("CFG"),
|
||||
io.Int.Output("WIDTH"),
|
||||
io.Int.Output("HEIGHT"),
|
||||
io.Int.Output("TARGET_WIDTH"),
|
||||
io.Int.Output("TARGET_HEIGHT"),
|
||||
io.String.Output("CLIP_L_POSITIVE"),
|
||||
io.String.Output("CLIP_G_POSITIVE"),
|
||||
io.String.Output("T5XXL_POSITIVE"),
|
||||
io.String.Output("CLIP_L_NEGATIVE"),
|
||||
io.String.Output("CLIP_G_NEGATIVE"),
|
||||
io.String.Output("T5XXL_NEGATIVE"),
|
||||
io.String.Output("LORA_TRIGGERS"),
|
||||
]
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def execute(cls, **kwargs) -> io.NodeOutput:
|
||||
kwargs["sampler"], kwargs["sigmas"] = build_sd3_sampler_sigmas(
|
||||
kwargs.get("sampler_name"), kwargs.get("steps"), kwargs.get("schedule_shift"),
|
||||
)
|
||||
|
||||
workflow_config = TTWorkflowSettings.create(**kwargs)
|
||||
args = {
|
||||
"workflow_config": workflow_config,
|
||||
"sampler": kwargs["sampler"],
|
||||
"sigmas": kwargs["sigmas"],
|
||||
"seed": kwargs.get("seed"),
|
||||
"steps": kwargs.get("steps"),
|
||||
"cfg": kwargs.get("cfg"),
|
||||
"width": kwargs.get("width"),
|
||||
"height": kwargs.get("height"),
|
||||
"target_width": kwargs.get("target_width"),
|
||||
"target_height": kwargs.get("target_height"),
|
||||
"clip_l_positive": kwargs.get("clip_l_positive"),
|
||||
"clip_g_positive": kwargs.get("clip_g_positive"),
|
||||
"t5xxl_positive": kwargs.get("t5xxl_positive"),
|
||||
"clip_l_negative": kwargs.get("clip_l_negative"),
|
||||
"clip_g_negative": kwargs.get("clip_g_negative"),
|
||||
"t5xxl_negative": kwargs.get("t5xxl_negative"),
|
||||
"lora_triggers": kwargs.get("lora_triggers"),
|
||||
}
|
||||
|
||||
return io.NodeOutput(*args.values())
|
||||
|
||||
|
||||
# ==============================================================================
|
||||
# V3 entrypoint — registers context nodes with ComfyUI
|
||||
# ==============================================================================
|
||||
|
||||
NODES = [
|
||||
TT_SdxlWorkflowSettingsNode,
|
||||
TT_SdxlWorkflowSettingsAdvancedNode,
|
||||
TT_FluxWorkflowSettingsNode,
|
||||
TT_FluxWorkflowSettingsAdvancedNode,
|
||||
TT_Flux2WorkflowSettingsNode,
|
||||
TT_Flux2WorkflowSettingsAdvancedNode,
|
||||
TT_Sd35GgufWorkflowSettingsNode,
|
||||
TT_Sd35GgufWorkflowSettingsAdvancedNode,
|
||||
]
|
||||
+1
-1
@@ -13,7 +13,7 @@ description = """A collection of custom nodes for ComfyUI that streamline common
|
||||
— VAE encoders and decoders: tiled and context-based
|
||||
— Image tools: load, resize, upscale, preview, save, ControlNet reference
|
||||
— Post-production: brightness, contrast, LUT color grading, film grain, upscaler"""
|
||||
version = "1.7.8"
|
||||
version = "2.0.0"
|
||||
license = { file = "LICENSE" }
|
||||
dependencies = ["gguf", "kornia"]
|
||||
|
||||
|
||||
Reference in New Issue
Block a user