feat: exclude source photos from automatic selection
Community checks / verify (push) Skipped
Verify Frame Flow / verify (push) Successful in 5m53s

This commit is contained in:
渡口浪人
2026-10-20 18:50:43 +08:00
parent e6a3974d08
commit 383ed45185
17 changed files with 735 additions and 23 deletions
+49 -1
View File
@@ -17,6 +17,7 @@ from starlette.exceptions import HTTPException
from pigway_frame_flow.api.history import install_history
from pigway_frame_flow.core.accounts import install_accounts, seed_accounts
from pigway_frame_flow.core.auth import require, seed_client
from pigway_frame_flow.core.photo_preferences import annotate, set_flags
from pigway_frame_flow.core.registry import DeviceRegistry
from pigway_frame_flow.display.service import DisplayService
from pigway_frame_flow.errors import ErrorCode as E
@@ -43,12 +44,14 @@ from pigway_frame_flow.models import (
Health,
History,
ImageContent,
ImageExclusion,
ImageStar,
ImageURLConfig,
Job,
JobInput,
Operation,
Options,
PhotoPreference,
PhotoSourceContent,
Playlist,
PlaylistInput,
@@ -625,6 +628,10 @@ def create_app(settings=None, start_worker=True):
409,
)
path.unlink(missing_ok=True)
db.execute(
"DELETE FROM photo_preferences WHERE source_id='@assets' AND item_id=?",
(asset_id,),
)
db.execute("DELETE FROM asset_metadata WHERE asset_id=?", (asset_id,))
db.execute("DELETE FROM asset_sources WHERE asset_id=?", (asset_id,))
db.execute("DELETE FROM assets WHERE id=?", (asset_id,))
@@ -649,6 +656,18 @@ def create_app(settings=None, start_worker=True):
)
return assets.info(asset_id)
@app.put(
"/api/v1/assets/{asset_id}/exclusion",
response_model=Asset,
tags=["Images"],
dependencies=[Depends(require("images:write"))],
)
def exclude_asset(asset_id: str, payload: ImageExclusion):
with service.lock:
assets.info(asset_id)
set_flags(db, "@assets", asset_id, excluded=payload.excluded)
return assets.info(asset_id)
@app.post(
"/api/v1/assets/batch",
tags=["Images"],
@@ -661,6 +680,10 @@ def create_app(settings=None, start_worker=True):
try:
if payload.action == "delete":
delete_asset(id)
elif payload.action in {"exclude", "include"}:
exclude_asset(
id, ImageExclusion(excluded=payload.action == "exclude")
)
else:
star_asset(id, ImageStar(starred=payload.action == "star"))
results.append({"asset_id": id, "status": "completed"})
@@ -782,13 +805,38 @@ def create_app(settings=None, start_worker=True):
source_id, refresh=refresh, target_size=size
)
return {
"items": items[offset : offset + limit],
"items": annotate(db, source_id, items[offset : offset + limit]),
"total": len(items),
"offset": offset,
"limit": limit,
"catalog_limit": service.photo_sources.get(source_id).catalog_limit,
}
@app.put(
"/api/v1/photo-sources/{source_id}/preferences",
tags=["Photo sources"],
dependencies=[Depends(require("admin"))],
)
def source_preference(
source_id: str, payload: PhotoPreference, device_id: str | None = None
):
source = service.photo_sources.get(source_id)
size = (1200, 1600)
if source.orientation == "auto":
size = frame_canvas(
registry.get(device_id or settings.device_id).device().display
)
items = service.photo_sources.catalog(source_id, target_size=size)
if not any(item["id"] == payload.item_id for item in items):
raise FlowError(E.NOT_FOUND, "Photo is not in this source", 404)
return set_flags(
db,
source_id,
payload.item_id,
starred=payload.starred,
excluded=payload.excluded,
)
@app.delete(
"/api/v1/devices/{device_id}/images/{image_id}",
response_model=Accepted,