AryanS17/Computer-Vision-Accessibility-Tool
1
1"""2tts_engine.py3-------------4Offline text-to-speech via pyttsx3, run on a background thread with a5queue so that speaking never blocks the video/detection loop.6 7Includes a simple "debounce" layer: identical phrases spoken again within8a short cooldown window are dropped. This is the main lever for reducing9notification fatigue (step 5 of the plan) -- without it, a static chair10directly ahead would trigger "Chair ahead, close" on every single frame.11"""12 13import queue14import threading15import time16from typing import Dict17 18import pyttsx319 20 21class SpeechEngine:22 def __init__(self, rate: int = 175, volume: float = 1.0, cooldown_seconds: float = 4.0):23 self._queue: "queue.Queue[str]" = queue.Queue()24 self._cooldown_seconds = cooldown_seconds25 self._last_spoken: Dict[str, float] = {}26 self._stop_flag = threading.Event()27 28 self._engine = pyttsx3.init()29 self._engine.setProperty("rate", rate)30 self._engine.setProperty("volume", volume)31 32 self._thread = threading.Thread(target=self._worker, daemon=True)33 self._thread.start()34 35 def _worker(self):36 while not self._stop_flag.is_set():37 try:38 phrase = self._queue.get(timeout=0.25)39 except queue.Empty:40 continue41 self._engine.say(phrase)42 self._engine.runAndWait()43 self._queue.task_done()44 45 def say(self, phrase: str, dedupe_key: str = None):46 """47 Queue a phrase to be spoken.48 dedupe_key: if provided, phrases sharing this key are suppressed49 if one was spoken within cooldown_seconds. Defaults to50 the phrase text itself, so repeating the exact same51 alert is what gets throttled by default.52 """53 key = dedupe_key or phrase54 now = time.time()55 last_time = self._last_spoken.get(key, 0)56 57 if now - last_time < self._cooldown_seconds:58 return # skip -- too soon since last identical alert59 60 self._last_spoken[key] = now61 self._queue.put(phrase)62 63 def say_many(self, phrases, dedupe_key_prefix: str = ""):64 for phrase in phrases:65 self.say(phrase, dedupe_key=f"{dedupe_key_prefix}{phrase}")66 67 def stop(self):68 self._stop_flag.set()69 self._thread.join(timeout=1.0)70 