From dc80133ba0898a57680f1c68fe18bac28a93d44c Mon Sep 17 00:00:00 2001 From: leoca Date: Wed, 14 Oct 2026 13:03:04 +0000 Subject: [PATCH 1/2] Escape the file name in the skip already transcribed glob A media file whose name contains glob characters, for example "Interview [Part 1].mp3", never matched its own result file because Path.glob read "[Part 1]" as a character class, so the file was transcribed again. --- buzz/plugins/skip_already_transcribed/plugin.py | 3 ++- tests/plugins/plugin_system_test.py | 16 ++++++++++++++++ 2 files changed, 18 insertions(+), 1 deletion(-) diff --git a/buzz/plugins/skip_already_transcribed/plugin.py b/buzz/plugins/skip_already_transcribed/plugin.py index ff9387553a..329ab5a269 100644 --- a/buzz/plugins/skip_already_transcribed/plugin.py +++ b/buzz/plugins/skip_already_transcribed/plugin.py @@ -1,3 +1,4 @@ +import glob import logging import os import re @@ -89,7 +90,7 @@ def _find_result_file_segments(self, file_path: str, task, context: PluginContex if not search_dir.is_dir(): continue for fmt in OutputFormat: - for candidate in search_dir.glob(f"{stem}*.{fmt.value}"): + for candidate in search_dir.glob(f"{glob.escape(stem)}*.{fmt.value}"): segments = self._parse_result_file(candidate, fmt, context) if segments is not None: return segments diff --git a/tests/plugins/plugin_system_test.py b/tests/plugins/plugin_system_test.py index 87db313060..20251304a3 100644 --- a/tests/plugins/plugin_system_test.py +++ b/tests/plugins/plugin_system_test.py @@ -788,6 +788,22 @@ def test_sat_skips_on_vtt_file(tmp_path): assert result[1].start == 3000 +def test_sat_skips_on_srt_file_with_brackets_in_name(tmp_path): + plugin = _sat_plugin() + audio = tmp_path / "Interview [Part 1].mp3" + audio.write_bytes(b"") + srt = tmp_path / "Interview [Part 1] (transcribed on 01-Jan-2024 12-00-00).srt" + srt.write_text( + "1\n00:00:01,000 --> 00:00:03,500\nHello there.\n\n", + encoding="utf-8", + ) + + ctx = _sat_context(config={"check_result_files": True, "check_database": False}) + result = plugin.check_skip(_SatTask(file_path=str(audio)), ctx) + assert result is not None + assert result[0].text == "Hello there." + + def test_sat_returns_none_when_result_files_disabled(tmp_path): plugin = _sat_plugin() audio = tmp_path / "audio.mp3" From 6e9a91973b12506906e7aa88c0b9ea94154718cf Mon Sep 17 00:00:00 2001 From: Raivis Dejus Date: Sat, 17 Oct 2026 10:30:45 +0300 Subject: [PATCH 2/2] Fix for regression --- .../skip_already_transcribed/plugin.py | 3 ++ tests/plugins/plugin_system_test.py | 46 +++++++++++++++++++ 2 files changed, 49 insertions(+) diff --git a/buzz/plugins/skip_already_transcribed/plugin.py b/buzz/plugins/skip_already_transcribed/plugin.py index 329ab5a269..9b331770fb 100644 --- a/buzz/plugins/skip_already_transcribed/plugin.py +++ b/buzz/plugins/skip_already_transcribed/plugin.py @@ -76,11 +76,14 @@ def check_skip( return None def _find_result_file_segments(self, file_path: str, task, context: PluginContext): + from buzz.paths import safe_filename_component from buzz.transcriber.transcriber import OutputFormat stem = Path(file_path).stem if stem.endswith("_speech"): stem = stem[:-7] + # Match the name get_output_file_path writes, e.g. "what?" -> "what_" + stem = safe_filename_component(stem) search_dirs = [Path(file_path).parent] if task.output_directory: diff --git a/tests/plugins/plugin_system_test.py b/tests/plugins/plugin_system_test.py index 20251304a3..f4dc30a8cb 100644 --- a/tests/plugins/plugin_system_test.py +++ b/tests/plugins/plugin_system_test.py @@ -804,6 +804,52 @@ def test_sat_skips_on_srt_file_with_brackets_in_name(tmp_path): assert result[0].text == "Hello there." +@pytest.mark.parametrize( + "stem", + [ + "plain", + "Interview [Part 1]", + "[2024] Talk", + "Interview (Part 1)", + "(2024) Talk", + "what?", + "a*b", + "Episode 3: Pilot", + 'say "hi"', + "ends.", + "ends ", + "con", + "talk_speech", + ], +) +def test_sat_finds_result_file_written_by_get_output_file_path(tmp_path, stem): + from buzz.model_loader import TranscriptionModel + from buzz.transcriber.transcriber import ( + OutputFormat, + Task, + get_output_file_path, + ) + + # The media file itself is not needed, so names invalid on Windows still work + audio = tmp_path / f"{stem}.mp3" + srt = get_output_file_path( + str(audio), + Task.TRANSCRIBE, + None, + TranscriptionModel(), + OutputFormat.SRT, + export_file_name_template="{{ input_file_name }} ({{ task }}d on {{ date_time }})", + ) + with open(srt, "w", encoding="utf-8") as f: + f.write("1\n00:00:01,000 --> 00:00:03,500\nHello there.\n\n") + + plugin = _sat_plugin() + ctx = _sat_context(config={"check_result_files": True, "check_database": False}) + result = plugin.check_skip(_SatTask(file_path=str(audio)), ctx) + assert result is not None + assert result[0].text == "Hello there." + + def test_sat_returns_none_when_result_files_disabled(tmp_path): plugin = _sat_plugin() audio = tmp_path / "audio.mp3"