File size: 5,732 Bytes
a347460 10c6e44 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 | """
Test speed/quality: Lead vs Backing vocals separation
Zakłada, że na wejściu dajesz JUŻ wyizolowany stem "vocals" (np. z Twojego htdemucs_6s),
dokładnie tak jak produkuje go worker.py. Testujemy tylko DRUGI etap pipeline'u.
Modele testowane (najlepsze wg community UVR / nomadkaraoke, stan na 2026):
- mel_band_roformer_karaoke_aufr33_viperx_sdr_10.1956.ckpt (MDXC / Mel-Band Roformer)
- UVR_MDXNET_KARA_2.onnx (MDX-Net, szybszy, starszy)
Wynik: lead_vocals.wav + backing_vocals.wav + czas przetwarzania + RTF (real-time factor)
"""
import os
import time
import shutil
import tempfile
from pathlib import Path
import gradio as gr
import torchaudio
from audio_separator.separator import Separator
MODEL_DIR = os.environ.get("MODEL_DIR", "/tmp/audio-separator-models")
os.makedirs(MODEL_DIR, exist_ok=True)
MODEL_CHOICES = {
"Mel-Band Roformer Karaoke (aufr33/viperx) - najlepsza jakość": "mel_band_roformer_karaoke_aufr33_viperx_sdr_10.1956.ckpt",
"UVR-MDX-NET Karaoke 2 - szybszy, starsza architektura": "UVR_MDXNET_KARA_2.onnx",
}
# Cache załadowanych modeli w pamięci procesu, żeby nie przeładowywać za każdym razem
_loaded_separators = {}
def get_separator(model_filename: str, output_dir: str) -> Separator:
if model_filename not in _loaded_separators:
sep = Separator(
output_dir=output_dir,
model_file_dir=MODEL_DIR,
)
sep.load_model(model_filename=model_filename)
_loaded_separators[model_filename] = sep
else:
# ten sam obiekt, ale trzeba zaktualizować output_dir na nowy job
_loaded_separators[model_filename].output_dir = output_dir
return _loaded_separators[model_filename]
def get_audio_duration(path: str) -> float:
info = torchaudio.info(path)
return info.num_frames / info.sample_rate
def run_separation(vocals_file, model_label, progress=gr.Progress()):
if vocals_file is None:
raise gr.Error("Wgraj plik ze stemem wokalu (np. vocals.wav z Twojego htdemucsa).")
model_filename = MODEL_CHOICES[model_label]
work_dir = tempfile.mkdtemp(prefix="leadback_")
try:
duration_sec = get_audio_duration(vocals_file)
progress(0.05, desc=f"Ładowanie modelu {model_filename} (pierwsze uruchomienie może pobierać wagi)...")
t_load_start = time.time()
separator = get_separator(model_filename, work_dir)
t_load = time.time() - t_load_start
progress(0.3, desc="Separacja lead / backing w toku...")
t_sep_start = time.time()
output_files = separator.separate(vocals_file)
t_sep = time.time() - t_sep_start
progress(0.95, desc="Finalizacja...")
# audio-separator zwraca listę plików wyjściowych - rozróżniamy po nazwie
lead_path, backing_path = None, None
for f in output_files:
full_path = f if os.path.isabs(f) else os.path.join(work_dir, f)
lower = full_path.lower()
if "instrumental" in lower or "backing" in lower or "no_vocal" in lower:
backing_path = full_path
else:
lead_path = full_path
# fallback, gdyby nazwy się nie zgadzały - bierzemy po kolei
if (lead_path is None or backing_path is None) and len(output_files) >= 2:
paths = [f if os.path.isabs(f) else os.path.join(work_dir, f) for f in output_files]
lead_path, backing_path = paths[0], paths[1]
rtf = t_sep / duration_sec if duration_sec > 0 else 0
report = (
f"**Model:** {model_filename}\n\n"
f"**Długość utworu:** {duration_sec:.1f}s\n\n"
f"**Czas ładowania modelu:** {t_load:.1f}s (tylko przy pierwszym uruchomieniu / zmianie modelu)\n\n"
f"**Czas samej separacji:** {t_sep:.1f}s\n\n"
f"**RTF (real-time factor):** {rtf:.2f}x "
f"({'szybciej niż realtime' if rtf < 1 else 'wolniej niż realtime'})\n\n"
f"_Wskazówka: RTF x2 znaczy, że 3-minutowy utwór = ~6s przetwarzania na tym sprzęcie._"
)
return lead_path, backing_path, report
except Exception as e:
raise gr.Error(f"Błąd separacji: {e}")
with gr.Blocks(title="Test: Lead vs Backing Vocals") as demo:
gr.Markdown(
"# 🎤 Test separacji Lead / Backing Vocals\n"
"Wgraj **już wyizolowany stem wokalu** (dokładnie taki jak produkuje Twój `worker.py` - plik `vocals.wav`/`.mp3`). "
"Ta appka testuje TYLKO drugi etap pipeline'u (lead vs backing), żebyś zobaczył realny narzut czasowy "
"przed wdrożeniem na produkcję.\n\n"
"⚠️ Pierwsze uruchomienie danego modelu pobierze jego wagi (setki MB) - kolejne testy będą szybsze."
)
with gr.Row():
with gr.Column():
vocals_input = gr.Audio(label="Stem wokalu (wejście)", type="filepath")
model_dropdown = gr.Dropdown(
choices=list(MODEL_CHOICES.keys()),
value=list(MODEL_CHOICES.keys())[0],
label="Model do testu",
)
run_btn = gr.Button("▶️ Uruchom separację", variant="primary")
with gr.Column():
lead_output = gr.Audio(label="Lead vocal (wynik)")
backing_output = gr.Audio(label="Backing vocals (wynik)")
report_output = gr.Markdown()
run_btn.click(
fn=run_separation,
inputs=[vocals_input, model_dropdown],
outputs=[lead_output, backing_output, report_output],
)
if __name__ == "__main__":
demo.queue().launch(
server_name="0.0.0.0",
server_port=int(os.environ.get("PORT", 7860)),
ssr_mode=False,
) |