| """ |
| 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", |
| } |
|
|
| |
| _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: |
| |
| _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...") |
|
|
| |
| 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 |
|
|
| |
| 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, |
| ) |