fix: two error paths that made a failure worse than the thing that failed
/api/clip: _clip_wav/_clip_flac send audio headers and then stream frames. A decode failure mid-stream unwound into _api_clip's handler, which called _json_err(500) — writing a second status line and a JSON body into a response that had already promised Content-Type: audio/wav and an exact Content-Length. Under HTTP/1.1 keep-alive that desyncs the connection for every subsequent request on it, so one bad clip corrupted unrelated traffic. Track whether the headers went out; if they did, log and force the connection closed instead, the same way _copy_to_response already handles under-delivery. config.ini: the int() coercion for split_minutes/max_retries/etc sat outside the try that wraps recorder construction, so `split_minutes = sixty` aborted the entire config load with an unhandled ValueError rather than skipping that one section like every other config error. Moved inside. split_duration is also clamped to >= 1 — 0 divided by zero in get_next_split_time(). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -272,8 +272,9 @@ class BaseRecorder(ABC):
|
||||
self.current_filename = None
|
||||
self._clock = clock or datetime.now
|
||||
|
||||
# Common settings
|
||||
self.split_duration = config.get('split_minutes', 60)
|
||||
# Common settings. split_duration is clamped: 0 would divide by zero
|
||||
# in get_next_split_time(), negative would split every tick.
|
||||
self.split_duration = max(1, config.get('split_minutes', 60))
|
||||
self.output_dir = config.get('output_directory', 'recordings')
|
||||
self.max_retries = config.get('max_retries', 10)
|
||||
self.retry_delay = config.get('retry_delay_seconds', 5)
|
||||
@@ -775,19 +776,20 @@ class RecorderManager:
|
||||
self.logger.warning(f"Section [{section}] has no 'type', skipping")
|
||||
continue
|
||||
|
||||
# Build source config by merging general with section-specific
|
||||
source_config = general.copy()
|
||||
|
||||
for key, value in config.items(section):
|
||||
if key == 'type':
|
||||
continue
|
||||
if key in ('split_minutes', 'max_retries', 'retry_delay_seconds', 'sample_rate', 'channels'):
|
||||
source_config[key] = int(value)
|
||||
else:
|
||||
source_config[key] = value
|
||||
|
||||
# Create recorder
|
||||
# Build the source config and create the recorder. Both are inside
|
||||
# the try: a non-numeric split_minutes used to raise out here and
|
||||
# abort the whole config load instead of skipping one section.
|
||||
try:
|
||||
source_config = general.copy()
|
||||
|
||||
for key, value in config.items(section):
|
||||
if key == 'type':
|
||||
continue
|
||||
if key in ('split_minutes', 'max_retries', 'retry_delay_seconds', 'sample_rate', 'channels'):
|
||||
source_config[key] = int(value)
|
||||
else:
|
||||
source_config[key] = value
|
||||
|
||||
if source_type == 'stream':
|
||||
recorder = StreamRecorder(section, source_config, self.logger)
|
||||
self.recorders.append(recorder)
|
||||
|
||||
Reference in New Issue
Block a user