fix: enforce Player download height limit
This commit is contained in:
@@ -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
@@ -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 = []
|
||||
|
||||
Reference in New Issue
Block a user