Files
webcam-server/README.md
T

7.3 KiB
Raw Blame History

Webcam

Webcam Server – Python MJPEG-Relay für ESP32-CAM und Raspberry-Pi-Kameras

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 (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
  • 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
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).

cp .env.example .env
Variable Beschreibung Standard
CAMERA_<name> Stream-URL einer Kamera, eine Zeile pro Kamera. Der Name wird Teil der URL (/cam1/). – (mindestens eine nötig)
SETUP_<name> 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 <name>/<JJJJ-MM-TT>/<HHMMSS>.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:

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

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:

sudo apt install python3-requests
sudo cp webcam-relay.service /etc/systemd/system/
sudo systemctl daemon-reload
sudo systemctl enable --now webcam-relay

Status und Logs:

systemctl status webcam-relay
journalctl -u webcam-relay -f

Endpunkte

Pfad Beschreibung
/ Web-Frontend: Übersicht aller Kameras
/<name>/ 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)
/<name>/stream.mjpg Livestream (multipart/x-mixed-replace)
/<name>/snapshot.jpg Aktuelles Einzelbild
POST /<name>/timelapse Timelapse steuern, Body {"enabled":true,"interval":300}
/<name>/timelapse.json Gespeicherte Timelapse-Bilder je Tag
/<name>/timelapse/<tag>/<datei>.jpg Einzelnes Timelapse-Bild
/<name>/timelapse/<tag>/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_<name> 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)

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)