Easy-to-manage touch screens
무료 오픈소스 터치스크린으로 USB 설정 가이드, 드래그‑드롭 편집기, 로컬 제어를 제공하며 Home Assistant용으로 제작되었습니다
Birdbrain이 새소리를 감지해 전화에 알려주며, 오디오가 외부로 나가지 않음; 계정은 선택 사항
Birdbrain listens and tells you which birds are singing, on your phone, so your audio never leaves it; an account is optional.
라이프스타일TypeScript · ShellGitHub @sabihusman온라인운영자 미확인
써 보셨어요? 한 번만 눌러 주세요. 로그인은 필요 없어요.
아직 감지된 업데이트가 없습니다. GitHub 릴리스가 나오면 여기에 보입니다.
개인 계정의 공개 저장소 소유자는 GitHub로 로그인해 운영자를 확인하고 소개를 직접 관리할 수 있습니다. 관리자 승인은 필요하지 않습니다.
로그인한 뒤 확인 파일을 만들어 이 저장소의 기본 브랜치에 30분 안에 커밋해 주세요. 다른 브랜치나 포크의 파일은 인정하지 않습니다. 파일을 올린 뒤 아래에서 확인하면 관리자 승인 없이 관리할 수 있습니다. 자격을 유지하려면 파일을 남겨 두세요.
로그인이 필요하면 로그인 화면으로 이동합니다. 로그인 후 이곳으로 돌아와 원하는 확인 버튼을 다시 눌러 주세요.
프로젝트 폴더에서 /nomorevibe verify를 실행하면 소유권을 확인하고 소개를 직접 관리할 수 있습니다.
1분 만에 확인하기On-device bird sound identification PWA with a visual explanation of how the AI identifies each bird. BirdNET + TensorFlow.js.
저장소 README에서 가져옴
README 전체를 불러오는 중입니다.
README 전문 보기 ↗Point your phone at the dawn chorus and see who is singing. Sabih’s Birdbrain is a progressive web app that identifies bird sounds in real time, entirely on the device, using the BirdNET V2.4 neural network running in TensorFlow.js inside a Web Worker. It records, re-analyses, explains its own identifications, looks species up, maps where you heard them, and works offline once installed.
Live app: https://sabihs-birdbrain.vercel.app. API: https://bird-brain.duckdns.org (server/).
| Listen (light) | Listen (dark) | Species | Map |
|---|---|---|---|
| Recording analysed | “Why this ID” heatmap | Species search | Settings (grouped rows) |
|---|---|---|---|
Screenshots taken with Playwright at iPhone 13 size from the built app. The species page shows the retry state because the build machine’s shared network address was rate-limited by Wikipedia.
Bird-ID apps upload your audio to a server, need an account, or stop working without signal. Good bioacoustic models exist and run fine on a phone, so the interesting question was: how much of a complete field tool can be built as a plain web page, with no backend and no data leaving the device, and can it also show what the model is doing instead of just printing a name?
Constraints that shaped it:
flowchart LR
Mic["Microphone\n(getUserMedia)"] --> AW["AudioWorklet\nmono batches"]
AW --> RS["Resample\n→ 48 kHz"]
RS --> WIN["3 s windows\n1.5 s overlap"]
WIN -->|"Float32Array\n(transferred)"| W
subgraph W["Web Worker (TensorFlow.js: WebGL → WASM → CPU)"]
direction LR
STFT["Own FFT/STFT\n(real part)"] --> MEL["2 mel spectrograms\n96×511 each"]
MEL --> NN["EfficientNet-B0-like\n6,522 sigmoid outputs"]
GEO["Geo model\n(lat, lon, week)"] --> FLT["Location/season\nfilter ≥ 0.03"]
NN --> FLT
end
FLT --> RES["Every species above\nthe threshold slider"]
RES --> UI["Live list · spectrogram shading\n· confidence timeline"]
RES --> DB[("IndexedDB\ndetections · recordings\n· Wikipedia cache")]
DB --> MAP["Leaflet map\n(clustered markers)"]
DB --> EXP["CSV / GeoJSON / WAV export"]
Every stage is visible in the app’s How it works panel, which lights up as audio passes through, and in the live visualisation: a waveform, a scrolling spectrogram drawn with the app’s own FFT (labelled as an approximation of the model’s internal mel spectrograms), the sliding 3-second analysis window, colour strips for the species found in each window, and a per-species confidence timeline.
Why this ID. On a saved recording you can ask why the model thinks a window contains, say, a House Finch. The app silences one time–frequency region at a time, re-runs the model, and paints the regions whose removal hurt the confidence most over the spectrogram in the species’ colour. It runs in the worker with a progress bar and a cancel button; the grid is coarse by default because each cell is a full model pass.
Species colours. Each species has a fixed colour derived from a hash of its scientific name in OKLCH, chosen to read on both light and dark backgrounds. The name is always shown next to the colour.
Two themes from docs/DESIGN_GUIDE.md: "Forest Night"
(dark, the default whatever the phone prefers) and "Field Guide, cream". The
sun/moon button in the header switches; the choice is remembered on the
device and applied before the first paint. Both themes pass WCAG AA on the
token pairs and the species dots (checked by tests); screenshots of every
Listen state in both themes are in docs/screenshots/.
Audio is processed on the device and never uploaded: identification, saving recordings and analysing imported clips (capped at 60 seconds) all happen in your browser, for everyone, signed in or not. Recordings and detections live in your browser's IndexedDB; you can export or delete them at any time.
What does leave the phone, and why:
The one-time model download from the hosting origin, Wikipedia summaries when you open a species page, and OpenStreetMap tiles when you open the map.
When you open the Places view on the Map tab: your sightings are grouped on
the phone into non-overlapping areas about 1 km across, and the centre of
each area without a saved name, rounded to three decimals (about 100 m),
goes to OpenStreetMap's Nominatim reverse geocoder
(nominatim.openstreetmap.org) to get the area's name, one request at a
time, at least 1.1 s apart. No species, times or account details are sent;
the names are kept on the phone for 180 days. Never on app start, and never
from the Birds view.
If you create an account (optional): your email address and password go to
the Birdbrain API at bird-brain.duckdns.org, which emails you a 6-digit
code on every sign-in and keeps a short-lived session token (the API only
accepts requests from https://sabihs-birdbrain.vercel.app). New accounts
wait for Sabih's approval.
If you are an approved member and use "Ask about this bird": the species'
scientific name, your question (up to 500 characters) and your
state/province code are sent. The code is decided on the device from your
location (US-IA, US-IL, US-WA, CA-AB, CA-ON, or none elsewhere
or when location is off). For questions about sightings near you ("near
me", "in my area"…) your position rounded to one decimal (about 10 km) is
added; your exact location never is. The server fetches the relevant
sections of the Wikipedia article (description, voice, range, behaviour,
diet, status; capped, cached for seven days), the species' NatureServe
Explorer conservation ranks (global and for your state/province; cached
30 days) and, when the server has an eBird key, the recent eBird sightings
in your state/province and near the rounded point (last 30 days; locality
name, date and count only; cached one hour), then asks an AI model to
answer from that text alone (the services set in the server's
AI_PROVIDER_ORDER, each one a backup for the one before: Anthropic Claude,
Ollama Cloud and/or Google Gemini; Settings › What we store names the ones in
use; all receive exactly the same data and nothing about you beyond what is
listed here), labelling each fact with its source
and saying "sources differ" when they disagree. The model never calls
these services. The answer comes back as plain text with the cited
sections and the sources used as links. When the article cannot be
fetched, the summary is used; when NatureServe or eBird are unavailable,
the answer says which sources it used. Questions are not logged. There are
daily limits per person and overall.
If you are an approved member and use Friends (Settings › Account): your display name is stored on the server and shown to friends instead of your email. Inviting a friend sends their address to the API, which always answers "Invite sent"; an email goes out only to an approved member who is not already your friend, and it names you by display name, never by email. Sharing is . If you turn on "Share my species list with friends" (the switch says exactly what is shared: "which birds you've spotted and the date you last spotted each one"), the app uploads, for each species, only the scientific name and the calendar day of your most recent sighting (, local day, no time), replacing the whole list after each new sighting. No locations, no counts, no recordings or clips, no history of earlier sightings, and nothing about species you have not spotted. Only accepted friends can read it, and they see your display name with those pairs, nothing else. Turning the switch off removes the list from the server immediately.
Member features keep everything on the device too: the species ID card, sighting history, heat map and the one saved clip per species are read from IndexedDB and are never uploaded. The heat map in particular is sensitive location data (it shows where you have been listening); it is built from your own detections only, is not part of the exports, and signing out hides it without deleting anything. Whether you are a member is decided from the session cached on the device, so no request is made to check it.
No analytics, no ads, no third-party trackers. The API's design and its review against the OWASP LLM Top 10 and ASVS are in docs/SECURITY.md.
The web app proves the pipeline but cannot listen in the background. V2 is a
Swift/SwiftUI app with AVAudioEngine capture, the audio background mode, and
BirdNET on Core ML or TensorFlow Lite using the Neural Engine, which should be
roughly an order of magnitude faster and far lighter on the battery. It will
keep the same data model and import the PWA’s CSV/GeoJSON exports, use MapKit
instead of Leaflet, and share the species-colour and week-of-year functions so
both apps agree.
npm install
npm run dev # http://localhost:5173/
npm run lint && npm run typecheck && npm test
npm run build # typecheck + vite build → dist/
npm run preview # serve the build at http://localhost:4173/
npm run test:e2e # Playwright: loads the model, analyses the bundled sample, goes offline
cd server && npm install && npm test # API unit + integration tests (fake mailer, in-memory SQLite)
Front-end build settings live in .env.example (VITE_API_BASE_URL,
VITE_BASE_PATH, VITE_MODEL_BASE_URL). The API is in server/ with its own
README-style notes in server/deploy/README.md, server/.env.example
(variable names only) and docs/SECURITY.md.
The 23 weight files (78 MiB) live in models/birdnet-v2.4/ and are served by
Nginx from the API host, not by Vercel, which keeps deployments small and the
monthly data transfer low (DECISIONS.md #56). Only labels.json stays in
public/, because the offline species list needs it.
scripts/server/publish-model.sh --dry-run # compare the local files with the server
scripts/server/publish-model.sh # copy and hash-check them (tar over ssh)
npm run check:dist # fails if weights ever end up in dist/
npm run dev serves models/ itself, so nothing extra is needed locally. For a
npm run preview or Playwright run against a local copy of the model, build with
VITE_MODEL_BASE_URL=/models/birdnet-v2.4/ (what CI does); a plain npm run build
points the app at https://bird-brain.duckdns.org/models/birdnet-v2.4/, which
sends CORS headers only to this app's own origins.
On Windows in Git Bash, put MSYS_NO_PATHCONV=1 in front of that build: otherwise
the leading slash is rewritten to a Windows path and the app ends up asking for the
model over file://.
Unit tests (Vitest) cover the FFT/STFT, resampling and windowing, threshold and
multi-label logic, the week-of-year calculation, species colours, WAV encoding
and decoding, occlusion masking, and CSV/GeoJSON export. The Playwright test
runs the built app in Chromium at iPhone size, waits for the models to load in
the worker, identifies the sample clip that ships with the official BirdNET
TF.js export (a redpoll), checks the WebAssembly fallback, then reloads offline
and does it again from the caches. GitHub Actions runs all of it on every push;
Vercel builds and deploys the front end from main.
Architecture notes for contributors are in CLAUDE.md; the build decisions and their alternatives are in DECISIONS.md; sources, versions and licences of everything third-party are in docs/SOURCES.md.
models/birdnet-v2.4/, acoustic and
location/season models, label list, and the sample clip): © the BirdNET team,
Stefan Kahl et al., K. Lisa Yang Center for Conservation Bioacoustics at the
Cornell Lab of Ornithology and Chemnitz University of Technology, licensed
CC BY-NC-SA 4.0, non-commercial use only. They are not covered by the MIT
licence. Taken from the official TensorFlow.js export in the
BirdNET-Analyzer
repository (tag v1.5.1). Citation: Kahl, S., Wood, C. M., Eibl, M., & Klinck,
H. (2021). BirdNET: A deep learning solution for avian diversity monitoring.
Ecological Informatics, 61, 101236.navigator.storage.persist()
result, CSV and GeoJSON export, delete all.YYYY-MM-DDDeleting your account (Settings › Account › "Delete my account": your password again and the word DELETE) removes, immediately and permanently, with no grace period, everything the server holds for you in one transaction: the account row, the password hash, every session and sign-in code, your display name, your shared species list, friendships and invites in both directions, and your daily question counters (the seven-day answer cache holds no name or email). A short confirmation is emailed to you. Sightings, recordings and clips exist only on your phone, so the confirmation screen offers "Also clear sightings saved on this phone" (off by default). The admin account cannot delete itself from the app. Copies in server backups are removed when those backups expire. The same facts, in one paragraph, appear on the sign-up form and in the Account card ("What we store").