From 052db8704bdcfcfffc994479d5a6a739a3725c8b Mon Sep 17 00:00:00 2001 From: Jesse Bannon Date: Mon, 25 Mar 2024 00:03:38 -0700 Subject: [PATCH] [FEATURE] Automatically handle playlists ordered in reverse --- .../entries/script/custom_functions.py | 49 +++++++++++++++++++ tests/e2e/entries/__init__.py | 0 tests/e2e/entries/script/__init__.py | 0 .../entries/script/test_custom_functions.py | 22 +++++++++ 4 files changed, 71 insertions(+) create mode 100644 tests/e2e/entries/__init__.py create mode 100644 tests/e2e/entries/script/__init__.py create mode 100644 tests/e2e/entries/script/test_custom_functions.py diff --git a/src/ytdl_sub/entries/script/custom_functions.py b/src/ytdl_sub/entries/script/custom_functions.py index 690095de..b34e51af 100644 --- a/src/ytdl_sub/entries/script/custom_functions.py +++ b/src/ytdl_sub/entries/script/custom_functions.py @@ -1,11 +1,14 @@ import os import posixpath +from typing import List from yt_dlp.utils import sanitize_filename +from ytdl_sub.downloaders.ytdlp import YTDLP from ytdl_sub.script.functions import Functions from ytdl_sub.script.types.map import Map from ytdl_sub.script.types.resolvable import AnyArgument +from ytdl_sub.script.types.resolvable import Boolean from ytdl_sub.script.types.resolvable import Integer from ytdl_sub.script.types.resolvable import ReturnableArgument from ytdl_sub.script.types.resolvable import String @@ -21,6 +24,51 @@ _days_in_month = [0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31] class CustomFunctions: + @staticmethod + def is_playlist_ordered_by_newest(url: String) -> Boolean: + """ + Queries a playlist-based URL using yt-dlp to see if it is ordered from newest + (lower playlist number) to oldest. + """ + info_only_kwargs = { + "skip_download": True, + "writethumbnail": False, + "writeinfojson": False, + "ignoreerrors": True, + "extract_flat": "discard", + "playlist_items": "0:5", + } + try: + url_info = YTDLP.extract_info( + ytdl_options_overrides=info_only_kwargs, + url=url.value, + ) + + if ( + not isinstance(url_info, dict) + or not isinstance(url_info.get("entries"), list) + or not len(url_info["entries"]) + ): + return Boolean(False) + + upload_dates: List[str] = [] + for entry in url_info["entries"]: + if entry.get("uploader_id") and entry.get("url"): + full_entry = YTDLP.extract_info( + ytdl_options_overrides=info_only_kwargs, url=entry.get("url") + ) + if isinstance(full_entry.get("upload_date"), str): + upload_dates.append(full_entry["upload_date"]) + + for idx in range(1, len(upload_dates)): + if upload_dates[idx - 1] < upload_dates[idx]: + return Boolean(False) + + return Boolean(True) + + except Exception: + return Boolean(False) + @staticmethod def legacy_bracket_safety(value: ReturnableArgument) -> ReturnableArgument: """ @@ -170,6 +218,7 @@ class CustomFunctions: Register Custom functions once and only once """ if not Functions.is_built_in("sanitize"): + Functions.register_function(CustomFunctions.is_playlist_ordered_by_newest) Functions.register_function(CustomFunctions.legacy_bracket_safety) Functions.register_function(CustomFunctions.truncate_filepath_if_too_long) Functions.register_function(CustomFunctions.to_native_filepath) diff --git a/tests/e2e/entries/__init__.py b/tests/e2e/entries/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/tests/e2e/entries/script/__init__.py b/tests/e2e/entries/script/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/tests/e2e/entries/script/test_custom_functions.py b/tests/e2e/entries/script/test_custom_functions.py new file mode 100644 index 00000000..61c9a6ca --- /dev/null +++ b/tests/e2e/entries/script/test_custom_functions.py @@ -0,0 +1,22 @@ +from unit.script.conftest import single_variable_output + + +class TestCustomFunctions: + def test_is_playlist_ordered_by_newest_true(self): + assert ( + single_variable_output( + "{%is_playlist_ordered_by_newest('https://www.youtube.com/playlist?list=PL5BC0FC26BECA5A35')}" + ) + is True + ) + + def test_is_playlist_ordered_by_newest_false(self): + assert ( + single_variable_output( + "{%is_playlist_ordered_by_newest('https://www.youtube.com/playlist?list=PL2KvlCGf4yFftX466OnFS8wvuSosBfUgm')}" + ) + is False + ) + + def test_is_playlist_ordered_by_newest_defaults_false(self): + assert single_variable_output("{%is_playlist_ordered_by_newest('aaaaaa')}") is False