feat: name cut clips by wall-clock time; fix recording filename format

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>
This commit is contained in:
2026-06-11 14:30:30 +02:00
parent 2caf23f17d
commit 5e7620627b
7 changed files with 97 additions and 55 deletions
+9 -4
View File
@@ -42,6 +42,13 @@ except ImportError:
SOUNDFILE_AVAILABLE = False
# Fixed recording-filename timestamp format. This is the recording's *start*
# time and is the single source of truth for the clock: web.py parses it back
# out to derive the displayed date and to name cut clips with real wall-clock
# times. It is intentionally not configurable — both files must agree on it.
FILENAME_FORMAT = '%Y%m%d_%H%M%S'
# =============================================================================
# Audio Device & Backend System
# =============================================================================
@@ -329,7 +336,6 @@ class BaseRecorder(ABC):
# Common settings
self.split_duration = config.get('split_minutes', 60)
self.output_dir = config.get('output_directory', 'recordings')
self.filename_pattern = config.get('filename_pattern', '%Y%m%d_%H%M%S')
self.max_retries = config.get('max_retries', 10)
self.retry_delay = config.get('retry_delay_seconds', 5)
self.file_format = config.get('format', 'auto')
@@ -343,9 +349,9 @@ class BaseRecorder(ABC):
return next_split.replace(second=0, microsecond=0)
def generate_filename(self, ext: str) -> str:
"""Generate filename from pattern with strftime substitution."""
"""Generate filename from the fixed start-time format (see FILENAME_FORMAT)."""
now = self._clock()
filename = now.strftime(self.filename_pattern) + f".{ext}"
filename = now.strftime(FILENAME_FORMAT) + f".{ext}"
full_path = os.path.join(self.output_dir, filename)
Path(full_path).parent.mkdir(parents=True, exist_ok=True)
return full_path
@@ -806,7 +812,6 @@ class RecorderManager:
general = {
'output_directory': config.get('general', 'output_directory', fallback='recordings'),
'split_minutes': config.getint('general', 'split_minutes', fallback=60),
'filename_pattern': config.get('general', 'filename_pattern', fallback='%Y%m%d_%H%M%S', raw=True),
'max_retries': config.getint('general', 'max_retries', fallback=10),
'retry_delay_seconds': config.getint('general', 'retry_delay_seconds', fallback=5),
'log_level': config.get('general', 'log_level', fallback='INFO').upper(),