tests WIP

This commit is contained in:
Jesse Bannon 2022-09-28 23:21:44 -07:00
parent bc839936a6
commit 220076d7d1
14 changed files with 320 additions and 278 deletions

View file

@ -3,6 +3,9 @@ from typing import Any
from typing import Dict from typing import Dict
from typing import Optional from typing import Optional
import mergedeep
from ytdl_sub.prebuilt_presets import PREBUILT_PRESETS
from ytdl_sub.utils.yaml import load_yaml from ytdl_sub.utils.yaml import load_yaml
from ytdl_sub.validators.strict_dict_validator import StrictDictValidator from ytdl_sub.validators.strict_dict_validator import StrictDictValidator
from ytdl_sub.validators.validators import LiteralDictValidator from ytdl_sub.validators.validators import LiteralDictValidator
@ -77,9 +80,21 @@ class ConfigFile(StrictDictValidator):
super().__init__(name, value) super().__init__(name, value)
self.config_options = self._validate_key("configuration", ConfigOptions) self.config_options = self._validate_key("configuration", ConfigOptions)
prebuilt_presets = PREBUILT_PRESETS
# Make sure presets is a dictionary. Will be validated in `PresetValidator` # Make sure presets is a dictionary. Will be validated in `PresetValidator`
self.presets = self._validate_key("presets", LiteralDictValidator) self.presets = self._validate_key("presets", LiteralDictValidator)
# Ensure custom presets do not collide with prebuilt presets
for preset_name in self.presets.keys:
if preset_name in prebuilt_presets:
raise self._validation_exception(
f"preset name '{preset_name}' conflicts with a prebuilt preset"
)
# Merge prebuilt presets into the config so custom presets can use them
mergedeep.merge(self.presets._value, prebuilt_presets)
def initialize(self): def initialize(self):
""" """
Configures things (umask, pgid) prior to any downloading Configures things (umask, pgid) prior to any downloading

View file

@ -0,0 +1,25 @@
from pathlib import Path
from typing import Any, Dict
import mergedeep
from ytdl_sub.prebuilt_presets.tv_show.out import PrebuiltKodiTVShowPresets
from ytdl_sub.utils.yaml import load_yaml
import pathlib
def _merge_presets() -> Dict[str, Any]:
merged_configs: Dict[str, Any] = {}
# Get all presets from the loose YAML files
for file in pathlib.Path(__file__).parent.resolve().rglob("*"):
if file.is_file() and file.name.endswith('yaml'):
mergedeep.merge(merged_configs, load_yaml(file))
# Get all presets from published preset configs
mergedeep.merge(merged_configs, *PrebuiltKodiTVShowPresets.get_presets())
return merged_configs['presets']
PREBUILT_PRESETS: Dict[str, Any] = _merge_presets()

View file

@ -1,5 +1,7 @@
presets: presets:
kodi-safe: kodi-safe-nfo:
nfo_tags: nfo_tags:
kodi_safe: True kodi_safe: True
output_directory_nfo_tags:
kodi_safe: True

View file

@ -1,14 +0,0 @@
presets:
plex:
overrides:
season_poster_prefix: ""
jellyfin:
overrides:
poster_file_name: "poster"
season_poster_prefix: "-poster"
kodi:
presets:
- "jellyfin"

View file

@ -1,6 +1,6 @@
presets: presets:
kodi-music-videos: kodi-music-videos:
presets: preset:
- "jellyfin-music-videos" - "jellyfin-music-videos"
- "kodi-safe" - "kodi-safe"

View file

