Adds a Cut panel inside every player row (✂ Cut button in the actions
column opens the row and focuses the Start field). Users type start and
end times in m:ss or h:mm:ss format; Download cut triggers
GET /api/cut which runs ffmpeg -c copy (no re-encode) and streams the
result as an attachment.
Clicking an analysis chip now also pre-fills the cut panel start/end
with the loud-section boundaries.
Server switched to ThreadingHTTPServer so ffmpeg runs don't block
other requests. ffmpeg added to Dockerfile apt install.
Client-side filter bar with live filename search and from/to date
pickers. Rendering is split into renderFiles() + applyFilters() so
filters can be re-applied without re-fetching. Subtitle shows
'N of M shown' when a filter is active. Clear button resets all fields.
Analysis chips are now buttons. Clicking one opens the player (if
not already open) and seeks to the section start. J skips to the
previous loud section, K to the next. Shortcuts are suppressed when
focus is inside an input field.
With preload=none the browser never fetches metadata, so Chrome
cannot populate the duration field for FLAC files. On player open:
set preload=metadata and call audio.load() to trigger a metadata-only
fetch. Also render a server-computed duration label beneath the audio
element as a fallback for formats the browser cannot parse.
WAV nframes and FLAC total_samples are both unfinalized while the
recorder has the file open, producing wildly wrong durations
(e.g. 53375995583:39:01). Return None (shown as —) instead.
Adds a DELETE /api/files/<name> endpoint that refuses to remove files
currently being recorded (409). The UI shows a red '✕ Delete' button per
row (disabled while REC), confirms before proceeding, and removes both
the data row and the hidden player row from the DOM on success without
a full page reload. README updated accordingly.
- Show total audio storage used and disk free/total in the page header
- Add per-page threshold input (seeded from server --threshold) so the
loudness threshold can be adjusted without restarting the server;
each Analyse request sends the current UI value to the backend
- Fix empty Duration column: FLAC, OGG, and Opus files now report
duration via soundfile header metadata (no full decode required)
- New /api/storage and /api/config endpoints support the above features
Clicking Analyse on a file currently being recorded caused libsndfile to
fail with 'Internal psf_fseek() failed' because an in-progress FLAC file
has no seektable (written only on close()).
Client: Analyse button is disabled with a tooltip while isRec is true.
Server: _api_analyze checks status.json and returns 409 with a readable
message if the file is still being recorded, before attempting to open it.
_safe_path now uses path.is_file() instead of path.exists() so empty or
directory-traversal filenames never resolve to a directory and get served
as a 0-byte octet-stream download. This was the cause of the browser
downloading a file named 'recordings' with 0 bytes when Play was clicked.
Removed Content-Disposition: inline from _stream responses — it is not
needed for <audio> playback and it was what labelled the erroneous
directory response as 'recordings' in the save dialog.
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