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:
@@ -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
|
||||
|
||||
@@ -71,7 +71,7 @@ class LoadAudioUI:
|
||||
}
|
||||
}
|
||||
|
||||
CATEGORY = "audio"
|
||||
CATEGORY = "WhatDreamsCost"
|
||||
RETURN_TYPES = ("AUDIO", "FLOAT")
|
||||
RETURN_NAMES = ("audio", "duration")
|
||||
FUNCTION = "load_audio"
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -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."
|
||||
|
||||
@@ -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=[
|
||||
|
||||
@@ -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=[
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user