Skip to content

Commit 339d164

Browse files
committed
feat: improve subtitle matching logic
1 parent 0b69897 commit 339d164

2 files changed

Lines changed: 72 additions & 3 deletions

File tree

backend/app/subtitles.py

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -196,9 +196,6 @@ def _collect_matching_subtitles(subtitle_root: Path, target_stem: str) -> dict[s
196196
continue
197197

198198
candidate_stem = normalize_subtitle_stem(candidate.stem)
199-
if not candidate_stem.startswith(target_stem):
200-
continue
201-
202199
resolved_candidate = _resolve_within_root(candidate, subtitle_root)
203200
if resolved_candidate is None:
204201
continue
@@ -207,6 +204,9 @@ def _collect_matching_subtitles(subtitle_root: Path, target_stem: str) -> dict[s
207204
exact_matches_by_format[source_format].append(resolved_candidate)
208205
continue
209206

207+
if not _contains_subtitle_stem(candidate_stem, target_stem):
208+
continue
209+
210210
prefix_matches_by_format[source_format].append(resolved_candidate)
211211

212212
for source_format in SUPPORTED_SUBTITLE_SOURCE_FORMATS:
@@ -224,3 +224,16 @@ def _resolve_within_root(candidate: Path, subtitle_root: Path) -> Path | None:
224224
return resolved_candidate
225225

226226
return None
227+
228+
229+
def _contains_subtitle_stem(candidate_stem: str, target_stem: str) -> bool:
230+
match_start = candidate_stem.find(target_stem)
231+
while match_start != -1:
232+
match_end = match_start + len(target_stem)
233+
has_left_boundary = match_start == 0 or not candidate_stem[match_start - 1].isalnum()
234+
has_right_boundary = match_end == len(candidate_stem) or not candidate_stem[match_end].isalnum()
235+
if has_left_boundary and has_right_boundary:
236+
return True
237+
match_start = candidate_stem.find(target_stem, match_start + 1)
238+
239+
return False

backend/tests/test_subtitles.py

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -157,6 +157,37 @@ async def test_list_file_subtitles_supports_prefix_language_suffixes(client, tes
157157
)
158158

159159

160+
@pytest.mark.asyncio
161+
async def test_list_file_subtitles_supports_duplicate_leading_prefixes(client, test_run_dir):
162+
subtitle_root = test_run_dir / "subtitles-duplicate-prefix"
163+
_write_subtitle_file(
164+
subtitle_root, "260503 260503 Rajira! Sunday 川﨑桜、梅澤美波 [REQUEST].ass", "[Script Info]\nTitle: Duplicate Prefix\n"
165+
)
166+
167+
with (
168+
patch("backend.app.config.settings.subtitle_path", str(subtitle_root)),
169+
patch("backend.app.security.settings.allow_public_downloads", True),
170+
):
171+
token_data = await create_token(client, max_uploads=1)
172+
upload_token = token_data["token"]
173+
download_token = token_data["download_token"]
174+
175+
upload_data = await initiate_upload(client, upload_token, "260503 Rajira! Sunday 川﨑桜、梅澤美波.mp4", 5, "video/mp4")
176+
upload_id = upload_data["upload_id"]
177+
status_code = await upload_file_via_tus(client, upload_id, b"hello", upload_token)
178+
179+
assert status_code == status.HTTP_200_OK, "Upload should complete successfully"
180+
181+
response = await client.get(
182+
app.url_path_for("get_file_subtitle", download_token=download_token, upload_id=upload_id, source_format="ass")
183+
)
184+
185+
assert response.status_code == status.HTTP_200_OK, "Repeated leading prefixes should still allow subtitle matching"
186+
assert "Title: Duplicate Prefix" in response.text, (
187+
"Subtitle matching should tolerate duplicate leading date prefixes before the shared filename stem"
188+
)
189+
190+
160191
@pytest.mark.asyncio
161192
async def test_list_file_subtitles_prefers_exact_stem_over_prefix_matches(client, test_run_dir):
162193
subtitle_root = test_run_dir / "subtitles-exact-first"
@@ -185,6 +216,31 @@ async def test_list_file_subtitles_prefers_exact_stem_over_prefix_matches(client
185216
assert "Title: Exact" in response.text, "Exact stem matches should win over language-suffixed prefix matches"
186217

187218

219+
@pytest.mark.asyncio
220+
async def test_list_file_subtitles_ignores_midword_contains_matches(client, test_run_dir):
221+
subtitle_root = test_run_dir / "subtitles-midword"
222+
_write_subtitle_file(subtitle_root, "myepisode.ass", "[Script Info]\nTitle: Midword\n")
223+
224+
with (
225+
patch("backend.app.config.settings.subtitle_path", str(subtitle_root)),
226+
patch("backend.app.security.settings.allow_public_downloads", True),
227+
):
228+
token_data = await create_token(client, max_uploads=1)
229+
upload_token = token_data["token"]
230+
download_token = token_data["download_token"]
231+
232+
upload_data = await initiate_upload(client, upload_token, "episode.mkv", 5, "video/mp4")
233+
upload_id = upload_data["upload_id"]
234+
status_code = await upload_file_via_tus(client, upload_id, b"hello", upload_token)
235+
236+
assert status_code == status.HTTP_200_OK, "Upload should complete successfully"
237+
238+
response = await client.get(app.url_path_for("list_file_subtitles", download_token=download_token, upload_id=upload_id))
239+
240+
assert response.status_code == status.HTTP_200_OK, "Subtitle manifest should still be returned"
241+
assert response.json()["subtitles"] == [], "Contains matching should not match unrelated mid-word stems"
242+
243+
188244
@pytest.mark.asyncio
189245
async def test_list_file_subtitles_skips_ambiguous_extension_matches(client, test_run_dir):
190246
subtitle_root = test_run_dir / "subtitles-ambiguous"

0 commit comments

Comments
 (0)