Add gitignor
This commit is contained in:
84
assistant/audio.py
Normal file
84
assistant/audio.py
Normal file
@@ -0,0 +1,84 @@
|
||||
import sys
|
||||
import subprocess
|
||||
from . import config
|
||||
|
||||
|
||||
def play_audio(pcm_bytes: bytes) -> None:
|
||||
"""
|
||||
Joue des bytes PCM bruts (S16LE, mono) via le lecteur système.
|
||||
|
||||
- Linux : pipe direct vers aplay (aucun fichier temporaire)
|
||||
- macOS : pipe vers afplay via stdin (format AIFF/raw)
|
||||
- Windows: conversion via PowerShell (fallback)
|
||||
"""
|
||||
platform = sys.platform
|
||||
|
||||
if platform.startswith("linux"):
|
||||
_play_pcm_aplay(pcm_bytes)
|
||||
elif platform == "darwin":
|
||||
_play_pcm_macos(pcm_bytes)
|
||||
elif platform == "win32":
|
||||
_play_pcm_windows(pcm_bytes)
|
||||
else:
|
||||
raise RuntimeError(f"Plateforme non supportée : {platform}")
|
||||
|
||||
|
||||
def _play_pcm_aplay(pcm_bytes: bytes) -> None:
|
||||
"""Pipe WAV directement vers aplay (auto-détecte le format depuis le header)."""
|
||||
proc = subprocess.Popen(
|
||||
["aplay", "-q", "-"],
|
||||
stdin=subprocess.PIPE,
|
||||
)
|
||||
proc.communicate(pcm_bytes)
|
||||
if proc.returncode != 0:
|
||||
raise RuntimeError(f"aplay a échoué (code {proc.returncode})")
|
||||
|
||||
|
||||
def _play_pcm_macos(pcm_bytes: bytes) -> None:
|
||||
"""Joue du WAV sur macOS via afplay (pipe stdin via fichier temporaire)."""
|
||||
import tempfile, os
|
||||
# afplay ne lit pas depuis stdin, on utilise un fichier temporaire
|
||||
with tempfile.NamedTemporaryFile(suffix=".wav", delete=False) as tmp:
|
||||
tmp.write(pcm_bytes)
|
||||
tmp_path = tmp.name
|
||||
try:
|
||||
subprocess.run(["afplay", tmp_path], check=True)
|
||||
finally:
|
||||
os.unlink(tmp_path)
|
||||
|
||||
|
||||
def _play_pcm_windows(pcm_bytes: bytes) -> None:
|
||||
import tempfile, os
|
||||
with tempfile.NamedTemporaryFile(suffix=".wav", delete=False) as tmp:
|
||||
tmp.write(_pcm_to_wav(pcm_bytes))
|
||||
tmp_path = tmp.name
|
||||
try:
|
||||
subprocess.run(
|
||||
["powershell", "-c", f'(New-Object Media.SoundPlayer "{tmp_path}").PlaySync()'],
|
||||
check=True,
|
||||
)
|
||||
finally:
|
||||
os.unlink(tmp_path)
|
||||
|
||||
|
||||
def _pcm_to_wav(pcm_bytes: bytes) -> bytes:
|
||||
"""Ajoute un header WAV minimal au PCM brut S16LE mono."""
|
||||
import struct
|
||||
sample_rate = config.TTS_PCM_SAMPLE_RATE
|
||||
num_channels = 1
|
||||
bits_per_sample = 16
|
||||
byte_rate = sample_rate * num_channels * bits_per_sample // 8
|
||||
block_align = num_channels * bits_per_sample // 8
|
||||
data_size = len(pcm_bytes)
|
||||
header = struct.pack(
|
||||
"<4sI4s4sIHHIIHH4sI",
|
||||
b"RIFF", 36 + data_size, b"WAVE",
|
||||
b"fmt ", 16, 1, num_channels,
|
||||
sample_rate, byte_rate, block_align, bits_per_sample,
|
||||
b"data", data_size,
|
||||
)
|
||||
return header + pcm_bytes
|
||||
|
||||
|
||||
def _command_exists(cmd: str) -> bool:
|
||||
return subprocess.run(["which", cmd], capture_output=True).returncode == 0
|
||||
Reference in New Issue
Block a user