Trail Ear
An offline bird companion. Record birdsong on a trail, get a field-journal entry back. Nothing leaves your machine.
Trail Ear exists to make the screen the shortest part of a walk. You put the phone away, listen, record a few minutes, and look at the results afterwards. Detection runs locally with an open bird-sound model, and an optional local language model turns the species list into a short journal entry.
Table of contents
- Features
- How it works
- Quick start
- Usage
- Example
- Project structure
- Testing
- Design decisions
- Privacy
- Limitations
- Roadmap
- Contributing
- License and attribution
Features
- Fully offline after the one-time model downloads. No account, no API key, no signal needed.
- Location-aware detection. Optional latitude, longitude and week narrow the candidate species list.
- Readable journal output in Markdown, ready to paste into notes or a blog.
- Optional local LLM via Ollama. Any model tag works.
- Graceful fallback. If no model is running, you still get a clean species summary.
- Zero runtime dependencies for the journal step (Python standard library only).
How it works
-
Record. Capture a few minutes of birdsong on your phone or recorder, and save it as a
.wavfile. -
Detect. Run
trail_ear listen. It passes the recording to BirdNET-Analyzer, an open model that runs locally, and gets back a CSV of detections. If you give it your latitude, longitude and week, BirdNET narrows its candidate species to those plausible for your area and season. - Filter. Detections below your confidence threshold (default 0.5) are dropped, so weak guesses don't clutter the journal.
- Group. The remaining detections are grouped by species and sorted by how often each was heard, with the best confidence and first timestamp for each.
- Write. Trail Ear builds a Markdown journal from the summary. If a local Ollama model is running, it also adds a short prose entry. If not, you still get the clean species list.
-
Save. The journal is printed to the terminal or saved to the file you chose with
-o.
Already have a BirdNET results file? Skip steps 1 and 2 and run trail_ear journal on the CSV directly.
Quick start
Requirements: Python 3.9 or newer.
Try the journal step on the bundled sample data (no installs needed):
git clone <your-repo-url> trail-ear && cd trail-ear
python3 -m trail_ear journal sample/detections.csv --place "the lake path" --no-llm
For real recordings, install the detector:
pip install birdnet-analyzer
For prose journal entries, install Ollama and pull a small model:
ollama pull llama3.2:3b
Usage
Trail Ear has two commands.
journal: write a journal from an existing results file
python3 -m trail_ear journal results.csv --place "Riverside loop" -o journal.md
listen: analyze a recording, then write the journal
python3 -m trail_ear listen walk.wav --lat 22.57 --lon 88.36 --week 41 \
--place "Riverside loop" -o journal.md
Options
| Option | Applies to | Default | Description |
|---|---|---|---|
--place TEXT |
both | the trail |
Place name used in the journal. |
--min-conf FLOAT |
both | 0.5 |
Drop detections below this confidence (0 to 1). |
--model TAG |
both | llama3.2:3b |
Ollama model tag used for the prose entry. |
--no-llm |
both | off | Skip the language model and use the plain template. |
-o, --out FILE |
both | stdout | Write the journal to a Markdown file. |
--lat, --lon
|
listen |
none | Coordinates used to narrow BirdNET's species list. |
--week INT |
listen |
none | Week of the year for BirdNET's seasonal filter. Check the BirdNET-Analyzer docs for the expected scale. |
Input format
journal reads a BirdNET-Analyzer CSV with these columns:
Start (s),End (s),Scientific name,Common name,Confidence
Example
The following uses the bundled sample/detections.csv. It is sample data for demonstration, not a real field recording.
python3 -m trail_ear journal sample/detections.csv --place "the lake path" --no-llm
# Field journal, 2026-10-11
Listened at the lake path. Heard 4 species:
- **Red-vented Bulbul** (Pycnonotus cafer): 3 detection(s), best confidence 91%, first at 0:00
- **Asian Koel** (Eudynamys scolopaceus): 2 detection(s), best confidence 88%, first at 0:09
- **Oriental Magpie-Robin** (Copsychus saularis): 1 detection(s), best confidence 77%, first at 0:06
- **Common Myna** (Acridotheres tristis): 1 detection(s), best confidence 52%, first at 0:15
Confidence is the model's score, not proof. Confirm anything surprising by eye or with a second recording.
Note that the Common Tailorbird row (confidence 0.31) in the sample file is dropped by the default --min-conf 0.5. Lower the threshold to include it.
With a local model running, an extra ## In words section is appended. Its wording depends on the model you use, so it will differ from run to run.
Project structure
trail-ear/
├── trail_ear/
│ ├── __init__.py
│ ├── __main__.py # CLI entry point (argparse)
│ ├── detect.py # Thin wrapper around BirdNET-Analyzer's CLI
│ └── journal.py # Parsing, grouping, prompt building, journal writing
├── tests/
│ └── test_journal.py # Unit tests (standard library unittest)
├── sample/
│ └── detections.csv # Demo data, not a real recording
├── requirements.txt
├── LICENSE
└── README.md
Testing
python3 -m unittest discover -s tests -v
The tests cover confidence filtering, species grouping and ordering, template output, the empty-detections case, prompt constraints, and the fallback when no Ollama server is reachable. They do not require BirdNET or Ollama to be installed.
Design decisions
- The language model cannot invent birds. The prompt restricts it to the detected species and forbids claims that a species is rare or unusual, because the tool holds no rarity data and small models will fabricate such claims confidently.
- Fail soft. A field tool that crashes when a dependency is missing gets abandoned. Every optional piece falls back to something useful.
- Standard library for the core. Fewer dependencies means fewer things to break on a laptop that has not seen a network in days.
-
Detection is a thin wrapper.
detect.pyshells out to BirdNET-Analyzer rather than reimplementing it, so improvements upstream arrive for free.
Privacy
Audio, coordinates and journal text stay on your machine. The only network call in the codebase is to a local Ollama server on localhost. Coordinates are used solely to filter BirdNET's species list locally.
Limitations
-
detect.pyis a thin wrapper that was written from BirdNET-Analyzer's documented command line. Verify its flags againstpython -m birdnet_analyzer.analyze --helpfor your installed version. - Detections are model confidence scores, not ground truth. Verify surprising results by eye or with a second recording.
- Species coverage and accuracy vary by region and recording quality. Wind, traffic and phone microphones all hurt results.
- No rarity or conservation data. Trail Ear reports what was heard, not whether it is unusual.
- Command line only. There is no GUI or mobile app.
Roadmap
Ideas, not promises:
- [ ] Split long recordings and report a per-minute timeline
- [ ] Merge several recordings into one trip journal
- [ ] Export to CSV or eBird-style checklists
- [ ] Optional species photo and call reference links
- [ ] Packaging as a pip-installable CLI (
pip install trail-ear) - [ ] Simple on-device recorder workflow for phones
Contributing
Issues and pull requests are welcome. Please:
- Open an issue describing the change before large work.
- Add or update tests for behavior changes (
python3 -m unittest discover -s tests). - Keep the core dependency-free where practical.
License and attribution
Trail Ear's own code is released under the MIT License.
It builds on third-party open-source work that carries its own terms:
- BirdNET-Analyzer by the BirdNET team (K. Lisa Yang Center for Conservation Bioacoustics at the Cornell Lab of Ornithology, and Chemnitz University of Technology). The BirdNET model weights are published under a non-commercial license. Check the current terms before any commercial use.
- Ollama and the language model you choose to run with it, each under their own licenses.

Top comments (0)