feat(voice-client): PySide6 desktop client and Windows build scripts
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
This commit is contained in:
61
voice_confirmation_client/core/playback.py
Normal file
61
voice_confirmation_client/core/playback.py
Normal file
@@ -0,0 +1,61 @@
|
||||
"""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."
|
||||
)
|
||||
Reference in New Issue
Block a user