<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Adarsh Shaw</title>
    <description>The latest articles on DEV Community by Adarsh Shaw (@adarsh_shaw).</description>
    <link>https://dev.to/adarsh_shaw</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4176029%2F19ac3bc1-70b8-49b6-ba0c-1e5a1db89315.png</url>
      <title>DEV Community: Adarsh Shaw</title>
      <link>https://dev.to/adarsh_shaw</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/adarsh_shaw"/>
    <language>en</language>
    <item>
      <title>I Put My Phone in My Pocket and Let an Open Model Listen to the Birds</title>
      <dc:creator>Adarsh Shaw</dc:creator>
      <pubDate>Sun, 11 Oct 2026 18:24:58 +0000</pubDate>
      <link>https://dev.to/adarsh_shaw/i-put-my-phone-in-my-pocket-and-let-an-open-model-listen-to-the-birds-2pob</link>
      <guid>https://dev.to/adarsh_shaw/i-put-my-phone-in-my-pocket-and-let-an-open-model-listen-to-the-birds-2pob</guid>
      <description>&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Facfss87lm5v1npd2g814.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Facfss87lm5v1npd2g814.png" alt=" " width="800" height="533"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h1&gt;
  
  
  Trail Ear
&lt;/h1&gt;

&lt;p&gt;&lt;strong&gt;An offline bird companion. Record birdsong on a trail, get a field-journal entry back. Nothing leaves your machine.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Trail Ear exists to make the screen the &lt;em&gt;shortest&lt;/em&gt; 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.&lt;/p&gt;




&lt;h2&gt;
  
  
  Table of contents
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Features&lt;/li&gt;
&lt;li&gt;How it works&lt;/li&gt;
&lt;li&gt;Quick start&lt;/li&gt;
&lt;li&gt;Usage&lt;/li&gt;
&lt;li&gt;Example&lt;/li&gt;
&lt;li&gt;Project structure&lt;/li&gt;
&lt;li&gt;Testing&lt;/li&gt;
&lt;li&gt;Design decisions&lt;/li&gt;
&lt;li&gt;Privacy&lt;/li&gt;
&lt;li&gt;Limitations&lt;/li&gt;
&lt;li&gt;Roadmap&lt;/li&gt;
&lt;li&gt;Contributing&lt;/li&gt;
&lt;li&gt;License and attribution&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Features
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Fully offline&lt;/strong&gt; after the one-time model downloads. No account, no API key, no signal needed.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Location-aware detection.&lt;/strong&gt; Optional latitude, longitude and week narrow the candidate species list.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Readable journal output&lt;/strong&gt; in Markdown, ready to paste into notes or a blog.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Optional local LLM&lt;/strong&gt; via &lt;a href="https://ollama.com" rel="noopener noreferrer"&gt;Ollama&lt;/a&gt;. Any model tag works.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Graceful fallback.&lt;/strong&gt; If no model is running, you still get a clean species summary.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Zero runtime dependencies&lt;/strong&gt; for the journal step (Python standard library only).&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  How it works
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Record.&lt;/strong&gt; Capture a few minutes of birdsong on your phone or recorder, and save it as a &lt;code&gt;.wav&lt;/code&gt; file.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Detect.&lt;/strong&gt; Run &lt;code&gt;trail_ear listen&lt;/code&gt;. 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.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Filter.&lt;/strong&gt; Detections below your confidence threshold (default 0.5) are dropped, so weak guesses don't clutter the journal.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Group.&lt;/strong&gt; The remaining detections are grouped by species and sorted by how often each was heard, with the best confidence and first timestamp for each.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Write.&lt;/strong&gt; 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.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Save.&lt;/strong&gt; The journal is printed to the terminal or saved to the file you chose with &lt;code&gt;-o&lt;/code&gt;.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Already have a BirdNET results file? Skip steps 1 and 2 and run &lt;code&gt;trail_ear journal&lt;/code&gt; on the CSV directly.&lt;/p&gt;

&lt;h2&gt;
  
  
  Quick start
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Requirements:&lt;/strong&gt; Python 3.9 or newer.&lt;/p&gt;

