From 825678a1c476f23f4770efb1278cc8533d358608 Mon Sep 17 00:00:00 2001 From: imSp4rky Date: Fri, 2 Oct 2026 20:06:56 -0600 Subject: [PATCH] fix(dl): keep the --enrich original language after get_tracks A service that sets title.language in get_tracks replaced the metadata language, so 'orig' picked the wrong audio. Video tags and original flags move to the enrich language with the title. --- .../test_apply_original_language.py | 53 +++++++++++++++++++ unshackle/commands/dl.py | 5 +- unshackle/core/utilities.py | 18 +++++++ 3 files changed, 75 insertions(+), 1 deletion(-) create mode 100644 tests/lang_selection/test_apply_original_language.py diff --git a/tests/lang_selection/test_apply_original_language.py b/tests/lang_selection/test_apply_original_language.py new file mode 100644 index 00000000..f2e566c5 --- /dev/null +++ b/tests/lang_selection/test_apply_original_language.py @@ -0,0 +1,53 @@ +from types import SimpleNamespace + +from langcodes import Language + +from unshackle.core.utilities import apply_original_language + + +class FakeTracks(SimpleNamespace): + def __iter__(self): + return iter([*self.videos, *self.audio]) + + +def track(lang, original=False): + return SimpleNamespace(language=Language.get(lang) if lang else None, is_original_lang=original) + + +def title(language, videos=(), audio=()): + return SimpleNamespace( + language=Language.get(language) if language else None, + tracks=FakeTracks(videos=list(videos), audio=list(audio)), + ) + + +def test_overrides_the_service_guess_and_its_videos(): + t = title("es", videos=[track("es"), track("es")]) + apply_original_language(t, Language.get("it")) + assert str(t.language) == "it" + assert [str(v.language) for v in t.tracks.videos] == ["it", "it"] + + +def test_videos_in_another_language_keep_it(): + t = title("es", videos=[track("es"), track("ja"), track(None)]) + apply_original_language(t, Language.get("it")) + assert [v.language and str(v.language) for v in t.tracks.videos] == ["it", "ja", None] + + +def test_no_service_language_keeps_video_tags(): + t = title(None, videos=[track("en")]) + apply_original_language(t, Language.get("it")) + assert str(t.language) == "it" + assert str(t.tracks.videos[0].language) == "en" + + +def test_original_flag_moves_to_the_enrich_language(): + t = title("es", audio=[track("es-419", original=True), track("it-IT")]) + apply_original_language(t, Language.get("it")) + assert [a.is_original_lang for a in t.tracks.audio] == [False, True] + + +def test_same_language_leaves_flags_alone(): + t = title("it", videos=[track(None, original=True)]) + apply_original_language(t, Language.get("it")) + assert t.tracks.videos[0].is_original_lang is True diff --git a/unshackle/commands/dl.py b/unshackle/commands/dl.py index 6af4dc77..79bed827 100644 --- a/unshackle/commands/dl.py +++ b/unshackle/commands/dl.py @@ -74,6 +74,7 @@ from unshackle.core.tracks.dv_fixup import apply_dv_fixup from unshackle.core.tracks.hybrid import Hybrid from unshackle.core.tracks.track import assert_fragments_decrypted, has_encrypted_sample_entry from unshackle.core.utilities import ( + apply_original_language, as_requested, declared_kwargs, embedded_audio_langs, @@ -1759,13 +1760,13 @@ class dl: cache_region = service.current_region if hasattr(service, "current_region") else None cache_account_hash = get_account_hash(service.credential) if hasattr(service, "credential") else None + enrich_lang: Optional[Language] = None if self.enrich: sample_title = titles[0] if hasattr(titles, "__getitem__") else titles kind = "tv" if isinstance(sample_title, Episode) else "movie" enrich_title: Optional[str] = None enrich_year: Optional[int] = None - enrich_lang: Optional[Language] = None enrich_result = providers.resolve_by_ids( self.tmdb_id, @@ -2099,6 +2100,8 @@ class dl: try: title.tracks.add(service.get_tracks(title), warn_only=True) title.tracks.chapters = service.get_chapters(title) + if enrich_lang: + apply_original_language(title, enrich_lang) except Exception as e: if self.debug_logger: self.debug_logger.log_error( diff --git a/unshackle/core/utilities.py b/unshackle/core/utilities.py index 39455238..2674363e 100644 --- a/unshackle/core/utilities.py +++ b/unshackle/core/utilities.py @@ -248,6 +248,24 @@ def embedded_audio_langs(videos: Sequence[Any], keep_videos: bool) -> list[str]: return [video.data["audio_language"] for video in videos if video.data.get("audio_language")] +def apply_original_language(title: Any, language: Language) -> None: + """ + Make ``language`` the title's original language over the one the service set. + + A service tags its videos and original-language flags with its own guess, so the videos that + carry that guess move to ``language`` and every track's flag is set again. + """ + guess = title.language + if guess == language: + return + for video in title.tracks.videos: + if guess and video.language == guess: + video.language = language + for track in title.tracks: + track.is_original_lang = bool(track.language and is_close_match(track.language, [language])) + title.language = language + + def find_missing_langs( requested: Sequence[str], available: Sequence[Union[str, Language, None]],