Files
hobbs-iac/compose/hf-pdocker-01/frigate-notify/app/routes/known_objects.py
T
jeremy dddc6734d0 feat: frigate-notify v2 (modular app, CLIP recognition) + clip-service
Splits the single main.py into config/database/models/routes/vision/recognition/
ollama_client/cleanup modules, adds a web UI, and introduces clip-service for
CLIP (ViT-L-14) embeddings.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-11 11:19:03 -05:00

178 lines
5.0 KiB
Python

"""Known objects API endpoints for frigate-notify v2."""
import logging
from typing import Optional
from fastapi import APIRouter, HTTPException, Query
from fastapi.responses import Response
from database import get_connection
from models import KnownObjectResponse, KnownObjectUpdate
logger = logging.getLogger(__name__)
router = APIRouter(prefix="/api/known-objects", tags=["known-objects"])
@router.get("")
async def list_known_objects(
type: Optional[str] = Query(default=None, pattern="^(person|vehicle|pet)$"),
):
"""List all known objects with embedding_count. Optional type filter."""
conn = get_connection()
query = """
SELECT
ko.id, ko.name, ko.type, ko.action,
ko.created_at, ko.updated_at,
COUNT(e.id) AS embedding_count
FROM known_objects ko
LEFT JOIN embeddings e ON ko.id = e.known_object_id
"""
params: list = []
if type:
query += " WHERE ko.type = ?"
params.append(type)
query += " GROUP BY ko.id ORDER BY ko.name"
cursor = conn.execute(query, params)
rows = cursor.fetchall()
return [
{
"id": row["id"],
"name": row["name"],
"type": row["type"],
"action": row["action"],
"created_at": row["created_at"],
"updated_at": row["updated_at"],
"embedding_count": row["embedding_count"],
}
for row in rows
]
@router.get("/{object_id}")
async def get_known_object(object_id: int):
"""Get a single known object with list of its embeddings (id, created_at only)."""
conn = get_connection()
cursor = conn.execute(
"SELECT id, name, type, action, created_at, updated_at FROM known_objects WHERE id = ?",
(object_id,),
)
row = cursor.fetchone()
if not row:
raise HTTPException(status_code=404, detail="Known object not found")
# Get embeddings list
emb_cursor = conn.execute(
"SELECT id, created_at FROM embeddings WHERE known_object_id = ? ORDER BY created_at",
(object_id,),
)
embeddings = [
{"id": e["id"], "created_at": e["created_at"]} for e in emb_cursor.fetchall()
]
return {
"id": row["id"],
"name": row["name"],
"type": row["type"],
"action": row["action"],
"created_at": row["created_at"],
"updated_at": row["updated_at"],
"embeddings": embeddings,
}
@router.get("/{object_id}/thumbnails/{embedding_id}")
async def get_embedding_thumbnail(object_id: int, embedding_id: int):
"""Return raw JPEG thumbnail from embeddings table."""
conn = get_connection()
cursor = conn.execute(
"SELECT thumbnail FROM embeddings WHERE id = ? AND known_object_id = ?",
(embedding_id, object_id),
)
row = cursor.fetchone()
if not row:
raise HTTPException(status_code=404, detail="Embedding not found")
if row["thumbnail"] is None:
raise HTTPException(status_code=404, detail="No thumbnail available")
return Response(content=row["thumbnail"], media_type="image/jpeg")
@router.put("/{object_id}")
async def update_known_object(object_id: int, body: KnownObjectUpdate):
"""Update name/type/action for a known object. Sets updated_at."""
conn = get_connection()
cursor = conn.execute(
"SELECT id FROM known_objects WHERE id = ?",
(object_id,),
)
if not cursor.fetchone():
raise HTTPException(status_code=404, detail="Known object not found")
updates = []
params: list = []
if body.name is not None:
updates.append("name = ?")
params.append(body.name)
if body.type is not None:
updates.append("type = ?")
params.append(body.type)
if body.action is not None:
updates.append("action = ?")
params.append(body.action)
if not updates:
raise HTTPException(status_code=400, detail="No fields to update")
updates.append("updated_at = CURRENT_TIMESTAMP")
params.append(object_id)
conn.execute(
f"UPDATE known_objects SET {', '.join(updates)} WHERE id = ?",
params,
)
conn.commit()
# Return the updated object
cursor = conn.execute(
"SELECT id, name, type, action, created_at, updated_at FROM known_objects WHERE id = ?",
(object_id,),
)
row = cursor.fetchone()
return KnownObjectResponse(
id=row["id"],
name=row["name"],
type=row["type"],
action=row["action"],
created_at=row["created_at"],
updated_at=row["updated_at"],
)
@router.delete("/{object_id}")
async def delete_known_object(object_id: int):
"""Delete a known object. Embeddings cascade delete automatically."""
conn = get_connection()
cursor = conn.execute(
"SELECT id FROM known_objects WHERE id = ?",
(object_id,),
)
if not cursor.fetchone():
raise HTTPException(status_code=404, detail="Known object not found")
conn.execute("DELETE FROM known_objects WHERE id = ?", (object_id,))
conn.commit()
return {"status": "ok"}