Files

175 lines
5.9 KiB
Python
Raw Permalink Normal View History

import json
from uuid import uuid4
from fastapi import Depends, Query
from fastapi.responses import Response
from pydantic import Field
from pigway_frame_flow.core.auth import require
from pigway_frame_flow.display.thumbnail_plans import (
ThumbnailPlan,
get_plan,
render,
save_plan,
)
from pigway_frame_flow.errors import ErrorCode as E
from pigway_frame_flow.errors import FlowError
from pigway_frame_flow.models import StrictModel
class ThumbnailRequest(StrictModel):
source_id: str = Field(min_length=1, max_length=120)
item_id: str = Field(min_length=1, max_length=2048)
plan: ThumbnailPlan
def install_thumbnails(app, service):
from pigway_frame_flow.display.thumbnail_gallery import record, recover
recover(service)
@app.get(
"/api/v1/thumbnail-devices", dependencies=[Depends(require("images:read"))]
)
def devices():
return [
{
"id": "watch",
"name": "Watch",
"width": 410,
"height": 502,
"transport_connected": False,
"status": "generation_only",
}
]
@app.get("/api/v1/thumbnails", dependencies=[Depends(require("images:read"))])
def gallery(offset: int = Query(0, ge=0), limit: int = Query(50, ge=1, le=200)):
rows = service.db.all(
"SELECT assets.id,asset_sources.payload FROM assets JOIN asset_sources ON asset_sources.asset_id=assets.id "
"WHERE json_extract(asset_sources.payload,'$.type')='thumbnail' "
"ORDER BY assets.created_at DESC,assets.id DESC LIMIT ? OFFSET ?",
(limit, offset),
)
total = service.db.one(
"SELECT COUNT(*) AS count FROM assets JOIN asset_sources ON asset_sources.asset_id=assets.id "
"WHERE json_extract(asset_sources.payload,'$.type')='thumbnail'"
)["count"]
return {
"items": [
{
**service.assets.info(row["id"]),
"thumbnail": json.loads(row["payload"]),
}
for row in rows
],
"total": total,
"offset": offset,
"limit": limit,
}
@app.get("/api/v1/thumbnail-plans", dependencies=[Depends(require("images:read"))])
def plans():
return [
dict(
id=r["key"].removeprefix("thumbnail-plan:"), **json.loads(r["payload"])
)
for r in service.db.all(
"SELECT key,payload FROM app_config WHERE key LIKE 'thumbnail-plan:%' ORDER BY key"
)
]
@app.post("/api/v1/thumbnail-plans", dependencies=[Depends(require("admin"))])
def create(plan: ThumbnailPlan):
plan_id = uuid4().hex
save_plan(service, plan_id, plan)
return dict(id=plan_id, **plan.model_dump())
@app.get(
"/api/v1/thumbnail-plans/{plan_id}",
dependencies=[Depends(require("images:read"))],
)
def detail(plan_id: str):
return dict(id=plan_id, **get_plan(service, plan_id).model_dump())
@app.put(
"/api/v1/thumbnail-plans/{plan_id}", dependencies=[Depends(require("admin"))]
)
def update(plan_id: str, plan: ThumbnailPlan):
with service.lock:
get_plan(service, plan_id)
save_plan(service, plan_id, plan)
return dict(id=plan_id, **plan.model_dump())
@app.delete(
"/api/v1/thumbnail-plans/{plan_id}",
dependencies=[Depends(require("admin"))],
status_code=204,
)
def delete(plan_id: str):
with service.lock:
get_plan(service, plan_id)
for row in service.db.all("SELECT payload FROM jobs"):
job = json.loads(row["payload"])
if job.get("thumbnail_plan_id") == plan_id:
raise FlowError(
E.CONFLICT,
"Thumbnail plan is used by a task",
409,
{"reason": "thumbnailPlanInUse"},
)
for row in service.db.all(
"SELECT payload FROM operations WHERE status IN ('QUEUED','RENDERING','UPLOADING','UNKNOWN')"
):
if (
json.loads(row["payload"])
.get("options", {})
.get("thumbnail_plan_id")
== plan_id
):
raise FlowError(
E.CONFLICT,
"Thumbnail plan is used by a pending operation",
409,
{"reason": "thumbnailPlanInUse"},
)
service.db.execute(
"DELETE FROM app_config WHERE key=?", ("thumbnail-plan:" + plan_id,)
)
def image(request):
data, _, _ = service.photo_sources.read(
request.source_id,
request.item_id,
target_size=(request.plan.width, request.plan.height),
)
return render(data, request.plan)
@app.post(
"/api/v1/thumbnails/preview", dependencies=[Depends(require("images:read"))]
)
def preview(request: ThumbnailRequest):
return Response(
image(request),
media_type="image/jpeg",
headers={"Cache-Control": "no-store"},
)
@app.post("/api/v1/thumbnails/generate", dependencies=[Depends(require("admin"))])
def generate(request: ThumbnailRequest):
asset = service.assets.put(
image(request), request.plan.name + ".jpg", "generated"
)
record(
service,
asset["asset_id"],
{
"width": request.plan.width,
"height": request.plan.height,
"plan": request.plan.model_dump(),
"generated_at": asset["created_at"],
"source": {"source_id": request.source_id, "item_id": request.item_id},
},
)
return asset