From 665f9f90ab532ccd3904abb01ce70f50ecfeef44 Mon Sep 17 00:00:00 2001 From: RyanOnTheInside <7623207+ryanontheinside@users.noreply.github.com> Date: Mon, 9 Feb 2026 09:15:34 -0500 Subject: [PATCH] fix: localize annoying imports todo: replace librosa with numpy and torch native functions --- nodes/audio/audio_nodes.py | 5 ++-- nodes/audio/audio_nodes_utility.py | 4 ++- nodes/audio/audio_processor_legacy.py | 40 ++++++++++++++------------- nodes/audio/audio_utils.py | 2 +- nodes/audio/midi_nodes.py | 5 +++- nodes/flex/feature_extractors_midi.py | 1 - nodes/flex/features_audio.py | 15 +++++++--- 7 files changed, 43 insertions(+), 29 deletions(-) diff --git a/nodes/audio/audio_nodes.py b/nodes/audio/audio_nodes.py index 99adc59..e054dd5 100644 --- a/nodes/audio/audio_nodes.py +++ b/nodes/audio/audio_nodes.py @@ -1,7 +1,7 @@ import torch import numpy as np from scipy.ndimage import zoom -from .audio_processor_legacy import AudioVisualizer, PygameAudioVisualizer + from scipy import signal import comfy.model_management as mm import folder_paths @@ -421,7 +421,8 @@ class AudioFeatureVisualizer(AudioNodeBase): def visualize_audio_feature(self, audio, video_frames, visualization_type, frame_rate, x_axis, y_axis, cmap, visualizer): num_frames, height, width, _ = video_frames.shape - if visualizer == "pygame": + from .audio_processor_legacy import AudioVisualizer, PygameAudioVisualizer + if visualizer == "pygame": visualizer = PygameAudioVisualizer(audio, num_frames, height, width, frame_rate) elif visualizer == "matplotlib": visualizer = AudioVisualizer(audio, num_frames, height, width, frame_rate, x_axis, y_axis, cmap) diff --git a/nodes/audio/audio_nodes_utility.py b/nodes/audio/audio_nodes_utility.py index 9c59ff6..b181244 100644 --- a/nodes/audio/audio_nodes_utility.py +++ b/nodes/audio/audio_nodes_utility.py @@ -11,7 +11,6 @@ from .audio_utils import ( ) import torch from ...tooltips import apply_tooltips -import librosa import numpy as np class AudioUtility(AudioNodeBase): @@ -232,6 +231,7 @@ class AudioInfo(AudioUtility): def _detect_key(self, audio_mono, sample_rate): """Detect musical key using chromagram and Krumhansl-Kessler key profiles.""" + import librosa # Extract chromagram using Constant-Q Transform (better for pitch detection) chromagram = librosa.feature.chroma_cqt(y=audio_mono, sr=sample_rate) @@ -269,6 +269,7 @@ class AudioInfo(AudioUtility): return best_key def get_audio_info(self, audio, frame_rate): + import librosa # Get basic audio info waveform = audio['waveform'] sample_rate = audio['sample_rate'] @@ -399,6 +400,7 @@ class Knob(AudioUtility): FUNCTION = "enhance_audio" def enhance_audio(self, audio, knob, other_knob): + import librosa waveform, sample_rate = audio['waveform'], audio['sample_rate'] # Make a copy of the input waveform diff --git a/nodes/audio/audio_processor_legacy.py b/nodes/audio/audio_processor_legacy.py index cb2b3df..d822973 100644 --- a/nodes/audio/audio_processor_legacy.py +++ b/nodes/audio/audio_processor_legacy.py @@ -1,14 +1,8 @@ import numpy as np import torch -import librosa import cv2 import matplotlib.pyplot as plt from comfy.utils import ProgressBar -import numpy as np -import librosa -import torch -import cv2 -import matplotlib.pyplot as plt from ... import ProgressMixin @@ -42,13 +36,7 @@ class BaseAudioProcessor(ProgressMixin): end_sample = int(end_time * self.sample_rate) return self.audio[start_sample:end_sample] -import librosa -import librosa.display -import matplotlib.pyplot as plt -import numpy as np -import torch - -class AudioVisualizer(BaseAudioProcessor): +class AudioVisualizer(BaseAudioProcessor): def __init__(self, audio, num_frames, height, width, frame_rate, x_axis, y_axis, cmap): super().__init__(audio, num_frames, height, width, frame_rate) @@ -58,6 +46,8 @@ class AudioVisualizer(BaseAudioProcessor): self.cmap = cmap def _generate_frame(self, data): + import librosa + import librosa.display fig, ax = plt.subplots(figsize=(self.width / 100, self.height / 100), dpi=100) librosa.display.specshow(data, sr=self.sample_rate, x_axis=self.x_axis, y_axis=self.y_axis, cmap=self.cmap, ax=ax) if self.x_axis == 'off': @@ -81,6 +71,7 @@ class AudioVisualizer(BaseAudioProcessor): return frame def create_spectrogram(self): + import librosa frames = [] self.start_progress(self.num_frames, "Creating Spectrogram") for i in range(self.num_frames): @@ -97,6 +88,8 @@ class AudioVisualizer(BaseAudioProcessor): return torch.from_numpy(frames).float() / 255.0 def create_waveform(self): + import librosa + import librosa.display frames = [] self.start_progress(self.num_frames, "Creating Waveform") for i in range(self.num_frames): @@ -130,6 +123,7 @@ class AudioVisualizer(BaseAudioProcessor): return torch.from_numpy(frames).float() / 255.0 def create_mfcc(self): + import librosa frames = [] self.start_progress(self.num_frames, "Creating MFCC") for i in range(self.num_frames): @@ -144,6 +138,7 @@ class AudioVisualizer(BaseAudioProcessor): return torch.from_numpy(frames).float() / 255.0 def create_chroma(self): + import librosa frames = [] self.start_progress(self.num_frames, "Creating Chroma") for i in range(self.num_frames): @@ -158,6 +153,7 @@ class AudioVisualizer(BaseAudioProcessor): return torch.from_numpy(frames).float() / 255.0 def create_tonnetz(self): + import librosa frames = [] self.start_progress(self.num_frames, "Creating Tonnetz") for i in range(self.num_frames): @@ -172,6 +168,7 @@ class AudioVisualizer(BaseAudioProcessor): return torch.from_numpy(frames).float() / 255.0 def create_spectral_centroid(self): + import librosa frames = [] self.start_progress(self.num_frames, "Creating Spectral Centroid") for i in range(self.num_frames): @@ -214,22 +211,20 @@ class AudioFeatureExtractor(BaseAudioProcessor): return np.array([np.sqrt(np.mean(self._get_audio_frame(i)**2)) for i in range(self.num_frames)]) def _spectral_centroid(self): + import librosa return np.array([np.mean(librosa.feature.spectral_centroid(y=self._get_audio_frame(i), sr=self.sample_rate)[0]) for i in range(self.num_frames)]) def _onset_detection(self): + import librosa return np.array([np.mean(librosa.onset.onset_strength(y=self._get_audio_frame(i), sr=self.sample_rate)) for i in range(self.num_frames)]) def _chroma_features(self): + import librosa return np.array([np.mean(librosa.feature.chroma_stft(y=self._get_audio_frame(i), sr=self.sample_rate), axis=1) for i in range(self.num_frames)]) -import numpy as np -import torch -import pygame -import librosa -from comfy.utils import ProgressBar - class PygameAudioVisualizer(BaseAudioProcessor): def __init__(self, audio, num_frames, height, width, frame_rate, scroll_direction='left'): + import pygame super().__init__(audio, num_frames, height, width, frame_rate) self.height = height self.width = width @@ -244,6 +239,7 @@ class PygameAudioVisualizer(BaseAudioProcessor): self.total_samples = len(self.full_waveform) def _surface_to_array(self, surface): + import pygame # Convert Pygame surface to NumPy array frame_array = pygame.surfarray.array3d(surface) # Transpose to (H, W, C) @@ -310,6 +306,7 @@ class PygameAudioVisualizer(BaseAudioProcessor): return torch.from_numpy(frames) def _generate_spectrogram_frame(self, audio_frame): + import librosa self.screen.fill((0, 0, 0)) # Clear the screen # Compute spectrogram n_fft = min(2048, len(audio_frame)) @@ -345,6 +342,7 @@ class PygameAudioVisualizer(BaseAudioProcessor): return torch.from_numpy(frames) def _generate_mfcc_frame(self, audio_frame): + import librosa self.screen.fill((0, 0, 0)) # Clear the screen # Compute MFCCs mfccs = librosa.feature.mfcc(y=audio_frame, sr=self.sample_rate, n_mfcc=20) @@ -377,6 +375,7 @@ class PygameAudioVisualizer(BaseAudioProcessor): return torch.from_numpy(frames) def _generate_chroma_frame(self, audio_frame): + import librosa self.screen.fill((0, 0, 0)) # Clear the screen # Compute Chroma features chromagram = librosa.feature.chroma_stft(y=audio_frame, sr=self.sample_rate) @@ -409,6 +408,7 @@ class PygameAudioVisualizer(BaseAudioProcessor): return torch.from_numpy(frames) def _generate_tonnetz_frame(self, audio_frame): + import librosa self.screen.fill((0, 0, 0)) # Clear the screen # Compute Tonnetz tonnetz = librosa.feature.tonnetz(y=audio_frame, sr=self.sample_rate) @@ -441,6 +441,7 @@ class PygameAudioVisualizer(BaseAudioProcessor): return torch.from_numpy(frames) def _generate_spectral_centroid_frame(self, audio_frame): + import librosa self.screen.fill((0, 0, 0)) # Clear the screen # Compute Spectral Centroid centroid = librosa.feature.spectral_centroid(y=audio_frame, sr=self.sample_rate) @@ -459,4 +460,5 @@ class PygameAudioVisualizer(BaseAudioProcessor): return self.screen.copy() def __del__(self): + import pygame pygame.quit() \ No newline at end of file diff --git a/nodes/audio/audio_utils.py b/nodes/audio/audio_utils.py index 7262ee0..723fba3 100644 --- a/nodes/audio/audio_utils.py +++ b/nodes/audio/audio_utils.py @@ -2,7 +2,6 @@ import torch import torchaudio import torchaudio.transforms as T import torchaudio.functional as F -import librosa import numpy as np import torch.nn.functional as nnf @@ -85,6 +84,7 @@ def time_stretch(waveform, rate): device = waveform.device waveform = waveform.cpu() + import librosa # Convert to numpy for librosa processing waveform_np = waveform.numpy() diff --git a/nodes/audio/midi_nodes.py b/nodes/audio/midi_nodes.py index e6029d1..0083ab6 100644 --- a/nodes/audio/midi_nodes.py +++ b/nodes/audio/midi_nodes.py @@ -1,6 +1,5 @@ import torch import numpy as np -import mido import os import folder_paths import math @@ -350,6 +349,7 @@ class MIDILoader: CATEGORY = "RyanOnTheInside/Audio/MIDI" def load_midi(self, midi_file, track_selection, start_measure=1, start_beat=1, end_measure=0, end_beat=1): + import mido try: midi_path = folder_paths.get_full_path("midi_files", midi_file) if not midi_path or not os.path.exists(midi_path): @@ -491,6 +491,7 @@ class MIDILoader: @classmethod def analyze_midi(cls, midi_path, start_measure=1, start_beat=1, end_measure=0, end_beat=1): + import mido midi_data = mido.MidiFile(midi_path) # Get the total measures @@ -546,6 +547,7 @@ class MIDILoader: @classmethod def VALIDATE_INPUTS(cls, midi_file, track_selection, start_measure=1, start_beat=1, end_measure=0, end_beat=1): + import mido midi_path = folder_paths.get_full_path("midi_files", midi_file) if not midi_path or not os.path.isfile(midi_path): return f"MIDI file not found: {midi_file}" @@ -642,6 +644,7 @@ async def upload_midi(request): @routes.post('/refresh_midi_data') async def refresh_midi_data(request): + import mido data = await request.json() midi_file = data.get('midi_file') track_selection = data.get('track_selection') diff --git a/nodes/flex/feature_extractors_midi.py b/nodes/flex/feature_extractors_midi.py index 7d9c1fb..e7a4df7 100644 --- a/nodes/flex/feature_extractors_midi.py +++ b/nodes/flex/feature_extractors_midi.py @@ -1,4 +1,3 @@ -import mido import os import folder_paths from .features_midi import MIDIFeature diff --git a/nodes/flex/features_audio.py b/nodes/flex/features_audio.py index 2baf3cf..3acd633 100644 --- a/nodes/flex/features_audio.py +++ b/nodes/flex/features_audio.py @@ -1,12 +1,10 @@ from .features import BaseFeature -import librosa import numpy as np from scipy.signal import hilbert from scipy import signal import functools from abc import ABC, abstractmethod import numpy as np -import librosa from scipy.signal import medfilt, hilbert from scipy import signal import functools @@ -96,6 +94,7 @@ class AudioFeature(BaseAudioFeature): return self def _calculate_feature(self, frame): + import librosa if frame.size == 0: return 0.0 if self.feature_name == 'amplitude_envelope': @@ -125,7 +124,6 @@ class AudioFeature(BaseAudioFeature): self.features[self.feature_name] = normalized.tolist() import numpy as np -import librosa class RhythmFeature(BaseAudioFeature): def __init__(self, feature_name, audio, frame_count, frame_rate, width, height, feature_type='beat_locations', time_signature=4): @@ -150,8 +148,9 @@ class RhythmFeature(BaseAudioFeature): ] def extract(self): + import librosa self.features = {} - + # Extract basic rhythm features tempo, beat_frames = librosa.beat.beat_track(y=self.audio_array, sr=self.sample_rate) onset_env = librosa.onset.onset_strength(y=self.audio_array, sr=self.sample_rate) @@ -178,6 +177,7 @@ class RhythmFeature(BaseAudioFeature): return self def _extract_beat_locations(self, beat_frames): + import librosa beat_times = librosa.frames_to_time(beat_frames, sr=self.sample_rate) beat_sequence = np.zeros(self.frame_count) for beat_time in beat_times: @@ -200,6 +200,7 @@ class RhythmFeature(BaseAudioFeature): self.features[self.feature_name] = resampled_onset.tolist() def _extract_beat_emphasis(self, beat_frames, onset_env): + import librosa beat_emphasis = np.zeros(self.frame_count) for beat in beat_frames: if beat < len(onset_env): @@ -224,12 +225,14 @@ class RhythmFeature(BaseAudioFeature): self.features[self.feature_name] = resampled_syncopation.tolist() def _extract_rhythm_regularity(self, beat_frames): + import librosa # Measure regularity by calculating the standard deviation of inter-beat intervals ibi = np.diff(librosa.frames_to_time(beat_frames, sr=self.sample_rate)) regularity = 1 / (1 + np.std(ibi)) # Invert so that higher values mean more regular self.features[self.feature_name] = [regularity] * self.frame_count def _extract_beat_types(self, beat_frames): + import librosa beat_times = librosa.frames_to_time(beat_frames, sr=self.sample_rate) down_beats = np.zeros(self.frame_count) up_beats = np.zeros(self.frame_count) @@ -327,11 +330,13 @@ class PitchFeature(BaseAudioFeature): @classmethod def pitch_to_note(cls, pitch): + import librosa if pitch == 0: return "N/A" return librosa.hz_to_note(pitch) def _calculate_pitch_sequence(self): + import librosa # Try to use CREPE model if available if self.crepe_model != "none": try: @@ -385,6 +390,7 @@ class PitchFeature(BaseAudioFeature): self.features[self.feature_name + '_confidence'] = confidences.tolist() def _fallback_pitch_estimation(self): + import librosa pitches, magnitudes = librosa.piptrack( y=self.audio_array, sr=self.sample_rate, @@ -429,6 +435,7 @@ class PitchFeature(BaseAudioFeature): self.features[self.feature_name] = feature_values.tolist() def _extract_semitone(self): + import librosa pitches = np.array(self.features[self.feature_name + '_pitch']) midi_notes = librosa.hz_to_midi(pitches) midi_notes = np.round(midi_notes)