import sys import os import yaml import time import traceback import gc gc.enable() import librosa from pathlib import Path BASE_DIR = Path(__file__).resolve().parent sys.path.append(str(BASE_DIR)) from extra_utils import hf_spaces_gpu, dw_file, extra_clear_torch_cache, nuclear_clear_model, emergency_ram_clear, tz, print_current_device from datetime import datetime import torch import rich nn = torch.nn import json from tqdm import tqdm import numpy as np from typing import Literal, Optional, List, Tuple, Any, Dict from ml_collections import ConfigDict from omegaconf import OmegaConf import gradio as gr from audio import read, write, output_formats, subtractor, check, easy_resampler, ensemble_types, ensemble, multiread, get_audio_files_from_list, stereo_to_mono, gain, get_metadata, check_taglib_not_installed from args_parser import parse_separator_args, tobool from namer import Namer from i18n import _i18n import contextlib import base64 import secrets from copy import deepcopy def generate_random_string(): return base64.b64encode(secrets.token_bytes(18)).decode('utf-8') def generate_metadata_from_stem(input_file_name: str, metadata: dict, stem: str, model_name: str, comment: str): new_metadata = {} if metadata: new_metadata = deepcopy(metadata) if "TITLE" in metadata: new_metadata["TITLE"] = f"[{model_name} / {stem}] {metadata['TITLE']}" else: new_metadata["TITLE"] = f"[{model_name} / {stem}] {input_file_name}" if "COMMENT" in metadata: new_metadata["COMMENT"] = comment else: new_metadata["COMMENT"] = comment else: new_metadata["TITLE"] = f"[{model_name} / {stem}] {input_file_name}" new_metadata["COMMENT"] = comment return new_metadata class EnsembleFlowValidateError(Exception): pass class PathNotExist(Exception): pass class PathsNotExist(Exception): pass class PathNotSpecified(Exception): pass class PathsNotSpecified(Exception): pass class FileIsNotAudio(Exception): pass class FilesIsNotAudio(Exception): pass class MixNotFound(Exception): pass class MixIsEmpty(Exception): pass class UnknownModelType(Exception): pass class DemixError(Exception): pass class ConfigNotLoaded(Exception): pass class ModelNotLoaded(Exception): pass class ModelStateDictError(Exception): pass model_types = ( "mel_band_roformer", "bs_roformer", "mdx23c", "scnet", "scnet_masked", "scnet_tran", "htdemucs", "bandit", "bandit_v2", "mdxnet", "vr", "medley_vox" ) custom_model_types = model_types[:9] HAS_OLD_AMP = False if hasattr(torch, "cuda"): if hasattr(torch.cuda, "amp"): if hasattr(torch.cuda.amp, "autocast"): HAS_OLD_AMP = True HAS_NEW_AMP = False if hasattr(torch, "amp"): if hasattr(torch.amp, "autocast"): HAS_NEW_AMP = True def get_autocast_context(device_type="cuda", enabled=True): if HAS_NEW_AMP: return torch.amp.autocast(device_type=device_type, enabled=enabled) elif HAS_OLD_AMP: return torch.cuda.amp.autocast(enabled=enabled) else: # Если AMP не поддерживается вообще return contextlib.nullcontext() # Или пустой контекст def _getWindowingArray(window_size: int, fade_size: int) -> torch.Tensor: """ Создать массив окна для плавного склеивания Args: window_size: Размер окна fade_size: Размер зоны затухания Returns: Массив окна """ fadein = torch.linspace(0, 1, fade_size) fadeout = torch.linspace(1, 0, fade_size) window = torch.ones(window_size) window[-fade_size:] = fadeout window[:fade_size] = fadein return window base_params = { "sec": { "type": "float", "component": "number", "minimum": 1, "maximum": 30, "step": 0.1, "default": 7, "info": "separation_segment_size_info" }, "size": { "type": "int", "component": "slider", "minimum": 128, "maximum": 1024, "step": 128, "default": 256, "info": "separation_segment_size_info" }, "wsize": { "type": "int", "component": "slider", "minimum": 320, "maximum": 1024, "step": 64, "default": 512, "info": "separation_window_size_info" }, "hop": { "type": "int", "component": "slider", "minimum": 512, "maximum": 2048, "step": 512, "default": 1024, "info": "separation_hop_info" }, "overlap": { "type": "int", "component": "slider", "minimum": 1, "maximum": 16, "step": 1, "default": 2, "info": "separation_overlap_info" }, "batch": { "type": "int", "component": "slider", "minimum": 1, "maximum": 16, "step": 1, "default": 1, "info": "separation_batch_size_info" }, "threshold" : { "type": "float", "component": "slider", "minimum": 0.1, "maximum": 0.3, "step": 0.1, "default": 0.2, }, "aggression": { "type": "int", "component": "slider", "minimum": 0, "maximum": 100, "step": 1, "default": 5, "info": "separation_aggresion_info" }, "enable": { "type": "bool", "component": "checkbox", "default": True }, "disable": { "type": "bool", "component": "checkbox", "default": False }, } add_params = { "mdxc": { "mdxc_segment_size": base_params["size"], "mdxc_batch_size": base_params["batch"], "mdxc_overlap": base_params["overlap"], "mdxc_denoise": base_params["disable"], "mdxc_override_segment": base_params["disable"] }, "demucs": { "demucs_segment": base_params["sec"], "demucs_batch_size": base_params["batch"], "demucs_overlap": base_params["overlap"], "demucs_denoise": base_params["disable"], "demucs_override_segment": base_params["disable"] }, "mdx": { "mdx_hop_length": base_params["hop"], "mdx_segment_size": base_params["size"], "mdx_batch_size": base_params["batch"], "mdx_overlap": base_params["overlap"], "mdx_denoise": base_params["disable"], "mdx_override_segment": base_params["disable"] }, "vr": { "vr_window_size": base_params["wsize"], "vr_batch_size": base_params["batch"], "vr_aggression": base_params["aggression"], "vr_post_process": base_params["disable"], "vr_post_process_threshold": base_params["threshold"], "vr_high_end_process": {**base_params["disable"], "info": "separation_hi-end_process_info"} }, "mvox": { "mvox_segment": base_params["sec"], "mvox_overlap": base_params["overlap"], "mvox_override_segment": base_params["disable"] } } add_params_list = [] add_params_group = [] add_params_args = {} default_add_params = {} for t_tab, t_components in add_params.items(): add_params_group.append(t_tab) for t_component, t_settings in t_components.items(): add_params_list.append(t_component) add_params_args[t_component] = {"default": t_settings["default"], "type": t_settings["type"]} default_add_params[t_component] = t_settings["default"] def get_add_params(args): """Безопасно получает add_params из args""" if hasattr(args, 'add_params') and args.add_params is not None: return vars(args.add_params) return {} def parse_model_type_add_params(model_type: str): if model_type == "htdemucs": return list(add_params["demucs"].keys()) elif model_type == "mdxnet": return list(add_params["mdx"].keys()) elif model_type == "medley_vox": return list(add_params["mvox"].keys()) elif model_type == "vr": return list(add_params["vr"].keys()) else: return list(add_params["mdxc"].keys()) def get_stems_from_config_simple(conf: str | Path, model_type: str): instruments = [] if not conf: return instruments conf = Path(conf) if not conf.exists(): raise PathNotExist(_i18n("path_not_exist")) try: if model_type == "htdemucs": config = OmegaConf.load(conf) else: with conf.open("r", encoding="utf-8") as f: config = ConfigDict(yaml.load(f, Loader=yaml.FullLoader)) instruments = config.training.instruments except FileNotFoundError: print(FileNotFoundError(_i18n("config_not_found", path=conf))) except Exception as e: print(ValueError(_i18n("config_load_error", error=str(e)))) return instruments class MSSI: # Music Source Separation Inference def __init__(self, output_dir=".", output_format=output_formats[0], use_spec_invert=False, device="cuda" if torch.cuda.is_available() else "cpu", ): self.output_dir = Path(output_dir) self.output_dir.mkdir(parents=True, exist_ok=True) self.model_types = model_types self.custom_model_types = custom_model_types self.output_format = output_format self.device = torch.device(device) print_current_device(self.device) self.use_spec_invert = use_spec_invert self.model = None self.model_module = None self.state_dict = {} self.model_loaded = False self.model_type = None self.ckpt_path = None self.conf_path = None self.config = None self.target_instrument = None self.instruments = [] self.input_mix = None self.input_file_name = None self.input_file_metadata = {} self.sample_rate = None self.selected_instruments = [] self.output_files_list = [] self.add_params = {} self.output_arrays: dict[str, np.ndarray] = {} def settings(self, output_dir=".", output_format=output_formats[0], use_spec_invert=False, ): self.output_dir = Path(output_dir) self.output_dir.mkdir(parents=True, exist_ok=True) self.output_format = output_format self.use_spec_invert = use_spec_invert def set_add_params(self, **kwargs): self.add_params = kwargs def load_config(self, model_type: str, conf: str | Path): if not conf: raise PathNotSpecified(_i18n("path_not_specified")) self.conf_path = Path(conf) if not self.conf_path.exists(): self.conf_path = None raise PathNotExist(_i18n("path_not_exist")) if model_type not in self.model_types: raise UnknownModelType(_i18n("unknown_model_type", model_type=model_type)) self.model_type = model_type try: if self.model_type == "htdemucs": self.config = OmegaConf.load(self.conf_path) self.sample_rate = self.config.training.samplerate else: with self.conf_path.open("r", encoding="utf-8") as f: self.config = ConfigDict(yaml.load(f, Loader=yaml.FullLoader)) self.sample_rate = self.config.audio.sample_rate self.target_instrument = self.config.training.target_instrument self.instruments = self.config.training.instruments print(_i18n("config_loaded")+": "+self.conf_path.name) except FileNotFoundError: self.config = None self.conf_path = None self.model_type = None self.target_instrument = None self.instruments = [] self.sample_rate = None raise FileNotFoundError(_i18n("config_not_found", path=conf)) from e except Exception as e: self.config = None self.conf_path = None self.model_type = None self.target_instrument = None self.instruments = [] self.sample_rate = None raise ValueError(_i18n("config_load_error", error=str(e))) from e def prefer_target_instrument(self): if self.target_instrument: return [self.target_instrument] else: return self.instruments def print_instruments(self): print(_i18n("stems")+": "+", ".join(self.instruments)) print(_i18n("target_instrument")+": "+(self.target_instrument if self.target_instrument else _i18n("no"))) def validate_selected_instruments(self, selected_stems: list = []): correct_stems_list = [] uncorrect_stems_list = [] if selected_stems: print(_i18n("selected_stems")+": "+", ".join(selected_stems)) for stem in selected_stems: stem_is_correct = False for stem_orig in self.instruments: if stem_orig == stem: correct_stems_list.append(stem_orig) stem_is_correct = True break if not stem_is_correct: uncorrect_stems_list.append(stem) print(_i18n("corrected_selected_stems")+": "+", ".join(correct_stems_list)) if uncorrect_stems_list: print(_i18n("uncorrected_selected_stems")+": "+", ".join(uncorrect_stems_list)) return correct_stems_list def load_model_instance(self): if self.config is None or self.model_type is None: raise ConfigNotLoaded(_i18n("config_is_not_loaded")) if self.model_type == "mdx23c": from models import mdx23c_tfc_tdf_v3 as module self.model_module = module.TFC_TDF_net self.model = self.model_module(self.config) del module elif self.model_type == "mdxnet": from models import mdx_net as module self.model_module = module.MDXNet self.model = self.model_module(**dict(self.config.model)) del module elif self.model_type == "vr": from models import vr_arch as module self.model_module = module.get_model self.model = self.model_module(self.config) del module elif self.model_type == "htdemucs": models_path = BASE_DIR / 'models' sys.path.append(str(models_path)) from demucs import get_model as module self.model_module = module self.model = self.model_module(self.config) del module elif self.model_type == "mel_band_roformer": if hasattr(self.config, "windowed"): from models.windowed_roformer import model as module self.model_module = module.MelBandRoformerWSA self.model = self.model_module(**dict(self.config.model)) del module elif hasattr(self.config, "conformer"): from models import bs_roformer as module self.model_module = module.MelBandConformer self.model = self.model_module(**dict(self.config.model)) del module else: from models import bs_roformer as module self.model_module = module.MelBandRoformer self.model = self.model_module(**dict(self.config.model)) del module elif self.model_type == "bs_roformer": if hasattr(self.config, "sw"): from models import bs_roformer as module self.model_module = module.BSRoformer_SW self.model = self.model_module(**dict(self.config.model)) del module elif hasattr(self.config, "fno"): from models import bs_roformer as module self.model_module = module.BSRoformer_FNO self.model = self.model_module(**dict(self.config.model)) del module elif hasattr(self.config, "hyperace"): from models import bs_roformer as module self.model_module = module.BSRoformerHyperACE self.model = self.model_module(**dict(self.config.model)) del module elif hasattr(self.config, "hyperace2"): from models import bs_roformer as module self.model_module = module.BSRoformerHyperACE_2 self.model = self.model_module(**dict(self.config.model)) del module elif hasattr(self.config, "conformer"): from models import bs_roformer as module self.model_module = module.BSConformer self.model = self.model_module(**dict(self.config.model)) del module elif hasattr(self.config, "conditional"): from models import bs_roformer as module self.model_module = module.BSRoformer_Conditional self.model = self.model_module(**dict(self.config.model)) del module elif hasattr(self.config, "unwa_inst_large_2"): from models import bs_roformer as module self.model_module = module.BSRoformer_2 self.model = self.model_module(**dict(self.config.model)) del module elif hasattr(self.config, "siamese"): from models import bs_roformer as module self.model_module = module.BSSiameseRoformer self.model = self.model_module(**dict(self.config.model)) del module else: from models import bs_roformer as module self.model_module = module.BSRoformer self.model = self.model_module(**dict(self.config.model)) del module elif self.model_type == "bandit": from models.bandit.core import model as module self.model_module = module.MultiMaskMultiSourceBandSplitRNNSimple self.model = self.model_module(**self.config.model) del module elif self.model_type == "bandit_v2": from models.bandit_v2 import bandit as module self.model_module = module.Bandit self.model = self.model_module(**self.config.kwargs) del module elif self.model_type == "scnet_unofficial": from models import scnet_unofficial as module self.model_module = module.SCNet self.model = self.model_module(**self.config.model) del module elif self.model_type == "scnet": from models import scnet as module self.model_module = module.SCNet self.model = self.model_module(**self.config.model) del module elif self.model_type == 'scnet_masked': from models.scnet import scnet_masked as module self.model_module = module.SCNet self.model = self.model_module(**self.config.model) del module elif self.model_type == 'scnet_tran': from models.scnet import scnet_tran as module self.model_module = module.SCNet_Tran self.model = self.model_module(**self.config.model) del module elif self.model_type == 'medley_vox': from models import medley_vox as module self.model_module = module.load_model_with_args self.model = self.model_module(self.config.model) del module else: raise UnknownModelType(_i18n("unknown_model_type", model_type=self.model_type)) def clear_model(self): """Стандартная очистка (сохранена для совместимости)""" # Принудительно переносим модель на CPU перед удалением if self.model is not None: if hasattr(self.model, "cpu"): self.model = self.model.cpu() del self.model self.model = None del self.model_module self.model_module = None self.model_loaded = False self.state_dict.clear() if hasattr(self.state_dict, 'clear') else None self.state_dict = {} self.config = None self.target_instrument = None self.instruments = [] self.output_arrays.clear() self.ckpt_path = None self.conf_path = None self.model_type = None gc.collect() gc.collect() extra_clear_torch_cache() nuclear_clear_model() emergency_ram_clear() self.clear_gpu_cache() def clear_gpu_cache(self): gc.collect() torch.clear_autocast_cache() if self.device.type == "mps": torch.mps.empty_cache() if self.device.type == "cuda": torch.cuda.synchronize() torch.cuda.ipc_collect() torch.cuda.empty_cache() def load_checkpoint(self, ckpt: str | Path): if not ckpt: raise PathNotSpecified(_i18n("path_not_specified")) self.ckpt_path = Path(ckpt) if not self.ckpt_path.exists(): self.ckpt_path = None raise PathNotExist(_i18n("path_not_exist")) if not self.model: self.ckpt_path = None raise ModelNotLoaded(_i18n("model_not_loaded")) if self.model_type == "mdxnet": try: self.model.init_onnx_session(self.ckpt_path, self.device, 0) self.model_loaded = True print(_i18n("checkpoint_loaded") + ": " + self.ckpt_path.name) except Exception as e: self.model_loaded = False self.ckpt_path = None self.clear_model() print(_i18n("load_checkpoint_error", error=e)) return else: try: try: self.state_dict = torch.load( self.ckpt_path, map_location=self.device, weights_only=True ) except torch.serialization.pickle.UnpicklingError: self.state_dict = torch.load( self.ckpt_path, map_location=self.device, weights_only=False ) except Exception as e: self.model_loaded = False self.ckpt_path = None self.clear_model() print(_i18n("load_checkpoint_error", error=e)) return if "state" in self.state_dict: self.state_dict = self.state_dict["state"] if "state_dict" in self.state_dict: self.state_dict = self.state_dict["state_dict"] if "model_state_dict" in self.state_dict: self.state_dict = self.state_dict["model_state_dict"] if self.model_type == "medley_vox": has_ema_keys = any(k.startswith("ema_model") for k in self.state_dict.keys()) if has_ema_keys: self.state_dict = {k: v for k, v in self.state_dict.items() if k.startswith("ema_model")} new_state_dict = {} for k, v in self.state_dict.items(): if k.startswith("ema_model.module."): new_key = k.replace("ema_model.module.", "") elif k.startswith("ema_model."): new_key = k.replace("ema_model.", "") elif k.startswith("online_model.module."): new_key = k.replace("online_model.module.", "") elif k.startswith("online_model."): new_key = k.replace("online_model.", "") elif k.startswith("module."): new_key = k.replace("module.", "") else: new_key = k if new_key not in ["initted", "step"]: new_state_dict[new_key] = v self.state_dict = new_state_dict del new_state_dict try: self.model.load_state_dict(self.state_dict) self.state_dict = {} self.model_loaded = True self.model.to(self.device) self.model.eval() print(_i18n("checkpoint_loaded") + ": " + self.ckpt_path.name) except RuntimeError as e: try: self.model.load_state_dict(self.state_dict, strict=False) self.state_dict = {} self.model_loaded = True self.model.to(self.device) self.model.eval() print(_i18n("load_state_dict_error", error=e)) print(_i18n("checkpoint_loaded") + ": " + self.ckpt_path.name) except RuntimeError as e_2: self.state_dict = {} self.model_loaded = False self.ckpt_path = None self.clear_model() print(_i18n("load_state_dict_error", error=e_2)) return def load_mix(self, path: str): self.input_file_name = None self.input_mix = None if self.config is None: raise ConfigNotLoaded(_i18n("config_is_not_loaded")) mono_bool = False if hasattr(self.config, "model"): if hasattr(self.config.model, "stereo"): mono_bool = not self.config.model.stereo if not path: raise PathNotSpecified(_i18n("path_not_specified")) input_file = Path(path) if not input_file.exists(): raise PathNotExist(_i18n("path_not_exist")) if check(path): self.input_file_name = input_file.stem self.input_mix, _ = read(path=input_file, sr=self.sample_rate, mono=mono_bool) self.input_mix = self.input_mix.copy() self.input_file_metadata = get_metadata(input_file) print(_i18n("loaded_mix")+": "+input_file.name) print(_i18n("array_shape")+": "+str(self.input_mix.shape)) else: raise FileIsNotAudio(_i18n("file_is_not_audio", path=path)) def load_array(self, array: np.ndarray, orig_sr: int): self.input_file_name = "temp_array" if self.config is None: raise ConfigNotLoaded(_i18n("config_is_not_loaded")) mono_bool = False if hasattr(self.config, "model"): if hasattr(self.config.model, "stereo"): mono_bool = not self.config.model.stereo self.input_mix = easy_resampler(array.copy(), orig_sr, self.sample_rate) if orig_sr != self.sample_rate else array.copy() if mono_bool: self.input_mix = stereo_to_mono(self.input_mix) print(_i18n("loaded_mix")+": "+_i18n("from_array")) print(_i18n("array_shape")+": "+str(self.input_mix.shape)) def demix(self, add_text: str = ""): if self.input_mix is None: raise MixNotFound(_i18n("mix_not_found")) if self.input_mix.size == 0: raise MixIsEmpty(_i18n("mix_is_empty")) if not self.model_loaded: raise ModelNotLoaded(_i18n("model_not_loaded")) if self.model_type == "mdxnet": mix_tensor = torch.tensor(self.input_mix, dtype=torch.float32).to(self.device) batch_size = 1 dim_t = 256 hop_length: int = self.add_params.get("mdx_hop_length", 1024) batch_size: int = self.add_params.get("mdx_batch_size", 1) num_overlap: int = self.add_params.get("mdx_overlap", 2) denoise: bool = self.add_params.get("mdx_denoise", False) if self.add_params.get("mdx_override_segment", False): segment_size: int = self.add_params.get("mdx_segment_size", dim_t) else: segment_size: int = dim_t segment_size = round(segment_size / 128) * 128 stem_name = self.target_instrument chunk_size = hop_length * (segment_size - 1) fade_size = chunk_size // 10 step = chunk_size // num_overlap border = chunk_size - step self.model.post_init(segment_size, self.device) length_init = mix_tensor.shape[-1] if length_init > 2 * border and border > 0: wave = nn.functional.pad(mix_tensor, (border, border), mode="reflect") window = _getWindowingArray(chunk_size, fade_size).to(self.device) with torch.no_grad(): result = torch.zeros_like(wave, device=self.device) counter = torch.zeros_like(wave, device=self.device) i = 0 batch_data = [] batch_locations = [] denoise_str = " "+_i18n("denoise") if denoise else "" with tqdm(total=wave.shape[1], desc=_i18n("processing") + denoise_str + str(add_text), unit=_i18n("samples")) as progress_bar: while i < wave.shape[1]: part = wave[:, i : i + chunk_size] chunk_len = part.shape[-1] if chunk_len < chunk_size: pad_mode = "reflect" if chunk_len > chunk_size // 2 else "constant" part = nn.functional.pad( part, (0, chunk_size - chunk_len), mode=pad_mode, value=0 ) batch_data.append(part) batch_locations.append((i, chunk_len)) i += step if len(batch_data) >= batch_size or i >= wave.shape[1]: arr = torch.stack(batch_data, dim=0) for j, (start, seg_len) in enumerate(batch_locations): if denoise: processed_spec1 = self.model.forward(self.model.stft(arr[j : j + 1], chunk_size, hop_length, segment_size)) processed_spec2 = self.model.forward(self.model.stft(-(arr[j : j + 1]), chunk_size, hop_length, segment_size)) processed_wav = (self.model.istft(processed_spec1, chunk_size, hop_length, segment_size) + -self.model.istft(processed_spec2, chunk_size, hop_length, segment_size)) * 0.5 else: processed_spec = self.model.forward(self.model.stft(arr[j : j + 1], chunk_size, hop_length, segment_size)) processed_wav = self.model.istft(processed_spec, chunk_size, hop_length, segment_size) window_segment = window[..., :seg_len] result[:, start : start + seg_len] += ( processed_wav[0, :, :seg_len] * window_segment ) counter[:, start : start + seg_len] += window_segment batch_data.clear() batch_locations.clear() progress_bar.update(step) estimated_sources = result / counter if length_init > 2 * border and border > 0: estimated_sources = estimated_sources[..., border:-border] result_separation = estimated_sources.detach().cpu().numpy() result_separation = np.nan_to_num( result_separation, nan=0.0, posinf=0.0, neginf=0.0 ) self.output_arrays = {stem_name: result_separation} del mix_tensor, window, result, counter, batch_data, batch_locations del estimated_sources, result_separation if denoise: del processed_spec1, processed_spec2, processed_wav else: del processed_spec, processed_wav elif self.model_type == "vr": from models.vr_arch import spec_utils, NON_ACCOM_STEMS aggression: int = self.add_params.get("vr_aggression", 5) enable_post_process: bool = self.add_params.get("vr_post_process", False) high_end_process: bool = self.add_params.get("vr_high_end_process", False) post_process_threshold: float = self.add_params.get("vr_post_process_threshold", 0.2) batch_size: int = self.add_params.get("vr_batch_size", 1) window_size: int = self.add_params.get("vr_window_size", 512) sr = self.sample_rate model_sample_rate = self.model.model_params.param["sr"] primary_stem, secondary_stem = self.instruments[0], self.instruments[1] aggr = float(int(aggression) / 100) aggressiveness = { "value": aggr, "split_bin": self.model.model_params.param["band"][1]["crop_stop"], "aggr_correction": self.model.model_params.param.get("aggr_correction"), } input_high_end_h = None input_high_end = None X_wave, X_spec_s = {}, {} bands_n = len(self.model.model_params.param["band"]) for d in tqdm(range(bands_n, 0, -1), desc=_i18n("processing") + str(add_text), unit=_i18n("bands")): bp = self.model.model_params.param["band"][d] wav_resolution = bp["res_type"] if self.device.type == "mps": wav_resolution = "polyphase" if d == bands_n: X_wave[d] = librosa.resample( y=self.input_mix, orig_sr=self.sample_rate, target_sr=bp["sr"], res_type=wav_resolution, ) X_spec_s[d] = spec_utils.wave_to_spectrogram( X_wave[d], bp["hl"], bp["n_fft"], self.model.model_params, band=d, is_v51_model=self.config.model.is_vr5, ) if X_wave[d].ndim == 1: X_wave[d] = np.asarray([X_wave[d], X_wave[d]]) else: X_wave[d] = librosa.resample( X_wave[d + 1], orig_sr=self.model.model_params.param["band"][d + 1]["sr"], target_sr=bp["sr"], res_type=wav_resolution, ) X_spec_s[d] = spec_utils.wave_to_spectrogram( X_wave[d], bp["hl"], bp["n_fft"], self.model.model_params, band=d, is_v51_model=self.config.model.is_vr5, ) if d == bands_n and high_end_process: input_high_end_h = (bp["n_fft"] // 2 - bp["crop_stop"]) + ( self.model.model_params.param["pre_filter_stop"] - self.model.model_params.param["pre_filter_start"] ) input_high_end = X_spec_s[d][ :, bp["n_fft"] // 2 - input_high_end_h : bp["n_fft"] // 2, : ] X_spec = spec_utils.combine_spectrograms( X_spec_s, self.model.model_params, is_v51_model=self.config.model.is_vr5 ) del X_wave, X_spec_s def spec_to_wav(spec, high_end_process, input_high_end, input_high_end_h): if ( high_end_process and isinstance(input_high_end, np.ndarray) and input_high_end_h is not None # Check if it's not None ): input_high_end_ = spec_utils.mirroring( "mirroring", spec, input_high_end, self.model.model_params ) wav = spec_utils.cmb_spectrogram_to_wave( spec, self.model.model_params, input_high_end_h, input_high_end_, is_v51_model=self.config.model.is_vr5, ) else: wav = spec_utils.cmb_spectrogram_to_wave( spec, self.model.model_params, is_v51_model=self.config.model.is_vr5 ) return wav def _execute(X_mag_pad: np.ndarray, roi_size: int) -> np.ndarray: X_dataset = [] patches = (X_mag_pad.shape[2] - 2 * self.model.offset) // roi_size for i in tqdm(range(patches), desc=_i18n("processing") + str(add_text), unit=_i18n("patches")): start = i * roi_size X_mag_window = X_mag_pad[:, :, start : start + window_size] X_dataset.append(X_mag_window) X_dataset = np.asarray(X_dataset) self.model.eval() with torch.no_grad(): mask = [] for i in tqdm(range(0, patches, batch_size), desc=_i18n("processing") + str(add_text), unit=_i18n("chunks")): X_batch = X_dataset[i : i + batch_size] X_batch = torch.from_numpy(X_batch).to(self.device) pred = self.model.predict_mask(X_batch) if not pred.size()[3] > 0: raise ValueError( _i18n("window_size_error") ) pred = pred.detach().cpu().numpy() pred = np.concatenate(pred, axis=2) mask.append(pred) if len(mask) == 0: raise ValueError( _i18n("window_size_error") ) mask = np.concatenate(mask, axis=2) return mask def postprocess( mask: np.ndarray, X_mag: np.ndarray, X_phase: np.ndarray ) -> Tuple[np.ndarray, np.ndarray]: is_non_accom_stem = False for stem in NON_ACCOM_STEMS: if stem == primary_stem.lower(): is_non_accom_stem = True if mask.shape[0] == 4: print(_i18n("vr_aggr_and_post_process_not_applied_vr_6")) if self.model.is_complex: y_spec = X_spec * mask[:2] v_spec = X_spec * mask[2:] else: X_mag1 = np.abs(X_spec) X_phase1 = np.exp(1.j * np.angle(X_spec)) y_spec = X_mag1 * mask[:2] * X_phase1 v_spec = X_mag1 * mask[2:] * X_phase1 else: mask = spec_utils.adjust_aggr(mask, is_non_accom_stem, aggressiveness) if enable_post_process: mask = spec_utils.merge_artifacts( mask, thres=post_process_threshold ) y_spec = mask * X_mag * np.exp(1.0j * X_phase) v_spec = (1 - mask) * X_mag * np.exp(1.0j * X_phase) return y_spec, v_spec X_mag, X_phase = spec_utils.preprocess(X_spec) n_frame = X_mag.shape[2] pad_l, pad_r, roi_size = spec_utils.make_padding( n_frame, window_size, self.model.offset ) X_mag_pad = np.pad(X_mag, ((0, 0), (0, 0), (pad_l, pad_r)), mode="constant") mag_max = X_mag_pad.max() X_mag_pad /= mag_max if hasattr(self.model, 'is_complex') and self.model.is_complex: X_spec_pad = np.pad(X_spec, ((0, 0), (0, 0), (pad_l, pad_r)), mode="constant") X_spec_pad /= mag_max mask = _execute(X_spec_pad, roi_size) else: mask = _execute(X_mag_pad, roi_size) mask = mask[:, :, :n_frame] y_spec, v_spec = postprocess(mask, X_mag, X_phase) y_spec = np.nan_to_num(y_spec, nan=0.0, posinf=0.0, neginf=0.0) v_spec = np.nan_to_num(v_spec, nan=0.0, posinf=0.0, neginf=0.0) primary_stem_array = spec_to_wav(y_spec, high_end_process, input_high_end, input_high_end_h) primary_stem_array = librosa.resample( primary_stem_array, orig_sr=model_sample_rate, target_sr=sr, ).T secondary_stem_array = spec_to_wav(v_spec, high_end_process, input_high_end, input_high_end_h) secondary_stem_array = librosa.resample( secondary_stem_array, orig_sr=model_sample_rate, target_sr=sr, ).T self.output_arrays = { primary_stem: primary_stem_array, secondary_stem: secondary_stem_array, } del X_spec, X_mag, X_phase, X_mag_pad, mask del y_spec, v_spec, primary_stem_array, secondary_stem_array elif self.model_type == "htdemucs": mix = torch.tensor(self.input_mix, dtype=torch.float32) segment_sec: int = self.add_params.get("demucs_segment", 10) denoise: bool = self.add_params.get("demucs_denoise", False) num_overlap = self.add_params.get("demucs_overlap", 2) batch_size: int = self.add_params.get("demucs_batch_size", 1) if self.add_params.get("demucs_override_segment", False): chunk_size = self.config.training.samplerate * segment_sec else: chunk_size = getattr(self.config.training, "segment", 10) * self.config.training.samplerate num_instruments = len(self.instruments) step = chunk_size // num_overlap fade_size = chunk_size // 10 windowing_array = _getWindowingArray(chunk_size, fade_size) use_amp = getattr(self.config.training, "use_amp", True) with torch.inference_mode(): req_shape = (num_instruments,) + mix.shape result = torch.zeros(req_shape, dtype=torch.float32) counter = torch.zeros(req_shape, dtype=torch.float32) i = 0 batch_data = [] batch_locations = [] denoise_str = " "+_i18n("denoise") if denoise else "" with tqdm(total=mix.shape[1], desc=_i18n("processing") + denoise_str + str(add_text), unit=_i18n("samples")) as progress_bar: while i < mix.shape[1]: part = mix[:, i : i + chunk_size].to(self.device) chunk_len = part.shape[-1] pad_mode = "reflect" if chunk_len > chunk_size // 2 else "constant" part = nn.functional.pad( part, (0, chunk_size - chunk_len), mode=pad_mode, value=0 ) batch_data.append(part) batch_locations.append((i, chunk_len)) i += step if len(batch_data) >= batch_size or i >= mix.shape[1]: arr = torch.stack(batch_data, dim=0) if denoise: x1 = self.model(arr) x2 = self.model(-arr) x = (x1 + -x2) * 0.5 else: x = self.model(arr) window = windowing_array.clone() if i - step == 0: window[:fade_size] = 1 elif i >= mix.shape[1]: window[-fade_size:] = 1 for j, (start, seg_len) in enumerate(batch_locations): result[..., start : start + seg_len] += ( x[j, ..., :seg_len].cpu() * window[..., :seg_len] ) counter[..., start : start + seg_len] += window[..., :seg_len] batch_data.clear() batch_locations.clear() progress_bar.update(step) estimated_sources = result / counter estimated_sources = estimated_sources.detach().cpu().numpy() np.nan_to_num(estimated_sources, copy=False, nan=0.0) if num_instruments <= 1: self.output_arrays = estimated_sources else: instruments = self.instruments self.output_arrays = {k: v for k, v in zip(instruments, estimated_sources)} del mix, result, counter, batch_data, batch_locations if denoise: del estimated_sources, x, x1, x2 else: del estimated_sources, x elif self.model_type == "medley_vox": import pyloudnorm as pyln from models.medley_vox.loudness_utils import loudnorm, db2linear if self.add_params.get("mvox_override_segment", False): segment_sec: int = self.add_params.get("mvox_segment", self.config.model.seq_dur) else: segment_sec = self.config.model.seq_dur overlap: int = self.add_params.get("mvox_overlap", 2) stems: List[str] = self.instruments if self.input_mix.ndim == 1: self.input_mix = np.expand_dims(self.input_mix, axis=0) num_channels = 1 elif self.input_mix.ndim == 2: if self.input_mix.shape[0] <= self.input_mix.shape[1]: num_channels = self.input_mix.shape[0] else: self.input_mix = self.input_mix.T num_channels = self.input_mix.shape[0] samplerate = self.config.model.sample_rate chunk_size = int(samplerate * segment_sec) step = chunk_size // overlap fade_size = chunk_size // 10 n_src = self.config.model.n_src # Получаем количество источников из конфига meter = pyln.Meter(samplerate) try: if num_channels > 1: mix_for_loudnorm = self.input_mix.T else: mix_for_loudnorm = self.input_mix[0] mixture_d, adjusted_gain = loudnorm(mix_for_loudnorm, -24.0, meter) if num_channels > 1: if isinstance(mixture_d, np.ndarray) and mixture_d.ndim == 2: mixture_d = mixture_d.T else: mixture_d = np.tile(mixture_d, (num_channels, 1)) else: if mixture_d.ndim == 1: mixture_d = mixture_d.reshape(1, -1) except Exception as e: print(_i18n("loudnorm_error", error=str(e))) mixture_d = mix.copy() rms = np.sqrt(np.mean(mix**2)) target_rms = 0.1 if rms > 0: adjusted_gain = 20 * np.log10(target_rms / rms) mixture_d = mix * (target_rms / rms) else: adjusted_gain = 0 length_init = mixture_d.shape[1] windowing_array = _getWindowingArray(chunk_size, fade_size).to(self.device) result_stems = {stem: np.zeros((num_channels, length_init), dtype=np.float32) for stem in stems} mix_tensor = torch.tensor(mixture_d, dtype=torch.float32).to(self.device) counters = {stem: torch.zeros((num_channels, length_init), dtype=torch.float32, device=self.device) for stem in stems} i = 0 with tqdm(total=length_init, desc=_i18n("processing") + str(add_text), unit=_i18n("samples")) as progress_bar: while i < length_init: end_idx = min(i + chunk_size, length_init) chunk = mix_tensor[:, i:end_idx] cur_chunk_len = chunk.shape[1] chunk_results = torch.zeros((num_channels, n_src, cur_chunk_len), dtype=torch.float32, device=self.device) for ch in range(num_channels): channel_chunk = chunk[ch:ch+1, :] if cur_chunk_len < chunk_size: pad_len = chunk_size - cur_chunk_len channel_chunk = torch.nn.functional.pad( channel_chunk, (0, pad_len), mode='constant', value=0 ) channel_chunk = channel_chunk.unsqueeze(0) with torch.no_grad(): out_chunk = self.model.separate(channel_chunk) chunk_results[ch, :, :cur_chunk_len] = out_chunk[0, :, :cur_chunk_len].cpu() window = windowing_array[:cur_chunk_len].clone() if i == 0: window[:fade_size] = 1 if end_idx >= length_init: window[-fade_size:] = 1 for stem_idx, stem in enumerate(stems): result_stems[stem][:, i:end_idx] += chunk_results[:, stem_idx, :].cpu().numpy() * window.cpu().numpy() counters[stem][:, i:end_idx] += window i += step progress_bar.update(step) for stem in stems: counters_np = counters[stem].detach().cpu().numpy() mask = counters_np > 0 result_stems[stem][mask] /= counters_np[mask] result_stems[stem] = result_stems[stem] * db2linear(-adjusted_gain) self.output_arrays = result_stems del mix_tensor, mixture_d, counters del result_stems, chunk_results del meter else: mix = torch.tensor(self.input_mix, dtype=torch.float32).to(self.device) segment: int = self.add_params.get("mdxc_segment_size", 256) if hasattr(self.config, "model"): if hasattr(self.config.model, "stft_hop_length"): hop_length = self.config.model.stft_hop_length elif hasattr(self.config.model, "hop_size"): hop_length = self.config.model.hop_size elif hasattr(self.config.model, "hop_length"): hop_length = self.config.model.hop_length if hasattr(self.config, "audio"): if hasattr(self.config.audio, "hop_length"): hop_length = self.config.audio.hop_length if hasattr(self.config, "kwargs"): if hasattr(self.config.kwargs, "hop_length"): hop_length = self.config.kwargs.hop_length if self.add_params.get("mdxc_override_segment", False): chunk_size = int(hop_length) * (int(segment) - 1) else: chunk_size = self.config.audio.chunk_size instruments = self.prefer_target_instrument() num_instruments = len(instruments) denoise: bool = self.add_params.get("mdxc_denoise", False) num_overlap: int = self.add_params.get("mdxc_overlap", 2) fade_size = chunk_size // 10 step = chunk_size // num_overlap border = chunk_size - step length_init = mix.shape[-1] windowing_array = _getWindowingArray(chunk_size, fade_size) if length_init > 2 * border and border > 0: mix = nn.functional.pad(mix, (border, border), mode="reflect") batch_size: int = self.add_params.get("mdxc_batch_size", 1) use_amp = getattr(self.config.training, "use_amp", True) with torch.inference_mode(), get_autocast_context(self.device.type, use_amp): req_shape = (num_instruments,) + mix.shape result = torch.zeros(req_shape, dtype=torch.float32) counter = torch.zeros(req_shape, dtype=torch.float32) i = 0 batch_data = [] batch_locations = [] denoise_str = " "+_i18n("denoise") if denoise else "" with tqdm(total=mix.shape[1], desc=_i18n("processing") + denoise_str + str(add_text), unit=_i18n("samples")) as progress_bar: while i < mix.shape[1]: part = mix[:, i : i + chunk_size].to(self.device) chunk_len = part.shape[-1] pad_mode = "reflect" if chunk_len > chunk_size // 2 else "constant" part = nn.functional.pad( part, (0, chunk_size - chunk_len), mode=pad_mode, value=0 ) batch_data.append(part) batch_locations.append((i, chunk_len)) i += step if len(batch_data) >= batch_size or i >= mix.shape[1]: arr = torch.stack(batch_data, dim=0) if denoise: x1 = self.model(arr) x2 = self.model(-arr) x = (x1 + -x2) * 0.5 else: x = self.model(arr) window = windowing_array.clone() if i - step == 0: window[:fade_size] = 1 elif i >= mix.shape[1]: window[-fade_size:] = 1 for j, (start, seg_len) in enumerate(batch_locations): result[..., start : start + seg_len] += ( x[j, ..., :seg_len].cpu() * window[..., :seg_len] ) counter[..., start : start + seg_len] += window[..., :seg_len] batch_data.clear() batch_locations.clear() progress_bar.update(step) estimated_sources = result / counter estimated_sources = estimated_sources.detach().cpu().numpy() np.nan_to_num(estimated_sources, copy=False, nan=0.0) if length_init > 2 * border and border > 0: estimated_sources = estimated_sources[..., border:-border] self.output_arrays = {k: v for k, v in zip(instruments, estimated_sources)} del mix, result, counter, batch_data, batch_locations if denoise: del estimated_sources, x, x1, x2 else: del estimated_sources, x self.add_second_stem() return def add_second_stem(self): if self.target_instrument: second_stem = [instrument for instrument in self.instruments if instrument != self.target_instrument][0] self.output_arrays[second_stem] = subtractor(self.input_mix, self.output_arrays[self.target_instrument], self.sample_rate, self.sample_rate, spectrogram=self.use_spec_invert)[0] print(_i18n("added_second_stem") + ": " + second_stem) else: return def delete_unselected_stems(self, selected_stems: list): if selected_stems: output_keys = list(self.output_arrays.keys()) deleted_keys = [] for stem in output_keys: if stem not in selected_stems: self.output_arrays[stem] = None del self.output_arrays[stem] deleted_keys.append(stem) print(_i18n("deleted_stems") + f": " + ",".join(deleted_keys)) else: return def extract_instrumental(self, extract_instrumental: bool, selected_stems: list = [], return_: bool = False, invert_plus: bool = False): if extract_instrumental: self.output_arrays["invert"] = self.input_mix.copy() if self.output_arrays: unselected_keys = [key_1 for key_1 in self.output_arrays if key_1 not in [*selected_stems, "invert"]] if invert_plus and unselected_keys: print(_i18n("invert_plus_applied")) self.output_arrays["invert"] = ensemble([self.output_arrays[stem] for stem in unselected_keys], [self.sample_rate] * len(unselected_keys), "max_fft")[0] self.delete_unselected_stems([*selected_stems, "invert"]) else: self.delete_unselected_stems([*selected_stems, "invert"]) for stem in selected_stems: self.output_arrays["invert"] = subtractor(self.output_arrays["invert"], self.output_arrays[stem], self.sample_rate, self.sample_rate, spectrogram=self.use_spec_invert)[0] else: self.delete_unselected_stems(selected_stems) if return_: return self.output_arrays["invert"] def write(self, template: str, format_return: str = "name_stems_list", prefer_float: bool = False): model_name = self.ckpt_path.stem if "COMMENT" in self.input_file_metadata: orig_comment = self.input_file_metadata["COMMENT"] else: orig_comment = "" comment = "" if self.add_params: list_add_params_current_model_type = parse_model_type_add_params(self.model_type) if orig_comment: comment += orig_comment + "\n\n" comment += f"{_i18n('separation_params')} [{model_name}]:\n\n" for param, param_value in self.add_params.items(): if param in list_add_params_current_model_type: comment += f"{_i18n(param)} / {(_i18n('yes') if param_value == True else _i18n('no')) if isinstance(param_value, bool) else param_value}\n" results = [] writed_stems = [] print(_i18n("format_return") + ": " + _i18n(format_return)) for stem, array in tqdm(self.output_arrays.items(), desc=_i18n("writing"), unit=_i18n('files')): custom_name = Namer.template( template, STEM=stem, MODEL=model_name, NAME=Namer.short_input_name_template(template, STEM=stem, MODEL=model_name, NAME=self.input_file_name) ) writed_stems.append([stem, write(Namer.iter(self.output_dir / f"{custom_name}.{self.output_format}"), array, self.sample_rate, 320, prefer_float, generate_metadata_from_stem(self.input_file_name, self.input_file_metadata, stem, model_name, comment))]) if writed_stems: match format_return: case "name_stems_list": results = [self.input_file_name, writed_stems] case "stems_list": results = writed_stems case "stems_list_append_self": self.output_files_list.append(writed_stems) case "name_stems_list_append_self": self.output_files_list.append([self.input_file_name, writed_stems]) return results def clear_mix(self): self.input_file_name = None self.input_mix = None self.input_file_metadata = {} self.output_arrays.clear() def clear_outputs(self): self.output_files_list.clear() def get_outputs(self): return self.output_files_list def _process(self, i: int, total: int, path: str, template: str, selected_stems: list = [], extract_instrumental: bool = True, invert_plus: bool = False, prefer_float: bool = False): template = Namer.sanitize(template) template = Namer.dedup_template(template, keys=["NAME", "MODEL", "STEM"]) template = Namer.short(template, length=40) self.clear_mix() self.load_mix(path) try: self.demix(f" | {i}/{total} {_i18n('files')}") except Exception as e: self.clear_mix() raise DemixError(_i18n("demix_error", error=e)) from e self.extract_instrumental(extract_instrumental, selected_stems=selected_stems, invert_plus=invert_plus) self.write(template, "name_stems_list_append_self", prefer_float) self.clear_mix() def _process_array_ensemble(self, i: int, total: int, array: np.ndarray, sr: int, primary_stem: str | None = None, invert: bool = False): self.clear_mix() self.load_array(array, sr) try: self.demix(f" | {i}/{total} {_i18n('models')} | {self.ckpt_path.stem}") except Exception as e: self.clear_mix() raise DemixError(_i18n("demix_error", error=e)) from e if invert: result = self.extract_instrumental(True, selected_stems=[primary_stem], return_=True) else: result = self.output_arrays[primary_stem] return result, self.sample_rate def _process_array(self, i: int, total: int, array: np.ndarray, sr: int): self.clear_mix() self.load_array(array, sr) try: self.demix(f" | {i}/{total} {_i18n('arrays')} | {self.ckpt_path.stem}") except Exception as e: self.clear_mix() raise DemixError(_i18n("demix_error", error=e)) from e return self.output_arrays, self.sample_rate def _process_array_iter_ensemble(self, i: int, total: int, iter_index: int, iter_total: int, array: np.ndarray, sr: int, primary_stem: str | None = None, invert: bool = False): self.clear_mix() self.load_array(array, sr) try: self.demix(f" | {i}/{total} {_i18n('models')} | {self.ckpt_path.stem} | {_i18n('iteration')} {iter_index}/{iter_total}") except Exception as e: self.clear_mix() raise DemixError(_i18n("demix_error", error=e)) from e if invert: result = self.extract_instrumental(True, selected_stems=[primary_stem], return_=True) else: result = self.output_arrays[primary_stem] return result, self.sample_rate def load_model(self, model_type: str, ckpt: str | Path, conf: str | Path): self.clear_model() self.load_config(model_type=model_type, conf=conf) self.load_model_instance() self.load_checkpoint(ckpt=ckpt) def inference(self, input: str | list, /, *inputs, template: str = "NAME_MDOEL_STEM", selected_stems: list = [], extract_instrumental: bool = False, invert_plus: bool = False, prefer_float: bool = False): self.clear_outputs() all_inputs = [] if isinstance(input, list): all_inputs.extend(input) else: all_inputs.append(input) if inputs: all_inputs.extend(inputs) total = len(all_inputs) for i, input_file in enumerate(all_inputs, start=1): try: self._process(i, total, input_file, template=template, selected_stems=selected_stems, extract_instrumental=extract_instrumental, invert_plus=invert_plus, prefer_float=prefer_float) except Exception as e: traceback.print_exc() return self.get_outputs() class ModelManager: def __init__(self): self.info = {} self.info_url = "https://huggingface.co/noblebarkrr/mvsepless_resources/resolve/main/models.json?download=true" self.info_path = Path(BASE_DIR) / "models.json" self.load_info() self.cache_dir = Path(BASE_DIR) / "separation_cache" self.cache_dir.mkdir(parents=True, exist_ok=True) # Убираем lambda-функции, заменяем на обычные методы def get_all_models(self): return [mn for mn in self.info] def get_stems(self, model_name): return [stem for stem in self.info.get(model_name, {}).get("stems", [])] def get_target_instrument(self, model_name): return self.info.get(model_name, {}).get("target_instrument", None) def get_model_type(self, model_name): return self.info.get(model_name, {}).get("model_type", "") def get_links(self, model_name): return (self.info.get(model_name, {}).get("checkpoint_url", None), self.info.get(model_name, {}).get("config_url", None)) def generate_local_paths(self, model_name): return (self.cache_dir / f"{model_name}.ckpt", self.cache_dir / f"{model_name}_config.yaml") def check_installed(self, model_name): return [path.exists() for path in self.generate_local_paths(model_name)] def check_installed2(self, model_name): return all(self.check_installed(model_name)) def load_info(self): self.info = json.loads(self.info_path.read_text("utf-8")) def show_info(self, limit: int = None, stem: str = None, only_installed: bool = False): models = [] if stem: models = [ model for model in self.get_all_models() if (stem in self.get_stems(model) or stem.lower() in self.get_stems(model) or stem.upper() in self.get_stems(model) or stem.capitalize() in self.get_stems(model) or stem.title() in self.get_stems(model)) ] else: models = self.get_all_models() if only_installed: models = [model for model in models if self.check_installed2(model)] if limit: models = models[:limit] console = rich.console.Console() table = rich.table.Table(title=_i18n("model_info"), show_lines=True) table.add_column(_i18n("model_name"), no_wrap=True) table.add_column(_i18n("output_stems")) table.add_section() table.add_row(_i18n("table_model_info_installed_legend"), _i18n("table_model_info_target_instrument_legend")) table.add_section() if models: for model_ in models: target_instrument = self.get_target_instrument(model_) stems = self.get_stems(model_) if target_instrument: for i, stem in enumerate(stems): if stem == target_instrument: stems[i] = f"[green]{stem}[/]" stems_str = ", ".join(stems) table.add_row(f"[green]{model_}[/]" if self.check_installed2(model_) else model_, stems_str) else: table.add_row(_i18n("na"), _i18n("na")) console.print(table) def update_info(self, alt1): dw_file("https://modelscope.cn/models/noblebarkrr/mvsepless_resources/resolve/master/models_alt1.json" if alt1 else self.info_url, self.info_path) print(_i18n("model_info_updated")) def download(self, model_name: str): status = "" urls = self.get_links(model_name) local_paths = self.generate_local_paths(model_name) local_exists = self.check_installed(model_name) for url, local_path, exists in zip(urls, local_paths, local_exists): if not exists: dw_file(url, local_path) if all(local_exists): status = _i18n("model_already_downloaded") else: status = _i18n("model_downloaded") print(status) return status class Ensembler: def __init__(self): self.arrays = [] self.srs = [] def add_array(self, y: np.ndarray, sr: int): self.arrays.append(y) self.srs.append(sr) def get_arrays(self): return self.arrays def get_srs(self): return self.srs def clear(self): self.arrays.clear() class Separator(ModelManager): def __init__(self): super().__init__() @hf_spaces_gpu # (duration=120) Для спейса LongQuota / длинная квота на HuggingFace ZeroGPU (по умолчанию 60 секунд) def separate_base( self, mssi: MSSI, input_valid_files: list[str | Path], model_name: str, template: str, checkpoint: str | Path, config: str | Path, selected_stems: list, extract_instrumental: bool, invert_plus: bool, prefer_float: bool ): mssi.clear_model() mssi.load_model(self.get_model_type(model_name), checkpoint, config) mssi.print_instruments() selected_stems = mssi.validate_selected_instruments(selected_stems) results = mssi.inference(input_valid_files, template=template, selected_stems=selected_stems, extract_instrumental=extract_instrumental, invert_plus=invert_plus, prefer_float=prefer_float) mssi.clear_model() return results def separate( self, input_files: list[str | Path], output_dir: str | Path = Path("."), output_format: str = output_formats[0], template: str = "NAME_(STEM)_MODEL", model_name: str = "bs_6stem", extract_instrumental: bool = False, use_spec_invert: bool = False, invert_plus: bool = False, prefer_float: bool = False, selected_stems: list = [], add_params: dict = {} ): if not output_dir: output_dir = "" input_valid_files = get_audio_files_from_list(input_files, only_files=False) if not input_valid_files: raise PathsNotSpecified(_i18n("paths_not_specified")) mssi = MSSI() mssi.settings(output_dir=output_dir, output_format=output_format, use_spec_invert=use_spec_invert) mssi.set_add_params(**add_params) self.download(model_name) checkpoint, config = self.generate_local_paths(model_name) results = self.separate_base(mssi, input_valid_files, model_name, template, checkpoint, config, selected_stems, extract_instrumental, invert_plus, prefer_float) return results @hf_spaces_gpu # (duration=120) Для спейса LongQuota / длинная квота на HuggingFace ZeroGPU (по умолчанию 60 секунд) def custom_separate( self, input_files: list, output_dir: str | Path = Path("."), output_format: str = output_formats[0], template: str = "NAME_(STEM)_MODEL", model_type: str = "bs_roformer", ckpt: str = "model.ckpt", conf: str = "conf.ckpt", extract_instrumental: bool = False, use_spec_invert: bool = False, invert_plus: bool = False, prefer_float: bool = False, selected_stems: list = [], add_params: dict = {} ): if not output_dir: output_dir = "" input_valid_files = get_audio_files_from_list(input_files, only_files=False) if not input_valid_files: raise PathsNotSpecified(_i18n("paths_not_specified")) checkpoint, config = Path(ckpt), Path(conf) model_name = checkpoint.stem mssi = MSSI() mssi.settings(output_dir=output_dir, output_format=output_format, use_spec_invert=use_spec_invert) mssi.set_add_params(**add_params) mssi.clear_model() mssi.load_model(model_type, checkpoint, config) self.previous_model_name = model_name mssi.print_instruments() selected_stems = mssi.validate_selected_instruments(selected_stems) results = mssi.inference(input_valid_files, template=template, selected_stems=selected_stems, extract_instrumental=extract_instrumental, invert_plus=invert_plus, prefer_float=prefer_float) mssi.clear_model() del mssi return results def print_flow(self, flow): """Print current ensemble flow in a formatted table (like show_info)""" if not flow: return console = rich.console.Console() table = rich.table.Table(title="", show_lines=True) table.add_column("#", style="cyan", no_wrap=True) table.add_column(_i18n("model_name")) table.add_column(_i18n("primary_stem")) table.add_column(_i18n("invert")) table.add_column(_i18n("weights"), justify="right") for idx, (model_name, primary_stem, invert, weight) in enumerate(flow, start=1): invert_str = _i18n("yes") if invert else _i18n("no") table.add_row( str(idx), model_name, primary_stem, invert_str, f"{weight:.2f}" if isinstance(weight, (int, float)) else str(weight) ) console.print(table) def print_flow_iter(self, flow): """Print current ensemble flow in a formatted table (like show_info)""" if not flow: return console = rich.console.Console() table = rich.table.Table(title="", show_lines=True) table.add_column("#", style="cyan", no_wrap=True) table.add_column(_i18n("model_name")) table.add_column(_i18n("primary_stem")) table.add_column(_i18n("invert")) for idx, (model_name, primary_stem, invert) in enumerate(flow, start=1): invert_str = _i18n("yes") if invert else _i18n("no") table.add_row( str(idx), model_name, primary_stem, invert_str ) console.print(table) def validate_flow(self, flow: list | tuple, non_exists_warn: bool = False, iterative: bool = False): """ Валидация потока моделей для ансамбля Args: flow: Список кортежей/списков вида [model_name, primary_stem, invert] или [model_name, primary_stem, invert, weight] Returns: list: Валидированный поток моделей Raises: EnsembleFlowValidateError: Если есть ошибки валидации """ models = self.get_all_models() errors = [] warns = [] validated_flow = [] if not flow: raise EnsembleFlowValidateError(_i18n("flow_empty")) if not isinstance(flow, (list, tuple)): raise EnsembleFlowValidateError( _i18n("flow_validation_error", error=_i18n("flow_not_list")) ) for i, model_flow in enumerate(flow): valid = True error_parts = [] if not isinstance(model_flow, (list, tuple)): errors.append({ 'index': i, 'error': _i18n("flow_item_not_list", type=type(model_flow).__name__) }) continue if len(model_flow) not in (3, 4): errors.append({ 'index': i, 'error': _i18n("flow_invalid_length", length=len(model_flow), expected="3 or 4") }) continue if len(model_flow) == 3: model_name, primary_stem, invert = model_flow weight = 1.0 has_weight = False else: model_name, primary_stem, invert, weight = model_flow has_weight = True if not isinstance(model_name, str): error_parts.append(_i18n("flow_invalid_type", field="model_name", expected="str", got=type(model_name).__name__)) valid = False model_exists = False else: model_exists = model_name in models if not model_exists: if non_exists_warn: warns.append(_i18n("flow_model_not_found", model=model_name, available="self.get_all_models()")) else: error_parts.append(_i18n("flow_model_not_found", model=model_name, available="self.get_all_models()")) valid = False # Валидация primary_stem if not isinstance(primary_stem, str): error_parts.append(_i18n("flow_invalid_type", field="primary_stem", expected="str", got=type(primary_stem).__name__)) valid = False elif model_exists: available_stems = self.get_stems(model_name) if primary_stem not in available_stems: if non_exists_warn: warns.append(_i18n("flow_stem_not_found", stem=primary_stem, model=model_name, available=', '.join(available_stems))) else: error_parts.append(_i18n("flow_stem_not_found", stem=primary_stem, model=model_name, available=', '.join(available_stems))) valid = False if not isinstance(invert, bool): error_parts.append(_i18n("flow_invalid_type", field="invert", expected="bool", got=type(invert).__name__)) valid = False if has_weight: try: if isinstance(weight, str): try: weight = float(weight) except ValueError: error_parts.append(_i18n("flow_weight_empty")) valid = False weight = 1.0 elif not isinstance(weight, (int, float)): error_parts.append(_i18n("flow_invalid_type", field="weight", expected="number", got=type(weight).__name__)) valid = False weight = 1.0 else: weight = float(weight) except (ValueError, TypeError): error_parts.append(_i18n("flow_weight_invalid", value=weight)) valid = False weight = 1.0 if valid: if iterative: if has_weight: validated_flow.append([model_name, primary_stem, invert]) else: validated_flow.append([model_name, primary_stem, invert]) else: if has_weight: validated_flow.append([model_name, primary_stem, invert, weight]) else: validated_flow.append([model_name, primary_stem, invert, 1.0]) else: errors.append({ 'index': i, 'error': '; '.join(error_parts), 'raw': model_flow }) for error in errors: if 'raw' in error: print(f"{error['raw']} <-- {error['error']}") else: print(f"[{error['index']}] <-- {error['error']}") if errors: error_messages = [ f"#{e['index']}: {e['error']}" for e in errors ] raise EnsembleFlowValidateError( _i18n("flow_validation_errors", count=len(errors)) + "\n" + "\n".join(error_messages) ) warns_str = "\n".join(warns) if warns else "" return validated_flow, warns_str @hf_spaces_gpu # (duration=120) Для спейса LongQuota / длинная квота на HuggingFace ZeroGPU (по умолчанию 60 секунд) def auto_ensemble_base( self, mssi: MSSI, model_name: str, checkpoint: str | Path, config: str | Path, i: int, model_count: int, mix: np.ndarray, orig_sr: int, primary_stem: str, invert: bool ): mssi.clear_model() mssi.load_model(self.get_model_type(model_name), checkpoint, config) mssi.print_instruments() output, model_sr = mssi._process_array_ensemble(i, model_count, mix, orig_sr, primary_stem, invert) mssi.clear_model() return output, model_sr def auto_ensemble( self, input_file: str | Path, output_dir: str | Path = Path("."), flow: list[list[str | bool | int | float]] = [], template: str = "NAME_TYPE_COUNT", etype: str = ensemble_types[0], output_format: str = output_formats[0], use_spec_invert: bool = False, save_primary_stems: bool = False, prefer_float: bool = False, ) -> tuple[str, str, list[str]]: if not output_dir: output_dir = "" if not input_file: raise PathNotSpecified(_i18n("path_not_specified")) input_file = Path(input_file) if not input_file.exists(): raise PathNotExist(_i18n("path_not_exist")) if not check(input_file): raise FileIsNotAudio(_i18n("file_is_not_audio", path=input_file)) if not flow: print(_i18n("flow_empty")) gr.Info(title=_i18n("flow_empty"), message="") return None, None, [] flow, warns = self.validate_flow(flow) self.print_flow(flow) output_dir = Path(output_dir) output_dir.mkdir(parents=True, exist_ok=True) model_count = len(flow) print(_i18n("ensemble_type")+": "+etype) print(_i18n("ensemble_models_count")+": "+str(model_count)) input_mix, orig_sr = read(input_file, sr=44100) metadata = get_metadata(input_file) if "COMMENT" in metadata: orig_comment = metadata["COMMENT"] else: orig_comment = "" comment = "" if flow: if orig_comment: comment += orig_comment + "\n\n" comment += f"{_i18n('ensemble_settings')}:\n\n" for (mn, pr_stem, invert, weight) in flow: comment += f"{mn} / {pr_stem} / {invert} / {weight}\n" template = Namer.sanitize(template) template = Namer.dedup_template(template, keys=["NAME", "TYPE", "COUNT"]) template = Namer.short(template, length=40) invert_key = "_invert" custom_name = Namer.template( template, TYPE=etype, COUNT=model_count, NAME=Namer.short_input_name_template(template, TYPE=etype, COUNT=model_count, NAME=input_file.stem) ) auto_ensembler = Ensembler() weights = [] saved_primary_stems = [] mssi = MSSI() mssi.set_add_params(**{"demucs_denoise": True, "mdx_denoise": True}) for i, (model_name, primary_stem, invert, weight) in enumerate(flow, start=1): print(f"{_i18n('model')} {i}/{model_count}") try: self.download(model_name) checkpoint, config = self.generate_local_paths(model_name) output, model_sr = self.auto_ensemble_base(mssi, model_name, checkpoint, config, i, model_count, input_mix, orig_sr, primary_stem, invert) auto_ensembler.add_array(output, model_sr) weights.append(weight) if save_primary_stems: primary_stem_file_name = primary_stem + (invert_key if invert else "") primary_stem_path = write(Namer.iter(output_dir / model_name / f"{model_name}_{primary_stem_file_name}.flac"), output, model_sr, 320, prefer_float) saved_primary_stems.append(primary_stem_path) except Exception as e: print(_i18n("error_occured_separation")+": "+str(e)) gr.Warning(message=""+f'{_i18n("error_occured_separation")}'.replace("\n", "
")+": "+str(e)+"
", title="") continue extracted_primary_stems = auto_ensembler.get_arrays() srs = auto_ensembler.get_srs() output_array, sr_ = ensemble(extracted_primary_stems, srs, etype, weights) extracted_primary_stems = None auto_ensembler.clear() auto_ensembler, output = None, None del auto_ensembler, output, mssi inverted_array, i_sr = subtractor(input_mix, output_array, orig_sr, sr_, spectrogram=use_spec_invert) new_metadata = {} new_metadata_invert = {} if metadata: new_metadata = deepcopy(metadata) if "TITLE" in metadata: new_metadata["TITLE"] = f"[{etype} / {model_count}] {metadata['TITLE']}" new_metadata_invert["TITLE"] = f"[{etype} / {model_count} ({_i18n('invert')})] {metadata['TITLE']}" else: new_metadata["TITLE"] = f"[{etype} / {model_count}] {input_file.stem}" new_metadata_invert["TITLE"] = f"[{etype} / {model_count} ({_i18n('invert')})] {input_file.stem}" if "COMMENT" in metadata: new_metadata["COMMENT"] = comment else: new_metadata["COMMENT"] = comment else: new_metadata["TITLE"] = f"[{etype} / {model_count}] {input_file.stem}" new_metadata_invert["TITLE"] = f"[{etype} / {model_count} ({_i18n('invert')})] {input_file.stem}" new_metadata["COMMENT"] = comment return write(Namer.iter(output_dir / f"{custom_name}.{output_format}"), output_array, sr_, 320, prefer_float, new_metadata), write(Namer.iter(output_dir / f"{Namer.short(custom_name+invert_key)}.{output_format}"), inverted_array, i_sr, 320, prefer_float, new_metadata_invert), saved_primary_stems @hf_spaces_gpu # (duration=120) Для спейса LongQuota / длинная квота на HuggingFace ZeroGPU (по умолчанию 60 секунд) def iterative_ensemble_base(self, mssi: MSSI, model_name: str, checkpoint: str, config: str, i: int, model_count: int, iter_index: int, iter_total: int, current_mix: np.ndarray, orig_sr: int, primary_stem: str, invert: bool): mssi.clear_model() mssi.load_model( self.get_model_type(model_name), checkpoint, config ) mssi.print_instruments() output, model_sr = mssi._process_array_iter_ensemble( i, model_count, iter_index, iter_total, current_mix, orig_sr, primary_stem, invert ) mssi.clear_model() return output, model_sr def iterative_ensemble( self, input_file: str | Path, output_dir: str | Path = Path("."), flow: list[list[str | bool]] = [], num_iters: int = 4, output_format: str = output_formats[0], template: str = "NAME_ITER", save_intermediate: bool = False, prefer_float: bool = False ) -> tuple[str, list[str]]: if not output_dir: output_dir = Path(".") output_dir = Path(output_dir) output_dir.mkdir(parents=True, exist_ok=True) if not input_file: raise PathNotSpecified(_i18n("path_not_specified")) input_file = Path(input_file) if not input_file.exists(): raise PathNotExist(_i18n("path_not_exist")) if not check(input_file): raise FileIsNotAudio(_i18n("file_is_not_audio", path=input_file)) if flow is None: print(_i18n("flow_empty")) gr.Info(title=_i18n("flow_empty"), message="") return None, [] flow, warns = self.validate_flow(flow, iterative=True) self.print_flow_iter(flow) input_mix, orig_sr = read(input_file, sr=44100) metadata = get_metadata(input_file) if "COMMENT" in metadata: orig_comment = metadata["COMMENT"] else: orig_comment = "" comment = "" if flow: if orig_comment: comment += orig_comment + "\n\n" comment += f"{_i18n('num_iters')}: {num_iters}\n\n" comment += f"{_i18n('ensemble_settings')}:\n\n" for (mn, pr_stem, invert, weight) in flow: comment += f"{mn} / {pr_stem} / {invert} / {weight}\n" current_mix = input_mix.copy() print(_i18n("num_iters") + f": {num_iters}") intermediate_files = [] model_count = len(flow) template = Namer.sanitize(template) template = Namer.dedup_template(template, keys=["NAME", "ITER"]) template = Namer.short(template, length=40) new_metadata = {} if metadata: new_metadata = deepcopy(metadata) if "TITLE" in metadata: new_metadata["TITLE"] = f"[{_i18n('num_iters')}: {num_iters} / {model_count}] {metadata['TITLE']}" else: new_metadata["TITLE"] = f"[{_i18n('num_iters')}: {num_iters} / {model_count}] {input_file.stem}" if "COMMENT" in metadata: new_metadata["COMMENT"] = comment else: new_metadata["COMMENT"] = comment else: new_metadata["TITLE"] = f"[{_i18n('num_iters')}: {num_iters} / {model_count}] {input_file.stem}" new_metadata["COMMENT"] = comment mssi = MSSI() mssi.set_add_params(**{"demucs_denoise": True, "mdx_denoise": True}) for iteration in range(1, num_iters + 1): print(f"{_i18n('iteration')} {iteration}/{num_iters}") auto_ensembler = Ensembler() for i, (model_name, primary_stem, invert) in enumerate(flow, start=1): print(f"{_i18n('model')} {i}/{model_count}") try: self.download(model_name) checkpoint, config = self.generate_local_paths(model_name) output, model_sr = self.iterative_ensemble_base(mssi, model_name, checkpoint, config, i, model_count, iteration, num_iters, current_mix, orig_sr, primary_stem, invert) auto_ensembler.add_array(output, model_sr) except Exception as e: print(_i18n("error_occured_separation") + ": " + str(e)) continue extracted_stems = auto_ensembler.get_arrays() srs = auto_ensembler.get_srs() if not extracted_stems: raise Exception(_i18n("no_models_succeeded")) ensemble_result, ensemble_sr = ensemble( extracted_stems, srs, "max_fft" ) auto_ensembler.clear() invert_ensemble_result, _ = subtractor( current_mix, ensemble_result, orig_sr, ensemble_sr, spectrogram=False ) new_mix, _ = subtractor( current_mix, gain(invert_ensemble_result, 0.5), orig_sr, ensemble_sr, spectrogram=False, max_sr=True ) if save_intermediate or iteration == num_iters: iter_name = Namer.template( template, ITER=f"iter_{iteration}", NAME=Namer.short_input_name_template(template, ITER=f"iter_{iteration}", NAME=input_file.stem) ) if iteration == num_iters: final_name = iter_name + "_final" final_path = Namer.iter(output_dir / f"{final_name}.{output_format}") result_path = write(final_path, ensemble_result, ensemble_sr, 320, prefer_float, new_metadata) else: dry_name = "dry_" + iter_name iter_path = Namer.iter(output_dir / f"{iter_name}.flac") dry_iter_path = Namer.iter(output_dir / f"{dry_name}.flac") iter_path = write(iter_path, new_mix, orig_sr, 320, prefer_float) dry_iter_path = write(dry_iter_path, ensemble_result, ensemble_sr, 320, prefer_float) intermediate_files.append(iter_path) intermediate_files.append(dry_iter_path) current_mix = new_mix gc.collect() if torch.cuda.is_available(): torch.cuda.empty_cache() del mssi return result_path, intermediate_files def manual_ensemble( self, input_files: list[str | Path], output_dir: str | Path = Path("."), weights: list[float] | None = None, template: str = "ensembled_TYPE_COUNT", etype: str = ensemble_types[0], output_format: str = output_formats[0], prefer_float: bool = False ) -> str: if not output_dir: output_dir = "" input_valid_files = get_audio_files_from_list(input_files, only_files=True) if not input_valid_files: raise PathsNotSpecified(_i18n("paths_not_specified")) output_dir = Path(output_dir) output_dir.mkdir(parents=True, exist_ok=True) arrays, srs = multiread(input_valid_files) model_count = len(srs) results, max_sr = ensemble(arrays, srs, etype, weights) template = Namer.sanitize(template) template = Namer.dedup_template(template, keys=["TYPE", "COUNT"]) template = Namer.short(template, length=40) custom_name = Namer.template( template, TYPE=etype, COUNT=model_count ) return write(Namer.iter(output_dir / f"{custom_name}.{output_format}"), results, max_sr, 320, prefer_float) def subtract(self, audio1: str | Path, audio2: str | Path, output_dir: str | Path = Path("."), output_format: str = output_formats[0], use_spec_invert: bool = False, template: str = "invert_TYPE_NAME", prefer_float: bool = False): if not output_dir: output_dir = "" output_dir = Path(output_dir) output_dir.mkdir(parents=True, exist_ok=True) if not audio1 or not audio2: raise PathsNotSpecified(_i18n("paths_not_specified")) audio1, audio2 = Path(audio1), Path(audio2) if not audio1.exists() or not audio2.exists(): raise PathsNotExist(_i18n("paths_not_exist")) if not check(audio1) or not check(audio2): raise FilesIsNotAudio(_i18n("files_is_not_audio")) template = Namer.sanitize(template) template = Namer.dedup_template(template, keys=["NAME", "TYPE"]) template = Namer.short(template, length=40) invert_type_key = ("spectrogram" if use_spec_invert else "waveform") custom_name = Namer.template( template, TYPE=invert_type_key, NAME=Namer.short_input_name_template(template, TYPE=invert_type_key, NAME=audio1.stem) ) y1, sr1 = read(audio1) metadata = get_metadata(audio1) y2, sr2 = read(audio2) inverted, min_sr = subtractor(y1, y2, sr1, sr2, spectrogram=use_spec_invert) new_metadata = {} if metadata: new_metadata = deepcopy(metadata) if "TITLE" in metadata: new_metadata["TITLE"] = f"[{_i18n('invert')}] {metadata['TITLE']}" else: new_metadata["TITLE"] = f"[{_i18n('invert')}] {audio1.stem}" else: new_metadata["TITLE"] = f"[{_i18n('invert')}] {audio1.stem}" return write(Namer.iter(output_dir / f"{custom_name}.{output_format}"), inverted, min_sr, 320, prefer_float, new_metadata) if __name__ == "__main__": check_taglib_not_installed() separator = Separator() args = parse_separator_args(add_params_args) if args.mode == "separate": separator.separate( input_files=args.input, output_dir=args.output_dir, output_format=args.output_format, template=args.template, model_name=args.model_name, extract_instrumental=args.extract_instrumental, use_spec_invert=args.use_spec_invert, invert_plus=args.invert_plus, selected_stems=args.selected_stems, add_params=get_add_params(args), prefer_float=args.prefer_float ) elif args.mode == "custom_separate": separator.custom_separate( input_files=args.input, output_dir=args.output_dir, output_format=args.output_format, template=args.template, model_type=args.model_type, ckpt=args.checkpoint_path, conf=args.config_path, extract_instrumental=args.extract_instrumental, use_spec_invert=args.use_spec_invert, invert_plus=args.invert_plus, selected_stems=args.selected_stems, add_params=get_add_params(args), prefer_float=args.prefer_float ) elif args.mode == "auto_ensemble": if args.preset: flow = json.loads(Path(args.preset).read_text("utf-8")) elif args.flow: flow = [] for params in args.flow: list_values_param = params.split(":") if len(list_values_param) == 4: flow.append([str(list_values_param[0]), str(list_values_param[1]), tobool(list_values_param[2]), float(list_values_param[3])]) else: raise ValueError() separator.auto_ensemble( input_file=args.input, output_dir=args.output_dir, flow=flow, template=args.template, etype=args.ensemble_type, output_format=args.output_format, use_spec_invert=args.use_spec_invert, save_primary_stems=args.save_primary_stems, prefer_float=args.prefer_float ) elif args.mode == "iterative_ensemble": if args.preset: flow = json.loads(Path(args.preset).read_text("utf-8")) elif args.flow: flow = [] for params in args.flow: list_values_param = params.split(":") if len(list_values_param) == 3: flow.append([str(list_values_param[0]), str(list_values_param[1]), tobool(list_values_param[2])]) else: raise ValueError(_i18n("arg_iterative_flow_help")) result_path, intermediate_files = separator.iterative_ensemble( input_file=args.input, output_dir=args.output_dir, flow=flow, num_iters=args.num_iters, output_format=args.output_format, template=args.template, save_intermediate=args.save_intermediate, prefer_float=args.prefer_float ) print(_i18n("ensemble_complete") + f": {result_path}") if intermediate_files: print(_i18n("saved_intermediate_files") + f": {', '.join(intermediate_files)}") elif args.mode == "manual_ensemble": separator.manual_ensemble( input_files=args.input, output_dir=args.output_dir, weights=args.weights, template=args.template, etype=args.ensemble_type, output_format=args.output_format, prefer_float=args.prefer_float ) elif args.mode == "subtract": separator.subtract( audio1=args.input_1, audio2=args.input_2, output_dir=args.output_dir, output_format=args.output_format, use_spec_invert=args.spec_invert, template=args.template, prefer_float=args.prefer_float ) elif args.mode == "info": if args.update: separator.update_info(args.model_scope) elif args.download: separator.download(args.model_name) elif args.clear_cache: separator.cache_dir.unlink(missing_ok=True) else: separator.show_info(args.limit, args.stem, args.only_installed)