Download camera.py from Marnu25/x-tracker: direct link, hf CLI and curl.
- Browser
- Download file 5.52 kB
-
https://huggingface.co/spaces/Marnu25/x-tracker/resolve/main/camera.py
- Command line
-
hf download hf://spaces/Marnu25/x-tracker/camera.py
-
curl -L -o camera.py https://huggingface.co/spaces/Marnu25/x-tracker/resolve/main/camera.py
5.52 kB
| import threading | |
| import time | |
| from typing import Optional, Tuple | |
| import cv2 | |
| class CameraManager: | |
| def __init__(self, device_index: int = 0, width: int = 1280, height: int = 720): | |
| self.device_index = device_index | |
| self.target_width = width | |
| self.target_height = height | |
| self.cap: Optional[cv2.VideoCapture] = None | |
| self.thread: Optional[threading.Thread] = None | |
| self.running = False | |
| self.lock = threading.Lock() | |
| self.latest_raw_frame = None | |
| self.latest_jpeg: Optional[bytes] = None | |
| self.actual_width = 0 | |
| self.actual_height = 0 | |
| self.fps = 0.0 | |
| self.client_count = 0 | |
| self.started_at: Optional[float] = None | |
| self.new_frame_event = threading.Event() | |
| def start(self) -> Tuple[bool, str]: | |
| with self.lock: | |
| if self.running: | |
| return True, "Camera already running" | |
| try: | |
| cap = cv2.VideoCapture(self.device_index, cv2.CAP_V4L2) | |
| if not cap.isOpened(): | |
| cap = cv2.VideoCapture(self.device_index) | |
| if not cap.isOpened(): | |
| return False, f"Could not open video device /dev/video{self.device_index}" | |
| # Configure optimal 720p MJPEG hardware capture | |
| cap.set(cv2.CAP_PROP_FOURCC, cv2.VideoWriter_fourcc(*'MJPG')) | |
| cap.set(cv2.CAP_PROP_FRAME_WIDTH, self.target_width) | |
| cap.set(cv2.CAP_PROP_FRAME_HEIGHT, self.target_height) | |
| cap.set(cv2.CAP_PROP_FPS, 30) | |
| ret, frame = cap.read() | |
| if not ret or frame is None: | |
| cap.release() | |
| return False, "Failed to read initial frame from camera sensor" | |
| h, w = frame.shape[:2] | |
| self.actual_width = w | |
| self.actual_height = h | |
| self.cap = cap | |
| self.running = True | |
| self.started_at = time.time() | |
| self.thread = threading.Thread(target=self._capture_loop, daemon=True) | |
| self.thread.start() | |
| return True, f"Camera started successfully ({w}x{h})" | |
| except Exception as e: | |
| if self.cap: | |
| self.cap.release() | |
| self.cap = None | |
| return False, f"Exception while opening camera: {str(e)}" | |
| def stop(self) -> Tuple[bool, str]: | |
| with self.lock: | |
| if not self.running: | |
| return True, "Camera is already in standby" | |
| self.running = False | |
| self.new_frame_event.set() | |
| if self.thread: | |
| self.thread.join(timeout=2.0) | |
| self.thread = None | |
| with self.lock: | |
| if self.cap: | |
| self.cap.release() | |
| self.cap = None | |
| self.latest_jpeg = None | |
| self.latest_raw_frame = None | |
| self.fps = 0.0 | |
| self.started_at = None | |
| return True, "Camera put into standby, device released" | |
| def _capture_loop(self): | |
| frame_counter = 0 | |
| fps_timer = time.time() | |
| while self.running and self.cap and self.cap.isOpened(): | |
| ret, frame = self.cap.read() | |
| if not ret or frame is None: | |
| time.sleep(0.01) | |
| continue | |
| # Encode frame to JPEG | |
| ret_enc, jpeg = cv2.imencode('.jpg', frame, [int(cv2.IMWRITE_JPEG_QUALITY), 85]) | |
| if ret_enc: | |
| jpeg_bytes = jpeg.tobytes() | |
| with self.lock: | |
| self.latest_raw_frame = frame | |
| self.latest_jpeg = jpeg_bytes | |
| # Signal new frame to waiting stream clients | |
| self.new_frame_event.set() | |
| self.new_frame_event.clear() | |
| frame_counter += 1 | |
| now = time.time() | |
| if now - fps_timer >= 1.0: | |
| self.fps = round(frame_counter / (now - fps_timer), 1) | |
| frame_counter = 0 | |
| fps_timer = now | |
| # Cleanup if exited loop | |
| with self.lock: | |
| if self.cap: | |
| self.cap.release() | |
| self.cap = None | |
| def get_latest_jpeg(self) -> Optional[bytes]: | |
| with self.lock: | |
| return self.latest_jpeg | |
| def get_status(self) -> dict: | |
| uptime = int(time.time() - self.started_at) if self.running and self.started_at else 0 | |
| return { | |
| "running": self.running, | |
| "width": self.actual_width, | |
| "height": self.actual_height, | |
| "fps": self.fps, | |
| "uptime_seconds": uptime, | |
| "client_count": self.client_count, | |
| "device": f"/dev/video{self.device_index}", | |
| } | |
| def generate_mjpeg(self): | |
| self.client_count += 1 | |
| try: | |
| while self.running: | |
| # Wait for next frame signal (up to 200ms) | |
| self.new_frame_event.wait(timeout=0.2) | |
| jpeg = self.get_latest_jpeg() | |
| if jpeg: | |
| yield ( | |
| b"--frame\r\n" | |
| b"Content-Type: image/jpeg\r\n" | |
| b"Content-Length: " + str(len(jpeg)).encode() + b"\r\n\r\n" | |
| + jpeg + b"\r\n" | |
| ) | |
| else: | |
| time.sleep(0.05) | |
| finally: | |
| self.client_count = max(0, self.client_count - 1) | |