&lt;p&gt;Try the journal step on the bundled sample data (no installs needed):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git clone &amp;lt;your-repo-url&amp;gt; trail-ear &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;cd &lt;/span&gt;trail-ear
python3 &lt;span class="nt"&gt;-m&lt;/span&gt; trail_ear journal sample/detections.csv &lt;span class="nt"&gt;--place&lt;/span&gt; &lt;span class="s2"&gt;"the lake path"&lt;/span&gt; &lt;span class="nt"&gt;--no-llm&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For real recordings, install the detector:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pip &lt;span class="nb"&gt;install &lt;/span&gt;birdnet-analyzer
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For prose journal entries, install &lt;a href="https://ollama.com" rel="noopener noreferrer"&gt;Ollama&lt;/a&gt; and pull a small model:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ollama pull llama3.2:3b
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Usage
&lt;/h2&gt;

&lt;p&gt;Trail Ear has two commands.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;code&gt;journal&lt;/code&gt;: write a journal from an existing results file
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;python3 &lt;span class="nt"&gt;-m&lt;/span&gt; trail_ear journal results.csv &lt;span class="nt"&gt;--place&lt;/span&gt; &lt;span class="s2"&gt;"Riverside loop"&lt;/span&gt; &lt;span class="nt"&gt;-o&lt;/span&gt; journal.md
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  &lt;code&gt;listen&lt;/code&gt;: analyze a recording, then write the journal
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;python3 &lt;span class="nt"&gt;-m&lt;/span&gt; trail_ear listen walk.wav &lt;span class="nt"&gt;--lat&lt;/span&gt; 22.57 &lt;span class="nt"&gt;--lon&lt;/span&gt; 88.36 &lt;span class="nt"&gt;--week&lt;/span&gt; 41 &lt;span class="se"&gt;\&lt;/span&gt;
        &lt;span class="nt"&gt;--place&lt;/span&gt; &lt;span class="s2"&gt;"Riverside loop"&lt;/span&gt; &lt;span class="nt"&gt;-o&lt;/span&gt; journal.md
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Options
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Option&lt;/th&gt;
&lt;th&gt;Applies to&lt;/th&gt;
&lt;th&gt;Default&lt;/th&gt;
&lt;th&gt;Description&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;--place TEXT&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;both&lt;/td&gt;
&lt;td&gt;&lt;code&gt;the trail&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Place name used in the journal.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;--min-conf FLOAT&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;both&lt;/td&gt;
&lt;td&gt;&lt;code&gt;0.5&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Drop detections below this confidence (0 to 1).&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;--model TAG&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;both&lt;/td&gt;
&lt;td&gt;&lt;code&gt;llama3.2:3b&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Ollama model tag used for the prose entry.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;--no-llm&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;both&lt;/td&gt;
&lt;td&gt;off&lt;/td&gt;
&lt;td&gt;Skip the language model and use the plain template.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;-o, --out FILE&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;both&lt;/td&gt;
&lt;td&gt;stdout&lt;/td&gt;
&lt;td&gt;Write the journal to a Markdown file.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;--lat&lt;/code&gt;, &lt;code&gt;--lon&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;&lt;code&gt;listen&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;none&lt;/td&gt;
&lt;td&gt;Coordinates used to narrow BirdNET's species list.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;--week INT&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;listen&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;none&lt;/td&gt;
&lt;td&gt;Week of the year for BirdNET's seasonal filter. Check the BirdNET-Analyzer docs for the expected scale.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  Input format
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;journal&lt;/code&gt; reads a BirdNET-Analyzer CSV with these columns:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Start (s),End (s),Scientific name,Common name,Confidence
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Example
&lt;/h2&gt;

&lt;p&gt;The following uses the bundled &lt;code&gt;sample/detections.csv&lt;/code&gt;. &lt;strong&gt;It is sample data for demonstration, not a real field recording.&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;python3 &lt;span class="nt"&gt;-m&lt;/span&gt; trail_ear journal sample/detections.csv &lt;span class="nt"&gt;--place&lt;/span&gt; &lt;span class="s2"&gt;"the lake path"&lt;/span&gt; &lt;span class="nt"&gt;--no-llm&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight markdown"&gt;&lt;code&gt;&lt;span class="gh"&gt;# Field journal, 2026-10-11&lt;/span&gt;

