# AppRobotWebcam Webcam-Service für den AppRobot. Liefert Live-MJPEG-Streams und HD-Standbilder über einen einzelnen HTTP-Port — als Docker-Container, ohne externe Streaming-Server. ## Was es tut | | | |---|---| | **Live-Stream** | MJPEG multipart im Browser ``, ~139 ms Latenz | | **HD-Snapshot** | Ein JPEG pro Kamera auf Knopfdruck oder per HTTP GET | | **Snapshot alle** | Alle Kameras parallel in einem Schritt | | **REST-API** | Kameraliste, Snapshots, Streams — für andere Container nutzbar | ## Kameras (aktuell) | ID | Modell | Live | HD-Grab | |---|---|---|---| | cam0 | Logitech C270 | 640×480 | 1280×960 | | cam1 | Logitech C270 | 640×480 | 1280×960 | | cam2 | Logitech C920 | 640×480 | 1920×1080 | Konfiguration ausschliesslich über `cameras.json` — kein Redeploy bei Kamera-Änderungen. ## Zugriff ``` http://:8444/ Viewer http://:8444/api/stream/cam0 Live-MJPEG http://:8444/api/snapshot/cam0 640er JPEG http://:8444/api/snapshot/cam0/hires HD-JPEG http://:8444/api/cameras Kamera-Metadaten (JSON) http://:8444/health Status ``` ## API-Referenz Basis-URL: `http://:8444` --- ### `GET /api/cameras` Vollständige Kamera-Metadaten. Primärer Einstiegspunkt für andere Container. ```json { "cameras": [ { "id": "cam0", "name": "Kamera 0", "position": "front", "stream": true, "hires": true, "encode": "copybsf", "mseCodec": null, "note": "usb-046d_0825_3BB3FE20-video-index0", "calibrationUrl": "/api/cameras/cam0/calibration" } ] } ``` `calibrationUrl` fehlt, wenn keine `.npz` unter `data/calibration/{id}/` liegt. `encode`: `"copybsf"` (MJPEG-Copy, Default) | `"mjpeg"` (Re-Encode) | `"h264"` (GPU, MSE). `mseCodec`: nur bei `encode="h264"` gesetzt, z.B. `"avc1.4D001F"`. --- ### `GET /api/cameras/{id}/calibration` Liefert die `.npz`-Kalibrierungsdatei (Kameramatrix + Verzerrungskoeffizienten) als Binary. ``` Content-Type: application/octet-stream Content-Disposition: attachment; filename="cam0_calibration.npz" Cache-Control: public, max-age=86400 ``` 404 wenn keine Kalibrierung vorhanden. Einlesen in Python: ```python import numpy as np, requests d = np.load(requests.get(".../api/cameras/cam0/calibration", stream=True).raw) K, D = d["camera_matrix"], d["dist_coeffs"] ``` --- ### `GET /api/snapshot/{id}` Letztes Live-JPEG (Live-Auflösung, z.B. 640×480) aus dem RAM-Puffer. Bei `stream: false`: one-shot — öffnet Gerät kurz, schließt es wieder. ``` Content-Type: image/jpeg X-Camera-Id: cam0 X-Frame-Width: 640 X-Timestamp: 2026-06-10T07:30:00.000Z Cache-Control: no-store ``` 503 wenn kein Frame verfügbar (Kamera nicht erreichbar). --- ### `GET /api/snapshot/{id}/hires` HD-JPEG (volle Auflösung, z.B. 1280×960 oder 1920×1080). Pausiert den Live-Stream kurz, nimmt ein Einzelbild auf, startet Live neu. Gleiche Response-Headers wie `/api/snapshot/{id}`. --- ### `GET /api/stream/{id}` Live-Stream. Format hängt vom `encode`-Modus ab: | encode | Content-Type | Player | |---|---|---| | `copybsf` / `mjpeg` | `multipart/x-mixed-replace; boundary=frame` | `` | | `h264` | `video/mp4` (fragmentiertes MP4) | `