From 310759959a6ef512f3b3a9cd2513b389c07aaa75 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Mon, 6 Jul 2026 07:34:19 +0900 Subject: [PATCH] fix(silence): keep negative silence_start intervals as split boundaries ffmpeg's silencedetect back-dates silence_start by the detection window, so a recording that begins in silence reports a slightly negative silence_start (e.g. -0.008). The parser regex only accepted [0-9]+, so the negative start failed to match, the following silence_end was orphaned, and the entire interval was silently dropped. When such a silence spans past the segment midpoint it is the best split boundary; dropping it forced an unwanted mid-audio hard split. Allow an optional leading sign in the silence_start/silence_end value patterns. Co-Authored-By: Claude Opus 4.8 Claude-Session: https://claude.ai/code/session_01CJRVbDrp1vGYkJgNHMGPpG --- media_shrinker.py | 4 ++-- tests/test_media_shrinker.py | 33 +++++++++++++++++++++++++++++++++ 2 files changed, 35 insertions(+), 2 deletions(-) diff --git a/media_shrinker.py b/media_shrinker.py index d72a8d5..3573eb7 100644 --- a/media_shrinker.py +++ b/media_shrinker.py @@ -84,8 +84,8 @@ BITRATE_SAFETY_MARGIN = 0.92 OPUS_MAX_BITRATE_BPS = 510_000 OPUS_MIN_REASONABLE_BITRATE_BPS = 16_000 -SILENCE_START_RE = re.compile(r"silence_start:\s*(?P[0-9]+(?:\.[0-9]+)?)") -SILENCE_END_RE = re.compile(r"silence_end:\s*(?P[0-9]+(?:\.[0-9]+)?)") +SILENCE_START_RE = re.compile(r"silence_start:\s*(?P-?[0-9]+(?:\.[0-9]+)?)") +SILENCE_END_RE = re.compile(r"silence_end:\s*(?P-?[0-9]+(?:\.[0-9]+)?)") class MediaShrinkerError(RuntimeError): diff --git a/tests/test_media_shrinker.py b/tests/test_media_shrinker.py index 0ab2397..8936e02 100644 --- a/tests/test_media_shrinker.py +++ b/tests/test_media_shrinker.py @@ -915,6 +915,39 @@ def test_parse_silencedetect_intervals_pairs_long_silence_start_and_end( ], ) + def test_parse_silencedetect_intervals_keeps_negative_start_at_recording_head( + self, + ) -> None: + # ffmpeg back-dates silence_start by the detection window, so a file that + # begins in silence reports a slightly negative silence_start. The whole + # interval must survive so it can still be used as a split boundary. + stderr = """ + [silencedetect @ 0x1] silence_start: -0.00816327 + [silencedetect @ 0x1] silence_end: 150.5 | silence_duration: 150.5 + """ + + intervals = parse_silencedetect_intervals(stderr) + + self.assertEqual( + intervals, + [SilenceInterval(start_seconds=-0.00816327, end_seconds=150.5)], + ) + + def test_negative_start_silence_still_drives_split_point(self) -> None: + # A silence that opens the recording but extends well past the midpoint is + # the best split boundary; dropping it forces an unwanted mid-audio cut. + intervals = parse_silencedetect_intervals( + "[silencedetect @ 0x1] silence_start: -0.008\n" + "[silencedetect @ 0x1] silence_end: 150.5 | silence_duration: 150.5\n" + ) + segments = build_segments( + duration_seconds=300.0, + max_segment_duration_seconds=200.0, + silence_intervals=intervals, + ) + + self.assertEqual(segments[0].duration_seconds, 150.5) + class SilenceDetectionTests(unittest.TestCase): @patch("media_shrinker.subprocess.run")