fix: detector rejects non-16-bit WAV and stops padding the last section

_compute_rms_windows_wav honours sampwidth for frame arithmetic but
decodes as int16 unconditionally, so an 8- or 24-bit WAV produced
meaningless RMS values and therefore meaningless sections, silently.
The recorder only writes 16-bit, but --dir points at arbitrary
directories. analyze_wav now refuses anything else, turning a silent
wrong answer into a visible one.

_loud_sections closed mid-file sections at last_loud_t + window_dur but
the trailing one at the file duration. A file whose last loud event is
followed by less than min_gap of quiet reported a section stretched by
up to min_gap seconds, and the clip served for it carried that silence.
Now capped at the last loud window, consistent with every other section
and with the score, which only ever covered the loud windows.

DETECTOR_VERSION 2 -> 3 so existing caches are recomputed.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
2026-07-29 20:30:46 +02:00
co-authored by Claude Opus 5
parent 9bb1229aa9
commit b661e166ed
2 changed files with 56 additions and 2 deletions
+15 -2
View File
@@ -61,7 +61,8 @@ ONSET_SECONDS = 0.5 # a section's score is capped by its sharpest dB rise
# Bumped whenever section detection/scoring changes in a way that makes old
# cached results wrong (not just differently parameterised). Caches written
# with another version never match and are recomputed on the next analyse.
DETECTOR_VERSION = 2
DETECTOR_VERSION = 3 # v3: a section open at EOF ends at its last loud
# window, not at the file duration
CLIP_MAX_SECONDS = 600 # upper bound on /api/clip length
@@ -388,8 +389,13 @@ def _loud_sections(rms_values: list, window_dur: float, duration: float,
start_t = None
last_loud_t = None
# A section still open at EOF ends at its last loud window, exactly like
# every section closed inside the loop — not at the file duration, which
# used to stretch it by up to min_gap of trailing silence and put that
# silence into the clip served for it.
if start_t is not None and (last_loud_t + window_dur - start_t) >= min_duration - 1e-9:
sections.append({'start': round(start_t, 1), 'end': round(duration, 1),
sections.append({'start': round(start_t, 1),
'end': round(min(last_loud_t + window_dur, duration), 1),
'score': _score()})
return sections
@@ -421,6 +427,13 @@ def analyze_wav(path: Path, window_samples: int = WINDOW_SAMPLES,
sampwidth = wf.getsampwidth()
framerate = wf.getframerate()
n_frames = wf.getnframes()
# _compute_rms_windows_wav decodes as int16 unconditionally, so an
# 8- or 24-bit file would yield garbage RMS and therefore garbage
# sections — silently. The recorder only writes 16-bit, but --dir
# can point at any directory.
if sampwidth != 2:
return {'error': f'only 16-bit WAV files can be analysed '
f'(this one is {sampwidth * 8}-bit)'}
rms_values = list(_compute_rms_windows_wav(
wf, channels, sampwidth, framerate, window_samples))
except Exception as e: