mirror of
https://github.com/comfyanonymous/ComfyUI.git
synced 2026-05-25 00:17:23 +08:00
Merge branch 'Comfy-Org:master' into master
This commit is contained in:
commit
36c69acab5
@ -82,6 +82,8 @@ class VAEEncodeAudio(IO.ComfyNode):
|
|||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def execute(cls, vae, audio) -> IO.NodeOutput:
|
def execute(cls, vae, audio) -> IO.NodeOutput:
|
||||||
|
if audio is None:
|
||||||
|
raise ValueError("VAEEncodeAudio: input audio is None (source video may have no audio track).")
|
||||||
sample_rate = audio["sample_rate"]
|
sample_rate = audio["sample_rate"]
|
||||||
vae_sample_rate = getattr(vae, "audio_sample_rate", 44100)
|
vae_sample_rate = getattr(vae, "audio_sample_rate", 44100)
|
||||||
if vae_sample_rate != sample_rate:
|
if vae_sample_rate != sample_rate:
|
||||||
@ -171,6 +173,8 @@ class SaveAudio(IO.ComfyNode):
|
|||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def execute(cls, audio, filename_prefix="ComfyUI", format="flac") -> IO.NodeOutput:
|
def execute(cls, audio, filename_prefix="ComfyUI", format="flac") -> IO.NodeOutput:
|
||||||
|
if audio is None:
|
||||||
|
raise ValueError("SaveAudio: input audio is None (source video may have no audio track).")
|
||||||
return IO.NodeOutput(
|
return IO.NodeOutput(
|
||||||
ui=UI.AudioSaveHelper.get_save_audio_ui(audio, filename_prefix=filename_prefix, cls=cls, format=format)
|
ui=UI.AudioSaveHelper.get_save_audio_ui(audio, filename_prefix=filename_prefix, cls=cls, format=format)
|
||||||
)
|
)
|
||||||
@ -198,6 +202,8 @@ class SaveAudioMP3(IO.ComfyNode):
|
|||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def execute(cls, audio, filename_prefix="ComfyUI", format="mp3", quality="128k") -> IO.NodeOutput:
|
def execute(cls, audio, filename_prefix="ComfyUI", format="mp3", quality="128k") -> IO.NodeOutput:
|
||||||
|
if audio is None:
|
||||||
|
raise ValueError("SaveAudioMP3: input audio is None (source video may have no audio track).")
|
||||||
return IO.NodeOutput(
|
return IO.NodeOutput(
|
||||||
ui=UI.AudioSaveHelper.get_save_audio_ui(
|
ui=UI.AudioSaveHelper.get_save_audio_ui(
|
||||||
audio, filename_prefix=filename_prefix, cls=cls, format=format, quality=quality
|
audio, filename_prefix=filename_prefix, cls=cls, format=format, quality=quality
|
||||||
@ -226,6 +232,8 @@ class SaveAudioOpus(IO.ComfyNode):
|
|||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def execute(cls, audio, filename_prefix="ComfyUI", format="opus", quality="V3") -> IO.NodeOutput:
|
def execute(cls, audio, filename_prefix="ComfyUI", format="opus", quality="V3") -> IO.NodeOutput:
|
||||||
|
if audio is None:
|
||||||
|
raise ValueError("SaveAudioOpus: input audio is None (source video may have no audio track).")
|
||||||
return IO.NodeOutput(
|
return IO.NodeOutput(
|
||||||
ui=UI.AudioSaveHelper.get_save_audio_ui(
|
ui=UI.AudioSaveHelper.get_save_audio_ui(
|
||||||
audio, filename_prefix=filename_prefix, cls=cls, format=format, quality=quality
|
audio, filename_prefix=filename_prefix, cls=cls, format=format, quality=quality
|
||||||
@ -252,6 +260,8 @@ class PreviewAudio(IO.ComfyNode):
|
|||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def execute(cls, audio) -> IO.NodeOutput:
|
def execute(cls, audio) -> IO.NodeOutput:
|
||||||
|
if audio is None:
|
||||||
|
raise ValueError("PreviewAudio: input audio is None (source video may have no audio track).")
|
||||||
return IO.NodeOutput(ui=UI.PreviewAudio(audio, cls=cls))
|
return IO.NodeOutput(ui=UI.PreviewAudio(audio, cls=cls))
|
||||||
|
|
||||||
save_flac = execute # TODO: remove
|
save_flac = execute # TODO: remove
|
||||||
@ -392,21 +402,26 @@ class TrimAudioDuration(IO.ComfyNode):
|
|||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def execute(cls, audio, start_index, duration) -> IO.NodeOutput:
|
def execute(cls, audio, start_index, duration) -> IO.NodeOutput:
|
||||||
|
if audio is None:
|
||||||
|
return IO.NodeOutput(None)
|
||||||
waveform = audio["waveform"]
|
waveform = audio["waveform"]
|
||||||
sample_rate = audio["sample_rate"]
|
sample_rate = audio["sample_rate"]
|
||||||
audio_length = waveform.shape[-1]
|
audio_length = waveform.shape[-1]
|
||||||
|
|
||||||
|
if audio_length == 0:
|
||||||
|
return IO.NodeOutput(audio)
|
||||||
|
|
||||||
if start_index < 0:
|
if start_index < 0:
|
||||||
start_frame = audio_length + int(round(start_index * sample_rate))
|
start_frame = audio_length + int(round(start_index * sample_rate))
|
||||||
else:
|
else:
|
||||||
start_frame = int(round(start_index * sample_rate))
|
start_frame = int(round(start_index * sample_rate))
|
||||||
start_frame = max(0, min(start_frame, audio_length - 1))
|
start_frame = max(0, min(start_frame, audio_length))
|
||||||
|
|
||||||
end_frame = start_frame + int(round(duration * sample_rate))
|
end_frame = start_frame + int(round(duration * sample_rate))
|
||||||
end_frame = max(0, min(end_frame, audio_length))
|
end_frame = max(0, min(end_frame, audio_length))
|
||||||
|
|
||||||
if start_frame >= end_frame:
|
if start_frame >= end_frame:
|
||||||
raise ValueError("AudioTrim: Start time must be less than end time and be within the audio length.")
|
raise ValueError("TrimAudioDuration: Start time must be less than end time and be within the audio length.")
|
||||||
|
|
||||||
return IO.NodeOutput({"waveform": waveform[..., start_frame:end_frame], "sample_rate": sample_rate})
|
return IO.NodeOutput({"waveform": waveform[..., start_frame:end_frame], "sample_rate": sample_rate})
|
||||||
|
|
||||||
@ -433,11 +448,13 @@ class SplitAudioChannels(IO.ComfyNode):
|
|||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def execute(cls, audio) -> IO.NodeOutput:
|
def execute(cls, audio) -> IO.NodeOutput:
|
||||||
|
if audio is None:
|
||||||
|
return IO.NodeOutput(None, None)
|
||||||
waveform = audio["waveform"]
|
waveform = audio["waveform"]
|
||||||
sample_rate = audio["sample_rate"]
|
sample_rate = audio["sample_rate"]
|
||||||
|
|
||||||
if waveform.shape[1] != 2:
|
if waveform.shape[1] != 2:
|
||||||
raise ValueError("AudioSplit: Input audio has only one channel.")
|
raise ValueError(f"AudioSplit: Input audio must be stereo (2 channels), got {waveform.shape[1]} channel(s).")
|
||||||
|
|
||||||
left_channel = waveform[..., 0:1, :]
|
left_channel = waveform[..., 0:1, :]
|
||||||
right_channel = waveform[..., 1:2, :]
|
right_channel = waveform[..., 1:2, :]
|
||||||
@ -465,6 +482,12 @@ class JoinAudioChannels(IO.ComfyNode):
|
|||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def execute(cls, audio_left, audio_right) -> IO.NodeOutput:
|
def execute(cls, audio_left, audio_right) -> IO.NodeOutput:
|
||||||
|
if audio_left is None and audio_right is None:
|
||||||
|
return IO.NodeOutput(None)
|
||||||
|
if audio_left is None:
|
||||||
|
return IO.NodeOutput(audio_right)
|
||||||
|
if audio_right is None:
|
||||||
|
return IO.NodeOutput(audio_left)
|
||||||
waveform_left = audio_left["waveform"]
|
waveform_left = audio_left["waveform"]
|
||||||
sample_rate_left = audio_left["sample_rate"]
|
sample_rate_left = audio_left["sample_rate"]
|
||||||
waveform_right = audio_right["waveform"]
|
waveform_right = audio_right["waveform"]
|
||||||
@ -538,6 +561,12 @@ class AudioConcat(IO.ComfyNode):
|
|||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def execute(cls, audio1, audio2, direction) -> IO.NodeOutput:
|
def execute(cls, audio1, audio2, direction) -> IO.NodeOutput:
|
||||||
|
if audio1 is None and audio2 is None:
|
||||||
|
return IO.NodeOutput(None)
|
||||||
|
if audio1 is None:
|
||||||
|
return IO.NodeOutput(audio2)
|
||||||
|
if audio2 is None:
|
||||||
|
return IO.NodeOutput(audio1)
|
||||||
waveform_1 = audio1["waveform"]
|
waveform_1 = audio1["waveform"]
|
||||||
waveform_2 = audio2["waveform"]
|
waveform_2 = audio2["waveform"]
|
||||||
sample_rate_1 = audio1["sample_rate"]
|
sample_rate_1 = audio1["sample_rate"]
|
||||||
@ -585,6 +614,12 @@ class AudioMerge(IO.ComfyNode):
|
|||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def execute(cls, audio1, audio2, merge_method) -> IO.NodeOutput:
|
def execute(cls, audio1, audio2, merge_method) -> IO.NodeOutput:
|
||||||
|
if audio1 is None and audio2 is None:
|
||||||
|
return IO.NodeOutput(None)
|
||||||
|
if audio1 is None:
|
||||||
|
return IO.NodeOutput(audio2)
|
||||||
|
if audio2 is None:
|
||||||
|
return IO.NodeOutput(audio1)
|
||||||
waveform_1 = audio1["waveform"]
|
waveform_1 = audio1["waveform"]
|
||||||
waveform_2 = audio2["waveform"]
|
waveform_2 = audio2["waveform"]
|
||||||
sample_rate_1 = audio1["sample_rate"]
|
sample_rate_1 = audio1["sample_rate"]
|
||||||
@ -595,6 +630,9 @@ class AudioMerge(IO.ComfyNode):
|
|||||||
length_1 = waveform_1.shape[-1]
|
length_1 = waveform_1.shape[-1]
|
||||||
length_2 = waveform_2.shape[-1]
|
length_2 = waveform_2.shape[-1]
|
||||||
|
|
||||||
|
if length_1 == 0 or length_2 == 0:
|
||||||
|
return IO.NodeOutput({"waveform": waveform_1, "sample_rate": output_sample_rate})
|
||||||
|
|
||||||
if length_2 > length_1:
|
if length_2 > length_1:
|
||||||
logging.info(f"AudioMerge: Trimming audio2 from {length_2} to {length_1} samples to match audio1 length.")
|
logging.info(f"AudioMerge: Trimming audio2 from {length_2} to {length_1} samples to match audio1 length.")
|
||||||
waveform_2 = waveform_2[..., :length_1]
|
waveform_2 = waveform_2[..., :length_1]
|
||||||
@ -646,6 +684,8 @@ class AudioAdjustVolume(IO.ComfyNode):
|
|||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def execute(cls, audio, volume) -> IO.NodeOutput:
|
def execute(cls, audio, volume) -> IO.NodeOutput:
|
||||||
|
if audio is None:
|
||||||
|
return IO.NodeOutput(None)
|
||||||
if volume == 0:
|
if volume == 0:
|
||||||
return IO.NodeOutput(audio)
|
return IO.NodeOutput(audio)
|
||||||
waveform = audio["waveform"]
|
waveform = audio["waveform"]
|
||||||
@ -729,8 +769,14 @@ class AudioEqualizer3Band(IO.ComfyNode):
|
|||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def execute(cls, audio, low_gain_dB, low_freq, mid_gain_dB, mid_freq, mid_q, high_gain_dB, high_freq) -> IO.NodeOutput:
|
def execute(cls, audio, low_gain_dB, low_freq, mid_gain_dB, mid_freq, mid_q, high_gain_dB, high_freq) -> IO.NodeOutput:
|
||||||
|
if audio is None:
|
||||||
|
return IO.NodeOutput(None)
|
||||||
waveform = audio["waveform"]
|
waveform = audio["waveform"]
|
||||||
sample_rate = audio["sample_rate"]
|
sample_rate = audio["sample_rate"]
|
||||||
|
|
||||||
|
if waveform.shape[-1] == 0:
|
||||||
|
return IO.NodeOutput(audio)
|
||||||
|
|
||||||
eq_waveform = waveform.clone()
|
eq_waveform = waveform.clone()
|
||||||
|
|
||||||
# 1. Apply Low Shelf (Bass)
|
# 1. Apply Low Shelf (Bass)
|
||||||
|
|||||||
@ -1,9 +1,23 @@
|
|||||||
|
from collections import defaultdict
|
||||||
|
|
||||||
import torch
|
import torch
|
||||||
|
|
||||||
from comfy.model_detection import detect_unet_config, model_config_from_unet_config
|
from comfy.model_detection import detect_unet_config, model_config_from_unet_config
|
||||||
import comfy.supported_models
|
import comfy.supported_models
|
||||||
|
|
||||||
|
|
||||||
|
def _freeze(value):
|
||||||
|
"""Recursively convert a value to a hashable form so configs can be
|
||||||
|
compared/used as dict keys or set members."""
|
||||||
|
if isinstance(value, dict):
|
||||||
|
return frozenset((k, _freeze(v)) for k, v in value.items())
|
||||||
|
if isinstance(value, (list, tuple)):
|
||||||
|
return tuple(_freeze(v) for v in value)
|
||||||
|
if isinstance(value, set):
|
||||||
|
return frozenset(_freeze(v) for v in value)
|
||||||
|
return value
|
||||||
|
|
||||||
|
|
||||||
def _make_longcat_comfyui_sd():
|
def _make_longcat_comfyui_sd():
|
||||||
"""Minimal ComfyUI-format state dict for pre-converted LongCat-Image weights."""
|
"""Minimal ComfyUI-format state dict for pre-converted LongCat-Image weights."""
|
||||||
sd = {}
|
sd = {}
|
||||||
@ -110,3 +124,21 @@ class TestModelDetection:
|
|||||||
model_config = model_config_from_unet_config(unet_config, sd)
|
model_config = model_config_from_unet_config(unet_config, sd)
|
||||||
assert model_config is not None
|
assert model_config is not None
|
||||||
assert type(model_config).__name__ == "FluxSchnell"
|
assert type(model_config).__name__ == "FluxSchnell"
|
||||||
|
|
||||||
|
def test_unet_config_and_required_keys_combination_is_unique(self):
|
||||||
|
"""Each model in the registry must have a unique combination of
|
||||||
|
``unet_config`` and ``required_keys``. If two models share the same
|
||||||
|
combination, ``BASE.matches`` cannot disambiguate between them and the
|
||||||
|
first one in the list will always win."""
|
||||||
|
models = comfy.supported_models.models
|
||||||
|
groups = defaultdict(list)
|
||||||
|
for model in models:
|
||||||
|
key = (_freeze(model.unet_config), _freeze(model.required_keys))
|
||||||
|
groups[key].append(model.__name__)
|
||||||
|
|
||||||
|
duplicates = {k: names for k, names in groups.items() if len(names) > 1}
|
||||||
|
assert not duplicates, (
|
||||||
|
"Found models sharing the same (unet_config, required_keys) "
|
||||||
|
"combination, which makes detection ambiguous: "
|
||||||
|
+ "; ".join(", ".join(names) for names in duplicates.values())
|
||||||
|
)
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user