From 677214db64b2f21b495e4f1b04e33a7bfc4902a9 Mon Sep 17 00:00:00 2001 From: Jesse Bannon Date: Thu, 29 Sep 2022 20:50:00 -0700 Subject: [PATCH] organize --- src/ytdl_sub/prebuilt_presets/__init__.py | 5 +- .../prebuilt_presets/tv_show/__init__.py | 102 ++++++++++++ .../prebuilt_presets/tv_show/episode.yaml | 54 ++++-- src/ytdl_sub/prebuilt_presets/tv_show/out.py | 154 ------------------ .../{media_players.yaml => players.yaml} | 0 ...wnload_strategy.yaml => tv_show_type.yaml} | 4 + .../prebuilt_presets/test_prebuilt_presets.py | 70 ++++++-- 7 files changed, 207 insertions(+), 182 deletions(-) delete mode 100644 src/ytdl_sub/prebuilt_presets/tv_show/out.py rename src/ytdl_sub/prebuilt_presets/tv_show/{media_players.yaml => players.yaml} (100%) rename src/ytdl_sub/prebuilt_presets/tv_show/{download_strategy.yaml => tv_show_type.yaml} (92%) diff --git a/src/ytdl_sub/prebuilt_presets/__init__.py b/src/ytdl_sub/prebuilt_presets/__init__.py index f91ee5e1..7ae377a4 100644 --- a/src/ytdl_sub/prebuilt_presets/__init__.py +++ b/src/ytdl_sub/prebuilt_presets/__init__.py @@ -1,12 +1,11 @@ import pathlib -from pathlib import Path from typing import Any from typing import Dict import mergedeep -from ytdl_sub.prebuilt_presets.tv_show.out import PrebuiltJellyfinTVShowPresets -from ytdl_sub.prebuilt_presets.tv_show.out import PrebuiltKodiTVShowPresets +from ytdl_sub.prebuilt_presets.tv_show import PrebuiltJellyfinTVShowPresets +from ytdl_sub.prebuilt_presets.tv_show import PrebuiltKodiTVShowPresets from ytdl_sub.utils.yaml import load_yaml diff --git a/src/ytdl_sub/prebuilt_presets/tv_show/__init__.py b/src/ytdl_sub/prebuilt_presets/tv_show/__init__.py index e69de29b..2ced4e82 100644 --- a/src/ytdl_sub/prebuilt_presets/tv_show/__init__.py +++ b/src/ytdl_sub/prebuilt_presets/tv_show/__init__.py @@ -0,0 +1,102 @@ +from typing import Any +from typing import Dict +from typing import List +from typing import Optional + +Preset = Dict[str, Any] + + +class PrebuiltPresets: + BASE_PRESET: str + + def _preset(self, name: str, presets: List[str]) -> Preset: + return {"presets": {name: {"preset": [self.BASE_PRESET] + presets}}} + + def _tv_show_url(self, name: str) -> Preset: + """ + Kodi TV Show with seasons as years, episodes ordered by upload date + """ + return self._preset( + name=name, presets=["tv-show-url", "season-by-year--episode-by-month-day"] + ) + + def _tv_show_youtube_channel(self, name: str) -> Preset: + return self._preset( + name=name, presets=["tv-show-youtube-channel", "season-by-year--episode-by-month-day"] + ) + + def _tv_show_collection(self, name: str) -> Preset: + return self._preset( + name=name, + presets=["tv-show-collection", "season-by-collection--episode-by-year-month-day"], + ) + + @classmethod + def get_preset_names(cls) -> List[str]: + preset_names = [prop for prop in dir(cls) if isinstance(getattr(cls, prop), property)] + return preset_names + + @classmethod + def get_collection_preset_names(cls) -> List[str]: + return [name for name in cls.get_preset_names() if "collection" in name] + + @classmethod + def get_non_collection_preset_names(cls) -> List[str]: + return [name for name in cls.get_preset_names() if "collection" not in name] + + @classmethod + def get_presets(cls, preset_names: Optional[List[str]] = None) -> List[Preset]: + if preset_names is None: + preset_names = cls.get_preset_names() + + return [getattr(cls(), preset_name) for preset_name in preset_names] + + +class PrebuiltKodiTVShowPresets(PrebuiltPresets): + BASE_PRESET = "kodi-tv-show" + + @property + def kodi_tv_show_url(self) -> Preset: + """ + Kodi TV Show with seasons as years, episodes ordered by upload date + """ + return self._tv_show_url(name="kodi_tv_show_url") + + @property + def kodi_tv_show_youtube_channel(self) -> Preset: + """ + Kodi TV Show from a YouTube channel with seasons as years, episodes ordered by upload date + """ + return self._tv_show_youtube_channel(name="kodi_tv_show_youtube_channel") + + @property + def kodi_tv_show_collection(self) -> Preset: + """ + Kodi TV Show from a collection of multiple URLs. TODO: finish docstring + """ + return self._tv_show_collection(name="kodi_tv_show_collection") + + +class PrebuiltJellyfinTVShowPresets(PrebuiltPresets): + BASE_PRESET = "jellyfin-tv-show" + + @property + def jellyfin_tv_show_url(self) -> Preset: + """ + Kodi TV Show with seasons as years, episodes ordered by upload date + """ + return self._tv_show_url(name="jellyfin_tv_show_url") + + @property + def jellyfin_tv_show_youtube_channel(self) -> Preset: + """ + Kodi TV Show from a YouTube channel with seasons as years, episodes ordered by upload date + """ + return self._tv_show_youtube_channel(name="jellyfin_tv_show_youtube_channel") + + @property + def jellyfin_tv_show_collection(self) -> Preset: + """ + Kodi TV Show from a collection of multiple URLs. TODO: finish docstring + """ + return self._tv_show_collection(name="jellyfin_tv_show_collection") diff --git a/src/ytdl_sub/prebuilt_presets/tv_show/episode.yaml b/src/ytdl_sub/prebuilt_presets/tv_show/episode.yaml index 6c508ae8..bacc8072 100644 --- a/src/ytdl_sub/prebuilt_presets/tv_show/episode.yaml +++ b/src/ytdl_sub/prebuilt_presets/tv_show/episode.yaml @@ -27,40 +27,66 @@ presets: episode_air_date_standardized: "{upload_date_standardized}" episode_file_name: "s{season_number_padded}.e{episode_number_padded} - {uid_sanitized}" episode_file_path: "Season {season_number_padded}/{episode_file_name}" + episode_number_suffix: "" #################################################################################################### season-by-year: overrides: season_number: "{upload_year}" - season_number_padded: "{upload_year}" + season_number_padded: "{season_number}" - episode-by-month-day: + season-by-year-month: + overrides: + season_number: "{upload_year}{upload_month_padded}" + season_number_padded: "{season_number}" + +#################################################################################################### + + season-by-year-month--episode-by-day: preset: + - "episode-base" + - "season-by-year-month" + overrides: + episode_number: "{upload_day}" + episode_number_padded: "{upload_day_padded}" + + season-by-year--episode-by-month-day: + preset: + - "episode-base" - "season-by-year" - - "episode-base" overrides: - episode_number: "{upload_month}{upload_day_padded}" - episode_number_padded: "{upload_month_padded}{upload_day_padded}" + episode_number: "{upload_month}{upload_day_padded}{episode_number_suffix}" + episode_number_padded: "{upload_month_padded}{upload_day_padded}{episode_number_suffix}" - episode-by-month-day-reversed: + season-by-year--episode-by-month-day-reversed: preset: + - "episode-base" - "season-by-year" - - "episode-base" overrides: - episode_number: "{upload_day_of_year_reversed}" - episode_number_padded: "{upload_day_of_year_reversed_padded}" + episode_number: "{upload_day_of_year_reversed}{episode_number_suffix}" + episode_number_padded: "{upload_day_of_year_reversed_padded}{episode_number_suffix}" - episode-by-year-month-day: + season-by-collection--episode-by-year-month-day: preset: - "episode-base" overrides: - episode_number: "{upload_year_truncated}{upload_month_padded}{upload_day_padded}" + episode_number: "{upload_year_truncated}{upload_month_padded}{upload_day_padded}{episode_number_suffix}" episode_number_padded: "{episode_number}" - episode-by-year-month-day-reversed: + season-by-collection--episode-by-year-month-day-reversed: preset: - "episode-base" overrides: - episode_number: "{upload_year_truncated_reversed}{upload_month_reversed_padded}{upload_day_reversed_padded}" - episode_number_padded: "{episode_number}" \ No newline at end of file + episode_number: "{upload_year_truncated_reversed}{upload_month_reversed_padded}{upload_day_reversed_padded}{episode_number_suffix}" + episode_number_padded: "{episode_number}" + +#################################################################################################### + + episode-add-hour-granularity: + overrides: + episode_number_suffix: "{epoch_hour}{playlist_index_reversed_padded6}" + + episode-add-hour-granularity-reversed: + overrides: + episode_number_suffix: "{epoch_hour}{playlist_index_padded6}" diff --git a/src/ytdl_sub/prebuilt_presets/tv_show/out.py b/src/ytdl_sub/prebuilt_presets/tv_show/out.py deleted file mode 100644 index a96333d4..00000000 --- a/src/ytdl_sub/prebuilt_presets/tv_show/out.py +++ /dev/null @@ -1,154 +0,0 @@ -from typing import Any -from typing import Dict -from typing import List -from typing import Optional - -Preset = Dict[str, Any] - - -class PrebuiltPresets: - BASE_PRESET: str - - def _preset(self, name: str, presets: List[str]) -> Preset: - return {"presets": {name: {"preset": [self.BASE_PRESET] + presets}}} - - def _tv_show_url(self, name: str) -> Preset: - """ - Kodi TV Show with seasons as years, episodes ordered by upload date - """ - return self._preset(name=name, presets=["tv-show-url", "episode-by-month-day"]) - - def _tv_show_url_reversed(self, name: str) -> Preset: - return self._preset(name=name, presets=["tv-show-url", "episode-by-month-day-reversed"]) - - def _tv_show_youtube_channel(self, name: str) -> Preset: - return self._preset(name=name, presets=["tv-show-youtube-channel", "episode-by-month-day"]) - - def _tv_show_youtube_channel_reversed(self, name: str) -> Preset: - return self._preset(name=name, presets=["tv-show-youtube-channel", "episode-by-month-day"]) - - def _tv_show_collection(self, name: str) -> Preset: - return self._preset(name=name, presets=["tv-show-collection", "episode-by-year-month-day"]) - - def _tv_show_collection_reversed(self, name: str) -> Preset: - return self._preset( - name=name, presets=["tv-show-collection", "episode-by-year-month-day-reversed"] - ) - - @classmethod - def get_preset_names(cls) -> List[str]: - preset_names = [prop for prop in dir(cls) if isinstance(getattr(cls, prop), property)] - return preset_names - - @classmethod - def get_collection_preset_names(cls) -> List[str]: - return [name for name in cls.get_preset_names() if "collection" in name] - - @classmethod - def get_non_collection_preset_names(cls) -> List[str]: - return [name for name in cls.get_preset_names() if "collection" not in name] - - @classmethod - def get_presets(cls, preset_names: Optional[List[str]] = None) -> List[Preset]: - if preset_names is None: - preset_names = cls.get_preset_names() - - return [getattr(cls(), preset_name) for preset_name in preset_names] - - -class PrebuiltKodiTVShowPresets(PrebuiltPresets): - BASE_PRESET = "kodi-tv-show" - - @property - def kodi_tv_show_url(self) -> Preset: - """ - Kodi TV Show with seasons as years, episodes ordered by upload date - """ - return self._tv_show_url(name="kodi_tv_show_url") - - @property - def kodi_tv_show_url_reversed(self) -> Preset: - """ - Kodi TV Show with seasons as years, episodes ordered by upload date in descending order - (more recent uploads have smaller episode number) - """ - return self._tv_show_url_reversed(name="kodi_tv_show_url_reversed") - - @property - def kodi_tv_show_youtube_channel(self) -> Preset: - """ - Kodi TV Show from a YouTube channel with seasons as years, episodes ordered by upload date - """ - return self._tv_show_youtube_channel(name="kodi_tv_show_youtube_channel") - - @property - def kodi_tv_show_youtube_channel_reversed(self) -> Preset: - """ - Kodi TV Show from a YouTube channel with seasons as years, episodes ordered by upload date - in descending order (more recent uploads have smaller episode number) - """ - return self._tv_show_youtube_channel_reversed(name="kodi_tv_show_youtube_channel_reversed") - - @property - def kodi_tv_show_collection(self) -> Preset: - """ - Kodi TV Show from a collection of multiple URLs. TODO: finish docstring - """ - return self._tv_show_collection(name="kodi_tv_show_collection") - - @property - def kodi_tv_show_collection_reversed(self) -> Preset: - """ - Kodi TV Show from a collection of multiple URLs. TODO: finish docstring - """ - return self._tv_show_collection_reversed(name="kodi_tv_show_collection_reversed") - - -class PrebuiltJellyfinTVShowPresets(PrebuiltPresets): - BASE_PRESET = "jellyfin-tv-show" - - @property - def jellyfin_tv_show_url(self) -> Preset: - """ - Kodi TV Show with seasons as years, episodes ordered by upload date - """ - return self._tv_show_url(name="jellyfin_tv_show_url") - - @property - def jellyfin_tv_show_url_reversed(self) -> Preset: - """ - Kodi TV Show with seasons as years, episodes ordered by upload date in descending order - (more recent uploads have smaller episode number) - """ - return self._tv_show_url_reversed(name="jellyfin_tv_show_url_reversed") - - @property - def jellyfin_tv_show_youtube_channel(self) -> Preset: - """ - Kodi TV Show from a YouTube channel with seasons as years, episodes ordered by upload date - """ - return self._tv_show_youtube_channel(name="jellyfin_tv_show_youtube_channel") - - @property - def jellyfin_tv_show_youtube_channel_reversed(self) -> Preset: - """ - Kodi TV Show from a YouTube channel with seasons as years, episodes ordered by upload date - in descending order (more recent uploads have smaller episode number) - """ - return self._tv_show_youtube_channel_reversed( - name="jellyfin_tv_show_youtube_channel_reversed" - ) - - @property - def jellyfin_tv_show_collection(self) -> Preset: - """ - Kodi TV Show from a collection of multiple URLs. TODO: finish docstring - """ - return self._tv_show_collection(name="jellyfin_tv_show_collection") - - @property - def jellyfin_tv_show_collection_reversed(self) -> Preset: - """ - Kodi TV Show from a collection of multiple URLs. TODO: finish docstring - """ - return self._tv_show_collection_reversed(name="jellyfin_tv_show_collection_reversed") diff --git a/src/ytdl_sub/prebuilt_presets/tv_show/media_players.yaml b/src/ytdl_sub/prebuilt_presets/tv_show/players.yaml similarity index 100% rename from src/ytdl_sub/prebuilt_presets/tv_show/media_players.yaml rename to src/ytdl_sub/prebuilt_presets/tv_show/players.yaml diff --git a/src/ytdl_sub/prebuilt_presets/tv_show/download_strategy.yaml b/src/ytdl_sub/prebuilt_presets/tv_show/tv_show_type.yaml similarity index 92% rename from src/ytdl_sub/prebuilt_presets/tv_show/download_strategy.yaml rename to src/ytdl_sub/prebuilt_presets/tv_show/tv_show_type.yaml index 976620c4..fd1d8185 100644 --- a/src/ytdl_sub/prebuilt_presets/tv_show/download_strategy.yaml +++ b/src/ytdl_sub/prebuilt_presets/tv_show/tv_show_type.yaml @@ -2,6 +2,7 @@ presets: #################################################################################################### + # TV show from any single source. Uses latest entry's thumbnail as tv show poster tv-show-url: generic: download_strategy: "source" @@ -12,6 +13,7 @@ presets: #################################################################################################### + # TV show from a single YouTube channel. Uses avatar and banner as poster and fanart. tv-show-youtube-channel: generic: download_strategy: "source" @@ -24,6 +26,8 @@ presets: #################################################################################################### + # TV show from a collection. Must specify additional `tv-show-collection-season` presets in + # addition. Each season sets its own `collection_season_number/_padded` tv-show-collection: generic: download_strategy: "collection" diff --git a/tests/unit/prebuilt_presets/test_prebuilt_presets.py b/tests/unit/prebuilt_presets/test_prebuilt_presets.py index 2f53592c..2ba03ccd 100644 --- a/tests/unit/prebuilt_presets/test_prebuilt_presets.py +++ b/tests/unit/prebuilt_presets/test_prebuilt_presets.py @@ -5,8 +5,8 @@ import pytest from ytdl_sub.config.config_file import ConfigFile from ytdl_sub.config.preset import Preset -from ytdl_sub.prebuilt_presets import PrebuiltKodiTVShowPresets -from ytdl_sub.prebuilt_presets.tv_show.out import PrebuiltJellyfinTVShowPresets +from ytdl_sub.prebuilt_presets.tv_show import PrebuiltJellyfinTVShowPresets +from ytdl_sub.prebuilt_presets.tv_show import PrebuiltKodiTVShowPresets @pytest.fixture @@ -18,18 +18,44 @@ def config() -> ConfigFile: class TestPrebuiltTVShowPresets: @pytest.mark.parametrize( - "preset_name", + "media_player_preset", [ *PrebuiltKodiTVShowPresets.get_non_collection_preset_names(), *PrebuiltJellyfinTVShowPresets.get_non_collection_preset_names(), ], ) - def test_non_collection_presets_compile(self, config, preset_name: str): + @pytest.mark.parametrize( + "tv_show_structure_preset", + [ + "season-by-year-month--episode-by-day", + "season-by-year--episode-by-month-day", + "season-by-year--episode-by-month-day-reversed", + ], + ) + @pytest.mark.parametrize( + "episode_hour_granularity_preset", + [ + None, + "episode-add-hour-granularity", + "episode-add-hour-granularity-reversed", + ], + ) + def test_non_collection_presets_compile( + self, + config: ConfigFile, + media_player_preset: str, + tv_show_structure_preset: str, + episode_hour_granularity_preset: str, + ): + parent_presets = [media_player_preset, tv_show_structure_preset] + if episode_hour_granularity_preset: + parent_presets.append(episode_hour_granularity_preset) + preset = Preset.from_dict( config=config, - preset_name=f"{preset_name}-test", + preset_name=f"{media_player_preset}-test", preset_dict={ - "preset": preset_name, + "preset": parent_presets, "overrides": { "url": "https://your.name.here", "tv_show_name": "test tv show", @@ -40,20 +66,42 @@ class TestPrebuiltTVShowPresets: assert preset @pytest.mark.parametrize( - "preset_name", + "media_player_preset", [ *PrebuiltKodiTVShowPresets.get_collection_preset_names(), *PrebuiltJellyfinTVShowPresets.get_collection_preset_names(), ], ) @pytest.mark.parametrize( - "season_indices", [[1], [1, 2], [1, 2, 3], [1, 2, 3, 4], [1, 2, 3, 4, 5], [4, 1]] + "tv_show_structure_preset", + [ + "season-by-collection--episode-by-year-month-day", + "season-by-collection--episode-by-year-month-day-reversed", + ], ) + @pytest.mark.parametrize( + "episode_hour_granularity_preset", + [ + None, + "episode-add-hour-granularity", + "episode-add-hour-granularity-reversed", + ], + ) + @pytest.mark.parametrize("season_indices", [[1], [1, 2, 3, 4, 5]]) @pytest.mark.parametrize("is_season_1_youtube_channel", [True, False]) def test_collection_presets_compile( - self, config, preset_name: str, season_indices: List[int], is_season_1_youtube_channel: bool + self, + config, + media_player_preset: str, + tv_show_structure_preset: str, + episode_hour_granularity_preset: str, + season_indices: List[int], + is_season_1_youtube_channel: bool, ): - parent_presets: List[str] = [preset_name] + parent_presets: List[str] = [media_player_preset, tv_show_structure_preset] + if episode_hour_granularity_preset: + parent_presets.append(episode_hour_granularity_preset) + overrides: Dict[str, str] = {} for season_index in season_indices: parent_presets.append(f"tv-show-collection-season-{season_index}") @@ -70,7 +118,7 @@ class TestPrebuiltTVShowPresets: preset = Preset.from_dict( config=config, - preset_name=f"{preset_name}-test", + preset_name=f"{media_player_preset}-test", preset_dict={ "preset": parent_presets, "overrides": dict(