Handle invalid random photos before device writes and trace selections

This commit is contained in:
Pigway
2026-10-06 08:47:03 +08:00
parent 59345f12a7
commit 0a5b859ca0
8 changed files with 231 additions and 21 deletions
+73 -16
View File
@@ -132,6 +132,11 @@ class DisplayService:
)
def update(self, id, status, result=None, error=None):
if result:
row = self.db.one("SELECT result FROM operations WHERE id=?", (id,))
previous = json.loads(row["result"]) if row and row["result"] else {}
if "photo_attempts" in previous:
result = {**result, "photo_attempts": previous["photo_attempts"]}
self.db.execute(
"UPDATE operations SET status=?,updated_at=?,result=COALESCE(?,result),error=? WHERE id=?",
(
@@ -158,7 +163,7 @@ class DisplayService:
)
return self.db.one("SELECT * FROM device_state WHERE device_id=?", (id,))
def content_asset(self, content, device_id=None):
def content_asset(self, content, device_id=None, operation_id=None):
if isinstance(content, TemplateContent):
values = self.renderer.resolve(content.template, content.data)
data = self.renderer.render(content.template, values, content.profile)
@@ -180,22 +185,74 @@ class DisplayService:
self.registry.get(device_id or self.settings.device_id).device().display
)
size = frame_canvas(display, source.orientation)
data, name, caption = self.photo_sources.read(
content.source_id, content.item_id, target_size=size
import random
items = self.photo_sources.catalog(content.source_id, target_size=size)
candidates = (
[item for item in items if item["id"] == content.item_id]
if content.item_id
else random.SystemRandom().sample(items, min(5, len(items)))
)
rendered = (
render_captioned_photo(
data,
caption,
self.renderer.font,
content.fit,
content.profile,
size=size,
if not candidates:
raise FlowError(
E.NOT_FOUND, "Photo source contains no matching images", 404
)
if caption
else convert(data, content.fit, content.profile, size=size)
attempts = []
for item in candidates:
attempt = {
"source_id": content.source_id,
"item_id": item["id"],
"name": item["name"],
"status": "SELECTED",
}
attempts.append(attempt)
if operation_id:
self.db.execute(
"UPDATE operations SET result=? WHERE id=?",
(encode({"photo_attempts": attempts}), operation_id),
)
try:
data, name, caption = self.photo_sources.read(
content.source_id, item["id"], target_size=size
)
rendered = (
render_captioned_photo(
data,
caption,
self.renderer.font,
content.fit,
content.profile,
size=size,
)
if caption
else convert(data, content.fit, content.profile, size=size)
)
except FlowError as exc:
attempt["status"] = "SKIPPED"
attempt["code"] = exc.code
if operation_id:
self.db.execute(
"UPDATE operations SET result=? WHERE id=?",
(encode({"photo_attempts": attempts}), operation_id),
)
# Only local preparation can choose another image. No device
# write has begun; explicit selections never change silently.
if content.item_id or exc.code not in (
E.INVALID_REQUEST,
E.NOT_FOUND,
):
raise
continue
attempt["status"] = "PREPARED"
if operation_id:
self.db.execute(
"UPDATE operations SET result=? WHERE id=?",
(encode({"photo_attempts": attempts}), operation_id),
)
return self.assets.put(rendered, name)
raise FlowError(
E.INVALID_REQUEST, "No decodable photo found within 5 candidates"
)
return self.assets.put(rendered, name)
if isinstance(content, URLContent):
data = self.assets.fetch_url(content.url)
elif isinstance(content, ImageContent) and content.asset_id:
@@ -322,7 +379,7 @@ class DisplayService:
from pigway_frame_flow.models import Content
asset = self.content_asset(
TypeAdapter(Content).validate_python(payload), device_id
TypeAdapter(Content).validate_python(payload), device_id, id
)
if action == "local-upload":
asset = self.assets.promote(asset["asset_id"])
@@ -402,7 +459,7 @@ class DisplayService:
)
image_id = matches[0].image_id
else:
asset = self.content_asset(content, device_id)
asset = self.content_asset(content, device_id, id)
image_id = None
self.db.execute(
"UPDATE device_state SET previous_image_id=? WHERE device_id=?",