v1.3.3 Fixed errors due to audio encoding. Fixed minor widget issue.

- Fixed duration_seconds input
- Both duration settings are now shown by default
- Audio latent fix
This commit is contained in:
WhatDreamsCost
2026-05-15 17:35:11 -05:00
parent b8161d3cc0
commit e4db1fc892
10 changed files with 40 additions and 36 deletions

View File

@@ -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

View File

@@ -71,7 +71,7 @@ class LoadAudioUI:
}
}
CATEGORY = "audio"
CATEGORY = "WhatDreamsCost"
RETURN_TYPES = ("AUDIO", "FLOAT")
RETURN_NAMES = ("audio", "duration")
FUNCTION = "load_audio"

View File

@@ -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:

View File

@@ -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)

View File

@@ -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."

View File

@@ -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=[

View File

@@ -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=[

View File

@@ -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

View File

@@ -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)

View File

@@ -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