Fix Porcupine initialization and add fix script
This commit is contained in:
parent
08b4aece2b
commit
8d1a8ea12b
66
fix_porcupine.sh
Normal file
66
fix_porcupine.sh
Normal file
@ -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
|
||||||
@ -2,7 +2,6 @@
|
|||||||
"""
|
"""
|
||||||
Hotword Detector
|
Hotword Detector
|
||||||
Detects wake words: "Hey Osiris" / "你好 Osiris"
|
Detects wake words: "Hey Osiris" / "你好 Osiris"
|
||||||
|
|
||||||
Supports:
|
Supports:
|
||||||
- Porcupine (PicoVoice) for accurate hotword detection
|
- Porcupine (PicoVoice) for accurate hotword detection
|
||||||
- Custom keyword spotting
|
- Custom keyword spotting
|
||||||
@ -17,19 +16,22 @@ import wave
|
|||||||
from typing import Optional, Callable, List
|
from typing import Optional, Callable, List
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
|
# Try to import Porcupine
|
||||||
|
HAS_PORCUPINE = False
|
||||||
|
porcupine_instance = None
|
||||||
try:
|
try:
|
||||||
import pvporcupine
|
import pvporcupine
|
||||||
import pyaudio
|
|
||||||
HAS_PORCUPINE = True
|
HAS_PORCUPINE = True
|
||||||
|
logging.info(f"Porcupine module found (version: {pvporcupine.__version__})")
|
||||||
except ImportError:
|
except ImportError:
|
||||||
HAS_PORCUPINE = False
|
|
||||||
logging.warning("Porcupine not installed. Install with: pip install pvporcupine")
|
logging.warning("Porcupine not installed. Install with: pip install pvporcupine")
|
||||||
|
|
||||||
|
# Try to import WebRTC VAD
|
||||||
|
HAS_VAD = False
|
||||||
try:
|
try:
|
||||||
import webrtcvad
|
import webrtcvad
|
||||||
HAS_VAD = True
|
HAS_VAD = True
|
||||||
except ImportError:
|
except ImportError:
|
||||||
HAS_VAD = False
|
|
||||||
logging.warning("WebRTC VAD not installed")
|
logging.warning("WebRTC VAD not installed")
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
@ -46,7 +48,6 @@ class HotwordDetector:
|
|||||||
"sample_rate": 16000,
|
"sample_rate": 16000,
|
||||||
"frame_length": 512
|
"frame_length": 512
|
||||||
})
|
})
|
||||||
|
|
||||||
self.hotwords = self.config.get("hotwords", [])
|
self.hotwords = self.config.get("hotwords", [])
|
||||||
self.is_running = False
|
self.is_running = False
|
||||||
self.callback = None
|
self.callback = None
|
||||||
@ -91,15 +92,35 @@ class HotwordDetector:
|
|||||||
return
|
return
|
||||||
|
|
||||||
try:
|
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(
|
self.porcupine = pvporcupine.create(
|
||||||
|
access_key=api_key,
|
||||||
keywords=["hey osiris"],
|
keywords=["hey osiris"],
|
||||||
sensitivities=[0.5]
|
sensitivities=[0.5]
|
||||||
)
|
)
|
||||||
self.keyword_index = 0
|
self.keyword_index = 0
|
||||||
logger.info("Porcupine initialized with 'Hey Osiris'")
|
logger.info("✓ Porcupine initialized with 'Hey Osiris'")
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.warning(f"Porcupine initialization failed: {e}")
|
logger.warning(f"Porcupine initialization failed: {e}")
|
||||||
|
logger.warning("Falling back to simple detection")
|
||||||
self.porcupine = None
|
self.porcupine = None
|
||||||
|
|
||||||
def set_callback(self, callback: Callable[[], None]):
|
def set_callback(self, callback: Callable[[], None]):
|
||||||
@ -157,7 +178,12 @@ class HotwordDetector:
|
|||||||
break
|
break
|
||||||
|
|
||||||
# Read audio frame
|
# 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(
|
pcm = struct.unpack_from(
|
||||||
f"h{self.porcupine.frame_length}",
|
f"h{self.porcupine.frame_length}",
|
||||||
pcm
|
pcm
|
||||||
@ -167,11 +193,11 @@ class HotwordDetector:
|
|||||||
keyword_index = self.porcupine.process(pcm)
|
keyword_index = self.porcupine.process(pcm)
|
||||||
|
|
||||||
if keyword_index >= 0:
|
if keyword_index >= 0:
|
||||||
logger.info("Hotword detected!")
|
logger.info("🎯 Hotword detected!")
|
||||||
if self.callback:
|
if self.callback:
|
||||||
self.callback()
|
self.callback()
|
||||||
return "hey osiris"
|
return "hey osiris"
|
||||||
|
|
||||||
except KeyboardInterrupt:
|
except KeyboardInterrupt:
|
||||||
logger.info("Detection interrupted")
|
logger.info("Detection interrupted")
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
@ -189,77 +215,67 @@ class HotwordDetector:
|
|||||||
Detects any speech as hotword.
|
Detects any speech as hotword.
|
||||||
"""
|
"""
|
||||||
logger.warning("Using simple voice detection (not recommended)")
|
logger.warning("Using simple voice detection (not recommended)")
|
||||||
|
|
||||||
# This is a placeholder - in production you'd use:
|
# This is a placeholder - in production you'd use:
|
||||||
# - Snowboy
|
# - Snowboy
|
||||||
# - Custom trained model
|
# - Custom trained model
|
||||||
# - Or just use Porcupine
|
# - Or just use Porcupine
|
||||||
|
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def stop(self):
|
def stop(self):
|
||||||
"""Stop detection."""
|
"""Stop detection."""
|
||||||
self.is_running = False
|
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):
|
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:
|
Args:
|
||||||
1. Recording multiple samples of the keyword
|
keyword: Keyword phrase
|
||||||
2. Training with Porcupine Console
|
output_path: Path to save the model
|
||||||
3. Exporting the model
|
|
||||||
"""
|
"""
|
||||||
logger.info(f"Custom hotword creation not implemented: {keyword}")
|
if not HAS_PORCUPINE:
|
||||||
logger.info("Use Porcupine Console to train custom keywords")
|
raise RuntimeError("Porcupine not available")
|
||||||
|
|
||||||
|
# This would require Porcupine training API
|
||||||
|
logger.warning("Custom hotword training not yet implemented")
|
||||||
|
|
||||||
|
|
||||||
class SimpleHotwordDetector:
|
class SimpleHotwordDetector:
|
||||||
"""
|
"""Simple energy-based hotword detection."""
|
||||||
Simple hotword detection using audio level threshold.
|
|
||||||
Fallback when Porcupine is not available.
|
|
||||||
"""
|
|
||||||
|
|
||||||
def __init__(self, keyword: str = "hey osiris"):
|
def __init__(self, keyword: str = "hey osiris"):
|
||||||
self.keyword = keyword
|
self.keyword = keyword
|
||||||
self.threshold = 0.5
|
|
||||||
self.is_running = False
|
|
||||||
|
|
||||||
def detect(self, timeout: int = None) -> Optional[str]:
|
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.")
|
logger.warning("Simple detection is not reliable. Install Porcupine for best results.")
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
"""Test hotword detection."""
|
"""Test hotword detection."""
|
||||||
print("\n" + "="*60)
|
import time
|
||||||
print(" 🔍 Hotword Detector Test")
|
|
||||||
print(" Say 'Hey Osiris' or '你好 Osiris'")
|
|
||||||
print("="*60)
|
|
||||||
|
|
||||||
detector = HotwordDetector()
|
|
||||||
|
|
||||||
def on_hotword():
|
def on_hotword():
|
||||||
print("\n🎉 HOTWORD DETECTED!")
|
print("✨ Hotword detected!")
|
||||||
|
|
||||||
|
detector = HotwordDetector()
|
||||||
detector.set_callback(on_hotword)
|
detector.set_callback(on_hotword)
|
||||||
|
|
||||||
|
print("Listening for hotword... (Ctrl+C to stop)")
|
||||||
|
|
||||||
try:
|
try:
|
||||||
result = detector.detect(timeout=30)
|
while True:
|
||||||
|
result = detector.detect(timeout=30)
|
||||||
if result:
|
if result:
|
||||||
print(f"Detected: {result}")
|
print(f"Detected: {result}")
|
||||||
else:
|
time.sleep(1)
|
||||||
print("No hotword detected")
|
|
||||||
|
|
||||||
except KeyboardInterrupt:
|
except KeyboardInterrupt:
|
||||||
print("\nTest stopped")
|
print("\nStopped")
|
||||||
|
detector.stop()
|
||||||
detector.stop()
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == '__main__':
|
||||||
logging.basicConfig(level=logging.INFO)
|
|
||||||
main()
|
main()
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user