jellyfin and kodi prebuilt tv
This commit is contained in:
parent
7aab2315c0
commit
78d9236e5f
6 changed files with 163 additions and 8 deletions
|
|
@ -22,6 +22,7 @@ from ytdl_sub.downloaders.downloader import DownloaderValidator
|
||||||
from ytdl_sub.entries.entry import Entry
|
from ytdl_sub.entries.entry import Entry
|
||||||
from ytdl_sub.plugins.plugin import Plugin
|
from ytdl_sub.plugins.plugin import Plugin
|
||||||
from ytdl_sub.plugins.plugin import PluginOptions
|
from ytdl_sub.plugins.plugin import PluginOptions
|
||||||
|
from ytdl_sub.utils.yaml import dump_yaml
|
||||||
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.string_formatter_validators import DictFormatterValidator
|
from ytdl_sub.validators.string_formatter_validators import DictFormatterValidator
|
||||||
|
|
@ -328,8 +329,8 @@ class Preset(StrictDictValidator):
|
||||||
)
|
)
|
||||||
|
|
||||||
# Merge all presets
|
# Merge all presets
|
||||||
self._value = mergedeep.merge(
|
self._value = dict(
|
||||||
{}, *reversed(presets_to_merge), strategy=mergedeep.Strategy.ADDITIVE
|
mergedeep.merge({}, *reversed(presets_to_merge), strategy=mergedeep.Strategy.ADDITIVE)
|
||||||
)
|
)
|
||||||
|
|
||||||
def __init__(self, config: ConfigFile, name: str, value: Any):
|
def __init__(self, config: ConfigFile, name: str, value: Any):
|
||||||
|
|
@ -411,3 +412,7 @@ class Preset(StrictDictValidator):
|
||||||
)
|
)
|
||||||
|
|
||||||
return subscriptions
|
return subscriptions
|
||||||
|
|
||||||
|
@property
|
||||||
|
def yaml(self) -> str:
|
||||||
|
return dump_yaml({"presets": {self._name: self._value}})
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,7 @@ from typing import Dict
|
||||||
|
|
||||||
import mergedeep
|
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.out import PrebuiltKodiTVShowPresets
|
||||||
from ytdl_sub.utils.yaml import load_yaml
|
from ytdl_sub.utils.yaml import load_yaml
|
||||||
|
|
||||||
|
|
@ -18,7 +19,11 @@ def _merge_presets() -> Dict[str, Any]:
|
||||||
mergedeep.merge(merged_configs, load_yaml(file))
|
mergedeep.merge(merged_configs, load_yaml(file))
|
||||||
|
|
||||||
# Get all presets from published preset configs
|
# Get all presets from published preset configs
|
||||||
mergedeep.merge(merged_configs, *PrebuiltKodiTVShowPresets.get_presets())
|
mergedeep.merge(
|
||||||
|
merged_configs,
|
||||||
|
*PrebuiltKodiTVShowPresets.get_presets(),
|
||||||
|
*PrebuiltJellyfinTVShowPresets.get_presets()
|
||||||
|
)
|
||||||
|
|
||||||
return merged_configs["presets"]
|
return merged_configs["presets"]
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -32,6 +32,28 @@ presets:
|
||||||
season_number: "{collection_season_number}"
|
season_number: "{collection_season_number}"
|
||||||
season_number_padded: "{collection_season_number_padded}"
|
season_number_padded: "{collection_season_number_padded}"
|
||||||
|
|
||||||
|
tv-show-collection-season-1-youtube-channel:
|
||||||
|
generic:
|
||||||
|
urls:
|
||||||
|
- url: "{collection_season_1_url}"
|
||||||
|
variables:
|
||||||
|
collection_season_number: "1"
|
||||||
|
collection_season_number_padded: "01"
|
||||||
|
playlist_thumbnails:
|
||||||
|
- name: "{season_poster_file_name}"
|
||||||
|
uid: "latest_entry"
|
||||||
|
- name: "{tv_show_poster_file_name}"
|
||||||
|
uid: "avatar_uncropped"
|
||||||
|
- name: "{tv_show_fanart_file_name}"
|
||||||
|
uid: "banner_uncropped"
|
||||||
|
|
||||||
|
output_directory_nfo_tags:
|
||||||
|
tags:
|
||||||
|
namedseason:
|
||||||
|
- tag: "{collection_season_1_name}"
|
||||||
|
attributes:
|
||||||
|
number: "1"
|
||||||
|
|
||||||
tv-show-collection-season-1:
|
tv-show-collection-season-1:
|
||||||
generic:
|
generic:
|
||||||
urls:
|
urls:
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
from typing import Any
|
from typing import Any
|
||||||
from typing import Dict
|
from typing import Dict
|
||||||
from typing import List
|
from typing import List
|
||||||
|
from typing import Optional
|
||||||
|
|
||||||
Preset = Dict[str, Any]
|
Preset = Dict[str, Any]
|
||||||
|
|
||||||
|
|
@ -40,8 +41,19 @@ class PrebuiltPresets:
|
||||||
return preset_names
|
return preset_names
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def get_presets(cls) -> List[Preset]:
|
def get_collection_preset_names(cls) -> List[str]:
|
||||||
return [getattr(cls(), preset_name) for preset_name in cls.get_preset_names()]
|
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):
|
class PrebuiltKodiTVShowPresets(PrebuiltPresets):
|
||||||
|
|
@ -90,3 +102,53 @@ class PrebuiltKodiTVShowPresets(PrebuiltPresets):
|
||||||
Kodi TV Show from a collection of multiple URLs. TODO: finish docstring
|
Kodi TV Show from a collection of multiple URLs. TODO: finish docstring
|
||||||
"""
|
"""
|
||||||
return self._tv_show_collection_reversed(name="kodi_tv_show_collection_reversed")
|
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")
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
import os.path
|
import os.path
|
||||||
|
from io import StringIO
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import Dict
|
from typing import Dict
|
||||||
|
|
||||||
|
|
@ -37,3 +38,9 @@ def load_yaml(file_path: str | Path) -> Dict:
|
||||||
raise InvalidYamlException(
|
raise InvalidYamlException(
|
||||||
f"'{file_path}' has invalid YAML, copy-paste it into a YAML checker to find the issue."
|
f"'{file_path}' has invalid YAML, copy-paste it into a YAML checker to find the issue."
|
||||||
) from yaml_exception
|
) from yaml_exception
|
||||||
|
|
||||||
|
|
||||||
|
def dump_yaml(to_dump: Dict) -> str:
|
||||||
|
string_io = StringIO()
|
||||||
|
yaml.safe_dump(to_dump, string_io, indent=2, allow_unicode=True, sort_keys=True)
|
||||||
|
return string_io.getvalue()
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,4 @@
|
||||||
|
from typing import Dict
|
||||||
from typing import List
|
from typing import List
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
@ -5,6 +6,7 @@ import pytest
|
||||||
from ytdl_sub.config.config_file import ConfigFile
|
from ytdl_sub.config.config_file import ConfigFile
|
||||||
from ytdl_sub.config.preset import Preset
|
from ytdl_sub.config.preset import Preset
|
||||||
from ytdl_sub.prebuilt_presets import PrebuiltKodiTVShowPresets
|
from ytdl_sub.prebuilt_presets import PrebuiltKodiTVShowPresets
|
||||||
|
from ytdl_sub.prebuilt_presets.tv_show.out import PrebuiltJellyfinTVShowPresets
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
|
|
@ -15,9 +17,15 @@ def config() -> ConfigFile:
|
||||||
|
|
||||||
|
|
||||||
class TestPrebuiltTVShowPresets:
|
class TestPrebuiltTVShowPresets:
|
||||||
@pytest.mark.parametrize("preset_name", PrebuiltKodiTVShowPresets.get_preset_names())
|
@pytest.mark.parametrize(
|
||||||
def test_presets_compile(self, config, preset_name: str):
|
"preset_name",
|
||||||
Preset.from_dict(
|
[
|
||||||
|
*PrebuiltKodiTVShowPresets.get_non_collection_preset_names(),
|
||||||
|
*PrebuiltJellyfinTVShowPresets.get_non_collection_preset_names(),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
def test_non_collection_presets_compile(self, config, preset_name: str):
|
||||||
|
preset = Preset.from_dict(
|
||||||
config=config,
|
config=config,
|
||||||
preset_name=f"{preset_name}-test",
|
preset_name=f"{preset_name}-test",
|
||||||
preset_dict={
|
preset_dict={
|
||||||
|
|
@ -29,3 +37,49 @@ class TestPrebuiltTVShowPresets:
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
assert preset
|
||||||
|
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
"preset_name",
|
||||||
|
[
|
||||||
|
*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]]
|
||||||
|
)
|
||||||
|
@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
|
||||||
|
):
|
||||||
|
parent_presets: List[str] = [preset_name]
|
||||||
|
overrides: Dict[str, str] = {}
|
||||||
|
for season_index in season_indices:
|
||||||
|
parent_presets.append(f"tv-show-collection-season-{season_index}")
|
||||||
|
if season_index == 1 and is_season_1_youtube_channel:
|
||||||
|
parent_presets[-1] += "-youtube-channel"
|
||||||
|
|
||||||
|
overrides = dict(
|
||||||
|
overrides,
|
||||||
|
**{
|
||||||
|
f"collection_season_{season_index}_name": f"Season {season_index}",
|
||||||
|
f"collection_season_{season_index}_url": f"https://season.{season_index}.com",
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
preset = Preset.from_dict(
|
||||||
|
config=config,
|
||||||
|
preset_name=f"{preset_name}-test",
|
||||||
|
preset_dict={
|
||||||
|
"preset": parent_presets,
|
||||||
|
"overrides": dict(
|
||||||
|
overrides,
|
||||||
|
**{
|
||||||
|
"tv_show_name": "test tv show",
|
||||||
|
"tv_show_directory": "output_path",
|
||||||
|
},
|
||||||
|
),
|
||||||
|
},
|
||||||
|
)
|
||||||
|
assert preset
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue