joeygambino commited on
Commit
11e0767
·
verified ·
1 Parent(s): 9ec4cda

docs: hires routing table everywhere (hires_factor = routing switch); PREVIEW-labeled SaveVideo; routing-framed tooltip; refreshed v1.5 zip

Browse files
ComfyUI_JoyAI_Echo_v1.5_COMPLETE.zip CHANGED
@@ -1,3 +1,3 @@
1
  version https://git-lfs.github.com/spec/v1
2
- oid sha256:3bfbcac02d879a2a0be248289464082f59aa5e977506425669287d43cd996354
3
- size 136376
 
1
  version https://git-lfs.github.com/spec/v1
2
+ oid sha256:cbb87dac03ddc9b22665642794e996bdf2b7deec88d73cc8f1115b037159cd5b
3
+ size 180737
README.md CHANGED
@@ -1,309 +1,315 @@
1
- ---
2
- license: other
3
- license_name: ltx-2-community-license
4
- license_link: https://huggingface.co/Lightricks/LTX-2/blob/main/LICENSE.txt
5
- tags:
6
- - comfyui
7
- - joyai-echo
8
- - ltx-video
9
- - multishot
10
- - patch
11
- ---
12
-
13
- # JoyAI-Echo GGUF nodes — multishot fixes + automation patch
14
-
15
- A set of bug fixes and features layered on top of the community
16
- `ComfyUI_JoyAI_Echo_GGUF_Nodes` pack (the Rebels GGUF loader stack around
17
- JoyAI-Echo). Everything here targets the **multi-shot** path (`JoyEcho_Generate`
18
- + the discrete Rebels loaders / `JoyEcho_ModelLoader`).
19
-
20
- This is a **patch drop**, not a standalone pack: copy these files over a working
21
- install of the same pack (back up first). The files are interdependent — in
22
- particular `nodes.py` calls new signatures added to the two `libs/` files, so
23
- apply them together.
24
-
25
- Tested on an RTX 5090 (32 GB) and a 3090 (24 GB), ComfyUI 0.26–0.27,
26
- torch 2.8–2.11, with the JoyAI-Echo bf16 release and self-built Q8 GGUFs.
27
-
28
- ---
29
-
30
- ## Files in this package
31
-
32
- ```
33
- nodes.py # JoyEcho_TextEncode / _Generate / _ModelLoader / _LLMEnhance
34
- __init__.py # registrations for the new nodes
35
- rebels_loaders.py # discrete GGUF loaders (text-encoder fixes)
36
- joyecho_prompt_source.py (new node) # one dropdown: .txt briefs + .json scripts
37
- joyecho_ref_picker.py (new node) # auto reference-image picker by character name
38
- joyecho_ref_batch.py (new node) # None-tolerant image batcher
39
- joyecho_script_picker.py (new node) # JSON dropdown (superseded by PromptSource)
40
- libs/ltx_distillation/models/ltx_wrapper.py # fp8 quantization passthrough
41
- libs/ltx_core/loader/fuse_loras.py # kohya-LoRA fusion + alpha scaling + fuse telemetry
42
- libs/ltx_core/quantization/policy.py # fp8_scaled_mm_torch policy (+ sm_89 gate)
43
- libs/ltx_core/quantization/fp8_torch_mm.py # native-fp8 Linear forward (torch._scaled_mm)
44
- libs/ltx_distillation/utils.py # tiled VAE decode
45
- libs/ltx_distillation/inference/memory_multishot.py # memory bank TRIM FIX (critical)
46
- libs/ltx_distillation/inference/bidirectional_pipeline.py # dtype hardening vs fp8 params
47
- libs/ltx_distillation/inference/memory_bidirectional_pipeline.py # dtype hardening vs fp8 params
48
- prompts/long_story_writer_system_prompt.md # (optional) de-musicked + character-age edits
49
- ```
50
-
51
- The files are interdependent - apply the whole set together, never cherry-pick
52
- (a nodes.py newer than its libs/ raises AttributeError at load).
53
-
54
- ---
55
-
56
- ## Bug fixes
57
-
58
- ### 1. `enable_audio_memory=False` silently disabled ALL cross-shot memory
59
- The pack computed `audio_memory_latent=None` when audio memory was off, and the
60
- video **memory-bank save was gated on that latent being non-None** — so with
61
- audio memory off (the standard anti-drone setting) the bank never filled and
62
- cross-shot **identity** silently died (symptom: `memory_size=0` every shot even
63
- with `memory_max_size=7`; a new face each shot).
64
- Fix: memory storage is now unconditional; `enable_audio_memory` gates only the
65
- audio-memory **injection** path. Verify: console `memory_size=` should climb
66
- 0,1,2,… capped at your `memory_max_size`. (`nodes.py`)
67
-
68
- ### 1b. Memory bank trim was a NO-OP whenever `memory_max_size <= num_fix_frames` (CRITICAL)
69
- `PairedAudioVideoMemoryBank._trim()` computed `tail[-keep_tail:]` - and when
70
- `keep_tail == 0` (e.g. the common max_size=3 / num_fix_frames=3 combo),
71
- `tail[-0:]` is the WHOLE list, so the bank grew unbounded: every shot
72
- conditioned on EVERY prior shot. Symptom: console `memory_size=` climbing
73
- 0,1,2,...,N-1 past your cap, and severe compounding quality degradation over
74
- long runs (waxy skin, contrast crush, smearing by the late shots - the "gets
75
- worse as it goes" failure). Fixed with a proper zero-tail branch + anchor
76
- clamp; `memory_size=` now freezes at your cap. This one fix eliminated the entire
77
- long-run degradation in our tests. (`libs/.../memory_multishot.py`)
78
-
79
- ### 2. GGUF text-encoder loader (`RebelsJE_TextEncoder`)
80
- Two fixes so a text-only Gemma-3 GGUF loads cleanly:
81
- - **meta-strip**: drop `vision_tower` / `multi_modal_projector` / `lm_head`
82
- (the text-only GGUF has no weights for them → "Cannot copy out of meta tensor").
83
- - **device-unify**: pin the embeddings-processor to the encoder's actual device
84
- (GGUF Gemma runs on CPU while the connector was on cuda → addmm device mismatch).
85
- - **fp8 gemma scale-key layouts**: the `our_fp8` swap only recognized its own
86
- export layout (bare module names + `.scale_weight`); standard HF/comfy-style
87
- fp8 gemma files (`<module>.weight` + `.weight_scale`, e.g. community
88
- abliterated builds) silently loaded with **zero modules swapped** — the
89
- encoder stayed bf16 with no indication. Both layouts are now accepted
90
- (per-tensor scalar scales; per-channel scales are skipped and those modules
91
- stay bf16), and a loud warning prints if a file matches neither.
92
- (`rebels_loaders.py`)
93
-
94
- ---
95
-
96
- ## Features
97
-
98
- ### 3. Split per-domain negative lever (`JoyEcho_TextEncode`)
99
- The DMD pipeline has no CFG, so the only steering lever is embedding-space.
100
- Instead of one `negative_prompt`/`negative_scale` that steers both branches,
101
- this splits it:
102
- - `negative_prompt_video` / `negative_scale_video` — kills burned-in
103
- captions/subtitles. Working value ~0.5. **Above ~0.8 it over-rotates the
104
- video context and locks every shot to shot 1's composition** (scene-lock).
105
- - `negative_prompt_audio` / `negative_scale_audio` — kills invented
106
- music/score. Keep ≤ ~0.4 or dialogue suffers.
107
- Steering is norm-preserving (RescaleCFG-style): `cond' = renorm(cond + s*(cond − neg))`.
108
- Old single-widget names still work as a fallback. (`nodes.py`)
109
-
110
- ### 4. Passthrough mode (`JoyEcho_LLMEnhance`)
111
- `mode = "passthrough (raw JSON, skip LLM)"` — feed a finished
112
- `{"prompts":[...]}` script straight through with no LLM call / no API key.
113
- Auto-detects when `story_idea` already parses as that JSON. (`nodes.py`)
114
-
115
- ### 5. Reference-image conditioning — I2V-as-reference (`JoyEcho_Generate`)
116
- New `reference_image` (IMAGE batch, up to 4). Identity references are prepended
117
- as **video-only conditioning clips** at the memory-encode step — they are
118
- **never** written into the paired audio/video bank. (An earlier attempt that
119
- seeded refs into the bank with zero-filled audio latents injected loud
120
- background noise with 2+ refs; video-only conditioning avoids it entirely.)
121
- Also new: `head_trim_frames` (auto 8 with refs) drops the first N frames of each
122
- shot, where the model morphs out of the reference/memory content. The trim is
123
- applied once right after decode, so the final output, the per-shot preview
124
- files, and any external concat of them stay frame-identical. (`nodes.py`)
125
-
126
- ### 6. Shot transitions (`JoyEcho_Generate`)
127
- `transition`: `cut` (original) / `dissolve` (overlap cross-dissolve + equal-power
128
- audio crossfade) / `vhs_glitch` (analog static burst at each boundary: snow,
129
- tear bands, dropout lines + a raised-cosine tape-noise audio bed).
130
- `transition_frames`, `glitch_intensity` tune it. (`nodes.py`)
131
-
132
- ### 7. fp8 transformer quantization (`JoyEcho_ModelLoader`)
133
- New `fp8_transformer` toggle. Quantizes the DiT's attention/FF linear weights to
134
- `float8_e4m3fn` **at load, from the normal bf16 checkpoint** (uses the vendored
135
- `ltx_core.quantization.QuantizationPolicy.fp8_cast()` — upcasts per-layer at
136
- inference). Roughly halves DiT weight memory and halves sequential-offload PCIe
137
- traffic; keeps memory training + all tensors; VAEs/text-encoder/non-linears stay
138
- bf16. Ignored when a GGUF DiT is selected (already quantized).
139
- (`nodes.py` + `libs/ltx_distillation/models/ltx_wrapper.py` — new `quantization`
140
- param; the quantized build path skips the post-load dtype cast that would
141
- otherwise silently upcast fp8 back to bf16.)
142
-
143
- ### 8. Tiled VAE decode (`JoyEcho_Generate`)
144
- Decoding a long high-res shot (e.g. 241f @ 1280×736) in one pass hard-aborts the
145
- VAE decode on a 24–32 GB card (fatal cuDNN abort mid-conv, not a catchable OOM).
146
- New `decode_tiling` (`auto`/`on`/`off`) routes decode through the vendored
147
- `VideoDecoder.tiled_decode` — **temporal-only** 64-frame chunks with 24-frame
148
- blended overlap (no spatial tiles → no spatial seams), streaming each chunk to
149
- CPU. `auto` engages only above a size threshold, so small renders keep the
150
- original single-pass decode bit-for-bit.
151
- (`nodes.py` + `libs/ltx_distillation/utils.py` — `decode_benchmark_sample` gains
152
- a `video_tiling_config` kwarg + `_decode_video_tiled_uint8`.)
153
-
154
- ### 9. Model dropdown (`JoyEcho_ModelLoader`)
155
- New `model_file` combo lists every `.safetensors` / `.gguf` under the ComfyUI
156
- `checkpoints` / `diffusion_models` / `unet` dirs. Pick a `.safetensors` → full
157
- checkpoint (replaces `checkpoint_path`); pick a `.gguf` → DiT loaded from GGUF
158
- while `checkpoint_path` still supplies the VAEs / vocoder / text connectors.
159
- `"(use checkpoint_path)"` keeps the old typed-path behavior. A matching
160
- `lora_file` dropdown lists every `.safetensors` under `models/loras`
161
- (applied at `lora_strength` on the safetensors DiT path; ignored for GGUF). Plus a clear
162
- early error if `gemma_path` is a `.gguf`/file/sidecar-less dir (this loader
163
- needs the HF `gemma-3-12b-it` folder; GGUF Gemma only works via
164
- `RebelsJE_TextEncoder`). (`nodes.py`)
165
-
166
- ### 10. LoRA loading hardening (`JoyEcho_ModelLoader` + `libs/.../fuse_loras.py`)
167
- - A `lora_file` dropdown picks LoRAs from `models/loras` (existing
168
- `lora_strength` widget applies).
169
- - Fusion now supports **kohya naming** (`lora_down`/`lora_up`) in addition to
170
- PEFT (`lora_A`/`lora_B`), with standard `alpha/rank` scaling — previously a
171
- kohya-named LoRA silently did NOTHING (zero keys matched, no warning).
172
- - Fusion prints how many weights fused, and WARNS LOUDLY when a provided LoRA
173
- matched zero keys.
174
- - The loader refuses **ComfyUI-quantized checkpoints** (`.comfy_quant` marker
175
- tensors, e.g. "fp8mixed learned" builds) with a clear error: this loader
176
- never applies their weight scales (the model would silently load mis-scaled)
177
- and LoRA fusion on them crashes with shape errors. Use bf16 checkpoints.
178
-
179
- ### 11. Automation / batching nodes (new)
180
- - **`JoyEcho_PromptSource`** — one dropdown listing LPFF-style `.txt` briefs
181
- (from the inspire-pack prompts tree) **and** passthrough `.json` scripts
182
- (`input/joyecho_prompts/`). Multi-block briefs fan out like
183
- LoadPromptsFromFile. Emits `story_idea` (→ LLMEnhance) + `character`
184
- (→ RefPicker) + `count`. Replaces the LPFF→UnzipPrompt chain and lets you
185
- switch prompt sources with one dropdown instead of rewiring.
186
- - **`JoyEcho_RefPicker`** — auto-selects a character reference image from a
187
- folder tree keyed by character name (a `character_pick` dropdown of the
188
- folder names, a typed/wired `character` string, or a prompt scan — dialogue
189
- mentions are stripped so only the on-screen subject wins). The dropdown
190
- survives model refreshes, an explicitly named character that matches no
191
- folder refuses to fall back to the prompt scan (a wiped/typo'd name can't
192
- silently become the wrong character's face), and the cache signature
193
- includes the prompt text (without it, ComfyUI could serve a cached pick
194
- from a previous queue item). `on_no_match=no_reference` returns nothing so
195
- a batch keeps running.
196
- - **`JoyEcho_RefBatch`** — None-tolerant image batcher: combines up to 4
197
- optional IMAGE inputs (e.g. two RefPickers for a two-character shot), skips
198
- missing refs, resizes mismatched sizes to the first image, outputs `None` if
199
- all are missing (Generate then just skips identity seeding). The stock KJNodes
200
- `ImageBatchMulti` crashes with `'NoneType' has no attribute 'shape'` on a
201
- missing ref; this replaces it.
202
- - **`JoyEcho_ScriptPicker`** — JSON dropdown (superseded by PromptSource; kept
203
- for compatibility).
204
-
205
- ### 12. GPU encode hot-swap (`JoyEcho_TextEncode`)
206
- With `low_vram` the Gemma encoder used to encode every shot on CPU (~10s+ per
207
- shot). The encode pass now borrows the (idle) GPU when the encoder fits free
208
- VRAM - with a fits-check, an OOM fallback to CPU, and a move-back before the
209
- denoise phase. 20-shot encodes drop from minutes to seconds. (`nodes.py`)
210
-
211
- ### 13. `encoder_fp8` (`JoyEcho_ModelLoader`)
212
- Stores the Gemma encoder's linear weights as float8_e4m3fn with per-layer
213
- upcast at encode (encode runs once per item, so the upcast tax that makes
214
- fp8 slow on the DiT is irrelevant here). Wrapper drops ~24GB -> ~21GB and the
215
- GPU hot-swap engages on 32GB cards; JD's connector projections stay bf16.
216
-
217
- ### 14. `fp8_scaled_mm` (`JoyEcho_ModelLoader`) - native fp8 compute
218
- Stores the DiT's attention/FF linears as fp8 AND runs the matmuls natively
219
- via `torch._scaled_mm` - no per-layer upcast tax (measured x2.8 raw kernel /
220
- x1.5 end-to-end vs bf16 on an RTX 5090). ~22GB resident enables
221
- `sequential_offload=False` at moderate resolutions. REQUIREMENTS: sm_89+
222
- GPU (RTX 40/50 - clear error on older cards, with a per-device runtime
223
- fallback to upcast), and a **bf16 source checkpoint** (an fp8 FILE would load
224
- every tensor fp8 with the cast skipped and crash the noise path - guarded
225
- with a clear error). Tensorwise dynamic activation quant: A/B your content
226
- before adopting.
227
-
228
- ### 15. `resident_blocks` (`JoyEcho_Generate`)
229
- Sequential offload middle ground: pin the first N of 48 transformer blocks
230
- permanently on GPU, stream the rest. N=24 halves the per-step PCIe traffic;
231
- raise until VRAM is nearly full. Composes with fp8 modes (fp8 blocks are
232
- half the bytes both resident and streamed).
233
-
234
- ### 16. Hires-fix second pass (`JoyEcho_Generate`)
235
- `hires_factor` (>1.0) + `hires_denoise`: after all shots render, each shot is
236
- bicubic-upscaled, VAE re-encoded, re-noised at a tail sigma and re-denoised
237
- through the DMD ladder at the TARGET resolution - the model synthesizes real
238
- detail (RTX-class upscalers only sharpen what exists). Runs in 65-frame
239
- windows with cross-fade (a 24GB card survives 1920x1088 refines); memory
240
- bank and per-shot previews stay base-res; failures fall back to the base
241
- frames. Audio is untouched.
242
-
243
- ### 17. Reference scheduling upgrades (`JoyEcho_RefPicker` + `_Generate`)
244
- - Script-carried ref pinning: `{"prompts": [...], "refs": {"zara":
245
- "zara_file.png"}}` pins a scene-matched reference per character (a
246
- full-scene ref SETS the render's scene - match it to the script).
247
- - Re-entry injection: a character returning after a 3+-shot absence gets
248
- their ref re-injected at the return shot automatically (the rolling memory
249
- window is 4; long absences otherwise re-invent the character).
250
- - Generate's ref dedup is schedule-aware (the same image scheduled at two
251
- shots survives; cap 6 scheduled entries).
252
-
253
- ### 18. Robustness
254
- - Pipelines no longer derive their working dtype from
255
- `next(parameters()).dtype` (an fp8 first-param crashed `torch.randn`);
256
- fp8 dtypes are skipped with a bfloat16 fallback.
257
- - fp8 gemma swap accepts both `.scale_weight` and `.weight_scale` layouts
258
- and warns loudly on zero matches instead of silently staying bf16.
259
-
260
- ### 19. Finishing: AutoFinish is hires-aware
261
- - The background AutoFinish worker builds the true MASTER after each run
262
- (per-shot masters concatenated stream-copy, VHS glitch at boundaries, one
263
- clean mux). The in-graph SaveVideo output is a convenience preview - its
264
- re-encode is bit-starved and can show faint per-frame detail pumping;
265
- always publish the MASTER.
266
- - **When `hires_factor` > 1.0, the worker detects the run's `shot_hires_*`
267
- masters and finishes from them directly - the RTX upscale stage is skipped
268
- automatically.** The generative hires refine synthesizes real detail
269
- (pores, hair strands, fabric weave) that a spatial upscaler cannot, so it
270
- replaces RTX as the quality path when enabled.
271
- - The RTX path is still there for anyone who wants it: run with
272
- `hires_factor = 1.0` and the worker upscales the base shots via
273
- RTXBatchVideoUpscale exactly as before (an incomplete hires pass also
274
- falls back to RTX). The AutoFinish node ships bypassed in the workflow -
275
- enable it to get automatic masters.
276
- - The glitch pass re-encodes with `bf 0` + `tune grain` (same recipe as the
277
- per-shot saves) so the master never reintroduces B-frame pumping.
278
-
279
- ---
280
-
281
- ## Applying
282
-
283
- 1. Back up your existing pack folder.
284
- 2. Copy each file over the same relative path in
285
- `ComfyUI/custom_nodes/ComfyUI_JoyAI_Echo_GGUF_Nodes/`.
286
- 3. Restart ComfyUI. New widgets append at the **end** of existing nodes, so
287
- saved graphs keep their values; the four new nodes appear under the
288
- `JoyAI-Echo` category. Press `R` after adding model files to refresh the
289
- `model_file` dropdown.
290
-
291
- The `libs/` files must match the vendored `ltx_core` / `ltx_distillation` in
292
- your pack (same JoyAI-Echo release). If your `libs/` differ substantially,
293
- cherry-pick the changes described above rather than overwriting.
294
-
295
- Not included (intentionally): model weights, the `gemma_assets/` tokenizer
296
- binaries, `.bak` snapshots, and `__pycache__`.
297
-
298
- ---
299
-
300
- ## Credits
301
-
302
- This patch stands on other people's work:
303
-
304
- - **JD Joy Future Academy** - [JoyAI-Echo](https://huggingface.co/jdopensource/JoyAI-Echo), the multishot memory model this whole stack serves (research / non-commercial license).
305
- - **Lightricks** - [LTX-2 / LTX-2.3](https://huggingface.co/Lightricks/LTX-2.3) (LTX-2 Community License).
306
- - **TenStrip** - the [LTX2.3 DMD LoRAs](https://huggingface.co/TenStrip/LTX2.3_DMD_Lora); the hires `strong (tenstrip 4-step)` mode uses his published upscale sigma ladder verbatim.
307
- - **RealRebelAI** - the Rebels GGUF loader stack this patches, and the [Q6_K_RM GGUF](https://huggingface.co/realrebelai/JoyAI-Echo_GGUF) whose tensor canon the self-built GGUFs mirror.
308
- - **Comfy-Org** - the comfy-quants `int8_tensorwise` + ConvRot export recipe behind the INT8 checkpoint.
309
- - **Google** - Gemma 3 12B, the text encoder (Gemma license).
 
 
 
 
 
 
 
1
+ ---
2
+ license: other
3
+ license_name: ltx-2-community-license
4
+ license_link: https://huggingface.co/Lightricks/LTX-2/blob/main/LICENSE.txt
5
+ tags:
6
+ - comfyui
7
+ - joyai-echo
8
+ - ltx-video
9
+ - multishot
10
+ - patch
11
+ ---
12
+
13
+ # JoyAI-Echo GGUF nodes — multishot fixes + automation patch
14
+
15
+ A set of bug fixes and features layered on top of the community
16
+ `ComfyUI_JoyAI_Echo_GGUF_Nodes` pack (the Rebels GGUF loader stack around
17
+ JoyAI-Echo). Everything here targets the **multi-shot** path (`JoyEcho_Generate`
18
+ + the discrete Rebels loaders / `JoyEcho_ModelLoader`).
19
+
20
+ This is a **patch drop**, not a standalone pack: copy these files over a working
21
+ install of the same pack (back up first). The files are interdependent — in
22
+ particular `nodes.py` calls new signatures added to the two `libs/` files, so
23
+ apply them together.
24
+
25
+ Tested on an RTX 5090 (32 GB) and a 3090 (24 GB), ComfyUI 0.26–0.27,
26
+ torch 2.8–2.11, with the JoyAI-Echo bf16 release and self-built Q8 GGUFs.
27
+
28
+ ---
29
+
30
+ ## Files in this package
31
+
32
+ ```
33
+ nodes.py # JoyEcho_TextEncode / _Generate / _ModelLoader / _LLMEnhance
34
+ __init__.py # registrations for the new nodes
35
+ rebels_loaders.py # discrete GGUF loaders (text-encoder fixes)
36
+ joyecho_prompt_source.py (new node) # one dropdown: .txt briefs + .json scripts
37
+ joyecho_ref_picker.py (new node) # auto reference-image picker by character name
38
+ joyecho_ref_batch.py (new node) # None-tolerant image batcher
39
+ joyecho_script_picker.py (new node) # JSON dropdown (superseded by PromptSource)
40
+ libs/ltx_distillation/models/ltx_wrapper.py # fp8 quantization passthrough
41
+ libs/ltx_core/loader/fuse_loras.py # kohya-LoRA fusion + alpha scaling + fuse telemetry
42
+ libs/ltx_core/quantization/policy.py # fp8_scaled_mm_torch policy (+ sm_89 gate)
43
+ libs/ltx_core/quantization/fp8_torch_mm.py # native-fp8 Linear forward (torch._scaled_mm)
44
+ libs/ltx_distillation/utils.py # tiled VAE decode
45
+ libs/ltx_distillation/inference/memory_multishot.py # memory bank TRIM FIX (critical)
46
+ libs/ltx_distillation/inference/bidirectional_pipeline.py # dtype hardening vs fp8 params
47
+ libs/ltx_distillation/inference/memory_bidirectional_pipeline.py # dtype hardening vs fp8 params
48
+ prompts/long_story_writer_system_prompt.md # (optional) de-musicked + character-age edits
49
+ ```
50
+
51
+ The files are interdependent - apply the whole set together, never cherry-pick
52
+ (a nodes.py newer than its libs/ raises AttributeError at load).
53
+
54
+ ---
55
+
56
+ ## Bug fixes
57
+
58
+ ### 1. `enable_audio_memory=False` silently disabled ALL cross-shot memory
59
+ The pack computed `audio_memory_latent=None` when audio memory was off, and the
60
+ video **memory-bank save was gated on that latent being non-None** — so with
61
+ audio memory off (the standard anti-drone setting) the bank never filled and
62
+ cross-shot **identity** silently died (symptom: `memory_size=0` every shot even
63
+ with `memory_max_size=7`; a new face each shot).
64
+ Fix: memory storage is now unconditional; `enable_audio_memory` gates only the
65
+ audio-memory **injection** path. Verify: console `memory_size=` should climb
66
+ 0,1,2,… capped at your `memory_max_size`. (`nodes.py`)
67
+
68
+ ### 1b. Memory bank trim was a NO-OP whenever `memory_max_size <= num_fix_frames` (CRITICAL)
69
+ `PairedAudioVideoMemoryBank._trim()` computed `tail[-keep_tail:]` - and when
70
+ `keep_tail == 0` (e.g. the common max_size=3 / num_fix_frames=3 combo),
71
+ `tail[-0:]` is the WHOLE list, so the bank grew unbounded: every shot
72
+ conditioned on EVERY prior shot. Symptom: console `memory_size=` climbing
73
+ 0,1,2,...,N-1 past your cap, and severe compounding quality degradation over
74
+ long runs (waxy skin, contrast crush, smearing by the late shots - the "gets
75
+ worse as it goes" failure). Fixed with a proper zero-tail branch + anchor
76
+ clamp; `memory_size=` now freezes at your cap. This one fix eliminated the entire
77
+ long-run degradation in our tests. (`libs/.../memory_multishot.py`)
78
+
79
+ ### 2. GGUF text-encoder loader (`RebelsJE_TextEncoder`)
80
+ Two fixes so a text-only Gemma-3 GGUF loads cleanly:
81
+ - **meta-strip**: drop `vision_tower` / `multi_modal_projector` / `lm_head`
82
+ (the text-only GGUF has no weights for them → "Cannot copy out of meta tensor").
83
+ - **device-unify**: pin the embeddings-processor to the encoder's actual device
84
+ (GGUF Gemma runs on CPU while the connector was on cuda → addmm device mismatch).
85
+ - **fp8 gemma scale-key layouts**: the `our_fp8` swap only recognized its own
86
+ export layout (bare module names + `.scale_weight`); standard HF/comfy-style
87
+ fp8 gemma files (`<module>.weight` + `.weight_scale`, e.g. community
88
+ abliterated builds) silently loaded with **zero modules swapped** — the
89
+ encoder stayed bf16 with no indication. Both layouts are now accepted
90
+ (per-tensor scalar scales; per-channel scales are skipped and those modules
91
+ stay bf16), and a loud warning prints if a file matches neither.
92
+ (`rebels_loaders.py`)
93
+
94
+ ---
95
+
96
+ ## Features
97
+
98
+ ### 3. Split per-domain negative lever (`JoyEcho_TextEncode`)
99
+ The DMD pipeline has no CFG, so the only steering lever is embedding-space.
100
+ Instead of one `negative_prompt`/`negative_scale` that steers both branches,
101
+ this splits it:
102
+ - `negative_prompt_video` / `negative_scale_video` — kills burned-in
103
+ captions/subtitles. Working value ~0.5. **Above ~0.8 it over-rotates the
104
+ video context and locks every shot to shot 1's composition** (scene-lock).
105
+ - `negative_prompt_audio` / `negative_scale_audio` — kills invented
106
+ music/score. Keep ≤ ~0.4 or dialogue suffers.
107
+ Steering is norm-preserving (RescaleCFG-style): `cond' = renorm(cond + s*(cond − neg))`.
108
+ Old single-widget names still work as a fallback. (`nodes.py`)
109
+
110
+ ### 4. Passthrough mode (`JoyEcho_LLMEnhance`)
111
+ `mode = "passthrough (raw JSON, skip LLM)"` — feed a finished
112
+ `{"prompts":[...]}` script straight through with no LLM call / no API key.
113
+ Auto-detects when `story_idea` already parses as that JSON. (`nodes.py`)
114
+
115
+ ### 5. Reference-image conditioning — I2V-as-reference (`JoyEcho_Generate`)
116
+ New `reference_image` (IMAGE batch, up to 4). Identity references are prepended
117
+ as **video-only conditioning clips** at the memory-encode step — they are
118
+ **never** written into the paired audio/video bank. (An earlier attempt that
119
+ seeded refs into the bank with zero-filled audio latents injected loud
120
+ background noise with 2+ refs; video-only conditioning avoids it entirely.)
121
+ Also new: `head_trim_frames` (auto 8 with refs) drops the first N frames of each
122
+ shot, where the model morphs out of the reference/memory content. The trim is
123
+ applied once right after decode, so the final output, the per-shot preview
124
+ files, and any external concat of them stay frame-identical. (`nodes.py`)
125
+
126
+ ### 6. Shot transitions (`JoyEcho_Generate`)
127
+ `transition`: `cut` (original) / `dissolve` (overlap cross-dissolve + equal-power
128
+ audio crossfade) / `vhs_glitch` (analog static burst at each boundary: snow,
129
+ tear bands, dropout lines + a raised-cosine tape-noise audio bed).
130
+ `transition_frames`, `glitch_intensity` tune it. (`nodes.py`)
131
+
132
+ ### 7. fp8 transformer quantization (`JoyEcho_ModelLoader`)
133
+ New `fp8_transformer` toggle. Quantizes the DiT's attention/FF linear weights to
134
+ `float8_e4m3fn` **at load, from the normal bf16 checkpoint** (uses the vendored
135
+ `ltx_core.quantization.QuantizationPolicy.fp8_cast()` — upcasts per-layer at
136
+ inference). Roughly halves DiT weight memory and halves sequential-offload PCIe
137
+ traffic; keeps memory training + all tensors; VAEs/text-encoder/non-linears stay
138
+ bf16. Ignored when a GGUF DiT is selected (already quantized).
139
+ (`nodes.py` + `libs/ltx_distillation/models/ltx_wrapper.py` — new `quantization`
140
+ param; the quantized build path skips the post-load dtype cast that would
141
+ otherwise silently upcast fp8 back to bf16.)
142
+
143
+ ### 8. Tiled VAE decode (`JoyEcho_Generate`)
144
+ Decoding a long high-res shot (e.g. 241f @ 1280×736) in one pass hard-aborts the
145
+ VAE decode on a 24–32 GB card (fatal cuDNN abort mid-conv, not a catchable OOM).
146
+ New `decode_tiling` (`auto`/`on`/`off`) routes decode through the vendored
147
+ `VideoDecoder.tiled_decode` — **temporal-only** 64-frame chunks with 24-frame
148
+ blended overlap (no spatial tiles → no spatial seams), streaming each chunk to
149
+ CPU. `auto` engages only above a size threshold, so small renders keep the
150
+ original single-pass decode bit-for-bit.
151
+ (`nodes.py` + `libs/ltx_distillation/utils.py` — `decode_benchmark_sample` gains
152
+ a `video_tiling_config` kwarg + `_decode_video_tiled_uint8`.)
153
+
154
+ ### 9. Model dropdown (`JoyEcho_ModelLoader`)
155
+ New `model_file` combo lists every `.safetensors` / `.gguf` under the ComfyUI
156
+ `checkpoints` / `diffusion_models` / `unet` dirs. Pick a `.safetensors` → full
157
+ checkpoint (replaces `checkpoint_path`); pick a `.gguf` → DiT loaded from GGUF
158
+ while `checkpoint_path` still supplies the VAEs / vocoder / text connectors.
159
+ `"(use checkpoint_path)"` keeps the old typed-path behavior. A matching
160
+ `lora_file` dropdown lists every `.safetensors` under `models/loras`
161
+ (applied at `lora_strength` on the safetensors DiT path; ignored for GGUF). Plus a clear
162
+ early error if `gemma_path` is a `.gguf`/file/sidecar-less dir (this loader
163
+ needs the HF `gemma-3-12b-it` folder; GGUF Gemma only works via
164
+ `RebelsJE_TextEncoder`). (`nodes.py`)
165
+
166
+ ### 10. LoRA loading hardening (`JoyEcho_ModelLoader` + `libs/.../fuse_loras.py`)
167
+ - A `lora_file` dropdown picks LoRAs from `models/loras` (existing
168
+ `lora_strength` widget applies).
169
+ - Fusion now supports **kohya naming** (`lora_down`/`lora_up`) in addition to
170
+ PEFT (`lora_A`/`lora_B`), with standard `alpha/rank` scaling — previously a
171
+ kohya-named LoRA silently did NOTHING (zero keys matched, no warning).
172
+ - Fusion prints how many weights fused, and WARNS LOUDLY when a provided LoRA
173
+ matched zero keys.
174
+ - The loader refuses **ComfyUI-quantized checkpoints** (`.comfy_quant` marker
175
+ tensors, e.g. "fp8mixed learned" builds) with a clear error: this loader
176
+ never applies their weight scales (the model would silently load mis-scaled)
177
+ and LoRA fusion on them crashes with shape errors. Use bf16 checkpoints.
178
+
179
+ ### 11. Automation / batching nodes (new)
180
+ - **`JoyEcho_PromptSource`** — one dropdown listing LPFF-style `.txt` briefs
181
+ (from the inspire-pack prompts tree) **and** passthrough `.json` scripts
182
+ (`input/joyecho_prompts/`). Multi-block briefs fan out like
183
+ LoadPromptsFromFile. Emits `story_idea` (→ LLMEnhance) + `character`
184
+ (→ RefPicker) + `count`. Replaces the LPFF→UnzipPrompt chain and lets you
185
+ switch prompt sources with one dropdown instead of rewiring.
186
+ - **`JoyEcho_RefPicker`** — auto-selects a character reference image from a
187
+ folder tree keyed by character name (a `character_pick` dropdown of the
188
+ folder names, a typed/wired `character` string, or a prompt scan — dialogue
189
+ mentions are stripped so only the on-screen subject wins). The dropdown
190
+ survives model refreshes, an explicitly named character that matches no
191
+ folder refuses to fall back to the prompt scan (a wiped/typo'd name can't
192
+ silently become the wrong character's face), and the cache signature
193
+ includes the prompt text (without it, ComfyUI could serve a cached pick
194
+ from a previous queue item). `on_no_match=no_reference` returns nothing so
195
+ a batch keeps running.
196
+ - **`JoyEcho_RefBatch`** — None-tolerant image batcher: combines up to 4
197
+ optional IMAGE inputs (e.g. two RefPickers for a two-character shot), skips
198
+ missing refs, resizes mismatched sizes to the first image, outputs `None` if
199
+ all are missing (Generate then just skips identity seeding). The stock KJNodes
200
+ `ImageBatchMulti` crashes with `'NoneType' has no attribute 'shape'` on a
201
+ missing ref; this replaces it.
202
+ - **`JoyEcho_ScriptPicker`** — JSON dropdown (superseded by PromptSource; kept
203
+ for compatibility).
204
+
205
+ ### 12. GPU encode hot-swap (`JoyEcho_TextEncode`)
206
+ With `low_vram` the Gemma encoder used to encode every shot on CPU (~10s+ per
207
+ shot). The encode pass now borrows the (idle) GPU when the encoder fits free
208
+ VRAM - with a fits-check, an OOM fallback to CPU, and a move-back before the
209
+ denoise phase. 20-shot encodes drop from minutes to seconds. (`nodes.py`)
210
+
211
+ ### 13. `encoder_fp8` (`JoyEcho_ModelLoader`)
212
+ Stores the Gemma encoder's linear weights as float8_e4m3fn with per-layer
213
+ upcast at encode (encode runs once per item, so the upcast tax that makes
214
+ fp8 slow on the DiT is irrelevant here). Wrapper drops ~24GB -> ~21GB and the
215
+ GPU hot-swap engages on 32GB cards; JD's connector projections stay bf16.
216
+
217
+ ### 14. `fp8_scaled_mm` (`JoyEcho_ModelLoader`) - native fp8 compute
218
+ Stores the DiT's attention/FF linears as fp8 AND runs the matmuls natively
219
+ via `torch._scaled_mm` - no per-layer upcast tax (measured x2.8 raw kernel /
220
+ x1.5 end-to-end vs bf16 on an RTX 5090). ~22GB resident enables
221
+ `sequential_offload=False` at moderate resolutions. REQUIREMENTS: sm_89+
222
+ GPU (RTX 40/50 - clear error on older cards, with a per-device runtime
223
+ fallback to upcast), and a **bf16 source checkpoint** (an fp8 FILE would load
224
+ every tensor fp8 with the cast skipped and crash the noise path - guarded
225
+ with a clear error). Tensorwise dynamic activation quant: A/B your content
226
+ before adopting.
227
+
228
+ ### 15. `resident_blocks` (`JoyEcho_Generate`)
229
+ Sequential offload middle ground: pin the first N of 48 transformer blocks
230
+ permanently on GPU, stream the rest. N=24 halves the per-step PCIe traffic;
231
+ raise until VRAM is nearly full. Composes with fp8 modes (fp8 blocks are
232
+ half the bytes both resident and streamed).
233
+
234
+ ### 16. Hires-fix second pass (`JoyEcho_Generate`)
235
+ `hires_factor` (>1.0) + `hires_denoise`: after all shots render, each shot is
236
+ bicubic-upscaled, VAE re-encoded, re-noised at a tail sigma and re-denoised
237
+ through the DMD ladder at the TARGET resolution - the model synthesizes real
238
+ detail (RTX-class upscalers only sharpen what exists). Runs in 65-frame
239
+ windows with cross-fade (a 24GB card survives 1920x1088 refines); memory
240
+ bank and per-shot previews stay base-res; failures fall back to the base
241
+ frames. Audio is untouched.
242
+
243
+ ### 17. Reference scheduling upgrades (`JoyEcho_RefPicker` + `_Generate`)
244
+ - Script-carried ref pinning: `{"prompts": [...], "refs": {"zara":
245
+ "zara_file.png"}}` pins a scene-matched reference per character (a
246
+ full-scene ref SETS the render's scene - match it to the script).
247
+ - Re-entry injection: a character returning after a 3+-shot absence gets
248
+ their ref re-injected at the return shot automatically (the rolling memory
249
+ window is 4; long absences otherwise re-invent the character).
250
+ - Generate's ref dedup is schedule-aware (the same image scheduled at two
251
+ shots survives; cap 6 scheduled entries).
252
+
253
+ ### 18. Robustness
254
+ - Pipelines no longer derive their working dtype from
255
+ `next(parameters()).dtype` (an fp8 first-param crashed `torch.randn`);
256
+ fp8 dtypes are skipped with a bfloat16 fallback.
257
+ - fp8 gemma swap accepts both `.scale_weight` and `.weight_scale` layouts
258
+ and warns loudly on zero matches instead of silently staying bf16.
259
+
260
+ ### 19. Finishing: who builds your master (READ THIS before touching hires)
261
+ `hires_factor` is a ROUTING switch, not a quality slider - it decides which
262
+ pipeline builds your final video:
263
+
264
+ | you want | hires_factor | hires_denoise | master comes from |
265
+ |---|---|---|---|
266
+ | **default: zero detail-shimmer** | **1.0** | (ignored) | base shots, upscaled by AutoFinish (bicubic + contrast-adaptive sharpen - deterministic, seconds per shot) |
267
+ | synthesized detail (pores/hair), accepts slight per-frame texture shimmer | 1.5 | subtle / medium / strong | your refined shots, used as-is (AutoFinish skips its own upscale) |
268
+ | deterministic upscale baked into the shot files | 1.5 | spatial | spatial-upscaled shots - EVEN latent grids only (height AND width /32 must be even: 768-height yes, 736 no) |
269
+ | the old RTX path | 1.0 | (ignored) | base shots via RTXBatchVideoUpscale (`upscale_mode: rtx (legacy)` on the AutoFinish node) |
270
+
271
+ - **Judge and publish only the `*_MASTER.mp4`.** The in-graph SaveVideo
272
+ output (prefixed `PREVIEW` in the shipped workflow) is a convenience
273
+ preview: its re-encode is bit-starved and shows artifacts the master does
274
+ not have. This is a ComfyUI limitation, not a render problem.
275
+ - Resolutions: everything works at any /32 size; only the `spatial` hires
276
+ mode additionally needs the /32 result EVEN on both axes (1280x768 and
277
+ 1344x768 qualify; 1280x736 and 1536x864 do not - spatial will smear one
278
+ edge and warn in the console).
279
+ - All other widgets are genuinely free: seed, num_frames (long shots are
280
+ fine - the old ~10s lip-sync limit was the fps bug, now fixed), fps,
281
+ memory sizes, head_trim.
282
+ - The master pipeline re-encodes with `bf 0` + `tune grain` end to end, so
283
+ masters never reintroduce B-frame pumping.
284
+
285
+ ---
286
+
287
+ ## Applying
288
+
289
+ 1. Back up your existing pack folder.
290
+ 2. Copy each file over the same relative path in
291
+ `ComfyUI/custom_nodes/ComfyUI_JoyAI_Echo_GGUF_Nodes/`.
292
+ 3. Restart ComfyUI. New widgets append at the **end** of existing nodes, so
293
+ saved graphs keep their values; the four new nodes appear under the
294
+ `JoyAI-Echo` category. Press `R` after adding model files to refresh the
295
+ `model_file` dropdown.
296
+
297
+ The `libs/` files must match the vendored `ltx_core` / `ltx_distillation` in
298
+ your pack (same JoyAI-Echo release). If your `libs/` differ substantially,
299
+ cherry-pick the changes described above rather than overwriting.
300
+
301
+ Not included (intentionally): model weights, the `gemma_assets/` tokenizer
302
+ binaries, `.bak` snapshots, and `__pycache__`.
303
+
304
+ ---
305
+
306
+ ## Credits
307
+
308
+ This patch stands on other people's work:
309
+
310
+ - **JD Joy Future Academy** - [JoyAI-Echo](https://huggingface.co/jdopensource/JoyAI-Echo), the multishot memory model this whole stack serves (research / non-commercial license).
311
+ - **Lightricks** - [LTX-2 / LTX-2.3](https://huggingface.co/Lightricks/LTX-2.3) (LTX-2 Community License).
312
+ - **TenStrip** - the [LTX2.3 DMD LoRAs](https://huggingface.co/TenStrip/LTX2.3_DMD_Lora); the hires `strong (tenstrip 4-step)` mode uses his published upscale sigma ladder verbatim.
313
+ - **RealRebelAI** - the Rebels GGUF loader stack this patches, and the [Q6_K_RM GGUF](https://huggingface.co/realrebelai/JoyAI-Echo_GGUF) whose tensor canon the self-built GGUFs mirror.
314
+ - **Comfy-Org** - the comfy-quants `int8_tensorwise` + ConvRot export recipe behind the INT8 checkpoint.
315
+ - **Google** - Gemma 3 12B, the text encoder (Gemma license).
nodes.py CHANGED
@@ -1199,13 +1199,17 @@ class JoyEcho_Generate:
1199
  }),
1200
  "hires_factor": ("FLOAT", {
1201
  "default": 1.0, "min": 1.0, "max": 2.0, "step": 0.05,
1202
- "tooltip": "Hires-fix second pass: after all shots render, upscale each "
1203
- "shot (bicubic), re-encode, and re-denoise the tail of the DMD "
1204
- "ladder at the higher resolution - the model SYNTHESIZES real "
1205
- "detail (unlike RTX, which only sharpens what exists). 1.0 = "
1206
- "off. Costs roughly one extra denoise step per shot at the "
1207
- "target res, in 65-frame windows (3090-safe). Memory bank and "
1208
- "per-shot previews stay at base res.",
 
 
 
 
1209
  }),
1210
  "hires_denoise": (["subtle (1 step)", "medium (2 steps)",
1211
  "strong (tenstrip 4-step)",
 
1199
  }),
1200
  "hires_factor": ("FLOAT", {
1201
  "default": 1.0, "min": 1.0, "max": 2.0, "step": 0.05,
1202
+ "tooltip": "ROUTING switch, not a quality slider - it decides who builds "
1203
+ "your master. 1.0 (RECOMMENDED): shots stay base-res and the "
1204
+ "AutoFinish worker upscales them deterministically "
1205
+ "(bicubic+CAS) - zero per-frame detail shimmer. Above 1.0: the "
1206
+ "selected hires_denoise pass runs in-render and the master is "
1207
+ "built from ITS output instead (AutoFinish detects the hires "
1208
+ "shots and skips its own upscale). Only raise this when you "
1209
+ "deliberately want the refine modes' synthesized detail "
1210
+ "(slight texture shimmer) or the spatial mode (even /32 dims "
1211
+ "only, e.g. 768-height). Judge results from the *_MASTER.mp4 "
1212
+ "file, never the in-canvas preview.",
1213
  }),
1214
  "hires_denoise": (["subtle (1 step)", "medium (2 steps)",
1215
  "strong (tenstrip 4-step)",
workflow/JoyEcho_Multishot_Workflow_PUBLIC.json CHANGED
@@ -193,232 +193,11 @@
193
  }
194
  },
195
  "widgets_values": [
196
- "joyecho/output",
197
  "auto",
198
  "auto"
199
  ]
200
  },
201
- {
202
- "id": 6,
203
- "type": "CreateVideo",
204
- "pos": [
205
- 4328.590366291457,
206
- 359.91012828201144
207
- ],
208
- "size": [
209
- 301.3775449810605,
210
- 102
211
- ],
212
- "flags": {},
213
- "order": 14,
214
- "mode": 0,
215
- "inputs": [
216
- {
217
- "localized_name": "images",
218
- "name": "images",
219
- "type": "IMAGE",
220
- "link": 113
221
- },
222
- {
223
- "localized_name": "audio",
224
- "name": "audio",
225
- "shape": 7,
226
- "type": "AUDIO",
227
- "link": 108
228
- },
229
- {
230
- "localized_name": "fps",
231
- "name": "fps",
232
- "type": "FLOAT",
233
- "widget": {
234
- "name": "fps"
235
- },
236
- "link": null
237
- },
238
- {
239
- "localized_name": "bit_depth",
240
- "name": "bit_depth",
241
- "shape": 7,
242
- "type": "INT",
243
- "widget": {
244
- "name": "bit_depth"
245
- },
246
- "link": null
247
- }
248
- ],
249
- "outputs": [
250
- {
251
- "localized_name": "VIDEO",
252
- "name": "VIDEO",
253
- "type": "VIDEO",
254
- "slot_index": 0,
255
- "links": [
256
- 10
257
- ]
258
- }
259
- ],
260
- "title": "Create Video (Merge All Shots)",
261
- "properties": {
262
- "cnr_id": "comfy-core",
263
- "ver": "0.26.2",
264
- "Node name for S&R": "CreateVideo",
265
- "ue_properties": {
266
- "widget_ue_connectable": {},
267
- "version": "7.8",
268
- "input_ue_unconnectable": {}
269
- }
270
- },
271
- "widgets_values": [
272
- 25,
273
- 8
274
- ]
275
- },
276
- {
277
- "id": 59,
278
- "type": "JoyEcho_LLMEnhance",
279
- "pos": [
280
- 900,
281
- 100
282
- ],
283
- "size": [
284
- 1161.0279043079718,
285
- 736.9512031915835
286
- ],
287
- "flags": {},
288
- "order": 8,
289
- "mode": 0,
290
- "inputs": [
291
- {
292
- "localized_name": "story_idea",
293
- "name": "story_idea",
294
- "type": "STRING",
295
- "widget": {
296
- "name": "story_idea"
297
- },
298
- "link": 114
299
- },
300
- {
301
- "localized_name": "mode",
302
- "name": "mode",
303
- "type": "COMBO",
304
- "widget": {
305
- "name": "mode"
306
- },
307
- "link": null
308
- },
309
- {
310
- "localized_name": "api_key",
311
- "name": "api_key",
312
- "type": "STRING",
313
- "widget": {
314
- "name": "api_key"
315
- },
316
- "link": null
317
- },
318
- {
319
- "localized_name": "system_prompt",
320
- "name": "system_prompt",
321
- "type": "STRING",
322
- "widget": {
323
- "name": "system_prompt"
324
- },
325
- "link": null
326
- },
327
- {
328
- "localized_name": "base_url",
329
- "name": "base_url",
330
- "shape": 7,
331
- "type": "STRING",
332
- "widget": {
333
- "name": "base_url"
334
- },
335
- "link": null
336
- },
337
- {
338
- "localized_name": "model_name",
339
- "name": "model_name",
340
- "shape": 7,
341
- "type": "STRING",
342
- "widget": {
343
- "name": "model_name"
344
- },
345
- "link": null
346
- },
347
- {
348
- "localized_name": "num_shots",
349
- "name": "num_shots",
350
- "shape": 7,
351
- "type": "INT",
352
- "widget": {
353
- "name": "num_shots"
354
- },
355
- "link": null
356
- },
357
- {
358
- "localized_name": "temperature",
359
- "name": "temperature",
360
- "shape": 7,
361
- "type": "FLOAT",
362
- "widget": {
363
- "name": "temperature"
364
- },
365
- "link": null
366
- },
367
- {
368
- "localized_name": "num_frames",
369
- "name": "num_frames",
370
- "shape": 7,
371
- "type": "INT",
372
- "widget": {
373
- "name": "num_frames"
374
- },
375
- "link": null
376
- },
377
- {
378
- "localized_name": "fps",
379
- "name": "fps",
380
- "shape": 7,
381
- "type": "FLOAT",
382
- "widget": {
383
- "name": "fps"
384
- },
385
- "link": null
386
- }
387
- ],
388
- "outputs": [
389
- {
390
- "localized_name": "prompts_json",
391
- "name": "prompts_json",
392
- "type": "STRING",
393
- "links": [
394
- 115,
395
- 116
396
- ]
397
- }
398
- ],
399
- "properties": {
400
- "aux_id": "RealRebelAI/ComfyUI_JoyAI_Echo_GGUF_Nodes",
401
- "ver": "0d9e32fcd2d255a16604368fcaa1875c380c7f3b",
402
- "Node name for S&R": "JoyEcho_LLMEnhance",
403
- "ue_properties": {
404
- "widget_ue_connectable": {},
405
- "input_ue_unconnectable": {},
406
- "version": "7.8"
407
- }
408
- },
409
- "widgets_values": [
410
- "",
411
- "long_story (multi-shot)",
412
- "",
413
- "",
414
- "http://localhost:11434/v1",
415
- "glm-5.2:cloud",
416
- 6,
417
- 0.7,
418
- 321,
419
- 25
420
- ]
421
- },
422
  {
423
  "id": 65,
424
  "type": "JoyEcho_LoraStacker",
@@ -723,7 +502,7 @@
723
  },
724
  "widgets_values": [
725
  "by_seed",
726
- 1696602241,
727
  "randomize",
728
  "joyecho_refs",
729
  "no_reference",
@@ -802,7 +581,7 @@
802
  }
803
  },
804
  "widgets_values": [
805
- "{\n \"prompts\": [\n \"Consumer camcorder video, 1080p, modest dynamic range, saturated color, slight edge softness: a locked static medium close-up at her eye level on the small corner stage of an empty dive bar, hot magenta and blue stage lights raking across her from the rig above, a scuffed drum kit and a taped-up amp behind her, black walls layered with faded gig flyers, her head and shoulders filling most of the frame and her mouth fully visible. Quiet diegetic sound only: the low buzz of the stage lighting rig, a beer cooler humming off in the dark, and her voice close and dry on the camcorder mic. ID_A is a woman in her early twenties with a bleached platinum pixie cut with choppy short bangs, a small silver septum ring, a row of small silver hoops up one ear, sharp winged black eyeliner, wearing a battered black leather jacket covered in enamel band pins and safety pins over a ripped neon-pink band tee. ID_A's voice is a bright, quick young American voice with a playful edge, casual American accent, casual contractions, captured with close camcorder-microphone proximity. ID_A's mouth stays clearly visible in the frame, and the mouth movement stays naturally synchronized with every spoken line. ID_A is the only person speaking in this shot, the source of the live camcorder dialogue. One continuous line with natural breaths and brief pauses; ID_A's first mouth movement begins with the first audible word, and the mouth settles naturally after the final word. At a quick, natural speed, she stands still at the mic stand without touching it, looking into the lens, the lip movement aligning closely with the audio throughout the line. In a bright, quick voice with a playful edge, ID_A says, \\\"Alright, watch close. Five places, one me. Nothing carries this face around but one sentence, repeated like a chorus. That's the whole gig. Count the costume changes - there aren't any.\\\"\",\n \"Consumer camcorder video, 1080p, modest dynamic range, saturated color, slight edge softness: a slow steady push-in to a medium close-up at her eye level in a late-night diner booth, teal and pink neon tubing burning in the window beside her and doubling in soft color across the chrome napkin holder and the red vinyl seat, her head and shoulders filling most of the frame and her mouth fully visible, a thick white coffee mug already on the table in front of her, untouched. Quiet diegetic sound only: the neon transformer buzzing in the window, a milkshake machine whirring somewhere behind the counter, and the vinyl seat creaking once as she settles. ID_A is a woman in her early twenties with a bleached platinum pixie cut with choppy short bangs, a small silver septum ring, a row of small silver hoops up one ear, sharp winged black eyeliner, wearing a battered black leather jacket covered in enamel band pins and safety pins over a ripped neon-pink band tee. ID_A's voice is a bright, quick young American voice with a playful edge, casual American accent, casual contractions, captured with close camcorder-microphone proximity. ID_A's mouth stays clearly visible in the frame, and the mouth movement stays naturally synchronized with every spoken line. ID_A is the only person speaking in this shot, the source of the live camcorder dialogue. One continuous line with natural breaths and brief pauses; ID_A's first mouth movement begins with the first audible word, and the mouth settles naturally after the final word. At a quick, natural speed, she sits still in the booth, looking into the lens, the lip movement aligning closely with the audio throughout the line. In a bright, quick voice with a playful edge, ID_A says, \\\"New room, new light, same everything else. Same jacket, same pins, same voice with the same attitude. If I drift even a little, you'd clock it instantly. So clock it.\\\"\",\n \"Consumer camcorder video, 1080p, modest dynamic range, saturated color, slight edge softness: a locked static medium close-up at her eye level in a fluorescent-lit laundromat, a wall of mustard-yellow washing machines behind her with one drum tumbling mid-cycle, chipped mint-green floor tile, a rolling wire basket parked beside her, her head and shoulders filling most of the frame and her mouth fully visible. Quiet diegetic sound only: the tumbling dryer thumping in rhythm, coins rattling in a metal tray, and the fluorescent fixtures humming overhead. ID_A is a woman in her early twenties with a bleached platinum pixie cut with choppy short bangs, a small silver septum ring, a row of small silver hoops up one ear, sharp winged black eyeliner, wearing a battered black leather jacket covered in enamel band pins and safety pins over a ripped neon-pink band tee. ID_A's voice is a bright, quick young American voice with a playful edge, casual American accent, casual contractions, captured with close camcorder-microphone proximity. ID_A's mouth stays clearly visible in the frame, and the mouth movement stays naturally synchronized with every spoken line. ID_A is the only person speaking in this shot, the source of the live camcorder dialogue. One continuous line with natural breaths and brief pauses; ID_A's first mouth movement begins with the first audible word, and the mouth settles naturally after the final word. At a quick, natural speed, she leans one shoulder against a washer, standing still, looking into the lens, the lip movement aligning closely with the audio throughout the line. In a bright, quick voice with a playful edge, ID_A says, \\\"Third spot. Half a minute in. My voice hasn't moved an inch - same speed, same edge, same accent. The machines are louder than my identity crisis. Which is zero.\\\"\",\n \"Consumer camcorder video, 1080p, modest dynamic range, saturated color, slight edge softness: a slow lateral drift to a medium close-up at her eye level in a cramped record store aisle, wooden crates of vinyl records packed tight on both sides, hand-written orange dividers poking up from the rows, a wall of album sleeves behind her in reds and yellows, warm tube amp light from the listening corner, her head and shoulders filling most of the frame and her mouth fully visible. Quiet diegetic sound only: a record crackling quietly on the shop turntable, sleeves flipping somewhere in another aisle, and the floorboards creaking under her weight shift. ID_A is a woman in her early twenties with a bleached platinum pixie cut with choppy short bangs, a small silver septum ring, a row of small silver hoops up one ear, sharp winged black eyeliner, wearing a battered black leather jacket covered in enamel band pins and safety pins over a ripped neon-pink band tee. ID_A's voice is a bright, quick young American voice with a playful edge, casual American accent, casual contractions, captured with close camcorder-microphone proximity. ID_A's mouth stays clearly visible in the frame, and the mouth movement stays naturally synchronized with every spoken line. ID_A is the only person speaking in this shot, the source of the live camcorder dialogue. One continuous line with natural breaths and brief pauses; ID_A's first mouth movement begins with the first audible word, and the mouth settles naturally after the final word. At a quick, natural speed, she stands still between the crates, hands in her jacket pockets, looking into the lens, the lip movement aligning closely with the audio throughout the line. In a bright, quick voice with a playful edge, ID_A says, \\\"Every one of these shots rendered separately. Different room, different seed, zero shared pixels. One sentence holds me together. Honestly? Most bands can't stay this consistent.\\\"\",\n \"Consumer camcorder video, 1080p, modest dynamic range, saturated color, slight edge softness: a locked static medium close-up at her eye level on a flat gravel rooftop at dusk, the sky behind her banded deep orange to violet, strings of cafe bulbs slung between two vent stacks catching the edge of her jacket pins, the city below reduced to warm scattered windows, her head and shoulders filling most of the frame and her mouth fully visible. Quiet diegetic sound only: wind moving over the parapet, distant traffic far below, and one of the bulb strings ticking against its wire in the breeze. ID_A is a woman in her early twenties with a bleached platinum pixie cut with choppy short bangs, a small silver septum ring, a row of small silver hoops up one ear, sharp winged black eyeliner, wearing a battered black leather jacket covered in enamel band pins and safety pins over a ripped neon-pink band tee. ID_A's voice is a bright, quick young American voice with a playful edge, casual American accent, casual contractions, captured with close camcorder-microphone proximity. ID_A's mouth stays clearly visible in the frame, and the mouth movement stays naturally synchronized with every spoken line. ID_A is the only person speaking in this shot, the source of the live camcorder dialogue. One continuous line with natural breaths and brief pauses; ID_A's first mouth movement begins with the first audible word, and the mouth settles naturally after the final word. At a quick, natural speed, she stands still against the skyline, looking into the lens, the lip movement aligning closely with the audio throughout the line. In a bright, quick voice with a playful edge, ID_A says, \\\"So that's the trick - there isn't one. Same face, same voice, five rooms deep. If I'm a file, I'm at least a catchy one. See you around.\\\"\"\n ]\n}"
806
  ]
807
  },
808
  {
@@ -834,35 +613,6 @@
834
  "color": "#432",
835
  "bgcolor": "#653"
836
  },
837
- {
838
- "id": 60,
839
- "type": "Note",
840
- "pos": [
841
- 2780.2351442980976,
842
- 806.7950609883953
843
- ],
844
- "size": [
845
- 1057.751230034948,
846
- 248.99507986020944
847
- ],
848
- "flags": {},
849
- "order": 4,
850
- "mode": 0,
851
- "inputs": [],
852
- "outputs": [],
853
- "properties": {
854
- "ue_properties": {
855
- "widget_ue_connectable": {},
856
- "version": "7.8",
857
- "input_ue_unconnectable": {}
858
- }
859
- },
860
- "widgets_values": [
861
- "MODEL LOADER - what to set, what to leave\n------------------------------------------------\nSET THESE TWO:\n* model_file - the DiT checkpoint. fp8 build (23 GB) for 24 GB cards; bf16 (43 GB) for 32 GB+ (needs sequential offload).\n* gemma_file - your Gemma-3-12B text encoder, picked from models/text_encoders or models/clip. Single-file .safetensors or .gguf. A GGUF encoder is the light option (fits 24 GB systems easily).\n\nLEAVE ALONE UNLESS YOU KNOW WHY:\n* checkpoint_path - only needed when model_file is a .gguf (GGUF = DiT only; this must then point at a full safetensors to supply the VAEs + vocoder).\n* gemma_path - only for an HF gemma-3-12b-it DIRECTORY (dropdowns list files, not folders).\n* lora_file / lora_path - optional style LoRA. Ignored for GGUF DiTs.\n\nVRAM GUIDE:\n* 24 GB: fp8 model_file + low_vram ON (encoder runs on CPU; the encode is cached per prompt, so it only costs once).\n* 32 GB: bf16 model_file if downloaded; low_vram still recommended (the bf16 encoder alone is ~23 GB).\n* RTX 40/50-series: turn fp8_scaled_mm ON - native fp8 matmul, real speedup, and at moderate resolutions the DiT can run with sequential_offload OFF. Older GPUs: leave it off.\n* encoder_fp8 - halves encoder memory at a slight embedding shift (voice quality is the canary). Try it if RAM-starved."
862
- ],
863
- "color": "#432",
864
- "bgcolor": "#653"
865
- },
866
  {
867
  "id": 11,
868
  "type": "easy seed",
@@ -875,7 +625,7 @@
875
  106
876
  ],
877
  "flags": {},
878
- "order": 5,
879
  "mode": 0,
880
  "inputs": [
881
  {
@@ -909,121 +659,115 @@
909
  }
910
  },
911
  "widgets_values": [
912
- 2,
913
- "increment",
914
  null
915
  ]
916
  },
917
  {
918
- "id": 52,
919
- "type": "JoyEcho_PromptSource",
920
  "pos": [
921
- 0,
922
- 286
923
  ],
924
  "size": [
925
- 724.5169729521541,
926
- 201.68048280657888
927
  ],
928
  "flags": {},
929
- "order": 7,
930
  "mode": 0,
931
  "inputs": [
932
  {
933
- "localized_name": "source_file",
934
- "name": "source_file",
935
- "type": "COMBO",
936
- "widget": {
937
- "name": "source_file"
938
- },
939
- "link": null
940
- },
941
- {
942
- "localized_name": "load_cap",
943
- "name": "load_cap",
944
- "shape": 7,
945
- "type": "INT",
946
- "widget": {
947
- "name": "load_cap"
948
- },
949
- "link": null
950
  },
951
  {
952
- "localized_name": "start_index",
953
- "name": "start_index",
954
  "shape": 7,
955
- "type": "INT",
956
- "widget": {
957
- "name": "start_index"
958
- },
959
- "link": 117
960
  },
961
  {
962
- "localized_name": "character_override",
963
- "name": "character_override",
964
- "shape": 7,
965
- "type": "STRING",
966
  "widget": {
967
- "name": "character_override"
968
  },
969
  "link": null
970
  },
971
  {
972
- "localized_name": "manual_path",
973
- "name": "manual_path",
974
  "shape": 7,
975
- "type": "STRING",
976
  "widget": {
977
- "name": "manual_path"
978
  },
979
  "link": null
980
  }
981
  ],
982
  "outputs": [
983
  {
984
- "localized_name": "story_idea",
985
- "name": "story_idea",
986
- "shape": 6,
987
- "type": "STRING",
988
- "links": [
989
- 96,
990
- 114
991
- ]
992
- },
993
- {
994
- "localized_name": "character",
995
- "name": "character",
996
- "shape": 6,
997
- "type": "STRING",
998
  "links": [
999
- 100
1000
  ]
1001
- },
1002
- {
1003
- "localized_name": "count",
1004
- "name": "count",
1005
- "type": "INT",
1006
- "links": null
1007
  }
1008
  ],
 
1009
  "properties": {
1010
- "aux_id": "RealRebelAI/ComfyUI_JoyAI_Echo_GGUF_Nodes",
1011
- "ver": "0d9e32fcd2d255a16604368fcaa1875c380c7f3b",
1012
- "Node name for S&R": "JoyEcho_PromptSource",
1013
  "ue_properties": {
1014
  "widget_ue_connectable": {},
1015
- "input_ue_unconnectable": {},
1016
- "version": "7.8"
1017
  }
1018
  },
1019
  "widgets_values": [
1020
- "JSON: THE_HEADLINER_PUNK_BF16_JOYECHO.json",
1021
- 1,
1022
- 0,
1023
- "",
1024
- ""
1025
  ]
1026
  },
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1027
  {
1028
  "id": 64,
1029
  "type": "JoyEcho_ModelLoader",
@@ -1178,15 +922,15 @@
1178
  }
1179
  },
1180
  "widgets_values": [
1181
- "diffusion_models: ltx23_echoVid-ltxAud_surgical_bf16.safetensors",
1182
- "",
1183
  "text_encoders: gemma3-12b-BASE-clean-Q8_0.gguf",
1184
  "",
1185
  "(use lora_path / none)",
1186
  "",
1187
  0,
1188
  false,
1189
- true,
1190
  false,
1191
  true
1192
  ]
@@ -1478,17 +1222,17 @@
1478
  }
1479
  },
1480
  "widgets_values": [
1481
- 211080272,
1482
  "randomize",
1483
- 321,
1484
- 768,
1485
- 1344,
1486
  25,
1487
  2,
1488
- 5,
1489
  3,
1490
  true,
1491
- 48,
1492
  true,
1493
  "joyecho/shot",
1494
  "vhs_glitch",
@@ -1507,12 +1251,12 @@
1507
  "id": 58,
1508
  "type": "JoyEcho_AutoFinish",
1509
  "pos": [
1510
- 4328.590366291457,
1511
  99.91012828201144
1512
  ],
1513
  "size": [
1514
  328.24765625,
1515
- 178
1516
  ],
1517
  "flags": {},
1518
  "order": 13,
@@ -1578,6 +1322,16 @@
1578
  "name": "shots_subdir"
1579
  },
1580
  "link": null
 
 
 
 
 
 
 
 
 
 
1581
  }
1582
  ],
1583
  "outputs": [
@@ -1603,10 +1357,267 @@
1603
  "widgets_values": [
1604
  true,
1605
  " multishot",
1606
- 1.42,
1607
  "ULTRA",
1608
  8,
1609
- "joyecho"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1610
  ]
1611
  }
1612
  ],
