This commit is contained in:
Jesse Bannon 2023-12-09 00:32:08 -08:00
parent 97949b3e13
commit 1d465a7d69
6 changed files with 145 additions and 182 deletions

View file

@ -31,6 +31,7 @@ from ytdl_sub.script.utils.exceptions import VariableDoesNotExist
from ytdl_sub.utils.exceptions import StringFormattingVariableNotFoundException from ytdl_sub.utils.exceptions import StringFormattingVariableNotFoundException
from ytdl_sub.utils.exceptions import ValidationException from ytdl_sub.utils.exceptions import ValidationException
from ytdl_sub.utils.logger import Logger from ytdl_sub.utils.logger import Logger
from ytdl_sub.utils.script import ScriptUtils
from ytdl_sub.utils.scriptable import Scriptable from ytdl_sub.utils.scriptable import Scriptable
from ytdl_sub.utils.yaml import dump_yaml from ytdl_sub.utils.yaml import dump_yaml
from ytdl_sub.validators.strict_dict_validator import StrictDictValidator from ytdl_sub.validators.strict_dict_validator import StrictDictValidator
@ -182,9 +183,9 @@ class Preset(_PresetShell):
def _script_builder(self) -> ScriptBuilder: def _script_builder(self) -> ScriptBuilder:
# Set the formatter variables to be the overrides # Set the formatter variables to be the overrides
script = ScriptBuilder( script = ScriptBuilder(
Scriptable.add_sanitized_variables(self.overrides.dict_with_format_strings) ScriptUtils.add_sanitized_variables(self.overrides.dict_with_format_strings)
) )
script.add_resolved(Scriptable.add_dummy_variables(self._source_variables)) script.add_resolved(ScriptUtils.add_dummy_variables(self._source_variables))
return script return script
@functools.cached_property @functools.cached_property
@ -193,7 +194,7 @@ class Preset(_PresetShell):
Contains actualized script which should hold all Override variables Contains actualized script which should hold all Override variables
""" """
return self._script_builder.add_resolved( return self._script_builder.add_resolved(
Scriptable.add_dummy_variables(self._added_variables) ScriptUtils.add_dummy_variables(self._added_variables)
).partial_build() ).partial_build()
def __validate_and_get_plugins(self) -> PresetPlugins: def __validate_and_get_plugins(self) -> PresetPlugins:
@ -214,7 +215,7 @@ class Preset(_PresetShell):
script_builder = copy.deepcopy(self._script_builder) script_builder = copy.deepcopy(self._script_builder)
self.downloader_options.validate_with_variables(script=script_builder.partial_build()) self.downloader_options.validate_with_variables(script=script_builder.partial_build())
script_builder.add_resolved( script_builder.add_resolved(
Scriptable.add_dummy_variables(self.downloader_options.added_source_variables()) ScriptUtils.add_dummy_variables(self.downloader_options.added_source_variables())
) )
for _, plugin_options in sorted( for _, plugin_options in sorted(
@ -223,7 +224,7 @@ class Preset(_PresetShell):
# Validate current plugin using source + added plugin variables # Validate current plugin using source + added plugin variables
plugin_options.validate_with_variables(script=script_builder.partial_build()) plugin_options.validate_with_variables(script=script_builder.partial_build())
script_builder.add_resolved( script_builder.add_resolved(
Scriptable.add_dummy_variables(self.downloader_options.added_source_variables()) ScriptUtils.add_dummy_variables(self.downloader_options.added_source_variables())
) )
@functools.cache @functools.cache

View file

@ -1,52 +1,23 @@
import math import math
from typing import Any
from typing import Dict from typing import Dict
from typing import List from typing import List
from typing import Optional from typing import Optional
import mergedeep
from ytdl_sub.entries.base_entry import BaseEntry from ytdl_sub.entries.base_entry import BaseEntry
from ytdl_sub.entries.base_entry import TBaseEntry from ytdl_sub.entries.base_entry import TBaseEntry
from ytdl_sub.entries.entry import Entry from ytdl_sub.entries.entry import Entry
from ytdl_sub.entries.variables.kwargs import DESCRIPTION from ytdl_sub.entries.script.variable_definitions import VARIABLES as v
from ytdl_sub.entries.variables.kwargs import PLAYLIST_COUNT from ytdl_sub.entries.script.variable_definitions import MetadataVariable
from ytdl_sub.entries.variables.kwargs import PLAYLIST_DESCRIPTION from ytdl_sub.entries.script.variable_scripts import ENTRY_DEFAULT_VARIABLES
from ytdl_sub.entries.variables.kwargs import PLAYLIST_ENTRY from ytdl_sub.entries.script.variable_scripts import ENTRY_REQUIRED_VARIABLES
from ytdl_sub.entries.variables.kwargs import PLAYLIST_INDEX
from ytdl_sub.entries.variables.kwargs import PLAYLIST_MAX_UPLOAD_YEAR
from ytdl_sub.entries.variables.kwargs import PLAYLIST_MAX_UPLOAD_YEAR_TRUNCATED
from ytdl_sub.entries.variables.kwargs import PLAYLIST_TITLE
from ytdl_sub.entries.variables.kwargs import PLAYLIST_UID
from ytdl_sub.entries.variables.kwargs import PLAYLIST_UPLOADER
from ytdl_sub.entries.variables.kwargs import PLAYLIST_UPLOADER_ID
from ytdl_sub.entries.variables.kwargs import PLAYLIST_UPLOADER_URL
from ytdl_sub.entries.variables.kwargs import PLAYLIST_WEBPAGE_URL
from ytdl_sub.entries.variables.kwargs import SOURCE_COUNT
from ytdl_sub.entries.variables.kwargs import SOURCE_DESCRIPTION
from ytdl_sub.entries.variables.kwargs import SOURCE_ENTRY
from ytdl_sub.entries.variables.kwargs import SOURCE_INDEX
from ytdl_sub.entries.variables.kwargs import SOURCE_TITLE
from ytdl_sub.entries.variables.kwargs import SOURCE_UID
from ytdl_sub.entries.variables.kwargs import SOURCE_UPLOADER
from ytdl_sub.entries.variables.kwargs import SOURCE_UPLOADER_ID
from ytdl_sub.entries.variables.kwargs import SOURCE_UPLOADER_URL
from ytdl_sub.entries.variables.kwargs import SOURCE_WEBPAGE_URL
from ytdl_sub.entries.variables.kwargs import TITLE
from ytdl_sub.entries.variables.kwargs import UID
from ytdl_sub.entries.variables.kwargs import UPLOADER
from ytdl_sub.entries.variables.kwargs import UPLOADER_ID
from ytdl_sub.entries.variables.kwargs import UPLOADER_URL
from ytdl_sub.entries.variables.kwargs import WEBPAGE_URL
class ParentType:
PLAYLIST = "playlist"
SOURCE = "source"
def _sort_entries(entries: List[TBaseEntry]) -> List[TBaseEntry]: def _sort_entries(entries: List[TBaseEntry]) -> List[TBaseEntry]:
"""Try sorting by playlist_id first, then fall back to uid""" """Try sorting by playlist_id first, then fall back to uid"""
return sorted(entries, key=lambda ent: (ent.kwargs_get(PLAYLIST_INDEX, math.inf), ent.uid)) return sorted(
entries, key=lambda ent: (ent.kwargs_get(v.playlist_index.metadata_key, math.inf), ent.uid)
)
class EntryParent(BaseEntry): class EntryParent(BaseEntry):
@ -73,86 +44,38 @@ class EntryParent(BaseEntry):
self.entry_children() self.entry_children()
) )
def _playlist_variables(self, idx: int, children: List[TBaseEntry], parent_type: str) -> Dict: def _sibling_entry_metadata(self) -> List[Dict[str, Any]]:
_count = self.kwargs_get(PLAYLIST_COUNT, len(children)) sibling_entry_metadata: List[Dict[str, Any]] = []
_index = children[idx].kwargs_get(PLAYLIST_INDEX, idx + 1) variable_filter: List[MetadataVariable] = list(ENTRY_REQUIRED_VARIABLES.keys()) + list(
ENTRY_DEFAULT_VARIABLES.keys()
if parent_type == ParentType.SOURCE: )
return {SOURCE_INDEX: _index, SOURCE_COUNT: _count} for entry in self.entry_children():
return { sibling_entry_metadata.append(
SOURCE_INDEX: self.kwargs_get(SOURCE_INDEX, 1), {var.metadata_key: entry.kwargs_get(var.metadata_key) for var in variable_filter}
SOURCE_COUNT: self.kwargs_get(SOURCE_INDEX, 1), )
PLAYLIST_INDEX: _index, return sibling_entry_metadata
PLAYLIST_COUNT: _count,
}
def _parent_variables(self, parent_type: str) -> Dict:
def _(source_key: str, playlist_key: str) -> str:
return playlist_key if parent_type == ParentType.PLAYLIST else source_key
def __(key: str) -> Optional[str]:
return self.kwargs_get(key=key)
return {
_(SOURCE_ENTRY, PLAYLIST_ENTRY): self._kwargs,
_(SOURCE_TITLE, PLAYLIST_TITLE): __(TITLE),
_(SOURCE_WEBPAGE_URL, PLAYLIST_WEBPAGE_URL): __(WEBPAGE_URL),
_(SOURCE_UID, PLAYLIST_UID): __(UID),
_(SOURCE_DESCRIPTION, PLAYLIST_DESCRIPTION): __(DESCRIPTION),
_(SOURCE_UPLOADER, PLAYLIST_UPLOADER): __(UPLOADER),
_(SOURCE_UPLOADER_ID, PLAYLIST_UPLOADER_ID): __(UPLOADER_ID),
_(SOURCE_UPLOADER_URL, PLAYLIST_UPLOADER_URL): __(UPLOADER_URL),
}
def _get_entry_children_variable_list(self, variable_name: str) -> List[str | int]:
return [getattr(entry_child, variable_name) for entry_child in self.entry_children()]
def _entry_aggregate_variables(self) -> Dict:
if not self.entry_children():
return {}
return {
PLAYLIST_MAX_UPLOAD_YEAR: max(self._get_entry_children_variable_list("upload_year")),
PLAYLIST_MAX_UPLOAD_YEAR_TRUNCATED: max(
self._get_entry_children_variable_list("upload_year_truncated")
),
}
# pylint: disable=protected-access
def _set_child_variables(self, parents: Optional[List["EntryParent"]] = None) -> "EntryParent": def _set_child_variables(self, parents: Optional[List["EntryParent"]] = None) -> "EntryParent":
if parents is None: if parents is None:
parents = [self] parents = [self]
self.add_kwargs(
self._playlist_variables(idx=0, children=parents, parent_type=ParentType.SOURCE)
)
kwargs_to_add: Dict = {} kwargs_to_add: Dict[str, Any] = {
v.sibling_entry_metadata.metadata_key: self._sibling_entry_metadata()
}
if len(parents) >= 1: if len(parents) >= 1:
mergedeep.merge(kwargs_to_add, parents[-1]._parent_variables(ParentType.PLAYLIST)) kwargs_to_add[v.playlist_metadata.metadata_key] = parents[-1]._kwargs
if len(parents) >= 2: if len(parents) >= 2:
mergedeep.merge(kwargs_to_add, parents[-2]._parent_variables(ParentType.SOURCE)) kwargs_to_add[v.source_metadata.metadata_key] = parents[-2]._kwargs
if len(parents) >= 3: if len(parents) >= 3:
raise ValueError( raise ValueError(
"ytdl-sub currently does support more than 3 layers of playlists/entries. " "ytdl-sub currently does support more than 3 layers of playlists/entries. "
"If you encounter this error, please file a ticket with the URLs used." "If you encounter this error, please file a ticket with the URLs used."
) )
mergedeep.merge(kwargs_to_add, self._entry_aggregate_variables()) for entry_child in self.entry_children():
for idx, entry_child in enumerate(self.entry_children()):
entry_child.add_kwargs(
self._playlist_variables(
idx=idx, children=self.entry_children(), parent_type=ParentType.PLAYLIST
)
)
entry_child.add_kwargs(kwargs_to_add) entry_child.add_kwargs(kwargs_to_add)
for idx, parent_child in enumerate(self.parent_children()): for parent_child in self.parent_children():
parent_child.add_kwargs(
self._playlist_variables(
idx=idx, children=self.parent_children(), parent_type=ParentType.SOURCE
)
)
parent_child._set_child_variables(parents=parents + [parent_child]) parent_child._set_child_variables(parents=parents + [parent_child])
return self return self

