addet test set

This commit is contained in:
marcinator_2 committed 2026-10-14 17:40:31 +02:00
1 parent d370f992ce
commit b0f35bf3cc
21 files changed
+1796 -183

No files matched your search

+98 -10
View File
@@ -3,6 +3,12 @@
from contextlib import closing
from datetime import datetime, timedelta, timezone
from pathlib import Path
from io import BytesIO
import re
import tempfile
import uuid
from PIL import Image
import math
import os
import sqlite3
@@ -36,6 +42,7 @@ class EventStore:
def __init__(self, state_dir: Path):
state_dir.mkdir(parents=True, exist_ok=True)
self.path = state_dir / "events.sqlite3"
self.image_dir = state_dir / "event-images"
with closing(self._connect()) as connection:
with connection:
connection.execute("""
@@ -75,6 +82,8 @@ class EventStore:
"ALTER TABLE events ADD COLUMN profile_id INTEGER "
"REFERENCES profiles(id) ON DELETE SET NULL"
)
if "image_filename" not in columns:
connection.execute("ALTER TABLE events ADD COLUMN image_filename TEXT")
connection.execute("""
CREATE TABLE IF NOT EXISTS profile_templates (
profile_id INTEGER PRIMARY KEY
@@ -98,18 +107,87 @@ class EventStore:
def record_test_trigger(self, mode: str) -> int:
return self._record_trigger(mode, "test_trigger")
def record_detection_trigger(self, mode: str) -> int:
return self._record_trigger(mode, "person_line_crossing")
def record_detection_trigger(self, mode: str, *, jpeg=None, region=None) -> int:
return self._record_trigger(mode, "person_line_crossing", jpeg, region)
def _record_trigger(self, mode: str, kind: str) -> int:
def record_motion_trigger(self, mode: str, *, jpeg=None, region=None) -> int:
return self._record_trigger(mode, "motion_direction_test", jpeg, region)
def _save_image(self, jpeg: bytes, region) -> str:
if (region is None or len(region) != 4 or
any(not math.isfinite(v) or not 0 <= v <= 1 for v in region) or
region[0] >= region[2] or region[1] >= region[3]):
raise ValueError("Ungültiger Bildbereich")
with Image.open(BytesIO(jpeg)) as image:
width, height = image.size
# Round inward: no pixel outside the chosen private region is saved.
box = (math.ceil(region[0]*width), math.ceil(region[1]*height),
math.floor(region[2]*width), math.floor(region[3]*height))
if box[2] <= box[0] or box[3] <= box[1]:
raise ValueError("Bildbereich ist zu klein")
cropped = image.crop(box).convert('RGB')
self.image_dir.mkdir(mode=0o700, parents=True, exist_ok=True)
os.chmod(self.image_dir, 0o700)
filename = uuid.uuid4().hex + '.jpg'
target = self.image_dir / filename
with tempfile.NamedTemporaryFile(dir=self.image_dir, suffix='.tmp', delete=False) as stream:
temporary = Path(stream.name)
try:
cropped.save(stream, format='JPEG', quality=90)
stream.flush()
os.fsync(stream.fileno())
os.replace(temporary, target)
finally:
temporary.unlink(missing_ok=True)
return filename
def _record_trigger(self, mode: str, kind: str, jpeg=None, region=None) -> int:
occurred_at = _timestamp(_utc_now())
filename = self._save_image(jpeg, region) if jpeg is not None else None
try:
with closing(self._connect()) as connection:
with connection:
cursor = connection.execute(
"INSERT INTO events (occurred_at, kind, mode, image_filename) VALUES (?, ?, ?, ?)",
(occurred_at, kind, mode, filename),
)
event_id = cursor.lastrowid
return event_id
except Exception:
if filename:
(self.image_dir / filename).unlink(missing_ok=True)
raise
def _image_path(self, filename):
if not isinstance(filename, str) or not re.fullmatch(r'[0-9a-f]{32}\.jpg', filename):
raise KeyError("Kein Bild für dieses Ereignis")
path = self.image_dir / filename
if path.is_symlink():
raise KeyError("Kein Bild für dieses Ereignis")
return path
def image_path(self, event_id: int) -> Path:
with closing(self._connect()) as connection:
row = connection.execute("SELECT image_filename FROM events WHERE id = ?", (event_id,)).fetchone()
if row is None:
raise KeyError("Ereignis nicht gefunden")
path = self._image_path(row['image_filename'])
if not path.is_file():
raise KeyError("Bild nicht vorhanden")
return path
def delete_image(self, event_id: int) -> bool:
with closing(self._connect()) as connection:
with connection:
cursor = connection.execute(
"INSERT INTO events (occurred_at, kind, mode) VALUES (?, ?, ?)",
(occurred_at, kind, mode),
)
return cursor.lastrowid
row = connection.execute("SELECT image_filename FROM events WHERE id = ?", (event_id,)).fetchone()
if row is None:
raise KeyError("Ereignis nicht gefunden")
if row['image_filename'] is None:
return False
path = self._image_path(row['image_filename'])
path.unlink(missing_ok=True)
connection.execute("UPDATE events SET image_filename = NULL WHERE id = ?", (event_id,))
return True
def recent(self, limit: int = 20) -> list[dict]:
if not 1 <= limit <= 100:
@@ -118,11 +196,21 @@ class EventStore:
with closing(self._connect()) as connection:
rows = connection.execute(
"SELECT e.id, e.occurred_at, e.kind, e.mode, "
"e.profile_id, p.label AS profile_label "
"e.profile_id, e.image_filename, p.label AS profile_label "
"FROM events e LEFT JOIN profiles p ON p.id = e.profile_id "
"ORDER BY e.id DESC LIMIT ?", (limit,),
).fetchall()
return [dict(row) for row in rows]
result = []
for row in rows:
event = dict(row)
filename = event.pop('image_filename')
try:
present = self._image_path(filename).is_file()
except KeyError:
present = False
event['image_url'] = f"/api/events/{event['id']}/image" if present else None
result.append(event)
return result
def create_profile(self, label: str) -> dict:
label = label.strip()