51 lines
1.7 KiB
Python
51 lines
1.7 KiB
Python
"""
|
|
Basic Pitch service: audio (MP3/WAV/etc.) to MIDI via Spotify's Basic Pitch model.
|
|
|
|
Requires Python 3.11 or 3.12 due to tensorflow-macos constraints.
|
|
See requirements-transcribe.txt for setup instructions.
|
|
"""
|
|
import asyncio
|
|
import tempfile
|
|
from pathlib import Path
|
|
|
|
_IMPORT_ERROR: str | None = None
|
|
|
|
try:
|
|
from basic_pitch.inference import predict # type: ignore
|
|
from basic_pitch import ICASSP_2022_MODEL_PATH # type: ignore
|
|
except ImportError as e:
|
|
_IMPORT_ERROR = (
|
|
"basic-pitch is not installed. MP3 transcription requires Python 3.11 or 3.12. "
|
|
"See backend/requirements-transcribe.txt for setup instructions. "
|
|
f"Original error: {e}"
|
|
)
|
|
|
|
|
|
async def transcribe_audio_to_midi(audio_bytes: bytes, filename: str) -> bytes:
|
|
"""Transcribe audio bytes to MIDI bytes using Basic Pitch."""
|
|
if _IMPORT_ERROR:
|
|
raise RuntimeError(_IMPORT_ERROR)
|
|
return await asyncio.get_running_loop().run_in_executor(
|
|
None, _transcribe, audio_bytes, filename
|
|
)
|
|
|
|
|
|
def _transcribe(audio_bytes: bytes, filename: str) -> bytes:
|
|
suffix = Path(filename).suffix or ".mp3"
|
|
with tempfile.NamedTemporaryFile(suffix=suffix, delete=False) as f:
|
|
f.write(audio_bytes)
|
|
audio_path = f.name
|
|
|
|
_model_output, midi_data, _note_events = predict( # type: ignore[name-defined]
|
|
audio_path, ICASSP_2022_MODEL_PATH # type: ignore[name-defined]
|
|
)
|
|
|
|
midi_tmp = tempfile.NamedTemporaryFile(suffix=".mid", delete=False)
|
|
midi_tmp.close()
|
|
midi_data.write(midi_tmp.name)
|
|
|
|
midi_bytes = Path(midi_tmp.name).read_bytes()
|
|
Path(audio_path).unlink(missing_ok=True)
|
|
Path(midi_tmp.name).unlink(missing_ok=True)
|
|
return midi_bytes
|