2026-10-10 09:28:37 +08:00
"""Random selection and layout shared by preview, display and scheduled jobs."""
import random
from io import BytesIO
from PIL import Image , ImageOps
2026-10-20 18:50:43 +08:00
from pigway_frame_flow.core.photo_preferences import eligible
2026-10-10 09:28:37 +08:00
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 )
2026-10-20 18:50:43 +08:00
items = eligible (
service . db ,
content . source_id ,
service . photo_sources . catalog ( content . source_id ),
)
2026-10-10 09:28:37 +08:00
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" ]])
2026-10-24 00:38:00 +08:00
with Image . open (
BytesIO ( data ), formats = ( "JPEG" , "PNG" , "WEBP" )
) as image :
2026-10-10 09:28:37 +08:00
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 (
2026-10-20 18:50:43 +08:00
"SELECT id AS asset_id FROM assets WHERE kind='images' AND NOT EXISTS (SELECT 1 FROM photo_preferences p WHERE p.source_id='@assets' AND p.item_id=assets.id AND p.excluded=1) ORDER BY created_at DESC LIMIT 10000"
2026-10-10 09:28:37 +08:00
)
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 ,
)