I’ve got a spare Pi running DietPi with a screen taped to the wall, and I wanted it to cycle through photos from my Immich library. The obvious answer is a Chromium kiosk pointed at a slideshow page. I didn’t want that.
Why not just use a browser
A browser on a Pi is a lot of software to keep alive for something this simple: a renderer, a windowing system, a kiosk wrapper, all so you can occasionally swap an <img src>. That’s a lot of surface area for something that just needs to put pixels on a screen every 30 minutes. go-frame writes straight to /dev/fb0. No X11, no compositor, no browser process to babysit or restart when it falls over.
Layout logic
The one bit of actual design work is deciding what to show. Immich gives you a mix of portrait and landscape shots, and a naive “one photo, full screen” approach wastes a lot of space on portrait images. go-frame checks orientation and either shows a single landscape photo full-screen or pairs two portraits side-by-side. Small thing, but it’s the difference between the frame looking deliberate and looking like a slideshow.
Not showing the same photo twice
With a big library, you want enough randomness that it doesn’t feel repetitive, but not so much that you see the same photo twice in a week. History tracking solves this: every image shown gets logged with a timestamp, and selection excludes anything shown within a configurable window (7 days by default). Selection itself uses crypto/rand, not math/rand. There’s no security requirement here, but cryptographic randomness is a good default when you don’t have a reason to reach for anything weaker.
Caching
Hitting the Immich API on every single run doesn’t scale well against a cron job firing every 30 minutes, so there’s a local asset cache in front of it. Pagination is handled properly too, so this works the same whether your library has 500 photos or 50,000.
Running it
It’s driven by cron rather than a long-running daemon, which keeps the failure mode simple. If a run fails, the next one just tries again half an hour later:
*/30 7-23 * * * /root/go-frame/go-frame --config /root/go-frame/config.json >> /root/.go-frame/go-frame.log 2>&1
There’s a dry-run mode that writes to a PNG instead of the framebuffer, and a simulate mode that exercises selection and caching without downloading anything. Both useful when you’re iterating on a machine that isn’t the Pi with the screen attached.
Small project, but it’s a good example of matching the tool to the job. The screen doesn’t need a browser. It needs pixels.