From 6b2cc45550be65d1f62c9789e51c5cb8906b9a66 Mon Sep 17 00:00:00 2001 From: Joachim Hummel Date: Sun, 18 Oct 2026 13:34:33 +0200 Subject: [PATCH] Timelapse pro Kamera (Ein/Aus, Intervall) im Relay, Galerie im Frontend --- .env.example | 6 +++ .gitignore | 1 + README.md | 8 +++- frontend.html | 126 +++++++++++++++++++++++++++++++++++++++++++++++-- relay.py | 127 ++++++++++++++++++++++++++++++++++++++++++++++++-- 5 files changed, 261 insertions(+), 7 deletions(-) diff --git a/.env.example b/.env.example index e2da8e5..ace283f 100644 --- a/.env.example +++ b/.env.example @@ -7,6 +7,12 @@ CAMERA_cam3=http://IP:PORT/pfad # Optional: Konfigurations-URL pro Kamera (Button "Setup" im Frontend), z.B. ESP32-CAM Auflösung #SETUP_cam2=http://IP/control?var=framesize&val=11 +# Optional: Timelapse. Ein/aus und Intervall je Kamera im Frontend; hier nur Standard-Intervall (Sek.), Ordner, Aufbewahrung +#TIMELAPSE_INTERVAL=300 +# Zielordner (default: timelapse/ neben relay.py; eigener Pfad muss für den Dienstbenutzer beschreibbar sein) +#TIMELAPSE_DIR=/pfad/zum/ordner +#TIMELAPSE_KEEP_DAYS=30 + PORT=8000 # Optional: Login (nur sinnvoll mit HTTPS in Zoraxy) #AUTH_USER=joachim diff --git a/.gitignore b/.gitignore index d50a09f..9983369 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ .env __pycache__/ +timelapse/ diff --git a/README.md b/README.md index 3737802..6eae8d5 100644 --- a/README.md +++ b/README.md @@ -52,6 +52,9 @@ cp .env.example .env |---|---|---| | `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 | @@ -105,6 +108,9 @@ journalctl -u webcam-relay -f | `/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 | | `/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 | @@ -114,7 +120,7 @@ Das Frontend liegt in `frontend.html` (reines HTML/CSS/JS, keine Abhängigkeiten - 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 +- 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) - 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 diff --git a/frontend.html b/frontend.html index 91deb06..3484f8a 100644 --- a/frontend.html +++ b/frontend.html @@ -55,6 +55,7 @@ main{padding:22px;max-width:1800px;margin:0 auto} .btn.danger:hover{box-shadow:0 4px 16px rgba(239,68,68,.45)} .btn.ok{background:linear-gradient(135deg,#22c55e,#15803d);box-shadow:0 2px 10px rgba(34,197,94,.25)} .btn.ok:hover{box-shadow:0 4px 16px rgba(34,197,94,.45)} +.btn.rec{background:linear-gradient(135deg,#ef4444,#b91c1c);color:#fff;border:0} .btn.alt{background:#222a3a;box-shadow:none;color:var(--fg);border:1px solid var(--line)} .btn.alt:hover{border-color:var(--accent);color:var(--accent)} .grid .cap{cursor:grab} @@ -67,6 +68,18 @@ main{padding:22px;max-width:1800px;margin:0 auto} .strip .btn{padding:5px 9px} .tile:fullscreen{border:0;border-radius:0;display:flex;flex-direction:column;background:#000} .tile:fullscreen .view{flex:1;aspect-ratio:auto} +#tl{position:fixed;inset:0;z-index:20;background:rgba(5,7,11,.92);display:flex;flex-direction:column;padding:16px;gap:12px} +#tl .bar{display:flex;flex-wrap:wrap;gap:10px;align-items:center} +#tl .bar h2{margin:0;font-size:16px} +#tl select{background:var(--card);color:var(--fg);border:1px solid var(--line);border-radius:9px;padding:6px 10px;font:inherit} +#tl .body{flex:1;min-height:0;display:flex;flex-direction:column;gap:12px} +#tl .big{flex:1;min-height:0;background:#000;border-radius:var(--r);display:flex;align-items:center;justify-content:center;position:relative} +#tl .big img{max-width:100%;max-height:100%;object-fit:contain} +#tl .big .when{position:absolute;left:12px;bottom:10px;background:rgba(0,0,0,.6);padding:2px 10px;border-radius:999px;font-size:13px;font-variant-numeric:tabular-nums} +#tl .thumbs{display:flex;gap:8px;overflow-x:auto;padding-bottom:4px} +#tl .thumbs img{height:72px;aspect-ratio:16/9;object-fit:cover;border-radius:6px;border:2px solid transparent;cursor:pointer;flex:none;background:#000} +#tl .thumbs img.sel{border-color:var(--accent)} +#tl .empty{color:var(--muted);margin:auto} @media (max-width:700px){.grid{grid-template-columns:1fr}} @media (max-width:600px){header{padding:12px 14px}main{padding:12px}.btn span{display:none}.btn{padding:7px 10px}} @@ -87,6 +100,7 @@ main{padding:22px;max-width:1800px;margin:0 auto}
+