I love to find new and creative ways to use the technology I have at home and generative AI. So when I saw a thread by Ole Lehmann of a dynamic bird print of birds based on birdsong heard nearby, I knew I had to try and make my own; even if I don’t have an e-ink display yet.

The plate at the top of the page is one day in the garden, from the first version of this project; the latest is always at /birds/.
There’s an open source project called BirdNET which is far more fully featured than I expected. It takes in any streaming audio source (in my case the audio from two of my Unifi cameras), uses Cornell’s AI model (trained on 6,000+ species), and identifies which birds are singing.

How a bird becomes a print
BirdNET gives me the day’s species, each with a confidence score. I take the best of those, send the names to Gemini, and ask for a single hand-coloured plate in the style of Audubon’s ‘The Birds of America’, titled ‘Birds of Sala’ with the date. Node-RED runs the whole thing. BirdNET is running on my Unraid server.
The flow is short:
- BirdNET reports the day’s species which are accessible via an API

- Node-RED keeps a list high-confidence ones and writes the prompt.

- Gemini generates the print.
A hand-coloured antique ornithological illustration in the style of John James Audubon's "The Birds of America". The artwork fills the sheet as a single piece of aged paper with subtle integrated paper texture, soft foxing and warm cream tone — no inner border, no second sheet, no drop shadow, not a photograph of a framed print. The birds are the clear focus, rendered large, detailed and prominent. Foliage is minimal and secondary: just a few sparse bare twigs to perch on, with only an occasional leaf, never competing with the birds. Compose as a single complete vignette fully contained within the image: every branch and twig ends naturally inside the frame and resolves to a tip, nothing cropped at the edges, with a comfortable margin of plain aged paper around the whole composition. Naturalistic poses, fine engraved linework, soft hand-applied watercolour. Render these species accurately and clearly separated: ${list}. Elegant copperplate-engraved title at the top reading "Birds of Sala — ${today}", each bird's name in period engraver's script beside it. Portrait orientation.- Node-RED posts the print and the species list to my site.
- The site shows the print at /birds/ and counts the species on the homepage.
It only makes a new print when a bird turns up that it hasn’t already drawn that day. Otherwise it would redraw the same birds over and over and rinse my token budget; so a new species is what triggers a new plate.
One problem was getting the image off the machine. Home Assistant runs on my local network and isn’t reachable from the internet. Fine for showing it in my dashboard but this site can’t pull from it. Instead, Node-RED pushes. It POSTs the print and the species to a small endpoint on my site, behind a token, and a Cloudflare Worker stores them and writes them into the page when someone loads it. No javascript needed. Fully indexable.
One catch was image caching. The image is overwritten and Cloudflare caches for a year. This fix is a hash of the image appended as a cache-buster https://craigmdennis.com/birds/poster.png?v=31dfae0dc9.
My daughters are an owl and a pheasant
One unexpected outcome was the detection of a pheasant and a tawny owl. I thought these seemed unlikely even though I selected to filter the detections to birds expected in this region. BirdNET gives you the ability to listen to the recordings that it based the detection on and—when I listened to said recording—I discovered it was my oldest daughter making hooting sounds, and my youngest babbling away.
Fortunately, BirdNET allows you to customise thresholds for discovery. I opted for 5 high-confidence detections to be needed in order for a bird to be considered ‘detected’.
Why I split the plate into portraits
The single plate was doing too much work. Only redrawing when a new bird turned up kept the cost down, but even then Gemini redrew the whole plate from scratch: every bird on the sheet, not just the new arrival. One new visitor and I was paying to redraw all the others, and they’d come back slightly different every time.
So I split it up. Each species now gets its own portrait, drawn once. A new bird only draws that new bird; the ones already on the page stay exactly as they were. The day’s birds show up as a grid of individual portraits at /birds/, and the homepage still shows how many species have been about this week. Each portrait is drawn from scratch, so the same bird on another day comes back a little different.
I didn’t want to lose the combined plate though. A whole flock composed on one Audubon sheet is what got me started, and I still like it. Daily it was expensive and didn’t always come out right, so it’s becoming a weekly image instead: one ‘Birds of Sala’ plate of everything heard that week, sitting above the daily grid. A week-in-review is worth the cost once a week. That’s where this is going next.
Until I get an e-ink display working
The idea came from a thread by Ole Lehmann about a project by Teddy Warner, who also uses BirdNET to name the birds and Gemini to paint them (his come out as Japanese woodblock prints; mine as Audubon plates), then hangs the results on a wooden-framed e-ink display by his window that updates with whatever it heard that day.
I want that frame on my wall. I don’t have one working yet; until I do, /birds/ is where mine lives. As well as in my Home Assistant dashboard.

Bats!
We have bats here in Sweden too and there is an add-on to detect bats in the same way! I’ve not had any detected yet, but I’ve added the integration anyway.

