From 568e297d365b92510ee3e69596c8d83a68fc7f60 Mon Sep 17 00:00:00 2001 From: BoulderBadgeDad Date: Thu, 25 Jun 2026 19:11:35 -0700 Subject: [PATCH] youtube quality: map profile -> yt-dlp format selection pure format_selection(profile) -> {format, format_sort, merge_output_format}. caps to the resolution ceiling (falls back to uncapped so above-cap-only videos still grab), ranks codec/res/fps/sdr as soft prefs (never excludes a stream). the one piece the youtube downloader needs regardless of how the engine is wired. exact yt-dlp tokens tunable on live yt-dlp; tests pin the shape. --- core/video/youtube_quality.py | 40 ++++++++++++++++++++++++++++- tests/test_video_youtube_quality.py | 36 ++++++++++++++++++++++++++ 2 files changed, 75 insertions(+), 1 deletion(-) diff --git a/core/video/youtube_quality.py b/core/video/youtube_quality.py index 0355335e..3451404b 100644 --- a/core/video/youtube_quality.py +++ b/core/video/youtube_quality.py @@ -54,6 +54,44 @@ def normalize(raw: Any) -> dict: return d +# Resolution → pixel-height ceiling for yt-dlp's ``height<=`` filter. "best" = no cap. +_RES_HEIGHT = {"best": None, "4320p": 4320, "2160p": 2160, "1440p": 1440, + "1080p": 1080, "720p": 720, "480p": 480, "360p": 360} + + +def format_selection(profile: Any) -> dict: + """Map a profile to the yt-dlp options the (download) engine passes through: + ``{format, format_sort, merge_output_format}``. Pure — no DB, no network. + + * ``format`` takes the best video+audio capped to the resolution ceiling, falling + back to an uncapped best so a video that only exists above the cap still grabs. + * ``format_sort`` is an ordered soft-preference list (codec → resolution → fps → + SDR) — yt-dlp picks the top match, so these never *exclude* a stream, they rank it. + * ``merge_output_format`` is the container yt-dlp muxes into. + + NB: the exact yt-dlp tokens are tunable against the live yt-dlp version when the + downloader is wired; the shape (one capped format expr + a ranked sort list) is the + contract the tests pin. + """ + p = normalize(profile) + height = _RES_HEIGHT.get(p["max_resolution"]) + if height: + fmt = "bv*[height<=%d]+ba/b[height<=%d]/bv*+ba/b" % (height, height) + else: + fmt = "bv*+ba/b" + + sort: list[str] = [] + if p["video_codec"] != "any": + sort.append("vcodec:%s" % p["video_codec"]) # soft codec preference + sort.append("res:%d" % height if height else "res") # prefer the ceiling, else highest + if p["prefer_60fps"]: + sort.append("fps") # prefer higher frame rate + if not p["allow_hdr"]: + sort.append("hdr:SDR") # rank SDR above HDR (don't exclude) + + return {"format": fmt, "format_sort": sort, "merge_output_format": p["container"]} + + def load(db) -> dict: """Read + normalize the stored profile, or the default if none/garbage.""" raw = db.get_setting("youtube_quality_profile") @@ -74,5 +112,5 @@ def save(db, raw: Any) -> dict: __all__ = [ "RESOLUTIONS", "CODECS", "CONTAINERS", - "default_profile", "normalize", "load", "save", + "default_profile", "normalize", "format_selection", "load", "save", ] diff --git a/tests/test_video_youtube_quality.py b/tests/test_video_youtube_quality.py index 8b90f437..6045e6f4 100644 --- a/tests/test_video_youtube_quality.py +++ b/tests/test_video_youtube_quality.py @@ -11,6 +11,7 @@ from core.video.youtube_quality import ( CONTAINERS, RESOLUTIONS, default_profile, + format_selection, load, normalize, save, @@ -79,3 +80,38 @@ def test_load_recovers_from_corrupt_json(): db = _FakeDB() db.set_setting("youtube_quality_profile", "{nope") assert load(db) == default_profile() + + +# ── yt-dlp format mapping (what the downloader passes through) ──────────────── +def test_format_selection_default_caps_to_1080_sdr_60fps_mp4(): + sel = format_selection(default_profile()) + assert sel["format"] == "bv*[height<=1080]+ba/b[height<=1080]/bv*+ba/b" + assert sel["merge_output_format"] == "mp4" + # default: any-codec (no vcodec pref), prefer the cap res, 60fps, SDR + assert sel["format_sort"] == ["res:1080", "fps", "hdr:SDR"] + + +def test_format_selection_best_has_no_height_cap(): + sel = format_selection({"max_resolution": "best", "video_codec": "any", + "prefer_60fps": False, "allow_hdr": True}) + assert sel["format"] == "bv*+ba/b" + assert sel["format_sort"] == ["res"] # no codec/fps/sdr prefs → just highest res + + +def test_format_selection_codec_pref_leads_the_sort(): + sel = format_selection({"max_resolution": "2160p", "video_codec": "av1", + "container": "mkv", "prefer_60fps": True, "allow_hdr": False}) + assert sel["format"].startswith("bv*[height<=2160]") + assert sel["format_sort"] == ["vcodec:av1", "res:2160", "fps", "hdr:SDR"] + assert sel["merge_output_format"] == "mkv" + + +def test_format_selection_allow_hdr_drops_the_sdr_rank(): + sel = format_selection({"max_resolution": "1080p", "video_codec": "any", + "prefer_60fps": True, "allow_hdr": True}) + assert "hdr:SDR" not in sel["format_sort"] + + +def test_format_selection_normalizes_garbage_input(): + # a junk profile still yields a valid default selection (never raises) + assert format_selection("nope")["format"] == "bv*[height<=1080]+ba/b[height<=1080]/bv*+ba/b"