@@ -1805,10 +1816,10 @@
1805
  "config": {},
1806
  "extra": {
1807
  "ds": {
1808
- "scale": 0.8784600000001012,
1809
  "offset": [
1810
- -2169.0519384541776,
1811
- 540.1476020823327
1812
  ]
1813
  },
1814
  "frontendVersion": "1.45.21",
 
193
  }
194
  },
195
  "widgets_values": [
196
+ "joyecho/PREVIEW",
197
  "auto",
198
  "auto"
199
  ]
200
  },
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
201
  {
202
  "id": 65,
203
  "type": "JoyEcho_LoraStacker",
 
502
  },
503
  "widgets_values": [
504
  "by_seed",
505
+ 1444127669,
506
  "randomize",
507
  "joyecho_refs",
508
  "no_reference",
 
581
  }
582
  },
583
  "widgets_values": [
584
+ "{\n \"prompts\": [\n \"Consumer camcorder video, 1080p, modest dynamic range, saturated color, slight edge softness: a locked static medium two-shot at their eye level in a narrow retro arcade aisle, cabinet marquees strobing red, cyan and yellow light across both of them, ID_A on the left and ID_B on the right, standing side by side facing the lens, both faces clear and both mouths fully visible, carpet printed with a wild geometric pattern underfoot. Quiet diegetic sound only: attract-mode jingles chirping from the cabinets, a coin dropping into a tray somewhere down the aisle, and the hum of a dozen CRT screens. ID_A is a woman in her early twenties with a bleached platinum pixie cut with choppy short bangs, a small silver septum ring, a row of small silver hoops up one ear, sharp winged black eyeliner, wearing a battered black leather jacket covered in enamel band pins and safety pins over a ripped neon-pink band tee. ID_A's voice is a bright, quick young American voice with a playful edge, casual American accent, casual contractions, captured with close camcorder-microphone proximity. ID_A's mouth stays clearly visible in the frame, and the mouth movement stays naturally synchronized with every spoken line. ID_B is a woman in her early twenties with long jet-black hair with blunt straight bangs, pale matte foundation, deep plum lipstick, a black velvet choker, stacked silver rings on both hands, wearing a long-sleeved black lace top under a charcoal pinafore dress. ID_B's voice is a low, dry, deadpan young American voice, casual American accent, casual contractions, captured with close camcorder-microphone proximity. ID_B's mouth stays clearly visible in the frame, and the mouth movement stays naturally synchronized with every spoken line. ID_A is the only person speaking in this shot, the source of the live camcorder dialogue. One continuous line with natural breaths and brief pauses; ID_A's first mouth movement begins with the first audible word, and the mouth settles naturally after the final word. ID_B does not speak at any point in this shot; ID_B's mouth stays fully closed and still the whole time, listening. At a quick, natural speed, they stand still side by side; ID_A looks into the lens, the lip movement aligning closely with the audio throughout the line. In a bright, quick voice with a playful edge, ID_A says, \\\"This is my friend. She doesn't smile - that's her whole thing. Two of us now: two faces, two voices, one renderer keeping both straight. Watch us.\\\"\",\n \"Consumer camcorder video, 1080p, modest dynamic range, saturated color, slight edge softness: a slow steady push-in to a medium two-shot at their eye level at a bowling alley ball return, lane neon in violet and orange washing over them from behind, ID_B on the left and ID_A on the right leaning against the ball return rack, both faces clear and both mouths fully visible, a rack of marbled house balls in loud colors beside them. Quiet diegetic sound only: a ball rumbling down a distant lane, pins scattering once, and the low drone of the lane machinery. ID_A is a woman in her early twenties with a bleached platinum pixie cut with choppy short bangs, a small silver septum ring, a row of small silver hoops up one ear, sharp winged black eyeliner, wearing a battered black leather jacket covered in enamel band pins and safety pins over a ripped neon-pink band tee. ID_A's voice is a bright, quick young American voice with a playful edge, casual American accent, casual contractions, captured with close camcorder-microphone proximity. ID_A's mouth stays clearly visible in the frame, and the mouth movement stays naturally synchronized with every spoken line. ID_B is a woman in her early twenties with long jet-black hair with blunt straight bangs, pale matte foundation, deep plum lipstick, a black velvet choker, stacked silver rings on both hands, wearing a long-sleeved black lace top under a charcoal pinafore dress. ID_B's voice is a low, dry, deadpan young American voice, casual American accent, casual contractions, captured with close camcorder-microphone proximity. ID_B's mouth stays clearly visible in the frame, and the mouth movement stays naturally synchronized with every spoken line. ID_B is the only person speaking in this shot, the source of the live camcorder dialogue. One continuous line with natural breaths and brief pauses; ID_B's first mouth movement begins with the first audible word, and the mouth settles naturally after the final word. ID_A does not speak at any point in this shot; ID_A's mouth stays fully closed and still the whole time, listening. At an unhurried speed, they hold still; ID_B looks flatly into the lens, the lip movement aligning closely with the audio throughout the line. In a low, dry deadpan, ID_B says, \\\"She talks enough for both of us. Different room, same us. My voice stays down here. Hers stays up there. If either drifts, the trick is dead.\\\"\",\n \"Consumer camcorder video, 1080p, modest dynamic range, saturated color, slight edge softness: a locked static medium close-up at her eye level in front of a sunlit graffiti wall, layered tags in electric blue, orange and white behind her, cracked asphalt underfoot, ID_A alone in frame, her head and shoulders filling most of the frame and her mouth fully visible. Quiet diegetic sound only: a skateboard rolling past somewhere off frame, sparrows on a wire, and wind flicking the corner of a peeled poster. ID_A is a woman in her early twenties with a bleached platinum pixie cut with choppy short bangs, a small silver septum ring, a row of small silver hoops up one ear, sharp winged black eyeliner, wearing a battered black leather jacket covered in enamel band pins and safety pins over a ripped neon-pink band tee. ID_A's voice is a bright, quick young American voice with a playful edge, casual American accent, casual contractions, captured with close camcorder-microphone proximity. ID_A's mouth stays clearly visible in the frame, and the mouth movement stays naturally synchronized with every spoken line. ID_A is the only person speaking in this shot, the source of the live camcorder dialogue. One continuous line with natural breaths and brief pauses; ID_A's first mouth movement begins with the first audible word, and the mouth settles naturally after the final word. At a quick, natural speed, she stands still against the wall, looking into the lens, the lip movement aligning closely with the audio throughout the line. In a bright, quick voice with a playful edge, ID_A says, \\\"Solo shot. She's fine, she's off being mysterious somewhere. Point is, the room only has to hold one of us right now. Easy mode. Next one's hers.\\\"\",\n \"Consumer camcorder video, 1080p, modest dynamic range, saturated color, slight edge softness: a locked static medium close-up at her eye level in a red-lit lounge corner, deep crimson bulbs in wall sconces over wine-dark velvet wallpaper, a single small round table with an unlit candle beside her, ID_B alone in frame, her head and shoulders filling most of the frame and her mouth fully visible, the red light putting hard shine on her black hair. Quiet diegetic sound only: a slow bass line thudding faintly through the wall, glasses clinking once in another room, and the sconce bulbs buzzing softly. ID_B is a woman in her early twenties with long jet-black hair with blunt straight bangs, pale matte foundation, deep plum lipstick, a black velvet choker, stacked silver rings on both hands, wearing a long-sleeved black lace top under a charcoal pinafore dress. ID_B's voice is a low, dry, deadpan young American voice, casual American accent, casual contractions, captured with close camcorder-microphone proximity. ID_B's mouth stays clearly visible in the frame, and the mouth movement stays naturally synchronized with every spoken line. ID_B is the only person speaking in this shot, the source of the live camcorder dialogue. One continuous line with natural breaths and brief pauses; ID_B's first mouth movement begins with the first audible word, and the mouth settles naturally after the final word. At an unhurried speed, she sits perfectly still, looking flatly into the lens, the lip movement aligning closely with the audio throughout the line. In a low, dry deadpan, ID_B says, \\\"My turn. Notice the voice did not come back wrong. Same low, same flat, same me. Two characters in one memory. That is the actual experiment here.\\\"\",\n \"Consumer camcorder video, 1080p, modest dynamic range, saturated color, slight edge softness: a locked static medium two-shot at their eye level on a neon-lit street corner at night, a wall of layered shop signs in pink, green and blue behind them, wet asphalt throwing colored reflections at the bottom of frame, ID_A on the left with ID_B on the right, both faces clear and both mouths fully visible. Quiet diegetic sound only: a distant siren fading, shoe scuffs on the wet pavement, and the layered hum of the shop signs. ID_A is a woman in her early twenties with a bleached platinum pixie cut with choppy short bangs, a small silver septum ring, a row of small silver hoops up one ear, sharp winged black eyeliner, wearing a battered black leather jacket covered in enamel band pins and safety pins over a ripped neon-pink band tee. ID_A's voice is a bright, quick young American voice with a playful edge, casual American accent, casual contractions, captured with close camcorder-microphone proximity. ID_A's mouth stays clearly visible in the frame, and the mouth movement stays naturally synchronized with every spoken line. ID_B is a woman in her early twenties with long jet-black hair with blunt straight bangs, pale matte foundation, deep plum lipstick, a black velvet choker, stacked silver rings on both hands, wearing a long-sleeved black lace top under a charcoal pinafore dress. ID_B's voice is a low, dry, deadpan young American voice, casual American accent, casual contractions, captured with close camcorder-microphone proximity. ID_B's mouth stays clearly visible in the frame, and the mouth movement stays naturally synchronized with every spoken line. ID_A is the only person speaking in this shot, the source of the live camcorder dialogue. One continuous line with natural breaths and brief pauses; ID_A's first mouth movement begins with the first audible word, and the mouth settles naturally after the final word. ID_B does not speak at any point in this shot; ID_B's mouth stays fully closed and still the whole time, listening. At a quick, natural speed, they stand still under the signs; ID_A looks into the lens, the lip movement aligning closely with the audio throughout the line. In a bright, quick voice with a playful edge, ID_A says, \\\"Two faces, two voices, five rooms, zero drift. Her deadpan survived. My eyeliner survived. If this works, everything bigger works. Alright - we're out.\\\"\"\n ]\n}"
585
  ]