View file

@ -27,7 +27,12 @@ class MetadataVariable(Variable):
@dataclass(frozen=True) @dataclass(frozen=True)
class DerivedMetadata(MetadataVariable, Metadata): class RelativeMetadata(MetadataVariable, Metadata):
pass
@dataclass(frozen=True)
class SiblingMetadata(MetadataVariable):
pass pass
@ -42,6 +47,18 @@ class _Variables:
""" """
return Metadata("entry_metadata") return Metadata("entry_metadata")
@property
def playlist_metadata(self) -> RelativeMetadata:
return RelativeMetadata("playlist_metadata", metadata_key="playlist_metadata")
@property
def source_metadata(self) -> RelativeMetadata:
return RelativeMetadata("source_metadata", metadata_key="source_metadata")
@property
def sibling_entry_metadata(self) -> SiblingMetadata:
return SiblingMetadata("sibling_entry_metadata", metadata_key="sibling_entry_metadata")
@property @property
def uid(self) -> MetadataVariable: def uid(self) -> MetadataVariable:
""" """
@ -223,7 +240,7 @@ class _Variables:
Name of the source (i.e. channel with multiple playlists) if it exists, otherwise Name of the source (i.e. channel with multiple playlists) if it exists, otherwise
returns its playlist_title. returns its playlist_title.
""" """
return MetadataVariable("source_title", metadata_key="source_title") return MetadataVariable("source_title", metadata_key=self.title.metadata_key)
@property @property
def source_title_sanitized(self) -> Variable: def source_title_sanitized(self) -> Variable:
@ -243,7 +260,7 @@ class _Variables:
str str
The source unique id if it exists, otherwise returns the playlist unique ID. The source unique id if it exists, otherwise returns the playlist unique ID.
""" """
return MetadataVariable("source_uid", metadata_key="source_uid") return MetadataVariable("source_uid", metadata_key=self.uid.metadata_key)
@property @property
def source_index(self) -> MetadataVariable: def source_index(self) -> MetadataVariable:
@ -256,7 +273,7 @@ class _Variables:
It is recommended to not use this unless you know the source will never add new content 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). (it is easy for this value to change).
""" """
return MetadataVariable("source_index", metadata_key="source_index") return MetadataVariable("source_index", metadata_key=self.playlist_index.metadata_key)
@property @property
def source_index_padded(self) -> Variable: def source_index_padded(self) -> Variable:
@ -276,7 +293,7 @@ class _Variables:
int int
The source count if it exists, otherwise returns the playlist count. The source count if it exists, otherwise returns the playlist count.
""" """
return MetadataVariable("source_count", metadata_key="source_count") return MetadataVariable("source_count", metadata_key=self.playlist_count.metadata_key)
@property @property
def source_webpage_url(self) -> MetadataVariable: def source_webpage_url(self) -> MetadataVariable:
@ -286,7 +303,7 @@ class _Variables:
str str
The source webpage url if it exists, otherwise returns the playlist webpage url. The source webpage url if it exists, otherwise returns the playlist webpage url.
""" """
return MetadataVariable("source_webpage_url", metadata_key="source_webpage_url") return MetadataVariable("source_webpage_url", metadata_key=self.webpage_url.metadata_key)
@property @property
def source_description(self) -> MetadataVariable: def source_description(self) -> MetadataVariable:
@ -296,7 +313,7 @@ class _Variables:
str str
The source description if it exists, otherwise returns the playlist description. The source description if it exists, otherwise returns the playlist description.
""" """
return MetadataVariable("source_description", metadata_key="source_description") return MetadataVariable("source_description", metadata_key=self.description.metadata_key)
@property @property
def playlist_uid(self) -> MetadataVariable: def playlist_uid(self) -> MetadataVariable:
@ -306,7 +323,7 @@ class _Variables:
str str
The playlist unique ID if it exists, otherwise return Variable("") The playlist unique ID if it exists, otherwise return Variable("")
""" """
return MetadataVariable(variable_name="playlist_uid", metadata_key="playlist_uid") return MetadataVariable(variable_name="playlist_uid", metadata_key=self.uid.metadata_key)
@property @property
def playlist_title(self) -> MetadataVariable: def playlist_title(self) -> MetadataVariable:
@ -316,7 +333,9 @@ class _Variables:
str str
Name of its parent playlist/channel if it exists, otherwise returns its title. Name of its parent playlist/channel if it exists, otherwise returns its title.
""" """
return MetadataVariable(variable_name="playlist_title", metadata_key="playlist_title") return MetadataVariable(
variable_name="playlist_title", metadata_key=self.title.metadata_key
)
@property @property
def playlist_title_sanitized(self) -> Variable: def playlist_title_sanitized(self) -> Variable:
@ -413,7 +432,7 @@ class _Variables:
The playlist description if it exists, otherwise returns the entry's description. The playlist description if it exists, otherwise returns the entry's description.
""" """
return MetadataVariable( return MetadataVariable(
variable_name="playlist_description", metadata_key="playlist_description" variable_name="playlist_description", metadata_key=self.description.metadata_key
) )
@property @property
@ -425,7 +444,7 @@ class _Variables:
The playlist webpage url if it exists. Otherwise, returns the entry webpage url. The playlist webpage url if it exists. Otherwise, returns the entry webpage url.
""" """
return MetadataVariable( return MetadataVariable(
variable_name="playlist_webpage_url", metadata_key="playlist_webpage_url" variable_name="playlist_webpage_url", metadata_key=self.webpage_url.metadata_key
) )
@property @property
@ -461,7 +480,7 @@ class _Variables:
str str
The playlist uploader id if it exists, otherwise returns the entry uploader ID. The playlist uploader id if it exists, otherwise returns the entry uploader ID.
""" """
return MetadataVariable("playlist_uploader_id", metadata_key="playlist_uploader_id") return MetadataVariable("playlist_uploader_id", metadata_key=self.uploader_id.metadata_key)
@property @property
def playlist_uploader(self) -> MetadataVariable: def playlist_uploader(self) -> MetadataVariable:
@ -471,7 +490,7 @@ class _Variables:
str str
The playlist uploader if it exists, otherwise return Variable("") The playlist uploader if it exists, otherwise return Variable("")
""" """
return MetadataVariable("playlist_uploader", metadata_key="playlist_uploader") return MetadataVariable("playlist_uploader", metadata_key=self.uploader.metadata_key)
@property @property
def playlist_uploader_sanitized(self) -> Variable: def playlist_uploader_sanitized(self) -> Variable:
@ -491,7 +510,9 @@ class _Variables:
str str
The playlist uploader url if it exists, otherwise returns the playlist webpage_url. The playlist uploader url if it exists, otherwise returns the playlist webpage_url.
""" """
return MetadataVariable("playlist_uploader_url", metadata_key="playlist_uploader_url") return MetadataVariable(
"playlist_uploader_url", metadata_key=self.uploader_url.metadata_key
)
@property @property
def source_uploader_id(self) -> MetadataVariable: def source_uploader_id(self) -> MetadataVariable:
@ -501,7 +522,7 @@ class _Variables:
str str
The source uploader id if it exists, otherwise returns the playlist_uploader_id The source uploader id if it exists, otherwise returns the playlist_uploader_id
""" """
return MetadataVariable("source_uploader_id", metadata_key="source_uploader_id") return MetadataVariable("source_uploader_id", metadata_key=self.uploader_id.metadata_key)
@property @property
def source_uploader(self) -> MetadataVariable: def source_uploader(self) -> MetadataVariable:
@ -511,7 +532,7 @@ class _Variables:
str str
The source uploader if it exists, otherwise return Variable("") The source uploader if it exists, otherwise return Variable("")
""" """
return MetadataVariable("source_uploader", metadata_key="source_uploader") return MetadataVariable("source_uploader", metadata_key=self.uploader.metadata_key)
@property @property
def source_uploader_url(self) -> MetadataVariable: def source_uploader_url(self) -> MetadataVariable:
@ -521,7 +542,7 @@ class _Variables:
str str
The source uploader url if it exists, otherwise returns the source webpage_url. The source uploader url if it exists, otherwise returns the source webpage_url.
""" """
return MetadataVariable("source_uploader_url", metadata_key="source_uploader_url") return MetadataVariable("source_uploader_url", metadata_key=self.uploader_url.metadata_key)
@property @property
def creator(self) -> MetadataVariable: def creator(self) -> MetadataVariable:

