Frame mode is the main thing I built this round: a smarter slideshow that picks photos with some intent rather than just grabbing one at random.
Frame Mode #
The first implementation was simple: fetch all media sorted by capture time and pick one at random. This worked but felt hollow. The frame would jump between unrelated photos with no sense of narrative or fairness, and images from large folders would dominate just by volume.
Making a More Advanced Frame Algorithm #
Random selection has no memory or intent. I wanted something that tells a story instead, built around a few ideas:
- Prefer images that have been shown less often
- Surface “on this day” memories by matching the calendar date across past years
- Group images by folder to create coherent narrative segments
- Distribute display counts fairly so every image gets roughly equal coverage over time
The algorithm pulls from two sources: “On This Day” memories (photos from the same calendar date in past years) and folder-grouped browsing (a diverse sample across all your folders). Both are merged into a queue that the frame works through sequentially.
Fatigue Tracking #
The queue refills when it runs low. Before picking anything, a fatigue map is built from the display log:
SELECT mediaId, COUNT(*), MAX(shownAt)
FROM FrameLog
GROUP BY mediaIdEach entry holds a show count and last-shown timestamp. Honestly a simple counter per image would have been enough, but I kept the full log because I had plans to do frequency analysis, like figuring out which hours or times of year certain images tend to get shown. Never got around to it.
Track 1: On This Day #
Photos taken on today’s date in previous years, sorted by fatigue count ascending, capped at 10, then re-sorted chronologically.
Track 2: Folder-Grouped Browsing #
- Group all media by folder
- Per folder: keep the 10 least-shown images, re-sort chronologically
- Score each folder by average show count (
groupFatigue) - Shuffle, sort by
groupFatigueascending, keep at most 10 folders - Flatten and append to the queue
Picking Images Within a Folder #
Say you have a folder of 300 photos from a two-week trip. If you just sort by fatigue and take the 10 least-shown, you will always get images from the same part of the trip, whichever cluster happens to have the lowest counts right now. Once those get shown a few times, the next 10 take over. You end up slowly marching through the folder in order rather than getting a spread across the whole thing.
What I wanted instead was one image from each “chapter” of the folder on every refresh. The solution is to split the folder into K equal buckets chronologically and pick the least-shown image from each one:
300 images, K = 10
bucket 0: photos 0-29 → pick least-shown
bucket 1: photos 30-59 → pick least-shown
...
bucket 9: photos 270-299 → pick least-shownstart(i) = i * N / K
end(i) = (i + 1) * N / KWithin each bucket you still prefer unseen or rarely-shown images, so fairness is maintained locally too. Over time, as images in each segment accumulate counts, different ones get picked from the same bucket on the next refresh.
The queue refill runs on the main thread, and every time it triggers it re-runs the “on this day” track, which means the same images can appear in both tracks. Good enough for now, if I come back to this, that’s the first thing to fix.
Testcontainers #
Not much to say here, I wired up Testcontainers so I can test my queries against a real DB and catch bugs rather than guessing if things work.
I am happy with where this landed for now. I will pick it back up when I have the time and headspace for it.