5.4 KiB
ISR Roadmap
notify.py — NTFY Loudness Notifications
Context
Street ambience recorder. Goal: detect notable audio events (speech, thunder, sustained unusual sounds) in hourly recording files and push a notification via a self-hosted NTFY server. Generic short events (car horn, passing vehicle) should be filtered out by a minimum section duration.
Design decisions
| Topic | Decision |
|---|---|
| Detection | RMS + minimum section duration filter (KISS — no FFT for now) |
| Timing | Configurable: immediate / daily / both |
| Config | [notify] section in existing config.ini |
| Code structure | notify.py imports analyze_wav / analyze_flac from web.py (DRY) |
| Source name | Included in notification body; configurable display name per source |
Config additions (config.example.ini)
Add a [notify] section to config.ini:
[notify]
enabled = true
ntfy_url = https://ntfy.example.com/mytopic ; full URL incl. topic
mode = immediate ; immediate | daily | both
daily_time = 08:00 ; HH:MM — used in daily and both modes
debounce_minutes = 60 ; immediate mode: suppress repeat notifications within this window
min_section_duration = 2.0 ; seconds — sections shorter than this are ignored (filters car horns etc.)
min_sections = 1 ; number of qualifying sections required to trigger a notification
loudness_threshold = 0.05 ; RMS 0–1, same scale as web.py analysis threshold
Per recording source, add an optional display_name:
[radio1]
type = stream
url = http://icecast.example.com:8000/live
display_name = Street mic north ; shown in notification; defaults to section name [radio1]
Notification format
Title: ISR — Notable audio · Street mic north
Body: radio1_20260427_0300.wav
3 notable sections (≥ 2.0 s each)
→ 00:12 – 00:18
→ 01:45 – 01:52
→ 47:03 – 47:11
Peak RMS: 0.312
Daily digest example:
Title: ISR Daily Digest · 2026-04-27
Body: Street mic north — 4 files with notable events
03:00 file · 3 sections (peak 0.312)
07:00 file · 1 section (peak 0.091)
14:00 file · 2 sections (peak 0.204)
21:00 file · 1 section (peak 0.178)
Implementation plan
Phase 1 — Core
-
config.example.ini— add[notify]section anddisplay_namekey to source section examples (as shown above). -
notify.py— file watcher- Polls
recordings/status.jsonevery 30 s. - Tracks which files were in
activeon the previous poll. - When a file disappears from
activeit was just closed → queues it for analysis. - Skips files with extensions that cannot be analysed (anything other than
.wav/.flac).
- Polls
-
notify.py— analysis + filter- Imports
analyze_wav/analyze_flacfromweb.py. - Applies
loudness_thresholdfrom[notify]config. - Filters resulting sections to those with duration ≥
min_section_duration. - Counts filtered sections against
min_sectionsthreshold.
- Imports
-
notify.py— NTFY HTTP POST- Plain
urllibPOST tontfy_url(no extra dependencies). - Sets
Titleand message body as described above. - Logs success / failure to stdout.
- Plain
Phase 2 — Cadence modes
-
Immediate mode with debounce
- Fires right after the file closes and analysis passes.
- Persists last-notification timestamp per source to a small
notify_state.jsonin the recordings directory. - Suppresses sending if last notification for that source was within
debounce_minutes.
-
Daily digest mode
- Appends qualifying events to
notify_log.jsonlin the recordings directory (one JSON line per event: timestamp, source, filename, sections, peak RMS). - On each poll checks whether
daily_timehas passed today and no digest has been sent yet (tracked innotify_state.json). - Reads all undigested entries from
notify_log.jsonl, groups bydisplay_name, sends one notification per source with notable activity. - Marks entries as digested.
- Appends qualifying events to
-
Both mode
- Immediate path: only fires when peak RMS exceeds a second, higher
threshold (
alarm_threshold, default0.3; add to[notify]config). - Daily digest path: fires for everything that passes
min_sections.
- Immediate path: only fires when peak RMS exceeds a second, higher
threshold (
Phase 3 — Integration
-
Docker — optional
notifyservice indocker-compose.yml:notify: build: . command: python notify.py volumes: - ./recordings:/app/recordings - ./config.ini:/app/config.ini:ro restart: unless-stopped -
README — new section documenting
notify.pyusage, config keys, and Docker setup.
Open questions (decide before implementing)
- Log rotation:
notify_log.jsonlgrows indefinitely. Options: cap at N days (configurable), cap at N MB, or leave cleanup to the user. No decision made yet. - Multiple NTFY topics per source: current design uses one global topic.
If per-source topics are ever needed,
ntfy_urlcould be moved to the source section and override the global one. - FFT / frequency analysis (future): distinguishing thunder (low rumble,
50–200 Hz) from speech (300–3000 Hz) from vehicles would reduce false
positives further. Deferred — requires
numpyand adds meaningful complexity.