@ -1,5 +1,7 @@
presets: presets:
####################################################################################################
tv-show-url: tv-show-url:
generic: generic:
download_strategy: "source" download_strategy: "source"
@ -22,17 +24,100 @@ presets:
#################################################################################################### ####################################################################################################
tv-show-collection-youtube-channel-season: tv-show-collection:
generic: generic:
download_strategy: "collection" download_strategy: "collection"
urls:
- url: "{youtube_channel_url}"
playlist_thumbnails:
- name: "{season_number_padded}{season_poster_prefix}.jpg"
uid: "latest_entry"
- name: "{poster_file_name}"
uid: "avatar_uncropped"
- name: "{fanart_file_name}"
uid: "banner_uncropped"
overrides: overrides:
season_number: "{collection_season_number}"
season_number_padded: "{collection_season_number_padded}"
tv-show-collection-season-1:
generic:
urls:
- url: "{collection_season_1_url}"
variables:
collection_season_number: "1"
collection_season_number_padded: "01"
playlist_thumbnails:
- name: "{season_poster_name}"
uid: "latest_entry"
output_directory_nfo_tags:
tags:
namedseason:
- tag: "{collection_season_1_name}"
attributes:
number: "1"
tv-show-collection-season-2:
generic:
urls:
- url: "{collection_season_2_url}"
variables:
collection_season_number: "2"
collection_season_number_padded: "02"
playlist_thumbnails:
- name: "{season_poster_name}"
uid: "latest_entry"
output_directory_nfo_tags:
tags:
namedseason:
- tag: "{collection_season_2_name}"
attributes:
number: "2"
tv-show-collection-season-3:
generic:
urls:
- url: "{collection_season_3_url}"
variables:
collection_season_number: "3"
collection_season_number_padded: "03"
playlist_thumbnails:
- name: "{season_poster_name}"
uid: "latest_entry"
output_directory_nfo_tags:
tags:
namedseason:
- tag: "{collection_season_3_name}"
attributes:
number: "3"
tv-show-collection-season-4:
generic:
urls:
- url: "{collection_season_4_url}"
variables:
collection_season_number: "4"
collection_season_number_padded: "04"
playlist_thumbnails:
- name: "{season_poster_name}"
uid: "latest_entry"
output_directory_nfo_tags:
tags:
namedseason:
- tag: "{collection_season_4_name}"
attributes:
number: "4"
tv-show-collection-season-5:
generic:
urls:
- url: "{collection_season_5_url}"
variables:
collection_season_number: "5"
collection_season_number_padded: "05"
playlist_thumbnails:
- name: "{season_poster_name}"
uid: "latest_entry"
output_directory_nfo_tags:
tags:
namedseason:
- tag: "{collection_season_5_name}"
attributes:
number: "5"

View file

@ -22,36 +22,11 @@ presets:
# episode_number_padded # episode_number_padded
season_title: "{season_number_padded}" season_title: "{season_number_padded}"
episode_title: "{upload_date_standardized} - {title}" episode_title: "{upload_date_standardized} - {title}"
episode_file_name: "s{season_number_padded}.e{episode_number_padded} - {uid_sanitized}"
episode_file_path: "Season {season_number_padded}/{episode_file_name}"
####################################################################################################
episode-nfo:
presets:
- "episode-base"
nfo_tags:
nfo_name: "{episode_file_path}.nfo"
nfo_root: "episodedetails"
tags:
season: "{season_number}"
episode: "{episode_number}"
title: "{episode_title}"
plot: "{episode_plot}"
year: "{episode_year}"
aired: "{episode_air_date_standardized}"
output_directory_nfo_tags:
nfo_name: "tvshow.nfo"
nfo_root: "tvshow"
tags:
title: "{tv_show_name}"
overrides:
episode_plot: "{webpage_url}" episode_plot: "{webpage_url}"
episode_year: "{upload_year}" episode_year: "{upload_year}"
episode_air_date_standardized: "{upload_date_standardized}" 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}"
#################################################################################################### ####################################################################################################
@ -60,12 +35,26 @@ presets:
season_number: "{upload_year}" season_number: "{upload_year}"
season_number_padded: "{upload_year}" season_number_padded: "{upload_year}"
episode-by-day: episode-by-month-day:
preset:
- "season-by-year"
overrides: overrides:
episode_number: "{upload_month}{upload_day_padded}" episode_number: "{upload_month}{upload_day_padded}"
episode_number_padded: "{upload_month_padded}{upload_day_padded}" episode_number_padded: "{upload_month_padded}{upload_day_padded}"
episode-by-day-reversed: episode-by-month-day-reversed:
preset:
- "season-by-year"
overrides: overrides:
episode_number: "{upload_day_of_year_reversed}" episode_number: "{upload_day_of_year_reversed}"
episode_number_padded: "{upload_day_of_year_reversed_padded}" episode_number_padded: "{upload_day_of_year_reversed_padded}"
episode-by-year-month-day:
overrides:
episode_number: "{upload_year_truncated}{upload_month_padded}{upload_day_padded}"
episode_number_padded: "{episode_number}"
episode-by-year-month-day-reversed:
overrides:
episode_number: "{upload_year_truncated_reversed}{upload_month_reversed_padded}{upload_day_reversed_padded}"
episode_number_padded: "{episode_number}"

