File size: 2,940 Bytes
b260242
 
 
 
 
 
 
 
 
 
 
fc8c46f
 
 
 
 
 
 
 
 
 
b260242
 
 
fc8c46f
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
b260242
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
"""L2 tests for post-processing — Demucs and ffmpeg are mocked."""

from __future__ import annotations

from pathlib import Path
from unittest.mock import MagicMock

import post_process as pp


def test_separate_stems_returns_four_paths(tmp_path, monkeypatch):
    """Mocks the lower-level demucs path used by post_process.separate_stems:
    torchaudio.load → apply_model → sf.write. The wrapper convenience API
    (Separator.separate_audio_file) is intentionally not in this code path
    because it only ships with demucs >= 4.1."""
    import sys
    import types

    import numpy as np
    import torch

    src = tmp_path / "song.wav"
    src.write_bytes(b"RIFF" + b"\0" * 100)

    fake_model = MagicMock()
    fake_model.samplerate = 44100
    fake_model.sources = ["drums", "bass", "other", "vocals"]
    fake_model.audio_channels = 2
    monkeypatch.setattr(pp, "_get_demucs", lambda: fake_model)

    fake_torchaudio = types.ModuleType("torchaudio")
    fake_torchaudio.load = lambda _path: (torch.zeros((2, 44100)), 44100)
    fake_torchaudio.functional = types.SimpleNamespace(resample=lambda wav, _sr_in, _sr_out: wav)
    monkeypatch.setitem(sys.modules, "torchaudio", fake_torchaudio)

    fake_demucs_apply = types.ModuleType("demucs.apply")
    fake_demucs_apply.apply_model = lambda _m, batch, **_kw: torch.zeros((batch.shape[0], 4, 2, 44100))
    monkeypatch.setitem(sys.modules, "demucs.apply", fake_demucs_apply)

    written: list[str] = []

    def fake_sf_write(path, _data, _sr):
        written.append(path)
        Path(path).write_bytes(b"RIFF" + b"\0" * 100)

    fake_sf = types.ModuleType("soundfile")
    fake_sf.write = fake_sf_write
    fake_sf.read = lambda _path: (np.zeros((44100, 2)), 44100)
    monkeypatch.setitem(sys.modules, "soundfile", fake_sf)

    stems = pp.separate_stems(src)
    assert set(stems.keys()) == {"vocals", "drums", "bass", "other"}
    for p in stems.values():
        assert Path(p).exists()


def test_normalise_lufs_invokes_pyloudnorm(monkeypatch, tmp_path):
    src = tmp_path / "in.wav"
    src.write_bytes(b"RIFF" + b"\0" * 100)
    captured = {}

    def fake_norm(in_path, out_path, target_lufs):
        captured.update({"in": in_path, "out": out_path, "target": target_lufs})
        Path(out_path).write_bytes(b"RIFF")

    monkeypatch.setattr(pp, "_pyloudnorm_normalise", fake_norm)

    out = pp.normalise_lufs(src, target_lufs=-14.0)
    assert captured["target"] == -14.0
    assert Path(out).exists()


def test_to_mp3_invokes_ffmpeg(monkeypatch, tmp_path):
    src = tmp_path / "in.wav"
    src.write_bytes(b"RIFF")
    captured = {}

    def fake_run(cmd, **kw):
        captured["cmd"] = cmd
        Path(cmd[-1]).write_bytes(b"ID3")
        return MagicMock(returncode=0)

    monkeypatch.setattr(pp.subprocess, "run", fake_run)
    out = pp.to_mp3(src, bitrate_kbps=320)
    assert Path(out).exists()
    assert "320k" in " ".join(captured["cmd"])