From 8cd6ed12e0a3c86d5f8c2b7f4e2cca7df176715e Mon Sep 17 00:00:00 2001 From: Jesse Bannon Date: Sun, 10 Dec 2023 08:07:46 -0800 Subject: [PATCH] no more entry script, maybe bad idea??? --- 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 | 185 ++++++++++-------- tests/unit/entries/test_entry.py | 70 +++++-- 16 files changed, 289 insertions(+), 296 deletions(-) diff --git a/src/ytdl_sub/config/overrides.py b/src/ytdl_sub/config/overrides.py index ad1e9544..7fb2533f 100644 --- a/src/ytdl_sub/config/overrides.py +++ b/src/ytdl_sub/config/overrides.py @@ -1,22 +1,29 @@ 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.scriptable import Scriptable +from ytdl_sub.utils.script import ScriptUtils 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, Scriptable): + +class Overrides(DictFormatterValidator): """ 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 @@ -62,8 +69,7 @@ class Overrides(DictFormatterValidator, Scriptable): ) def __init__(self, name, value): - DictFormatterValidator.__init__(self, name, value) - Scriptable.__init__(self) + super().__init__(name, value) # Add sanitized overrides for key in self._keys: @@ -81,14 +87,53 @@ class Overrides(DictFormatterValidator, Scriptable): sanitize=sanitized, ) - 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())) + self.script = Script(copy.deepcopy(VARIABLE_SCRIPTS)) + self.unresolvable: Set[str] = copy.deepcopy(UNRESOLVED_VARIABLES) + 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: """ @@ -98,10 +143,18 @@ class Overrides(DictFormatterValidator, Scriptable): """ 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: """ @@ -118,17 +171,15 @@ class Overrides(DictFormatterValidator, Scriptable): ------- 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( - script.resolve_once( + self.script.resolve_once( dict({"tmp_var": formatter.format_string}, **(function_overrides or {})), - unresolvable=unresolvable, + unresolvable=self.unresolvable.union( + VARIABLES.entry_metadata.variable_name + if isinstance(formatter, OverridesStringFormatterValidator) + else set() + ), )["tmp_var"] ) ) diff --git a/src/ytdl_sub/config/preset.py b/src/ytdl_sub/config/preset.py index 7e70b74e..5a9d639f 100644 --- a/src/ytdl_sub/config/preset.py +++ b/src/ytdl_sub/config/preset.py @@ -361,12 +361,7 @@ class Preset(_PresetShell): # values from multiple validators self.__recursive_preset_validate() - 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 - } - ) + self.overrides.initialize_script(unresolved_variables=set(list(self._added_variables.keys()))) @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 756d34d3..443e09c4 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] - 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 - } + 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 + }, + ) ) entries.append(entry) diff --git a/src/ytdl_sub/downloaders/url/downloader.py b/src/ytdl_sub/downloaders/url/downloader.py index 36c2a240..2df58059 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, entry=entry) + thumbnail_name = self.overrides.apply_formatter(thumbnail_info.name) 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 = entry.get_str(v.ytdl_sub_input_url) + entry_collection_url = self.overrides.get_str(v.ytdl_sub_input_url) # If the collection URL cannot find its mapping, use the last URL collection_url = ( @@ -459,15 +459,16 @@ class MultiUrlDownloader(SourcePlugin[MultiUrlValidator]): for entry in self._iterate_entries( url_validator=collection_url, parents=parents, orphans=orphan_entries ): - 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 - ) - } + + self.overrides.add( + dict( + entry._kwargs, + **{ + 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 e7e5c13f..bc9ba51b 100644 --- a/src/ytdl_sub/entries/entry.py +++ b/src/ytdl_sub/entries/entry.py @@ -2,17 +2,13 @@ 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 @@ -21,49 +17,11 @@ YTDL_SUB_ENTRY_VARIABLES_KWARG_KEY: str = "ytdl_sub_entry_variables" TType = TypeVar("TType") -class Entry(BaseEntry, Scriptable): +class Entry(BaseEntry): """ 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: """ @@ -71,7 +29,7 @@ class Entry(BaseEntry, Scriptable): 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.get_str(VARIABLES.ext) + ext = self.kwargs(VARIABLES.ext.metadata_key) for possible_ext in [ext, "mkv"]: file_path = str(Path(self.working_directory()) / f"{self.uid}.{possible_ext}") if os.path.isfile(file_path): @@ -97,7 +55,7 @@ class Entry(BaseEntry, Scriptable): ------- The download thumbnail's file name """ - return f"{self.get_str(VARIABLES.uid)}.{self.get_str(VARIABLES.thumbnail_ext)}" + return f"{self.kwargs(VARIABLES.uid.metadata_key)}.jpg" def get_download_thumbnail_path(self) -> str: """Returns the entry's thumbnail's file path to where it was downloaded""" @@ -121,12 +79,12 @@ class Entry(BaseEntry, Scriptable): return None - def write_info_json(self) -> None: + def write_info_json(self, overrides: Overrides) -> 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"] = self.to_dict() + kwargs_dict["ytdl_sub_entry_variables"] = overrides.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: @@ -168,12 +126,3 @@ class Entry(BaseEntry, Scriptable): 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 bb126a1f..192620d9 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,6 +279,11 @@ 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 940452eb..38292601 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 = copy.deepcopy(entry.to_dict()) + source_var_dict = self.overrides.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 e7bb92e4..a8fb065b 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, entry=entry) + tag_value = self.overrides.apply_formatter(formatter=tag_formatter) 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 2ff09d55..19ded393 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, entry=entry), + text=self.overrides.apply_formatter(formatter=string_tag), attributes={}, ) for string_tag in string_tags @@ -110,11 +110,9 @@ 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, entry=entry), + text=self.overrides.apply_formatter(formatter=attribute_tag.tag), attributes={ - attr_name: self.overrides.apply_formatter( - formatter=attr_formatter, entry=entry - ) + attr_name: self.overrides.apply_formatter(formatter=attr_formatter) for attr_name, attr_formatter in attribute_tag.attributes.dict.items() }, ) @@ -128,9 +126,7 @@ 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, entry=entry - ) + nfo_root = self.overrides.apply_formatter(formatter=self.plugin_options.nfo_root) nfo_tags = self._get_xml_element_dict(entry=entry) # If the nfo tags are empty, then stop continuing @@ -152,9 +148,7 @@ 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, entry=entry - ) + nfo_file_name = self.overrides.apply_formatter(formatter=self.plugin_options.nfo_name) # 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 678e50df..154c838d 100644 --- a/src/ytdl_sub/plugins/regex.py +++ b/src/ytdl_sub/plugins/regex.py @@ -288,34 +288,16 @@ 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, 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(): + 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: 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 @@ -348,16 +330,13 @@ 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( - entry=entry, variable_name=variable_name + variable_name=variable_name ): continue self._add_processed_regex_variable_name(entry, variable_name) - regex_input_str = self._get_regex_input_string( - entry=entry, - variable_name=variable_name, - ) + regex_input_str = self.overrides.get_str(variable_name) if ( regex_options.exclude is not None @@ -375,49 +354,22 @@ class RegexPlugin(Plugin[RegexOptions]): if not regex_options.has_defaults: return self._try_skip_entry(entry=entry, variable_name=variable_name) - # 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 + # otherwise, use defaults + self.overrides.add( + { + regex_options.capture_group_names[i]: default + for i, default in enumerate(regex_options.capture_group_defaults) + } ) - # 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: - # Add the value... - entry.add_variables( - variables_to_add={ + self.overrides.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 4dff539d..12064ad6 100644 --- a/src/ytdl_sub/plugins/subtitles.py +++ b/src/ytdl_sub/plugins/subtitles.py @@ -196,7 +196,6 @@ 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 e27af4d8..07306197 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, entry=entry) + tag_value = self.overrides.apply_formatter(formatter=tag_formatter) 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 312fb46f..216d6bc5 100644 --- a/src/ytdl_sub/subscriptions/subscription_download.py +++ b/src/ytdl_sub/subscriptions/subscription_download.py @@ -62,9 +62,7 @@ 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, entry=entry - ) + output_file_name = self.overrides.apply_formatter(formatter=self.output_options.file_name) self._enhanced_download_archive.save_file_to_output_directory( file_name=entry.get_download_file_name(), file_metadata=entry_metadata, @@ -75,7 +73,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, entry=entry + formatter=self.output_options.thumbnail_name ) # Copy the thumbnails since they could be used later for other things @@ -92,7 +90,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, entry=entry + formatter=self.output_options.info_json_name ) # 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 d3662c5e..7e9223bc 100644 --- a/src/ytdl_sub/utils/script.py +++ b/src/ytdl_sub/utils/script.py @@ -36,6 +36,8 @@ 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 15371142..663357eb 100644 --- a/tests/unit/entries/conftest.py +++ b/tests/unit/entries/conftest.py @@ -50,112 +50,128 @@ def download_file_name(uid, ext): @pytest.fixture -def mock_entry_to_dict( - uid, - title, - ext, - extractor, - upload_date, - thumbnail_ext, - webpage_url, -): +def mock_entry_to_dict(): return { - "uid": uid, - "uid_sanitized": uid, + "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", + }, "epoch": 1596878400, "epoch_date": "20200808", "epoch_hour": "09", - "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, + "ext": "mp5", + "extractor": "xtract", "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", - "webpage_url": webpage_url, + "playlist_count": 1, + "playlist_description": "", "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_count": 1, - "playlist_max_upload_year": 2021, - "playlist_max_upload_year_truncated": 21, + "playlist_metadata": {}, + "playlist_metadata_sanitized": "{}", "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_sanitized": "abc123", "playlist_uploader_id": "abc123", + "playlist_uploader_sanitized": "abc123", "playlist_uploader_url": "https://yourname.here", - "source_count": 1, - "source_description": "", - "source_index": 1, - "source_index_padded": "01", - "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", - "uid_sanitized_plex": "abc123", - "title_sanitized_plex": "entry {title}", - "release_date": upload_date, + "playlist_webpage_url": "https://yourname.here", + "release_date": "20210112", "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", + "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_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", } @@ -178,7 +194,4 @@ def mock_entry_kwargs( @pytest.fixture def mock_entry(mock_entry_kwargs): - return Entry(entry_dict=mock_entry_kwargs, working_directory=".").initialize_script( - override_variables={}, - unresolvable=set(), - ) + return Entry(entry_dict=mock_entry_kwargs, working_directory=".") diff --git a/tests/unit/entries/test_entry.py b/tests/unit/entries/test_entry.py index b1311df4..02c5254b 100644 --- a/tests/unit/entries/test_entry.py +++ b/tests/unit/entries/test_entry.py @@ -1,13 +1,39 @@ +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_entry, mock_entry_to_dict): - out = mock_entry.to_dict() - del out["entry_metadata"] + def test_entry_to_dict(self, mock_overrides_factory, mock_entry, mock_entry_to_dict): + out = mock_overrides_factory(mock_entry).to_dict() assert out == mock_entry_to_dict def test_entry_missing_kwarg(self, mock_entry): @@ -27,6 +53,7 @@ class TestEntry(object): ) def test_entry_reverse_variables( self, + mock_overrides_factory, mock_entry_kwargs, upload_date, year_rev, @@ -35,16 +62,16 @@ class TestEntry(object): month_rev_pad, 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() + overrides = mock_overrides_factory( + Entry(entry_dict=mock_entry_kwargs, working_directory=".") ) - 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 + + 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 @pytest.mark.parametrize( "upload_date, day_year, day_year_rev, day_year_pad, day_year_rev_pad", @@ -54,14 +81,21 @@ class TestEntry(object): ], ) def test_entry_upload_day_of_year_variables( - self, mock_entry_kwargs, upload_date, day_year, day_year_rev, day_year_pad, day_year_rev_pad + self, + mock_overrides_factory, + mock_entry_kwargs, + upload_date, + day_year, + day_year_rev, + day_year_pad, + day_year_rev_pad, ): mock_entry_kwargs["upload_date"] = upload_date - entry = Entry(entry_dict=mock_entry_kwargs, working_directory=".").initialize_script( - override_variables={}, unresolvable=set() + overrides = mock_overrides_factory( + Entry(entry_dict=mock_entry_kwargs, working_directory=".") ) - 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 + 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