Listened at the lake path. Heard 4 species:
&lt;span class="p"&gt;
-&lt;/span&gt; &lt;span class="gs"&gt;**Red-vented Bulbul**&lt;/span&gt; (Pycnonotus cafer): 3 detection(s), best confidence 91%, first at 0:00
&lt;span class="p"&gt;-&lt;/span&gt; &lt;span class="gs"&gt;**Asian Koel**&lt;/span&gt; (Eudynamys scolopaceus): 2 detection(s), best confidence 88%, first at 0:09
&lt;span class="p"&gt;-&lt;/span&gt; &lt;span class="gs"&gt;**Oriental Magpie-Robin**&lt;/span&gt; (Copsychus saularis): 1 detection(s), best confidence 77%, first at 0:06
&lt;span class="p"&gt;-&lt;/span&gt; &lt;span class="gs"&gt;**Common Myna**&lt;/span&gt; (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.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Note that the Common Tailorbird row (confidence 0.31) in the sample file is dropped by the default &lt;code&gt;--min-conf 0.5&lt;/code&gt;. Lower the threshold to include it.&lt;/p&gt;

&lt;p&gt;With a local model running, an extra &lt;code&gt;## In words&lt;/code&gt; section is appended. Its wording depends on the model you use, so it will differ from run to run.&lt;/p&gt;

&lt;h2&gt;
  
  
  Project structure
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;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
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Testing
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;python3 &lt;span class="nt"&gt;-m&lt;/span&gt; unittest discover &lt;span class="nt"&gt;-s&lt;/span&gt; tests &lt;span class="nt"&gt;-v&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;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.&lt;/p&gt;

&lt;h2&gt;
  
  
  Design decisions
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;The language model cannot invent birds.&lt;/strong&gt; 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.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Fail soft.&lt;/strong&gt; A field tool that crashes when a dependency is missing gets abandoned. Every optional piece falls back to something useful.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Standard library for the core.&lt;/strong&gt; Fewer dependencies means fewer things to break on a laptop that has not seen a network in days.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Detection is a thin wrapper.&lt;/strong&gt; &lt;code&gt;detect.py&lt;/code&gt; shells out to BirdNET-Analyzer rather than reimplementing it, so improvements upstream arrive for free.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Privacy
&lt;/h2&gt;

&lt;p&gt;Audio, coordinates and journal text stay on your machine. The only network call in the codebase is to a local Ollama server on &lt;code&gt;localhost&lt;/code&gt;. Coordinates are used solely to filter BirdNET's species list locally.&lt;/p&gt;

&lt;h2&gt;
  
  
  Limitations
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;detect.py&lt;/code&gt; is a thin wrapper that was written from BirdNET-Analyzer's documented command line.&lt;/strong&gt; Verify its flags against &lt;code&gt;python -m birdnet_analyzer.analyze --help&lt;/code&gt; for your installed version.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Detections are model confidence scores, not ground truth.&lt;/strong&gt; Verify surprising results by eye or with a second recording.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Species coverage and accuracy&lt;/strong&gt; vary by region and recording quality. Wind, traffic and phone microphones all hurt results.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;No rarity or conservation data.&lt;/strong&gt; Trail Ear reports what was heard, not whether it is unusual.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Command line only.&lt;/strong&gt; There is no GUI or mobile app.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Roadmap
&lt;/h2&gt;

&lt;p&gt;Ideas, not promises:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;[ ] Split long recordings and report a per-minute timeline&lt;/li&gt;
&lt;li&gt;[ ] Merge several recordings into one trip journal&lt;/li&gt;
&lt;li&gt;[ ] Export to CSV or eBird-style checklists&lt;/li&gt;
&lt;li&gt;[ ] Optional species photo and call reference links&lt;/li&gt;
&lt;li&gt;[ ] Packaging as a pip-installable CLI (&lt;code&gt;pip install trail-ear&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;[ ] Simple on-device recorder workflow for phones&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Contributing
&lt;/h2&gt;

&lt;p&gt;Issues and pull requests are welcome. Please:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Open an issue describing the change before large work.&lt;/li&gt;
&lt;li&gt;Add or update tests for behavior changes (&lt;code&gt;python3 -m unittest discover -s tests&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;Keep the core dependency-free where practical.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  License and attribution
&lt;/h2&gt;

&lt;p&gt;Trail Ear's own code is released under the &lt;a href="https://dev.toLICENSE"&gt;MIT License&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;It builds on third-party open-source work that carries its own terms:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://github.com/birdnet-team/BirdNET-Analyzer" rel="noopener noreferrer"&gt;BirdNET-Analyzer&lt;/a&gt; by the BirdNET team (K. Lisa Yang Center for Conservation Bioacoustics at the Cornell Lab of Ornithology, and Chemnitz University of Technology). &lt;strong&gt;The BirdNET model weights are published under a non-commercial license. Check the current terms before any commercial use.&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://ollama.com" rel="noopener noreferrer"&gt;Ollama&lt;/a&gt; and the language model you choose to run with it, each under their own licenses.&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>devchallenge</category>
      <category>hf26challenge</category>
    </item>
  </channel>
</rss>
