Add optional photo providers, layout studio and scoped integrations

This commit is contained in:
Pigway
2026-10-08 23:18:11 +08:00
parent 3643fae86a
commit 69a0841868
38 changed files with 2843 additions and 41 deletions
+86
View File
@@ -15,6 +15,7 @@ from pigway_frame_flow.models import (
FutureContent,
ImageContent,
PhotoSourceContent,
ProviderContent,
TemplateContent,
URLContent,
)
@@ -37,6 +38,13 @@ class DisplayService:
assets,
renderer,
)
from typing import Callable
self.operation_handlers: dict[str, Callable] = {}
self.reconciliation_handlers: dict[str, Callable] = {}
from pigway_frame_flow.providers.external import ExternalProviders
self.providers = ExternalProviders(self)
self.lock = threading.RLock()
self.retention = Retention(self)
self.cloud_library = CloudLibrary(self)
@@ -56,8 +64,23 @@ class DisplayService:
raise FlowError(
E.UNSUPPORTED_CAPABILITY, "Content type is unsupported", 422
)
if isinstance(request.content, ProviderContent):
self.providers.config(request.content.provider_id)
self.renderer.info(request.content.template)
if isinstance(request.content, TemplateContent):
self.renderer.info(request.content.template)
if request.content.template in ("collage", "poster"):
from pigway_frame_flow.extensions.layout import LayoutPlan
try:
plan = LayoutPlan.model_validate(request.content.data.get("layout"))
except ValueError:
raise FlowError(
E.INVALID_REQUEST,
"Create a valid layout in Layout Studio first",
) from None
for region in plan.regions:
self.assets.path(region.asset_id)
if isinstance(request.content, PhotoSourceContent):
self.photo_sources.get(request.content.source_id)
if writing and not self.settings.mock and not self.settings.write_enabled:
@@ -168,6 +191,18 @@ class DisplayService:
return self.db.one("SELECT * FROM device_state WHERE device_id=?", (id,))
def content_asset(self, content, device_id=None, operation_id=None):
if isinstance(content, ProviderContent):
values = {**content.data, **self.providers.get(content.provider_id)}
return self.content_asset(
TemplateContent(
type="template",
template=content.template,
data=values,
profile=content.profile,
),
device_id,
operation_id,
)
if isinstance(content, TemplateContent):
values = self.renderer.resolve(content.template, content.data)
data = self.renderer.render(content.template, values, content.profile)
@@ -219,6 +254,53 @@ class DisplayService:
data, name, caption = self.photo_sources.read(
content.source_id, item["id"], target_size=size
)
if (
source.minimum_sharpness
or source.minimum_brightness
or source.maximum_brightness < 255
or source.avoid_duplicates
):
from pigway_frame_flow.extensions.quality import (
passes,
photo_quality,
)
quality = photo_quality(data)
if not passes(quality, source):
raise FlowError(
E.INVALID_REQUEST,
"Photo fails configured quality thresholds",
)
previous_quality = self.db.one(
"SELECT payload FROM app_config WHERE key=?",
("recent-photo:" + content.source_id,),
)
fingerprints = (
json.loads(previous_quality["payload"])
if previous_quality
else []
)
if source.avoid_duplicates and any(
int(quality["fingerprint"], 16)
.__xor__(int(old, 16))
.bit_count()
<= 4
for old in fingerprints
):
raise FlowError(
E.CONFLICT,
"Photo resembles a recently selected photo",
409,
)
self.db.execute(
"INSERT OR REPLACE INTO app_config VALUES(?,?)",
(
"recent-photo:" + content.source_id,
encode(
(fingerprints + [quality["fingerprint"]])[-200:]
),
),
)
rendered = (
render_captioned_photo(
data,
@@ -376,6 +458,8 @@ class DisplayService:
def _execute(self, op, payload):
id, device_id, action = op["id"], op["device_id"], op["action"]
driver = self.registry.get(device_id)
if action in self.operation_handlers:
return self.operation_handlers[action](op, payload)
if action in ("render", "local-upload"):
self.update(id, "RENDERING")
from pydantic import TypeAdapter
@@ -699,6 +783,8 @@ class DisplayService:
def reconcile(self, id, acknowledge=False):
with self.lock:
op = self.operation(id)
if op["action"] in self.reconciliation_handlers:
return self.reconciliation_handlers[op["action"]](op, acknowledge)
driver = self.registry.get(op["device_id"])
device = driver.device(refresh=True)
images = driver.images()