Cut downloads were named by byte offsets (`..._cut_740s-750s.flac`). They are
now named by the actual recording time the slice covers, e.g.
`20260523_22-31-30_22-32-30.flac` for a 22:31:30->22:32:30 cut of a recording
started at 22:00:00.
To make this reliable, the recording filename is now a fixed
`%Y%m%d_%H%M%S` start-time format (`FILENAME_FORMAT`) shared by isr.py and
web.py, replacing the user-configurable `filename_pattern` (web.py never reads
config.ini, so a custom pattern could not be parsed back). web.py parses the
start time out of the filename via `_recording_start()` and builds cut names
with `_cut_filename()`. The DATE column now also comes from the filename
(falling back to mtime only for non-standard names), since mtime is the last
write, not the start.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
_flush_buffer_to_file wrote (and for FLAC, encoded) every chunk while
holding buffer_lock, blocking the capture callback for the duration of
each disk flush. Swap the buffer out under the lock and write outside.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- Drop unused struct import, AudioDevice.extra/description fields
- Remove unused get_preferred_backend() and the backend priority
machinery (only one backend exists)
- Deduplicate SoundcardRecorder.close_current_file via super()
- Remove duplicate config-exists check in main()
- Simplify --list-devices output: drop dead monitor grouping and the
nonexistent pipewire backend example
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
tests/test_isr.py:
- Remove unused imports: io, SimpleNamespace, call
- Remove test_mp3_chunks_written_to_file — it mocked connect_stream but
never called record(), making all the mock scaffolding dead; the actual
assertion (bytes written to a file) is already covered by
TestAudioFileWriter and TestGenerateFilename
isr.py:
- Update AudioDevice.backend comment: only the ALSA backend exists now
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
AudioSystem.find_device() now falls back to treating an unmatched spec as
a direct ALSA PCM name when the ALSA backend is available. Virtual devices
defined in asound.conf (dsnoop, plug, etc.) never appear in 'arecord -l'
so they were always rejected as 'not found', even when valid.
ALSAStream now captures arecord stderr via a reader thread instead of
discarding it, so errors like 'Device or resource busy' are logged as
warnings and visible in docker compose logs.
docker-compose.yml: mount ./recordings at /app/recordings (matches
output_directory = recordings in config.ini); previously the recorder
wrote to /app/recordings while the web container read from /recordings,
causing all files to appear missing — explaining the 9-byte Not-found
download from /stream/ and the 0-byte recordings in the UI.
Add stop_grace_period: 30s so Docker waits long enough for files to close.
isr.py: replace per-thread join(timeout=5) with a shared 25 s deadline;
with N recorders the old code could exceed Docker's SIGKILL window and
leave WAV/FLAC files unclosed (corrupt headers).
web.py: add Content-Disposition: inline to /stream/ responses so
browsers never treat the audio response as a file download.
CLAUDE.md: document web.py endpoints, status.json lifecycle, corrected
Docker volume layout, and web.py CLI flags.
web.py:
- Extend loudness analysis to FLAC files via soundfile (numpy required)
- Add /stream/ endpoint with HTTP Range support for seekable inline playback
- Add collapsible ▶ Play button per row (hidden by default); src loaded lazily
- Add /api/status endpoint returning active filenames from status.json
- Animated ● REC badge on in-progress files, polled every 5 s
- Full WCAG: skip link, aria-expanded/controls, aria-label, role=img on
waveform SVG, role=list on loud-section chips, focus-visible outlines,
aria-live on subtitle, focus moved to <audio> when player opens
isr.py:
- Write recordings/status.json atomically every 2 s while recording
- Delete status.json on clean shutdown so web UI shows no stale state