586
  },
587
  {
 
613
  "color": "#432",
614
  "bgcolor": "#653"
615
  },
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
616
  {
617
  "id": 11,
618
  "type": "easy seed",
 
625
  106
626
  ],
627
  "flags": {},
628
+ "order": 4,
629
  "mode": 0,
630
  "inputs": [
631
  {
 
659
  }
660
  },
661
  "widgets_values": [
662
+ 0,
663
+ "fixed",
664
  null
665
  ]
666
  },
667
  {
668
+ "id": 6,
669
+ "type": "CreateVideo",
670
  "pos": [
671
+ 4328.590366291457,
672
+ 359.91012828201144
673
  ],
674
  "size": [
675
+ 301.3775449810605,
676
+ 102
677
  ],
678
  "flags": {},
679
+ "order": 14,
680
  "mode": 0,
681
  "inputs": [
682
  {
683
+ "localized_name": "images",
684
+ "name": "images",
685
+ "type": "IMAGE",
686
+ "link": 113
 
 
 
 
 
 
 
 
 
 
 
 
 
687
  },
688
  {
689
+ "localized_name": "audio",
690
+ "name": "audio",
691
  "shape": 7,
692
+ "type": "AUDIO",
693
+ "link": 108
 
 
 
694
  },
695
  {
696
+ "localized_name": "fps",
697
+ "name": "fps",
698
+ "type": "FLOAT",
 
699
  "widget": {
700
+ "name": "fps"
701
  },
702
  "link": null
703
  },
704
  {
705
+ "localized_name": "bit_depth",
706
+ "name": "bit_depth",
707
  "shape": 7,
708
+ "type": "INT",
709
  "widget": {
710
+ "name": "bit_depth"
711
  },
712
  "link": null
713
  }
714
  ],
715
  "outputs": [
716
  {
717
+ "localized_name": "VIDEO",
718
+ "name": "VIDEO",
719
+ "type": "VIDEO",
720
+ "slot_index": 0,
 
 
 
 
 
 
 
 
 
 
721
  "links": [
722
+ 10
723
  ]
 
 
 
 
 
 
724
  }
725
  ],
726
+ "title": "Create Video (Merge All Shots)",
727
  "properties": {
728
+ "cnr_id": "comfy-core",
729
+ "ver": "0.26.2",
730
+ "Node name for S&R": "CreateVideo",
731
  "ue_properties": {
732
  "widget_ue_connectable": {},
733
+ "version": "7.8",
734
+ "input_ue_unconnectable": {}
735
  }
736
  },
737
  "widgets_values": [
738
+ 25,
739
+ 8
 
 
 
740
  ]
741
  },
