mirror of
https://github.com/unshackle-dl/unshackle.git
synced 2026-11-12 20:15:10 +00:00
Each keeps one kind of track inside its track type. A run of more than one title skips a title that has no such track. The REST API rejects a non-boolean value for a boolean dl param, and the DASH parser reads audio description from Role elements.
91 lines
4.1 KiB
Python
91 lines
4.1 KiB
Python
"""The kind-only flags: --audio-description-only and --forced-subs-only.
|
|
|
|
The filters in dl.result are not extractable without executing the whole download loop, so
|
|
their wiring is pinned against the source, the same way test_required_langs_gate does it.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import inspect
|
|
|
|
import pytest
|
|
|
|
from unshackle.commands.dl import dl
|
|
from unshackle.core.api.handlers import validate_download_parameters
|
|
from unshackle.core.utilities import kind_only_conflict
|
|
|
|
KEYS = ["audio_description_only", "forced_subs_only"]
|
|
BASE = {"service": "EXAMPLE", "title": "x"}
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
"args,only_key,base_key,expected",
|
|
[
|
|
(["-ado"], "audio_description_only", "audio_description", (True, False)),
|
|
(["-ad"], "audio_description_only", "audio_description", (False, True)),
|
|
([], "audio_description_only", "audio_description", (False, False)),
|
|
(["-fso"], "forced_subs_only", "forced_subs", (True, False)),
|
|
(["-fs"], "forced_subs_only", "forced_subs", (False, True)),
|
|
],
|
|
)
|
|
def test_only_flag_does_not_parse_as_its_base_flag(args, only_key, base_key, expected):
|
|
params = dl.cli.make_context("dl", args, resilient_parsing=True).params
|
|
assert (params[only_key], params[base_key]) == expected
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
"params,expected",
|
|
[
|
|
({"audio_description_only": True, "audio_only": True}, None),
|
|
({"audio_description_only": True}, None),
|
|
({"audio_description_only": True, "no_audio": True}, ("audio_description_only", "no_audio")),
|
|
({"audio_description_only": True, "video_only": True}, ("audio_description_only", "video_only")),
|
|
({"forced_subs_only": True, "subs_only": True}, None),
|
|
({"forced_subs_only": True, "no_subs": True}, ("forced_subs_only", "no_subs")),
|
|
({"forced_subs_only": True, "audio_only": True}, ("forced_subs_only", "audio_only")),
|
|
({"no_audio": True, "video_only": True}, None),
|
|
# the *_only track-type flags combine: -A -S keeps audio, so -ado is not dropped
|
|
({"audio_description_only": True, "audio_only": True, "subs_only": True}, None),
|
|
({"forced_subs_only": True, "subs_only": True, "audio_only": True}, None),
|
|
({"audio_description_only": True, "subs_only": True}, ("audio_description_only", "subs_only")),
|
|
],
|
|
)
|
|
def test_kind_only_conflict(params, expected):
|
|
assert kind_only_conflict(params) == expected
|
|
|
|
|
|
def test_api_rejects_a_conflict():
|
|
assert validate_download_parameters({**BASE, "audio_description_only": True, "no_audio": True}) == (
|
|
"Cannot use both audio_description_only and no_audio"
|
|
)
|
|
assert validate_download_parameters({**BASE, "audio_description_only": True, "audio_only": True}) is None
|
|
|
|
|
|
@pytest.mark.parametrize("key", [*KEYS, "audio_description", "audio_only"])
|
|
def test_api_rejects_a_non_boolean_flag(key):
|
|
# the string "false" is truthy in dl.result, so it would turn the flag on
|
|
assert validate_download_parameters({**BASE, key: "false"}) == f"{key} must be a boolean"
|
|
assert validate_download_parameters({**BASE, key: None}) is None
|
|
|
|
|
|
class TestFilterWiring:
|
|
source = inspect.getsource(dl.result)
|
|
|
|
def test_ado_runs_when_the_title_has_no_separate_audio(self):
|
|
# embedded audio is standard audio: the filter must sit before the `len(...) > 0` guard
|
|
ado = self.source.index("if keep_audio and audio_description_only:")
|
|
guard = self.source.index("if keep_audio and len(title.tracks.audio) > 0:")
|
|
assert ado < guard
|
|
|
|
def test_ado_does_not_count_embedded_audio(self):
|
|
# both the --require-audio gate and the missing-language check
|
|
assert self.source.count("keep_videos and not audio_description_only") == 2
|
|
|
|
def test_only_filters_keep_one_kind(self):
|
|
assert "title.tracks.select_audio(lambda x: x.descriptive)" in self.source
|
|
assert "title.tracks.select_subtitles(lambda x: x.forced)" in self.source
|
|
|
|
def test_fso_implies_forced_subs(self):
|
|
# services read forced_subs from the ctx params to fetch forced tracks at all
|
|
assert "if forced_s_lang or fsl_excl or forced_subs_only:" in self.source
|