Add microphone picker and device management features
- Implement `list_input_devices` to retrieve and filter input-capable audio devices. - Create `MicPickerDialog` for selecting and saving microphone settings. - Add `set_env_value` function to update .env file with selected microphone index. - Update UI components to integrate microphone selection functionality. - Enhance tests for microphone device handling and environment variable management.
This commit is contained in:
+37
-1
@@ -9,6 +9,7 @@ from __future__ import annotations
|
||||
|
||||
import os
|
||||
import platform
|
||||
import re
|
||||
from pathlib import Path
|
||||
|
||||
HERE = Path(__file__).resolve().parent.parent # project root (one above bolt_pet/)
|
||||
@@ -29,6 +30,26 @@ def _load_env() -> None:
|
||||
_load_env()
|
||||
|
||||
|
||||
def set_env_value(key: str, value: str) -> None:
|
||||
"""Write *key=value* into .env, in place — replacing (or uncommenting)
|
||||
an existing line for it, appending a new one otherwise. Used by the tray's
|
||||
microphone picker so a device pick survives a restart without hand-editing
|
||||
the file. Settings read once at import time (like MIC_DEVICE) still need
|
||||
that restart to take effect; this only updates the file and os.environ."""
|
||||
env_path = HERE / ".env"
|
||||
lines = env_path.read_text(encoding="utf-8").splitlines() if env_path.exists() else []
|
||||
pattern = re.compile(rf"^\s*#?\s*{re.escape(key)}\s*=")
|
||||
new_line = f"{key}={value}"
|
||||
for i, line in enumerate(lines):
|
||||
if pattern.match(line):
|
||||
lines[i] = new_line
|
||||
break
|
||||
else:
|
||||
lines.append(new_line)
|
||||
env_path.write_text("\n".join(lines) + "\n", encoding="utf-8")
|
||||
os.environ[key] = value
|
||||
|
||||
|
||||
def _node_name() -> str:
|
||||
try:
|
||||
return platform.node() or "desktop"
|
||||
@@ -115,7 +136,22 @@ DIALOGUE_VOICES = os.environ.get("DIALOGUE_VOICES", "")
|
||||
|
||||
# ── mic / VAD (same tuning knobs as bolt_desk.py) ───────────────────────────
|
||||
|
||||
MIC_DEVICE = os.environ.get("MIC_DEVICE", "") or None # sounddevice name/index
|
||||
def _mic_device(raw: str):
|
||||
"""sounddevice treats an int index and a string name very differently: a
|
||||
string is matched as a (fragile, often ambiguous) substring against every
|
||||
device name, while an int is used as-is. .env only ever gives us a
|
||||
string, so a numeric one — what the tray's mic picker saves, since the
|
||||
same physical device usually shows up once per host API (MME,
|
||||
DirectSound, WASAPI, ...) under the same name — has to be cast back to
|
||||
int here, or it goes through the substring path and matches nothing (or
|
||||
everything) instead."""
|
||||
raw = raw.strip()
|
||||
if not raw:
|
||||
return None
|
||||
return int(raw) if raw.lstrip("-").isdigit() else raw
|
||||
|
||||
|
||||
MIC_DEVICE = _mic_device(os.environ.get("MIC_DEVICE", ""))
|
||||
SPEAKER_DEVICE = os.environ.get("SPEAKER_DEVICE", "") or None
|
||||
|
||||
RMS_THRESHOLD = int(os.environ.get("VAD_RMS_THRESHOLD", "300"))
|
||||
|
||||
Reference in New Issue
Block a user