Files
pigway-frame-flow/pigway_frame_flow/extensions/auto_collage.py
T

81 lines
3.1 KiB
Python
Raw Normal View History

"""Random selection and layout shared by preview, display and scheduled jobs."""
import random
from io import BytesIO
from PIL import Image, ImageOps
from pigway_frame_flow.errors import ErrorCode as E
from pigway_frame_flow.errors import FlowError
from pigway_frame_flow.extensions.layout import LayoutInput, LayoutStudio
from pigway_frame_flow.models import TemplateContent
def prepare(service, content, device_id=None):
rng = random.SystemRandom()
ids = []
hashes = set()
if content.source_id:
from pigway_frame_flow.extensions.quality import passes, photo_quality
source = service.photo_sources.get(content.source_id)
items = service.photo_sources.catalog(content.source_id)
candidates = rng.sample(items, min(len(items), content.count * 5))
for item in candidates:
try:
data, name, _ = service.photo_sources.read(
content.source_id, item["id"]
)
quality = photo_quality(data)
if not passes(quality, source) or quality["sha256"] in hashes:
continue
if source.avoid_duplicates and quality["fingerprint"] in hashes:
continue
hashes.update([quality["sha256"], quality["fingerprint"]])
with Image.open(BytesIO(data)) as image:
photo = ImageOps.exif_transpose(image).convert("RGB")
photo.thumbnail((2400, 2400))
output = BytesIO()
photo.save(output, "JPEG", quality=92)
asset = service.assets.put(output.getvalue(), name, "cache")
if asset["asset_id"] not in ids:
ids.append(asset["asset_id"])
except (OSError, ValueError):
continue
if len(ids) == content.count:
break
else:
rows = service.db.all(
"SELECT id AS asset_id FROM assets WHERE kind='images' ORDER BY created_at DESC LIMIT 10000"
)
ids = rng.sample(
[row["asset_id"] for row in rows], min(content.count, len(rows))
)
if len(ids) != content.count:
raise FlowError(
E.INVALID_REQUEST, "Not enough distinct photos for requested count", 422
)
values = content.model_dump(exclude={"type", "source_id", "count", "profile"})
if content.orientation == "auto":
from pigway_frame_flow.renderers.frame_geometry import frame_canvas
display = (
service.registry.get(device_id or service.settings.device_id)
.device()
.display
)
width, height = frame_canvas(display)
values["orientation"] = "landscape" if width > height else "portrait"
plan = LayoutStudio(service.renderer).plan(
LayoutInput(
asset_ids=ids,
**values,
)
)
return TemplateContent(
type="template",
template="poster" if content.style == "poster" else "collage",
data={"layout": plan.model_dump()},
profile=content.profile,
)