mroctopus / transcriber /separate.py
Ewan
Fix Demucs segment size: htdemucs max is 7.8, use 7
b34d3ea
"""Demucs source separation for full-song transcription."""
import subprocess
import sys
from pathlib import Path
def separate(input_path: str, output_dir: str, model: str = "htdemucs") -> dict:
"""Run Demucs source separation on an audio file.
Args:
input_path: Path to input audio file
output_dir: Directory to write separated stems
model: Demucs model name (default: htdemucs)
Returns:
dict with stem paths: {
'vocals': str,
'drums': str,
'bass': str,
'other': str, # contains piano, guitar, synths, etc.
}
"""
input_path = Path(input_path)
output_dir = Path(output_dir)
output_dir.mkdir(parents=True, exist_ok=True)
python = sys.executable
cmd = [
python, "-m", "demucs",
"--name", model,
"--out", str(output_dir),
"--segment", "7", # Process in 7s chunks (htdemucs max is 7.8)
str(input_path),
]
result = subprocess.run(cmd, capture_output=True, text=True, timeout=600)
if result.returncode != 0:
# Filter out download progress bars from stderr to find real error
stderr_lines = result.stderr.splitlines()
error_lines = [
ln for ln in stderr_lines
if not any(x in ln for x in ['%|', 'B/s]', 'Downloading', '━'])
]
error_msg = '\n'.join(error_lines[-20:]).strip() or result.stderr[-500:]
raise RuntimeError(f"Demucs failed: {error_msg}")
# Demucs outputs to: output_dir/{model}/{filename_without_ext}/
stems_dir = output_dir / model / input_path.stem
if not stems_dir.exists():
raise FileNotFoundError(f"Demucs output not found at {stems_dir}")
stems = {}
for name in ("vocals", "drums", "bass", "other"):
path = stems_dir / f"{name}.wav"
if not path.exists():
raise FileNotFoundError(f"Missing stem: {path}")
stems[name] = str(path)
return stems