sathvik77 commited on
Commit
0abe23e
·
1 Parent(s): abae7c8

feat(backend): demucs two-stem -> mp3 adapter

Browse files
Files changed (2) hide show
  1. separate.py +42 -0
  2. tests/test_separate.py +21 -0
separate.py ADDED
@@ -0,0 +1,42 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import subprocess
2
+ import sys
3
+ from pathlib import Path
4
+ from processor import _get_device
5
+
6
+
7
+ def _run_demucs(audio_path: Path) -> Path:
8
+ """Run demucs in two-stems mode targeting vocals; return path to no_vocals stem."""
9
+ out_root = audio_path.parent / "demucs_out"
10
+ out_root.mkdir(parents=True, exist_ok=True)
11
+ cmd = [
12
+ sys.executable,
13
+ "-m",
14
+ "demucs",
15
+ "--two-stems", "vocals",
16
+ "-n", "htdemucs",
17
+ "--device", _get_device(),
18
+ "--mp3",
19
+ "--mp3-bitrate", "192",
20
+ "-o", str(out_root),
21
+ str(audio_path),
22
+ ]
23
+ subprocess.run(cmd, check=True)
24
+ # demucs writes to {out_root}/htdemucs/{audio_stem}/no_vocals.mp3
25
+ candidate = out_root / "htdemucs" / audio_path.stem / "no_vocals.mp3"
26
+ if not candidate.exists():
27
+ raise RuntimeError(f"demucs did not produce {candidate}")
28
+ return candidate
29
+
30
+
31
+ def _encode_mp3(wav_path: Path, mp3_path: Path) -> Path:
32
+ """Re-encode (or copy) a stem file to the target mp3_path."""
33
+ subprocess.run(
34
+ ["ffmpeg", "-y", "-i", str(wav_path), "-b:a", "192k", str(mp3_path)],
35
+ check=True, capture_output=True,
36
+ )
37
+ return mp3_path
38
+
39
+
40
+ def separate_vocals_to_mp3(audio_path: Path) -> Path:
41
+ no_vocals_stem = _run_demucs(audio_path)
42
+ return _encode_mp3(no_vocals_stem, audio_path.parent / "no_vocals.mp3")
tests/test_separate.py ADDED
@@ -0,0 +1,21 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from unittest.mock import patch, MagicMock
2
+ from pathlib import Path
3
+ from separate import separate_vocals_to_mp3
4
+
5
+
6
+ @patch("separate._run_demucs")
7
+ @patch("separate._encode_mp3")
8
+ def test_separate_returns_mp3_path(mock_encode, mock_demucs, tmp_path):
9
+ audio = tmp_path / "in.m4a"
10
+ audio.write_bytes(b"x")
11
+ no_vocals_wav = tmp_path / "no_vocals.wav"
12
+ no_vocals_wav.write_bytes(b"y")
13
+ mock_demucs.return_value = no_vocals_wav
14
+ out_mp3 = tmp_path / "no_vocals.mp3"
15
+ mock_encode.return_value = out_mp3
16
+
17
+ result = separate_vocals_to_mp3(audio)
18
+
19
+ assert result == out_mp3
20
+ mock_demucs.assert_called_once_with(audio)
21
+ mock_encode.assert_called_once_with(no_vocals_wav, audio.parent / "no_vocals.mp3")