View file

@ -1,220 +0,0 @@
presets:
###############################################################################
# Single video as an episode
jellyfin-episode:
output_options:
output_directory: "{tv_shows_directory}/{tv_show_name_sanitized}"
file_name: "{episode_file_path}.{ext}"
thumbnail_name: "{episode_file_path}-thumb.jpg"
info_json_name: "{episode_file_path}.{info_json_ext}"
maintain_download_archive: True
nfo_tags:
nfo_name: "{episode_file_path}.nfo"
nfo_root: "episodedetails"
kodi_safe: True
tags:
season: "{season_number}"
episode: "{episode_number}"
title: "{episode_title}"
year: "{episode_year}"
plot: "{episode_plot}"
aired: "{episode_air_date_standardized}"
chapters:
embed_chapters: True
overrides:
# MUST DEFINE:
# tv_shows_directory
# tv_show_name
# season_number
# season_number_padded
# episode_number
# episode_number_padded
season_title: "{season_number_padded}"
episode_title: "{upload_date_standardized} - {title}"
episode_file_name: "s{season_number_padded}.e{episode_number_padded} - {uid_sanitized}"
episode_file_path: "Season {season_number_padded}/{episode_file_name}"
###############################################################################
# Create a TV show from a youtube playlist or channel
yt_show:
preset: "yt_episode"
output_directory_nfo_tags:
nfo_name: "tvshow.nfo"
nfo_root: "tvshow"
kodi_safe: True
tags:
title: "{show_name}"
namedseason:
- tag: "{season_name}"
attributes:
number: "{season}"
genre: "{genre}"
###############################################################################
# Entire channel as a TV show
yt_channel:
preset: "yt_show"
youtube:
download_strategy: "channel"
channel_avatar_path: "poster.jpg"
channel_banner_path: "fanart.jpg"
###############################################################################
# Playlist as a TV show
yt_playlist:
preset: "yt_show"
youtube:
download_strategy: "playlist"
###############################################################################
# Entire channel as a TV show
###############################################################################
# Only store recent videos in the last {time_to_live} timespan.
# Defaults to 2 months
only_recent:
ytdl_options:
break_on_reject: True
output_options:
keep_files_after: "today-{time_to_live}"
date_range:
after: "today-{time_to_live}"
overrides:
time_to_live: "2months"
###############################################################################
# For channels/playlists/etc with known sponsor content.
# Wait 2 days after upload for sponsorblock submissions, then cut it out.
sponsor_block:
date_range:
before: "today-2days"
chapters:
sponsorblock_categories:
- "outro"
- "selfpromo"
- "preview"
- "interaction"
- "sponsor"
- "music_offtopic"
- "intro"
remove_sponsorblock_categories: "all"
force_key_frames: False
###############################################################################
# Genres
genre_music:
overrides:
genre: "Music"
genre_news:
overrides:
genre: "News"
genre_finance:
overrides:
genre: "Finance"
genre_podcast:
overrides:
genre: "Podcast"
genre_cooking:
overrides:
genre: "Cooking"
genre_gaming:
overrides:
genre: "Gaming"
###############################################################################
# Other
include_description:
nfo_tags:
tags:
plot: "{webpage_url}\n\n{description}"
###############################################################################
# Other
yt_collection:
preset: "yt_show"
generic:
download_strategy: "collection"
urls:
- url: "{channel_url}"
variables:
url_season_name: "{channel_season_name}"
url_season_index: "{channel_season_index}"
playlist_thumbnails:
- name: "season{channel_season_index}-poster.jpg"
uid: "latest_entry"
- name: "poster.jpg"
uid: "avatar_uncropped"
- name: "fanart.jpg"
uid: "banner_uncropped"
output_directory_nfo_tags:
nfo_name: "tvshow.nfo"
nfo_root: "tvshow"
kodi_safe: True
tags:
namedseason:
- tag: "{channel_season_name}"
attributes:
number: "{channel_season_index}"
overrides:
# Need to fill out channel_url
channel_season_name: "Videos"
channel_season_index: "1"
season: "{url_season_index}"
season_name: "{url_season_name}"
yt_collection_p1:
preset: "yt_collection"
generic:
download_strategy: "collection"
urls:
- url: "{playlist1_url}"
variables:
url_season_name: "{playlist1_season_name}"
url_season_index: "{playlist1_season_index}"
playlist_thumbnails:
- name: "season{playlist1_season_index}-poster.jpg"
uid: "latest_entry"
output_directory_nfo_tags:
tags:
namedseason:
- tag: "{playlist1_season_name}"
attributes:
number: "{playlist1_season_index}"
overrides:
# Need channel_url, playlist1_url, playlist1_season_name
playlist1_season_index: "2"
yt_collection_p2:
preset: "yt_collection_p1"
generic:
download_strategy: "collection"
urls:
- url: "{playlist2_url}"
variables:
url_season_name: "{playlist2_season_name}"
url_season_index: "{playlist2_season_index}"
playlist_thumbnails:
- name: "season{playlist2_season_index}-poster.jpg"
uid: "latest_entry"
output_directory_nfo_tags:
tags:
namedseason:
- tag: "{playlist2_season_name}"
attributes:
number: "{playlist2_season_index}"
overrides:
# Need channel_url, playlist1_url, playlist1_season_name, playlist2_url, playlist2_season_name
playlist2_season_index: "3"

