refactor: one Range-capable file sender; HEAD support; assorted hygiene
web.py * _download and _stream each had their own copy loop; _download had no Range support at all, so an interrupted download of a multi-hundred-MB recording had to restart. Both now go through _serve_bytes(), which keeps _stream's live-header `prefix` trick (the patched bytes are the same length as the header they replace, so Range math is unaffected). * do_HEAD answers / , /stream/ and /download/ as a body-less do_GET. /api/clip and /api/cut return 405 instead — generating a clip or shelling out to ffmpeg only to discard the result is worse than not answering. * _api_delete drops the deleted file's _DURATION_CACHE entry, which was never evicted. * WAV durations are rounded to 2dp like FLAC's already were. isr.py * _open_output_file flushed the buffer and then called close_current_file(), which is overridden to flush again. * log_level/log_file were merged into the dict copied into every source's config; no recorder reads them. Read them where they are used. webui.html * scoreOrder() is memoised on the section array's identity — U/I otherwise re-sorted a whole day (thousands of sections) per keypress. Verified with a byte-level harness: full/partial/open-ended/unsatisfiable ranges byte-identical to disk, download Range, HEAD on all three supported paths returning headers with an empty body, 405 on /api/clip, and live-header patching still correct both whole-file and for a range straddling the patched/disk boundary. Not done from the audit: BaseRecorder.generate_filename's mkdir per file open. It runs once per split (once an hour by default), so "once at startup suffices" was not worth a behaviour change. `nul` stays in .gitignore — it guards against a Windows shell artifact on a Windows box. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -35,7 +35,7 @@ Dependencies: `requests` (streams), `numpy` + `soundfile` (FLAC output and FLAC
|
||||
- Clips: `_api_clip()` validates params, `_clip_wav()` / `_clip_flac()` stream the decoded slice, `_wav_header()` builds the 44-byte PCM header.
|
||||
- Filenames as a clock: `_recording_start()` parses the start time out of a filename stem; `_cut_filename()` turns a (stem, ext, start, end) into a wall-clock-named cut. Both the listing `date` field and `_api_cut()` use them.
|
||||
- Live headers: `_live_wav_header()`, `_live_flac_header()` (+ `_flac_frame_samples()`, CRC-8 verified).
|
||||
- Serving: `_stream()` (Range support), `_copy_to_response()`, `_safe_path()` (path traversal guard).
|
||||
- Serving: `_serve_bytes()` is the single Range-capable file sender — `_stream()` (live header patch via its `prefix` argument, `no_store`) and `_download()` (`Content-Disposition`) both go through it, and it is what makes `do_HEAD` a body-less `do_GET`. Plus `_copy_to_response()`, `_safe_path()` (path traversal guard).
|
||||
|
||||
`webui.html` (one `<script>` block):
|
||||
- Clip review: `clipQueue`/`clipCursor`/`scoreCursor` globals, `playClip()`, `playFileSection()`, `hideClipBar()`; markup is the `#clip-bar` div. The clip label shows wall-clock occurrence time + dB prominence + position (`17:09:56 to 17:09:57 · +30 dB (30 / 426 by time)`): queue entries carry `absStart` (epoch s), derived from `fileStartEpoch(f.date)` — the filename clock — with in-file offsets as fallback for non-standard names; only the filename/in-file offset lives in the tooltip now. **Queue entries carry the filename, never a row index:** `_idx` is reassigned positionally on every `load()`, and `pollStatus()` reloads whenever a recording starts/stops/rolls over, so a stored index goes stale mid-review (a past bug had "Open in file"/cut fields target the row one off after a split rollover). Resolve at use time via `fileIdxByName()`. **`playClip()` builds one `text` string used for both `label.textContent` and the `announce()` aria-live message** — they must never diverge (a past bug had the announcement read `Clip N of M: …` while the label read the wall-clock form). **Two cursors, deliberately independent:** `clipCursor` is the time-order position and follows every play; `scoreCursor` is the loudness walk's own position in `scoreOrder()` and is moved *only* by U/I (and "highlights only" auto-advance) in `stepClip`'s by-score branch — J/K must never touch it, so a J/K detour to review nearby clips leaves the loudness walk's place intact and the next U/I resumes the ranking where it left off. `scoreCursor` resets to `-1` on every explicit jump / queue (re-)arm (`hideClipBar`, `playFileSection`, `jumpToDaySection`, both day-highlights arm points), so the next U/I re-anchors on `clipCursor` (the section just selected) else the loudest. `playClip(i, byScore)`: when `byScore` the count shows `scoreCursor` (rank, suffix "by loudness"), else `i` (time-order index, suffix "by time") — the suffix tells the blind user which dimension the count is in. `stepClip`'s by-score branch passes `byScore=true`; every other caller (chip click, J/K, Prev/Next) leaves it false. Both U/I walks — `stepClip`'s by-score branch and the in-player branch of the keydown handler — share `nextInOrder(order, pos, dir, jump)` for the stepping arithmetic (returns `null` at either end); they differ only in what anchors `pos` and what "play" means, and must not re-inline it.
|
||||
|
||||
Reference in New Issue
Block a user