199f762616
- PipeWire/pw-record backend (no PortAudio needed) - tested mic live - VAD: Energy (immediate) + Silero (torch CPU) switchable - Wakeword: openWakeWord TFLite hey_jarvis 0.4 - STT: faster-whisper base int8 CPU - model loads OK on i3 - SpeakerID: ECAPA embeddings + fallback stats, enrollment store - Vision: OpenCV Haar + face_recognition + webcam cam0 640x480 verified - LLM: ollama/qwen2.5:3b, gemini_cli, hermes_api, echo strategies - TTS: Piper + espeak + echo fallbacks - Core pipeline: IDLE -> WAKE -> RECORDING -> STT -> SpeakerID/FaceID fusion -> LLM -> TTS -> followup window - Config via jarvis.yaml, soul.md persona - CLI tools: run, devices, enroll-speaker, enroll-face, test-vad, test-mic - Systemd user service ready - Tested on iMac 2010 i3 550 15GB Ubuntu 26.04 CPU-only
42 lines
1.3 KiB
Python
42 lines
1.3 KiB
Python
import cv2, logging
|
|
import numpy as np
|
|
from typing import Optional
|
|
log = logging.getLogger(__name__)
|
|
class Camera:
|
|
def __init__(self, camera_id=0, width=640, height=480):
|
|
self.camera_id = camera_id
|
|
self.width = width
|
|
self.height = height
|
|
self.cap: Optional[cv2.VideoCapture] = None
|
|
def open(self):
|
|
if self.cap and self.cap.isOpened():
|
|
return True
|
|
try:
|
|
self.cap = cv2.VideoCapture(self.camera_id)
|
|
self.cap.set(cv2.CAP_PROP_FRAME_WIDTH, self.width)
|
|
self.cap.set(cv2.CAP_PROP_FRAME_HEIGHT, self.height)
|
|
for _ in range(5):
|
|
self.cap.read()
|
|
if not self.cap.isOpened():
|
|
log.warning(f"Camera {self.camera_id} failed to open")
|
|
return False
|
|
log.info(f"Camera {self.camera_id} opened")
|
|
return True
|
|
except Exception as e:
|
|
log.error(f"Camera open error: {e}")
|
|
return False
|
|
def capture_frame(self) -> Optional[np.ndarray]:
|
|
if not self.open():
|
|
return None
|
|
ret, frame = self.cap.read()
|
|
if not ret:
|
|
return None
|
|
return frame
|
|
def close(self):
|
|
if self.cap:
|
|
try:
|
|
self.cap.release()
|
|
except:
|
|
pass
|
|
self.cap=None
|