feat: generate source-only watch thumbnails after photo uploads
This commit is contained in:
@@ -205,7 +205,12 @@ class DisplayService:
|
||||
result.pop("preview_url", None)
|
||||
row = self.db.one("SELECT result FROM operations WHERE id=?", (id,))
|
||||
previous = json.loads(row["result"]) if row and row["result"] else {}
|
||||
for key in ("photo_attempts", "upload_intent", "sync_observations"):
|
||||
for key in (
|
||||
"photo_attempts",
|
||||
"upload_intent",
|
||||
"sync_observations",
|
||||
"wrist_thumbnail",
|
||||
):
|
||||
if key in previous:
|
||||
result = {**result, key: previous[key]}
|
||||
self.db.execute(
|
||||
@@ -265,7 +270,13 @@ class DisplayService:
|
||||
)
|
||||
|
||||
def content_asset(
|
||||
self, content, device_id=None, operation_id=None, *, staging=False
|
||||
self,
|
||||
content,
|
||||
device_id=None,
|
||||
operation_id=None,
|
||||
*,
|
||||
staging=False,
|
||||
source_image=None,
|
||||
):
|
||||
if isinstance(content, AutoCollageContent):
|
||||
from pigway_frame_flow.extensions.auto_collage import prepare
|
||||
@@ -419,6 +430,8 @@ class DisplayService:
|
||||
):
|
||||
raise
|
||||
continue
|
||||
if source_image is not None:
|
||||
source_image["data"] = data
|
||||
attempt["status"] = "PREPARED"
|
||||
if operation_id:
|
||||
self.db.execute(
|
||||
@@ -625,15 +638,26 @@ class DisplayService:
|
||||
|
||||
from pigway_frame_flow.models import Content
|
||||
|
||||
source_image: dict[str, bytes] = {}
|
||||
asset = self.content_asset(
|
||||
TypeAdapter(Content).validate_python(payload), device_id, id
|
||||
TypeAdapter(Content).validate_python(payload),
|
||||
device_id,
|
||||
id,
|
||||
source_image=source_image,
|
||||
)
|
||||
self.assets.record_origin(asset["asset_id"], op)
|
||||
if action == "local-upload":
|
||||
asset = self.assets.promote(asset["asset_id"])
|
||||
self.update(
|
||||
id, "COMPLETED", dict(asset_id=asset["asset_id"], url=asset["url"])
|
||||
)
|
||||
result = dict(asset_id=asset["asset_id"], url=asset["url"])
|
||||
if action == "local-upload":
|
||||
from pigway_frame_flow.display.wrist_thumbnail import publish
|
||||
|
||||
thumbnail = publish(
|
||||
self, device_id, asset["asset_id"], source_image.get("data")
|
||||
)
|
||||
if thumbnail:
|
||||
result["wrist_thumbnail"] = thumbnail
|
||||
self.update(id, "COMPLETED", result)
|
||||
self.db.history(op, "COMPLETED")
|
||||
return
|
||||
device = driver.device(refresh=True)
|
||||
@@ -757,6 +781,7 @@ class DisplayService:
|
||||
content = request.content
|
||||
self.update(id, "RENDERING")
|
||||
asset = None
|
||||
source_image = {}
|
||||
if isinstance(content, ImageContent) and (
|
||||
content.image_id or content.image_name
|
||||
):
|
||||
@@ -769,7 +794,9 @@ class DisplayService:
|
||||
)
|
||||
image_id = matches[0].image_id
|
||||
else:
|
||||
asset = self.content_asset(content, device_id, id, staging=True)
|
||||
asset = self.content_asset(
|
||||
content, device_id, id, staging=True, source_image=source_image
|
||||
)
|
||||
self.assets.record_origin(asset["asset_id"], op)
|
||||
image_id = None
|
||||
self.db.execute(
|
||||
@@ -803,6 +830,14 @@ class DisplayService:
|
||||
else:
|
||||
driver.display(image_id)
|
||||
result = {"image_id": image_id}
|
||||
if asset:
|
||||
from pigway_frame_flow.display.wrist_thumbnail import publish
|
||||
|
||||
thumbnail = publish(
|
||||
self, device_id, asset["asset_id"], source_image.get("data")
|
||||
)
|
||||
if thumbnail:
|
||||
result["wrist_thumbnail"] = thumbnail
|
||||
if asset:
|
||||
result["rendered_asset_id"] = asset["asset_id"]
|
||||
source = self.db.one(
|
||||
|
||||
@@ -0,0 +1,57 @@
|
||||
"""Source-only rendition for the current 410 × 502 watch display."""
|
||||
|
||||
import hashlib
|
||||
import json
|
||||
import logging
|
||||
from datetime import datetime
|
||||
from zoneinfo import ZoneInfo
|
||||
|
||||
from pigway_frame_flow.errors import FlowError
|
||||
from pigway_frame_flow.renderers.images import convert
|
||||
from pigway_frame_flow.storage.database import encode
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
SIZE = (410, 502)
|
||||
|
||||
|
||||
def publish(service, device_id, asset_id, data=None):
|
||||
# Templates and old gallery assets have no verified undecorated source.
|
||||
row = service.db.one(
|
||||
"SELECT payload FROM asset_sources WHERE asset_id=?", (asset_id,)
|
||||
)
|
||||
source = json.loads(row["payload"]) if row else {}
|
||||
if source.get("type") != "photo_source":
|
||||
return None
|
||||
try:
|
||||
if data is None:
|
||||
data, _, _ = service.photo_sources.read(
|
||||
source["source_id"], source["item_id"], target_size=SIZE
|
||||
)
|
||||
rendered = convert(data, "cover", "color", size=SIZE)
|
||||
asset = service.assets.put(rendered, "watch.jpg", "cache")
|
||||
now = datetime.now(ZoneInfo(service.settings.timezone))
|
||||
result = {
|
||||
"asset_id": asset["asset_id"],
|
||||
"url": asset["url"],
|
||||
"width": SIZE[0],
|
||||
"height": SIZE[1],
|
||||
"date": now.date().isoformat(),
|
||||
"timezone": service.settings.timezone,
|
||||
"generated_at": now.isoformat(),
|
||||
"sha256": hashlib.sha256(rendered).hexdigest(),
|
||||
"bytes": len(rendered),
|
||||
"source": source,
|
||||
"fit": "cover",
|
||||
"source_rendition": "preview"
|
||||
if service.photo_sources.get(source["source_id"]).kind == "synology-photos"
|
||||
else "source",
|
||||
}
|
||||
service.db.execute(
|
||||
"INSERT OR REPLACE INTO app_config VALUES(?,?)",
|
||||
("wrist-thumbnail:" + device_id, encode(result)),
|
||||
)
|
||||
return result
|
||||
except (FlowError, OSError, ValueError):
|
||||
# An auxiliary rendition must never cause a successful upload to replay.
|
||||
logger.warning("Watch thumbnail generation failed for asset %s", asset_id)
|
||||
return None
|
||||
@@ -44,6 +44,7 @@ class LocalRetention:
|
||||
"SELECT payload FROM template_config",
|
||||
"SELECT payload FROM operations WHERE status IN ('QUEUED','RENDERING','UPLOADING','SYNCING','UNKNOWN')",
|
||||
"SELECT payload FROM app_config WHERE key LIKE 'upload-retry:%'",
|
||||
"SELECT payload FROM app_config WHERE key LIKE 'wrist-thumbnail:%'",
|
||||
]:
|
||||
payloads += [row["payload"] for row in db.all(query)]
|
||||
for payload in payloads:
|
||||
|
||||
Reference in New Issue
Block a user