VIDEO NOT IMAGES + history4all

This commit is contained in:
lda committed 2026-03-01 14:17:41 +07:00
1 parent c716d23151
commit 4201a3cbb1
6 files changed
+66 -13

No files matched your search

+2
View File
@@ -14,6 +14,8 @@ wheels/
# artifacts/logs i forgor what are the paths uhhhhhhh # artifacts/logs i forgor what are the paths uhhhhhhh
alerts.jsonl alerts.jsonl
output.jsonl
output.mp4
static/alerts/ static/alerts/
screenshots/ screenshots/
events/ events/
+5 -2
View File
@@ -122,10 +122,13 @@ class MySQLRepository:
conn.close() conn.close()
return [DetectionRecord(**r) for r in rows] return [DetectionRecord(**r) for r in rows]
def clear_detections(self, user_id: int) -> int: def clear_detections(self, user_id: Optional[int] = None) -> int:
conn = self._connect() conn = self._connect()
cursor = conn.cursor() cursor = conn.cursor()
cursor.execute("DELETE FROM detections WHERE user_id = %s", (user_id,)) if user_id is None:
cursor.execute("DELETE FROM detections")
else:
cursor.execute("DELETE FROM detections WHERE user_id = %s", (user_id,))
conn.commit() conn.commit()
count = cursor.rowcount count = cursor.rowcount
conn.close() conn.close()
+2 -2
View File
@@ -59,8 +59,8 @@ class Repository(Protocol):
"""Get detection history, optionally filtered by user. Newest first.""" """Get detection history, optionally filtered by user. Newest first."""
... ...
def clear_detections(self, user_id: int) -> int: def clear_detections(self, user_id: Optional[int] = None) -> int:
"""Delete all detections for a user. Returns number of rows deleted.""" """Delete detections, optionally filtered by user. Returns rows deleted."""
... ...
def close(self) -> None: def close(self) -> None:
+7 -2
View File
@@ -109,9 +109,14 @@ class SQLiteRepository:
).fetchall() ).fetchall()
return [DetectionRecord(**dict(r)) for r in rows] return [DetectionRecord(**dict(r)) for r in rows]
def clear_detections(self, user_id: int) -> int: def clear_detections(self, user_id: Optional[int] = None) -> int:
conn = self._get_conn() conn = self._get_conn()
cursor = conn.execute("DELETE FROM detections WHERE user_id = ?", (user_id,)) if user_id is None:
cursor = conn.execute("DELETE FROM detections")
else:
cursor = conn.execute(
"DELETE FROM detections WHERE user_id = ?", (user_id,)
)
conn.commit() conn.commit()
return cursor.rowcount return cursor.rowcount
+49 -6
View File
@@ -32,7 +32,7 @@ from pipeline.config import PipelineConfig
from pipeline.core import SmokingDetectionPipeline from pipeline.core import SmokingDetectionPipeline
from pipeline.models import YoloCigaretteDetector, YoloClassifier, YoloPersonDetector from pipeline.models import YoloCigaretteDetector, YoloClassifier, YoloPersonDetector
from pipeline.protocols import Sink from pipeline.protocols import Sink
from pipeline.sinks import DatabaseSink, FileAlertStore, MetricsSink from pipeline.sinks import FileAlertStore, MetricsSink, SmartEventSink
from pipeline.sources.stream import ThreadedStreamSource from pipeline.sources.stream import ThreadedStreamSource
from pipeline.staged_core import StageBasedPipeline from pipeline.staged_core import StageBasedPipeline
from pipeline.types import Frame from pipeline.types import Frame
@@ -62,6 +62,48 @@ class LiveFrameSink(Sink):
pass pass
class DatabaseSmartEventSink(SmartEventSink):
"""Records event clips and stores clip path in DB as alert evidence."""
def __init__(
self,
db_repo: Repository,
fps: float,
user_id: int = 1,
camera_source: str = "Pipeline",
output_dir: str = "static/events",
):
super().__init__(output_dir=output_dir, fps=fps)
self.db_repo = db_repo
self.user_id = user_id
self.camera_source = camera_source
def _trigger_event(self, trigger_frame: Frame):
was_recording = self.recording
super()._trigger_event(trigger_frame)
# Save one DB row per newly started event clip
if was_recording or not self.recording or self.current_event_id is None:
return
ticket = trigger_frame.ticket
if not ticket:
return
filename = f"event_{self.current_event_id}.mp4"
web_path = f"/static/events/{filename}"
try:
self.db_repo.save_detection(
user_id=self.user_id,
image_path=web_path,
confidence=ticket.confidence,
camera_source=self.camera_source,
)
logger.info("Saved event alert to DB: %s", web_path)
except Exception as e:
logger.error("Failed to save event alert to DB: %s", e)
def _get_lan_address() -> tuple[str, str]: def _get_lan_address() -> tuple[str, str]:
"""Return (ip, hostname) for the primary LAN interface.""" """Return (ip, hostname) for the primary LAN interface."""
ip = "127.0.0.1" ip = "127.0.0.1"
@@ -160,7 +202,7 @@ async def app_lifespan(_app: FastAPI):
# 4. Sinks # 4. Sinks
sinks: list[Sink] = [ sinks: list[Sink] = [
LiveFrameSink(), LiveFrameSink(),
DatabaseSink(db, image_dir="static/alerts"), DatabaseSmartEventSink(db, fps=source.fps, output_dir="static/events"),
MetricsSink(log_interval=200), MetricsSink(log_interval=200),
FileAlertStore("alerts.jsonl"), FileAlertStore("alerts.jsonl"),
] ]
@@ -231,6 +273,7 @@ app = FastAPI(title="Smoking Detection", lifespan=app_lifespan) # type: ignore
# Serve static assets (alert images, CSS/JS, etc.) # Serve static assets (alert images, CSS/JS, etc.)
Path("static/alerts").mkdir(parents=True, exist_ok=True) Path("static/alerts").mkdir(parents=True, exist_ok=True)
Path("static/events").mkdir(parents=True, exist_ok=True)
app.mount("/static", StaticFiles(directory="static"), name="static") app.mount("/static", StaticFiles(directory="static"), name="static")
@@ -269,7 +312,7 @@ def login(username: str = Form(...), password: str = Form(...)):
@app.get("/history") @app.get("/history")
def get_history(user_id: int): def get_history(user_id: int | None = None):
try: try:
records = db.get_detections(user_id) records = db.get_detections(user_id)
return [ return [
@@ -288,10 +331,10 @@ def get_history(user_id: int):
@app.post("/clear_history") @app.post("/clear_history")
def clear_history(user_id: int = Form(...)): def clear_history():
try: try:
deleted = db.clear_detections(user_id) deleted = db.clear_detections()
logger.info("Cleared %d records for user %d", deleted, user_id) logger.info("Cleared %d records", deleted)
return {"status": "success", "message": "History cleared"} return {"status": "success", "message": "History cleared"}
except Exception as e: except Exception as e:
return {"status": "error", "message": str(e)} return {"status": "error", "message": str(e)}