commit 4b98fd2ea161cec62cb9b5db7a926194d8d8388c Author: ddontsov93 <32699505+ddontsov93@users.noreply.github.com> Date: Mon Dec 22 17:34:54 2025 +0300 Initial release: Audio Separator Node diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..418127a --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +__pycache__/ +*.pyc +.git/ \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..e1017e6 --- /dev/null +++ b/README.md @@ -0,0 +1,8 @@ +# ComfyUI Audio Separator + +A custom node for ComfyUI that uses `audio-separator` (UVR5 CLI) to clean audio, remove noise, or split vocals using GPU. + +## Installation +1. Clone this repo into `custom_nodes`. +2. Install requirements: `pip install -r requirements.txt` +3. Put your UVR models (.pth/.onnx) into `ComfyUI/models/UVR`. \ No newline at end of file diff --git a/__init__.py b/__init__.py new file mode 100644 index 0000000..fddc102 --- /dev/null +++ b/__init__.py @@ -0,0 +1,11 @@ +from .audio_separator_node import AudioSeparatorNode + +NODE_CLASS_MAPPINGS = { + "AudioSeparatorNode": AudioSeparatorNode +} + +NODE_DISPLAY_NAME_MAPPINGS = { + "AudioSeparatorNode": "🎧 Audio Separator" +} + +__all__ = ['NODE_CLASS_MAPPINGS', 'NODE_DISPLAY_NAME_MAPPINGS'] \ No newline at end of file diff --git a/audio_separator_node.py b/audio_separator_node.py new file mode 100644 index 0000000..8e96226 --- /dev/null +++ b/audio_separator_node.py @@ -0,0 +1,120 @@ +import os +import folder_paths +import torch +import torchaudio +import tempfile +import shutil +from audio_separator.separator import Separator + +# Определяем путь к моделям: ComfyUI/models/UVR +COMFY_MODELS_DIR = folder_paths.models_dir +UVR_MODELS_DIR = os.path.join(COMFY_MODELS_DIR, "UVR") + +# Если папки нет - создаем +if not os.path.exists(UVR_MODELS_DIR): + os.makedirs(UVR_MODELS_DIR) + +class AudioSeparatorNode: + def __init__(self): + pass + + @classmethod + def INPUT_TYPES(s): + # Сканируем папку models/UVR на наличие моделей + model_files = [f for f in os.listdir(UVR_MODELS_DIR) if f.endswith(('.pth', '.onnx', '.bin'))] + + # Если папка пустая, добавляем дефолтную для скачивания + if not model_files: + model_files = ["UVR-MDX-NET-Inst_HQ_3.onnx", "1_HP-UVR.pth", "UVR-DeNoise.pth"] + + return { + "required": { + "audio": ("AUDIO",), + "model_filename": (model_files, {"default": model_files[0] if model_files else "UVR-MDX-NET-Inst_HQ_3.onnx"}), + "window_size": ("INT", {"default": 512, "min": 320, "max": 1024, "step": 64, "tooltip": "VR Model Window Size. 512 is High Quality. 320 is Fast."}), + "aggression": ("INT", {"default": 10, "min": 1, "max": 50, "step": 1, "tooltip": "VR Model Aggression. Higher = more noise removal but potential artifacts."}), + "batch_size": ("INT", {"default": 4, "min": 1, "max": 16, "step": 1, "tooltip": "Batch size for GPU processing."}), + }, + "optional": { + "normalization": ("FLOAT", {"default": 0.9, "min": 0.1, "max": 1.0, "step": 0.1}), + } + } + + RETURN_TYPES = ("AUDIO", "AUDIO") + RETURN_NAMES = ("Vocals (Clean)", "Instrumental (Noise)") + FUNCTION = "separate_audio" + CATEGORY = "Audio/Processing" + + def separate_audio(self, audio, model_filename, window_size, aggression, batch_size, normalization): + # 1. Подготовка аудио из Comfy + waveform = audio['waveform'] + sample_rate = audio['sample_rate'] + + # Если батч > 1, берем только первый (упрощение для аудио) + if waveform.dim() == 3: + waveform = waveform[0] + + # Создаем временную папку для работы + temp_dir = tempfile.mkdtemp() + input_path = os.path.join(temp_dir, "input_temp.wav") + + # Сохраняем временный файл + torchaudio.save(input_path, waveform, sample_rate) + + # 2. Инициализация Сепаратора + print(f"🎧 Audio Separator: Initializing with model '{model_filename}'...") + + separator = Separator( + model_file_dir=UVR_MODELS_DIR, # Ищем модели в ComfyUI/models/UVR + output_dir=temp_dir, # Результат кладем во временную папку + output_format="WAV", + normalization_threshold=normalization, + vr_params={ + "batch_size": batch_size, + "window_size": window_size, + "aggression": aggression, + }, + mdx_params={ + "batch_size": batch_size, # Для MDX моделей тоже применяем батч + } + ) + + # Загружаем модель + try: + separator.load_model(model_filename=model_filename) + except Exception as e: + shutil.rmtree(temp_dir) + raise ValueError(f"Ошибка загрузки модели '{model_filename}'. Убедитесь, что она лежит в {UVR_MODELS_DIR}. Ошибка: {e}") + + # 3. Обработка + print(f"🎧 Audio Separator: Processing on GPU...") + output_files = separator.separate(input_path) + + # Определяем, какой файл есть какой + # Обычно output_files[0] - это первичный стем (Vocals), [1] - вторичный (Inst) + # Но проверим по именам для надежности + vocals_path = os.path.join(temp_dir, output_files[0]) + instr_path = os.path.join(temp_dir, output_files[1]) + + for f in output_files: + full_p = os.path.join(temp_dir, f) + if "(Vocals)" in f: vocals_path = full_p + if "(Instrumental)" in f or "(Noise)" in f: instr_path = full_p + + # 4. Возврат в Comfy + try: + vocals_wave, _ = torchaudio.load(vocals_path) + instr_wave, _ = torchaudio.load(instr_path) + except Exception as e: + shutil.rmtree(temp_dir) + raise RuntimeError(f"Ошибка чтения результата: {e}") + + # Формируем формат (Batch, Channels, Samples) + vocals_out = {"waveform": vocals_wave.unsqueeze(0), "sample_rate": sample_rate} + instr_out = {"waveform": instr_wave.unsqueeze(0), "sample_rate": sample_rate} + + # Удаляем временные файлы + shutil.rmtree(temp_dir) + print("🎧 Audio Separator: Done!") + + return (vocals_out, instr_out) \ No newline at end of file diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..9ab4e6a --- /dev/null +++ b/requirements.txt @@ -0,0 +1 @@ +audio-separator[gpu] \ No newline at end of file