7.3 KiB
Webcam
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:
ffmpegfü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-pythonundnumpy
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
.enventhä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/FFD9ausschneidet. - 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: noverhindert 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) |
