NhanNguyen1309/audio-separation-api
0
1from __future__ import annotations2 3from dataclasses import dataclass, field4from pathlib import Path5 6from app.config import settings7 8 9@dataclass(slots=True)10class ChordAudioSource:11 path: Path12 source: str13 warnings: list[str] = field(default_factory=list)14 15 16def _find_first(root: Path, names: tuple[str, ...]) -> Path | None:17 for name in names:18 candidates = sorted(path for path in root.rglob(name) if path.is_file())19 if candidates:20 return candidates[0]21 return None22 23 24def _combine_stems(bass_path: Path, other_path: Path, output_path: Path) -> None:25 import librosa # type: ignore[import-untyped]26 import numpy as np27 import soundfile as sf28 29 bass, sr = librosa.load(str(bass_path), sr=44100, mono=True)30 other, _ = librosa.load(str(other_path), sr=sr, mono=True)31 length = max(len(bass), len(other))32 bass = np.pad(bass, (0, length - len(bass)))33 other = np.pad(other, (0, length - len(other)))34 output_path.parent.mkdir(parents=True, exist_ok=True)35 sf.write(str(output_path), (bass + other) / 2.0, sr)36 37 38def _build_harmonic_audio(original_path: Path, output_path: Path) -> None:39 import librosa # type: ignore[import-untyped]40 import soundfile as sf41 42 audio, sr = librosa.load(str(original_path), sr=11025, mono=True)43 harmonic = librosa.effects.harmonic(audio, kernel_size=17)44 output_path.parent.mkdir(parents=True, exist_ok=True)45 sf.write(str(output_path), harmonic, sr)46 47 48def prepare_chord_detection_audio(49 original_audio_path: str | Path,50 output_path: str | Path | None = None,51 existing_stems_dir: str | Path | None = None,52) -> ChordAudioSource:53 original = Path(original_audio_path)54 harmonic_output = Path(output_path) if output_path else original.with_name("harmonic.wav")55 56 if not settings.chord_use_harmonic_source:57 return ChordAudioSource(path=original, source="original")58 59 if existing_stems_dir:60 stems_root = Path(existing_stems_dir)61 instrumental = _find_first(stems_root, ("instrumental.wav", "no_vocals.wav"))62 if instrumental:63 return ChordAudioSource(path=instrumental, source="instrumental")64 65 bass = _find_first(stems_root, ("bass.wav",))66 other = _find_first(stems_root, ("other.wav",))67 if bass and other:68 try:69 _combine_stems(bass, other, harmonic_output)70 return ChordAudioSource(path=harmonic_output, source="bass_other")71 except Exception: # noqa: BLE00172 pass73 74 try:75 _build_harmonic_audio(original, harmonic_output)76 return ChordAudioSource(path=harmonic_output, source="harmonic")77 except Exception: # noqa: BLE00178 return ChordAudioSource(path=original, source="original", warnings=["harmonic_audio_fallback_used"])79 