742
+ {
743
+ "id": 60,
744
+ "type": "Note",
745
+ "pos": [
746
+ 2780.2351442980976,
747
+ 806.7950609883953
748
+ ],
749
+ "size": [
750
+ 1057.751230034948,
751
+ 248.99507986020944
752
+ ],
753
+ "flags": {},
754
+ "order": 5,
755
+ "mode": 0,
756
+ "inputs": [],
757
+ "outputs": [],
758
+ "properties": {
759
+ "ue_properties": {
760
+ "widget_ue_connectable": {},
761
+ "version": "7.8",
762
+ "input_ue_unconnectable": {}
763
+ }
764
+ },
765
+ "widgets_values": [
766
+ "MODEL LOADER - what to set, what to leave\n------------------------------------------------\nSET THESE TWO:\n* model_file - the DiT checkpoint. fp8 build (23 GB) for 24 GB cards; bf16 (43 GB) for 32 GB+ (needs sequential offload).\n* gemma_file - your Gemma-3-12B text encoder, picked from models/text_encoders or models/clip. Single-file .safetensors or .gguf. A GGUF encoder is the light option (fits 24 GB systems easily).\n\nLEAVE ALONE UNLESS YOU KNOW WHY:\n* checkpoint_path - only needed when model_file is a .gguf (GGUF = DiT only; this must then point at a full safetensors to supply the VAEs + vocoder).\n* gemma_path - only for an HF gemma-3-12b-it DIRECTORY (dropdowns list files, not folders).\n* lora_file / lora_path - optional style LoRA. Ignored for GGUF DiTs.\n\nVRAM GUIDE:\n* 24 GB: fp8 model_file + low_vram ON (encoder runs on CPU; the encode is cached per prompt, so it only costs once).\n* 32 GB: bf16 model_file if downloaded; low_vram still recommended (the bf16 encoder alone is ~23 GB).\n* RTX 40/50-series: turn fp8_scaled_mm ON - native fp8 matmul, real speedup, and at moderate resolutions the DiT can run with sequential_offload OFF. Older GPUs: leave it off.\n* encoder_fp8 - halves encoder memory at a slight embedding shift (voice quality is the canary). Try it if RAM-starved."
767
+ ],
768
+ "color": "#432",
769
+ "bgcolor": "#653"
770
+ },
771
  {
772
  "id": 64,
773
  "type": "JoyEcho_ModelLoader",
 
922
  }
