Demucs / app.py
harp-dev's picture
Tidy the launch call and drop stray files
cfedfd6 verified
Raw History Blame Contribute Delete
5.56 kB
import spaces
from pyharp.core import ModelCard, build_endpoint
from pyharp.media.audio import save_audio
from pyharp.labels import LabelList
from pyharp.tags import Subcategory
from audiotools import AudioSignal
from demucs import pretrained
from demucs.apply import apply_model
import gradio as gr
import torch
from pathlib import Path
import numpy as np
# ModelCard
model_card = ModelCard(
name="Demucs",
description=(
"Separates a full music track into four individual audio stems: Drums, "
"Bass, Vocals, Other-instrumental using the Demucs model. Available "
"models are - htdemucs: Best overall fidelity and natural sound, "
"mdx_extra: Balanced trade-off, faster processing with a small drop in "
"quality, mdx_extra_q: Fastest & lightest, noticeable quality loss, "
"ideal for quick previews. Repository: "
"https://github.com/facebookresearch/demucs Paper: "
"https://arxiv.org/abs/2111.03600"
),
author="Simon Rouard, Francisco Massa, Alexandre Défossez",
tags=[Subcategory.STEM_SEPARATION, 'stems']
)
DEMUX_MODELS = ["htdemucs", "mdx_extra", "mdx_extra_q"]
STEM_NAMES = ["Drums", "Bass", "Vocals", "Other"]
# Global model cache
LOADED_MODELS = {}
def get_cached_model(model_name: str):
if model_name not in LOADED_MODELS:
model = pretrained.get_model(model_name)
model.eval()
device = "cuda" if torch.cuda.is_available() else "cpu"
model.to(device)
LOADED_MODELS[model_name] = model
return LOADED_MODELS[model_name]
# Separation Logic
@spaces.GPU
def separate_all_stems(audio_file_path: str, model_name: str):
model = get_cached_model(model_name)
signal = AudioSignal(audio_file_path)
signal = signal.resample(44100) # expects 44.1kHz
is_mono = signal.num_channels == 1
if is_mono:
signal.audio_data = signal.audio_data.repeat(1, 2, 1) # [batch, channels, samples]
signal._num_channels = 2
sr = signal.sample_rate
# Ensure audio_data is a torch.Tensor
audio = signal.audio_data
if isinstance(audio, np.ndarray):
audio = torch.from_numpy(audio)
audio = audio.float() # [channels, samples] or [channels, samples, ?]
# Remove extra trailing dimensions
if audio.ndim > 2:
audio = audio.squeeze()
# Final shape: [1, channels, samples]
waveform = audio.unsqueeze(0)
with torch.no_grad():
stems_batch = apply_model(
model,
waveform.to(next(model.parameters()).device),
overlap=0.2,
shifts=1,
split=True,
)
stems = stems_batch[0]
output_signals = []
for stem in stems:
if is_mono:
stem = stem.mean(dim=0, keepdim=True)
signal = AudioSignal(stem.cpu().numpy().astype("float32"), sample_rate=sr)
output_signals.append(signal)
# Combine drums + bass + other = instrumental
drums, bass, other, vocals = output_signals
instrumental = drums + bass + other
return [drums, bass, vocals, other]
# Process Function
def process_fn(audio_file_path, model_name):
try:
if model_name not in DEMUX_MODELS:
raise ValueError(f"Unsupported model selected: {model_name}")
output_signals = separate_all_stems(audio_file_path, model_name)
is_mp3 = Path(audio_file_path).suffix.lower() == ".mp3"
extension = "mp3" if is_mp3 else "wav"
outputs = []
for stem_name, signal in zip(STEM_NAMES, output_signals):
filename = f"demucs_{model_name}_{stem_name.lower().replace(' ', '_')}.{extension}"
output_path = Path(filename)
save_audio(signal, output_path)
outputs.append(str(output_path))
return tuple(outputs)
except gr.Error:
raise
except Exception as e:
raise gr.Error(str(e))
# Gradio App
if __name__ == "__main__":
with gr.Blocks() as demo:
input_audio = gr.Audio(type="filepath", label="Input Audio").harp_required(True).set_info((
"The mixture to separate. Mono input is duplicated to stereo first."
))
model_dropdown = gr.Dropdown(
choices=DEMUX_MODELS,
label="Demucs Model",
value="htdemucs",
info=(
"Choose which Demucs variant to use for separation:\n"
"1. htdemucs: High-quality Hybrid Transformer Demucs trained on MusDB + 800 songs.\n"
"2. mdx_extra: Trained with extended data. Balanced model offering a clear trade-off between quality and speed.\n"
"3. mdx_extra_q: Quantized lightweight version of mdx_extra.\n"
)
)
output_drums = gr.Audio(type="filepath", label="Drums").set_info("The isolated drums.")
output_bass = gr.Audio(type="filepath", label="Bass").set_info("The isolated bass.")
output_vocals = gr.Audio(type="filepath", label="Vocals").set_info("The isolated vocal.")
output_instrumental = gr.Audio(type="filepath", label="Other").set_info((
"Everything the other three stems did not take."
))
app = build_endpoint(
model_card=model_card,
input_components=[input_audio, model_dropdown],
output_components=[
output_drums,
output_bass,
output_vocals,
output_instrumental
],
process_fn=process_fn
)
demo.queue().launch(pwa=True, show_error=True)