From a4daeb5f7d92d0670433951089e4dac3d24052b9 Mon Sep 17 00:00:00 2001 From: IAMCCS Date: Tue, 27 Jan 2026 13:17:39 +0100 Subject: [PATCH] Updated IAMCCS-nodes to version 1.3.3 --- AUTOLINK_PAPER.md | 185 + CHANGELOG.md | 44 + IAMCCS_WAN22_SVI_PRO_v-2_FIXED2.json | 7855 +++++++++++++++++ README.md | 56 +- __init__.py | 215 +- assets/autolink.png | Bin 0 -> 70523 bytes assets/extension.png | Bin 0 -> 85282 bytes assets/gguf.png | Bin 0 -> 36717 bytes docs/LTX2_EXTENSION_MODULE_COMPLETE_GUIDE.md | 851 ++ docs/LTX2_EXTENSION_NODES_GUIDE_EN.md | 394 + WanImageMotion.md => docs/WanImageMotion.md | 0 iamccs_autolink.py | 76 + iamccs_gguf_accelerator.py | 277 + iamccs_ltx2_extension_module.py | 1161 +++ iamccs_ltx2_lora_stack_segmented6.py | 288 + iamccs_ltx2_tools.py | 246 +- pyproject.toml | 4 +- version.json | 4 +- web/iamccs_autolink_converter.js | 2795 ++++++ web/iamccs_ltx2_time_length_sync.js | 117 + web/js/iamccs_ltx2_validator_sync.js | 190 - .../IAMCCS_LTX2_ExtensionModule_Example.json | 247 + 22 files changed, 14798 insertions(+), 207 deletions(-) create mode 100644 AUTOLINK_PAPER.md create mode 100644 IAMCCS_WAN22_SVI_PRO_v-2_FIXED2.json create mode 100644 assets/autolink.png create mode 100644 assets/extension.png create mode 100644 assets/gguf.png create mode 100644 docs/LTX2_EXTENSION_MODULE_COMPLETE_GUIDE.md create mode 100644 docs/LTX2_EXTENSION_NODES_GUIDE_EN.md rename WanImageMotion.md => docs/WanImageMotion.md (100%) create mode 100644 iamccs_autolink.py create mode 100644 iamccs_gguf_accelerator.py create mode 100644 iamccs_ltx2_extension_module.py create mode 100644 iamccs_ltx2_lora_stack_segmented6.py create mode 100644 web/iamccs_autolink_converter.js create mode 100644 web/iamccs_ltx2_time_length_sync.js delete mode 100644 web/js/iamccs_ltx2_validator_sync.js create mode 100644 workflow_examples/IAMCCS_LTX2_ExtensionModule_Example.json diff --git a/AUTOLINK_PAPER.md b/AUTOLINK_PAPER.md new file mode 100644 index 0000000..d71f4b9 --- /dev/null +++ b/AUTOLINK_PAPER.md @@ -0,0 +1,185 @@ +# IAMCCS AutoLink — Paper & Usage Instructions (EN/IT) + +## English + +### 1) What is AutoLink? +AutoLink is a **Set/Get** workflow tool designed to keep ComfyUI graphs clean and maintainable. + +Instead of long cables across the canvas, AutoLink lets you: +- Convert direct connections into **Set** (source) + **Get** (destination) pairs +- Restore the original direct connections when needed +- Apply repeatable filters (groups/blacklist), layout rules, and colors + +Everything is controlled by a dedicated “tool” node that operates on the canvas. + +### 2) Components +AutoLink is made of four logical elements: + +1. **AutoLink Converter** + - Buttons to convert/restore links. +2. **AutoLink Arguments** + - Central configuration: group filters, alignment/layout, packing/anti-overlap, colors, blacklist. +3. **AutoLink Set** + - Created near the source node: captures an output and exposes it under a key. +4. **AutoLink Get** + - Created near the destination node: retrieves the key and feeds the target input. + +### 3) Quickstart +1. Add to the canvas: + - **AutoLink Arguments** + - **AutoLink Converter** +2. Connect **AutoLink Arguments** output to the Converter `arg` input. +3. Adjust options (or keep defaults). +4. Click **Convert All Links**. + +To revert: +- Click **Restore Direct Links**. + +### 4) v1.3.3 reliability updates (important) +AutoLink Set/Get nodes are **UI tools** and are treated as **virtual** nodes. To prevent “missing required input” prompt errors, the extension automatically: +- Materializes direct links **only during prompt serialization/queue**, then restores the AutoLink wiring +- Supports nested graphs/subgraphs +- Truncates long AutoLink titles with an ellipsis (`…`) so they stay inside the node header + +--- + +## Italiano + +### 1) Cos’è AutoLink +AutoLink è un sistema **Set/Get** pensato per rendere i workflow ComfyUI più ordinati, leggibili e facili da mantenere. + +Invece di avere cavi lunghi che attraversano la canvas, AutoLink permette di: +- Convertire automaticamente collegamenti diretti in coppie **Set** (sorgente) + **Get** (destinazione) +- Ripristinare i collegamenti originali quando serve +- Gestire filtri, gruppi, layout e colori in modo ripetibile + +Il tutto è controllato da un nodo “tool” che opera sulla canvas. + +### 2) I nodi coinvolti +AutoLink è composto da quattro elementi logici: + +1. **AutoLink Converter** + - Contiene i pulsanti per convertire/ripristinare i collegamenti. +2. **AutoLink Arguments** + - Contiene tutte le opzioni: filtri per gruppi, layout, packing/anti-overlap, colori, blacklist. +3. **AutoLink Set** + - Viene creato vicino al nodo sorgente: cattura un output e lo espone con una chiave. +4. **AutoLink Get** + - Viene creato vicino al nodo destinazione: recupera la chiave del Set e alimenta l’input. + +### 3) Quickstart (workflow consigliato) +1. Aggiungi in canvas: + - **AutoLink Arguments** + - **AutoLink Converter** +2. Collega l’output di **AutoLink Arguments** all’input `arg` di **AutoLink Converter**. +3. Imposta le opzioni nel nodo **AutoLink Arguments** (anche lasciando i default). +4. Premi **Convert All Links** nel nodo **AutoLink Converter**. + +Per tornare indietro: +- Premi **Restore Direct Links** nel Converter. + +### 4) Aggiornamenti affidabilità v1.3.3 (importante) +I nodi Set/Get di AutoLink sono strumenti **lato UI** e vengono trattati come nodi **virtuali**. Per evitare errori di prompt del tipo “required input missing”, l’estensione: +- Materializza i link diretti **solo durante la queue/serializzazione del prompt**, poi ripristina il wiring AutoLink +- Supporta grafi annidati/subgraph +- Tronca i titoli AutoLink troppo lunghi con ellissi (`…`) per non farli uscire dal nodo + +--- + +## 5) Opzioni principali (Arguments) + +### 4.1 GroupExclude +- Se abilitato, **non converte** i collegamenti tra due nodi che stanno **dentro lo stesso group**. +- I collegamenti che **entrano** o **escono** dal group possono comunque essere convertiti (dipende anche da GroupInOutExclude). + +Quando usarlo: +- Se un group rappresenta un “blocco logico” che vuoi tenere cablato internamente. + +### 4.2 GroupInOutExclude +Gestisce i link che attraversano un confine di group: +- `None`: nessuna esclusione. +- `ExcludeEnter`: non converte i link che **entrano** in un group. +- `ExcludeExit`: non converte i link che **escono** da un group. +- `ExcludeBoth`: combina entrambe. + +### 4.3 Align mode +Determina come vengono posizionati Set/Get dopo la conversione e quando fai relayout. + +Opzioni principali: +- `TopToDown`, `BottomToTop`, `CenterUpDown`, `CenterDownUp` +- `AlignX_Right`, `AlignX_Left` +- `Columns_Down`, `Columns_Up` +- `Rake_Down`, `Rake_Up` +- **`Proportional`** (consigliato per layout “come i cavi”) + +#### Align = Proportional (come nell’immagine) +Con `Proportional`, Set e Get vengono agganciati alla **stessa altezza (Y)** del relativo connettore (slot) del nodo: +- Set: si allinea alla Y dello **slot di output** sorgente +- Get: si allinea alla Y dello **slot di input** destinazione + +In caso di collisioni, mantiene la Y e cerca spazio spostandosi orizzontalmente. + +### 4.4 Packing mode +Controlla l’anti-overlap durante posizionamento e relayout: +- `AvoidAll`: evita sovrapposizioni con tutti i nodi. +- `AvoidNonAutoLink`: evita solo i nodi non-AutoLink (Set/Get possono compattarsi fra loro). + +### 4.5 SeparateCol + colori +- `SeparateCol`: se attivo, permette di usare colori diversi per Set e Get. +- `AutoLinkColor`: colore base (Set). +- `AutoLinkColorGet`: colore dei Get (solo se SeparateCol è attivo). + +### 4.6 ColorTitles +Cambia il colore del testo del titolo dei nodi AutoLink: +- `White` +- `Black` +- `Auto` + +### 4.7 Blacklist (ID e Types) +AutoLink permette di escludere nodi dalla conversione: + +- `all_nodes_sel`: + - OFF: la blacklist lavora per **tipo** (`[TYPE] ...`) + - ON: la blacklist lavora per **ID singolo nodo** + +- `add_to_blacklist`: + - Scegli un nodo (ID) o un tipo. + +- `blacklist_mode` (solo per nodi singoli): + - `both`: esclude link dove il nodo è sorgente o destinazione + - `only_output`: esclude solo quando il nodo è sorgente (output) + - `only_input`: esclude solo quando il nodo è destinazione (input) + +- `EXECUTE`: + - Applica davvero l’inserimento (o l’update della modalità) e poi pulisce i widget. + +- `blacklist_view`: + - Elenco leggibile: `id - nome nodo - (modalità)` e `[TYPE] ...`. + - Selezionare una voce **non rimuove nulla**. + +- `remove_blacklist`: + - Rimuove la voce attualmente selezionata in `blacklist_view`. + +--- + +## 6) Best practices +- Prima di convertire “tutto”, imposta la blacklist per escludere nodi che vuoi lasciare cablati. +- Usa `GroupExclude` per mantenere “blocchi” interni puliti. +- Usa `Proportional` quando vuoi un layout che segua visivamente l’ordine degli slot (come routing naturale dei cavi). +- Se la canvas è molto piena, prova `PackingMode = AvoidAll`. + +--- + +## 7) Troubleshooting +- **Convert All Links non sembra fare nulla**: + - Verifica che `AutoLink Arguments` sia collegato all’input `arg` del Converter. + - Controlla blacklist e filtri group. +- **Nodi sovrapposti**: + - Prova `PackingMode = AvoidAll`. + - Cambia align mode o usa relayout cambiando `align_mode`. + +--- + +## 8) Documentazione correlata +- AUTOLINK_README.md +- AUTOLINK_TECHNICAL_PAPER.md diff --git a/CHANGELOG.md b/CHANGELOG.md index 8d9da74..c22007e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,47 @@ +# IAMCCS Nodes - Changelog + +## 🆕 Version 1.3.3 — AutoLink + LTX-2 Extension Module (Stability Update) + +Date: 2026-01-26 + +### AutoLink (frontend) +- AutoLink Set/Get + Converter for compact “wireless” graphs +- Convert/Restore tools: + - `Convert All Links` + - `Restore Direct Links` +- Group-aware filters: `GroupExclude`, `GroupInOutExclude` +- Layout controls: multiple align modes (including `Proportional`) + packing/anti-overlap +- Styling controls: color presets, optional separate Set/Get colors, title text color +- Blacklist improvements: per-node (directional) and per-type entries + +Stability fixes: +- AutoLink links are now materialized automatically during queue/prompt serialization (then restored), preventing “missing required input” prompt errors +- Works with nested graphs/subgraphs +- Long AutoLink titles are truncated with an ellipsis (`…`) to prevent overflow + +### LTX-2 Extension (backend nodes) +- Added **LTX-2 Extension Module** (`IAMCCS_LTX2_ExtensionModule`): + - Extends/merges image batches with overlap management + - Built-in math operations for overlap/start-frames logic + - AutoLink integration for overlap sharing between iterations (`autolink_overlap_in/out`) + - Multiple blending modes: cut, linear_blend, ease_in_out, filmic_crossfade, perceptual_crossfade + - Automatic `start_images` extraction for the next pass + - `total_frames` / `validate_ltx2` moved out to dedicated validation utilities + +- Added **LTX-2 Get Images From Batch** (`IAMCCS_LTX2_GetImageFromBatch`): + - Extract frames from start/end or by explicit range + +- Added **LTX-2 Frame Count Validator** (`IAMCCS_LTX2_FrameCountValidator`): + - Validates/corrects counts to the LTX-2 `8n+1` rule + - Intended to be placed before the LTX Sampler + +### LTX-2 frame-count robustness +- `IAMCCS_LTX2_TimeFrameCount` snaps computed `length` to the next valid `8n+1` +- UI seconds↔length sync snaps to valid `8n+1` lengths +- Optional VAE encode auto-padding to valid `8n+1` (defensive safeguard) + +--- + ## 🆕 Version 1.3.2 — LTX-2 Nodes Pack Date: 2026-01-15 diff --git a/IAMCCS_WAN22_SVI_PRO_v-2_FIXED2.json b/IAMCCS_WAN22_SVI_PRO_v-2_FIXED2.json new file mode 100644 index 0000000..a8135df --- /dev/null +++ b/IAMCCS_WAN22_SVI_PRO_v-2_FIXED2.json @@ -0,0 +1,7855 @@ +{ + "id": "ec7da562-7e21-4dac-a0d2-f4441e1efd3b", + "revision": 0, + "last_node_id": 406, + "last_link_id": 637, + "nodes": [ + { + "id": 90, + "type": "VAELoader", + "pos": [ + 355.83329161693285, + -299.32503868581756 + ], + "size": [ + 344.731689453125, + 59.98149108886719 + ], + "flags": {}, + "order": 0, + "mode": 0, + "inputs": [], + "outputs": [ + { + "name": "VAE", + "type": "VAE", + "slot_index": 0, + "links": [ + 284 + ] + } + ], + "properties": { + "cnr_id": "comfy-core", + "ver": "0.3.45", + "Node name for S&R": "VAELoader", + "models": [ + { + "name": "wan_2.1_vae.safetensors", + "url": "https://huggingface.co/Comfy-Org/Wan_2.2_ComfyUI_Repackaged/resolve/main/split_files/vae/wan_2.1_vae.safetensors", + "directory": "vae" + } + ], + "ue_properties": { + "widget_ue_connectable": {}, + "version": "7.1", + "input_ue_unconnectable": {} + } + }, + "widgets_values": [ + "wan_2.1_vae.safetensors" + ], + "color": "#322", + "bgcolor": "#533" + }, + { + "id": 127, + "type": "KSamplerSelect", + "pos": [ + 345.08992579346346, + -1123.9621547462161 + ], + "size": [ + 270, + 58 + ], + "flags": {}, + "order": 1, + "mode": 0, + "inputs": [], + "outputs": [ + { + "name": "SAMPLER", + "type": "SAMPLER", + "links": [ + 253 + ] + } + ], + "properties": { + "cnr_id": "comfy-core", + "ver": "0.5.1", + "Node name for S&R": "KSamplerSelect", + "ue_properties": { + "widget_ue_connectable": {}, + "version": "7.5.2", + "input_ue_unconnectable": {} + } + }, + "widgets_values": [ + "euler" + ] + }, + { + "id": 128, + "type": "SplitSigmas", + "pos": [ + 654.4312613872978, + -995.7349316352739 + ], + "size": [ + 270, + 78 + ], + "flags": {}, + "order": 88, + "mode": 0, + "inputs": [ + { + "name": "sigmas", + "type": "SIGMAS", + "link": 250 + } + ], + "outputs": [ + { + "name": "high_sigmas", + "type": "SIGMAS", + "links": [ + 254 + ] + }, + { + "name": "low_sigmas", + "type": "SIGMAS", + "links": [ + 255 + ] + } + ], + "properties": { + "cnr_id": "comfy-core", + "ver": "0.5.1", + "Node name for S&R": "SplitSigmas", + "ue_properties": { + "widget_ue_connectable": {}, + "version": "7.5.2", + "input_ue_unconnectable": {} + } + }, + "widgets_values": [ + 2 + ] + }, + { + "id": 129, + "type": "SetNode", + "pos": [ + 662.1608990780405, + -1095.7303654898724 + ], + "size": [ + 210, + 61.01185634578792 + ], + "flags": { + "collapsed": true + }, + "order": 53, + "mode": 0, + "inputs": [ + { + "name": "SAMPLER", + "type": "SAMPLER", + "link": 253 + } + ], + "outputs": [ + { + "name": "*", + "type": "*", + "links": null + } + ], + "title": "Set_sampler", + "properties": { + "previousName": "sampler", + "ue_properties": { + "widget_ue_connectable": {}, + "version": "7.5.2", + "input_ue_unconnectable": {} + } + }, + "widgets_values": [ + "sampler" + ], + "color": "#614a4a", + "bgcolor": "#3b2c2c" + }, + { + "id": 130, + "type": "SetNode", + "pos": [ + 977.1417459346173, + -982.3415261171592 + ], + "size": [ + 210, + 60 + ], + "flags": { + "collapsed": true + }, + "order": 95, + "mode": 0, + "inputs": [ + { + "name": "SIGMAS", + "type": "SIGMAS", + "link": 254 + } + ], + "outputs": [ + { + "name": "*", + "type": "*", + "links": null + } + ], + "title": "Set_sigmas_high", + "properties": { + "previousName": "sigmas_high", + "ue_properties": { + "widget_ue_connectable": {}, + "version": "7.5.2", + "input_ue_unconnectable": {} + } + }, + "widgets_values": [ + "sigmas_high" + ], + "color": "#485248", + "bgcolor": "#272e27" + }, + { + "id": 131, + "type": "SetNode", + "pos": [ + 976.7971196007186, + -921.6081690337912 + ], + "size": [ + 210, + 60 + ], + "flags": { + "collapsed": true + }, + "order": 96, + "mode": 0, + "inputs": [ + { + "name": "SIGMAS", + "type": "SIGMAS", + "link": 255 + } + ], + "outputs": [ + { + "name": "*", + "type": "*", + "links": null + } + ], + "title": "Set_sigmas_low", + "properties": { + "previousName": "sigmas_low", + "ue_properties": { + "widget_ue_connectable": {}, + "version": "7.5.2", + "input_ue_unconnectable": {} + } + }, + "widgets_values": [ + "sigmas_low" + ], + "color": "#485248", + "bgcolor": "#272e27" + }, + { + "id": 143, + "type": "SetNode", + "pos": [ + 741.7428782352613, + -416.88225262179515 + ], + "size": [ + 210, + 60 + ], + "flags": { + "collapsed": true + }, + "order": 62, + "mode": 0, + "inputs": [ + { + "name": "CLIP", + "type": "CLIP", + "link": 283 + } + ], + "outputs": [ + { + "name": "*", + "type": "*", + "links": null + } + ], + "title": "Set_clip_model", + "properties": { + "previousName": "clip_model", + "ue_properties": { + "widget_ue_connectable": {}, + "version": "7.5.2", + "input_ue_unconnectable": {} + } + }, + "widgets_values": [ + "clip_model" + ], + "color": "#432", + "bgcolor": "#653" + }, + { + "id": 144, + "type": "SetNode", + "pos": [ + 748.4211301174612, + -318.9330297956516 + ], + "size": [ + 210, + 60 + ], + "flags": { + "collapsed": true + }, + "order": 52, + "mode": 0, + "inputs": [ + { + "name": "VAE", + "type": "VAE", + "link": 284 + } + ], + "outputs": [ + { + "name": "*", + "type": "*", + "links": [] + } + ], + "title": "Set_vae", + "properties": { + "previousName": "vae", + "ue_properties": { + "widget_ue_connectable": {}, + "version": "7.5.2", + "input_ue_unconnectable": {} + } + }, + "widgets_values": [ + "vae" + ], + "color": "#322", + "bgcolor": "#533" + }, + { + "id": 301, + "type": "GetNode", + "pos": [ + 3087.7162093215893, + -1427.513264257069 + ], + "size": [ + 210, + 34 + ], + "flags": { + "collapsed": true + }, + "order": 2, + "mode": 0, + "inputs": [], + "outputs": [ + { + "name": "CLIP", + "type": "CLIP", + "links": [ + 510 + ] + } + ], + "title": "Get_clip_model", + "properties": { + "ue_properties": { + "widget_ue_connectable": {}, + "version": "7.5.2", + "input_ue_unconnectable": {} + } + }, + "widgets_values": [ + "clip_model" + ], + "color": "#233", + "bgcolor": "#355" + }, + { + "id": 302, + "type": "GetNode", + "pos": [ + 3087.7162093215893, + -1332.115190772293 + ], + "size": [ + 210, + 34 + ], + "flags": { + "collapsed": true + }, + "order": 3, + "mode": 0, + "inputs": [], + "outputs": [ + { + "name": "MODEL", + "type": "MODEL", + "links": [ + 512 + ] + } + ], + "title": "Get_model_low", + "properties": { + "ue_properties": { + "widget_ue_connectable": {}, + "version": "7.5.2", + "input_ue_unconnectable": {} + } + }, + "widgets_values": [ + "model_low" + ], + "color": "#223", + "bgcolor": "#335" + }, + { + "id": 303, + "type": "GetNode", + "pos": [ + 3087.7162093215893, + -1379.814227514681 + ], + "size": [ + 210, + 34 + ], + "flags": { + "collapsed": true + }, + "order": 4, + "mode": 0, + "inputs": [], + "outputs": [ + { + "name": "MODEL", + "type": "MODEL", + "links": [ + 511 + ] + } + ], + "title": "Get_model_high", + "properties": { + "ue_properties": { + "widget_ue_connectable": {}, + "version": "7.5.2", + "input_ue_unconnectable": {} + } + }, + "widgets_values": [ + "model_high" + ], + "color": "#223", + "bgcolor": "#335" + }, + { + "id": 304, + "type": "GetNode", + "pos": [ + 3087.7162093215893, + -1284.4161540299058 + ], + "size": [ + 210, + 34 + ], + "flags": { + "collapsed": true + }, + "order": 5, + "mode": 0, + "inputs": [], + "outputs": [ + { + "name": "VAE", + "type": "VAE", + "links": [ + 513 + ] + } + ], + "title": "Get_vae", + "properties": { + "ue_properties": { + "widget_ue_connectable": {}, + "version": "7.5.2", + "input_ue_unconnectable": {} + } + }, + "widgets_values": [ + "vae" + ], + "color": "#233", + "bgcolor": "#355" + }, + { + "id": 307, + "type": "GetNode", + "pos": [ + 3087.7162093215893, + -1474.0112283999702 + ], + "size": [ + 210, + 34 + ], + "flags": { + "collapsed": true + }, + "order": 6, + "mode": 0, + "inputs": [], + "outputs": [ + { + "name": "SIGMAS", + "type": "SIGMAS", + "links": [ + 509 + ] + } + ], + "title": "Get_sigmas_low", + "properties": { + "ue_properties": { + "widget_ue_connectable": {}, + "version": "7.5.2", + "input_ue_unconnectable": {} + } + }, + "widgets_values": [ + "sigmas_low" + ], + "color": "#233", + "bgcolor": "#355" + }, + { + "id": 308, + "type": "GetNode", + "pos": [ + 3087.7162093215893, + -1523.2692797219427 + ], + "size": [ + 210, + 34 + ], + "flags": { + "collapsed": true + }, + "order": 7, + "mode": 0, + "inputs": [], + "outputs": [ + { + "name": "SIGMAS", + "type": "SIGMAS", + "links": [ + 508 + ] + } + ], + "title": "Get_sigmas_high", + "properties": { + "ue_properties": { + "widget_ue_connectable": {}, + "version": "7.5.2", + "input_ue_unconnectable": {} + } + }, + "widgets_values": [ + "sigmas_high" + ], + "color": "#233", + "bgcolor": "#355" + }, + { + "id": 317, + "type": "SetNode", + "pos": [ + 681.7975573430999, + -658.9482118523099 + ], + "size": [ + 210, + 60 + ], + "flags": { + "collapsed": true + }, + "order": 54, + "mode": 0, + "inputs": [ + { + "name": "INT", + "type": "INT", + "link": 521 + } + ], + "outputs": [ + { + "name": "*", + "type": "*", + "links": null + } + ], + "title": "Set_total_seconds", + "properties": { + "previousName": "total_seconds", + "ue_properties": { + "widget_ue_connectable": {}, + "version": "7.5.2", + "input_ue_unconnectable": {} + } + }, + "widgets_values": [ + "total_seconds" + ], + "color": "#1b4669", + "bgcolor": "#29699c" + }, + { + "id": 320, + "type": "GetNode", + "pos": [ + 1675.0856338721744, + -1560.5379786555038 + ], + "size": [ + 210, + 34 + ], + "flags": { + "collapsed": true + }, + "order": 8, + "mode": 0, + "inputs": [], + "outputs": [ + { + "name": "SAMPLER", + "type": "SAMPLER", + "links": [ + 528 + ] + } + ], + "title": "Get_sampler", + "properties": { + "ue_properties": { + "widget_ue_connectable": {}, + "version": "7.5.2", + "input_ue_unconnectable": {} + } + }, + "widgets_values": [ + "sampler" + ], + "color": "#614a4a", + "bgcolor": "#3b2c2c" + }, + { + "id": 322, + "type": "GetNode", + "pos": [ + 1675.0856338721744, + -1369.7418316859557 + ], + "size": [ + 210, + 34 + ], + "flags": { + "collapsed": true + }, + "order": 9, + "mode": 0, + "inputs": [], + "outputs": [ + { + "name": "MODEL", + "type": "MODEL", + "links": [ + 532 + ] + } + ], + "title": "Get_model_high", + "properties": { + "ue_properties": { + "widget_ue_connectable": {}, + "version": "7.5.2", + "input_ue_unconnectable": {} + } + }, + "widgets_values": [ + "model_high" + ], + "color": "#223", + "bgcolor": "#335" + }, + { + "id": 323, + "type": "RandomNoise", + "pos": [ + 1894.7623606534441, + -1599.584724707526 + ], + "size": [ + 270, + 82 + ], + "flags": {}, + "order": 10, + "mode": 0, + "inputs": [], + "outputs": [ + { + "name": "NOISE", + "type": "NOISE", + "links": [ + 527 + ] + } + ], + "properties": { + "cnr_id": "comfy-core", + "ver": "0.5.1", + "Node name for S&R": "RandomNoise", + "ue_properties": { + "widget_ue_connectable": {}, + "version": "7.5.2", + "input_ue_unconnectable": {} + } + }, + "widgets_values": [ + 88888, + "fixed" + ] + }, + { + "id": 324, + "type": "GetNode", + "pos": [ + 1675.0856338721744, + -1463.9388325712432 + ], + "size": [ + 210, + 34 + ], + "flags": { + "collapsed": true + }, + "order": 11, + "mode": 0, + "inputs": [], + "outputs": [ + { + "name": "SIGMAS", + "type": "SIGMAS", + "links": [ + 530 + ] + } + ], + "title": "Get_sigmas_low", + "properties": { + "ue_properties": { + "widget_ue_connectable": {}, + "version": "7.5.2", + "input_ue_unconnectable": {} + } + }, + "widgets_values": [ + "sigmas_low" + ], + "color": "#485248", + "bgcolor": "#272e27" + }, + { + "id": 325, + "type": "GetNode", + "pos": [ + 1675.0856338721744, + -1513.1968838932153 + ], + "size": [ + 210, + 34 + ], + "flags": { + "collapsed": true + }, + "order": 12, + "mode": 0, + "inputs": [], + "outputs": [ + { + "name": "SIGMAS", + "type": "SIGMAS", + "links": [ + 529 + ] + } + ], + "title": "Get_sigmas_high", + "properties": { + "ue_properties": { + "widget_ue_connectable": {}, + "version": "7.5.2", + "input_ue_unconnectable": {} + } + }, + "widgets_values": [ + "sigmas_high" + ], + "color": "#485248", + "bgcolor": "#272e27" + }, + { + "id": 326, + "type": "GetNode", + "pos": [ + 1675.0856338721744, + -1322.0427949435682 + ], + "size": [ + 210, + 34 + ], + "flags": { + "collapsed": true + }, + "order": 13, + "mode": 0, + "inputs": [], + "outputs": [ + { + "name": "MODEL", + "type": "MODEL", + "links": [ + 533 + ] + } + ], + "title": "Get_model_low", + "properties": { + "ue_properties": { + "widget_ue_connectable": {}, + "version": "7.5.2", + "input_ue_unconnectable": {} + } + }, + "widgets_values": [ + "model_low" + ], + "color": "#223", + "bgcolor": "#335" + }, + { + "id": 300, + "type": "GetNode", + "pos": [ + 3087.7162093215893, + -1570.6103744842308 + ], + "size": [ + 210, + 34 + ], + "flags": { + "collapsed": true + }, + "order": 14, + "mode": 0, + "inputs": [], + "outputs": [ + { + "name": "SAMPLER", + "type": "SAMPLER", + "links": [ + 507 + ] + } + ], + "title": "Get_sampler", + "properties": { + "ue_properties": { + "widget_ue_connectable": {}, + "version": "7.5.2", + "input_ue_unconnectable": {} + } + }, + "widgets_values": [ + "sampler" + ], + "color": "#233", + "bgcolor": "#355" + }, + { + "id": 122, + "type": "BasicScheduler", + "pos": [ + 351.695490116646, + -1011.961093236818 + ], + "size": [ + 270, + 106 + ], + "flags": {}, + "order": 74, + "mode": 0, + "inputs": [ + { + "name": "model", + "type": "MODEL", + "link": 227 + } + ], + "outputs": [ + { + "name": "SIGMAS", + "type": "SIGMAS", + "links": [ + 250 + ] + } + ], + "properties": { + "cnr_id": "comfy-core", + "ver": "0.5.1", + "Node name for S&R": "BasicScheduler", + "ue_properties": { + "widget_ue_connectable": {}, + "version": "7.5.2", + "input_ue_unconnectable": {} + } + }, + "widgets_values": [ + "simple", + 4, + 1 + ] + }, + { + "id": 316, + "type": "PrimitiveInt", + "pos": [ + 352.3842164378712, + -714.101590154819 + ], + "size": [ + 270, + 82 + ], + "flags": {}, + "order": 15, + "mode": 0, + "inputs": [], + "outputs": [ + { + "name": "INT", + "type": "INT", + "links": [ + 521 + ] + } + ], + "title": "TIME (sec)", + "properties": { + "cnr_id": "comfy-core", + "ver": "0.6.0", + "Node name for S&R": "PrimitiveInt", + "ue_properties": { + "widget_ue_connectable": {}, + "version": "7.5.2", + "input_ue_unconnectable": {} + } + }, + "widgets_values": [ + 15, + "fixed" + ] + }, + { + "id": 135, + "type": "VAEEncode", + "pos": [ + 1202.9242449349158, + -1809.7829258316697 + ], + "size": [ + 140, + 46 + ], + "flags": {}, + "order": 77, + "mode": 0, + "inputs": [ + { + "name": "pixels", + "type": "IMAGE", + "link": 269 + }, + { + "name": "vae", + "type": "VAE", + "link": 402 + } + ], + "outputs": [ + { + "name": "LATENT", + "type": "LATENT", + "links": [ + 323 + ] + } + ], + "properties": { + "cnr_id": "comfy-core", + "ver": "0.5.1", + "Node name for S&R": "VAEEncode", + "ue_properties": { + "widget_ue_connectable": {}, + "version": "7.5.2", + "input_ue_unconnectable": {} + } + }, + "widgets_values": [], + "color": "#322", + "bgcolor": "#533" + }, + { + "id": 188, + "type": "GetNode", + "pos": [ + 1039.5145597877395, + -1731.9407714607933 + ], + "size": [ + 210, + 34 + ], + "flags": { + "collapsed": true + }, + "order": 16, + "mode": 0, + "inputs": [], + "outputs": [ + { + "name": "VAE", + "type": "VAE", + "links": [ + 402 + ] + } + ], + "title": "Get_vae", + "properties": { + "ue_properties": { + "widget_ue_connectable": {}, + "version": "7.5.2", + "input_ue_unconnectable": {} + } + }, + "widgets_values": [ + "vae" + ], + "color": "#2a363b", + "bgcolor": "#3f5159" + }, + { + "id": 169, + "type": "SetNode", + "pos": [ + 1218.3615041051412, + -1704.8600039082394 + ], + "size": [ + 210, + 60 + ], + "flags": { + "collapsed": true + }, + "order": 91, + "mode": 0, + "inputs": [ + { + "name": "LATENT", + "type": "LATENT", + "link": 323 + } + ], + "outputs": [ + { + "name": "*", + "type": "*", + "links": [] + } + ], + "title": "Set_anchor_samples", + "properties": { + "previousName": "anchor_samples", + "ue_properties": { + "widget_ue_connectable": {}, + "version": "7.5.2", + "input_ue_unconnectable": {} + } + }, + "widgets_values": [ + "anchor_samples" + ], + "color": "#323", + "bgcolor": "#535" + }, + { + "id": 343, + "type": "PreviewImage", + "pos": [ + 1060.9773166853965, + -1627.2267382312257 + ], + "size": [ + 317.8207483037961, + 278.42035414580585 + ], + "flags": {}, + "order": 78, + "mode": 0, + "inputs": [ + { + "name": "images", + "type": "IMAGE", + "link": 558 + } + ], + "outputs": [], + "properties": { + "cnr_id": "comfy-core", + "ver": "0.6.0", + "Node name for S&R": "PreviewImage", + "ue_properties": { + "widget_ue_connectable": {}, + "version": "7.5.2", + "input_ue_unconnectable": {} + } + }, + "widgets_values": [] + }, + { + "id": 136, + "type": "ImageResizeKJv2", + "pos": [ + 745.003267826397, + -1719.2812777506963 + ], + "size": [ + 270, + 336 + ], + "flags": {}, + "order": 63, + "mode": 0, + "inputs": [ + { + "name": "image", + "type": "IMAGE", + "link": 581 + }, + { + "name": "mask", + "shape": 7, + "type": "MASK", + "link": null + } + ], + "outputs": [ + { + "name": "IMAGE", + "type": "IMAGE", + "links": [ + 269, + 558 + ] + }, + { + "name": "width", + "type": "INT", + "links": null + }, + { + "name": "height", + "type": "INT", + "links": null + }, + { + "name": "mask", + "type": "MASK", + "links": null + } + ], + "properties": { + "cnr_id": "comfyui-kjnodes", + "ver": "79f529a84a8c20fe5dcdfa984c6be7a94102c014", + "Node name for S&R": "ImageResizeKJv2", + "aux_id": "kijai/ComfyUI-KJNodes", + "ue_properties": { + "widget_ue_connectable": {}, + "version": "7.5.2", + "input_ue_unconnectable": {} + } + }, + "widgets_values": [ + 832, + 480, + "lanczos", + "crop", + "0, 0, 0", + "center", + 2, + "cpu" + ], + "color": "#2a363b", + "bgcolor": "#3f5159" + }, + { + "id": 328, + "type": "GetNode", + "pos": [ + 1708.642311291139, + -1273.1434062573542 + ], + "size": [ + 210, + 34 + ], + "flags": { + "collapsed": true + }, + "order": 17, + "mode": 0, + "inputs": [], + "outputs": [ + { + "name": "VAE", + "type": "VAE", + "links": [ + 534 + ] + } + ], + "title": "Get_vae", + "properties": { + "ue_properties": { + "widget_ue_connectable": {}, + "version": "7.5.2", + "input_ue_unconnectable": {} + } + }, + "widgets_values": [ + "vae" + ], + "color": "#332922", + "bgcolor": "#593930" + }, + { + "id": 321, + "type": "GetNode", + "pos": [ + 1675.0856338721744, + -1417.4408684283426 + ], + "size": [ + 210, + 34 + ], + "flags": { + "collapsed": true + }, + "order": 18, + "mode": 0, + "inputs": [], + "outputs": [ + { + "name": "CLIP", + "type": "CLIP", + "links": [ + 531 + ] + } + ], + "title": "Get_clip_model", + "properties": { + "ue_properties": { + "widget_ue_connectable": {}, + "version": "7.5.2", + "input_ue_unconnectable": {} + } + }, + "widgets_values": [ + "clip_model" + ], + "color": "#322", + "bgcolor": "#533" + }, + { + "id": 329, + "type": "GetNode", + "pos": [ + 1644.9799352504144, + -1220.4091252064923 + ], + "size": [ + 210, + 50 + ], + "flags": { + "collapsed": true + }, + "order": 19, + "mode": 0, + "inputs": [], + "outputs": [ + { + "name": "LATENT", + "type": "LATENT", + "links": [ + 535 + ] + } + ], + "title": "Get_anchor_samples", + "properties": { + "ue_properties": { + "widget_ue_connectable": {}, + "version": "7.5.2", + "input_ue_unconnectable": {} + } + }, + "widgets_values": [ + "anchor_samples" + ], + "color": "#233", + "bgcolor": "#355" + }, + { + "id": 344, + "type": "easy showAnything", + "pos": [ + 3754.2235861310023, + -186.94428116436305 + ], + "size": [ + 210, + 88 + ], + "flags": {}, + "order": 93, + "mode": 0, + "inputs": [ + { + "name": "anything", + "shape": 7, + "type": "*", + "link": 559 + } + ], + "outputs": [ + { + "name": "output", + "type": "*", + "links": null + } + ], + "properties": { + "cnr_id": "comfyui-easy-use", + "ver": "1.3.4", + "Node name for S&R": "easy showAnything", + "ue_properties": { + "widget_ue_connectable": {}, + "version": "7.5.2", + "input_ue_unconnectable": {} + } + }, + "widgets_values": [ + "2" + ], + "color": "#222", + "bgcolor": "#000" + }, + { + "id": 354, + "type": "SetNode", + "pos": [ + 3807.3395545707976, + -282.2032626406764 + ], + "size": [ + 210, + 60 + ], + "flags": { + "collapsed": true + }, + "order": 94, + "mode": 0, + "inputs": [ + { + "name": "INT", + "type": "INT", + "link": 584 + } + ], + "outputs": [ + { + "name": "*", + "type": "*", + "links": null + } + ], + "title": "Set_INDEX", + "properties": { + "previousName": "INDEX", + "ue_properties": { + "widget_ue_connectable": {}, + "version": "7.5.2", + "input_ue_unconnectable": {} + } + }, + "widgets_values": [ + "INDEX" + ], + "color": "#1b4669", + "bgcolor": "#29699c" + }, + { + "id": 360, + "type": "SetNode", + "pos": [ + 2573.9742090241366, + -1589.25148277492 + ], + "size": [ + 210, + 60 + ], + "flags": { + "collapsed": true + }, + "order": 80, + "mode": 0, + "inputs": [ + { + "name": "LATENT", + "type": "LATENT", + "link": 587 + } + ], + "outputs": [ + { + "name": "*", + "type": "*", + "links": null + } + ], + "title": "Set_SAMPLES", + "properties": { + "previousName": "SAMPLES", + "ue_properties": { + "widget_ue_connectable": {}, + "version": "7.5.2", + "input_ue_unconnectable": {} + } + }, + "widgets_values": [ + "SAMPLES" + ], + "color": "#323", + "bgcolor": "#535" + }, + { + "id": 327, + "type": "08be7d9e-8766-4c78-98c9-50fc97389ca0", + "pos": [ + 1897.741881805851, + -1451.5549362688987 + ], + "size": [ + 276.7490534966155, + 288 + ], + "flags": { + "collapsed": false + }, + "order": 69, + "mode": 0, + "inputs": [ + { + "name": "noise", + "type": "NOISE", + "link": 527 + }, + { + "name": "sampler", + "type": "SAMPLER", + "link": 528 + }, + { + "label": "sigmas_high", + "name": "sigmas", + "type": "SIGMAS", + "link": 529 + }, + { + "label": "sigmas_low", + "name": "sigmas_1", + "type": "SIGMAS", + "link": 530 + }, + { + "name": "clip", + "type": "CLIP", + "link": 531 + }, + { + "label": "model_high", + "name": "model", + "type": "MODEL", + "link": 532 + }, + { + "label": "model_low", + "name": "model_1", + "type": "MODEL", + "link": 533 + }, + { + "name": "vae", + "type": "VAE", + "link": 534 + }, + { + "name": "anchor_samples", + "type": "LATENT", + "link": 535 + }, + { + "name": "prev_samples", + "type": "LATENT", + "link": null + }, + { + "label": "prev_images", + "name": "source_images", + "type": "IMAGE", + "link": null + }, + { + "label": "text_prompt", + "name": "text", + "type": "STRING", + "widget": { + "name": "text" + }, + "link": 542 + } + ], + "outputs": [ + { + "label": "samples", + "name": "output", + "type": "LATENT", + "links": [ + 587 + ] + }, + { + "label": "decoded_images", + "name": "IMAGE", + "type": "IMAGE", + "links": [ + 536, + 588 + ] + } + ], + "properties": { + "proxyWidgets": [ + [ + "-1", + "text" + ] + ], + "cnr_id": "comfy-core", + "ver": "0.5.1", + "ue_properties": { + "widget_ue_connectable": { + "text": true + }, + "version": "7.5.2", + "input_ue_unconnectable": {} + } + }, + "widgets_values": [ + "" + ] + }, + { + "id": 361, + "type": "SetNode", + "pos": [ + 2553.6333766071825, + -1410.5432995019082 + ], + "size": [ + 210, + 60 + ], + "flags": { + "collapsed": true + }, + "order": 82, + "mode": 0, + "inputs": [ + { + "name": "IMAGE", + "type": "IMAGE", + "link": 588 + } + ], + "outputs": [ + { + "name": "*", + "type": "*", + "links": null + } + ], + "title": "Set_DECODED_IMG", + "properties": { + "previousName": "DECODED_IMG", + "ue_properties": { + "widget_ue_connectable": {}, + "version": "7.5.2", + "input_ue_unconnectable": {} + } + }, + "widgets_values": [ + "DECODED_IMG" + ], + "color": "#2a363b", + "bgcolor": "#3f5159" + }, + { + "id": 362, + "type": "GetNode", + "pos": [ + 2710.82910510753, + -439.5695377237848 + ], + "size": [ + 210, + 60 + ], + "flags": { + "collapsed": true + }, + "order": 20, + "mode": 0, + "inputs": [], + "outputs": [ + { + "name": "LATENT", + "type": "LATENT", + "links": [ + 589 + ] + } + ], + "title": "Get_SAMPLES", + "properties": { + "ue_properties": { + "widget_ue_connectable": {}, + "version": "7.5.2", + "input_ue_unconnectable": {} + } + }, + "widgets_values": [ + "SAMPLES" + ], + "color": "#323", + "bgcolor": "#535" + }, + { + "id": 364, + "type": "RandomNoise //Inspire", + "pos": [ + 3356.8486032823566, + -1841.294453367152 + ], + "size": [ + 270, + 226 + ], + "flags": {}, + "order": 21, + "mode": 0, + "inputs": [], + "outputs": [ + { + "name": "NOISE", + "type": "NOISE", + "links": [ + 591 + ] + } + ], + "properties": { + "cnr_id": "comfyui-inspire-pack", + "ver": "1.23.0", + "Node name for S&R": "RandomNoise //Inspire", + "ue_properties": { + "widget_ue_connectable": {}, + "input_ue_unconnectable": {}, + "version": "7.5.2" + } + }, + "widgets_values": [ + 605056063716529, + "randomize", + "GPU(=A1111)", + "incremental", + 0, + 0, + "linear", + 0 + ], + "color": "#233", + "bgcolor": "#355" + }, + { + "id": 370, + "type": "SetNode", + "pos": [ + 1890.1400062875023, + -414.548054104644 + ], + "size": [ + 210, + 60 + ], + "flags": { + "collapsed": true + }, + "order": 79, + "mode": 0, + "inputs": [ + { + "name": "*", + "type": "*", + "link": 596 + } + ], + "outputs": [ + { + "name": "*", + "type": "*", + "links": null + } + ], + "title": "Set_1", + "properties": { + "previousName": "1", + "ue_properties": { + "widget_ue_connectable": {}, + "version": "7.5.2", + "input_ue_unconnectable": {} + } + }, + "widgets_values": [ + "1" + ] + }, + { + "id": 373, + "type": "SetNode", + "pos": [ + 1812.2931506091395, + 353.60665480222355 + ], + "size": [ + 210, + 60 + ], + "flags": { + "collapsed": true + }, + "order": 67, + "mode": 0, + "inputs": [ + { + "name": "STRING", + "type": "STRING", + "link": 599 + } + ], + "outputs": [ + { + "name": "*", + "type": "*", + "links": null + } + ], + "title": "Set_4", + "properties": { + "previousName": "4", + "ue_properties": { + "widget_ue_connectable": {}, + "version": "7.5.2", + "input_ue_unconnectable": {} + } + }, + "widgets_values": [ + "4" + ] + }, + { + "id": 375, + "type": "SetNode", + "pos": [ + 1825.1500409212122, + 611.5563821128518 + ], + "size": [ + 210, + 60 + ], + "flags": { + "collapsed": true + }, + "order": 68, + "mode": 0, + "inputs": [ + { + "name": "STRING", + "type": "STRING", + "link": 600 + } + ], + "outputs": [ + { + "name": "*", + "type": "*", + "links": null + } + ], + "title": "Set_5", + "properties": { + "previousName": "5", + "ue_properties": { + "widget_ue_connectable": {}, + "version": "7.5.2", + "input_ue_unconnectable": {} + } + }, + "widgets_values": [ + "5" + ] + }, + { + "id": 371, + "type": "SetNode", + "pos": [ + 1813.8187046602716, + -156.75784369809392 + ], + "size": [ + 210, + 60 + ], + "flags": { + "collapsed": true + }, + "order": 65, + "mode": 0, + "inputs": [ + { + "name": "STRING", + "type": "STRING", + "link": 597 + } + ], + "outputs": [ + { + "name": "*", + "type": "*", + "links": null + } + ], + "title": "Set_2", + "properties": { + "previousName": "2", + "ue_properties": { + "widget_ue_connectable": {}, + "version": "7.5.2", + "input_ue_unconnectable": {} + } + }, + "widgets_values": [ + "2" + ] + }, + { + "id": 372, + "type": "SetNode", + "pos": [ + 1812.0605307161154, + 149.6614023477076 + ], + "size": [ + 210, + 60 + ], + "flags": { + "collapsed": true + }, + "order": 66, + "mode": 0, + "inputs": [ + { + "name": "STRING", + "type": "STRING", + "link": 598 + } + ], + "outputs": [ + { + "name": "*", + "type": "*", + "links": null + } + ], + "title": "Set_3", + "properties": { + "previousName": "3", + "ue_properties": { + "widget_ue_connectable": {}, + "version": "7.5.2", + "input_ue_unconnectable": {} + } + }, + "widgets_values": [ + "3" + ] + }, + { + "id": 319, + "type": "SetNode", + "pos": [ + 1832.833492770389, + -354.07545083810413 + ], + "size": [ + 285.9129641428478, + 71.01161137141946 + ], + "flags": { + "collapsed": true + }, + "order": 64, + "mode": 0, + "inputs": [ + { + "name": "STRING", + "type": "STRING", + "link": 525 + } + ], + "outputs": [ + { + "name": "STRING", + "type": "STRING", + "links": [ + 596 + ] + } + ], + "title": "Set_PRO_SCE", + "properties": { + "previousName": "PRO_SCE", + "ue_properties": { + "widget_ue_connectable": {}, + "version": "7.5.2", + "input_ue_unconnectable": {} + } + }, + "widgets_values": [ + "PRO_SCE" + ] + }, + { + "id": 376, + "type": "GetNode", + "pos": [ + 2141.9401855391334, + -447.938295263549 + ], + "size": [ + 210, + 60 + ], + "flags": { + "collapsed": true + }, + "order": 22, + "mode": 0, + "inputs": [], + "outputs": [ + { + "name": "STRING", + "type": "STRING", + "links": [ + 601 + ] + } + ], + "title": "Get_1", + "properties": { + "ue_properties": { + "widget_ue_connectable": {}, + "version": "7.5.2", + "input_ue_unconnectable": {} + } + }, + "widgets_values": [ + "1" + ], + "color": "#323", + "bgcolor": "#535" + }, + { + "id": 381, + "type": "GetNode", + "pos": [ + 2137.9775768846725, + -385.8593127819167 + ], + "size": [ + 210, + 60 + ], + "flags": { + "collapsed": true + }, + "order": 23, + "mode": 0, + "inputs": [], + "outputs": [ + { + "name": "STRING", + "type": "STRING", + "links": [ + 602 + ] + } + ], + "title": "Get_2", + "properties": { + "ue_properties": { + "widget_ue_connectable": {}, + "version": "7.5.2", + "input_ue_unconnectable": {} + } + }, + "widgets_values": [ + "2" + ], + "color": "#323", + "bgcolor": "#535" + }, + { + "id": 378, + "type": "GetNode", + "pos": [ + 2140.619315987647, + -317.1762002088025 + ], + "size": [ + 210, + 60 + ], + "flags": { + "collapsed": true + }, + "order": 24, + "mode": 0, + "inputs": [], + "outputs": [ + { + "name": "STRING", + "type": "STRING", + "links": [ + 603 + ] + } + ], + "title": "Get_3", + "properties": { + "ue_properties": { + "widget_ue_connectable": {}, + "version": "7.5.2", + "input_ue_unconnectable": {} + } + }, + "widgets_values": [ + "3" + ], + "color": "#323", + "bgcolor": "#535" + }, + { + "id": 379, + "type": "GetNode", + "pos": [ + 2139.2985189914766, + -244.53055153654546 + ], + "size": [ + 210, + 60 + ], + "flags": { + "collapsed": true + }, + "order": 25, + "mode": 0, + "inputs": [], + "outputs": [ + { + "name": "STRING", + "type": "STRING", + "links": [ + 604 + ] + } + ], + "title": "Get_4", + "properties": { + "ue_properties": { + "widget_ue_connectable": {}, + "version": "7.5.2", + "input_ue_unconnectable": {} + } + }, + "widgets_values": [ + "4" + ], + "color": "#323", + "bgcolor": "#535" + }, + { + "id": 380, + "type": "GetNode", + "pos": [ + 2143.260982535303, + -169.24330887194787 + ], + "size": [ + 210, + 60 + ], + "flags": { + "collapsed": true + }, + "order": 26, + "mode": 0, + "inputs": [], + "outputs": [ + { + "name": "STRING", + "type": "STRING", + "links": [ + 605 + ] + } + ], + "title": "Get_5", + "properties": { + "ue_properties": { + "widget_ue_connectable": {}, + "version": "7.5.2", + "input_ue_unconnectable": {} + } + }, + "widgets_values": [ + "5" + ], + "color": "#323", + "bgcolor": "#535" + }, + { + "id": 355, + "type": "GetNode", + "pos": [ + 2133.335438565198, + -90.52530295530687 + ], + "size": [ + 210, + 60 + ], + "flags": { + "collapsed": true + }, + "order": 27, + "mode": 0, + "inputs": [], + "outputs": [ + { + "name": "INT", + "type": "INT", + "links": [ + 606 + ] + } + ], + "title": "Get_INDEX", + "properties": { + "ue_properties": { + "widget_ue_connectable": {}, + "version": "7.5.2", + "input_ue_unconnectable": {} + } + }, + "widgets_values": [ + "INDEX" + ], + "color": "#1b4669", + "bgcolor": "#29699c" + }, + { + "id": 356, + "type": "easy anythingIndexSwitch", + "pos": [ + 2299.2861542452824, + -408.0690692741314 + ], + "size": [ + 270, + 158 + ], + "flags": {}, + "order": 55, + "mode": 0, + "inputs": [ + { + "name": "value0", + "shape": 7, + "type": "*", + "link": 601 + }, + { + "name": "value1", + "shape": 7, + "type": "*", + "link": 602 + }, + { + "name": "index", + "type": "INT", + "widget": { + "name": "index" + }, + "link": 606 + }, + { + "name": "value2", + "type": "*", + "link": 603 + }, + { + "name": "value3", + "type": "*", + "link": 604 + }, + { + "name": "value4", + "type": "*", + "link": 605 + }, + { + "name": "value5", + "type": "*", + "link": null + } + ], + "outputs": [ + { + "name": "value", + "type": "*", + "links": [ + 610 + ] + } + ], + "properties": { + "cnr_id": "comfyui-easy-use", + "ver": "e94405e610bce3416b6c887d6b70601ab7a32dd7", + "Node name for S&R": "easy anythingIndexSwitch", + "ue_properties": { + "widget_ue_connectable": {}, + "input_ue_unconnectable": {}, + "version": "7.5.2" + } + }, + "widgets_values": [ + 0 + ], + "color": "#323", + "bgcolor": "#535" + }, + { + "id": 306, + "type": "GetNode", + "pos": [ + 3073.1870795871378, + -1230.1128420853995 + ], + "size": [ + 210, + 34 + ], + "flags": { + "collapsed": true + }, + "order": 28, + "mode": 0, + "inputs": [], + "outputs": [ + { + "name": "LATENT", + "type": "LATENT", + "links": [ + 514 + ] + } + ], + "title": "Get_anchor_samples", + "properties": { + "ue_properties": { + "widget_ue_connectable": {}, + "version": "7.5.2", + "input_ue_unconnectable": {} + } + }, + "widgets_values": [ + "anchor_samples" + ], + "color": "#233", + "bgcolor": "#355" + }, + { + "id": 387, + "type": "GetNode", + "pos": [ + 3120.203717692286, + -1124.0959115059263 + ], + "size": [ + 210, + 60 + ], + "flags": { + "collapsed": true + }, + "order": 29, + "mode": 0, + "inputs": [], + "outputs": [ + { + "name": "*", + "type": "*", + "links": [ + 614 + ] + } + ], + "title": "GetNode", + "properties": { + "ue_properties": { + "widget_ue_connectable": {}, + "version": "7.5.2", + "input_ue_unconnectable": {} + } + }, + "widgets_values": [ + "VAL_2_OUT" + ], + "color": "#233", + "bgcolor": "#355" + }, + { + "id": 386, + "type": "GetNode", + "pos": [ + 3095.257824214445, + -1177.63464820337 + ], + "size": [ + 210, + 60 + ], + "flags": { + "collapsed": true + }, + "order": 30, + "mode": 0, + "inputs": [], + "outputs": [ + { + "name": "*", + "type": "*", + "links": [ + 612 + ] + } + ], + "title": "Get_VAL_1_OUT", + "properties": { + "ue_properties": { + "widget_ue_connectable": {}, + "version": "7.5.2", + "input_ue_unconnectable": {} + } + }, + "widgets_values": [ + "VAL_1_OUT" + ], + "color": "#233", + "bgcolor": "#355" + }, + { + "id": 383, + "type": "GetNode", + "pos": [ + 3093.6280275156714, + -1078.941688326561 + ], + "size": [ + 210, + 60 + ], + "flags": { + "collapsed": true + }, + "order": 31, + "mode": 0, + "inputs": [], + "outputs": [ + { + "name": "*", + "type": "*", + "links": [ + 609 + ] + } + ], + "title": "Get_FINAL_PROMPT", + "properties": { + "ue_properties": { + "widget_ue_connectable": {}, + "version": "7.5.2", + "input_ue_unconnectable": {} + } + }, + "widgets_values": [ + "FINAL_PROMPT" + ], + "color": "#233", + "bgcolor": "#355" + }, + { + "id": 390, + "type": "GetNode", + "pos": [ + 3410.258687770349, + -454.94745298440506 + ], + "size": [ + 210, + 60 + ], + "flags": { + "collapsed": true + }, + "order": 32, + "mode": 0, + "inputs": [], + "outputs": [ + { + "name": "LATENT", + "type": "LATENT", + "links": [ + 617 + ] + } + ], + "title": "Get_SAMPLES_EXT", + "properties": { + "ue_properties": { + "widget_ue_connectable": {}, + "version": "7.5.2", + "input_ue_unconnectable": {} + } + }, + "widgets_values": [ + "SAMPLES_EXT" + ], + "color": "#323", + "bgcolor": "#535" + }, + { + "id": 391, + "type": "GetNode", + "pos": [ + 3453.84614952902, + -358.52695049387427 + ], + "size": [ + 210, + 60 + ], + "flags": { + "collapsed": true + }, + "order": 33, + "mode": 0, + "inputs": [], + "outputs": [ + { + "name": "IMAGE", + "type": "IMAGE", + "links": [ + 618 + ] + } + ], + "title": "Get_IMG_EXT", + "properties": { + "ue_properties": { + "widget_ue_connectable": {}, + "version": "7.5.2", + "input_ue_unconnectable": {} + } + }, + "widgets_values": [ + "IMG_EXT" + ], + "color": "#2a363b", + "bgcolor": "#3f5159" + }, + { + "id": 333, + "type": "easy forLoopEnd", + "pos": [ + 3747.5955818749308, + -439.828366748678 + ], + "size": [ + 164.203125, + 86 + ], + "flags": {}, + "order": 84, + "mode": 0, + "inputs": [ + { + "name": "flow", + "shape": 5, + "type": "FLOW_CONTROL", + "link": 544 + }, + { + "name": "initial_value1", + "shape": 7, + "type": "*", + "link": 617 + }, + { + "name": "initial_value2", + "type": "*", + "link": 618 + }, + { + "name": "initial_value3", + "type": "*", + "link": null + } + ], + "outputs": [ + { + "name": "value1", + "type": "*", + "links": null + }, + { + "name": "value2", + "type": "*", + "links": [ + 547 + ] + }, + { + "name": "value3", + "type": "*", + "links": null + } + ], + "properties": { + "cnr_id": "comfyui-easy-use", + "ver": "1.3.4", + "Node name for S&R": "easy forLoopEnd", + "ue_properties": { + "widget_ue_connectable": {}, + "version": "7.5.2", + "input_ue_unconnectable": {} + } + }, + "widgets_values": [], + "color": "#1f1f48", + "bgcolor": "rgba(24,24,27,.9)" + }, + { + "id": 384, + "type": "SetNode", + "pos": [ + 3430.1483400212774, + -308.4147809874838 + ], + "size": [ + 210, + 60 + ], + "flags": { + "collapsed": true + }, + "order": 86, + "mode": 0, + "inputs": [ + { + "name": "*", + "type": "*", + "link": 611 + } + ], + "outputs": [ + { + "name": "*", + "type": "*", + "links": null + } + ], + "title": "Set_VAL_1_OUT", + "properties": { + "previousName": "VAL_1_OUT", + "ue_properties": { + "widget_ue_connectable": {}, + "version": "7.5.2", + "input_ue_unconnectable": {} + } + }, + "widgets_values": [ + "VAL_1_OUT" + ] + }, + { + "id": 294, + "type": "easy forLoopStart", + "pos": [ + 3041.4010551205247, + -453.7040243730285 + ], + "size": [ + 293.4524540345526, + 189.57603486092376 + ], + "flags": {}, + "order": 73, + "mode": 0, + "inputs": [ + { + "name": "initial_value1", + "shape": 7, + "type": "*", + "link": 589 + }, + { + "name": "total", + "type": "INT", + "widget": { + "name": "total" + }, + "link": 501 + }, + { + "name": "initial_value2", + "type": "*", + "link": 590 + }, + { + "name": "initial_value3", + "type": "*", + "link": null + } + ], + "outputs": [ + { + "name": "flow", + "shape": 5, + "type": "FLOW_CONTROL", + "links": [ + 544 + ] + }, + { + "name": "index", + "type": "INT", + "links": [ + 523 + ] + }, + { + "name": "value1", + "type": "*", + "links": [ + 611 + ] + }, + { + "name": "value2", + "type": "*", + "links": [ + 613 + ] + }, + { + "name": "value3", + "type": "*", + "links": null + } + ], + "properties": { + "cnr_id": "comfyui-easy-use", + "ver": "1.3.4", + "Node name for S&R": "easy forLoopStart", + "ue_properties": { + "widget_ue_connectable": { + "total": true + }, + "version": "7.5.2", + "input_ue_unconnectable": {} + } + }, + "widgets_values": [ + 1 + ], + "color": "#1f1f48", + "bgcolor": "rgba(24,24,27,.9)" + }, + { + "id": 363, + "type": "GetNode", + "pos": [ + 2692.2057223509055, + -392.9442465016886 + ], + "size": [ + 210, + 60 + ], + "flags": { + "collapsed": true + }, + "order": 34, + "mode": 0, + "inputs": [], + "outputs": [ + { + "name": "IMAGE", + "type": "IMAGE", + "links": [ + 590 + ] + } + ], + "title": "Get_DECODED_IMG", + "properties": { + "ue_properties": { + "widget_ue_connectable": {}, + "version": "7.5.2", + "input_ue_unconnectable": {} + } + }, + "widgets_values": [ + "DECODED_IMG" + ], + "color": "#2a363b", + "bgcolor": "#3f5159" + }, + { + "id": 296, + "type": "GetNode", + "pos": [ + 2790.2328263897875, + -128.01915919011503 + ], + "size": [ + 210, + 58 + ], + "flags": { + "collapsed": true + }, + "order": 35, + "mode": 0, + "inputs": [], + "outputs": [ + { + "name": "INT", + "type": "INT", + "links": [ + 502 + ] + } + ], + "title": "Get_total_seconds", + "properties": { + "ue_properties": { + "widget_ue_connectable": {}, + "version": "7.5.2", + "input_ue_unconnectable": {} + } + }, + "widgets_values": [ + "total_seconds" + ], + "color": "#1b4669", + "bgcolor": "#29699c" + }, + { + "id": 385, + "type": "SetNode", + "pos": [ + 3428.8275430251097, + -408.7978377159869 + ], + "size": [ + 210, + 60 + ], + "flags": { + "collapsed": true + }, + "order": 87, + "mode": 0, + "inputs": [ + { + "name": "*", + "type": "*", + "link": 613 + } + ], + "outputs": [ + { + "name": "*", + "type": "*", + "links": null + } + ], + "title": "Set_VAL_2_OUT", + "properties": { + "previousName": "VAL_2_OUT", + "ue_properties": { + "widget_ue_connectable": {}, + "version": "7.5.2", + "input_ue_unconnectable": {} + } + }, + "widgets_values": [ + "VAL_2_OUT" + ] + }, + { + "id": 318, + "type": "SimpleMath+", + "pos": [ + 3467.662012462077, + -237.02688120748343 + ], + "size": [ + 210, + 98 + ], + "flags": { + "collapsed": false + }, + "order": 85, + "mode": 0, + "inputs": [ + { + "name": "a", + "shape": 7, + "type": "INT,FLOAT", + "link": 523 + }, + { + "name": "b", + "shape": 7, + "type": "INT,FLOAT", + "link": null + }, + { + "name": "c", + "shape": 7, + "type": "*", + "link": null + } + ], + "outputs": [ + { + "name": "INT", + "type": "INT", + "links": [ + 559, + 584 + ] + }, + { + "name": "FLOAT", + "type": "FLOAT", + "links": null + } + ], + "properties": { + "cnr_id": "comfyui_essentials", + "ver": "9d9f4bedfc9f0321c19faf71855e228c93bd0dc9", + "Node name for S&R": "SimpleMath+", + "ue_properties": { + "widget_ue_connectable": {}, + "version": "7.5.2", + "input_ue_unconnectable": {} + } + }, + "widgets_values": [ + "a+1" + ], + "color": "#222", + "bgcolor": "#000" + }, + { + "id": 388, + "type": "SetNode", + "pos": [ + 3741.175351303154, + -1609.108207074329 + ], + "size": [ + 210, + 60 + ], + "flags": { + "collapsed": true + }, + "order": 71, + "mode": 0, + "inputs": [ + { + "name": "LATENT", + "type": "LATENT", + "link": 615 + } + ], + "outputs": [ + { + "name": "*", + "type": "*", + "links": null + } + ], + "title": "Set_SAMPLES_EXT", + "properties": { + "previousName": "SAMPLES_EXT", + "ue_properties": { + "widget_ue_connectable": {}, + "version": "7.5.2", + "input_ue_unconnectable": {} + } + }, + "widgets_values": [ + "SAMPLES_EXT" + ], + "color": "#323", + "bgcolor": "#535" + }, + { + "id": 389, + "type": "SetNode", + "pos": [ + 3746.75323260296, + -1564.63635194269 + ], + "size": [ + 210, + 60 + ], + "flags": { + "collapsed": true + }, + "order": 72, + "mode": 0, + "inputs": [ + { + "name": "IMAGE", + "type": "IMAGE", + "link": 616 + } + ], + "outputs": [ + { + "name": "*", + "type": "*", + "links": null + } + ], + "title": "Set_IMG_EXT", + "properties": { + "previousName": "IMG_EXT", + "ue_properties": { + "widget_ue_connectable": {}, + "version": "7.5.2", + "input_ue_unconnectable": {} + } + }, + "widgets_values": [ + "IMG_EXT" + ], + "color": "#2a363b", + "bgcolor": "#3f5159" + }, + { + "id": 392, + "type": "GetNode", + "pos": [ + 3764.1785236785868, + -1502.5088633661057 + ], + "size": [ + 210, + 60 + ], + "flags": { + "collapsed": true + }, + "order": 36, + "mode": 0, + "inputs": [], + "outputs": [ + { + "name": "IMAGE", + "type": "IMAGE", + "links": [ + 619 + ] + } + ], + "title": "Get_IMG_EXT", + "properties": { + "ue_properties": { + "widget_ue_connectable": {}, + "version": "7.5.2", + "input_ue_unconnectable": {} + } + }, + "widgets_values": [ + "IMG_EXT" + ], + "color": "#2a363b", + "bgcolor": "#3f5159" + }, + { + "id": 382, + "type": "SetNode", + "pos": [ + 2559.6084771059286, + 26.033590896645308 + ], + "size": [ + 210, + 60 + ], + "flags": { + "collapsed": true + }, + "order": 83, + "mode": 0, + "inputs": [ + { + "name": "*", + "type": "*", + "link": 608 + } + ], + "outputs": [ + { + "name": "*", + "type": "*", + "links": null + } + ], + "title": "Set_FINAL_PROMPT", + "properties": { + "previousName": "FINAL_PROMPT", + "ue_properties": { + "widget_ue_connectable": {}, + "version": "7.5.2", + "input_ue_unconnectable": {} + } + }, + "widgets_values": [ + "FINAL_PROMPT" + ] + }, + { + "id": 293, + "type": "easy showAnything", + "pos": [ + 2213.0617412416673, + 3.6766057328171695 + ], + "size": [ + 314.7015167467025, + 240.5347855297273 + ], + "flags": {}, + "order": 70, + "mode": 0, + "inputs": [ + { + "name": "anything", + "shape": 7, + "type": "*", + "link": 610 + } + ], + "outputs": [ + { + "name": "output", + "type": "*", + "links": [ + 608 + ] + } + ], + "properties": { + "cnr_id": "comfyui-easy-use", + "ver": "1.3.4", + "Node name for S&R": "easy showAnything", + "ue_properties": { + "widget_ue_connectable": {}, + "version": "7.5.2", + "input_ue_unconnectable": {} + } + }, + "widgets_values": [ + "Subject: Her hair lifts slightly as breeze stirs — she turns her head, listening.\nScene: Dark trees line the shore; moonlight breaks through clouds, casting long shadows.\nAction: She pauses, tilts her face upward — a single drop of water rolls down her neck. Camera circles gently around her shoulders." + ], + "color": "#2a363b", + "bgcolor": "#3f5159" + }, + { + "id": 352, + "type": "UnetLoaderGGUF", + "pos": [ + -1238.8688609733217, + -1176.9012180793875 + ], + "size": [ + 270, + 58 + ], + "flags": {}, + "order": 37, + "mode": 0, + "inputs": [], + "outputs": [ + { + "name": "MODEL", + "type": "MODEL", + "links": [ + 629 + ] + } + ], + "properties": { + "cnr_id": "ComfyUI-GGUF", + "ver": "c2e3b0adc3f805d401d745f69c88cc7e3eb5db75", + "Node name for S&R": "UnetLoaderGGUF", + "ue_properties": { + "widget_ue_connectable": {}, + "input_ue_unconnectable": {}, + "version": "7.5.2" + } + }, + "widgets_values": [ + "smoothMixWan22I2VT2V_i2vHigh-Q5_K_S.gguf" + ] + }, + { + "id": 393, + "type": "Any Switch (rgthree)", + "pos": [ + -826.1487818347597, + -1068.58490983122 + ], + "size": [ + 263.72841796875, + 106 + ], + "flags": {}, + "order": 60, + "mode": 0, + "inputs": [ + { + "dir": 3, + "name": "any_01", + "type": "MODEL", + "link": 629 + }, + { + "dir": 3, + "name": "any_02", + "type": "MODEL", + "link": 621 + }, + { + "dir": 3, + "name": "any_03", + "type": "MODEL", + "link": null + }, + { + "dir": 3, + "name": "any_04", + "type": "MODEL", + "link": null + }, + { + "dir": 3, + "name": "any_05", + "type": "MODEL", + "link": null + } + ], + "outputs": [ + { + "dir": 4, + "label": "MODEL", + "name": "*", + "shape": 3, + "type": "MODEL", + "links": [ + 630 + ] + } + ], + "properties": { + "cnr_id": "rgthree-comfy", + "ver": "8ff50e4521881eca1fe26aec9615fc9362474931", + "ue_properties": { + "widget_ue_connectable": {}, + "input_ue_unconnectable": {}, + "version": "7.5.2" + } + }, + "widgets_values": [] + }, + { + "id": 137, + "type": "SetNode", + "pos": [ + -113.34490841255587, + -1040.637788378767 + ], + "size": [ + 210, + 60 + ], + "flags": { + "collapsed": true + }, + "order": 89, + "mode": 0, + "inputs": [ + { + "name": "MODEL", + "type": "MODEL", + "link": 631 + } + ], + "outputs": [ + { + "name": "*", + "type": "*", + "links": null + } + ], + "title": "Set_model_high", + "properties": { + "previousName": "model_high", + "ue_properties": { + "widget_ue_connectable": {}, + "version": "7.5.2", + "input_ue_unconnectable": {} + } + }, + "widgets_values": [ + "model_high" + ], + "color": "#223", + "bgcolor": "#335" + }, + { + "id": 104, + "type": "ModelSamplingSD3", + "pos": [ + 553.2262655678377, + -846.5806579969146 + ], + "size": [ + 210, + 60 + ], + "flags": {}, + "order": 59, + "mode": 0, + "inputs": [ + { + "name": "model", + "type": "MODEL", + "link": 632 + } + ], + "outputs": [ + { + "name": "MODEL", + "type": "MODEL", + "slot_index": 0, + "links": [ + 227 + ] + } + ], + "properties": { + "cnr_id": "comfy-core", + "ver": "0.3.45", + "Node name for S&R": "ModelSamplingSD3", + "ue_properties": { + "widget_ue_connectable": {}, + "version": "7.1", + "input_ue_unconnectable": {} + } + }, + "widgets_values": [ + 5.000000000000001 + ], + "color": "#223", + "bgcolor": "#335" + }, + { + "id": 399, + "type": "GetNode", + "pos": [ + 362.68875235022864, + -834.008595837459 + ], + "size": [ + 210, + 60 + ], + "flags": { + "collapsed": true + }, + "order": 38, + "mode": 0, + "inputs": [], + "outputs": [ + { + "name": "MODEL", + "type": "MODEL", + "links": [ + 632 + ] + } + ], + "title": "Get_model_high", + "properties": { + "ue_properties": { + "widget_ue_connectable": {}, + "version": "7.5.2", + "input_ue_unconnectable": {} + } + }, + "widgets_values": [ + "model_high" + ], + "color": "#223", + "bgcolor": "#335" + }, + { + "id": 394, + "type": "UNETLoader", + "pos": [ + -1241.2882522654688, + -922.1960497026663 + ], + "size": [ + 270, + 82 + ], + "flags": {}, + "order": 39, + "mode": 2, + "inputs": [], + "outputs": [ + { + "name": "MODEL", + "type": "MODEL", + "links": [ + 621 + ] + } + ], + "properties": { + "cnr_id": "comfy-core", + "ver": "0.8.2", + "Node name for S&R": "UNETLoader", + "ue_properties": { + "widget_ue_connectable": {}, + "input_ue_unconnectable": {}, + "version": "7.5.2" + } + }, + "widgets_values": [ + "Wan2_2-I2V-A14B-HIGH_fp8_e5m2_scaled_KJ.safetensors", + "default" + ] + }, + { + "id": 398, + "type": "IAMCCS_WanLoRAStackModelIO", + "pos": [ + -498.2439244668632, + -1127.660968837162 + ], + "size": [ + 321.9859375, + 270 + ], + "flags": {}, + "order": 75, + "mode": 0, + "inputs": [ + { + "name": "model", + "type": "MODEL", + "link": 630 + }, + { + "name": "lora", + "shape": 7, + "type": "LORA", + "link": null + } + ], + "outputs": [ + { + "name": "MODEL", + "type": "MODEL", + "links": [ + 631 + ] + } + ], + "properties": { + "aux_id": "IAMCCS/IAMCCS-nodes", + "ver": "87665e54a4b06a50fc631609f53d3bacdd10fc3e", + "Node name for S&R": "IAMCCS_WanLoRAStackModelIO", + "ue_properties": { + "widget_ue_connectable": {}, + "input_ue_unconnectable": {}, + "version": "7.5.2" + } + }, + "widgets_values": [ + "WAN22\\wan2.2_i2v_A14b_high_noise_lora_rank64_lightx2v_4step_1022.safetensors", + 1, + "SVI_v2_PRO_Wan2.2-I2V-A14B_HIGH_lora_rank_128_fp16.safetensors", + 1, + "no", + 0, + "no", + 0, + "flow" + ] + }, + { + "id": 401, + "type": "UnetLoaderGGUF", + "pos": [ + -1237.3403049907386, + -636.5259029825223 + ], + "size": [ + 270, + 58 + ], + "flags": {}, + "order": 40, + "mode": 0, + "inputs": [], + "outputs": [ + { + "name": "MODEL", + "type": "MODEL", + "links": [ + 633 + ] + } + ], + "properties": { + "cnr_id": "ComfyUI-GGUF", + "ver": "c2e3b0adc3f805d401d745f69c88cc7e3eb5db75", + "Node name for S&R": "UnetLoaderGGUF", + "ue_properties": { + "widget_ue_connectable": {}, + "input_ue_unconnectable": {}, + "version": "7.5.2" + } + }, + "widgets_values": [ + "smoothMixWan22I2VT2V_i2vLow-Q5_K_S.gguf" + ] + }, + { + "id": 403, + "type": "UNETLoader", + "pos": [ + -1239.7596962828857, + -381.8207346058019 + ], + "size": [ + 270, + 82 + ], + "flags": {}, + "order": 41, + "mode": 2, + "inputs": [], + "outputs": [ + { + "name": "MODEL", + "type": "MODEL", + "links": [ + 634 + ] + } + ], + "properties": { + "cnr_id": "comfy-core", + "ver": "0.8.2", + "Node name for S&R": "UNETLoader", + "ue_properties": { + "widget_ue_connectable": {}, + "input_ue_unconnectable": {}, + "version": "7.5.2" + } + }, + "widgets_values": [ + "Wan2_2-I2V-A14B-LOW_fp8_e5m2_scaled_KJ.safetensors", + "default" + ] + }, + { + "id": 405, + "type": "IAMCCS_WanLoRAStackModelIO", + "pos": [ + -496.71536848427945, + -587.2856537402969 + ], + "size": [ + 321.9859375, + 270 + ], + "flags": {}, + "order": 76, + "mode": 0, + "inputs": [ + { + "name": "model", + "type": "MODEL", + "link": 635 + }, + { + "name": "lora", + "shape": 7, + "type": "LORA", + "link": null + } + ], + "outputs": [ + { + "name": "MODEL", + "type": "MODEL", + "links": [ + 636 + ] + } + ], + "properties": { + "aux_id": "IAMCCS/IAMCCS-nodes", + "ver": "87665e54a4b06a50fc631609f53d3bacdd10fc3e", + "Node name for S&R": "IAMCCS_WanLoRAStackModelIO", + "ue_properties": { + "widget_ue_connectable": {}, + "input_ue_unconnectable": {}, + "version": "7.5.2" + } + }, + "widgets_values": [ + "WAN22\\wan2.2_i2v_A14b_low_noise_lora_rank64_lightx2v_4step_1022.safetensors", + 1, + "SVI_v2_PRO_Wan2.2-I2V-A14B_LOW_lora_rank_128_fp16.safetensors", + 1, + "no", + 0, + "no", + 0, + "flow" + ] + }, + { + "id": 138, + "type": "SetNode", + "pos": [ + -102.50868968170452, + -541.4089488487232 + ], + "size": [ + 210, + 60 + ], + "flags": { + "collapsed": true + }, + "order": 90, + "mode": 0, + "inputs": [ + { + "name": "MODEL", + "type": "MODEL", + "link": 636 + } + ], + "outputs": [ + { + "name": "*", + "type": "*", + "links": null + } + ], + "title": "Set_model_low", + "properties": { + "previousName": "model_low", + "ue_properties": { + "widget_ue_connectable": {}, + "version": "7.5.2", + "input_ue_unconnectable": {} + } + }, + "widgets_values": [ + "model_low" + ], + "color": "#223", + "bgcolor": "#335" + }, + { + "id": 402, + "type": "Any Switch (rgthree)", + "pos": [ + -824.620225852176, + -528.2095947343555 + ], + "size": [ + 263.72841796875, + 106 + ], + "flags": {}, + "order": 61, + "mode": 0, + "inputs": [ + { + "dir": 3, + "name": "any_01", + "type": "MODEL", + "link": 633 + }, + { + "dir": 3, + "name": "any_02", + "type": "MODEL", + "link": 634 + }, + { + "dir": 3, + "name": "any_03", + "type": "MODEL", + "link": null + }, + { + "dir": 3, + "name": "any_04", + "type": "MODEL", + "link": null + }, + { + "dir": 3, + "name": "any_05", + "type": "MODEL", + "link": null + } + ], + "outputs": [ + { + "dir": 4, + "label": "MODEL", + "name": "*", + "shape": 3, + "type": "MODEL", + "links": [ + 635 + ] + } + ], + "properties": { + "cnr_id": "rgthree-comfy", + "ver": "8ff50e4521881eca1fe26aec9615fc9362474931", + "ue_properties": { + "widget_ue_connectable": {}, + "input_ue_unconnectable": {}, + "version": "7.5.2" + } + }, + "widgets_values": [] + }, + { + "id": 400, + "type": "Fast Groups Muter (rgthree)", + "pos": [ + -1677.3790186204328, + -1080.4718119335234 + ], + "size": [ + 308.4172663125313, + 106 + ], + "flags": {}, + "order": 42, + "mode": 0, + "inputs": [], + "outputs": [ + { + "name": "OPT_CONNECTION", + "type": "*", + "links": null + } + ], + "title": "HIGH MODELS", + "properties": { + "matchColors": "", + "matchTitle": "HIGH", + "showNav": true, + "showAllGraphs": true, + "sort": "position", + "customSortAlphabet": "", + "toggleRestriction": "default", + "ue_properties": { + "widget_ue_connectable": {}, + "input_ue_unconnectable": {}, + "version": "7.5.2" + } + } + }, + { + "id": 406, + "type": "Fast Groups Muter (rgthree)", + "pos": [ + -1678.581175874653, + -521.4907400194472 + ], + "size": [ + 297.7202816630951, + 96.73454637652048 + ], + "flags": {}, + "order": 43, + "mode": 0, + "inputs": [], + "outputs": [ + { + "name": "OPT_CONNECTION", + "type": "*", + "links": null + } + ], + "properties": { + "matchColors": "", + "matchTitle": "LOW", + "showNav": true, + "showAllGraphs": true, + "sort": "position", + "customSortAlphabet": "", + "toggleRestriction": "default", + "ue_properties": { + "widget_ue_connectable": {}, + "input_ue_unconnectable": {}, + "version": "7.5.2" + } + } + }, + { + "id": 84, + "type": "CLIPLoader", + "pos": [ + 355.83329161693285, + -459.3250386858174 + ], + "size": [ + 346.391845703125, + 106 + ], + "flags": {}, + "order": 44, + "mode": 0, + "inputs": [], + "outputs": [ + { + "name": "CLIP", + "type": "CLIP", + "slot_index": 0, + "links": [ + 283 + ] + } + ], + "properties": { + "cnr_id": "comfy-core", + "ver": "0.3.45", + "Node name for S&R": "CLIPLoader", + "models": [ + { + "name": "umt5_xxl_fp8_e4m3fn_scaled.safetensors", + "url": "https://huggingface.co/Comfy-Org/Wan_2.1_ComfyUI_repackaged/resolve/main/split_files/text_encoders/umt5_xxl_fp8_e4m3fn_scaled.safetensors", + "directory": "text_encoders" + } + ], + "ue_properties": { + "widget_ue_connectable": {}, + "version": "7.1", + "input_ue_unconnectable": {} + } + }, + "widgets_values": [ + "umt5_xxl_fp16.safetensors", + "wan", + "default" + ], + "color": "#432", + "bgcolor": "#653" + }, + { + "id": 351, + "type": "LoadImage", + "pos": [ + 390.01329440687584, + -1740.0305411550864 + ], + "size": [ + 282.798828125, + 314.0000000000001 + ], + "flags": {}, + "order": 45, + "mode": 0, + "inputs": [], + "outputs": [ + { + "name": "IMAGE", + "type": "IMAGE", + "links": [ + 581 + ] + }, + { + "name": "MASK", + "type": "MASK", + "links": null + } + ], + "properties": { + "cnr_id": "comfy-core", + "ver": "0.8.2", + "Node name for S&R": "LoadImage", + "ue_properties": { + "widget_ue_connectable": {}, + "input_ue_unconnectable": {}, + "version": "7.5.2" + } + }, + "widgets_values": [ + "QWEN_00005_.png", + "image" + ], + "color": "#233", + "bgcolor": "#355" + }, + { + "id": 331, + "type": "VHS_VideoCombine", + "pos": [ + 2244.8226198473562, + -1619.3363502998589 + ], + "size": [ + 273.9599911039079, + 334 + ], + "flags": {}, + "order": 81, + "mode": 0, + "inputs": [ + { + "name": "images", + "type": "IMAGE", + "link": 536 + }, + { + "name": "audio", + "shape": 7, + "type": "AUDIO", + "link": null + }, + { + "name": "meta_batch", + "shape": 7, + "type": "VHS_BatchManager", + "link": null + }, + { + "name": "vae", + "shape": 7, + "type": "VAE", + "link": null + } + ], + "outputs": [ + { + "name": "Filenames", + "type": "VHS_FILENAMES", + "links": null + } + ], + "properties": { + "cnr_id": "comfyui-videohelpersuite", + "ver": "8550981384301e9bc5bfea83e5c2c75258102593", + "Node name for S&R": "VHS_VideoCombine", + "ue_properties": { + "widget_ue_connectable": {}, + "version": "7.5.2", + "input_ue_unconnectable": {} + } + }, + "widgets_values": { + "frame_rate": 24, + "loop_count": 0, + "filename_prefix": "Wan22_SVI_Pro_progress", + "format": "video/h264-mp4", + "pix_fmt": "yuv420p", + "crf": 19, + "save_metadata": true, + "trim_to_audio": false, + "pingpong": false, + "save_output": false, + "videopreview": { + "hidden": false, + "paused": false, + "params": { + "filename": "Wan22_SVI_Pro_progress_00001.mp4", + "subfolder": "", + "type": "temp", + "format": "video/h264-mp4", + "frame_rate": 16, + "workflow": "Wan22_SVI_Pro_progress_00001.png", + "fullpath": "E:\\ComfyUI-Easy-Install\\ComfyUI-Easy-Install\\ComfyUI\\temp\\Wan22_SVI_Pro_progress_00001.mp4" + } + } + } + }, + { + "id": 312, + "type": "VHS_VideoCombine", + "pos": [ + 4022.0653168767003, + -503.7850316115217 + ], + "size": [ + 797.5640031222306, + 1695.7776054118665 + ], + "flags": {}, + "order": 92, + "mode": 0, + "inputs": [ + { + "name": "images", + "type": "IMAGE", + "link": 547 + }, + { + "name": "audio", + "shape": 7, + "type": "AUDIO", + "link": null + }, + { + "name": "meta_batch", + "shape": 7, + "type": "VHS_BatchManager", + "link": null + }, + { + "name": "vae", + "shape": 7, + "type": "VAE", + "link": null + } + ], + "outputs": [ + { + "name": "Filenames", + "type": "VHS_FILENAMES", + "links": null + } + ], + "properties": { + "cnr_id": "comfyui-videohelpersuite", + "ver": "8550981384301e9bc5bfea83e5c2c75258102593", + "Node name for S&R": "VHS_VideoCombine", + "ue_properties": { + "widget_ue_connectable": {}, + "version": "7.5.2", + "input_ue_unconnectable": {} + } + }, + "widgets_values": { + "frame_rate": 24, + "loop_count": 0, + "filename_prefix": "Wan22_SVI_Pro_final", + "format": "video/h264-mp4", + "pix_fmt": "yuv420p", + "crf": 19, + "save_metadata": true, + "trim_to_audio": false, + "pingpong": false, + "save_output": true, + "videopreview": { + "hidden": false, + "paused": false, + "params": { + "filename": "Wan22_SVI_Pro_final_00001.mp4", + "subfolder": "", + "type": "output", + "format": "video/h264-mp4", + "frame_rate": 16, + "workflow": "Wan22_SVI_Pro_final_00001.png", + "fullpath": "E:\\ComfyUI-Easy-Install\\ComfyUI-Easy-Install\\ComfyUI\\output\\Wan22_SVI_Pro_final_00001.mp4" + } + } + }, + "color": "#323", + "bgcolor": "#535" + }, + { + "id": 310, + "type": "VHS_VideoCombine", + "pos": [ + 4014.2040273560083, + -1596.1503951468137 + ], + "size": [ + 296.5255405114517, + 334 + ], + "flags": {}, + "order": 58, + "mode": 0, + "inputs": [ + { + "name": "images", + "type": "IMAGE", + "link": 619 + }, + { + "name": "audio", + "shape": 7, + "type": "AUDIO", + "link": null + }, + { + "name": "meta_batch", + "shape": 7, + "type": "VHS_BatchManager", + "link": null + }, + { + "name": "vae", + "shape": 7, + "type": "VAE", + "link": null + } + ], + "outputs": [ + { + "name": "Filenames", + "type": "VHS_FILENAMES", + "links": null + } + ], + "properties": { + "cnr_id": "comfyui-videohelpersuite", + "ver": "8550981384301e9bc5bfea83e5c2c75258102593", + "Node name for S&R": "VHS_VideoCombine", + "ue_properties": { + "widget_ue_connectable": {}, + "version": "7.5.2", + "input_ue_unconnectable": {} + } + }, + "widgets_values": { + "frame_rate": 24, + "loop_count": 0, + "filename_prefix": "Wan22_SVI_Pro_progress", + "format": "video/h264-mp4", + "pix_fmt": "yuv420p", + "crf": 19, + "save_metadata": true, + "trim_to_audio": false, + "pingpong": false, + "save_output": false, + "videopreview": { + "hidden": false, + "paused": false, + "params": { + "filename": "Wan22_SVI_Pro_progress_00003.mp4", + "subfolder": "", + "type": "temp", + "format": "video/h264-mp4", + "frame_rate": 16, + "workflow": "Wan22_SVI_Pro_progress_00003.png", + "fullpath": "E:\\ComfyUI-Easy-Install\\ComfyUI-Easy-Install\\ComfyUI\\temp\\Wan22_SVI_Pro_progress_00003.mp4" + } + } + } + }, + { + "id": 298, + "type": "MathExpression|pysssss", + "pos": [ + 2780.213484401525, + -334.5103596947303 + ], + "size": [ + 210, + 128 + ], + "flags": { + "collapsed": false + }, + "order": 57, + "mode": 0, + "inputs": [ + { + "name": "a", + "shape": 7, + "type": "INT,FLOAT,IMAGE,LATENT", + "link": 502 + }, + { + "name": "b", + "shape": 7, + "type": "INT,FLOAT,IMAGE,LATENT", + "link": null + }, + { + "name": "c", + "shape": 7, + "type": "INT,FLOAT,IMAGE,LATENT", + "link": null + } + ], + "outputs": [ + { + "name": "INT", + "type": "INT", + "links": [ + 501 + ] + }, + { + "name": "FLOAT", + "type": "FLOAT", + "links": null + } + ], + "properties": { + "cnr_id": "comfyui-custom-scripts", + "ver": "1.2.5", + "ue_properties": { + "widget_ue_connectable": {}, + "version": "7.5.2", + "input_ue_unconnectable": {} + } + }, + "widgets_values": [ + "ceil(((a*24)/81)-1)" + ], + "color": "#222", + "bgcolor": "#000" + }, + { + "id": 309, + "type": "c2a1c2eb-20cf-4b0b-aa1a-ebed356dec34", + "pos": [ + 3340.415516199842, + -1548.9524693507599 + ], + "size": [ + 326.62850054315595, + 288 + ], + "flags": { + "collapsed": false + }, + "order": 56, + "mode": 0, + "inputs": [ + { + "name": "noise", + "type": "NOISE", + "link": 591 + }, + { + "name": "sampler", + "type": "SAMPLER", + "link": 507 + }, + { + "label": "sigmas_high", + "name": "sigmas", + "type": "SIGMAS", + "link": 508 + }, + { + "label": "sigmas_low", + "name": "sigmas_1", + "type": "SIGMAS", + "link": 509 + }, + { + "name": "clip", + "type": "CLIP", + "link": 510 + }, + { + "label": "model_high", + "name": "model", + "type": "MODEL", + "link": 511 + }, + { + "label": "model_low", + "name": "model_1", + "type": "MODEL", + "link": 512 + }, + { + "name": "vae", + "type": "VAE", + "link": 513 + }, + { + "name": "anchor_samples", + "type": "LATENT", + "link": 514 + }, + { + "name": "prev_samples", + "type": "LATENT", + "link": 612 + }, + { + "label": "prev_images", + "name": "source_images", + "type": "IMAGE", + "link": 614 + }, + { + "label": "text_prompt", + "name": "text", + "type": "STRING", + "widget": { + "name": "text" + }, + "link": 609 + } + ], + "outputs": [ + { + "label": "samples", + "name": "output", + "type": "LATENT", + "links": [ + 615 + ] + }, + { + "name": "extended_images", + "type": "IMAGE", + "links": [ + 616 + ] + } + ], + "properties": { + "proxyWidgets": [ + [ + "-1", + "text" + ] + ], + "cnr_id": "comfy-core", + "ver": "0.5.1", + "ue_properties": { + "widget_ue_connectable": { + "text": true + }, + "version": "7.5.2", + "input_ue_unconnectable": {} + } + }, + "widgets_values": [ + "" + ], + "color": "#233", + "bgcolor": "#355" + }, + { + "id": 288, + "type": "PrimitiveStringMultiline", + "pos": [ + 1390.7468186339183, + -451.802917546974 + ], + "size": [ + 400, + 200 + ], + "flags": {}, + "order": 46, + "mode": 0, + "inputs": [], + "outputs": [ + { + "name": "STRING", + "type": "STRING", + "links": [ + 525 + ] + } + ], + "title": "Prompt", + "properties": { + "cnr_id": "comfy-core", + "ver": "0.6.0", + "Node name for S&R": "PrimitiveStringMultiline", + "ue_properties": { + "widget_ue_connectable": {}, + "version": "7.5.2", + "input_ue_unconnectable": {} + } + }, + "widgets_values": [ + "A goblin walks towards camera" + ], + "color": "#232", + "bgcolor": "#353", + "shape": 1 + }, + { + "id": 366, + "type": "PrimitiveStringMultiline", + "pos": [ + 1391.5730127508996, + -192.65684439498244 + ], + "size": [ + 400, + 200 + ], + "flags": {}, + "order": 47, + "mode": 0, + "inputs": [], + "outputs": [ + { + "name": "STRING", + "type": "STRING", + "links": [ + 597 + ] + } + ], + "title": "Prompt", + "properties": { + "cnr_id": "comfy-core", + "ver": "0.6.0", + "Node name for S&R": "PrimitiveStringMultiline", + "ue_properties": { + "widget_ue_connectable": {}, + "version": "7.5.2", + "input_ue_unconnectable": {} + } + }, + "widgets_values": [ + "a goblin walks towards camera looking to the right and to the left" + ], + "color": "#232", + "bgcolor": "#353", + "shape": 1 + }, + { + "id": 367, + "type": "PrimitiveStringMultiline", + "pos": [ + 1389.1714317481963, + 64.3045726672012 + ], + "size": [ + 400, + 200 + ], + "flags": {}, + "order": 48, + "mode": 0, + "inputs": [], + "outputs": [ + { + "name": "STRING", + "type": "STRING", + "links": [ + 598 + ] + } + ], + "title": "Prompt", + "properties": { + "cnr_id": "comfy-core", + "ver": "0.6.0", + "Node name for S&R": "PrimitiveStringMultiline", + "ue_properties": { + "widget_ue_connectable": {}, + "version": "7.5.2", + "input_ue_unconnectable": {} + } + }, + "widgets_values": [ + "a goblin walks towards camera and suddenly sotps and look at the camera smiling" + ], + "color": "#232", + "bgcolor": "#353", + "shape": 1 + }, + { + "id": 368, + "type": "PrimitiveStringMultiline", + "pos": [ + 1387.9706412468447, + 316.46272878490896 + ], + "size": [ + 400, + 200 + ], + "flags": {}, + "order": 49, + "mode": 0, + "inputs": [], + "outputs": [ + { + "name": "STRING", + "type": "STRING", + "links": [ + 599 + ] + } + ], + "title": "Prompt", + "properties": { + "cnr_id": "comfy-core", + "ver": "0.6.0", + "Node name for S&R": "PrimitiveStringMultiline", + "ue_properties": { + "widget_ue_connectable": {}, + "version": "7.5.2", + "input_ue_unconnectable": {} + } + }, + "widgets_values": [ + "camera slowly doll in to the face of the goblin while smiling looking at the camera" + ], + "color": "#232", + "bgcolor": "#353", + "shape": 1 + }, + { + "id": 369, + "type": "PrimitiveStringMultiline", + "pos": [ + 1387.9707072062238, + 578.2272089134299 + ], + "size": [ + 400, + 200 + ], + "flags": {}, + "order": 50, + "mode": 0, + "inputs": [], + "outputs": [ + { + "name": "STRING", + "type": "STRING", + "links": [ + 600 + ] + } + ], + "title": "Prompt", + "properties": { + "cnr_id": "comfy-core", + "ver": "0.6.0", + "Node name for S&R": "PrimitiveStringMultiline", + "ue_properties": { + "widget_ue_connectable": {}, + "version": "7.5.2", + "input_ue_unconnectable": {} + } + }, + "widgets_values": [ + "camera zoom to the face of the goblin while the goblin screams scared" + ], + "color": "#232", + "bgcolor": "#353", + "shape": 1 + }, + { + "id": 332, + "type": "GetNode", + "pos": [ + 1645.5110994766185, + -1167.594634960315 + ], + "size": [ + 210, + 60 + ], + "flags": { + "collapsed": true + }, + "order": 51, + "mode": 0, + "inputs": [], + "outputs": [ + { + "name": "STRING", + "type": "STRING", + "links": [ + 542 + ] + } + ], + "title": "Get_PRO_SCE", + "properties": { + "ue_properties": { + "widget_ue_connectable": {}, + "version": "7.5.2", + "input_ue_unconnectable": {} + } + }, + "widgets_values": [ + "PRO_SCE" + ] + } + ], + "links": [ + [ + 227, + 104, + 0, + 122, + 0, + "MODEL" + ], + [ + 250, + 122, + 0, + 128, + 0, + "SIGMAS" + ], + [ + 253, + 127, + 0, + 129, + 0, + "SAMPLER" + ], + [ + 254, + 128, + 0, + 130, + 0, + "SIGMAS" + ], + [ + 255, + 128, + 1, + 131, + 0, + "SIGMAS" + ], + [ + 269, + 136, + 0, + 135, + 0, + "IMAGE" + ], + [ + 283, + 84, + 0, + 143, + 0, + "CLIP" + ], + [ + 284, + 90, + 0, + 144, + 0, + "VAE" + ], + [ + 323, + 135, + 0, + 169, + 0, + "LATENT" + ], + [ + 402, + 188, + 0, + 135, + 1, + "VAE" + ], + [ + 501, + 298, + 0, + 294, + 1, + "INT" + ], + [ + 502, + 296, + 0, + 298, + 0, + "INT" + ], + [ + 507, + 300, + 0, + 309, + 1, + "SAMPLER" + ], + [ + 508, + 308, + 0, + 309, + 2, + "SIGMAS" + ], + [ + 509, + 307, + 0, + 309, + 3, + "SIGMAS" + ], + [ + 510, + 301, + 0, + 309, + 4, + "CLIP" + ], + [ + 511, + 303, + 0, + 309, + 5, + "MODEL" + ], + [ + 512, + 302, + 0, + 309, + 6, + "MODEL" + ], + [ + 513, + 304, + 0, + 309, + 7, + "VAE" + ], + [ + 514, + 306, + 0, + 309, + 8, + "LATENT" + ], + [ + 521, + 316, + 0, + 317, + 0, + "INT" + ], + [ + 523, + 294, + 1, + 318, + 0, + "INT" + ], + [ + 525, + 288, + 0, + 319, + 0, + "STRING" + ], + [ + 527, + 323, + 0, + 327, + 0, + "NOISE" + ], + [ + 528, + 320, + 0, + 327, + 1, + "SAMPLER" + ], + [ + 529, + 325, + 0, + 327, + 2, + "SIGMAS" + ], + [ + 530, + 324, + 0, + 327, + 3, + "SIGMAS" + ], + [ + 531, + 321, + 0, + 327, + 4, + "CLIP" + ], + [ + 532, + 322, + 0, + 327, + 5, + "MODEL" + ], + [ + 533, + 326, + 0, + 327, + 6, + "MODEL" + ], + [ + 534, + 328, + 0, + 327, + 7, + "VAE" + ], + [ + 535, + 329, + 0, + 327, + 8, + "LATENT" + ], + [ + 536, + 327, + 1, + 331, + 0, + "IMAGE" + ], + [ + 542, + 332, + 0, + 327, + 11, + "STRING" + ], + [ + 544, + 294, + 0, + 333, + 0, + "FLOW_CONTROL" + ], + [ + 547, + 333, + 1, + 312, + 0, + "IMAGE" + ], + [ + 558, + 136, + 0, + 343, + 0, + "IMAGE" + ], + [ + 559, + 318, + 0, + 344, + 0, + "INT" + ], + [ + 581, + 351, + 0, + 136, + 0, + "IMAGE" + ], + [ + 584, + 318, + 0, + 354, + 0, + "INT" + ], + [ + 587, + 327, + 0, + 360, + 0, + "LATENT" + ], + [ + 588, + 327, + 1, + 361, + 0, + "IMAGE" + ], + [ + 589, + 362, + 0, + 294, + 0, + "LATENT" + ], + [ + 590, + 363, + 0, + 294, + 2, + "IMAGE" + ], + [ + 591, + 364, + 0, + 309, + 0, + "NOISE" + ], + [ + 596, + 319, + 0, + 370, + 0, + "*" + ], + [ + 597, + 366, + 0, + 371, + 0, + "STRING" + ], + [ + 598, + 367, + 0, + 372, + 0, + "STRING" + ], + [ + 599, + 368, + 0, + 373, + 0, + "STRING" + ], + [ + 600, + 369, + 0, + 375, + 0, + "STRING" + ], + [ + 601, + 376, + 0, + 356, + 0, + "STRING" + ], + [ + 602, + 381, + 0, + 356, + 1, + "STRING" + ], + [ + 603, + 378, + 0, + 356, + 3, + "*" + ], + [ + 604, + 379, + 0, + 356, + 4, + "STRING" + ], + [ + 605, + 380, + 0, + 356, + 5, + "STRING" + ], + [ + 606, + 355, + 0, + 356, + 2, + "INT" + ], + [ + 608, + 293, + 0, + 382, + 0, + "*" + ], + [ + 609, + 383, + 0, + 309, + 11, + "STRING" + ], + [ + 610, + 356, + 0, + 293, + 0, + "*" + ], + [ + 611, + 294, + 2, + 384, + 0, + "*" + ], + [ + 612, + 386, + 0, + 309, + 9, + "LATENT" + ], + [ + 613, + 294, + 3, + 385, + 0, + "*" + ], + [ + 614, + 387, + 0, + 309, + 10, + "IMAGE" + ], + [ + 615, + 309, + 0, + 388, + 0, + "LATENT" + ], + [ + 616, + 309, + 1, + 389, + 0, + "IMAGE" + ], + [ + 617, + 390, + 0, + 333, + 1, + "LATENT" + ], + [ + 618, + 391, + 0, + 333, + 2, + "IMAGE" + ], + [ + 619, + 392, + 0, + 310, + 0, + "IMAGE" + ], + [ + 621, + 394, + 0, + 393, + 1, + "MODEL" + ], + [ + 629, + 352, + 0, + 393, + 0, + "MODEL" + ], + [ + 630, + 393, + 0, + 398, + 0, + "MODEL" + ], + [ + 631, + 398, + 0, + 137, + 0, + "MODEL" + ], + [ + 632, + 399, + 0, + 104, + 0, + "MODEL" + ], + [ + 633, + 401, + 0, + 402, + 0, + "MODEL" + ], + [ + 634, + 403, + 0, + 402, + 1, + "MODEL" + ], + [ + 635, + 402, + 0, + 405, + 0, + "MODEL" + ], + [ + 636, + 405, + 0, + 138, + 0, + "MODEL" + ] + ], + "groups": [ + { + "id": 2, + "title": "VIDEO SEGMENT 1", + "bounding": [ + 1627.7879936848228, + -1692.9363502998588, + 1064.2991298092993, + 763.8100906453444 + ], + "color": "#3f789e", + "font_size": 24, + "flags": {} + }, + { + "id": 3, + "title": "Settting", + "bounding": [ + 325.894876776323, + -1239.6985783450068, + 835.4165891719979, + 687.4493307537022 + ], + "color": "#3f789e", + "font_size": 24, + "flags": {} + }, + { + "id": 5, + "title": "IMAGE", + "bounding": [ + 314.7837694552321, + -1892.3713006966948, + 1128.6676930763565, + 575.0895040708374 + ], + "color": "#3f789e", + "font_size": 24, + "flags": {} + }, + { + "id": 6, + "title": "LOOP_MODULE", + "bounding": [ + 2634.680788596277, + -535.0273371340219, + 1349.7319792372286, + 475.0123258172445 + ], + "color": "#3f789e", + "font_size": 24, + "flags": {} + }, + { + "id": 7, + "title": "PROMPT INDEX", + "bounding": [ + 2123.335438565198, + -521.5382952635489, + 455.95071568008416, + 441.0129923082427 + ], + "color": "#3f789e", + "font_size": 24, + "flags": {} + }, + { + "id": 8, + "title": "PROMPTS", + "bounding": [ + 1377.9706412468445, + -525.4029175469739, + 609.8490525406571, + 1313.6301264604044 + ], + "color": "#3f789e", + "font_size": 24, + "flags": {} + }, + { + "id": 9, + "title": "Group", + "bounding": [ + 3063.1870795871378, + -1914.894453367152, + 1257.5424882803222, + 1156.0549951068547 + ], + "color": "#3f789e", + "font_size": 24, + "flags": {} + }, + { + "id": 10, + "title": "HIGH - GGUF MODEL", + "bounding": [ + -1248.8688609733217, + -1250.5012180793872, + 290, + 141.6 + ], + "color": "#b58b2a", + "font_size": 24, + "flags": {} + }, + { + "id": 11, + "title": "HIGH DIFF MODEL", + "bounding": [ + -1251.2882522654688, + -995.7960497026663, + 290, + 165.6 + ], + "color": "#b58b2a", + "font_size": 24, + "flags": {} + }, + { + "id": 12, + "title": "LOW - GGUF MODEL", + "bounding": [ + -1247.3403049907386, + -710.1259029825222, + 290, + 141.6 + ], + "color": "#a1309b", + "font_size": 24, + "flags": {} + }, + { + "id": 13, + "title": "LOW - DIFF MODEL", + "bounding": [ + -1249.7596962828857, + -455.42073460580184, + 290, + 165.6 + ], + "color": "#a1309b", + "font_size": 24, + "flags": {} + }, + { + "id": 14, + "title": "MODELS A", + "bounding": [ + -1687.3790186204328, + -1294.101218079387, + 1733.823172707877, + 473.9051683767208 + ], + "color": "#3f789e", + "font_size": 24, + "flags": {} + }, + { + "id": 15, + "title": "MODELS B", + "bounding": [ + -1688.581175874653, + -753.7259029825221, + 1741.1095955679482, + 473.9051683767208 + ], + "color": "#3f789e", + "font_size": 24, + "flags": {} + } + ], + "definitions": { + "subgraphs": [ + { + "id": "c2a1c2eb-20cf-4b0b-aa1a-ebed356dec34", + "version": 1, + "state": { + "lastGroupId": 0, + "lastNodeId": 186, + "lastLinkId": 395, + "lastRerouteId": 0 + }, + "revision": 0, + "config": {}, + "name": "VIDEO_EXTENSION_MODULE", + "inputNode": { + "id": -10, + "bounding": [ + 4115.81544227034, + -400.92473247969093, + 132.1328125, + 280 + ] + }, + "outputNode": { + "id": -20, + "bounding": [ + 6400.849747062121, + -230.5874192045676, + 139.8203125, + 80 + ] + }, + "inputs": [ + { + "id": "8b4a7fa4-f3be-4c26-b0dd-88848c9ab797", + "name": "noise", + "type": "NOISE", + "linkIds": [ + 363, + 378 + ], + "localized_name": "noise", + "pos": [ + 4227.94825477034, + -380.92473247969093 + ] + }, + { + "id": "969b9107-4ce1-4202-9957-acd32444ba4c", + "name": "sampler", + "type": "SAMPLER", + "linkIds": [ + 370, + 379 + ], + "localized_name": "sampler", + "pos": [ + 4227.94825477034, + -360.92473247969093 + ] + }, + { + "id": "33e3cb1c-f033-4042-a118-733adb12f19d", + "name": "sigmas", + "type": "SIGMAS", + "linkIds": [ + 381 + ], + "localized_name": "sigmas", + "label": "sigmas_high", + "pos": [ + 4227.94825477034, + -340.92473247969093 + ] + }, + { + "id": "693f230c-fe4f-4a3f-a154-39f6f0aee5cf", + "name": "sigmas_1", + "type": "SIGMAS", + "linkIds": [ + 376 + ], + "localized_name": "sigmas_1", + "label": "sigmas_low", + "pos": [ + 4227.94825477034, + -320.92473247969093 + ] + }, + { + "id": "8a165693-1f38-4451-8a8f-626af3cbc814", + "name": "clip", + "type": "CLIP", + "linkIds": [ + 306, + 359 + ], + "localized_name": "clip", + "pos": [ + 4227.94825477034, + -300.92473247969093 + ] + }, + { + "id": "5f2cf9c8-9abe-4676-ab8a-06bacf33cea1", + "name": "model", + "type": "MODEL", + "linkIds": [ + 308 + ], + "localized_name": "model", + "label": "model_high", + "pos": [ + 4227.94825477034, + -280.92473247969093 + ] + }, + { + "id": "3fcc850b-b642-4e2e-b44f-8d524513470d", + "name": "model_1", + "type": "MODEL", + "linkIds": [ + 311 + ], + "localized_name": "model_1", + "label": "model_low", + "pos": [ + 4227.94825477034, + -260.92473247969093 + ] + }, + { + "id": "8c2de8a0-efbf-441b-b32d-313c571e81a5", + "name": "vae", + "type": "VAE", + "linkIds": [ + 317 + ], + "localized_name": "vae", + "pos": [ + 4227.94825477034, + -240.92473247969093 + ] + }, + { + "id": "61713356-47df-4089-a6f1-fa9e39a19ef3", + "name": "anchor_samples", + "type": "LATENT", + "linkIds": [ + 389 + ], + "localized_name": "anchor_samples", + "pos": [ + 4227.94825477034, + -220.92473247969093 + ] + }, + { + "id": "4f4ab288-e9b4-4fff-8501-6556415ea12d", + "name": "prev_samples", + "type": "LATENT", + "linkIds": [ + 390 + ], + "localized_name": "prev_samples", + "shape": 7, + "pos": [ + 4227.94825477034, + -200.92473247969093 + ] + }, + { + "id": "f4a82cb8-05fb-47fc-a580-a3fb3c32e99c", + "name": "source_images", + "type": "IMAGE", + "linkIds": [ + 319 + ], + "localized_name": "source_images", + "label": "prev_images", + "pos": [ + 4227.94825477034, + -180.92473247969093 + ] + }, + { + "id": "4e92d0f3-27fe-41cf-962c-bcce804c4c07", + "name": "text", + "type": "STRING", + "linkIds": [ + 386 + ], + "label": "text_prompt", + "pos": [ + 4227.94825477034, + -160.92473247969093 + ] + } + ], + "outputs": [ + { + "id": "ba13141d-868a-42a1-ac48-e51d5bd57c48", + "name": "output", + "type": "LATENT", + "linkIds": [ + 384 + ], + "label": "samples", + "pos": [ + 6420.849747062121, + -210.5874192045676 + ] + }, + { + "id": "c16acdef-88c8-49fd-a2dc-4aca876df35f", + "name": "extended_images", + "type": "IMAGE", + "linkIds": [ + 385 + ], + "pos": [ + 6420.849747062121, + -190.5874192045676 + ] + } + ], + "widgets": [], + "nodes": [ + { + "id": 182, + "type": "CLIPTextEncode", + "pos": [ + 4557.246296160685, + 88.09894919868954 + ], + "size": [ + 400, + 200 + ], + "flags": { + "collapsed": true + }, + "order": 6, + "mode": 0, + "inputs": [ + { + "localized_name": "clip", + "name": "clip", + "type": "CLIP", + "link": 359 + } + ], + "outputs": [ + { + "localized_name": "CONDITIONING", + "name": "CONDITIONING", + "type": "CONDITIONING", + "links": [ + 388 + ] + } + ], + "properties": { + "cnr_id": "comfy-core", + "ver": "0.5.1", + "Node name for S&R": "CLIPTextEncode", + "ue_properties": { + "widget_ue_connectable": {}, + "version": "7.5.2", + "input_ue_unconnectable": {} + } + }, + "widgets_values": [ + "色调艳丽,过曝,静态,细节模糊不清,字幕,风格,作品,画作,画面,静止,整体发灰,最差质量,低质量,JPEG压缩残留,丑陋的,残缺的,多余的手指,画得不好的手部,画得不好的脸部,畸形的,毁容的,形态畸形的肢体,手指融合,静止不动的画面,杂乱的背景,三条腿,背景人很多,倒着走" + ], + "color": "#332922", + "bgcolor": "#593930" + }, + { + "id": 158, + "type": "ScheduledCFGGuidance", + "pos": [ + 4979.369448463057, + -577.2273949478679 + ], + "size": [ + 270.0875, + 146 + ], + "flags": {}, + "order": 2, + "mode": 0, + "inputs": [ + { + "localized_name": "model", + "name": "model", + "type": "MODEL", + "link": 308 + }, + { + "localized_name": "positive", + "name": "positive", + "type": "CONDITIONING", + "link": 391 + }, + { + "localized_name": "negative", + "name": "negative", + "type": "CONDITIONING", + "link": 392 + } + ], + "outputs": [ + { + "localized_name": "GUIDER", + "name": "GUIDER", + "type": "GUIDER", + "links": [ + 380 + ] + } + ], + "properties": { + "cnr_id": "comfyui-kjnodes", + "ver": "79f529a84a8c20fe5dcdfa984c6be7a94102c014", + "Node name for S&R": "ScheduledCFGGuidance", + "aux_id": "kijai/ComfyUI-KJNodes", + "ue_properties": { + "widget_ue_connectable": {}, + "version": "7.5.2", + "input_ue_unconnectable": {} + } + }, + "widgets_values": [ + 1, + 0, + 1 + ], + "color": "#233", + "bgcolor": "#355" + }, + { + "id": 159, + "type": "ScheduledCFGGuidance", + "pos": [ + 4892.021207244808, + -378.6041455925486 + ], + "size": [ + 270.0875, + 146 + ], + "flags": {}, + "order": 3, + "mode": 0, + "inputs": [ + { + "localized_name": "model", + "name": "model", + "type": "MODEL", + "link": 311 + }, + { + "localized_name": "positive", + "name": "positive", + "type": "CONDITIONING", + "link": 394 + }, + { + "localized_name": "negative", + "name": "negative", + "type": "CONDITIONING", + "link": 395 + } + ], + "outputs": [ + { + "localized_name": "GUIDER", + "name": "GUIDER", + "type": "GUIDER", + "links": [ + 369 + ] + } + ], + "properties": { + "cnr_id": "comfyui-kjnodes", + "ver": "79f529a84a8c20fe5dcdfa984c6be7a94102c014", + "Node name for S&R": "ScheduledCFGGuidance", + "aux_id": "kijai/ComfyUI-KJNodes", + "ue_properties": { + "widget_ue_connectable": {}, + "version": "7.5.2", + "input_ue_unconnectable": {} + } + }, + "widgets_values": [ + 1, + 0, + 1 + ], + "color": "#233", + "bgcolor": "#355" + }, + { + "id": 185, + "type": "SamplerCustomAdvanced", + "pos": [ + 5199.957283942452, + -362.71893743481655 + ], + "size": [ + 257.5849944250107, + 365.06318566771085 + ], + "flags": {}, + "order": 8, + "mode": 0, + "inputs": [ + { + "localized_name": "noise", + "name": "noise", + "type": "NOISE", + "link": 378 + }, + { + "localized_name": "guider", + "name": "guider", + "type": "GUIDER", + "link": 380 + }, + { + "localized_name": "sampler", + "name": "sampler", + "type": "SAMPLER", + "link": 379 + }, + { + "localized_name": "sigmas", + "name": "sigmas", + "type": "SIGMAS", + "link": 381 + }, + { + "localized_name": "latent_image", + "name": "latent_image", + "type": "LATENT", + "link": 393 + } + ], + "outputs": [ + { + "localized_name": "output", + "name": "output", + "type": "LATENT", + "links": [ + 383 + ] + }, + { + "localized_name": "denoised_output", + "name": "denoised_output", + "type": "LATENT", + "links": null + } + ], + "properties": { + "cnr_id": "comfy-core", + "ver": "0.5.1", + "Node name for S&R": "SamplerCustomAdvanced", + "ue_properties": { + "widget_ue_connectable": {}, + "version": "7.5.2", + "input_ue_unconnectable": {} + } + }, + "widgets_values": [], + "color": "#233", + "bgcolor": "#355" + }, + { + "id": 184, + "type": "SamplerCustomAdvanced", + "pos": [ + 5492.177651225263, + -359.40395912698784 + ], + "size": [ + 289.26606114292565, + 397.26606114292565 + ], + "flags": {}, + "order": 7, + "mode": 0, + "inputs": [ + { + "localized_name": "noise", + "name": "noise", + "type": "NOISE", + "link": 368 + }, + { + "localized_name": "guider", + "name": "guider", + "type": "GUIDER", + "link": 369 + }, + { + "localized_name": "sampler", + "name": "sampler", + "type": "SAMPLER", + "link": 370 + }, + { + "localized_name": "sigmas", + "name": "sigmas", + "type": "SIGMAS", + "link": 376 + }, + { + "localized_name": "latent_image", + "name": "latent_image", + "type": "LATENT", + "link": 383 + } + ], + "outputs": [ + { + "localized_name": "output", + "name": "output", + "type": "LATENT", + "links": [ + 373, + 384 + ] + }, + { + "localized_name": "denoised_output", + "name": "denoised_output", + "type": "LATENT", + "links": null + } + ], + "properties": { + "cnr_id": "comfy-core", + "ver": "0.5.1", + "Node name for S&R": "SamplerCustomAdvanced", + "ue_properties": { + "widget_ue_connectable": {}, + "version": "7.5.2", + "input_ue_unconnectable": {} + } + }, + "widgets_values": [], + "color": "#233", + "bgcolor": "#355" + }, + { + "id": 183, + "type": "DisableNoise", + "pos": [ + 5396.93830547369, + -449.90225361537813 + ], + "size": [ + 140, + 26 + ], + "flags": { + "collapsed": true + }, + "order": 0, + "mode": 0, + "inputs": [], + "outputs": [ + { + "localized_name": "NOISE", + "name": "NOISE", + "type": "NOISE", + "links": [ + 368 + ] + } + ], + "properties": { + "cnr_id": "comfy-core", + "ver": "0.5.1", + "Node name for S&R": "DisableNoise", + "ue_properties": { + "widget_ue_connectable": {}, + "version": "7.5.2", + "input_ue_unconnectable": {} + } + }, + "widgets_values": [], + "color": "#233", + "bgcolor": "#355" + }, + { + "id": 168, + "type": "ImageBatchExtendWithOverlap", + "pos": [ + 5933.047625581195, + -167.66778635634307 + ], + "size": [ + 321.6236328125, + 146 + ], + "flags": {}, + "order": 5, + "mode": 0, + "inputs": [ + { + "localized_name": "source_images", + "name": "source_images", + "type": "IMAGE", + "link": 319 + }, + { + "localized_name": "new_images", + "name": "new_images", + "shape": 7, + "type": "IMAGE", + "link": 321 + } + ], + "outputs": [ + { + "localized_name": "source_images", + "name": "source_images", + "type": "IMAGE", + "links": null + }, + { + "localized_name": "start_images", + "name": "start_images", + "type": "IMAGE", + "links": null + }, + { + "localized_name": "extended_images", + "name": "extended_images", + "type": "IMAGE", + "links": [ + 385 + ] + } + ], + "properties": { + "cnr_id": "comfyui-kjnodes", + "ver": "79f529a84a8c20fe5dcdfa984c6be7a94102c014", + "Node name for S&R": "ImageBatchExtendWithOverlap", + "aux_id": "kijai/ComfyUI-KJNodes", + "ue_properties": { + "widget_ue_connectable": {}, + "version": "7.5.2", + "input_ue_unconnectable": {} + } + }, + "widgets_values": [ + 5, + "source", + "linear_blend" + ], + "color": "#233", + "bgcolor": "#355" + }, + { + "id": 162, + "type": "VAEDecode", + "pos": [ + 5894.565863157888, + -333.7746725024751 + ], + "size": [ + 210, + 46 + ], + "flags": {}, + "order": 4, + "mode": 0, + "inputs": [ + { + "localized_name": "samples", + "name": "samples", + "type": "LATENT", + "link": 373 + }, + { + "localized_name": "vae", + "name": "vae", + "type": "VAE", + "link": 317 + } + ], + "outputs": [ + { + "localized_name": "IMAGE", + "name": "IMAGE", + "type": "IMAGE", + "slot_index": 0, + "links": [ + 321 + ] + } + ], + "properties": { + "cnr_id": "comfy-core", + "ver": "0.3.45", + "Node name for S&R": "VAEDecode", + "ue_properties": { + "widget_ue_connectable": {}, + "version": "7.1", + "input_ue_unconnectable": {} + } + }, + "widgets_values": [], + "color": "#233", + "bgcolor": "#355" + }, + { + "id": 152, + "type": "CLIPTextEncode", + "pos": [ + 4545.284023464112, + 17.65102760535699 + ], + "size": [ + 479.7835317198833, + 88 + ], + "flags": { + "collapsed": true + }, + "order": 1, + "mode": 0, + "inputs": [ + { + "localized_name": "clip", + "name": "clip", + "type": "CLIP", + "link": 306 + }, + { + "localized_name": "text", + "name": "text", + "type": "STRING", + "widget": { + "name": "text" + }, + "link": 386 + } + ], + "outputs": [ + { + "localized_name": "CONDITIONING", + "name": "CONDITIONING", + "type": "CONDITIONING", + "slot_index": 0, + "links": [ + 387 + ] + } + ], + "title": "CLIP Text Encode (Positive Prompt)", + "properties": { + "cnr_id": "comfy-core", + "ver": "0.3.45", + "Node name for S&R": "CLIPTextEncode", + "ue_properties": { + "widget_ue_connectable": {}, + "version": "7.1", + "input_ue_unconnectable": {} + } + }, + "widgets_values": [ + "" + ], + "color": "#232", + "bgcolor": "#353" + }, + { + "id": 186, + "type": "IAMCCS_WanImageMotion", + "pos": [ + 4502.750755505038, + -414.8038352702417 + ], + "size": [ + 317.90234375, + 286 + ], + "flags": {}, + "order": 9, + "mode": 0, + "inputs": [ + { + "localized_name": "positive", + "name": "positive", + "type": "CONDITIONING", + "link": 387 + }, + { + "localized_name": "negative", + "name": "negative", + "type": "CONDITIONING", + "link": 388 + }, + { + "localized_name": "anchor_samples", + "name": "anchor_samples", + "type": "LATENT", + "link": 389 + }, + { + "localized_name": "prev_samples", + "name": "prev_samples", + "shape": 7, + "type": "LATENT", + "link": 390 + } + ], + "outputs": [ + { + "localized_name": "positive", + "name": "positive", + "type": "CONDITIONING", + "links": [ + 391, + 394 + ] + }, + { + "localized_name": "negative", + "name": "negative", + "type": "CONDITIONING", + "links": [ + 392, + 395 + ] + }, + { + "localized_name": "latent", + "name": "latent", + "type": "LATENT", + "links": [ + 393 + ] + } + ], + "properties": { + "aux_id": "IAMCCS/IAMCCS-nodes", + "ver": "87665e54a4b06a50fc631609f53d3bacdd10fc3e", + "Node name for S&R": "IAMCCS_WanImageMotion", + "ue_properties": { + "widget_ue_connectable": {}, + "input_ue_unconnectable": {}, + "version": "7.5.2" + } + }, + "widgets_values": [ + 81, + 1, + 1, + "motion_only (prev_samples)", + false, + "auto", + "normal", + false + ], + "color": "#233", + "bgcolor": "#355" + } + ], + "groups": [], + "links": [ + { + "id": 321, + "origin_id": 162, + "origin_slot": 0, + "target_id": 168, + "target_slot": 1, + "type": "IMAGE" + }, + { + "id": 306, + "origin_id": -10, + "origin_slot": 4, + "target_id": 152, + "target_slot": 0, + "type": "CLIP" + }, + { + "id": 308, + "origin_id": -10, + "origin_slot": 5, + "target_id": 158, + "target_slot": 0, + "type": "MODEL" + }, + { + "id": 311, + "origin_id": -10, + "origin_slot": 6, + "target_id": 159, + "target_slot": 0, + "type": "MODEL" + }, + { + "id": 317, + "origin_id": -10, + "origin_slot": 7, + "target_id": 162, + "target_slot": 1, + "type": "VAE" + }, + { + "id": 319, + "origin_id": -10, + "origin_slot": 10, + "target_id": 168, + "target_slot": 0, + "type": "IMAGE" + }, + { + "id": 359, + "origin_id": -10, + "origin_slot": 4, + "target_id": 182, + "target_slot": 0, + "type": "CLIP" + }, + { + "id": 363, + "origin_id": -10, + "origin_slot": 0, + "target_id": 150, + "target_slot": 0, + "type": "NOISE" + }, + { + "id": 368, + "origin_id": 183, + "origin_slot": 0, + "target_id": 184, + "target_slot": 0, + "type": "NOISE" + }, + { + "id": 369, + "origin_id": 159, + "origin_slot": 0, + "target_id": 184, + "target_slot": 1, + "type": "GUIDER" + }, + { + "id": 370, + "origin_id": -10, + "origin_slot": 1, + "target_id": 184, + "target_slot": 2, + "type": "SAMPLER" + }, + { + "id": 373, + "origin_id": 184, + "origin_slot": 0, + "target_id": 162, + "target_slot": 0, + "type": "LATENT" + }, + { + "id": 376, + "origin_id": -10, + "origin_slot": 3, + "target_id": 184, + "target_slot": 3, + "type": "SIGMAS" + }, + { + "id": 378, + "origin_id": -10, + "origin_slot": 0, + "target_id": 185, + "target_slot": 0, + "type": "NOISE" + }, + { + "id": 379, + "origin_id": -10, + "origin_slot": 1, + "target_id": 185, + "target_slot": 2, + "type": "SAMPLER" + }, + { + "id": 380, + "origin_id": 158, + "origin_slot": 0, + "target_id": 185, + "target_slot": 1, + "type": "GUIDER" + }, + { + "id": 381, + "origin_id": -10, + "origin_slot": 2, + "target_id": 185, + "target_slot": 3, + "type": "SIGMAS" + }, + { + "id": 383, + "origin_id": 185, + "origin_slot": 0, + "target_id": 184, + "target_slot": 4, + "type": "LATENT" + }, + { + "id": 384, + "origin_id": 184, + "origin_slot": 0, + "target_id": -20, + "target_slot": 0, + "type": "LATENT" + }, + { + "id": 385, + "origin_id": 168, + "origin_slot": 2, + "target_id": -20, + "target_slot": 1, + "type": "IMAGE" + }, + { + "id": 386, + "origin_id": -10, + "origin_slot": 11, + "target_id": 152, + "target_slot": 1, + "type": "STRING" + }, + { + "id": 387, + "origin_id": 152, + "origin_slot": 0, + "target_id": 186, + "target_slot": 0, + "type": "CONDITIONING" + }, + { + "id": 388, + "origin_id": 182, + "origin_slot": 0, + "target_id": 186, + "target_slot": 1, + "type": "CONDITIONING" + }, + { + "id": 389, + "origin_id": -10, + "origin_slot": 8, + "target_id": 186, + "target_slot": 2, + "type": "LATENT" + }, + { + "id": 390, + "origin_id": -10, + "origin_slot": 9, + "target_id": 186, + "target_slot": 3, + "type": "LATENT" + }, + { + "id": 391, + "origin_id": 186, + "origin_slot": 0, + "target_id": 158, + "target_slot": 1, + "type": "CONDITIONING" + }, + { + "id": 392, + "origin_id": 186, + "origin_slot": 1, + "target_id": 158, + "target_slot": 2, + "type": "CONDITIONING" + }, + { + "id": 393, + "origin_id": 186, + "origin_slot": 2, + "target_id": 185, + "target_slot": 4, + "type": "LATENT" + }, + { + "id": 394, + "origin_id": 186, + "origin_slot": 0, + "target_id": 159, + "target_slot": 1, + "type": "CONDITIONING" + }, + { + "id": 395, + "origin_id": 186, + "origin_slot": 1, + "target_id": 159, + "target_slot": 2, + "type": "CONDITIONING" + } + ], + "extra": { + "workflowRendererVersion": "LG", + "ue_links": [], + "links_added_by_ue": [] + } + }, + { + "id": "08be7d9e-8766-4c78-98c9-50fc97389ca0", + "version": 1, + "state": { + "lastGroupId": 0, + "lastNodeId": 186, + "lastLinkId": 398, + "lastRerouteId": 0 + }, + "revision": 0, + "config": {}, + "name": "Initial", + "inputNode": { + "id": -10, + "bounding": [ + 4115.81544227034, + -400.92473247969093, + 132.1328125, + 280 + ] + }, + "outputNode": { + "id": -20, + "bounding": [ + 6095.627231727035, + -219.44839331354646, + 136.80859375, + 80 + ] + }, + "inputs": [ + { + "id": "8b4a7fa4-f3be-4c26-b0dd-88848c9ab797", + "name": "noise", + "type": "NOISE", + "linkIds": [ + 363, + 378 + ], + "localized_name": "noise", + "pos": [ + 4227.94825477034, + -380.92473247969093 + ] + }, + { + "id": "969b9107-4ce1-4202-9957-acd32444ba4c", + "name": "sampler", + "type": "SAMPLER", + "linkIds": [ + 370, + 379 + ], + "localized_name": "sampler", + "pos": [ + 4227.94825477034, + -360.92473247969093 + ] + }, + { + "id": "33e3cb1c-f033-4042-a118-733adb12f19d", + "name": "sigmas", + "type": "SIGMAS", + "linkIds": [ + 381 + ], + "localized_name": "sigmas", + "label": "sigmas_high", + "pos": [ + 4227.94825477034, + -340.92473247969093 + ] + }, + { + "id": "693f230c-fe4f-4a3f-a154-39f6f0aee5cf", + "name": "sigmas_1", + "type": "SIGMAS", + "linkIds": [ + 376 + ], + "localized_name": "sigmas_1", + "label": "sigmas_low", + "pos": [ + 4227.94825477034, + -320.92473247969093 + ] + }, + { + "id": "8a165693-1f38-4451-8a8f-626af3cbc814", + "name": "clip", + "type": "CLIP", + "linkIds": [ + 306, + 359 + ], + "localized_name": "clip", + "pos": [ + 4227.94825477034, + -300.92473247969093 + ] + }, + { + "id": "5f2cf9c8-9abe-4676-ab8a-06bacf33cea1", + "name": "model", + "type": "MODEL", + "linkIds": [ + 308 + ], + "localized_name": "model", + "label": "model_high", + "pos": [ + 4227.94825477034, + -280.92473247969093 + ] + }, + { + "id": "3fcc850b-b642-4e2e-b44f-8d524513470d", + "name": "model_1", + "type": "MODEL", + "linkIds": [ + 311 + ], + "localized_name": "model_1", + "label": "model_low", + "pos": [ + 4227.94825477034, + -260.92473247969093 + ] + }, + { + "id": "8c2de8a0-efbf-441b-b32d-313c571e81a5", + "name": "vae", + "type": "VAE", + "linkIds": [ + 317 + ], + "localized_name": "vae", + "pos": [ + 4227.94825477034, + -240.92473247969093 + ] + }, + { + "id": "61713356-47df-4089-a6f1-fa9e39a19ef3", + "name": "anchor_samples", + "type": "LATENT", + "linkIds": [ + 390 + ], + "localized_name": "anchor_samples", + "pos": [ + 4227.94825477034, + -220.92473247969093 + ] + }, + { + "id": "4f4ab288-e9b4-4fff-8501-6556415ea12d", + "name": "prev_samples", + "type": "LATENT", + "linkIds": [ + 391 + ], + "localized_name": "prev_samples", + "shape": 7, + "pos": [ + 4227.94825477034, + -200.92473247969093 + ] + }, + { + "id": "f4a82cb8-05fb-47fc-a580-a3fb3c32e99c", + "name": "source_images", + "type": "IMAGE", + "linkIds": [], + "localized_name": "source_images", + "label": "prev_images", + "pos": [ + 4227.94825477034, + -180.92473247969093 + ] + }, + { + "id": "9c7cacd9-109f-46d3-8644-536e43f8ab3e", + "name": "text", + "type": "STRING", + "linkIds": [ + 389 + ], + "label": "text_prompt", + "pos": [ + 4227.94825477034, + -160.92473247969093 + ] + } + ], + "outputs": [ + { + "id": "309763cc-bd21-44e1-83ab-0d95e80c8ec3", + "name": "output", + "type": "LATENT", + "linkIds": [ + 386 + ], + "label": "samples", + "pos": [ + 6115.627231727035, + -199.44839331354646 + ] + }, + { + "id": "274b2726-e09c-433d-b072-c23df311f391", + "name": "IMAGE", + "type": "IMAGE", + "linkIds": [ + 387 + ], + "label": "decoded_images", + "pos": [ + 6115.627231727035, + -179.44839331354646 + ] + } + ], + "widgets": [], + "nodes": [ + { + "id": 152, + "type": "CLIPTextEncode", + "pos": [ + 4384.025453293967, + -27.480377907595074 + ], + "size": [ + 510, + 160 + ], + "flags": { + "collapsed": true + }, + "order": 1, + "mode": 0, + "inputs": [ + { + "localized_name": "clip", + "name": "clip", + "type": "CLIP", + "link": 306 + }, + { + "localized_name": "text", + "name": "text", + "type": "STRING", + "widget": { + "name": "text" + }, + "link": 389 + } + ], + "outputs": [ + { + "localized_name": "CONDITIONING", + "name": "CONDITIONING", + "type": "CONDITIONING", + "slot_index": 0, + "links": [ + 394 + ] + } + ], + "title": "CLIP Text Encode (Positive Prompt)", + "properties": { + "cnr_id": "comfy-core", + "ver": "0.3.45", + "Node name for S&R": "CLIPTextEncode", + "ue_properties": { + "widget_ue_connectable": {}, + "version": "7.1", + "input_ue_unconnectable": {} + } + }, + "widgets_values": [ + "" + ], + "color": "#232", + "bgcolor": "#353" + }, + { + "id": 182, + "type": "CLIPTextEncode", + "pos": [ + 4368.148994310642, + 41.02177929190853 + ], + "size": [ + 400, + 200 + ], + "flags": { + "collapsed": true + }, + "order": 5, + "mode": 0, + "inputs": [ + { + "localized_name": "clip", + "name": "clip", + "type": "CLIP", + "link": 359 + } + ], + "outputs": [ + { + "localized_name": "CONDITIONING", + "name": "CONDITIONING", + "type": "CONDITIONING", + "links": [ + 395 + ] + } + ], + "properties": { + "cnr_id": "comfy-core", + "ver": "0.5.1", + "Node name for S&R": "CLIPTextEncode", + "ue_properties": { + "widget_ue_connectable": {}, + "version": "7.5.2", + "input_ue_unconnectable": {} + } + }, + "widgets_values": [ + "色调艳丽,过曝,静态,细节模糊不清,字幕,风格,作品,画作,画面,静止,整体发灰,最差质量,低质量,JPEG压缩残留,丑陋的,残缺的,多余的手指,画得不好的手部,画得不好的脸部,畸形的,毁容的,形态畸形的肢体,手指融合,静止不动的画面,杂乱的背景,三条腿,背景人很多,倒着走" + ], + "color": "#332922", + "bgcolor": "#593930" + }, + { + "id": 158, + "type": "ScheduledCFGGuidance", + "pos": [ + 5148.9073437359175, + -688.5339493273569 + ], + "size": [ + 270.0875, + 146 + ], + "flags": {}, + "order": 2, + "mode": 0, + "inputs": [ + { + "localized_name": "model", + "name": "model", + "type": "MODEL", + "link": 308 + }, + { + "localized_name": "positive", + "name": "positive", + "type": "CONDITIONING", + "link": 392 + }, + { + "localized_name": "negative", + "name": "negative", + "type": "CONDITIONING", + "link": 393 + } + ], + "outputs": [ + { + "localized_name": "GUIDER", + "name": "GUIDER", + "type": "GUIDER", + "links": [ + 380 + ] + } + ], + "properties": { + "cnr_id": "comfyui-kjnodes", + "ver": "79f529a84a8c20fe5dcdfa984c6be7a94102c014", + "Node name for S&R": "ScheduledCFGGuidance", + "aux_id": "kijai/ComfyUI-KJNodes", + "ue_properties": { + "widget_ue_connectable": {}, + "version": "7.5.2", + "input_ue_unconnectable": {} + } + }, + "widgets_values": [ + 1, + 0, + 1 + ], + "color": "#233", + "bgcolor": "#355" + }, + { + "id": 185, + "type": "SamplerCustomAdvanced", + "pos": [ + 4934.701702975717, + -293.2393678373789 + ], + "size": [ + 257.5849944250107, + 365.06318566771085 + ], + "flags": {}, + "order": 7, + "mode": 0, + "inputs": [ + { + "localized_name": "noise", + "name": "noise", + "type": "NOISE", + "link": 378 + }, + { + "localized_name": "guider", + "name": "guider", + "type": "GUIDER", + "link": 380 + }, + { + "localized_name": "sampler", + "name": "sampler", + "type": "SAMPLER", + "link": 379 + }, + { + "localized_name": "sigmas", + "name": "sigmas", + "type": "SIGMAS", + "link": 381 + }, + { + "localized_name": "latent_image", + "name": "latent_image", + "type": "LATENT", + "link": 396 + } + ], + "outputs": [ + { + "localized_name": "output", + "name": "output", + "type": "LATENT", + "links": [ + 383 + ] + }, + { + "localized_name": "denoised_output", + "name": "denoised_output", + "type": "LATENT", + "links": null + } + ], + "properties": { + "cnr_id": "comfy-core", + "ver": "0.5.1", + "Node name for S&R": "SamplerCustomAdvanced", + "ue_properties": { + "widget_ue_connectable": {}, + "version": "7.5.2", + "input_ue_unconnectable": {} + } + }, + "widgets_values": [], + "color": "#233", + "bgcolor": "#355" + }, + { + "id": 183, + "type": "DisableNoise", + "pos": [ + 5208.455762821815, + -482.50762168567115 + ], + "size": [ + 140, + 26 + ], + "flags": { + "collapsed": true + }, + "order": 0, + "mode": 0, + "inputs": [], + "outputs": [ + { + "localized_name": "NOISE", + "name": "NOISE", + "type": "NOISE", + "links": [ + 368 + ] + } + ], + "properties": { + "cnr_id": "comfy-core", + "ver": "0.5.1", + "Node name for S&R": "DisableNoise", + "ue_properties": { + "widget_ue_connectable": {}, + "version": "7.5.2", + "input_ue_unconnectable": {} + } + }, + "widgets_values": [], + "color": "#233", + "bgcolor": "#355" + }, + { + "id": 159, + "type": "ScheduledCFGGuidance", + "pos": [ + 5443.28682439448, + -550.9843234134333 + ], + "size": [ + 270.0875, + 146 + ], + "flags": {}, + "order": 3, + "mode": 0, + "inputs": [ + { + "localized_name": "model", + "name": "model", + "type": "MODEL", + "link": 311 + }, + { + "localized_name": "positive", + "name": "positive", + "type": "CONDITIONING", + "link": 397 + }, + { + "localized_name": "negative", + "name": "negative", + "type": "CONDITIONING", + "link": 398 + } + ], + "outputs": [ + { + "localized_name": "GUIDER", + "name": "GUIDER", + "type": "GUIDER", + "links": [ + 369 + ] + } + ], + "properties": { + "cnr_id": "comfyui-kjnodes", + "ver": "79f529a84a8c20fe5dcdfa984c6be7a94102c014", + "Node name for S&R": "ScheduledCFGGuidance", + "aux_id": "kijai/ComfyUI-KJNodes", + "ue_properties": { + "widget_ue_connectable": {}, + "version": "7.5.2", + "input_ue_unconnectable": {} + } + }, + "widgets_values": [ + 1, + 0, + 1 + ], + "color": "#233", + "bgcolor": "#355" + }, + { + "id": 162, + "type": "VAEDecode", + "pos": [ + 5685.934273792993, + -270.08803856524406 + ], + "size": [ + 210, + 46 + ], + "flags": {}, + "order": 4, + "mode": 0, + "inputs": [ + { + "localized_name": "samples", + "name": "samples", + "type": "LATENT", + "link": 373 + }, + { + "localized_name": "vae", + "name": "vae", + "type": "VAE", + "link": 317 + } + ], + "outputs": [ + { + "localized_name": "IMAGE", + "name": "IMAGE", + "type": "IMAGE", + "slot_index": 0, + "links": [ + 387 + ] + } + ], + "properties": { + "cnr_id": "comfy-core", + "ver": "0.3.45", + "Node name for S&R": "VAEDecode", + "ue_properties": { + "widget_ue_connectable": {}, + "version": "7.1", + "input_ue_unconnectable": {} + } + }, + "widgets_values": [], + "color": "#233", + "bgcolor": "#355" + }, + { + "id": 184, + "type": "SamplerCustomAdvanced", + "pos": [ + 5247.316600641524, + -320.48695240078024 + ], + "size": [ + 289.26606114292565, + 397.26606114292565 + ], + "flags": {}, + "order": 6, + "mode": 0, + "inputs": [ + { + "localized_name": "noise", + "name": "noise", + "type": "NOISE", + "link": 368 + }, + { + "localized_name": "guider", + "name": "guider", + "type": "GUIDER", + "link": 369 + }, + { + "localized_name": "sampler", + "name": "sampler", + "type": "SAMPLER", + "link": 370 + }, + { + "localized_name": "sigmas", + "name": "sigmas", + "type": "SIGMAS", + "link": 376 + }, + { + "localized_name": "latent_image", + "name": "latent_image", + "type": "LATENT", + "link": 383 + } + ], + "outputs": [ + { + "localized_name": "output", + "name": "output", + "type": "LATENT", + "links": [ + 373, + 386 + ] + }, + { + "localized_name": "denoised_output", + "name": "denoised_output", + "type": "LATENT", + "links": null + } + ], + "properties": { + "cnr_id": "comfy-core", + "ver": "0.5.1", + "Node name for S&R": "SamplerCustomAdvanced", + "ue_properties": { + "widget_ue_connectable": {}, + "version": "7.5.2", + "input_ue_unconnectable": {} + } + }, + "widgets_values": [], + "color": "#233", + "bgcolor": "#355" + }, + { + "id": 186, + "type": "IAMCCS_WanImageMotion", + "pos": [ + 4542.307651881915, + -702.5206901237116 + ], + "size": [ + 317.90234375, + 286 + ], + "flags": {}, + "order": 8, + "mode": 0, + "inputs": [ + { + "localized_name": "positive", + "name": "positive", + "type": "CONDITIONING", + "link": 394 + }, + { + "localized_name": "negative", + "name": "negative", + "type": "CONDITIONING", + "link": 395 + }, + { + "localized_name": "anchor_samples", + "name": "anchor_samples", + "type": "LATENT", + "link": 390 + }, + { + "localized_name": "prev_samples", + "name": "prev_samples", + "shape": 7, + "type": "LATENT", + "link": 391 + } + ], + "outputs": [ + { + "localized_name": "positive", + "name": "positive", + "type": "CONDITIONING", + "links": [ + 392, + 397 + ] + }, + { + "localized_name": "negative", + "name": "negative", + "type": "CONDITIONING", + "links": [ + 393, + 398 + ] + }, + { + "localized_name": "latent", + "name": "latent", + "type": "LATENT", + "links": [ + 396 + ] + } + ], + "properties": { + "aux_id": "IAMCCS/IAMCCS-nodes", + "ver": "87665e54a4b06a50fc631609f53d3bacdd10fc3e", + "Node name for S&R": "IAMCCS_WanImageMotion", + "ue_properties": { + "widget_ue_connectable": {}, + "input_ue_unconnectable": {}, + "version": "7.5.2" + } + }, + "widgets_values": [ + 81, + 0, + 1, + "motion_only (prev_samples)", + false, + "auto", + "normal", + false + ], + "color": "#233", + "bgcolor": "#355" + } + ], + "groups": [], + "links": [ + { + "id": 306, + "origin_id": -10, + "origin_slot": 4, + "target_id": 152, + "target_slot": 0, + "type": "CLIP" + }, + { + "id": 308, + "origin_id": -10, + "origin_slot": 5, + "target_id": 158, + "target_slot": 0, + "type": "MODEL" + }, + { + "id": 311, + "origin_id": -10, + "origin_slot": 6, + "target_id": 159, + "target_slot": 0, + "type": "MODEL" + }, + { + "id": 317, + "origin_id": -10, + "origin_slot": 7, + "target_id": 162, + "target_slot": 1, + "type": "VAE" + }, + { + "id": 359, + "origin_id": -10, + "origin_slot": 4, + "target_id": 182, + "target_slot": 0, + "type": "CLIP" + }, + { + "id": 363, + "origin_id": -10, + "origin_slot": 0, + "target_id": 150, + "target_slot": 0, + "type": "NOISE" + }, + { + "id": 368, + "origin_id": 183, + "origin_slot": 0, + "target_id": 184, + "target_slot": 0, + "type": "NOISE" + }, + { + "id": 369, + "origin_id": 159, + "origin_slot": 0, + "target_id": 184, + "target_slot": 1, + "type": "GUIDER" + }, + { + "id": 370, + "origin_id": -10, + "origin_slot": 1, + "target_id": 184, + "target_slot": 2, + "type": "SAMPLER" + }, + { + "id": 373, + "origin_id": 184, + "origin_slot": 0, + "target_id": 162, + "target_slot": 0, + "type": "LATENT" + }, + { + "id": 376, + "origin_id": -10, + "origin_slot": 3, + "target_id": 184, + "target_slot": 3, + "type": "SIGMAS" + }, + { + "id": 378, + "origin_id": -10, + "origin_slot": 0, + "target_id": 185, + "target_slot": 0, + "type": "NOISE" + }, + { + "id": 379, + "origin_id": -10, + "origin_slot": 1, + "target_id": 185, + "target_slot": 2, + "type": "SAMPLER" + }, + { + "id": 380, + "origin_id": 158, + "origin_slot": 0, + "target_id": 185, + "target_slot": 1, + "type": "GUIDER" + }, + { + "id": 381, + "origin_id": -10, + "origin_slot": 2, + "target_id": 185, + "target_slot": 3, + "type": "SIGMAS" + }, + { + "id": 383, + "origin_id": 185, + "origin_slot": 0, + "target_id": 184, + "target_slot": 4, + "type": "LATENT" + }, + { + "id": 386, + "origin_id": 184, + "origin_slot": 0, + "target_id": -20, + "target_slot": 0, + "type": "LATENT" + }, + { + "id": 387, + "origin_id": 162, + "origin_slot": 0, + "target_id": -20, + "target_slot": 1, + "type": "IMAGE" + }, + { + "id": 389, + "origin_id": -10, + "origin_slot": 11, + "target_id": 152, + "target_slot": 1, + "type": "STRING" + }, + { + "id": 390, + "origin_id": -10, + "origin_slot": 8, + "target_id": 186, + "target_slot": 2, + "type": "LATENT" + }, + { + "id": 391, + "origin_id": -10, + "origin_slot": 9, + "target_id": 186, + "target_slot": 3, + "type": "LATENT" + }, + { + "id": 392, + "origin_id": 186, + "origin_slot": 0, + "target_id": 158, + "target_slot": 1, + "type": "CONDITIONING" + }, + { + "id": 393, + "origin_id": 186, + "origin_slot": 1, + "target_id": 158, + "target_slot": 2, + "type": "CONDITIONING" + }, + { + "id": 394, + "origin_id": 152, + "origin_slot": 0, + "target_id": 186, + "target_slot": 0, + "type": "CONDITIONING" + }, + { + "id": 395, + "origin_id": 182, + "origin_slot": 0, + "target_id": 186, + "target_slot": 1, + "type": "CONDITIONING" + }, + { + "id": 396, + "origin_id": 186, + "origin_slot": 2, + "target_id": 185, + "target_slot": 4, + "type": "LATENT" + }, + { + "id": 397, + "origin_id": 186, + "origin_slot": 0, + "target_id": 159, + "target_slot": 1, + "type": "CONDITIONING" + }, + { + "id": 398, + "origin_id": 186, + "origin_slot": 1, + "target_id": 159, + "target_slot": 2, + "type": "CONDITIONING" + } + ], + "extra": { + "workflowRendererVersion": "LG", + "ue_links": [], + "links_added_by_ue": [] + } + } + ] + }, + "config": {}, + "extra": { + "ds": { + "scale": 0.35676040523396557, + "offset": [ + -452.99173270133963, + 1915.1583210546055 + ] + }, + "frontendVersion": "1.35.9", + "workflowRendererVersion": "LG", + "VHS_latentpreview": true, + "VHS_latentpreviewrate": 0, + "VHS_MetadataImage": true, + "VHS_KeepIntermediate": true, + "ue_links": [], + "iamccs_annotations": { + "version": 2, + "enabled": false, + "color": "#ff4444", + "width": 7, + "opacity": 1, + "eraser": false, + "constantScreen": false, + "dashed": false, + "hidden": false, + "penOnly": false, + "hiDPIx2": false, + "selOverride": false, + "pinMode": false, + "tool": "draw", + "selectMode": "rect", + "transformMode": "fixed", + "widthDraw": 7, + "widthErase": 48, + "opacityDraw": 1, + "opacityErase": 1, + "textFontFamily": "Arial", + "textFontSize": 28, + "textColor": "#111111", + "textFontWeight": "normal", + "textFontStyle": "normal", + "textUnderline": false, + "paths": [], + "layers": [ + { + "name": "Layer 1", + "kind": "draw", + "visible": true, + "locked": false, + "paths": [], + "style": { + "color": "#ff4444", + "dashed": false, + "widthDraw": 7, + "widthErase": 48, + "opacityDraw": 1, + "opacityErase": 1 + } + } + ], + "currentLayerIdx": 0, + "stickers": [], + "stickerStyle": { + "frameColor": "#ffffff", + "paddingPx": 10, + "borderWidthPx": 2, + "shadow": true, + "shadowStrength": 12 + } + }, + "iamccs_annotations_multi": { + "version": 2, + "graphs": { + "ec7da562-7e21-4dac-a0d2-f4441e1efd3b": { + "version": 2, + "enabled": false, + "color": "#ff4444", + "width": 7, + "opacity": 1, + "eraser": false, + "constantScreen": false, + "dashed": false, + "hidden": false, + "penOnly": false, + "hiDPIx2": false, + "selOverride": false, + "pinMode": false, + "tool": "draw", + "selectMode": "rect", + "transformMode": "fixed", + "widthDraw": 7, + "widthErase": 48, + "opacityDraw": 1, + "opacityErase": 1, + "textFontFamily": "Arial", + "textFontSize": 28, + "textColor": "#111111", + "textFontWeight": "normal", + "textFontStyle": "normal", + "textUnderline": false, + "paths": [], + "layers": [ + { + "name": "Layer 1", + "kind": "draw", + "visible": true, + "locked": false, + "paths": [], + "style": { + "color": "#ff4444", + "dashed": false, + "widthDraw": 7, + "widthErase": 48, + "opacityDraw": 1, + "opacityErase": 1 + } + } + ], + "currentLayerIdx": 0, + "stickers": [], + "stickerStyle": { + "frameColor": "#ffffff", + "paddingPx": 10, + "borderWidthPx": 2, + "shadow": true, + "shadowStrength": 12 + } + }, + "c2a1c2eb-20cf-4b0b-aa1a-ebed356dec34": { + "version": 2, + "enabled": false, + "color": "#ff4444", + "width": 7, + "opacity": 1, + "eraser": false, + "constantScreen": false, + "dashed": false, + "hidden": false, + "penOnly": false, + "hiDPIx2": false, + "selOverride": false, + "pinMode": false, + "tool": "draw", + "selectMode": "rect", + "transformMode": "fixed", + "widthDraw": 7, + "widthErase": 48, + "opacityDraw": 1, + "opacityErase": 1, + "textFontFamily": "Arial", + "textFontSize": 28, + "textColor": "#111111", + "textFontWeight": "normal", + "textFontStyle": "normal", + "textUnderline": false, + "paths": [], + "layers": [ + { + "name": "Layer 1", + "kind": "draw", + "visible": true, + "locked": false, + "paths": [], + "style": { + "color": "#ff4444", + "dashed": false, + "widthDraw": 7, + "widthErase": 48, + "opacityDraw": 1, + "opacityErase": 1 + } + } + ], + "currentLayerIdx": 0, + "stickers": [], + "stickerStyle": { + "frameColor": "#ffffff", + "paddingPx": 10, + "borderWidthPx": 2, + "shadow": true, + "shadowStrength": 12 + } + }, + "08be7d9e-8766-4c78-98c9-50fc97389ca0": { + "version": 2, + "enabled": false, + "color": "#ff4444", + "width": 7, + "opacity": 1, + "eraser": false, + "constantScreen": false, + "dashed": false, + "hidden": false, + "penOnly": false, + "hiDPIx2": false, + "selOverride": false, + "pinMode": false, + "tool": "draw", + "selectMode": "rect", + "transformMode": "fixed", + "widthDraw": 7, + "widthErase": 48, + "opacityDraw": 1, + "opacityErase": 1, + "textFontFamily": "Arial", + "textFontSize": 28, + "textColor": "#111111", + "textFontWeight": "normal", + "textFontStyle": "normal", + "textUnderline": false, + "paths": [], + "layers": [ + { + "name": "Layer 1", + "kind": "draw", + "visible": true, + "locked": false, + "paths": [], + "style": { + "color": "#ff4444", + "dashed": false, + "widthDraw": 7, + "widthErase": 48, + "opacityDraw": 1, + "opacityErase": 1 + } + } + ], + "currentLayerIdx": 0, + "stickers": [], + "stickerStyle": { + "frameColor": "#ffffff", + "paddingPx": 10, + "borderWidthPx": 2, + "shadow": true, + "shadowStrength": 12 + } + } + } + }, + "links_added_by_ue": [] + }, + "version": 0.4 +} \ No newline at end of file diff --git a/README.md b/README.md index 214b1fc..6ed25c8 100644 --- a/README.md +++ b/README.md @@ -7,7 +7,59 @@ ### Category: ComfyUI Custom Nodes ### Main Feature: Fix for LoRA loading in native WANAnimate workflows -Version: 1.3.2 +Version: 1.3.3 + +# UPDATE VERSION 1-3-3 + +## 🆕 Version 1.3.3 — AutoLink + LTX-2 Extension Module + +Date: 2026-01-26 + +Highlights (EN): +- AutoLink (frontend): convert direct links into compact Set/Get nodes + restore when needed. + +![[Node piece](assets/autolink.png)](https://github.com/IAMCCS/IAMCCS-nodes/blob/main/assets/autolink.png) + +- LTX-2: Extension Module + helpers for iterative long video extension workflows. + +![[Node piece](assets/extension.png)](https://github.com/IAMCCS/IAMCCS-nodes/blob/main/assets/extension.png) + +Docs: + +- LTX-2 Extension Module (EN/IT): `LTX2_EXTENSION_MODULE_README.md` +- LTX-2 Nodes Guide: `LTX2_EXTENSION_NODES_GUIDE_EN.md` + +GGUF / OOM tips: +- If you use `IAMCCS_GGUF_accelerator` and you are close to the VRAM limit, consider PyTorch allocator tuning to reduce fragmentation (must be set **before** launching ComfyUI). + - Example: `PYTORCH_ALLOC_CONF=backend:cudaMallocAsync` + - Example (native allocator): `PYTORCH_ALLOC_CONF=max_split_size_mb:128,garbage_collection_threshold:0.8` + - Example (experimental, native allocator): `PYTORCH_ALLOC_CONF=expandable_segments:True` + +### IAMCCS_GGUF_accelerator (how to use) + +![[Node piece](assets/gguf.png)](https://github.com/IAMCCS/IAMCCS-nodes/blob/main/assets/gguf.png) + +This node modifies a GGUF `MODEL` so ComfyUI-GGUF can avoid expensive per-step CPU↔GPU patch movement. + +Recommended usage: +- Place it **after** your GGUF model loader and **before** LoRA application / sampling. +- Default: `mode = auto_oom_safe`. + - If free VRAM is low, it automatically disables `patch_on_device` and avoids pre-moving patches. + - If a CUDA OOM happens while moving patches, it falls back to CPU/offload (when `oom_fallback = true`). + +Suggested starting values on 12GB GPUs: +- `mode = auto_oom_safe` +- `min_free_vram_mb = 1500` (raise to 2000–3000 if you still get OOMs) +- Keep `move_patches_now = true` only if you have headroom; set to `false` if you want the safest VRAM behavior. + +PyTorch allocator tuning (set before start): +- You can use `PYTORCH_ALLOC_CONF` (or the legacy alias `PYTORCH_CUDA_ALLOC_CONF`) to reduce fragmentation. +- Windows example (PowerShell, current session): + - `$env:PYTORCH_ALLOC_CONF = "backend:cudaMallocAsync"` +- Windows example (CMD / .bat): + - `set PYTORCH_ALLOC_CONF=backend:cudaMallocAsync` + +--- # UPDATE VERSION 1-3-2 @@ -48,7 +100,7 @@ Highlights: ![[Node piece](assets/wanmotion.png)](https://github.com/IAMCCS/IAMCCS-nodes/blob/main/assets/wanmotion.png) Highlights: -- Added `IAMCCS WanImageMotion` node: drop-in replacement for KJNodes `WanImageToVideoSVIPro` with motion amplitude control to fix slow-motion issues in WAN SVI Pro workflows. +- Added `IAMCCS WanImageMotion` node: drop-in replacement for common WAN SVI Pro image-to-video nodes, with motion amplitude control to fix slow-motion issues in WAN SVI Pro workflows. - Motion modes: apply boost to `prev_samples` only or all non-first latents. - VRAM profiles: normal / chunked / per-frame loop / CPU offload for memory-constrained systems. - `include_padding_in_motion` toggle: enables motion boost on padded frames when anchor has single frame (T=1). diff --git a/__init__.py b/__init__.py index ddd529f..f6a1c1f 100644 --- a/__init__.py +++ b/__init__.py @@ -2,6 +2,9 @@ # __init__.py — Registro nodi IAMCCS # ========================================================== +import logging +import os + from .iamccs_wan_lora_stack import ( IAMCCS_WanLoRAStack, IAMCCS_ModelWithLoRA, @@ -18,17 +21,45 @@ from .iamccs_ltx2_lora_stack import ( IAMCCS_LTX2_LoRAStackModelIO, ) +from .iamccs_ltx2_lora_stack_segmented6 import ( + IAMCCS_LTX2_LoRAStackSegmented6, + IAMCCS_LTX2_ModelWithLoRA_Segmented6, +) + from .iamccs_ltx2_tools import ( IAMCCS_LTX2_FrameRateSync, IAMCCS_LTX2_Validator, IAMCCS_LTX2_TimeFrameCount, + IAMCCS_LTX2_EnsureFrames8nPlus1, IAMCCS_LTX2_ControlPreprocess, + IAMCCS_LTX2_ImageBatchPadReflect, + IAMCCS_LTX2_ImageBatchCropByPad, +) + +from .iamccs_ltx2_extension_module import ( + IAMCCS_LTX2_ExtensionModule, + IAMCCS_LTX2_ExtensionModule_simple, + IAMCCS_LTX2_GetImageFromBatch, + IAMCCS_LTX2_ReferenceImageSwitch, + IAMCCS_LTX2_ReferenceStartFramesInjector, + IAMCCS_LTX2_FrameCountValidator, ) from .iamccs_wan_svipro_motion import ( IAMCCS_WanImageMotion, ) +from .iamccs_autolink import ( + IAMCCS_SetAutoLink, + IAMCCS_GetAutoLink, + IAMCCS_AutoLinkConverter, + IAMCCS_AutoLinkArguments, +) + +from .iamccs_gguf_accelerator import ( + IAMCCS_GGUF_accelerator, +) + # Nodi principali NODE_CLASS_MAPPINGS = { "IAMCCS_WanLoRAStack": IAMCCS_WanLoRAStack, @@ -42,12 +73,30 @@ NODE_CLASS_MAPPINGS = { "IAMCCS_ModelWithLoRA_LTX2": IAMCCS_ModelWithLoRA_LTX2, "IAMCCS_ModelWithLoRA_LTX2_Staged": IAMCCS_ModelWithLoRA_LTX2_Staged, "IAMCCS_LTX2_LoRAStackModelIO": IAMCCS_LTX2_LoRAStackModelIO, + "IAMCCS_LTX2_LoRAStackSegmented6": IAMCCS_LTX2_LoRAStackSegmented6, + "IAMCCS_LTX2_ModelWithLoRA_Segmented6": IAMCCS_LTX2_ModelWithLoRA_Segmented6, "IAMCCS_LTX2_FrameRateSync": IAMCCS_LTX2_FrameRateSync, "IAMCCS_LTX2_Validator": IAMCCS_LTX2_Validator, "IAMCCS_LTX2_TimeFrameCount": IAMCCS_LTX2_TimeFrameCount, + "IAMCCS_LTX2_EnsureFrames8nPlus1": IAMCCS_LTX2_EnsureFrames8nPlus1, "IAMCCS_LTX2_ControlPreprocess": IAMCCS_LTX2_ControlPreprocess, + "IAMCCS_LTX2_ImageBatchPadReflect": IAMCCS_LTX2_ImageBatchPadReflect, + "IAMCCS_LTX2_ImageBatchCropByPad": IAMCCS_LTX2_ImageBatchCropByPad, + "IAMCCS_LTX2_ExtensionModule": IAMCCS_LTX2_ExtensionModule, + "IAMCCS_LTX2_ExtensionModule_simple": IAMCCS_LTX2_ExtensionModule_simple, + "IAMCCS_LTX2_GetImageFromBatch": IAMCCS_LTX2_GetImageFromBatch, + "IAMCCS_LTX2_ReferenceImageSwitch": IAMCCS_LTX2_ReferenceImageSwitch, + "IAMCCS_LTX2_ReferenceStartFramesInjector": IAMCCS_LTX2_ReferenceStartFramesInjector, + "IAMCCS_LTX2_FrameCountValidator": IAMCCS_LTX2_FrameCountValidator, "IAMCCS_WanImageMotion": IAMCCS_WanImageMotion, + + "IAMCCS_SetAutoLink": IAMCCS_SetAutoLink, + "IAMCCS_GetAutoLink": IAMCCS_GetAutoLink, + "IAMCCS_AutoLinkConverter": IAMCCS_AutoLinkConverter, + "IAMCCS_AutoLinkArguments": IAMCCS_AutoLinkArguments, + + "IAMCCS_GGUF_accelerator": IAMCCS_GGUF_accelerator, } NODE_DISPLAY_NAME_MAPPINGS = { @@ -60,15 +109,177 @@ NODE_DISPLAY_NAME_MAPPINGS = { "IAMCCS_ModelWithLoRA_LTX2": "Apply LoRA to MODEL (LTX-2, quiet logs)", "IAMCCS_ModelWithLoRA_LTX2_Staged": "Apply LoRA to MODEL (LTX-2, staged) (BETA)", "IAMCCS_LTX2_LoRAStackModelIO": "LoRA Stack (Model In→Out) LTX-2", + "IAMCCS_LTX2_LoRAStackSegmented6": "LoRA Stack (LTX-2, segmented: 3 seg × 2 stages)", + "IAMCCS_LTX2_ModelWithLoRA_Segmented6": "Apply LoRA to MODEL (LTX-2, segmented: 3 seg × 2 stages)", "IAMCCS_LTX2_FrameRateSync": "LTX-2 FrameRate Sync (int+float)", "IAMCCS_LTX2_Validator": "LTX-2 Validator (16px, 8n +1)", "IAMCCS_LTX2_TimeFrameCount": "LTX-2 TimeFrameCount", + "IAMCCS_LTX2_EnsureFrames8nPlus1": "LTX-2 Ensure Frames (8n + 1)", "IAMCCS_LTX2_ControlPreprocess": "LTX-2 Control Preprocess (aux)", - "IAMCCS_WanImageMotion": "IAMCCS WanImageMotion", + "IAMCCS_LTX2_ImageBatchPadReflect": "LTX-2 Pad Reflect (IMAGE batch)", + "IAMCCS_LTX2_ImageBatchCropByPad": "LTX-2 Crop By Pad (IMAGE batch)", + "IAMCCS_LTX2_ExtensionModule": "LTX-2 Extension Module 🎬", + "IAMCCS_LTX2_ExtensionModule_simple": "LTX-2 Extension Module (simple) 🎬", + "IAMCCS_LTX2_GetImageFromBatch": "LTX-2 Get Images From Batch 🎞️", + "IAMCCS_LTX2_ReferenceImageSwitch": "LTX-2 Reference Image Switch 🧷", + "IAMCCS_LTX2_ReferenceStartFramesInjector": "LTX-2 Inject Reference Into Start Frames 🧬", + "IAMCCS_LTX2_FrameCountValidator": "LTX-2 Frame Count Validator ✅ (8n+1)", + "IAMCCS_WanImageMotion": "WanImageMotion", + + "IAMCCS_SetAutoLink": "Set AutoLink", + "IAMCCS_GetAutoLink": "Get AutoLink", + "IAMCCS_AutoLinkConverter": "AutoLink Converter", + "IAMCCS_AutoLinkArguments": "AutoLink Arguments", + + "IAMCCS_GGUF_accelerator": "GGUF Accelerator (patch_on_device)", } -# Web directory for JavaScript extensions WEB_DIRECTORY = "./web" __all__ = ["NODE_CLASS_MAPPINGS", "NODE_DISPLAY_NAME_MAPPINGS", "WEB_DIRECTORY"] + + +def _iamccs_install_ltx2_vae_encode_autofix() -> None: + """Prevents hard-crash when LTX-2 VAE receives invalid frame counts. + + Lightricks video VAE encode requires a frame count of the form 1 + 8*x. + Some workflows can produce off-by-a-few batches (e.g. 240 instead of 241), + which otherwise raises ValueError and stops execution. + + This patch pads by repeating the last frame up to the next valid count. + Opt-in via IAMCCS_LTX2_VAE_ENCODE_AUTOFIX=1. + """ + + # Default OFF: user requested workflow-level fixes without monkeypatching VAE. + if str(os.getenv("IAMCCS_LTX2_VAE_ENCODE_AUTOFIX", "0")).strip().lower() in {"0", "false", "no", "off"}: + return + + log = logging.getLogger("IAMCCS.LTX2.VAE") + + try: + import torch + except Exception: + return + + try: + from comfy.ldm.lightricks.vae import causal_video_autoencoder as _cvae + except Exception: + # ComfyUI / LTXVideo not installed or import path changed. + return + + cls = getattr(_cvae, "CausalVideoAutoencoder", None) + if cls is None: + return + + orig_encode = getattr(cls, "encode", None) + if orig_encode is None: + return + + if getattr(orig_encode, "__iamccs_ltx2_autofix__", False): + return + + def _round_up_8n1(frames: int) -> int: + frames = int(frames) + if frames <= 1: + return 1 + rem = (frames - 1) % 8 + if rem == 0: + return frames + return frames + (8 - rem) + + def _is_valid_8n1(frames: int) -> bool: + frames = int(frames) + return frames >= 1 and (frames - 1) % 8 == 0 + + def _pad_repeat_last(x: "torch.Tensor", dim: int, pad: int) -> "torch.Tensor": + # Take last slice along `dim` (keeps dimension) and repeat it `pad` times. + slc = [slice(None)] * x.ndim + slc[dim] = slice(-1, None) + last = x[tuple(slc)] + reps = [1] * x.ndim + reps[dim] = int(pad) + last_rep = last.repeat(*reps) + return torch.cat([x, last_rep], dim=dim) + + def _candidate_frame_dims(x: "torch.Tensor") -> list[int]: + # Most common layouts: + # - (B, C, T, H, W) -> frames dim = 2 + # - (T, H, W, C) -> frames dim = 0 (ComfyUI IMAGE batches) + # We only try dims that are >1 and *not obviously channels*. + dims: list[int] = [] + if x.ndim == 5: + # Prefer T, then fallbacks + dims = [2, 0, 1] + elif x.ndim == 4: + dims = [0] + else: + dims = [0] + + out: list[int] = [] + for d in dims: + try: + size = int(x.shape[d]) + except Exception: + continue + if size <= 1: + continue + # Heuristic: channels are usually small (1..4). Don't treat that as frames. + if size in (1, 2, 3, 4) and x.ndim >= 4 and d in (1, 3): + continue + out.append(d) + # Ensure uniqueness, preserve order + seen = set() + unique: list[int] = [] + for d in out: + if d in seen: + continue + seen.add(d) + unique.append(d) + return unique + + def encode_patched(self, pixels_in: "torch.Tensor"): + try: + return orig_encode(self, pixels_in) + except ValueError as e: + msg = str(e) + if "Invalid number of frames" not in msg: + raise + + if not isinstance(pixels_in, torch.Tensor) or pixels_in.ndim < 4: + raise + + # Try padding along the most likely frame dimension(s). + last_err: Exception | None = e + for dim in _candidate_frame_dims(pixels_in): + frames_in = int(pixels_in.shape[dim]) + if _is_valid_8n1(frames_in): + continue + + frames_fixed = _round_up_8n1(frames_in) + pad = frames_fixed - frames_in + if pad <= 0: + continue + + try: + pixels_fixed = _pad_repeat_last(pixels_in, dim=dim, pad=pad) + log.warning( + "[LTX2 VAE encode autofix] Padded frames dim=%d %d -> %d (pad=%d) to satisfy 1+8*x rule", + dim, + frames_in, + frames_fixed, + pad, + ) + return orig_encode(self, pixels_fixed) + except Exception as ee: + last_err = ee + continue + + # If all attempts failed, re-raise the original ValueError. + raise e + + encode_patched.__iamccs_ltx2_autofix__ = True + setattr(cls, "encode", encode_patched) + + +_iamccs_install_ltx2_vae_encode_autofix() diff --git a/assets/autolink.png b/assets/autolink.png new file mode 100644 index 0000000000000000000000000000000000000000..a1b61ef2beb860c42fbc445a5c15e96ebf717a86 GIT binary patch literal 70523 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 literal 0 HcmV?d00001 diff --git a/assets/extension.png b/assets/extension.png new file mode 100644 index 0000000000000000000000000000000000000000..7965fe61e279979468b748c869d39fa0faed8f27 GIT binary patch literal 85282 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 literal 0 HcmV?d00001 diff --git a/assets/gguf.png b/assets/gguf.png new file mode 100644 index 0000000000000000000000000000000000000000..ef0454c50775372a00e9716b58fad20d5d0fb89f GIT binary patch literal 36717 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 frames] --> B[Extension Module] + C[Generation 2
121 frames] --> B + B --> D[extended_images
217 frames] + B --> E[start_images
17 frames 8n+1] + E --> F[Next Generation Input] + + style B fill:#2a363b,stroke:#3f5159,color:#fff + style E fill:#233,stroke:#355,color:#fff +``` + +### Complete Multi-Segment Workflow + +``` +┌─────────────────────────────────────────────────────────────────┐ +│ ITERATIVE EXTENSION LOOP │ +└─────────────────────────────────────────────────────────────────┘ + +Iteration 1: Initial Generation +┌──────────────────┐ +│ Initial Image │ 1 frame +└────────┬─────────┘ + │ + v +┌──────────────────┐ +│ LTX Sampler │ Generate 121 frames +│ (8×15 + 1) │ +└────────┬─────────┘ + │ + v +┌──────────────────┐ +│ VAE Decode │ Latent → Images +└────────┬─────────┘ + │ + v + source_images (121 frames) + │ + └──────────────────────────────────┐ + │ +Iteration 2: First Extension │ +┌──────────────────┐ │ +│ Extension │◄───────────────────────┘ +│ Module │◄── new_images (121 frames from Gen 2) +│ overlap=25 │ +│ mode=linear │ +└────────┬─────────┘ + │ + ├──► extended_images (217 frames) + │ 121 - 25 + 121 = 217 + │ + └──► start_images (17 frames) + 25 → 24 (math: a-1) → 17 (8n+1 conform) + │ + v + ┌──────────────────┐ + │ LTX Sampler │ Gen 3 (121 frames) + │ uses 17 frames │ + └────────┬─────────┘ + │ + v + new_images + │ + └──► Loop continues... + +Final Output: +┌──────────────────┐ +│ Video Segments │ +│ 217 + 217 + ... │ +│ Seamless Concat │ +└──────────────────┘ +``` + +### Internal Processing Flow + +``` +INPUT IMAGES + │ + ├─── source_images (previous generation) + │ │ + │ └─── Last 25 frames ──┐ + │ │ + └─── new_images (current generation) + │ │ + └─── First 25 frames ───┤ + │ + ┌────────v────────┐ + │ OVERLAP ZONE │ + │ 25 frames │ + └────────┬────────┘ + │ + ┌────────v────────┐ + │ BLENDING │ + │ linear_blend │ + │ Alpha: 0→1 │ + └────────┬────────┘ + │ + ┌──────────────────┴──────────────────┐ + │ │ + ┌─────────v─────────┐ ┌──────────v──────────┐ + │ extended_images │ │ start_images │ + │ Full merged batch │ │ For next iteration │ + │ (source-25+new) │ │ With 8n+1 conform │ + └────────────────────┘ └─────────────────────┘ +``` + +--- + +## Parameters Reference + +### Core Parameters + +#### `overlap_frames` (INT) +- **Default**: 10 +- **Range**: 1-256 +- **Recommended**: 25-40 for smooth transitions +- **Purpose**: Number of frames to overlap and blend between segments + +**Impact**: +- **Low (8-15)**: Fast processing, visible seams possible +- **Medium (20-30)**: ✅ **Recommended** - Good balance +- **High (40-60)**: Very smooth, but higher computational cost + +**Formula**: `extended_length = source_count - overlap + new_count` + +Example with overlap=25: +``` +source: [1...121] +new: [1...121] +overlap: 25 frames +extended: 121 - 25 + 121 = 217 frames +``` + +--- + +#### `overlap_side` (DROPDOWN) +- **Options**: `source` | `new_images` +- **Default**: `source` +- **Purpose**: Which batch to take overlap frames from + +``` +overlap_side = "source": + Take last 25 from source + Take first 25 from new + Blend source→new (recommended) + +overlap_side = "new_images": + Take first 25 from new + Take last 25 from source + Blend new→source (reverse) +``` + +**Use Cases**: +- `source`: ✅ **Standard** - Smooth forward progression +- `new_images`: Experimental - reverse blending effect + +--- + +#### `overlap_mode` (DROPDOWN) +- **Options**: `cut` | `linear_blend` | `ease_in_out` | `filmic_crossfade` | `perceptual_crossfade` +- **Default**: `linear_blend` + +### Blending Modes Comparison + +| Mode | Speed | Quality | Use Case | Formula | +|------|-------|---------|----------|---------| +| **cut** | ⚡⚡⚡ | ⭐ | Testing, no blend needed | Direct concatenation | +| **linear_blend** | ⚡⚡ | ⭐⭐⭐⭐ | ✅ **General use** | `(1-t)×src + t×dst` | +| **ease_in_out** | ⚡⚡ | ⭐⭐⭐⭐⭐ | Smooth artistic transitions | `3t² - 2t³` | +| **filmic_crossfade** | ⚡ | ⭐⭐⭐⭐⭐ | Color-accurate blending | Gamma 2.2 correction | +| **perceptual_crossfade** | ⚡ | ⭐⭐⭐⭐⭐ | Best quality (needs Kornia) | LAB color space blend | + +**Visual Comparison**: +``` +Alpha progression over 25 frames: + +linear_blend: +0.0 ████░░░░░░░░░░░░░░░░░░░░ 1.0 + │ │ + Linear interpolation + +ease_in_out: +0.0 ██▓▓▒▒░░░░░░░░░░▒▒▓▓████ 1.0 + │ Slow→Fast→Slow │ + Smooth S-curve + +filmic_crossfade: +0.0 ███▓▓▒▒░░░░░░░░░░▒▓▓███ 1.0 + │ Gamma-corrected │ + Perceptually uniform +``` + +**Recommendations**: +- **General video**: `linear_blend` (fast, reliable) +- **High quality**: `ease_in_out` (smooth, cinematic) +- **Color-critical**: `filmic_crossfade` or `perceptual_crossfade` +- **Testing/Debug**: `cut` (no blending overhead) + +--- + +#### `enable_math` (BOOLEAN) +- **Default**: `true` +- **Purpose**: Enable mathematical operations on overlap value for start_images calculation + +When enabled, applies `math_operation` to calculate the number of frames for `start_images`. + +--- + +#### `math_operation` (DROPDOWN) +- **Options**: `none` | `a-b` | `a-1` | `a+b` | `a*b` | `a/b` | `min(a,b)` | `max(a,b)` +- **Default**: `a-b` +- **Variables**: + - `a` = overlap_frames + - `b` = math_value_b (optional input) + +**Common Use Cases**: + +| Operation | Example | Result | Use Case | +|-----------|---------|--------|----------| +| `none` | overlap=25 | 25 | Direct use of overlap | +| `a-1` | 25-1 | 24 | ✅ **Standard** - LTX-2 workflow | +| `a-b` | 25-15 | 10 | Custom frame count | +| `a/b` | 25/2.5 | 10 | Proportional reduction | + +**Recommended Configuration**: +```json +{ + "overlap_frames": 25, + "enable_math": true, + "math_operation": "a-1" +} +``` +Result: 25 - 1 = 24 frames → 17 frames (after 8n+1 conform) + +--- + +#### `start_frames_rule` (DROPDOWN) +- **Options**: `none` | `ltx2_round_down` | `ltx2_nearest` +- **Default**: `none` +- **Purpose**: Enforce LTX-2 8n+1 rule for VideoVAE encoding + +### LTX-2 Frame Count Rule + +LTX-2 VideoVAE requires frame counts following the formula: **`frames = 8n + 1`** + +Valid frame counts: `1, 9, 17, 25, 33, 41, 49, 57, 65, 73, 81, 89, 97, 105, 113, 121...` + +**Examples**: + +| Input | ltx2_round_down | ltx2_nearest | none | +|-------|----------------|--------------|------| +| 24 | 17 (8×2+1) | 17 (closer) | 24 ❌ | +| 26 | 25 (8×3+1) | 25 (closer) | 26 ❌ | +| 30 | 25 (8×3+1) | 33 (closer) | 30 ❌ | +| 17 | 17 ✅ | 17 ✅ | 17 ✅ | + +**When to Use**: +- ✅ **Always use** `ltx2_round_down` or `ltx2_nearest` when start_images feeds into a sampler +- ❌ **Never use** when output is only for preview/saving (not encoding) + +**Critical**: Without this, you'll get errors like: +``` +Error: Expected frame count 8n+1, got 24 +``` + +--- + +### Advanced Quality Parameters + +#### `color_match_mode` (DROPDOWN) +- **Options**: `none` | `luma_only` | `per_channel` +- **Default**: `none` +- **Purpose**: Match color/exposure of new_images to source_images tail + +**Use Cases**: +- **Lighting changes**: Different segments with varying brightness +- **Color shifts**: Camera auto-balance between shots +- **Consistency**: Maintain uniform look across segments + +``` +none: + source: █████████▓▓▓▓▓ (bright end) + new: ▒▒▒▒▒░░░░░░░░ (dark start) + → Visible seam + +luma_only: + Match overall brightness only + → Quick, preserves color tone + +per_channel: + Match R, G, B independently + → Best quality, may shift colors +``` + +--- + +#### `color_match_strength` (FLOAT) +- **Range**: 0.0-1.0 +- **Default**: 1.0 +- **Purpose**: Blend factor for color matching + +``` +strength = 0.0: No correction +strength = 0.5: Partial correction +strength = 1.0: Full correction +``` + +--- + +#### `seam_search_mode` (DROPDOWN) +- **Options**: `none` | `best_of_k` +- **Default**: `none` +- **Purpose**: Search for optimal seam position within overlap zone + +**How It Works**: +``` +Standard overlap (offset=0): +source: ████████████████████▓▓▓▓▓ +new: ░░░░░░░░░░░░░░░░░ + ↑ Potential seam + +Best-of-k search (k=8): +Tries offsets 0-8: +offset=0: ▓▓▓▓▓ vs ░░░░░ → score: 0.85 +offset=1: ▓▓▓▓▓ vs ░░░░░ → score: 0.72 +offset=2: ▓▓▓▓▓ vs ░░░░░ → score: 0.65 ✅ Best! +... +Chooses offset=2 (lowest discontinuity) +``` + +**Scoring Metrics**: +- Color/luma continuity (weighted by `metric_weight_color`) +- Edge continuity (weighted by `metric_weight_edges`) + +**Trade-offs**: +- ✅ Reduces visible seams +- ✅ Handles motion/camera cuts better +- ❌ Slower (tests k candidates) +- ❌ May "skip" frames from new_images + +--- + +## Usage Scenarios + +### Scenario 1: Standard Video Extension (Recommended) + +**Goal**: Extend a video smoothly without visible seams + +**Configuration**: +```json +{ + "overlap_frames": 25, + "overlap_side": "source", + "overlap_mode": "linear_blend", + "enable_math": true, + "math_operation": "a-1", + "start_frames_rule": "ltx2_round_down", + "color_match_mode": "none", + "seam_search_mode": "none" +} +``` + +**Workflow**: +1. Generate segment 1 (121 frames) +2. Extract last 17 frames (8×2+1) +3. Generate segment 2 with those 17 frames as reference +4. Extension Module merges with 25-frame overlap +5. Repeat + +**Output**: Seamless 217-frame video (then 313, 409, etc.) + +--- + +### Scenario 2: High-Quality Cinematic Extension + +**Goal**: Maximum quality with perceptual blending + +**Configuration**: +```json +{ + "overlap_frames": 40, + "overlap_side": "source", + "overlap_mode": "perceptual_crossfade", + "enable_math": true, + "math_operation": "a-1", + "start_frames_rule": "ltx2_nearest", + "color_match_mode": "per_channel", + "color_match_strength": 0.8, + "seam_search_mode": "best_of_k", + "k_search": 16 +} +``` + +**Best For**: +- Film production +- High-resolution output +- Color-critical content +- Complex lighting scenarios + +--- + +### Scenario 3: Fast Preview / Testing + +**Goal**: Quick iteration, minimal processing + +**Configuration**: +```json +{ + "overlap_frames": 10, + "overlap_side": "source", + "overlap_mode": "cut", + "enable_math": true, + "math_operation": "a-1", + "start_frames_rule": "ltx2_round_down", + "color_match_mode": "none", + "seam_search_mode": "none" +} +``` + +**Best For**: +- Testing prompts +- Workflow debugging +- Quick previews + +--- + +### Scenario 4: Lighting-Corrected Extension + +**Goal**: Handle varying lighting between segments + +**Configuration**: +```json +{ + "overlap_frames": 30, + "overlap_side": "source", + "overlap_mode": "ease_in_out", + "enable_math": true, + "math_operation": "a-1", + "start_frames_rule": "ltx2_round_down", + "color_match_mode": "luma_only", + "color_match_strength": 1.0, + "color_reference_window": 12 +} +``` + +**Best For**: +- Outdoor scenes (sun changes) +- Mixed lighting conditions +- Auto-exposure variations + +--- + +## Advanced Features + +### Two-Stage Overlap Strategy + +Replicating the "early version" workflow behavior with separate overlap values: + +```python +# Early version used: +# - overlap=10 for frame extraction +# - overlap=25 for blending + +# Extension Module equivalent: +{ + "overlap_frames": 25, # For blending + "math_operation": "a/b", # Calculate extraction + "math_value_b": 2.5, # 25/2.5 = 10 + "start_frames_rule": "ltx2_round_down" +} + +# Result: +# - Blending uses 25 frames (smooth) +# - start_images calculated from 10 → 9 → 9 frames (8×1+1) +``` + +--- + +### Custom Frame Count Calculation + +**Example**: Generate 33 frames for next iteration (8×4+1) + +```json +{ + "overlap_frames": 25, + "math_operation": "a+b", + "math_value_b": 9, // 25 + 9 = 34 + "start_frames_rule": "ltx2_round_down" // 34 → 33 +} +``` + +--- + +### Adaptive Overlap with AutoLink + +When using AutoLink for iterative loops: + +```json +{ + "overlap_frames": 25, + "autolink_overlap_in": 0, // Override if > 0 from AutoLink + // ... other params ... +} + +// Extension Module outputs: +// autolink_overlap_out → feeds next iteration's autolink_overlap_in +``` + +--- + +## Troubleshooting + +### Problem: Visible seams between segments + +**Symptoms**: Hard cuts, color shifts, motion jumps + +**Solutions**: +1. ✅ Increase `overlap_frames` to 25-40 +2. ✅ Change to `ease_in_out` or `filmic_crossfade` +3. ✅ Enable `color_match_mode = "luma_only"` +4. ✅ Try `seam_search_mode = "best_of_k"` with `k_search = 8` + +--- + +### Problem: Error "Expected 8n+1 frames" + +**Symptoms**: Workflow fails at sampler/encoder + +**Solutions**: +1. ✅ Set `start_frames_rule = "ltx2_round_down"` +2. ✅ Verify `enable_math = true` +3. ✅ Check math formula produces reasonable values +4. ❌ Don't use `start_frames_rule` if output is for preview only + +--- + +### Problem: Videos too long / memory issues + +**Symptoms**: Out of memory, slow processing + +**Solutions**: +1. ✅ Reduce `overlap_frames` to 15-20 +2. ✅ Use `overlap_mode = "linear_blend"` (faster) +3. ✅ Disable `seam_search_mode` +4. ✅ Process in smaller batches + +--- + +### Problem: Color mismatch at seams + +**Symptoms**: Brightness/hue shifts visible + +**Solutions**: +1. ✅ Enable `color_match_mode = "per_channel"` +2. ✅ Set `color_match_strength = 0.8-1.0` +3. ✅ Increase `color_reference_window` to 16-24 +4. ✅ Use `filmic_crossfade` for gamma-correct blending + +--- + +## Best Practices + +### 1. Start with Recommended Defaults + +```json +{ + "overlap_frames": 25, + "overlap_side": "source", + "overlap_mode": "linear_blend", + "enable_math": true, + "math_operation": "a-1", + "start_frames_rule": "ltx2_round_down", + "color_match_mode": "none", + "seam_search_mode": "none" +} +``` + +Then optimize based on your specific needs. + +--- + +### 2. Overlap Guidelines by Content Type + +| Content Type | Overlap | Blend Mode | Reason | +|--------------|---------|------------|--------| +| **Static scenes** | 15-20 | linear_blend | Less motion, simpler blend | +| **Camera movement** | 25-40 | ease_in_out | Smooth motion transition | +| **Fast action** | 30-50 | filmic_crossfade | Avoid motion artifacts | +| **Talking heads** | 20-30 | linear_blend | Consistent framing | +| **Nature/landscape** | 25-35 | perceptual_crossfade | Color accuracy | + +--- + +### 3. Processing Order + +Always follow this order in your workflow: + +``` +1. Initial Image + ↓ +2. LTX Sampler (8n+1 frames) + ↓ +3. VAE Decode + ↓ +4. Extension Module + ├─→ extended_images (for final output) + └─→ start_images (for next iteration) + ↓ +5. Loop back to step 2 +``` + +**Critical**: Never feed `extended_images` back into the sampler directly - always use `start_images` (conformant to 8n+1). + +--- + +### 4. Testing Workflow + +Before full production: + +1. Test with `overlap=10`, `mode=cut` (fast preview) +2. Verify no errors with `start_frames_rule = "ltx2_round_down"` +3. Increase overlap to 25, switch to `linear_blend` +4. Fine-tune with quality features if needed + +--- + +### 5. Output Validation + +Check the `report` output for each iteration: + +``` +Source: 121 frames | +Overlap (effective): 25 frames | +Start range: start_index=96, num_frames=17 | +Math: a-1 | +Start frames rule: ltx2_round_down | +Extended: 217 frames | +Extension delta: +96 frames | +Blend mode: linear_blend +``` + +Verify: +- ✅ `num_frames` is 8n+1 (9, 17, 25, 33, etc.) +- ✅ `Extension delta` is positive +- ✅ No warnings in console + +--- + +## Performance Optimization + +### Memory Usage + +| Configuration | Memory Impact | Speed | +|---------------|---------------|-------| +| overlap=10, cut | Low | ⚡⚡⚡ | +| overlap=25, linear | Medium | ⚡⚡ | +| overlap=40, ease_in_out | Medium-High | ⚡⚡ | +| overlap=40, perceptual + seam search | High | ⚡ | + +--- + +### Batch Processing Tips + +For very long videos (10+ segments): + +1. **Save intermediate results**: + ``` + Segment 1 → Save + Segment 2 → Save + ... + Final concatenation separately + ``` + +2. **Use progressive overlap**: + ``` + Segments 1-3: overlap=25 (quality) + Segments 4+: overlap=15 (speed) + ``` + +3. **Monitor VRAM**: + - Each 121-frame batch ≈ 4-8GB VRAM + - Reduce resolution if needed + +--- + +## Workflow Diagrams + +### Complete Extension Pipeline + +``` +┌────────────────────────────────────────────────────────────────┐ +│ INITIALIZATION │ +└────────────────────────────────────────────────────────────────┘ + +┌─────────────┐ ┌─────────────┐ ┌─────────────┐ +│ Load Model │────>│ Load VAE │────>│ Load CLIP │ +└─────────────┘ └─────────────┘ └─────────────┘ + │ │ │ + └───────────────────┴───────────────────┘ + │ + v +┌────────────────────────────────────────────────────────────────┐ +│ GENERATION LOOP START │ +└────────────────────────────────────────────────────────────────┘ + +Iteration N: +┌─────────────┐ +│ start_images│ (17 frames, 8×2+1) +│ from prev │ +└──────┬──────┘ + │ + v +┌─────────────────────────────────────────────────────────────┐ +│ SUBGRAPH: Samplers │ +│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │ +│ │ VAE Encode │────>│ LTX Sampler │────>│ VAE Decode │ │ +│ │ (to latent) │ │ (121 frames)│ │ (to images) │ │ +│ └─────────────┘ └─────────────┘ └─────────────┘ │ +└─────────────────────────────────────────────────────────────┘ + │ + v + new_images (121 frames) + │ + └──────────────────────────┐ + │ +┌─────────────────────────────────v──────────────────────────┐ +│ Extension Module │ +│ │ +│ source_images (121) + new_images (121) │ +│ │ │ +│ v │ +│ ┌────────────────────────┐ │ +│ │ Overlap Extraction │ │ +│ │ Last 25 from source │ │ +│ │ First 25 from new │ │ +│ └────────┬───────────────┘ │ +│ │ │ +│ v │ +│ ┌────────────────────────┐ │ +│ │ Blending │ │ +│ │ Mode: linear_blend │ │ +│ │ Alpha: 0→1 over 25 │ │ +│ └────────┬───────────────┘ │ +│ │ │ +│ v │ +│ ┌────────────────────────┐ │ +│ │ Concatenation │ │ +│ │ [prefix][blend][suffix]│ │ +│ └────────┬───────────────┘ │ +│ │ │ +│ ├─────────────────────────────┐ │ +│ │ │ │ +│ v v │ +│ extended_images (217) start_images (17, 8n+1) │ +│ │ │ │ +└───────────┼─────────────────────────────┼─────────────────┘ + │ │ + v └─> Next Iteration + ┌───────────────┐ + │ CreateVideo │ + │ Concatenate │ + │ with Audio │ + └───────┬───────┘ + │ + v + ┌───────────────┐ + │ SaveVideo │ + │ Final Output │ + └───────────────┘ +``` + +--- + +### Overlap Blending Visualization + +``` +Source Batch (121 frames): +[████████████████████████████████████████████████████▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓] + └─ Last 25 frames ─┘ + +New Batch (121 frames): + [░░░░░░░░░░░░░░░░░░░░░░░░░████████████████████████████████████████████████████] + └─ First 25 frames ─┘ + +Blending Zone (25 frames with linear alpha): +Frame: 1 2 3 4 5 ... 23 24 25 +Alpha: 0.00 0.04 0.08 0.12 0.16 ... 0.92 0.96 1.00 + ████ ███▓ ███▒ ██▒░ ██░░ ... ░▒██ ░▓███ ░███ + +Blended: (1-α)×source + α×new + +Extended Result (217 frames): +[████████████████████████████████████████████████████▓▓▒▒░░████████████████████████████████████████████████████████████████████] + └─ Smooth transition ─┘ +``` + +--- + +## Conclusion + +The Extension Module provides a powerful, flexible solution for iterative video generation with LTX-2. Key takeaways: + +1. **Always use 8n+1 conformance** (`ltx2_round_down`) when feeding samplers +2. **Start with overlap=25** and `linear_blend` for best results +3. **Enable quality features** (color match, seam search) only when needed +4. **Monitor the report output** to verify correct operation +5. **Test with simple configs first**, then optimize + +For support and updates, see the [IAMCCS-nodes repository](https://github.com/IAMCCS/IAMCCS-nodes). + +--- + +*Document Version: 1.0* +*Last Updated: January 2026* +*Extension Module Version: 87665e5* diff --git a/docs/LTX2_EXTENSION_NODES_GUIDE_EN.md b/docs/LTX2_EXTENSION_NODES_GUIDE_EN.md new file mode 100644 index 0000000..795ee80 --- /dev/null +++ b/docs/LTX2_EXTENSION_NODES_GUIDE_EN.md @@ -0,0 +1,394 @@ +# IAMCCS LTX-2 Extension Nodes — Final Guide (EN) + +This document explains how to use the IAMCCS LTX-2 nodes for **long-length / multi-segment video generation and extension** in ComfyUI, including the purpose of each widget and recommended usage patterns. + +## What problems these nodes solve + +1. **Seam artifacts between segments** (visible cut, flicker, exposure shift) +2. **Bad seam position** (the extension starts at an awkward frame) +3. **LTX VideoVAE frame-count constraint**: some encode paths require the number of guide frames to be of the form: + +$$N = 1 + 8k$$ + +4. **Workflow simplification**: reduce reliance on multiple helper nodes for overlap math, ranges, etc. + +--- + +## Quick decision guide (what to touch first) + +### If you get the LTX VideoVAE error: “Encode input must have 1 + 8 * x frames” + +This is the **`8n+1`** rule: the number of frames going into certain LTX/LTXV encode paths must be: + +$$N = 1 + 8k$$ + +In iterative extension workflows, this usually affects **the guide/start frames** you feed into the next segment. + +Use these fixes in this order: + +1) **Set `safe_mode = native_workflow_safe`** in `IAMCCS_LTX2_ExtensionModule` + - This extracts the start frames exactly like the original stable workflow: + - `start_images = extended_images[-overlap_frames:-1]` + +2) If you still need a strict `8n+1` count, set **`start_frames_rule`**: + - `ltx2_round_down`: most predictable and “never increases” the frame count. + - `ltx2_nearest`: useful if you want the closest valid count (may go up or down). + +3) If the frame rule is needed elsewhere (not on the extension module), use `IAMCCS_LTX2_FrameCountValidator` on the integer driving that node. + +### If the seam is visible (hard cut / flicker) + +- Start with: + - `overlap_mode = ease_in_out` (or `linear_blend` if you want the simplest behavior) + - keep overlap modest (common range: ~8–24 frames; larger overlap can help but costs compute/time) + +### If exposure/white balance shifts at the seam + +- Enable color matching: + - `color_match_mode = luma_only` (usually the safest) + - `color_match_strength = 0.3..0.7` + - `color_reference_window = 6..12` + +### If the seam “restarts weirdly” (bad timing / rewind) + +- Use seam search (try only after overlap/blend): + - `seam_search_mode = best_of_k` + - `k_search = 8..24` + +### If you are using AutoLink overlap loops + +- Prefer wiring `autolink_overlap_in` / `autolink_overlap_out` so each iteration can override overlap cleanly. + +### About `IAMCCS_LTX2_ExtensionModule_simple` + +- `IAMCCS_LTX2_ExtensionModule_simple` is the **minimal** variant of the Extension Module. +- It exposes only the core overlap/blend/math widgets (no color match, seam search, metrics). +- It does **not** expose `safe_mode` or `start_frames_rule` as widgets. +- It **always enforces** the LTX-2 start-frame rule $N = 1 + 8k$ automatically (round-down), to avoid VideoVAE encode frame-count errors. + +## Nodes overview + +- `IAMCCS_LTX2_ExtensionModule` + - Merges the previous segment (`source_images`) with the new segment (`new_images`) using overlap/blend. + - Outputs `extended_images` (merged batch) and `start_images` (frames used to guide the next segment). + - Optional seam improvements: exposure/color matching and best-of-k seam selection. + - Optional “native safe” extraction that matches the original stable workflow behavior. + - Optional AutoLink overlap loop I/O: + - `autolink_overlap_in` (override overlap when > 0) + - `autolink_overlap_out` (feed the next iteration) + +- `IAMCCS_LTX2_GetImageFromBatch` + - Extracts frames from the start/end of an image batch, or by an explicit range. + - Adds optional auto-count and diagnostics outputs. + - Optional “native safe” mode matching `images[-count:-1]` in from-end mode. + +- `IAMCCS_LTX2_ReferenceImageSwitch` + - Safe way to inject a **reference image** to improve identity/style consistency **without breaking overlap continuity**. + - Default is `none`, so existing workflows are unchanged. + +- `IAMCCS_LTX2_ReferenceStartFramesInjector` + - (New) Injects/blends the reference directly into the **guide/conditioning frames** (`start_images` / segment `images`). + - Useful when feeding the reference into `image_1` (empty latent image) has **weak or no identity effect**. + - Can be applied to **only one segment** (e.g. segment 3 only). + +- `IAMCCS_LTX2_FrameCountValidator` + - Helper to validate/correct an integer frame count to the `1 + 8*k` rule. + +--- + +## 1) IAMCCS_LTX2_ExtensionModule + +### Inputs + +**Required** + +- `source_images` (IMAGE) + - The current accumulated batch (previous segment output). +- `overlap_frames` (INT) + - How many frames overlap between segments. +- `overlap_side` (dropdown) + - `source`: overlap uses the tail of `source_images` against the head of `new_images`. + - `new_images`: swaps which side is treated as source/destination for blending. +- `overlap_mode` (dropdown) + - `cut`: hard cut (fastest, most visible seam). + - `linear_blend`: linear crossfade. + - `ease_in_out`: smoother crossfade. + - `filmic_crossfade`: gamma-aware blend (often smoother in highlights). + - `perceptual_crossfade`: LAB blend via Kornia (falls back if Kornia not installed). +- `enable_math` (BOOLEAN) + - Enables the built-in “how many start frames to output” calculation. +- `math_operation` (dropdown) + - Applies to `overlap_frames` (as `a`) and `math_value_b` (as `b`) when computing how many frames to output as `start_images`. + - Typical: `a-b` or `a-1`. + +**Safety / LTX rule** + +- `safe_mode` (dropdown) + - `none`: uses the node’s normal start-images logic. + - `native_workflow_safe`: extracts start images exactly like the proven stable graph: + - `start_images = extended_images[-overlap_frames:-1]` + - Use this if you are hitting the LTX VideoVAE error “Encode input must have 1 + 8 * x frames”. + +- `start_frames_rule` (dropdown) + - `none`: do not modify the calculated number of start frames. + - `ltx2_round_down`: force the count down to the nearest valid `1 + 8*k`. + - `ltx2_nearest`: choose the nearest valid `1 + 8*k` within bounds. + - Use this when a downstream node (VideoVAE encode/guide) requires `1 + 8*k` frame counts. + +**Quality upgrades (defaults are safe/off)** + +- `color_match_mode` (dropdown) + - `none`: no change (original behavior). + - `luma_only`: match exposure/contrast on luma. + - `per_channel`: match mean/std per RGB channel. +- `color_match_strength` (FLOAT 0..1) + - Blend between original and matched. +- `color_reference_window` (INT) + - Number of frames used from tail/head for statistics. + +- `seam_search_mode` (dropdown) + - `none`: no seam search. + - `best_of_k`: search for a better seam by testing candidate offsets. +- `k_search` (INT) + - How many candidate offsets to test (0 disables). +- `metric_weight_color` (FLOAT) + - Weight of luma continuity in the seam score. +- `metric_weight_edges` (FLOAT) + - Weight of edge continuity in the seam score. + +**Optional** + +- `new_images` (IMAGE) + - The newly generated segment. + - If omitted, the node can be used as a “prep” node (it will still output `start_images` from the current batch). +- `math_value_b` (INT) + - Used by `math_operation`. + +### Outputs + +- `source_images` (IMAGE) — passthrough +- `start_images` (IMAGE) — frames to feed as guide for the next segment +- `extended_images` (IMAGE) — merged batch +- `overlap_frames` (INT) +- `calculated_frames` (INT) — actual number of frames output in `start_images` +- `extension_frames` (INT) — how many frames were added +- `report` (STRING) + +### Recommended settings + +- Most stable: `safe_mode = native_workflow_safe`, `overlap_mode = ease_in_out` (or `linear_blend`) +- If you see exposure shift: `color_match_mode = luma_only`, `strength = 0.3..0.7` +- If you see weird seam timing: `seam_search_mode = best_of_k`, `k_search = 8..24` + +--- + +## 2) IAMCCS_LTX2_GetImageFromBatch + +### Purpose +A small helper to extract frames for the next segment or for debugging. + +### Inputs + +- `images` (IMAGE) +- `mode` (dropdown) + - `from_start`: take the first `count` frames + - `from_end`: take the last `count` frames + - `range`: take `[start_index:end_index)` +- `count` (INT) + +**Upgrades** + +- `auto_count_mode` (dropdown) + - `none`: use `count` widget. + - `prefer_input`: use `count_in` if connected. + - `use_widget`: explicitly use the widget value. +- `diagnostics` (dropdown) + - `none`: normal behavior. + - `basic`: exposes `start_index` and `end_index` outputs. + +**Safety / LTX rule** + +- `count_rule` (dropdown) + - `none` / `ltx2_round_down` / `ltx2_nearest` for `1 + 8*k`. +- `safe_mode` (dropdown) + - `none`: normal extraction. + - `native_workflow_safe`: for `from_end` uses `images[-count:-1]`. + +**Optional** + +- `count_in` (INT) +- `start_index` / `end_index` (INT) for `range` mode. + +### Outputs + +- `images` (IMAGE) +- `count` (INT) +- `report` (STRING) +- `start_index`, `end_index` (INT) + +--- + +## 3) IAMCCS_LTX2_ReferenceImageSwitch + +### Why this node exists +In long-length generation, you typically want: +- **Continuity** driven by overlap/start frames +- **Identity/style consistency** reinforced by a stable reference image + +This node lets you add a reference image **without replacing** the overlap continuity input. + +### Inputs + +- `default_image` (IMAGE) + - What the workflow already used before (pass-through by default). +- `mode` (dropdown) + - `none`: output `default_image` (fully backward-compatible). + - `use_reference`: output `reference_image`. + - `blend`: output mix of `default_image` and `reference_image`. +- `blend_strength` (FLOAT) + - Only for `blend` mode. +- `reference_image` (optional IMAGE) + - If not connected, the node behaves like `none`. + +### Output + +- `image` (IMAGE) +- `report` (STRING) + +### Practical usage + +- Insert it on the **auxiliary** image input of your segment sampler (often called `image_1`). +- Keep overlap/start frames connected exactly as before. +- If you enable `use_reference`/`blend`, the reference is **automatically resized** to match `default_image` (more stable for downstream nodes). + +Note: in many LTX/LTXV workflows, feeding the reference into `image_1` (empty latent image) may not be enough to “lock” identity when a face is revealed later in the segment. In that case, use the node below. + +--- + +## 3b) IAMCCS_LTX2_ReferenceStartFramesInjector + +### Why it exists +If identity drifts even with a reference, it often means the reference is connected to an input that the model barely uses. This node modifies the actual guide/conditioning frames. + +### Inputs + +- `start_images` (IMAGE) + - The guide frames that feed the segment (typically `start_images` from the extension module, or the sampler’s `images` input). +- `mode` + - `none`: passthrough. + - `inject`: replaces the selected frames with the reference. + - `blend`: mixes reference and original frames. +- `blend_strength` (0..1) + - Only used for `blend` (0 = no effect, 1 = full reference). In `inject` it behaves like 1. +- `frames_to_inject` (INT) + - How many guide frames to modify. +- `ramp` (BOOLEAN) + - If `true`, applies a gradual ramp across the injected frames. +- `position` + - `tail`: last K frames (usually best, closest to the seam). + - `head`: first K frames. +- `reference_image` (optional IMAGE) + - Usually the output of `IAMCCS_LTX2_ReferenceImageSwitch`. + +### Outputs + +- `start_images` (IMAGE) +- `report` (STRING) + +### Recommended starter settings + +- If identity is not sticking but you want to preserve continuity: + - `mode = blend` + - `frames_to_inject = 3..6` + - `blend_strength = 0.5..0.85` + - `ramp = true` + - `position = tail` + +If you see seam discontinuity, lower `blend_strength` and/or reduce `frames_to_inject`. + +--- + +## How to decide when/where to use a reference + +Quick checklist: + +1. **Is the face/identity visible in the first frames of the segment?** + - Yes → a reference can work well. + - No (reveal happens mid/late segment) → the reference may have little leverage: consider cutting segments so the reveal starts at the segment boundary, or use `ReferenceStartFramesInjector` (and/or dedicated tools like FaceID/IPAdapter if compatible). + +2. **What are you stabilizing?** + - Style / global look → `ReferenceImageSwitch` (or `color_match_mode` in ExtensionModule) is often enough. + - Identity (specific face) → `ReferenceStartFramesInjector` is more likely required. + +3. **Where to wire it?** + - `image_1` / empty latent image: can be a hint, not guaranteed. + - `images` / start frames (conditioning): highest impact. + +4. **How to limit it to one segment (e.g. segment 3 only)** + - Place `ReferenceStartFramesInjector` only in the path feeding that segment’s `images` / `start_images`. + - Leave other segments untouched (no injector). + +## 4) IAMCCS_LTX2_FrameCountValidator + +### Inputs + +- `frame_count` (INT) +- `auto_correct` (BOOLEAN) +- `correction_mode` (`nearest` / `round_up` / `round_down`) + +### Outputs + +- `validated_count` (INT) +- `is_valid` (BOOLEAN) +- `nearest_valid` (INT) +- `report` (STRING) + +--- + +## Common workflows / use cases + +### A) Long-length extension (multi segment) +1. Generate segment 1. +2. Use `IAMCCS_LTX2_ExtensionModule` to compute `start_images` and merge segments. +3. Feed `start_images` into the next segment guide/conditioning. +4. Repeat. + +Recommended: enable `safe_mode = native_workflow_safe` if you see LTX frame-count errors. + +### B) Reduce seams +- Prefer `ease_in_out` or `filmic_crossfade`. +- Use `color_match_mode` if you see exposure shifts. +- Use `best_of_k` seam search if the seam starts at a bad moment. + +### C) Improve identity consistency +- Add `IAMCCS_LTX2_ReferenceImageSwitch` to `image_1`. +- Connect a single reference image and set mode to `blend` (start at 0.2..0.4). + +--- + +## Troubleshooting + +- **“IAMCCS_LTX2_ReferenceImageSwitch not found”** + - Ensure you updated the IAMCCS nodes and restart ComfyUI. + - The node must be exported in the package registry (`__init__.py`). + +- **“Encode input must have 1 + 8 * x frames”** + - Use `safe_mode = native_workflow_safe` or set `start_frames_rule/count_rule` to enforce `1 + 8*k`. + +- **Border motion artifacts (edge warping / flicker)** + - Note: `metric_weight_edges` and `best_of_k` improve seam selection *inside the overlap* between segments; they do not automatically “fix” frame borders. + - Common improvements: + - Avoid changing resize/crop between segments; keep one resolution end-to-end. + - Prefer “clean” resolutions (multiples of 64 where possible) to reduce VAE boundary artifacts. + - Quick workaround: apply a small crop (e.g., 8–16 px per side) then resize back. + - Helpful nodes (IAMCCS): + - `IAMCCS_LTX2_ImageBatchPadReflect`: adds a reflect border (increases resolution). + - `IAMCCS_LTX2_ImageBatchCropByPad`: removes that border (back to target resolution). + - Recommended usage (when you want the model to have more border context): + - Pick `pad_x/pad_y` (e.g., 16). + - Generate at a higher resolution: `W_pad = W + 2*pad_x`, `H_pad = H + 2*pad_y` (including `EmptyImage`). + - If you have “initial”/reference images at the old resolution, run them through `PadReflect` to reach `W_pad x H_pad`. + - At the end (before `CreateVideo`), run `CropByPad` with the same `pad_x/pad_y` to return to `W x H`. + +- **Reference image causes a resolution error** + - Resize/crop the reference to match your workflow resolution before feeding it. diff --git a/WanImageMotion.md b/docs/WanImageMotion.md similarity index 100% rename from WanImageMotion.md rename to docs/WanImageMotion.md diff --git a/iamccs_autolink.py b/iamccs_autolink.py new file mode 100644 index 0000000..0a39693 --- /dev/null +++ b/iamccs_autolink.py @@ -0,0 +1,76 @@ +# IAMCCS AutoLink - Wireless node connections +# 1:1 copy of KJ nodes functionality (Set/Get/Converter) + +# Questi nodi sono PURAMENTE FRONTEND - non fanno nulla in Python +# Tutta la logica è in JavaScript + +class IAMCCS_SetAutoLink: + """Set AutoLink - Virtual node (frontend only)""" + + @classmethod + def INPUT_TYPES(cls): + return { + "required": {}, + } + + RETURN_TYPES = () + FUNCTION = "noop" + CATEGORY = "IAMCCS/AutoLink" + + def noop(self): + # Questo non viene mai eseguito - il nodo è virtuale + return () + + +class IAMCCS_GetAutoLink: + """Get AutoLink - Virtual node (frontend only)""" + + @classmethod + def INPUT_TYPES(cls): + return { + "required": {}, + } + + RETURN_TYPES = () + FUNCTION = "noop" + CATEGORY = "IAMCCS/AutoLink" + + def noop(self): + # Questo non viene mai eseguito - il nodo è virtuale + return () + + +class IAMCCS_AutoLinkConverter: + """AutoLink Converter - UI tool""" + + @classmethod + def INPUT_TYPES(cls): + return { + "required": {}, + "optional": { + "arg": ("AUTOLINK_ARG",), + } + } + + RETURN_TYPES = () + FUNCTION = "noop" + CATEGORY = "IAMCCS/AutoLink" + + def noop(self, arg=None): + return () + + +class IAMCCS_AutoLinkArguments: + """AutoLink Arguments - Configuration node""" + + @classmethod + def INPUT_TYPES(cls): + return {"required": {}} + + RETURN_TYPES = ("AUTOLINK_ARG",) + FUNCTION = "noop" + CATEGORY = "IAMCCS/AutoLink" + + def noop(self): + return (None,) + diff --git a/iamccs_gguf_accelerator.py b/iamccs_gguf_accelerator.py new file mode 100644 index 0000000..bc2539a --- /dev/null +++ b/iamccs_gguf_accelerator.py @@ -0,0 +1,277 @@ +from __future__ import annotations + +import logging +from typing import Any, Tuple + +import torch + + +_log = logging.getLogger("IAMCCS.GGUF.Accelerator") + + +def _move_to_device_recursive(obj: Any, device: torch.device) -> Tuple[Any, int, int]: + """Recursively move torch.Tensor objects inside obj to device. + + Returns (new_obj, tensor_count_moved, bytes_moved). + """ + if isinstance(obj, torch.Tensor): + try: + # If already on target device, do nothing. + if obj.device == device: + return obj, 0, 0 + moved = obj.to(device, non_blocking=True) + bytes_moved = moved.element_size() * moved.numel() + return moved, 1, bytes_moved + except Exception: + # If move fails (rare), keep original. + return obj, 0, 0 + + if isinstance(obj, tuple): + out_items: list[Any] = [] + moved_count = 0 + moved_bytes = 0 + changed = False + for item in obj: + new_item, c, b = _move_to_device_recursive(item, device) + out_items.append(new_item) + moved_count += c + moved_bytes += b + changed = changed or (new_item is not item) + return (tuple(out_items) if changed else obj), moved_count, moved_bytes + + if isinstance(obj, list): + out_list: list[Any] = [] + moved_count = 0 + moved_bytes = 0 + changed = False + for item in obj: + new_item, c, b = _move_to_device_recursive(item, device) + out_list.append(new_item) + moved_count += c + moved_bytes += b + changed = changed or (new_item is not item) + return (out_list if changed else obj), moved_count, moved_bytes + + if isinstance(obj, dict): + out_dict: dict[Any, Any] = {} + moved_count = 0 + moved_bytes = 0 + changed = False + for k, v in obj.items(): + new_v, c, b = _move_to_device_recursive(v, device) + out_dict[k] = new_v + moved_count += c + moved_bytes += b + changed = changed or (new_v is not v) + return (out_dict if changed else obj), moved_count, moved_bytes + + return obj, 0, 0 + + +def _normalize_device(value: Any, fallback: torch.device) -> torch.device: + if value is None: + return fallback + try: + return torch.device(value) + except Exception: + return fallback + + +def _cuda_device_index(device: torch.device) -> int | None: + if device.type != "cuda": + return None + if device.index is not None: + return int(device.index) + try: + return int(torch.cuda.current_device()) + except Exception: + return 0 + + +def _cuda_mem_info_mb(device: torch.device) -> tuple[float, float] | None: + if not torch.cuda.is_available() or device.type != "cuda": + return None + try: + idx = _cuda_device_index(device) + if idx is None: + return None + free_b, total_b = torch.cuda.mem_get_info(idx) + return (float(free_b) / (1024 * 1024), float(total_b) / (1024 * 1024)) + except Exception: + return None + + +def _cuda_gc() -> None: + if not torch.cuda.is_available(): + return + try: + torch.cuda.empty_cache() + except Exception: + pass + try: + torch.cuda.ipc_collect() + except Exception: + pass + + +class IAMCCS_GGUF_accelerator: + """GGUF accelerator: forces patch_on_device to reduce per-step CPU↔GPU patch movement. + + Intended for GGUF UNet models with LoRA patches where repeatedly moving patch tensors to GPU + can dominate runtime on low VRAM setups. + + Notes: + - This node does not change sampling parameters. + - It can increase VRAM usage depending on how many/large LoRA patches are present. + """ + + @classmethod + def INPUT_TYPES(cls): + return { + "required": { + "model": ("MODEL",), + "mode": (["auto_oom_safe", "manual"], { + "default": "auto_oom_safe", + "tooltip": "auto_oom_safe: tries patch_on_device+eager move, falls back to offload on OOM | manual: use toggles below" + }), + "patch_on_device": ("BOOLEAN", {"default": True}), + "move_patches_now": ("BOOLEAN", { + "default": True, + "tooltip": "If enabled, attempts to pre-move patch tensors to the model load_device to reduce runtime transfers. Can increase VRAM usage." + }), + "min_free_vram_mb": ("INT", { + "default": 1500, + "min": 0, + "max": 65536, + "step": 64, + "tooltip": "(auto_oom_safe) If free VRAM is below this, we disable patch_on_device to reduce OOM risk. 0 disables the check." + }), + "oom_fallback": ("BOOLEAN", { + "default": True, + "tooltip": "If a CUDA OOM happens while moving patches, automatically switches to offload and continues." + }), + } + } + + RETURN_TYPES = ("MODEL", "STRING") + RETURN_NAMES = ("model", "report") + FUNCTION = "accelerate" + CATEGORY = "IAMCCS/Optimize" + + def accelerate(self, model, mode: str, patch_on_device: bool, move_patches_now: bool, min_free_vram_mb: int, oom_fallback: bool): + # Clone to avoid mutating upstream graph state. + try: + model_out = model.clone() + except Exception: + model_out = model + + applied = [] + + mode = str(mode or "auto_oom_safe") + + # Decide devices. + load_device = _normalize_device( + getattr(model_out, "load_device", None), + torch.device("cuda" if torch.cuda.is_available() else "cpu"), + ) + offload_device = _normalize_device(getattr(model_out, "offload_device", None), torch.device("cpu")) + + # auto mode chooses patch strategy based on VRAM headroom. + chosen_patch_on_device = bool(patch_on_device) + chosen_move_now = bool(move_patches_now) + + if mode == "auto_oom_safe" and load_device.type == "cuda": + info = _cuda_mem_info_mb(load_device) + if info is not None and int(min_free_vram_mb) > 0: + free_mb, total_mb = info + if free_mb < float(min_free_vram_mb): + chosen_patch_on_device = False + chosen_move_now = False + applied.append(f"auto:disable_patch_on_device(free≈{free_mb:.0f}MiB None: + nonlocal moved_tensors, moved_bytes + patches = getattr(model_out, "patches") + new_patches, moved_tensors, moved_bytes = _move_to_device_recursive(patches, target_device) + if new_patches is not patches: + setattr(model_out, "patches", new_patches) + + # If patch_on_device is disabled, try to ensure patches live on offload_device (CPU) + # to reduce persistent VRAM usage. + if not bool(chosen_patch_on_device) and hasattr(model_out, "patches"): + try: + target_device = offload_device + _try_move_patches() + applied.append("model.patches(move_to_offload_device)") + except Exception: + pass + + # 3) Optional: eagerly move patch tensors to load_device. + # This mirrors what GGUF's `move_patch_to_device` would do later, but doing it once + # can eliminate huge per-layer overhead. + if bool(chosen_patch_on_device) and bool(chosen_move_now) and hasattr(model_out, "patches"): + try: + target_device = load_device + _cuda_gc() + _try_move_patches() + applied.append("model.patches(move_to_load_device)") + except RuntimeError as e: + msg = str(e).lower() + is_oom = ("out of memory" in msg) or ("cuda" in msg and "memory" in msg) + if bool(oom_fallback) and is_oom: + _log.warning("OOM while moving patches to CUDA; falling back to offload. Error: %s", e) + _cuda_gc() + try: + setattr(model_out, "patch_on_device", False) + applied.append("fallback:disable_patch_on_device") + except Exception: + pass + # Also attempt to move patches back to CPU/offload_device. + try: + target_device = offload_device + _try_move_patches() + applied.append("fallback:move_patches_to_offload_device") + except Exception: + pass + else: + _log.warning("Failed to move patches to device: %s", e) + except Exception as e: + _log.warning("Failed to move patches to device: %s", e) + + mb = moved_bytes / (1024 * 1024) if moved_bytes else 0.0 + mem_info = _cuda_mem_info_mb(load_device) if load_device.type == "cuda" else None + mem_str = "" + if mem_info is not None: + free_mb, total_mb = mem_info + mem_str = f" | cuda_free≈{free_mb:.0f}/{total_mb:.0f} MiB" + report = ( + f"mode={mode} | patch_on_device={bool(getattr(model_out, 'patch_on_device', chosen_patch_on_device))} | " + f"move_patches_now={bool(chosen_move_now)} | load_device={load_device} | offload_device={offload_device}{mem_str} | " + f"applied={applied or ['(none)']} | moved_tensors={moved_tensors} | moved≈{mb:.1f} MiB" + ) + + return (model_out, report) diff --git a/iamccs_ltx2_extension_module.py b/iamccs_ltx2_extension_module.py new file mode 100644 index 0000000..e3fabf1 --- /dev/null +++ b/iamccs_ltx2_extension_module.py @@ -0,0 +1,1161 @@ +# iamccs_ltx2_extension_module.py +# =============================================================== +# IAMCCS LTX-2 Extension Module +# All-in-one node for LTX-2 video extension workflows +# Combines: Image batch extension, overlap management, math operations +# =============================================================== + +from __future__ import annotations + +import logging +import math +from typing import Optional, Tuple + +import torch +import torch.nn.functional as F + + +_log = logging.getLogger("IAMCCS.LTX2.ExtensionModule") + + +class IAMCCS_LTX2_ExtensionModule: + """ + All-in-one extension module for LTX-2 video generation workflows. + Combines image batch extension with overlap, math operations, and frame calculations. + + Features: + - Automatic overlap frame calculation with configurable modes + - Multiple blending modes (linear, ease_in_out, filmic, perceptual) + - Built-in math operations for frame calculations + - Start images extraction for next generation pass + - Compatible with iterative video extension workflows + """ + + @classmethod + def INPUT_TYPES(cls): + return { + "required": { + # Source images (from previous generation or initial frames) + "source_images": ("IMAGE", { + "tooltip": "The source images to extend (from previous generation)" + }), + + # Overlap configuration + "overlap_frames": ("INT", { + "default": 10, + "min": 1, + "max": 256, + "step": 1, + "tooltip": "Number of overlapping frames between batches" + }), + + "overlap_side": (["source", "new_images"], { + "default": "source", + "tooltip": "Which side to take overlap frames from" + }), + + "overlap_mode": ([ + "cut", + "linear_blend", + "ease_in_out", + "filmic_crossfade", + "perceptual_crossfade" + ], { + "default": "linear_blend", + "tooltip": "Blending method for overlapping frames" + }), + + # Math operations for frame calculations + "enable_math": ("BOOLEAN", { + "default": True, + "tooltip": "Enable math calculations for frame adjustments" + }), + + "math_operation": (["none", "a-b", "a-1", "a+b", "a*b", "a/b", "min(a,b)", "max(a,b)"], { + "default": "a-b", + "tooltip": "Math operation to perform on overlap value" + }), + + "safe_mode": (["none", "native_workflow_safe"], { + "default": "none", + "tooltip": "Compatibility: mimic the original workflow behavior (start_images extracted as images[-overlap_frames:-1])" + }), + + "start_frames_rule": (["none", "ltx2_round_down", "ltx2_nearest"], { + "default": "none", + "tooltip": "Optional: force start_images frame count to LTX rule (1 + 8*x) for VideoVAE encode" + }), + + # Quality upgrades (default: none = keep current behavior) + "color_match_mode": (["none", "luma_only", "per_channel"], { + "default": "none", + "tooltip": "Optional: match color/exposure of new_images to the tail of source_images before merging" + }), + "color_match_strength": ("FLOAT", { + "default": 1.0, + "min": 0.0, + "max": 1.0, + "step": 0.05, + "tooltip": "0=no effect, 1=full match (only used if color_match_mode != none)" + }), + "color_reference_window": ("INT", { + "default": 8, + "min": 1, + "max": 256, + "step": 1, + "tooltip": "How many frames from the tail/head to use for stats matching" + }), + + "seam_search_mode": (["none", "best_of_k"], { + "default": "none", + "tooltip": "Optional: search inside new_images for a better seam start (reduces rewind/odd restarts)" + }), + "k_search": ("INT", { + "default": 0, + "min": 0, + "max": 64, + "step": 1, + "tooltip": "How many candidate offsets to test (0 disables). Used only if seam_search_mode=best_of_k" + }), + "metric_weight_color": ("FLOAT", { + "default": 1.0, + "min": 0.0, + "max": 5.0, + "step": 0.1, + "tooltip": "Weight for color/luma continuity metric" + }), + "metric_weight_edges": ("FLOAT", { + "default": 0.5, + "min": 0.0, + "max": 5.0, + "step": 0.1, + "tooltip": "Weight for edge continuity metric" + }), + }, + "optional": { + # New images (from current generation pass) + "new_images": ("IMAGE", { + "tooltip": "The newly generated images to extend with" + }), + + # Optional math operands + "math_value_b": ("INT", { + "default": 1, + "min": 0, + "max": 256, + "step": 1, + "tooltip": "Second operand for math operations (b)" + }), + } + } + + RETURN_TYPES = ("IMAGE", "IMAGE", "IMAGE", "INT", "INT", "INT", "STRING") + RETURN_NAMES = ( + "source_images", + "start_images", + "extended_images", + "overlap_frames", + "calculated_frames", + "extension_frames", + "report" + ) + FUNCTION = "process_extension" + CATEGORY = "IAMCCS/LTX-2" + + def _validate_ltx2_frames(self, frames: int) -> Tuple[bool, int]: + """ + Validate if frame count follows LTX-2 rule (8n+1). + Returns (is_valid, nearest_valid) + """ + if frames < 1: + return False, 1 + + remainder = (frames - 1) % 8 + if remainder == 0: + return True, frames + + # Find nearest valid value + down = frames - remainder + up = frames + (8 - remainder) + nearest = up if (up - frames) <= (frames - down) else max(1, down) + + return False, nearest + + def _execute_math(self, operation: str, a: int, b: int) -> int: + """Execute simple math operation safely""" + try: + if operation == "none" or operation == "": + return a + elif operation == "a-b": + return max(0, a - b) + elif operation == "a-1": + return max(0, a - 1) + elif operation == "a+b": + return a + b + elif operation == "a*b": + return a * b + elif operation == "a/b": + return int(a / b) if b != 0 else a + elif operation == "min(a,b)": + return min(a, b) + elif operation == "max(a,b)": + return max(a, b) + else: + return a + except Exception as e: + _log.warning(f"Math operation failed: {e}, returning a={a}") + return a + + def _blend_images( + self, + blend_src: torch.Tensor, + blend_dst: torch.Tensor, + mode: str + ) -> torch.Tensor: + """ + Blend two image batches using specified mode. + Both inputs should have same shape: [N, H, W, C] + """ + overlap = blend_src.shape[0] + device = blend_src.device + dtype = blend_src.dtype + + if mode == "cut": + # No blending, just return destination + return blend_dst + + elif mode == "linear_blend": + # Simple linear interpolation + alpha = torch.linspace(0, 1, overlap + 2, device=device, dtype=dtype)[1:-1] + alpha = alpha.view(-1, 1, 1, 1) + return (1 - alpha) * blend_src + alpha * blend_dst + + elif mode == "ease_in_out": + # Smooth easing curve + t = torch.linspace(0, 1, overlap + 2, device=device, dtype=dtype)[1:-1] + eased_t = 3 * t * t - 2 * t * t * t + eased_t = eased_t.view(-1, 1, 1, 1) + return (1 - eased_t) * blend_src + eased_t * blend_dst + + elif mode == "filmic_crossfade": + # Gamma-corrected blend for more natural transitions + gamma = 2.2 + alpha = torch.linspace(0, 1, overlap + 2, device=device, dtype=dtype)[1:-1] + alpha = alpha.view(-1, 1, 1, 1) + + linear_src = torch.pow(blend_src.clamp(0, 1), gamma) + linear_dst = torch.pow(blend_dst.clamp(0, 1), gamma) + blended = (1 - alpha) * linear_src + alpha * linear_dst + return torch.pow(blended, 1.0 / gamma) + + elif mode == "perceptual_crossfade": + # Blend in LAB color space for perceptually uniform transitions + try: + import kornia + alpha = torch.linspace(0, 1, overlap + 2, device=device, dtype=dtype)[1:-1] + alpha = alpha.view(-1, 1, 1, 1) + + # Convert to LAB space + src_nchw = blend_src.movedim(-1, 1) + dst_nchw = blend_dst.movedim(-1, 1) + lab_src = kornia.color.rgb_to_lab(src_nchw) + lab_dst = kornia.color.rgb_to_lab(dst_nchw) + + # Blend in LAB + blended_lab = (1 - alpha) * lab_src + alpha * lab_dst + + # Convert back to RGB + blended_rgb = kornia.color.lab_to_rgb(blended_lab) + return blended_rgb.movedim(1, -1) + except ImportError: + _log.warning("Kornia not available, falling back to linear blend") + return self._blend_images(blend_src, blend_dst, "linear_blend") + + else: + # Fallback to linear + return self._blend_images(blend_src, blend_dst, "linear_blend") + + def _apply_ltx2_frame_rule(self, frames: int, rule: str, max_allowed: int) -> int: + """Apply LTX (1 + 8*x) rule to a frame count. rule='none' keeps value.""" + frames = int(frames) + max_allowed = max(1, int(max_allowed)) + frames = max(1, min(frames, max_allowed)) + + if rule == "none" or rule == "": + return frames + + remainder = (frames - 1) % 8 + if remainder == 0: + return frames + + down = max(1, frames - remainder) + up = frames + (8 - remainder) + + if rule == "ltx2_round_down": + return max(1, min(down, max_allowed)) + + # nearest + candidates = [] + if down <= max_allowed: + candidates.append(down) + if up <= max_allowed: + candidates.append(up) + if not candidates: + return max(1, min(down, max_allowed)) + # choose min |delta|, prefer down on tie + candidates.sort(key=lambda v: (abs(v - frames), v > frames)) + return int(candidates[0]) + + def _compute_mean_std_per_channel(self, images: torch.Tensor) -> Tuple[torch.Tensor, torch.Tensor]: + """Compute per-channel mean/std across batch+spatial dims for NHWC images.""" + if images.numel() == 0: + raise ValueError("Empty image tensor") + mean = images.mean(dim=(0, 1, 2)) + var = images.var(dim=(0, 1, 2), unbiased=False) + std = torch.sqrt(var.clamp_min(1e-8)) + return mean, std + + def _match_color_exposure( + self, + new_images: torch.Tensor, + source_images: torch.Tensor, + mode: str, + strength: float, + reference_window: int, + ) -> torch.Tensor: + """Match new_images color/exposure to the tail of source_images. Images are NHWC in [0,1].""" + if mode == "none" or strength <= 0.0: + return new_images + + src_count = int(source_images.shape[0]) + new_count = int(new_images.shape[0]) + w = max(1, int(reference_window)) + src_ref = source_images[max(0, src_count - w):src_count] + new_ref = new_images[: min(w, new_count)] + + if mode == "per_channel": + src_mean, src_std = self._compute_mean_std_per_channel(src_ref) + new_mean, new_std = self._compute_mean_std_per_channel(new_ref) + scale = (src_std / new_std).view(1, 1, 1, -1) + shift = (src_mean - (src_std / new_std) * new_mean).view(1, 1, 1, -1) + matched = (new_images * scale + shift).clamp(0, 1) + elif mode == "luma_only": + # Match exposure/contrast on luma, apply same affine to all channels + weights = torch.tensor([0.2126, 0.7152, 0.0722], device=new_images.device, dtype=new_images.dtype) + src_y = (src_ref * weights.view(1, 1, 1, -1)).sum(dim=-1) + new_y = (new_ref * weights.view(1, 1, 1, -1)).sum(dim=-1) + src_mean = src_y.mean() + src_std = src_y.std(unbiased=False).clamp_min(1e-6) + new_mean = new_y.mean() + new_std = new_y.std(unbiased=False).clamp_min(1e-6) + scale = (src_std / new_std) + shift = (src_mean - scale * new_mean) + matched = (new_images * scale + shift).clamp(0, 1) + else: + return new_images + + s = float(max(0.0, min(1.0, strength))) + return ((1.0 - s) * new_images + s * matched).clamp(0, 1) + + def _downsample_nhwc(self, images: torch.Tensor, size: int = 64) -> torch.Tensor: + """Downsample NHWC images to size x size in NCHW.""" + nchw = images.movedim(-1, 1) + return F.interpolate(nchw, size=(size, size), mode="bilinear", align_corners=False) + + def _sobel_mag(self, gray_nchw: torch.Tensor) -> torch.Tensor: + """Sobel magnitude for NCHW grayscale tensor.""" + device = gray_nchw.device + dtype = gray_nchw.dtype + kx = torch.tensor([[-1.0, 0.0, 1.0], [-2.0, 0.0, 2.0], [-1.0, 0.0, 1.0]], device=device, dtype=dtype).view(1, 1, 3, 3) + ky = torch.tensor([[-1.0, -2.0, -1.0], [0.0, 0.0, 0.0], [1.0, 2.0, 1.0]], device=device, dtype=dtype).view(1, 1, 3, 3) + gx = F.conv2d(gray_nchw, kx, padding=1) + gy = F.conv2d(gray_nchw, ky, padding=1) + return torch.sqrt(gx * gx + gy * gy + 1e-8) + + def _best_of_k_offset( + self, + source_tail: torch.Tensor, + new_images: torch.Tensor, + blend_overlap: int, + k_search: int, + w_color: float, + w_edges: float, + ) -> int: + """Pick an offset into new_images that best matches source_tail over the overlap window.""" + if k_search <= 0: + return 0 + + new_count = int(new_images.shape[0]) + max_offset = min(int(k_search), max(0, new_count - blend_overlap)) + if max_offset <= 0: + return 0 + + # Prepare features (downsample + luma + edges) + weights = torch.tensor([0.2126, 0.7152, 0.0722], device=new_images.device, dtype=new_images.dtype) + src_win = source_tail[-blend_overlap:] + src_ds = self._downsample_nhwc(src_win, size=64) + src_luma = (src_ds * weights.view(1, 3, 1, 1)).sum(dim=1, keepdim=True) + src_edges = self._sobel_mag(src_luma) + + best_offset = 0 + best_score = None + + for off in range(0, max_offset + 1): + cand = new_images[off: off + blend_overlap] + if int(cand.shape[0]) != blend_overlap: + continue + cand_ds = self._downsample_nhwc(cand, size=64) + cand_luma = (cand_ds * weights.view(1, 3, 1, 1)).sum(dim=1, keepdim=True) + cand_edges = self._sobel_mag(cand_luma) + + color_mse = (src_luma - cand_luma).pow(2).mean() + edge_mse = (src_edges - cand_edges).pow(2).mean() + score = float(w_color) * color_mse + float(w_edges) * edge_mse + + if best_score is None or score < best_score: + best_score = score + best_offset = off + + return int(best_offset) + + def process_extension( + self, + source_images: torch.Tensor, + overlap_frames: int, + overlap_side: str, + overlap_mode: str, + enable_math: bool, + math_operation: str, + safe_mode: str, + start_frames_rule: str, + color_match_mode: str, + color_match_strength: float, + color_reference_window: int, + seam_search_mode: str, + k_search: int, + metric_weight_color: float, + metric_weight_edges: float, + new_images: Optional[torch.Tensor] = None, + math_value_b: int = 1, + ): + # Initialize + source_count = int(source_images.shape[0]) + overlap_frames_in = int(overlap_frames) + + # Validate inputs (match KJNodes semantics: if overlap is too large, just passthrough) + if source_count < 1: + raise ValueError("source_images batch is empty") + + if overlap_frames_in < 1: + overlap_frames_in = 1 + + if overlap_frames_in >= source_count: + report = ( + f"Source: {source_count} frames | " + f"Overlap (effective): {overlap_frames_in} frames | " + f"Start images: {source_count} frames | " + f"Extended: {source_count} frames | " + f"Extension delta: +0 frames | " + f"Blend mode: {overlap_mode} | " + f"Overlap side: {overlap_side}" + ) + _log.info(f"[LTX2_ExtensionModule] {report}") + return ( + source_images, + source_images, + source_images, + overlap_frames_in, + source_count, + 0, + report, + ) + + # Initialize output + # If no new_images are provided, behave as a "prep" node: + # - extended_images == source_images + # - start_images extracted from the (current) batch + extended_images = source_images + extension_frames_count = 0 + + # Process extension if new images are provided + if new_images is not None: + new_count = int(new_images.shape[0]) + + # Validate shapes + if source_images.shape[1:3] != new_images.shape[1:3]: + raise ValueError( + f"Source and new images must have same resolution: " + f"{tuple(source_images.shape[1:3])} vs {tuple(new_images.shape[1:3])}" + ) + + # Overlap used for blending (matches ImageBatchExtendWithOverlap) + blend_overlap = min(overlap_frames_in, source_count, new_count) + + # Option 5: Best-of-K seam search (choose a better start inside new_images) + chosen_offset = 0 + if seam_search_mode == "best_of_k" and int(k_search) > 0 and blend_overlap > 0: + chosen_offset = self._best_of_k_offset( + source_images[-blend_overlap:], + new_images, + blend_overlap=blend_overlap, + k_search=int(k_search), + w_color=float(metric_weight_color), + w_edges=float(metric_weight_edges), + ) + if chosen_offset > 0: + new_images = new_images[chosen_offset:] + new_count = int(new_images.shape[0]) + blend_overlap = min(overlap_frames_in, source_count, new_count) + + # Option 3: Color/Exposure match (apply before blending) + if color_match_mode != "none" and float(color_match_strength) > 0.0: + new_images = self._match_color_exposure( + new_images=new_images, + source_images=source_images, + mode=str(color_match_mode), + strength=float(color_match_strength), + reference_window=int(color_reference_window), + ) + + prefix = source_images[:-blend_overlap] + if overlap_side == "source": + blend_src = source_images[-blend_overlap:] + blend_dst = new_images[:blend_overlap] + else: # new_images + blend_src = new_images[:blend_overlap] + blend_dst = source_images[-blend_overlap:] + + suffix = new_images[blend_overlap:] + + if overlap_mode == "cut": + # Match KJNodes cut semantics + if overlap_side == "new_images": + extended_images = torch.cat((source_images, new_images[blend_overlap:]), dim=0) + else: + extended_images = torch.cat((source_images[:-blend_overlap], new_images), dim=0) + else: + blended = self._blend_images(blend_src, blend_dst, overlap_mode) + extended_images = torch.cat((prefix, blended, suffix), dim=0) + + extension_frames_count = int(extended_images.shape[0] - source_count) + + # Compute start_images from the CURRENT batch for the NEXT iteration. + # When chaining multiple generations, using the post-merge batch (extended_images) + # avoids graph cycles and removes the need for external math/range nodes. + base_count = int(extended_images.shape[0]) + safe_mode = str(safe_mode or "none") + if safe_mode == "native_workflow_safe": + # Match the original graph: start_images is computed with + # start = total - overlap_frames, end = total - 1 (exclusive) + if base_count <= 1: + start_images = extended_images[:1] + start_index = 0 + start_end = int(start_images.shape[0]) + else: + start_end = base_count - 1 + start_index = max(0, start_end - overlap_frames_in) + start_images = extended_images[start_index:start_end] + else: + start_index = max(0, base_count - overlap_frames_in) + + calculated_frames = overlap_frames_in + if enable_math and math_operation != "none": + calculated_frames = self._execute_math(math_operation, overlap_frames_in, math_value_b) + + max_start_frames = max(1, base_count - start_index) + calculated_frames = max(1, min(int(calculated_frames), max_start_frames)) + + # Optional: enforce LTX (1+8*x) rule for VideoVAE encode + calculated_frames = self._apply_ltx2_frame_rule(calculated_frames, str(start_frames_rule), max_start_frames) + + start_end = min(base_count, start_index + calculated_frames) + start_images = extended_images[start_index:start_end] + + # Generate report + report = ( + f"Source: {source_count} frames | " + f"Overlap (effective): {overlap_frames_in} frames | " + f"Start range (from current batch): start_index={start_index}, num_frames={start_images.shape[0]} | " + f"Math: {math_operation if enable_math else 'disabled'} | " + f"Start frames rule: {start_frames_rule} | " + f"Safe mode: {safe_mode} | " + f"Extended: {int(extended_images.shape[0]) if extended_images is not None else 0} frames | " + f"Extension delta: +{extension_frames_count} frames | " + f"Blend mode: {overlap_mode} | " + f"Overlap side: {overlap_side} | " + f"Color match: {color_match_mode} | " + f"Seam search: {seam_search_mode}" + ) + + _log.info(f"[LTX2_ExtensionModule] {report}") + + return ( + source_images, # Pass through source + start_images, # Start images for next pass + extended_images, # Extended result + overlap_frames_in, # Original overlap value + int(start_images.shape[0]), # Actual start-frame count + extension_frames_count, # How many frames were added + report # Detailed report + ) + + +class IAMCCS_LTX2_GetImageFromBatch: + """ + Extracts a specific range of images from a batch. + Useful for: + - Getting start images for next iteration + - Extracting specific frames from generation + - Creating sub-batches from large batches + """ + + @classmethod + def INPUT_TYPES(cls): + return { + "required": { + "images": ("IMAGE", { + "tooltip": "Input image batch" + }), + "mode": (["from_start", "from_end", "range"], { + "default": "from_end", + "tooltip": "Extraction mode" + }), + "count": ("INT", { + "default": 10, + "min": 1, + "max": 10000, + "step": 1, + "tooltip": "Number of frames to extract (for from_start/from_end)" + }), + + # Upgrade options (default: none = keep current behavior) + "auto_count_mode": (["none", "prefer_input", "use_widget"], { + "default": "none", + "tooltip": "Optional: auto-drive count from an INT input (e.g. overlap_frames)" + }), + "diagnostics": (["none", "basic"], { + "default": "none", + "tooltip": "Optional: expose start/end indices as extra outputs" + }), + + "count_rule": (["none", "ltx2_round_down", "ltx2_nearest"], { + "default": "none", + "tooltip": "Optional: force count to LTX rule (1 + 8*x) for VideoVAE encode" + }), + + "safe_mode": (["none", "native_workflow_safe"], { + "default": "none", + "tooltip": "Compatibility: mimic original GetImageRangeFromBatch behavior for from_end (images[-count:-1])" + }), + }, + "optional": { + "count_in": ("INT", { + "default": 10, + "min": 1, + "max": 10000, + "step": 1, + "tooltip": "Optional count input (used if auto_count_mode=prefer_input)" + }), + "start_index": ("INT", { + "default": 0, + "min": 0, + "max": 10000, + "step": 1, + "tooltip": "Start index for range mode" + }), + "end_index": ("INT", { + "default": 10, + "min": 0, + "max": 10000, + "step": 1, + "tooltip": "End index for range mode (exclusive)" + }), + } + } + + RETURN_TYPES = ("IMAGE", "INT", "STRING", "INT", "INT") + RETURN_NAMES = ("images", "count", "report", "start_index", "end_index") + FUNCTION = "extract" + CATEGORY = "IAMCCS/LTX-2" + + def extract(self, images, mode, count, auto_count_mode, diagnostics, count_rule, safe_mode, count_in=None, start_index=0, end_index=10): + """Extract images from batch""" + total = images.shape[0] + + def apply_ltx_rule(n: int, rule: str, max_allowed: int) -> int: + n = int(n) + max_allowed = max(1, int(max_allowed)) + n = max(1, min(n, max_allowed)) + if rule == "none" or rule == "": + return n + remainder = (n - 1) % 8 + if remainder == 0: + return n + down = max(1, n - remainder) + up = n + (8 - remainder) + if rule == "ltx2_round_down": + return max(1, min(down, max_allowed)) + candidates = [] + if down <= max_allowed: + candidates.append(down) + if up <= max_allowed: + candidates.append(up) + if not candidates: + return max(1, min(down, max_allowed)) + candidates.sort(key=lambda v: (abs(v - n), v > n)) + return int(candidates[0]) + + # Option C: Auto-Count + effective_count = int(count) + if auto_count_mode != "none" and count_in is not None: + if auto_count_mode == "prefer_input": + effective_count = int(count_in) + elif auto_count_mode == "use_widget": + effective_count = int(count) + + effective_count = max(1, min(effective_count, int(total))) + + safe_mode = str(safe_mode or "none") + if safe_mode == "native_workflow_safe" and mode == "from_end": + # Match: start = total - count, end = total - 1 (exclusive) + if int(total) <= 1: + result = images[:1] + used_start = 0 + used_end = int(result.shape[0]) + report = f"Extracted (safe) {result.shape[0]} frames from batch of {total}" + else: + used_start = max(0, int(total) - int(effective_count)) + used_end = max(0, int(total) - 1) + result = images[used_start:used_end] + report = f"Extracted (safe) frames {used_start} to {used_end} ({result.shape[0]} frames) from batch of {total}" + return (result, result.shape[0], report, used_start, used_end) + + # Optional: enforce LTX (1+8*x) rule for VideoVAE encode + if mode in ("from_start", "from_end"): + effective_count = apply_ltx_rule(effective_count, str(count_rule), int(total)) + + if mode == "from_start": + result = images[:effective_count] + used_start = 0 + used_end = effective_count + report = f"Extracted first {effective_count} frames from batch of {total}" + + elif mode == "from_end": + result = images[-effective_count:] + used_start = int(total) - int(effective_count) + used_end = int(total) + report = f"Extracted last {effective_count} frames from batch of {total}" + + else: # range + start_index = max(0, min(start_index, total)) + end_index = max(start_index, min(end_index, total)) + result = images[start_index:end_index] + actual_count = result.shape[0] + used_start = int(start_index) + used_end = int(end_index) + report = f"Extracted frames {start_index} to {end_index} ({actual_count} frames) from batch of {total}" + + return (result, result.shape[0], report, used_start, used_end) + + +class IAMCCS_LTX2_ReferenceImageSwitch: + """Selects a reference image (or keeps the default). + + Intended use: feed the output into a segment node's optional/secondary image input + (e.g. `image_1`) to reinforce identity/style consistency WITHOUT touching the + overlap/start-image continuity input. + + Default behavior is `none` which preserves old workflows. + """ + + @classmethod + def INPUT_TYPES(cls): + return { + "required": { + "default_image": ("IMAGE", {"tooltip": "Fallback image (e.g. EmptyImage)"}), + "mode": (["none", "use_reference", "blend"], { + "default": "none", + "tooltip": "none: pass default_image | use_reference: output reference_image | blend: mix both" + }), + "blend_strength": ("FLOAT", { + "default": 1.0, + "min": 0.0, + "max": 1.0, + "step": 0.05, + "tooltip": "Only used if mode=blend (0=default, 1=reference)" + }), + }, + "optional": { + "reference_image": ("IMAGE", {"tooltip": "Optional reference image (usually batch size 1)"}), + }, + } + + RETURN_TYPES = ("IMAGE", "STRING") + RETURN_NAMES = ("image", "report") + FUNCTION = "select" + CATEGORY = "IAMCCS/LTX-2" + + def _match_batch(self, base: torch.Tensor, other: torch.Tensor) -> torch.Tensor: + """Broadcast/crop `other` batch to match `base` batch length when possible.""" + bn = int(base.shape[0]) + on = int(other.shape[0]) + if bn == on: + return other + if on == 1 and bn > 1: + return other.repeat(bn, 1, 1, 1) + if bn == 1 and on > 1: + return other[:1] + # Both >1 but mismatch: crop to min + m = min(bn, on) + return other[:m] + + def _resize_to(self, image: torch.Tensor, target_h: int, target_w: int) -> torch.Tensor: + if int(image.shape[1]) == int(target_h) and int(image.shape[2]) == int(target_w): + return image + # IMAGE tensors in ComfyUI are [N, H, W, C] + x = image.permute(0, 3, 1, 2) + x = F.interpolate(x, size=(int(target_h), int(target_w)), mode="bilinear", align_corners=False) + x = x.permute(0, 2, 3, 1) + return x.clamp(0, 1) + + def select(self, default_image: torch.Tensor, mode: str, blend_strength: float, reference_image: Optional[torch.Tensor] = None): + mode = str(mode or "none") + if mode == "none" or reference_image is None: + return (default_image, f"Reference switch: {mode} (using default_image)") + + ref = self._match_batch(default_image, reference_image) + base = default_image + # If we cropped the ref, crop base too to keep alignment. + if int(ref.shape[0]) != int(base.shape[0]): + base = base[: int(ref.shape[0])] + + target_h, target_w = int(base.shape[1]), int(base.shape[2]) + resized = False + if int(ref.shape[1]) != target_h or int(ref.shape[2]) != target_w: + ref = self._resize_to(ref, target_h, target_w) + resized = True + + if mode == "use_reference": + return (ref, f"Reference switch: use_reference{' (resized)' if resized else ''}") + + # blend + s = float(max(0.0, min(1.0, blend_strength))) + out = ((1.0 - s) * base + s * ref).clamp(0, 1) + return (out, f"Reference switch: blend (strength={s:.2f}){' (resized)' if resized else ''}") + + +class IAMCCS_LTX2_ReferenceStartFramesInjector: + """Inject a reference image into the conditioning frames (start_images). + + Why: in LTX extension workflows, the model mostly follows `images` (conditioning frames). + Feeding a reference into an auxiliary/empty-latent image slot often has little/no effect on identity. + This node lets you (optionally) blend the reference into the last (or first) K conditioning frames. + + Default mode is `none` to preserve old workflows. + """ + + @classmethod + def INPUT_TYPES(cls): + return { + "required": { + "start_images": ("IMAGE", {"tooltip": "Conditioning frames (e.g. start_images from ExtensionModule)"}), + "mode": (["none", "inject", "blend"], { + "default": "none", + "tooltip": "none: passthrough | inject: replace frames with reference | blend: mix with existing" + }), + "blend_strength": ("FLOAT", { + "default": 1.0, + "min": 0.0, + "max": 1.0, + "step": 0.05, + "tooltip": "Used for mode=blend (0=no change, 1=full reference). For mode=inject it's treated as 1.0" + }), + "frames_to_inject": ("INT", { + "default": 1, + "min": 1, + "max": 64, + "step": 1, + "tooltip": "How many conditioning frames to modify" + }), + "ramp": ("BOOLEAN", { + "default": True, + "tooltip": "If true, gradually increases strength across the injected frames" + }), + "position": (["tail", "head"], { + "default": "tail", + "tooltip": "Where to inject (tail=last K frames, head=first K frames)" + }), + }, + "optional": { + "reference_image": ("IMAGE", {"tooltip": "Reference image (batch 1 is ok; will be resized to match start_images)"}), + }, + } + + RETURN_TYPES = ("IMAGE", "STRING") + RETURN_NAMES = ("start_images", "report") + FUNCTION = "inject" + CATEGORY = "IAMCCS/LTX-2" + + def _resize_to(self, image: torch.Tensor, target_h: int, target_w: int) -> torch.Tensor: + if int(image.shape[1]) == int(target_h) and int(image.shape[2]) == int(target_w): + return image + x = image.permute(0, 3, 1, 2) + x = F.interpolate(x, size=(int(target_h), int(target_w)), mode="bilinear", align_corners=False) + x = x.permute(0, 2, 3, 1) + return x.clamp(0, 1) + + def _repeat_or_crop_batch(self, desired_n: int, image: torch.Tensor) -> torch.Tensor: + n = int(image.shape[0]) + if n == desired_n: + return image + if n == 1 and desired_n > 1: + return image.repeat(desired_n, 1, 1, 1) + return image[:desired_n] + + def inject( + self, + start_images: torch.Tensor, + mode: str, + blend_strength: float, + frames_to_inject: int, + ramp: bool, + position: str, + reference_image: Optional[torch.Tensor] = None, + ): + mode = str(mode or "none") + if mode == "none" or reference_image is None: + return (start_images, f"StartFrames injector: {mode} (passthrough)") + + base = start_images + total = int(base.shape[0]) + k = int(max(1, min(int(frames_to_inject), total))) + pos = str(position or "tail") + + target_h, target_w = int(base.shape[1]), int(base.shape[2]) + ref = self._resize_to(reference_image, target_h, target_w) + ref = self._repeat_or_crop_batch(k, ref) + + out = base.clone() + if pos == "head": + idxs = list(range(0, k)) + else: # tail + idxs = list(range(total - k, total)) + + # Strength handling + if mode == "inject": + max_s = 1.0 + else: + max_s = float(max(0.0, min(1.0, blend_strength))) + + used = 0 + for j, i in enumerate(idxs): + if ramp and k > 1: + s = max_s * float(j + 1) / float(k) + else: + s = max_s + out[i] = ((1.0 - s) * out[i] + s * ref[j]).clamp(0, 1) + used += 1 + + return (out, f"StartFrames injector: {mode} ({pos}, frames={used}, strength={max_s:.2f}, resized)") + + +class IAMCCS_LTX2_FrameCountValidator: + """ + Validates and corrects frame counts for LTX-2 (8n+1 rule). + Outputs: validated count, is_valid flag, nearest valid count. + """ + + @classmethod + def INPUT_TYPES(cls): + return { + "required": { + "frame_count": ("INT", { + "default": 81, + "min": 1, + "max": 10000, + "step": 1, + "tooltip": "Frame count to validate" + }), + "auto_correct": ("BOOLEAN", { + "default": True, + "tooltip": "Automatically correct to nearest valid value" + }), + "correction_mode": (["nearest", "round_up", "round_down"], { + "default": "nearest", + "tooltip": "How to correct invalid values" + }), + } + } + + RETURN_TYPES = ("INT", "BOOLEAN", "INT", "STRING") + RETURN_NAMES = ("validated_count", "is_valid", "nearest_valid", "report") + FUNCTION = "validate" + CATEGORY = "IAMCCS/LTX-2" + + def validate(self, frame_count, auto_correct, correction_mode): + """Validate LTX-2 frame count (8n+1 rule)""" + + # Check if valid + remainder = (frame_count - 1) % 8 + is_valid = remainder == 0 + + if is_valid: + report = f"✅ {frame_count} is valid (8n+1 rule)" + return (frame_count, True, frame_count, report) + + # Calculate corrections + down = frame_count - remainder + up = frame_count + (8 - remainder) + + if correction_mode == "round_up": + nearest = up + elif correction_mode == "round_down": + nearest = max(1, down) + else: # nearest + nearest = up if (up - frame_count) <= (frame_count - down) else max(1, down) + + # Output + output_count = nearest if auto_correct else frame_count + + report = ( + f"❌ {frame_count} is NOT valid (8n+1 rule)\n" + f"Remainder: {remainder}\n" + f"Nearest valid: {nearest} (n={(nearest-1)//8})\n" + f"Output: {output_count} ({'corrected' if auto_correct else 'uncorrected'})" + ) + + if auto_correct: + _log.info(f"[LTX2_Validator] Corrected {frame_count} → {nearest}") + + return (output_count, False, nearest, report) + + +class IAMCCS_LTX2_ExtensionModule_simple(IAMCCS_LTX2_ExtensionModule): + """A truly minimal Extension Module. + + Goals: + - Keep ONLY the core widgets (overlap + blend + math) + - No additional "quality" options (color match / seam search / metrics) + - No user-facing safe_mode/start_frames_rule widgets + - Always enforce LTX-2 start-frames rule (1 + 8*k) automatically + """ + + @classmethod + def INPUT_TYPES(cls): + return { + "required": { + "source_images": ("IMAGE", { + "tooltip": "The source images to extend (from previous generation)" + }), + "overlap_frames": ("INT", { + "default": 10, + "min": 1, + "max": 256, + "step": 1, + "tooltip": "Number of overlapping frames between batches" + }), + "overlap_side": (["source", "new_images"], { + "default": "source", + "tooltip": "Which side to take overlap frames from" + }), + "overlap_mode": ([ + "cut", + "linear_blend", + "ease_in_out", + "filmic_crossfade", + "perceptual_crossfade" + ], { + "default": "linear_blend", + "tooltip": "Blending method for overlapping frames" + }), + "enable_math": ("BOOLEAN", { + "default": True, + "tooltip": "Enable math calculations for frame adjustments" + }), + "math_operation": (["none", "a-b", "a-1", "a+b", "a*b", "a/b", "min(a,b)", "max(a,b)"], { + "default": "a-b", + "tooltip": "Math operation to perform on overlap value" + }), + "math_value_b": ("INT", { + "default": 1, + "min": 0, + "max": 256, + "step": 1, + "tooltip": "Second operand for math operations (b)" + }), + }, + "optional": { + "new_images": ("IMAGE", { + "tooltip": "The newly generated images to extend with" + }), + }, + } + + RETURN_TYPES = IAMCCS_LTX2_ExtensionModule.RETURN_TYPES + RETURN_NAMES = IAMCCS_LTX2_ExtensionModule.RETURN_NAMES + FUNCTION = IAMCCS_LTX2_ExtensionModule.FUNCTION + CATEGORY = "IAMCCS/LTX-2" + + def process_extension( + self, + source_images: torch.Tensor, + overlap_frames: int, + overlap_side: str, + overlap_mode: str, + enable_math: bool, + math_operation: str, + math_value_b: int, + new_images: Optional[torch.Tensor] = None, + ): + # Fixed behavior knobs (not user-exposed in the simple node) + safe_mode = "none" + start_frames_rule = "ltx2_round_down" # always enforce 8n+1 + + color_match_mode = "none" + color_match_strength = 0.0 + color_reference_window = 8 + + seam_search_mode = "none" + k_search = 0 + metric_weight_color = 1.0 + metric_weight_edges = 0.5 + + return super().process_extension( + source_images=source_images, + overlap_frames=overlap_frames, + overlap_side=overlap_side, + overlap_mode=overlap_mode, + enable_math=enable_math, + math_operation=math_operation, + safe_mode=safe_mode, + start_frames_rule=start_frames_rule, + color_match_mode=color_match_mode, + color_match_strength=color_match_strength, + color_reference_window=color_reference_window, + seam_search_mode=seam_search_mode, + k_search=k_search, + metric_weight_color=metric_weight_color, + metric_weight_edges=metric_weight_edges, + new_images=new_images, + math_value_b=int(math_value_b), + ) + + +# Node registration +NODE_CLASS_MAPPINGS = { + "IAMCCS_LTX2_ExtensionModule": IAMCCS_LTX2_ExtensionModule, + "IAMCCS_LTX2_ExtensionModule_simple": IAMCCS_LTX2_ExtensionModule_simple, + "IAMCCS_LTX2_GetImageFromBatch": IAMCCS_LTX2_GetImageFromBatch, + "IAMCCS_LTX2_ReferenceImageSwitch": IAMCCS_LTX2_ReferenceImageSwitch, + "IAMCCS_LTX2_ReferenceStartFramesInjector": IAMCCS_LTX2_ReferenceStartFramesInjector, + "IAMCCS_LTX2_FrameCountValidator": IAMCCS_LTX2_FrameCountValidator, +} + +NODE_DISPLAY_NAME_MAPPINGS = { + "IAMCCS_LTX2_ExtensionModule": "LTX-2 Extension Module 🎬", + "IAMCCS_LTX2_ExtensionModule_simple": "LTX-2 Extension Module (simple) 🎬", + "IAMCCS_LTX2_GetImageFromBatch": "LTX-2 Get Images From Batch 🎞️", + "IAMCCS_LTX2_ReferenceImageSwitch": "LTX-2 Reference Image Switch 🧷", + "IAMCCS_LTX2_ReferenceStartFramesInjector": "LTX-2 Inject Reference Into Start Frames 🧬", + "IAMCCS_LTX2_FrameCountValidator": "LTX-2 Frame Count Validator ✅ (8n+1)", +} diff --git a/iamccs_ltx2_lora_stack_segmented6.py b/iamccs_ltx2_lora_stack_segmented6.py new file mode 100644 index 0000000..c16d511 --- /dev/null +++ b/iamccs_ltx2_lora_stack_segmented6.py @@ -0,0 +1,288 @@ +# iamccs_ltx2_lora_stack_segmented6.py +# =============================================================== +# Segmented LoRA stacks for workflows with 3 segments × 2 stages. +# Outputs either 6 LoRA stacks or 6 MODELs (MODEL only, no CLIP). +# =============================================================== + +import logging +from typing import Any, Dict, Optional + +import comfy.sd +import comfy.utils +import folder_paths + +from .iamccs_ltx2_lora_stack import SuppressLTX2MissingKeysFilter, standardize_ltx2_lora_keys + + +def _load_lora_state_dict(name: str, cache: Dict[str, Any]) -> Optional[dict]: + if not name or name == "no": + return None + if name in cache: + return cache[name] + + path = folder_paths.get_full_path_or_raise("loras", name) + sd = comfy.utils.load_torch_file(path, safe_load=True) + sd = standardize_ltx2_lora_keys(sd) + cache[name] = sd + return sd + + +def _append_lora(stack: list, name: str, strength: float, cache: Dict[str, Any]) -> None: + if not name or name == "no": + return + s = float(strength) + if s == 0.0: + return + + sd = _load_lora_state_dict(name, cache) + if not sd: + return + + stack.append({"name": name, "strength": s, "state_dict": sd}) + + +def _build_segment_stage_stack( + *, + fixed_lora: str, + fixed_strength: float, + var_lora1: str, + var1_strength: float, + var_lora2: str, + var2_strength: float, + cache: Dict[str, Any], +) -> list: + stack: list = [] + _append_lora(stack, fixed_lora, fixed_strength, cache) + _append_lora(stack, var_lora1, var1_strength, cache) + _append_lora(stack, var_lora2, var2_strength, cache) + return stack + + +def _apply_lora_stack_to_model(model, lora_stack: list): + if not lora_stack: + return model + + model_out = model + for entry in lora_stack: + sd = entry["state_dict"] + strength = float(entry["strength"]) + model_out, _ = comfy.sd.load_lora_for_models(model_out, None, sd, strength, 0) + return model_out + + +class IAMCCS_LTX2_LoRAStackSegmented6: + """Builds 6 LORA stacks: 3 segments × 2 stages (MODEL-only workflows).""" + + @classmethod + def INPUT_TYPES(cls): + lora_list = folder_paths.get_filename_list("loras") + ["no"] + + required: Dict[str, Any] = { + "fixed_lora": (lora_list, {"default": "no"}), + } + + # 3 segments (0..2), each has 2 stages and 2 variable loras + for seg in range(3): + required[f"seg{seg}_var_lora1"] = (lora_list, {"default": "no"}) + required[f"seg{seg}_var_lora2"] = (lora_list, {"default": "no"}) + + # fixed strength per stage + required[f"seg{seg}_fixed_strength_stage1"] = ( + "FLOAT", + {"default": 0.0, "min": -5.0, "max": 5.0, "step": 0.01}, + ) + required[f"seg{seg}_fixed_strength_stage2"] = ( + "FLOAT", + {"default": 0.0, "min": -5.0, "max": 5.0, "step": 0.01}, + ) + + # var strengths per stage + for i in (1, 2): + required[f"seg{seg}_var{i}_strength_stage1"] = ( + "FLOAT", + {"default": 0.0, "min": -5.0, "max": 5.0, "step": 0.01}, + ) + required[f"seg{seg}_var{i}_strength_stage2"] = ( + "FLOAT", + {"default": 0.0, "min": -5.0, "max": 5.0, "step": 0.01}, + ) + + return {"required": required} + + RETURN_TYPES = ("LORA", "LORA", "LORA", "LORA", "LORA", "LORA") + RETURN_NAMES = ( + "seg0_stage1_lora", + "seg0_stage2_lora", + "seg1_stage1_lora", + "seg1_stage2_lora", + "seg2_stage1_lora", + "seg2_stage2_lora", + ) + FUNCTION = "build" + CATEGORY = "IAMCCS/LoRA" + + def build(self, fixed_lora: str, **kwargs): + cache: Dict[str, Any] = {} + + out: list[list] = [] + for seg in range(3): + var1 = str(kwargs.get(f"seg{seg}_var_lora1") or "no") + var2 = str(kwargs.get(f"seg{seg}_var_lora2") or "no") + + fixed_s1 = kwargs.get(f"seg{seg}_fixed_strength_stage1", 0.0) + fixed_s2 = kwargs.get(f"seg{seg}_fixed_strength_stage2", 0.0) + + v1s1 = kwargs.get(f"seg{seg}_var1_strength_stage1", 0.0) + v1s2 = kwargs.get(f"seg{seg}_var1_strength_stage2", 0.0) + v2s1 = kwargs.get(f"seg{seg}_var2_strength_stage1", 0.0) + v2s2 = kwargs.get(f"seg{seg}_var2_strength_stage2", 0.0) + + out.append( + _build_segment_stage_stack( + fixed_lora=fixed_lora, + fixed_strength=fixed_s1, + var_lora1=var1, + var1_strength=v1s1, + var_lora2=var2, + var2_strength=v2s1, + cache=cache, + ) + ) + out.append( + _build_segment_stage_stack( + fixed_lora=fixed_lora, + fixed_strength=fixed_s2, + var_lora1=var1, + var1_strength=v1s2, + var_lora2=var2, + var2_strength=v2s2, + cache=cache, + ) + ) + + # Logging summary (compact) + total = sum(len(s) for s in out) + if total == 0: + logging.warning("[IAMCCS_LTX2_LoRAStackSegmented6] ⚠ No LoRA selected") + else: + logging.info(f"[IAMCCS_LTX2_LoRAStackSegmented6] ✅ Built 6 stacks ({total} active entries)") + + return tuple(out) + + +class IAMCCS_LTX2_ModelWithLoRA_Segmented6: + """Applies 6 stacks (3 segments × 2 stages) to a base MODEL and outputs 6 MODELs.""" + + @classmethod + def INPUT_TYPES(cls): + # Mirror config of stack node, but include base model + lora_list = folder_paths.get_filename_list("loras") + ["no"] + + required: Dict[str, Any] = { + "model": ("MODEL",), + "fixed_lora": (lora_list, {"default": "no"}), + } + + for seg in range(3): + required[f"seg{seg}_var_lora1"] = (lora_list, {"default": "no"}) + required[f"seg{seg}_var_lora2"] = (lora_list, {"default": "no"}) + + required[f"seg{seg}_fixed_strength_stage1"] = ( + "FLOAT", + {"default": 0.0, "min": -5.0, "max": 5.0, "step": 0.01}, + ) + required[f"seg{seg}_fixed_strength_stage2"] = ( + "FLOAT", + {"default": 0.0, "min": -5.0, "max": 5.0, "step": 0.01}, + ) + + for i in (1, 2): + required[f"seg{seg}_var{i}_strength_stage1"] = ( + "FLOAT", + {"default": 0.0, "min": -5.0, "max": 5.0, "step": 0.01}, + ) + required[f"seg{seg}_var{i}_strength_stage2"] = ( + "FLOAT", + {"default": 0.0, "min": -5.0, "max": 5.0, "step": 0.01}, + ) + + return {"required": required} + + RETURN_TYPES = ("MODEL", "MODEL", "MODEL", "MODEL", "MODEL", "MODEL") + RETURN_NAMES = ( + "seg0_stage1_model", + "seg0_stage2_model", + "seg1_stage1_model", + "seg1_stage2_model", + "seg2_stage1_model", + "seg2_stage2_model", + ) + FUNCTION = "apply_segmented" + CATEGORY = "IAMCCS/LoRA" + + def apply_segmented(self, model, fixed_lora: str, **kwargs): + cache: Dict[str, Any] = {} + + # Build all 6 stacks + stacks: list[list] = [] + for seg in range(3): + var1 = str(kwargs.get(f"seg{seg}_var_lora1") or "no") + var2 = str(kwargs.get(f"seg{seg}_var_lora2") or "no") + + fixed_s1 = kwargs.get(f"seg{seg}_fixed_strength_stage1", 0.0) + fixed_s2 = kwargs.get(f"seg{seg}_fixed_strength_stage2", 0.0) + + v1s1 = kwargs.get(f"seg{seg}_var1_strength_stage1", 0.0) + v1s2 = kwargs.get(f"seg{seg}_var1_strength_stage2", 0.0) + v2s1 = kwargs.get(f"seg{seg}_var2_strength_stage1", 0.0) + v2s2 = kwargs.get(f"seg{seg}_var2_strength_stage2", 0.0) + + stacks.append( + _build_segment_stage_stack( + fixed_lora=fixed_lora, + fixed_strength=fixed_s1, + var_lora1=var1, + var1_strength=v1s1, + var_lora2=var2, + var2_strength=v2s1, + cache=cache, + ) + ) + stacks.append( + _build_segment_stage_stack( + fixed_lora=fixed_lora, + fixed_strength=fixed_s2, + var_lora1=var1, + var1_strength=v1s2, + var_lora2=var2, + var2_strength=v2s2, + cache=cache, + ) + ) + + # Apply with log suppression + logger = logging.getLogger() + missing_keys_filter = SuppressLTX2MissingKeysFilter() + logger.addFilter(missing_keys_filter) + try: + models = [] + for idx, stack in enumerate(stacks): + out_model = _apply_lora_stack_to_model(model, stack) + models.append(out_model) + if stack: + names = ", ".join(f"{e['name']}({e['strength']})" for e in stack) + logging.info(f"[IAMCCS_LTX2_ModelWithLoRA_Segmented6] segStage[{idx}] -> {names}") + return tuple(models) + finally: + logger.removeFilter(missing_keys_filter) + + +NODE_CLASS_MAPPINGS = { + "IAMCCS_LTX2_LoRAStackSegmented6": IAMCCS_LTX2_LoRAStackSegmented6, + "IAMCCS_LTX2_ModelWithLoRA_Segmented6": IAMCCS_LTX2_ModelWithLoRA_Segmented6, +} + +NODE_DISPLAY_NAME_MAPPINGS = { + "IAMCCS_LTX2_LoRAStackSegmented6": "LoRA Stack (LTX-2, segmented: 3 seg × 2 stages)", + "IAMCCS_LTX2_ModelWithLoRA_Segmented6": "Apply LoRA to MODEL (LTX-2, segmented: 3 seg × 2 stages)", +} diff --git a/iamccs_ltx2_tools.py b/iamccs_ltx2_tools.py index 5584e6b..930ea2a 100644 --- a/iamccs_ltx2_tools.py +++ b/iamccs_ltx2_tools.py @@ -15,6 +15,9 @@ from typing import Tuple import torch +_F = torch.nn.functional + + _log = logging.getLogger("IAMCCS.LTX2.Tools") @@ -101,6 +104,13 @@ class IAMCCS_LTX2_Validator: "autofix": ("BOOLEAN", {"default": True}), "length_fix": (["up", "down", "nearest"], {"default": "up"}), }, + # Optional pass-through: if provided, we will make the IMAGE batch frame-count + # match the validated length (8n+1) by padding/cropping. This is the workflow-safe + # way to guarantee the VAE encode constraint without adding extra nodes. + "optional": { + "images": ("IMAGE", {}), + "images_mode": (["pad_repeat_last", "crop_end"], {"default": "pad_repeat_last"}), + }, } RETURN_TYPES = ("IMAGE", "INT", "STRING") @@ -150,6 +160,8 @@ class IAMCCS_LTX2_Validator: length: int, autofix: bool, length_fix: str, + images: torch.Tensor | None = None, + images_mode: str = "pad_repeat_last", ): width_in = int(width) height_in = int(height) @@ -178,20 +190,64 @@ class IAMCCS_LTX2_Validator: batch_fixed = max(1, batch_in) color_fixed = max(0, min(255, color_in)) - # EmptyImage-compatible IMAGE tensor: [B,H,W,3] float in [0,1] - fill = float(color_fixed) / 255.0 - img = torch.full( - (batch_fixed, int(height_fixed), int(width_fixed), 3), - fill, - dtype=torch.float32, - device="cpu", - ) + def _pad_repeat_last_frames(x: torch.Tensor, pad: int) -> torch.Tensor: + if pad <= 0: + return x + last = x[-1:, ...].repeat(int(pad), 1, 1, 1) + return torch.cat([x, last], dim=0) + + # If an IMAGE batch is provided, enforce that its frames match the validated length. + # This is the actual guarantee needed by LTX VAE encode. + if images is not None: + img_in = images + if not torch.is_floating_point(img_in): + img_in = img_in.float() / 255.0 + # Expect ComfyUI IMAGE: [frames,H,W,C] + if img_in.ndim != 4: + raise ValueError("images must be a ComfyUI IMAGE tensor with shape [frames,H,W,C]") + + frames_in = int(img_in.shape[0]) + frames_out = int(length_fixed) + if not autofix: + frames_out = frames_in + + mode = str(images_mode or "pad_repeat_last") + if mode not in ("pad_repeat_last", "crop_end"): + mode = "pad_repeat_last" + + if frames_out == frames_in: + img = img_in + elif frames_out < frames_in: + # Crop end to match target length + img = img_in[:frames_out, ...] + else: + # Pad by repeating last frame + img = _pad_repeat_last_frames(img_in, frames_out - frames_in) + + # Note: we do NOT resize spatially here; this node's width/height validation is + # meant for parameter sanity and EmptyImage-like generation. Spatial resizing remains + # the responsibility of the workflow. + else: + # EmptyImage-compatible IMAGE tensor: [B,H,W,3] float in [0,1] + fill = float(color_fixed) / 255.0 + img = torch.full( + (batch_fixed, int(height_fixed), int(width_fixed), 3), + fill, + dtype=torch.float32, + device="cpu", + ) modified = (width_fixed != width_in) or (height_fixed != height_in) or (length_fixed != length_in) or (batch_fixed != batch_in) or (color_fixed != color_in) + if images is not None and isinstance(images, torch.Tensor) and images.ndim == 4: + modified = modified or (int(images.shape[0]) != int(img.shape[0])) implied_fps_str = "n/a" if seconds_in > 0: implied_fps = (float(length_fixed) - 1.0) / seconds_in implied_fps_str = f"{implied_fps:.3f}" + images_note = "" + if images is not None and isinstance(images, torch.Tensor) and images.ndim == 4: + images_note = f" | images_frames: in={int(images.shape[0])} -> out={int(img.shape[0])} (mode={images_mode}, autofix={autofix})" + report = ( f"input: {width_in}x{height_in}, batch={batch_in}, color={color_in} | " f"seconds_input={seconds_in:.3f}, length_input={length_in} | " @@ -200,6 +256,7 @@ class IAMCCS_LTX2_Validator: f"implied_fps={implied_fps_str} | " f"autofix={autofix} (len_fix={length_fix}) | modified={modified} | " f"delta: +{pad_w}w, +{pad_h}h, {pad_len:+d} length" + f"{images_note}" ) if not (w_ok and h_ok and l_ok): @@ -229,6 +286,13 @@ class IAMCCS_LTX2_TimeFrameCount: seconds_in = float(seconds) length_in = int(length) length_fixed = max(1, length_in) + # LTX-2 encode constraint: frames must be 8n + 1. + # Round UP to avoid shortening the requested duration. + pad = 0 + rem = (length_fixed - 1) % 8 + if rem != 0: + pad = 8 - rem + length_fixed = length_fixed + pad seconds_fixed = max(0.01, seconds_in) implied_fps_str = "n/a" @@ -236,14 +300,176 @@ class IAMCCS_LTX2_TimeFrameCount: implied_fps = (float(length_fixed) - 1.0) / seconds_fixed implied_fps_str = f"{implied_fps:.3f}" + snap = "ok" if pad == 0 else f"up(+{pad})" report = ( f"seconds_input={seconds_in:.3f}, length_input={length_in} -> " - f"seconds={seconds_fixed:.3f}, length={length_fixed} | implied_fps={implied_fps_str}" + f"seconds={seconds_fixed:.3f}, length={length_fixed} | implied_fps={implied_fps_str} | ltx2_8n+1={snap}" ) return (int(length_fixed), float(seconds_fixed), report) +class IAMCCS_LTX2_ImageBatchPadReflect: + @classmethod + def INPUT_TYPES(cls): + return { + "required": { + "images": ("IMAGE", {}), + "pad_x": ("INT", {"default": 16, "min": 0, "max": 512, "step": 1}), + "pad_y": ("INT", {"default": 16, "min": 0, "max": 512, "step": 1}), + "pad_mode": (["reflect", "replicate"], {"default": "reflect"}), + } + } + + RETURN_TYPES = ("IMAGE", "INT", "INT", "STRING") + RETURN_NAMES = ("images", "pad_x", "pad_y", "report") + FUNCTION = "pad" + CATEGORY = "IAMCCS/LTX-2" + + def pad(self, images: torch.Tensor, pad_x: int, pad_y: int, pad_mode: str): + # images: [B,H,W,C] float + if images is None: + raise ValueError("images is required") + + b, h, w, c = images.shape + px = max(0, int(pad_x)) + py = max(0, int(pad_y)) + + # reflect requires pad < dim; clamp to avoid runtime errors + px_eff = min(px, max(0, w - 1)) + py_eff = min(py, max(0, h - 1)) + + if px_eff == 0 and py_eff == 0: + return (images, 0, 0, f"PadReflect: no-op (input {w}x{h})") + + mode = str(pad_mode or "reflect") + if mode not in ("reflect", "replicate"): + mode = "reflect" + + x = images.permute(0, 3, 1, 2) # [B,C,H,W] + # pad format: (left, right, top, bottom) + x = _F.pad(x, (px_eff, px_eff, py_eff, py_eff), mode=mode) + out = x.permute(0, 2, 3, 1).contiguous() + + report = ( + f"PadReflect: mode={mode}, requested=({px},{py}), used=({px_eff},{py_eff}) | " + f"{w}x{h} -> {int(out.shape[2])}x{int(out.shape[1])}" + ) + return (out, int(px_eff), int(py_eff), report) + + +class IAMCCS_LTX2_ImageBatchCropByPad: + @classmethod + def INPUT_TYPES(cls): + return { + "required": { + "images": ("IMAGE", {}), + "pad_x": ("INT", {"default": 16, "min": 0, "max": 512, "step": 1}), + "pad_y": ("INT", {"default": 16, "min": 0, "max": 512, "step": 1}), + } + } + + RETURN_TYPES = ("IMAGE", "STRING") + RETURN_NAMES = ("images", "report") + FUNCTION = "crop" + CATEGORY = "IAMCCS/LTX-2" + + def crop(self, images: torch.Tensor, pad_x: int, pad_y: int): + if images is None: + raise ValueError("images is required") + + b, h, w, c = images.shape + px = max(0, int(pad_x)) + py = max(0, int(pad_y)) + + if px == 0 and py == 0: + return (images, f"CropByPad: no-op (input {w}x{h})") + + # Clamp so we never invert the crop + px_eff = min(px, max(0, (w - 1) // 2)) + py_eff = min(py, max(0, (h - 1) // 2)) + + out = images[:, py_eff : h - py_eff, px_eff : w - px_eff, :] + report = ( + f"CropByPad: requested=({px},{py}), used=({px_eff},{py_eff}) | " + f"{w}x{h} -> {int(out.shape[2])}x{int(out.shape[1])}" + ) + return (out, report) + + +class IAMCCS_LTX2_EnsureFrames8nPlus1: + @classmethod + def INPUT_TYPES(cls): + return { + "required": { + "images": ("IMAGE", {}), + # LTX-2 encode constraint: frames must be 1 + 8*k. + # "pad" is workflow-safe (never shortens), "crop" is deterministic. + "mode": (["pad_repeat_last", "crop_end"], {"default": "pad_repeat_last"}), + "fix": (["up", "down", "nearest"], {"default": "up"}), + } + } + + RETURN_TYPES = ("IMAGE", "INT", "STRING") + RETURN_NAMES = ("images", "frames", "report") + FUNCTION = "ensure" + CATEGORY = "IAMCCS/LTX-2" + + def _frames_rule_fix(self, frames: int, mode: str) -> tuple[int, int]: + frames = int(frames) + if frames < 1: + frames = 1 + + rem = (frames - 1) % 8 + if rem == 0: + return frames, 0 + + down = frames - rem + up = frames + (8 - rem) + + if mode == "down": + fixed = max(1, down) + elif mode == "nearest": + fixed = up if (up - frames) <= (frames - down) else max(1, down) + else: + fixed = up + + return fixed, fixed - frames + + def ensure(self, images: torch.Tensor, mode: str, fix: str): + if images is None: + raise ValueError("images is required") + + if not isinstance(images, torch.Tensor) or images.ndim != 4: + raise ValueError("images must be a ComfyUI IMAGE tensor with shape [frames,H,W,C]") + + frames_in = int(images.shape[0]) + frames_fixed, delta = self._frames_rule_fix(frames_in, str(fix or "up")) + + if frames_fixed == frames_in: + return (images, frames_in, f"EnsureFrames8n+1: ok ({frames_in})") + + mode = str(mode or "pad_repeat_last") + if mode == "crop_end": + # For crop mode, prefer shortening, regardless of requested 'fix'. + rem = (frames_in - 1) % 8 + frames_fixed = frames_in if rem == 0 else max(1, frames_in - rem) + out = images[:frames_fixed, ...] + return (out, frames_fixed, f"EnsureFrames8n+1: crop_end {frames_in} -> {frames_fixed}") + + # pad_repeat_last (workflow-safe) + if frames_fixed < frames_in: + # If user selected fix=down/nearest and it resulted in fewer frames, + # still keep behavior consistent with padding node: do a crop. + out = images[:frames_fixed, ...] + return (out, frames_fixed, f"EnsureFrames8n+1: crop_end {frames_in} -> {frames_fixed} (fix={fix})") + + pad = int(frames_fixed - frames_in) + last = images[-1:, ...].repeat(pad, 1, 1, 1) + out = torch.cat([images, last], dim=0) + return (out, frames_fixed, f"EnsureFrames8n+1: pad_repeat_last {frames_in} -> {frames_fixed} (pad={pad}, fix={fix})") + + class IAMCCS_LTX2_ControlPreprocess: @classmethod def INPUT_TYPES(cls): @@ -341,6 +567,7 @@ NODE_CLASS_MAPPINGS = { "IAMCCS_LTX2_FrameRateSync": IAMCCS_LTX2_FrameRateSync, "IAMCCS_LTX2_Validator": IAMCCS_LTX2_Validator, "IAMCCS_LTX2_TimeFrameCount": IAMCCS_LTX2_TimeFrameCount, + "IAMCCS_LTX2_EnsureFrames8nPlus1": IAMCCS_LTX2_EnsureFrames8nPlus1, "IAMCCS_LTX2_ControlPreprocess": IAMCCS_LTX2_ControlPreprocess, } @@ -348,5 +575,6 @@ NODE_DISPLAY_NAME_MAPPINGS = { "IAMCCS_LTX2_FrameRateSync": "LTX-2 FrameRate Sync (int+float)", "IAMCCS_LTX2_Validator": "LTX-2 Validator (32px, 8n +1)", "IAMCCS_LTX2_TimeFrameCount": "LTX-2 TimeFrameCount", + "IAMCCS_LTX2_EnsureFrames8nPlus1": "LTX-2 Ensure Frames (8n + 1)", "IAMCCS_LTX2_ControlPreprocess": "LTX-2 Control Preprocess (aux)", } diff --git a/pyproject.toml b/pyproject.toml index a518d9e..0a15e6b 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,7 +1,7 @@ [project] name = "iamccs-wan-lora-fixer-stack" -version = "1.3.2" -description = "IAMCCS multi-LoRA stack & MODEL IO WAN remap + LTX-2 LoRA/tools nodes (FrameRate Sync, Validator, Control Preprocess, LTX-2 LoRA stacks)." +version = "1.3.3" +description = "IAMCCS nodes for ComfyUI: multi-LoRA stack + MODEL IO WAN/Flow remap + LTX-2 LoRA/tools + LTX-2 Extension Module + AutoLink Set/Get + Converter (frontend)." license = { text = "MIT" } authors = [ { name = "Carmine Cristallo Scalzi (IAMCCS)", email = "info@carminecristalloscalzi.com" } diff --git a/version.json b/version.json index 44bc625..e3be690 100644 --- a/version.json +++ b/version.json @@ -1,6 +1,6 @@ { "name": "iamccs-wan-lora-fixer-stack", - "version": "1.3.2", + "version": "1.3.3", "author": "Carmine Cristallo Scalzi (IAMCCS)", - "description": "IAMCCS nodes for ComfyUI: multi-LoRA stack + direct MODEL IO WAN/Flow remap + LTX-2 LoRA & utility nodes (FrameRate Sync, Validator, Control Preprocess, LTX-2 LoRA stacks). WAN 2.1 + 2.2 compatible." + "description": "IAMCCS nodes for ComfyUI: multi-LoRA stack + direct MODEL IO WAN/Flow remap + LTX-2 LoRA & utility nodes + LTX-2 Extension Module + AutoLink (Set/Get + Converter, frontend). WAN 2.1 + 2.2 compatible." } diff --git a/web/iamccs_autolink_converter.js b/web/iamccs_autolink_converter.js new file mode 100644 index 0000000..bb6b601 --- /dev/null +++ b/web/iamccs_autolink_converter.js @@ -0,0 +1,2795 @@ +// IAMCCS AutoLink - Based on KJ nodes SetNode/GetNode +// Nodi puramente frontend - non eseguono niente lato Python + +import { app } from "../../scripts/app.js"; + +const SET_TYPE = "IAMCCS_SetAutoLink"; +const GET_TYPE = "IAMCCS_GetAutoLink"; +const CONVERTER_TYPE = "IAMCCS_AutoLinkConverter"; +const ARGUMENTS_TYPE = "IAMCCS_AutoLinkArguments"; +const KJ_SET_TYPE = "SetNode"; +const KJ_GET_TYPE = "GetNode"; + +console.log("[IAMCCS AutoLink] Loading extension..."); + +function alphaSort(values) { + return [...values].sort((a, b) => String(a).localeCompare(String(b), undefined, { sensitivity: 'base' })); +} + +function getWidget(node, widgetName) { + if (!node?.widgets?.length) return null; + return node.widgets.find(w => w?.name === widgetName || w?.label === widgetName) || null; +} + +function getWidgetValue(node, widgetName) { + return getWidget(node, widgetName)?.value; +} + +function isValidAutolinkKey(value) { + const s = String(value ?? "").trim(); + return !!s && s !== "*"; +} + +function getAutolinkKey(node) { + const v = getWidgetValue(node, "name"); + if (isValidAutolinkKey(v)) return String(v).trim(); + const outName = node?.outputs?.[0]?.name; + if (isValidAutolinkKey(outName)) return String(outName).trim(); + const inName = node?.inputs?.[0]?.name; + if (isValidAutolinkKey(inName)) return String(inName).trim(); + return ""; +} + +function setAutolinkKeyAndTitle(node, key) { + const safeKey = String(key ?? "").trim(); + if (!node || !isValidAutolinkKey(safeKey)) return; + + setWidgetValue(node, "name", safeKey); + node.title = safeKey; + + if (node.type === SET_TYPE) { + if (node.inputs?.[0]) node.inputs[0].name = safeKey; + if (node.outputs?.[0]) node.outputs[0].name = safeKey; + } + + const nameWidget = getWidget(node, "name"); + if (nameWidget) nameWidget.lastValue = safeKey; +} + +function normalizeAutolinkIOSlots(graph, node, { wantInputs = 0, wantOutputs = 0 } = {}) { + if (!node) return; + + // Ensure arrays exist + if (!node.inputs) node.inputs = []; + if (!node.outputs) node.outputs = []; + + // Ensure at least one slot exists when requested + if (wantInputs > 0 && node.inputs.length === 0 && typeof node.addInput === "function") { + node.addInput("*", "*"); + } + if (wantOutputs > 0 && node.outputs.length === 0 && typeof node.addOutput === "function") { + node.addOutput("*", "*"); + } + + // If multiple inputs got serialized (buggy old workflows), try to move any link onto slot 0 + if (wantInputs > 0 && node.inputs.length > 1) { + try { + const in0HasLink = !!node.inputs?.[0]?.link; + if (!in0HasLink) { + const idx = node.inputs.findIndex((inp, i) => i > 0 && inp?.link != null); + if (idx > 0) { + const linkId = node.inputs[idx].link; + const link = graph?.links?.[linkId]; + if (link) { + try { node.disconnectInput?.(idx); } catch {} + const src = graph?.getNodeById?.(link.origin_id); + if (src) { + try { + // Reconnect to slot 0 + src.connect(link.origin_slot, node, 0); + } catch {} + } + } + } + } + } catch {} + + // Remove extra input slots (keep only slot 0) + try { + while (node.inputs.length > 1) { + if (typeof node.removeInput === "function") node.removeInput(1); + else node.inputs.splice(1, 1); + } + } catch {} + } + + // Same for outputs: if multiple outputs exist, try to migrate links to slot 0 + if (wantOutputs > 0 && node.outputs.length > 1) { + try { + const out0Links = node.outputs?.[0]?.links; + const out0HasLinks = Array.isArray(out0Links) && out0Links.length > 0; + if (!out0HasLinks) { + for (let i = 1; i < node.outputs.length; i++) { + const links = node.outputs?.[i]?.links; + if (!Array.isArray(links) || links.length === 0) continue; + for (const linkId of [...links]) { + const link = graph?.links?.[linkId]; + if (!link) continue; + const dst = graph?.getNodeById?.(link.target_id); + if (!dst) continue; + const ts = link.target_slot; + try { dst.disconnectInput?.(ts); } catch {} + try { node.connect(0, dst, ts); } catch {} + } + } + } + } catch {} + + // Remove extra output slots (keep only slot 0) + try { + while (node.outputs.length > 1) { + if (typeof node.removeOutput === "function") node.removeOutput(1); + else node.outputs.splice(1, 1); + } + } catch {} + } +} + +function makeUniqueAutolinkSetName(graph, desired) { + const raw = String(desired ?? "").trim(); + let base = raw; + + // sanitize + base = base.replace(/\s+/g, "_"); + base = base.replace(/^\*+|\*+$/g, ""); + base = base.trim(); + if (!isValidAutolinkKey(base)) base = "output"; + + // prefer lower-case keys for readability + base = String(base).trim(); + const baseLower = base.toLowerCase(); + + const used = new Set( + (graph?._nodes || []) + .filter(n => n?.type === SET_TYPE) + .map(n => getAutolinkKey(n)) + .filter(v => isValidAutolinkKey(v)) + ); + + // If already has _N suffix, keep it unless it collides. + const m = baseLower.match(/^(.+?)_(\d+)$/); + if (m) { + const stem = m[1]; + let n = parseInt(m[2], 10); + let candidate = `${stem}_${n}`; + while (used.has(candidate)) { + n++; + candidate = `${stem}_${n}`; + } + return candidate; + } + + // Always add _0, _1, ... (matches convertAllLinks behavior) + let n = 0; + let candidate = `${baseLower}_${n}`; + while (used.has(candidate)) { + n++; + candidate = `${baseLower}_${n}`; + } + return candidate; +} + +function parseHexColor(hex) { + if (!hex || typeof hex !== 'string') return null; + const s = hex.trim(); + if (!s.startsWith('#')) return null; + const h = s.slice(1); + if (h.length === 3) { + const r = parseInt(h[0] + h[0], 16); + const g = parseInt(h[1] + h[1], 16); + const b = parseInt(h[2] + h[2], 16); + if (Number.isNaN(r) || Number.isNaN(g) || Number.isNaN(b)) return null; + return { r, g, b }; + } + if (h.length === 6) { + const r = parseInt(h.slice(0, 2), 16); + const g = parseInt(h.slice(2, 4), 16); + const b = parseInt(h.slice(4, 6), 16); + if (Number.isNaN(r) || Number.isNaN(g) || Number.isNaN(b)) return null; + return { r, g, b }; + } + return null; +} + +function getTitleTextColor(mode, bgcolorHex) { + const m = String(mode || 'White'); + if (m === 'Black') return '#000000'; + if (m === 'White') return '#ffffff'; + // Auto + const rgb = parseHexColor(bgcolorHex); + if (!rgb) return '#ffffff'; + // relative luminance-ish (sufficient for UI contrast) + const lum = (0.2126 * rgb.r + 0.7152 * rgb.g + 0.0722 * rgb.b) / 255; + return lum > 0.55 ? '#000000' : '#ffffff'; +} + +function applyNodeTitleTextColor(node, mode) { + if (!node) return; + const color = getTitleTextColor(mode, node.bgcolor); + // LiteGraph variants (best-effort) + node.title_text_color = color; + node.titleTextColor = color; + // Some variants read textcolor for title text too + node.textcolor = color; + node.textColor = color; + node.properties = node.properties || {}; + node.properties.autolink_title_color = color; +} + +let AUTOLINK_TITLE_COLOR_HOOK_INSTALLED = false; +function installAutolinkTitleColorCanvasHook() { + if (AUTOLINK_TITLE_COLOR_HOOK_INSTALLED) return; + try { + const LGraphCanvas = window?.LGraphCanvas; + const LiteGraph = window?.LiteGraph; + if (!LGraphCanvas?.prototype?.drawNode || !LiteGraph) return; + + const originalDrawNode = LGraphCanvas.prototype.drawNode; + if (originalDrawNode?.__iamccs_autolink_title_hook) { + AUTOLINK_TITLE_COLOR_HOOK_INSTALLED = true; + return; + } + + function wrappedDrawNode(node, ctx) { + const isAutolink = node && (node.type === SET_TYPE || node.type === GET_TYPE); + if (!isAutolink) return originalDrawNode.apply(this, arguments); + + const desired = node?.properties?.autolink_title_color || node?.title_text_color || node?.textcolor; + if (!desired) return originalDrawNode.apply(this, arguments); + + // Some ComfyUI/LiteGraph builds force title text to white on "dark" nodes. + // To respect ColorTitles (White/Black/Auto) without drawing a second title, + // we temporarily wrap ctx.fillText and override fillStyle only for the title draw call. + const prevFillText = ctx?.fillText; + let titleA = ""; + let titleB = ""; + try { + const rawTitle = (typeof node?.getTitle === "function") ? node.getTitle() : node?.title; + titleA = String(rawTitle ?? ""); + titleB = String(node?.title ?? ""); + if (node?.pinned) { + if (titleA) titleA += "📌"; + if (titleB) titleB += "📌"; + } + } catch { + // ignore + } + + if (typeof prevFillText === "function") { + ctx.fillText = function(text, x, y, maxWidth) { + try { + const t = String(text ?? ""); + const isTitle = (t && (t === titleA || t === titleB || (titleA && titleA.startsWith(t)) || (titleB && titleB.startsWith(t)))); + // title is drawn in the title bar area (usually negative y) + if (isTitle && typeof y === "number" && y < 0) { + const prevStyle = ctx.fillStyle; + ctx.fillStyle = desired; + try { + return prevFillText.apply(this, arguments); + } finally { + ctx.fillStyle = prevStyle; + } + } + } catch { + // ignore + } + return prevFillText.apply(this, arguments); + }; + } + + const prevNodeTitleColor = LiteGraph.NODE_TITLE_COLOR; + const prevSelectedTitleColor = LiteGraph.NODE_SELECTED_TITLE_COLOR; + const prevNodeTextColor = LiteGraph.NODE_TEXT_COLOR; + const prevSelectedTextColor = LiteGraph.NODE_SELECTED_TEXT_COLOR; + if (typeof LiteGraph.NODE_TITLE_COLOR !== "undefined") LiteGraph.NODE_TITLE_COLOR = desired; + if (typeof LiteGraph.NODE_SELECTED_TITLE_COLOR !== "undefined") LiteGraph.NODE_SELECTED_TITLE_COLOR = desired; + if (typeof LiteGraph.NODE_TEXT_COLOR !== "undefined") LiteGraph.NODE_TEXT_COLOR = desired; + if (typeof LiteGraph.NODE_SELECTED_TEXT_COLOR !== "undefined") LiteGraph.NODE_SELECTED_TEXT_COLOR = desired; + + try { + return originalDrawNode.apply(this, arguments); + } finally { + if (typeof prevFillText === "function") ctx.fillText = prevFillText; + if (typeof prevNodeTitleColor !== "undefined") LiteGraph.NODE_TITLE_COLOR = prevNodeTitleColor; + if (typeof prevSelectedTitleColor !== "undefined") LiteGraph.NODE_SELECTED_TITLE_COLOR = prevSelectedTitleColor; + if (typeof prevNodeTextColor !== "undefined") LiteGraph.NODE_TEXT_COLOR = prevNodeTextColor; + if (typeof prevSelectedTextColor !== "undefined") LiteGraph.NODE_SELECTED_TEXT_COLOR = prevSelectedTextColor; + } + } + wrappedDrawNode.__iamccs_autolink_title_hook = true; + LGraphCanvas.prototype.drawNode = wrappedDrawNode; + AUTOLINK_TITLE_COLOR_HOOK_INSTALLED = true; + } catch (e) { + console.warn("[IAMCCS AutoLink] Failed to install title color hook", e); + } +} + +function getCurrentArgumentsNode() { + try { + return app?.graph?._nodes?.find(n => n?.type === ARGUMENTS_TYPE) || null; + } catch { + return null; + } +} + +function getCurrentColorTitlesMode() { + const argNode = getCurrentArgumentsNode(); + return argNode?.properties?.color_titles || "White"; +} + +const AUTOLINK_COLORS = { + Gray: { set: { color: "#1f1f1f", bgcolor: "#3a3a3a" }, get: { color: "#2a2a2a", bgcolor: "#555555" } }, + Blue: { set: { color: "#1b4669", bgcolor: "#29699c" }, get: { color: "#234f73", bgcolor: "#347cb8" } }, + Green: { set: { color: "#1f5a3a", bgcolor: "#2d7d52" }, get: { color: "#2a6b46", bgcolor: "#3aa66c" } }, + Red: { set: { color: "#6a1b1b", bgcolor: "#9c2929" }, get: { color: "#7a2323", bgcolor: "#b83434" } }, + Orange: { set: { color: "#6b3e1a", bgcolor: "#9c5b29" }, get: { color: "#7a4a22", bgcolor: "#b86c34" } }, + Purple: { set: { color: "#3f1b69", bgcolor: "#5e299c" }, get: { color: "#4a237a", bgcolor: "#7034b8" } }, + Yellow: { set: { color: "#6b651a", bgcolor: "#9c9229" }, get: { color: "#7a7422", bgcolor: "#b8aa34" } }, + Teal: { set: { color: "#1b6961", bgcolor: "#299c90" }, get: { color: "#237a71", bgcolor: "#34b8aa" } }, + Pink: { set: { color: "#691b46", bgcolor: "#9c2969" }, get: { color: "#7a2351", bgcolor: "#b8347c" } }, +}; + +function getAutolinkColorPreset(colorName, role, separateCol, colorGetName) { + const safeRole = role === 'get' ? 'get' : 'set'; + const base = AUTOLINK_COLORS[colorName] || AUTOLINK_COLORS.Gray; + if (!separateCol) return base.set; // stessa identità colore per set/get + + if (safeRole === 'get' && colorGetName && AUTOLINK_COLORS[colorGetName]) { + return AUTOLINK_COLORS[colorGetName].get; + } + + return base[safeRole]; +} + +function applyNodeColors(node, preset) { + if (!node || !preset) return; + node.color = preset.color; + node.bgcolor = preset.bgcolor; +} + +function recolorExistingAutoLinks(graph, colorSetName = "Gray", separateCol = false, colorGetName = "Gray", colorTitles = "White") { + if (!graph) return; + const sets = graph._nodes.filter(n => n?.type === SET_TYPE); + const gets = graph._nodes.filter(n => n?.type === GET_TYPE); + + for (const setNode of sets) { + // Migrazione: vecchi workflow (prima del widget colore) possono aver scritto il nome nel widget colore. + const maybeName = getWidgetValue(setNode, "name"); + const maybeColor = getWidgetValue(setNode, "AutoLinkColor"); + if ((!maybeName || !String(maybeName).trim()) && maybeColor && !AUTOLINK_COLORS[maybeColor]) { + const migrated = String(maybeColor).trim(); + setAutolinkKeyAndTitle(setNode, migrated); + setWidgetValue(setNode, "AutoLinkColor", "Gray"); + setNode.properties = setNode.properties || {}; + setNode.properties.autolink_color_name = "Gray"; + } + + const key = getAutolinkKey(setNode); + + setNode.properties = setNode.properties || {}; + if (setNode.properties.autolink_color_locked === undefined) { + setNode.properties.autolink_color_locked = false; + } + + const locked = !!setNode.properties.autolink_color_locked; + const chosen = locked + ? (setNode.properties.autolink_color_name || colorSetName) + : colorSetName; + + // mantieni la sorgente di verità sempre in autolink_color_name + setNode.properties.autolink_color_name = chosen; + applyNodeColors(setNode, getAutolinkColorPreset(chosen, 'set', separateCol, colorGetName)); + applyNodeTitleTextColor(setNode, colorTitles); + } + + for (const getNode of gets) { + const key = getAutolinkKey(getNode); + const chosen = key + ? (graph._nodes.find(n => n?.type === SET_TYPE && getAutolinkKey(n) === key)?.properties?.autolink_color_name || colorSetName) + : colorSetName; + applyNodeColors(getNode, getAutolinkColorPreset(chosen, 'get', separateCol, colorGetName)); + getNode.properties = getNode.properties || {}; + getNode.properties.autolink_color_name = chosen; + applyNodeTitleTextColor(getNode, colorTitles); + } + + graph.setDirtyCanvas(true, true); +} + +function applyColorToAutolinkKey(graph, key, colorName, separateCol = false, colorGetName = "Gray", colorTitles = "White") { + if (!graph || !key) return; + const safeKey = String(key).trim(); + const sets = graph._nodes.filter(n => n?.type === SET_TYPE && getAutolinkKey(n) === safeKey); + const gets = graph._nodes.filter(n => n?.type === GET_TYPE && getAutolinkKey(n) === safeKey); + + for (const setNode of sets) { + setNode.properties = setNode.properties || {}; + setNode.properties.autolink_color_name = colorName; + setNode.properties.autolink_color_locked = true; + applyNodeColors(setNode, getAutolinkColorPreset(colorName, 'set', separateCol, colorGetName)); + applyNodeTitleTextColor(setNode, colorTitles); + } + for (const getNode of gets) { + getNode.properties = getNode.properties || {}; + getNode.properties.autolink_color_name = colorName; + applyNodeColors(getNode, getAutolinkColorPreset(colorName, 'get', separateCol, colorGetName)); + applyNodeTitleTextColor(getNode, colorTitles); + } + + graph.setDirtyCanvas(true, true); +} + +// === VISUAL FLOW TRACER === +let flowTracerEnabled = false; +let flowCanvas = null; +let animationFrameId = null; +let activeFlows = []; + +function toggleFlowTracer(enabled) { + flowTracerEnabled = enabled; + + if (enabled && !flowCanvas) { + flowCanvas = document.createElement('canvas'); + flowCanvas.id = 'autolink-flow-overlay'; + flowCanvas.style.position = 'absolute'; + flowCanvas.style.top = '0'; + flowCanvas.style.left = '0'; + flowCanvas.style.pointerEvents = 'none'; + flowCanvas.style.zIndex = '999'; + document.body.appendChild(flowCanvas); + + const resizeCanvas = () => { + flowCanvas.width = window.innerWidth; + flowCanvas.height = window.innerHeight; + }; + resizeCanvas(); + window.addEventListener('resize', resizeCanvas); + + startFlowAnimation(); + console.log("[IAMCCS AutoLink] ✨ Flow Tracer enabled"); + } else if (!enabled && flowCanvas) { + if (animationFrameId) { + cancelAnimationFrame(animationFrameId); + animationFrameId = null; + } + flowCanvas.remove(); + flowCanvas = null; + activeFlows = []; + console.log("[IAMCCS AutoLink] Flow Tracer disabled"); + } +} + +function startFlowAnimation() { + if (!flowTracerEnabled || !flowCanvas) return; + + const ctx = flowCanvas.getContext('2d'); + ctx.clearRect(0, 0, flowCanvas.width, flowCanvas.height); + + // Trova tutte le coppie Set-Get attive + activeFlows = []; + const setNodes = app.graph._nodes.filter(n => n.type === SET_TYPE); + const getNodes = app.graph._nodes.filter(n => n.type === GET_TYPE); + + setNodes.forEach(setNode => { + const setName = getAutolinkKey(setNode); + if (!setName) return; + + const matchingGets = getNodes.filter(g => getAutolinkKey(g) === setName); + matchingGets.forEach(getNode => { + const dataType = setNode.outputs[0]?.type || '*'; + activeFlows.push({ + setNode: setNode, + getNode: getNode, + dataType: dataType, + particles: initParticles(5) + }); + }); + }); + + animateFlows(ctx); +} + +function initParticles(count) { + const particles = []; + for (let i = 0; i < count; i++) { + particles.push({ + progress: Math.random(), + speed: 0.005 + Math.random() * 0.01, + size: 3 + Math.random() * 3 + }); + } + return particles; +} + +function animateFlows(ctx) { + if (!flowTracerEnabled || !flowCanvas) return; + + ctx.clearRect(0, 0, flowCanvas.width, flowCanvas.height); + + activeFlows.forEach(flow => { + const setPos = getNodeScreenPosition(flow.setNode); + const getPos = getNodeScreenPosition(flow.getNode); + const color = getTypeColor(flow.dataType); + + // Disegna linea di connessione + ctx.strokeStyle = color; + ctx.lineWidth = 2; + ctx.globalAlpha = 0.3; + ctx.setLineDash([5, 5]); + ctx.beginPath(); + ctx.moveTo(setPos.x, setPos.y); + ctx.lineTo(getPos.x, getPos.y); + ctx.stroke(); + ctx.setLineDash([]); + + // Anima particelle + flow.particles.forEach(particle => { + particle.progress += particle.speed; + if (particle.progress > 1) particle.progress = 0; + + const x = setPos.x + (getPos.x - setPos.x) * particle.progress; + const y = setPos.y + (getPos.y - setPos.y) * particle.progress; + + ctx.globalAlpha = 0.8; + ctx.fillStyle = color; + ctx.beginPath(); + ctx.arc(x, y, particle.size, 0, Math.PI * 2); + ctx.fill(); + }); + }); + + ctx.globalAlpha = 1.0; + animationFrameId = requestAnimationFrame(() => animateFlows(ctx)); +} + +function getNodeScreenPosition(node) { + const canvasElement = document.querySelector('.litegraph'); + if (!canvasElement || !app.canvas) { + return { x: 0, y: 0 }; + } + + const rect = canvasElement.getBoundingClientRect(); + const scale = app.canvas.ds.scale; + const offset = app.canvas.ds.offset; + + const x = rect.left + (node.pos[0] + node.size[0] / 2) * scale + offset[0] * scale; + const y = rect.top + (node.pos[1] + node.size[1] / 2) * scale + offset[1] * scale; + + return { x, y }; +} + +function getTypeColor(dataType) { + const colors = { + 'MODEL': '#4a9eff', + 'VAE': '#ff4a9e', + 'CLIP': '#4aff9e', + 'IMAGE': '#ffff4a', + 'LATENT': '#ff9e4a', + 'CONDITIONING': '#9e4aff', + 'MASK': '#ff6b6b', + 'FLOAT': '#6bffa3', + 'INT': '#a3c6ff' + }; + return colors[dataType] || '#ffffff'; +} + +function _iamccsFixLinkIntegrity(graph) { + try { + if (!graph || !graph.links) return; + + // 1) Ensure each graph.links entry is reflected in origin.outputs[*].links and target.inputs[*].link + for (const [idStr, link] of Object.entries(graph.links)) { + const linkId = Number(idStr); + if (!link || !Number.isFinite(linkId)) continue; + + const origin = graph.getNodeById?.(link.origin_id); + const target = graph.getNodeById?.(link.target_id); + const os = Number(link.origin_slot); + const ts = Number(link.target_slot); + if (!origin || !target || !Number.isFinite(os) || !Number.isFinite(ts)) continue; + + // Origin + try { + origin.outputs = origin.outputs || []; + const out = origin.outputs[os]; + if (out) { + if (!Array.isArray(out.links)) out.links = []; + if (!out.links.includes(linkId)) out.links.push(linkId); + } + } catch {} + + // Target + try { + target.inputs = target.inputs || []; + const inp = target.inputs[ts]; + if (inp) { + // Never overwrite an existing target link: if this link isn't the one + // referenced by the input, it's an orphan/duplicate and should be removed. + if (inp.link == null) { + inp.link = linkId; + } else if (Number(inp.link) !== linkId) { + try { + // Remove orphan link from origin output list + const o2 = graph.getNodeById?.(link.origin_id); + const os2 = Number(link.origin_slot); + if (o2?.outputs?.[os2]?.links && Array.isArray(o2.outputs[os2].links)) { + o2.outputs[os2].links = o2.outputs[os2].links.filter(x => Number(x) !== linkId); + } + } catch {} + try { delete graph.links[linkId]; } catch {} + continue; + } + } + } catch {} + } + + // 2) Remove stale link IDs from outputs[*].links that don't exist in graph.links + const existing = new Set(Object.keys(graph.links).map(k => Number(k)).filter(Number.isFinite)); + for (const node of graph._nodes || []) { + if (!node?.outputs?.length) continue; + for (const out of node.outputs) { + if (!out || !Array.isArray(out.links) || out.links.length === 0) continue; + out.links = out.links.filter(id => existing.has(Number(id))); + } + } + } catch (e) { + console.warn("[IAMCCS AutoLink] Link integrity fix failed", e); + } +} + +function _iamccsForceRemoveLink(graph, linkId) { + if (!graph || !graph.links || linkId == null) return; + const id = Number(linkId); + if (!Number.isFinite(id)) return; + const link = graph.links?.[id]; + if (!link) return; + + // Remove from origin output links array if possible + try { + const origin = graph.getNodeById?.(link.origin_id); + const os = Number(link.origin_slot); + if (origin?.outputs?.[os]?.links && Array.isArray(origin.outputs[os].links)) { + origin.outputs[os].links = origin.outputs[os].links.filter(x => Number(x) !== id); + } + } catch {} + + // Remove from target input pointer if it points to this link + try { + const target = graph.getNodeById?.(link.target_id); + const ts = Number(link.target_slot); + if (target?.inputs?.[ts] && Number(target.inputs[ts].link) === id) { + target.inputs[ts].link = null; + } + } catch {} + + try { + // Prefer native removal if present + if (typeof graph.removeLink === "function") { + graph.removeLink(id); + return; + } + } catch {} + + try { delete graph.links[id]; } catch {} +} + +function _iamccsDisconnectTargetInput(graph, dstNode, ts) { + try { + const slot = Number(ts); + if (!Number.isFinite(slot) || !dstNode) return; + const prevId = dstNode?.inputs?.[slot]?.link; + + try { dstNode.disconnectInput?.(slot); } catch {} + + const still = dstNode?.inputs?.[slot]?.link; + if (still != null) { + // If disconnect failed, force-remove whatever link the input points to + _iamccsForceRemoveLink(graph, still); + try { + if (dstNode?.inputs?.[slot]) dstNode.inputs[slot].link = null; + } catch {} + } else if (prevId != null) { + // Some implementations clear input.link but keep graph.links/origin.outputs stale + _iamccsForceRemoveLink(graph, prevId); + } + } catch {} +} + +function _iamccsRemoveOtherLinksToTarget(graph, targetId, targetSlot, keepLinkId) { + try { + const tid = Number(targetId); + const ts = Number(targetSlot); + const keep = keepLinkId != null ? Number(keepLinkId) : null; + if (!graph?.links || !Number.isFinite(tid) || !Number.isFinite(ts)) return; + + for (const [idStr, link] of Object.entries(graph.links)) { + const id = Number(idStr); + if (!Number.isFinite(id) || !link) continue; + if (keep != null && id === keep) continue; + if (Number(link.target_id) === tid && Number(link.target_slot) === ts) { + _iamccsForceRemoveLink(graph, id); + } + } + } catch {} +} + +// === CSS FIXES === +// Fix per titoli che escono dai nodi contratti +const style = document.createElement('style'); +style.textContent = ` + .litegraph .node.collapsed .title { + max-width: calc(100% - 40px); + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + display: inline-block; + } +`; +document.head.appendChild(style); + +// === CONVERTER NODE === +app.registerExtension({ + name: "iamccs.autolink.converter", + + async beforeRegisterNodeDef(nodeType, nodeData, app) { + // Ensure native title coloring works without overlay text + installAutolinkTitleColorCanvasHook(); + + if (nodeData?.name === CONVERTER_TYPE) { + const onNodeCreated = nodeType.prototype.onNodeCreated; + nodeType.prototype.onNodeCreated = function() { + const result = onNodeCreated?.apply(this, arguments); + + this.addWidget("button", "🔗 Convert All Links", null, () => { + const config = getBlacklistFromInput(this); + convertAllLinks( + app.graph, + config.blacklist, + config.blacklistTypes, + config.blacklistNodeModes, + config.includeKijNodes, + config.groupExclude, + config.groupInOutExclude, + config.alignMode, + config.packingMode, + config.colorSet, + config.colorGet, + config.separateCol, + config.colorTitles + ); + }); + + this.addWidget("button", "↩️ Restore Direct Links", null, () => { + restoreDirectLinks(app.graph); + }); + + return result; + }; + } + + if (nodeData?.name === ARGUMENTS_TYPE) { + const onNodeCreated = nodeType.prototype.onNodeCreated; + nodeType.prototype.onNodeCreated = function() { + const result = onNodeCreated?.apply(this, arguments); + + if (!this.properties) this.properties = {}; + if (!this.properties.blacklist) this.properties.blacklist = []; + if (!this.properties.blacklist_types) this.properties.blacklist_types = []; + if (!this.properties.blacklist_node_modes) this.properties.blacklist_node_modes = {}; + if (this.properties.blacklist_add_mode === undefined) this.properties.blacklist_add_mode = "both"; + if (this.properties.blacklist_pending_value === undefined) this.properties.blacklist_pending_value = ""; + if (this.properties.blacklist_view_selected === undefined) this.properties.blacklist_view_selected = ""; + if (this.properties.include_kijnodes === undefined) this.properties.include_kijnodes = false; + if (this.properties.flow_tracer === undefined) this.properties.flow_tracer = false; + if (this.properties.all_nodes_sel === undefined) this.properties.all_nodes_sel = false; + if (this.properties.align_mode === undefined) this.properties.align_mode = "TopToDown"; + if (this.properties.packing_mode === undefined) this.properties.packing_mode = "AvoidAll"; + if (this.properties.autolink_color_set === undefined) this.properties.autolink_color_set = "Gray"; + if (this.properties.autolink_color_get === undefined) this.properties.autolink_color_get = "Gray"; + if (this.properties.separate_col === undefined) this.properties.separate_col = false; + if (this.properties.color_titles === undefined) this.properties.color_titles = "White"; + if (this.properties.group_inout_exclude === undefined) this.properties.group_inout_exclude = "None"; + // Backward compat: old typo key was group_exlude + if (this.properties.group_exclude === undefined) { + if (this.properties.group_exlude !== undefined) { + this.properties.group_exclude = this.properties.group_exlude; + } else { + this.properties.group_exclude = false; + } + } + + const addDivider = () => { + // Inert divider (non-interactive, non-serializzato). Serve solo come separazione visiva. + const divider = { + type: "iamccs_divider", + name: "", + value: "", + options: { serialize: false }, + computeSize: function (width) { + const w = Number.isFinite(width) ? width : 200; + return [Math.max(40, w), 10]; + }, + draw: function (ctx, node, widget_width, y, H) { + try { + ctx.save(); + const margin = 10; + const w = Number.isFinite(widget_width) ? widget_width : (node?.size?.[0] || 200); + const x1 = margin; + const x2 = Math.max(margin + 10, w - margin); + const yy = y + Math.floor((H || 10) / 2); + + ctx.globalAlpha = 0.35; + ctx.strokeStyle = "rgba(128,128,128,0.85)"; + ctx.lineWidth = 1; + ctx.beginPath(); + ctx.moveTo(x1, yy); + ctx.lineTo(x2, yy); + ctx.stroke(); + ctx.restore(); + } catch (e) { + // mute + } + }, + mouse: function () { + // Non cattura i click: resta "muto" e permette drag del nodo. + return false; + }, + }; + + if (typeof this.addCustomWidget === "function") { + this.addCustomWidget(divider); + } + }; + + // Normalize per-node modes: every blacklisted node must have a mode, default = both + try { + this.properties.blacklist_node_modes = this.properties.blacklist_node_modes || {}; + const ids = new Set((this.properties.blacklist || []).map(v => Number(v)).filter(v => Number.isFinite(v))); + for (const id of ids) { + const k = String(id); + if (!this.properties.blacklist_node_modes[k]) this.properties.blacklist_node_modes[k] = "both"; + } + for (const k of Object.keys(this.properties.blacklist_node_modes)) { + const id = Number(k); + if (!ids.has(id)) delete this.properties.blacklist_node_modes[k]; + } + } catch (e) { + console.warn("[IAMCCS AutoLink] blacklist_node_modes normalize failed", e); + } + + // Toggle per Visual Flow Tracer + this.addWidget( + "toggle", + "visual_flow_tracer", + this.properties.flow_tracer, + (value) => { + this.properties.flow_tracer = value; + toggleFlowTracer(value); + console.log("[IAMCCS AutoLink] Flow Tracer:", value); + } + ); + + // Toggle per mostrare tutti i nodi singolarmente nella blacklist + this.addWidget( + "toggle", + "all_nodes_sel", + this.properties.all_nodes_sel, + (value) => { + this.properties.all_nodes_sel = value; + console.log("[IAMCCS AutoLink] Show all nodes individually:", value); + } + ); + + // Toggle per includere KijNodes nella conversione + this.addWidget( + "toggle", + "include_kijnodes", + this.properties.include_kijnodes, + (value) => { + this.properties.include_kijnodes = value; + console.log("[IAMCCS AutoLink] Include KijNodes:", value); + } + ); + + // Toggle: esclude i link interni allo stesso Group (ma non quelli che attraversano il confine) + this.addWidget( + "toggle", + "GroupExclude", + this.properties.group_exclude, + (value) => { + this.properties.group_exclude = value; + // keep legacy key in sync + this.properties.group_exlude = value; + console.log("[IAMCCS AutoLink] GroupExclude:", value); + } + ); + + // Dropdown: filtra i link che entrano/escono dai group + this.addWidget( + "combo", + "GroupInOutExclude", + this.properties.group_inout_exclude, + (value) => { + this.properties.group_inout_exclude = value; + console.log("[IAMCCS AutoLink] GroupInOutExclude:", value); + }, + { + values: () => ["None", "ExcludeEnter", "ExcludeExit", "ExcludeBoth"] + } + ); + + // Divider: GroupInOutExclude -> Align + addDivider(); + + // Dropdown: modalità di allineamento/packing per Set/Get creati automaticamente + this.addWidget( + "combo", + "align_mode", + this.properties.align_mode, + (value) => { + this.properties.align_mode = value; + console.log("[IAMCCS AutoLink] Align mode:", value); + + // Re-layout esistente: permette di riallineare dopo che gli AutoLink sono stati creati + try { + relayoutExistingAutoLinks(app.graph, value, this.properties.packing_mode); + } catch (e) { + console.error("[IAMCCS AutoLink] Relayout error:", e); + } + }, + { + values: () => [ + "TopToDown", + "BottomToTop", + "CenterUpDown", + "CenterDownUp", + "Proportional", + "AlignX_Right", + "AlignX_Left", + "Columns_Down", + "Columns_Up", + "Rake_Down", + "Rake_Up", + ] + } + ); + + // Dropdown: regole overlap/packing (operativo anche post conversione) + this.addWidget( + "combo", + "packing_mode", + this.properties.packing_mode, + (value) => { + this.properties.packing_mode = value; + console.log("[IAMCCS AutoLink] Packing mode:", value); + + try { + relayoutExistingAutoLinks(app.graph, this.properties.align_mode, value); + } catch (e) { + console.error("[IAMCCS AutoLink] Relayout error:", e); + } + }, + { + values: () => [ + "AvoidAll", + "AvoidNonAutoLink", + ] + } + ); + + // Divider: Packing -> SeparateCol + addDivider(); + + // Toggle + dropdown colori AutoLink (operativo anche post conversione) + this.addWidget( + "toggle", + "SeparateCol", + this.properties.separate_col, + (value) => { + this.properties.separate_col = value; + console.log("[IAMCCS AutoLink] SeparateCol:", value); + recolorExistingAutoLinks(app.graph, this.properties.autolink_color_set, value, this.properties.autolink_color_get, this.properties.color_titles); + } + ); + + this.addWidget( + "combo", + "AutoLinkColor", + this.properties.autolink_color_set, + (value) => { + this.properties.autolink_color_set = value; + console.log("[IAMCCS AutoLink] AutoLinkColor (set/base):", value); + recolorExistingAutoLinks(app.graph, value, this.properties.separate_col, this.properties.autolink_color_get, this.properties.color_titles); + }, + { + values: () => alphaSort(Object.keys(AUTOLINK_COLORS)) + } + ); + + this.addWidget( + "combo", + "AutoLinkColorGet", + this.properties.autolink_color_get, + (value) => { + this.properties.autolink_color_get = value; + console.log("[IAMCCS AutoLink] AutoLinkColorGet:", value); + recolorExistingAutoLinks(app.graph, this.properties.autolink_color_set, this.properties.separate_col, value, this.properties.color_titles); + }, + { + values: () => alphaSort(Object.keys(AUTOLINK_COLORS)) + } + ); + + // Dropdown: colore testo titoli (AutoLink) + this.addWidget( + "combo", + "ColorTitles", + this.properties.color_titles, + (value) => { + this.properties.color_titles = value; + console.log("[IAMCCS AutoLink] ColorTitles:", value); + recolorExistingAutoLinks(app.graph, this.properties.autolink_color_set, this.properties.separate_col, this.properties.autolink_color_get, value); + }, + { + values: () => ["White", "Black", "Auto"] + } + ); + + // Divider: ColorTitles -> Blacklist + addDivider(); + + // Dropdown per aggiungere nodi/tipi alla blacklist + this.addWidget( + "combo", + "add_to_blacklist", + this.properties.blacklist_pending_value, + (value) => { + // Keep selection "loaded"; actual add happens via EXECUTE + this.properties.blacklist_pending_value = value || ""; + console.log("[IAMCCS AutoLink] Pending blacklist selection:", this.properties.blacklist_pending_value); + }, + { + values: () => { + if (this.properties.all_nodes_sel) { + // Mostra tutti i nodi singolarmente + const nodes = app.graph._nodes.filter(n => + n.type !== SET_TYPE && + n.type !== GET_TYPE && + n.type !== CONVERTER_TYPE && + n.type !== ARGUMENTS_TYPE && + true + ); + // Solo ID (no title/type) + const sorted = alphaSort(nodes.map(n => String(n.id))); + return ["", ...sorted]; + } else { + // Mostra solo tipi di nodi + const nodeTypes = new Set(); + app.graph._nodes.forEach(n => { + if (n.type !== SET_TYPE && + n.type !== GET_TYPE && + n.type !== CONVERTER_TYPE && + n.type !== ARGUMENTS_TYPE && + !this.properties.blacklist_types.includes(n.type)) { + nodeTypes.add(n.type); + } + }); + const sorted = alphaSort(Array.from(nodeTypes)).map(t => `[TYPE] ${t}`); + return ["", ...sorted]; + } + } + } + ); + + // Sposta blacklist mode sotto add_to_blacklist + this.addWidget( + "combo", + "blacklist_mode", + this.properties.blacklist_add_mode, + (value) => { + this.properties.blacklist_add_mode = value; + console.log("[IAMCCS AutoLink] Blacklist mode:", value); + }, + { + values: () => ["", "both", "only_output", "only_input"] + } + ); + + // EXECUTE: applica la modalità al nodo selezionato (o aggiunge tipo) + this.addWidget( + "button", + "EXECUTE", + null, + () => { + const value = (this.properties.blacklist_pending_value || "").trim(); + if (!value) return; + + const pickedMode = (this.properties.blacklist_add_mode || "").trim() || "both"; + + if (value.startsWith("[TYPE] ")) { + const nodeType = value.replace(/^\[TYPE\] /, ""); + this.properties.blacklist_types = this.properties.blacklist_types || []; + if (nodeType && !this.properties.blacklist_types.includes(nodeType)) { + this.properties.blacklist_types.push(nodeType); + console.log("[IAMCCS AutoLink] Added node type to blacklist:", nodeType); + } + } else { + const nodeId = parseInt(value); + if (!Number.isFinite(nodeId)) return; + this.properties.blacklist = this.properties.blacklist || []; + this.properties.blacklist_node_modes = this.properties.blacklist_node_modes || {}; + + if (!this.properties.blacklist.includes(nodeId)) this.properties.blacklist.push(nodeId); + this.properties.blacklist_node_modes[String(nodeId)] = pickedMode; + console.log("[IAMCCS AutoLink] Added/Updated node blacklist mode:", nodeId, this.properties.blacklist_node_modes[String(nodeId)]); + } + + // Clear pending selection + mode after executing (so they "disappear" from UI) + this.properties.blacklist_pending_value = ""; + this.properties.blacklist_add_mode = ""; + const widget = this.widgets?.find(w => w?.name === "add_to_blacklist" || w?.label === "add_to_blacklist"); + if (widget) widget.value = ""; + + const modeWidget = this.widgets?.find(w => w?.name === "blacklist_mode" || w?.label === "blacklist_mode"); + if (modeWidget) modeWidget.value = ""; + } + ); + + // Dropdown che mostra la blacklist (selezione non distruttiva) + this.addWidget( + "combo", + "blacklist_view", + this.properties.blacklist_view_selected, + (value) => { + this.properties.blacklist_view_selected = value || ""; + console.log("[IAMCCS AutoLink] Blacklist selection:", this.properties.blacklist_view_selected); + }, + { + values: () => { + const items = []; + + // Aggiungi tipi + (this.properties.blacklist_types || []).forEach(type => { + items.push(`[TYPE] ${type}`); + }); + + // Aggiungi singoli nodi + (this.properties.blacklist || []).forEach(id => { + const node = app.graph.getNodeById(id); + const mode = this.properties.blacklist_node_modes?.[String(id)] || "both"; + const name = node ? (node.title || node.type) : "(deleted)"; + items.push(`${id} - ${name} - (${mode})`); + }); + + return ["", ...alphaSort(items)]; + } + } + ); + + // Remove button: rimuove l'elemento selezionato nella blacklist_view + this.addWidget( + "button", + "remove_blacklist", + null, + () => { + const value = (this.properties.blacklist_view_selected || "").trim(); + if (!value) return; + + if (value.startsWith("[TYPE] ")) { + const nodeType = value.replace(/^\[TYPE\] /, ""); + const index = (this.properties.blacklist_types || []).indexOf(nodeType); + if (index !== -1) { + this.properties.blacklist_types.splice(index, 1); + console.log("[IAMCCS AutoLink] Removed node type from blacklist:", nodeType); + } + } else { + const [nodeIdStr] = value.split(" - "); + const nodeId = parseInt(nodeIdStr); + const index = (this.properties.blacklist || []).indexOf(nodeId); + if (index !== -1) { + this.properties.blacklist.splice(index, 1); + if (this.properties.blacklist_node_modes) delete this.properties.blacklist_node_modes[String(nodeId)]; + console.log("[IAMCCS AutoLink] Removed node from blacklist:", nodeId); + } + } + + // Clear selection after removal + this.properties.blacklist_view_selected = ""; + const widget = this.widgets?.find(w => w?.name === "blacklist_view" || w?.label === "blacklist_view"); + if (widget) widget.value = ""; + } + ); + + // Dropdown per navigare tra Set/Get + this.addWidget( + "combo", + "jump_to_autolink", + "", + (value) => { + if (!value) return; + + const [nodeIdStr] = value.split(" - "); + const nodeId = parseInt(nodeIdStr); + const node = app.graph.getNodeById(nodeId); + + if (node) { + app.canvas.centerOnNode(node); + app.canvas.selectNode(node); + } + }, + { + values: () => { + const autoLinkNodes = app.graph._nodes.filter(n => + n.type === SET_TYPE || n.type === GET_TYPE + ); + return ["", ...alphaSort(autoLinkNodes.map(n => `${n.id} - ${n.title || n.type}`))]; + } + } + ); + + // Auto-resize so newly added widgets are not hidden + try { + const s = this.computeSize?.(); + if (Array.isArray(s) && s.length >= 2) { + const w = Math.max(this.size?.[0] || 0, s[0] || 0); + const h = Math.max(this.size?.[1] || 0, s[1] || 0); + this.size = [w, h]; + } + } catch (e) { + console.warn("[IAMCCS AutoLink] Arguments auto-resize failed", e); + } + try { app?.graph?.setDirtyCanvas?.(true, true); } catch {} + + this.isVirtualNode = true; + + return result; + }; + } + + // Set node - come KJ SetNode + if (nodeData?.name === SET_TYPE) { + const onNodeCreated = nodeType.prototype.onNodeCreated; + nodeType.prototype.onNodeCreated = function() { + const result = onNodeCreated?.apply(this, arguments); + const node = this; + + // Dropdown colore (se cambi un Set, cambia anche i Get corrispondenti) + node.properties = node.properties || {}; + if (!node.properties.autolink_color_name) node.properties.autolink_color_name = "Gray"; + this.addWidget( + "combo", + "AutoLinkColor", + node.properties.autolink_color_name, + (value) => { + const key = getAutolinkKey(node); + + // prendi lo stato SeparateCol + colore get dal primo Arguments disponibile (se presente) + const argNode = app.graph._nodes.find(n => n?.type === ARGUMENTS_TYPE); + const separateCol = !!argNode?.properties?.separate_col; + const colorGetName = argNode?.properties?.autolink_color_get || "Gray"; + const colorTitles = argNode?.properties?.color_titles || "White"; + + node.properties.autolink_color_name = value; + node.properties.autolink_color_locked = true; + applyColorToAutolinkKey(app.graph, key, value, separateCol, colorGetName, colorTitles); + }, + { + values: () => alphaSort(Object.keys(AUTOLINK_COLORS)) + } + ); + + // Aggiungi widget per il nome + const nameWidget = this.addWidget("text", "name", "", (value) => { + if (isValidAutolinkKey(value)) { + const safe = String(value).trim(); + node.title = safe; + + // Mantieni i port name coerenti con la chiave + if (node.inputs && node.inputs[0]) node.inputs[0].name = safe; + if (node.outputs && node.outputs[0]) node.outputs[0].name = safe; + + // Salva il valore corrente per il prossimo cambiamento (tracking locale) + nameWidget.lastValue = safe; + + // Applica colore testo titolo (se configurato) + applyNodeTitleTextColor(node, getCurrentColorTitlesMode()); + } else { + // evita titoli '*' o vuoti + if (String(value ?? "").trim() === "*") { + try { setWidgetValue(node, "name", ""); } catch {} + } + if (String(node.title ?? "").trim() === "*" || !String(node.title ?? "").trim()) { + node.title = "Set AutoLink"; + } + } + }); + + // Inizializza lastValue quando il nodo viene caricato + if (!nameWidget.lastValue && nameWidget.value) { + nameWidget.lastValue = nameWidget.value; + } + + // Input/output: normalizza workflow vecchi (che possono avere input duplicati) + normalizeAutolinkIOSlots(app.graph, node, { wantInputs: 1, wantOutputs: 1 }); + + // Callback quando si collega + this.onConnectionsChange = function(slotType, slot, isConnect, link_info) { + if (slotType === 1 && isConnect && link_info) { + const fromNode = app.graph.getNodeById(link_info.origin_id); + if (fromNode && fromNode.outputs && fromNode.outputs[link_info.origin_slot]) { + const outputType = fromNode.outputs[link_info.origin_slot].type; + + // Usa sempre un nome leggibile e stabile (slot-name), non il tipo puro. + // Questo produce base tipo "model"/"image" e poi lo rendiamo unico: model_0, image_2, ... + let suggestedBase = getSlotName(fromNode, link_info.origin_slot, true); + if (!isValidAutolinkKey(suggestedBase)) { + if (outputType && outputType !== "*") suggestedBase = String(outputType).trim().toLowerCase(); + else suggestedBase = `output_${link_info.origin_slot}`; + } + + // Imposta tipo + node.inputs[0].type = outputType; + node.outputs[0].type = outputType; + + // Se il converter ha già impostato un nome unico, NON sovrascriverlo qui. + // Auto-fill solo quando il widget è vuoto. + const currentKey = getAutolinkKey(node); + const desiredKey = isValidAutolinkKey(currentKey) + ? currentKey + : makeUniqueAutolinkSetName(app.graph, suggestedBase); + + // Imposta chiave + UI + porta coerenti + setAutolinkKeyAndTitle(node, desiredKey); + + // Applica colore testo titolo (se configurato) + applyNodeTitleTextColor(node, getCurrentColorTitlesMode()); + } + } + }; + + // Overlay draw: rende visibile ColorTitles anche se LiteGraph ignora title_text_color + // (removed) per-node overlay title drawing; native title is colored via canvas hook + + // Migrazione: vecchi workflow (prima del widget colore) possono aver scritto il nome nel widget colore. + const colorWidget = getWidget(node, "AutoLinkColor"); + const colorVal = colorWidget?.value; + const nameVal = nameWidget?.value; + if (colorVal && !AUTOLINK_COLORS[colorVal] && (!nameVal || !String(nameVal).trim())) { + const migrated = String(colorVal).trim(); + setAutolinkKeyAndTitle(node, migrated); + if (colorWidget) colorWidget.value = "Gray"; + node.properties = node.properties || {}; + node.properties.autolink_color_name = "Gray"; + node.properties.autolink_color_locked = false; + } + + // Fix workflow vecchi: titolo o name a '*' + try { + const rawName = getWidgetValue(node, "name"); + if (String(rawName ?? "").trim() === "*") setWidgetValue(node, "name", ""); + if (String(node.title ?? "").trim() === "*") node.title = "Set AutoLink"; + } catch {} + + // Nodo virtuale - non serializza per il prompt + this.isVirtualNode = true; + + return result; + }; + } + + // Get node - come KJ GetNode + if (nodeData?.name === GET_TYPE) { + const onNodeCreated = nodeType.prototype.onNodeCreated; + nodeType.prototype.onNodeCreated = function() { + const result = onNodeCreated?.apply(this, arguments); + const node = this; + + // Combo dinamico con lista Set disponibili + this.addWidget("combo", "name", "", (value) => { + node.onRename(); + }, { + values: () => { + const setNodes = app.graph._nodes.filter(n => n.type === SET_TYPE); + return alphaSort(setNodes.map(n => getAutolinkKey(n)).filter(v => v)); + } + }); + + // Normalizza output duplicati su workflow vecchi + normalizeAutolinkIOSlots(app.graph, node, { wantInputs: 0, wantOutputs: 1 }); + + this.onRename = function() { + const setterName = getAutolinkKey(node); + const setter = app.graph._nodes.find(n => + n.type === SET_TYPE && getAutolinkKey(n) === setterName + ); + + if (setter) { + const linkType = setter.outputs[0].type; + node.outputs[0].type = linkType; + node.outputs[0].name = linkType; + node.title = setterName; + + applyNodeTitleTextColor(node, getCurrentColorTitlesMode()); + } + }; + + // Fix workflow vecchi: titolo a '*' + try { + if (String(node.title ?? "").trim() === "*") node.title = "Get AutoLink"; + } catch {} + // (removed) per-node overlay title drawing; native title is colored via canvas hook + + // Override getInputLink per prendere da Set + this.getInputLink = function(slot) { + const setterName = getAutolinkKey(node); + const setter = app.graph._nodes.find(n => + n.type === SET_TYPE && getAutolinkKey(n) === setterName + ); + + if (setter) { + const slotInfo = setter.inputs[slot]; + if (slotInfo) { + const link = app.graph.links[slotInfo.link]; + return link; + } + } + return null; + }; + + // Nodo virtuale - non serializza per il prompt + this.isVirtualNode = true; + + return result; + }; + } + } +}); + +// === FUNZIONI CONVERSIONE === + +function getNodeById(graph, id) { + return graph.getNodeById ? graph.getNodeById(id) : graph._nodes.find(n => n.id === id); +} + +function createNode(graph, type, x, y) { + const node = LiteGraph.createNode(type); + if (!node) return null; + node.pos = [x, y]; + graph.add(node); + return node; +} + +function setWidgetValue(node, name, value) { + if (!node || !node.widgets || node.widgets.length === 0) return false; + + // 1) Exact match (ideal case) + const exact = node.widgets.find(w => w?.name === name); + if (exact) { + exact.value = value; + return true; + } + + // 2) Case-insensitive / substring match (some custom nodes don't use "name" literally) + const lowered = String(name).toLowerCase(); + const fuzzy = node.widgets.find(w => typeof w?.name === 'string' && w.name.toLowerCase().includes(lowered)); + if (fuzzy) { + fuzzy.value = value; + return true; + } + + // 3) Fallback: if there's a single widget, assume it's the name + if (node.widgets.length === 1) { + node.widgets[0].value = value; + return true; + } + + return false; +} + +function getSlotName(node, slot, isOutput) { + if (!node) return `slot_${slot}`; + const slots = isOutput ? node.outputs : node.inputs; + + const info = slots?.[slot]; + const name = info?.name; + if (name === "*") { + const t = info?.type; + if (t && t !== "*") return String(t).trim().toLowerCase(); + return isOutput ? `output_${slot}` : `input_${slot}`; + } + + return name || (isOutput ? `output_${slot}` : `input_${slot}`); +} + +function getBlacklistFromInput(converterNode) { + // Cerca il nodo Arguments collegato all'input arg + if (!converterNode.inputs || converterNode.inputs.length === 0) { + return { + blacklist: [], + blacklistTypes: [], + blacklistNodeModes: {}, + includeKijNodes: false, + groupExclude: false, + groupInOutExclude: "None", + alignMode: "TopToDown", + packingMode: "AvoidAll", + colorSet: "Gray", + colorGet: "Gray", + separateCol: false, + colorTitles: "White", + }; + } + + const argInput = converterNode.inputs.find(i => i.name === "arg"); + if (!argInput || !argInput.link) { + return { + blacklist: [], + blacklistTypes: [], + blacklistNodeModes: {}, + includeKijNodes: false, + groupExclude: false, + groupInOutExclude: "None", + alignMode: "TopToDown", + packingMode: "AvoidAll", + colorSet: "Gray", + colorGet: "Gray", + separateCol: false, + colorTitles: "White", + }; + } + + const link = app.graph.links[argInput.link]; + if (!link) { + return { + blacklist: [], + blacklistTypes: [], + blacklistNodeModes: {}, + includeKijNodes: false, + groupExclude: false, + groupInOutExclude: "None", + alignMode: "TopToDown", + packingMode: "AvoidAll", + colorSet: "Gray", + colorGet: "Gray", + separateCol: false, + colorTitles: "White", + }; + } + + const argNode = app.graph.getNodeById(link.origin_id); + if (!argNode || argNode.type !== ARGUMENTS_TYPE) { + return { + blacklist: [], + blacklistTypes: [], + blacklistNodeModes: {}, + includeKijNodes: false, + groupExclude: false, + groupInOutExclude: "None", + alignMode: "TopToDown", + packingMode: "AvoidAll", + colorSet: "Gray", + colorGet: "Gray", + separateCol: false, + colorTitles: "White", + }; + } + + return { + blacklist: argNode.properties?.blacklist || [], + blacklistTypes: argNode.properties?.blacklist_types || [], + blacklistNodeModes: argNode.properties?.blacklist_node_modes || {}, + includeKijNodes: argNode.properties?.include_kijnodes || false, + groupExclude: (argNode.properties?.group_exclude ?? argNode.properties?.group_exlude) || false, + groupInOutExclude: argNode.properties?.group_inout_exclude || "None", + alignMode: argNode.properties?.align_mode || "TopToDown", + packingMode: argNode.properties?.packing_mode || "AvoidAll", + colorSet: argNode.properties?.autolink_color_set || "Gray", + colorGet: argNode.properties?.autolink_color_get || "Gray", + separateCol: !!argNode.properties?.separate_col, + colorTitles: argNode.properties?.color_titles || "White", + }; +} + +function relayoutExistingAutoLinks(graph, alignMode = "TopToDown", packingMode = "AvoidAll") { + if (!graph) return; + + const GRID_SIZE = 80; + const GET_OFFSET_X = -220; + + const isArrayLike = (v, minLen) => v != null && typeof v.length === 'number' && v.length >= minLen; + const getNodePos = (n) => { + const x = (isArrayLike(n?.pos, 2) ? n.pos[0] : n?.pos?.[0]) ?? 0; + const y = (isArrayLike(n?.pos, 2) ? n.pos[1] : n?.pos?.[1]) ?? 0; + return [x, y]; + }; + const getNodeSize = (n) => { + const w = (isArrayLike(n?.size, 2) ? n.size[0] : n?.size?.[0]) ?? 200; + const h = (isArrayLike(n?.size, 2) ? n.size[1] : n?.size?.[1]) ?? 100; + return [w, h]; + }; + + const isAutoLinkNode = (node) => node?.type === SET_TYPE || node?.type === GET_TYPE; + const shouldTreatAsObstacle = (node) => { + if (!node) return false; + if (packingMode === "AvoidAll") return true; + if (packingMode === "AvoidNonAutoLink") return !isAutoLinkNode(node); + return true; + }; + + const rectsOverlap = (ax, ay, aw, ah, bx, by, bw, bh) => + ax < bx + bw && ax + aw > bx && ay < by + bh && ay + ah > by; + + function seriesCentered(firstUp = true) { + return (i) => { + if (i === 0) return 0; + const k = Math.ceil(i / 2); + const sign = (i % 2 === 1) ? (firstUp ? -1 : 1) : (firstUp ? 1 : -1); + return sign * k; + }; + } + + function layoutDelta(i, mode) { + switch (mode) { + case "TopToDown": + case "stack_down": + return { dx: 0, dy: i }; + case "BottomToTop": + case "stack_up": + return { dx: 0, dy: -i }; + case "CenterUpDown": + case "stack_center_up": + return { dx: 0, dy: seriesCentered(true)(i) }; + case "CenterDownUp": + case "stack_center_down": + return { dx: 0, dy: seriesCentered(false)(i) }; + case "AlignX_Right": + case "row_right": + return { dx: i, dy: 0 }; + case "AlignX_Left": + case "row_left": + return { dx: -i, dy: 0 }; + case "Columns_Down": + case "columns_down": { + const rows = 10; + const col = Math.floor(i / rows); + const row = i % rows; + return { dx: col, dy: row }; + } + case "Columns_Up": + case "columns_up": { + const rows = 10; + const col = Math.floor(i / rows); + const row = i % rows; + return { dx: col, dy: -row }; + } + case "Rake_Down": + case "rake_down": + return { dx: i, dy: i }; + case "Rake_Up": + case "rake_up": + return { dx: i, dy: -i }; + case "Proportional": + // Keep Y anchored; move horizontally if collisions + return { dx: seriesCentered(true)(i), dy: 0 }; + default: + return { dx: 0, dy: seriesCentered(true)(i) }; + } + } + + function findFreePosition(baseX, baseY, offsetX, occupied, mode, ignoreNode, extraObstacles = []) { + const maxAttempts = 250; + for (let attempts = 0; attempts < maxAttempts; attempts++) { + const { dx, dy } = layoutDelta(attempts, mode); + const testX = baseX + offsetX + dx * GRID_SIZE; + const testY = baseY + dy * GRID_SIZE; + const posKey = `${Math.round(testX / GRID_SIZE)}_${Math.round(testY / GRID_SIZE)}`; + if (occupied.has(posKey)) continue; + + let overlaps = false; + for (const node of graph._nodes) { + if (!shouldTreatAsObstacle(node)) continue; + if (ignoreNode && node === ignoreNode) continue; + const [nx, ny] = getNodePos(node); + const [nw, nh] = getNodeSize(node); + if (rectsOverlap(testX, testY, 150, 26, nx, ny, nw, nh)) { + overlaps = true; + break; + } + } + + if (!overlaps && extraObstacles && extraObstacles.length) { + for (const r of extraObstacles) { + if (rectsOverlap(testX, testY, 150, 26, r.x, r.y, r.w, r.h)) { + overlaps = true; + break; + } + } + } + + if (!overlaps) { + occupied.add(posKey); + return [testX, testY]; + } + } + return [baseX + offsetX, baseY]; + } + + const sets = graph._nodes.filter(n => n?.type === SET_TYPE); + const gets = graph._nodes.filter(n => n?.type === GET_TYPE); + + // 1) Re-layout Sets: ancorati al nodo origine che li alimenta + const occupiedSet = new Set(); + const setRecords = []; + for (const setNode of sets) { + const inLinkId = setNode?.inputs?.[0]?.link; + const inLink = inLinkId != null ? graph.links?.[inLinkId] : null; + if (!inLink) continue; + + const originNode = getNodeById(graph, inLink.origin_id); + if (!originNode) continue; + + const originSlot = inLink.origin_slot ?? 0; + const name = getAutolinkKey(setNode); + setRecords.push({ setNode, originNode, originSlot, name }); + } + + setRecords.sort((a, b) => { + if (a.originNode.id !== b.originNode.id) return a.originNode.id - b.originNode.id; + if (a.originSlot !== b.originSlot) return a.originSlot - b.originSlot; + return String(a.name).localeCompare(String(b.name)); + }); + + const placedAutoLinkRects = []; + + for (const rec of setRecords) { + const [ox, oy] = getNodePos(rec.originNode); + const [ow] = getNodeSize(rec.originNode); + const baseX = ox + ow; + let baseY = oy; + if (alignMode === "Proportional" && typeof rec.originNode?.getConnectionPos === 'function') { + const p = rec.originNode.getConnectionPos(false, rec.originSlot); + if (Array.isArray(p) && p.length >= 2) baseY = p[1] ?? baseY; + } + const [nx, ny] = findFreePosition(baseX, baseY, 20, occupiedSet, alignMode, rec.setNode, placedAutoLinkRects); + rec.setNode.pos = [nx, ny]; + placedAutoLinkRects.push({ x: nx, y: ny, w: 150, h: 26 }); + } + + // 2) Re-layout Gets: ancorati al nodo target che alimentano + const occupiedGet = new Set(); + const getRecords = []; + for (const getNode of gets) { + // preferisci link reale in uscita + const outLinks = getNode?.outputs?.[0]?.links; + const linkId = Array.isArray(outLinks) && outLinks.length > 0 ? outLinks[0] : null; + const outLink = linkId != null ? graph.links?.[linkId] : null; + const targetId = outLink?.target_id ?? getNode?.properties?.metadata?.target?.id; + if (targetId == null) continue; + const targetNode = getNodeById(graph, targetId); + if (!targetNode) continue; + const name = getAutolinkKey(getNode); + const targetSlot = outLink?.target_slot; + getRecords.push({ getNode, targetNode, targetSlot, name }); + } + + getRecords.sort((a, b) => { + if (a.targetNode.id !== b.targetNode.id) return a.targetNode.id - b.targetNode.id; + if (alignMode === "Proportional") { + const as = Number.isFinite(a.targetSlot) ? a.targetSlot : 0; + const bs = Number.isFinite(b.targetSlot) ? b.targetSlot : 0; + if (as !== bs) return as - bs; + } + return String(a.name).localeCompare(String(b.name)); + }); + + for (const rec of getRecords) { + const [tx, ty] = getNodePos(rec.targetNode); + const baseX = tx; + let baseY = ty; + if (alignMode === "Proportional" && Number.isFinite(rec.targetSlot) && typeof rec.targetNode?.getConnectionPos === 'function') { + const p = rec.targetNode.getConnectionPos(true, rec.targetSlot); + if (Array.isArray(p) && p.length >= 2) baseY = p[1] ?? baseY; + } + const [nx, ny] = findFreePosition(baseX, baseY, GET_OFFSET_X, occupiedGet, alignMode, rec.getNode, placedAutoLinkRects); + rec.getNode.pos = [nx, ny]; + placedAutoLinkRects.push({ x: nx, y: ny, w: 150, h: 26 }); + } + + graph.setDirtyCanvas(true, true); +} + +function getGroupForNode(graph, node) { + const rawGroups = graph?._groups ?? graph?.groups ?? []; + const groups = Array.isArray(rawGroups) + ? rawGroups + : (rawGroups && typeof rawGroups === 'object' ? Object.values(rawGroups) : []); + + if (!node || !groups || groups.length === 0) return null; + + const isArrayLike = (v, minLen) => v != null && typeof v.length === 'number' && v.length >= minLen; + + const nodeW = (isArrayLike(node.size, 2) ? node.size[0] : node.size?.[0]) ?? 0; + const nodeH = (isArrayLike(node.size, 2) ? node.size[1] : node.size?.[1]) ?? 0; + const nodeX = (isArrayLike(node.pos, 2) ? node.pos[0] : node.pos?.[0]) ?? 0; + const nodeY = (isArrayLike(node.pos, 2) ? node.pos[1] : node.pos?.[1]) ?? 0; + const cx = nodeX + nodeW / 2; + const cy = nodeY + nodeH / 2; + + let bestGroup = null; + let bestArea = Infinity; + + for (let i = 0; i < groups.length; i++) { + const g = groups[i]; + if (!g) continue; + + let bounding = null; + if (isArrayLike(g.bounding, 4)) { + bounding = [g.bounding[0], g.bounding[1], g.bounding[2], g.bounding[3]]; + } else if (typeof g.getBounding === 'function') { + const b = g.getBounding(); + if (isArrayLike(b, 4)) bounding = [b[0], b[1], b[2], b[3]]; + } + + if (!bounding && isArrayLike(g.pos, 2) && isArrayLike(g.size, 2)) { + bounding = [g.pos[0], g.pos[1], g.size[0], g.size[1]]; + } + + if (!bounding) continue; + + const [gx, gy, gw, gh] = bounding; + if (cx >= gx && cx <= gx + gw && cy >= gy && cy <= gy + gh) { + const area = Math.abs(gw * gh); + if (area < bestArea) { + bestArea = area; + bestGroup = g; + } + } + } + + return bestGroup; +} + +function convertAllLinks( + graph, + blacklist = [], + blacklistTypes = [], + blacklistNodeModes = {}, + includeKijNodes = false, + groupExclude = false, + groupInOutExclude = "None", + alignMode = "TopToDown", + packingMode = "AvoidAll", + colorSet = "Gray", + colorGet = "Gray", + separateCol = false, + colorTitles = "White" +) { + // Backward compat for older call signature that didn't include blacklistNodeModes / colorTitles + if (typeof blacklistNodeModes === 'boolean') { + const oldIncludeKijNodes = blacklistNodeModes; + const oldGroupExclude = includeKijNodes; + const oldAlignMode = groupExclude; + const oldPackingMode = alignMode; + const oldColorSet = packingMode; + const oldColorGet = colorSet; + const oldSeparateCol = colorGet; + + blacklistNodeModes = {}; + includeKijNodes = !!oldIncludeKijNodes; + groupExclude = !!oldGroupExclude; + groupInOutExclude = "None"; + alignMode = oldAlignMode || "TopToDown"; + packingMode = oldPackingMode || "AvoidAll"; + colorSet = oldColorSet || "Gray"; + colorGet = oldColorGet || "Gray"; + separateCol = !!oldSeparateCol; + colorTitles = "White"; + } + + console.log("[IAMCCS AutoLink] Converting links..."); + console.log("[IAMCCS AutoLink] Blacklist IDs:", blacklist); + console.log("[IAMCCS AutoLink] Blacklist Types:", blacklistTypes); + console.log("[IAMCCS AutoLink] Blacklist Node Modes:", blacklistNodeModes); + console.log("[IAMCCS AutoLink] Include KijNodes:", includeKijNodes); + console.log("[IAMCCS AutoLink] GroupExclude:", groupExclude); + console.log("[IAMCCS AutoLink] GroupInOutExclude:", groupInOutExclude); + console.log("[IAMCCS AutoLink] Align mode:", alignMode); + console.log("[IAMCCS AutoLink] Packing mode:", packingMode); + console.log("[IAMCCS AutoLink] ColorSet:", colorSet); + console.log("[IAMCCS AutoLink] ColorGet:", colorGet); + console.log("[IAMCCS AutoLink] SeparateCol:", separateCol); + console.log("[IAMCCS AutoLink] ColorTitles:", colorTitles); + + const linksData = graph.links || {}; + const linksToConvert = []; + const kijNodesToConvert = []; + + for (const linkId in linksData) { + const link = linksData[linkId]; + if (!link) continue; + + const srcNode = getNodeById(graph, link.origin_id); + const dstNode = getNodeById(graph, link.target_id); + + if (!srcNode || !dstNode) continue; + + // Skip già AutoLink + if (srcNode.type === SET_TYPE || srcNode.type === GET_TYPE) continue; + if (dstNode.type === SET_TYPE || dstNode.type === GET_TYPE) continue; + + // Blacklist permanente: nodi Arguments e Converter non vengono mai convertiti + if (srcNode.type === ARGUMENTS_TYPE || srcNode.type === CONVERTER_TYPE || + dstNode.type === ARGUMENTS_TYPE || dstNode.type === CONVERTER_TYPE) { + console.log(`[IAMCCS AutoLink] Skipping AutoLink system node: ${srcNode.type} -> ${dstNode.type}`); + continue; + } + + // Gestione KijNodes + const srcIsKij = (srcNode.type === KJ_SET_TYPE || srcNode.type === KJ_GET_TYPE); + const dstIsKij = (dstNode.type === KJ_SET_TYPE || dstNode.type === KJ_GET_TYPE); + + if (srcIsKij || dstIsKij) { + if (includeKijNodes) { + // Converte KijNodes in AutoLink + kijNodesToConvert.push({ link, srcNode, dstNode, srcIsKij, dstIsKij }); + } else { + // Esclude permanentemente KijNodes e i loro collegamenti + console.log(`[IAMCCS AutoLink] Skipping KijNode: ${srcNode.type} -> ${dstNode.type}`); + } + continue; + } + + // Salta i nodi in blacklist utente (per ID o per tipo) + { + const srcMode = blacklistNodeModes?.[String(srcNode.id)] || (blacklist.includes(srcNode.id) ? "both" : null); + const dstMode = blacklistNodeModes?.[String(dstNode.id)] || (blacklist.includes(dstNode.id) ? "both" : null); + + const skipBecauseSrc = !!srcMode && (srcMode === "both" || srcMode === "only_output"); + const skipBecauseDst = !!dstMode && (dstMode === "both" || dstMode === "only_input"); + + if (skipBecauseSrc || skipBecauseDst) { + console.log( + `[IAMCCS AutoLink] Skipping blacklisted node by mode: src=${srcNode.id}(${srcMode || "-"}) dst=${dstNode.id}(${dstMode || "-"})` + ); + continue; + } + } + + if (blacklistTypes.includes(srcNode.type) || blacklistTypes.includes(dstNode.type)) { + console.log(`[IAMCCS AutoLink] Skipping blacklisted node type: ${srcNode.type} or ${dstNode.type}`); + continue; + } + + // GroupExlude: se entrambi i nodi sono dentro lo stesso Group, NON convertire quel collegamento. + // (I collegamenti in entrata/uscita dal group restano convertibili.) + if (groupExclude) { + const srcGroup = getGroupForNode(graph, srcNode); + const dstGroup = getGroupForNode(graph, dstNode); + if (srcGroup && srcGroup === dstGroup) continue; + } + + // GroupInOutExclude: se il link attraversa il confine di un group, puoi escludere le entrate/uscite + if (groupInOutExclude && groupInOutExclude !== "None") { + const srcGroup = getGroupForNode(graph, srcNode); + const dstGroup = getGroupForNode(graph, dstNode); + const crossesBoundary = (srcGroup !== dstGroup) && (srcGroup || dstGroup); + + if (crossesBoundary) { + const entersDstGroup = !!dstGroup && srcGroup !== dstGroup; + const exitsSrcGroup = !!srcGroup && srcGroup !== dstGroup; + + if ( + (groupInOutExclude === "ExcludeEnter" && entersDstGroup) || + (groupInOutExclude === "ExcludeExit" && exitsSrcGroup) || + (groupInOutExclude === "ExcludeBoth" && (entersDstGroup || exitsSrcGroup)) + ) { + continue; + } + } + } + + linksToConvert.push({ link, srcNode, dstNode }); + } + + // Raggruppa i link per origine (stesso nodo + stesso slot) per creare un solo Set per output multipli + const linksByOrigin = new Map(); + for (const linkData of linksToConvert) { + const key = `${linkData.srcNode.id}_${linkData.link.origin_slot}`; + if (!linksByOrigin.has(key)) { + linksByOrigin.set(key, { + srcNode: linkData.srcNode, + originSlot: linkData.link.origin_slot, + outputName: getSlotName(linkData.srcNode, linkData.link.origin_slot, true), + destinations: [] + }); + } + linksByOrigin.get(key).destinations.push({ + dstNode: linkData.dstNode, + targetSlot: linkData.link.target_slot, + linkId: linkData.link.id + }); + } + + // Sistema di griglia per posizionamento non sovrapposto + const GRID_SIZE = 80; + const SET_OFFSET_X = 250; // Distanza dal nodo sorgente + const GET_OFFSET_X = -220; // Distanza dal nodo destinazione + + const isAutoLinkNode = (node) => node?.type === SET_TYPE || node?.type === GET_TYPE; + function shouldTreatAsObstacle(node) { + if (!node) return false; + if (packingMode === "AvoidAll") return true; + if (packingMode === "AvoidNonAutoLink") return !isAutoLinkNode(node); + return true; + } + + function seriesCentered(firstUp = true) { + // 0, -1, +1, -2, +2 ... (firstUp=true) oppure 0, +1, -1, +2, -2 + return (i) => { + if (i === 0) return 0; + const k = Math.ceil(i / 2); + const sign = (i % 2 === 1) ? (firstUp ? -1 : 1) : (firstUp ? 1 : -1); + return sign * k; + }; + } + + function layoutDelta(i, mode) { + // Backward compat: accetta anche i vecchi nomi interni usati durante lo sviluppo + switch (mode) { + case "TopToDown": + case "stack_down": + return { dx: 0, dy: i }; + case "BottomToTop": + case "stack_up": + return { dx: 0, dy: -i }; + case "CenterUpDown": + case "stack_center_up": + return { dx: 0, dy: seriesCentered(true)(i) }; + case "CenterDownUp": + case "stack_center_down": + return { dx: 0, dy: seriesCentered(false)(i) }; + case "AlignX_Right": + case "row_right": + return { dx: i, dy: 0 }; + case "AlignX_Left": + case "row_left": + return { dx: -i, dy: 0 }; + case "Columns_Down": + case "columns_down": { + const rows = 10; + const col = Math.floor(i / rows); + const row = i % rows; + return { dx: col, dy: row }; + } + case "Columns_Up": + case "columns_up": { + const rows = 10; + const col = Math.floor(i / rows); + const row = i % rows; + return { dx: col, dy: -row }; + } + case "Rake_Down": + case "rake_down": + return { dx: i, dy: i }; + case "Rake_Up": + case "rake_up": + return { dx: i, dy: -i }; + case "Proportional": + // Keep Y anchored; move horizontally if collisions + return { dx: seriesCentered(true)(i), dy: 0 }; + default: + return { dx: 0, dy: seriesCentered(true)(i) }; + } + } + + function getAnchorY(node, slot, isOutput) { + if (!node) return 0; + // Prefer LiteGraph connector Y (best match with what you see on screen) + if (typeof node.getConnectionPos === 'function') { + const isInput = !isOutput; + const p = node.getConnectionPos(isInput, slot); + if (Array.isArray(p) && p.length >= 2 && Number.isFinite(p[1])) return p[1]; + } + // Fallback: proportional inside node bounding box + const y = node.pos?.[1] ?? 0; + const h = node.size?.[1] ?? 100; + const count = isOutput ? (node.outputs?.length || 1) : (node.inputs?.length || 1); + const idx = Number.isFinite(slot) ? slot : 0; + const t = (idx + 0.5) / Math.max(1, count); + return y + t * h; + } + + function findFreePosition(graph, baseX, baseY, offsetX, occupiedPositions, mode) { + const maxAttempts = 250; + + for (let attempts = 0; attempts < maxAttempts; attempts++) { + const { dx, dy } = layoutDelta(attempts, mode); + const testX = baseX + offsetX + dx * GRID_SIZE; + const testY = baseY + dy * GRID_SIZE; + const posKey = `${Math.round(testX / GRID_SIZE)}_${Math.round(testY / GRID_SIZE)}`; + + if (occupiedPositions.has(posKey)) continue; + + // Controlla sovrapposizione con nodi esistenti + let overlaps = false; + for (const node of graph._nodes) { + if (!shouldTreatAsObstacle(node)) continue; + const nodeRight = node.pos[0] + (node.size?.[0] || 200); + const nodeBottom = node.pos[1] + (node.size?.[1] || 100); + + if (testX < nodeRight && testX + 150 > node.pos[0] && + testY < nodeBottom && testY + 26 > node.pos[1]) { + overlaps = true; + break; + } + } + + if (!overlaps) { + occupiedPositions.add(posKey); + return [testX, testY]; + } + } + + // Fallback + return [baseX + offsetX, baseY]; + } + + const occupiedSetPositions = new Set(); + const occupiedGetPositions = new Set(); + const createdSets = new Map(); + + // Traccia i nomi dei Set già esistenti/creati per evitare duplicati + // - Preserva nomi numerati come "model_0" (non li riduce a "model") + const usedExactSetNames = new Set(); + const existingSets = graph._nodes.filter(n => n.type === SET_TYPE); + for (const existingSet of existingSets) { + const name = getAutolinkKey(existingSet); + if (name && String(name).trim()) usedExactSetNames.add(String(name).trim()); + } + + function makeUniqueSetName(desiredName) { + const desired = String(desiredName ?? "").trim(); + if (!desired) { + let i = 0; + while (usedExactSetNames.has(`output_${i}`)) i++; + const fallback = `output_${i}`; + usedExactSetNames.add(fallback); + return fallback; + } + + if (!usedExactSetNames.has(desired)) { + usedExactSetNames.add(desired); + return desired; + } + + const m = desired.match(/^(.+?)_(\d+)$/); + if (m) { + const base = m[1]; + let n = parseInt(m[2], 10) + 1; + while (usedExactSetNames.has(`${base}_${n}`)) n++; + const candidate = `${base}_${n}`; + usedExactSetNames.add(candidate); + return candidate; + } + + let n = 0; + while (usedExactSetNames.has(`${desired}_${n}`)) n++; + const candidate = `${desired}_${n}`; + usedExactSetNames.add(candidate); + return candidate; + } + + console.log(`[IAMCCS AutoLink] Creating ${linksByOrigin.size} Set nodes...`); + + // Crea Set nodes (uno per origine) + for (const [key, originData] of linksByOrigin) { + const { srcNode, originSlot, outputName, destinations } = originData; + + const setPos = findFreePosition( + graph, + srcNode.pos[0] + (srcNode.size?.[0] || 200), + (alignMode === "Proportional" ? getAnchorY(srcNode, originSlot, true) : srcNode.pos[1]), + 20, + occupiedSetPositions, + alignMode + ); + + const setNode = createNode(graph, SET_TYPE, setPos[0], setPos[1]); + if (!setNode) continue; + + setNode.properties = setNode.properties || {}; + setNode.properties.autolink_color_name = colorSet; + if (setNode.properties.autolink_color_locked === undefined) setNode.properties.autolink_color_locked = false; + applyNodeColors(setNode, getAutolinkColorPreset(colorSet, 'set', separateCol, colorGet)); + applyNodeTitleTextColor(setNode, colorTitles); + + // Ottieni il tipo dall'output del nodo sorgente + const outputType = srcNode.outputs?.[originSlot]?.type || "*"; + let outputSlotName = getSlotName(srcNode, originSlot, true); + + // Ulteriore fix: non permettere mai "*" come chiave + if (!outputSlotName || String(outputSlotName).trim() === "*") { + if (outputType && outputType !== "*") outputSlotName = String(outputType).trim().toLowerCase(); + else outputSlotName = `output_${originSlot}`; + } + + console.log(`[IAMCCS AutoLink] Processing: ${srcNode.title || srcNode.type}[${originSlot}] with name "${outputSlotName}"`); + + // Genera nome unico se esiste già un Set con questo nome. + // Importante: NON ridurre mai "model_0" a "model". + const uniqueName = makeUniqueSetName(outputSlotName); + + // Imposta tipo e nome correttamente + if (setNode.inputs && setNode.inputs[0]) { + setNode.inputs[0].type = outputType; + setNode.inputs[0].name = uniqueName; + } + if (setNode.outputs && setNode.outputs[0]) { + setNode.outputs[0].type = outputType; + setNode.outputs[0].name = uniqueName; + } + + setWidgetValue(setNode, "name", uniqueName); + setNode.title = `${uniqueName}`; + + console.log(`[IAMCCS AutoLink] ✓ Created Set node: "${uniqueName}" (from ${srcNode.title || srcNode.type})`); + + // Inizializza lastValue per il tracking delle modifiche + const nameWidget = getWidget(setNode, "name"); + if (nameWidget) nameWidget.lastValue = uniqueName; + + // Collassa + setTimeout(() => { + if (setNode.collapse) setNode.collapse(); + setNode.size = [150, 26]; + }, 0); + + // Collega il Set al nodo sorgente + srcNode.connect(originSlot, setNode, 0); + + // Salva il Set creato per creare i Get dopo + createdSets.set(key, { + setNode, + outputName: uniqueName, + outputType, + srcNode, + originSlot, + destinations + }); + } + + // Crea Get nodes (uno per destinazione) + for (const [key, setData] of createdSets) { + const { setNode, outputName, outputType, srcNode, originSlot, destinations } = setData; + + const sortedDest = [...destinations].sort((a, b) => { + const ay = a.dstNode?.pos?.[1] ?? 0; + const by = b.dstNode?.pos?.[1] ?? 0; + const ax = a.dstNode?.pos?.[0] ?? 0; + const bx = b.dstNode?.pos?.[0] ?? 0; + if (alignMode === "Proportional") { + const as = Number.isFinite(a.targetSlot) ? a.targetSlot : 0; + const bs = Number.isFinite(b.targetSlot) ? b.targetSlot : 0; + if (as !== bs) return as - bs; + } + if (alignMode === "AlignX_Right" || alignMode === "AlignX_Left" || alignMode === "row_right" || alignMode === "row_left") { + return ax - bx; + } + return ay - by; + }); + + for (const dest of sortedDest) { + const { dstNode, targetSlot, linkId } = dest; + + const getPos = findFreePosition( + graph, + dstNode.pos[0], + (alignMode === "Proportional" ? getAnchorY(dstNode, targetSlot, false) : dstNode.pos[1]), + GET_OFFSET_X, + occupiedGetPositions, + alignMode + ); + + const getNode = createNode(graph, GET_TYPE, getPos[0], getPos[1]); + if (!getNode) continue; + + getNode.properties = getNode.properties || {}; + // il get segue sempre il colore della sua chiave (quindi del set) + getNode.properties.autolink_color_name = setNode.properties?.autolink_color_name || colorSet; + applyNodeColors(getNode, getAutolinkColorPreset(getNode.properties.autolink_color_name, 'get', separateCol, colorGet)); + applyNodeTitleTextColor(getNode, colorTitles); + + // Imposta tipo e nome correttamente + if (getNode.outputs && getNode.outputs[0]) { + getNode.outputs[0].type = outputType; + getNode.outputs[0].name = outputName; + } + + setWidgetValue(getNode, "name", outputName); + getNode.title = `${outputName}`; + + // Collassa + setTimeout(() => { + if (getNode.collapse) getNode.collapse(); + getNode.size = [150, 26]; + }, 0); + + // Salva metadata per restore + const metadata = { + iamccs_autolink: true, + output_name: outputName, + origin: { id: srcNode.id, slot: originSlot }, + target: { id: dstNode.id, slot: targetSlot } + }; + + setNode.properties = setNode.properties || {}; + getNode.properties = getNode.properties || {}; + if (!setNode.properties.metadata) { + setNode.properties.metadata = metadata; + } + getNode.properties.metadata = metadata; + + // Disconnetti link originale + _iamccsDisconnectTargetInput(graph, dstNode, targetSlot); + + // Collega Get al nodo destinazione + getNode.connect(0, dstNode, targetSlot); + } + } + + // Converti KijNodes in AutoLink se richiesto + if (includeKijNodes && kijNodesToConvert.length > 0) { + console.log(`[IAMCCS AutoLink] Converting ${kijNodesToConvert.length} KijNode connections...`); + + for (const { link, srcNode, dstNode, srcIsKij, dstIsKij } of kijNodesToConvert) { + // Se src è KijNode (SetNode o GetNode), lo sostituiamo con AutoLink + if (srcIsKij && srcNode.type === KJ_SET_TYPE) { + // Converti KJ SetNode in IAMCCS SetNode + const kijName = srcNode.widgets?.[0]?.value || "output"; + const newSetNode = createNode(graph, SET_TYPE, srcNode.pos[0], srcNode.pos[1]); + + if (newSetNode) { + setWidgetValue(newSetNode, "name", kijName); + newSetNode.title = `${kijName}`; + + // Ricollega input del KJ SetNode + if (srcNode.inputs?.[0]?.link) { + const inputLink = graph.links[srcNode.inputs[0].link]; + if (inputLink) { + const inputSrcNode = getNodeById(graph, inputLink.origin_id); + if (inputSrcNode) { + inputSrcNode.connect(inputLink.origin_slot, newSetNode, 0); + } + } + } + + // Rimuovi il KJ SetNode + graph.remove(srcNode); + } + } + + if (srcIsKij && srcNode.type === KJ_GET_TYPE) { + // Converti KJ GetNode in IAMCCS GetNode + const kijName = srcNode.widgets?.[0]?.value || "output"; + const newGetNode = createNode(graph, GET_TYPE, srcNode.pos[0], srcNode.pos[1]); + + if (newGetNode) { + setWidgetValue(newGetNode, "name", kijName); + newGetNode.title = `${kijName}`; + + // Ricollega output del KJ GetNode + if (srcNode.outputs?.[0]?.links) { + for (const linkId of srcNode.outputs[0].links) { + const outputLink = graph.links[linkId]; + if (outputLink) { + const outputDstNode = getNodeById(graph, outputLink.target_id); + if (outputDstNode) { + newGetNode.connect(0, outputDstNode, outputLink.target_slot); + } + } + } + } + + // Rimuovi il KJ GetNode + graph.remove(srcNode); + } + } + } + } + + console.log(`[IAMCCS AutoLink] ✓ Converted ${linksToConvert.length} links`); + // Normalizza colori (utile se ci sono già Set/Get in scena) + recolorExistingAutoLinks(graph, colorSet, separateCol, colorGet, colorTitles); + _iamccsFixLinkIntegrity(graph); + graph.setDirtyCanvas(true, true); +} + +function restoreDirectLinks(graph) { + console.log("[IAMCCS AutoLink] Restoring links..."); + + const nodes = graph._nodes || []; + const setNodes = []; + const getNodes = []; + + // Raccogli tutti i nodi Set e Get + for (const node of nodes) { + if (node.type === SET_TYPE) { + setNodes.push(node); + } else if (node.type === GET_TYPE) { + getNodes.push(node); + } + } + + console.log(`[IAMCCS AutoLink] Found ${setNodes.length} Set nodes, ${getNodes.length} Get nodes`); + + // Raggruppa per nome + const byName = new Map(); + + for (const setNode of setNodes) { + const name = getAutolinkKey(setNode); + if (!name) continue; + + if (!byName.has(name)) { + byName.set(name, { set: null, gets: [] }); + } + byName.get(name).set = setNode; + } + + for (const getNode of getNodes) { + const name = getAutolinkKey(getNode); + if (!name) continue; + + if (!byName.has(name)) { + byName.set(name, { set: null, gets: [] }); + } + byName.get(name).gets.push(getNode); + } + + const _iamccsDidConnect = (srcNode, originSlot, dstNode, targetSlot) => { + try { + const ts = Number(targetSlot); + const os = Number(originSlot); + const linkId = dstNode?.inputs?.[ts]?.link; + if (linkId == null) return null; + const link = graph.links?.[linkId]; + if (!link) return null; + if (link.origin_id !== srcNode.id) return null; + if (link.target_id !== dstNode.id) return null; + if (Number(link.origin_slot) !== os) return null; + if (Number(link.target_slot) !== ts) return null; + return linkId; + } catch (e) { + return null; + } + }; + + const safeConnect = (srcNode, originSlot, dstNode, targetSlot) => { + const os = Number(originSlot); + const ts = Number(targetSlot); + if (!Number.isFinite(os) || !Number.isFinite(ts)) return null; + + try { + srcNode.connect(os, dstNode, ts); + const ok = _iamccsDidConnect(srcNode, os, dstNode, ts); + if (ok != null) return ok; + } catch (e) { + // fallthrough + } + + try { + if (typeof graph.connect === "function") { + graph.connect(srcNode.id, os, dstNode.id, ts); + const ok2 = _iamccsDidConnect(srcNode, os, dstNode, ts); + if (ok2 != null) return ok2; + } + } catch (e) { + // fallthrough + } + + try { + if (typeof graph.addLink === "function") { + graph.addLink(srcNode, os, dstNode, ts); + const ok3 = _iamccsDidConnect(srcNode, os, dstNode, ts); + if (ok3 != null) return ok3; + } + } catch (e) { + // fallthrough + } + + return null; + }; + + let restored = 0; + let failed = 0; + + const keyToSet = new Map(); + for (const setNode of setNodes) { + const key = getAutolinkKey(setNode); + if (key) keyToSet.set(key, setNode); + } + const keyToGets = new Map(); + for (const getNode of getNodes) { + const key = getAutolinkKey(getNode); + if (!key) continue; + if (!keyToGets.has(key)) keyToGets.set(key, []); + keyToGets.get(key).push(getNode); + } + + const restoredGetIds = new Set(); + const keysWithFailures = new Set(); + + for (const getNode of getNodes) { + const key = getAutolinkKey(getNode) || ""; + const md = getNode?.properties?.metadata; + const origin = md?.origin; + const target = md?.target; + + if (!origin?.id || origin?.slot === undefined || !target?.id || target?.slot === undefined) { + continue; + } + + const srcNode = getNodeById(graph, origin.id); + const dstNode = getNodeById(graph, target.id); + const originSlot = origin.slot; + const targetSlot = target.slot; + + if (!srcNode || !dstNode) { + failed++; + if (key) keysWithFailures.add(key); + continue; + } + + const ts = Number(targetSlot); + if (!Number.isFinite(ts)) { + failed++; + if (key) keysWithFailures.add(key); + continue; + } + + // Preserve old link in case we fail to restore + let prev = null; + try { + const prevId = dstNode?.inputs?.[ts]?.link; + const prevLink = prevId != null ? graph.links?.[prevId] : null; + if (prevLink) prev = { origin_id: prevLink.origin_id, origin_slot: prevLink.origin_slot }; + } catch (e) { + prev = null; + } + + let hadGetLink = false; + try { + const out = getNode.outputs?.[0]; + hadGetLink = !!(out?.links && out.links.length); + } catch (e) { + hadGetLink = false; + } + + _iamccsDisconnectTargetInput(graph, dstNode, ts); + + const ok = safeConnect(srcNode, originSlot, dstNode, ts); + if (ok === null) { + failed++; + if (key) keysWithFailures.add(key); + + // rollback previous direct link if present + if (prev) { + try { + const prevSrc = getNodeById(graph, prev.origin_id); + if (prevSrc) safeConnect(prevSrc, prev.origin_slot, dstNode, ts); + } catch (e) { + // ignore + } + } + + if (hadGetLink) { + try { getNode.connect(0, dstNode, ts); } catch (e) {} + } + continue; + } + + // Ensure we don't leave duplicate/orphan links pointing to the same target input + _iamccsRemoveOtherLinksToTarget(graph, dstNode.id, ts, ok); + + restored++; + restoredGetIds.add(getNode.id); + } + + if (restored === 0) { + console.warn("[IAMCCS AutoLink] No metadata restores performed; falling back to legacy restore"); + + for (const [name, { set, gets }] of byName) { + if (!set || !gets.length) continue; + + const setInputLink = set.inputs?.[0]?.link; + if (!setInputLink) continue; + const link = graph.links?.[setInputLink]; + if (!link) continue; + + const srcNode = getNodeById(graph, link.origin_id); + if (!srcNode) continue; + const originSlot = link.origin_slot; + + for (const getNode of gets) { + const getOutput = getNode.outputs?.[0]; + if (!getOutput?.links?.length) continue; + + const outLinks = [...getOutput.links]; + for (const linkId of outLinks) { + const outLink = graph.links?.[linkId]; + if (!outLink) continue; + + const dstNode = getNodeById(graph, outLink.target_id); + if (!dstNode) continue; + const targetSlot = outLink.target_slot; + const ts = Number(targetSlot); + if (!Number.isFinite(ts)) continue; + + // Preserve old link in case we fail to restore + let prev = null; + try { + const prevId = dstNode?.inputs?.[ts]?.link; + const prevLink = prevId != null ? graph.links?.[prevId] : null; + if (prevLink) prev = { origin_id: prevLink.origin_id, origin_slot: prevLink.origin_slot }; + } catch (e) { + prev = null; + } + + _iamccsDisconnectTargetInput(graph, dstNode, ts); + + const ok = safeConnect(srcNode, originSlot, dstNode, ts); + if (ok === null) { + failed++; + keysWithFailures.add(name); + + // rollback previous direct link if present + if (prev) { + try { + const prevSrc = getNodeById(graph, prev.origin_id); + if (prevSrc) safeConnect(prevSrc, prev.origin_slot, dstNode, ts); + } catch (e) { + // ignore + } + } + continue; + } + + // Ensure we don't leave duplicate/orphan links pointing to the same target input + _iamccsRemoveOtherLinksToTarget(graph, dstNode.id, ts, ok); + + restored++; + restoredGetIds.add(getNode.id); + } + } + } + } + + const nodesToRemove = new Set(); + for (const getNode of getNodes) { + if (restoredGetIds.has(getNode.id)) nodesToRemove.add(getNode); + } + for (const [key, setNode] of keyToSet) { + const gets = keyToGets.get(key) || []; + const allGetsRestored = gets.length > 0 && gets.every(g => restoredGetIds.has(g.id)); + if (allGetsRestored && !keysWithFailures.has(key)) { + nodesToRemove.add(setNode); + } + } + + console.log(`[IAMCCS AutoLink] Removing ${nodesToRemove.size} AutoLink nodes (safe mode)...`); + + // Make sure UI updates immediately after link rewiring + try { _iamccsFixLinkIntegrity(graph); } catch (e) {} + try { graph.setDirtyCanvas(true, true); } catch (e) {} + + // Rimuovi i nodi in modo asincrono per permettere agli eventi di stabilizzarsi + if (nodesToRemove.size > 0) { + setTimeout(() => { + for (const node of nodesToRemove) { + try { + // Disconnetti tutti i collegamenti prima di rimuovere + if (node.inputs) { + for (let i = node.inputs.length - 1; i >= 0; i--) { + try { + if (node.disconnectInput) node.disconnectInput(i); + } catch (e) { + // ignore + } + } + } + if (node.outputs) { + for (let i = node.outputs.length - 1; i >= 0; i--) { + try { + if (node.disconnectOutput) node.disconnectOutput(i); + } catch (e) { + // ignore + } + } + } + + graph.remove(node); + } catch (e) { + console.error(`[IAMCCS AutoLink] Error removing node ${node.id}:`, e); + } + } + try { _iamccsFixLinkIntegrity(graph); } catch (e) {} + try { graph.setDirtyCanvas(true, true); } catch (e) {} + }, 100); + } + + console.log(`[IAMCCS AutoLink] ✓ Restored ${restored} links`); + if (failed > 0) console.log(`[IAMCCS AutoLink] ⚠ Restore failures: ${failed}`); + console.log(`[IAMCCS AutoLink] ✓ Removed ${nodesToRemove.size} AutoLink nodes`); +} + +console.log("[IAMCCS AutoLink] Extension loaded"); + +// ---- Runtime patch: ensure AutoLink graphs can execute ---- +// AutoLink Set/Get nodes are frontend helpers; the backend nodes are no-op. +// To run a workflow, we temporarily restore direct links before queueing, +// then reload the original graph so the user keeps AutoLink nodes. +function _iamccsPatchQueuePromptForAutolink() { + try { + if (app.__iamccs_autolink_queue_patch_installed) return; + if (typeof app?.queuePrompt !== "function") return; + + const originalQueuePrompt = app.queuePrompt; + app.queuePrompt = async function (...args) { + const graph = app?.graph; + const hasAutoLink = !!graph?._nodes?.some(n => n?.type === SET_TYPE || n?.type === GET_TYPE); + if (!hasAutoLink) { + return await originalQueuePrompt.apply(this, args); + } + + let snapshot = null; + try { + snapshot = typeof graph.serialize === "function" ? graph.serialize() : null; + } catch (e) { + snapshot = null; + } + + try { + // Convert AutoLink nodes into direct links (and remove them) for execution. + // This mutates the live graph, so we restore from snapshot in finally. + try { restoreDirectLinks(graph); } catch (e) { + console.warn("[IAMCCS AutoLink] restoreDirectLinks failed before queuePrompt", e); + } + + try { _iamccsFixLinkIntegrity(graph); } catch {} + + return await originalQueuePrompt.apply(this, args); + } finally { + // Restore the original AutoLink graph for the UI. + if (snapshot) { + try { + if (typeof app.loadGraphData === "function") { + await app.loadGraphData(snapshot); + } else if (typeof graph?.configure === "function") { + graph.configure(snapshot); + graph.setDirtyCanvas?.(true, true); + } + } catch (e) { + console.warn("[IAMCCS AutoLink] Failed to restore graph snapshot after queuePrompt", e); + } + } + } + }; + + app.__iamccs_autolink_queue_patch_installed = true; + console.log("[IAMCCS AutoLink] Patched app.queuePrompt (temporary restore for execution)"); + } catch (e) { + console.warn("[IAMCCS AutoLink] Failed to patch queuePrompt", e); + } +} + +_iamccsPatchQueuePromptForAutolink(); + diff --git a/web/iamccs_ltx2_time_length_sync.js b/web/iamccs_ltx2_time_length_sync.js new file mode 100644 index 0000000..56639ed --- /dev/null +++ b/web/iamccs_ltx2_time_length_sync.js @@ -0,0 +1,117 @@ +// IAMCCS LTX-2 seconds <-> length sync +// Frontend-only helper for: +// - IAMCCS_LTX2_TimeFrameCount +// - IAMCCS_LTX2_Validator +// Uses FPS to convert: +// length(frames) = 1 + seconds * fps +// seconds = (length-1) / fps + +import { app } from "../../scripts/app.js"; + +const TIMEFRAME_TYPE = "IAMCCS_LTX2_TimeFrameCount"; +const VALIDATOR_TYPE = "IAMCCS_LTX2_Validator"; +const FRAMERATE_SYNC_TYPE = "IAMCCS_LTX2_FrameRateSync"; + +console.log("[IAMCCS LTX2] Loading seconds/length sync..."); + +function getWidget(node, name) { + if (!node?.widgets?.length) return null; + return node.widgets.find(w => w?.name === name || w?.label === name) || null; +} + +function clampNumber(v, min, max) { + const n = Number(v); + if (!Number.isFinite(n)) return min; + return Math.max(min, Math.min(max, n)); +} + +function getGraphFpsOrDefault(defaultFps = 25) { + try { + const nodes = app?.graph?._nodes || []; + const fr = nodes.find(n => n?.type === FRAMERATE_SYNC_TYPE); + if (!fr) return defaultFps; + + const wFps = getWidget(fr, "fps"); + const wMode = getWidget(fr, "int_mode"); + const fpsIn = clampNumber(wFps?.value ?? defaultFps, 1.0, 240.0); + const mode = String(wMode?.value || "round"); + + if (mode === "floor") return Math.max(1, Math.floor(fpsIn)); + if (mode === "ceil") return Math.max(1, Math.ceil(fpsIn)); + if (mode === "fixed") return Math.max(1, Math.round(fpsIn)); + // round + return Math.max(1, Math.round(fpsIn)); + } catch (e) { + return defaultFps; + } +} + +function snapLengthToLtx2RuleUp(length) { + // LTX-2 constraint: 1 + 8*x frames + const n = Math.max(1, Math.round(Number(length) || 1)); + const rem = (n - 1) % 8; + if (rem === 0) return n; + return n + (8 - rem); +} + +function installSecondsLengthSync(node, { snapRule = false } = {}) { + const wSeconds = getWidget(node, "seconds"); + const wLength = getWidget(node, "length"); + if (!wSeconds || !wLength) return; + + let updating = false; + + const updateLengthFromSeconds = () => { + const fps = getGraphFpsOrDefault(25); + const seconds = clampNumber(wSeconds.value, 0.01, 3600); + let length = clampNumber(1 + Math.round(seconds * fps), 1, 16385); + if (snapRule) length = snapLengthToLtx2RuleUp(length); + wLength.value = length; + }; + + const updateSecondsFromLength = () => { + const fps = getGraphFpsOrDefault(25); + const length = clampNumber(wLength.value, 1, 16385); + const seconds = (length - 1) / fps; + // keep precision consistent with widget step 0.01 + wSeconds.value = Math.round(clampNumber(seconds, 0.01, 3600) * 100) / 100; + }; + + const hookWidget = (widget, fn) => { + const prev = widget.callback; + widget.callback = function () { + const r = prev?.apply(this, arguments); + if (updating) return r; + updating = true; + try { + fn(); + node.setDirtyCanvas(true, true); + } finally { + updating = false; + } + return r; + }; + }; + + hookWidget(wSeconds, updateLengthFromSeconds); + hookWidget(wLength, updateSecondsFromLength); +} + +app.registerExtension({ + name: "iamccs.ltx2.time_length_sync", + + async beforeRegisterNodeDef(nodeType, nodeData) { + if (!nodeData?.name) return; + const name = nodeData.name; + + if (name !== TIMEFRAME_TYPE && name !== VALIDATOR_TYPE) return; + + const onNodeCreated = nodeType.prototype.onNodeCreated; + nodeType.prototype.onNodeCreated = function () { + const r = onNodeCreated?.apply(this, arguments); + // Snap to 8n+1 on both nodes, since LTX-2 VAE encode requires it. + installSecondsLengthSync(this, { snapRule: true }); + return r; + }; + }, +}); diff --git a/web/js/iamccs_ltx2_validator_sync.js b/web/js/iamccs_ltx2_validator_sync.js deleted file mode 100644 index c2b692e..0000000 --- a/web/js/iamccs_ltx2_validator_sync.js +++ /dev/null @@ -1,190 +0,0 @@ -import { app } from "/scripts/app.js"; - -function findWidget(node, name) { - return node?.widgets?.find((w) => w?.name === name); -} - -function clampNumber(value, min, max) { - const v = Number(value); - if (!Number.isFinite(v)) return min; - return Math.min(max, Math.max(min, v)); -} - -function getNearestFrameRateSyncFpsOrFallback(node, fallback = 24.0) { - try { - const graph = node?.graph; - const nodes = graph?._nodes || graph?.nodes || []; - const frNodes = nodes.filter((n) => n?.comfyClass === "IAMCCS_LTX2_FrameRateSync"); - if (!frNodes.length) return fallback; - - const pos = node?.pos || [0, 0]; - let best = frNodes[0]; - let bestD2 = Infinity; - - for (const n of frNodes) { - const p = n?.pos || [0, 0]; - const dx = Number(p[0]) - Number(pos[0]); - const dy = Number(p[1]) - Number(pos[1]); - const d2 = dx * dx + dy * dy; - if (d2 < bestD2) { - bestD2 = d2; - best = n; - } - } - - const fpsWidget = findWidget(best, "fps") || findWidget(best, "value"); - const fps = Number(fpsWidget?.value); - if (!Number.isFinite(fps) || fps <= 0) return fallback; - return fps; - } catch (e) { - return fallback; - } -} - -function framesTo8n1(frames, mode) { - let f = Math.max(1, Math.round(Number(frames) || 1)); - const rem = (f - 1) % 8; - if (rem === 0) return f; - - const down = Math.max(1, f - rem); - const up = f + (8 - rem); - - if (mode === "down") return down; - if (mode === "nearest") return (up - f) <= (f - down) ? up : down; - return up; -} - -function shouldEnable8n1Autofix(node) { - const autofixWidget = findWidget(node, "autofix"); - const lengthFixWidget = findWidget(node, "length_fix"); - - return { - hasAutofixWidgets: Boolean(autofixWidget && lengthFixWidget), - getAutofix: () => Boolean(autofixWidget?.value), - getLengthFix: () => String(lengthFixWidget?.value || "up"), - autofixWidget, - lengthFixWidget, - }; -} - -function syncLtx2SecondsLengthNode(node) { - const secondsWidget = findWidget(node, "seconds"); - const lengthWidget = findWidget(node, "length"); - - if (!secondsWidget || !lengthWidget) return; - - const { - hasAutofixWidgets, - getAutofix, - getLengthFix, - autofixWidget, - lengthFixWidget, - } = shouldEnable8n1Autofix(node); - - let isUpdating = false; - - const applyAutofix = (len) => { - const safeLen = Math.max(1, Math.round(Number(len) || 1)); - if (!hasAutofixWidgets) return safeLen; - if (!getAutofix()) return safeLen; - return framesTo8n1(safeLen, getLengthFix()); - }; - - const setWidgetValue = (widget, value) => { - widget.value = value; - // Ensure UI refresh - app.graph?.setDirtyCanvas(true, false); - }; - - const syncFromSeconds = () => { - const fps = getNearestFrameRateSyncFpsOrFallback(node, 24.0); - const seconds = clampNumber(secondsWidget.value, 0.0, 3600.0); - - const rawLength = Math.round(seconds * fps) + 1; - const fixedLength = applyAutofix(rawLength); - const fixedSeconds = (fixedLength - 1) / fps; - - setWidgetValue(lengthWidget, fixedLength); - // Keep both consistent with what the node will actually output. - setWidgetValue(secondsWidget, Number(fixedSeconds.toFixed(2))); - }; - - const syncFromLength = () => { - const fps = getNearestFrameRateSyncFpsOrFallback(node, 24.0); - const len = Math.max(1, Math.round(Number(lengthWidget.value) || 1)); - const fixedLength = applyAutofix(len); - const fixedSeconds = (fixedLength - 1) / fps; - - setWidgetValue(lengthWidget, fixedLength); - setWidgetValue(secondsWidget, Number(fixedSeconds.toFixed(2))); - }; - - const withGuard = (fn) => { - if (isUpdating) return; - isUpdating = true; - try { - fn(); - } finally { - isUpdating = false; - } - }; - - const originalSecondsCb = secondsWidget.callback; - secondsWidget.callback = function (value) { - originalSecondsCb?.call(this, value); - withGuard(syncFromSeconds); - }; - - const originalLengthCb = lengthWidget.callback; - lengthWidget.callback = function (value) { - originalLengthCb?.call(this, value); - withGuard(syncFromLength); - }; - - const originalAutofixCb = autofixWidget?.callback; - if (autofixWidget) { - autofixWidget.callback = function (value) { - originalAutofixCb?.call(this, value); - withGuard(syncFromLength); - }; - } - - const originalLengthFixCb = lengthFixWidget?.callback; - if (lengthFixWidget) { - lengthFixWidget.callback = function (value) { - originalLengthFixCb?.call(this, value); - withGuard(syncFromLength); - }; - } - - // Initial sync after creation/configure. - withGuard(syncFromLength); -} - -app.registerExtension({ - name: "IAMCCS.LTX2Validator.SecondsLengthSync", - - async beforeRegisterNodeDef(nodeType, nodeData, app) { - const supported = new Set([ - "IAMCCS_LTX2_Validator", - "IAMCCS_LTX2_TimeFrameCount", - ]); - if (!supported.has(nodeData?.name)) return; - - const onNodeCreated = nodeType.prototype.onNodeCreated; - nodeType.prototype.onNodeCreated = function () { - const r = onNodeCreated?.apply(this, arguments); - const node = this; - setTimeout(() => syncLtx2SecondsLengthNode(node), 10); - return r; - }; - - const configure = nodeType.prototype.configure; - nodeType.prototype.configure = function () { - const r = configure?.apply(this, arguments); - const node = this; - setTimeout(() => syncLtx2SecondsLengthNode(node), 10); - return r; - }; - }, -}); diff --git a/workflow_examples/IAMCCS_LTX2_ExtensionModule_Example.json b/workflow_examples/IAMCCS_LTX2_ExtensionModule_Example.json new file mode 100644 index 0000000..20e3f28 --- /dev/null +++ b/workflow_examples/IAMCCS_LTX2_ExtensionModule_Example.json @@ -0,0 +1,247 @@ +{ + "last_node_id": 8, + "last_link_id": 12, + "nodes": [ + { + "id": 1, + "type": "LoadImage", + "pos": [100, 100], + "size": [315, 314], + "flags": {}, + "order": 0, + "mode": 0, + "outputs": [ + { + "name": "IMAGE", + "type": "IMAGE", + "links": [1], + "slot_index": 0 + } + ], + "properties": { + "Node name for S&R": "LoadImage" + }, + "widgets_values": [ + "example_frames.png" + ] + }, + { + "id": 2, + "type": "IAMCCS_GetAutoLink", + "pos": [100, 450], + "size": [210, 58], + "flags": {}, + "order": 1, + "mode": 0, + "outputs": [ + { + "name": "INT", + "type": "INT", + "links": [2], + "slot_index": 0, + "label": "OVERLAP_IMAGE_COUNT" + } + ], + "properties": { + "Node name for S&R": "IAMCCS_GetAutoLink" + }, + "widgets_values": [] + }, + { + "id": 3, + "type": "IAMCCS_LTX2_ExtensionModule", + "pos": [450, 100], + "size": [400, 600], + "flags": {}, + "order": 2, + "mode": 0, + "inputs": [ + { + "name": "source_images", + "type": "IMAGE", + "link": 1 + }, + { + "name": "new_images", + "type": "IMAGE", + "link": null + }, + { + "name": "math_value_b", + "type": "INT", + "link": null + }, + { + "name": "autolink_overlap_in", + "type": "INT", + "link": 2 + } + ], + "outputs": [ + { + "name": "source_images", + "type": "IMAGE", + "links": [], + "slot_index": 0 + }, + { + "name": "start_images", + "type": "IMAGE", + "links": [3], + "slot_index": 1 + }, + { + "name": "extended_images", + "type": "IMAGE", + "links": [4], + "slot_index": 2 + }, + { + "name": "overlap_frames", + "type": "INT", + "links": [], + "slot_index": 3 + }, + { + "name": "calculated_frames", + "type": "INT", + "links": [], + "slot_index": 4 + }, + { + "name": "extension_frames", + "type": "INT", + "links": [], + "slot_index": 5 + }, + { + "name": "autolink_overlap_out", + "type": "INT", + "links": [5], + "slot_index": 6 + }, + { + "name": "report", + "type": "STRING", + "links": [6], + "slot_index": 7 + } + ], + "properties": { + "Node name for S&R": "IAMCCS_LTX2_ExtensionModule" + }, + "widgets_values": [ + 10, + "source", + "linear_blend", + true, + "a-1", + 121, + true + ] + }, + { + "id": 4, + "type": "IAMCCS_SetAutoLink", + "pos": [900, 100], + "size": [210, 58], + "flags": {}, + "order": 3, + "mode": 0, + "inputs": [ + { + "name": "value", + "type": "INT", + "link": 5, + "label": "OVERLAP_IMAGE_COUNT" + } + ], + "properties": { + "Node name for S&R": "IAMCCS_SetAutoLink" + }, + "widgets_values": [] + }, + { + "id": 5, + "type": "ShowText", + "pos": [900, 200], + "size": [400, 200], + "flags": {}, + "order": 4, + "mode": 0, + "inputs": [ + { + "name": "text", + "type": "STRING", + "link": 6 + } + ], + "properties": { + "Node name for S&R": "ShowText" + }, + "widgets_values": [] + }, + { + "id": 6, + "type": "PreviewImage", + "pos": [900, 450], + "size": [315, 246], + "flags": {}, + "order": 5, + "mode": 0, + "inputs": [ + { + "name": "images", + "type": "IMAGE", + "link": 3 + } + ], + "properties": { + "Node name for S&R": "PreviewImage" + } + }, + { + "id": 7, + "type": "PreviewImage", + "pos": [900, 750], + "size": [315, 246], + "flags": {}, + "order": 6, + "mode": 0, + "inputs": [ + { + "name": "images", + "type": "IMAGE", + "link": 4 + } + ], + "properties": { + "Node name for S&R": "PreviewImage" + } + } + ], + "links": [ + [1, 1, 0, 3, 0, "IMAGE"], + [2, 2, 0, 3, 3, "INT"], + [3, 3, 1, 6, 0, "IMAGE"], + [4, 3, 2, 7, 0, "IMAGE"], + [5, 3, 6, 4, 0, "INT"], + [6, 3, 7, 5, 0, "STRING"] + ], + "groups": [], + "config": {}, + "extra": { + "ds": { + "scale": 1, + "offset": [0, 0] + } + }, + "version": 0.4, + "workflow_info": { + "name": "IAMCCS LTX-2 Extension Module - Example Workflow", + "description": "Demonstrates the LTX-2 Extension Module with AutoLink integration.\n\nFeatures:\n- GetAutoLink for receiving overlap count from previous iteration\n- LTX2_ExtensionModule for batch extension with blending\n- SetAutoLink for passing overlap count to next iteration\n- Math operation (a-1) to reduce overlap each iteration\n- Preview of start_images and extended_images\n\nUsage:\n1. Load initial frames\n2. First iteration uses static overlap_frames=10\n3. Subsequent iterations use AutoLink value (decremented by 1)\n4. Start images become source for next pass\n5. Loop continues until desired length reached", + "author": "IAMCCS", + "version": "1.0", + "created": "2026-01-24" + } +}