923
  },
924
  "widgets_values": [
925
+ "diffusion_models: LTX23-echoVid-ltxAud-Surgical-DiT-Q8_0.gguf",
926
+ "F:\\ComfyUI_windows_portable_nvidia\\ComfyUI_windows_portable\\ComfyUI\\models\\diffusion_models\\ltx23_echoVid-ltxAud_surgical_bf16.safetensors",
927
  "text_encoders: gemma3-12b-BASE-clean-Q8_0.gguf",
928
  "",
929
  "(use lora_path / none)",
930
  "",
931
  0,
932
  false,
933
+ false,
934
  false,
935
  true
936
  ]
 
1222
  }
1223
  },
1224
  "widgets_values": [
1225
+ 1274354006,
1226
  "randomize",
1227
+ 361,
1228
+ 736,
1229
+ 1280,
1230
  25,
1231
  2,
1232
+ 7,
1233
  3,
1234
  true,
1235
+ 96,
1236
  true,
1237
  "joyecho/shot",
1238
  "vhs_glitch",
 
1251
  "id": 58,
1252
  "type": "JoyEcho_AutoFinish",
1253
  "pos": [
1254
+ 4329.297195655411,
1255
  99.91012828201144
1256
  ],
1257
  "size": [
1258
  328.24765625,
1259
+ 202
1260
  ],
1261
  "flags": {},
1262
  "order": 13,
 
1322
  "name": "shots_subdir"
1323
  },
