Files
unshackle/tests/lang_selection/test_regional_selection_loop.py
imSp4rky 4b6d15cc2a feat(dl)!: treat a missing -sl language as a warning
A missing -sl language now warns and continues, with or without --best-available. --require-subs counts a forced track only when selection keeps it, and a title with no track left is skipped instead of failing at mux.

BREAKING CHANGE: a missing -sl subtitle language no longer fails the run. Use --require-subs to fail on it.
2026-11-09 15:01:54 -07:00

200 lines
8.2 KiB
Python

"""Run regional language requests through the real dl.result() selection loop."""
from __future__ import annotations
from typing import Any
import pytest
from langcodes import Language
from tests.lang_selection import test_kind_only_selection as harness
from unshackle.core.titles import Episode
from unshackle.core.tracks import Audio, Subtitle, Tracks, Video
class RegionalService(harness.FakeService):
"""One episode in `title_lang`, with one video, one audio track per entry of `audio` and the given subtitles."""
def __init__(self, title_lang: str, audio: list[str], subs: list[tuple[str, str]]) -> None:
super().__init__({1: {}})
self.title_lang, self.audio, self.subs = title_lang, audio, subs
def get_titles_cached(self) -> Any:
title = super().get_titles_cached()
for episode in title:
episode.language = Language.get(self.title_lang)
return title
def get_tracks(self, title: Episode) -> Tracks:
tracks = Tracks()
lang = self.audio[0].split(":")[0]
tracks.add(Video(id_="v", url="https://x/v", language=lang, codec=Video.Codec.AVC, range_=Video.Range.SDR))
for entry in self.audio:
# "es-ES:EC3" gives the track a codec other than AAC
lang, _, codec = entry.partition(":")
tracks.add(
Audio(id_=entry, url=f"https://x/{entry}", language=lang, codec=Audio.Codec[codec or "AAC"], bitrate=1)
)
for lang, kind in self.subs:
sdh, forced = kind == "sdh", kind == "forced"
sub_id = f"{lang}{'-' + kind if kind else ''}"
tracks.add(
Subtitle(
id_=sub_id,
url=f"https://x/{sub_id}",
language=lang,
codec=Subtitle.Codec.WebVTT,
sdh=sdh,
forced=forced,
)
)
return tracks
def run(
monkeypatch: pytest.MonkeyPatch,
caplog: pytest.LogCaptureFixture,
service: RegionalService,
**flags: Any,
) -> Any:
return harness.run(monkeypatch, caplog, {1: {}}, service=service, **flags)
def test_orig_takes_another_region_of_the_title_language(
monkeypatch: pytest.MonkeyPatch, caplog: pytest.LogCaptureFixture
) -> None:
ids = run(monkeypatch, caplog, RegionalService("en-US", ["en-GB", "fr-FR"], []))
assert ids["audio"] == ["en-GB"]
assert ids["video"] == ["v"]
def test_orig_keeps_the_script_the_title_region_implies(
monkeypatch: pytest.MonkeyPatch, caplog: pytest.LogCaptureFixture
) -> None:
ids = run(monkeypatch, caplog, RegionalService("zh-TW", ["zh-HK", "zh-CN"], []))
assert ids["audio"] == ["zh-HK"]
def test_a_regional_audio_request_misses_rather_than_take_a_sibling(
monkeypatch: pytest.MonkeyPatch, caplog: pytest.LogCaptureFixture
) -> None:
assert run(monkeypatch, caplog, RegionalService("fr-FR", ["fr-CA"], []), a_lang=["fr-FR"]) == 1
assert "fr-FR not found in audio tracks" in caplog.text
def test_a_regional_sdh_track_is_stripped_when_only_a_sibling_is_plain(
monkeypatch: pytest.MonkeyPatch, caplog: pytest.LogCaptureFixture
) -> None:
service = RegionalService("en-US", ["en-US"], [("en-US", "sdh"), ("en-GB", "")])
ids = run(monkeypatch, caplog, service, s_lang=["en-US"])
assert sorted(ids["subs"]) == ["en-US-sdh", "en-US-sdh_stripped"]
def test_a_forced_track_does_not_hide_the_plain_track_a_region_means(
monkeypatch: pytest.MonkeyPatch, caplog: pytest.LogCaptureFixture
) -> None:
service = RegionalService("ko", ["ko"], [("es", ""), ("es-ES", "forced"), ("fr", "")])
assert run(monkeypatch, caplog, service, s_lang=["es-ES"])["subs"] == ["es"]
def test_a_bare_sdh_track_is_stripped_when_only_another_variant_is_plain(
monkeypatch: pytest.MonkeyPatch, caplog: pytest.LogCaptureFixture
) -> None:
service = RegionalService("en", ["en"], [("en", "sdh"), ("en-GB", "")])
assert sorted(run(monkeypatch, caplog, service, s_lang=["en"])["subs"]) == ["en-sdh", "en-sdh_stripped"]
def test_a_bare_audio_request_takes_the_variant_it_means(
monkeypatch: pytest.MonkeyPatch, caplog: pytest.LogCaptureFixture
) -> None:
service = RegionalService("en", ["en", "pt-PT", "pt-BR"], [])
assert run(monkeypatch, caplog, service, a_lang=["pt"])["audio"] == ["pt-BR"]
def test_orig_takes_the_title_variant_over_the_one_a_bare_tag_means(
monkeypatch: pytest.MonkeyPatch, caplog: pytest.LogCaptureFixture
) -> None:
assert run(monkeypatch, caplog, RegionalService("fr-CA", ["fr-FR", "fr-CA", "en"], []))["audio"] == ["fr-CA"]
@pytest.mark.parametrize(
("subs", "flags", "expected"),
[
# -fs keeps a forced track that -sl names, even when no plain track has its language
([("en", "forced")], {"s_lang": ["en"], "forced_subs": True}, ["en-forced"]),
([("es", ""), ("es-ES", "forced")], {"s_lang": ["es-ES"], "forced_subs": True}, ["es", "es-ES-forced"]),
# an SDH track cannot take the plain variant's place, and joins it as the same variant
([("en-US", ""), ("en", "sdh")], {"s_lang": ["en"]}, ["en-US", "en-sdh"]),
# -fsl matches across every forced track, so the result does not depend on -sl
([("es-ES", "forced"), ("es-419", "forced")], {"forced_s_lang": ["es"]}, ["es-ES-forced"]),
(
[("fr", ""), ("es-ES", "forced"), ("es-419", "forced")],
{"s_lang": ["fr"], "forced_s_lang": ["es"]},
["es-ES-forced", "fr"],
),
# -sl orig resolves to the title language
([("en-GB", ""), ("en-US", "")], {"s_lang": ["orig"]}, ["en-GB"]),
],
)
def test_subtitle_selection(
monkeypatch: pytest.MonkeyPatch, caplog: pytest.LogCaptureFixture, subs: list, flags: dict, expected: list
) -> None:
ids = run(monkeypatch, caplog, RegionalService("en-GB", ["ja"], subs), lang=["ja"], **flags)
assert sorted(ids["subs"]) == expected
def test_a_forced_only_language_without_fs_is_reported_missing(
monkeypatch: pytest.MonkeyPatch, caplog: pytest.LogCaptureFixture
) -> None:
service = RegionalService("en", ["ja"], [("en", ""), ("es-419", ""), ("es-ES", "forced")])
ids = run(monkeypatch, caplog, service, lang=["ja"], s_lang=["en", "es-ES"])
assert "es-ES not found in subtitle tracks, continuing with: en" in caplog.text
assert ids["subs"] == ["en"]
def test_a_missing_subtitle_language_continues_without_subtitles(
monkeypatch: pytest.MonkeyPatch, caplog: pytest.LogCaptureFixture
) -> None:
service = RegionalService("en", ["ja"], [("en", "")])
ids = run(monkeypatch, caplog, service, lang=["ja"], s_lang=["fr"])
assert "fr not found in subtitle tracks, continuing without subtitles" in caplog.text
assert ids["subs"] == []
def test_a_missing_orig_subtitle_is_reported_as_orig(
monkeypatch: pytest.MonkeyPatch, caplog: pytest.LogCaptureFixture
) -> None:
service = RegionalService("ja", ["ja"], [("en", "")])
ids = run(monkeypatch, caplog, service, s_lang=["orig", "en"])
assert "orig not found in subtitle tracks, continuing with: en" in caplog.text
assert ids["subs"] == ["en"]
def test_require_subs_fails_on_a_forced_track_that_is_dropped(
monkeypatch: pytest.MonkeyPatch, caplog: pytest.LogCaptureFixture
) -> None:
service = RegionalService("ja", ["ja"], [("en", "forced")])
assert run(monkeypatch, caplog, service, require_subs=["en"]) == 1
def test_require_subs_counts_a_forced_track_that_fs_keeps(
monkeypatch: pytest.MonkeyPatch, caplog: pytest.LogCaptureFixture
) -> None:
service = RegionalService("ja", ["ja"], [("en", "forced")])
assert run(monkeypatch, caplog, service, require_subs=["en"], forced_subs=True)["subs"] == ["en-forced"]
def test_an_exclusion_removes_every_regional_form(
monkeypatch: pytest.MonkeyPatch, caplog: pytest.LogCaptureFixture
) -> None:
service = RegionalService("ja", ["ja", "es-419", "es-ES"], [])
assert run(monkeypatch, caplog, service, a_lang=["all", "-es"])["audio"] == ["ja"]
def test_the_meant_variant_does_not_depend_on_codec(
monkeypatch: pytest.MonkeyPatch, caplog: pytest.LogCaptureFixture
) -> None:
service = RegionalService("ja", ["ja", "es-ES:EC3", "es-419"], [])
ids = run(monkeypatch, caplog, service, a_lang=["es"], acodec=[Audio.Codec.AAC, Audio.Codec.EC3])
assert ids["audio"] == ["es-ES:EC3"]