Download main.py from Marnu25/x-tracker: direct link, hf CLI and curl.
- Browser
- Download file 14.4 kB
-
https://huggingface.co/spaces/Marnu25/x-tracker/resolve/main/main.py
- Command line
-
hf download hf://spaces/Marnu25/x-tracker/main.py
-
curl -L -o main.py https://huggingface.co/spaces/Marnu25/x-tracker/resolve/main/main.py
14.4 kB
| import base64 | |
| import hashlib | |
| import hmac | |
| import json | |
| import os | |
| import shutil | |
| import socket | |
| import time | |
| from datetime import datetime | |
| from pathlib import Path | |
| from typing import Optional | |
| from pydantic import BaseModel | |
| from fastapi import FastAPI, HTTPException, Request, Response, WebSocket, WebSocketDisconnect | |
| from fastapi.middleware.cors import CORSMiddleware | |
| from fastapi.responses import FileResponse, StreamingResponse | |
| from fastapi.staticfiles import StaticFiles | |
| from camera_manager import CameraManager | |
| from filters import AVAILABLE_FILTERS | |
| from recorder import VideoRecorder | |
| BASE_DIR = Path(__file__).resolve().parent | |
| STORAGE_DIR = BASE_DIR / "storage" | |
| SNAPSHOT_DIR = STORAGE_DIR / "snapshots" | |
| RECORDING_DIR = STORAGE_DIR / "recordings" | |
| CONFIG_PATH = STORAGE_DIR / "cameras.json" | |
| STATIC_DIR = BASE_DIR / "static" | |
| TEMPLATES_DIR = BASE_DIR / "templates" | |
| SNAPSHOT_DIR.mkdir(parents=True, exist_ok=True) | |
| RECORDING_DIR.mkdir(parents=True, exist_ok=True) | |
| app = FastAPI(title="X-Tracker Studio - GPU AI Vision & Multi-Camera Tracking") | |
| app.add_middleware( | |
| CORSMiddleware, | |
| allow_origins=["*"], | |
| allow_credentials=True, | |
| allow_methods=["*"], | |
| allow_headers=["*"], | |
| ) | |
| camera = CameraManager(config_path=CONFIG_PATH) | |
| recorder = VideoRecorder(camera=camera, storage_dir=RECORDING_DIR) | |
| class AddCameraRequest(BaseModel): | |
| name: str | |
| source: str | |
| type: str = "network" | |
| def get_network_ips(): | |
| lan_ip = "127.0.0.1" | |
| tailscale_ip = None | |
| try: | |
| s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) | |
| s.connect(("8.8.8.8", 80)) | |
| lan_ip = s.getsockname()[0] | |
| s.close() | |
| except Exception: | |
| pass | |
| try: | |
| ts_out = os.popen("tailscale ip -4 2>/dev/null").read().strip() | |
| if ts_out: | |
| tailscale_ip = ts_out | |
| except Exception: | |
| pass | |
| return lan_ip, tailscale_ip | |
| def startup_event(): | |
| camera.start() | |
| def shutdown_event(): | |
| if recorder.is_recording: | |
| recorder.stop() | |
| camera.stop() | |
| def get_dashboard(): | |
| return FileResponse(str(TEMPLATES_DIR / "index.html")) | |
| def get_system_status(): | |
| cam_status = camera.get_status() | |
| rec_status = recorder.get_status() | |
| lan_ip, ts_ip = get_network_ips() | |
| port = int(os.environ.get("PORT", 7860)) | |
| disk_total, disk_used, disk_free = shutil.disk_usage(str(STORAGE_DIR)) | |
| return { | |
| "status": "online", | |
| "camera": cam_status, | |
| "recorder": rec_status, | |
| "network": { | |
| "lan_ip": lan_ip, | |
| "tailscale_ip": ts_ip, | |
| "port": port, | |
| "lan_url": f"http://{lan_ip}:{port}", | |
| "tailscale_url": f"http://{ts_ip}:{port}" if ts_ip else None, | |
| }, | |
| "storage": { | |
| "free_gb": round(disk_free / (1024**3), 2), | |
| "total_gb": round(disk_total / (1024**3), 2), | |
| } | |
| } | |
| # --- Filter Endpoints --- | |
| def get_filters(): | |
| return { | |
| "available_filters": AVAILABLE_FILTERS, | |
| "active_filter": camera.current_filter | |
| } | |
| def set_active_filter(filter_name: str): | |
| ok, msg = camera.set_filter(filter_name) | |
| if not ok: | |
| raise HTTPException(status_code=400, detail=msg) | |
| return { | |
| "status": "success", | |
| "message": msg, | |
| "active_filter": camera.current_filter | |
| } | |
| # --- Zoom & AI Detection Endpoints --- | |
| class FocusPointRequest(BaseModel): | |
| x: float | |
| y: float | |
| zoom: Optional[float] = None | |
| def reset_zoom(): | |
| camera.set_zoom(1.0, 0.5, 0.5) | |
| camera.set_auto_tracking_mode("off") | |
| return {"status": "success", "message": "Zoom and focus reset to center 1.0x"} | |
| def set_zoom_focus_point(payload: FocusPointRequest): | |
| ok, msg = camera.set_zoom_focus_point(payload.x, payload.y, payload.zoom) | |
| return { | |
| "status": "success", | |
| "message": msg, | |
| "zoom_center_x": camera.zoom_center_x, | |
| "zoom_center_y": camera.zoom_center_y, | |
| "zoom_level": camera.zoom_level | |
| } | |
| def set_zoom_target_object(object_id: str): | |
| target = None if object_id in ["none", "clear", "null", "reset"] else object_id | |
| ok, msg = camera.set_focus_target_object(target) | |
| return { | |
| "status": "success", | |
| "message": msg, | |
| "target_object_id": camera.target_object_id, | |
| "auto_tracking_mode": camera.auto_tracking_mode | |
| } | |
| def set_auto_tracking_mode(mode: str): | |
| ok, msg = camera.set_auto_tracking_mode(mode) | |
| if not ok: | |
| raise HTTPException(status_code=400, detail=msg) | |
| return { | |
| "status": "success", | |
| "message": msg, | |
| "auto_tracking_mode": camera.auto_tracking_mode | |
| } | |
| def set_zoom(level: float): | |
| ok, msg, new_zoom = camera.set_zoom(level) | |
| return {"status": "success", "message": msg, "zoom_level": new_zoom} | |
| def toggle_ai_detection(): | |
| new_state = not camera.ai_detection_enabled | |
| ok, msg, enabled = camera.set_detection(new_state) | |
| return {"status": "success", "message": msg, "ai_detection_enabled": enabled} | |
| def set_ai_detection(state: str): | |
| enabled = (state.lower() in ["1", "true", "on", "enable"]) | |
| ok, msg, enabled = camera.set_detection(enabled) | |
| return {"status": "success", "message": msg, "ai_detection_enabled": enabled} | |
| def get_detections(): | |
| return { | |
| "ai_detection_enabled": camera.ai_detection_enabled, | |
| "detections": camera.current_detections, | |
| "telemetry": camera.current_telemetry, | |
| "count": len(camera.current_detections) | |
| } | |
| def set_hud_overlay(state: str): | |
| enabled = (state.lower() in ["1", "true", "on", "enable"]) | |
| ok, msg, enabled = camera.set_hud_overlay(enabled) | |
| return {"status": "success", "message": msg, "show_hud_overlay": enabled} | |
| # --- Multi-Camera Endpoints --- | |
| def get_registered_cameras(): | |
| return { | |
| "cameras": camera.get_cameras(), | |
| "active_camera_id": camera.active_camera_id | |
| } | |
| def switch_active_camera(cam_id: str): | |
| if recorder.is_recording: | |
| recorder.stop() | |
| ok, msg = camera.switch_camera(cam_id) | |
| if not ok: | |
| raise HTTPException(status_code=400, detail=msg) | |
| return {"status": "success", "message": msg, "active_camera": camera.get_active_camera()} | |
| def register_new_camera(req: AddCameraRequest): | |
| ok, msg, new_cam = camera.add_camera(name=req.name, source=req.source, cam_type=req.type) | |
| if not ok: | |
| raise HTTPException(status_code=400, detail=msg) | |
| return {"status": "success", "message": msg, "camera": new_cam} | |
| def delete_camera(cam_id: str): | |
| ok, msg = camera.remove_camera(cam_id) | |
| if not ok: | |
| raise HTTPException(status_code=400, detail=msg) | |
| return {"status": "success", "message": msg} | |
| # --- Remote Power Control --- | |
| def remote_start_camera(): | |
| ok, msg = camera.start() | |
| if not ok: | |
| raise HTTPException(status_code=500, detail=msg) | |
| return {"status": "success", "message": msg, "camera": camera.get_status()} | |
| def remote_stop_camera(): | |
| if recorder.is_recording: | |
| recorder.stop() | |
| ok, msg = camera.stop() | |
| return {"status": "success", "message": msg, "camera": camera.get_status()} | |
| # --- Streaming, Snapshot, Recording --- | |
| def get_live_stream(): | |
| if not camera.running: | |
| camera.start() | |
| return StreamingResponse( | |
| camera.generate_mjpeg(), | |
| media_type="multipart/x-mixed-replace; boundary=frame" | |
| ) | |
| def remote_snapshot(): | |
| if not camera.running: | |
| ok, msg = camera.start() | |
| if not ok: | |
| raise HTTPException(status_code=503, detail=f"Cannot snapshot: {msg}") | |
| jpeg = None | |
| for _ in range(10): | |
| jpeg = camera.get_clean_jpeg() | |
| if jpeg: | |
| break | |
| time.sleep(0.05) | |
| if not jpeg: | |
| raise HTTPException(status_code=503, detail="No video frame available to capture") | |
| ts = datetime.now().strftime("%Y%m%d_%H%M%S") | |
| filt = camera.current_filter | |
| filename = f"snap_{filt}_{ts}.jpg" | |
| filepath = SNAPSHOT_DIR / filename | |
| filepath.write_bytes(jpeg) | |
| return { | |
| "status": "success", | |
| "filename": filename, | |
| "url": f"/media/snapshots/{filename}", | |
| "size_bytes": len(jpeg), | |
| "timestamp": ts, | |
| "filter": filt, | |
| "width": camera.actual_width, | |
| "height": camera.actual_height, | |
| } | |
| def remote_record_start(): | |
| ok, msg, filename = recorder.start() | |
| if not ok: | |
| raise HTTPException(status_code=400, detail=msg) | |
| return {"status": "success", "message": msg, "filename": filename, "recorder": recorder.get_status()} | |
| def remote_record_stop(): | |
| ok, msg, info = recorder.stop() | |
| if not ok: | |
| raise HTTPException(status_code=400, detail=msg) | |
| return {"status": "success", "message": msg, "info": info} | |
| MEDIA_SECRET = os.getenv("SECRET_KEY", "xtracker-media-secure-key-2026") | |
| def generate_signed_media_url(media_type: str, filename: str, expires_in: int = 3600) -> str: | |
| exp = int(time.time()) + expires_in | |
| msg = f"{media_type}:{filename}:{exp}".encode() | |
| sig = hmac.new(MEDIA_SECRET.encode(), msg, hashlib.sha256).hexdigest()[:16] | |
| return f"/media/{media_type}/{filename}?exp={exp}&sig={sig}" | |
| # --- Media Endpoints --- | |
| def list_media(): | |
| snapshots = [] | |
| for f in sorted(SNAPSHOT_DIR.glob("*.jpg"), key=os.path.getmtime, reverse=True): | |
| st = f.stat() | |
| snapshots.append({ | |
| "name": f.name, | |
| "url": generate_signed_media_url("snapshots", f.name, expires_in=3600), | |
| "size_bytes": st.st_size, | |
| "size_formatted": f"{round(st.st_size / 1024, 1)} KB", | |
| "modified": datetime.fromtimestamp(st.st_mtime).strftime("%Y-%m-%d %H:%M:%S"), | |
| "type": "image", | |
| }) | |
| recordings = [] | |
| for f in sorted(RECORDING_DIR.glob("*.mp4"), key=os.path.getmtime, reverse=True): | |
| st = f.stat() | |
| recordings.append({ | |
| "name": f.name, | |
| "url": generate_signed_media_url("recordings", f.name, expires_in=3600), | |
| "size_bytes": st.st_size, | |
| "size_formatted": f"{round(st.st_size / (1024*1024), 2)} MB" if st.st_size >= 1048576 else f"{round(st.st_size / 1024, 1)} KB", | |
| "modified": datetime.fromtimestamp(st.st_mtime).strftime("%Y-%m-%d %H:%M:%S"), | |
| "type": "video", | |
| }) | |
| return { | |
| "snapshots": snapshots, | |
| "recordings": recordings, | |
| "total_count": len(snapshots) + len(recordings) | |
| } | |
| def serve_snapshot_image(filename: str): | |
| f = SNAPSHOT_DIR / Path(filename).name | |
| if not f.exists(): | |
| raise HTTPException(status_code=404, detail="Snapshot not found") | |
| return FileResponse(str(f), media_type="image/jpeg") | |
| def serve_recording_video(filename: str, request: Request): | |
| file_path = RECORDING_DIR / Path(filename).name | |
| if not file_path.exists(): | |
| raise HTTPException(status_code=404, detail="Recording not found") | |
| file_size = file_path.stat().st_size | |
| range_header = request.headers.get("Range") | |
| if not range_header: | |
| return FileResponse(str(file_path), media_type="video/mp4") | |
| try: | |
| h = range_header.replace("bytes=", "").split("-") | |
| start = int(h[0]) if h[0] else 0 | |
| end = int(h[1]) if len(h) > 1 and h[1] else file_size - 1 | |
| except ValueError: | |
| return FileResponse(str(file_path), media_type="video/mp4") | |
| if start >= file_size or end >= file_size or start > end: | |
| return Response(status_code=416, headers={"Content-Range": f"bytes */{file_size}"}) | |
| chunk_size = (end - start) + 1 | |
| with open(file_path, "rb") as f: | |
| f.seek(start) | |
| data = f.read(chunk_size) | |
| headers = { | |
| "Content-Range": f"bytes {start}-{end}/{file_size}", | |
| "Accept-Ranges": "bytes", | |
| "Content-Length": str(chunk_size), | |
| "Content-Type": "video/mp4", | |
| } | |
| return Response(content=data, status_code=206, headers=headers) | |
| async def infer_frame(request: Request): | |
| """Remote GPU inference endpoint for Hugging Face Spaces offloading.""" | |
| import cv2 | |
| import numpy as np | |
| body = await request.body() | |
| if not body: | |
| raise HTTPException(status_code=400, detail="Empty image payload") | |
| arr = np.frombuffer(body, dtype=np.uint8) | |
| frame = cv2.imdecode(arr, cv2.IMREAD_COLOR) | |
| if frame is None: | |
| raise HTTPException(status_code=400, detail="Invalid JPEG buffer") | |
| dets, telem = camera.detector.detect(frame) | |
| return { | |
| "status": "success", | |
| "detections": dets, | |
| "telemetry": telem, | |
| "keypoints": camera.detector.latest_keypoints, | |
| "gpu_active": bool(telem.get("gpu_active", False)) | |
| } | |
| async def push_camera_frame(request: Request): | |
| """Ingests live JPEG frames from Browser Webcam (getUserMedia) or local station relay.""" | |
| body = await request.body() | |
| ok = camera.push_external_frame(body) | |
| return {"status": "success" if ok else "ignored"} | |
| async def websocket_frame_ingest(websocket: WebSocket): | |
| """Ultra-low-latency binary WebSocket ingest for live Browser Webcam frames.""" | |
| await websocket.accept() | |
| try: | |
| while True: | |
| data = await websocket.receive_bytes() | |
| camera.push_external_frame(data) | |
| except WebSocketDisconnect: | |
| pass | |
| except Exception: | |
| pass | |
| app.mount("/static", StaticFiles(directory=str(STATIC_DIR)), name="static") | |
| if __name__ == "__main__": | |
| import uvicorn | |
| port = int(os.environ.get("PORT", 7860)) | |
| uvicorn.run(app, host="0.0.0.0", port=port) | |