refactor(tracks): honour the best token and share one match rule

sort_subtitles resolved only the all token, while the video and audio sorts
resolved all and best. by_language now asks matching_languages rather than
repeating the RFC 4647 preference, with no behaviour change.
This commit is contained in:
imSp4rky
2026-08-03 10:29:09 -06:00
parent a6997f6bca
commit a5f5e7d14d
2 changed files with 14 additions and 17 deletions
+9 -2
View File
@@ -180,11 +180,18 @@ def test_no_priority_leaves_order_unchanged(many: Tracks) -> None:
assert langs(many) == ["ar", "bg", "en", "es", "fr", "ja"] assert langs(many) == ["ar", "bg", "en", "es", "fr", "ja"]
def test_all_alone_still_floats_the_original_language(many: Tracks) -> None: @pytest.mark.parametrize("token", ["all", "best"])
many.sort_subtitles(by_language=["all"], group_by="language") def test_magic_token_alone_floats_the_original_language(many: Tracks, token: str) -> None:
many.sort_subtitles(by_language=[token], group_by="language")
assert langs(many) == ["ja", "ar", "bg", "en", "es", "fr"] assert langs(many) == ["ja", "ar", "bg", "en", "es", "fr"]
@pytest.mark.parametrize("token", ["all", "best"])
def test_priority_outranks_either_magic_token(many: Tracks, token: str) -> None:
many.sort_subtitles(by_language=["en", "es", token], group_by="language")
assert langs(many) == ["en", "es", "ja", "ar", "bg", "fr"]
@pytest.fixture @pytest.fixture
def regionals() -> Tracks: def regionals() -> Tracks:
t = Tracks() t = Tracks()
+5 -15
View File
@@ -7,7 +7,7 @@ from functools import partial
from pathlib import Path from pathlib import Path
from typing import Any, Callable, Iterator, Optional, Sequence, Union from typing import Any, Callable, Iterator, Optional, Sequence, Union
from langcodes import Language, closest_supported_match from langcodes import Language
from rich.progress import Progress, SpinnerColumn, TaskID, TextColumn, TimeRemainingColumn from rich.progress import Progress, SpinnerColumn, TaskID, TextColumn, TimeRemainingColumn
from rich.table import Table from rich.table import Table
from rich.tree import Tree from rich.tree import Tree
@@ -15,7 +15,7 @@ from rich.tree import Tree
from unshackle.core import binaries from unshackle.core import binaries
from unshackle.core.config import config from unshackle.core.config import config
from unshackle.core.console import GradientPulseBarColumn, console from unshackle.core.console import GradientPulseBarColumn, console
from unshackle.core.constants import LANGUAGE_EXACT_DISTANCE, LANGUAGE_MAX_DISTANCE, AnyTrack, TrackT from unshackle.core.constants import AnyTrack, TrackT
from unshackle.core.events import events from unshackle.core.events import events
from unshackle.core.tracks.attachment import Attachment from unshackle.core.tracks.attachment import Attachment
from unshackle.core.tracks.audio import Audio from unshackle.core.tracks.audio import Audio
@@ -351,7 +351,7 @@ class Tracks:
by_type() by_type()
# sections # sections
for language in reversed(by_language or []): for language in reversed(by_language or []):
if str(language) == "all": if str(language) in ("all", "best"):
language = next((x.language for x in self.subtitles if x.is_original_lang), "") language = next((x.language for x in self.subtitles if x.is_original_lang), "")
if not language: if not language:
continue continue
@@ -469,21 +469,11 @@ class Tracks:
def by_language( def by_language(
tracks: list[TrackT], languages: list[str], per_language: int = 0, exact_match: bool = False tracks: list[TrackT], languages: list[str], per_language: int = 0, exact_match: bool = False
) -> list[TrackT]: ) -> list[TrackT]:
distance = LANGUAGE_EXACT_DISTANCE if exact_match else LANGUAGE_MAX_DISTANCE
selected: list[TrackT] = [] selected: list[TrackT] = []
seen_ids: set[str] = set() seen_ids: set[str] = set()
for language in languages: for language in languages:
matches = [x for x in tracks if closest_supported_match(str(x.language), [language], distance)] wanted = matching_languages(language, [x.language for x in tracks], exact_match)
if exact_match and len(matches) > 1: matches = [x for x in tracks if str(x.language) in wanted]
# CLDR tag_distance measures intelligibility, not tag identity: it rates a base
# language and its "paradigm" regional variant as the same language (distance 0
# for ar/ar-EG, en/en-US, pt/pt-BR), so exact mode alone cannot separate them.
# Follow RFC 4647 Lookup: prefer the most specific (string-equal) tag, and fall
# back to the fuzzy match only when none exists (e.g. zh matches cmn).
want = Language.get(language).to_tag().casefold()
exact_hits = [x for x in matches if Language.get(str(x.language)).to_tag().casefold() == want]
if exact_hits:
matches = exact_hits
# Overlapping tags can still resolve to the same physical track; dedupe by id so # Overlapping tags can still resolve to the same physical track; dedupe by id so
# callers never get duplicate tracks. # callers never get duplicate tracks.
for track in matches[: per_language or None]: for track in matches[: per_language or None]: