fix: enforce Player download height limit

This commit is contained in:
Media
2026-08-12 18:18:38 -05:00
parent c2544c44c7
commit 8db46df69e
2 changed files with 128 additions and 6 deletions
+7 -4
View File
@@ -57,6 +57,12 @@ def canonical_playlist_path(media_base: Path, slot: int) -> Path:
return canonical_slot_directory(media_base, slot) / "playlist.m3u"
def player_format_selector(max_height: int) -> str:
"""Select the best Player media without exceeding its configured height."""
return f"bv*[height<={max_height}]+ba/b[height<={max_height}]"
def title_from_filename(path: Path) -> str:
stem = path.stem
if " - " in stem:
@@ -297,10 +303,7 @@ class DownloadManager:
/ "%(playlist_index)03d - %(title)s [%(id)s].%(ext)s"
),
"-f",
(
f"bv*[height<={self.config.downloads.max_height}]+ba/"
f"b[height<={self.config.downloads.max_height}]/b"
),
player_format_selector(self.config.downloads.max_height),
url,
]
self._run_command(
+121 -2
View File
@@ -8,8 +8,14 @@ import unittest
from concurrent.futures import Future
from pathlib import Path
from yt_dlp import YoutubeDL
from controller.config import ControllerConfig, DownloadConfig, StorageConfig
from controller.downloads import DownloadManager, canonical_playlist_path
from controller.downloads import (
DownloadManager,
canonical_playlist_path,
player_format_selector,
)
from controller.models import SlotProtectedError
from controller.store import ControllerStore
@@ -169,7 +175,120 @@ class DownloadManagerTests(unittest.TestCase):
self.assertTrue(all(not path.exists() for path in working_paths))
self.assertEqual(self.cookie.read_text(encoding="utf-8"), "cookie data")
download_arguments = runner.calls[-1][0]
self.assertIn("bv*[height<=720]+ba/b[height<=720]/b", download_arguments)
self.assertIn("bv*[height<=720]+ba/b[height<=720]", download_arguments)
@staticmethod
def _select_player_format(max_height, formats):
with YoutubeDL({"quiet": True}) as ydl:
selector = ydl.build_format_selector(
player_format_selector(max_height)
)
selected = list(ydl._select_formats(formats, selector))
return selected
def test_player_selector_chooses_separate_true_1080_video_and_audio(self):
formats = [
{
"format_id": "22",
"url": "https://example.test/720.mp4",
"ext": "mp4",
"height": 720,
"width": 1280,
"vcodec": "avc1.64001f",
"acodec": "mp4a.40.2",
"tbr": 2500,
"filesize": 1,
},
{
"format_id": "399",
"url": "https://example.test/1080.mp4",
"ext": "mp4",
"height": 1080,
"width": 1920,
"vcodec": "av01.0.08M.08",
"acodec": "none",
"tbr": 4500,
"filesize": 1,
},
{
"format_id": "251",
"url": "https://example.test/audio.webm",
"ext": "webm",
"vcodec": "none",
"acodec": "opus",
"tbr": 160,
"filesize": 1,
},
]
selected = self._select_player_format(1080, formats)
self.assertEqual(len(selected), 1)
self.assertEqual(selected[0]["height"], 1080)
self.assertEqual(
[item["format_id"] for item in selected[0]["requested_formats"]],
["399", "251"],
)
self.assertEqual(selected[0]["vcodec"], "av01.0.08M.08")
self.assertEqual(selected[0]["acodec"], "opus")
def test_player_selector_preserves_lower_source_resolution(self):
formats = [
{
"format_id": "18",
"url": "https://example.test/360.mp4",
"ext": "mp4",
"height": 360,
"width": 640,
"vcodec": "avc1.42001e",
"acodec": "mp4a.40.2",
"tbr": 700,
"filesize": 1,
},
{
"format_id": "22",
"url": "https://example.test/720.mp4",
"ext": "mp4",
"height": 720,
"width": 1280,
"vcodec": "avc1.64001f",
"acodec": "mp4a.40.2",
"tbr": 2500,
"filesize": 1,
},
]
selected = self._select_player_format(1080, formats)
self.assertEqual(len(selected), 1)
self.assertEqual(selected[0]["format_id"], "22")
self.assertEqual(selected[0]["height"], 720)
def test_player_selector_never_uses_unfiltered_over_height_fallback(self):
formats = [
{
"format_id": "401",
"url": "https://example.test/2160.mp4",
"ext": "mp4",
"height": 2160,
"width": 3840,
"vcodec": "av01.0.12M.08",
"acodec": "none",
"tbr": 12000,
"filesize": 1,
},
{
"format_id": "251",
"url": "https://example.test/audio.webm",
"ext": "webm",
"vcodec": "none",
"acodec": "opus",
"tbr": 160,
"filesize": 1,
},
]
self.assertEqual(self._select_player_format(1080, formats), [])
def test_worker_failure_is_contained_and_persisted(self):
events = []