first commit

This commit is contained in:
2026-08-20 21:26:05 +02:00
commit 81717cb9a8
10 changed files with 777 additions and 0 deletions
+113
View File
@@ -0,0 +1,113 @@
#!/usr/bin/env python3
"""Doorbell JPEG proxy: prefetch from Frigate and serve a baseline 480px-wide JPEG."""
from __future__ import annotations
import hashlib
import io
import os
import threading
import time
import requests
from flask import Flask, request
from PIL import Image
FRIGATE_URL = os.environ.get(
"FRIGATE_URL", "http://10.0.1.197:5050/api/doorbell/latest.jpg?h=270"
)
TARGET_WIDTH = int(os.environ.get("TARGET_WIDTH", "480"))
JPEG_QUALITY = int(os.environ.get("JPEG_QUALITY", "40"))
FETCH_TIMEOUT = float(os.environ.get("FETCH_TIMEOUT", "5"))
PREFETCH_HZ = float(os.environ.get("PREFETCH_HZ", "10"))
app = Flask(__name__)
_lock = threading.Lock()
_jpeg = b""
_etag = ""
_width = 0
_height = 0
def _encode(frame: Image.Image) -> tuple[bytes, int, int]:
frame = frame.convert("RGB")
if frame.width != TARGET_WIDTH:
height = max(1, round(frame.height * TARGET_WIDTH / frame.width))
frame = frame.resize((TARGET_WIDTH, height), Image.BILINEAR)
buf = io.BytesIO()
frame.save(
buf,
format="JPEG",
quality=JPEG_QUALITY,
subsampling=2,
optimize=False,
progressive=False,
)
return buf.getvalue(), frame.width, frame.height
def _prefetch_loop() -> None:
session = requests.Session()
interval = 1.0 / max(PREFETCH_HZ, 0.5)
while True:
started = time.monotonic()
try:
response = session.get(FRIGATE_URL, timeout=FETCH_TIMEOUT)
response.raise_for_status()
with Image.open(io.BytesIO(response.content)) as image:
encoded, width, height = _encode(image)
etag = '"' + hashlib.md5(encoded).hexdigest() + '"'
with _lock:
global _jpeg, _etag, _width, _height
_jpeg = encoded
_etag = etag
_width = width
_height = height
except Exception as exc: # noqa: BLE001 — keep last good frame
print(f"prefetch failed: {exc}", flush=True)
delay = interval - (time.monotonic() - started)
if delay > 0:
time.sleep(delay)
def _snapshot() -> tuple[bytes, str, int, int]:
with _lock:
return _jpeg, _etag, _width, _height
@app.get("/")
@app.get("/latest.jpg")
def latest():
body, etag, width, height = _snapshot()
if not body:
return "Image not ready", 503, {"Content-Type": "text/plain"}
if request.headers.get("If-None-Match") == etag:
return ("", 304, {"ETag": etag, "Cache-Control": "no-cache"})
return (
body,
200,
{
"Content-Type": "image/jpeg",
"Content-Length": str(len(body)),
"ETag": etag,
"Last-Modified": time.strftime("%a, %d %b %Y %H:%M:%S GMT", time.gmtime()),
"Cache-Control": "no-cache",
"Accept-Ranges": "none",
"X-Image-Size": f"{width}x{height}",
},
)
@app.get("/health")
def health():
body, _, width, height = _snapshot()
status = 200 if body else 503
return {"ready": bool(body), "bytes": len(body), "size": f"{width}x{height}"}, status
if __name__ == "__main__":
threading.Thread(target=_prefetch_loop, name="prefetch", daemon=True).start()
app.run(host="0.0.0.0", port=int(os.environ.get("PORT", "80")), threaded=True)