mirror of
https://github.com/unshackle-dl/unshackle.git
synced 2026-11-12 20:15:10 +00:00
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:
@@ -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()
|
||||||
|
|||||||
@@ -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]:
|
||||||
|
|||||||
Reference in New Issue
Block a user