Files

65 lines
2.0 KiB
Python

"""
pianoplayer service: annotate MusicXML with fingering (Parncutt algorithm).
Uses the pianoplayer v3 API (run_annotate) which handles part routing and
hand assignment internally.
"""
import asyncio
import os
import tempfile
from pathlib import Path
# Configurable via env var; default 120 s is generous for complex scores.
_FINGERING_TIMEOUT = int(os.environ.get("FINGERING_TIMEOUT_S", "120"))
async def annotate_fingering(musicxml: str) -> str:
"""Add finger annotations to MusicXML using the pianoplayer library."""
try:
return await asyncio.wait_for(
asyncio.get_running_loop().run_in_executor(None, _annotate, musicxml),
timeout=_FINGERING_TIMEOUT,
)
except asyncio.TimeoutError:
raise RuntimeError(
"Fingering generation timed out. "
"Try a shorter score or increase FINGERING_TIMEOUT_S."
)
def _annotate(musicxml: str) -> str:
import io
import contextlib
from pianoplayer.core import run_annotate
xml_path = None
out_path = None
try:
with tempfile.NamedTemporaryFile(
suffix=".xml", delete=False, mode="w", encoding="utf-8"
) as f:
f.write(musicxml)
xml_path = f.name
out_tmp = tempfile.NamedTemporaryFile(suffix=".xml", delete=False)
out_tmp.close()
out_path = out_tmp.name
# Suppress Rich progress/summary output so server logs stay clean.
with contextlib.redirect_stdout(io.StringIO()), contextlib.redirect_stderr(io.StringIO()):
run_annotate(
xml_path,
outputfile=out_path,
n_measures=1000,
auto_routing=True, # let pianoplayer detect which part is L/R
quiet=True,
)
return Path(out_path).read_text(encoding="utf-8")
finally:
if xml_path:
Path(xml_path).unlink(missing_ok=True)
if out_path:
Path(out_path).unlink(missing_ok=True)