1324
  "link": null
1325
+ },
1326
+ {
1327
+ "localized_name": "upscale_mode",
1328
+ "name": "upscale_mode",
1329
+ "shape": 7,
1330
+ "type": "COMBO",
1331
+ "widget": {
1332
+ "name": "upscale_mode"
1333
+ },
1334
+ "link": null
1335
  }
1336
  ],
1337
  "outputs": [
 
1357
  "widgets_values": [
1358
  true,
1359
  " multishot",
1360
+ 1.5,
1361
  "ULTRA",
1362
  8,
1363
+ "joyecho",
1364
+ "bicubic+cas (deterministic)"
1365
+ ]
1366
+ },
1367
+ {
1368
+ "id": 52,
1369
+ "type": "JoyEcho_PromptSource",
1370
+ "pos": [
1371
+ 1.3774104683194017,
1372
+ 286
1373
+ ],
1374
+ "size": [
1375
+ 724.5169729521541,
1376
+ 201.68048280657888
1377
+ ],
1378
+ "flags": {},
1379
+ "order": 7,
1380
+ "mode": 0,
1381
+ "inputs": [
1382
+ {
1383
+ "localized_name": "source_file",
1384
+ "name": "source_file",
1385
+ "type": "COMBO",
1386
+ "widget": {
1387
+ "name": "source_file"
1388
+ },
1389
+ "link": null
1390
+ },
1391
+ {
1392
+ "localized_name": "load_cap",
1393
+ "name": "load_cap",
1394
+ "shape": 7,
1395
+ "type": "INT",
1396
+ "widget": {
1397
+ "name": "load_cap"
1398
+ },
1399
+ "link": null
1400
+ },
1401
+ {
1402
+ "localized_name": "start_index",
1403
+ "name": "start_index",
1404
+ "shape": 7,
1405
+ "type": "INT",
1406
+ "widget": {
1407
+ "name": "start_index"
1408
+ },
1409
+ "link": 117
1410
+ },
1411
+ {
1412
+ "localized_name": "character_override",
1413
+ "name": "character_override",
1414
+ "shape": 7,
1415
+ "type": "STRING",
1416
+ "widget": {
1417
+ "name": "character_override"
1418
+ },
1419
+ "link": null
1420
+ },
1421
+ {
1422
+ "localized_name": "manual_path",
1423
+ "name": "manual_path",
1424
+ "shape": 7,
1425
+ "type": "STRING",
1426
+ "widget": {
1427
+ "name": "manual_path"
1428
+ },
1429
+ "link": null
1430
+ }
1431
+ ],
1432
+ "outputs": [
1433
+ {
1434
+ "localized_name": "story_idea",
1435
+ "name": "story_idea",
1436
+ "shape": 6,
1437
+ "type": "STRING",
1438
+ "links": [
1439
+ 96,
1440
+ 114
1441
+ ]
1442
+ },
1443
+ {
1444
+ "localized_name": "character",
1445
+ "name": "character",
1446
+ "shape": 6,
1447
+ "type": "STRING",
1448
+ "links": [
1449
+ 100
1450
+ ]
1451
+ },
1452
+ {
1453
+ "localized_name": "count",
1454
+ "name": "count",
1455
+ "type": "INT",
1456
+ "links": null
1457
+ }
1458
+ ],
1459
+ "properties": {
1460
+ "aux_id": "RealRebelAI/ComfyUI_JoyAI_Echo_GGUF_Nodes",
1461
+ "ver": "0d9e32fcd2d255a16604368fcaa1875c380c7f3b",
1462
+ "Node name for S&R": "JoyEcho_PromptSource",
1463
+ "ue_properties": {
1464
+ "widget_ue_connectable": {},
1465
+ "input_ue_unconnectable": {},
1466
+ "version": "7.8"
1467
+ }
1468
+ },
1469
+ "widgets_values": [
1470
+ "JSON: THE_DOUBLE_BILL_PUNK_GOTH_JOYECHO.json",
1471
+ 1,
1472
+ 0,
1473
+ "",
1474
+ ""
1475
+ ]
1476
+ },
1477
+ {
1478
+ "id": 59,
1479
+ "type": "JoyEcho_LLMEnhance",
1480
+ "pos": [
1481
+ 900,
1482
+ 100
1483
+ ],
1484
+ "size": [
1485
+ 1161.0279043079718,
1486
+ 736.9512031915835
1487
+ ],
1488
+ "flags": {},
1489
+ "order": 8,
1490
+ "mode": 0,
1491
+ "inputs": [
1492
+ {
1493
+ "localized_name": "story_idea",
1494
+ "name": "story_idea",
1495
+ "type": "STRING",
1496
+ "widget": {
1497
+ "name": "story_idea"
1498
+ },
1499
+ "link": 114
1500
+ },
1501
+ {
1502
+ "localized_name": "mode",
1503
+ "name": "mode",
1504
+ "type": "COMBO",
1505
+ "widget": {
1506
+ "name": "mode"
1507
+ },
1508
+ "link": null
1509
+ },
1510
+ {
1511
+ "localized_name": "api_key",
1512
+ "name": "api_key",
1513
+ "type": "STRING",
1514
+ "widget": {
1515
+ "name": "api_key"
1516
+ },
1517
+ "link": null
1518
+ },
1519
+ {
1520
+ "localized_name": "system_prompt",
1521
+ "name": "system_prompt",
1522
+ "type": "STRING",
1523
+ "widget": {
1524
+ "name": "system_prompt"
1525
+ },
1526
+ "link": null
1527
+ },
1528
+ {
1529
+ "localized_name": "base_url",
1530
+ "name": "base_url",
1531
+ "shape": 7,
1532
+ "type": "STRING",
1533
+ "widget": {
1534
+ "name": "base_url"
1535
+ },
1536
+ "link": null
1537
+ },
1538
+ {
1539
+ "localized_name": "model_name",
1540
+ "name": "model_name",
1541
+ "shape": 7,
1542
+ "type": "STRING",
1543
+ "widget": {
1544
+ "name": "model_name"
1545
+ },
1546
+ "link": null
1547
+ },
1548
+ {
1549
+ "localized_name": "num_shots",
1550
+ "name": "num_shots",
1551
+ "shape": 7,
1552
+ "type": "INT",
1553
+ "widget": {
1554
+ "name": "num_shots"
1555
+ },
1556
+ "link": null
1557
+ },
1558
+ {
1559
+ "localized_name": "temperature",
1560
+ "name": "temperature",
1561
+ "shape": 7,
1562
+ "type": "FLOAT",
1563
+ "widget": {
1564
+ "name": "temperature"
1565
+ },
1566
+ "link": null
1567
+ },
1568
+ {
1569
+ "localized_name": "num_frames",
1570
+ "name": "num_frames",
1571
+ "shape": 7,
1572
+ "type": "INT",
1573
+ "widget": {
1574
+ "name": "num_frames"
1575
+ },
1576
+ "link": null
1577
+ },
1578
+ {
1579
+ "localized_name": "fps",
1580
+ "name": "fps",
1581
+ "shape": 7,
1582
+ "type": "FLOAT",
1583
+ "widget": {
1584
+ "name": "fps"
1585
+ },
1586
+ "link": null
1587
+ }
1588
+ ],
1589
+ "outputs": [
1590
+ {
1591
+ "localized_name": "prompts_json",
1592
+ "name": "prompts_json",
1593
+ "type": "STRING",
1594
+ "links": [
1595
+ 115,
1596
+ 116
1597
+ ]
1598
+ }
1599
+ ],
1600
+ "properties": {
1601
+ "aux_id": "RealRebelAI/ComfyUI_JoyAI_Echo_GGUF_Nodes",
1602
+ "ver": "0d9e32fcd2d255a16604368fcaa1875c380c7f3b",
1603
+ "Node name for S&R": "JoyEcho_LLMEnhance",
1604
+ "ue_properties": {
1605
+ "widget_ue_connectable": {},
1606
+ "input_ue_unconnectable": {},
1607
+ "version": "7.8"
1608
+ }
1609
+ },
1610
+ "widgets_values": [
1611
+ "",
1612
+ "passthrough (raw JSON, skip LLM)",
1613
+ "",
1614
+ "",
1615
+ "http://localhost:11434/v1",
1616
+ "glm-5.2:cloud",
1617
+ 0,
1618
+ 0.7,
1619
+ 361,
1620
+ 25
1621
  ]
1622
  }
1623
  ],
 
1816
  "config": {},
1817
  "extra": {
1818
  "ds": {
1819
+ "scale": 0.7260000000000874,
1820
  "offset": [
1821
+ -1323.7145260311115,
1822
+ 519.5332970752396
1823
  ]
1824
  },
1825
  "frontendVersion": "1.45.21",