From 148e73fddeae00ca23e607bc0936b62c1d0722a4 Mon Sep 17 00:00:00 2001 From: Jesse Bannon Date: Sun, 10 Dec 2023 09:15:18 -0800 Subject: [PATCH] Revert "no more entry script, maybe bad idea???" This reverts commit 4f08b5300e932392ee0a6252a112202c018831ad. --- src/ytdl_sub/config/overrides.py | 91 ++------- src/ytdl_sub/config/preset.py | 7 +- .../info_json/info_json_downloader.py | 22 +-- src/ytdl_sub/downloaders/url/downloader.py | 23 ++- src/ytdl_sub/entries/entry.py | 63 ++++++- .../entries/script/variable_scripts.py | 9 +- src/ytdl_sub/plugins/internal/view.py | 2 +- src/ytdl_sub/plugins/music_tags.py | 2 +- src/ytdl_sub/plugins/nfo_tags.py | 16 +- src/ytdl_sub/plugins/regex.py | 82 ++++++-- src/ytdl_sub/plugins/subtitles.py | 1 + src/ytdl_sub/plugins/video_tags.py | 2 +- .../subscriptions/subscription_download.py | 8 +- src/ytdl_sub/utils/script.py | 2 - tests/unit/entries/conftest.py | 175 ++++++++---------- tests/unit/entries/test_entry.py | 72 ++----- 16 files changed, 292 insertions(+), 285 deletions(-) diff --git a/src/ytdl_sub/config/overrides.py b/src/ytdl_sub/config/overrides.py index 7fb2533f..ad1e9544 100644 --- a/src/ytdl_sub/config/overrides.py +++ b/src/ytdl_sub/config/overrides.py @@ -1,29 +1,22 @@ import copy from typing import Any from typing import Dict +from typing import Optional from typing import Set -from typing import Type -from typing import TypeVar -from typing import final from yt_dlp.utils import sanitize_filename +from ytdl_sub.entries.entry import Entry from ytdl_sub.entries.script.variable_definitions import VARIABLES -from ytdl_sub.entries.script.variable_definitions import Variable -from ytdl_sub.entries.script.variable_scripts import UNRESOLVED_VARIABLES -from ytdl_sub.entries.script.variable_scripts import VARIABLE_SCRIPTS from ytdl_sub.entries.variables.override_variables import SUBSCRIPTION_NAME from ytdl_sub.script.parser import parse from ytdl_sub.script.script import Script -from ytdl_sub.utils.script import ScriptUtils +from ytdl_sub.utils.scriptable import Scriptable from ytdl_sub.validators.string_formatter_validators import DictFormatterValidator -from ytdl_sub.validators.string_formatter_validators import OverridesStringFormatterValidator from ytdl_sub.validators.string_formatter_validators import StringFormatterValidator -TType = TypeVar("TType") - -class Overrides(DictFormatterValidator): +class Overrides(DictFormatterValidator, Scriptable): """ Optional. This section allows you to define variables that can be used in any string formatter. For example, if you want your file and thumbnail files to match without copy-pasting a large @@ -69,7 +62,8 @@ class Overrides(DictFormatterValidator): ) def __init__(self, name, value): - super().__init__(name, value) + DictFormatterValidator.__init__(self, name, value) + Scriptable.__init__(self) # Add sanitized overrides for key in self._keys: @@ -87,53 +81,14 @@ class Overrides(DictFormatterValidator): sanitize=sanitized, ) - self.script = Script(copy.deepcopy(VARIABLE_SCRIPTS)) - self.unresolvable: Set[str] = copy.deepcopy(UNRESOLVED_VARIABLES) + self.unresolvable.add(VARIABLES.entry_metadata.variable_name) + + def initialize_script(self, unresolved_variables: Dict[str, str]) -> None: + self.script.add(dict(self.dict_with_format_strings, **unresolved_variables)) + self.unresolvable.update(set(unresolved_variables.keys())) - def initialize_script(self, unresolved_variables: Set[str]) -> None: - self.unresolvable |= unresolved_variables - self.script.add( - dict( - self.dict_with_format_strings, - **{ - unresolved: f"{{%throw('Variable {unresolved} has not been resolved yet')}}" - for unresolved in self.unresolvable - }, - ) - ) self.update_script() - def add_entry_kwargs(self, entry_kwargs: Dict[str, Any]) -> "Overrides": - self.unresolvable.remove(VARIABLES.entry_metadata.variable_name) - self.script.add( - {VARIABLES.entry_metadata.variable_name: ScriptUtils.to_script(entry_kwargs)} - ) - self.update_script() - return self - - def add(self, values: Dict[str, Any]) -> None: - self.unresolvable -= set(list(values.keys())) - self.script.add( - ScriptUtils.add_sanitized_variables( - {name: ScriptUtils.to_script(value) for name, value in values.items()} - ), - unresolvable=self.unresolvable, - ) - self.update_script() - - def update_script(self) -> None: - self.script.resolve(unresolvable=self.unresolvable, update=True) - - def get(self, variable: Variable | str, expected_type: Type[TType]) -> TType: - out = self.script.resolve(unresolvable=self.unresolvable).get_native(variable.variable_name) - return expected_type(out) - - def get_str(self, variable: Variable) -> str: - return self.get(variable, str) - - def get_int(self, variable: Variable) -> int: - return self.get(variable, int) - @property def subscription_name(self) -> str: """ @@ -143,18 +98,10 @@ class Overrides(DictFormatterValidator): """ return self._root_name - @final - def to_dict(self) -> Dict[str, str]: - """ - Returns - ------- - Dictionary containing all variables - """ - return self.script.resolve().as_native() - def apply_formatter( self, formatter: StringFormatterValidator, + entry: Optional[Entry] = None, function_overrides: Dict[str, str] = None, ) -> str: """ @@ -171,15 +118,17 @@ class Overrides(DictFormatterValidator): ------- The format_string after .format has been called """ + script: Script = self.script + unresolvable: Set[str] = self.unresolvable + if entry: + script = entry.script + unresolvable = entry.unresolvable + return formatter.post_process( str( - self.script.resolve_once( + script.resolve_once( dict({"tmp_var": formatter.format_string}, **(function_overrides or {})), - unresolvable=self.unresolvable.union( - VARIABLES.entry_metadata.variable_name - if isinstance(formatter, OverridesStringFormatterValidator) - else set() - ), + unresolvable=unresolvable, )["tmp_var"] ) ) diff --git a/src/ytdl_sub/config/preset.py b/src/ytdl_sub/config/preset.py index 5a9d639f..7e70b74e 100644 --- a/src/ytdl_sub/config/preset.py +++ b/src/ytdl_sub/config/preset.py @@ -361,7 +361,12 @@ class Preset(_PresetShell): # values from multiple validators self.__recursive_preset_validate() - self.overrides.initialize_script(unresolved_variables=set(list(self._added_variables.keys()))) + self.overrides.initialize_script( + unresolved_variables={ + var_name: f"{{%throw('Plugin variable {var_name} has not been created yet')}}" + for var_name in self._added_variables + } + ) @property def name(self) -> str: 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 443e09c4..756d34d3 100644 --- a/src/ytdl_sub/downloaders/info_json/info_json_downloader.py +++ b/src/ytdl_sub/downloaders/info_json/info_json_downloader.py @@ -109,17 +109,17 @@ class InfoJsonDownloader(SourcePlugin[InfoJsonDownloaderOptions]): prior_variables = entry.kwargs(YTDL_SUB_ENTRY_VARIABLES_KWARG_KEY) del entry._kwargs[YTDL_SUB_ENTRY_VARIABLES_KWARG_KEY] - self.overrides.add( - dict( - entry._kwargs, - **{ - inj.variable_name: prior_variables.get( - inj.variable_name, - VARIABLE_SCRIPTS[inj.variable_name], - ) - for inj in ENTRY_INJECTED_VARIABLES - }, - ) + entry.initialize_script( + override_variables=self.overrides.dict_with_format_strings, + unresolvable=self.overrides.unresolvable, + ).add( + { + inj.variable_name: prior_variables.get( + inj.variable_name, + VARIABLE_SCRIPTS[inj.variable_name], + ) + for inj in ENTRY_INJECTED_VARIABLES + } ) entries.append(entry) diff --git a/src/ytdl_sub/downloaders/url/downloader.py b/src/ytdl_sub/downloaders/url/downloader.py index 2df58059..36c2a240 100644 --- a/src/ytdl_sub/downloaders/url/downloader.py +++ b/src/ytdl_sub/downloaders/url/downloader.py @@ -71,7 +71,7 @@ class UrlDownloaderThumbnailPlugin(SourcePluginExtension): Downloads and moves channel avatar and banner images to the output directory. """ for thumbnail_info in thumbnail_list_info.list: - thumbnail_name = self.overrides.apply_formatter(thumbnail_info.name) + thumbnail_name = self.overrides.apply_formatter(thumbnail_info.name, entry=entry) thumbnail_id = self.overrides.apply_formatter(thumbnail_info.uid) # If the thumbnail name is an empty string, completely ignore trying to download it @@ -176,7 +176,7 @@ class UrlDownloaderCollectionVariablePlugin(SourcePluginExtension): """ # 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 = self.overrides.get_str(v.ytdl_sub_input_url) + entry_collection_url = entry.get_str(v.ytdl_sub_input_url) # If the collection URL cannot find its mapping, use the last URL collection_url = ( @@ -459,16 +459,15 @@ class MultiUrlDownloader(SourcePlugin[MultiUrlValidator]): for entry in self._iterate_entries( url_validator=collection_url, parents=parents, orphans=orphan_entries ): - - self.overrides.add( - dict( - entry._kwargs, - **{ - v.ytdl_sub_input_url.variable_name: self.overrides.apply_formatter( - collection_url.url - ) - }, - ) + entry.initialize_script( + override_variables=self.overrides.dict_with_format_strings, + unresolvable=self.overrides.unresolvable, + ).add( + { + v.ytdl_sub_input_url.variable_name: self.overrides.apply_formatter( + collection_url.url + ) + } ) yield entry diff --git a/src/ytdl_sub/entries/entry.py b/src/ytdl_sub/entries/entry.py index bc9ba51b..e7e5c13f 100644 --- a/src/ytdl_sub/entries/entry.py +++ b/src/ytdl_sub/entries/entry.py @@ -2,13 +2,17 @@ import copy import json import os from pathlib import Path +from typing import Dict from typing import Optional +from typing import Set +from typing import Type from typing import TypeVar from typing import final -from ytdl_sub.config.overrides import Overrides from ytdl_sub.entries.base_entry import BaseEntry from ytdl_sub.entries.script.variable_definitions import VARIABLES +from ytdl_sub.entries.script.variable_definitions import Variable +from ytdl_sub.utils.scriptable import Scriptable from ytdl_sub.validators.audo_codec_validator import AUDIO_CODEC_EXTS from ytdl_sub.validators.audo_codec_validator import VIDEO_CODEC_EXTS @@ -17,11 +21,49 @@ YTDL_SUB_ENTRY_VARIABLES_KWARG_KEY: str = "ytdl_sub_entry_variables" TType = TypeVar("TType") -class Entry(BaseEntry): +class Entry(BaseEntry, Scriptable): """ Entry object to represent a single media object returned from yt-dlp. """ + def __init__(self, entry_dict: Dict, working_directory: str): + BaseEntry.__init__(self, entry_dict=entry_dict, working_directory=working_directory) + Scriptable.__init__(self) + + def initialize_script( + self, override_variables: Dict[str, str], unresolvable: Set[str] + ) -> "Entry": + # TODO: CLEAN THIS SHIT UP + # Overrides contains added variables that are unresolvable, add them here + self.unresolvable |= unresolvable + + # Remove the entry variable + self.unresolvable.remove(VARIABLES.entry_metadata.variable_name) + + # Add entry metadata, but avoid the `.add()` helper since it also adds sanitized + self.script.add({VARIABLES.entry_metadata.variable_name: f"{{{json.dumps(self._kwargs)}}}"}) + self.script.add( + { + unresolved: f"{{%throw('Variable {unresolved} has not been resolved yet')}}" + for unresolved in self.unresolvable + } + ) + + # use .add here to get sanitized + self.add(override_variables) + self.update_script() + return self + + def get(self, variable: Variable, expected_type: Type[TType]) -> TType: + out = self.script.resolve(unresolvable=self.unresolvable).get_native(variable.variable_name) + return expected_type(out) + + def get_str(self, variable: Variable) -> str: + return self.get(variable, str) + + def get_int(self, variable: Variable) -> int: + return self.get(variable, int) + @property def ext(self) -> str: """ @@ -29,7 +71,7 @@ class Entry(BaseEntry): This is not reflected in the entry. See if the mkv file exists and return "mkv" if so, otherwise, return the original extension. """ - ext = self.kwargs(VARIABLES.ext.metadata_key) + ext = self.get_str(VARIABLES.ext) for possible_ext in [ext, "mkv"]: file_path = str(Path(self.working_directory()) / f"{self.uid}.{possible_ext}") if os.path.isfile(file_path): @@ -55,7 +97,7 @@ class Entry(BaseEntry): ------- The download thumbnail's file name """ - return f"{self.kwargs(VARIABLES.uid.metadata_key)}.jpg" + return f"{self.get_str(VARIABLES.uid)}.{self.get_str(VARIABLES.thumbnail_ext)}" def get_download_thumbnail_path(self) -> str: """Returns the entry's thumbnail's file path to where it was downloaded""" @@ -79,12 +121,12 @@ class Entry(BaseEntry): return None - def write_info_json(self, overrides: Overrides) -> None: + def write_info_json(self) -> None: """ Write the entry's _kwargs back into the info.json file as well as its source variables """ kwargs_dict = copy.deepcopy(self._kwargs) - kwargs_dict["ytdl_sub_entry_variables"] = overrides.to_dict() + kwargs_dict["ytdl_sub_entry_variables"] = self.to_dict() kwargs_json = json.dumps(kwargs_dict, ensure_ascii=False, sort_keys=True, indent=2) with open(self.get_download_info_json_path(), "w", encoding="utf-8") as file: @@ -126,3 +168,12 @@ class Entry(BaseEntry): break return file_exists + + @final + def to_dict(self) -> Dict[str, str]: + """ + Returns + ------- + Dictionary containing all variables + """ + return self.script.resolve().as_native() diff --git a/src/ytdl_sub/entries/script/variable_scripts.py b/src/ytdl_sub/entries/script/variable_scripts.py index 192620d9..bb126a1f 100644 --- a/src/ytdl_sub/entries/script/variable_scripts.py +++ b/src/ytdl_sub/entries/script/variable_scripts.py @@ -118,7 +118,7 @@ ENTRY_HARDCODED_VARIABLES: Dict[Variable, str] = { ENTRY_RELATIVE_VARIABLES: Dict[MetadataVariable, str] = { v.playlist_metadata: entry_get(v.playlist_metadata, {}), v.source_metadata: entry_get(v.source_metadata, {}), - v.sibling_entry_metadata: entry_get(v.sibling_entry_metadata, []), + v.sibling_entry_metadata: entry_get(v.sibling_entry_metadata, "{ [] }"), } ENTRY_REQUIRED_VARIABLES: Dict[MetadataVariable, str] = { @@ -279,11 +279,6 @@ mergedeep.merge( VARIABLE_SCRIPTS: Dict[str, str] = { var.variable_name: script for var, script in _VARIABLE_SCRIPTS.items() } +UNRESOLVED_VARIABLES: Set[str] = {var.variable_name for var in ENTRY_INJECTED_VARIABLES} -UNRESOLVED_VARIABLES: Set[str] = { - var.variable_name - for var in list(ENTRY_EMPTY_METADATA.keys()) - + list(ENTRY_INJECTED_VARIABLES.keys()) - + list(ENTRY_RELATIVE_VARIABLES.keys()) -} CustomFunctions.register() diff --git a/src/ytdl_sub/plugins/internal/view.py b/src/ytdl_sub/plugins/internal/view.py index 38292601..940452eb 100644 --- a/src/ytdl_sub/plugins/internal/view.py +++ b/src/ytdl_sub/plugins/internal/view.py @@ -58,7 +58,7 @@ class ViewPlugin(Plugin[ViewOptions]): """ Adds all source variables to the entry """ - source_var_dict = self.overrides.to_dict() + source_var_dict = copy.deepcopy(entry.to_dict()) for key in source_var_dict.keys(): source_var_dict[key] = self._truncate_value(source_var_dict[key]) diff --git a/src/ytdl_sub/plugins/music_tags.py b/src/ytdl_sub/plugins/music_tags.py index a8fb065b..e7bb92e4 100644 --- a/src/ytdl_sub/plugins/music_tags.py +++ b/src/ytdl_sub/plugins/music_tags.py @@ -157,7 +157,7 @@ class MusicTagsPlugin(Plugin[MusicTagsOptions]): tags_to_write: Dict[str, List[str]] = defaultdict(list) for tag_name, tag_formatters in self.plugin_options.tags.as_lists.items(): for tag_formatter in tag_formatters: - tag_value = self.overrides.apply_formatter(formatter=tag_formatter) + tag_value = self.overrides.apply_formatter(formatter=tag_formatter, entry=entry) tags_to_write[tag_name].append(tag_value) # write the actual tags if its not a dry run diff --git a/src/ytdl_sub/plugins/nfo_tags.py b/src/ytdl_sub/plugins/nfo_tags.py index 19ded393..2ff09d55 100644 --- a/src/ytdl_sub/plugins/nfo_tags.py +++ b/src/ytdl_sub/plugins/nfo_tags.py @@ -99,7 +99,7 @@ class SharedNfoTagsPlugin(Plugin[SharedNfoTagsOptions], ABC): for key, string_tags in self.plugin_options.tags.string_tags.items(): tags = [ XmlElement( - text=self.overrides.apply_formatter(formatter=string_tag), + text=self.overrides.apply_formatter(formatter=string_tag, entry=entry), attributes={}, ) for string_tag in string_tags @@ -110,9 +110,11 @@ class SharedNfoTagsPlugin(Plugin[SharedNfoTagsOptions], ABC): for key, attribute_tags in self.plugin_options.tags.attribute_tags.items(): tags = [ XmlElement( - text=self.overrides.apply_formatter(formatter=attribute_tag.tag), + text=self.overrides.apply_formatter(formatter=attribute_tag.tag, entry=entry), attributes={ - attr_name: self.overrides.apply_formatter(formatter=attr_formatter) + attr_name: self.overrides.apply_formatter( + formatter=attr_formatter, entry=entry + ) for attr_name, attr_formatter in attribute_tag.attributes.dict.items() }, ) @@ -126,7 +128,9 @@ class SharedNfoTagsPlugin(Plugin[SharedNfoTagsOptions], ABC): def _create_nfo(self, entry: Entry, save_to_entry: bool = True) -> None: # Write the nfo tags to XML with the nfo_root - nfo_root = self.overrides.apply_formatter(formatter=self.plugin_options.nfo_root) + nfo_root = self.overrides.apply_formatter( + formatter=self.plugin_options.nfo_root, entry=entry + ) nfo_tags = self._get_xml_element_dict(entry=entry) # If the nfo tags are empty, then stop continuing @@ -148,7 +152,9 @@ class SharedNfoTagsPlugin(Plugin[SharedNfoTagsOptions], ABC): xml = to_xml(nfo_dict=nfo_tags, nfo_root=nfo_root) - nfo_file_name = self.overrides.apply_formatter(formatter=self.plugin_options.nfo_name) + nfo_file_name = self.overrides.apply_formatter( + formatter=self.plugin_options.nfo_name, entry=entry + ) # Save the nfo's XML to file nfo_file_path = Path(self.working_directory) / nfo_file_name diff --git a/src/ytdl_sub/plugins/regex.py b/src/ytdl_sub/plugins/regex.py index 154c838d..678e50df 100644 --- a/src/ytdl_sub/plugins/regex.py +++ b/src/ytdl_sub/plugins/regex.py @@ -288,16 +288,34 @@ class RegexPlugin(Plugin[RegexOptions]): # Otherwise, error raise RegexNoMatchException(f"Regex failed to match '{variable_name}' from '{entry.title}'") - def _can_process_at_metadata_stage(self, variable_name: str) -> bool: - # Try to see if it can resolve - try: - self.overrides.apply_formatter(formatter=self.overrides.dict[variable_name]) - # If it can not from missing variables (from post-metadata stage), return False - except StringFormattingVariableNotFoundException: + def _can_process_at_metadata_stage(self, entry: Entry, variable_name: str) -> bool: + # If the variable is an override... + if variable_name in self.overrides.dict: + # Try to see if it can resolve + try: + self.overrides.apply_formatter( + formatter=self.overrides.dict[variable_name], + entry=entry, + ) + # If it can not from missing variables (from post-metadata stage), return False + except StringFormattingVariableNotFoundException: + return False + # If it is a source variable and not present, return false + elif variable_name not in entry.to_dict(): return False return True + def _get_regex_input_string(self, entry: Entry, variable_name: str) -> str: + # Apply override formatter if it's an override + if variable_name in self.overrides.dict: + return self.overrides.apply_formatter( + formatter=self.overrides.dict[variable_name], + entry=entry, + ) + # Otherwise pluck from the entry's source variable + return entry.to_dict()[variable_name] + def _modify_entry_metadata(self, entry: Entry, is_metadata_stage: bool) -> Optional[Entry]: """ Parameters @@ -330,13 +348,16 @@ class RegexPlugin(Plugin[RegexOptions]): # If it's the metadata stage, and it can't be processed, skip until post-metadata if is_metadata_stage and not self._can_process_at_metadata_stage( - variable_name=variable_name + entry=entry, variable_name=variable_name ): continue self._add_processed_regex_variable_name(entry, variable_name) - regex_input_str = self.overrides.get_str(variable_name) + regex_input_str = self._get_regex_input_string( + entry=entry, + variable_name=variable_name, + ) if ( regex_options.exclude is not None @@ -354,22 +375,49 @@ class RegexPlugin(Plugin[RegexOptions]): if not regex_options.has_defaults: return self._try_skip_entry(entry=entry, variable_name=variable_name) - # otherwise, use defaults - self.overrides.add( - { - regex_options.capture_group_names[i]: default - for i, default in enumerate(regex_options.capture_group_defaults) - } + # otherwise, use defaults (apply them using the original entry source dict) + source_variables_and_overrides_dict = dict( + entry.to_dict(), **self.overrides.dict_with_format_strings ) + # add both the default... + entry.add_variables( + variables_to_add={ + regex_options.capture_group_names[i]: default.apply_formatter( + variable_dict=source_variables_and_overrides_dict + ) + for i, default in enumerate(regex_options.capture_group_defaults) + }, + ) + # and sanitized default + entry.add_variables( + variables_to_add={ + f"{regex_options.capture_group_names[i]}_sanitized": sanitize_filename( + default.apply_formatter( + variable_dict=source_variables_and_overrides_dict + ) + ) + for i, default in enumerate(regex_options.capture_group_defaults) + }, + ) # There is a capture, add the source variables to the entry as # {source_var}_capture_1, {source_var}_capture_2, ... else: - self.overrides.add( - { + # Add the value... + entry.add_variables( + variables_to_add={ regex_options.capture_group_names[i]: capture for i, capture in enumerate(maybe_capture) - } + }, + ) + # And the sanitized value + entry.add_variables( + variables_to_add={ + f"{regex_options.capture_group_names[i]}_sanitized": sanitize_filename( + capture + ) + for i, capture in enumerate(maybe_capture) + }, ) return entry diff --git a/src/ytdl_sub/plugins/subtitles.py b/src/ytdl_sub/plugins/subtitles.py index 12064ad6..4dff539d 100644 --- a/src/ytdl_sub/plugins/subtitles.py +++ b/src/ytdl_sub/plugins/subtitles.py @@ -196,6 +196,7 @@ class SubtitlesPlugin(Plugin[SubtitleOptions]): subtitle_file_name = f"{entry.uid}.{lang}.{self.plugin_options.subtitles_type}" output_subtitle_file_name = self.overrides.apply_formatter( formatter=self.plugin_options.subtitles_name, + entry=entry, function_overrides={"lang": lang}, ) diff --git a/src/ytdl_sub/plugins/video_tags.py b/src/ytdl_sub/plugins/video_tags.py index 07306197..e27af4d8 100644 --- a/src/ytdl_sub/plugins/video_tags.py +++ b/src/ytdl_sub/plugins/video_tags.py @@ -76,7 +76,7 @@ class VideoTagsPlugin(Plugin[VideoTagsOptions]): tags_to_write: Dict[str, str] = {} for tag_name, tag_formatter in self.plugin_options.tags.dict.items(): - tag_value = self.overrides.apply_formatter(formatter=tag_formatter) + tag_value = self.overrides.apply_formatter(formatter=tag_formatter, entry=entry) tags_to_write[tag_name] = tag_value # write the actual tags if its not a dry run diff --git a/src/ytdl_sub/subscriptions/subscription_download.py b/src/ytdl_sub/subscriptions/subscription_download.py index 216d6bc5..312fb46f 100644 --- a/src/ytdl_sub/subscriptions/subscription_download.py +++ b/src/ytdl_sub/subscriptions/subscription_download.py @@ -62,7 +62,9 @@ class SubscriptionDownload(BaseSubscription, ABC): Optional. Metadata to record to the transaction log for this entry """ # Move the file after all direct file modifications are complete - output_file_name = self.overrides.apply_formatter(formatter=self.output_options.file_name) + output_file_name = self.overrides.apply_formatter( + formatter=self.output_options.file_name, entry=entry + ) self._enhanced_download_archive.save_file_to_output_directory( file_name=entry.get_download_file_name(), file_metadata=entry_metadata, @@ -73,7 +75,7 @@ class SubscriptionDownload(BaseSubscription, ABC): # Always pretend to include the thumbnail in a dry-run if self.output_options.thumbnail_name and (dry_run or entry.is_thumbnail_downloaded()): output_thumbnail_name = self.overrides.apply_formatter( - formatter=self.output_options.thumbnail_name + formatter=self.output_options.thumbnail_name, entry=entry ) # Copy the thumbnails since they could be used later for other things @@ -90,7 +92,7 @@ class SubscriptionDownload(BaseSubscription, ABC): if self.output_options.info_json_name: output_info_json_name = self.overrides.apply_formatter( - formatter=self.output_options.info_json_name + formatter=self.output_options.info_json_name, entry=entry ) # if not dry-run, write the info json diff --git a/src/ytdl_sub/utils/script.py b/src/ytdl_sub/utils/script.py index 7e9223bc..d3662c5e 100644 --- a/src/ytdl_sub/utils/script.py +++ b/src/ytdl_sub/utils/script.py @@ -36,8 +36,6 @@ class ScriptUtils: out = f"{{%float({value})}}" elif isinstance(value, bool): out = f"{{%bool({value})}}" - elif isinstance(value, dict): - out = f"{{ {json.dumps(value)} }}" else: out = json.dumps(value) diff --git a/tests/unit/entries/conftest.py b/tests/unit/entries/conftest.py index 663357eb..15371142 100644 --- a/tests/unit/entries/conftest.py +++ b/tests/unit/entries/conftest.py @@ -50,128 +50,112 @@ def download_file_name(uid, ext): @pytest.fixture -def mock_entry_to_dict(): +def mock_entry_to_dict( + uid, + title, + ext, + extractor, + upload_date, + thumbnail_ext, + webpage_url, +): return { - "channel": "abc123", - "channel_id": "abc123", - "channel_sanitized": "abc123", - "comments": "", - "comments_sanitized": "", - "creator": "abc123", - "creator_sanitized": "abc123", - "description": "", - "download_index": 1, - "download_index_padded6": "000001", - "download_index_sanitized": "1", - "entry_metadata": { - "epoch": 1596878400, - "ext": "mp5", - "extractor": "xtract", - "extractor_key": "test_extractor_key", - "id": "abc123", - "thumbnail": "abc123.jpg", - "title": "entry {title}", - "upload_date": "20210112", - "webpage_url": "https://yourname.here", - }, + "uid": uid, + "uid_sanitized": uid, "epoch": 1596878400, "epoch_date": "20200808", "epoch_hour": "09", - "ext": "mp5", - "extractor": "xtract", + "title": "entry {title}", + "title_sanitized": "entry {title}", + "ext": ext, + "description": "", + "comments": "", + "requested_subtitles": "", + "sponsorblock_chapters": "", + "creator": "abc123", + "creator_sanitized": "abc123", + "channel": "abc123", + "channel_sanitized": "abc123", + "channel_id": uid, + "extractor": extractor, "extractor_key": "test_extractor_key", + "uploader": "abc123", + "uploader_id": "abc123", + "uploader_url": "https://yourname.here", + "download_index": 1, + "download_index_padded6": "000001", + "upload_date_index": 1, + "upload_date_index_padded": "01", + "upload_date_index_reversed": 99, + "upload_date_index_reversed_padded": "99", + "upload_date": upload_date, + "upload_date_standardized": "2021-01-12", + "upload_year": 2021, + "upload_year_truncated": 21, + "upload_year_truncated_reversed": 79, + "upload_month": 1, + "upload_month_padded": "01", + "upload_month_reversed": 12, + "upload_month_reversed_padded": "12", + "upload_day": 12, + "upload_day_padded": "12", + "upload_day_reversed": 20, + "upload_day_reversed_padded": "20", + "upload_day_of_year": 12, + "upload_day_of_year_padded": "012", + "upload_day_of_year_reversed": 354, + "upload_day_of_year_reversed_padded": "354", + "thumbnail_ext": thumbnail_ext, "info_json_ext": "info.json", - "playlist_count": 1, - "playlist_description": "", + "webpage_url": webpage_url, "playlist_index": 1, "playlist_index_padded": "01", "playlist_index_padded6": "000001", "playlist_index_reversed": 1, "playlist_index_reversed_padded": "01", "playlist_index_reversed_padded6": "000001", - "playlist_metadata": {}, - "playlist_metadata_sanitized": "{}", + "playlist_count": 1, + "playlist_max_upload_year": 2021, + "playlist_max_upload_year_truncated": 21, "playlist_title": "entry {title}", "playlist_title_sanitized": "entry {title}", + "playlist_description": "", + "playlist_webpage_url": "https://yourname.here", "playlist_uid": "abc123", "playlist_uploader": "abc123", - "playlist_uploader_id": "abc123", "playlist_uploader_sanitized": "abc123", + "playlist_uploader_id": "abc123", "playlist_uploader_url": "https://yourname.here", - "playlist_webpage_url": "https://yourname.here", - "release_date": "20210112", - "release_date_standardized": "2021-01-12", - "release_day": 12, - "release_day_of_year": 12, - "release_day_of_year_padded": "012", - "release_day_of_year_reversed": 354, - "release_day_of_year_reversed_padded": "354", - "release_day_padded": "12", - "release_day_reversed": 20, - "release_day_reversed_padded": "20", - "release_month": 1, - "release_month_padded": "01", - "release_month_reversed": 12, - "release_month_reversed_padded": "12", - "release_year": 2021, - "release_year_truncated": 21, - "release_year_truncated_reversed": 79, - "requested_subtitles": "", - "requested_subtitles_sanitized": "", - "sibling_entry_metadata": [], - "sibling_entry_metadata_sanitized": "[]", "source_count": 1, "source_description": "", "source_index": 1, "source_index_padded": "01", - "source_metadata": {}, - "source_metadata_sanitized": "{}", "source_title": "entry {title}", "source_title_sanitized": "entry {title}", + "source_webpage_url": "https://yourname.here", "source_uid": "abc123", "source_uploader": "abc123", "source_uploader_id": "abc123", "source_uploader_url": "https://yourname.here", - "source_webpage_url": "https://yourname.here", - "sponsorblock_chapters": "", - "sponsorblock_chapters_sanitized": "", - "subscription_name": "test", - "subscription_name_sanitized": "test", - "thumbnail_ext": "jpg", - "title": "entry {title}", - "title_sanitized": "entry {title}", - "title_sanitized_plex": "entry {title}", - "uid": "abc123", - "uid_sanitized": "abc123", "uid_sanitized_plex": "abc123", - "upload_date": "20210112", - "upload_date_index": 1, - "upload_date_index_padded": "01", - "upload_date_index_reversed": 99, - "upload_date_index_reversed_padded": "99", - "upload_date_index_sanitized": "1", - "upload_date_standardized": "2021-01-12", - "upload_day": 12, - "upload_day_of_year": 12, - "upload_day_of_year_padded": "012", - "upload_day_of_year_reversed": 354, - "upload_day_of_year_reversed_padded": "354", - "upload_day_padded": "12", - "upload_day_reversed": 20, - "upload_day_reversed_padded": "20", - "upload_month": 1, - "upload_month_padded": "01", - "upload_month_reversed": 12, - "upload_month_reversed_padded": "12", - "upload_year": 2021, - "upload_year_truncated": 21, - "upload_year_truncated_reversed": 79, - "uploader": "abc123", - "uploader_id": "abc123", - "uploader_url": "https://yourname.here", - "webpage_url": "https://yourname.here", - "ytdl_sub_input_url": "https://yourname.here", - "ytdl_sub_input_url_sanitized": "https:⧸⧸yourname.here", + "title_sanitized_plex": "entry {title}", + "release_date": upload_date, + "release_date_standardized": "2021-01-12", + "release_year": 2021, + "release_year_truncated": 21, + "release_year_truncated_reversed": 79, + "release_month": 1, + "release_month_padded": "01", + "release_month_reversed": 12, + "release_month_reversed_padded": "12", + "release_day": 12, + "release_day_padded": "12", + "release_day_reversed": 20, + "release_day_reversed_padded": "20", + "release_day_of_year": 12, + "release_day_of_year_padded": "012", + "release_day_of_year_reversed": 354, + "release_day_of_year_reversed_padded": "354", } @@ -194,4 +178,7 @@ def mock_entry_kwargs( @pytest.fixture def mock_entry(mock_entry_kwargs): - return Entry(entry_dict=mock_entry_kwargs, working_directory=".") + return Entry(entry_dict=mock_entry_kwargs, working_directory=".").initialize_script( + override_variables={}, + unresolvable=set(), + ) diff --git a/tests/unit/entries/test_entry.py b/tests/unit/entries/test_entry.py index 02c5254b..b1311df4 100644 --- a/tests/unit/entries/test_entry.py +++ b/tests/unit/entries/test_entry.py @@ -1,39 +1,13 @@ -from typing import Callable - import pytest -from ytdl_sub.config.overrides import Overrides from ytdl_sub.entries.entry import Entry from ytdl_sub.entries.script.variable_definitions import VARIABLES as v -from ytdl_sub.entries.script.variable_scripts import ENTRY_INJECTED_VARIABLES -from ytdl_sub.entries.script.variable_scripts import ENTRY_RELATIVE_VARIABLES -from ytdl_sub.entries.script.variable_scripts import UNRESOLVED_VARIABLES - - -@pytest.fixture -def mock_overrides_factory() -> Callable[[Entry], Overrides]: - def _mock_overrides_factory(entry: Entry) -> Overrides: - overrides = Overrides(name="test", value={}) - overrides.initialize_script(unresolved_variables=set()) - - overrides.add( - { - var.variable_name: format_string - for var, format_string in ( - list(ENTRY_INJECTED_VARIABLES.items()) + list(ENTRY_RELATIVE_VARIABLES.items()) - ) - } - ) - - overrides.add_entry_kwargs(entry._kwargs) - return overrides - - return _mock_overrides_factory class TestEntry(object): - def test_entry_to_dict(self, mock_overrides_factory, mock_entry, mock_entry_to_dict): - out = mock_overrides_factory(mock_entry).to_dict() + def test_entry_to_dict(self, mock_entry, mock_entry_to_dict): + out = mock_entry.to_dict() + del out["entry_metadata"] assert out == mock_entry_to_dict def test_entry_missing_kwarg(self, mock_entry): @@ -53,7 +27,6 @@ class TestEntry(object): ) def test_entry_reverse_variables( self, - mock_overrides_factory, mock_entry_kwargs, upload_date, year_rev, @@ -62,16 +35,16 @@ class TestEntry(object): month_rev_pad, day_rev_pad, ): - mock_entry_kwargs["upload_date"] = upload_date - overrides = mock_overrides_factory( - Entry(entry_dict=mock_entry_kwargs, working_directory=".") - ) - assert overrides.get_int(v.upload_year_truncated_reversed) == year_rev - assert overrides.get_int(v.upload_month_reversed) == month_rev - assert overrides.get_int(v.upload_day_reversed) == day_rev - assert overrides.get_str(v.upload_month_reversed_padded) == month_rev_pad - assert overrides.get_str(v.upload_day_reversed_padded) == day_rev_pad + mock_entry_kwargs["upload_date"] = upload_date + entry = Entry(entry_dict=mock_entry_kwargs, working_directory=".").initialize_script( + override_variables={}, unresolvable=set() + ) + assert entry.get_int(v.upload_year_truncated_reversed) == year_rev + assert entry.get_int(v.upload_month_reversed) == month_rev + assert entry.get_int(v.upload_day_reversed) == day_rev + assert entry.get_str(v.upload_month_reversed_padded) == month_rev_pad + assert entry.get_str(v.upload_day_reversed_padded) == day_rev_pad @pytest.mark.parametrize( "upload_date, day_year, day_year_rev, day_year_pad, day_year_rev_pad", @@ -81,21 +54,14 @@ class TestEntry(object): ], ) def test_entry_upload_day_of_year_variables( - self, - mock_overrides_factory, - mock_entry_kwargs, - upload_date, - day_year, - day_year_rev, - day_year_pad, - day_year_rev_pad, + self, mock_entry_kwargs, upload_date, day_year, day_year_rev, day_year_pad, day_year_rev_pad ): mock_entry_kwargs["upload_date"] = upload_date - overrides = mock_overrides_factory( - Entry(entry_dict=mock_entry_kwargs, working_directory=".") + entry = Entry(entry_dict=mock_entry_kwargs, working_directory=".").initialize_script( + override_variables={}, unresolvable=set() ) - assert overrides.get_int(v.upload_day_of_year) == day_year - assert overrides.get_int(v.upload_day_of_year_reversed) == day_year_rev - assert overrides.get_str(v.upload_day_of_year_padded) == day_year_pad - assert overrides.get_str(v.upload_day_of_year_reversed_padded) == day_year_rev_pad + assert entry.get_int(v.upload_day_of_year) == day_year + assert entry.get_int(v.upload_day_of_year_reversed) == day_year_rev + assert entry.get_str(v.upload_day_of_year_padded) == day_year_pad + assert entry.get_str(v.upload_day_of_year_reversed_padded) == day_year_rev_pad