A few year ago, I saw a very cool product. Haikubox, is a small device that listens to birds at home, identifies them and gives you a nice web interface to see what’s going on. Over time, I realized one microphone on a property this size is a problem. If the birds came near our pond it heard them, but we regularly saw and heard birds in the front yard, back yard and other side of the house that didn’t register.

In spring cleaning, I decided I would let it go so it went on eBay. It sold this week and given my newfound Claude Code confidence, I had to wonder, could I build my own? It turns out yes, quickly, and much better using my existing infrastructure.

Be sure to check out a newer post about the custom dashboard I built on top of this data.

Local, Private with More Mics

I have a pretty extensive home security system that uses an array of Amcrest IP cameras, many of which have microphones. RTSP streams mean I could easily grab audio feeds and process them. I already have a server that I use for AI processing, so hosting a model and doing the analysis could live there, and I already use TimeScaleDB for Home Assistant sensor logging, so I had a Postgres database ready to go.

Haikubox uses BIRDnet, which I could also use myself, so at this point it was just using Claude Code to assemble the pieces.

Implementation

The whole thing is a single Python package run via Docker. A supervisor process fans out one worker per camera with four cameras listening. I could easily increase the number of cameras, but I am covering front, back, and both sides. Each worker owns its own ffmpeg subprocess and its own BirdNET analyzer, and detections land in TimescaleDB. Total codebase is under 400 lines.

The audio loop

BirdNET wants 48 kHz mono 3-second windows, so the worker primes a numpy buffer with 3 seconds of samples, runs it through birdnetlib’s RecordingBuffer, and slides the window forward on each iteration.

Every window also feeds in lat/lon so BirdNET restricts its candidate species list to things actually plausible in my area.

Storage

db.py is a thin psycopg2 wrapper. Schema is one table, detections, with a TimescaleDB hypertable.

The hypertable makes time-range queries over months of data cheap, and continuous aggregates are a one-liner when I want “top species per day” to stop being a full scan.

Config and deploy

The Dockerfile installs ffmpeg, birdnetlib, and tflite-runtime (CPU-only TensorFlow Lite, no CUDA), and pre-warms the BirdNET model at build time so cold starts are instant. Docker uses host network mode so the container can reach cameras on the LAN without port gymnastics.

That’s effectively the whole thing. The model is doing the clever work; the code is mostly plumbing around ffmpeg processing and database logging.

Performance wise, it’s barely noticeable on the server.

Visualization

The last part was how to see the data. Since I already use Grafana and TimeScaleDB to visualize temperature and other Home Assistant data, connecting to the detections table was a breeze.

I don’t get the bird images like the Haikubox site, but that’s something I could probably integrate matching the scientific name with Wikipedia or another site. I really just want to know what was here and when, this does that perfectly.

Haikubox’s app also gave me notifications of new species. That’s something I’ll probably wire up with n8n. Keep a list of what’s been seen up to date and notify if something shows up that isn’t on the list.

I keep my source in a private gitea repo, so it’s not public, but if you have any interest in the project, send me an email and I’d be happy to clean it up and put it on Github.