2026-10-23 13:27:17 +08:00
|
|
|
|
"""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)
|
|
|
|
|
|
|
|
|
|
|
|
|
2026-10-23 14:47:27 +08:00
|
|
|
|
def publish(service, device_id, asset_id, data=None, plan_id=None):
|
2026-10-23 13:27:17 +08:00
|
|
|
|
# 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:
|
2026-10-23 14:47:27 +08:00
|
|
|
|
from pigway_frame_flow.display.thumbnail_plans import get_plan, render
|
|
|
|
|
|
|
|
|
|
|
|
plan = get_plan(service, plan_id) if plan_id else None
|
|
|
|
|
|
size = (plan.width, plan.height) if plan else SIZE
|
2026-10-23 13:27:17 +08:00
|
|
|
|
if data is None:
|
|
|
|
|
|
data, _, _ = service.photo_sources.read(
|
2026-10-23 14:47:27 +08:00
|
|
|
|
source["source_id"], source["item_id"], target_size=size
|
2026-10-23 13:27:17 +08:00
|
|
|
|
)
|
2026-10-23 14:47:27 +08:00
|
|
|
|
rendered = (
|
|
|
|
|
|
render(data, plan) if plan else convert(data, "cover", "color", size=SIZE)
|
|
|
|
|
|
)
|
2026-10-23 13:27:17 +08:00
|
|
|
|
asset = service.assets.put(rendered, "watch.jpg", "cache")
|
|
|
|
|
|
now = datetime.now(ZoneInfo(service.settings.timezone))
|
|
|
|
|
|
result = {
|
|
|
|
|
|
"asset_id": asset["asset_id"],
|
|
|
|
|
|
"url": asset["url"],
|
2026-10-23 14:47:27 +08:00
|
|
|
|
"width": size[0],
|
|
|
|
|
|
"height": size[1],
|
2026-10-23 13:27:17 +08:00
|
|
|
|
"date": now.date().isoformat(),
|
|
|
|
|
|
"timezone": service.settings.timezone,
|
|
|
|
|
|
"generated_at": now.isoformat(),
|
|
|
|
|
|
"sha256": hashlib.sha256(rendered).hexdigest(),
|
|
|
|
|
|
"bytes": len(rendered),
|
|
|
|
|
|
"source": source,
|
2026-10-23 14:47:27 +08:00
|
|
|
|
"fit": plan.fit if plan else "cover",
|
|
|
|
|
|
"plan_id": plan_id,
|
|
|
|
|
|
"plan": plan.model_dump() if plan else None,
|
2026-10-23 13:27:17 +08:00
|
|
|
|
"source_rendition": "preview"
|
|
|
|
|
|
if service.photo_sources.get(source["source_id"]).kind == "synology-photos"
|
|
|
|
|
|
else "source",
|
|
|
|
|
|
}
|
2026-10-23 15:00:29 +08:00
|
|
|
|
from pigway_frame_flow.display.thumbnail_gallery import record
|
|
|
|
|
|
|
|
|
|
|
|
record(service, asset["asset_id"], {**result, "source_frame_id": device_id})
|
2026-10-23 13:27:17 +08:00
|
|
|
|
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
|