Files
2026-05-06 17:56:38 +02:00

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