diff --git a/fix_porcupine.sh b/fix_porcupine.sh new file mode 100644 index 0000000..60ed731 --- /dev/null +++ b/fix_porcupine.sh @@ -0,0 +1,66 @@ +#!/bin/bash +# +# Fix Porcupine Installation +# AI Now Inc - Del Mar Demo Unit +# + +set -e + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +cd "$SCRIPT_DIR" + +echo "๐Ÿ”ง Fixing Porcupine Installation..." +echo "" + +# Load API key from .env if it exists +if [ -f ".env" ]; then + source .env + if [ -n "$PICOVOICE_API_KEY" ]; then + echo "โœ“ Found API key in .env" + export PICOVOICE_API_KEY="$PICOVOICE_API_KEY" + else + echo "โš ๏ธ No API key found in .env" + echo "Please run ./setup_porcupine.sh first" + exit 1 + fi +else + echo "โš ๏ธ .env file not found" + echo "Please run ./setup_porcupine.sh first" + exit 1 +fi + +# Check if venv exists and is valid +if [ ! -f "venv/bin/activate" ]; then + echo "๐Ÿ“ฆ Virtual environment not found or broken. Recreating..." + rm -rf venv + python3 -m venv venv +fi + +# Activate venv +source venv/bin/activate + +# Upgrade pip +echo "๐Ÿ“ฆ Upgrading pip..." +pip install --upgrade pip + +# Install Porcupine +echo "๐ŸŽค Installing Porcupine..." +pip install pvporcupine + +# Test installation +echo "" +echo "๐Ÿงช Testing Porcupine installation..." +if python3 -c "import pvporcupine; print('โœ“ Porcupine version:', pvporcupine.__version__)"; then + echo "" + echo "โœ… Porcupine installed successfully!" + echo "" + echo "Now you can run:" + echo " ./start_tui.sh" + echo "" + echo "Say 'Hey Osiris' to test hotword detection." +else + echo "" + echo "โŒ Porcupine installation failed!" + echo "Please check your API key and internet connection." + exit 1 +fi diff --git a/hotword_detector.py b/hotword_detector.py index 261c93f..e947d2a 100755 --- a/hotword_detector.py +++ b/hotword_detector.py @@ -2,7 +2,6 @@ """ Hotword Detector Detects wake words: "Hey Osiris" / "ไฝ ๅฅฝ Osiris" - Supports: - Porcupine (PicoVoice) for accurate hotword detection - Custom keyword spotting @@ -17,19 +16,22 @@ import wave from typing import Optional, Callable, List from pathlib import Path +# Try to import Porcupine +HAS_PORCUPINE = False +porcupine_instance = None try: import pvporcupine - import pyaudio HAS_PORCUPINE = True + logging.info(f"Porcupine module found (version: {pvporcupine.__version__})") except ImportError: - HAS_PORCUPINE = False logging.warning("Porcupine not installed. Install with: pip install pvporcupine") +# Try to import WebRTC VAD +HAS_VAD = False try: import webrtcvad HAS_VAD = True except ImportError: - HAS_VAD = False logging.warning("WebRTC VAD not installed") logger = logging.getLogger(__name__) @@ -46,7 +48,6 @@ class HotwordDetector: "sample_rate": 16000, "frame_length": 512 }) - self.hotwords = self.config.get("hotwords", []) self.is_running = False self.callback = None @@ -91,15 +92,35 @@ class HotwordDetector: return try: - # Create Porcupine instance with custom keywords + # Get API key from environment or .env file + api_key = os.getenv('PICOVOICE_API_KEY') + if not api_key: + # Try to load from .env file + env_file = Path(__file__).parent / '.env' + if env_file.exists(): + with open(env_file) as f: + for line in f: + if line.startswith('PICOVOICE_API_KEY='): + api_key = line.split('=')[1].strip() + break + + if not api_key: + logger.warning("Porcupine API key not found. Set PICOVOICE_API_KEY environment variable.") + self.porcupine = None + return + + # Initialize Porcupine with the built-in "hey osiris" keyword self.porcupine = pvporcupine.create( + access_key=api_key, keywords=["hey osiris"], sensitivities=[0.5] ) self.keyword_index = 0 - logger.info("Porcupine initialized with 'Hey Osiris'") + logger.info("โœ“ Porcupine initialized with 'Hey Osiris'") + except Exception as e: logger.warning(f"Porcupine initialization failed: {e}") + logger.warning("Falling back to simple detection") self.porcupine = None def set_callback(self, callback: Callable[[], None]): @@ -157,7 +178,12 @@ class HotwordDetector: break # Read audio frame - pcm = stream.read(self.porcupine.frame_length, exception_on_overflow=False) + pcm = stream.read( + self.porcupine.frame_length, + exception_on_overflow=False + ) + + # Convert to signed 16-bit integers pcm = struct.unpack_from( f"h{self.porcupine.frame_length}", pcm @@ -167,11 +193,11 @@ class HotwordDetector: keyword_index = self.porcupine.process(pcm) if keyword_index >= 0: - logger.info("Hotword detected!") + logger.info("๐ŸŽฏ Hotword detected!") if self.callback: self.callback() return "hey osiris" - + except KeyboardInterrupt: logger.info("Detection interrupted") except Exception as e: @@ -189,77 +215,67 @@ class HotwordDetector: Detects any speech as hotword. """ logger.warning("Using simple voice detection (not recommended)") - # This is a placeholder - in production you'd use: # - Snowboy # - Custom trained model # - Or just use Porcupine - return None def stop(self): """Stop detection.""" self.is_running = False - logger.info("Hotword detection stopped") + if self.porcupine: + self.porcupine.delete() def create_custom_hotword(self, keyword: str, output_path: str): """ - Create custom hotword model (requires Porcupine training). + Create custom hotword model (Porcupine only). - This is a placeholder - actual implementation requires: - 1. Recording multiple samples of the keyword - 2. Training with Porcupine Console - 3. Exporting the model + Args: + keyword: Keyword phrase + output_path: Path to save the model """ - logger.info(f"Custom hotword creation not implemented: {keyword}") - logger.info("Use Porcupine Console to train custom keywords") + if not HAS_PORCUPINE: + raise RuntimeError("Porcupine not available") + + # This would require Porcupine training API + logger.warning("Custom hotword training not yet implemented") class SimpleHotwordDetector: - """ - Simple hotword detection using audio level threshold. - Fallback when Porcupine is not available. - """ + """Simple energy-based hotword detection.""" def __init__(self, keyword: str = "hey osiris"): self.keyword = keyword - self.threshold = 0.5 - self.is_running = False def detect(self, timeout: int = None) -> Optional[str]: - """Simple energy-based detection.""" + """Simple detection - not reliable.""" logger.warning("Simple detection is not reliable. Install Porcupine for best results.") return None def main(): """Test hotword detection.""" - print("\n" + "="*60) - print(" ๐Ÿ” Hotword Detector Test") - print(" Say 'Hey Osiris' or 'ไฝ ๅฅฝ Osiris'") - print("="*60) - - detector = HotwordDetector() + import time def on_hotword(): - print("\n๐ŸŽ‰ HOTWORD DETECTED!") + print("โœจ Hotword detected!") + detector = HotwordDetector() detector.set_callback(on_hotword) + print("Listening for hotword... (Ctrl+C to stop)") + try: - result = detector.detect(timeout=30) - - if result: - print(f"Detected: {result}") - else: - print("No hotword detected") - + while True: + result = detector.detect(timeout=30) + if result: + print(f"Detected: {result}") + time.sleep(1) except KeyboardInterrupt: - print("\nTest stopped") - - detector.stop() + print("\nStopped") + detector.stop() -if __name__ == "__main__": - logging.basicConfig(level=logging.INFO) +if __name__ == '__main__': main()