DEV Community

Cover image for I Put My Phone in My Pocket and Let an Open Model Listen to the Birds
Adarsh Shaw
Adarsh Shaw

Posted on AI-assisted

I Put My Phone in My Pocket and Let an Open Model Listen to the Birds

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

  • 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

  1. Record. Capture a few minutes of birdsong on your phone or recorder, and save it as a .wav file.
  2. 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.
  3. Filter. Detections below your confidence threshold (default 0.5) are dropped, so weak guesses don't clutter the journal.
  4. 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.
  5. 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.
  6. 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
Enter fullscreen mode Exit fullscreen mode

For real recordings, install the detector:

pip install birdnet-analyzer
Enter fullscreen mode Exit fullscreen mode

For prose journal entries, install Ollama and pull a small model:

ollama pull llama3.2:3b
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode
# 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.
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

Testing

python3 -m unittest discover -s tests -v
Enter fullscreen mode Exit fullscreen mode

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.py shells 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.py is a thin wrapper that was written from BirdNET-Analyzer's documented command line. Verify its flags against python -m birdnet_analyzer.analyze --help for 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:

  1. Open an issue describing the change before large work.
  2. Add or update tests for behavior changes (python3 -m unittest discover -s tests).
  3. 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)