insane number of changes
This commit is contained in:
parent
6157648183
commit
f2ec156138
14 changed files with 299 additions and 198 deletions
|
|
@ -1,68 +0,0 @@
|
||||||
import copy
|
|
||||||
from abc import ABC
|
|
||||||
from typing import Any
|
|
||||||
from typing import Dict
|
|
||||||
from typing import Type
|
|
||||||
|
|
||||||
from ytdl_subscribe.downloaders.downloader import DownloaderValidator
|
|
||||||
from ytdl_subscribe.downloaders.soundcloud_downloader import (
|
|
||||||
SoundcloudAlbumsAndSinglesDownloadOptions,
|
|
||||||
)
|
|
||||||
from ytdl_subscribe.downloaders.youtube_downloader import YoutubeChannelDownloaderOptions
|
|
||||||
from ytdl_subscribe.downloaders.youtube_downloader import YoutubePlaylistDownloaderOptions
|
|
||||||
from ytdl_subscribe.downloaders.youtube_downloader import YoutubeVideoDownloaderOptions
|
|
||||||
from ytdl_subscribe.validators.strict_dict_validator import StrictDictValidator
|
|
||||||
from ytdl_subscribe.validators.validators import StringValidator
|
|
||||||
|
|
||||||
|
|
||||||
class DownloadStrategyValidator(StrictDictValidator, ABC):
|
|
||||||
"""
|
|
||||||
Validates the download strategy of a source. Does not validate the source options.
|
|
||||||
"""
|
|
||||||
|
|
||||||
# All media sources must define a download strategy
|
|
||||||
_required_keys = {"download_strategy"}
|
|
||||||
|
|
||||||
# Extra fields will be strict-validated using other StictDictValidators
|
|
||||||
_allow_extra_keys = True
|
|
||||||
|
|
||||||
_download_strategy_to_source_mapping: Dict[str, Type[DownloaderValidator]] = {}
|
|
||||||
|
|
||||||
def __init__(self, name: str, value: Any):
|
|
||||||
super().__init__(name=name, value=value)
|
|
||||||
download_strategy_name = self._validate_key(
|
|
||||||
key="download_strategy",
|
|
||||||
validator=StringValidator,
|
|
||||||
).value
|
|
||||||
|
|
||||||
if download_strategy_name not in self._possible_download_strategies:
|
|
||||||
raise self._validation_exception(
|
|
||||||
f"download_strategy must be one of the following: "
|
|
||||||
f"{', '.join(self._possible_download_strategies)}"
|
|
||||||
)
|
|
||||||
|
|
||||||
# Remove the 'download_strategy' key before passing the other keys to the actual
|
|
||||||
# source validator
|
|
||||||
source_validator_dict = copy.deepcopy(self._dict)
|
|
||||||
del source_validator_dict["download_strategy"]
|
|
||||||
|
|
||||||
source_validator_class = self._download_strategy_to_source_mapping[download_strategy_name]
|
|
||||||
self.source_validator = source_validator_class(name=self._name, value=source_validator_dict)
|
|
||||||
|
|
||||||
@property
|
|
||||||
def _possible_download_strategies(self):
|
|
||||||
return sorted(list(self._download_strategy_to_source_mapping.keys()))
|
|
||||||
|
|
||||||
|
|
||||||
class SoundcloudDownloadStrategyValidator(DownloadStrategyValidator):
|
|
||||||
_download_strategy_to_source_mapping = {
|
|
||||||
"albums_and_singles": SoundcloudAlbumsAndSinglesDownloadOptions
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
class YoutubeDownloadStrategyValidator(DownloadStrategyValidator):
|
|
||||||
_download_strategy_to_source_mapping = {
|
|
||||||
"channel": YoutubeChannelDownloaderOptions,
|
|
||||||
"playlist": YoutubePlaylistDownloaderOptions,
|
|
||||||
"video": YoutubeVideoDownloaderOptions,
|
|
||||||
}
|
|
||||||
|
|
@ -1,16 +1,20 @@
|
||||||
|
from abc import ABC
|
||||||
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
|
from typing import Optional
|
||||||
|
from typing import Tuple
|
||||||
from typing import Type
|
from typing import Type
|
||||||
|
|
||||||
from yt_dlp.utils import sanitize_filename
|
from yt_dlp.utils import sanitize_filename
|
||||||
|
|
||||||
from ytdl_subscribe.config.download_strategy_validators import DownloadStrategyValidator
|
from ytdl_subscribe.config.preset_class_mappings import DownloadStrategyMapping
|
||||||
from ytdl_subscribe.config.download_strategy_validators import SoundcloudDownloadStrategyValidator
|
from ytdl_subscribe.config.preset_class_mappings import PluginMapping
|
||||||
from ytdl_subscribe.config.download_strategy_validators import YoutubeDownloadStrategyValidator
|
from ytdl_subscribe.downloaders.downloader import Downloader
|
||||||
from ytdl_subscribe.downloaders.downloader import DownloaderValidator
|
from ytdl_subscribe.downloaders.downloader import DownloaderValidator
|
||||||
from ytdl_subscribe.entries.entry import Entry
|
from ytdl_subscribe.entries.entry import Entry
|
||||||
|
from ytdl_subscribe.plugins.plugin import Plugin
|
||||||
|
from ytdl_subscribe.plugins.plugin import PluginOptions
|
||||||
from ytdl_subscribe.utils.exceptions import StringFormattingVariableNotFoundException
|
from ytdl_subscribe.utils.exceptions import StringFormattingVariableNotFoundException
|
||||||
from ytdl_subscribe.utils.exceptions import ValidationException
|
from ytdl_subscribe.utils.exceptions import ValidationException
|
||||||
from ytdl_subscribe.validators.strict_dict_validator import StrictDictValidator
|
from ytdl_subscribe.validators.strict_dict_validator import StrictDictValidator
|
||||||
|
|
@ -20,19 +24,15 @@ from ytdl_subscribe.validators.string_formatter_validators import StringFormatte
|
||||||
from ytdl_subscribe.validators.validators import BoolValidator
|
from ytdl_subscribe.validators.validators import BoolValidator
|
||||||
from ytdl_subscribe.validators.validators import DictValidator
|
from ytdl_subscribe.validators.validators import DictValidator
|
||||||
from ytdl_subscribe.validators.validators import LiteralDictValidator
|
from ytdl_subscribe.validators.validators import LiteralDictValidator
|
||||||
|
from ytdl_subscribe.validators.validators import StringValidator
|
||||||
from ytdl_subscribe.validators.validators import Validator
|
from ytdl_subscribe.validators.validators import Validator
|
||||||
|
|
||||||
PRESET_SOURCE_VALIDATOR_MAPPING: Dict[str, Type[DownloadStrategyValidator]] = {
|
|
||||||
"soundcloud": SoundcloudDownloadStrategyValidator,
|
|
||||||
"youtube": YoutubeDownloadStrategyValidator,
|
|
||||||
}
|
|
||||||
|
|
||||||
PRESET_REQUIRED_KEYS = {"output_options"}
|
PRESET_REQUIRED_KEYS = {"output_options"}
|
||||||
PRESET_OPTIONAL_KEYS = {
|
PRESET_OPTIONAL_KEYS = {
|
||||||
"metadata_options",
|
|
||||||
"ytdl_options",
|
"ytdl_options",
|
||||||
"overrides",
|
"overrides",
|
||||||
*PRESET_SOURCE_VALIDATOR_MAPPING.keys(),
|
*DownloadStrategyMapping.sources(),
|
||||||
|
*PluginMapping.plugins(),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -109,38 +109,95 @@ class OutputOptions(StrictDictValidator):
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class DownloadStrategyValidator(StrictDictValidator, ABC):
|
||||||
|
"""
|
||||||
|
Ensures a download strategy exists for a source. Does not validate any more than that.
|
||||||
|
The respective Downloader's option validator will do that.
|
||||||
|
"""
|
||||||
|
|
||||||
|
# All media sources must define a download strategy
|
||||||
|
_required_keys = {"download_strategy"}
|
||||||
|
|
||||||
|
# Extra fields will be strict-validated using other StictDictValidators
|
||||||
|
_allow_extra_keys = True
|
||||||
|
|
||||||
|
def __init__(self, name: str, value: Any):
|
||||||
|
super().__init__(name=name, value=value)
|
||||||
|
self.name = self._validate_key(
|
||||||
|
key="download_strategy",
|
||||||
|
validator=StringValidator,
|
||||||
|
).value
|
||||||
|
|
||||||
|
|
||||||
class PresetValidator(StrictDictValidator):
|
class PresetValidator(StrictDictValidator):
|
||||||
_required_keys = PRESET_REQUIRED_KEYS
|
_required_keys = PRESET_REQUIRED_KEYS
|
||||||
_optional_keys = PRESET_OPTIONAL_KEYS
|
_optional_keys = PRESET_OPTIONAL_KEYS
|
||||||
|
|
||||||
@property
|
def __validate_and_get_downloader(self, downloader_source: str) -> Type[Downloader]:
|
||||||
def __available_sources(self) -> List[str]:
|
downloader_strategy = self._validate_key(
|
||||||
return sorted(list(PRESET_SOURCE_VALIDATOR_MAPPING.keys()))
|
key=downloader_source, validator=DownloadStrategyValidator
|
||||||
|
).name
|
||||||
|
|
||||||
def __validate_and_get_subscription_source(self) -> DownloaderValidator:
|
return DownloadStrategyMapping.get(
|
||||||
download_strategy_validator: Optional[DownloadStrategyValidator] = None
|
source=downloader_source, download_strategy=downloader_strategy
|
||||||
|
)
|
||||||
|
|
||||||
|
def __validate_and_get_downloader_options(
|
||||||
|
self, downloader_source: str, downloader: Type[Downloader]
|
||||||
|
) -> DownloaderValidator:
|
||||||
|
# Remove the download_strategy key before validating it against the downloader options
|
||||||
|
# TODO: make this cleaner
|
||||||
|
del self._dict[downloader_source]["download_strategy"]
|
||||||
|
return self._validate_key(
|
||||||
|
key=downloader_source, validator=downloader.downloader_options_type
|
||||||
|
)
|
||||||
|
|
||||||
|
def __validate_and_get_downloader_and_options(
|
||||||
|
self,
|
||||||
|
) -> Tuple[Type[Downloader], DownloaderValidator]:
|
||||||
|
downloader: Optional[Type[Downloader]] = None
|
||||||
|
download_options: Optional[DownloaderValidator] = None
|
||||||
|
downloader_sources = DownloadStrategyMapping.sources()
|
||||||
|
|
||||||
for key in self._keys:
|
for key in self._keys:
|
||||||
|
# skip if the key is not a download source
|
||||||
|
if key not in downloader_sources:
|
||||||
|
continue
|
||||||
|
|
||||||
# Ensure there are not multiple sources, i.e. youtube and soundcloud
|
# Ensure there are not multiple sources, i.e. youtube and soundcloud
|
||||||
if key in self.__available_sources and download_strategy_validator:
|
if downloader:
|
||||||
raise ValidationException(
|
raise ValidationException(
|
||||||
f"'{self._name}' can only have one of the following sources: "
|
f"'{self._name}' can only have one of the following sources: "
|
||||||
f"{', '.join(self.__available_sources)}"
|
f"{', '.join(downloader_sources)}"
|
||||||
)
|
)
|
||||||
|
|
||||||
if key in PRESET_SOURCE_VALIDATOR_MAPPING:
|
downloader = self.__validate_and_get_downloader(downloader_source=key)
|
||||||
download_strategy_validator = self._validate_key(
|
download_options = self.__validate_and_get_downloader_options(
|
||||||
key=key, validator=PRESET_SOURCE_VALIDATOR_MAPPING[key]
|
downloader_source=key, downloader=downloader
|
||||||
)
|
|
||||||
|
|
||||||
# If subscription source was not set, error
|
|
||||||
if not download_strategy_validator:
|
|
||||||
raise ValidationException(
|
|
||||||
f"'{self._name} must have one of the following sources: "
|
|
||||||
f"{', '.join(self.__available_sources)}"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
return download_strategy_validator.source_validator
|
# If downloader was not set, error since it is required
|
||||||
|
if not downloader:
|
||||||
|
raise ValidationException(
|
||||||
|
f"'{self._name} must have one of the following sources: "
|
||||||
|
f"{', '.join(downloader_sources)}"
|
||||||
|
)
|
||||||
|
|
||||||
|
return downloader, download_options
|
||||||
|
|
||||||
|
def __validate_and_get_plugins(self) -> List[Tuple[Type[Plugin], PluginOptions]]:
|
||||||
|
plugins: List[Tuple[Type[Plugin], PluginOptions]] = []
|
||||||
|
|
||||||
|
for key in self._keys:
|
||||||
|
if key not in PluginMapping.plugins():
|
||||||
|
continue
|
||||||
|
|
||||||
|
plugin = PluginMapping.get(plugin=key)
|
||||||
|
plugin_options = self._validate_key(key=key, validator=plugin.plugin_options_type)
|
||||||
|
|
||||||
|
plugins.append((plugin, plugin_options))
|
||||||
|
|
||||||
|
return plugins
|
||||||
|
|
||||||
def __validate_override_string_formatter_validator(
|
def __validate_override_string_formatter_validator(
|
||||||
self, formatter_validator: OverridesStringFormatterValidator
|
self, formatter_validator: OverridesStringFormatterValidator
|
||||||
|
|
@ -186,20 +243,19 @@ class PresetValidator(StrictDictValidator):
|
||||||
def __init__(self, name: str, value: Any):
|
def __init__(self, name: str, value: Any):
|
||||||
super().__init__(name=name, value=value)
|
super().__init__(name=name, value=value)
|
||||||
|
|
||||||
self.subscription_source = self.__validate_and_get_subscription_source()
|
self.downloader, self.downloader_options = self.__validate_and_get_downloader_and_options()
|
||||||
|
|
||||||
self.output_options = self._validate_key(
|
self.output_options = self._validate_key(
|
||||||
key="output_options",
|
key="output_options",
|
||||||
validator=OutputOptions,
|
validator=OutputOptions,
|
||||||
)
|
)
|
||||||
|
|
||||||
# TODO: REPLACE METADATA OPTIONS WITH PLUGINS
|
|
||||||
|
|
||||||
self.ytdl_options = self._validate_key(
|
self.ytdl_options = self._validate_key(
|
||||||
key="ytdl_options", validator=YTDLOptions, default={}
|
key="ytdl_options", validator=YTDLOptions, default={}
|
||||||
)
|
)
|
||||||
|
|
||||||
self.overrides = self._validate_key(key="overrides", validator=Overrides, default={})
|
self.overrides = self._validate_key(key="overrides", validator=Overrides, default={})
|
||||||
|
self.plugins = self.__validate_and_get_plugins()
|
||||||
|
|
||||||
# After all options are initialized, perform a recursive post-validate that requires
|
# After all options are initialized, perform a recursive post-validate that requires
|
||||||
# values from multiple validators
|
# values from multiple validators
|
||||||
|
|
|
||||||
107
ytdl_subscribe/config/preset_class_mappings.py
Normal file
107
ytdl_subscribe/config/preset_class_mappings.py
Normal file
|
|
@ -0,0 +1,107 @@
|
||||||
|
from typing import Dict
|
||||||
|
from typing import List
|
||||||
|
from typing import Type
|
||||||
|
|
||||||
|
from ytdl_subscribe.downloaders.downloader import Downloader
|
||||||
|
from ytdl_subscribe.downloaders.soundcloud_downloader import SoundcloudAlbumsAndSinglesDownloader
|
||||||
|
from ytdl_subscribe.downloaders.youtube_downloader import YoutubeChannelDownloader
|
||||||
|
from ytdl_subscribe.downloaders.youtube_downloader import YoutubePlaylistDownloader
|
||||||
|
from ytdl_subscribe.downloaders.youtube_downloader import YoutubeVideoDownloader
|
||||||
|
from ytdl_subscribe.plugins.convert_thumbnail import ConvertThumbnailPlugin
|
||||||
|
from ytdl_subscribe.plugins.music_tags import MusicTagsPlugin
|
||||||
|
from ytdl_subscribe.plugins.nfo_tags import NfoTagsPlugin
|
||||||
|
from ytdl_subscribe.plugins.output_directory_nfo_tags import OutputDirectoryNfoTagsPlugin
|
||||||
|
from ytdl_subscribe.plugins.plugin import Plugin
|
||||||
|
|
||||||
|
|
||||||
|
class DownloadStrategyMapping:
|
||||||
|
"""
|
||||||
|
Maps downloader strategies defined in the preset to its respective downloader class
|
||||||
|
"""
|
||||||
|
|
||||||
|
_MAPPING: Dict[str, Dict[str, Type[Downloader]]] = {
|
||||||
|
"youtube": {
|
||||||
|
"video": YoutubeVideoDownloader,
|
||||||
|
"playlist": YoutubePlaylistDownloader,
|
||||||
|
"channel": YoutubeChannelDownloader,
|
||||||
|
},
|
||||||
|
"soundcloud": {
|
||||||
|
"albums and singles": SoundcloudAlbumsAndSinglesDownloader,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def sources(cls) -> List[str]:
|
||||||
|
"""
|
||||||
|
Returns
|
||||||
|
-------
|
||||||
|
Available download sources
|
||||||
|
"""
|
||||||
|
return sorted(list(cls._MAPPING.keys()))
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def _validate_is_source(cls, source: str) -> None:
|
||||||
|
"""
|
||||||
|
Ensure the source exists
|
||||||
|
"""
|
||||||
|
if source not in cls.sources():
|
||||||
|
raise ValueError(f"Tried to use source '{source}' that does not exist")
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def source_download_strategies(cls, source: str) -> List[str]:
|
||||||
|
"""
|
||||||
|
Parameters
|
||||||
|
----------
|
||||||
|
source
|
||||||
|
Name of the source
|
||||||
|
|
||||||
|
Returns
|
||||||
|
-------
|
||||||
|
Available download strategies for the given source
|
||||||
|
"""
|
||||||
|
cls._validate_is_source(source)
|
||||||
|
return sorted(list(cls._MAPPING[source].keys()))
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def _validate_is_download_strategy(cls, source: str, download_strategy: str) -> None:
|
||||||
|
"""
|
||||||
|
Ensure the download strategy for the given source exists
|
||||||
|
"""
|
||||||
|
if download_strategy not in cls.source_download_strategies(source):
|
||||||
|
raise ValueError(
|
||||||
|
f"Tried to use download strategy '{download_strategy}' with source '{source}', "
|
||||||
|
f"which does not exist"
|
||||||
|
)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def get(cls, source: str, download_strategy: str) -> Type[Downloader]:
|
||||||
|
cls._validate_is_download_strategy(source, download_strategy)
|
||||||
|
return cls._MAPPING[source][download_strategy]
|
||||||
|
|
||||||
|
|
||||||
|
class PluginMapping:
|
||||||
|
"""
|
||||||
|
Maps plugins defined in the preset to its respective plugin class
|
||||||
|
"""
|
||||||
|
|
||||||
|
_MAPPING: Dict[str, Type[Plugin]] = {
|
||||||
|
"convert_thumbnail": ConvertThumbnailPlugin,
|
||||||
|
"music_tags": MusicTagsPlugin,
|
||||||
|
"nfo_tags": NfoTagsPlugin,
|
||||||
|
"output_directory_nfo_tags": OutputDirectoryNfoTagsPlugin,
|
||||||
|
}
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def plugins(cls) -> List[str]:
|
||||||
|
"""
|
||||||
|
Returns
|
||||||
|
-------
|
||||||
|
Available download sources
|
||||||
|
"""
|
||||||
|
return sorted(list(cls._MAPPING.keys()))
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def get(cls, plugin: str) -> Type[Plugin]:
|
||||||
|
if plugin not in cls.plugins():
|
||||||
|
raise ValueError(f"Tried to use plugin '{plugin}' that does not exist")
|
||||||
|
return cls._MAPPING[plugin]
|
||||||
|
|
@ -58,25 +58,11 @@ class SubscriptionValidator(StrictDictValidator):
|
||||||
)
|
)
|
||||||
|
|
||||||
def to_subscription(self) -> Subscription:
|
def to_subscription(self) -> Subscription:
|
||||||
# TODO: Fix this abomination
|
return Subscription(
|
||||||
return None
|
name=self._name,
|
||||||
# subscription_class: Optional[Type[Subscription]] = None
|
config_options=self.config.config_options,
|
||||||
# if isinstance(self.preset.subscription_source, SoundcloudAlbumsAndSinglesDownloadOptions):
|
preset_options=self.preset,
|
||||||
# subscription_class = SoundcloudAlbumsAndSinglesSubscription
|
)
|
||||||
# elif isinstance(self.preset.subscription_source, YoutubePlaylistDownloaderValidator):
|
|
||||||
# subscription_class = YoutubePlaylistSubscription
|
|
||||||
# elif isinstance(self.preset.subscription_source, YoutubeVideoDownloaderValidator):
|
|
||||||
# subscription_class = YoutubeVideoSubscription
|
|
||||||
# elif isinstance(self.preset.subscription_source, YoutubeChannelDownloaderValidator):
|
|
||||||
# subscription_class = YoutubeChannelSubscription
|
|
||||||
# if subscription_class is None:
|
|
||||||
# raise ValueError("subscription source class not found")
|
|
||||||
#
|
|
||||||
# return subscription_class(
|
|
||||||
# name=self._name,
|
|
||||||
# config_options=self.config.config_options,
|
|
||||||
# preset_options=self.preset,
|
|
||||||
# )
|
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def from_dict(
|
def from_dict(
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,7 @@ from typing import Dict
|
||||||
from typing import Generic
|
from typing import Generic
|
||||||
from typing import List
|
from typing import List
|
||||||
from typing import Optional
|
from typing import Optional
|
||||||
|
from typing import Type
|
||||||
from typing import TypeVar
|
from typing import TypeVar
|
||||||
|
|
||||||
import yt_dlp as ytdl
|
import yt_dlp as ytdl
|
||||||
|
|
@ -24,12 +25,15 @@ DownloaderOptionsT = TypeVar("DownloaderOptionsT", bound=DownloaderValidator)
|
||||||
DownloaderEntryT = TypeVar("DownloaderEntryT", bound=Entry)
|
DownloaderEntryT = TypeVar("DownloaderEntryT", bound=Entry)
|
||||||
|
|
||||||
|
|
||||||
class Downloader(Generic[DownloaderOptionsT, DownloaderEntryT]):
|
class Downloader(Generic[DownloaderOptionsT, DownloaderEntryT], ABC):
|
||||||
"""
|
"""
|
||||||
Class that interacts with ytdl to perform the download of metadata and content,
|
Class that interacts with ytdl to perform the download of metadata and content,
|
||||||
and should translate that to list of Entry objects.
|
and should translate that to list of Entry objects.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
downloader_options_type: Type[DownloaderOptionsT] = NotImplemented
|
||||||
|
downloader_entry_type: Type[DownloaderEntryT] = NotImplemented
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def ytdl_option_overrides(cls) -> Dict:
|
def ytdl_option_overrides(cls) -> Dict:
|
||||||
"""Global overrides that even overwrite user input"""
|
"""Global overrides that even overwrite user input"""
|
||||||
|
|
|
||||||
|
|
@ -35,8 +35,8 @@ SoundcloudDownloaderOptionsT = TypeVar(
|
||||||
|
|
||||||
|
|
||||||
class SoundcloudDownloader(
|
class SoundcloudDownloader(
|
||||||
Generic[SoundcloudDownloaderOptionsT],
|
|
||||||
Downloader[SoundcloudDownloaderOptionsT, SoundcloudTrack],
|
Downloader[SoundcloudDownloaderOptionsT, SoundcloudTrack],
|
||||||
|
Generic[SoundcloudDownloaderOptionsT],
|
||||||
ABC,
|
ABC,
|
||||||
):
|
):
|
||||||
"""
|
"""
|
||||||
|
|
@ -44,6 +44,8 @@ class SoundcloudDownloader(
|
||||||
SoundcloudTrack / SoundcloudAlbumTrack objects
|
SoundcloudTrack / SoundcloudAlbumTrack objects
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
downloader_entry_type = SoundcloudTrack
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def ytdl_option_defaults(cls) -> Dict:
|
def ytdl_option_defaults(cls) -> Dict:
|
||||||
"""Returns default format to be best mp3"""
|
"""Returns default format to be best mp3"""
|
||||||
|
|
@ -97,6 +99,8 @@ class SoundcloudAlbumsAndSinglesDownloadOptions(SoundcloudDownloaderOptions):
|
||||||
class SoundcloudAlbumsAndSinglesDownloader(
|
class SoundcloudAlbumsAndSinglesDownloader(
|
||||||
SoundcloudDownloader[SoundcloudAlbumsAndSinglesDownloadOptions]
|
SoundcloudDownloader[SoundcloudAlbumsAndSinglesDownloadOptions]
|
||||||
):
|
):
|
||||||
|
downloader_options_type = SoundcloudDownloaderOptions
|
||||||
|
|
||||||
def download(self) -> List[SoundcloudTrack]:
|
def download(self) -> List[SoundcloudTrack]:
|
||||||
"""
|
"""
|
||||||
Soundcloud subscription to download albums and tracks as singles.
|
Soundcloud subscription to download albums and tracks as singles.
|
||||||
|
|
|
||||||
|
|
@ -30,13 +30,15 @@ YoutubeDownloaderOptionsT = TypeVar("YoutubeDownloaderOptionsT", bound=YoutubeDo
|
||||||
|
|
||||||
|
|
||||||
class YoutubeDownloader(
|
class YoutubeDownloader(
|
||||||
Generic[YoutubeDownloaderOptionsT], Downloader[YoutubeDownloaderOptionsT, YoutubeVideo], ABC
|
Downloader[YoutubeDownloaderOptionsT, YoutubeVideo], Generic[YoutubeDownloaderOptionsT], ABC
|
||||||
):
|
):
|
||||||
"""
|
"""
|
||||||
Class that handles downloading youtube entries via ytdl and converting them into
|
Class that handles downloading youtube entries via ytdl and converting them into
|
||||||
YoutubeVideo objects
|
YoutubeVideo objects
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
downloader_entry_type = YoutubeVideo
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def playlist_url(cls, playlist_id: str) -> str:
|
def playlist_url(cls, playlist_id: str) -> str:
|
||||||
"""Returns full playlist url"""
|
"""Returns full playlist url"""
|
||||||
|
|
@ -131,6 +133,8 @@ class YoutubeVideoDownloaderOptions(YoutubeDownloaderOptions):
|
||||||
|
|
||||||
|
|
||||||
class YoutubeVideoDownloader(YoutubeDownloader[YoutubeVideoDownloaderOptions]):
|
class YoutubeVideoDownloader(YoutubeDownloader[YoutubeVideoDownloaderOptions]):
|
||||||
|
downloader_options_type = YoutubeVideoDownloaderOptions
|
||||||
|
|
||||||
def download(self) -> List[YoutubeVideo]:
|
def download(self) -> List[YoutubeVideo]:
|
||||||
video = self.download_video(video_id=self.download_options.video_id.value)
|
video = self.download_video(video_id=self.download_options.video_id.value)
|
||||||
return [video]
|
return [video]
|
||||||
|
|
@ -149,6 +153,8 @@ class YoutubePlaylistDownloaderOptions(YoutubeDownloaderOptions):
|
||||||
|
|
||||||
|
|
||||||
class YoutubePlaylistDownloader(YoutubeDownloader[YoutubePlaylistDownloaderOptions]):
|
class YoutubePlaylistDownloader(YoutubeDownloader[YoutubePlaylistDownloaderOptions]):
|
||||||
|
downloader_options_type = YoutubePlaylistDownloaderOptions
|
||||||
|
|
||||||
def download(self) -> List[YoutubeVideo]:
|
def download(self) -> List[YoutubeVideo]:
|
||||||
return self.download_playlist(playlist_id=self.download_options.playlist_id.value)
|
return self.download_playlist(playlist_id=self.download_options.playlist_id.value)
|
||||||
|
|
||||||
|
|
@ -168,6 +174,8 @@ class YoutubeChannelDownloaderOptions(YoutubeDownloaderOptions, DownloadDateRang
|
||||||
|
|
||||||
|
|
||||||
class YoutubeChannelDownloader(YoutubeDownloader[YoutubeChannelDownloaderOptions]):
|
class YoutubeChannelDownloader(YoutubeDownloader[YoutubeChannelDownloaderOptions]):
|
||||||
|
downloader_options_type = YoutubeChannelDownloaderOptions
|
||||||
|
|
||||||
def download(self) -> List[YoutubeVideo]:
|
def download(self) -> List[YoutubeVideo]:
|
||||||
ytdl_options_overrides = {}
|
ytdl_options_overrides = {}
|
||||||
source_date_range = self.download_options.get_date_range()
|
source_date_range = self.download_options.get_date_range()
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@ from PIL.Image import Image
|
||||||
|
|
||||||
from ytdl_subscribe.entries.entry import Entry
|
from ytdl_subscribe.entries.entry import Entry
|
||||||
from ytdl_subscribe.plugins.plugin import Plugin
|
from ytdl_subscribe.plugins.plugin import Plugin
|
||||||
from ytdl_subscribe.plugins.plugin import PluginValidator
|
from ytdl_subscribe.plugins.plugin import PluginOptions
|
||||||
from ytdl_subscribe.validators.string_select_validator import StringSelectValidator
|
from ytdl_subscribe.validators.string_select_validator import StringSelectValidator
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -14,7 +14,7 @@ class ThumbnailTypes(StringSelectValidator):
|
||||||
_select_values = {"jpg"}
|
_select_values = {"jpg"}
|
||||||
|
|
||||||
|
|
||||||
class ConvertThumbnailValidator(PluginValidator):
|
class ConvertThumbnailOptions(PluginOptions):
|
||||||
_required_keys = {"to"}
|
_required_keys = {"to"}
|
||||||
|
|
||||||
def __init__(self, name, value):
|
def __init__(self, name, value):
|
||||||
|
|
@ -22,7 +22,9 @@ class ConvertThumbnailValidator(PluginValidator):
|
||||||
self.convert_to = self._validate_key(key="to", validator=ThumbnailTypes)
|
self.convert_to = self._validate_key(key="to", validator=ThumbnailTypes)
|
||||||
|
|
||||||
|
|
||||||
class ConvertThumbnail(Plugin[ConvertThumbnailValidator]):
|
class ConvertThumbnailPlugin(Plugin[ConvertThumbnailOptions]):
|
||||||
|
plugin_options_type = ConvertThumbnailOptions
|
||||||
|
|
||||||
def post_process_entry(self, entry: Entry):
|
def post_process_entry(self, entry: Entry):
|
||||||
"""
|
"""
|
||||||
Convert the source thumbnail to the desired type. Leave the original file name and
|
Convert the source thumbnail to the desired type. Leave the original file name and
|
||||||
|
|
|
||||||
|
|
@ -2,12 +2,12 @@ import music_tag
|
||||||
|
|
||||||
from ytdl_subscribe.entries.entry import Entry
|
from ytdl_subscribe.entries.entry import Entry
|
||||||
from ytdl_subscribe.plugins.plugin import Plugin
|
from ytdl_subscribe.plugins.plugin import Plugin
|
||||||
from ytdl_subscribe.plugins.plugin import PluginValidator
|
from ytdl_subscribe.plugins.plugin import PluginOptions
|
||||||
from ytdl_subscribe.validators.string_formatter_validators import DictFormatterValidator
|
from ytdl_subscribe.validators.string_formatter_validators import DictFormatterValidator
|
||||||
from ytdl_subscribe.validators.validators import StringValidator
|
from ytdl_subscribe.validators.validators import StringValidator
|
||||||
|
|
||||||
|
|
||||||
class MusicTagsValidator(PluginValidator):
|
class MusicTagsOptions(PluginOptions):
|
||||||
_required_keys = {"tags"}
|
_required_keys = {"tags"}
|
||||||
_optional_keys = {"multi_value_separator"}
|
_optional_keys = {"multi_value_separator"}
|
||||||
|
|
||||||
|
|
@ -21,7 +21,9 @@ class MusicTagsValidator(PluginValidator):
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
class MusicTags(Plugin[MusicTagsValidator]):
|
class MusicTagsPlugin(Plugin[MusicTagsOptions]):
|
||||||
|
plugin_options_type = MusicTagsOptions
|
||||||
|
|
||||||
def post_process_entry(self, entry: Entry):
|
def post_process_entry(self, entry: Entry):
|
||||||
"""
|
"""
|
||||||
Tags the entry's audio file using values defined in the metadata options
|
Tags the entry's audio file using values defined in the metadata options
|
||||||
|
|
|
||||||
|
|
@ -4,12 +4,12 @@ import dicttoxml
|
||||||
|
|
||||||
from ytdl_subscribe.entries.entry import Entry
|
from ytdl_subscribe.entries.entry import Entry
|
||||||
from ytdl_subscribe.plugins.plugin import Plugin
|
from ytdl_subscribe.plugins.plugin import Plugin
|
||||||
from ytdl_subscribe.plugins.plugin import PluginValidator
|
from ytdl_subscribe.plugins.plugin import PluginOptions
|
||||||
from ytdl_subscribe.validators.string_formatter_validators import DictFormatterValidator
|
from ytdl_subscribe.validators.string_formatter_validators import DictFormatterValidator
|
||||||
from ytdl_subscribe.validators.string_formatter_validators import StringFormatterValidator
|
from ytdl_subscribe.validators.string_formatter_validators import StringFormatterValidator
|
||||||
|
|
||||||
|
|
||||||
class NfoTagsValidator(PluginValidator):
|
class NfoTagsOptions(PluginOptions):
|
||||||
_required_keys = {"nfo_name", "nfo_root", "tags"}
|
_required_keys = {"nfo_name", "nfo_root", "tags"}
|
||||||
|
|
||||||
def __init__(self, name, value):
|
def __init__(self, name, value):
|
||||||
|
|
@ -20,7 +20,9 @@ class NfoTagsValidator(PluginValidator):
|
||||||
self.tags = self._validate_key(key="tags", validator=DictFormatterValidator)
|
self.tags = self._validate_key(key="tags", validator=DictFormatterValidator)
|
||||||
|
|
||||||
|
|
||||||
class NfoTags(Plugin[NfoTagsValidator]):
|
class NfoTagsPlugin(Plugin[NfoTagsOptions]):
|
||||||
|
plugin_options_type = NfoTagsOptions
|
||||||
|
|
||||||
def post_process_entry(self, entry: Entry):
|
def post_process_entry(self, entry: Entry):
|
||||||
"""
|
"""
|
||||||
Creates an entry's NFO file using values defined in the metadata options
|
Creates an entry's NFO file using values defined in the metadata options
|
||||||
|
|
|
||||||
|
|
@ -3,12 +3,12 @@ from pathlib import Path
|
||||||
import dicttoxml
|
import dicttoxml
|
||||||
|
|
||||||
from ytdl_subscribe.plugins.plugin import Plugin
|
from ytdl_subscribe.plugins.plugin import Plugin
|
||||||
from ytdl_subscribe.plugins.plugin import PluginValidator
|
from ytdl_subscribe.plugins.plugin import PluginOptions
|
||||||
from ytdl_subscribe.validators.string_formatter_validators import OverridesDictFormatterValidator
|
from ytdl_subscribe.validators.string_formatter_validators import OverridesDictFormatterValidator
|
||||||
from ytdl_subscribe.validators.string_formatter_validators import OverridesStringFormatterValidator
|
from ytdl_subscribe.validators.string_formatter_validators import OverridesStringFormatterValidator
|
||||||
|
|
||||||
|
|
||||||
class OutputDirectoryNfoTagsValidator(PluginValidator):
|
class OutputDirectoryNfoTagsOptions(PluginOptions):
|
||||||
"""
|
"""
|
||||||
Validates config values for the output directory nfo tags plugin.
|
Validates config values for the output directory nfo tags plugin.
|
||||||
"""
|
"""
|
||||||
|
|
@ -28,7 +28,9 @@ class OutputDirectoryNfoTagsValidator(PluginValidator):
|
||||||
self.tags = self._validate_key(key="tags", validator=OverridesDictFormatterValidator)
|
self.tags = self._validate_key(key="tags", validator=OverridesDictFormatterValidator)
|
||||||
|
|
||||||
|
|
||||||
class NfoTags(Plugin[OutputDirectoryNfoTagsValidator]):
|
class OutputDirectoryNfoTagsPlugin(Plugin[OutputDirectoryNfoTagsOptions]):
|
||||||
|
plugin_options_type = OutputDirectoryNfoTagsOptions
|
||||||
|
|
||||||
def post_process_subscription(self):
|
def post_process_subscription(self):
|
||||||
"""
|
"""
|
||||||
Creates an NFO file in the root of the output directory
|
Creates an NFO file in the root of the output directory
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
from abc import ABC
|
from abc import ABC
|
||||||
from typing import Generic
|
from typing import Generic
|
||||||
|
from typing import Type
|
||||||
from typing import TypeVar
|
from typing import TypeVar
|
||||||
from typing import final
|
from typing import final
|
||||||
|
|
||||||
|
|
@ -9,24 +10,26 @@ from ytdl_subscribe.validators.strict_dict_validator import StrictDictValidator
|
||||||
from ytdl_subscribe.ytdl_additions.enhanced_download_archive import EnhancedDownloadArchive
|
from ytdl_subscribe.ytdl_additions.enhanced_download_archive import EnhancedDownloadArchive
|
||||||
|
|
||||||
|
|
||||||
class PluginValidator(StrictDictValidator):
|
class PluginOptions(StrictDictValidator):
|
||||||
"""
|
"""
|
||||||
Class that defines the parameters to a plugin
|
Class that defines the parameters to a plugin
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
|
||||||
PluginValidatorT = TypeVar("PluginValidatorT", bound=PluginValidator)
|
PluginOptionsT = TypeVar("PluginOptionsT", bound=PluginOptions)
|
||||||
|
|
||||||
|
|
||||||
class Plugin(Generic[PluginValidatorT], ABC):
|
class Plugin(Generic[PluginOptionsT], ABC):
|
||||||
"""
|
"""
|
||||||
Class to define the new plugin functionality
|
Class to define the new plugin functionality
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
plugin_options_type: Type[PluginOptionsT] = NotImplemented
|
||||||
|
|
||||||
@final
|
@final
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
plugin_options: PluginValidatorT,
|
plugin_options: PluginOptionsT,
|
||||||
output_directory: str,
|
output_directory: str,
|
||||||
overrides: Overrides,
|
overrides: Overrides,
|
||||||
enhanced_download_archive: EnhancedDownloadArchive,
|
enhanced_download_archive: EnhancedDownloadArchive,
|
||||||
|
|
|
||||||
|
|
@ -1,32 +1,29 @@
|
||||||
import contextlib
|
import contextlib
|
||||||
import os
|
import os
|
||||||
import shutil
|
import shutil
|
||||||
from abc import ABC
|
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from shutil import copyfile
|
from shutil import copyfile
|
||||||
from typing import Dict
|
from typing import Dict
|
||||||
from typing import Generic
|
|
||||||
from typing import List
|
from typing import List
|
||||||
from typing import Optional
|
from typing import Optional
|
||||||
|
from typing import Tuple
|
||||||
from typing import Type
|
from typing import Type
|
||||||
from typing import TypeVar
|
|
||||||
|
|
||||||
from ytdl_subscribe.config.config_file import ConfigOptions
|
from ytdl_subscribe.config.config_file import ConfigOptions
|
||||||
from ytdl_subscribe.config.preset import OutputOptions
|
from ytdl_subscribe.config.preset import OutputOptions
|
||||||
from ytdl_subscribe.config.preset import Overrides
|
from ytdl_subscribe.config.preset import Overrides
|
||||||
from ytdl_subscribe.config.preset import PresetValidator
|
from ytdl_subscribe.config.preset import PresetValidator
|
||||||
|
from ytdl_subscribe.config.preset import YTDLOptions
|
||||||
from ytdl_subscribe.downloaders.downloader import Downloader
|
from ytdl_subscribe.downloaders.downloader import Downloader
|
||||||
from ytdl_subscribe.downloaders.downloader import DownloaderValidator
|
from ytdl_subscribe.downloaders.downloader import DownloaderValidator
|
||||||
from ytdl_subscribe.entries.entry import Entry
|
from ytdl_subscribe.entries.entry import Entry
|
||||||
|
from ytdl_subscribe.plugins.plugin import Plugin
|
||||||
|
from ytdl_subscribe.plugins.plugin import PluginOptions
|
||||||
from ytdl_subscribe.validators.date_range_validator import DownloadDateRangeSource
|
from ytdl_subscribe.validators.date_range_validator import DownloadDateRangeSource
|
||||||
from ytdl_subscribe.ytdl_additions.enhanced_download_archive import EnhancedDownloadArchive
|
from ytdl_subscribe.ytdl_additions.enhanced_download_archive import EnhancedDownloadArchive
|
||||||
|
|
||||||
SourceT = TypeVar("SourceT", bound=DownloaderValidator)
|
|
||||||
EntryT = TypeVar("EntryT", bound=Entry)
|
|
||||||
DownloaderT = TypeVar("DownloaderT", bound=Downloader)
|
|
||||||
|
|
||||||
|
class Subscription:
|
||||||
class Subscription(Generic[SourceT, EntryT], ABC):
|
|
||||||
"""
|
"""
|
||||||
Subscription classes are the 'controllers' that perform...
|
Subscription classes are the 'controllers' that perform...
|
||||||
|
|
||||||
|
|
@ -65,16 +62,20 @@ class Subscription(Generic[SourceT, EntryT], ABC):
|
||||||
)
|
)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def entry_type(self) -> Type[EntryT]:
|
def downloader_class(self) -> Type[Downloader]:
|
||||||
"""
|
return self.__preset_options.downloader
|
||||||
:return: The Entry type this subscription uses to represent downloaded media
|
|
||||||
"""
|
|
||||||
return EntryT.__class__
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def source_options(self) -> SourceT:
|
def downloader_options(self) -> DownloaderValidator:
|
||||||
"""Returns the source options defined for this subscription"""
|
return self.__preset_options.downloader_options
|
||||||
return self.__preset_options.subscription_source
|
|
||||||
|
@property
|
||||||
|
def plugins(self) -> List[Tuple[Type[Plugin], PluginOptions]]:
|
||||||
|
return self.__preset_options.plugins
|
||||||
|
|
||||||
|
@property
|
||||||
|
def ytdl_options(self) -> YTDLOptions:
|
||||||
|
return self.__preset_options.ytdl_options
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def output_options(self) -> OutputOptions:
|
def output_options(self) -> OutputOptions:
|
||||||
|
|
@ -98,21 +99,6 @@ class Subscription(Generic[SourceT, EntryT], ABC):
|
||||||
"""
|
"""
|
||||||
return self.overrides.apply_formatter(formatter=self.output_options.output_directory)
|
return self.overrides.apply_formatter(formatter=self.output_options.output_directory)
|
||||||
|
|
||||||
def get_downloader(
|
|
||||||
self, downloader_type: Type[DownloaderT], source_ytdl_options: Optional[Dict] = None
|
|
||||||
) -> DownloaderT:
|
|
||||||
"""Returns the downloader that will be used to download media for this subscription"""
|
|
||||||
# if source_ytdl_options are present, override the ytdl_options with them
|
|
||||||
ytdl_options = self.__preset_options.ytdl_options.dict
|
|
||||||
if source_ytdl_options:
|
|
||||||
ytdl_options = dict(ytdl_options, **source_ytdl_options)
|
|
||||||
|
|
||||||
return downloader_type(
|
|
||||||
working_directory=self.working_directory,
|
|
||||||
ytdl_options=ytdl_options,
|
|
||||||
download_archive_file_name=self._enhanced_download_archive.archive_file_name,
|
|
||||||
)
|
|
||||||
|
|
||||||
def _copy_file_to_output_directory(self, source_file_path: str, output_file_name: str):
|
def _copy_file_to_output_directory(self, source_file_path: str, output_file_name: str):
|
||||||
destination_file_path = Path(self.output_directory) / Path(output_file_name)
|
destination_file_path = Path(self.output_directory) / Path(output_file_name)
|
||||||
os.makedirs(os.path.dirname(destination_file_path), exist_ok=True)
|
os.makedirs(os.path.dirname(destination_file_path), exist_ok=True)
|
||||||
|
|
@ -157,42 +143,49 @@ class Subscription(Generic[SourceT, EntryT], ABC):
|
||||||
|
|
||||||
yield
|
yield
|
||||||
|
|
||||||
date_range = None
|
# TODO: add date range in output options
|
||||||
if isinstance(self.source_options, DownloadDateRangeSource):
|
# date_range = None
|
||||||
date_range = self.source_options.get_date_range()
|
# if isinstance(self.source_options, DownloadDateRangeSource):
|
||||||
|
# date_range = self.source_options.get_date_range()
|
||||||
|
#
|
||||||
|
# if date_range and self.output_options.maintain_stale_file_deletion:
|
||||||
|
# self._enhanced_download_archive.remove_stale_files(date_range=date_range)
|
||||||
|
#
|
||||||
|
# self._enhanced_download_archive.save_download_archive()
|
||||||
|
|
||||||
if date_range and self.output_options.maintain_stale_file_deletion:
|
def _initialize_plugins(self) -> List[Plugin]:
|
||||||
self._enhanced_download_archive.remove_stale_files(date_range=date_range)
|
plugins: List[Plugin] = []
|
||||||
|
for plugin_type, plugin_options in self.plugins:
|
||||||
|
plugin = plugin_type(
|
||||||
|
plugin_options=plugin_options,
|
||||||
|
output_directory=self.output_directory,
|
||||||
|
overrides=self.overrides,
|
||||||
|
enhanced_download_archive=self._enhanced_download_archive,
|
||||||
|
)
|
||||||
|
|
||||||
self._enhanced_download_archive.save_download_archive()
|
plugins.append(plugin)
|
||||||
|
|
||||||
|
return plugins
|
||||||
|
|
||||||
def download(self):
|
def download(self):
|
||||||
"""
|
"""
|
||||||
Performs the subscription download.
|
Performs the subscription download.
|
||||||
"""
|
"""
|
||||||
|
plugins = self._initialize_plugins()
|
||||||
with self._prepare_working_directory(), self._maintain_archive_file():
|
with self._prepare_working_directory(), self._maintain_archive_file():
|
||||||
entries = self._extract_info()
|
downloader = self.downloader_class(
|
||||||
|
working_directory=self.working_directory,
|
||||||
|
download_options=self.downloader_options,
|
||||||
|
ytdl_options=self.ytdl_options.dict,
|
||||||
|
download_archive_file_name=self._enhanced_download_archive.archive_file_name,
|
||||||
|
)
|
||||||
|
|
||||||
|
entries = downloader.download()
|
||||||
|
for plugin in plugins:
|
||||||
|
for entry in entries:
|
||||||
|
plugin.post_process_entry(entry)
|
||||||
|
|
||||||
|
plugin.post_process_subscription()
|
||||||
|
|
||||||
for entry in entries:
|
for entry in entries:
|
||||||
self.post_process_entry(entry)
|
self._copy_entry_files_to_output_directory(entry=entry)
|
||||||
|
|
||||||
def _extract_info(self) -> List[EntryT]:
|
|
||||||
"""
|
|
||||||
Extracts only the info of the source, does not download it
|
|
||||||
"""
|
|
||||||
raise NotImplementedError("Each source needs to implement how it extracts info")
|
|
||||||
|
|
||||||
def post_process_entry(self, entry: Entry) -> None:
|
|
||||||
"""
|
|
||||||
After downloading an entry to the working directory, perform all post-processing, which
|
|
||||||
includes:
|
|
||||||
|
|
||||||
* Adding metadata to the entry file itself (music tags)
|
|
||||||
* Moving the entry file + thumbnail to the output directory with its formatted name
|
|
||||||
* Creating new metadata files (NFO) to reside alongside the entry files
|
|
||||||
|
|
||||||
:param entry: The entry to post-process
|
|
||||||
"""
|
|
||||||
# before move
|
|
||||||
self._copy_entry_files_to_output_directory(entry=entry)
|
|
||||||
|
|
||||||
# after move
|
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue