# Webcam ![Webcam Server – Python MJPEG-Relay für ESP32-CAM und Raspberry-Pi-Kameras](picture/webcam-server.png) ## Das Problem – und die Lösung Günstige Netzwerkkameras wie ESP32-CAM oder Raspberry-Pi-Kameras liefern ihren Stream nur per **unverschlüsseltem HTTP** und hängen im **privaten Netz** (z. B. `192.168.x.x`). Direkt ins Internet gehören sie nicht: kein HTTPS, keine Anmeldung, und schon wenige Zuschauer bringen einen ESP32 an seine Grenzen. Außerdem blockiert der Browser HTTP-Streams auf HTTPS-Seiten (Mixed Content). Dieses Projekt löst das mit einem kleinen Relay: ``` Kamera (HTTP, privates Netz) → relay.py → Zoraxy (HTTPS, Login) → Browser im Internet ``` - Die Kameras bleiben im Heimnetz und sind von außen nie direkt erreichbar. Nach außen sichtbar ist nur der Relay. - **HTTPS** übernimmt der Reverse Proxy [Zoraxy](https://github.com/tobychui/zoraxy) (Zertifikat, Domain). Der Relay selbst spricht nur HTTP. - **Zugriffsschutz** per Basic-Auth (`AUTH_USER` / `AUTH_PASS`) oder in Zoraxy. - Jede Kamera wird nur **einmal** abgeholt, egal wie viele Leute zuschauen. - Das Web-Frontend zeigt alle Kameras mit Status, Foto, Vollbild und Start/Stop. Die Kamera-Einstellung (z. B. Auflösung) lässt sich dort ebenfalls auslösen, ohne dass der Browser die Kamera direkt ansprechen muss. > Ohne `AUTH_USER` / `AUTH_PASS` (oder einen Login in Zoraxy) kann jeder, der die Adresse kennt, die Kameras sehen. Für den Betrieb im Internet also immer einen Login einrichten. ## Bestandteile Zwei kleine Python-Tools für MJPEG-Kameras: - **`relay.py`** – holt den MJPEG-Stream jeder Kamera nur **einmal** ab und stellt ihn für beliebig viele Zuschauer per HTTP bereit (gedacht für den Betrieb hinter einem Reverse Proxy wie Zoraxy). - **`webcam.py`** – Kommandozeilenwerkzeug, um einen MJPEG-Stream zu testen, Einzelbilder/Frames zu speichern oder live anzuzeigen. ## Voraussetzungen - Python 3 - [`requests`](https://pypi.org/project/requests/) - Optional: `ffmpeg` für den Video-Download der Timelapse als MP4 (`apt install ffmpeg`); ohne ffmpeg liefert das Relay ein MJPEG-AVI - Optional für `webcam.py --show`: `opencv-python` und `numpy` ```bash pip install requests pip install opencv-python numpy # nur für --show ``` ## Relay (`relay.py`) ### Konfiguration Die Konfiguration erfolgt über eine `.env`-Datei neben `relay.py` oder über Umgebungsvariablen (echte Umgebungsvariablen haben Vorrang). ```bash cp .env.example .env ``` | Variable | Beschreibung | Standard | |---|---|---| | `CAMERA_` | Stream-URL einer Kamera, eine Zeile pro Kamera. Der Name wird Teil der URL (`/cam1/`). | – (mindestens eine nötig) | | `SETUP_` | Optional: Konfigurations-URL der Kamera (z. B. `http://IP/control?var=framesize&val=11`). Das Relay ruft sie auf, wenn im Frontend „Setup“ geklickt wird. | – | | `TIMELAPSE_INTERVAL` | Optional: Standard-Intervall der Timelapse in Sekunden. Ein/aus und Intervall werden je Kamera im Frontend gesetzt und in `state.json` gespeichert. | `300` | | `TIMELAPSE_DIR` | Zielordner, Struktur `//.jpg`. | `timelapse/` neben `relay.py` | | `TIMELAPSE_KEEP_DAYS` | Tage aufbewahren, ältere Tagesordner werden gelöscht (0 = unbegrenzt). | `0` | | `PORT` | Port, auf dem der Relay lauscht | `8000` | | `AUTH_USER` / `AUTH_PASS` | Optional: Basic-Auth. Nur sinnvoll mit HTTPS (z. B. in Zoraxy). | aus | Beispiel: ```ini CAMERA_cam1=http://192.168.1.8:8080/stream.mjpg CAMERA_cam2=http://192.168.1.9:8080/stream.mjpg PORT=8000 #AUTH_USER=benutzername #AUTH_PASS=geheim ``` > `.env` enthält ggf. Zugangsdaten – nicht ins Repository einchecken. ### Starten ```bash python3 relay.py ``` ### Als systemd-Dienst `webcam-relay.service` liegt im Repository. Pfade und Benutzer (`/home/joachim/git/webcam`, `joachim`) bei Bedarf in der Datei anpassen. Der Dienst nutzt das System-Python (`/usr/bin/python3`). Dort muss `requests` installiert sein, auch wenn es z. B. in Conda oder einem venv schon vorhanden ist: ```bash sudo apt install python3-requests ``` ```bash sudo cp webcam-relay.service /etc/systemd/system/ sudo systemctl daemon-reload sudo systemctl enable --now webcam-relay ``` Status und Logs: ```bash systemctl status webcam-relay journalctl -u webcam-relay -f ``` ### Endpunkte | Pfad | Beschreibung | |---|---| | `/` | Web-Frontend: Übersicht aller Kameras | | `//` | Web-Frontend: große Ansicht einer Kamera, die anderen als Vorschau-Leiste | | `/status.json` | Online-Status aller Kameras (wird vom Frontend alle 5 s abgefragt) | | `//stream.mjpg` | Livestream (`multipart/x-mixed-replace`) | | `//snapshot.jpg` | Aktuelles Einzelbild | | `POST //timelapse` | Timelapse steuern, Body `{"enabled":true,"interval":300}` | | `//timelapse.json` | Gespeicherte Timelapse-Bilder je Tag | | `//timelapse//.jpg` | Einzelnes Timelapse-Bild | | `//timelapse//video?fps=10` | Tagesvideo als Download (MP4 mit ffmpeg, sonst MJPEG-AVI); `fps` 1–60 | | `/stream.mjpg`, `/snapshot.jpg` | Alias für die erste Kamera (Abwärtskompatibilität) | | `/health` | Status aller Kameras (`200` wenn alle liefern, sonst `503`); ohne Authentifizierung erreichbar | ### Web-Frontend Das Frontend liegt in `frontend.html` (reines HTML/CSS/JS, keine Abhängigkeiten) und wird vom Relay ausgeliefert. Änderungen an der Datei wirken sofort, ohne Neustart. - Dunkles Design, zwei Kameras nebeneinander (auf dem Handy untereinander); Klick auf den Namen öffnet die große Ansicht - Statuspunkt und Hinweis „Kamera offline“ pro Kamera, automatisches Neuverbinden, Zähler „X / Y online“ - Buttons pro Kamera: Start, Stop, Reload, Foto (Download), Vollbild; „Timelapse“ schaltet die Aufnahme je Kamera ein/aus, „Galerie“ zeigt die Bilder (Tag wählen, anklicken, abspielen; Intervall einstellbar) und lädt ein Tagesvideo herunter (5/10/25 fps) - Toolbar: Alle starten / stoppen / neu laden - „Setup“ pro Kamera und „Setup alle“, sofern `SETUP_` konfiguriert ist; das Relay ruft die URL auf, der Browser muss die Kamera nicht erreichen - Streams werden getrennt, solange der Tab im Hintergrund ist - Alle Links sind relativ, daher funktioniert es auch unter einem Proxy-Prefix wie `/webcam/` ### Funktionsweise - Pro Kamera läuft ein Hintergrund-Thread, der den Stream liest und die JPEG-Frames anhand der Marker `FFD8`/`FFD9` ausschneidet. - Bei Verbindungsfehlern wird nach 3 Sekunden automatisch neu verbunden. - Alle Zuschauer bekommen jeweils das neueste Frame – die Kamera wird unabhängig von der Zuschauerzahl nur einmal belastet. - Der Header `X-Accel-Buffering: no` verhindert Pufferung durch den Proxy. ## Stream-Tool (`webcam.py`) ```bash python3 webcam.py # Verbindung testen, Frame-Infos ausgeben python3 webcam.py --snapshot bild.jpg # ein Einzelbild speichern python3 webcam.py --save frames/ -n 50 # 50 Frames als JPEG speichern python3 webcam.py --show # Live-Fenster (q oder Esc beendet) python3 webcam.py --url http://IP:PORT/stream.mjpg ``` | Option | Beschreibung | |---|---| | `--url` | Stream-URL (Standard ist in `webcam.py` hinterlegt) | | `--snapshot DATEI` | Ein Frame speichern und beenden | | `--save ORDNER` | Frames im Ordner speichern | | `-n N` | Nach N Frames beenden (`0` = endlos) | | `--show` | Live-Fenster anzeigen (OpenCV) |