diff --git a/js/ltx_director.js b/js/ltx_director.js index 2aa7f56..63ef414 100644 --- a/js/ltx_director.js +++ b/js/ltx_director.js @@ -1642,34 +1642,20 @@ class TimelineEditor { const mode = this.displayModeWidget ? this.displayModeWidget.value : "seconds"; if (this.durationFramesWidget) { - const isVisible = mode === "frames"; - // We don't set type to "hidden" anymore because it breaks rendering in Nodes 2.0. - // We keep the type as "INT" and use computeSize to hide it. + // Always visible regardless of display mode this.durationFramesWidget.type = "INT"; if (!this.durationFramesWidget.options) this.durationFramesWidget.options = {}; - this.durationFramesWidget.options.hidden = !isVisible; - this.durationFramesWidget.hidden = !isVisible; - - if (isVisible) { - delete this.durationFramesWidget.computeSize; - } else { - this.durationFramesWidget.computeSize = () => [0, 0]; - } + this.durationFramesWidget.options.hidden = false; + this.durationFramesWidget.hidden = false; + delete this.durationFramesWidget.computeSize; } if (this.durationSecondsWidget) { - const isVisible = mode === "seconds"; - // We don't set type to "hidden" anymore because it breaks rendering in Nodes 2.0. - // We keep the type as "FLOAT" and use computeSize to hide it. + // Always visible regardless of display mode this.durationSecondsWidget.type = "FLOAT"; if (!this.durationSecondsWidget.options) this.durationSecondsWidget.options = {}; - this.durationSecondsWidget.options.hidden = !isVisible; - this.durationSecondsWidget.hidden = !isVisible; - - if (isVisible) { - delete this.durationSecondsWidget.computeSize; - } else { - this.durationSecondsWidget.computeSize = () => [0, 0]; - } + this.durationSecondsWidget.options.hidden = false; + this.durationSecondsWidget.hidden = false; + delete this.durationSecondsWidget.computeSize; } // Force node resize and redraw deferred to next tick diff --git a/load_audio_ui.py b/load_audio_ui.py index cd7fe8e..eaa84dc 100644 --- a/load_audio_ui.py +++ b/load_audio_ui.py @@ -71,7 +71,7 @@ class LoadAudioUI: } } - CATEGORY = "audio" + CATEGORY = "WhatDreamsCost" RETURN_TYPES = ("AUDIO", "FLOAT") RETURN_NAMES = ("audio", "duration") FUNCTION = "load_audio" diff --git a/load_video_ui.py b/load_video_ui.py index 81c030b..a67d3f3 100644 --- a/load_video_ui.py +++ b/load_video_ui.py @@ -64,7 +64,7 @@ class LoadVideoUI: RETURN_TYPES = ("IMAGE", "AUDIO", "FLOAT", "INT") RETURN_NAMES = ("images", "audio", "duration", "frame_count") FUNCTION = "load_video" - CATEGORY = "Custom/Video" + CATEGORY = "WhatDreamsCost" def load_video(self, video, frame_rate, display_mode, start_time, end_time, duration, start_frame, end_frame, duration_frames, custom_width=0, custom_height=0, resize_method="maintain aspect ratio", crop_x=0.0, crop_y=0.0, crop_w=1.0, crop_h=1.0, **kwargs): if not video: diff --git a/ltx_director.py b/ltx_director.py index 9a96e93..92d3b9a 100644 --- a/ltx_director.py +++ b/ltx_director.py @@ -378,7 +378,7 @@ class LTXDirector(io.ComfyNode): return io.Schema( node_id="LTXDirector", display_name="LTX Director", - category="conditioning/prompt_relay", + category="WhatDreamsCost", description=( "Same as Prompt Relay Encode, but local prompts and segment lengths are edited " "visually as draggable blocks on a timeline. The duration_frames input only sets the " @@ -584,9 +584,11 @@ class LTXDirector(io.ComfyNode): if audio_vae is not None: # Helper to generate empty latent def get_empty_latent(): + # Support both raw AudioVAE objects and ComfyUI VAE wrappers. + inner = getattr(audio_vae, "first_stage_model", audio_vae) z_channels = audio_vae.latent_channels - audio_freq = audio_vae.first_stage_model.latent_frequency_bins - num_audio_latents = audio_vae.first_stage_model.num_of_latents_from_frames(ltxv_length, float(frame_rate)) + audio_freq = inner.latent_frequency_bins + num_audio_latents = inner.num_of_latents_from_frames(ltxv_length, float(frame_rate)) audio_latents = torch.zeros( (1, z_channels, num_audio_latents, audio_freq), device=comfy.model_management.intermediate_device(), @@ -597,8 +599,23 @@ class LTXDirector(io.ComfyNode): try: if audio_out is not None: # 1. Encode audio waveform into latent space - # VAE expects shape (batch, samples, channels), so we move dim 1 (channels) to the end - latent_samples = audio_vae.encode(audio_out["waveform"].movedim(1, -1)) + waveform = audio_out["waveform"] + if waveform.ndim == 2: + waveform = waveform.unsqueeze(0) + if waveform.ndim != 3: + raise ValueError( + f"Expected custom audio waveform with 2 or 3 dims, got shape {tuple(waveform.shape)}" + ) + + # Wrapped ComfyUI VAE expects (batch, samples, channels); + # raw AudioVAE expects a dict with waveform in (batch, channels, samples). + if hasattr(audio_vae, "first_stage_model"): + latent_samples = audio_vae.encode(waveform.movedim(1, -1)) + else: + latent_samples = audio_vae.encode({ + "waveform": waveform, + "sample_rate": audio_out["sample_rate"], + }) if latent_samples.numel() == 0: raise ValueError("Encoded audio latent is empty (0 elements).") @@ -629,7 +646,8 @@ class LTXDirector(io.ComfyNode): audio_latent = get_empty_latent() log.info("[PromptRelay] Auto-generated empty audio latent.") except Exception as e: - log.warning("[PromptRelay] Could not generate empty audio latent: %s", e) + log.error("[PromptRelay] Could not generate empty audio latent: %s", e) + raise e return io.NodeOutput(patched, conditioning, latent, audio_latent, guide_data, float(frame_rate), audio_out) diff --git a/ltx_director_guide.py b/ltx_director_guide.py index 6ede4c9..7476792 100644 --- a/ltx_director_guide.py +++ b/ltx_director_guide.py @@ -11,7 +11,7 @@ class LTXDirectorGuide(LTXVAddGuide): return io.Schema( node_id="LTXDirectorGuide", display_name="LTX Director Guide", - category="LTXVCustom", + category="WhatDreamsCost", description=( "Applies guide images from a Prompt Relay Timeline node at the frame positions " "and strengths defined on the timeline. Connect guide_data from the timeline node." diff --git a/ltx_keyframer.py b/ltx_keyframer.py index e508a34..a876293 100644 --- a/ltx_keyframer.py +++ b/ltx_keyframer.py @@ -38,7 +38,7 @@ class LTXKeyframer(io.ComfyNode): return io.Schema( node_id="LTXKeyframer", display_name="LTX Keyframer", - category="LTXVCustom", + category="WhatDreamsCost", description="Replaces video latent frames with the encoded input images. Number of widgets is dynamically configured.", inputs=inputs, outputs=[ diff --git a/ltx_sequencer.py b/ltx_sequencer.py index aa49cf7..2a440ef 100644 --- a/ltx_sequencer.py +++ b/ltx_sequencer.py @@ -54,7 +54,7 @@ class LTXSequencer(LTXVAddGuide): return io.Schema( node_id="LTXSequencer", display_name="LTX Sequencer", - category="LTXVCustom", + category="WhatDreamsCost", description="Add multiple guide images at specified frame indices or seconds with strengths. Number of widgets is dynamically configured.", inputs=inputs, outputs=[ diff --git a/multi_image_loader.py b/multi_image_loader.py index e57ecbe..a290894 100644 --- a/multi_image_loader.py +++ b/multi_image_loader.py @@ -26,7 +26,7 @@ class MultiImageLoader: RETURN_TYPES = ("IMAGE",) * 51 RETURN_NAMES = ("multi_output",) + tuple(f"image_{i+1}" for i in range(50)) FUNCTION = "load_images" - CATEGORY = "image" + CATEGORY = "WhatDreamsCost" def resize_image(self, image, width, height, resize_method="keep proportion", interpolation="nearest", multiple_of=0): MAX_RESOLUTION = 8192 diff --git a/pyproject.toml b/pyproject.toml index d2f6952..08c0c8f 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,7 +1,7 @@ [project] name = "WhatDreamsCost-ComfyUI" description = "A variety of custom ComfyUI nodes and workflows for creatives." -version = "1.3.2" +version = "1.3.3" license = {file = "LICENSE"} # classifiers = [ # # For OS-independent nodes (works on all operating systems) diff --git a/speech_length_calculator.py b/speech_length_calculator.py index 00d847b..81edcf6 100644 --- a/speech_length_calculator.py +++ b/speech_length_calculator.py @@ -20,7 +20,7 @@ class SpeechLengthCalculator: # Added "text" to RETURN_NAMES RETURN_NAMES = ("slow_frame_count", "average_frame_count", "fast_frame_count", "text") FUNCTION = "calculate_speech" - CATEGORY = "text/speech" + CATEGORY = "WhatDreamsCost" def calculate_speech(self, text, fps, additional_time=0.0, text_input=None): # Prioritize the connected text_input if provided, otherwise fallback to the text widget