Updated IAMCCS-nodes to version 1.4.4

This commit is contained in:
IAMCCS
2026-05-04 07:08:57 +02:00
parent 8ba4abb4d3
commit 2d30c4f564
8 changed files with 4749 additions and 598 deletions
+4
View File
@@ -1,5 +1,9 @@
# IAMCCS Nodes - Changelog
## 🆕 2026-05-04 - version 1.4.4 — Supernodes v.2 and bug fixed pplus utilities added
## 🆕 2026-04-24 - version 1.4.3 — Supwenodes, more LTX and WAN 2.2 nodes and utilities added
## 🆕 2026-04-13 - version 1.4.1 — Multigen wrappers added
## 🆕 2026-03-27 - version 1.4.0 — Audio extension ltx-2.3
+1 -1
View File
@@ -9,7 +9,7 @@
### Category: ComfyUI Custom Nodes
### Main Feature: Fix for LoRA loading in native WANAnimate workflows + general nodes 4 ComfyUI
Version: 1.4.3
Version: 1.4.4 (Supernodes and wan 2.2 + LTX 2.2 utilities added)
## IAMCCS SuperNodes requirements
+3
View File
@@ -59,6 +59,7 @@ from .iamccs_ltx2_tools import (
IAMCCS_LTX2_ImageBatchCropByPad,
IAMCCS_SegmentPlanner,
IAMCCS_SegmentPlannerSettings,
IAMCCS_AudioSegmentAutoPlanner,
IAMCCS_SegmentPlannerLinked,
IAMCCS_SegmentPlanFromPlanner,
IAMCCS_SourceRangeFromSegmentPlan,
@@ -313,6 +314,7 @@ NODE_CLASS_MAPPINGS = {
"IAMCCS_LTX2_ImageBatchCropByPad": IAMCCS_LTX2_ImageBatchCropByPad,
"IAMCCS_SegmentPlanner": IAMCCS_SegmentPlanner,
"IAMCCS_SegmentPlannerSettings": IAMCCS_SegmentPlannerSettings,
"IAMCCS_AudioSegmentAutoPlanner": IAMCCS_AudioSegmentAutoPlanner,
"IAMCCS_SegmentPlannerLinked": IAMCCS_SegmentPlannerLinked,
"IAMCCS_SegmentPlanFromPlanner": IAMCCS_SegmentPlanFromPlanner,
"IAMCCS_SourceRangeFromSegmentPlan": IAMCCS_SourceRangeFromSegmentPlan,
@@ -501,6 +503,7 @@ NODE_DISPLAY_NAME_MAPPINGS = {
"IAMCCS_LTX2_ImageBatchCropByPad": "LTX-2 Crop By Pad (IMAGE batch)",
"IAMCCS_SegmentPlanner": "Segment Planner (song -> LTX frames)",
"IAMCCS_SegmentPlannerSettings": "Segment Planner Settings (shared)",
"IAMCCS_AudioSegmentAutoPlanner": "Audio Segment Auto Planner (audio -> segmenti)",
"IAMCCS_SegmentPlannerLinked": "Segment Planner Linked (shared inputs)",
"IAMCCS_SegmentPlanFromPlanner": "Segment Plan From Planner (per index)",
"IAMCCS_SourceRangeFromSegmentPlan": "Source Range From Segment Plan",
+159 -1
View File
@@ -10,7 +10,7 @@ from __future__ import annotations
import logging
import math
from typing import Tuple
from typing import Any, Mapping, Tuple
import torch
@@ -23,6 +23,22 @@ _F = torch.nn.functional
_log = logging.getLogger("IAMCCS.LTX2.Tools")
def _audio_duration_seconds(audio: Any) -> float:
if not isinstance(audio, Mapping):
raise ValueError("audio input is required")
waveform = audio.get("waveform")
sample_rate = int(audio.get("sample_rate", 0) or 0)
if waveform is None or sample_rate <= 0:
raise ValueError("audio input must contain waveform and sample_rate")
try:
total_samples = int(waveform.shape[-1])
except Exception as exc:
raise ValueError("audio waveform has no sample dimension") from exc
if total_samples <= 0:
raise ValueError("audio waveform is empty")
return float(total_samples) / float(sample_rate)
def _to_grayscale(image: torch.Tensor) -> torch.Tensor:
# image: [B,H,W,C] float in [0,1]
if image.shape[-1] == 1:
@@ -893,6 +909,148 @@ class IAMCCS_SegmentPlannerSettings(IAMCCS_SegmentPlanner):
)
class IAMCCS_AudioSegmentAutoPlanner(IAMCCS_SegmentPlanner):
SPLIT_BY_SEGMENTS = "choose segment count (audio / segments)"
SPLIT_BY_SECONDS = "choose seconds per segment (auto count)"
SEGMENT_COUNT_CHOICES = (1, 2, 3, 4, 5, 6, 8, 10, 12)
SEGMENT_SECONDS_CHOICES = (5.0, 8.0, 10.0, 12.0, 15.0, 20.0, 30.0)
@classmethod
def INPUT_TYPES(cls):
return {
"required": {
"audio": ("AUDIO",),
"fps": ("FLOAT", {"default": 25.0, "min": 0.001, "max": 240.0, "step": 0.01}),
"split_mode": ([cls.SPLIT_BY_SEGMENTS, cls.SPLIT_BY_SECONDS], {"default": cls.SPLIT_BY_SEGMENTS}),
"segment_count": (list(cls.SEGMENT_COUNT_CHOICES), {"default": 2}),
"target_segment_seconds": (list(cls.SEGMENT_SECONDS_CHOICES), {"default": 10.0}),
"overlap_frames": ("INT", {"default": 9, "min": 0, "max": 4096, "step": 1}),
"ltx_round_mode": (["up", "nearest", "down"], {"default": "up"}),
}
}
RETURN_TYPES = (
"FLOAT", "FLOAT", "STRING", "STRING", "INT",
"INT", "INT", "INT", "INT", "INT", "INT", "FLOAT", "STRING",
)
RETURN_NAMES = (
"audio_duration_s",
"segment_duration_s",
"planning_mode",
"segment_preset",
"overlap_frames",
"segment_count",
"total_frames",
"unique_segment_frames",
"first_segment_raw_frames",
"continuation_raw_frames",
"last_segment_unique_frames",
"fps_out",
"report",
)
FUNCTION = "plan_audio_segments"
CATEGORY = "IAMCCS/LTX-2"
def _preset_from_seconds(self, seconds: float) -> str:
preset, _rec = self._recommend_profile_for_segment_seconds(float(seconds))
if preset is not None:
return str(preset)
choices = ((5.0, "5sec"), (10.0, "10sec"), (15.0, "15sec"), (20.0, "20sec"))
return min(choices, key=lambda item: abs(float(seconds) - item[0]))[1]
@staticmethod
def _first_number(value: Any, default: float) -> float:
if isinstance(value, (int, float)):
return float(value)
text = str(value or "").strip()
token = ""
seen_decimal = False
for ch in text:
if ch.isdigit():
token += ch
elif ch == "." and token and not seen_decimal:
token += ch
seen_decimal = True
elif token:
break
if not token or token == ".":
return float(default)
try:
return float(token)
except Exception:
return float(default)
def _normalize_split_mode(self, split_mode: Any) -> tuple[str, str]:
text = str(split_mode or "").strip().lower()
if text in ("fixed_segment_seconds", "choose_seconds_per_segment_auto_count"):
return "fixed_segment_seconds", self.SPLIT_BY_SECONDS
if "seconds per segment" in text or "auto count" in text:
return "fixed_segment_seconds", self.SPLIT_BY_SECONDS
return "fixed_segment_count", self.SPLIT_BY_SEGMENTS
def plan_audio_segments(
self,
audio,
fps: float,
split_mode: str,
segment_count: int,
target_segment_seconds: float,
overlap_frames: int,
ltx_round_mode: str,
):
audio_duration_s = max(0.01, _audio_duration_seconds(audio))
fps = max(0.001, float(fps))
split_mode_key, split_mode_label = self._normalize_split_mode(split_mode)
requested_segment_count = max(1, int(round(self._first_number(segment_count, 2))))
target_segment_seconds = max(0.01, float(self._first_number(target_segment_seconds, 10.0)))
if split_mode_key == "fixed_segment_seconds":
segment_duration_s = target_segment_seconds
else:
segment_duration_s = max(0.01, audio_duration_s / float(requested_segment_count))
segment_preset = self._preset_from_seconds(segment_duration_s)
planning_mode = "manual_segment_seconds"
planner_result = self.plan(
audio_duration_s,
fps,
segment_duration_s,
planning_mode,
segment_preset,
int(overlap_frames),
str(ltx_round_mode),
0,
)
computed_segment_count = int(planner_result[4])
report = (
f"audio_duration={audio_duration_s:.3f}s @ {fps:.3f}fps | split_mode={split_mode_key} ({split_mode_label}) | "
f"requested_segments={requested_segment_count} | computed_segments={computed_segment_count} | "
f"segment_duration={segment_duration_s:.3f}s | total={int(planner_result[0])}f | "
f"unique={int(planner_result[1])}f | first_raw={int(planner_result[2])}f | "
f"continuation_raw={int(planner_result[3])}f | last_unique={int(planner_result[6])}f | "
f"preset={segment_preset} | overlap={int(overlap_frames)}f | ltx_round={ltx_round_mode}"
)
_log.info("[AudioSegmentAutoPlanner] %s", report)
return (
float(audio_duration_s),
float(segment_duration_s),
planning_mode,
segment_preset,
int(overlap_frames),
computed_segment_count,
int(planner_result[0]),
int(planner_result[1]),
int(planner_result[2]),
int(planner_result[3]),
int(planner_result[6]),
float(planner_result[17]),
report,
)
class IAMCCS_SegmentPlannerLinked(IAMCCS_SegmentPlanner):
@classmethod
def INPUT_TYPES(cls):
File diff suppressed because it is too large Load Diff
+7 -5
View File
@@ -42,7 +42,7 @@ class IAMCCS_SuperNodes_SecondStage:
"required": {
**merge_backend_panel_inputs({
"second_stage_mode": (["off", "latent_upscale_refine", "latent_upscale_refine_x2_beta"],),
"stage2_model_policy": (["replace_stage1_if_connected", "keep_stage1_model", "prefer_stage2_else_primary"],),
"stage2_model_policy": (["replace_stage1_if_connected", "stage2_model_if_connected", "prefer_stage2_else_primary", "keep_stage1_model"],),
"second_stage_upscale_model": (_LATENT_UPSCALE_MODEL_NAMES, {"default": _LATENT_UPSCALE_MODEL_NAMES[0]}),
"second_stage_reinject_strength": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 1.0, "step": 0.01}),
"second_stage_cfg": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 30.0, "step": 0.1}),
@@ -51,7 +51,7 @@ class IAMCCS_SuperNodes_SecondStage:
},
"optional": {
"stage2_model": ("MODEL", {"lazy": True}),
"linx": (SUPERNODE_LINX_TYPE,),
"linx": (SUPERNODE_LINX_TYPE, {"lazy": True}),
"model": ("MODEL", {"lazy": True}),
},
"hidden": {
@@ -59,11 +59,13 @@ class IAMCCS_SuperNodes_SecondStage:
},
}
def check_lazy_status(self, second_stage_mode, stage2_model=None, model=None, **kwargs):
def check_lazy_status(self, second_stage_mode, stage2_model_policy="replace_stage1_if_connected", stage2_model=None, model=None, linx=None, **kwargs):
if str(second_stage_mode) == "off":
return []
if str(stage2_model_policy) == "keep_stage1_model":
return [] if model is not None or linx is not None else ["model", "linx"]
needed = []
if stage2_model is None:
if stage2_model is None and model is None:
needed.append("stage2_model")
return needed
@@ -137,7 +139,7 @@ class IAMCCS_SuperNodes_SecondStage:
selected_model = primary_model
policy = str(stage2_model_policy)
if connected_stage2_model is not None:
if policy in {"replace_stage1_if_connected", "prefer_stage2_else_primary"}:
if policy in {"replace_stage1_if_connected", "stage2_model_if_connected", "prefer_stage2_else_primary"}:
selected_model = connected_stage2_model
elif selected_model is None:
selected_model = connected_stage2_model
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "iamccs-nodes",
"version": "1.4.3",
"version": "1.4.4",
"author": "Carmine Cristallo Scalzi (IAMCCS)",
"description": "IAMCCS nodes for ComfyUI: IAMCCS echosystem for ComfyUI, nodes 4 LoRA, WAN 2.2, WAN 2.1 and LTX-2 pipelines, WANIMAGEMOTION pro added."
}
File diff suppressed because it is too large Load Diff