Add voice_confirmation_client (poll, TTS MP3 playback, mic WAV resolve), PyInstaller spec, start/build helpers, and API unit tests. Pending manual testing: end-to-end on OR workstations and packaged exe. Made-with: Cursor
62 lines
1.6 KiB
Python
62 lines
1.6 KiB
Python
"""Play MP3 bytes via system player or bundled ffplay."""
|
|
|
|
from __future__ import annotations
|
|
|
|
import base64
|
|
import os
|
|
import shutil
|
|
import subprocess
|
|
import sys
|
|
import tempfile
|
|
from pathlib import Path
|
|
|
|
from voice_confirmation_client.core.paths import find_ffplay
|
|
|
|
|
|
def play_mp3_from_base64(b64: str) -> None:
|
|
raw_b64 = "".join((b64 or "").split())
|
|
if not raw_b64:
|
|
raise ValueError("empty prompt_audio_mp3_base64")
|
|
data = base64.b64decode(raw_b64, validate=False)
|
|
with tempfile.NamedTemporaryFile(suffix=".mp3", delete=False) as f:
|
|
f.write(data)
|
|
tmp = f.name
|
|
try:
|
|
_play_mp3_path(Path(tmp))
|
|
finally:
|
|
try:
|
|
os.unlink(tmp)
|
|
except OSError:
|
|
pass
|
|
|
|
|
|
def _play_mp3_path(path: Path) -> None:
|
|
bundled = find_ffplay()
|
|
if bundled and bundled.is_file():
|
|
subprocess.run(
|
|
[str(bundled), "-nodisp", "-autoexit", "-loglevel", "quiet", str(path)],
|
|
check=True,
|
|
timeout=600,
|
|
)
|
|
return
|
|
ffplay = shutil.which("ffplay")
|
|
if ffplay:
|
|
subprocess.run(
|
|
[ffplay, "-nodisp", "-autoexit", "-loglevel", "quiet", str(path)],
|
|
check=True,
|
|
timeout=600,
|
|
)
|
|
return
|
|
if sys.platform == "darwin":
|
|
subprocess.run(["afplay", str(path)], check=True, timeout=600)
|
|
return
|
|
if os.name == "nt":
|
|
os.startfile(str(path)) # type: ignore[attr-defined]
|
|
import time
|
|
|
|
time.sleep(5)
|
|
return
|
|
raise RuntimeError(
|
|
"No MP3 player found. Install ffmpeg (ffplay) or run on macOS with afplay."
|
|
)
|