diff --git a/src/ytdl_sub/config/validators/variable_validation.py b/src/ytdl_sub/config/validators/variable_validation.py index c47249e6..74ed78fe 100644 --- a/src/ytdl_sub/config/validators/variable_validation.py +++ b/src/ytdl_sub/config/validators/variable_validation.py @@ -16,36 +16,28 @@ from ytdl_sub.validators.string_formatter_validators import validate_formatters class ResolutionLevel: ORIGINAL = 0 - FILL = 1 - RESOLVE = 2 - INTERNAL = 3 + RESOLVE = 1 + INTERNAL = 2 @classmethod def name_of(cls, resolution_level: int) -> str: if resolution_level == cls.ORIGINAL: return "original" - if resolution_level == cls.FILL: - return "fill" if resolution_level == cls.RESOLVE: return "resolve" if resolution_level == cls.INTERNAL: return "internal" + raise ValueError("Invalid resolution level") @classmethod def all(cls) -> List[int]: - return [cls.ORIGINAL, cls.FILL, cls.RESOLVE, cls.INTERNAL] - - raise ValueError("Invalid resolution level") + return [cls.ORIGINAL, cls.RESOLVE, cls.INTERNAL] class VariableValidation: def _apply_resolution_level(self) -> None: - if self._resolution_level == ResolutionLevel.FILL: - # If anything is resolvable, 'fill' it with the resolved value. - # This will happen automatically when validating - pass - elif self._resolution_level == ResolutionLevel.RESOLVE: + if self._resolution_level == ResolutionLevel.RESOLVE: # Partial resolve everything, but not including internal variables self.unresolved_variables |= VARIABLES.variable_names(include_sanitized=True) self.script = self.script.resolve_partial(unresolvable=self.unresolved_variables) @@ -61,7 +53,7 @@ class VariableValidation: downloader_options: MultiUrlValidator, output_options: OutputOptions, plugins: PresetPlugins, - resolution_level: int = ResolutionLevel.FILL, + resolution_level: int = ResolutionLevel.RESOLVE, ): self.overrides = overrides self.downloader_options = downloader_options diff --git a/src/ytdl_sub/script/script.py b/src/ytdl_sub/script/script.py index d0977fd5..1bf06e9d 100644 --- a/src/ytdl_sub/script/script.py +++ b/src/ytdl_sub/script/script.py @@ -281,10 +281,10 @@ class Script: self._variables[variable_name] = ResolvedSyntaxTree(ast=[resolved]) def _recursive_get_unresolved_output_filter_variables( - self, current_var: SyntaxTree, subset_to_resolve: Set[str], unresolvable: Set[Variable] + self, current_var: SyntaxTree, subset_to_resolve: Set[str], unresolvable: Set[Variable], allow_partial_resolve: bool = False ) -> Set[str]: for var_dep in current_var.variables: - if var_dep in unresolvable: + if var_dep in unresolvable and not allow_partial_resolve: raise ScriptVariableNotResolved( f"Output filter variable contains the variable {var_dep} " f"which is set as unresolvable" @@ -299,12 +299,14 @@ class Script: current_var=self._variables[var_dep.name], subset_to_resolve=subset_to_resolve, unresolvable=unresolvable, + allow_partial_resolve=allow_partial_resolve, ) for custom_func_dep in current_var.custom_functions: subset_to_resolve |= self._recursive_get_unresolved_output_filter_variables( current_var=self._functions[custom_func_dep.name], subset_to_resolve=subset_to_resolve, unresolvable=unresolvable, + allow_partial_resolve=allow_partial_resolve, ) for lambda_func in current_var.lambdas: @@ -313,6 +315,7 @@ class Script: current_var=self._functions[lambda_func.value], subset_to_resolve=subset_to_resolve, unresolvable=unresolvable, + allow_partial_resolve=allow_partial_resolve, ) return subset_to_resolve @@ -321,6 +324,7 @@ class Script: self, output_filter: Set[str], unresolvable: Set[Variable], + allow_partial_resolvable: bool = False, ) -> Set[str]: """ When an output filter is applied, only a subset of variables that the filter @@ -340,6 +344,7 @@ class Script: current_var=self._variables[output_filter_variable], subset_to_resolve=subset_to_resolve, unresolvable=unresolvable, + allow_partial_resolve=allow_partial_resolvable, ) return subset_to_resolve @@ -706,10 +711,11 @@ class Script: """ return set(to_function_definition_name(name) for name in self._functions.keys()) - def resolve_partial( + def _resolve_partial( self, unresolvable: Optional[Set[str]] = None, - ) -> "Script": + output_filter: Optional[Set[str]] = None, + ) -> Dict[str, SyntaxTree]: """ Returns ------- @@ -724,12 +730,14 @@ class Script: if name not in unresolvable } + to_partially_resolve: Set[Variable] = {Variable(name) for name in output_filter} if output_filter else set(unresolved.keys()) + partially_resolved = True while partially_resolved: partially_resolved = False - for variable in list(unresolved.keys()): + for variable in list(to_partially_resolve): definition = unresolved[variable] maybe_resolved = definition @@ -750,6 +758,7 @@ class Script: if isinstance(maybe_resolved, Resolvable): resolved[variable] = maybe_resolved del unresolved[variable] + to_partially_resolve.remove(variable) partially_resolved = True else: unresolved[variable] = maybe_resolved @@ -758,11 +767,34 @@ class Script: # which means we can iterate again partially_resolved |= definition != maybe_resolved - return copy.deepcopy(self).add_parsed( - {var_name: self._variables[var_name] for var_name in unresolvable} - | { + return { var.name: ResolvedSyntaxTree(ast=[definition]) for var, definition in resolved.items() - } - | {var.name: SyntaxTree(ast=[definition]) for var, definition in unresolved.items()} - ) + } | {var.name: SyntaxTree(ast=[definition]) for var, definition in unresolved.items()} + + + def resolve_partial( + self, + unresolvable: Optional[Set[str]] = None, + ) -> "Script": + out = self._resolve_partial(unresolvable=unresolvable) + + return copy.deepcopy(self).add_parsed( + {var_name: self._variables[var_name] for var_name in unresolvable} + | out + ) + + def resolve_partial_once( + self, + variable_definitions: Dict[str, SyntaxTree], + unresolvable: Optional[Set[str]] = None + ) -> Dict[str, SyntaxTree]: + try: + self.add_parsed(variable_definitions) + return self._resolve_partial( + unresolvable=unresolvable, + output_filter=set(list(variable_definitions.keys())), + ) + finally: + for name in variable_definitions.keys(): + self._variables.pop(name, None) diff --git a/src/ytdl_sub/subscriptions/base_subscription.py b/src/ytdl_sub/subscriptions/base_subscription.py index 03f703b9..fa1bfda2 100644 --- a/src/ytdl_sub/subscriptions/base_subscription.py +++ b/src/ytdl_sub/subscriptions/base_subscription.py @@ -257,7 +257,7 @@ class BaseSubscription(ABC): """ return self._preset_options.yaml - def resolved_yaml(self, resolution_level: int = ResolutionLevel.FILL) -> str: + def resolved_yaml(self, resolution_level: int = ResolutionLevel.RESOLVE) -> str: """ Returns ------- diff --git a/src/ytdl_sub/validators/string_formatter_validators.py b/src/ytdl_sub/validators/string_formatter_validators.py index 38e9c8b1..639c57de 100644 --- a/src/ytdl_sub/validators/string_formatter_validators.py +++ b/src/ytdl_sub/validators/string_formatter_validators.py @@ -253,10 +253,12 @@ def _validate_formatter( if resolve_partial and not is_static_formatter: formatter_hash = get_md5_hash(formatter_validator.format_string) + parsed = ( - mock_script.add_parsed({formatter_hash: formatter_validator.parsed}) - .resolve_partial(unresolvable=unresolved_variables) - .definition_of(formatter_hash) + mock_script.resolve_partial_once( + variable_definitions={formatter_hash: formatter_validator.parsed}, + unresolvable=unresolved_variables + )[formatter_hash] ) # Add lambda functions to custom function names, if it's custom diff --git a/tests/resources/expected_json/tv_show/inspect_sub_internal.json b/tests/resources/expected_json/tv_show/inspect_sub_internal.json new file mode 100644 index 00000000..2bfd5d43 --- /dev/null +++ b/tests/resources/expected_json/tv_show/inspect_sub_internal.json @@ -0,0 +1,248 @@ +{ + "chapters": { + "allow_chapters_from_comments": false, + "embed_chapters": true, + "enable": "True", + "force_key_frames": false + }, + "date_range": { + "breaks": "True", + "enable": "True", + "type": "upload_date" + }, + "download": [ + { + "download_reverse": "True", + "include_sibling_metadata": false, + "playlist_thumbnails": [ + { + "name": "poster.jpg", + "uid": "avatar_uncropped" + }, + { + "name": "fanart.jpg", + "uid": "banner_uncropped" + } + ], + "source_thumbnails": [ + { + "name": "poster.jpg", + "uid": "avatar_uncropped" + }, + { + "name": "fanart.jpg", + "uid": "banner_uncropped" + } + ], + "url": "https://www.youtube.com/@novapbs", + "variables": {}, + "webpage_url": "{ %map_get( entry_metadata, \"webpage_url\" ) }", + "ytdl_options": {} + } + ], + "file_convert": { + "convert_to": "mp4", + "convert_with": "yt-dlp", + "enable": "True" + }, + "format": "(bv*[ext=mp4][vcodec~='^((he|a)vc|h26[45])']+ba[ext=m4a]) / (bv[ext=mp4]*+ba[ext=m4a]/b)", + "output_options": { + "download_archive_name": ".ytdl-sub-NOVA PBS-download-archive.json", + "file_name": "{ %concat( %string( %sanitize( %concat( \"Season \", %string( %int( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ), \"year\" ) ) ) ) ) ), \"/\", %string( %sanitize( %concat( \"s\", %string( %int( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ), \"year\" ) ) ), \".e\", %string( %pad_zero( %int( %concat( %int( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ), \"month\" ) ), %string( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ), \"day_padded\" ) ), \"01\" ) ), 6 ) ), \" - \", %string( %sanitize_plex_episode( %map_get_non_empty( entry_metadata, \"title\", %map_get( entry_metadata, \"id\" ) ) ) ) ) ) ) ) }.{ ext }", + "info_json_name": "{ %concat( %string( %sanitize( %concat( \"Season \", %string( %int( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ), \"year\" ) ) ) ) ) ), \"/\", %string( %sanitize( %concat( \"s\", %string( %int( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ), \"year\" ) ) ), \".e\", %string( %pad_zero( %int( %concat( %int( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ), \"month\" ) ), %string( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ), \"day_padded\" ) ), \"01\" ) ), 6 ) ), \" - \", %string( %sanitize_plex_episode( %map_get_non_empty( entry_metadata, \"title\", %map_get( entry_metadata, \"id\" ) ) ) ) ) ) ) ) }.info.json", + "keep_files_date_eval": "{ %string( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ), \"date_standardized\" ) ) }", + "maintain_download_archive": true, + "output_directory": "/var/folders/rw/hl1xmkmj68zdl2kjx3l0dwzc0000gn/T/tmp5_pbxsle/NOVA PBS", + "preserve_mtime": false, + "thumbnail_name": "{ %concat( %string( %sanitize( %concat( \"Season \", %string( %int( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ), \"year\" ) ) ) ) ) ), \"/\", %string( %sanitize( %concat( \"s\", %string( %int( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ), \"year\" ) ) ), \".e\", %string( %pad_zero( %int( %concat( %int( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ), \"month\" ) ), %string( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ), \"day_padded\" ) ), \"01\" ) ), 6 ) ), \" - \", %string( %sanitize_plex_episode( %map_get_non_empty( entry_metadata, \"title\", %map_get( entry_metadata, \"id\" ) ) ) ) ) ) ) ) }-thumb.jpg" + }, + "overrides": { + "%bilateral_url": "{ %if( %and( enable_bilateral_scraping, subscription_has_download_archive, %is_bilateral_url( $0 ) ), $0, '' ) }", + "%episode_ordering_": "{ %eq( %lower( tv_show_by_date_episode_ordering ), $0 ) }", + "%is_bilateral_url": "{ %contains( $0, \"youtube.com/playlist\" ) }", + "%ordering_pair_eq": "{ %eq( [ tv_show_by_date_season_ordering, tv_show_by_date_episode_ordering ], [ $0, $1 ] ) }", + "%season_ordering_": "{ %eq( %lower( tv_show_by_date_season_ordering ), $0 ) }", + "assert_not_collection": true, + "avatar_uncropped_thumbnail_file_name": "poster.jpg", + "banner_uncropped_thumbnail_file_name": "fanart.jpg", + "enable_bilateral_scraping": true, + "enable_resolution_assert": true, + "enable_throttle_protection": true, + "episode_content_rating": "TV-14", + "episode_date_standardized": "{ %string( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ), \"date_standardized\" ) ) }", + "episode_file_name": "s{ %int( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ), \"year\" ) ) }.e{ %pad_zero( %int( %concat( %int( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ), \"month\" ) ), %string( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ), \"day_padded\" ) ), upload_date_index_padded ) ), 6 ) } - { %sanitize_plex_episode( %map_get_non_empty( entry_metadata, \"title\", %map_get( entry_metadata, \"id\" ) ) ) }", + "episode_file_path": "{ %sanitize( %concat( \"Season \", %string( %int( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ), \"year\" ) ) ) ) ) }/{ %sanitize( %concat( \"s\", %string( %int( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ), \"year\" ) ) ), \".e\", %string( %pad_zero( %int( %concat( %int( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ), \"month\" ) ), %string( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ), \"day_padded\" ) ), upload_date_index_padded ) ), 6 ) ), \" - \", %string( %sanitize_plex_episode( %map_get_non_empty( entry_metadata, \"title\", %map_get( entry_metadata, \"id\" ) ) ) ) ) ) }", + "episode_number": "{ %int( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ), \"month\" ) ) }{ %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ), \"day_padded\" ) }{ upload_date_index_padded }", + "episode_number_and_padded_": "{ [ %concat( %int( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ), \"month\" ) ), %string( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ), \"day_padded\" ) ), upload_date_index_padded ), 6 ] }", + "episode_number_padded": "{ %pad_zero( %int( %concat( %int( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ), \"month\" ) ), %string( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ), \"day_padded\" ) ), upload_date_index_padded ) ), 6 ) }", + "episode_plot": "{ %map_get( entry_metadata, \"webpage_url\" ) }\n\n{ %map_get_non_empty( entry_metadata, \"description\", '' ) }", + "episode_title": "{ %string( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ), \"date_standardized\" ) ) } - { %map_get_non_empty( entry_metadata, \"title\", %map_get( entry_metadata, \"id\" ) ) }", + "episode_year": "{ %slice( %string( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ), \"date_standardized\" ) ), 0, 4 ) }", + "file_title": "{ %sanitize_plex_episode( %map_get_non_empty( entry_metadata, \"title\", %map_get( entry_metadata, \"id\" ) ) ) }", + "file_uid": "{ %sanitize_plex_episode( %map_get( entry_metadata, \"id\" ) ) }", + "include_sibling_metadata": false, + "modified_webpage_url": "{ %map_get( entry_metadata, \"webpage_url\" ) }", + "only_recent_date_range": "2months", + "only_recent_max_files": 30, + "resolution_assert": "{ %if( %and( enable_resolution_assert, %ne( height, 0 ), %not( resolution_assert_is_ignored ) ), %assert( %gte( height, resolution_assert_height_gte ), %concat( \"Entry \", title, \" downloaded at a low resolution (\", resolution_readable, \"), you've probably been throttled. \", \"Stopping further downloads, wait a few hours and try again. \", \"Disable using the override variable `enable_resolution_assert: False`.\" ) ), \"false is no-op\" ) }", + "resolution_assert_height_gte": 361, + "resolution_assert_ignore_titles": "{ [ ] }", + "resolution_assert_is_ignored": "{ %print_if_true( %concat( %map_get_non_empty( entry_metadata, \"title\", %map_get( entry_metadata, \"id\" ) ), \" has a match in resolution_assert_ignore_titles, skipping resolution assert.\" ), %contains_any( %map_get_non_empty( entry_metadata, \"title\", %map_get( entry_metadata, \"id\" ) ), [ ] ) ) }", + "resolution_assert_print": true, + "resolution_readable": "{ %map_get_non_empty( entry_metadata, \"width\", 0 ) }x{ %map_get_non_empty( entry_metadata, \"height\", 0 ) }", + "s01_name": "", + "s01_url": "", + "season_directory_name": "Season { %int( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ), \"year\" ) ) }", + "season_number": "{ %int( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ), \"year\" ) ) }", + "season_number_padded": "{ %int( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ), \"year\" ) ) }", + "season_poster_file_name": "{ %sanitize( %concat( \"Season \", %string( %int( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ), \"year\" ) ) ) ) ) }/Season{ %int( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ), \"year\" ) ) }.jpg", + "subscription_array": [ + "https://www.youtube.com/@novapbs" + ], + "subscription_indent_1": "Documentaries", + "subscription_indent_2": "TV-14", + "subscription_value": "https://www.youtube.com/@novapbs", + "subscription_value_1": "https://www.youtube.com/@novapbs", + "thumbnail_file_name": "{ %concat( %string( %sanitize( %concat( \"Season \", %string( %int( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ), \"year\" ) ) ) ) ) ), \"/\", %string( %sanitize( %concat( \"s\", %string( %int( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ), \"year\" ) ) ), \".e\", %string( %pad_zero( %int( %concat( %int( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ), \"month\" ) ), %string( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ), \"day_padded\" ) ), upload_date_index_padded ) ), 6 ) ), \" - \", %string( %sanitize_plex_episode( %map_get_non_empty( entry_metadata, \"title\", %map_get( entry_metadata, \"id\" ) ) ) ) ) ) ) ) }-thumb.jpg", + "tv_show_by_date_episode_ordering": "upload-month-day", + "tv_show_by_date_ordering_pair_validation_": "{ [ %concat( %int( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ), \"month\" ) ), %string( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ), \"day_padded\" ) ), upload_date_index_padded ), 6 ] }", + "tv_show_by_date_season_ordering": "upload-year", + "tv_show_content_rating": "TV-14", + "tv_show_content_rating_default": "TV-14", + "tv_show_date_range_type": "upload_date", + "tv_show_directory": "/var/folders/rw/hl1xmkmj68zdl2kjx3l0dwzc0000gn/T/tmp5_pbxsle", + "tv_show_fanart_file_name": "fanart.jpg", + "tv_show_genre": "Documentaries", + "tv_show_genre_default": "ytdl-sub", + "tv_show_name": "NOVA PBS", + "tv_show_poster_file_name": "poster.jpg", + "url": "https://www.youtube.com/@novapbs", + "url10": "", + "url100": "", + "url11": "", + "url12": "", + "url13": "", + "url14": "", + "url15": "", + "url16": "", + "url17": "", + "url18": "", + "url19": "", + "url2": "", + "url20": "", + "url21": "", + "url22": "", + "url23": "", + "url24": "", + "url25": "", + "url26": "", + "url27": "", + "url28": "", + "url29": "", + "url3": "", + "url30": "", + "url31": "", + "url32": "", + "url33": "", + "url34": "", + "url35": "", + "url36": "", + "url37": "", + "url38": "", + "url39": "", + "url4": "", + "url40": "", + "url41": "", + "url42": "", + "url43": "", + "url44": "", + "url45": "", + "url46": "", + "url47": "", + "url48": "", + "url49": "", + "url5": "", + "url50": "", + "url51": "", + "url52": "", + "url53": "", + "url54": "", + "url55": "", + "url56": "", + "url57": "", + "url58": "", + "url59": "", + "url6": "", + "url60": "", + "url61": "", + "url62": "", + "url63": "", + "url64": "", + "url65": "", + "url66": "", + "url67": "", + "url68": "", + "url69": "", + "url7": "", + "url70": "", + "url71": "", + "url72": "", + "url73": "", + "url74": "", + "url75": "", + "url76": "", + "url77": "", + "url78": "", + "url79": "", + "url8": "", + "url80": "", + "url81": "", + "url82": "", + "url83": "", + "url84": "", + "url85": "", + "url86": "", + "url87": "", + "url88": "", + "url89": "", + "url9": "", + "url90": "", + "url91": "", + "url92": "", + "url93": "", + "url94": "", + "url95": "", + "url96": "", + "url97": "", + "url98": "", + "url99": "", + "urls": [ + "https://www.youtube.com/@novapbs" + ] + }, + "throttle_protection": { + "enable": true, + "sleep_per_download_s": { + "max": "28.4", + "min": "13.8" + }, + "sleep_per_request_s": { + "max": "0.75", + "min": "0.0" + }, + "sleep_per_subscription_s": { + "max": "26.1", + "min": "16.3" + } + }, + "video_tags": { + "contentRating": "TV-14", + "date": "{ %string( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ), \"date_standardized\" ) ) }", + "episode_id": "{ %int( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ), \"month\" ) ) }{ %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ), \"day_padded\" ) }01", + "genre": "Documentaries", + "show": "NOVA PBS", + "synopsis": "{ %map_get( entry_metadata, \"webpage_url\" ) }\n\n{ %map_get_non_empty( entry_metadata, \"description\", '' ) }", + "title": "{ %string( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ), \"date_standardized\" ) ) } - { %map_get_non_empty( entry_metadata, \"title\", %map_get( entry_metadata, \"id\" ) ) }", + "year": "{ %slice( %string( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ), \"date_standardized\" ) ), 0, 4 ) }" + } +} \ No newline at end of file diff --git a/tests/resources/expected_json/tv_show/inspect_sub_original.json b/tests/resources/expected_json/tv_show/inspect_sub_original.json new file mode 100644 index 00000000..ff9b7097 --- /dev/null +++ b/tests/resources/expected_json/tv_show/inspect_sub_original.json @@ -0,0 +1,259 @@ +{ + "presets": { + "NOVA PBS": { + "chapters": { + "embed_chapters": true + }, + "date_range": { + "type": "{tv_show_date_range_type}" + }, + "download": [ + { + "download_reverse": false, + "url": "{ %array_apply(urls, %bilateral_url) }", + "webpage_url": "{modified_webpage_url}", + "ytdl_options": { + "playlist_items": "-1:0:-1" + } + }, + { + "include_sibling_metadata": "{include_sibling_metadata}", + "playlist_thumbnails": [ + { + "name": "{avatar_uncropped_thumbnail_file_name}", + "uid": "avatar_uncropped" + }, + { + "name": "{banner_uncropped_thumbnail_file_name}", + "uid": "banner_uncropped" + } + ], + "source_thumbnails": [ + { + "name": "{avatar_uncropped_thumbnail_file_name}", + "uid": "avatar_uncropped" + }, + { + "name": "{banner_uncropped_thumbnail_file_name}", + "uid": "banner_uncropped" + } + ], + "url": "{ %array_at(urls, 0) }", + "webpage_url": "{modified_webpage_url}" + }, + { + "include_sibling_metadata": "{include_sibling_metadata}", + "url": "{ %array_slice(urls, 1) }", + "webpage_url": "{modified_webpage_url}" + } + ], + "file_convert": { + "convert_to": "mp4" + }, + "format": "(bv*[ext=mp4][vcodec~='^((he|a)vc|h26[45])']+ba[ext=m4a]) / (bv[ext=mp4]*+ba[ext=m4a]/b)", + "output_options": { + "file_name": "{episode_file_path}.{ext}", + "info_json_name": "{episode_file_path}.{info_json_ext}", + "keep_files_date_eval": "{episode_date_standardized}", + "maintain_download_archive": true, + "output_directory": "{tv_show_directory}/{tv_show_name_sanitized}", + "thumbnail_name": "{thumbnail_file_name}" + }, + "overrides": { + "%bilateral_url": "{ \n %if(\n %and(\n enable_bilateral_scraping,\n subscription_has_download_archive,\n %is_bilateral_url($0)\n ),\n $0,\n \"\"\n )\n}", + "%episode_ordering_": "{ %eq( %lower(tv_show_by_date_episode_ordering), $0 ) }", + "%is_bilateral_url": "{ %contains( $0, \"youtube.com/playlist\" ) }", + "%ordering_pair_eq": "{\n %eq([tv_show_by_date_season_ordering, tv_show_by_date_episode_ordering], [$0, $1])\n}", + "%season_ordering_": "{ %eq( %lower(tv_show_by_date_season_ordering), $0 ) }", + "assert_not_collection": "{\n %assert(\n %and(\n %not( %bool(s01_url) ),\n %not( %bool(s01_name) )\n ),\n \"Provided `s01_url` or `s01_name` variable to TV Show by Date preset when it expects `url`. Perhaps you meant to use the `TV Show Collection` preset?\"\n )\n}", + "avatar_uncropped_thumbnail_file_name": "{tv_show_poster_file_name}", + "banner_uncropped_thumbnail_file_name": "{tv_show_fanart_file_name}", + "enable_bilateral_scraping": true, + "enable_resolution_assert": true, + "enable_throttle_protection": true, + "episode_content_rating": "{tv_show_content_rating}", + "episode_date_standardized": "{\n %if(\n %contains(tv_show_by_date_season_ordering, \"release\"),\n release_date_standardized,\n upload_date_standardized\n )\n}", + "episode_file_name": "s{season_number_padded}.e{episode_number_padded} - {file_title}", + "episode_file_path": "{season_directory_name_sanitized}/{episode_file_name_sanitized}", + "episode_number": "{ %array_at(episode_number_and_padded_, 0) }", + "episode_number_and_padded_": "{\n %elif(\n %episode_ordering_( \"upload-day\" ), [ %concat(upload_day, upload_date_index_padded), 4 ],\n %episode_ordering_( \"upload-month-day\" ), [ %concat(upload_month, upload_day_padded, upload_date_index_padded), 6],\n %episode_ordering_( \"upload-month-day-reversed\" ), [ %concat(upload_day_of_year_reversed, upload_date_index_reversed_padded), 5],\n %episode_ordering_( \"release-day\" ), [ %concat(release_day, upload_date_index_padded), 4 ],\n %episode_ordering_( \"release-month-day\" ), [ %concat(release_month, release_day_padded, upload_date_index_padded), 6],\n %episode_ordering_( \"release-month-day-reversed\" ), [ %concat(release_day_of_year_reversed, upload_date_index_reversed_padded), 5],\n %episode_ordering_( \"download-index\" ), [ download_index, 6 ],\n %throw(\n 'tv_show_by_date_episode_ordering must be one of the following: \"upload-day\", \"upload-month-day\", \"upload-month-day-reversed\", \"release-day\", \"release-month-day\", \"release-month-day-reversed\", \"download-index\"'\n )\n )\n}", + "episode_number_padded": "{ %pad_zero( %int(episode_number), %int(%array_at(episode_number_and_padded_, 1))) }", + "episode_plot": "{webpage_url}\n\n{description}", + "episode_title": "{episode_date_standardized} - {title}", + "episode_year": "{%slice(episode_date_standardized, 0, 4)}", + "file_title": "{title_sanitized_plex}", + "file_uid": "{uid_sanitized_plex}", + "include_sibling_metadata": false, + "modified_webpage_url": "{webpage_url}", + "only_recent_date_range": "2months", + "only_recent_max_files": 30, + "resolution_assert": "{\n %if(\n %and(\n enable_resolution_assert,\n %ne( height, 0 ),\n %not(resolution_assert_is_ignored)\n ),\n %assert(\n %gte( height, resolution_assert_height_gte ),\n %concat(\n \"Entry \",\n title,\n \" downloaded at a low resolution (\",\n resolution_readable,\n \"), you've probably been throttled. \",\n \"Stopping further downloads, wait a few hours and try again. \",\n \"Disable using the override variable `enable_resolution_assert: False`.\"\n )\n ),\n \"false is no-op\"\n )\n}", + "resolution_assert_height_gte": 361, + "resolution_assert_ignore_titles": "{ [] }", + "resolution_assert_is_ignored": "{\n %print_if_true(\n %concat(title, \" has a match in resolution_assert_ignore_titles, skipping resolution assert.\"),\n %contains_any(title, resolution_assert_ignore_titles)\n )\n}", + "resolution_assert_print": "{\n %print(\n %if(\n enable_resolution_assert,\n \"Resolution assert is enabled, will fail on low-quality video downloads and presume throttle. Disable using the override variable `enable_resolution_assert: False`\",\n \"Resolution assert is disabled. Use at your own risk!\"\n ),\n enable_resolution_assert,\n -1\n )\n}", + "resolution_readable": "{width}x{height}", + "s01_name": "", + "s01_url": "", + "season_directory_name": "Season {season_number_padded}", + "season_number": "{\n %elif(\n %season_ordering_( \"upload-year\" ), upload_year,\n %season_ordering_( \"upload-year-month\" ), %concat(upload_year, upload_month_padded),\n %season_ordering_( \"release-year\" ), release_year,\n %season_ordering_( \"release-year-month\" ), %concat(release_year, release_month_padded),\n %throw(\n 'tv_show_by_date_season_ordering must be one of the following: \"upload-year\", \"upload-year-month\", \"release-year\", \"release-year-month\"'\n )\n )\n}", + "season_number_padded": "{season_number}", + "season_poster_file_name": "{season_directory_name_sanitized}/Season{season_number_padded}.jpg", + "subscription_array": "{%from_json('''[\"https://www.youtube.com/@novapbs\"]''')}", + "subscription_indent_1": "Documentaries", + "subscription_indent_2": "{tv_show_content_rating_default}", + "subscription_value": "https://www.youtube.com/@novapbs", + "subscription_value_1": "https://www.youtube.com/@novapbs", + "thumbnail_file_name": "{episode_file_path}-thumb.jpg", + "tv_show_by_date_episode_ordering": "upload-month-day", + "tv_show_by_date_ordering_pair_validation_": "{\n %assert_then(\n %or(\n %ordering_pair_eq(\"upload-year\", \"upload-month-day\"),\n %ordering_pair_eq(\"upload-year\", \"upload-month-day-reversed\"),\n %ordering_pair_eq(\"upload-year\", \"download-index\"),\n %ordering_pair_eq(\"upload-year-month\", \"upload-day\"),\n %ordering_pair_eq(\"release-year\", \"release-month-day\"),\n %ordering_pair_eq(\"release-year\", \"release-month-day-reversed\"),\n %ordering_pair_eq(\"release-year\", \"download-index\"),\n %ordering_pair_eq(\"release-year-month\", \"release-day\")\n ),\n episode_number_and_padded_,\n \"Detected incompatibility between tv_show_by_date_season_ordering and tv_show_by_date_episode_ordering. Ensure you are not using both upload and release date, and that the year/month/day are included in the combined season and episode.\"\n )\n}", + "tv_show_by_date_season_ordering": "upload-year", + "tv_show_content_rating": "{subscription_indent_2}", + "tv_show_content_rating_default": "TV-14", + "tv_show_date_range_type": "{\n %if(\n %contains(tv_show_by_date_season_ordering, \"release\"),\n \"release_date\",\n \"upload_date\"\n )\n}", + "tv_show_directory": "/var/folders/rw/hl1xmkmj68zdl2kjx3l0dwzc0000gn/T/tmpi3yfj1k0", + "tv_show_fanart_file_name": "fanart.jpg", + "tv_show_genre": "{subscription_indent_1}", + "tv_show_genre_default": "ytdl-sub", + "tv_show_name": "{subscription_name}", + "tv_show_poster_file_name": "poster.jpg", + "url": "{subscription_value}", + "url10": "", + "url100": "", + "url11": "", + "url12": "", + "url13": "", + "url14": "", + "url15": "", + "url16": "", + "url17": "", + "url18": "", + "url19": "", + "url2": "", + "url20": "", + "url21": "", + "url22": "", + "url23": "", + "url24": "", + "url25": "", + "url26": "", + "url27": "", + "url28": "", + "url29": "", + "url3": "", + "url30": "", + "url31": "", + "url32": "", + "url33": "", + "url34": "", + "url35": "", + "url36": "", + "url37": "", + "url38": "", + "url39": "", + "url4": "", + "url40": "", + "url41": "", + "url42": "", + "url43": "", + "url44": "", + "url45": "", + "url46": "", + "url47": "", + "url48": "", + "url49": "", + "url5": "", + "url50": "", + "url51": "", + "url52": "", + "url53": "", + "url54": "", + "url55": "", + "url56": "", + "url57": "", + "url58": "", + "url59": "", + "url6": "", + "url60": "", + "url61": "", + "url62": "", + "url63": "", + "url64": "", + "url65": "", + "url66": "", + "url67": "", + "url68": "", + "url69": "", + "url7": "", + "url70": "", + "url71": "", + "url72": "", + "url73": "", + "url74": "", + "url75": "", + "url76": "", + "url77": "", + "url78": "", + "url79": "", + "url8": "", + "url80": "", + "url81": "", + "url82": "", + "url83": "", + "url84": "", + "url85": "", + "url86": "", + "url87": "", + "url88": "", + "url89": "", + "url9": "", + "url90": "", + "url91": "", + "url92": "", + "url93": "", + "url94": "", + "url95": "", + "url96": "", + "url97": "", + "url98": "", + "url99": "", + "urls": "{subscription_array}" + }, + "preset": [ + "_season_by_year", + "plex_tv_show_by_date", + "season_by_year__episode_by_month_day", + "Plex TV Show by Date", + "__preset__" + ], + "throttle_protection": { + "enable": "{\n %print(\n %if(\n enable_throttle_protection,\n \"Throttle protection is enabled. Disable using the override variable `enable_throttle_protection: False`\",\n \"Throttle protection is disabled. Use at your own risk!\"\n ),\n enable_throttle_protection\n )\n}", + "sleep_per_download_s": { + "max": 28.4, + "min": 13.8 + }, + "sleep_per_request_s": { + "max": 0.75, + "min": 0.0 + }, + "sleep_per_subscription_s": { + "max": 26.1, + "min": 16.3 + } + }, + "video_tags": { + "contentRating": "{episode_content_rating}", + "date": "{episode_date_standardized}", + "episode_id": "{episode_number}", + "genre": "{tv_show_genre}", + "show": "{tv_show_name}", + "synopsis": "{episode_plot}", + "title": "{episode_title}", + "year": "{episode_year}" + }, + "ytdl_options": { + "break_on_existing": true + } + } + } +} \ No newline at end of file diff --git a/tests/resources/expected_json/tv_show/inspect_sub_resolve.json b/tests/resources/expected_json/tv_show/inspect_sub_resolve.json new file mode 100644 index 00000000..a019b80d --- /dev/null +++ b/tests/resources/expected_json/tv_show/inspect_sub_resolve.json @@ -0,0 +1,248 @@ +{ + "chapters": { + "allow_chapters_from_comments": false, + "embed_chapters": true, + "enable": "True", + "force_key_frames": false + }, + "date_range": { + "breaks": "True", + "enable": "True", + "type": "upload_date" + }, + "download": [ + { + "download_reverse": "True", + "include_sibling_metadata": false, + "playlist_thumbnails": [ + { + "name": "poster.jpg", + "uid": "avatar_uncropped" + }, + { + "name": "fanart.jpg", + "uid": "banner_uncropped" + } + ], + "source_thumbnails": [ + { + "name": "poster.jpg", + "uid": "avatar_uncropped" + }, + { + "name": "fanart.jpg", + "uid": "banner_uncropped" + } + ], + "url": "https://www.youtube.com/@novapbs", + "variables": {}, + "webpage_url": "{ webpage_url }", + "ytdl_options": {} + } + ], + "file_convert": { + "convert_to": "mp4", + "convert_with": "yt-dlp", + "enable": "True" + }, + "format": "(bv*[ext=mp4][vcodec~='^((he|a)vc|h26[45])']+ba[ext=m4a]) / (bv[ext=mp4]*+ba[ext=m4a]/b)", + "output_options": { + "download_archive_name": ".ytdl-sub-NOVA PBS-download-archive.json", + "file_name": "{ %concat( %string( %sanitize( %concat( \"Season \", %string( upload_year ) ) ) ), \"/\", %string( %sanitize( %concat( \"s\", %string( upload_year ), \".e\", %string( %pad_zero( %int( %concat( upload_month, upload_day_padded, upload_date_index_padded ) ), 6 ) ), \" - \", %string( title_sanitized_plex ) ) ) ) ) }.{ ext }", + "info_json_name": "{ %concat( %string( %sanitize( %concat( \"Season \", %string( upload_year ) ) ) ), \"/\", %string( %sanitize( %concat( \"s\", %string( upload_year ), \".e\", %string( %pad_zero( %int( %concat( upload_month, upload_day_padded, upload_date_index_padded ) ), 6 ) ), \" - \", %string( title_sanitized_plex ) ) ) ) ) }.{ info_json_ext }", + "keep_files_date_eval": "{ upload_date_standardized }", + "maintain_download_archive": true, + "output_directory": "/var/folders/rw/hl1xmkmj68zdl2kjx3l0dwzc0000gn/T/tmp9xwmie5t/NOVA PBS", + "preserve_mtime": false, + "thumbnail_name": "{ %concat( %string( %sanitize( %concat( \"Season \", %string( upload_year ) ) ) ), \"/\", %string( %sanitize( %concat( \"s\", %string( upload_year ), \".e\", %string( %pad_zero( %int( %concat( upload_month, upload_day_padded, upload_date_index_padded ) ), 6 ) ), \" - \", %string( title_sanitized_plex ) ) ) ) ) }-thumb.jpg" + }, + "overrides": { + "%bilateral_url": "{ %if( %and( enable_bilateral_scraping, subscription_has_download_archive, %is_bilateral_url( $0 ) ), $0, '' ) }", + "%episode_ordering_": "{ %eq( %lower( tv_show_by_date_episode_ordering ), $0 ) }", + "%is_bilateral_url": "{ %contains( $0, \"youtube.com/playlist\" ) }", + "%ordering_pair_eq": "{ %eq( [ tv_show_by_date_season_ordering, tv_show_by_date_episode_ordering ], [ $0, $1 ] ) }", + "%season_ordering_": "{ %eq( %lower( tv_show_by_date_season_ordering ), $0 ) }", + "assert_not_collection": true, + "avatar_uncropped_thumbnail_file_name": "poster.jpg", + "banner_uncropped_thumbnail_file_name": "fanart.jpg", + "enable_bilateral_scraping": true, + "enable_resolution_assert": true, + "enable_throttle_protection": true, + "episode_content_rating": "TV-14", + "episode_date_standardized": "{ upload_date_standardized }", + "episode_file_name": "s{ upload_year }.e{ %pad_zero( %int( %concat( upload_month, upload_day_padded, upload_date_index_padded ) ), 6 ) } - { title_sanitized_plex }", + "episode_file_path": "{ %sanitize( %concat( \"Season \", %string( upload_year ) ) ) }/{ %sanitize( %concat( \"s\", %string( upload_year ), \".e\", %string( %pad_zero( %int( %concat( upload_month, upload_day_padded, upload_date_index_padded ) ), 6 ) ), \" - \", %string( title_sanitized_plex ) ) ) }", + "episode_number": "{ upload_month }{ upload_day_padded }{ upload_date_index_padded }", + "episode_number_and_padded_": "{ [ %concat( upload_month, upload_day_padded, upload_date_index_padded ), 6 ] }", + "episode_number_padded": "{ %pad_zero( %int( %concat( upload_month, upload_day_padded, upload_date_index_padded ) ), 6 ) }", + "episode_plot": "{ webpage_url }\n\n{ description }", + "episode_title": "{ upload_date_standardized } - { title }", + "episode_year": "{ %slice( upload_date_standardized, 0, 4 ) }", + "file_title": "{ title_sanitized_plex }", + "file_uid": "{ uid_sanitized_plex }", + "include_sibling_metadata": false, + "modified_webpage_url": "{ webpage_url }", + "only_recent_date_range": "2months", + "only_recent_max_files": 30, + "resolution_assert": "{ %if( %and( enable_resolution_assert, %ne( height, 0 ), %not( resolution_assert_is_ignored ) ), %assert( %gte( height, resolution_assert_height_gte ), %concat( \"Entry \", title, \" downloaded at a low resolution (\", resolution_readable, \"), you've probably been throttled. \", \"Stopping further downloads, wait a few hours and try again. \", \"Disable using the override variable `enable_resolution_assert: False`.\" ) ), \"false is no-op\" ) }", + "resolution_assert_height_gte": 361, + "resolution_assert_ignore_titles": "{ [ ] }", + "resolution_assert_is_ignored": "{ %print_if_true( %concat( title, \" has a match in resolution_assert_ignore_titles, skipping resolution assert.\" ), %contains_any( title, [ ] ) ) }", + "resolution_assert_print": true, + "resolution_readable": "{ width }x{ height }", + "s01_name": "", + "s01_url": "", + "season_directory_name": "Season { upload_year }", + "season_number": "{ upload_year }", + "season_number_padded": "{ upload_year }", + "season_poster_file_name": "{ %sanitize( %concat( \"Season \", %string( upload_year ) ) ) }/Season{ upload_year }.jpg", + "subscription_array": [ + "https://www.youtube.com/@novapbs" + ], + "subscription_indent_1": "Documentaries", + "subscription_indent_2": "TV-14", + "subscription_value": "https://www.youtube.com/@novapbs", + "subscription_value_1": "https://www.youtube.com/@novapbs", + "thumbnail_file_name": "{ %concat( %string( %sanitize( %concat( \"Season \", %string( upload_year ) ) ) ), \"/\", %string( %sanitize( %concat( \"s\", %string( upload_year ), \".e\", %string( %pad_zero( %int( %concat( upload_month, upload_day_padded, upload_date_index_padded ) ), 6 ) ), \" - \", %string( title_sanitized_plex ) ) ) ) ) }-thumb.jpg", + "tv_show_by_date_episode_ordering": "upload-month-day", + "tv_show_by_date_ordering_pair_validation_": "{ [ %concat( upload_month, upload_day_padded, upload_date_index_padded ), 6 ] }", + "tv_show_by_date_season_ordering": "upload-year", + "tv_show_content_rating": "TV-14", + "tv_show_content_rating_default": "TV-14", + "tv_show_date_range_type": "upload_date", + "tv_show_directory": "/var/folders/rw/hl1xmkmj68zdl2kjx3l0dwzc0000gn/T/tmp9xwmie5t", + "tv_show_fanart_file_name": "fanart.jpg", + "tv_show_genre": "Documentaries", + "tv_show_genre_default": "ytdl-sub", + "tv_show_name": "NOVA PBS", + "tv_show_poster_file_name": "poster.jpg", + "url": "https://www.youtube.com/@novapbs", + "url10": "", + "url100": "", + "url11": "", + "url12": "", + "url13": "", + "url14": "", + "url15": "", + "url16": "", + "url17": "", + "url18": "", + "url19": "", + "url2": "", + "url20": "", + "url21": "", + "url22": "", + "url23": "", + "url24": "", + "url25": "", + "url26": "", + "url27": "", + "url28": "", + "url29": "", + "url3": "", + "url30": "", + "url31": "", + "url32": "", + "url33": "", + "url34": "", + "url35": "", + "url36": "", + "url37": "", + "url38": "", + "url39": "", + "url4": "", + "url40": "", + "url41": "", + "url42": "", + "url43": "", + "url44": "", + "url45": "", + "url46": "", + "url47": "", + "url48": "", + "url49": "", + "url5": "", + "url50": "", + "url51": "", + "url52": "", + "url53": "", + "url54": "", + "url55": "", + "url56": "", + "url57": "", + "url58": "", + "url59": "", + "url6": "", + "url60": "", + "url61": "", + "url62": "", + "url63": "", + "url64": "", + "url65": "", + "url66": "", + "url67": "", + "url68": "", + "url69": "", + "url7": "", + "url70": "", + "url71": "", + "url72": "", + "url73": "", + "url74": "", + "url75": "", + "url76": "", + "url77": "", + "url78": "", + "url79": "", + "url8": "", + "url80": "", + "url81": "", + "url82": "", + "url83": "", + "url84": "", + "url85": "", + "url86": "", + "url87": "", + "url88": "", + "url89": "", + "url9": "", + "url90": "", + "url91": "", + "url92": "", + "url93": "", + "url94": "", + "url95": "", + "url96": "", + "url97": "", + "url98": "", + "url99": "", + "urls": [ + "https://www.youtube.com/@novapbs" + ] + }, + "throttle_protection": { + "enable": true, + "sleep_per_download_s": { + "max": "28.4", + "min": "13.8" + }, + "sleep_per_request_s": { + "max": "0.75", + "min": "0.0" + }, + "sleep_per_subscription_s": { + "max": "26.1", + "min": "16.3" + } + }, + "video_tags": { + "contentRating": "TV-14", + "date": "{ upload_date_standardized }", + "episode_id": "{ upload_month }{ upload_day_padded }{ upload_date_index_padded }", + "genre": "Documentaries", + "show": "NOVA PBS", + "synopsis": "{ webpage_url }\n\n{ description }", + "title": "{ upload_date_standardized } - { title }", + "year": "{ %slice( upload_date_standardized, 0, 4 ) }" + } +} \ No newline at end of file diff --git a/tests/unit/config/test_subscription.py b/tests/unit/config/test_subscription.py index 61854da4..f3774f1a 100644 --- a/tests/unit/config/test_subscription.py +++ b/tests/unit/config/test_subscription.py @@ -535,674 +535,3 @@ def test_music_video_subscriptions(default_config: ConfigFile, music_video_subsc assert gnr_urls[0] == "https://www.youtube.com/playlist?list=PLOTK54q5K4INNXaHKtmXYr6J7CajWjqeJ" assert gnr.get("subscription_indent_1").native == "Rock" assert gnr_urls[1] == "https://www.youtube.com/watch?v=OldpIhHPsbs" - - -def test_default_docker_config_and_subscriptions( - docker_default_subscription_path: Path, output_directory: str -): - default_config = ConfigFile.from_file_path("docker/root/defaults/config.yaml") - default_subs = Subscription.from_file_path( - config=default_config, subscription_path=docker_default_subscription_path - ) - assert len(default_subs) == 1 - - resolved_yaml_as_json = yaml.safe_load(default_subs[0].resolved_yaml()) - - assert resolved_yaml_as_json == { - "chapters": { - "allow_chapters_from_comments": False, - "embed_chapters": True, - "enable": "True", - "force_key_frames": False, - }, - "date_range": {"breaks": "True", "enable": "True", "type": "upload_date"}, - "download": [ - { - "download_reverse": "True", - "include_sibling_metadata": False, - "playlist_thumbnails": [ - {"name": "{avatar_uncropped_thumbnail_file_name}", "uid": "avatar_uncropped"}, - {"name": "{banner_uncropped_thumbnail_file_name}", "uid": "banner_uncropped"}, - ], - "source_thumbnails": [ - {"name": "{avatar_uncropped_thumbnail_file_name}", "uid": "avatar_uncropped"}, - {"name": "{banner_uncropped_thumbnail_file_name}", "uid": "banner_uncropped"}, - ], - "url": "https://www.youtube.com/@novapbs", - "variables": {}, - "webpage_url": "{modified_webpage_url}", - "ytdl_options": {}, - } - ], - "file_convert": {"convert_to": "mp4", "convert_with": "yt-dlp", "enable": "True"}, - "format": "(bv*[ext=mp4][vcodec~='^((he|a)vc|h26[45])']+ba[ext=m4a]) / (bv[ext=mp4]*+ba[ext=m4a]/b)", - "output_options": { - "download_archive_name": ".ytdl-sub-NOVA PBS-download-archive.json", - "file_name": "{episode_file_path}.{ext}", - "info_json_name": "{episode_file_path}.{info_json_ext}", - "keep_files_date_eval": "{episode_date_standardized}", - "maintain_download_archive": True, - "output_directory": f"{output_directory}/NOVA PBS", - "preserve_mtime": False, - "thumbnail_name": "{thumbnail_file_name}", - }, - "overrides": { - "%bilateral_url": "{ %if( %and( enable_bilateral_scraping, " - "subscription_has_download_archive, " - "%is_bilateral_url( $0 ) ), $0, '' ) }", - "%episode_ordering_": "{ %eq( %lower( " "tv_show_by_date_episode_ordering ), $0 ) " "}", - "%is_bilateral_url": '{ %contains( $0, "youtube.com/playlist" ) ' "}", - "%ordering_pair_eq": "{ %eq( [ tv_show_by_date_season_ordering, " - "tv_show_by_date_episode_ordering ], [ $0, " - "$1 ] ) }", - "%season_ordering_": "{ %eq( %lower( " "tv_show_by_date_season_ordering ), $0 ) }", - "assert_not_collection": "{ %assert( %and( %not( %bool( s01_url " - ") ), %not( %bool( s01_name ) ) ), " - '"Provided `s01_url` or `s01_name` ' - "variable to TV Show by Date preset " - "when it expects `url`. Perhaps you " - "meant to use the `TV Show Collection` " - 'preset?" ) }', - "avatar_uncropped_thumbnail_file_name": "{ " "tv_show_poster_file_name " "}", - "banner_uncropped_thumbnail_file_name": "{ " "tv_show_fanart_file_name " "}", - "enable_bilateral_scraping": "{ %bool(True) }", - "enable_resolution_assert": "{ %bool(True) }", - "enable_throttle_protection": "{ %bool(True) }", - "episode_content_rating": "{ tv_show_content_rating }", - "episode_date_standardized": "{ %if( %contains( " - "tv_show_by_date_season_ordering, " - '"release" ), ' - "release_date_standardized, " - "upload_date_standardized ) }", - "episode_file_name": '{ %concat( %string( "s" ), %string( ' - 'season_number_padded ), %string( ".e" ), ' - "%string( episode_number_padded ), " - '%string( " - " ), %string( file_title ) ) ' - "}", - "episode_file_path": "{ %concat( %string( " - "season_directory_name_sanitized ), " - '%string( "/" ), %string( ' - "episode_file_name_sanitized ) ) }", - "episode_number": "{ %array_at( episode_number_and_padded_, 0 ) " "}", - "episode_number_and_padded_": "{ %elif( %episode_ordering_( " - '"upload-day" ), [ %concat( ' - "upload_day, " - "upload_date_index_padded ), 4 ], " - "%episode_ordering_( " - '"upload-month-day" ), [ %concat( ' - "upload_month, upload_day_padded, " - "upload_date_index_padded ), 6 ], " - "%episode_ordering_( " - '"upload-month-day-reversed" ), [ ' - "%concat( " - "upload_day_of_year_reversed, " - "upload_date_index_reversed_padded " - "), 5 ], %episode_ordering_( " - '"release-day" ), [ %concat( ' - "release_day, " - "upload_date_index_padded ), 4 ], " - "%episode_ordering_( " - '"release-month-day" ), [ ' - "%concat( release_month, " - "release_day_padded, " - "upload_date_index_padded ), 6 ], " - "%episode_ordering_( " - '"release-month-day-reversed" ), ' - "[ %concat( " - "release_day_of_year_reversed, " - "upload_date_index_reversed_padded " - "), 5 ], %episode_ordering_( " - '"download-index" ), [ ' - "download_index, 6 ], %throw( " - "'tv_show_by_date_episode_ordering " - "must be one of the following: " - '"upload-day", ' - '"upload-month-day", ' - '"upload-month-day-reversed", ' - '"release-day", ' - '"release-month-day", ' - '"release-month-day-reversed", ' - '"download-index"\' ) ) }', - "episode_number_padded": "{ %pad_zero( %int( episode_number ), " - "%int( %array_at( " - "episode_number_and_padded_, 1 ) ) ) }", - "episode_plot": '{ %concat( %string( webpage_url ), %string( "\n' - "\n" - '" ), %string( description ) ) }', - "episode_title": "{ %concat( %string( episode_date_standardized " - '), %string( " - " ), %string( title ) ) }', - "episode_year": "{ %slice( episode_date_standardized, 0, 4 ) }", - "file_title": "{ title_sanitized_plex }", - "file_uid": "{ uid_sanitized_plex }", - "include_sibling_metadata": "{ %bool(False) }", - "modified_webpage_url": "{ webpage_url }", - "music_directory": "/music", - "music_video_directory": "/music_videos", - "resolution_assert": "{ %if( %and( enable_resolution_assert, " - "%ne( height, 0 ), %not( " - "resolution_assert_is_ignored ) ), " - "%assert( %gte( height, " - "resolution_assert_height_gte ), %concat( " - '"Entry ", title, " downloaded at a low ' - 'resolution (", resolution_readable, "), ' - "you've probably been throttled. \", " - '"Stopping further downloads, wait a few ' - 'hours and try again. ", "Disable using ' - "the override variable " - '`enable_resolution_assert: False`." ) ), ' - '"false is no-op" ) }', - "resolution_assert_height_gte": "{ %int(361) }", - "resolution_assert_ignore_titles": "{ [ ] }", - "resolution_assert_is_ignored": "{ %print_if_true( %concat( " - 'title, " has a match in ' - "resolution_assert_ignore_titles, " - 'skipping resolution assert." ' - "), %contains_any( title, " - "resolution_assert_ignore_titles " - ") ) }", - "resolution_assert_print": "{ %print( %if( " - "enable_resolution_assert, " - '"Resolution assert is enabled, will ' - "fail on low-quality video downloads " - "and presume throttle. Disable using " - "the override variable " - '`enable_resolution_assert: False`", ' - '"Resolution assert is disabled. Use ' - 'at your own risk!" ), ' - "enable_resolution_assert, -1 ) }", - "resolution_readable": "{ %concat( %string( width ), %string( " - '"x" ), %string( height ) ) }', - "s01_name": "", - "s01_url": "", - "season_directory_name": '{ %concat( %string( "Season " ), ' - "%string( season_number_padded ) ) }", - "season_number": '{ %elif( %season_ordering_( "upload-year" ), ' - "upload_year, %season_ordering_( " - '"upload-year-month" ), %concat( upload_year, ' - "upload_month_padded ), %season_ordering_( " - '"release-year" ), release_year, ' - '%season_ordering_( "release-year-month" ), ' - "%concat( release_year, release_month_padded " - "), %throw( 'tv_show_by_date_season_ordering " - 'must be one of the following: "upload-year", ' - '"upload-year-month", "release-year", ' - '"release-year-month"\' ) ) }', - "season_number_padded": "{ season_number }", - "season_poster_file_name": "{ %concat( %string( " - "season_directory_name_sanitized ), " - '%string( "/Season" ), %string( ' - "season_number_padded ), %string( " - '".jpg" ) ) }', - "subscription_array": "{ %from_json( " - "'[\"https://www.youtube.com/@novapbs\"]' " - ") }", - "subscription_indent_1": "Documentaries", - "subscription_indent_2": "{ tv_show_content_rating_default }", - "subscription_value": "https://www.youtube.com/@novapbs", - "subscription_value_1": "https://www.youtube.com/@novapbs", - "thumbnail_file_name": "{ %concat( %string( episode_file_path " - '), %string( "-thumb.jpg" ) ) }', - "tv_show_by_date_episode_ordering": "upload-month-day", - "tv_show_by_date_ordering_pair_validation_": "{ %assert_then( " - "%or( " - "%ordering_pair_eq( " - '"upload-year", ' - '"upload-month-day" ' - "), " - "%ordering_pair_eq( " - '"upload-year", ' - '"upload-month-day-reversed" ' - "), " - "%ordering_pair_eq( " - '"upload-year", ' - '"download-index" ' - "), " - "%ordering_pair_eq( " - '"upload-year-month", ' - '"upload-day" ), ' - "%ordering_pair_eq( " - '"release-year", ' - '"release-month-day" ' - "), " - "%ordering_pair_eq( " - '"release-year", ' - '"release-month-day-reversed" ' - "), " - "%ordering_pair_eq( " - '"release-year", ' - '"download-index" ' - "), " - "%ordering_pair_eq( " - '"release-year-month", ' - '"release-day" ) ' - "), " - "episode_number_and_padded_, " - '"Detected ' - "incompatibility " - "between " - "tv_show_by_date_season_ordering " - "and " - "tv_show_by_date_episode_ordering. " - "Ensure you are " - "not using both " - "upload and " - "release date, and " - "that the " - "year/month/day " - "are included in " - "the combined " - "season and " - 'episode." ) }', - "tv_show_by_date_season_ordering": "upload-year", - "tv_show_content_rating": "{ subscription_indent_2 }", - "tv_show_content_rating_default": "TV-14", - "tv_show_date_range_type": "{ %if( %contains( " - "tv_show_by_date_season_ordering, " - '"release" ), "release_date", ' - '"upload_date" ) }', - "tv_show_directory": output_directory, - "tv_show_fanart_file_name": "fanart.jpg", - "tv_show_genre": "{ subscription_indent_1 }", - "tv_show_genre_default": "ytdl-sub", - "tv_show_name": "{ subscription_name }", - "tv_show_poster_file_name": "poster.jpg", - "url": "{ subscription_value }", - "url10": "", - "url100": "", - "url11": "", - "url12": "", - "url13": "", - "url14": "", - "url15": "", - "url16": "", - "url17": "", - "url18": "", - "url19": "", - "url2": "", - "url20": "", - "url21": "", - "url22": "", - "url23": "", - "url24": "", - "url25": "", - "url26": "", - "url27": "", - "url28": "", - "url29": "", - "url3": "", - "url30": "", - "url31": "", - "url32": "", - "url33": "", - "url34": "", - "url35": "", - "url36": "", - "url37": "", - "url38": "", - "url39": "", - "url4": "", - "url40": "", - "url41": "", - "url42": "", - "url43": "", - "url44": "", - "url45": "", - "url46": "", - "url47": "", - "url48": "", - "url49": "", - "url5": "", - "url50": "", - "url51": "", - "url52": "", - "url53": "", - "url54": "", - "url55": "", - "url56": "", - "url57": "", - "url58": "", - "url59": "", - "url6": "", - "url60": "", - "url61": "", - "url62": "", - "url63": "", - "url64": "", - "url65": "", - "url66": "", - "url67": "", - "url68": "", - "url69": "", - "url7": "", - "url70": "", - "url71": "", - "url72": "", - "url73": "", - "url74": "", - "url75": "", - "url76": "", - "url77": "", - "url78": "", - "url79": "", - "url8": "", - "url80": "", - "url81": "", - "url82": "", - "url83": "", - "url84": "", - "url85": "", - "url86": "", - "url87": "", - "url88": "", - "url89": "", - "url9": "", - "url90": "", - "url91": "", - "url92": "", - "url93": "", - "url94": "", - "url95": "", - "url96": "", - "url97": "", - "url98": "", - "url99": "", - "urls": "{ subscription_array }", - }, - "throttle_protection": { - "enable": True, - "sleep_per_download_s": {"max": "28.4", "min": "13.8"}, - "sleep_per_request_s": {"max": "0.75", "min": "0.0"}, - "sleep_per_subscription_s": {"max": "26.1", "min": "16.3"}, - }, - "video_tags": { - "contentRating": "{episode_content_rating}", - "date": "{episode_date_standardized}", - "episode_id": "{episode_number}", - "genre": "{tv_show_genre}", - "show": "{tv_show_name}", - "synopsis": "{episode_plot}", - "title": "{episode_title}", - "year": "{episode_year}", - }, - } - - -def test_default_docker_config_and_subscriptions_resolved_resolution( - docker_default_subscription_path: Path, output_directory: str -): - default_config = ConfigFile.from_file_path("docker/root/defaults/config.yaml") - default_subs = Subscription.from_file_path( - config=default_config, subscription_path=docker_default_subscription_path - ) - assert len(default_subs) == 1 - - resolved_yaml_as_json = yaml.safe_load( - default_subs[0].resolved_yaml(resolution_level=ResolutionLevel.RESOLVE) - ) - - assert resolved_yaml_as_json == { - "chapters": { - "allow_chapters_from_comments": False, - "embed_chapters": True, - "enable": "True", - "force_key_frames": False, - }, - "date_range": {"breaks": "True", "enable": "True", "type": "upload_date"}, - "download": [ - { - "download_reverse": "True", - "include_sibling_metadata": False, - "playlist_thumbnails": [ - {"name": "{avatar_uncropped_thumbnail_file_name}", "uid": "avatar_uncropped"}, - {"name": "{banner_uncropped_thumbnail_file_name}", "uid": "banner_uncropped"}, - ], - "source_thumbnails": [ - {"name": "{avatar_uncropped_thumbnail_file_name}", "uid": "avatar_uncropped"}, - {"name": "{banner_uncropped_thumbnail_file_name}", "uid": "banner_uncropped"}, - ], - "url": "https://www.youtube.com/@novapbs", - "variables": {}, - "webpage_url": "{modified_webpage_url}", - "ytdl_options": {}, - } - ], - "file_convert": {"convert_to": "mp4", "convert_with": "yt-dlp", "enable": "True"}, - "format": "(bv*[ext=mp4][vcodec~='^((he|a)vc|h26[45])']+ba[ext=m4a]) / (bv[ext=mp4]*+ba[ext=m4a]/b)", - "output_options": { - "download_archive_name": ".ytdl-sub-NOVA PBS-download-archive.json", - "file_name": "{episode_file_path}.{ext}", - "info_json_name": "{episode_file_path}.{info_json_ext}", - "keep_files_date_eval": "{episode_date_standardized}", - "maintain_download_archive": True, - "output_directory": f"{output_directory}/NOVA PBS", - "preserve_mtime": False, - "thumbnail_name": "{thumbnail_file_name}", - }, - "overrides": { - "%bilateral_url": "{ %if( %and( enable_bilateral_scraping, " - "subscription_has_download_archive, " - "%is_bilateral_url( $0 ) ), $0, '' ) }", - "%episode_ordering_": "{ %eq( %lower( " "tv_show_by_date_episode_ordering ), $0 ) " "}", - "%is_bilateral_url": '{ %contains( $0, "youtube.com/playlist" ) ' "}", - "%ordering_pair_eq": "{ %eq( [ tv_show_by_date_season_ordering, " - "tv_show_by_date_episode_ordering ], [ $0, " - "$1 ] ) }", - "%season_ordering_": "{ %eq( %lower( " "tv_show_by_date_season_ordering ), $0 ) }", - "assert_not_collection": True, - "avatar_uncropped_thumbnail_file_name": "poster.jpg", - "banner_uncropped_thumbnail_file_name": "fanart.jpg", - "enable_bilateral_scraping": True, - "enable_resolution_assert": True, - "enable_throttle_protection": True, - "episode_content_rating": "TV-14", - "episode_date_standardized": "{ upload_date_standardized }", - "episode_file_name": "s{ upload_year }.e{ %pad_zero( %int( " - "%concat( upload_month, upload_day_padded, " - "upload_date_index_padded ) ), 6 ) } - { " - "title_sanitized_plex }", - "episode_file_path": '{ %sanitize( %concat( "Season ", %string( ' - "upload_year ) ) ) }/{ %sanitize( %concat( " - '"s", %string( upload_year ), ".e", ' - "%string( %pad_zero( %int( %concat( " - "upload_month, upload_day_padded, " - 'upload_date_index_padded ) ), 6 ) ), " - ' - '", %string( title_sanitized_plex ) ) ) }', - "episode_number": "{ upload_month }{ upload_day_padded }{ " - "upload_date_index_padded }", - "episode_number_and_padded_": "{ [ %concat( upload_month, " - "upload_day_padded, " - "upload_date_index_padded ), 6 ] " - "}", - "episode_number_padded": "{ %pad_zero( %int( %concat( " - "upload_month, upload_day_padded, " - "upload_date_index_padded ) ), 6 ) }", - "episode_plot": "{ webpage_url }\n\n{ description }", - "episode_title": "{ upload_date_standardized } - { title }", - "episode_year": "{ %slice( upload_date_standardized, 0, 4 ) }", - "file_title": "{ title_sanitized_plex }", - "file_uid": "{ uid_sanitized_plex }", - "include_sibling_metadata": False, - "modified_webpage_url": "{ webpage_url }", - "music_directory": "/music", - "music_video_directory": "/music_videos", - "resolution_assert": "{ %if( %and( enable_resolution_assert, " - "%ne( height, 0 ), %not( " - "resolution_assert_is_ignored ) ), " - "%assert( %gte( height, " - "resolution_assert_height_gte ), %concat( " - '"Entry ", title, " downloaded at a low ' - 'resolution (", resolution_readable, "), ' - "you've probably been throttled. \", " - '"Stopping further downloads, wait a few ' - 'hours and try again. ", "Disable using ' - "the override variable " - '`enable_resolution_assert: False`." ) ), ' - '"false is no-op" ) }', - "resolution_assert_height_gte": 361, - "resolution_assert_ignore_titles": "{ [ ] }", - "resolution_assert_is_ignored": "{ %print_if_true( %concat( " - 'title, " has a match in ' - "resolution_assert_ignore_titles, " - 'skipping resolution assert." ' - "), %contains_any( title, " - "resolution_assert_ignore_titles " - ") ) }", - "resolution_assert_print": True, - "resolution_readable": "{ width }x{ height }", - "s01_name": "", - "s01_url": "", - "season_directory_name": "Season { upload_year }", - "season_number": "{ upload_year }", - "season_number_padded": "{ upload_year }", - "season_poster_file_name": '{ %sanitize( %concat( "Season ", ' - "%string( upload_year ) ) ) " - "}/Season{ upload_year }.jpg", - "subscription_array": ["https://www.youtube.com/@novapbs"], - "subscription_indent_1": "Documentaries", - "subscription_indent_2": "TV-14", - "subscription_value": "https://www.youtube.com/@novapbs", - "subscription_value_1": "https://www.youtube.com/@novapbs", - "thumbnail_file_name": "{ %concat( %string( %sanitize( %concat( " - '"Season ", %string( upload_year ) ) ) ' - '), "/", %string( %sanitize( %concat( ' - '"s", %string( upload_year ), ".e", ' - "%string( %pad_zero( %int( %concat( " - "upload_month, upload_day_padded, " - 'upload_date_index_padded ) ), 6 ) ), " ' - '- ", %string( title_sanitized_plex ) ) ' - ") ) ) }-thumb.jpg", - "tv_show_by_date_episode_ordering": "upload-month-day", - "tv_show_by_date_ordering_pair_validation_": "{ [ %concat( " - "upload_month, " - "upload_day_padded, " - "upload_date_index_padded " - "), 6 ] }", - "tv_show_by_date_season_ordering": "upload-year", - "tv_show_content_rating": "TV-14", - "tv_show_content_rating_default": "TV-14", - "tv_show_date_range_type": "upload_date", - "tv_show_directory": output_directory, - "tv_show_fanart_file_name": "fanart.jpg", - "tv_show_genre": "Documentaries", - "tv_show_genre_default": "ytdl-sub", - "tv_show_name": "NOVA PBS", - "tv_show_poster_file_name": "poster.jpg", - "url": "https://www.youtube.com/@novapbs", - "url10": "", - "url100": "", - "url11": "", - "url12": "", - "url13": "", - "url14": "", - "url15": "", - "url16": "", - "url17": "", - "url18": "", - "url19": "", - "url2": "", - "url20": "", - "url21": "", - "url22": "", - "url23": "", - "url24": "", - "url25": "", - "url26": "", - "url27": "", - "url28": "", - "url29": "", - "url3": "", - "url30": "", - "url31": "", - "url32": "", - "url33": "", - "url34": "", - "url35": "", - "url36": "", - "url37": "", - "url38": "", - "url39": "", - "url4": "", - "url40": "", - "url41": "", - "url42": "", - "url43": "", - "url44": "", - "url45": "", - "url46": "", - "url47": "", - "url48": "", - "url49": "", - "url5": "", - "url50": "", - "url51": "", - "url52": "", - "url53": "", - "url54": "", - "url55": "", - "url56": "", - "url57": "", - "url58": "", - "url59": "", - "url6": "", - "url60": "", - "url61": "", - "url62": "", - "url63": "", - "url64": "", - "url65": "", - "url66": "", - "url67": "", - "url68": "", - "url69": "", - "url7": "", - "url70": "", - "url71": "", - "url72": "", - "url73": "", - "url74": "", - "url75": "", - "url76": "", - "url77": "", - "url78": "", - "url79": "", - "url8": "", - "url80": "", - "url81": "", - "url82": "", - "url83": "", - "url84": "", - "url85": "", - "url86": "", - "url87": "", - "url88": "", - "url89": "", - "url9": "", - "url90": "", - "url91": "", - "url92": "", - "url93": "", - "url94": "", - "url95": "", - "url96": "", - "url97": "", - "url98": "", - "url99": "", - "urls": ["https://www.youtube.com/@novapbs"], - }, - "throttle_protection": { - "enable": True, - "sleep_per_download_s": {"max": "28.4", "min": "13.8"}, - "sleep_per_request_s": {"max": "0.75", "min": "0.0"}, - "sleep_per_subscription_s": {"max": "26.1", "min": "16.3"}, - }, - "video_tags": { - "contentRating": "{episode_content_rating}", - "date": "{episode_date_standardized}", - "episode_id": "{episode_number}", - "genre": "{tv_show_genre}", - "show": "{tv_show_name}", - "synopsis": "{episode_plot}", - "title": "{episode_title}", - "year": "{episode_year}", - }, - }