Files
pigway-frame-flow/pigway_frame_flow/api/thumbnails.py
T
渡口浪人 7178235022
Community checks / verify (push) Skipped
Verify Frame Flow / verify (push) Failing after 5m58s
feat: manage saved thumbnail plans and select plans for tasks
2026-10-23 14:47:27 +08:00

119 lines
4.0 KiB
Python

import json
from uuid import uuid4
from fastapi import Depends
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):
@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):
return service.assets.put(
image(request), request.plan.name + ".jpg", "generated"
)