Updated IAMCCS-nodes to version 1.4.2

This commit is contained in:
IAMCCS
2026-04-29 10:07:38 +02:00
parent aafd5731b4
commit b097a2b3e0
6 changed files with 1003 additions and 150 deletions
+3
View File
@@ -73,6 +73,7 @@ from .iamccs_ltx2_extension_module import (
IAMCCS_LoadImagesFromDirLite,
IAMCCS_SourceFramesToDisk,
IAMCCS_StartDirToVideoLatent,
IAMCCS_StartImagesToVideoLatent,
IAMCCS_VideoCombineFromDir,
IAMCCS_LTX2_ExtensionModule_simple,
IAMCCS_LTX2_GetImageFromBatch,
@@ -318,6 +319,7 @@ NODE_CLASS_MAPPINGS = {
"IAMCCS_LoadImagesFromDirLite": IAMCCS_LoadImagesFromDirLite,
"IAMCCS_SourceFramesToDisk": IAMCCS_SourceFramesToDisk,
"IAMCCS_StartDirToVideoLatent": IAMCCS_StartDirToVideoLatent,
"IAMCCS_StartImagesToVideoLatent": IAMCCS_StartImagesToVideoLatent,
"IAMCCS_VideoCombineFromDir": IAMCCS_VideoCombineFromDir,
"IAMCCS_LTX2_ExtensionModule_simple": IAMCCS_LTX2_ExtensionModule_simple,
"IAMCCS_LTX2_GetImageFromBatch": IAMCCS_LTX2_GetImageFromBatch,
@@ -500,6 +502,7 @@ NODE_DISPLAY_NAME_MAPPINGS = {
"IAMCCS_LoadImagesFromDirLite": "Load Images From Dir (Lite) 📁",
"IAMCCS_SourceFramesToDisk": "Source Frames To Disk 📼💾",
"IAMCCS_StartDirToVideoLatent": "Start Dir To Video Latent 🚀",
"IAMCCS_StartImagesToVideoLatent": "Start Images To Video Latent 🚀",
"IAMCCS_VideoCombineFromDir": "Video Combine From Dir 🎞️",
"IAMCCS_LTX2_ExtensionModule_simple": "LTX-2 Extension Module (simple) 🎬",
"IAMCCS_LTX2_GetImageFromBatch": "LTX-2 Get Images From Batch 🎞️",
+92
View File
@@ -1422,6 +1422,98 @@ class IAMCCS_StartDirToVideoLatent:
return ({"samples": samples, "noise_mask": conditioning_latent_frames_mask}, int(images.shape[0]), report)
class IAMCCS_StartImagesToVideoLatent:
@classmethod
def INPUT_TYPES(cls):
return {
"required": {
"start_images": ("IMAGE",),
"vae": ("VAE",),
"latent": ("LATENT",),
"mode": (["all", "from_start", "from_end"], {"default": "all"}),
"count": ("INT", {"default": 9, "min": 1, "max": 512, "step": 1}),
"insert_at_pixel_frame": ("INT", {"default": 0, "min": 0, "max": 100000, "step": 1}),
"strength": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 1.0, "step": 0.01}),
"preprocess": ("BOOLEAN", {"default": True}),
"preprocess_crf": ("INT", {"default": 33, "min": 0, "max": 100, "step": 1}),
}
}
RETURN_TYPES = ("LATENT", "INT", "STRING")
RETURN_NAMES = ("latent", "frames_loaded", "report")
FUNCTION = "inject"
CATEGORY = "IAMCCS/LTX-2"
def inject(self, start_images, vae, latent, mode: str, count: int, insert_at_pixel_frame: int, strength: float, preprocess: bool, preprocess_crf: int):
images = start_images
if images is None or getattr(images, "shape", None) is None or int(images.shape[0]) == 0:
raise ValueError("No start images provided")
count = max(1, int(count))
if mode == "from_start":
images = images[:count]
elif mode == "from_end":
images = images[-count:]
if preprocess:
try:
import comfy_extras.nodes_lt as nodes_lt # type: ignore
images = nodes_lt.LTXVPreprocess().execute(images, int(preprocess_crf))[0]
except Exception as e:
_log.warning("[IAMCCS_StartImagesToVideoLatent] preprocess fallback: %s", e)
samples = latent["samples"].clone()
scale_factors = getattr(vae, "downscale_index_formula", (8, 32, 32))
time_scale_factor, height_scale_factor, width_scale_factor = scale_factors
batch, _, latent_frames, latent_height, latent_width = samples.shape
width = latent_width * width_scale_factor
height = latent_height * height_scale_factor
if images.shape[1] != height or images.shape[2] != width:
try:
import comfy.utils # type: ignore
except Exception as e:
raise ImportError("comfy.utils is required for IAMCCS_StartImagesToVideoLatent") from e
pixels = comfy.utils.common_upscale(images.movedim(-1, 1), width, height, "bilinear", "center").movedim(1, -1)
else:
pixels = images
encoded = vae.encode(pixels[:, :, :, :3])
if isinstance(encoded, dict):
encoded = encoded.get("samples", encoded)
if encoded.ndim == 4:
encoded = encoded.unsqueeze(2)
if encoded.ndim != 5:
raise ValueError(f"Unexpected encoded latent shape: {tuple(encoded.shape)}")
if encoded.shape[0] != batch:
if encoded.shape[0] == 1 and batch == 1:
pass
elif batch == 1:
encoded = encoded[:1]
else:
raise ValueError("Encoded batch does not match target latent batch")
if "noise_mask" in latent:
conditioning_latent_frames_mask = latent["noise_mask"].clone()
else:
conditioning_latent_frames_mask = torch.ones((batch, 1, latent_frames, 1, 1), dtype=torch.float32, device=samples.device)
latent_idx = max(0, min(int(insert_at_pixel_frame) // max(1, int(time_scale_factor)), latent_frames - 1))
end_index = min(latent_idx + int(encoded.shape[2]), latent_frames)
samples[:, :, latent_idx:end_index] = encoded[:, :, :end_index - latent_idx]
conditioning_latent_frames_mask[:, :, latent_idx:end_index] = 1.0 - float(max(0.0, min(1.0, strength)))
report = (
f"Loaded {int(images.shape[0])} start images from input | "
f"insert_pixel={int(insert_at_pixel_frame)} -> latent_idx={latent_idx} | "
f"encoded_t={int(encoded.shape[2])} | replaced={int(end_index - latent_idx)} latent slots"
)
return ({"samples": samples, "noise_mask": conditioning_latent_frames_mask}, int(images.shape[0]), report)
class IAMCCS_VideoCombineFromDir:
@classmethod
def INPUT_TYPES(cls):
+15 -2
View File
@@ -774,6 +774,7 @@ class IAMCCS_SegmentPlanner:
continuation_loops = max(0, estimated_segments - 1)
remainder = total_frames - unique_segment_frames * max(0, estimated_segments - 1)
last_segment_unique_frames = unique_segment_frames if remainder <= 0 else int(remainder)
last_segment_raw_frames = first_segment_raw_frames if estimated_segments <= 1 else self._fix_ltx_frames(last_segment_unique_frames + overlap_frames, str(ltx_round_mode))
clamped_segment_index = min(segment_index, max(0, estimated_segments - 1))
current_segment_start_frames = unique_segment_frames * clamped_segment_index
@@ -783,7 +784,12 @@ class IAMCCS_SegmentPlanner:
current_segment_start_frames = min(current_segment_start_frames, max(0, total_frames - current_segment_unique_frames))
current_segment_end_frames = min(total_frames, current_segment_start_frames + current_segment_unique_frames)
current_remaining_frames_after = max(0, total_frames - current_segment_end_frames)
current_segment_raw_frames = first_segment_raw_frames if clamped_segment_index == 0 else continuation_raw_frames
if clamped_segment_index == 0:
current_segment_raw_frames = first_segment_raw_frames
elif clamped_segment_index >= estimated_segments - 1:
current_segment_raw_frames = last_segment_raw_frames
else:
current_segment_raw_frames = continuation_raw_frames
current_segment_start_s = float(current_segment_start_frames) / float(fps)
current_segment_end_s = float(current_segment_end_frames) / float(fps)
recommended_overlap_frames = int(rec["overlap_frames"])
@@ -981,6 +987,7 @@ class IAMCCS_SegmentPlanFromPlanner:
estimated_segments = max(1, int(estimated_segments))
last_segment_unique_frames = max(1, int(last_segment_unique_frames))
segment_index = max(0, min(int(segment_index), estimated_segments - 1))
overlap_hint_frames = max(0, continuation_raw_frames - unique_segment_frames)
current_segment_start_frames = unique_segment_frames * segment_index
if segment_index >= estimated_segments - 1:
@@ -990,7 +997,13 @@ class IAMCCS_SegmentPlanFromPlanner:
current_segment_end_frames = min(total_frames, current_segment_start_frames + current_segment_unique_frames)
current_remaining_frames_after = max(0, total_frames - current_segment_end_frames)
current_segment_raw_frames = first_segment_raw_frames if segment_index == 0 else continuation_raw_frames
if segment_index == 0:
current_segment_raw_frames = first_segment_raw_frames
elif segment_index >= estimated_segments - 1:
current_segment_raw_frames = max(1, current_segment_unique_frames + overlap_hint_frames)
current_segment_raw_frames = 1 + 8 * max(0, int(math.ceil(float(current_segment_raw_frames - 1) / 8.0)))
else:
current_segment_raw_frames = continuation_raw_frames
current_segment_start_s = float(current_segment_start_frames) / float(fps)
current_segment_end_s = float(current_segment_end_frames) / float(fps)
current_segment_report = (
File diff suppressed because it is too large Load Diff
+48 -1
View File
@@ -204,6 +204,48 @@ function ensurePlannerSettingsReportWidget(node) {
return widget;
}
function setWidgetVisibility(widget, visible) {
if (!widget || widget.type === "converted-widget") return;
widget.hidden = !visible;
widget.disabled = !visible;
if (widget.element) {
widget.element.style.display = visible ? "" : "none";
}
if (widget.inputEl) {
widget.inputEl.style.display = visible ? "" : "none";
}
if (visible) {
if (Object.prototype.hasOwnProperty.call(widget, "__iamccsOrigComputeSize")) {
widget.computeSize = widget.__iamccsOrigComputeSize;
} else {
delete widget.computeSize;
}
} else {
if (!Object.prototype.hasOwnProperty.call(widget, "__iamccsOrigComputeSize")) {
widget.__iamccsOrigComputeSize = widget.computeSize;
}
widget.computeSize = () => [0, -4];
widget.y = undefined;
widget.last_y = undefined;
}
}
function applySegmentPlannerSettingsVisibility(node) {
const wPlanning = getWidget(node, "planning_mode");
const wSeg = getWidget(node, "segment_duration_s");
const wPreset = getWidget(node, "segment_preset") || getWidget(node, "content_profile");
if (!wPlanning || !wSeg || !wPreset) return;
const planningMode = String(wPlanning.value || "manual_segment_seconds");
const explicitPresetMode = planningMode === "explicit_preset_seconds" || planningMode === "auto_profile";
setWidgetVisibility(wSeg, !explicitPresetMode);
setWidgetVisibility(wPreset, explicitPresetMode);
}
function ensurePlannerNodeSize(node) {
try {
const width = Math.max(460, Number(node.size?.[0] || 0));
@@ -235,6 +277,8 @@ function updateSegmentPlannerSettingsReport(node) {
const wAutoSync = getWidget(node, "auto_sync_overlap");
if (!wSeg || !wPlanning || !wProfile || !wOverlap || !wAutoSync) return;
applySegmentPlannerSettingsVisibility(node);
const segmentDuration = clampNumber(wSeg.value, 0.01, 3600.0);
const planningMode = String(wPlanning.value || "manual_segment_seconds");
const segmentPreset = String(wProfile.value || "10sec");
@@ -357,7 +401,8 @@ function updateSegmentPlannerPreview(node) {
const currentUnique = Math.max(1, Math.min(uniqueFrames, totalFrames - currentStart));
const currentEnd = Math.min(totalFrames, currentStart + currentUnique);
const currentRemaining = Math.max(0, totalFrames - currentEnd);
const currentRaw = clampedIndex === 0 ? firstRaw : nextRaw;
const lastRaw = segments <= 1 ? firstRaw : snapLengthToLtx2Rule(lastUnique + effectiveOverlapFrames, roundMode);
const currentRaw = clampedIndex === 0 ? firstRaw : (clampedIndex >= segments - 1 ? lastRaw : nextRaw);
const currentStartS = currentStart / fps;
const currentEndS = currentEnd / fps;
@@ -394,6 +439,7 @@ function updateSegmentPlannerPreview(node) {
`unique_segment_frames = ${uniqueFrames}`,
`first_segment_raw_frames = ${firstRaw}`,
`continuation_raw_frames = ${nextRaw}`,
`last_segment_raw_frames = ${lastRaw}`,
`estimated_segments = ${segments}`,
`continuation_loops = ${loops}`,
`last_segment_unique_frames = ${lastUnique}`,
@@ -470,6 +516,7 @@ function installSegmentPlannerSettingsSync(node) {
].filter(Boolean);
if (!widgets.length || node._iamccsSegmentPlannerSettingsSyncInstalled) {
applySegmentPlannerSettingsVisibility(node);
updateSegmentPlannerSettingsReport(node);
return;
}
+44 -4
View File
@@ -5,10 +5,10 @@ const PRESET_CONFIGS = {
presetWidget: "ui_preset",
defaultPreset: "balanced",
values: {
low_ram_safe: { modular_decode: "low_ram", steps: 16, image_compression: 40, continuity_anchor_mode: "off", anchor_refresh_interval: 2, anti_drift_mode: "off", anti_drift_strength: 0.0, identity_persistence_strength: 0.0 },
balanced: { modular_decode: "normal", steps: 20, image_compression: 33, continuity_anchor_mode: "off", anchor_refresh_interval: 3, anti_drift_mode: "off", anti_drift_strength: 0.0, identity_persistence_strength: 0.0 },
high_quality: { modular_decode: "high", steps: 24, image_compression: 28, continuity_anchor_mode: "off", anchor_refresh_interval: 1, anti_drift_mode: "off", anti_drift_strength: 0.0, identity_persistence_strength: 0.0 },
fast_preview: { modular_decode: "low_ram", steps: 12, image_compression: 45, continuity_anchor_mode: "off", anchor_refresh_interval: 2, anti_drift_mode: "off", anti_drift_strength: 0.0, identity_persistence_strength: 0.0 },
low_ram_safe: { modular_decode: "low_ram", steps: 16, image_compression: 40, continuity_anchor_mode: "off", anchor_refresh_interval: 2, anti_drift_mode: "off", anti_drift_strength: 0.0, identity_persistence_strength: 0.0, generation_mode: "img2vid", second_stage_mode: "off", stage2_model_policy: "stage2_model_if_connected", second_stage_reinject_strength: 0.0 },
balanced: { modular_decode: "normal", steps: 20, image_compression: 33, continuity_anchor_mode: "off", anchor_refresh_interval: 3, anti_drift_mode: "off", anti_drift_strength: 0.0, identity_persistence_strength: 0.0, generation_mode: "img2vid", second_stage_mode: "off", stage2_model_policy: "stage2_model_if_connected", second_stage_reinject_strength: 0.0 },
high_quality: { modular_decode: "high", steps: 24, image_compression: 28, continuity_anchor_mode: "off", anchor_refresh_interval: 1, anti_drift_mode: "off", anti_drift_strength: 0.0, identity_persistence_strength: 0.0, generation_mode: "img2vid", second_stage_mode: "off", stage2_model_policy: "stage2_model_if_connected", second_stage_reinject_strength: 0.0 },
fast_preview: { modular_decode: "low_ram", steps: 12, image_compression: 45, continuity_anchor_mode: "off", anchor_refresh_interval: 2, anti_drift_mode: "off", anti_drift_strength: 0.0, identity_persistence_strength: 0.0, generation_mode: "img2vid", second_stage_mode: "off", stage2_model_policy: "stage2_model_if_connected", second_stage_reinject_strength: 0.0 },
},
visibility: {
low_ram_safe: { anchor_image_strength: false, anti_drift_mode: false, anti_drift_strength: false, identity_persistence_strength: false, output_root: true },
@@ -50,6 +50,7 @@ const NODE_GROUPS = {
{ key: "anchor", label: "Anchor", color: "#b35c5c", widgets: ["continuity_anchor_mode", "anchor_refresh_interval", "anchor_image_strength", "anti_drift_mode", "anti_drift_strength", "identity_persistence_strength"] },
{ key: "modular", label: "Modular", color: "#46769a", widgets: ["modular_decode", "downstream_stage_mode", "output_root"] },
{ key: "debug", label: "Debug", color: "#8a8a36", widgets: ["segment_overlay_mode", "segment_overlay_text"] },
{ key: "stage2", label: "Mode + Second Stage", color: "#8a5ca0", widgets: ["generation_mode", "second_stage_mode", "stage2_model_policy", "second_stage_upscale_model", "second_stage_reinject_strength", "second_stage_cfg", "second_stage_manual_sigmas"] },
],
"IAMCCS-SuperNodes Second Stage": [
{ key: "stage2", label: "Second Stage", color: "#8a5ca0", widgets: ["second_stage_mode", "stage2_model_policy", "second_stage_upscale_model", "second_stage_reinject_strength", "second_stage_cfg", "second_stage_manual_sigmas"] },
@@ -324,9 +325,29 @@ function applyRenderAnchorLabels(node) {
if (node.comfyClass !== "IAMCCS-SuperNodes AU+IMG2VID Exec Render") {
return;
}
setWidgetLabel(node, "generation_mode", "Generation Mode");
setWidgetLabel(node, "continuity_anchor_mode", "Anchor Refresh");
setWidgetLabel(node, "anchor_refresh_interval", "Refresh Interval");
setWidgetLabel(node, "anchor_image_strength", "Anchor Guidance Strength");
setWidgetLabel(node, "second_stage_mode", "Second Stage");
setWidgetLabel(node, "stage2_model_policy", "Stage2 Model Policy");
setWidgetLabel(node, "second_stage_upscale_model", "2x Upscale Model");
setWidgetLabel(node, "second_stage_reinject_strength", "Anchor Reinject Strength");
}
function applyRenderSecondStageVisibility(node) {
if (node.comfyClass !== "IAMCCS-SuperNodes AU+IMG2VID Exec Render") {
return;
}
const mode = String(findWidget(node, "second_stage_mode")?.value || "off");
const stageExpanded = !!node.properties?.iamccs_section_stage2;
const enabled = stageExpanded && mode !== "off";
setWidgetVisibility(findWidget(node, "stage2_model_policy"), enabled);
setWidgetVisibility(findWidget(node, "second_stage_reinject_strength"), enabled);
setWidgetVisibility(findWidget(node, "second_stage_cfg"), enabled);
setWidgetVisibility(findWidget(node, "second_stage_manual_sigmas"), enabled);
setWidgetVisibility(findWidget(node, "second_stage_upscale_model"), enabled && mode === "latent_upscale_refine_x2_beta");
fitNodeToWidgets(node);
}
function syncDownstreamVaeDecodeModes(renderNode) {
@@ -373,6 +394,7 @@ function applyPresetConfig(node, nodeName) {
}
if (nodeName === "IAMCCS-SuperNodes AU+IMG2VID Exec Render") {
applyRenderAnchorLabels(node);
applyRenderSecondStageVisibility(node);
}
fitNodeToWidgets(node);
}
@@ -444,6 +466,9 @@ function applyGroupVisibility(node, group, propKey, button) {
for (const widgetName of group.widgets) {
setWidgetVisibility(findWidget(node, widgetName), isExpanded);
}
if (node.comfyClass === "IAMCCS-SuperNodes AU+IMG2VID Exec Render" && group.key === "stage2") {
applyRenderSecondStageVisibility(node);
}
fitNodeToWidgets(node);
app.graph.setDirtyCanvas(true, true);
}
@@ -482,6 +507,8 @@ function refreshNodeLayoutState(node, nodeName) {
applyVaeDecodeModeVisibility(node);
} else if (nodeName === "IAMCCS-SuperNodes AU+IMG2VID Exec Planner") {
applyPlannerModeVisibility(node);
} else if (nodeName === "IAMCCS-SuperNodes AU+IMG2VID Exec Render") {
applyRenderSecondStageVisibility(node);
} else {
fitNodeToWidgets(node);
}
@@ -685,6 +712,19 @@ app.registerExtension({
};
syncDownstreamVaeDecodeModes(this);
}
for (const widgetName of ["generation_mode", "second_stage_mode"]) {
const widget = findWidget(this, widgetName);
if (!widget) {
continue;
}
const originalCallback = widget.callback;
widget.callback = (...args) => {
originalCallback?.apply(widget, args);
applyRenderSecondStageVisibility(this);
app.graph.setDirtyCanvas(true, true);
};
}
applyRenderSecondStageVisibility(this);
}
if (nodeName === "IAMCCS-SuperNodes AU+IMG2VID Exec Planner") {
installExecPlannerExplicitPresetSync(this);