From 64f6a412c032783da7ba07efdf726941f3344b25 Mon Sep 17 00:00:00 2001 From: Jesse Bannon Date: Fri, 29 Dec 2023 12:53:05 -0800 Subject: [PATCH] maybe ready? --- .../config/validators/variable_validation.py | 2 +- .../info_json/info_json_downloader.py | 11 +- src/ytdl_sub/entries/entry.py | 12 +- src/ytdl_sub/entries/entry_parent.py | 9 +- .../entries/script/custom_functions.py | 4 +- .../entries/script/variable_definitions.py | 747 ++++++++++++------ .../entries/script/variable_scripts.py | 296 ------- src/ytdl_sub/entries/script/variable_types.py | 269 +++++++ .../entries/variables/override_variables.py | 2 +- src/ytdl_sub/utils/scriptable.py | 6 +- 10 files changed, 789 insertions(+), 569 deletions(-) delete mode 100644 src/ytdl_sub/entries/script/variable_scripts.py create mode 100644 src/ytdl_sub/entries/script/variable_types.py diff --git a/src/ytdl_sub/config/validators/variable_validation.py b/src/ytdl_sub/config/validators/variable_validation.py index d52489a7..af8a777f 100644 --- a/src/ytdl_sub/config/validators/variable_validation.py +++ b/src/ytdl_sub/config/validators/variable_validation.py @@ -13,7 +13,7 @@ from ytdl_sub.config.plugin.preset_plugins import PresetPlugins from ytdl_sub.config.preset_options import OutputOptions from ytdl_sub.config.validators.options import OptionsValidator from ytdl_sub.downloaders.url.validators import MultiUrlValidator -from ytdl_sub.entries.script.variable_scripts import VARIABLE_SCRIPTS +from ytdl_sub.entries.script.variable_definitions import VARIABLE_SCRIPTS from ytdl_sub.script.script import Script from ytdl_sub.validators.string_formatter_validators import validate_formatters 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 f1b29aa4..8049cc49 100644 --- a/src/ytdl_sub/downloaders/info_json/info_json_downloader.py +++ b/src/ytdl_sub/downloaders/info_json/info_json_downloader.py @@ -11,10 +11,9 @@ from ytdl_sub.config.validators.options import OptionsDictValidator from ytdl_sub.downloaders.source_plugin import SourcePlugin from ytdl_sub.downloaders.ytdl_options_builder import YTDLOptionsBuilder from ytdl_sub.entries.entry import Entry +from ytdl_sub.entries.script.variable_definitions import VARIABLE_SCRIPTS from ytdl_sub.entries.script.variable_definitions import VARIABLES from ytdl_sub.entries.script.variable_definitions import VariableDefinitions -from ytdl_sub.entries.script.variable_scripts import DOWNLOADER_INJECTED_VARIABLES -from ytdl_sub.entries.script.variable_scripts import VARIABLE_SCRIPTS from ytdl_sub.utils.exceptions import ValidationException from ytdl_sub.utils.file_handler import FileHandler from ytdl_sub.utils.file_handler import get_file_extension @@ -110,11 +109,11 @@ class InfoJsonDownloader(SourcePlugin[InfoJsonDownloaderOptions]): entry.initialize_script(self.overrides).add( { - inj: prior_variables.get( - inj.variable_name, - VARIABLE_SCRIPTS[inj.variable_name], + inj_variable_name: prior_variables.get( + inj_variable_name, + VARIABLE_SCRIPTS[inj_variable_name], ) - for inj in DOWNLOADER_INJECTED_VARIABLES + for inj_variable_name in v.injected_variables() } ) entries.append(entry) diff --git a/src/ytdl_sub/entries/entry.py b/src/ytdl_sub/entries/entry.py index b996c20b..c7442992 100644 --- a/src/ytdl_sub/entries/entry.py +++ b/src/ytdl_sub/entries/entry.py @@ -12,8 +12,10 @@ from typing import final 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.entries.script.variable_definitions import VariableDefinitions +from ytdl_sub.entries.script.variable_types import ArrayVariable +from ytdl_sub.entries.script.variable_types import StringVariable +from ytdl_sub.entries.script.variable_types import Variable from ytdl_sub.script.utils.exceptions import ScriptVariableNotResolved from ytdl_sub.utils.script import ScriptUtils from ytdl_sub.utils.scriptable import Scriptable @@ -23,8 +25,12 @@ from ytdl_sub.validators.audo_codec_validator import VIDEO_CODEC_EXTS v: VariableDefinitions = VARIABLES _YTDL_SUB_ENTRY_VARIABLES_KWARG_KEY: str = "ytdl_sub_entry_variables" -ytdl_sub_chapters_from_comments = Variable("ytdl_sub_chapters_from_comments") -ytdl_sub_split_by_chapters_parent_uid = Variable("ytdl_sub_split_by_chapters_parent_uid") +ytdl_sub_chapters_from_comments = ArrayVariable( + "ytdl_sub_chapters_from_comments", definition="{ [] }" +) +ytdl_sub_split_by_chapters_parent_uid = StringVariable( + "ytdl_sub_split_by_chapters_parent_uid", definition="{ %string('') }" +) TypeT = TypeVar("TypeT") diff --git a/src/ytdl_sub/entries/entry_parent.py b/src/ytdl_sub/entries/entry_parent.py index 21ff7d18..6cf7dcb1 100644 --- a/src/ytdl_sub/entries/entry_parent.py +++ b/src/ytdl_sub/entries/entry_parent.py @@ -3,15 +3,14 @@ from typing import Any from typing import Dict from typing import List from typing import Optional +from typing import Set from ytdl_sub.entries.base_entry import BaseEntry from ytdl_sub.entries.base_entry import TBaseEntry from ytdl_sub.entries.entry import Entry from ytdl_sub.entries.script.variable_definitions import VARIABLES -from ytdl_sub.entries.script.variable_definitions import MetadataVariable from ytdl_sub.entries.script.variable_definitions import VariableDefinitions -from ytdl_sub.entries.script.variable_scripts import ENTRY_DEFAULT_VARIABLES -from ytdl_sub.entries.script.variable_scripts import ENTRY_REQUIRED_VARIABLES +from ytdl_sub.entries.script.variable_types import MetadataVariable v: VariableDefinitions = VARIABLES @@ -53,8 +52,8 @@ class EntryParent(BaseEntry): def _sibling_entry_metadata(self) -> List[Dict[str, Any]]: sibling_entry_metadata: List[Dict[str, Any]] = [] - variable_filter: List[MetadataVariable] = list(ENTRY_REQUIRED_VARIABLES.keys()) + list( - ENTRY_DEFAULT_VARIABLES.keys() + variable_filter: Set[MetadataVariable] = ( + v.required_entry_variables() | v.default_entry_variables() ) for entry in self.entry_children(): sibling_entry_metadata.append( diff --git a/src/ytdl_sub/entries/script/custom_functions.py b/src/ytdl_sub/entries/script/custom_functions.py index a7440b9d..690095de 100644 --- a/src/ytdl_sub/entries/script/custom_functions.py +++ b/src/ytdl_sub/entries/script/custom_functions.py @@ -101,7 +101,7 @@ class CustomFunctions: - year (Integer) - month (Integer) - day (Integer) - - year_truncated (String, YY from YY[YY]) + - year_truncated (Integer, YY from YY[YY]) - month_padded (String) - day_padded (String) - year_truncated_reversed (Integer, 100 - year_truncated) @@ -149,7 +149,7 @@ class CustomFunctions: String("year"): Integer(year), String("month"): Integer(month), String("day"): Integer(day), - String("year_truncated"): String(year_truncated), + String("year_truncated"): Integer(year_truncated), String("month_padded"): String(month_padded), String("day_padded"): String(day_padded), String("year_truncated_reversed"): Integer(100 - year_truncated), diff --git a/src/ytdl_sub/entries/script/variable_definitions.py b/src/ytdl_sub/entries/script/variable_definitions.py index e711a214..a4d02e82 100644 --- a/src/ytdl_sub/entries/script/variable_definitions.py +++ b/src/ytdl_sub/entries/script/variable_definitions.py @@ -1,770 +1,954 @@ from abc import ABC -from dataclasses import dataclass +from typing import Dict +from typing import Set + +from ytdl_sub.entries.script.variable_types import ArrayMetadataVariable +from ytdl_sub.entries.script.variable_types import IntegerMetadataVariable +from ytdl_sub.entries.script.variable_types import IntegerVariable +from ytdl_sub.entries.script.variable_types import MapMetadataVariable +from ytdl_sub.entries.script.variable_types import MapVariable +from ytdl_sub.entries.script.variable_types import MetadataVariable +from ytdl_sub.entries.script.variable_types import StringDateVariable +from ytdl_sub.entries.script.variable_types import StringMetadataVariable +from ytdl_sub.entries.script.variable_types import StringVariable # This file contains mixins to a BaseEntry subclass. Ignore pylint's "no kwargs member" suggestion # pylint: disable=no-member # pylint: disable=too-many-public-methods -@dataclass(frozen=True) -class Variable: - variable_name: str - - -@dataclass(frozen=True) -class InternalVariable(Variable): - pass - - -@dataclass(frozen=True) -class Metadata(Variable): - pass - - -@dataclass(frozen=True) -class MetadataVariable(Variable): - metadata_key: str - - -@dataclass(frozen=True) -class RelativeMetadata(MetadataVariable, Metadata): - pass - - -@dataclass(frozen=True) -class SiblingMetadata(MetadataVariable): - pass - - class MetadataVariableDefinitions(ABC): @property - def entry_metadata(self: "VariableDefinitions") -> Metadata: + def entry_metadata(self: "VariableDefinitions") -> MapVariable: """ The entry's info.json """ - return Metadata("entry_metadata") + return MapVariable(variable_name="entry_metadata", definition="{ {} }") @property - def playlist_metadata(self: "VariableDefinitions") -> RelativeMetadata: + def playlist_metadata(self: "VariableDefinitions") -> MapMetadataVariable: """ Metadata from the playlist (i.e. the parent metadata, like playlist -> entry) """ - return RelativeMetadata("playlist_metadata", metadata_key="playlist_metadata") + return MapMetadataVariable.from_entry( + metadata_key="playlist_metadata", + default={}, + ) @property - def source_metadata(self: "VariableDefinitions") -> RelativeMetadata: + def source_metadata(self: "VariableDefinitions") -> MapMetadataVariable: """ Metadata from the source (i.e. the grandparent metadata, like channel -> playlist -> entry) """ - return RelativeMetadata("source_metadata", metadata_key="source_metadata") + return MapMetadataVariable.from_entry( + metadata_key="source_metadata", + default={}, + ) @property - def sibling_metadata(self: "VariableDefinitions") -> SiblingMetadata: + def sibling_metadata(self: "VariableDefinitions") -> ArrayMetadataVariable: """ Metadata from any sibling entries that reside in the same playlist as this entry. """ - return SiblingMetadata("sibling_metadata", metadata_key="sibling_metadata") + return ArrayMetadataVariable.from_entry( + metadata_key="sibling_metadata", + default=[], + ) class PlaylistVariableDefinitions(ABC): @property - def playlist_uid(self: "VariableDefinitions") -> MetadataVariable: + def playlist_uid(self: "VariableDefinitions") -> StringMetadataVariable: """ The playlist unique ID if it exists, otherwise return the entry unique ID. """ - return MetadataVariable(variable_name="playlist_uid", metadata_key="playlist_id") + return StringMetadataVariable.from_playlist(metadata_key="playlist_id", default=self.uid) @property - def playlist_title(self: "VariableDefinitions") -> MetadataVariable: + def playlist_title(self: "VariableDefinitions") -> StringMetadataVariable: """ Name of its parent playlist/channel if it exists, otherwise returns its title. """ - return MetadataVariable(variable_name="playlist_title", metadata_key="playlist_title") + return StringMetadataVariable.from_playlist( + metadata_key="playlist_title", default=self.title + ) @property - def playlist_index(self: "VariableDefinitions") -> MetadataVariable: + def playlist_index(self: "VariableDefinitions") -> IntegerMetadataVariable: """ Playlist index if it exists, otherwise returns ``1``. Note that for channels/playlists, any change (i.e. adding or removing a video) will make this value change. Use with caution. """ - return MetadataVariable(metadata_key="playlist_index", variable_name="playlist_index") + return IntegerMetadataVariable.from_entry(metadata_key="playlist_index", default=1) @property - def playlist_index_reversed(self: "VariableDefinitions") -> Variable: + def playlist_index_reversed(self: "VariableDefinitions") -> IntegerVariable: """ Playlist index reversed via ``playlist_count - playlist_index + 1`` """ - return Variable("playlist_index_reversed") + return IntegerVariable( + variable_name="playlist_index_reversed", + definition=f"""{{ + %sub( + {self.playlist_count.variable_name}, + {self.playlist_index.variable_name}, + -1 + ) + }}""", + ) @property - def playlist_index_padded(self: "VariableDefinitions") -> Variable: + def playlist_index_padded(self: "VariableDefinitions") -> StringVariable: """ playlist_index padded two digits """ - return Variable("playlist_index_padded") + return self.playlist_index.to_padded_int(variable_name="playlist_index_padded", pad=2) @property - def playlist_index_reversed_padded(self: "VariableDefinitions") -> Variable: + def playlist_index_reversed_padded(self: "VariableDefinitions") -> StringVariable: """ playlist_index_reversed padded two digits """ - return Variable("playlist_index_reversed_padded") + return self.playlist_index_reversed.to_padded_int( + variable_name="playlist_index_reversed_padded", + pad=2, + ) @property - def playlist_index_padded6(self: "VariableDefinitions") -> Variable: + def playlist_index_padded6(self: "VariableDefinitions") -> StringVariable: """ playlist_index padded six digits. """ - return Variable("playlist_index_padded6") + return self.playlist_index.to_padded_int(variable_name="playlist_index_padded6", pad=6) @property - def playlist_index_reversed_padded6(self: "VariableDefinitions") -> Variable: + def playlist_index_reversed_padded6(self: "VariableDefinitions") -> StringVariable: """ playlist_index_reversed padded six digits. """ - return Variable("playlist_index_reversed_padded6") + return self.playlist_index_reversed.to_padded_int( + variable_name="playlist_index_reversed_padded6", pad=6 + ) @property - def playlist_count(self: "VariableDefinitions") -> MetadataVariable: + def playlist_count(self: "VariableDefinitions") -> IntegerMetadataVariable: """ Playlist count if it exists, otherwise returns ``1``. Note that for channels/playlists, any change (i.e. adding or removing a video) will make this value change. Use with caution. """ - return MetadataVariable(variable_name="playlist_count", metadata_key="playlist_count") + return IntegerMetadataVariable.from_entry(metadata_key="playlist_count", default=1) @property - def playlist_description(self: "VariableDefinitions") -> MetadataVariable: + def playlist_description(self: "VariableDefinitions") -> StringMetadataVariable: """ The playlist description if it exists, otherwise returns the entry's description. """ - return MetadataVariable( - variable_name="playlist_description", metadata_key=self.description.metadata_key + return StringMetadataVariable.from_playlist( + metadata_key=self.description.metadata_key, + variable_name="playlist_description", + default=self.description, ) @property - def playlist_webpage_url(self: "VariableDefinitions") -> MetadataVariable: + def playlist_webpage_url(self: "VariableDefinitions") -> StringMetadataVariable: """ The playlist webpage url if it exists. Otherwise, returns the entry webpage url. """ - return MetadataVariable( - variable_name="playlist_webpage_url", metadata_key=self.webpage_url.metadata_key + return StringMetadataVariable.from_playlist( + metadata_key=self.webpage_url.metadata_key, + variable_name="playlist_webpage_url", + default=self.webpage_url, ) @property - def playlist_max_upload_date(self: "VariableDefinitions") -> Variable: + def playlist_max_upload_date(self: "VariableDefinitions") -> StringDateVariable: """ Max upload_date for all entries in this entry's playlist if it exists, otherwise returns ``upload_date`` """ - return Variable("playlist_max_upload_date") + return StringVariable( + variable_name="playlist_max_upload_date", + definition=f"""{{ + %array_reduce( + %if_passthrough( + %extract_field_from_siblings('{self.upload_date.variable_name}'), + [{self.upload_date.variable_name}] + ), + %max + ) + }}""", + ).as_date_variable() @property - def playlist_max_upload_year(self: "VariableDefinitions") -> Variable: + def playlist_max_upload_year(self: "VariableDefinitions") -> IntegerVariable: """ Max upload_year for all entries in this entry's playlist if it exists, otherwise returns ``upload_year`` """ - # override in EntryParent - return Variable("playlist_max_upload_year") + return self.playlist_max_upload_date.get_integer_date_metadata( + date_metadata_key="year", + variable_name="playlist_max_upload_year", + ) @property - def playlist_max_upload_year_truncated(self: "VariableDefinitions") -> Variable: + def playlist_max_upload_year_truncated(self: "VariableDefinitions") -> IntegerVariable: """ The max playlist truncated upload year for all entries in this entry's playlist if it exists, otherwise returns ``upload_year_truncated``. """ - return Variable("playlist_max_upload_year_truncated") + return self.playlist_max_upload_date.get_integer_date_metadata( + date_metadata_key="year_truncated", variable_name="playlist_max_upload_year_truncated" + ) @property - def playlist_uploader_id(self: "VariableDefinitions") -> MetadataVariable: + def playlist_uploader_id(self: "VariableDefinitions") -> StringMetadataVariable: """ The playlist uploader id if it exists, otherwise returns the entry uploader ID. """ - return MetadataVariable("playlist_uploader_id", metadata_key="playlist_uploader_id") + return StringMetadataVariable.from_entry(metadata_key="playlist_uploader_id") @property - def playlist_uploader(self: "VariableDefinitions") -> MetadataVariable: + def playlist_uploader(self: "VariableDefinitions") -> StringMetadataVariable: """ The playlist uploader if it exists, otherwise return the entry uploader. """ - return MetadataVariable("playlist_uploader", metadata_key=self.uploader.metadata_key) + return StringMetadataVariable.from_playlist( + metadata_key=self.uploader.metadata_key, + variable_name="playlist_uploader", + default=self.uploader, + ) @property - def playlist_uploader_url(self: "VariableDefinitions") -> MetadataVariable: + def playlist_uploader_url(self: "VariableDefinitions") -> StringMetadataVariable: """ The playlist uploader url if it exists, otherwise returns the playlist webpage_url. """ - return MetadataVariable( - "playlist_uploader_url", metadata_key=self.uploader_url.metadata_key + return StringMetadataVariable.from_playlist( + metadata_key=self.uploader_url.metadata_key, + variable_name="playlist_uploader_url", + default=self.webpage_url, ) class SourceVariableDefinitions(ABC): @property - def source_title(self: "VariableDefinitions") -> MetadataVariable: + def source_title(self: "VariableDefinitions") -> StringMetadataVariable: """ Name of the source (i.e. channel with multiple playlists) if it exists, otherwise returns its playlist_title. """ - return MetadataVariable("source_title", metadata_key=self.title.metadata_key) + return StringMetadataVariable.from_source( + metadata_key=self.title.metadata_key, + variable_name="source_title", + default=self.playlist_title, + ) @property - def source_uid(self: "VariableDefinitions") -> MetadataVariable: + def source_uid(self: "VariableDefinitions") -> StringMetadataVariable: """ The source unique id if it exists, otherwise returns the playlist unique ID. """ - return MetadataVariable("source_uid", metadata_key=self.uid.metadata_key) + return StringMetadataVariable.from_source( + metadata_key=self.uid.metadata_key, + variable_name="source_uid", + default=self.playlist_uid, + ) @property - def source_index(self: "VariableDefinitions") -> MetadataVariable: + def source_index(self: "VariableDefinitions") -> IntegerMetadataVariable: """ Source index if it exists, otherwise returns ``1``. It is recommended to not use this unless you know the source will never add new content (it is easy for this value to change). """ - return MetadataVariable("source_index", metadata_key=self.playlist_index.metadata_key) + return IntegerMetadataVariable.from_playlist( + metadata_key=self.playlist_index.metadata_key, + variable_name="source_index", + default=1, + ) @property - def source_index_padded(self: "VariableDefinitions") -> Variable: + def source_index_padded(self: "VariableDefinitions") -> StringVariable: """ - The source index, padded. + The source index, padded two digits. """ - return Variable("source_index_padded") + return self.source_index.to_padded_int(variable_name="source_index_padded", pad=2) @property - def source_count(self: "VariableDefinitions") -> MetadataVariable: + def source_count(self: "VariableDefinitions") -> IntegerMetadataVariable: """ - The source count if it exists, otherwise returns the playlist count. + The source count if it exists, otherwise returns ``1``. """ - return MetadataVariable("source_count", metadata_key=self.playlist_count.metadata_key) + return IntegerMetadataVariable.from_playlist( + metadata_key=self.playlist_count.metadata_key, + variable_name="source_count", + default=1, + ) @property - def source_webpage_url(self: "VariableDefinitions") -> MetadataVariable: + def source_webpage_url(self: "VariableDefinitions") -> StringMetadataVariable: """ The source webpage url if it exists, otherwise returns the playlist webpage url. """ - return MetadataVariable("source_webpage_url", metadata_key=self.webpage_url.metadata_key) + return StringMetadataVariable.from_source( + metadata_key=self.webpage_url.metadata_key, variable_name="source_webpage_url" + ) @property - def source_description(self: "VariableDefinitions") -> MetadataVariable: + def source_description(self: "VariableDefinitions") -> StringMetadataVariable: """ The source description if it exists, otherwise returns the playlist description. """ - return MetadataVariable("source_description", metadata_key=self.description.metadata_key) + return StringMetadataVariable.from_source( + metadata_key=self.description.metadata_key, + variable_name="source_description", + default=self.playlist_description, + ) @property - def source_uploader_id(self: "VariableDefinitions") -> MetadataVariable: + def source_uploader_id(self: "VariableDefinitions") -> StringMetadataVariable: """ The source uploader id if it exists, otherwise returns the playlist_uploader_id """ - return MetadataVariable("source_uploader_id", metadata_key=self.uploader_id.metadata_key) + return StringMetadataVariable.from_source( + metadata_key=self.uploader_id.metadata_key, + variable_name="source_uploader_id", + default=self.playlist_uploader_id, + ) @property - def source_uploader(self: "VariableDefinitions") -> MetadataVariable: + def source_uploader(self: "VariableDefinitions") -> StringMetadataVariable: """ The source uploader if it exists, otherwise return the playlist_uploader """ - return MetadataVariable("source_uploader", metadata_key=self.uploader.metadata_key) + return StringMetadataVariable.from_source( + metadata_key=self.uploader.metadata_key, + variable_name="source_uploader", + default=self.playlist_uploader, + ) @property - def source_uploader_url(self: "VariableDefinitions") -> MetadataVariable: + def source_uploader_url(self: "VariableDefinitions") -> StringMetadataVariable: """ The source uploader url if it exists, otherwise returns the source webpage_url. """ - return MetadataVariable("source_uploader_url", metadata_key=self.uploader_url.metadata_key) + return StringMetadataVariable.from_source( + metadata_key=self.uploader_url.metadata_key, + variable_name="source_uploader_url", + default=self.source_webpage_url, + ) class UploadDateVariableDefinitions(ABC): @property - def upload_date(self: "VariableDefinitions") -> MetadataVariable: + def upload_date(self: "VariableDefinitions") -> StringDateVariable: """ The entry’s uploaded date, in YYYYMMDD format. If not present, return today’s date. """ - return MetadataVariable(variable_name="upload_date", metadata_key="upload_date") + return StringMetadataVariable.from_entry(metadata_key="upload_date").as_date_variable() @property - def upload_year(self: "VariableDefinitions") -> Variable: + def upload_year(self: "VariableDefinitions") -> IntegerVariable: """ The entry's upload year """ - return Variable("upload_year") + return self.upload_date.get_integer_date_metadata( + date_metadata_key="year", variable_name="upload_year" + ) @property - def upload_year_truncated(self: "VariableDefinitions") -> Variable: + def upload_year_truncated(self: "VariableDefinitions") -> IntegerVariable: """ The last two digits of the upload year, i.e. 22 in 2022 """ - return Variable("upload_year_truncated") + return self.upload_date.get_integer_date_metadata( + date_metadata_key="year_truncated", variable_name="upload_year_truncated" + ) @property - def upload_year_truncated_reversed(self: "VariableDefinitions") -> Variable: + def upload_year_truncated_reversed(self: "VariableDefinitions") -> IntegerVariable: """ The upload year truncated, but reversed using ``100 - {upload_year_truncated}``, i.e. 2022 returns ``100 - 22`` = ``78`` """ - return Variable("upload_year_truncated_reversed") + return self.upload_date.get_integer_date_metadata( + date_metadata_key="year_truncated_reversed", + variable_name="upload_year_truncated_reversed", + ) @property - def upload_month_reversed(self: "VariableDefinitions") -> Variable: + def upload_month_reversed(self: "VariableDefinitions") -> IntegerVariable: """ The upload month, but reversed using ``13 - {upload_month}``, i.e. March returns ``10`` """ - return Variable("upload_month_reversed") + return self.upload_date.get_integer_date_metadata( + date_metadata_key="month_reversed", variable_name="upload_month_reversed" + ) @property - def upload_month_reversed_padded(self: "VariableDefinitions") -> Variable: + def upload_month_reversed_padded(self: "VariableDefinitions") -> StringVariable: """ The reversed upload month, but padded. i.e. November returns "02" """ - return Variable("upload_month_reversed_padded") + return self.upload_date.get_string_date_metadata( + date_metadata_key="month_reversed_padded", variable_name="upload_month_reversed_padded" + ) @property - def upload_month_padded(self: "VariableDefinitions") -> Variable: + def upload_month_padded(self: "VariableDefinitions") -> StringVariable: """ The entry's upload month padded to two digits, i.e. March returns "03" """ - return Variable("upload_month_padded") + return self.upload_date.get_string_date_metadata( + date_metadata_key="month_padded", variable_name="upload_month_padded" + ) @property - def upload_day_padded(self: "VariableDefinitions") -> Variable: + def upload_day_padded(self: "VariableDefinitions") -> StringVariable: """ The entry's upload day padded to two digits, i.e. the fifth returns "05" """ - return Variable("upload_day_padded") + return self.upload_date.get_string_date_metadata( + date_metadata_key="day_padded", variable_name="upload_day_padded" + ) @property - def upload_month(self: "VariableDefinitions") -> Variable: + def upload_month(self: "VariableDefinitions") -> IntegerVariable: """ The upload month as an integer (no padding). """ - return Variable("upload_month") + return self.upload_date.get_integer_date_metadata( + date_metadata_key="month", variable_name="upload_month" + ) @property - def upload_day(self: "VariableDefinitions") -> Variable: + def upload_day(self: "VariableDefinitions") -> IntegerVariable: """ The upload day as an integer (no padding). """ - return Variable("upload_day") + return self.upload_date.get_integer_date_metadata( + date_metadata_key="day", variable_name="upload_day" + ) @property - def upload_day_reversed(self: "VariableDefinitions") -> Variable: + def upload_day_reversed(self: "VariableDefinitions") -> IntegerVariable: """ The upload day, but reversed using ``{total_days_in_month} + 1 - {upload_day}``, i.e. August 8th would have upload_day_reversed of ``31 + 1 - 8`` = ``24`` """ - return Variable("upload_day_reversed") + return self.upload_date.get_integer_date_metadata( + date_metadata_key="day_reversed", variable_name="upload_day_reversed" + ) @property - def upload_day_reversed_padded(self: "VariableDefinitions") -> Variable: + def upload_day_reversed_padded(self: "VariableDefinitions") -> StringVariable: """ The reversed upload day, but padded. i.e. August 30th returns "02". """ - return Variable("upload_day_reversed_padded") + return self.upload_date.get_string_date_metadata( + date_metadata_key="day_reversed_padded", variable_name="upload_day_reversed_padded" + ) @property - def upload_day_of_year(self: "VariableDefinitions") -> Variable: + def upload_day_of_year(self: "VariableDefinitions") -> IntegerVariable: """ The day of the year, i.e. February 1st returns ``32`` """ - return Variable("upload_day_of_year") + return self.upload_date.get_integer_date_metadata( + date_metadata_key="day_of_year", variable_name="upload_day_of_year" + ) @property - def upload_day_of_year_padded(self: "VariableDefinitions") -> Variable: + def upload_day_of_year_padded(self: "VariableDefinitions") -> StringVariable: """ The upload day of year, but padded i.e. February 1st returns "032" """ - return Variable("upload_day_of_year_padded") + return self.upload_date.get_string_date_metadata( + date_metadata_key="day_of_year_padded", variable_name="upload_day_of_year_padded" + ) @property - def upload_day_of_year_reversed(self: "VariableDefinitions") -> Variable: + def upload_day_of_year_reversed(self: "VariableDefinitions") -> IntegerVariable: """ The upload day, but reversed using ``{total_days_in_year} + 1 - {upload_day}``, i.e. February 2nd would have upload_day_of_year_reversed of ``365 + 1 - 32`` = ``334`` """ - return Variable("upload_day_of_year_reversed") + return self.upload_date.get_integer_date_metadata( + date_metadata_key="day_of_year_reversed", variable_name="upload_day_of_year_reversed" + ) @property - def upload_day_of_year_reversed_padded(self: "VariableDefinitions") -> Variable: + def upload_day_of_year_reversed_padded(self: "VariableDefinitions") -> StringVariable: """ The reversed upload day of year, but padded i.e. December 31st returns "001" """ - return Variable("upload_day_of_year_reversed_padded") + return self.upload_date.get_string_date_metadata( + date_metadata_key="day_of_year_reversed_padded", + variable_name="upload_day_of_year_reversed_padded", + ) @property - def upload_date_standardized(self: "VariableDefinitions") -> Variable: + def upload_date_standardized(self: "VariableDefinitions") -> StringVariable: """ The uploaded date formatted as YYYY-MM-DD """ - return Variable("upload_date_standardized") + return self.upload_date.get_string_date_metadata( + date_metadata_key="date_standardized", variable_name="upload_date_standardized" + ) class ReleaseDateVariableDefinitions(ABC): @property - def release_date(self: "VariableDefinitions") -> MetadataVariable: + def release_date(self: "VariableDefinitions") -> StringDateVariable: """ The entry’s release date, in YYYYMMDD format. If not present, return the upload date. """ - return MetadataVariable(variable_name="release_date", metadata_key="release_date") + return StringMetadataVariable.from_entry( + metadata_key="release_date", default=self.upload_date + ).as_date_variable() @property - def release_year(self: "VariableDefinitions") -> Variable: + def release_year(self: "VariableDefinitions") -> IntegerVariable: """ - The entry's release year + The entry's upload year """ - return Variable("release_year") + return self.release_date.get_integer_date_metadata( + date_metadata_key="year", variable_name="release_year" + ) @property - def release_year_truncated(self: "VariableDefinitions") -> Variable: + def release_year_truncated(self: "VariableDefinitions") -> IntegerVariable: """ - The last two digits of the release year, i.e. 22 in 2022 + The last two digits of the upload year, i.e. 22 in 2022 """ - return Variable("release_year_truncated") + return self.release_date.get_integer_date_metadata( + date_metadata_key="year_truncated", variable_name="release_year_truncated" + ) @property - def release_year_truncated_reversed(self: "VariableDefinitions") -> Variable: + def release_year_truncated_reversed(self: "VariableDefinitions") -> IntegerVariable: """ - The release year truncated, but reversed using ``100 - {release_year_truncated}``, i.e. + The upload year truncated, but reversed using ``100 - {release_year_truncated}``, i.e. 2022 returns ``100 - 22`` = ``78`` """ - return Variable("release_year_truncated_reversed") + return self.release_date.get_integer_date_metadata( + date_metadata_key="year_truncated_reversed", + variable_name="release_year_truncated_reversed", + ) @property - def release_month_reversed(self: "VariableDefinitions") -> Variable: + def release_month_reversed(self: "VariableDefinitions") -> IntegerVariable: """ - The release month, but reversed - using ``13 - {release_month}``, i.e. March returns ``10`` + The upload month, but reversed using ``13 - {release_month}``, i.e. March returns ``10`` """ - return Variable("release_month_reversed") + return self.release_date.get_integer_date_metadata( + date_metadata_key="month_reversed", variable_name="release_month_reversed" + ) @property - def release_month_reversed_padded(self: "VariableDefinitions") -> Variable: + def release_month_reversed_padded(self: "VariableDefinitions") -> StringVariable: """ - The reversed release month, but padded. i.e. November returns "02" + The reversed upload month, but padded. i.e. November returns "02" """ - return Variable("release_month_reversed_padded") + return self.release_date.get_string_date_metadata( + date_metadata_key="month_reversed_padded", variable_name="release_month_reversed_padded" + ) @property - def release_month_padded(self: "VariableDefinitions") -> Variable: + def release_month_padded(self: "VariableDefinitions") -> StringVariable: """ - The entry's release month padded to two digits, i.e. March returns "03" + The entry's upload month padded to two digits, i.e. March returns "03" """ - return Variable("release_month_padded") + return self.release_date.get_string_date_metadata( + date_metadata_key="month_padded", variable_name="release_month_padded" + ) @property - def release_day_padded(self: "VariableDefinitions") -> Variable: + def release_day_padded(self: "VariableDefinitions") -> StringVariable: """ - The entry's release day padded to two digits, i.e. the fifth returns "05" + The entry's upload day padded to two digits, i.e. the fifth returns "05" """ - return Variable("release_day_padded") + return self.release_date.get_string_date_metadata( + date_metadata_key="day_padded", variable_name="release_day_padded" + ) @property - def release_month(self: "VariableDefinitions") -> Variable: + def release_month(self: "VariableDefinitions") -> IntegerVariable: """ - The release month as an integer (no padding). + The upload month as an integer (no padding). """ - return Variable("release_month") + return self.release_date.get_integer_date_metadata( + date_metadata_key="month", variable_name="release_month" + ) @property - def release_day(self: "VariableDefinitions") -> Variable: + def release_day(self: "VariableDefinitions") -> IntegerVariable: """ - The release day as an integer (no padding). + The upload day as an integer (no padding). """ - return Variable("release_day") + return self.release_date.get_integer_date_metadata( + date_metadata_key="day", variable_name="release_day" + ) @property - def release_day_reversed(self: "VariableDefinitions") -> Variable: + def release_day_reversed(self: "VariableDefinitions") -> IntegerVariable: """ - The release day, but reversed using ``{total_days_in_month} + 1 - {release_day}``, + The upload day, but reversed using ``{total_days_in_month} + 1 - {release_day}``, i.e. August 8th would have release_day_reversed of ``31 + 1 - 8`` = ``24`` """ - return Variable("release_day_reversed") + return self.release_date.get_integer_date_metadata( + date_metadata_key="day_reversed", variable_name="release_day_reversed" + ) @property - def release_day_reversed_padded(self: "VariableDefinitions") -> Variable: + def release_day_reversed_padded(self: "VariableDefinitions") -> StringVariable: """ - The reversed release day, but padded. i.e. August 30th returns "02". + The reversed upload day, but padded. i.e. August 30th returns "02". """ - return Variable("release_day_reversed_padded") + return self.release_date.get_string_date_metadata( + date_metadata_key="day_reversed_padded", variable_name="release_day_reversed_padded" + ) @property - def release_day_of_year(self: "VariableDefinitions") -> Variable: + def release_day_of_year(self: "VariableDefinitions") -> IntegerVariable: """ The day of the year, i.e. February 1st returns ``32`` """ - return Variable("release_day_of_year") + return self.release_date.get_integer_date_metadata( + date_metadata_key="day_of_year", variable_name="release_day_of_year" + ) @property - def release_day_of_year_padded(self: "VariableDefinitions") -> Variable: + def release_day_of_year_padded(self: "VariableDefinitions") -> StringVariable: """ - The release day of year, but padded i.e. February 1st returns "032" + The upload day of year, but padded i.e. February 1st returns "032" """ - return Variable("release_day_of_year_padded") + return self.release_date.get_string_date_metadata( + date_metadata_key="day_of_year_padded", variable_name="release_day_of_year_padded" + ) @property - def release_day_of_year_reversed(self: "VariableDefinitions") -> Variable: + def release_day_of_year_reversed(self: "VariableDefinitions") -> IntegerVariable: """ - The release day, but reversed using ``{total_days_in_year} + 1 - {release_day}``, + The upload day, but reversed using ``{total_days_in_year} + 1 - {release_day}``, i.e. February 2nd would have release_day_of_year_reversed of ``365 + 1 - 32`` = ``334`` """ - return Variable("release_day_of_year_reversed") + return self.release_date.get_integer_date_metadata( + date_metadata_key="day_of_year_reversed", variable_name="release_day_of_year_reversed" + ) @property - def release_day_of_year_reversed_padded(self: "VariableDefinitions") -> Variable: + def release_day_of_year_reversed_padded(self: "VariableDefinitions") -> StringVariable: """ - The reversed release day of year, but padded i.e. December 31st returns "001" + The reversed upload day of year, but padded i.e. December 31st returns "001" """ - return Variable("release_day_of_year_reversed_padded") + return self.release_date.get_string_date_metadata( + date_metadata_key="day_of_year_reversed_padded", + variable_name="release_day_of_year_reversed_padded", + ) @property - def release_date_standardized(self: "VariableDefinitions") -> Variable: + def release_date_standardized(self: "VariableDefinitions") -> StringVariable: """ - The release date formatted as YYYY-MM-DD + The uploaded date formatted as YYYY-MM-DD """ - return Variable("release_date_standardized") + return self.release_date.get_string_date_metadata( + date_metadata_key="date_standardized", variable_name="release_date_standardized" + ) class YtdlSubVariableDefinitions(ABC): @property - def ytdl_sub_input_url(self: "VariableDefinitions") -> Variable: + def ytdl_sub_input_url(self: "VariableDefinitions") -> StringVariable: """ The input URL used in ytdl-sub to create this entry. """ - return Variable("ytdl_sub_input_url") + return StringVariable(variable_name="ytdl_sub_input_url", definition="{ %string('') }") @property - def download_index(self: "VariableDefinitions") -> Variable: + def download_index(self: "VariableDefinitions") -> IntegerVariable: """ The i'th entry downloaded. NOTE that this is fetched dynamically from the download archive. """ - return Variable(variable_name="download_index") + return IntegerVariable(variable_name="download_index", definition="{ %int(1) }") @property - def download_index_padded6(self: "VariableDefinitions") -> Variable: + def download_index_padded6(self: "VariableDefinitions") -> StringVariable: """ The download_index padded six digits """ - return Variable("download_index_padded6") + return self.download_index.to_padded_int(variable_name="download_index_padded6", pad=6) @property - def upload_date_index(self: "VariableDefinitions") -> Variable: + def upload_date_index(self: "VariableDefinitions") -> IntegerVariable: """ The i'th entry downloaded with this upload date. """ - return Variable(variable_name="upload_date_index") + return IntegerVariable(variable_name="upload_date_index", definition="{ %int(1) }") @property - def upload_date_index_padded(self: "VariableDefinitions") -> Variable: + def upload_date_index_padded(self: "VariableDefinitions") -> StringVariable: """ The upload_date_index padded two digits """ - return Variable("upload_date_index_padded") + return self.upload_date_index.to_padded_int(variable_name="upload_date_index_padded", pad=2) @property - def upload_date_index_reversed(self: "VariableDefinitions") -> Variable: + def upload_date_index_reversed(self: "VariableDefinitions") -> IntegerVariable: """ 100 - upload_date_index """ - return Variable("upload_date_index_reversed") + return IntegerVariable( + variable_name="upload_date_index_reversed", + definition=f"{{%sub(100, {self.upload_date_index.variable_name})}}", + ) @property - def upload_date_index_reversed_padded(self: "VariableDefinitions") -> Variable: + def upload_date_index_reversed_padded(self: "VariableDefinitions") -> StringVariable: """ The upload_date_index padded two digits """ - return Variable("upload_date_index_reversed_padded") + return self.upload_date_index_reversed.to_padded_int( + variable_name="upload_date_index_reversed_padded", pad=2 + ) class EntryVariableDefinitions(ABC): @property - def uid(self: "VariableDefinitions") -> MetadataVariable: + def uid(self: "VariableDefinitions") -> StringMetadataVariable: """ The entry's unique ID """ - return MetadataVariable(metadata_key="id", variable_name="uid") + return StringMetadataVariable.from_entry(metadata_key="id", variable_name="uid") @property - def duration(self: "VariableDefinitions") -> MetadataVariable: + def duration(self: "VariableDefinitions") -> IntegerMetadataVariable: """ - The duration of the entry in seconds + The duration of the entry in seconds if it exists. Defaults to zero otherwise. """ - return MetadataVariable("duration", metadata_key="duration") + return IntegerMetadataVariable.from_entry(metadata_key="duration", default=0) @property - def uid_sanitized_plex(self: "VariableDefinitions") -> Variable: + def uid_sanitized_plex(self: "VariableDefinitions") -> StringVariable: """ The sanitized uid with additional sanitizing for Plex. Replaces numbers with fixed-width numbers so Plex does not recognize them as season or episode numbers. """ - return Variable("uid_sanitized_plex") + return self.uid.to_sanitized_plex("uid_sanitized_plex") @property - def ie_key(self: "VariableDefinitions") -> MetadataVariable: + def ie_key(self: "VariableDefinitions") -> StringMetadataVariable: """ The ie_key, used in legacy yt-dlp things as the 'info-extractor key' """ - return MetadataVariable(metadata_key="ie_key", variable_name="ie_key") + return StringMetadataVariable.from_entry(metadata_key="ie_key") @property - def extractor_key(self: "VariableDefinitions") -> MetadataVariable: + def extractor_key(self: "VariableDefinitions") -> StringMetadataVariable: """ The yt-dlp extractor key """ - return MetadataVariable(metadata_key="extractor_key", variable_name="extractor_key") + return StringMetadataVariable.from_entry(metadata_key="extractor_key") @property - def extractor(self: "VariableDefinitions") -> MetadataVariable: + def extractor(self: "VariableDefinitions") -> StringMetadataVariable: """ The yt-dlp extractor name """ - return MetadataVariable(variable_name="extractor", metadata_key="extractor") + return StringMetadataVariable.from_entry(metadata_key="extractor") @property - def epoch(self: "VariableDefinitions") -> MetadataVariable: + def epoch(self: "VariableDefinitions") -> IntegerMetadataVariable: """ The unix epoch of when the metadata was scraped by yt-dlp. """ - return MetadataVariable(metadata_key="epoch", variable_name="epoch") + return IntegerMetadataVariable.from_entry(metadata_key="epoch") @property - def epoch_date(self: "VariableDefinitions") -> Variable: + def epoch_date(self: "VariableDefinitions") -> StringVariable: """ The epoch's date, in YYYYMMDD format. """ - return Variable("epoch_date") + return StringVariable( + variable_name="epoch_date", + definition=f"{{%datetime_strftime({self.epoch.variable_name}, '%Y%m%d')}}", + ) @property - def epoch_hour(self: "VariableDefinitions") -> Variable: + def epoch_hour(self: "VariableDefinitions") -> StringVariable: """ The epoch's hour """ - return Variable("epoch_hour") + return StringVariable( + variable_name="epoch_hour", + definition=f"{{%datetime_strftime({self.epoch.variable_name}, '%H')}}", + ) @property - def title(self: "VariableDefinitions") -> MetadataVariable: + def title(self: "VariableDefinitions") -> StringMetadataVariable: """ The title of the entry. If a title does not exist, returns its unique ID. """ - return MetadataVariable(variable_name="title", metadata_key="title") + return StringMetadataVariable.from_entry(metadata_key="title", default=self.uid) @property - def title_sanitized_plex(self: "VariableDefinitions") -> Variable: + def title_sanitized_plex(self: "VariableDefinitions") -> StringVariable: """ The sanitized title with additional sanitizing for Plex. It replaces numbers with fixed-width numbers so Plex does not recognize them as season or episode numbers. """ - return Variable("title_sanitized_plex") + return self.title.to_sanitized_plex(variable_name="title_sanitized_plex") @property - def webpage_url(self: "VariableDefinitions") -> MetadataVariable: + def webpage_url(self: "VariableDefinitions") -> StringMetadataVariable: """ The url to the webpage. """ - return MetadataVariable(metadata_key="webpage_url", variable_name="webpage_url") + return StringMetadataVariable.from_entry(metadata_key="webpage_url") @property - def info_json_ext(self: "VariableDefinitions") -> Variable: + def info_json_ext(self: "VariableDefinitions") -> StringVariable: """ The "info.json" extension """ - return Variable("info_json_ext") + return StringVariable( + variable_name="info_json_ext", + definition="info.json", + ) @property - def description(self: "VariableDefinitions") -> MetadataVariable: + def description(self: "VariableDefinitions") -> StringMetadataVariable: """ The description if it exists. Otherwise, returns an emtpy string. """ - return MetadataVariable(variable_name="description", metadata_key="description") + return StringMetadataVariable.from_entry( + metadata_key="description", + default="", + ) @property - def uploader_id(self: "VariableDefinitions") -> MetadataVariable: + def uploader_id(self: "VariableDefinitions") -> StringMetadataVariable: """ The uploader id if it exists, otherwise return the unique ID. """ - return MetadataVariable(variable_name="uploader_id", metadata_key="uploader_id") + return StringMetadataVariable.from_entry( + metadata_key="uploader_id", + default=self.uid, + ) @property - def uploader(self: "VariableDefinitions") -> MetadataVariable: + def uploader(self: "VariableDefinitions") -> StringMetadataVariable: """ The uploader if it exists, otherwise return the uploader ID. """ - return MetadataVariable(variable_name="uploader", metadata_key="uploader") + return StringMetadataVariable.from_entry( + metadata_key="uploader", + default=self.uploader_id, + ) @property - def uploader_url(self: "VariableDefinitions") -> MetadataVariable: + def uploader_url(self: "VariableDefinitions") -> StringMetadataVariable: """ The uploader url if it exists, otherwise returns the webpage_url. """ - return MetadataVariable("uploader_url", metadata_key="uploader_url") + return StringMetadataVariable.from_entry( + metadata_key="uploader_url", + default=self.webpage_url, + ) @property - def creator(self: "VariableDefinitions") -> MetadataVariable: + def creator(self: "VariableDefinitions") -> StringMetadataVariable: """ The creator name if it exists, otherwise returns the channel. """ - return MetadataVariable(variable_name="creator", metadata_key="creator") + return StringMetadataVariable.from_entry( + metadata_key="creator", + default=self.channel, + ) @property - def channel(self: "VariableDefinitions") -> MetadataVariable: + def channel(self: "VariableDefinitions") -> StringMetadataVariable: """ The channel name if it exists, otherwise returns the uploader. """ - return MetadataVariable(variable_name="channel", metadata_key="channel") + return StringMetadataVariable.from_entry( + metadata_key="channel", + default=self.uploader, + ) @property - def channel_id(self: "VariableDefinitions") -> MetadataVariable: + def channel_id(self: "VariableDefinitions") -> StringMetadataVariable: """ The channel id if it exists, otherwise returns the entry uploader ID. """ - return MetadataVariable(variable_name="channel_id", metadata_key="channel_id") + return StringMetadataVariable.from_entry( + metadata_key="channel_id", + default=self.uploader_id, + ) @property - def ext(self: "VariableDefinitions") -> MetadataVariable: + def ext(self: "VariableDefinitions") -> StringMetadataVariable: """ The downloaded entry's file extension """ - return MetadataVariable(variable_name="ext", metadata_key="ext") + return StringMetadataVariable.from_entry(metadata_key="ext") @property - def thumbnail_ext(self: "VariableDefinitions") -> Variable: + def thumbnail_ext(self: "VariableDefinitions") -> StringVariable: """ The download entry's thumbnail extension. Will always return 'jpg'. Until there is a need to support other image types, we always convert to jpg. """ - return Variable("thumbnail_ext") + return StringVariable( + variable_name="thumbnail_ext", + definition="jpg", + ) @property - def comments(self: "VariableDefinitions") -> MetadataVariable: + def comments(self: "VariableDefinitions") -> ArrayMetadataVariable: """ Comments if they are requested """ - return MetadataVariable("comments", "comments") + return ArrayMetadataVariable( + metadata_key="comments", variable_name="comments", definition="{ [] }" + ) @property - def chapters(self: "VariableDefinitions") -> MetadataVariable: + def chapters(self: "VariableDefinitions") -> ArrayMetadataVariable: """ Chapters if they exist """ - return MetadataVariable("chapters", "chapters") + return ArrayMetadataVariable( + metadata_key="chapters", variable_name="chapters", definition="{ [] }" + ) @property - def sponsorblock_chapters(self: "VariableDefinitions") -> MetadataVariable: + def sponsorblock_chapters(self: "VariableDefinitions") -> ArrayMetadataVariable: """ Sponsorblock Chapters if they are requested and exist """ - return MetadataVariable("sponsorblock_chapters", "sponsorblock_chapters") + return ArrayMetadataVariable( + metadata_key="sponsorblock_chapters", + variable_name="sponsorblock_chapters", + definition="{ [] }", + ) @property - def requested_subtitles(self: "VariableDefinitions") -> MetadataVariable: + def requested_subtitles(self: "VariableDefinitions") -> MapMetadataVariable: """ Subtitles if they are requested and exist """ - return MetadataVariable("requested_subtitles", "requested_subtitles") + return MapMetadataVariable( + metadata_key="requested_subtitles", + variable_name="requested_subtitles", + definition="{ {} }", + ) class VariableDefinitions( @@ -776,8 +960,67 @@ class VariableDefinitions( ReleaseDateVariableDefinitions, YtdlSubVariableDefinitions, ): - pass + @classmethod + def scripts(cls) -> Dict[str, str]: + return { + property_name: getattr(VARIABLES, property_name).definition + for property_name in [ + prop for prop in dir(cls) if isinstance(getattr(cls, prop), property) + ] + } + + @classmethod + def injected_variables(cls) -> Set[MetadataVariable]: + return { + VARIABLES.download_index, + VARIABLES.comments, + VARIABLES.requested_subtitles, + VARIABLES.chapters, + VARIABLES.sponsorblock_chapters, + VARIABLES.ytdl_sub_input_url, + } + + @classmethod + def required_entry_variables(cls) -> Set[MetadataVariable]: + return { + VARIABLES.uid, + VARIABLES.extractor_key, + VARIABLES.epoch, + VARIABLES.webpage_url, + VARIABLES.ext, + } + + @classmethod + def default_entry_variables(cls) -> Set[MetadataVariable]: + return { + VARIABLES.title, + VARIABLES.extractor, + VARIABLES.description, + VARIABLES.ie_key, + VARIABLES.uploader_id, + VARIABLES.uploader, + VARIABLES.uploader_url, + VARIABLES.upload_date, + VARIABLES.release_date, + VARIABLES.channel, + VARIABLES.creator, + VARIABLES.channel_id, + VARIABLES.duration, + VARIABLES.playlist_index, + VARIABLES.playlist_count, + VARIABLES.playlist_uid, + VARIABLES.playlist_title, + VARIABLES.playlist_uploader_id, + } + + @classmethod + def unresolvable_static_variables(cls) -> Set[str]: + return { + VARIABLES.entry_metadata.variable_name, + } | cls.injected_variables() -# Singleton to use externally +# Singletons to use externally VARIABLES: VariableDefinitions = VariableDefinitions() +VARIABLE_SCRIPTS: Dict[str, str] = VariableDefinitions.scripts() +UNRESOLVED_VARIABLES: Set[str] = VariableDefinitions.unresolvable_static_variables() diff --git a/src/ytdl_sub/entries/script/variable_scripts.py b/src/ytdl_sub/entries/script/variable_scripts.py deleted file mode 100644 index 90808c37..00000000 --- a/src/ytdl_sub/entries/script/variable_scripts.py +++ /dev/null @@ -1,296 +0,0 @@ -from typing import Dict -from typing import List -from typing import Optional -from typing import Set - -import mergedeep - -from ytdl_sub.entries.script.custom_functions import CustomFunctions -from ytdl_sub.entries.script.variable_definitions import VARIABLES -from ytdl_sub.entries.script.variable_definitions import Metadata -from ytdl_sub.entries.script.variable_definitions import MetadataVariable -from ytdl_sub.entries.script.variable_definitions import Variable -from ytdl_sub.entries.script.variable_definitions import VariableDefinitions - -############################################################################################### -# Helpers - -v: VariableDefinitions = VARIABLES - - -def _pad_int(key: Variable, pad: int) -> str: - return f"{{%pad_zero({key.variable_name}, {pad})}}" - - -def _sanitized_plex(key: Variable) -> str: - return f"{{%sanitize_plex_episode({key.variable_name})}}" - - -def _date_metadata(date_key: Variable, metadata_key: str) -> str: - return f"{{%map_get(%to_date_metadata({date_key.variable_name}), '{metadata_key}')}}" - - -############################################################################################### -# Metadata Getters - - -def _get( - cast: str, - metadata: Metadata, - key: MetadataVariable, - default: Optional[Variable | str | int | Dict | List], -) -> str: - if default is None: - # TODO: assert with good error message if key DNE - out = f"%map_get({metadata.variable_name}, '{key.metadata_key}')" - elif isinstance(default, Variable): - args = f"{metadata.variable_name}, '{key.metadata_key}', {default.variable_name}" - out = f"%map_get_non_empty({args})" - elif isinstance(default, str): - out = f"%map_get_non_empty({metadata.variable_name}, '{key.metadata_key}', '{default}')" - elif isinstance(default, dict): - out = f"%map_get_non_empty({metadata.variable_name}, '{key.metadata_key}', {{}})" - elif isinstance(default, list): - out = f"%map_get_non_empty({metadata.variable_name}, '{key.metadata_key}', [])" - else: - out = f"%map_get_non_empty({metadata.variable_name}, '{key.metadata_key}', {default})" - - return f"{{ %legacy_bracket_safety(%{cast}({out})) }}" - - -############################################################################################### -# Entry Getters - - -def _entry_get_str(key: MetadataVariable, default: Optional[Variable | str] = None) -> str: - return _get("string", metadata=v.entry_metadata, key=key, default=default) - - -def _entry_get_int(key: MetadataVariable, default: Optional[Variable | int] = None) -> str: - return _get("int", metadata=v.entry_metadata, key=key, default=default) - - -def _entry_get_map(key: MetadataVariable, default: Optional[Variable | Dict] = None): - return _get("map", metadata=v.entry_metadata, key=key, default=default) - - -def _entry_get_array(key: MetadataVariable, default: Optional[Variable | List] = None): - return _get("array", metadata=v.entry_metadata, key=key, default=default) - - -############################################################################################### -# Playlist Getters - - -def _playlist_get_str(key: MetadataVariable, default: Optional[Variable | str] = None) -> str: - return _get("string", metadata=v.playlist_metadata, key=key, default=default) - - -def _playlist_get_int(key: MetadataVariable, default: Optional[Variable | int] = None) -> str: - return _get("int", metadata=v.playlist_metadata, key=key, default=default) - - -############################################################################################### -# Source Getters - - -def _source_get_str(key: MetadataVariable, default: Optional[Variable | str] = None) -> str: - return _get("string", metadata=v.source_metadata, key=key, default=default) - - -############################################################################################### -# Scripts - -ENTRY_EMPTY_METADATA: Dict[Variable, str] = {v.entry_metadata: "{ {} }"} - -ENTRY_HARDCODED_VARIABLES: Dict[Variable, str] = { - v.info_json_ext: "info.json", - v.thumbnail_ext: "jpg", -} - -ENTRY_RELATIVE_VARIABLES: Dict[MetadataVariable, str] = { - v.playlist_metadata: _entry_get_map(v.playlist_metadata, {}), - v.source_metadata: _entry_get_map(v.source_metadata, {}), - v.sibling_metadata: _entry_get_array(v.sibling_metadata, []), -} - -ENTRY_REQUIRED_VARIABLES: Dict[MetadataVariable, str] = { - v.uid: _entry_get_str(v.uid), - v.extractor_key: _entry_get_str(v.extractor_key), - v.epoch: _entry_get_int(v.epoch), - v.webpage_url: _entry_get_str(v.webpage_url), - v.ext: _entry_get_str(v.ext), -} - -ENTRY_DEFAULT_VARIABLES: Dict[MetadataVariable, str] = { - v.title: _entry_get_str(v.title, v.uid), - v.extractor: _entry_get_str(v.extractor, v.extractor_key), - v.description: _entry_get_str(v.description, ""), - v.ie_key: _entry_get_str(v.ie_key, v.extractor_key), - v.uploader_id: _entry_get_str(v.uploader_id, v.uid), - v.uploader: _entry_get_str(v.uploader, v.uploader_id), - v.uploader_url: _entry_get_str(v.uploader_url, v.webpage_url), - v.upload_date: _entry_get_str(v.upload_date, v.epoch_date), - v.release_date: _entry_get_str(v.release_date, v.upload_date), - v.channel: _entry_get_str(v.channel, v.uploader), - v.creator: _entry_get_str(v.creator, v.channel), - v.channel_id: _entry_get_str(v.channel_id, v.uploader_id), - v.duration: _entry_get_int(v.duration, 0), - v.playlist_index: _entry_get_int(v.playlist_index, 1), - v.playlist_count: _entry_get_int(v.playlist_count, 1), - v.playlist_uid: _entry_get_str(v.playlist_uid, v.uid), - v.playlist_title: _entry_get_str(v.playlist_title, v.title), - v.playlist_uploader_id: _entry_get_str(v.playlist_uploader_id, v.uploader_id), -} - -# MARK AS UNRESOLVABLE UNTIL THEY ARE ADDED IN THE DOWNLOADER -DOWNLOADER_INJECTED_VARIABLES: Dict[Variable, str] = { - v.download_index: "{%int(1)}", - v.upload_date_index: "{%int(1)}", - v.comments: "{ [] }", - v.requested_subtitles: "{ {} }", - v.chapters: "{ [] }", - v.sponsorblock_chapters: "{ [] }", - v.ytdl_sub_input_url: f"{{{v.source_webpage_url.variable_name}}}", -} - -ENTRY_DERIVED_VARIABLES: Dict[Variable, str] = { - v.uid_sanitized_plex: _sanitized_plex(v.uid), - v.title_sanitized_plex: _sanitized_plex(v.title), - v.epoch_date: f"{{%datetime_strftime({v.epoch.variable_name}, '%Y%m%d')}}", - v.epoch_hour: f"{{%datetime_strftime({v.epoch.variable_name}, '%H')}}", - v.download_index_padded6: _pad_int(v.download_index, 6), - v.upload_date_index_padded: _pad_int(v.upload_date_index, 2), - v.upload_date_index_reversed: f"{{%sub(100, {v.upload_date_index.variable_name})}}", - v.upload_date_index_reversed_padded: _pad_int(v.upload_date_index_reversed, 2), - v.playlist_index_reversed: ( - f"{{%sub({v.playlist_count.variable_name}, {v.playlist_index.variable_name}, -1)}}" - ), - v.playlist_index_padded: _pad_int(v.playlist_index, 2), - v.playlist_index_reversed_padded: _pad_int(v.playlist_index_reversed, 2), - v.playlist_index_padded6: _pad_int(v.playlist_index, 6), - v.playlist_index_reversed_padded6: _pad_int(v.playlist_index_reversed, 6), -} - -ENTRY_UPLOAD_DATE_VARIABLES: Dict[Variable, str] = { - v.upload_year: _date_metadata(v.upload_date, "year"), - v.upload_year_truncated: _date_metadata(v.upload_date, "year_truncated"), - v.upload_year_truncated_reversed: _date_metadata(v.upload_date, "year_truncated_reversed"), - v.upload_month_reversed: _date_metadata(v.upload_date, "month_reversed"), - v.upload_month_reversed_padded: _date_metadata(v.upload_date, "month_reversed_padded"), - v.upload_month_padded: _date_metadata(v.upload_date, "month_padded"), - v.upload_day_padded: _date_metadata(v.upload_date, "day_padded"), - v.upload_month: _date_metadata(v.upload_date, "month"), - v.upload_day: _date_metadata(v.upload_date, "day"), - v.upload_day_reversed: _date_metadata(v.upload_date, "day_reversed"), - v.upload_day_reversed_padded: _date_metadata(v.upload_date, "day_reversed_padded"), - v.upload_day_of_year: _date_metadata(v.upload_date, "day_of_year"), - v.upload_day_of_year_padded: _date_metadata(v.upload_date, "day_of_year_padded"), - v.upload_day_of_year_reversed: _date_metadata(v.upload_date, "day_of_year_reversed"), - v.upload_day_of_year_reversed_padded: _date_metadata( - v.upload_date, "day_of_year_reversed_padded" - ), - v.upload_date_standardized: _date_metadata(v.upload_date, "date_standardized"), -} - -ENTRY_RELEASE_DATE_VARIABLES: Dict[Variable, str] = { - v.release_year: _date_metadata(v.release_date, "year"), - v.release_year_truncated: _date_metadata(v.release_date, "year_truncated"), - v.release_year_truncated_reversed: _date_metadata(v.release_date, "year_truncated_reversed"), - v.release_month_reversed: _date_metadata(v.release_date, "month_reversed"), - v.release_month_reversed_padded: _date_metadata(v.release_date, "month_reversed_padded"), - v.release_month_padded: _date_metadata(v.release_date, "month_padded"), - v.release_day_padded: _date_metadata(v.release_date, "day_padded"), - v.release_month: _date_metadata(v.release_date, "month"), - v.release_day: _date_metadata(v.release_date, "day"), - v.release_day_reversed: _date_metadata(v.release_date, "day_reversed"), - v.release_day_reversed_padded: _date_metadata(v.release_date, "day_reversed_padded"), - v.release_day_of_year: _date_metadata(v.release_date, "day_of_year"), - v.release_day_of_year_padded: _date_metadata(v.release_date, "day_of_year_padded"), - v.release_day_of_year_reversed: _date_metadata(v.release_date, "day_of_year_reversed"), - v.release_day_of_year_reversed_padded: _date_metadata( - v.release_date, "day_of_year_reversed_padded" - ), - v.release_date_standardized: _date_metadata(v.release_date, "date_standardized"), -} - -PLAYLIST_VARIABLES: Dict[Variable, str] = { - v.playlist_webpage_url: _playlist_get_str(v.playlist_webpage_url, v.webpage_url), - v.playlist_description: _playlist_get_str(v.playlist_description, v.description), - v.playlist_uploader: _playlist_get_str(v.playlist_uploader, v.uploader), - v.playlist_uploader_url: _playlist_get_str(v.playlist_uploader_url, v.playlist_webpage_url), - v.source_index: _playlist_get_int(v.source_index, 1), - v.source_count: _playlist_get_int(v.source_count, 1), -} - - -SOURCE_VARIABLES: Dict[Variable, str] = { - v.source_uid: _source_get_str(v.source_uid, v.playlist_uid), - v.source_title: _source_get_str(v.source_title, v.playlist_title), - v.source_webpage_url: _source_get_str(v.source_webpage_url, v.playlist_webpage_url), - v.source_description: _source_get_str(v.source_description, v.playlist_description), - v.source_uploader_id: _source_get_str(v.source_uploader_id, v.playlist_uploader_id), - v.source_uploader: _source_get_str(v.source_uploader, v.playlist_uploader), - v.source_uploader_url: _source_get_str(v.source_uploader_url, v.source_webpage_url), -} - -SOURCE_DERIVED_VARIABLES: Dict[Variable, str] = { - v.source_index_padded: _pad_int(v.source_index, 2), -} - -SIBLING_VARIABLES: Dict[Variable, str] = { - v.playlist_max_upload_date: f"""{{ - %array_reduce( - %if_passthrough( - %extract_field_from_siblings('{v.upload_date.variable_name}'), - [{v.upload_date.variable_name}] - ), - %max - ) - }}""" -} - -SIBLING_DERIVED_VARIABLES: Dict[Variable, str] = { - v.playlist_max_upload_year: _date_metadata(v.playlist_max_upload_date, "year"), - v.playlist_max_upload_year_truncated: _date_metadata( - v.playlist_max_upload_date, "year_truncated" - ), -} - -_VARIABLE_SCRIPTS: Dict[Variable, str] = {} -mergedeep.merge( - _VARIABLE_SCRIPTS, - ENTRY_EMPTY_METADATA, - ENTRY_HARDCODED_VARIABLES, - ENTRY_RELATIVE_VARIABLES, - ENTRY_REQUIRED_VARIABLES, - ENTRY_DEFAULT_VARIABLES, - DOWNLOADER_INJECTED_VARIABLES, - ENTRY_DERIVED_VARIABLES, - ENTRY_UPLOAD_DATE_VARIABLES, - ENTRY_RELEASE_DATE_VARIABLES, - SIBLING_VARIABLES, - SIBLING_DERIVED_VARIABLES, - PLAYLIST_VARIABLES, - SOURCE_VARIABLES, - SOURCE_DERIVED_VARIABLES, -) - -VARIABLE_SCRIPTS: Dict[str, str] = { - var.variable_name: script for var, script in _VARIABLE_SCRIPTS.items() -} - - -def _keys(*variables: Dict[Variable, str]) -> Set[str]: - keys: Set[str] = set() - for variable_set in variables: - keys.update(set(var.variable_name for var in variable_set.keys())) - return keys - - -UNRESOLVED_VARIABLES: Set[str] = _keys( - ENTRY_EMPTY_METADATA, - DOWNLOADER_INJECTED_VARIABLES, -) - -CustomFunctions.register() diff --git a/src/ytdl_sub/entries/script/variable_types.py b/src/ytdl_sub/entries/script/variable_types.py new file mode 100644 index 00000000..7343ca4b --- /dev/null +++ b/src/ytdl_sub/entries/script/variable_types.py @@ -0,0 +1,269 @@ +from abc import ABC +from abc import abstractmethod +from dataclasses import dataclass +from typing import Dict +from typing import List +from typing import Optional +from typing import Type +from typing import TypeVar + +from ytdl_sub.script.types.array import Array +from ytdl_sub.script.types.map import Map +from ytdl_sub.script.types.resolvable import Integer +from ytdl_sub.script.types.resolvable import String + +ENTRY_METADATA_VARIABLE_NAME = "entry_metadata" +PLAYLIST_METADATA_VARIABLE_NAME = "playlist_metadata" +SOURCE_METADATA_VARIABLE_NAME = "source_metadata" + +TMetadataVariable = TypeVar("TMetadataVariable", bound="MetadataVariable") + + +def _get( + cast: str, + metadata_variable_name: str, + metadata_key: str, + variable_name: Optional[str], + default: Optional["Variable" | str | int | Dict | List], + as_type: Type[TMetadataVariable], +) -> TMetadataVariable: + if default is None: + # TODO: assert with good error message if key DNE + out = f"%map_get({metadata_variable_name}, '{metadata_key}')" + elif isinstance(default, Variable): + args = f"{metadata_variable_name}, '{metadata_key}', {default.variable_name}" + out = f"%map_get_non_empty({args})" + elif isinstance(default, str): + out = f"%map_get_non_empty({metadata_variable_name}, '{metadata_key}', '{default}')" + elif isinstance(default, dict): + out = f"%map_get_non_empty({metadata_variable_name}, '{metadata_key}', {{}})" + elif isinstance(default, list): + out = f"%map_get_non_empty({metadata_variable_name}, '{metadata_key}', [])" + else: + out = f"%map_get_non_empty({metadata_variable_name}, '{metadata_key}', {default})" + + return as_type( + variable_name=variable_name or metadata_key, + metadata_key=metadata_key, + definition=f"{{ %legacy_bracket_safety(%{cast}({out})) }}", + ) + + +@dataclass(frozen=True) +class Variable(ABC): + variable_name: str + definition: str + + @classmethod + @abstractmethod + def human_readable_type(cls) -> str: + """ + Script type of the variable, for documentation + """ + + +@dataclass(frozen=True) +class StringVariable(Variable): + @classmethod + def human_readable_type(cls) -> str: + return String.__name__ + + def to_sanitized_plex(self, variable_name: str) -> "StringVariable": + return StringVariable( + variable_name=variable_name, + definition=f"{{%sanitize_plex_episode({self.variable_name})}}", + ) + + def as_date_variable(self) -> "StringDateVariable": + return StringDateVariable( + variable_name=self.variable_name, + definition=self.definition, + ) + + +@dataclass(frozen=True) +class StringDateVariable(StringVariable): + def get_string_date_metadata( + self, date_metadata_key: str, variable_name: Optional[str] = None + ) -> StringVariable: + return StringVariable( + variable_name=variable_name or date_metadata_key, + definition=f"""{{ + %string( + %map_get( + %to_date_metadata({self.variable_name}), + '{date_metadata_key}' + ) + ) + }}""", + ) + + def get_integer_date_metadata( + self, date_metadata_key: str, variable_name: str + ) -> "IntegerVariable": + return IntegerVariable( + variable_name=variable_name, + definition=f"""{{ + %int( + %map_get( + %to_date_metadata({self.variable_name}), + '{date_metadata_key}' + ) + ) + }}""", + ) + + +@dataclass(frozen=True) +class IntegerVariable(Variable): + @classmethod + def human_readable_type(cls) -> str: + return Integer.__name__ + + def to_padded_int(self, variable_name: str, pad: int) -> StringVariable: + return StringVariable( + variable_name=variable_name, definition=f"{{%pad_zero({self.variable_name}, {pad})}}" + ) + + +@dataclass(frozen=True) +class ArrayVariable(Variable): + @classmethod + def human_readable_type(cls) -> str: + return Array.__name__ + + +@dataclass(frozen=True) +class MapVariable(Variable): + @classmethod + def human_readable_type(cls) -> str: + return Map.__name__ + + +@dataclass(frozen=True) +class MetadataVariable(Variable, ABC): + metadata_key: str + + +@dataclass(frozen=True) +class MapMetadataVariable(MetadataVariable, MapVariable): + @classmethod + def from_entry( + cls, + metadata_key: str, + variable_name: Optional[str] = None, + default: Optional["MapMetadataVariable" | Dict] = None, + ) -> "MapMetadataVariable": + return _get( + "map", + metadata_variable_name=ENTRY_METADATA_VARIABLE_NAME, + metadata_key=metadata_key, + variable_name=variable_name, + default=default, + as_type=MapMetadataVariable, + ) + + +@dataclass(frozen=True) +class ArrayMetadataVariable(MetadataVariable, ArrayVariable): + @classmethod + def from_entry( + cls, + metadata_key: str, + variable_name: Optional[str] = None, + default: Optional["ArrayMetadataVariable" | List] = None, + ) -> "ArrayMetadataVariable": + return _get( + "array", + metadata_variable_name=ENTRY_METADATA_VARIABLE_NAME, + metadata_key=metadata_key, + variable_name=variable_name, + default=default, + as_type=ArrayMetadataVariable, + ) + + +@dataclass(frozen=True) +class StringMetadataVariable(MetadataVariable, StringVariable): + @classmethod + def from_entry( + cls, + metadata_key: str, + variable_name: Optional[str] = None, + default: Optional[StringVariable | str] = None, + ) -> "StringMetadataVariable": + return _get( + "string", + metadata_variable_name=ENTRY_METADATA_VARIABLE_NAME, + metadata_key=metadata_key, + variable_name=variable_name, + default=default, + as_type=StringMetadataVariable, + ) + + @classmethod + def from_playlist( + cls, + metadata_key: str, + variable_name: Optional[str] = None, + default: Optional[StringVariable | str] = None, + ) -> "StringMetadataVariable": + return _get( + "string", + metadata_variable_name=PLAYLIST_METADATA_VARIABLE_NAME, + metadata_key=metadata_key, + variable_name=variable_name, + default=default, + as_type=StringMetadataVariable, + ) + + @classmethod + def from_source( + cls, + metadata_key: str, + variable_name: Optional[str] = None, + default: Optional[StringVariable | str] = None, + ) -> "StringMetadataVariable": + return _get( + "string", + metadata_variable_name=SOURCE_METADATA_VARIABLE_NAME, + metadata_key=metadata_key, + variable_name=variable_name, + default=default, + as_type=StringMetadataVariable, + ) + + +@dataclass(frozen=True) +class IntegerMetadataVariable(MetadataVariable, IntegerVariable): + @classmethod + def from_entry( + cls, + metadata_key: str, + variable_name: Optional[str] = None, + default: Optional[IntegerVariable | int] = None, + ) -> "IntegerMetadataVariable": + return _get( + "int", + metadata_variable_name=ENTRY_METADATA_VARIABLE_NAME, + metadata_key=metadata_key, + variable_name=variable_name, + default=default, + as_type=IntegerMetadataVariable, + ) + + @classmethod + def from_playlist( + cls, + metadata_key: str, + variable_name: Optional[str] = None, + default: Optional[IntegerVariable | int] = None, + ) -> "IntegerMetadataVariable": + return _get( + "int", + metadata_variable_name=PLAYLIST_METADATA_VARIABLE_NAME, + metadata_key=metadata_key, + variable_name=variable_name, + default=default, + as_type=IntegerMetadataVariable, + ) diff --git a/src/ytdl_sub/entries/variables/override_variables.py b/src/ytdl_sub/entries/variables/override_variables.py index 4e06d441..f3ee78b1 100644 --- a/src/ytdl_sub/entries/variables/override_variables.py +++ b/src/ytdl_sub/entries/variables/override_variables.py @@ -1,5 +1,5 @@ from ytdl_sub.entries.script.function_scripts import CUSTOM_FUNCTION_SCRIPTS -from ytdl_sub.entries.script.variable_scripts import VARIABLE_SCRIPTS +from ytdl_sub.entries.script.variable_definitions import VARIABLE_SCRIPTS from ytdl_sub.script.functions import Functions from ytdl_sub.script.utils.name_validation import is_valid_name diff --git a/src/ytdl_sub/utils/scriptable.py b/src/ytdl_sub/utils/scriptable.py index 105bce98..aba9d1fc 100644 --- a/src/ytdl_sub/utils/scriptable.py +++ b/src/ytdl_sub/utils/scriptable.py @@ -5,9 +5,9 @@ from typing import Dict from typing import Set from ytdl_sub.entries.script.function_scripts import CUSTOM_FUNCTION_SCRIPTS -from ytdl_sub.entries.script.variable_definitions import Variable -from ytdl_sub.entries.script.variable_scripts import UNRESOLVED_VARIABLES -from ytdl_sub.entries.script.variable_scripts import VARIABLE_SCRIPTS +from ytdl_sub.entries.script.variable_definitions import UNRESOLVED_VARIABLES +from ytdl_sub.entries.script.variable_definitions import VARIABLE_SCRIPTS +from ytdl_sub.entries.script.variable_types import Variable from ytdl_sub.script.script import Script from ytdl_sub.script.utils.exceptions import RuntimeException from ytdl_sub.utils.exceptions import StringFormattingException