Automated bird watching, with classifications and insights for UniFi Protect.
27
stars
47
commits
Python
primary language
Feb 27, 2026
updated
Bird detection and classification system for UniFi Protect with web interface.
The easiest way to run Birdhomie is with Docker. All settings have sensible defaults - you only need to configure your UniFi Protect credentials.
services:
birdhomie:
image: ghcr.io/dewey/birdhomie:latest
container_name: birdhomie
restart: unless-stopped
ports:
- "5000:5000"
volumes:
- birdhomie-data:/app/data
environment:
# Required: UniFi Protect credentials
- UFP_ADDRESS=192.168.1.1
- UFP_USERNAME=your-username
- UFP_PASSWORD=your-password
- UFP_CAMERA_ID=your-camera-id
volumes:
birdhomie-data:
Update the four required environment variables:
| Variable | Description |
|---|---|
UFP_ADDRESS | IP address of your UniFi Protect NVR |
UFP_USERNAME | Local Access Only user username |
UFP_PASSWORD | Local Access Only user password |
UFP_CAMERA_ID | Camera ID (find in Protect: Settings → Camera → Advanced) |
To create a Local Access Only user, go to your UniFi Protect web UI and create a new user with "Full Management" permissions.
docker compose up -d
Access the web interface at http://localhost:5000
If you prefer running without Docker:
# Install uv if needed
curl -LsSf https://astral.sh/uv/install.sh | sh
# Clone and install
git clone https://github.com/dewey/birdhomie
cd birdhomie
uv sync
# Configure environment
cp .envrc.example .envrc
# Edit .envrc with your UniFi Protect credentials
# If using direnv
direnv allow
# Or source manually
source .envrc
# Run in development mode
make dev
The application will be available at http://127.0.0.1:5001
All configuration options have sensible defaults. Only UniFi Protect credentials are required.
| Variable | Description |
|---|---|
UFP_ADDRESS | IP address of your UniFi Protect NVR |
UFP_USERNAME | Local Access Only user username |
UFP_PASSWORD | Local Access Only user password |
UFP_CAMERA_ID | Camera ID to monitor |
| Variable | Default | Description |
|---|---|---|
UFP_DETECTION_TYPES | motion | Detection types to monitor |
UFP_SSL_VERIFY | 0 | Verify SSL certificates (set to 1 to enable) |
UFP_DOWNLOAD_INTERVAL_MINUTES | 60 | How often to check for new events |
PROCESSOR_INTERVAL_MINUTES | 5 | How often to process files |
PROCESSOR_WORKERS | 1 | Parallel video processing workers |
MIN_SPECIES_CONFIDENCE | 0.85 | Minimum confidence for species ID (0.0-1.0) |
MIN_DETECTION_CONFIDENCE | 0.80 | Minimum confidence for bird detection (0.0-1.0) |
FRAME_SKIP | 5 | Process every Nth frame |
FILE_RETENTION_DAYS | 30 | Days to keep input files after successful processing |
UFP_INITIAL_SYNC_DAYS | 30 | Days to look back on initial sync |
GUNICORN_WORKERS | 2 | Number of web server workers (production) |
PORT | 5000 | Web server port |
NNPACK_DISABLE | 0 | Set to 1 for CPUs without AVX2/FMA support |
BIRDHOMIE_DB_DIR | data/ | Directory for the SQLite database file |
| Variable | Default | Description |
|---|---|---|
FLASK_DEBUG | 0 | Enable debug mode with hot reloading |
SECRET_KEY | dev-secret-key | Flask session secret (change for production) |
FACE_ANNOTATION_BATCH_SIZE | 100 | Batch size for face annotation |
By default, everything lives under a single data/ directory:
data/
├── birdhomie.db # SQLite database
├── input/ # Downloaded videos
├── output/ # Processed results and crops
├── species_images/ # Cached species images
└── models/ # ML models
If you need to store the database on a separate volume (for example to avoid overlapping Docker mounts), set BIRDHOMIE_DB_DIR to a different path:
environment:
- BIRDHOMIE_DB_DIR=/app/db
volumes:
- birdhomie-db:/app/db # Database on fast storage
- birdhomie-files:/app/data # Large files on bulk storage
When BIRDHOMIE_DB_DIR is not set, the database is stored in data/ alongside everything else.
Access the web interface at http://localhost:5000 (Docker) or http://127.0.0.1:5001 (manual).
Pages:
/ - Dashboard with recent visits and species/species - List of all detected species/species/<id> - Species detail page with photos and visits/visits/<id> - Individual visit details with video player/visits/<id>/split - Split a visit when multiple birds are detected/files - List of all processed files/tasks - Background task status/labeling - Manual labeling interface for face annotations/labeling/stats - Labeling progress and statistics/settings - Application settingsUniFi Protect occasionally creates multiple overlapping motion detection events for the same bird activity due to a known bug. Birdhomie provides a merge feature to consolidate duplicates:
What happens when you merge:
ignoreddeleted_at timestamp)The application automatically downloads smart detection events from UniFi Protect at the configured interval (default: every 60 minutes).
You can trigger downloads manually from the Tasks page in the web interface or via API:
curl http://localhost:5000/tasks/trigger/unifi_download
source .envrc # If not using direnv
make dev
This compiles translations, enables hot reload, and runs on http://127.0.0.1:5001.
To test with real production data locally, you can sync your server's data directory using rsync:
Configure sync settings in your .envrc:
export SYNC_REMOTE=ubuntu@your-server.example.com:/home/ubuntu/services/birdhomie/data
Run the sync command:
make sync-data
This will download the database, models, processed files, and species images to your local ./data/ directory. The logs/ directory is automatically excluded from sync.
Prerequisites:
| Command | Description |
|---|---|
make dev | Run with hot reloading |
make run | Run in normal mode |
make compile-translations | Compile .po to .mo files |
make extract-translations | Extract translatable strings |
make update-translations | Update translation catalogs |
make migrate | Run pending migrations |
make process | Run file processor manually |
make sync-data | Sync production data locally for testing |
make clean | Clean generated files |
make help | Show available commands |
Currently supported languages: English (en), German (de)
To add translations:
make extract-translations
make update-translations
# Edit src/birdhomie/translations/<lang>/LC_MESSAGES/messages.po
make compile-translations
Make sure you've configured the required UniFi Protect credentials in your .envrc or docker-compose.yml.
Change the port mapping in docker-compose.yml or stop the conflicting process.
If you're running on a CPU without AVX2/FMA support (Intel pre-Haswell/2013, AMD pre-Excavator/2015), you may experience high CPU usage during model inference. Set NNPACK_DISABLE=1 in your environment to disable NNPACK optimizations.
MIT License - see LICENSE for details.
47 commits
Python
61.9%
HTML
37.3%
Automated bird watching, with classifications and insights for UniFi Protect.
27
stars
47
commits
Python
primary language
Feb 27, 2026
updated
Bird detection and classification system for UniFi Protect with web interface.
The easiest way to run Birdhomie is with Docker. All settings have sensible defaults - you only need to configure your UniFi Protect credentials.
services:
birdhomie:
image: ghcr.io/dewey/birdhomie:latest
container_name: birdhomie
restart: unless-stopped
ports:
- "5000:5000"
volumes:
- birdhomie-data:/app/data
environment:
# Required: UniFi Protect credentials
- UFP_ADDRESS=192.168.1.1
- UFP_USERNAME=your-username
- UFP_PASSWORD=your-password
- UFP_CAMERA_ID=your-camera-id
volumes:
birdhomie-data:
Update the four required environment variables:
| Variable | Description |
|---|---|
UFP_ADDRESS | IP address of your UniFi Protect NVR |
UFP_USERNAME | Local Access Only user username |
UFP_PASSWORD | Local Access Only user password |
UFP_CAMERA_ID | Camera ID (find in Protect: Settings → Camera → Advanced) |
To create a Local Access Only user, go to your UniFi Protect web UI and create a new user with "Full Management" permissions.
docker compose up -d
Access the web interface at http://localhost:5000
If you prefer running without Docker:
# Install uv if needed
curl -LsSf https://astral.sh/uv/install.sh | sh
# Clone and install
git clone https://github.com/dewey/birdhomie
cd birdhomie
uv sync
# Configure environment
cp .envrc.example .envrc
# Edit .envrc with your UniFi Protect credentials
# If using direnv
direnv allow
# Or source manually
source .envrc
# Run in development mode
make dev
The application will be available at http://127.0.0.1:5001
All configuration options have sensible defaults. Only UniFi Protect credentials are required.
| Variable | Description |
|---|---|
UFP_ADDRESS | IP address of your UniFi Protect NVR |
UFP_USERNAME | Local Access Only user username |
UFP_PASSWORD | Local Access Only user password |
UFP_CAMERA_ID | Camera ID to monitor |
| Variable | Default | Description |
|---|---|---|
UFP_DETECTION_TYPES | motion | Detection types to monitor |
UFP_SSL_VERIFY | 0 | Verify SSL certificates (set to 1 to enable) |
UFP_DOWNLOAD_INTERVAL_MINUTES | 60 | How often to check for new events |
PROCESSOR_INTERVAL_MINUTES | 5 | How often to process files |
PROCESSOR_WORKERS | 1 | Parallel video processing workers |
MIN_SPECIES_CONFIDENCE | 0.85 | Minimum confidence for species ID (0.0-1.0) |
MIN_DETECTION_CONFIDENCE | 0.80 | Minimum confidence for bird detection (0.0-1.0) |
FRAME_SKIP | 5 | Process every Nth frame |
FILE_RETENTION_DAYS | 30 | Days to keep input files after successful processing |
UFP_INITIAL_SYNC_DAYS | 30 | Days to look back on initial sync |
GUNICORN_WORKERS | 2 | Number of web server workers (production) |
PORT | 5000 | Web server port |
NNPACK_DISABLE | 0 | Set to 1 for CPUs without AVX2/FMA support |
BIRDHOMIE_DB_DIR | data/ | Directory for the SQLite database file |
| Variable | Default | Description |
|---|---|---|
FLASK_DEBUG | 0 | Enable debug mode with hot reloading |
SECRET_KEY | dev-secret-key | Flask session secret (change for production) |
FACE_ANNOTATION_BATCH_SIZE | 100 | Batch size for face annotation |
By default, everything lives under a single data/ directory:
data/
├── birdhomie.db # SQLite database
├── input/ # Downloaded videos
├── output/ # Processed results and crops
├── species_images/ # Cached species images
└── models/ # ML models
If you need to store the database on a separate volume (for example to avoid overlapping Docker mounts), set BIRDHOMIE_DB_DIR to a different path:
environment:
- BIRDHOMIE_DB_DIR=/app/db
volumes:
- birdhomie-db:/app/db # Database on fast storage
- birdhomie-files:/app/data # Large files on bulk storage
When BIRDHOMIE_DB_DIR is not set, the database is stored in data/ alongside everything else.
Access the web interface at http://localhost:5000 (Docker) or http://127.0.0.1:5001 (manual).
Pages:
/ - Dashboard with recent visits and species/species - List of all detected species/species/<id> - Species detail page with photos and visits/visits/<id> - Individual visit details with video player/visits/<id>/split - Split a visit when multiple birds are detected/files - List of all processed files/tasks - Background task status/labeling - Manual labeling interface for face annotations/labeling/stats - Labeling progress and statistics/settings - Application settingsUniFi Protect occasionally creates multiple overlapping motion detection events for the same bird activity due to a known bug. Birdhomie provides a merge feature to consolidate duplicates:
What happens when you merge:
ignoreddeleted_at timestamp)The application automatically downloads smart detection events from UniFi Protect at the configured interval (default: every 60 minutes).
You can trigger downloads manually from the Tasks page in the web interface or via API:
curl http://localhost:5000/tasks/trigger/unifi_download
source .envrc # If not using direnv
make dev
This compiles translations, enables hot reload, and runs on http://127.0.0.1:5001.
To test with real production data locally, you can sync your server's data directory using rsync:
Configure sync settings in your .envrc:
export SYNC_REMOTE=ubuntu@your-server.example.com:/home/ubuntu/services/birdhomie/data
Run the sync command:
make sync-data
This will download the database, models, processed files, and species images to your local ./data/ directory. The logs/ directory is automatically excluded from sync.
Prerequisites:
| Command | Description |
|---|---|
make dev | Run with hot reloading |
make run | Run in normal mode |
make compile-translations | Compile .po to .mo files |
make extract-translations | Extract translatable strings |
make update-translations | Update translation catalogs |
make migrate | Run pending migrations |
make process | Run file processor manually |
make sync-data | Sync production data locally for testing |
make clean | Clean generated files |
make help | Show available commands |
Currently supported languages: English (en), German (de)
To add translations:
make extract-translations
make update-translations
# Edit src/birdhomie/translations/<lang>/LC_MESSAGES/messages.po
make compile-translations
Make sure you've configured the required UniFi Protect credentials in your .envrc or docker-compose.yml.
Change the port mapping in docker-compose.yml or stop the conflicting process.
If you're running on a CPU without AVX2/FMA support (Intel pre-Haswell/2013, AMD pre-Excavator/2015), you may experience high CPU usage during model inference. Set NNPACK_DISABLE=1 in your environment to disable NNPACK optimizations.
MIT License - see LICENSE for details.
47 commits
Python
61.9%
HTML
37.3%