"""Réglages audio Alfred : ALSA carte 1 et états logiques thread-safe.""" import os import re import subprocess import threading _LOCK = threading.RLock() _STATE = {'volume': 70, 'speaker_muted': False, 'mic_muted': False, 'mic_gain': None} _CARD = os.getenv('ALFRED_ALSA_CARD', '1') _CONTROL = os.getenv('ALFRED_ALSA_VOLUME_CONTROL', 'Master') def _amixer(*args): return subprocess.run(['amixer', '-c', _CARD, *args], capture_output=True, text=True, timeout=5, check=True).stdout def initialize(): """Lit le volume existant sans modifier les réglages audio au démarrage.""" try: output = _amixer('sget', _CONTROL) matches = re.findall(r'\[(\d+)%\]', output) with _LOCK: if matches: _STATE['volume'] = int(matches[0]) # ALFRED_MIC_GAIN_V2 : gain Capture, sans toucher à Mic Boost. try: gain_out = _amixer('sget', os.getenv('ALFRED_ALSA_CAPTURE_CONTROL', 'Capture')) gains = re.findall(r'\[(\d+)%\]', gain_out) _STATE['mic_gain'] = int(gains[0]) if gains else None except (OSError, subprocess.SubprocessError) as exc: print(f'[ALFRED AUDIO] Gain Capture indisponible : {exc}', flush=True) # Ne pas déduire speaker_muted du mixer : état propre à Alfred. except (OSError, subprocess.SubprocessError) as exc: print(f'[ALFRED AUDIO] Lecture ALSA impossible : {exc}', flush=True) def get_settings(): with _LOCK: return dict(_STATE) def update_settings(payload): if not isinstance(payload, dict): raise ValueError('Un objet JSON est attendu') allowed = {'volume', 'speaker_muted', 'mic_muted', 'mic_gain'} if set(payload) - allowed: raise ValueError('Paramètre audio inconnu') with _LOCK: new = dict(_STATE) if 'volume' in payload: v = payload['volume'] if isinstance(v, bool) or not isinstance(v, (int, float)) or not 0 <= v <= 100: raise ValueError('volume doit être entre 0 et 100') new['volume'] = round(v) if 'mic_gain' in payload: v = payload['mic_gain'] if isinstance(v, bool) or not isinstance(v, (int, float)) or not 0 <= v <= 100: raise ValueError('mic_gain doit être entre 0 et 100') new['mic_gain'] = round(v) for key in ('speaker_muted', 'mic_muted'): if key in payload: if not isinstance(payload[key], bool): raise ValueError(f'{key} doit être un booléen') new[key] = payload[key] # Le gain Capture ne change que sur demande explicite. # Mic Boost et Internal Mic Boost restent inchangés. if 'mic_gain' in payload and new['mic_gain'] != _STATE['mic_gain']: _amixer('sset', os.getenv('ALFRED_ALSA_CAPTURE_CONTROL', 'Capture'), f"{new['mic_gain']}%") if new['volume'] != _STATE['volume']: _amixer('sset', _CONTROL, f"{new['volume']}%") _STATE.update(new) return dict(_STATE) def speaker_muted(): with _LOCK: return _STATE['speaker_muted'] def mic_muted(): with _LOCK: return _STATE['mic_muted']