View file

@ -1,4 +1,5 @@
from typing import Dict from typing import Dict
from typing import List
from typing import Optional from typing import Optional
from typing import Set from typing import Set
@ -35,7 +36,7 @@ def date_metadata(date_key: Variable, metadata_key: str) -> str:
def _get_internal( def _get_internal(
metadata: Metadata, key: MetadataVariable, default: Optional[Variable | str | int] metadata: Metadata, key: MetadataVariable, default: Optional[Variable | str | int | Dict | List]
) -> str: ) -> str:
if default is None: if default is None:
# TODO: assert with good error message if key DNE # TODO: assert with good error message if key DNE
@ -44,6 +45,10 @@ def _get_internal(
out = f"%map_get_non_empty({metadata.variable_name}, '{key.metadata_key}', {default.variable_name})" out = f"%map_get_non_empty({metadata.variable_name}, '{key.metadata_key}', {default.variable_name})"
elif isinstance(default, str): elif isinstance(default, str):
out = f"%map_get_non_empty({metadata.variable_name}, '{key.metadata_key}', '{default}')" 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: else:
out = f"%map_get_non_empty({metadata.variable_name}, '{key.metadata_key}', {default})" out = f"%map_get_non_empty({metadata.variable_name}, '{key.metadata_key}', {default})"
@ -56,7 +61,9 @@ def _get_int(
return f"{{%int({_get_internal(metadata=metadata, key=key, default=default)})}}" return f"{{%int({_get_internal(metadata=metadata, key=key, default=default)})}}"
def _get(metadata: Metadata, key: MetadataVariable, default: Optional[Variable | str | int]) -> str: def _get(
metadata: Metadata, key: MetadataVariable, default: Optional[Variable | str | int | Dict | List]
) -> str:
return f"{{{_get_internal(metadata=metadata, key=key, default=default)}}}" return f"{{{_get_internal(metadata=metadata, key=key, default=default)}}}"
@ -64,7 +71,9 @@ def _get(metadata: Metadata, key: MetadataVariable, default: Optional[Variable |
# Entry Getters # Entry Getters
def entry_get(key: MetadataVariable, default: Optional[Variable | str | int] = None) -> str: def entry_get(
key: MetadataVariable, default: Optional[Variable | str | int | Dict | List] = None
) -> str:
return _get(metadata=v.entry_metadata, key=key, default=default) return _get(metadata=v.entry_metadata, key=key, default=default)
@ -77,11 +86,11 @@ def entry_get_int(key: MetadataVariable, default: Optional[Variable | int] = Non
def playlist_get(key: MetadataVariable, default: Optional[Variable | str | int] = None) -> str: def playlist_get(key: MetadataVariable, default: Optional[Variable | str | int] = None) -> str:
return _get(metadata=v.entry_metadata, key=key, default=default) return _get(metadata=v.playlist_metadata, key=key, default=default)
def playlist_get_int(key: MetadataVariable, default: Optional[Variable | int] = None) -> str: def playlist_get_int(key: MetadataVariable, default: Optional[Variable | int] = None) -> str:
return _get_int(metadata=v.entry_metadata, key=key, default=default) return _get_int(metadata=v.playlist_metadata, key=key, default=default)
############################################################################################### ###############################################################################################
@ -89,11 +98,11 @@ def playlist_get_int(key: MetadataVariable, default: Optional[Variable | int] =
def source_get(key: MetadataVariable, default: Optional[Variable | str | int] = None) -> str: def source_get(key: MetadataVariable, default: Optional[Variable | str | int] = None) -> str:
return _get(metadata=v.entry_metadata, key=key, default=default) return _get(metadata=v.source_metadata, key=key, default=default)
def source_get_int(key: MetadataVariable, default: Optional[Variable | int] = None) -> str: def source_get_int(key: MetadataVariable, default: Optional[Variable | int] = None) -> str:
return _get_int(metadata=v.entry_metadata, key=key, default=default) return _get_int(metadata=v.source_metadata, key=key, default=default)
############################################################################################### ###############################################################################################
@ -106,6 +115,12 @@ ENTRY_HARDCODED_VARIABLES: Dict[Variable, str] = {
v.thumbnail_ext: "jpg", v.thumbnail_ext: "jpg",
} }
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, "{ [] }"),
}
ENTRY_REQUIRED_VARIABLES: Dict[MetadataVariable, str] = { ENTRY_REQUIRED_VARIABLES: Dict[MetadataVariable, str] = {
v.uid: entry_get(v.uid), v.uid: entry_get(v.uid),
v.extractor_key: entry_get(v.extractor_key), v.extractor_key: entry_get(v.extractor_key),
@ -133,7 +148,6 @@ ENTRY_DEFAULT_VARIABLES: Dict[MetadataVariable, str] = {
ENTRY_INJECTED_VARIABLES: Dict[Variable, str] = { ENTRY_INJECTED_VARIABLES: Dict[Variable, str] = {
v.download_index: "{%int(1)}", v.download_index: "{%int(1)}",
v.upload_date_index: "{%int(1)}", v.upload_date_index: "{%int(1)}",
v.playlist_max_upload_year: f"{{{v.upload_year.variable_name}}}",
v.comments: "", v.comments: "",
v.requested_subtitles: "", v.requested_subtitles: "",
v.sponsorblock_chapters: "", v.sponsorblock_chapters: "",
@ -216,7 +230,6 @@ PLAYLIST_DERIVED_VARIABLES: Dict[Variable, str] = {
v.playlist_index_padded6: pad_int(v.playlist_index, 6), v.playlist_index_padded6: pad_int(v.playlist_index, 6),
v.playlist_index_reversed_padded6: pad_int(v.playlist_index_reversed, 6), v.playlist_index_reversed_padded6: pad_int(v.playlist_index_reversed, 6),
v.playlist_uploader_sanitized: sanitized(v.playlist_uploader), v.playlist_uploader_sanitized: sanitized(v.playlist_uploader),
v.playlist_max_upload_year_truncated: f"{{%int(%slice(%string({v.playlist_max_upload_year.variable_name}), 2))}}",
} }
@ -237,11 +250,20 @@ SOURCE_DERIVED_VARIABLES: Dict[Variable, str] = {
v.source_index_padded: pad_int(v.source_index, 2), v.source_index_padded: pad_int(v.source_index, 2),
} }
SIBLING_VARIABLES: Dict[Variable, str] = {
v.playlist_max_upload_year: "TODO!!!",
}
SIBLING_DERIVED_VARIABLES: Dict[Variable, str] = {
v.playlist_max_upload_year_truncated: f"{{%int(%slice(%string({v.playlist_max_upload_year.variable_name}), 2))}}",
}
_VARIABLE_SCRIPTS: Dict[Variable, str] = {} _VARIABLE_SCRIPTS: Dict[Variable, str] = {}
mergedeep.merge( mergedeep.merge(
_VARIABLE_SCRIPTS, _VARIABLE_SCRIPTS,
ENTRY_EMPTY_METADATA, ENTRY_EMPTY_METADATA,
ENTRY_HARDCODED_VARIABLES, ENTRY_HARDCODED_VARIABLES,
ENTRY_RELATIVE_VARIABLES,
ENTRY_REQUIRED_VARIABLES, ENTRY_REQUIRED_VARIABLES,
ENTRY_DEFAULT_VARIABLES, ENTRY_DEFAULT_VARIABLES,
ENTRY_INJECTED_VARIABLES, ENTRY_INJECTED_VARIABLES,

View file

@ -0,0 +1,42 @@
import json
from typing import Any
from typing import Dict
from typing import Iterable
from ytdl_sub.script.types.resolvable import Resolvable
from ytdl_sub.script.types.resolvable import String
class ScriptUtils:
@classmethod
def add_dummy_variables(cls, variables: Iterable[str]) -> Dict[str, Resolvable]:
dummy_variables: Dict[str, Resolvable] = {}
for var in variables:
dummy_variables[var] = String("dummy_string")
dummy_variables[f"{var}_sanitized"] = String("dummy_string")
return dummy_variables
@classmethod
def add_sanitized_variables(cls, variables: Dict[str, str]) -> Dict[str, str]:
sanitized_variables = {
f"{name}_sanitized": f"{{%sanitize({name})}}" for name in variables.keys()
}
return dict(variables, **sanitized_variables)
@classmethod
def to_script(cls, value: Any) -> str:
if value is None:
out = ""
elif isinstance(value, str):
out = value
elif isinstance(value, int):
out = f"{{%int({value})}}"
elif isinstance(value, float):
out = f"{{%float({value})}}"
elif isinstance(value, bool):
out = f"{{%bool({value})}}"
else:
out = json.dumps(value)
return out

View file

@ -1,62 +1,16 @@
import copy import copy
import json
from abc import ABC from abc import ABC
from typing import Any from typing import Any
from typing import Dict from typing import Dict
from typing import Iterable
from typing import Set from typing import Set
from ytdl_sub.entries.script.variable_scripts import UNRESOLVED_VARIABLES 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_scripts import VARIABLE_SCRIPTS
from ytdl_sub.script.parser import parse
from ytdl_sub.script.script import Script from ytdl_sub.script.script import Script
from ytdl_sub.script.types.resolvable import Resolvable from ytdl_sub.utils.script import ScriptUtils
from ytdl_sub.script.types.resolvable import String
class Scriptable(ABC): class Scriptable(ABC):
@classmethod
def add_dummy_variables(cls, variables: Iterable[str]) -> Dict[str, Resolvable]:
dummy_variables: Dict[str, Resolvable] = {}
for var in variables:
dummy_variables[var] = String("dummy_string")
dummy_variables[f"{var}_sanitized"] = String("dummy_string")
return dummy_variables
@classmethod
def add_sanitized_variables(cls, variables: Dict[str, str]) -> Dict[str, str]:
sanitized_variables = {
f"{name}_sanitized": f"{{%sanitize({name})}}" for name in variables.keys()
}
return dict(variables, **sanitized_variables)
@classmethod
def wrappable_format_string(cls, format_string: str) -> str:
parsed = parse(format_string)
if resolvable := parsed.maybe_resolvable:
return f"'{str(resolvable)}'"
stripped_format_string = format_string.strip()
if stripped_format_string.startswith("{") and stripped_format_string.endswith("}"):
return stripped_format_string[1:-1]
return format_string
@classmethod
def to_script(cls, value: Any) -> str:
if value is None:
return ""
if isinstance(value, str):
return value
if isinstance(value, int):
return f"{{%int({value})}}"
if isinstance(value, float):
return f"{{%float({value})}}"
if isinstance(value, bool):
return f"{{%bool({value})}}"
return f"{{{json.dumps(value)}}}"
def __init__(self): def __init__(self):
self.script = Script(copy.deepcopy(VARIABLE_SCRIPTS)) self.script = Script(copy.deepcopy(VARIABLE_SCRIPTS))
self.unresolvable: Set[str] = copy.deepcopy(UNRESOLVED_VARIABLES) self.unresolvable: Set[str] = copy.deepcopy(UNRESOLVED_VARIABLES)
@ -66,8 +20,8 @@ class Scriptable(ABC):
def add(self, values: Dict[str, Any]) -> None: def add(self, values: Dict[str, Any]) -> None:
self.script.add( self.script.add(
self.add_sanitized_variables( ScriptUtils.add_sanitized_variables(
{name: self.to_script(value) for name, value in values.items()} {name: ScriptUtils.to_script(value) for name, value in values.items()}
) )
) )