From 017db953bf6285ee2f669c5ac87ecabb0df0d20b Mon Sep 17 00:00:00 2001 From: Jesse Bannon Date: Mon, 1 Apr 2024 04:24:41 -0700 Subject: [PATCH] [FEATURE] Automatically handle playlists ordered in reverse (#948) Playlists have always been a pain-point with ytdl-sub. If an author adds new videos to the end of a playlist, as opposed to the front, it breaks ytdl-sub's intuition of incremental scraping by breaking on the first (oldest) video. This update now makes it possible to handle this in the prebuilt TV Show presets: - Add each URL variable (`url`, `url2`, ...) into the `download` portion of the subscription twice - First definition is what we all know and use, simply scrapes first-to-last, then downloads last-to-first - Second definition does the following: - Check to see if a URL is a YouTube playlist URL, if so... - Set the field to download, but with modifications to scrape last-to-first, then download first-to-last - Otherwise... - Set the field to an empty string, which means ytdl-sub will skip it --- .../scripting/entry_variables.rst | 12 + .../scripting/static_variables.rst | 5 + src/ytdl_sub/config/overrides.py | 14 +- src/ytdl_sub/config/preset.py | 4 +- .../config/validators/variable_validation.py | 53 +-- .../info_json/info_json_downloader.py | 1 + src/ytdl_sub/downloaders/source_plugin.py | 2 +- src/ytdl_sub/downloaders/url/downloader.py | 156 ++++--- src/ytdl_sub/downloaders/url/validators.py | 20 +- .../entries/script/function_scripts.py | 1 + .../entries/script/variable_definitions.py | 20 + src/ytdl_sub/entries/script/variable_types.py | 8 + .../entries/variables/override_variables.py | 55 ++- .../prebuilt_presets/helpers/url.yaml | 415 ++++++++++++++++++ .../helpers/url_bilateral.yaml | 21 + .../prebuilt_presets/tv_show/tv_show.yaml | 9 - .../tv_show/tv_show_by_date.yaml | 9 + .../tv_show/tv_show_collection.yaml | 368 ++++++++++++++++ .../subscriptions/base_subscription.py | 66 ++- .../subscriptions/subscription_download.py | 36 +- .../subscriptions/subscription_validators.py | 16 +- src/ytdl_sub/utils/scriptable.py | 5 +- tests/e2e/entries/__init__.py | 0 tests/e2e/entries/script/__init__.py | 0 .../entries/script/test_custom_functions.py | 22 + tests/e2e/youtube/test_playlist.py | 111 +++++ ...son_by_year__episode_by_download_index.txt | 13 + ...son_by_year__episode_by_download_index.txt | 9 + ...son_by_year__episode_by_download_index.txt | 13 + ...son_by_year__episode_by_download_index.txt | 9 + ...on__episode_by_playlist_index_reversed.txt | 5 + ...on__episode_by_playlist_index_reversed.txt | 5 + ...on__episode_by_playlist_index_reversed.txt | 8 + ...on__episode_by_playlist_index_reversed.txt | 8 + ...on__episode_by_playlist_index_reversed.txt | 13 + ...on__episode_by_playlist_index_reversed.txt | 13 + ...son_by_year__episode_by_download_index.txt | 13 + ...son_by_year__episode_by_download_index.txt | 9 + ...son_by_year__episode_by_download_index.txt | 13 + ...son_by_year__episode_by_download_index.txt | 9 + ...on__episode_by_playlist_index_reversed.txt | 5 + ...on__episode_by_playlist_index_reversed.txt | 5 + ...on__episode_by_playlist_index_reversed.txt | 8 + ...on__episode_by_playlist_index_reversed.txt | 8 + ...on__episode_by_playlist_index_reversed.txt | 13 + ...on__episode_by_playlist_index_reversed.txt | 13 + ...son_by_year__episode_by_download_index.txt | 13 + ...son_by_year__episode_by_download_index.txt | 9 + ...son_by_year__episode_by_download_index.txt | 13 + ...son_by_year__episode_by_download_index.txt | 9 + ...on__episode_by_playlist_index_reversed.txt | 5 + ...on__episode_by_playlist_index_reversed.txt | 5 + ...on__episode_by_playlist_index_reversed.txt | 8 + ...on__episode_by_playlist_index_reversed.txt | 8 + ...on__episode_by_playlist_index_reversed.txt | 13 + ...on__episode_by_playlist_index_reversed.txt | 13 + .../test_playlist_bilateral_collection_p1.txt | 61 +++ .../test_playlist_bilateral_collection_p2.txt | 170 +++++++ .../youtube/test_playlist_bilateral_p1.txt | 56 +++ .../youtube/test_playlist_bilateral_p2.txt | 170 +++++++ tests/unit/config/test_config_file.py | 2 +- tests/unit/script/test_script.py | 1 + 62 files changed, 2015 insertions(+), 162 deletions(-) create mode 100644 src/ytdl_sub/prebuilt_presets/helpers/url_bilateral.yaml create mode 100644 tests/e2e/entries/__init__.py create mode 100644 tests/e2e/entries/script/__init__.py create mode 100644 tests/e2e/entries/script/test_custom_functions.py create mode 100644 tests/resources/transaction_log_summaries/youtube/test_playlist_bilateral_collection_p1.txt create mode 100644 tests/resources/transaction_log_summaries/youtube/test_playlist_bilateral_collection_p2.txt create mode 100644 tests/resources/transaction_log_summaries/youtube/test_playlist_bilateral_p1.txt create mode 100644 tests/resources/transaction_log_summaries/youtube/test_playlist_bilateral_p2.txt diff --git a/docs/source/config_reference/scripting/entry_variables.rst b/docs/source/config_reference/scripting/entry_variables.rst index 8aa85ca6..84247234 100644 --- a/docs/source/config_reference/scripting/entry_variables.rst +++ b/docs/source/config_reference/scripting/entry_variables.rst @@ -647,3 +647,15 @@ ytdl_sub_input_url :type: ``String`` :description: The input URL used in ytdl-sub to create this entry. + +ytdl_sub_input_url_count +~~~~~~~~~~~~~~~~~~~~~~~~ +:type: ``Integer`` +:description: + The total number of input URLs as defined in the subscription. + +ytdl_sub_input_url_index +~~~~~~~~~~~~~~~~~~~~~~~~ +:type: ``Integer`` +:description: + The index of the input URL as defined in the subscription, top-most being the 0th index. diff --git a/docs/source/config_reference/scripting/static_variables.rst b/docs/source/config_reference/scripting/static_variables.rst index e6e41527..2825edba 100644 --- a/docs/source/config_reference/scripting/static_variables.rst +++ b/docs/source/config_reference/scripting/static_variables.rst @@ -5,6 +5,11 @@ Static Variables Subscription Variables ---------------------- +subscription_has_download_archive +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +Returns True if the subscription has any entries recorded in a download archive. False +otherwise. + subscription_indent_i ~~~~~~~~~~~~~~~~~~~~~ For subscriptions in the form of diff --git a/src/ytdl_sub/config/overrides.py b/src/ytdl_sub/config/overrides.py index 3372e936..76092735 100644 --- a/src/ytdl_sub/config/overrides.py +++ b/src/ytdl_sub/config/overrides.py @@ -7,8 +7,8 @@ import mergedeep from ytdl_sub.entries.entry import Entry from ytdl_sub.entries.script.variable_definitions import VARIABLES +from ytdl_sub.entries.variables.override_variables import REQUIRED_OVERRIDE_VARIABLE_NAMES from ytdl_sub.entries.variables.override_variables import OverrideHelpers -from ytdl_sub.entries.variables.override_variables import SubscriptionVariables from ytdl_sub.script.parser import parse from ytdl_sub.script.script import Script from ytdl_sub.script.utils.exceptions import ScriptVariableNotResolved @@ -62,6 +62,7 @@ class Overrides(DictFormatterValidator, Scriptable): self.ensure_variable_name_valid(key) self.unresolvable.add(VARIABLES.entry_metadata.variable_name) + self.unresolvable.update(REQUIRED_OVERRIDE_VARIABLE_NAMES) def ensure_added_plugin_variable_valid(self, added_variable: str) -> bool: """ @@ -127,17 +128,10 @@ class Overrides(DictFormatterValidator, Scriptable): ) return ScriptUtils.add_sanitized_variables(initial_variables) - def initialize_script( - self, subscription_name: str, unresolved_variables: Set[str] - ) -> "Overrides": + def initialize_script(self, unresolved_variables: Set[str]) -> "Overrides": """ - Initialize the override script with override variables + any unresolved variables + Initialize the override script with any unresolved variables """ - self.script.add( - ScriptUtils.add_sanitized_variables( - {SubscriptionVariables.subscription_name(): subscription_name} - ) - ) self.script.add( self.initial_variables( unresolved_variables={ diff --git a/src/ytdl_sub/config/preset.py b/src/ytdl_sub/config/preset.py index 4fec587d..914ebc77 100644 --- a/src/ytdl_sub/config/preset.py +++ b/src/ytdl_sub/config/preset.py @@ -198,9 +198,7 @@ class Preset(_PresetShell): downloader_options=self.downloader_options, output_options=self.output_options, plugins=self.plugins, - ).initialize_overrides( - subscription_name=self.name, overrides=self.overrides - ).ensure_proper_usage() + ).initialize_preset_overrides(overrides=self.overrides).ensure_proper_usage() @property def name(self) -> str: diff --git a/src/ytdl_sub/config/validators/variable_validation.py b/src/ytdl_sub/config/validators/variable_validation.py index bdeb9fac..466762d9 100644 --- a/src/ytdl_sub/config/validators/variable_validation.py +++ b/src/ytdl_sub/config/validators/variable_validation.py @@ -13,13 +13,24 @@ from ytdl_sub.config.plugin.preset_plugins import PresetPlugins from ytdl_sub.config.preset_options import OutputOptions from ytdl_sub.config.validators.options import OptionsValidator from ytdl_sub.downloaders.url.validators import MultiUrlValidator -from ytdl_sub.entries.variables.override_variables import SubscriptionVariables +from ytdl_sub.entries.variables.override_variables import REQUIRED_OVERRIDE_VARIABLE_NAMES from ytdl_sub.script.script import Script from ytdl_sub.script.script import _is_function from ytdl_sub.utils.scriptable import BASE_SCRIPT from ytdl_sub.validators.string_formatter_validators import to_variable_dependency_format_string from ytdl_sub.validators.string_formatter_validators import validate_formatters +# Entry variables to mock during validation +_DUMMY_ENTRY_VARIABLES: Dict[str, str] = { + name: to_variable_dependency_format_string( + # pylint: disable=protected-access + script=BASE_SCRIPT, + parsed_format_string=BASE_SCRIPT._variables[name] + # pylint: enable=protected-access + ) + for name in BASE_SCRIPT.variable_names +} + def _add_dummy_variables(variables: Iterable[str]) -> Dict[str, str]: dummy_variables: Dict[str, str] = {} @@ -72,20 +83,7 @@ def _get_added_and_modified_variables( def _override_variables(overrides: Overrides) -> Set[str]: - return set(list(overrides.initial_variables().keys())) | { - SubscriptionVariables.subscription_name() - } - - -_DUMMY_ENTRY_VARIABLES: Dict[str, str] = { - name: to_variable_dependency_format_string( - # pylint: disable=protected-access - script=BASE_SCRIPT, - parsed_format_string=BASE_SCRIPT._variables[name] - # pylint: enable=protected-access - ) - for name in BASE_SCRIPT.variable_names -} + return set(list(overrides.initial_variables().keys())) class VariableValidation: @@ -103,13 +101,11 @@ class VariableValidation: self.resolved_variables: Set[str] = set() self.unresolved_variables: Set[str] = set() - def initialize_overrides( - self, subscription_name: str, overrides: Overrides - ) -> "VariableValidation": + def initialize_preset_overrides(self, overrides: Overrides) -> "VariableValidation": """ Do some gymnastics to initialize the Overrides script. """ - override_variables = _override_variables(overrides) + override_variables = set(list(overrides.initial_variables().keys())) # Set resolved variables as all entry + override variables # at this point to generate every possible added/modified variable @@ -145,9 +141,7 @@ class VariableValidation: # Initialize overrides with unresolved variables + modified variables to throw an error. # For modified variables, this is to prevent a resolve(update=True) to setting any # dependencies until it has been explicitly added - overrides = overrides.initialize_script( - subscription_name=subscription_name, unresolved_variables=self.unresolved_variables - ) + overrides = overrides.initialize_script(unresolved_variables=self.unresolved_variables) # copy the script and mock entry variables self.script = copy.deepcopy(overrides.script) @@ -162,7 +156,16 @@ class VariableValidation: def _update_script(self) -> None: _ = self.script.resolve(unresolvable=self.unresolved_variables, update=True) - def _add_variables(self, plugin_op: PluginOperation, options: OptionsValidator) -> Set[str]: + def _add_subscription_override_variables(self) -> None: + """ + Add dummy subscription variables for script validation + """ + self.resolved_variables |= REQUIRED_OVERRIDE_VARIABLE_NAMES + + def _add_variables(self, plugin_op: PluginOperation, options: OptionsValidator) -> None: + """ + Add dummy variables for script validation + """ added_variables = options.added_variables( resolved_variables=self.resolved_variables, unresolved_variables=self.unresolved_variables, @@ -175,14 +178,14 @@ class VariableValidation: self.resolved_variables |= resolved_variables self.unresolved_variables -= resolved_variables - return added_variables - def ensure_proper_usage(self) -> None: """ Validate variables resolve as plugins are executed, and return a mock script which contains actualized added variables from the plugins """ + self._add_variables(PluginOperation.DOWNLOADER, options=self.downloader_options) + self._add_subscription_override_variables() # Metadata variables to be added for plugin_options in PluginMapping.order_options_by( diff --git a/src/ytdl_sub/downloaders/info_json/info_json_downloader.py b/src/ytdl_sub/downloaders/info_json/info_json_downloader.py index 71f3c996..582962f5 100644 --- a/src/ytdl_sub/downloaders/info_json/info_json_downloader.py +++ b/src/ytdl_sub/downloaders/info_json/info_json_downloader.py @@ -118,6 +118,7 @@ class InfoJsonDownloader(SourcePlugin[InfoJsonDownloaderOptions]): ) entries.append(entry) + # TODO: MATCH A URL TO A URL_VALIDATOR !!! for entry in sorted(entries, key=lambda ent: ent.get(v.download_index, int)): # Remove each entry from the live download archive since it will get re-added # unless it is filtered diff --git a/src/ytdl_sub/downloaders/source_plugin.py b/src/ytdl_sub/downloaders/source_plugin.py index 167f67fd..f5357fa5 100644 --- a/src/ytdl_sub/downloaders/source_plugin.py +++ b/src/ytdl_sub/downloaders/source_plugin.py @@ -17,7 +17,7 @@ from ytdl_sub.entries.entry import Entry from ytdl_sub.ytdl_additions.enhanced_download_archive import EnhancedDownloadArchive -class SourcePluginExtension(Plugin[TOptionsValidator], ABC): +class SourcePluginExtension(Plugin[TOptionsValidator], Generic[TOptionsValidator], ABC): """ Plugins that get added automatically by using a downloader. Downloader options are the plugin options. diff --git a/src/ytdl_sub/downloaders/url/downloader.py b/src/ytdl_sub/downloaders/url/downloader.py index a0ceb535..8d50f1c3 100644 --- a/src/ytdl_sub/downloaders/url/downloader.py +++ b/src/ytdl_sub/downloaders/url/downloader.py @@ -25,6 +25,7 @@ from ytdl_sub.entries.script.variable_definitions import VARIABLES from ytdl_sub.entries.script.variable_definitions import VariableDefinitions from ytdl_sub.utils.file_handler import FileHandler from ytdl_sub.utils.logger import Logger +from ytdl_sub.utils.script import ScriptUtils from ytdl_sub.utils.thumbnail import ThumbnailTypes from ytdl_sub.utils.thumbnail import download_and_convert_url_thumbnail from ytdl_sub.utils.thumbnail import try_convert_download_thumbnail @@ -41,7 +42,30 @@ class URLDownloadState: self.entries_downloaded = 0 -class UrlDownloaderThumbnailPlugin(SourcePluginExtension): +class UrlDownloaderBasePluginExtension(SourcePluginExtension[MultiUrlValidator]): + def _match_entry_to_url_validator(self, entry: Entry) -> UrlValidator: + """ + Handle matching a URL to its original validator. This is for .info.json updates + when older entries have missing variables + """ + input_url_idx = entry.get(v.ytdl_sub_input_url_index, int) + entry_input_url = entry.get(v.ytdl_sub_input_url, str) + + if 0 <= input_url_idx < len(self.plugin_options.urls.list): + validator = self.plugin_options.urls.list[input_url_idx] + if self.overrides.apply_formatter(validator.url) == entry_input_url: + return validator + + # Match the first validator based on the URL, if one exists + for validator in self.plugin_options.urls.list: + if self.overrides.apply_formatter(validator.url) == entry_input_url: + return validator + + # Return the first validator if none exist + return self.plugin_options.urls.list[0] + + +class UrlDownloaderThumbnailPlugin(UrlDownloaderBasePluginExtension): def __init__( self, options: MultiUrlValidator, @@ -54,10 +78,6 @@ class UrlDownloaderThumbnailPlugin(SourcePluginExtension): enhanced_download_archive=enhanced_download_archive, ) self._thumbnails_downloaded: Set[str] = set() - self._collection_url_mapping: Dict[str, UrlValidator] = { - self.overrides.apply_formatter(collection_url.url): collection_url - for collection_url in options.urls.list - } def _download_parent_thumbnails( self, @@ -136,15 +156,14 @@ class UrlDownloaderThumbnailPlugin(SourcePluginExtension): if not self.is_dry_run: try_convert_download_thumbnail(entry=entry) - if (input_url := entry.get(v.ytdl_sub_input_url, str)) in self._collection_url_mapping: - self._download_url_thumbnails( - collection_url=self._collection_url_mapping[input_url], - entry=entry, - ) + self._download_url_thumbnails( + collection_url=self._match_entry_to_url_validator(entry=entry), + entry=entry, + ) return entry -class UrlDownloaderCollectionVariablePlugin(SourcePluginExtension): +class UrlDownloaderCollectionVariablePlugin(UrlDownloaderBasePluginExtension): def __init__( self, options: MultiUrlValidator, @@ -157,25 +176,12 @@ class UrlDownloaderCollectionVariablePlugin(SourcePluginExtension): enhanced_download_archive=enhanced_download_archive, ) self._thumbnails_downloaded: Set[str] = set() - self._collection_url_mapping: Dict[str, UrlValidator] = { - self.overrides.apply_formatter(collection_url.url): collection_url - for collection_url in options.urls.list - } def modify_entry_metadata(self, entry: Entry) -> Optional[Entry]: """ Add collection variables to the entry """ - # COLLECTION_URL is a recent variable that may not exist for old entries when updating. - # Try to use source_webpage_url if it does not exist - entry_collection_url = entry.get(v.ytdl_sub_input_url, str) - - # If the collection URL cannot find its mapping, use the last URL - collection_url = ( - self._collection_url_mapping.get(entry_collection_url) - or list(self._collection_url_mapping.values())[-1] - ) - + collection_url = self._match_entry_to_url_validator(entry=entry) entry.add(collection_url.variables.dict_with_format_strings) return entry @@ -232,8 +238,7 @@ class MultiUrlDownloader(SourcePlugin[MultiUrlValidator]): self._downloaded_entries: Set[str] = set() self._url_state: Optional[URLDownloadState] = None - @property - def download_ytdl_options(self) -> Dict: + def download_ytdl_options(self, url_idx: Optional[int] = None) -> Dict: """ Returns ------- @@ -242,19 +247,26 @@ class MultiUrlDownloader(SourcePlugin[MultiUrlValidator]): return ( self._download_ytdl_options_builder.clone() .add(self.ytdl_option_defaults(), before=True) + .add( + self.plugin_options.urls.list[url_idx].ytdl_options.dict + if url_idx is not None + else None, + before=True, + ) .to_dict() ) - @property - def metadata_ytdl_options(self) -> Dict: + def metadata_ytdl_options(self, ytdl_option_overrides: Dict) -> Dict: """ Returns ------- YTDL options dict for fetching metadata """ + return ( self._metadata_ytdl_options_builder.clone() .add(self.ytdl_option_defaults(), before=True) + .add(ytdl_option_overrides, before=True) .to_dict() ) @@ -265,7 +277,7 @@ class MultiUrlDownloader(SourcePlugin[MultiUrlValidator]): ------- True if dry-run is enabled. False otherwise. """ - return self.download_ytdl_options.get("skip_download", False) + return self.download_ytdl_options().get("skip_download", False) @property def is_entry_thumbnails_enabled(self) -> bool: @@ -274,7 +286,7 @@ class MultiUrlDownloader(SourcePlugin[MultiUrlValidator]): ------- True if entry thumbnails should be downloaded. False otherwise. """ - return self.download_ytdl_options.get("writethumbnail", False) + return self.download_ytdl_options().get("writethumbnail", False) ############################################################################################### # DOWNLOAD FUNCTIONS @@ -301,7 +313,7 @@ class MultiUrlDownloader(SourcePlugin[MultiUrlValidator]): clear_info_json_files Whether to delete info.json files after yield """ - archive_path = self.download_ytdl_options.get("download_archive", "") + archive_path = self.download_ytdl_options().get("download_archive", "") backup_archive_path = f"{archive_path}.backup" # If archive path exists, maintain download archive is enable @@ -336,7 +348,9 @@ class MultiUrlDownloader(SourcePlugin[MultiUrlValidator]): def _extract_entry_info_with_retry(self, entry: Entry) -> Entry: download_entry_dict = YTDLP.extract_info_with_retry( - ytdl_options_overrides=self.download_ytdl_options, + ytdl_options_overrides=self.download_ytdl_options( + url_idx=entry.get(v.ytdl_sub_input_url_index, int) + ), is_downloaded_fn=None if self.is_dry_run else entry.is_downloaded, is_thumbnail_downloaded_fn=None if (self.is_dry_run or not self.is_entry_thumbnails_enabled) @@ -352,27 +366,29 @@ class MultiUrlDownloader(SourcePlugin[MultiUrlValidator]): self, entries: List[Entry], download_reversed: bool ) -> Iterator[Entry]: # Iterate a list of entries, and delete the entries after yielding - indices = list(range(len(entries))) + entries_to_iter: List[Optional[Entry]] = entries + + indices = list(range(len(entries_to_iter))) if download_reversed: indices = reversed(indices) for idx in indices: self._url_state.entries_downloaded += 1 - if self._is_downloaded(entries[idx]): + if self._is_downloaded(entries_to_iter[idx]): download_logger.info( "Already downloaded entry %d/%d: %s", self._url_state.entries_downloaded, self._url_state.entries_total, - entries[idx].title, + entries_to_iter[idx].title, ) - del entries[idx] + entries_to_iter[idx] = None continue - yield entries[idx] - self._mark_downloaded(entries[idx]) + yield entries_to_iter[idx] + self._mark_downloaded(entries_to_iter[idx]) - del entries[idx] + entries_to_iter[idx] = None def _iterate_parent_entry( self, parent: EntryParent, download_reversed: bool @@ -390,7 +406,7 @@ class MultiUrlDownloader(SourcePlugin[MultiUrlValidator]): yield entry_child def _download_url_metadata( - self, url: str, include_sibling_metadata: bool + self, url: str, include_sibling_metadata: bool, ytdl_options_overrides: Dict ) -> Tuple[List[EntryParent], List[Entry]]: """ Downloads only info.json files and forms EntryParent trees @@ -398,7 +414,7 @@ class MultiUrlDownloader(SourcePlugin[MultiUrlValidator]): with self._separate_download_archives(): entry_dicts = YTDLP.extract_info_via_info_json( working_directory=self.working_directory, - ytdl_options_overrides=self.metadata_ytdl_options, + ytdl_options_overrides=ytdl_options_overrides, log_prefix_on_info_json_dl="Downloading metadata for", url=url, ) @@ -439,34 +455,50 @@ class MultiUrlDownloader(SourcePlugin[MultiUrlValidator]): ): yield orphan + def _download_metadata(self, url: str, validator: UrlValidator) -> Iterable[Entry]: + metadata_ytdl_options = self.metadata_ytdl_options( + ytdl_option_overrides=validator.ytdl_options.dict + ) + download_reversed = ScriptUtils.bool_formatter_output( + self.overrides.apply_formatter(validator.download_reverse) + ) + + parents, orphan_entries = self._download_url_metadata( + url=url, + include_sibling_metadata=validator.include_sibling_metadata, + ytdl_options_overrides=metadata_ytdl_options, + ) + + # TODO: Encapsulate this logic into its own class + self._url_state = URLDownloadState( + entries_total=sum(parent.num_children() for parent in parents) + len(orphan_entries) + ) + + download_logger.info("Beginning downloads for %s", url) + for entry in self._iterate_entries( + parents=parents, + orphans=orphan_entries, + download_reversed=download_reversed, + ): + yield entry + def download_metadata(self) -> Iterable[Entry]: """The function to perform the download of all media entries""" # download the bottom-most urls first since they are top-priority - for collection_url in reversed(self.collection.urls.list): + for idx, url_validator in reversed(list(enumerate(self.collection.urls.list))): # URLs can be empty. If they are, then skip - if not (url := self.overrides.apply_formatter(collection_url.url)): + if not (url := self.overrides.apply_formatter(url_validator.url)): continue - parents, orphan_entries = self._download_url_metadata( - url=url, include_sibling_metadata=collection_url.include_sibling_metadata - ) - - # TODO: Encapsulate this logic into its own class - self._url_state = URLDownloadState( - entries_total=sum(parent.num_children() for parent in parents) + len(orphan_entries) - ) - - download_logger.info( - "Beginning downloads for %s", self.overrides.apply_formatter(collection_url.url) - ) - for entry in self._iterate_entries( - parents=parents, - orphans=orphan_entries, - download_reversed=collection_url.download_reverse, - ): + for entry in self._download_metadata(url=url, validator=url_validator): entry.initialize_script(self.overrides).add( - {v.ytdl_sub_input_url: self.overrides.apply_formatter(collection_url.url)} + { + v.ytdl_sub_input_url: url, + v.ytdl_sub_input_url_index: idx, + v.ytdl_sub_input_url_count: len(self.collection.urls.list), + } ) + yield entry def download(self, entry: Entry) -> Optional[Entry]: diff --git a/src/ytdl_sub/downloaders/url/validators.py b/src/ytdl_sub/downloaders/url/validators.py index 53330306..61d794dd 100644 --- a/src/ytdl_sub/downloaders/url/validators.py +++ b/src/ytdl_sub/downloaders/url/validators.py @@ -4,10 +4,12 @@ from typing import Optional from typing import Set from ytdl_sub.config.plugin.plugin_operation import PluginOperation +from ytdl_sub.config.preset_options import YTDLOptions from ytdl_sub.config.validators.options import OptionsValidator from ytdl_sub.script.parser import parse 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 OverridesBooleanFormatterValidator from ytdl_sub.validators.string_formatter_validators import OverridesStringFormatterValidator from ytdl_sub.validators.string_formatter_validators import StringFormatterValidator from ytdl_sub.validators.validators import BoolValidator @@ -49,6 +51,7 @@ class UrlValidator(StrictDictValidator): "source_thumbnails", "playlist_thumbnails", "download_reverse", + "ytdl_options", "include_sibling_metadata", } @@ -77,7 +80,10 @@ class UrlValidator(StrictDictValidator): key="playlist_thumbnails", validator=UrlThumbnailListValidator, default=[] ) self._download_reverse = self._validate_key( - key="download_reverse", validator=BoolValidator, default=True + key="download_reverse", validator=OverridesBooleanFormatterValidator, default="True" + ) + self._ytdl_options = self._validate_key( + key="ytdl_options", validator=YTDLOptions, default={} ) self._include_sibling_metadata = self._validate_key( key="include_sibling_metadata", validator=BoolValidator, default=False @@ -148,12 +154,20 @@ class UrlValidator(StrictDictValidator): return self._source_thumbnails @property - def download_reverse(self) -> bool: + def download_reverse(self) -> OverridesBooleanFormatterValidator: """ Optional. Whether to download entries in the reverse order of the metadata downloaded. Defaults to True. """ - return self._download_reverse.value + return self._download_reverse + + @property + def ytdl_options(self) -> YTDLOptions: + """ + Optional. ``ytdl_options`` that only apply to this URL. These take precedence + over the plugin ``ytdl_options``. + """ + return self._ytdl_options @property def include_sibling_metadata(self) -> bool: diff --git a/src/ytdl_sub/entries/script/function_scripts.py b/src/ytdl_sub/entries/script/function_scripts.py index 68019a00..58f950bb 100644 --- a/src/ytdl_sub/entries/script/function_scripts.py +++ b/src/ytdl_sub/entries/script/function_scripts.py @@ -5,6 +5,7 @@ from ytdl_sub.entries.script.variable_definitions import VariableDefinitions v: VariableDefinitions = VARIABLES +# TODO: Make this a proper class with docstrings CUSTOM_FUNCTION_SCRIPTS: Dict[str, str] = { ############################################################################################# # SIBLING GETTER diff --git a/src/ytdl_sub/entries/script/variable_definitions.py b/src/ytdl_sub/entries/script/variable_definitions.py index a24f0aa7..4fc9e29e 100644 --- a/src/ytdl_sub/entries/script/variable_definitions.py +++ b/src/ytdl_sub/entries/script/variable_definitions.py @@ -750,6 +750,24 @@ class YtdlSubVariableDefinitions(ABC): """ return StringVariable(variable_name="ytdl_sub_input_url", definition="{ %string('') }") + @cached_property + def ytdl_sub_input_url_index(self: "VariableDefinitions") -> IntegerVariable: + """ + :description: + The index of the input URL as defined in the subscription, top-most being the 0th index. + """ + # init as -1 so if prior downloaded entries are known when they do not have this value + # in their .info.json + return IntegerVariable(variable_name="ytdl_sub_input_url_index", definition="{ %int(-1) }") + + @cached_property + def ytdl_sub_input_url_count(self: "VariableDefinitions") -> IntegerVariable: + """ + :description: + The total number of input URLs as defined in the subscription. + """ + return IntegerVariable(variable_name="ytdl_sub_input_url_count", definition="{ %int(0) }") + @cached_property def download_index(self: "VariableDefinitions") -> IntegerVariable: """ @@ -1100,6 +1118,8 @@ class VariableDefinitions( self.chapters, self.sponsorblock_chapters, self.ytdl_sub_input_url, + self.ytdl_sub_input_url_index, + self.ytdl_sub_input_url_count, } @cache diff --git a/src/ytdl_sub/entries/script/variable_types.py b/src/ytdl_sub/entries/script/variable_types.py index 7cca58cd..214d8a7c 100644 --- a/src/ytdl_sub/entries/script/variable_types.py +++ b/src/ytdl_sub/entries/script/variable_types.py @@ -9,6 +9,7 @@ from typing import TypeVar from ytdl_sub.script.types.array import Array from ytdl_sub.script.types.map import Map +from ytdl_sub.script.types.resolvable import Boolean from ytdl_sub.script.types.resolvable import Integer from ytdl_sub.script.types.resolvable import String @@ -63,6 +64,13 @@ class Variable(ABC): """ +@dataclass(frozen=True) +class BooleanVariable(Variable): + @classmethod + def human_readable_type(cls) -> str: + return Boolean.__name__ + + @dataclass(frozen=True) class StringVariable(Variable): @classmethod diff --git a/src/ytdl_sub/entries/variables/override_variables.py b/src/ytdl_sub/entries/variables/override_variables.py index 007aaf2b..68ec72d4 100644 --- a/src/ytdl_sub/entries/variables/override_variables.py +++ b/src/ytdl_sub/entries/variables/override_variables.py @@ -1,5 +1,12 @@ +from typing import Dict +from typing import Set + from ytdl_sub.entries.script.function_scripts import CUSTOM_FUNCTION_SCRIPTS from ytdl_sub.entries.script.variable_definitions import VARIABLE_SCRIPTS +from ytdl_sub.entries.script.variable_types import BooleanVariable +from ytdl_sub.entries.script.variable_types import MapVariable +from ytdl_sub.entries.script.variable_types import StringVariable +from ytdl_sub.entries.script.variable_types import Variable from ytdl_sub.script.functions import Functions from ytdl_sub.script.utils.name_validation import is_valid_name @@ -9,15 +16,15 @@ SUBSCRIPTION_ARRAY = "subscription_array" class SubscriptionVariables: @staticmethod - def subscription_name() -> str: + def subscription_name() -> StringVariable: """ Name of the subscription. For subscriptions types that use a prefix (``~``, ``+``), the prefix and all whitespace afterwards is stripped from the subscription name. """ - return "subscription_name" + return StringVariable(variable_name="subscription_name", definition="{ %string('') }") @staticmethod - def subscription_value() -> str: + def subscription_value() -> StringVariable: """ For subscriptions in the form of @@ -27,10 +34,10 @@ class SubscriptionVariables: ``subscription_value`` gets set to ``https://...``. """ - return "subscription_value" + return StringVariable(variable_name="subscription_value", definition="{ %string('') }") @staticmethod - def subscription_indent_i(index: int) -> str: + def subscription_indent_i(index: int) -> StringVariable: """ For subscriptions in the form of @@ -43,10 +50,12 @@ class SubscriptionVariables: ``subscription_indent_1`` and ``subscription_indent_2`` get set to ``Indent Value 1`` and ``Indent Value 2``. """ - return f"subscription_indent_{index + 1}" + return StringVariable( + variable_name=f"subscription_indent_{index + 1}", definition="{ %string('') }" + ) @staticmethod - def subscription_value_i(index: int) -> str: + def subscription_value_i(index: int) -> StringVariable: """ For subscriptions in the form of @@ -60,10 +69,12 @@ class SubscriptionVariables: and ``https://url2.com/...``. Note that ``subscription_value_1`` also gets set to ``subscription_value``. """ - return f"subscription_value_{index + 1}" + return StringVariable( + variable_name=f"subscription_value_{index + 1}", definition="{ %string('') }" + ) @staticmethod - def subscription_map() -> str: + def subscription_map() -> MapVariable: """ For subscriptions in the form of @@ -89,7 +100,17 @@ class SubscriptionVariables: ] } """ - return "subscription_map" + return MapVariable(variable_name="subscription_map", definition="{ {} }") + + @staticmethod + def subscription_has_download_archive() -> BooleanVariable: + """ + Returns True if the subscription has any entries recorded in a download archive. False + otherwise. + """ + return BooleanVariable( + variable_name="subscription_has_download_archive", definition="{ %bool(True) }" + ) class OverrideHelpers: @@ -124,3 +145,17 @@ class OverrideHelpers: return is_valid_name(name=name[1:]) return is_valid_name(name=name) + + +REQUIRED_OVERRIDE_VARIABLES: Set[Variable] = { + SubscriptionVariables.subscription_name(), + SubscriptionVariables.subscription_has_download_archive(), +} + +REQUIRED_OVERRIDE_VARIABLE_DEFINITIONS: Dict[str, str] = { + var.variable_name: var.definition for var in REQUIRED_OVERRIDE_VARIABLES +} + +REQUIRED_OVERRIDE_VARIABLE_NAMES: Set[str] = { + var.variable_name for var in REQUIRED_OVERRIDE_VARIABLES +} diff --git a/src/ytdl_sub/prebuilt_presets/helpers/url.yaml b/src/ytdl_sub/prebuilt_presets/helpers/url.yaml index 787e0713..3a9726dd 100644 --- a/src/ytdl_sub/prebuilt_presets/helpers/url.yaml +++ b/src/ytdl_sub/prebuilt_presets/helpers/url.yaml @@ -330,3 +330,418 @@ presets: url98: "{subscription_value_98}" url99: "{subscription_value_99}" url100: "{subscription_value_100}" + + + # multi-url with bilateral scraping built into it via + # inspection of the URL and conditionally adding another + # ytdl-sub url download with the scraping and download reversed + _multi_url_bilateral_inner: + preset: + - "_url_bilateral_overrides" + + download: + - url: "{%bilateral_url(url) }" + download_reverse: False + ytdl_options: + playlist_items: "-1:0:-1" + - url: "{ %bilateral_url(url2) }" + download_reverse: False + ytdl_options: + playlist_items: "-1:0:-1" + - url: "{ %bilateral_url(url3) }" + download_reverse: False + ytdl_options: + playlist_items: "-1:0:-1" + - url: "{ %bilateral_url(url4) }" + download_reverse: False + ytdl_options: + playlist_items: "-1:0:-1" + - url: "{ %bilateral_url(url5) }" + download_reverse: False + ytdl_options: + playlist_items: "-1:0:-1" + - url: "{ %bilateral_url(url6) }" + download_reverse: False + ytdl_options: + playlist_items: "-1:0:-1" + - url: "{ %bilateral_url(url7) }" + download_reverse: False + ytdl_options: + playlist_items: "-1:0:-1" + - url: "{ %bilateral_url(url8) }" + download_reverse: False + ytdl_options: + playlist_items: "-1:0:-1" + - url: "{ %bilateral_url(url9) }" + download_reverse: False + ytdl_options: + playlist_items: "-1:0:-1" + - url: "{ %bilateral_url(url10) }" + download_reverse: False + ytdl_options: + playlist_items: "-1:0:-1" + - url: "{ %bilateral_url(url11) }" + download_reverse: False + ytdl_options: + playlist_items: "-1:0:-1" + - url: "{ %bilateral_url(url12) }" + download_reverse: False + ytdl_options: + playlist_items: "-1:0:-1" + - url: "{ %bilateral_url(url13) }" + download_reverse: False + ytdl_options: + playlist_items: "-1:0:-1" + - url: "{ %bilateral_url(url14) }" + download_reverse: False + ytdl_options: + playlist_items: "-1:0:-1" + - url: "{ %bilateral_url(url15) }" + download_reverse: False + ytdl_options: + playlist_items: "-1:0:-1" + - url: "{ %bilateral_url(url16) }" + download_reverse: False + ytdl_options: + playlist_items: "-1:0:-1" + - url: "{ %bilateral_url(url17) }" + download_reverse: False + ytdl_options: + playlist_items: "-1:0:-1" + - url: "{ %bilateral_url(url18) }" + download_reverse: False + ytdl_options: + playlist_items: "-1:0:-1" + - url: "{ %bilateral_url(url19) }" + download_reverse: False + ytdl_options: + playlist_items: "-1:0:-1" + - url: "{ %bilateral_url(url20) }" + download_reverse: False + ytdl_options: + playlist_items: "-1:0:-1" + - url: "{ %bilateral_url(url21) }" + download_reverse: False + ytdl_options: + playlist_items: "-1:0:-1" + - url: "{ %bilateral_url(url22) }" + download_reverse: False + ytdl_options: + playlist_items: "-1:0:-1" + - url: "{ %bilateral_url(url23) }" + download_reverse: False + ytdl_options: + playlist_items: "-1:0:-1" + - url: "{ %bilateral_url(url24) }" + download_reverse: False + ytdl_options: + playlist_items: "-1:0:-1" + - url: "{ %bilateral_url(url25) }" + download_reverse: False + ytdl_options: + playlist_items: "-1:0:-1" + - url: "{ %bilateral_url(url26) }" + download_reverse: False + ytdl_options: + playlist_items: "-1:0:-1" + - url: "{ %bilateral_url(url27) }" + download_reverse: False + ytdl_options: + playlist_items: "-1:0:-1" + - url: "{ %bilateral_url(url28) }" + download_reverse: False + ytdl_options: + playlist_items: "-1:0:-1" + - url: "{ %bilateral_url(url29) }" + download_reverse: False + ytdl_options: + playlist_items: "-1:0:-1" + - url: "{ %bilateral_url(url30) }" + download_reverse: False + ytdl_options: + playlist_items: "-1:0:-1" + - url: "{ %bilateral_url(url31) }" + download_reverse: False + ytdl_options: + playlist_items: "-1:0:-1" + - url: "{ %bilateral_url(url32) }" + download_reverse: False + ytdl_options: + playlist_items: "-1:0:-1" + - url: "{ %bilateral_url(url33) }" + download_reverse: False + ytdl_options: + playlist_items: "-1:0:-1" + - url: "{ %bilateral_url(url34) }" + download_reverse: False + ytdl_options: + playlist_items: "-1:0:-1" + - url: "{ %bilateral_url(url35) }" + download_reverse: False + ytdl_options: + playlist_items: "-1:0:-1" + - url: "{ %bilateral_url(url36) }" + download_reverse: False + ytdl_options: + playlist_items: "-1:0:-1" + - url: "{ %bilateral_url(url37) }" + download_reverse: False + ytdl_options: + playlist_items: "-1:0:-1" + - url: "{ %bilateral_url(url38) }" + download_reverse: False + ytdl_options: + playlist_items: "-1:0:-1" + - url: "{ %bilateral_url(url39) }" + download_reverse: False + ytdl_options: + playlist_items: "-1:0:-1" + - url: "{ %bilateral_url(url40) }" + download_reverse: False + ytdl_options: + playlist_items: "-1:0:-1" + - url: "{ %bilateral_url(url41) }" + download_reverse: False + ytdl_options: + playlist_items: "-1:0:-1" + - url: "{ %bilateral_url(url42) }" + download_reverse: False + ytdl_options: + playlist_items: "-1:0:-1" + - url: "{ %bilateral_url(url43) }" + download_reverse: False + ytdl_options: + playlist_items: "-1:0:-1" + - url: "{ %bilateral_url(url44) }" + download_reverse: False + ytdl_options: + playlist_items: "-1:0:-1" + - url: "{ %bilateral_url(url45) }" + download_reverse: False + ytdl_options: + playlist_items: "-1:0:-1" + - url: "{ %bilateral_url(url46) }" + download_reverse: False + ytdl_options: + playlist_items: "-1:0:-1" + - url: "{ %bilateral_url(url47) }" + download_reverse: False + ytdl_options: + playlist_items: "-1:0:-1" + - url: "{ %bilateral_url(url48) }" + download_reverse: False + ytdl_options: + playlist_items: "-1:0:-1" + - url: "{ %bilateral_url(url49) }" + download_reverse: False + ytdl_options: + playlist_items: "-1:0:-1" + - url: "{ %bilateral_url(url50) }" + download_reverse: False + ytdl_options: + playlist_items: "-1:0:-1" + - url: "{ %bilateral_url(url51) }" + download_reverse: False + ytdl_options: + playlist_items: "-1:0:-1" + - url: "{ %bilateral_url(url52) }" + download_reverse: False + ytdl_options: + playlist_items: "-1:0:-1" + - url: "{ %bilateral_url(url53) }" + download_reverse: False + ytdl_options: + playlist_items: "-1:0:-1" + - url: "{ %bilateral_url(url54) }" + download_reverse: False + ytdl_options: + playlist_items: "-1:0:-1" + - url: "{ %bilateral_url(url55) }" + download_reverse: False + ytdl_options: + playlist_items: "-1:0:-1" + - url: "{ %bilateral_url(url56) }" + download_reverse: False + ytdl_options: + playlist_items: "-1:0:-1" + - url: "{ %bilateral_url(url57) }" + download_reverse: False + ytdl_options: + playlist_items: "-1:0:-1" + - url: "{ %bilateral_url(url58) }" + download_reverse: False + ytdl_options: + playlist_items: "-1:0:-1" + - url: "{ %bilateral_url(url59) }" + download_reverse: False + ytdl_options: + playlist_items: "-1:0:-1" + - url: "{ %bilateral_url(url60) }" + download_reverse: False + ytdl_options: + playlist_items: "-1:0:-1" + - url: "{ %bilateral_url(url61) }" + download_reverse: False + ytdl_options: + playlist_items: "-1:0:-1" + - url: "{ %bilateral_url(url62) }" + download_reverse: False + ytdl_options: + playlist_items: "-1:0:-1" + - url: "{ %bilateral_url(url63) }" + download_reverse: False + ytdl_options: + playlist_items: "-1:0:-1" + - url: "{ %bilateral_url(url64) }" + download_reverse: False + ytdl_options: + playlist_items: "-1:0:-1" + - url: "{ %bilateral_url(url65) }" + download_reverse: False + ytdl_options: + playlist_items: "-1:0:-1" + - url: "{ %bilateral_url(url66) }" + download_reverse: False + ytdl_options: + playlist_items: "-1:0:-1" + - url: "{ %bilateral_url(url67) }" + download_reverse: False + ytdl_options: + playlist_items: "-1:0:-1" + - url: "{ %bilateral_url(url68) }" + download_reverse: False + ytdl_options: + playlist_items: "-1:0:-1" + - url: "{ %bilateral_url(url69) }" + download_reverse: False + ytdl_options: + playlist_items: "-1:0:-1" + - url: "{ %bilateral_url(url70) }" + download_reverse: False + ytdl_options: + playlist_items: "-1:0:-1" + - url: "{ %bilateral_url(url71) }" + download_reverse: False + ytdl_options: + playlist_items: "-1:0:-1" + - url: "{ %bilateral_url(url72) }" + download_reverse: False + ytdl_options: + playlist_items: "-1:0:-1" + - url: "{ %bilateral_url(url73) }" + download_reverse: False + ytdl_options: + playlist_items: "-1:0:-1" + - url: "{ %bilateral_url(url74) }" + download_reverse: False + ytdl_options: + playlist_items: "-1:0:-1" + - url: "{ %bilateral_url(url75) }" + download_reverse: False + ytdl_options: + playlist_items: "-1:0:-1" + - url: "{ %bilateral_url(url76) }" + download_reverse: False + ytdl_options: + playlist_items: "-1:0:-1" + - url: "{ %bilateral_url(url77) }" + download_reverse: False + ytdl_options: + playlist_items: "-1:0:-1" + - url: "{ %bilateral_url(url78) }" + download_reverse: False + ytdl_options: + playlist_items: "-1:0:-1" + - url: "{ %bilateral_url(url79) }" + download_reverse: False + ytdl_options: + playlist_items: "-1:0:-1" + - url: "{ %bilateral_url(url80) }" + download_reverse: False + ytdl_options: + playlist_items: "-1:0:-1" + - url: "{ %bilateral_url(url81) }" + download_reverse: False + ytdl_options: + playlist_items: "-1:0:-1" + - url: "{ %bilateral_url(url82) }" + download_reverse: False + ytdl_options: + playlist_items: "-1:0:-1" + - url: "{ %bilateral_url(url83) }" + download_reverse: False + ytdl_options: + playlist_items: "-1:0:-1" + - url: "{ %bilateral_url(url84) }" + download_reverse: False + ytdl_options: + playlist_items: "-1:0:-1" + - url: "{ %bilateral_url(url85) }" + download_reverse: False + ytdl_options: + playlist_items: "-1:0:-1" + - url: "{ %bilateral_url(url86) }" + download_reverse: False + ytdl_options: + playlist_items: "-1:0:-1" + - url: "{ %bilateral_url(url87) }" + download_reverse: False + ytdl_options: + playlist_items: "-1:0:-1" + - url: "{ %bilateral_url(url88) }" + download_reverse: False + ytdl_options: + playlist_items: "-1:0:-1" + - url: "{ %bilateral_url(url89) }" + download_reverse: False + ytdl_options: + playlist_items: "-1:0:-1" + - url: "{ %bilateral_url(url90) }" + download_reverse: False + ytdl_options: + playlist_items: "-1:0:-1" + - url: "{ %bilateral_url(url91) }" + download_reverse: False + ytdl_options: + playlist_items: "-1:0:-1" + - url: "{ %bilateral_url(url92) }" + download_reverse: False + ytdl_options: + playlist_items: "-1:0:-1" + - url: "{ %bilateral_url(url93) }" + download_reverse: False + ytdl_options: + playlist_items: "-1:0:-1" + - url: "{ %bilateral_url(url94) }" + download_reverse: False + ytdl_options: + playlist_items: "-1:0:-1" + - url: "{ %bilateral_url(url95) }" + download_reverse: False + ytdl_options: + playlist_items: "-1:0:-1" + - url: "{ %bilateral_url(url96) }" + download_reverse: False + ytdl_options: + playlist_items: "-1:0:-1" + - url: "{ %bilateral_url(url97) }" + download_reverse: False + ytdl_options: + playlist_items: "-1:0:-1" + - url: "{ %bilateral_url(url98) }" + download_reverse: False + ytdl_options: + playlist_items: "-1:0:-1" + - url: "{ %bilateral_url(url99) }" + download_reverse: False + ytdl_options: + playlist_items: "-1:0:-1" + - url: "{%bilateral_url(url100) }" + download_reverse: False + ytdl_options: + playlist_items: "-1:0:-1" + + _multi_url_bilateral: + preset: + - "_multi_url_bilateral_inner" + - "_multi_url" \ No newline at end of file diff --git a/src/ytdl_sub/prebuilt_presets/helpers/url_bilateral.yaml b/src/ytdl_sub/prebuilt_presets/helpers/url_bilateral.yaml new file mode 100644 index 00000000..b785fc5e --- /dev/null +++ b/src/ytdl_sub/prebuilt_presets/helpers/url_bilateral.yaml @@ -0,0 +1,21 @@ +presets: + # multi-url with bilateral scraping built into it via + # inspection of the URL and conditionally adding another + # ytdl-sub url download with the scraping and download reversed + _url_bilateral_overrides: + overrides: + enable_bilateral_scraping: True + "%is_bilateral_url": >- + { %contains( $0, "youtube.com/playlist" ) } + "%bilateral_url": >- + { + %if( + %and( + enable_bilateral_scraping, + subscription_has_download_archive, + %is_bilateral_url($0) + ), + $0, + "" + ) + } \ No newline at end of file diff --git a/src/ytdl_sub/prebuilt_presets/tv_show/tv_show.yaml b/src/ytdl_sub/prebuilt_presets/tv_show/tv_show.yaml index fb97e0fb..463f122b 100644 --- a/src/ytdl_sub/prebuilt_presets/tv_show/tv_show.yaml +++ b/src/ytdl_sub/prebuilt_presets/tv_show/tv_show.yaml @@ -29,15 +29,6 @@ presets: - "_kodi_base" - "_jellyfin_tv_show" -#################################################################################################### - - # TV show from one or more sources. Uses {url}'s avatar and banner as poster and fanart - _tv_show_by_date: - preset: "_multi_url" - overrides: - avatar_uncropped_thumbnail_file_name: "{tv_show_poster_file_name}" - banner_uncropped_thumbnail_file_name: "{tv_show_fanart_file_name}" - #################################################################################################### _episode_video_tags: diff --git a/src/ytdl_sub/prebuilt_presets/tv_show/tv_show_by_date.yaml b/src/ytdl_sub/prebuilt_presets/tv_show/tv_show_by_date.yaml index 30027444..e676ec82 100644 --- a/src/ytdl_sub/prebuilt_presets/tv_show/tv_show_by_date.yaml +++ b/src/ytdl_sub/prebuilt_presets/tv_show/tv_show_by_date.yaml @@ -31,6 +31,15 @@ presets: - "plex_tv_show_by_date" - "season_by_year__episode_by_month_day" +#################################################################################################### + + # TV show from one or more sources. Uses {url}'s avatar and banner as poster and fanart + _tv_show_by_date: + preset: "_multi_url_bilateral" + overrides: + avatar_uncropped_thumbnail_file_name: "{tv_show_poster_file_name}" + banner_uncropped_thumbnail_file_name: "{tv_show_fanart_file_name}" + #################################################################################################### _season_by_year: diff --git a/src/ytdl_sub/prebuilt_presets/tv_show/tv_show_collection.yaml b/src/ytdl_sub/prebuilt_presets/tv_show/tv_show_collection.yaml index 8554fda9..888068d1 100644 --- a/src/ytdl_sub/prebuilt_presets/tv_show/tv_show_collection.yaml +++ b/src/ytdl_sub/prebuilt_presets/tv_show/tv_show_collection.yaml @@ -60,6 +60,9 @@ presets: ############## _tv_show_collection: + preset: + - "_tv_show_collection_bilateral" + download: - url: "{collection_season_1_url}" variables: @@ -79,6 +82,7 @@ presets: uid: "avatar_uncropped" - name: "{tv_show_fanart_file_name}" uid: "banner_uncropped" + - url: "{collection_season_2_url}" variables: collection_season_number: "2" @@ -86,6 +90,7 @@ presets: playlist_thumbnails: - name: "{season_poster_file_name}" uid: "latest_entry" + - url: "{collection_season_3_url}" variables: collection_season_number: "3" @@ -93,6 +98,7 @@ presets: playlist_thumbnails: - name: "{season_poster_file_name}" uid: "latest_entry" + - url: "{collection_season_4_url}" variables: collection_season_number: "4" @@ -100,6 +106,7 @@ presets: playlist_thumbnails: - name: "{season_poster_file_name}" uid: "latest_entry" + - url: "{collection_season_5_url}" variables: collection_season_number: "5" @@ -107,6 +114,7 @@ presets: playlist_thumbnails: - name: "{season_poster_file_name}" uid: "latest_entry" + - url: "{collection_season_6_url}" variables: collection_season_number: "6" @@ -114,6 +122,7 @@ presets: playlist_thumbnails: - name: "{season_poster_file_name}" uid: "latest_entry" + - url: "{collection_season_7_url}" variables: collection_season_number: "7" @@ -121,6 +130,7 @@ presets: playlist_thumbnails: - name: "{season_poster_file_name}" uid: "latest_entry" + - url: "{collection_season_8_url}" variables: collection_season_number: "8" @@ -128,6 +138,7 @@ presets: playlist_thumbnails: - name: "{season_poster_file_name}" uid: "latest_entry" + - url: "{collection_season_9_url}" variables: collection_season_number: "9" @@ -135,6 +146,7 @@ presets: playlist_thumbnails: - name: "{season_poster_file_name}" uid: "latest_entry" + - url: "{collection_season_10_url}" variables: collection_season_number: "10" @@ -142,6 +154,7 @@ presets: playlist_thumbnails: - name: "{season_poster_file_name}" uid: "latest_entry" + - url: "{collection_season_11_url}" variables: collection_season_number: "11" @@ -149,6 +162,7 @@ presets: playlist_thumbnails: - name: "{season_poster_file_name}" uid: "latest_entry" + - url: "{collection_season_12_url}" variables: collection_season_number: "12" @@ -156,6 +170,7 @@ presets: playlist_thumbnails: - name: "{season_poster_file_name}" uid: "latest_entry" + - url: "{collection_season_13_url}" variables: collection_season_number: "13" @@ -163,6 +178,7 @@ presets: playlist_thumbnails: - name: "{season_poster_file_name}" uid: "latest_entry" + - url: "{collection_season_14_url}" variables: collection_season_number: "14" @@ -170,6 +186,7 @@ presets: playlist_thumbnails: - name: "{season_poster_file_name}" uid: "latest_entry" + - url: "{collection_season_15_url}" variables: collection_season_number: "15" @@ -177,6 +194,7 @@ presets: playlist_thumbnails: - name: "{season_poster_file_name}" uid: "latest_entry" + - url: "{collection_season_16_url}" variables: collection_season_number: "16" @@ -184,6 +202,7 @@ presets: playlist_thumbnails: - name: "{season_poster_file_name}" uid: "latest_entry" + - url: "{collection_season_17_url}" variables: collection_season_number: "17" @@ -191,6 +210,7 @@ presets: playlist_thumbnails: - name: "{season_poster_file_name}" uid: "latest_entry" + - url: "{collection_season_18_url}" variables: collection_season_number: "18" @@ -198,6 +218,7 @@ presets: playlist_thumbnails: - name: "{season_poster_file_name}" uid: "latest_entry" + - url: "{collection_season_19_url}" variables: collection_season_number: "19" @@ -205,6 +226,7 @@ presets: playlist_thumbnails: - name: "{season_poster_file_name}" uid: "latest_entry" + - url: "{collection_season_20_url}" variables: collection_season_number: "20" @@ -212,6 +234,7 @@ presets: playlist_thumbnails: - name: "{season_poster_file_name}" uid: "latest_entry" + - url: "{collection_season_21_url}" variables: collection_season_number: "21" @@ -219,6 +242,7 @@ presets: playlist_thumbnails: - name: "{season_poster_file_name}" uid: "latest_entry" + - url: "{collection_season_22_url}" variables: collection_season_number: "22" @@ -226,6 +250,7 @@ presets: playlist_thumbnails: - name: "{season_poster_file_name}" uid: "latest_entry" + - url: "{collection_season_23_url}" variables: collection_season_number: "23" @@ -233,6 +258,7 @@ presets: playlist_thumbnails: - name: "{season_poster_file_name}" uid: "latest_entry" + - url: "{collection_season_24_url}" variables: collection_season_number: "24" @@ -240,6 +266,7 @@ presets: playlist_thumbnails: - name: "{season_poster_file_name}" uid: "latest_entry" + - url: "{collection_season_25_url}" variables: collection_season_number: "25" @@ -247,6 +274,7 @@ presets: playlist_thumbnails: - name: "{season_poster_file_name}" uid: "latest_entry" + - url: "{collection_season_26_url}" variables: collection_season_number: "26" @@ -254,6 +282,7 @@ presets: playlist_thumbnails: - name: "{season_poster_file_name}" uid: "latest_entry" + - url: "{collection_season_27_url}" variables: collection_season_number: "27" @@ -261,6 +290,7 @@ presets: playlist_thumbnails: - name: "{season_poster_file_name}" uid: "latest_entry" + - url: "{collection_season_28_url}" variables: collection_season_number: "28" @@ -268,6 +298,7 @@ presets: playlist_thumbnails: - name: "{season_poster_file_name}" uid: "latest_entry" + - url: "{collection_season_29_url}" variables: collection_season_number: "29" @@ -275,6 +306,7 @@ presets: playlist_thumbnails: - name: "{season_poster_file_name}" uid: "latest_entry" + - url: "{collection_season_30_url}" variables: collection_season_number: "30" @@ -282,6 +314,7 @@ presets: playlist_thumbnails: - name: "{season_poster_file_name}" uid: "latest_entry" + - url: "{collection_season_31_url}" variables: collection_season_number: "31" @@ -289,6 +322,7 @@ presets: playlist_thumbnails: - name: "{season_poster_file_name}" uid: "latest_entry" + - url: "{collection_season_32_url}" variables: collection_season_number: "32" @@ -296,6 +330,7 @@ presets: playlist_thumbnails: - name: "{season_poster_file_name}" uid: "latest_entry" + - url: "{collection_season_33_url}" variables: collection_season_number: "33" @@ -303,6 +338,7 @@ presets: playlist_thumbnails: - name: "{season_poster_file_name}" uid: "latest_entry" + - url: "{collection_season_34_url}" variables: collection_season_number: "34" @@ -310,6 +346,7 @@ presets: playlist_thumbnails: - name: "{season_poster_file_name}" uid: "latest_entry" + - url: "{collection_season_35_url}" variables: collection_season_number: "35" @@ -317,6 +354,7 @@ presets: playlist_thumbnails: - name: "{season_poster_file_name}" uid: "latest_entry" + - url: "{collection_season_36_url}" variables: collection_season_number: "36" @@ -324,6 +362,7 @@ presets: playlist_thumbnails: - name: "{season_poster_file_name}" uid: "latest_entry" + - url: "{collection_season_37_url}" variables: collection_season_number: "37" @@ -331,6 +370,7 @@ presets: playlist_thumbnails: - name: "{season_poster_file_name}" uid: "latest_entry" + - url: "{collection_season_38_url}" variables: collection_season_number: "38" @@ -338,6 +378,7 @@ presets: playlist_thumbnails: - name: "{season_poster_file_name}" uid: "latest_entry" + - url: "{collection_season_39_url}" variables: collection_season_number: "39" @@ -345,6 +386,7 @@ presets: playlist_thumbnails: - name: "{season_poster_file_name}" uid: "latest_entry" + - url: "{collection_season_40_url}" variables: collection_season_number: "40" @@ -648,6 +690,332 @@ presets: s39_url: "" s40_url: "" + _tv_show_collection_bilateral: + preset: + - "_url_bilateral_overrides" + + download: + - url: "{ %bilateral_url(collection_season_1_url) }" + variables: + collection_season_number: "1" + collection_season_name: "{collection_season_1_name}" + download_reverse: False + ytdl_options: + playlist_items: "-1:0:-1" + + - url: "{ %bilateral_url(collection_season_2_url) }" + variables: + collection_season_number: "2" + collection_season_name: "{collection_season_2_name}" + download_reverse: False + ytdl_options: + playlist_items: "-1:0:-1" + + - url: "{ %bilateral_url(collection_season_3_url) }" + variables: + collection_season_number: "3" + collection_season_name: "{collection_season_3_name}" + download_reverse: False + ytdl_options: + playlist_items: "-1:0:-1" + + - url: "{ %bilateral_url(collection_season_4_url) }" + variables: + collection_season_number: "4" + collection_season_name: "{collection_season_4_name}" + download_reverse: False + ytdl_options: + playlist_items: "-1:0:-1" + + - url: "{ %bilateral_url(collection_season_5_url) }" + variables: + collection_season_number: "5" + collection_season_name: "{collection_season_5_name}" + download_reverse: False + ytdl_options: + playlist_items: "-1:0:-1" + + - url: "{ %bilateral_url(collection_season_6_url) }" + variables: + collection_season_number: "6" + collection_season_name: "{collection_season_6_name}" + download_reverse: False + ytdl_options: + playlist_items: "-1:0:-1" + + - url: "{ %bilateral_url(collection_season_7_url) }" + variables: + collection_season_number: "7" + collection_season_name: "{collection_season_7_name}" + download_reverse: False + ytdl_options: + playlist_items: "-1:0:-1" + + - url: "{ %bilateral_url(collection_season_8_url) }" + variables: + collection_season_number: "8" + collection_season_name: "{collection_season_8_name}" + download_reverse: False + ytdl_options: + playlist_items: "-1:0:-1" + + - url: "{ %bilateral_url(collection_season_9_url) }" + variables: + collection_season_number: "9" + collection_season_name: "{collection_season_9_name}" + download_reverse: False + ytdl_options: + playlist_items: "-1:0:-1" + + - url: "{ %bilateral_url(collection_season_10_url) }" + variables: + collection_season_number: "10" + collection_season_name: "{collection_season_10_name}" + download_reverse: False + ytdl_options: + playlist_items: "-1:0:-1" + + - url: "{ %bilateral_url(collection_season_11_url) }" + variables: + collection_season_number: "11" + collection_season_name: "{collection_season_11_name}" + download_reverse: False + ytdl_options: + playlist_items: "-1:0:-1" + + - url: "{ %bilateral_url(collection_season_12_url) }" + variables: + collection_season_number: "12" + collection_season_name: "{collection_season_12_name}" + download_reverse: False + ytdl_options: + playlist_items: "-1:0:-1" + + - url: "{ %bilateral_url(collection_season_13_url) }" + variables: + collection_season_number: "13" + collection_season_name: "{collection_season_13_name}" + download_reverse: False + ytdl_options: + playlist_items: "-1:0:-1" + + - url: "{ %bilateral_url(collection_season_14_url) }" + variables: + collection_season_number: "14" + collection_season_name: "{collection_season_14_name}" + download_reverse: False + ytdl_options: + playlist_items: "-1:0:-1" + + - url: "{ %bilateral_url(collection_season_15_url) }" + variables: + collection_season_number: "15" + collection_season_name: "{collection_season_15_name}" + download_reverse: False + ytdl_options: + playlist_items: "-1:0:-1" + + - url: "{ %bilateral_url(collection_season_16_url) }" + variables: + collection_season_number: "16" + collection_season_name: "{collection_season_16_name}" + download_reverse: False + ytdl_options: + playlist_items: "-1:0:-1" + + - url: "{ %bilateral_url(collection_season_17_url) }" + variables: + collection_season_number: "17" + collection_season_name: "{collection_season_17_name}" + download_reverse: False + ytdl_options: + playlist_items: "-1:0:-1" + + - url: "{ %bilateral_url(collection_season_18_url) }" + variables: + collection_season_number: "18" + collection_season_name: "{collection_season_18_name}" + download_reverse: False + ytdl_options: + playlist_items: "-1:0:-1" + + - url: "{ %bilateral_url(collection_season_19_url) }" + variables: + collection_season_number: "19" + collection_season_name: "{collection_season_19_name}" + download_reverse: False + ytdl_options: + playlist_items: "-1:0:-1" + + - url: "{ %bilateral_url(collection_season_20_url) }" + variables: + collection_season_number: "20" + collection_season_name: "{collection_season_20_name}" + download_reverse: False + ytdl_options: + playlist_items: "-1:0:-1" + + - url: "{ %bilateral_url(collection_season_21_url) }" + variables: + collection_season_number: "21" + collection_season_name: "{collection_season_21_name}" + download_reverse: False + ytdl_options: + playlist_items: "-1:0:-1" + + - url: "{ %bilateral_url(collection_season_22_url) }" + variables: + collection_season_number: "22" + collection_season_name: "{collection_season_22_name}" + download_reverse: False + ytdl_options: + playlist_items: "-1:0:-1" + + - url: "{ %bilateral_url(collection_season_23_url) }" + variables: + collection_season_number: "23" + collection_season_name: "{collection_season_23_name}" + download_reverse: False + ytdl_options: + playlist_items: "-1:0:-1" + + - url: "{ %bilateral_url(collection_season_24_url) }" + variables: + collection_season_number: "24" + collection_season_name: "{collection_season_24_name}" + download_reverse: False + ytdl_options: + playlist_items: "-1:0:-1" + + - url: "{ %bilateral_url(collection_season_25_url) }" + variables: + collection_season_number: "25" + collection_season_name: "{collection_season_25_name}" + download_reverse: False + ytdl_options: + playlist_items: "-1:0:-1" + + - url: "{ %bilateral_url(collection_season_26_url) }" + variables: + collection_season_number: "26" + collection_season_name: "{collection_season_26_name}" + download_reverse: False + ytdl_options: + playlist_items: "-1:0:-1" + + - url: "{ %bilateral_url(collection_season_27_url) }" + variables: + collection_season_number: "27" + collection_season_name: "{collection_season_27_name}" + download_reverse: False + ytdl_options: + playlist_items: "-1:0:-1" + + - url: "{ %bilateral_url(collection_season_28_url) }" + variables: + collection_season_number: "28" + collection_season_name: "{collection_season_28_name}" + download_reverse: False + ytdl_options: + playlist_items: "-1:0:-1" + + - url: "{ %bilateral_url(collection_season_29_url) }" + variables: + collection_season_number: "29" + collection_season_name: "{collection_season_29_name}" + download_reverse: False + ytdl_options: + playlist_items: "-1:0:-1" + + - url: "{ %bilateral_url(collection_season_30_url) }" + variables: + collection_season_number: "30" + collection_season_name: "{collection_season_30_name}" + download_reverse: False + ytdl_options: + playlist_items: "-1:0:-1" + + - url: "{ %bilateral_url(collection_season_31_url) }" + variables: + collection_season_number: "31" + collection_season_name: "{collection_season_31_name}" + download_reverse: False + ytdl_options: + playlist_items: "-1:0:-1" + + - url: "{ %bilateral_url(collection_season_32_url) }" + variables: + collection_season_number: "32" + collection_season_name: "{collection_season_32_name}" + download_reverse: False + ytdl_options: + playlist_items: "-1:0:-1" + + - url: "{ %bilateral_url(collection_season_33_url) }" + variables: + collection_season_number: "33" + collection_season_name: "{collection_season_33_name}" + download_reverse: False + ytdl_options: + playlist_items: "-1:0:-1" + + - url: "{ %bilateral_url(collection_season_34_url) }" + variables: + collection_season_number: "34" + collection_season_name: "{collection_season_34_name}" + download_reverse: False + ytdl_options: + playlist_items: "-1:0:-1" + + - url: "{ %bilateral_url(collection_season_35_url) }" + variables: + collection_season_number: "35" + collection_season_name: "{collection_season_35_name}" + download_reverse: False + ytdl_options: + playlist_items: "-1:0:-1" + + - url: "{ %bilateral_url(collection_season_36_url) }" + variables: + collection_season_number: "36" + collection_season_name: "{collection_season_36_name}" + download_reverse: False + ytdl_options: + playlist_items: "-1:0:-1" + + - url: "{ %bilateral_url(collection_season_37_url) }" + variables: + collection_season_number: "37" + collection_season_name: "{collection_season_37_name}" + download_reverse: False + ytdl_options: + playlist_items: "-1:0:-1" + + - url: "{ %bilateral_url(collection_season_38_url) }" + variables: + collection_season_number: "38" + collection_season_name: "{collection_season_38_name}" + download_reverse: False + ytdl_options: + playlist_items: "-1:0:-1" + + - url: "{ %bilateral_url(collection_season_39_url) }" + variables: + collection_season_number: "39" + collection_season_name: "{collection_season_39_name}" + download_reverse: False + ytdl_options: + playlist_items: "-1:0:-1" + + - url: "{ %bilateral_url(collection_season_40_url) }" + variables: + collection_season_number: "40" + collection_season_name: "{collection_season_40_name}" + download_reverse: False + ytdl_options: + playlist_items: "-1:0:-1" + + #################################################################################################### # DEPRECATED SEASON PRESETS diff --git a/src/ytdl_sub/subscriptions/base_subscription.py b/src/ytdl_sub/subscriptions/base_subscription.py index c7872e8a..6ec7bb9e 100644 --- a/src/ytdl_sub/subscriptions/base_subscription.py +++ b/src/ytdl_sub/subscriptions/base_subscription.py @@ -9,6 +9,7 @@ from ytdl_sub.config.preset import Preset from ytdl_sub.config.preset_options import OutputOptions from ytdl_sub.config.preset_options import YTDLOptions from ytdl_sub.downloaders.url.validators import MultiUrlValidator +from ytdl_sub.entries.variables.override_variables import SubscriptionVariables from ytdl_sub.utils.file_handler import FileHandlerTransactionLog from ytdl_sub.utils.logger import Logger from ytdl_sub.ytdl_additions.enhanced_download_archive import EnhancedDownloadArchive @@ -16,6 +17,24 @@ from ytdl_sub.ytdl_additions.enhanced_download_archive import EnhancedDownloadAr logger = Logger.get("subscription") +def _initialize_download_archive( + output_options: OutputOptions, + overrides: Overrides, + working_directory: str, + output_directory: str, +) -> EnhancedDownloadArchive: + migrated_file_name: Optional[str] = None + if migrated_file_name_option := output_options.migrated_download_archive_name: + migrated_file_name = overrides.apply_formatter(migrated_file_name_option) + + return EnhancedDownloadArchive( + file_name=overrides.apply_formatter(output_options.download_archive_name), + working_directory=working_directory, + output_directory=output_directory, + migrated_file_name=migrated_file_name, + ).reinitialize(dry_run=True) + + class BaseSubscription(ABC): """ Subscription classes are the 'controllers' that perform... @@ -48,20 +67,43 @@ class BaseSubscription(ABC): self._config_options = config_options self._preset_options = preset_options - migrated_file_name: Optional[str] = None - if migrated_file_name_option := self.output_options.migrated_download_archive_name: - migrated_file_name = self.overrides.apply_formatter(migrated_file_name_option) + # Add overrides pre-archive + self.overrides.add( + { + SubscriptionVariables.subscription_name(): self.name, + } + ) - # TODO: Do not include this as part of the subscription - self._enhanced_download_archive = EnhancedDownloadArchive( - file_name=self.overrides.apply_formatter(self.output_options.download_archive_name), + self._enhanced_download_archive: Optional[ + EnhancedDownloadArchive + ] = _initialize_download_archive( + output_options=self.output_options, + overrides=self.overrides, working_directory=self.working_directory, output_directory=self.output_directory, - migrated_file_name=migrated_file_name, + ) + + # Add post-archive variables + self.overrides.add( + { + SubscriptionVariables.subscription_has_download_archive(): f"""{{ + %bool({self.download_archive.num_entries > 0}) + }}""", + } ) self._exception: Optional[Exception] = None + @property + def download_archive(self) -> EnhancedDownloadArchive: + """ + Returns + ------- + Initialized download archive + """ + assert self._enhanced_download_archive is not None + return self._enhanced_download_archive + @property def downloader_options(self) -> MultiUrlValidator: """ @@ -141,7 +183,7 @@ class BaseSubscription(ABC): ------- Number of entries added """ - return self._enhanced_download_archive.num_entries_added + return self.download_archive.num_entries_added @property def num_entries_modified(self) -> int: @@ -150,7 +192,7 @@ class BaseSubscription(ABC): ------- Number of entries modified """ - return self._enhanced_download_archive.num_entries_modified + return self.download_archive.num_entries_modified @property def num_entries_removed(self) -> int: @@ -159,7 +201,7 @@ class BaseSubscription(ABC): ------- Number of entries removed """ - return self._enhanced_download_archive.num_entries_removed + return self.download_archive.num_entries_removed @property def num_entries(self) -> int: @@ -168,7 +210,7 @@ class BaseSubscription(ABC): ------- The number of entries """ - return self._enhanced_download_archive.num_entries + return self.download_archive.num_entries @property def transaction_log(self) -> FileHandlerTransactionLog: @@ -177,7 +219,7 @@ class BaseSubscription(ABC): ------- Transaction log from the subscription """ - return self._enhanced_download_archive.get_file_handler_transaction_log() + return self.download_archive.get_file_handler_transaction_log() @property def exception(self) -> Optional[Exception]: diff --git a/src/ytdl_sub/subscriptions/subscription_download.py b/src/ytdl_sub/subscriptions/subscription_download.py index 06ce1bd2..b017d005 100644 --- a/src/ytdl_sub/subscriptions/subscription_download.py +++ b/src/ytdl_sub/subscriptions/subscription_download.py @@ -67,7 +67,7 @@ class SubscriptionDownload(BaseSubscription, ABC): output_file_name = self.overrides.apply_formatter( formatter=self.output_options.file_name, entry=entry ) - self._enhanced_download_archive.save_file_to_output_directory( + self.download_archive.save_file_to_output_directory( file_name=entry.get_download_file_name(), file_metadata=entry_metadata, output_file_name=output_file_name, @@ -81,7 +81,7 @@ class SubscriptionDownload(BaseSubscription, ABC): ) # Copy the thumbnails since they could be used later for other things - self._enhanced_download_archive.save_file_to_output_directory( + self.download_archive.save_file_to_output_directory( file_name=entry.get_download_thumbnail_name(), output_file_name=output_thumbnail_name, entry=entry, @@ -101,7 +101,7 @@ class SubscriptionDownload(BaseSubscription, ABC): if not dry_run: entry.write_info_json() - self._enhanced_download_archive.save_file_to_output_directory( + self.download_archive.save_file_to_output_directory( file_name=entry.get_download_info_json_name(), output_file_name=output_info_json_name, entry=entry, @@ -135,7 +135,7 @@ class SubscriptionDownload(BaseSubscription, ABC): Context manager to initialize the enhanced download archive """ if self.maintain_download_archive: - self._enhanced_download_archive.prepare_download_archive() + self.download_archive.prepare_download_archive() yield @@ -156,19 +156,19 @@ class SubscriptionDownload(BaseSubscription, ABC): ) if date_range_to_keep or self.output_options.keep_max_files is not None: - self._enhanced_download_archive.remove_stale_files( + self.download_archive.remove_stale_files( date_range=date_range_to_keep, keep_max_files=keep_max_files ) - self._enhanced_download_archive.save_download_mappings() - FileHandler.delete(self._enhanced_download_archive.working_file_path) + self.download_archive.save_download_mappings() + FileHandler.delete(self.download_archive.working_file_path) @contextlib.contextmanager def _remove_empty_directories_in_output_directory(self): try: yield finally: - if not self._enhanced_download_archive.is_dry_run: + if not self.download_archive.is_dry_run: for root, dir_names, _ in os.walk(Path(self.output_directory), topdown=False): for dir_name in dir_names: dir_path = Path(root) / dir_name @@ -194,7 +194,7 @@ class SubscriptionDownload(BaseSubscription, ABC): plugin_type( options=plugin_options, overrides=self.overrides, - enhanced_download_archive=self._enhanced_download_archive, + enhanced_download_archive=self.download_archive, ) for plugin_type, plugin_options in self.plugins.zipped() ] @@ -233,7 +233,7 @@ class SubscriptionDownload(BaseSubscription, ABC): # Re-save the download archive after each entry is moved to the output directory if self.maintain_download_archive: - self._enhanced_download_archive.save_download_mappings() + self.download_archive.save_download_mappings() def _process_entry( self, plugins: List[Plugin], dry_run: bool, entry: Entry, entry_metadata: FileMetadata @@ -323,7 +323,7 @@ class SubscriptionDownload(BaseSubscription, ABC): for plugin in plugins: plugin.post_process_subscription() - return self._enhanced_download_archive.get_file_handler_transaction_log() + return self.download_archive.get_file_handler_transaction_log() def download(self, dry_run: bool = False) -> FileHandlerTransactionLog: """ @@ -336,14 +336,14 @@ class SubscriptionDownload(BaseSubscription, ABC): directory. """ self._exception = None - self._enhanced_download_archive.reinitialize(dry_run=dry_run) + self.download_archive.reinitialize(dry_run=dry_run) plugins = self._initialize_plugins() subscription_ytdl_options = SubscriptionYTDLOptions( preset=self._preset_options, plugins=plugins, - enhanced_download_archive=self._enhanced_download_archive, + enhanced_download_archive=self.download_archive, overrides=self.overrides, working_directory=self.working_directory, dry_run=dry_run, @@ -351,7 +351,7 @@ class SubscriptionDownload(BaseSubscription, ABC): downloader = MultiUrlDownloader( options=self.downloader_options, - enhanced_download_archive=self._enhanced_download_archive, + enhanced_download_archive=self.download_archive, download_ytdl_options=subscription_ytdl_options.download_builder(), metadata_ytdl_options=subscription_ytdl_options.metadata_builder(), overrides=self.overrides, @@ -389,14 +389,14 @@ class SubscriptionDownload(BaseSubscription, ABC): If true, do not modify any video/audio files or move anything to the output directory. """ self._exception = None - self._enhanced_download_archive.reinitialize(dry_run=dry_run) + self.download_archive.reinitialize(dry_run=dry_run) plugins = self._initialize_plugins() subscription_ytdl_options = SubscriptionYTDLOptions( preset=self._preset_options, plugins=plugins, - enhanced_download_archive=self._enhanced_download_archive, + enhanced_download_archive=self.download_archive, overrides=self.overrides, working_directory=self.working_directory, dry_run=dry_run, @@ -406,7 +406,7 @@ class SubscriptionDownload(BaseSubscription, ABC): plugins.extend( MultiUrlDownloader( options=self.downloader_options, - enhanced_download_archive=self._enhanced_download_archive, + enhanced_download_archive=self.download_archive, download_ytdl_options=subscription_ytdl_options.download_builder(), metadata_ytdl_options=subscription_ytdl_options.metadata_builder(), overrides=self.overrides, @@ -415,7 +415,7 @@ class SubscriptionDownload(BaseSubscription, ABC): downloader = InfoJsonDownloader( options=InfoJsonDownloaderOptions(name="no-op", value={}), - enhanced_download_archive=self._enhanced_download_archive, + enhanced_download_archive=self.download_archive, download_ytdl_options=YTDLOptionsBuilder(), metadata_ytdl_options=YTDLOptionsBuilder(), overrides=self.overrides, diff --git a/src/ytdl_sub/subscriptions/subscription_validators.py b/src/ytdl_sub/subscriptions/subscription_validators.py index c247779a..6ce95cc6 100644 --- a/src/ytdl_sub/subscriptions/subscription_validators.py +++ b/src/ytdl_sub/subscriptions/subscription_validators.py @@ -32,7 +32,7 @@ class SubscriptionOutput(Validator, ABC): indent overrides to merge with the preset dict's overrides """ return { - SubscriptionVariables.subscription_indent_i(i): self._indent_overrides[i] + SubscriptionVariables.subscription_indent_i(i).variable_name: self._indent_overrides[i] for i in range(len(self._indent_overrides)) } @@ -143,7 +143,9 @@ class SubscriptionValueValidator(SubscriptionLeafValidator, StringValidator): presets=presets, indent_overrides=indent_overrides, ) - self._overrides_to_add[SubscriptionVariables.subscription_value()] = self.value + self._overrides_to_add[ + SubscriptionVariables.subscription_value().variable_name + ] = self.value class SubscriptionListValuesValidator(SubscriptionLeafValidator, StringListValidator): @@ -169,11 +171,11 @@ class SubscriptionListValuesValidator(SubscriptionLeafValidator, StringListValid # Write the first list value into subscription_value as well if idx == 0: self._overrides_to_add[ - SubscriptionVariables.subscription_value() + SubscriptionVariables.subscription_value().variable_name ] = list_value.value self._overrides_to_add[ - SubscriptionVariables.subscription_value_i(index=idx) + SubscriptionVariables.subscription_value_i(index=idx).variable_name ] = list_value.value @@ -217,9 +219,9 @@ class SubscriptionMapValidator(SubscriptionLeafValidator, LiteralDictValidator): presets=presets, indent_overrides=indent_overrides, ) - self._overrides_to_add[SubscriptionVariables.subscription_map()] = ScriptUtils.to_script( - self.dict - ) + self._overrides_to_add[ + SubscriptionVariables.subscription_map().variable_name + ] = ScriptUtils.to_script(self.dict) class SubscriptionValidator(SubscriptionOutput): diff --git a/src/ytdl_sub/utils/scriptable.py b/src/ytdl_sub/utils/scriptable.py index 6aff1db3..38780024 100644 --- a/src/ytdl_sub/utils/scriptable.py +++ b/src/ytdl_sub/utils/scriptable.py @@ -9,13 +9,16 @@ from ytdl_sub.entries.script.function_scripts import CUSTOM_FUNCTION_SCRIPTS from ytdl_sub.entries.script.variable_definitions import UNRESOLVED_VARIABLES from ytdl_sub.entries.script.variable_definitions import VARIABLE_SCRIPTS from ytdl_sub.entries.script.variable_types import Variable +from ytdl_sub.entries.variables.override_variables import REQUIRED_OVERRIDE_VARIABLE_DEFINITIONS from ytdl_sub.script.script import Script from ytdl_sub.script.utils.exceptions import RuntimeException from ytdl_sub.utils.exceptions import StringFormattingException from ytdl_sub.utils.script import ScriptUtils BASE_SCRIPT: Script = Script( - dict(ScriptUtils.add_sanitized_variables(VARIABLE_SCRIPTS), **CUSTOM_FUNCTION_SCRIPTS) + ScriptUtils.add_sanitized_variables(VARIABLE_SCRIPTS) + | ScriptUtils.add_sanitized_variables(REQUIRED_OVERRIDE_VARIABLE_DEFINITIONS) + | CUSTOM_FUNCTION_SCRIPTS ) diff --git a/tests/e2e/entries/__init__.py b/tests/e2e/entries/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/tests/e2e/entries/script/__init__.py b/tests/e2e/entries/script/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/tests/e2e/entries/script/test_custom_functions.py b/tests/e2e/entries/script/test_custom_functions.py new file mode 100644 index 00000000..61c9a6ca --- /dev/null +++ b/tests/e2e/entries/script/test_custom_functions.py @@ -0,0 +1,22 @@ +from unit.script.conftest import single_variable_output + + +class TestCustomFunctions: + def test_is_playlist_ordered_by_newest_true(self): + assert ( + single_variable_output( + "{%is_playlist_ordered_by_newest('https://www.youtube.com/playlist?list=PL5BC0FC26BECA5A35')}" + ) + is True + ) + + def test_is_playlist_ordered_by_newest_false(self): + assert ( + single_variable_output( + "{%is_playlist_ordered_by_newest('https://www.youtube.com/playlist?list=PL2KvlCGf4yFftX466OnFS8wvuSosBfUgm')}" + ) + is False + ) + + def test_is_playlist_ordered_by_newest_defaults_false(self): + assert single_variable_output("{%is_playlist_ordered_by_newest('aaaaaa')}") is False diff --git a/tests/e2e/youtube/test_playlist.py b/tests/e2e/youtube/test_playlist.py index 3c8e142f..e173c568 100644 --- a/tests/e2e/youtube/test_playlist.py +++ b/tests/e2e/youtube/test_playlist.py @@ -51,6 +51,49 @@ def playlist_preset_dict(output_directory): } +@pytest.fixture +def tv_show_by_date_bilateral_dict(output_directory): + return { + "preset": [ + "Jellyfin TV Show by Date", + ], + "format": "worst[ext=mp4]", + "match_filters": {"filters": ["title *= Feb.1"]}, + "overrides": { + "url": "https://www.youtube.com/playlist?list=PLd4Q7G88JqoekF0b30NYQcOTnTiIe9Ali", + "tv_show_directory": output_directory, + }, + "nfo_tags": { + "tags": { + "subscription_has_download_archive": "{subscription_has_download_archive}", + "download_index": "{download_index}", + } + }, + } + + +@pytest.fixture +def tv_show_collection_bilateral_dict(output_directory): + return { + "preset": [ + "Jellyfin TV Show Collection", + ], + "format": "worst[ext=mp4]", + "match_filters": {"filters": ["title *= Feb.1"]}, + "overrides": { + "s01_url": "https://www.youtube.com/playlist?list=PLd4Q7G88JqoekF0b30NYQcOTnTiIe9Ali", + "s01_name": "bilateral test", + "tv_show_directory": output_directory, + }, + "nfo_tags": { + "tags": { + "subscription_has_download_archive": "{subscription_has_download_archive}", + "download_index": "{download_index}", + } + }, + } + + class TestPlaylist: """ Downloads my old minecraft youtube channel, pretends they are music videos. Ensure the above @@ -222,3 +265,71 @@ class TestPlaylist: assert len(subscriptions) == 1 assert subscriptions[0].transaction_log.is_empty + + def test_tv_show_by_date_downloads_bilateral( + self, + tv_show_by_date_bilateral_dict: Dict, + output_directory: str, + default_config: ConfigFile, + ): + playlist_subscription = Subscription.from_dict( + config=default_config, + preset_name="bilateral_test", + preset_dict=tv_show_by_date_bilateral_dict, + ) + + transaction_log = playlist_subscription.download(dry_run=False) + assert_transaction_log_matches( + output_directory=output_directory, + transaction_log=transaction_log, + transaction_log_summary_file_name="youtube/test_playlist_bilateral_p1.txt", + ) + + # Now that one vid is downloaded, attempt to download all and see if bilateral + # logic kicks in + del tv_show_by_date_bilateral_dict["match_filters"] + playlist_subscription = Subscription.from_dict( + config=default_config, + preset_name="bilateral_test", + preset_dict=tv_show_by_date_bilateral_dict, + ) + transaction_log = playlist_subscription.download(dry_run=True) + assert_transaction_log_matches( + output_directory=output_directory, + transaction_log=transaction_log, + transaction_log_summary_file_name="youtube/test_playlist_bilateral_p2.txt", + ) + + def test_tv_show_collection_downloads_bilateral( + self, + tv_show_collection_bilateral_dict: Dict, + output_directory: str, + default_config: ConfigFile, + ): + playlist_subscription = Subscription.from_dict( + config=default_config, + preset_name="bilateral_test", + preset_dict=tv_show_collection_bilateral_dict, + ) + + transaction_log = playlist_subscription.download(dry_run=False) + assert_transaction_log_matches( + output_directory=output_directory, + transaction_log=transaction_log, + transaction_log_summary_file_name="youtube/test_playlist_bilateral_collection_p1.txt", + ) + + # Now that one vid is downloaded, attempt to download all and see if bilateral + # logic kicks in + del tv_show_collection_bilateral_dict["match_filters"] + playlist_subscription = Subscription.from_dict( + config=default_config, + preset_name="bilateral_test", + preset_dict=tv_show_collection_bilateral_dict, + ) + transaction_log = playlist_subscription.download(dry_run=True) + assert_transaction_log_matches( + output_directory=output_directory, + transaction_log=transaction_log, + transaction_log_summary_file_name="youtube/test_playlist_bilateral_collection_p2.txt", + ) diff --git a/tests/resources/transaction_log_summaries/unit/jellyfin_tv_show_by_date/season_by_year__episode_by_download_index/is_yt_0_many_urls_reformatted_to_season_by_year__episode_by_download_index.txt b/tests/resources/transaction_log_summaries/unit/jellyfin_tv_show_by_date/season_by_year__episode_by_download_index/is_yt_0_many_urls_reformatted_to_season_by_year__episode_by_download_index.txt index 0e8ee84a..bf8c85ca 100644 --- a/tests/resources/transaction_log_summaries/unit/jellyfin_tv_show_by_date/season_by_year__episode_by_download_index/is_yt_0_many_urls_reformatted_to_season_by_year__episode_by_download_index.txt +++ b/tests/resources/transaction_log_summaries/unit/jellyfin_tv_show_by_date/season_by_year__episode_by_download_index/is_yt_0_many_urls_reformatted_to_season_by_year__episode_by_download_index.txt @@ -3,6 +3,19 @@ Files created: {output_directory} .ytdl-sub-Best Prebuilt TV Show by Date-download-archive.json +Files modified: +---------------------------------------- +{output_directory}/Season 2020 + s2020.e000001 - Mock Entry 20-3.info.json + s2020.e000002 - Mock Entry 20-2.info.json + s2020.e000003 - Mock Entry 20-1.info.json + s2020.e000005 - Mock Entry 20-7.info.json + s2020.e000006 - Mock Entry 20-6.info.json + s2020.e000007 - Mock Entry 20-5.info.json + s2020.e000008 - Mock Entry 20-4.info.json +{output_directory}/Season 2021 + s2021.e000004 - Mock Entry 21-1.info.json + Files removed: ---------------------------------------- {output_directory} diff --git a/tests/resources/transaction_log_summaries/unit/jellyfin_tv_show_by_date/season_by_year__episode_by_download_index/is_yt_0_reformatted_to_season_by_year__episode_by_download_index.txt b/tests/resources/transaction_log_summaries/unit/jellyfin_tv_show_by_date/season_by_year__episode_by_download_index/is_yt_0_reformatted_to_season_by_year__episode_by_download_index.txt index 0e8ee84a..68b55020 100644 --- a/tests/resources/transaction_log_summaries/unit/jellyfin_tv_show_by_date/season_by_year__episode_by_download_index/is_yt_0_reformatted_to_season_by_year__episode_by_download_index.txt +++ b/tests/resources/transaction_log_summaries/unit/jellyfin_tv_show_by_date/season_by_year__episode_by_download_index/is_yt_0_reformatted_to_season_by_year__episode_by_download_index.txt @@ -3,6 +3,15 @@ Files created: {output_directory} .ytdl-sub-Best Prebuilt TV Show by Date-download-archive.json +Files modified: +---------------------------------------- +{output_directory}/Season 2020 + s2020.e000001 - Mock Entry 20-3.info.json + s2020.e000002 - Mock Entry 20-2.info.json + s2020.e000003 - Mock Entry 20-1.info.json +{output_directory}/Season 2021 + s2021.e000004 - Mock Entry 21-1.info.json + Files removed: ---------------------------------------- {output_directory} diff --git a/tests/resources/transaction_log_summaries/unit/jellyfin_tv_show_by_date/season_by_year__episode_by_download_index/is_yt_1_many_urls_reformatted_to_season_by_year__episode_by_download_index.txt b/tests/resources/transaction_log_summaries/unit/jellyfin_tv_show_by_date/season_by_year__episode_by_download_index/is_yt_1_many_urls_reformatted_to_season_by_year__episode_by_download_index.txt index 0e8ee84a..bf8c85ca 100644 --- a/tests/resources/transaction_log_summaries/unit/jellyfin_tv_show_by_date/season_by_year__episode_by_download_index/is_yt_1_many_urls_reformatted_to_season_by_year__episode_by_download_index.txt +++ b/tests/resources/transaction_log_summaries/unit/jellyfin_tv_show_by_date/season_by_year__episode_by_download_index/is_yt_1_many_urls_reformatted_to_season_by_year__episode_by_download_index.txt @@ -3,6 +3,19 @@ Files created: {output_directory} .ytdl-sub-Best Prebuilt TV Show by Date-download-archive.json +Files modified: +---------------------------------------- +{output_directory}/Season 2020 + s2020.e000001 - Mock Entry 20-3.info.json + s2020.e000002 - Mock Entry 20-2.info.json + s2020.e000003 - Mock Entry 20-1.info.json + s2020.e000005 - Mock Entry 20-7.info.json + s2020.e000006 - Mock Entry 20-6.info.json + s2020.e000007 - Mock Entry 20-5.info.json + s2020.e000008 - Mock Entry 20-4.info.json +{output_directory}/Season 2021 + s2021.e000004 - Mock Entry 21-1.info.json + Files removed: ---------------------------------------- {output_directory} diff --git a/tests/resources/transaction_log_summaries/unit/jellyfin_tv_show_by_date/season_by_year__episode_by_download_index/is_yt_1_reformatted_to_season_by_year__episode_by_download_index.txt b/tests/resources/transaction_log_summaries/unit/jellyfin_tv_show_by_date/season_by_year__episode_by_download_index/is_yt_1_reformatted_to_season_by_year__episode_by_download_index.txt index 0e8ee84a..68b55020 100644 --- a/tests/resources/transaction_log_summaries/unit/jellyfin_tv_show_by_date/season_by_year__episode_by_download_index/is_yt_1_reformatted_to_season_by_year__episode_by_download_index.txt +++ b/tests/resources/transaction_log_summaries/unit/jellyfin_tv_show_by_date/season_by_year__episode_by_download_index/is_yt_1_reformatted_to_season_by_year__episode_by_download_index.txt @@ -3,6 +3,15 @@ Files created: {output_directory} .ytdl-sub-Best Prebuilt TV Show by Date-download-archive.json +Files modified: +---------------------------------------- +{output_directory}/Season 2020 + s2020.e000001 - Mock Entry 20-3.info.json + s2020.e000002 - Mock Entry 20-2.info.json + s2020.e000003 - Mock Entry 20-1.info.json +{output_directory}/Season 2021 + s2021.e000004 - Mock Entry 21-1.info.json + Files removed: ---------------------------------------- {output_directory} diff --git a/tests/resources/transaction_log_summaries/unit/jellyfin_tv_show_collection/season_by_collection__episode_by_playlist_index/s_2/is_yt_0_reformatted_to_season_by_collection__episode_by_playlist_index_reversed.txt b/tests/resources/transaction_log_summaries/unit/jellyfin_tv_show_collection/season_by_collection__episode_by_playlist_index/s_2/is_yt_0_reformatted_to_season_by_collection__episode_by_playlist_index_reversed.txt index 5df3afee..a74b5293 100644 --- a/tests/resources/transaction_log_summaries/unit/jellyfin_tv_show_collection/season_by_collection__episode_by_playlist_index/s_2/is_yt_0_reformatted_to_season_by_collection__episode_by_playlist_index_reversed.txt +++ b/tests/resources/transaction_log_summaries/unit/jellyfin_tv_show_collection/season_by_collection__episode_by_playlist_index/s_2/is_yt_0_reformatted_to_season_by_collection__episode_by_playlist_index_reversed.txt @@ -208,6 +208,11 @@ Files created: title: 2021-08-08 - Mock Entry 21-1 year: 2021 +Files modified: +---------------------------------------- +{output_directory}/Season 01 + s01.e000003 - Mock Entry 20-5.info.json + Files removed: ---------------------------------------- {output_directory} diff --git a/tests/resources/transaction_log_summaries/unit/jellyfin_tv_show_collection/season_by_collection__episode_by_playlist_index/s_2/is_yt_1_reformatted_to_season_by_collection__episode_by_playlist_index_reversed.txt b/tests/resources/transaction_log_summaries/unit/jellyfin_tv_show_collection/season_by_collection__episode_by_playlist_index/s_2/is_yt_1_reformatted_to_season_by_collection__episode_by_playlist_index_reversed.txt index 5df3afee..a74b5293 100644 --- a/tests/resources/transaction_log_summaries/unit/jellyfin_tv_show_collection/season_by_collection__episode_by_playlist_index/s_2/is_yt_1_reformatted_to_season_by_collection__episode_by_playlist_index_reversed.txt +++ b/tests/resources/transaction_log_summaries/unit/jellyfin_tv_show_collection/season_by_collection__episode_by_playlist_index/s_2/is_yt_1_reformatted_to_season_by_collection__episode_by_playlist_index_reversed.txt @@ -208,6 +208,11 @@ Files created: title: 2021-08-08 - Mock Entry 21-1 year: 2021 +Files modified: +---------------------------------------- +{output_directory}/Season 01 + s01.e000003 - Mock Entry 20-5.info.json + Files removed: ---------------------------------------- {output_directory} diff --git a/tests/resources/transaction_log_summaries/unit/jellyfin_tv_show_collection/season_by_collection__episode_by_playlist_index_reversed/s_1/is_yt_0_reformatted_to_season_by_collection__episode_by_playlist_index_reversed.txt b/tests/resources/transaction_log_summaries/unit/jellyfin_tv_show_collection/season_by_collection__episode_by_playlist_index_reversed/s_1/is_yt_0_reformatted_to_season_by_collection__episode_by_playlist_index_reversed.txt index a74ca655..e5eedf1e 100644 --- a/tests/resources/transaction_log_summaries/unit/jellyfin_tv_show_collection/season_by_collection__episode_by_playlist_index_reversed/s_1/is_yt_0_reformatted_to_season_by_collection__episode_by_playlist_index_reversed.txt +++ b/tests/resources/transaction_log_summaries/unit/jellyfin_tv_show_collection/season_by_collection__episode_by_playlist_index_reversed/s_1/is_yt_0_reformatted_to_season_by_collection__episode_by_playlist_index_reversed.txt @@ -3,6 +3,14 @@ Files created: {output_directory} .ytdl-sub-Best Prebuilt TV Show Collection-download-archive.json +Files modified: +---------------------------------------- +{output_directory}/Season 01 + s01.e000001 - Mock Entry 20-3.info.json + s01.e000002 - Mock Entry 20-2.info.json + s01.e000003 - Mock Entry 20-1.info.json + s01.e000004 - Mock Entry 21-1.info.json + Files removed: ---------------------------------------- {output_directory} diff --git a/tests/resources/transaction_log_summaries/unit/jellyfin_tv_show_collection/season_by_collection__episode_by_playlist_index_reversed/s_1/is_yt_1_reformatted_to_season_by_collection__episode_by_playlist_index_reversed.txt b/tests/resources/transaction_log_summaries/unit/jellyfin_tv_show_collection/season_by_collection__episode_by_playlist_index_reversed/s_1/is_yt_1_reformatted_to_season_by_collection__episode_by_playlist_index_reversed.txt index a74ca655..e5eedf1e 100644 --- a/tests/resources/transaction_log_summaries/unit/jellyfin_tv_show_collection/season_by_collection__episode_by_playlist_index_reversed/s_1/is_yt_1_reformatted_to_season_by_collection__episode_by_playlist_index_reversed.txt +++ b/tests/resources/transaction_log_summaries/unit/jellyfin_tv_show_collection/season_by_collection__episode_by_playlist_index_reversed/s_1/is_yt_1_reformatted_to_season_by_collection__episode_by_playlist_index_reversed.txt @@ -3,6 +3,14 @@ Files created: {output_directory} .ytdl-sub-Best Prebuilt TV Show Collection-download-archive.json +Files modified: +---------------------------------------- +{output_directory}/Season 01 + s01.e000001 - Mock Entry 20-3.info.json + s01.e000002 - Mock Entry 20-2.info.json + s01.e000003 - Mock Entry 20-1.info.json + s01.e000004 - Mock Entry 21-1.info.json + Files removed: ---------------------------------------- {output_directory} diff --git a/tests/resources/transaction_log_summaries/unit/jellyfin_tv_show_collection/season_by_collection__episode_by_playlist_index_reversed/s_2/is_yt_0_reformatted_to_season_by_collection__episode_by_playlist_index_reversed.txt b/tests/resources/transaction_log_summaries/unit/jellyfin_tv_show_collection/season_by_collection__episode_by_playlist_index_reversed/s_2/is_yt_0_reformatted_to_season_by_collection__episode_by_playlist_index_reversed.txt index a74ca655..3b4a10f6 100644 --- a/tests/resources/transaction_log_summaries/unit/jellyfin_tv_show_collection/season_by_collection__episode_by_playlist_index_reversed/s_2/is_yt_0_reformatted_to_season_by_collection__episode_by_playlist_index_reversed.txt +++ b/tests/resources/transaction_log_summaries/unit/jellyfin_tv_show_collection/season_by_collection__episode_by_playlist_index_reversed/s_2/is_yt_0_reformatted_to_season_by_collection__episode_by_playlist_index_reversed.txt @@ -3,6 +3,19 @@ Files created: {output_directory} .ytdl-sub-Best Prebuilt TV Show Collection-download-archive.json +Files modified: +---------------------------------------- +{output_directory}/Season 01 + s01.e000001 - Mock Entry 20-7.info.json + s01.e000002 - Mock Entry 20-6.info.json + s01.e000003 - Mock Entry 20-5.info.json + s01.e000004 - Mock Entry 20-4.info.json +{output_directory}/Season 02 + s02.e000001 - Mock Entry 20-3.info.json + s02.e000002 - Mock Entry 20-2.info.json + s02.e000003 - Mock Entry 20-1.info.json + s02.e000004 - Mock Entry 21-1.info.json + Files removed: ---------------------------------------- {output_directory} diff --git a/tests/resources/transaction_log_summaries/unit/jellyfin_tv_show_collection/season_by_collection__episode_by_playlist_index_reversed/s_2/is_yt_1_reformatted_to_season_by_collection__episode_by_playlist_index_reversed.txt b/tests/resources/transaction_log_summaries/unit/jellyfin_tv_show_collection/season_by_collection__episode_by_playlist_index_reversed/s_2/is_yt_1_reformatted_to_season_by_collection__episode_by_playlist_index_reversed.txt index a74ca655..3b4a10f6 100644 --- a/tests/resources/transaction_log_summaries/unit/jellyfin_tv_show_collection/season_by_collection__episode_by_playlist_index_reversed/s_2/is_yt_1_reformatted_to_season_by_collection__episode_by_playlist_index_reversed.txt +++ b/tests/resources/transaction_log_summaries/unit/jellyfin_tv_show_collection/season_by_collection__episode_by_playlist_index_reversed/s_2/is_yt_1_reformatted_to_season_by_collection__episode_by_playlist_index_reversed.txt @@ -3,6 +3,19 @@ Files created: {output_directory} .ytdl-sub-Best Prebuilt TV Show Collection-download-archive.json +Files modified: +---------------------------------------- +{output_directory}/Season 01 + s01.e000001 - Mock Entry 20-7.info.json + s01.e000002 - Mock Entry 20-6.info.json + s01.e000003 - Mock Entry 20-5.info.json + s01.e000004 - Mock Entry 20-4.info.json +{output_directory}/Season 02 + s02.e000001 - Mock Entry 20-3.info.json + s02.e000002 - Mock Entry 20-2.info.json + s02.e000003 - Mock Entry 20-1.info.json + s02.e000004 - Mock Entry 21-1.info.json + Files removed: ---------------------------------------- {output_directory} diff --git a/tests/resources/transaction_log_summaries/unit/kodi_tv_show_by_date/season_by_year__episode_by_download_index/is_yt_0_many_urls_reformatted_to_season_by_year__episode_by_download_index.txt b/tests/resources/transaction_log_summaries/unit/kodi_tv_show_by_date/season_by_year__episode_by_download_index/is_yt_0_many_urls_reformatted_to_season_by_year__episode_by_download_index.txt index 0e8ee84a..bf8c85ca 100644 --- a/tests/resources/transaction_log_summaries/unit/kodi_tv_show_by_date/season_by_year__episode_by_download_index/is_yt_0_many_urls_reformatted_to_season_by_year__episode_by_download_index.txt +++ b/tests/resources/transaction_log_summaries/unit/kodi_tv_show_by_date/season_by_year__episode_by_download_index/is_yt_0_many_urls_reformatted_to_season_by_year__episode_by_download_index.txt @@ -3,6 +3,19 @@ Files created: {output_directory} .ytdl-sub-Best Prebuilt TV Show by Date-download-archive.json +Files modified: +---------------------------------------- +{output_directory}/Season 2020 + s2020.e000001 - Mock Entry 20-3.info.json + s2020.e000002 - Mock Entry 20-2.info.json + s2020.e000003 - Mock Entry 20-1.info.json + s2020.e000005 - Mock Entry 20-7.info.json + s2020.e000006 - Mock Entry 20-6.info.json + s2020.e000007 - Mock Entry 20-5.info.json + s2020.e000008 - Mock Entry 20-4.info.json +{output_directory}/Season 2021 + s2021.e000004 - Mock Entry 21-1.info.json + Files removed: ---------------------------------------- {output_directory} diff --git a/tests/resources/transaction_log_summaries/unit/kodi_tv_show_by_date/season_by_year__episode_by_download_index/is_yt_0_reformatted_to_season_by_year__episode_by_download_index.txt b/tests/resources/transaction_log_summaries/unit/kodi_tv_show_by_date/season_by_year__episode_by_download_index/is_yt_0_reformatted_to_season_by_year__episode_by_download_index.txt index 0e8ee84a..68b55020 100644 --- a/tests/resources/transaction_log_summaries/unit/kodi_tv_show_by_date/season_by_year__episode_by_download_index/is_yt_0_reformatted_to_season_by_year__episode_by_download_index.txt +++ b/tests/resources/transaction_log_summaries/unit/kodi_tv_show_by_date/season_by_year__episode_by_download_index/is_yt_0_reformatted_to_season_by_year__episode_by_download_index.txt @@ -3,6 +3,15 @@ Files created: {output_directory} .ytdl-sub-Best Prebuilt TV Show by Date-download-archive.json +Files modified: +---------------------------------------- +{output_directory}/Season 2020 + s2020.e000001 - Mock Entry 20-3.info.json + s2020.e000002 - Mock Entry 20-2.info.json + s2020.e000003 - Mock Entry 20-1.info.json +{output_directory}/Season 2021 + s2021.e000004 - Mock Entry 21-1.info.json + Files removed: ---------------------------------------- {output_directory} diff --git a/tests/resources/transaction_log_summaries/unit/kodi_tv_show_by_date/season_by_year__episode_by_download_index/is_yt_1_many_urls_reformatted_to_season_by_year__episode_by_download_index.txt b/tests/resources/transaction_log_summaries/unit/kodi_tv_show_by_date/season_by_year__episode_by_download_index/is_yt_1_many_urls_reformatted_to_season_by_year__episode_by_download_index.txt index 0e8ee84a..bf8c85ca 100644 --- a/tests/resources/transaction_log_summaries/unit/kodi_tv_show_by_date/season_by_year__episode_by_download_index/is_yt_1_many_urls_reformatted_to_season_by_year__episode_by_download_index.txt +++ b/tests/resources/transaction_log_summaries/unit/kodi_tv_show_by_date/season_by_year__episode_by_download_index/is_yt_1_many_urls_reformatted_to_season_by_year__episode_by_download_index.txt @@ -3,6 +3,19 @@ Files created: {output_directory} .ytdl-sub-Best Prebuilt TV Show by Date-download-archive.json +Files modified: +---------------------------------------- +{output_directory}/Season 2020 + s2020.e000001 - Mock Entry 20-3.info.json + s2020.e000002 - Mock Entry 20-2.info.json + s2020.e000003 - Mock Entry 20-1.info.json + s2020.e000005 - Mock Entry 20-7.info.json + s2020.e000006 - Mock Entry 20-6.info.json + s2020.e000007 - Mock Entry 20-5.info.json + s2020.e000008 - Mock Entry 20-4.info.json +{output_directory}/Season 2021 + s2021.e000004 - Mock Entry 21-1.info.json + Files removed: ---------------------------------------- {output_directory} diff --git a/tests/resources/transaction_log_summaries/unit/kodi_tv_show_by_date/season_by_year__episode_by_download_index/is_yt_1_reformatted_to_season_by_year__episode_by_download_index.txt b/tests/resources/transaction_log_summaries/unit/kodi_tv_show_by_date/season_by_year__episode_by_download_index/is_yt_1_reformatted_to_season_by_year__episode_by_download_index.txt index 0e8ee84a..68b55020 100644 --- a/tests/resources/transaction_log_summaries/unit/kodi_tv_show_by_date/season_by_year__episode_by_download_index/is_yt_1_reformatted_to_season_by_year__episode_by_download_index.txt +++ b/tests/resources/transaction_log_summaries/unit/kodi_tv_show_by_date/season_by_year__episode_by_download_index/is_yt_1_reformatted_to_season_by_year__episode_by_download_index.txt @@ -3,6 +3,15 @@ Files created: {output_directory} .ytdl-sub-Best Prebuilt TV Show by Date-download-archive.json +Files modified: +---------------------------------------- +{output_directory}/Season 2020 + s2020.e000001 - Mock Entry 20-3.info.json + s2020.e000002 - Mock Entry 20-2.info.json + s2020.e000003 - Mock Entry 20-1.info.json +{output_directory}/Season 2021 + s2021.e000004 - Mock Entry 21-1.info.json + Files removed: ---------------------------------------- {output_directory} diff --git a/tests/resources/transaction_log_summaries/unit/kodi_tv_show_collection/season_by_collection__episode_by_playlist_index/s_2/is_yt_0_reformatted_to_season_by_collection__episode_by_playlist_index_reversed.txt b/tests/resources/transaction_log_summaries/unit/kodi_tv_show_collection/season_by_collection__episode_by_playlist_index/s_2/is_yt_0_reformatted_to_season_by_collection__episode_by_playlist_index_reversed.txt index 5df3afee..a74b5293 100644 --- a/tests/resources/transaction_log_summaries/unit/kodi_tv_show_collection/season_by_collection__episode_by_playlist_index/s_2/is_yt_0_reformatted_to_season_by_collection__episode_by_playlist_index_reversed.txt +++ b/tests/resources/transaction_log_summaries/unit/kodi_tv_show_collection/season_by_collection__episode_by_playlist_index/s_2/is_yt_0_reformatted_to_season_by_collection__episode_by_playlist_index_reversed.txt @@ -208,6 +208,11 @@ Files created: title: 2021-08-08 - Mock Entry 21-1 year: 2021 +Files modified: +---------------------------------------- +{output_directory}/Season 01 + s01.e000003 - Mock Entry 20-5.info.json + Files removed: ---------------------------------------- {output_directory} diff --git a/tests/resources/transaction_log_summaries/unit/kodi_tv_show_collection/season_by_collection__episode_by_playlist_index/s_2/is_yt_1_reformatted_to_season_by_collection__episode_by_playlist_index_reversed.txt b/tests/resources/transaction_log_summaries/unit/kodi_tv_show_collection/season_by_collection__episode_by_playlist_index/s_2/is_yt_1_reformatted_to_season_by_collection__episode_by_playlist_index_reversed.txt index 5df3afee..a74b5293 100644 --- a/tests/resources/transaction_log_summaries/unit/kodi_tv_show_collection/season_by_collection__episode_by_playlist_index/s_2/is_yt_1_reformatted_to_season_by_collection__episode_by_playlist_index_reversed.txt +++ b/tests/resources/transaction_log_summaries/unit/kodi_tv_show_collection/season_by_collection__episode_by_playlist_index/s_2/is_yt_1_reformatted_to_season_by_collection__episode_by_playlist_index_reversed.txt @@ -208,6 +208,11 @@ Files created: title: 2021-08-08 - Mock Entry 21-1 year: 2021 +Files modified: +---------------------------------------- +{output_directory}/Season 01 + s01.e000003 - Mock Entry 20-5.info.json + Files removed: ---------------------------------------- {output_directory} diff --git a/tests/resources/transaction_log_summaries/unit/kodi_tv_show_collection/season_by_collection__episode_by_playlist_index_reversed/s_1/is_yt_0_reformatted_to_season_by_collection__episode_by_playlist_index_reversed.txt b/tests/resources/transaction_log_summaries/unit/kodi_tv_show_collection/season_by_collection__episode_by_playlist_index_reversed/s_1/is_yt_0_reformatted_to_season_by_collection__episode_by_playlist_index_reversed.txt index a74ca655..e5eedf1e 100644 --- a/tests/resources/transaction_log_summaries/unit/kodi_tv_show_collection/season_by_collection__episode_by_playlist_index_reversed/s_1/is_yt_0_reformatted_to_season_by_collection__episode_by_playlist_index_reversed.txt +++ b/tests/resources/transaction_log_summaries/unit/kodi_tv_show_collection/season_by_collection__episode_by_playlist_index_reversed/s_1/is_yt_0_reformatted_to_season_by_collection__episode_by_playlist_index_reversed.txt @@ -3,6 +3,14 @@ Files created: {output_directory} .ytdl-sub-Best Prebuilt TV Show Collection-download-archive.json +Files modified: +---------------------------------------- +{output_directory}/Season 01 + s01.e000001 - Mock Entry 20-3.info.json + s01.e000002 - Mock Entry 20-2.info.json + s01.e000003 - Mock Entry 20-1.info.json + s01.e000004 - Mock Entry 21-1.info.json + Files removed: ---------------------------------------- {output_directory} diff --git a/tests/resources/transaction_log_summaries/unit/kodi_tv_show_collection/season_by_collection__episode_by_playlist_index_reversed/s_1/is_yt_1_reformatted_to_season_by_collection__episode_by_playlist_index_reversed.txt b/tests/resources/transaction_log_summaries/unit/kodi_tv_show_collection/season_by_collection__episode_by_playlist_index_reversed/s_1/is_yt_1_reformatted_to_season_by_collection__episode_by_playlist_index_reversed.txt index a74ca655..e5eedf1e 100644 --- a/tests/resources/transaction_log_summaries/unit/kodi_tv_show_collection/season_by_collection__episode_by_playlist_index_reversed/s_1/is_yt_1_reformatted_to_season_by_collection__episode_by_playlist_index_reversed.txt +++ b/tests/resources/transaction_log_summaries/unit/kodi_tv_show_collection/season_by_collection__episode_by_playlist_index_reversed/s_1/is_yt_1_reformatted_to_season_by_collection__episode_by_playlist_index_reversed.txt @@ -3,6 +3,14 @@ Files created: {output_directory} .ytdl-sub-Best Prebuilt TV Show Collection-download-archive.json +Files modified: +---------------------------------------- +{output_directory}/Season 01 + s01.e000001 - Mock Entry 20-3.info.json + s01.e000002 - Mock Entry 20-2.info.json + s01.e000003 - Mock Entry 20-1.info.json + s01.e000004 - Mock Entry 21-1.info.json + Files removed: ---------------------------------------- {output_directory} diff --git a/tests/resources/transaction_log_summaries/unit/kodi_tv_show_collection/season_by_collection__episode_by_playlist_index_reversed/s_2/is_yt_0_reformatted_to_season_by_collection__episode_by_playlist_index_reversed.txt b/tests/resources/transaction_log_summaries/unit/kodi_tv_show_collection/season_by_collection__episode_by_playlist_index_reversed/s_2/is_yt_0_reformatted_to_season_by_collection__episode_by_playlist_index_reversed.txt index a74ca655..3b4a10f6 100644 --- a/tests/resources/transaction_log_summaries/unit/kodi_tv_show_collection/season_by_collection__episode_by_playlist_index_reversed/s_2/is_yt_0_reformatted_to_season_by_collection__episode_by_playlist_index_reversed.txt +++ b/tests/resources/transaction_log_summaries/unit/kodi_tv_show_collection/season_by_collection__episode_by_playlist_index_reversed/s_2/is_yt_0_reformatted_to_season_by_collection__episode_by_playlist_index_reversed.txt @@ -3,6 +3,19 @@ Files created: {output_directory} .ytdl-sub-Best Prebuilt TV Show Collection-download-archive.json +Files modified: +---------------------------------------- +{output_directory}/Season 01 + s01.e000001 - Mock Entry 20-7.info.json + s01.e000002 - Mock Entry 20-6.info.json + s01.e000003 - Mock Entry 20-5.info.json + s01.e000004 - Mock Entry 20-4.info.json +{output_directory}/Season 02 + s02.e000001 - Mock Entry 20-3.info.json + s02.e000002 - Mock Entry 20-2.info.json + s02.e000003 - Mock Entry 20-1.info.json + s02.e000004 - Mock Entry 21-1.info.json + Files removed: ---------------------------------------- {output_directory} diff --git a/tests/resources/transaction_log_summaries/unit/kodi_tv_show_collection/season_by_collection__episode_by_playlist_index_reversed/s_2/is_yt_1_reformatted_to_season_by_collection__episode_by_playlist_index_reversed.txt b/tests/resources/transaction_log_summaries/unit/kodi_tv_show_collection/season_by_collection__episode_by_playlist_index_reversed/s_2/is_yt_1_reformatted_to_season_by_collection__episode_by_playlist_index_reversed.txt index a74ca655..3b4a10f6 100644 --- a/tests/resources/transaction_log_summaries/unit/kodi_tv_show_collection/season_by_collection__episode_by_playlist_index_reversed/s_2/is_yt_1_reformatted_to_season_by_collection__episode_by_playlist_index_reversed.txt +++ b/tests/resources/transaction_log_summaries/unit/kodi_tv_show_collection/season_by_collection__episode_by_playlist_index_reversed/s_2/is_yt_1_reformatted_to_season_by_collection__episode_by_playlist_index_reversed.txt @@ -3,6 +3,19 @@ Files created: {output_directory} .ytdl-sub-Best Prebuilt TV Show Collection-download-archive.json +Files modified: +---------------------------------------- +{output_directory}/Season 01 + s01.e000001 - Mock Entry 20-7.info.json + s01.e000002 - Mock Entry 20-6.info.json + s01.e000003 - Mock Entry 20-5.info.json + s01.e000004 - Mock Entry 20-4.info.json +{output_directory}/Season 02 + s02.e000001 - Mock Entry 20-3.info.json + s02.e000002 - Mock Entry 20-2.info.json + s02.e000003 - Mock Entry 20-1.info.json + s02.e000004 - Mock Entry 21-1.info.json + Files removed: ---------------------------------------- {output_directory} diff --git a/tests/resources/transaction_log_summaries/unit/plex_tv_show_by_date/season_by_year__episode_by_download_index/is_yt_0_many_urls_reformatted_to_season_by_year__episode_by_download_index.txt b/tests/resources/transaction_log_summaries/unit/plex_tv_show_by_date/season_by_year__episode_by_download_index/is_yt_0_many_urls_reformatted_to_season_by_year__episode_by_download_index.txt index 0e8ee84a..03b8d35c 100644 --- a/tests/resources/transaction_log_summaries/unit/plex_tv_show_by_date/season_by_year__episode_by_download_index/is_yt_0_many_urls_reformatted_to_season_by_year__episode_by_download_index.txt +++ b/tests/resources/transaction_log_summaries/unit/plex_tv_show_by_date/season_by_year__episode_by_download_index/is_yt_0_many_urls_reformatted_to_season_by_year__episode_by_download_index.txt @@ -3,6 +3,19 @@ Files created: {output_directory} .ytdl-sub-Best Prebuilt TV Show by Date-download-archive.json +Files modified: +---------------------------------------- +{output_directory}/Season 2020 + s2020.e000001 - Mock Entry 20-3.info.json + s2020.e000002 - Mock Entry 20-2.info.json + s2020.e000003 - Mock Entry 20-1.info.json + s2020.e000005 - Mock Entry 20-7.info.json + s2020.e000006 - Mock Entry 20-6.info.json + s2020.e000007 - Mock Entry 20-5.info.json + s2020.e000008 - Mock Entry 20-4.info.json +{output_directory}/Season 2021 + s2021.e000004 - Mock Entry 21-1.info.json + Files removed: ---------------------------------------- {output_directory} diff --git a/tests/resources/transaction_log_summaries/unit/plex_tv_show_by_date/season_by_year__episode_by_download_index/is_yt_0_reformatted_to_season_by_year__episode_by_download_index.txt b/tests/resources/transaction_log_summaries/unit/plex_tv_show_by_date/season_by_year__episode_by_download_index/is_yt_0_reformatted_to_season_by_year__episode_by_download_index.txt index 0e8ee84a..7fce7e69 100644 --- a/tests/resources/transaction_log_summaries/unit/plex_tv_show_by_date/season_by_year__episode_by_download_index/is_yt_0_reformatted_to_season_by_year__episode_by_download_index.txt +++ b/tests/resources/transaction_log_summaries/unit/plex_tv_show_by_date/season_by_year__episode_by_download_index/is_yt_0_reformatted_to_season_by_year__episode_by_download_index.txt @@ -3,6 +3,15 @@ Files created: {output_directory} .ytdl-sub-Best Prebuilt TV Show by Date-download-archive.json +Files modified: +---------------------------------------- +{output_directory}/Season 2020 + s2020.e000001 - Mock Entry 20-3.info.json + s2020.e000002 - Mock Entry 20-2.info.json + s2020.e000003 - Mock Entry 20-1.info.json +{output_directory}/Season 2021 + s2021.e000004 - Mock Entry 21-1.info.json + Files removed: ---------------------------------------- {output_directory} diff --git a/tests/resources/transaction_log_summaries/unit/plex_tv_show_by_date/season_by_year__episode_by_download_index/is_yt_1_many_urls_reformatted_to_season_by_year__episode_by_download_index.txt b/tests/resources/transaction_log_summaries/unit/plex_tv_show_by_date/season_by_year__episode_by_download_index/is_yt_1_many_urls_reformatted_to_season_by_year__episode_by_download_index.txt index 0e8ee84a..03b8d35c 100644 --- a/tests/resources/transaction_log_summaries/unit/plex_tv_show_by_date/season_by_year__episode_by_download_index/is_yt_1_many_urls_reformatted_to_season_by_year__episode_by_download_index.txt +++ b/tests/resources/transaction_log_summaries/unit/plex_tv_show_by_date/season_by_year__episode_by_download_index/is_yt_1_many_urls_reformatted_to_season_by_year__episode_by_download_index.txt @@ -3,6 +3,19 @@ Files created: {output_directory} .ytdl-sub-Best Prebuilt TV Show by Date-download-archive.json +Files modified: +---------------------------------------- +{output_directory}/Season 2020 + s2020.e000001 - Mock Entry 20-3.info.json + s2020.e000002 - Mock Entry 20-2.info.json + s2020.e000003 - Mock Entry 20-1.info.json + s2020.e000005 - Mock Entry 20-7.info.json + s2020.e000006 - Mock Entry 20-6.info.json + s2020.e000007 - Mock Entry 20-5.info.json + s2020.e000008 - Mock Entry 20-4.info.json +{output_directory}/Season 2021 + s2021.e000004 - Mock Entry 21-1.info.json + Files removed: ---------------------------------------- {output_directory} diff --git a/tests/resources/transaction_log_summaries/unit/plex_tv_show_by_date/season_by_year__episode_by_download_index/is_yt_1_reformatted_to_season_by_year__episode_by_download_index.txt b/tests/resources/transaction_log_summaries/unit/plex_tv_show_by_date/season_by_year__episode_by_download_index/is_yt_1_reformatted_to_season_by_year__episode_by_download_index.txt index 0e8ee84a..7fce7e69 100644 --- a/tests/resources/transaction_log_summaries/unit/plex_tv_show_by_date/season_by_year__episode_by_download_index/is_yt_1_reformatted_to_season_by_year__episode_by_download_index.txt +++ b/tests/resources/transaction_log_summaries/unit/plex_tv_show_by_date/season_by_year__episode_by_download_index/is_yt_1_reformatted_to_season_by_year__episode_by_download_index.txt @@ -3,6 +3,15 @@ Files created: {output_directory} .ytdl-sub-Best Prebuilt TV Show by Date-download-archive.json +Files modified: +---------------------------------------- +{output_directory}/Season 2020 + s2020.e000001 - Mock Entry 20-3.info.json + s2020.e000002 - Mock Entry 20-2.info.json + s2020.e000003 - Mock Entry 20-1.info.json +{output_directory}/Season 2021 + s2021.e000004 - Mock Entry 21-1.info.json + Files removed: ---------------------------------------- {output_directory} diff --git a/tests/resources/transaction_log_summaries/unit/plex_tv_show_collection/season_by_collection__episode_by_playlist_index/s_2/is_yt_0_reformatted_to_season_by_collection__episode_by_playlist_index_reversed.txt b/tests/resources/transaction_log_summaries/unit/plex_tv_show_collection/season_by_collection__episode_by_playlist_index/s_2/is_yt_0_reformatted_to_season_by_collection__episode_by_playlist_index_reversed.txt index eb4eea57..12ec9ae4 100644 --- a/tests/resources/transaction_log_summaries/unit/plex_tv_show_collection/season_by_collection__episode_by_playlist_index/s_2/is_yt_0_reformatted_to_season_by_collection__episode_by_playlist_index_reversed.txt +++ b/tests/resources/transaction_log_summaries/unit/plex_tv_show_collection/season_by_collection__episode_by_playlist_index/s_2/is_yt_0_reformatted_to_season_by_collection__episode_by_playlist_index_reversed.txt @@ -110,6 +110,11 @@ Files created: title: 2021-08-08 - Mock Entry 21-1 year: 2021 +Files modified: +---------------------------------------- +{output_directory}/Season 01 + s01.e000003 - Mock Entry 20-5.info.json + Files removed: ---------------------------------------- {output_directory} diff --git a/tests/resources/transaction_log_summaries/unit/plex_tv_show_collection/season_by_collection__episode_by_playlist_index/s_2/is_yt_1_reformatted_to_season_by_collection__episode_by_playlist_index_reversed.txt b/tests/resources/transaction_log_summaries/unit/plex_tv_show_collection/season_by_collection__episode_by_playlist_index/s_2/is_yt_1_reformatted_to_season_by_collection__episode_by_playlist_index_reversed.txt index eb4eea57..12ec9ae4 100644 --- a/tests/resources/transaction_log_summaries/unit/plex_tv_show_collection/season_by_collection__episode_by_playlist_index/s_2/is_yt_1_reformatted_to_season_by_collection__episode_by_playlist_index_reversed.txt +++ b/tests/resources/transaction_log_summaries/unit/plex_tv_show_collection/season_by_collection__episode_by_playlist_index/s_2/is_yt_1_reformatted_to_season_by_collection__episode_by_playlist_index_reversed.txt @@ -110,6 +110,11 @@ Files created: title: 2021-08-08 - Mock Entry 21-1 year: 2021 +Files modified: +---------------------------------------- +{output_directory}/Season 01 + s01.e000003 - Mock Entry 20-5.info.json + Files removed: ---------------------------------------- {output_directory} diff --git a/tests/resources/transaction_log_summaries/unit/plex_tv_show_collection/season_by_collection__episode_by_playlist_index_reversed/s_1/is_yt_0_reformatted_to_season_by_collection__episode_by_playlist_index_reversed.txt b/tests/resources/transaction_log_summaries/unit/plex_tv_show_collection/season_by_collection__episode_by_playlist_index_reversed/s_1/is_yt_0_reformatted_to_season_by_collection__episode_by_playlist_index_reversed.txt index a74ca655..e5130f6a 100644 --- a/tests/resources/transaction_log_summaries/unit/plex_tv_show_collection/season_by_collection__episode_by_playlist_index_reversed/s_1/is_yt_0_reformatted_to_season_by_collection__episode_by_playlist_index_reversed.txt +++ b/tests/resources/transaction_log_summaries/unit/plex_tv_show_collection/season_by_collection__episode_by_playlist_index_reversed/s_1/is_yt_0_reformatted_to_season_by_collection__episode_by_playlist_index_reversed.txt @@ -3,6 +3,14 @@ Files created: {output_directory} .ytdl-sub-Best Prebuilt TV Show Collection-download-archive.json +Files modified: +---------------------------------------- +{output_directory}/Season 01 + s01.e000001 - Mock Entry 20-3.info.json + s01.e000002 - Mock Entry 20-2.info.json + s01.e000003 - Mock Entry 20-1.info.json + s01.e000004 - Mock Entry 21-1.info.json + Files removed: ---------------------------------------- {output_directory} diff --git a/tests/resources/transaction_log_summaries/unit/plex_tv_show_collection/season_by_collection__episode_by_playlist_index_reversed/s_1/is_yt_1_reformatted_to_season_by_collection__episode_by_playlist_index_reversed.txt b/tests/resources/transaction_log_summaries/unit/plex_tv_show_collection/season_by_collection__episode_by_playlist_index_reversed/s_1/is_yt_1_reformatted_to_season_by_collection__episode_by_playlist_index_reversed.txt index a74ca655..e5130f6a 100644 --- a/tests/resources/transaction_log_summaries/unit/plex_tv_show_collection/season_by_collection__episode_by_playlist_index_reversed/s_1/is_yt_1_reformatted_to_season_by_collection__episode_by_playlist_index_reversed.txt +++ b/tests/resources/transaction_log_summaries/unit/plex_tv_show_collection/season_by_collection__episode_by_playlist_index_reversed/s_1/is_yt_1_reformatted_to_season_by_collection__episode_by_playlist_index_reversed.txt @@ -3,6 +3,14 @@ Files created: {output_directory} .ytdl-sub-Best Prebuilt TV Show Collection-download-archive.json +Files modified: +---------------------------------------- +{output_directory}/Season 01 + s01.e000001 - Mock Entry 20-3.info.json + s01.e000002 - Mock Entry 20-2.info.json + s01.e000003 - Mock Entry 20-1.info.json + s01.e000004 - Mock Entry 21-1.info.json + Files removed: ---------------------------------------- {output_directory} diff --git a/tests/resources/transaction_log_summaries/unit/plex_tv_show_collection/season_by_collection__episode_by_playlist_index_reversed/s_2/is_yt_0_reformatted_to_season_by_collection__episode_by_playlist_index_reversed.txt b/tests/resources/transaction_log_summaries/unit/plex_tv_show_collection/season_by_collection__episode_by_playlist_index_reversed/s_2/is_yt_0_reformatted_to_season_by_collection__episode_by_playlist_index_reversed.txt index a74ca655..a2ea72f9 100644 --- a/tests/resources/transaction_log_summaries/unit/plex_tv_show_collection/season_by_collection__episode_by_playlist_index_reversed/s_2/is_yt_0_reformatted_to_season_by_collection__episode_by_playlist_index_reversed.txt +++ b/tests/resources/transaction_log_summaries/unit/plex_tv_show_collection/season_by_collection__episode_by_playlist_index_reversed/s_2/is_yt_0_reformatted_to_season_by_collection__episode_by_playlist_index_reversed.txt @@ -3,6 +3,19 @@ Files created: {output_directory} .ytdl-sub-Best Prebuilt TV Show Collection-download-archive.json +Files modified: +---------------------------------------- +{output_directory}/Season 01 + s01.e000001 - Mock Entry 20-7.info.json + s01.e000002 - Mock Entry 20-6.info.json + s01.e000003 - Mock Entry 20-5.info.json + s01.e000004 - Mock Entry 20-4.info.json +{output_directory}/Season 02 + s02.e000001 - Mock Entry 20-3.info.json + s02.e000002 - Mock Entry 20-2.info.json + s02.e000003 - Mock Entry 20-1.info.json + s02.e000004 - Mock Entry 21-1.info.json + Files removed: ---------------------------------------- {output_directory} diff --git a/tests/resources/transaction_log_summaries/unit/plex_tv_show_collection/season_by_collection__episode_by_playlist_index_reversed/s_2/is_yt_1_reformatted_to_season_by_collection__episode_by_playlist_index_reversed.txt b/tests/resources/transaction_log_summaries/unit/plex_tv_show_collection/season_by_collection__episode_by_playlist_index_reversed/s_2/is_yt_1_reformatted_to_season_by_collection__episode_by_playlist_index_reversed.txt index a74ca655..a2ea72f9 100644 --- a/tests/resources/transaction_log_summaries/unit/plex_tv_show_collection/season_by_collection__episode_by_playlist_index_reversed/s_2/is_yt_1_reformatted_to_season_by_collection__episode_by_playlist_index_reversed.txt +++ b/tests/resources/transaction_log_summaries/unit/plex_tv_show_collection/season_by_collection__episode_by_playlist_index_reversed/s_2/is_yt_1_reformatted_to_season_by_collection__episode_by_playlist_index_reversed.txt @@ -3,6 +3,19 @@ Files created: {output_directory} .ytdl-sub-Best Prebuilt TV Show Collection-download-archive.json +Files modified: +---------------------------------------- +{output_directory}/Season 01 + s01.e000001 - Mock Entry 20-7.info.json + s01.e000002 - Mock Entry 20-6.info.json + s01.e000003 - Mock Entry 20-5.info.json + s01.e000004 - Mock Entry 20-4.info.json +{output_directory}/Season 02 + s02.e000001 - Mock Entry 20-3.info.json + s02.e000002 - Mock Entry 20-2.info.json + s02.e000003 - Mock Entry 20-1.info.json + s02.e000004 - Mock Entry 21-1.info.json + Files removed: ---------------------------------------- {output_directory} diff --git a/tests/resources/transaction_log_summaries/youtube/test_playlist_bilateral_collection_p1.txt b/tests/resources/transaction_log_summaries/youtube/test_playlist_bilateral_collection_p1.txt new file mode 100644 index 00000000..91030c53 --- /dev/null +++ b/tests/resources/transaction_log_summaries/youtube/test_playlist_bilateral_collection_p1.txt @@ -0,0 +1,61 @@ +Files created: +---------------------------------------- +{output_directory} + .ytdl-sub-bilateral_test-download-archive.json + fanart.jpg + poster.jpg + season01-poster.jpg + tvshow.nfo + NFO tags: + tvshow: + genre: ytdl-sub + mpaa: TV-14 + namedseason: + attributes: + number: 1 + tag: bilateral test + title: bilateral_test +{output_directory}/Season 01 + s01.e11020101 - Jesse's Minecraft Server [Trailer - Feb.1]-thumb.jpg + s01.e11020101 - Jesse's Minecraft Server [Trailer - Feb.1].info.json + s01.e11020101 - Jesse's Minecraft Server [Trailer - Feb.1].mp4 + Video Tags: + contentRating: TV-14 + date: 2011-02-01 + episode_id: 11020101 + genre: ytdl-sub + show: bilateral_test + synopsis: + https://www.youtube.com/watch?v=0SVukUyys10 + + To join the server, you must apply at: + http://www.jesseminecraft.webs.com/ + + This is just a brief video of the server as of Feb. 1, 2011. + + Texture Pack I Use: + http://www.minecraftforum.net/viewtopic.php?f=25&t=29164 + title: 2011-02-01 - Jesse's Minecraft Server [Trailer - Feb.1] + year: 2011 + s01.e11020101 - Jesse's Minecraft Server [Trailer - Feb.1].nfo + NFO tags: + episodedetails: + aired: 2011-02-01 + download_index: 1 + episode: 11020101 + genre: ytdl-sub + mpaa: TV-14 + plot: + https://www.youtube.com/watch?v=0SVukUyys10 + + To join the server, you must apply at: + http://www.jesseminecraft.webs.com/ + + This is just a brief video of the server as of Feb. 1, 2011. + + Texture Pack I Use: + http://www.minecraftforum.net/viewtopic.php?f=25&t=29164 + season: 1 + subscription_has_download_archive: false + title: 2011-02-01 - Jesse's Minecraft Server [Trailer - Feb.1] + year: 2011 \ No newline at end of file diff --git a/tests/resources/transaction_log_summaries/youtube/test_playlist_bilateral_collection_p2.txt b/tests/resources/transaction_log_summaries/youtube/test_playlist_bilateral_collection_p2.txt new file mode 100644 index 00000000..2b9254c0 --- /dev/null +++ b/tests/resources/transaction_log_summaries/youtube/test_playlist_bilateral_collection_p2.txt @@ -0,0 +1,170 @@ +Files created: +---------------------------------------- +{output_directory}/Season 01 + s01.e11022701 - Jesse's Minecraft Server [Trailer - Feb.27]-thumb.jpg + s01.e11022701 - Jesse's Minecraft Server [Trailer - Feb.27].info.json + s01.e11022701 - Jesse's Minecraft Server [Trailer - Feb.27].mp4 + Video Tags: + contentRating: TV-14 + date: 2011-02-27 + episode_id: 11022701 + genre: ytdl-sub + show: bilateral_test + synopsis: + https://www.youtube.com/watch?v=qPybBrXspds + + Website Link: + http://jesseminecraft.webs.com/ + + All you have to do is read the rules, and fill out a quick, little application to join the Server so we know you read them. We do this to keep the griefers/newbs out, it only takes three minutes, it's not that big of a deal. + + Jesse's Minecraft Server is a City Roleplay/Survival Server, there is a currency, public transportation, strict rules for immersive gameplay, and has a decent size population. Over 750 people have logged onto the server. It is very important you read the rules, we don't tolerate idiots. + + There are over 200 empty properties that are ready for anyone to own! Join now! + + This is the server state as of Feb. 27, 2011. + ---------------------------------------------------------------------------------- + + Texture Pack: + http://www.minecraftforum.net/viewtopic.php?f=25&t=29164 + + Recording Software: + http://www.fraps.com/download.php + + Video Editing Software: + http://explore.live.com/windows-live-movie-maker?os=other + + Song: + Given to Fly - Pearl Jam + (Off of the 'Yield' album) + + I claim no ownership of this song, all the credit goes to Pearl Jam and their producers. + title: 2011-02-27 - Jesse's Minecraft Server [Trailer - Feb.27] + year: 2011 + s01.e11022701 - Jesse's Minecraft Server [Trailer - Feb.27].nfo + NFO tags: + episodedetails: + aired: 2011-02-27 + download_index: 2 + episode: 11022701 + genre: ytdl-sub + mpaa: TV-14 + plot: + https://www.youtube.com/watch?v=qPybBrXspds + + Website Link: + http://jesseminecraft.webs.com/ + + All you have to do is read the rules, and fill out a quick, little application to join the Server so we know you read them. We do this to keep the griefers/newbs out, it only takes three minutes, it's not that big of a deal. + + Jesse's Minecraft Server is a City Roleplay/Survival Server, there is a currency, public transportation, strict rules for immersive gameplay, and has a decent size population. Over 750 people have logged onto the server. It is very important you read the rules, we don't tolerate idiots. + + There are over 200 empty properties that are ready for anyone to own! Join now! + + This is the server state as of Feb. 27, 2011. + ---------------------------------------------------------------------------------- + + Texture Pack: + http://www.minecraftforum.net/viewtopic.php?f=25&t=29164 + + Recording Software: + http://www.fraps.com/download.php + + Video Editing Software: + http://explore.live.com/windows-live-movie-maker?os=other + + Song: + Given to Fly - Pearl Jam + (Off of the 'Yield' album) + + I claim no ownership of this song, all the credit goes to Pearl Jam and their producers. + season: 1 + subscription_has_download_archive: true + title: 2011-02-27 - Jesse's Minecraft Server [Trailer - Feb.27] + year: 2011 + s01.e11032101 - Jesse's Minecraft Server [Trailer - Mar.21]-thumb.jpg + s01.e11032101 - Jesse's Minecraft Server [Trailer - Mar.21].info.json + s01.e11032101 - Jesse's Minecraft Server [Trailer - Mar.21].mp4 + Video Tags: + contentRating: TV-14 + date: 2011-03-21 + episode_id: 11032101 + genre: ytdl-sub + show: bilateral_test + synopsis: + https://www.youtube.com/watch?v=DBjFvs6HafU + + Website Link: + http://jesseminecraft.webs.com/ + + To get on the whitelist, please look at the website linked above (^^^). Due to the overwhelming amount of people trying to join, I've made it so it costs $2 to become a member through paypal. All of it is explained on the website. + + Jesse's Minecraft Server is a City Roleplay/Survival Server, there is a currency, public transportation, strict rules for immersive gameplay, and has a decent size population. Over 1000 people have logged onto the server. It is very important you read the rules, we don't tolerate idiots. + + There are over 300 empty properties that are ready for anyone to own! Join now! + + This is the server state as of Mar. 21, 2011. + --------------------------------------------------------------------------------­-- + + Texture Pack: + http://www.minecraftforum.net/viewtopic.php?f=25&t=29164 + + Recording Software: + http://www.fraps.com/download.php + + Video Editing Software: + http://explore.live.com/windows-live-movie-maker?os=other + + Song: + Indifference - Pearl Jam + (Off of the 'Vs.' album) + + I claim no ownership of this song, all the credit goes to Pearl Jam and their producers. + title: 2011-03-21 - Jesse's Minecraft Server [Trailer - Mar.21] + year: 2011 + s01.e11032101 - Jesse's Minecraft Server [Trailer - Mar.21].nfo + NFO tags: + episodedetails: + aired: 2011-03-21 + download_index: 3 + episode: 11032101 + genre: ytdl-sub + mpaa: TV-14 + plot: + https://www.youtube.com/watch?v=DBjFvs6HafU + + Website Link: + http://jesseminecraft.webs.com/ + + To get on the whitelist, please look at the website linked above (^^^). Due to the overwhelming amount of people trying to join, I've made it so it costs $2 to become a member through paypal. All of it is explained on the website. + + Jesse's Minecraft Server is a City Roleplay/Survival Server, there is a currency, public transportation, strict rules for immersive gameplay, and has a decent size population. Over 1000 people have logged onto the server. It is very important you read the rules, we don't tolerate idiots. + + There are over 300 empty properties that are ready for anyone to own! Join now! + + This is the server state as of Mar. 21, 2011. + --------------------------------------------------------------------------------­-- + + Texture Pack: + http://www.minecraftforum.net/viewtopic.php?f=25&t=29164 + + Recording Software: + http://www.fraps.com/download.php + + Video Editing Software: + http://explore.live.com/windows-live-movie-maker?os=other + + Song: + Indifference - Pearl Jam + (Off of the 'Vs.' album) + + I claim no ownership of this song, all the credit goes to Pearl Jam and their producers. + season: 1 + subscription_has_download_archive: true + title: 2011-03-21 - Jesse's Minecraft Server [Trailer - Mar.21] + year: 2011 + +Files modified: +---------------------------------------- +{output_directory} + .ytdl-sub-bilateral_test-download-archive.json \ No newline at end of file diff --git a/tests/resources/transaction_log_summaries/youtube/test_playlist_bilateral_p1.txt b/tests/resources/transaction_log_summaries/youtube/test_playlist_bilateral_p1.txt new file mode 100644 index 00000000..d1476d0b --- /dev/null +++ b/tests/resources/transaction_log_summaries/youtube/test_playlist_bilateral_p1.txt @@ -0,0 +1,56 @@ +Files created: +---------------------------------------- +{output_directory} + .ytdl-sub-bilateral_test-download-archive.json + fanart.jpg + poster.jpg + tvshow.nfo + NFO tags: + tvshow: + genre: ytdl-sub + mpaa: TV-14 + title: bilateral_test +{output_directory}/Season 2011 + s2011.e020101 - Jesse's Minecraft Server [Trailer - Feb.1]-thumb.jpg + s2011.e020101 - Jesse's Minecraft Server [Trailer - Feb.1].info.json + s2011.e020101 - Jesse's Minecraft Server [Trailer - Feb.1].mp4 + Video Tags: + contentRating: TV-14 + date: 2011-02-01 + episode_id: 20101 + genre: ytdl-sub + show: bilateral_test + synopsis: + https://www.youtube.com/watch?v=0SVukUyys10 + + To join the server, you must apply at: + http://www.jesseminecraft.webs.com/ + + This is just a brief video of the server as of Feb. 1, 2011. + + Texture Pack I Use: + http://www.minecraftforum.net/viewtopic.php?f=25&t=29164 + title: 2011-02-01 - Jesse's Minecraft Server [Trailer - Feb.1] + year: 2011 + s2011.e020101 - Jesse's Minecraft Server [Trailer - Feb.1].nfo + NFO tags: + episodedetails: + aired: 2011-02-01 + download_index: 1 + episode: 20101 + genre: ytdl-sub + mpaa: TV-14 + plot: + https://www.youtube.com/watch?v=0SVukUyys10 + + To join the server, you must apply at: + http://www.jesseminecraft.webs.com/ + + This is just a brief video of the server as of Feb. 1, 2011. + + Texture Pack I Use: + http://www.minecraftforum.net/viewtopic.php?f=25&t=29164 + season: 2011 + subscription_has_download_archive: false + title: 2011-02-01 - Jesse's Minecraft Server [Trailer - Feb.1] + year: 2011 \ No newline at end of file diff --git a/tests/resources/transaction_log_summaries/youtube/test_playlist_bilateral_p2.txt b/tests/resources/transaction_log_summaries/youtube/test_playlist_bilateral_p2.txt new file mode 100644 index 00000000..bb7ca07e --- /dev/null +++ b/tests/resources/transaction_log_summaries/youtube/test_playlist_bilateral_p2.txt @@ -0,0 +1,170 @@ +Files created: +---------------------------------------- +{output_directory}/Season 2011 + s2011.e022701 - Jesse's Minecraft Server [Trailer - Feb.27]-thumb.jpg + s2011.e022701 - Jesse's Minecraft Server [Trailer - Feb.27].info.json + s2011.e022701 - Jesse's Minecraft Server [Trailer - Feb.27].mp4 + Video Tags: + contentRating: TV-14 + date: 2011-02-27 + episode_id: 22701 + genre: ytdl-sub + show: bilateral_test + synopsis: + https://www.youtube.com/watch?v=qPybBrXspds + + Website Link: + http://jesseminecraft.webs.com/ + + All you have to do is read the rules, and fill out a quick, little application to join the Server so we know you read them. We do this to keep the griefers/newbs out, it only takes three minutes, it's not that big of a deal. + + Jesse's Minecraft Server is a City Roleplay/Survival Server, there is a currency, public transportation, strict rules for immersive gameplay, and has a decent size population. Over 750 people have logged onto the server. It is very important you read the rules, we don't tolerate idiots. + + There are over 200 empty properties that are ready for anyone to own! Join now! + + This is the server state as of Feb. 27, 2011. + ---------------------------------------------------------------------------------- + + Texture Pack: + http://www.minecraftforum.net/viewtopic.php?f=25&t=29164 + + Recording Software: + http://www.fraps.com/download.php + + Video Editing Software: + http://explore.live.com/windows-live-movie-maker?os=other + + Song: + Given to Fly - Pearl Jam + (Off of the 'Yield' album) + + I claim no ownership of this song, all the credit goes to Pearl Jam and their producers. + title: 2011-02-27 - Jesse's Minecraft Server [Trailer - Feb.27] + year: 2011 + s2011.e022701 - Jesse's Minecraft Server [Trailer - Feb.27].nfo + NFO tags: + episodedetails: + aired: 2011-02-27 + download_index: 2 + episode: 22701 + genre: ytdl-sub + mpaa: TV-14 + plot: + https://www.youtube.com/watch?v=qPybBrXspds + + Website Link: + http://jesseminecraft.webs.com/ + + All you have to do is read the rules, and fill out a quick, little application to join the Server so we know you read them. We do this to keep the griefers/newbs out, it only takes three minutes, it's not that big of a deal. + + Jesse's Minecraft Server is a City Roleplay/Survival Server, there is a currency, public transportation, strict rules for immersive gameplay, and has a decent size population. Over 750 people have logged onto the server. It is very important you read the rules, we don't tolerate idiots. + + There are over 200 empty properties that are ready for anyone to own! Join now! + + This is the server state as of Feb. 27, 2011. + ---------------------------------------------------------------------------------- + + Texture Pack: + http://www.minecraftforum.net/viewtopic.php?f=25&t=29164 + + Recording Software: + http://www.fraps.com/download.php + + Video Editing Software: + http://explore.live.com/windows-live-movie-maker?os=other + + Song: + Given to Fly - Pearl Jam + (Off of the 'Yield' album) + + I claim no ownership of this song, all the credit goes to Pearl Jam and their producers. + season: 2011 + subscription_has_download_archive: true + title: 2011-02-27 - Jesse's Minecraft Server [Trailer - Feb.27] + year: 2011 + s2011.e032101 - Jesse's Minecraft Server [Trailer - Mar.21]-thumb.jpg + s2011.e032101 - Jesse's Minecraft Server [Trailer - Mar.21].info.json + s2011.e032101 - Jesse's Minecraft Server [Trailer - Mar.21].mp4 + Video Tags: + contentRating: TV-14 + date: 2011-03-21 + episode_id: 32101 + genre: ytdl-sub + show: bilateral_test + synopsis: + https://www.youtube.com/watch?v=DBjFvs6HafU + + Website Link: + http://jesseminecraft.webs.com/ + + To get on the whitelist, please look at the website linked above (^^^). Due to the overwhelming amount of people trying to join, I've made it so it costs $2 to become a member through paypal. All of it is explained on the website. + + Jesse's Minecraft Server is a City Roleplay/Survival Server, there is a currency, public transportation, strict rules for immersive gameplay, and has a decent size population. Over 1000 people have logged onto the server. It is very important you read the rules, we don't tolerate idiots. + + There are over 300 empty properties that are ready for anyone to own! Join now! + + This is the server state as of Mar. 21, 2011. + --------------------------------------------------------------------------------­-- + + Texture Pack: + http://www.minecraftforum.net/viewtopic.php?f=25&t=29164 + + Recording Software: + http://www.fraps.com/download.php + + Video Editing Software: + http://explore.live.com/windows-live-movie-maker?os=other + + Song: + Indifference - Pearl Jam + (Off of the 'Vs.' album) + + I claim no ownership of this song, all the credit goes to Pearl Jam and their producers. + title: 2011-03-21 - Jesse's Minecraft Server [Trailer - Mar.21] + year: 2011 + s2011.e032101 - Jesse's Minecraft Server [Trailer - Mar.21].nfo + NFO tags: + episodedetails: + aired: 2011-03-21 + download_index: 3 + episode: 32101 + genre: ytdl-sub + mpaa: TV-14 + plot: + https://www.youtube.com/watch?v=DBjFvs6HafU + + Website Link: + http://jesseminecraft.webs.com/ + + To get on the whitelist, please look at the website linked above (^^^). Due to the overwhelming amount of people trying to join, I've made it so it costs $2 to become a member through paypal. All of it is explained on the website. + + Jesse's Minecraft Server is a City Roleplay/Survival Server, there is a currency, public transportation, strict rules for immersive gameplay, and has a decent size population. Over 1000 people have logged onto the server. It is very important you read the rules, we don't tolerate idiots. + + There are over 300 empty properties that are ready for anyone to own! Join now! + + This is the server state as of Mar. 21, 2011. + --------------------------------------------------------------------------------­-- + + Texture Pack: + http://www.minecraftforum.net/viewtopic.php?f=25&t=29164 + + Recording Software: + http://www.fraps.com/download.php + + Video Editing Software: + http://explore.live.com/windows-live-movie-maker?os=other + + Song: + Indifference - Pearl Jam + (Off of the 'Vs.' album) + + I claim no ownership of this song, all the credit goes to Pearl Jam and their producers. + season: 2011 + subscription_has_download_archive: true + title: 2011-03-21 - Jesse's Minecraft Server [Trailer - Mar.21] + year: 2011 + +Files modified: +---------------------------------------- +{output_directory} + .ytdl-sub-bilateral_test-download-archive.json \ No newline at end of file diff --git a/tests/unit/config/test_config_file.py b/tests/unit/config/test_config_file.py index 74ec03d2..352ead2d 100644 --- a/tests/unit/config/test_config_file.py +++ b/tests/unit/config/test_config_file.py @@ -74,7 +74,7 @@ class TestConfigFilePartiallyValidatesPresets: expected_error_message="Validation error in partial_preset.download.1: " "'partial_preset.download.1' contains the field 'bad_key' which is not allowed. " "Allowed fields: download_reverse, include_sibling_metadata, playlist_thumbnails, " - "source_thumbnails, url, variables", + "source_thumbnails, url, variables, ytdl_options", ) @pytest.mark.parametrize( diff --git a/tests/unit/script/test_script.py b/tests/unit/script/test_script.py index dfa7994e..555bc429 100644 --- a/tests/unit/script/test_script.py +++ b/tests/unit/script/test_script.py @@ -80,6 +80,7 @@ class TestScript: } ) + assert script.resolve_once({"url": "{ %bilateral_url('nope') }"})["url"].native == "nope" script.add({"%bilateral_url_wrap": "{ %bilateral_url($0) }"}) assert (