too many changes for single commit

This commit is contained in:
jbannon 2022-04-05 06:45:26 +00:00
parent e0a375164c
commit 6215496c10
8 changed files with 94 additions and 64 deletions

View file

@ -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 += [

View file

@ -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(

View file

@ -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
)

View file

@ -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

View file

@ -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

View file

@ -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":

View file

@ -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]] = {

View file

@ -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")