too many changes for single commit
This commit is contained in:
parent
e0a375164c
commit
6215496c10
8 changed files with 94 additions and 64 deletions
|
|
@ -12,14 +12,22 @@ from ytdl_subscribe.validators.config.sources.soundcloud_validators import (
|
|||
)
|
||||
|
||||
|
||||
class SoundcloudAlbumsAndSinglesSubscription(Subscription):
|
||||
class SoundcloudSubscription(Subscription):
|
||||
source_validator_type = SoundcloudSourceValidator
|
||||
download_strategy_type = SoundcloudAlbumsAndSinglesDownloadValidator
|
||||
downloader_type = SoundcloudDownloader
|
||||
|
||||
@property
|
||||
def source_options(self) -> SoundcloudSourceValidator:
|
||||
return super().source_options
|
||||
|
||||
@property
|
||||
def downloader(self) -> SoundcloudDownloader:
|
||||
return super().downloader
|
||||
|
||||
|
||||
class SoundcloudAlbumsAndSinglesSubscription(SoundcloudSubscription):
|
||||
download_strategy_type = SoundcloudAlbumsAndSinglesDownloadValidator
|
||||
|
||||
@property
|
||||
def download_strategy_options(self) -> SoundcloudAlbumsAndSinglesDownloadValidator:
|
||||
return super().download_strategy_options
|
||||
|
|
@ -27,16 +35,11 @@ class SoundcloudAlbumsAndSinglesSubscription(Subscription):
|
|||
def extract_info(
|
||||
self,
|
||||
):
|
||||
|
||||
tracks: List[SoundcloudTrack] = []
|
||||
soundcloud_downloader = SoundcloudDownloader(
|
||||
output_directory=self.config_options.working_directory.value,
|
||||
ytdl_options=self.ytdl_options.dict,
|
||||
)
|
||||
|
||||
# Get the album info first. This tells us which track ids belong
|
||||
# to an album. Unfortunately we cannot use download_archive or info.json for this
|
||||
albums: List[SoundcloudAlbum] = soundcloud_downloader.download_albums(
|
||||
albums: List[SoundcloudAlbum] = self.downloader.download_albums(
|
||||
artist_name=self.download_strategy_options.username.value
|
||||
)
|
||||
|
||||
|
|
@ -46,7 +49,7 @@ class SoundcloudAlbumsAndSinglesSubscription(Subscription):
|
|||
)
|
||||
|
||||
# only add tracks that are not part of an album
|
||||
single_tracks = soundcloud_downloader.download_tracks(
|
||||
single_tracks = self.downloader.download_tracks(
|
||||
artist_name=self.download_strategy_options.username.value
|
||||
)
|
||||
tracks += [
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ import dicttoxml
|
|||
import music_tag
|
||||
from PIL import Image
|
||||
|
||||
from ytdl_subscribe.downloaders.downloader import Downloader
|
||||
from ytdl_subscribe.entries.entry import Entry
|
||||
from ytdl_subscribe.validators.config.config_validator import ConfigOptionsValidator
|
||||
from ytdl_subscribe.validators.config.metadata_options.metadata_options_validator import (
|
||||
|
|
@ -25,11 +26,15 @@ from ytdl_subscribe.validators.config.sources.source_validator import SourceVali
|
|||
|
||||
SOURCE_T = TypeVar("SOURCE_T", bound=SourceValidator)
|
||||
DOWNLOAD_STRATEGY_T = TypeVar("DOWNLOAD_STRATEGY_T", bound=DownloadStrategyValidator)
|
||||
DOWNLOADER_T = TypeVar("DOWNLOADER_T", bound=Downloader)
|
||||
|
||||
|
||||
class Subscription(object):
|
||||
SOURCE_T = TypeVar("SOURCE_T", bound=SourceValidator)
|
||||
|
||||
source_validator_type: Type[SOURCE_T]
|
||||
download_strategy_type: Type[DOWNLOAD_STRATEGY_T]
|
||||
downloader_type: Type[Downloader]
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
|
|
@ -54,17 +59,14 @@ class Subscription(object):
|
|||
overrides: OverridesValidator
|
||||
"""
|
||||
self.name = name
|
||||
self.config_options = config_options
|
||||
self._config_options = config_options
|
||||
self._source_options = source_options
|
||||
self._download_strategy_options = source_options.download_strategy
|
||||
|
||||
self.__source_options = source_options
|
||||
self.__download_strategy_options = source_options.download_strategy
|
||||
|
||||
if not isinstance(self.__source_options, self.source_validator_type):
|
||||
if not isinstance(self._source_options, self.source_validator_type):
|
||||
raise ValueError("Source options does not match the expected type")
|
||||
|
||||
if not isinstance(
|
||||
self.__download_strategy_options, self.download_strategy_type
|
||||
):
|
||||
if not isinstance(self._download_strategy_options, self.download_strategy_type):
|
||||
raise ValueError("Download strategy does not match the expected type")
|
||||
|
||||
self.output_options = output_options
|
||||
|
|
@ -74,18 +76,28 @@ class Subscription(object):
|
|||
|
||||
@property
|
||||
def source_options(self) -> SOURCE_T:
|
||||
return self.__source_options
|
||||
return self._source_options
|
||||
|
||||
@property
|
||||
def download_strategy_options(self) -> DOWNLOAD_STRATEGY_T:
|
||||
return self.__download_strategy_options
|
||||
return self._download_strategy_options
|
||||
|
||||
@property
|
||||
def working_directory(self) -> str:
|
||||
"""Returns the directory that the downloader saves files to"""
|
||||
return str(Path(self._config_options.working_directory.value) / Path(self.name))
|
||||
|
||||
@property
|
||||
def downloader(self) -> DOWNLOADER_T:
|
||||
return self.downloader_type(
|
||||
output_directory=self.working_directory,
|
||||
ytdl_options=self.ytdl_options.dict,
|
||||
)
|
||||
|
||||
def _post_process_tagging(self, entry: Entry):
|
||||
id3_options = self.metadata_options.id3
|
||||
t = music_tag.load_file(
|
||||
entry.file_path(
|
||||
relative_directory=self.config_options.working_directory.value
|
||||
)
|
||||
entry.file_path(relative_directory=self.working_directory)
|
||||
)
|
||||
for tag, tag_formatter in id3_options.tags.dict.items():
|
||||
t[tag] = entry.apply_formatter(
|
||||
|
|
@ -131,7 +143,7 @@ class Subscription(object):
|
|||
|
||||
# Move the file after all direct file modifications are complete
|
||||
entry_source_file_path = entry.file_path(
|
||||
relative_directory=self.config_options.working_directory.value
|
||||
relative_directory=self.working_directory
|
||||
)
|
||||
|
||||
output_directory = entry.apply_formatter(
|
||||
|
|
@ -150,7 +162,7 @@ class Subscription(object):
|
|||
# Download the thumbnail if its present
|
||||
if self.output_options.thumbnail_name:
|
||||
source_thumbnail_path = entry.thumbnail_path(
|
||||
relative_directory=self.config_options.working_directory.value
|
||||
relative_directory=self.working_directory
|
||||
)
|
||||
|
||||
output_thumbnail_name = entry.apply_formatter(
|
||||
|
|
|
|||
|
|
@ -10,23 +10,26 @@ from ytdl_subscribe.validators.config.sources.youtube_validators import (
|
|||
|
||||
class YoutubeSubscription(Subscription):
|
||||
source_validator_type = YoutubeSourceValidator
|
||||
download_strategy_type = YoutubePlaylistDownloadValidator
|
||||
downloader_type = YoutubeDownloader
|
||||
|
||||
@property
|
||||
def source_options(self) -> YoutubeSourceValidator:
|
||||
return super().source_options
|
||||
|
||||
@property
|
||||
def downloader(self) -> YoutubeDownloader:
|
||||
return super().downloader
|
||||
|
||||
|
||||
class YoutubePlaylistSubscription(YoutubeSubscription):
|
||||
download_strategy_type = YoutubePlaylistDownloadValidator
|
||||
|
||||
@property
|
||||
def download_strategy_options(self) -> YoutubePlaylistDownloadValidator:
|
||||
return super().download_strategy_options
|
||||
|
||||
def extract_info(self):
|
||||
youtube_downloader = YoutubeDownloader(
|
||||
output_directory=self.config_options.working_directory.value,
|
||||
ytdl_options=self.ytdl_options.dict,
|
||||
)
|
||||
|
||||
entries = youtube_downloader.download_playlist(
|
||||
entries = self.downloader.download_playlist(
|
||||
playlist_id=self.download_strategy_options.playlist_id.value
|
||||
)
|
||||
|
||||
|
|
|
|||
|
|
@ -1,9 +1,8 @@
|
|||
import re
|
||||
from keyword import iskeyword
|
||||
from typing import Dict
|
||||
from typing import List
|
||||
|
||||
from ytdl_subscribe.validators.base.validators import DictValidator
|
||||
from ytdl_subscribe.validators.base.validators import LiteralDictValidator
|
||||
from ytdl_subscribe.validators.base.validators import StringValidator
|
||||
|
||||
|
||||
|
|
@ -69,7 +68,7 @@ class StringFormatterValidator(StringValidator):
|
|||
return self._value
|
||||
|
||||
|
||||
class DictFormatterValidator(DictValidator):
|
||||
class DictFormatterValidator(LiteralDictValidator):
|
||||
"""
|
||||
Validates a dictionary made up of key: string_formatters
|
||||
"""
|
||||
|
|
@ -79,7 +78,3 @@ class DictFormatterValidator(DictValidator):
|
|||
|
||||
for key in self._keys:
|
||||
_ = self._validate_key(key=key, validator=StringFormatterValidator)
|
||||
|
||||
@property
|
||||
def dict(self) -> Dict[str, str]:
|
||||
return self._dict
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
from typing import Any
|
||||
from typing import Dict
|
||||
from typing import List
|
||||
from typing import Optional
|
||||
from typing import Type
|
||||
|
|
@ -154,3 +155,15 @@ class DictValidator(Validator):
|
|||
return None
|
||||
|
||||
return self._validate_key(key=key, validator=validator, default=default)
|
||||
|
||||
|
||||
class LiteralDictValidator(DictValidator):
|
||||
"""DictValidator with exposed dict and keys method"""
|
||||
|
||||
@property
|
||||
def dict(self) -> Dict:
|
||||
return super()._dict
|
||||
|
||||
@property
|
||||
def keys(self) -> List[str]:
|
||||
return super()._keys
|
||||
|
|
|
|||
|
|
@ -1,9 +1,11 @@
|
|||
from typing import Any
|
||||
from typing import Dict
|
||||
|
||||
import yaml
|
||||
|
||||
from ytdl_subscribe.validators.base.strict_dict_validator import StrictDictValidator
|
||||
from ytdl_subscribe.validators.base.validators import DictValidator
|
||||
from ytdl_subscribe.validators.base.validators import LiteralDictValidator
|
||||
from ytdl_subscribe.validators.base.validators import StringValidator
|
||||
|
||||
|
||||
|
|
@ -18,6 +20,12 @@ class ConfigOptionsValidator(StrictDictValidator):
|
|||
)
|
||||
|
||||
|
||||
class ConfigPresetsValidator(LiteralDictValidator):
|
||||
"""Shallow validator checking for the presets dict in the config"""
|
||||
|
||||
pass
|
||||
|
||||
|
||||
class ConfigFileValidator(StrictDictValidator):
|
||||
_required_keys = {"configuration", "presets"}
|
||||
|
||||
|
|
@ -26,7 +34,7 @@ class ConfigFileValidator(StrictDictValidator):
|
|||
self.config_options = self._validate_key(
|
||||
"configuration", ConfigOptionsValidator
|
||||
)
|
||||
self.presets = self._validate_key("presets", DictValidator)
|
||||
self.presets = self._validate_key("presets", ConfigPresetsValidator)
|
||||
|
||||
@classmethod
|
||||
def from_dict(cls, config_dict) -> "ConfigFileValidator":
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@ from ytdl_subscribe.validators.base.string_formatter_validator import (
|
|||
DictFormatterValidator,
|
||||
)
|
||||
from ytdl_subscribe.validators.base.validators import DictValidator
|
||||
from ytdl_subscribe.validators.base.validators import LiteralDictValidator
|
||||
from ytdl_subscribe.validators.config.metadata_options.metadata_options_validator import (
|
||||
MetadataOptionsValidator,
|
||||
)
|
||||
|
|
@ -29,23 +30,21 @@ from ytdl_subscribe.validators.config.sources.youtube_validators import (
|
|||
from ytdl_subscribe.validators.exceptions import ValidationException
|
||||
|
||||
|
||||
class YTDLOptionsValidator(DictValidator):
|
||||
@property
|
||||
def dict(self) -> Dict:
|
||||
return self._dict
|
||||
class YTDLOptionsValidator(LiteralDictValidator):
|
||||
"""Ensures `ytdl_options` is a dict"""
|
||||
|
||||
pass
|
||||
|
||||
|
||||
class OverridesValidator(DictFormatterValidator):
|
||||
@property
|
||||
def dict(self) -> Dict[str, str]:
|
||||
"""For overrides, create sanitized versions of each entry for convenience"""
|
||||
output_dict = copy.deepcopy(self._dict)
|
||||
for key in self._keys:
|
||||
output_dict[f"sanitized_{key}"] = sanitize_filename.sanitize(
|
||||
output_dict[key]
|
||||
)
|
||||
"""Ensures `overrides` is a dict"""
|
||||
|
||||
return output_dict
|
||||
def __init__(self, name, value):
|
||||
super().__init__(name, value)
|
||||
for key in self._keys:
|
||||
self._value[f"sanitized_{key}"] = sanitize_filename.sanitize(
|
||||
self._value[key]
|
||||
)
|
||||
|
||||
|
||||
PRESET_SOURCE_VALIDATOR_MAPPING: Dict[str, Type[SourceValidator]] = {
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
import copy
|
||||
from typing import Any
|
||||
from typing import List
|
||||
|
||||
|
|
@ -8,15 +9,12 @@ from ytdl_subscribe.subscriptions.soundcloud import (
|
|||
SoundcloudAlbumsAndSinglesSubscription,
|
||||
)
|
||||
from ytdl_subscribe.subscriptions.subscription import Subscription
|
||||
from ytdl_subscribe.subscriptions.youtube import YoutubeSubscription
|
||||
from ytdl_subscribe.subscriptions.youtube import YoutubePlaylistSubscription
|
||||
from ytdl_subscribe.validators.base.strict_dict_validator import StrictDictValidator
|
||||
from ytdl_subscribe.validators.base.validators import StringValidator
|
||||
from ytdl_subscribe.validators.config.config_validator import ConfigFileValidator
|
||||
from ytdl_subscribe.validators.config.preset_validator import PRESET_OPTIONAL_KEYS
|
||||
from ytdl_subscribe.validators.config.preset_validator import PRESET_REQUIRED_KEYS
|
||||
from ytdl_subscribe.validators.config.preset_validator import (
|
||||
PRESET_SOURCE_VALIDATOR_MAPPING,
|
||||
)
|
||||
from ytdl_subscribe.validators.config.preset_validator import OverridesValidator
|
||||
from ytdl_subscribe.validators.config.preset_validator import PresetValidator
|
||||
from ytdl_subscribe.validators.config.sources.soundcloud_validators import (
|
||||
|
|
@ -51,18 +49,17 @@ class SubscriptionValidator(StrictDictValidator):
|
|||
validator=StringValidator,
|
||||
).value
|
||||
|
||||
available_presets = self.config.presets._keys
|
||||
if preset_name not in available_presets:
|
||||
if preset_name not in self.config.presets.keys:
|
||||
raise self._validation_exception(
|
||||
f"'preset '{preset_name}' does not exist in the provided config. "
|
||||
f"Available presets: {', '.join(available_presets)}"
|
||||
f"Available presets: {', '.join(self.config.presets.keys)}"
|
||||
)
|
||||
|
||||
# A little hacky, we will override the preset with the contents of this subscription, then validate it
|
||||
# A little hacky, we will override the preset with the contents of this subscription,
|
||||
# then validate it
|
||||
preset_dict = copy.deepcopy(self.config.presets.dict[preset_name])
|
||||
preset_dict = mergedeep.merge(
|
||||
self.config.presets._dict[preset_name],
|
||||
self._dict,
|
||||
strategy=mergedeep.Strategy.REPLACE,
|
||||
preset_dict, self._dict, strategy=mergedeep.Strategy.REPLACE
|
||||
)
|
||||
del preset_dict["preset"]
|
||||
|
||||
|
|
@ -81,7 +78,7 @@ class SubscriptionValidator(StrictDictValidator):
|
|||
self.preset.subscription_source.download_strategy,
|
||||
YoutubePlaylistDownloadValidator,
|
||||
):
|
||||
subscription_class = YoutubeSubscription
|
||||
subscription_class = YoutubePlaylistSubscription
|
||||
else:
|
||||
raise ValueError("subscription source class not found")
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue