diff --git a/src/ytdl_sub/entries/base_entry.py b/src/ytdl_sub/entries/base_entry.py index 9553bed7..78a13aaf 100644 --- a/src/ytdl_sub/entries/base_entry.py +++ b/src/ytdl_sub/entries/base_entry.py @@ -41,7 +41,7 @@ class BaseEntryVariables: str The entry's unique ID """ - return self.kwargs(UID) + return str(self.kwargs(UID)) @property def uid_sanitized(self: "BaseEntry") -> str: diff --git a/src/ytdl_sub/entries/entry.py b/src/ytdl_sub/entries/entry.py index 705bf299..d6bfea9c 100644 --- a/src/ytdl_sub/entries/entry.py +++ b/src/ytdl_sub/entries/entry.py @@ -56,7 +56,7 @@ class Entry(EntryVariables, BaseEntry): The source `thumbnail` value and the actual downloaded thumbnail extension sometimes do not match. Return the actual downloaded thumbnail path. """ - thumbnails = self.kwargs("thumbnails") or [] + thumbnails = self.kwargs_get("thumbnails", []) possible_thumbnail_exts = {"jpg", "webp"} # Always check for jpg and webp thumbs for thumbnail in thumbnails: diff --git a/src/ytdl_sub/entries/variables/entry_variables.py b/src/ytdl_sub/entries/variables/entry_variables.py index a37a7eb2..28f8fafd 100644 --- a/src/ytdl_sub/entries/variables/entry_variables.py +++ b/src/ytdl_sub/entries/variables/entry_variables.py @@ -26,6 +26,7 @@ from ytdl_sub.entries.variables.kwargs import SOURCE_UPLOADER from ytdl_sub.entries.variables.kwargs import SOURCE_UPLOADER_ID from ytdl_sub.entries.variables.kwargs import SOURCE_UPLOADER_URL from ytdl_sub.entries.variables.kwargs import SOURCE_WEBPAGE_URL +from ytdl_sub.entries.variables.kwargs import UPLOAD_DATE # This file contains mixins to a BaseEntry subclass. Ignore pylint's "no kwargs member" suggestion # pylint: disable=no-member @@ -384,7 +385,7 @@ class EntryVariables(BaseEntryVariables): str The entry's uploaded date, in YYYYMMDD format. """ - return self.kwargs("upload_date") + return self.kwargs(UPLOAD_DATE) @property def upload_year(self: Self) -> int: diff --git a/src/ytdl_sub/entries/variables/kwargs.py b/src/ytdl_sub/entries/variables/kwargs.py index 80304ca2..f1726a8a 100644 --- a/src/ytdl_sub/entries/variables/kwargs.py +++ b/src/ytdl_sub/entries/variables/kwargs.py @@ -48,6 +48,7 @@ EXT = _("ext") TITLE = _("title") DESCRIPTION = _("description") WEBPAGE_URL = _("webpage_url") +UPLOAD_DATE = _("upload_date") UPLOADER = _("uploader") UPLOADER_ID = _("uploader_id") UPLOADER_URL = _("uploader_url") diff --git a/tests/unit/prebuilt_presets/conftest.py b/tests/unit/prebuilt_presets/conftest.py new file mode 100644 index 00000000..f39323e0 --- /dev/null +++ b/tests/unit/prebuilt_presets/conftest.py @@ -0,0 +1,122 @@ +import os +import tempfile +from pathlib import Path +from typing import Callable +from typing import Dict +from unittest.mock import patch + +import pytest + +from ytdl_sub.downloaders.downloader import Downloader +from ytdl_sub.entries.variables.kwargs import EPOCH +from ytdl_sub.entries.variables.kwargs import EXT +from ytdl_sub.entries.variables.kwargs import EXTRACTOR +from ytdl_sub.entries.variables.kwargs import PLAYLIST_ENTRY +from ytdl_sub.entries.variables.kwargs import PLAYLIST_INDEX +from ytdl_sub.entries.variables.kwargs import TITLE +from ytdl_sub.entries.variables.kwargs import UID +from ytdl_sub.entries.variables.kwargs import UPLOAD_DATE +from ytdl_sub.entries.variables.kwargs import WEBPAGE_URL + + +@pytest.fixture +def working_directory() -> str: + with tempfile.TemporaryDirectory() as temp_dir: + yield temp_dir + + +@pytest.fixture +def subscription_name(working_directory) -> str: + name = "subscription_test" + os.makedirs(Path(working_directory) / name, exist_ok=True) + return name + + +@pytest.fixture +def output_directory() -> str: + with tempfile.TemporaryDirectory() as temp_dir: + yield temp_dir + + +@pytest.fixture +def mock_file_factory(working_directory: str, subscription_name: str): + def _mock_file_factory(file_name: str): + with open(Path(working_directory) / subscription_name / file_name, "w"): + pass + + return _mock_file_factory + + +@pytest.fixture +def mock_entry_dict_factory(mock_file_factory) -> Callable: + def _mock_entry_dict_factory(uid: int, upload_date: str, playlist_index: int = 1) -> Dict: + entry_dict = { + UID: uid, + EPOCH: 1596878400, + PLAYLIST_INDEX: playlist_index, + EXTRACTOR: "mock-entry-dict", + TITLE: f"Mock Entry {uid}", + EXT: "mp4", + UPLOAD_DATE: upload_date, + WEBPAGE_URL: f"https://{uid}.com", + PLAYLIST_ENTRY: { + "thumbnails": [ + { + "id": "avatar_uncropped", + "url": "https://avatar_uncropped.com", + }, + { + "id": "banner_uncropped", + "url": "https://banner_uncropped.com", + }, + ] + }, + } + + # Create mock video file + mock_file_factory(file_name=f"{uid}.mp4") + mock_file_factory(file_name=f"{uid}.jpg") + return entry_dict + + return _mock_entry_dict_factory + + +@pytest.fixture +def mock_download_collection_thumbnail(mock_file_factory): + def _mock_download_thumbnail(output_path: str): + mock_file_factory(file_name=output_path.split("/")[-1]) + pass + + with patch.object( + Downloader, + "_download_thumbnail", + new=lambda _, thumbnail_url, output_thumbnail_path: _mock_download_thumbnail( + output_thumbnail_path + ), + ): + yield # TODO: create file here + + +@pytest.fixture +def mock_download_collection_entries( + mock_download_collection_thumbnail, mock_entry_dict_factory: Callable, working_directory: str +): + collection_1_entry_dicts = [ + mock_entry_dict_factory(uid=1, upload_date="20200808", playlist_index=1), + mock_entry_dict_factory(uid=2, upload_date="20200808", playlist_index=2), + mock_entry_dict_factory(uid=3, upload_date="20210808", playlist_index=3), + ] + collection_2_entry_dicts = [ + mock_entry_dict_factory(uid=4, upload_date="20200808", playlist_index=1), + mock_entry_dict_factory(uid=5, upload_date="20200808", playlist_index=2), + mock_entry_dict_factory(uid=6, upload_date="20210808", playlist_index=3), + ] + + with patch.object( + Downloader, "extract_info_via_info_json" + ) as mock_download_metadata, patch.object( + Downloader, "_download_entry", new=lambda _, entry: entry + ): + # Stub out metadata. TODO: update this if we do metadata plugins + mock_download_metadata.side_effect = [collection_1_entry_dicts, collection_2_entry_dicts] + yield diff --git a/tests/unit/prebuilt_presets/test_prebuilt_presets.py b/tests/unit/prebuilt_presets/test_prebuilt_presets.py index 01ebffa0..2ba84579 100644 --- a/tests/unit/prebuilt_presets/test_prebuilt_presets.py +++ b/tests/unit/prebuilt_presets/test_prebuilt_presets.py @@ -7,12 +7,14 @@ from ytdl_sub.config.config_file import ConfigFile from ytdl_sub.config.preset import Preset from ytdl_sub.prebuilt_presets.tv_show import PrebuiltJellyfinTVShowPresets from ytdl_sub.prebuilt_presets.tv_show import PrebuiltKodiTVShowPresets +from ytdl_sub.subscriptions.subscription import Subscription @pytest.fixture -def config() -> ConfigFile: +def config(working_directory) -> ConfigFile: return ConfigFile( - name="config", value={"configuration": {"working_directory": "test"}, "presets": {}} + name="config", + value={"configuration": {"working_directory": working_directory}, "presets": {}}, ) @@ -87,11 +89,14 @@ class TestPrebuiltTVShowPresets: "episode_add_hour_granularity_reversed", ], ) - @pytest.mark.parametrize("season_indices", [[1], [1, 2, 3, 4, 5]]) + @pytest.mark.parametrize("season_indices", [[1], [1, 2]]) @pytest.mark.parametrize("is_season_1_youtube_channel", [True, False]) def test_collection_presets_compile( self, config, + subscription_name, + output_directory, + mock_download_collection_entries, media_player_preset: str, tv_show_structure_preset: str, episode_hour_granularity_preset: str, @@ -116,18 +121,20 @@ class TestPrebuiltTVShowPresets: }, ) - preset = Preset.from_dict( + subscription = Subscription.from_dict( config=config, - preset_name=f"{media_player_preset}_test", + preset_name=subscription_name, preset_dict={ "preset": parent_presets, "overrides": dict( overrides, **{ - "tv_show_name": "test tv show", - "tv_show_directory": "output_path", + "tv_show_name": "Test TV Show", + "tv_show_directory": output_directory, }, ), }, ) - assert preset + + output_transactoin = subscription.download(dry_run=False) + assert output_transactoin