View file

@ -0,0 +1,31 @@
presets:
plex-tv-show:
overrides:
season_poster_name: "Season{season_number_padded}.jpg"
jellyfin-tv-show:
overrides:
season_poster_name: "season{season_number_padded}-poster.jpg"
nfo_tags:
nfo_name: "{episode_file_path}.nfo"
nfo_root: "episodedetails"
tags:
season: "{season_number}"
episode: "{episode_number}"
title: "{episode_title}"
plot: "{episode_plot}"
year: "{episode_year}"
aired: "{episode_air_date_standardized}"
output_directory_nfo_tags:
nfo_name: "tvshow.nfo"
nfo_root: "tvshow"
tags:
title: "{tv_show_name}"
kodi-tv-show:
preset:
- "jellyfin-tv-show"
- "kodi-safe-nfo"

View file

@ -0,0 +1,97 @@
from typing import Any, Dict
from typing import List
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_presets(cls) -> List[Preset]:
return [getattr(cls(), preset_name) for preset_name in cls.get_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")

View file

@ -1,4 +1,5 @@
import os.path import os.path
from pathlib import Path
from typing import Dict from typing import Dict
import yaml import yaml
@ -8,7 +9,7 @@ from ytdl_sub.utils.exceptions import FileNotFoundException
from ytdl_sub.utils.exceptions import InvalidYamlException from ytdl_sub.utils.exceptions import InvalidYamlException
def load_yaml(file_path: str) -> Dict: def load_yaml(file_path: str | Path) -> Dict:
""" """
Parameters Parameters
---------- ----------

View file

View file

@ -0,0 +1,31 @@
from typing import List
import pytest
from ytdl_sub.config.config_file import ConfigFile
from ytdl_sub.config.preset import Preset
from ytdl_sub.prebuilt_presets import PrebuiltKodiTVShowPresets
@pytest.fixture
def config() -> ConfigFile:
return ConfigFile(
name="config", value={"configuration": {"working_directory": "test"}, "presets": {}}
)
class TestPrebuiltTVShowPresets:
@pytest.mark.parametrize("preset_name", PrebuiltKodiTVShowPresets.get_preset_names())
def test_presets_compile(self, config, preset_name: str):
Preset.from_dict(
config=config,
preset_name=f"{preset_name}-test",
preset_dict={
'preset': preset_name,
'overrides': {
'tv_show_name': 'test tv show',
'tv_show_directory': 'output_path'
}
}
)