ytdl-sub 2025.11.8__py3-none-any.whl → 2025.12.31__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- ytdl_sub/__init__.py +1 -1
- ytdl_sub/config/config_validator.py +8 -0
- ytdl_sub/config/overrides.py +44 -13
- ytdl_sub/config/plugin/preset_plugins.py +33 -0
- ytdl_sub/config/preset.py +36 -8
- ytdl_sub/config/preset_options.py +33 -2
- ytdl_sub/config/validators/variable_validation.py +23 -147
- ytdl_sub/downloaders/url/validators.py +2 -2
- ytdl_sub/entries/variables/override_variables.py +1 -4
- ytdl_sub/prebuilt_presets/helpers/download_deletion_options.yaml +1 -2
- ytdl_sub/prebuilt_presets/tv_show/tv_show_collection.yaml +5206 -644
- ytdl_sub/script/parser.py +6 -1
- ytdl_sub/script/script.py +117 -46
- ytdl_sub/script/types/function.py +11 -6
- ytdl_sub/script/types/syntax_tree.py +28 -2
- ytdl_sub/script/types/variable_dependency.py +4 -4
- ytdl_sub/script/utils/name_validation.py +21 -0
- ytdl_sub/subscriptions/base_subscription.py +29 -3
- ytdl_sub/subscriptions/subscription_download.py +3 -0
- ytdl_sub/utils/exceptions.py +4 -0
- ytdl_sub/utils/file_handler.py +49 -0
- ytdl_sub/utils/script.py +19 -2
- ytdl_sub/validators/string_formatter_validators.py +63 -30
- ytdl_sub/validators/validators.py +3 -13
- ytdl_sub/ytdl_additions/enhanced_download_archive.py +20 -0
- {ytdl_sub-2025.11.8.dist-info → ytdl_sub-2025.12.31.dist-info}/METADATA +2 -2
- {ytdl_sub-2025.11.8.dist-info → ytdl_sub-2025.12.31.dist-info}/RECORD +31 -31
- {ytdl_sub-2025.11.8.dist-info → ytdl_sub-2025.12.31.dist-info}/WHEEL +0 -0
- {ytdl_sub-2025.11.8.dist-info → ytdl_sub-2025.12.31.dist-info}/entry_points.txt +0 -0
- {ytdl_sub-2025.11.8.dist-info → ytdl_sub-2025.12.31.dist-info}/licenses/LICENSE +0 -0
- {ytdl_sub-2025.11.8.dist-info → ytdl_sub-2025.12.31.dist-info}/top_level.txt +0 -0
ytdl_sub/__init__.py
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
__pypi_version__ = "2025.
|
|
1
|
+
__pypi_version__ = "2025.12.31";__local_version__ = "2025.12.31+1abe2a4"
|
|
@@ -12,6 +12,8 @@ from ytdl_sub.config.defaults import DEFAULT_FFPROBE_PATH
|
|
|
12
12
|
from ytdl_sub.config.defaults import DEFAULT_LOCK_DIRECTORY
|
|
13
13
|
from ytdl_sub.config.defaults import MAX_FILE_NAME_BYTES
|
|
14
14
|
from ytdl_sub.prebuilt_presets import PREBUILT_PRESETS
|
|
15
|
+
from ytdl_sub.utils.exceptions import SubscriptionPermissionError
|
|
16
|
+
from ytdl_sub.utils.file_handler import FileHandler
|
|
15
17
|
from ytdl_sub.validators.file_path_validators import FFmpegFileValidator
|
|
16
18
|
from ytdl_sub.validators.file_path_validators import FFprobeFileValidator
|
|
17
19
|
from ytdl_sub.validators.strict_dict_validator import StrictDictValidator
|
|
@@ -147,6 +149,12 @@ class ConfigOptions(StrictDictValidator):
|
|
|
147
149
|
key="file_name_max_bytes", validator=IntValidator, default=MAX_FILE_NAME_BYTES
|
|
148
150
|
)
|
|
149
151
|
|
|
152
|
+
if not FileHandler.is_path_writable(self.working_directory):
|
|
153
|
+
raise SubscriptionPermissionError(
|
|
154
|
+
"ytdl-sub does not have permissions to the working directory: "
|
|
155
|
+
f"{self.working_directory}"
|
|
156
|
+
)
|
|
157
|
+
|
|
150
158
|
@property
|
|
151
159
|
def working_directory(self) -> str:
|
|
152
160
|
"""
|
ytdl_sub/config/overrides.py
CHANGED
|
@@ -1,17 +1,19 @@
|
|
|
1
1
|
from typing import Any
|
|
2
2
|
from typing import Dict
|
|
3
|
+
from typing import Iterable
|
|
3
4
|
from typing import Optional
|
|
4
5
|
from typing import Set
|
|
5
6
|
|
|
6
|
-
import mergedeep
|
|
7
|
-
|
|
8
7
|
from ytdl_sub.entries.entry import Entry
|
|
9
8
|
from ytdl_sub.entries.script.variable_definitions import VARIABLES
|
|
10
9
|
from ytdl_sub.entries.variables.override_variables import REQUIRED_OVERRIDE_VARIABLE_NAMES
|
|
11
10
|
from ytdl_sub.entries.variables.override_variables import OverrideHelpers
|
|
12
11
|
from ytdl_sub.script.parser import parse
|
|
13
12
|
from ytdl_sub.script.script import Script
|
|
13
|
+
from ytdl_sub.script.types.function import BuiltInFunction
|
|
14
14
|
from ytdl_sub.script.types.resolvable import Resolvable
|
|
15
|
+
from ytdl_sub.script.types.resolvable import String
|
|
16
|
+
from ytdl_sub.script.types.syntax_tree import SyntaxTree
|
|
15
17
|
from ytdl_sub.script.utils.exceptions import ScriptVariableNotResolved
|
|
16
18
|
from ytdl_sub.utils.exceptions import InvalidVariableNameException
|
|
17
19
|
from ytdl_sub.utils.exceptions import StringFormattingException
|
|
@@ -88,6 +90,24 @@ class Overrides(UnstructuredDictFormatterValidator, Scriptable):
|
|
|
88
90
|
|
|
89
91
|
return True
|
|
90
92
|
|
|
93
|
+
def ensure_variable_names_not_a_plugin(self, plugin_names: Iterable[str]) -> None:
|
|
94
|
+
"""
|
|
95
|
+
Throws an error if an override variable or function has the same name as a
|
|
96
|
+
preset key. This is to avoid confusion when accidentally defining things in
|
|
97
|
+
overrides that are meant to be in the preset.
|
|
98
|
+
"""
|
|
99
|
+
for name in self.keys:
|
|
100
|
+
if name.startswith("%"):
|
|
101
|
+
name = name[1:]
|
|
102
|
+
|
|
103
|
+
if name in plugin_names:
|
|
104
|
+
raise self._validation_exception(
|
|
105
|
+
f"Override variable with name {name} cannot be used since it is"
|
|
106
|
+
" the name of a plugin. Perhaps you meant to define it as a plugin? If so,"
|
|
107
|
+
" indent it left to make it at the same level as overrides.",
|
|
108
|
+
exception_class=InvalidVariableNameException,
|
|
109
|
+
)
|
|
110
|
+
|
|
91
111
|
def ensure_variable_name_valid(self, name: str) -> None:
|
|
92
112
|
"""
|
|
93
113
|
Ensures the variable name does not collide with any entry variables or built-in functions.
|
|
@@ -115,29 +135,35 @@ class Overrides(UnstructuredDictFormatterValidator, Scriptable):
|
|
|
115
135
|
)
|
|
116
136
|
|
|
117
137
|
def initial_variables(
|
|
118
|
-
self, unresolved_variables: Optional[Dict[str,
|
|
119
|
-
) -> Dict[str,
|
|
138
|
+
self, unresolved_variables: Optional[Dict[str, SyntaxTree]] = None
|
|
139
|
+
) -> Dict[str, SyntaxTree]:
|
|
120
140
|
"""
|
|
121
141
|
Returns
|
|
122
142
|
-------
|
|
123
143
|
Variables and format strings for all Override variables + additional variables (Optional)
|
|
124
144
|
"""
|
|
125
|
-
initial_variables: Dict[str,
|
|
126
|
-
|
|
127
|
-
initial_variables
|
|
128
|
-
|
|
129
|
-
unresolved_variables if unresolved_variables else {},
|
|
130
|
-
)
|
|
131
|
-
return ScriptUtils.add_sanitized_variables(initial_variables)
|
|
145
|
+
initial_variables: Dict[str, SyntaxTree] = self.dict_with_parsed_format_strings
|
|
146
|
+
if unresolved_variables:
|
|
147
|
+
initial_variables |= unresolved_variables
|
|
148
|
+
return ScriptUtils.add_sanitized_parsed_variables(initial_variables)
|
|
132
149
|
|
|
133
150
|
def initialize_script(self, unresolved_variables: Set[str]) -> "Overrides":
|
|
134
151
|
"""
|
|
135
152
|
Initialize the override script with any unresolved variables
|
|
136
153
|
"""
|
|
137
|
-
self.script.
|
|
154
|
+
self.script.add_parsed(
|
|
138
155
|
self.initial_variables(
|
|
139
156
|
unresolved_variables={
|
|
140
|
-
var_name:
|
|
157
|
+
var_name: SyntaxTree(
|
|
158
|
+
ast=[
|
|
159
|
+
BuiltInFunction(
|
|
160
|
+
name="throw",
|
|
161
|
+
args=[
|
|
162
|
+
String(f"Plugin variable {var_name} has not been created yet")
|
|
163
|
+
],
|
|
164
|
+
)
|
|
165
|
+
]
|
|
166
|
+
)
|
|
141
167
|
for var_name in unresolved_variables
|
|
142
168
|
}
|
|
143
169
|
)
|
|
@@ -158,10 +184,15 @@ class Overrides(UnstructuredDictFormatterValidator, Scriptable):
|
|
|
158
184
|
script = entry.script
|
|
159
185
|
unresolvable = entry.unresolvable
|
|
160
186
|
|
|
187
|
+
# Update the script internally so long as we are not supplying overrides
|
|
188
|
+
# that could alter the script with one-off state
|
|
189
|
+
update = function_overrides is None
|
|
190
|
+
|
|
161
191
|
try:
|
|
162
192
|
return script.resolve_once(
|
|
163
193
|
dict({"tmp_var": formatter.format_string}, **(function_overrides or {})),
|
|
164
194
|
unresolvable=unresolvable,
|
|
195
|
+
update=update,
|
|
165
196
|
)["tmp_var"]
|
|
166
197
|
except ScriptVariableNotResolved as exc:
|
|
167
198
|
raise StringFormattingException(
|
|
@@ -1,5 +1,7 @@
|
|
|
1
|
+
from typing import Iterable
|
|
1
2
|
from typing import List
|
|
2
3
|
from typing import Optional
|
|
4
|
+
from typing import Set
|
|
3
5
|
from typing import Tuple
|
|
4
6
|
from typing import Type
|
|
5
7
|
|
|
@@ -44,3 +46,34 @@ class PresetPlugins:
|
|
|
44
46
|
if plugin_type in plugin_option_types:
|
|
45
47
|
return self.plugin_options[plugin_option_types.index(plugin_type)]
|
|
46
48
|
return None
|
|
49
|
+
|
|
50
|
+
def get_added_and_modified_variables(
|
|
51
|
+
self, additional_options: List[OptionsValidator]
|
|
52
|
+
) -> Iterable[Tuple[OptionsValidator, Set[str], Set[str]]]:
|
|
53
|
+
"""
|
|
54
|
+
Iterates and returns the plugin options, added variables, modified variables
|
|
55
|
+
"""
|
|
56
|
+
for plugin_options in self.plugin_options + additional_options:
|
|
57
|
+
added_variables: Set[str] = set()
|
|
58
|
+
modified_variables: Set[str] = set()
|
|
59
|
+
|
|
60
|
+
for plugin_added_variables in plugin_options.added_variables(
|
|
61
|
+
unresolved_variables=set(),
|
|
62
|
+
).values():
|
|
63
|
+
added_variables |= set(plugin_added_variables)
|
|
64
|
+
|
|
65
|
+
for plugin_modified_variables in plugin_options.modified_variables().values():
|
|
66
|
+
modified_variables = plugin_modified_variables
|
|
67
|
+
|
|
68
|
+
yield plugin_options, added_variables, modified_variables
|
|
69
|
+
|
|
70
|
+
def get_all_variables(self, additional_options: List[OptionsValidator]) -> Set[str]:
|
|
71
|
+
"""
|
|
72
|
+
Returns set of all added and modified variables' names.
|
|
73
|
+
"""
|
|
74
|
+
all_variables: Set[str] = set()
|
|
75
|
+
for _, added, modified in self.get_added_and_modified_variables(additional_options):
|
|
76
|
+
all_variables.update(added)
|
|
77
|
+
all_variables.update(modified)
|
|
78
|
+
|
|
79
|
+
return all_variables
|
ytdl_sub/config/preset.py
CHANGED
|
@@ -2,6 +2,7 @@ import copy
|
|
|
2
2
|
from typing import Any
|
|
3
3
|
from typing import Dict
|
|
4
4
|
from typing import List
|
|
5
|
+
from typing import Set
|
|
5
6
|
|
|
6
7
|
from mergedeep import mergedeep
|
|
7
8
|
|
|
@@ -11,7 +12,6 @@ from ytdl_sub.config.plugin.plugin_mapping import PluginMapping
|
|
|
11
12
|
from ytdl_sub.config.plugin.preset_plugins import PresetPlugins
|
|
12
13
|
from ytdl_sub.config.preset_options import OutputOptions
|
|
13
14
|
from ytdl_sub.config.preset_options import YTDLOptions
|
|
14
|
-
from ytdl_sub.config.validators.variable_validation import VariableValidation
|
|
15
15
|
from ytdl_sub.downloaders.url.validators import MultiUrlValidator
|
|
16
16
|
from ytdl_sub.prebuilt_presets import PREBUILT_PRESET_NAMES
|
|
17
17
|
from ytdl_sub.prebuilt_presets import PUBLISHED_PRESET_NAMES
|
|
@@ -172,6 +172,37 @@ class Preset(_PresetShell):
|
|
|
172
172
|
mergedeep.merge({}, *reversed(presets_to_merge), strategy=mergedeep.Strategy.ADDITIVE)
|
|
173
173
|
)
|
|
174
174
|
|
|
175
|
+
def _initialize_overrides_script(self, overrides: Overrides) -> Overrides:
|
|
176
|
+
"""
|
|
177
|
+
Do some gymnastics to initialize the Overrides script.
|
|
178
|
+
"""
|
|
179
|
+
unresolved_variables: Set[str] = set()
|
|
180
|
+
|
|
181
|
+
for (
|
|
182
|
+
plugin_options,
|
|
183
|
+
added_variables,
|
|
184
|
+
modified_variables,
|
|
185
|
+
) in self.plugins.get_added_and_modified_variables(
|
|
186
|
+
additional_options=[self.downloader_options, self.output_options]
|
|
187
|
+
):
|
|
188
|
+
for added_variable in added_variables:
|
|
189
|
+
if not overrides.ensure_added_plugin_variable_valid(added_variable=added_variable):
|
|
190
|
+
# pylint: disable=protected-access
|
|
191
|
+
raise plugin_options._validation_exception(
|
|
192
|
+
f"Cannot use the variable name {added_variable} because it exists as a"
|
|
193
|
+
" built-in ytdl-sub variable name."
|
|
194
|
+
)
|
|
195
|
+
# pylint: enable=protected-access
|
|
196
|
+
|
|
197
|
+
# Set unresolved as variables that are added but do not exist as
|
|
198
|
+
# entry/override variables since they are created at run-time
|
|
199
|
+
unresolved_variables |= added_variables | modified_variables
|
|
200
|
+
|
|
201
|
+
# Initialize overrides with unresolved variables + modified variables to throw an error.
|
|
202
|
+
# For modified variables, this is to prevent a resolve(update=True) to setting any
|
|
203
|
+
# dependencies until it has been explicitly added
|
|
204
|
+
return overrides.initialize_script(unresolved_variables=unresolved_variables)
|
|
205
|
+
|
|
175
206
|
def __init__(self, config: ConfigValidator, name: str, value: Any):
|
|
176
207
|
super().__init__(name=name, value=value)
|
|
177
208
|
|
|
@@ -192,13 +223,10 @@ class Preset(_PresetShell):
|
|
|
192
223
|
)
|
|
193
224
|
|
|
194
225
|
self.plugins: PresetPlugins = self._validate_and_get_plugins()
|
|
195
|
-
self.overrides = self.
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
output_options=self.output_options,
|
|
200
|
-
plugins=self.plugins,
|
|
201
|
-
).initialize_preset_overrides(overrides=self.overrides).ensure_proper_usage()
|
|
226
|
+
self.overrides = self._initialize_overrides_script(
|
|
227
|
+
overrides=self._validate_key(key="overrides", validator=Overrides, default={})
|
|
228
|
+
)
|
|
229
|
+
self.overrides.ensure_variable_names_not_a_plugin(plugin_names=PRESET_KEYS)
|
|
202
230
|
|
|
203
231
|
@property
|
|
204
232
|
def name(self) -> str:
|
|
@@ -8,6 +8,9 @@ from ytdl_sub.config.overrides import Overrides
|
|
|
8
8
|
from ytdl_sub.config.plugin.plugin_operation import PluginOperation
|
|
9
9
|
from ytdl_sub.config.validators.options import OptionsDictValidator
|
|
10
10
|
from ytdl_sub.entries.script.variable_definitions import VARIABLES as v
|
|
11
|
+
from ytdl_sub.utils.exceptions import SubscriptionPermissionError
|
|
12
|
+
from ytdl_sub.utils.exceptions import ValidationException
|
|
13
|
+
from ytdl_sub.utils.file_handler import FileHandler
|
|
11
14
|
from ytdl_sub.validators.file_path_validators import OverridesStringFormatterFilePathValidator
|
|
12
15
|
from ytdl_sub.validators.file_path_validators import StringFormatterFileNameValidator
|
|
13
16
|
from ytdl_sub.validators.string_datetime import StringDatetimeValidator
|
|
@@ -57,12 +60,24 @@ class YTDLOptions(UnstructuredOverridesDictFormatterValidator):
|
|
|
57
60
|
def to_native_dict(self, overrides: Overrides) -> Dict:
|
|
58
61
|
"""
|
|
59
62
|
Materializes the entire ytdl-options dict from OverrideStringFormatters into
|
|
60
|
-
native python
|
|
63
|
+
native python.
|
|
61
64
|
"""
|
|
62
|
-
|
|
65
|
+
out = {
|
|
63
66
|
key: overrides.apply_overrides_formatter_to_native(val)
|
|
64
67
|
for key, val in self.dict.items()
|
|
65
68
|
}
|
|
69
|
+
if "cookiefile" in out:
|
|
70
|
+
if not FileHandler.is_file_existent(out["cookiefile"]):
|
|
71
|
+
raise ValidationException(
|
|
72
|
+
f"Specified cookiefile {out['cookiefile']} but it does not exist as a file."
|
|
73
|
+
)
|
|
74
|
+
|
|
75
|
+
if not FileHandler.is_file_readable(out["cookiefile"]):
|
|
76
|
+
raise SubscriptionPermissionError(
|
|
77
|
+
f"Cannot read cookiefile {out['cookiefile']} due to permissions issue."
|
|
78
|
+
)
|
|
79
|
+
|
|
80
|
+
return out
|
|
66
81
|
|
|
67
82
|
|
|
68
83
|
# Disable for proper docstring formatting
|
|
@@ -107,6 +122,7 @@ class OutputOptions(OptionsDictValidator):
|
|
|
107
122
|
"keep_max_files",
|
|
108
123
|
"download_archive_standardized_date",
|
|
109
124
|
"keep_files_date_eval",
|
|
125
|
+
"preserve_mtime",
|
|
110
126
|
}
|
|
111
127
|
|
|
112
128
|
@classmethod
|
|
@@ -170,6 +186,10 @@ class OutputOptions(OptionsDictValidator):
|
|
|
170
186
|
default=f"{{{v.upload_date_standardized.variable_name}}}",
|
|
171
187
|
)
|
|
172
188
|
|
|
189
|
+
self._preserve_mtime = self._validate_key_if_present(
|
|
190
|
+
key="preserve_mtime", validator=BoolValidator, default=False
|
|
191
|
+
)
|
|
192
|
+
|
|
173
193
|
if (
|
|
174
194
|
self._keep_files_before or self._keep_files_after or self._keep_max_files
|
|
175
195
|
) and not self.maintain_download_archive:
|
|
@@ -309,6 +329,17 @@ class OutputOptions(OptionsDictValidator):
|
|
|
309
329
|
"""
|
|
310
330
|
return self._keep_max_files
|
|
311
331
|
|
|
332
|
+
@property
|
|
333
|
+
def preserve_mtime(self) -> bool:
|
|
334
|
+
"""
|
|
335
|
+
:expected type: Optional[Boolean]
|
|
336
|
+
:description:
|
|
337
|
+
Preserve the video's original upload time as the file modification time.
|
|
338
|
+
When True, sets the file's mtime to match the video's upload_date from
|
|
339
|
+
yt-dlp metadata. Defaults to False.
|
|
340
|
+
"""
|
|
341
|
+
return self._preserve_mtime.value
|
|
342
|
+
|
|
312
343
|
def added_variables(self, unresolved_variables: Set[str]) -> Dict[PluginOperation, Set[str]]:
|
|
313
344
|
return {
|
|
314
345
|
# PluginOperation.MODIFY_ENTRY_METADATA: {
|
|
@@ -1,10 +1,4 @@
|
|
|
1
|
-
import copy
|
|
2
1
|
from typing import Dict
|
|
3
|
-
from typing import Iterable
|
|
4
|
-
from typing import List
|
|
5
|
-
from typing import Optional
|
|
6
|
-
from typing import Set
|
|
7
|
-
from typing import Tuple
|
|
8
2
|
|
|
9
3
|
from ytdl_sub.config.overrides import Overrides
|
|
10
4
|
from ytdl_sub.config.plugin.plugin_mapping import PluginMapping
|
|
@@ -13,155 +7,27 @@ from ytdl_sub.config.plugin.preset_plugins import PresetPlugins
|
|
|
13
7
|
from ytdl_sub.config.preset_options import OutputOptions
|
|
14
8
|
from ytdl_sub.config.validators.options import OptionsValidator
|
|
15
9
|
from ytdl_sub.downloaders.url.validators import MultiUrlValidator
|
|
16
|
-
from ytdl_sub.entries.variables.override_variables import REQUIRED_OVERRIDE_VARIABLE_NAMES
|
|
17
|
-
from ytdl_sub.script.script import Script
|
|
18
|
-
from ytdl_sub.script.script import _is_function
|
|
19
|
-
from ytdl_sub.utils.scriptable import BASE_SCRIPT
|
|
20
|
-
from ytdl_sub.validators.string_formatter_validators import to_variable_dependency_format_string
|
|
21
10
|
from ytdl_sub.validators.string_formatter_validators import validate_formatters
|
|
22
11
|
|
|
23
|
-
# Entry variables to mock during validation
|
|
24
|
-
_DUMMY_ENTRY_VARIABLES: Dict[str, str] = {
|
|
25
|
-
name: to_variable_dependency_format_string(
|
|
26
|
-
# pylint: disable=protected-access
|
|
27
|
-
script=BASE_SCRIPT,
|
|
28
|
-
parsed_format_string=BASE_SCRIPT._variables[name],
|
|
29
|
-
# pylint: enable=protected-access
|
|
30
|
-
)
|
|
31
|
-
for name in BASE_SCRIPT.variable_names
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
def _add_dummy_variables(variables: Iterable[str]) -> Dict[str, str]:
|
|
36
|
-
dummy_variables: Dict[str, str] = {}
|
|
37
|
-
for var in variables:
|
|
38
|
-
dummy_variables[var] = ""
|
|
39
|
-
dummy_variables[f"{var}_sanitized"] = ""
|
|
40
|
-
|
|
41
|
-
return dummy_variables
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
def _add_dummy_overrides(overrides: Overrides) -> Dict[str, str]:
|
|
45
|
-
# Have the dummy override variable contain all variable deps that it uses in the string
|
|
46
|
-
dummy_overrides: Dict[str, str] = {}
|
|
47
|
-
for override_name in _override_variables(overrides):
|
|
48
|
-
if _is_function(override_name):
|
|
49
|
-
continue
|
|
50
|
-
|
|
51
|
-
# pylint: disable=protected-access
|
|
52
|
-
dummy_overrides[override_name] = to_variable_dependency_format_string(
|
|
53
|
-
script=overrides.script, parsed_format_string=overrides.script._variables[override_name]
|
|
54
|
-
)
|
|
55
|
-
# pylint: enable=protected-access
|
|
56
|
-
return dummy_overrides
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
def _get_added_and_modified_variables(
|
|
60
|
-
plugins: PresetPlugins, downloader_options: MultiUrlValidator, output_options: OutputOptions
|
|
61
|
-
) -> Iterable[Tuple[OptionsValidator, Set[str], Set[str]]]:
|
|
62
|
-
"""
|
|
63
|
-
Iterates and returns the plugin options, added variables, modified variables
|
|
64
|
-
"""
|
|
65
|
-
options: List[OptionsValidator] = plugins.plugin_options
|
|
66
|
-
options.append(downloader_options)
|
|
67
|
-
options.append(output_options)
|
|
68
|
-
|
|
69
|
-
for plugin_options in options:
|
|
70
|
-
added_variables: Set[str] = set()
|
|
71
|
-
modified_variables: Set[str] = set()
|
|
72
|
-
|
|
73
|
-
for plugin_added_variables in plugin_options.added_variables(
|
|
74
|
-
unresolved_variables=set(),
|
|
75
|
-
).values():
|
|
76
|
-
added_variables |= set(plugin_added_variables)
|
|
77
|
-
|
|
78
|
-
for plugin_modified_variables in plugin_options.modified_variables().values():
|
|
79
|
-
modified_variables = plugin_modified_variables
|
|
80
|
-
|
|
81
|
-
yield plugin_options, added_variables, modified_variables
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
def _override_variables(overrides: Overrides) -> Set[str]:
|
|
85
|
-
return set(list(overrides.initial_variables().keys()))
|
|
86
|
-
|
|
87
12
|
|
|
88
13
|
class VariableValidation:
|
|
89
14
|
def __init__(
|
|
90
15
|
self,
|
|
16
|
+
overrides: Overrides,
|
|
91
17
|
downloader_options: MultiUrlValidator,
|
|
92
18
|
output_options: OutputOptions,
|
|
93
19
|
plugins: PresetPlugins,
|
|
94
20
|
):
|
|
21
|
+
self.overrides = overrides
|
|
95
22
|
self.downloader_options = downloader_options
|
|
96
23
|
self.output_options = output_options
|
|
97
24
|
self.plugins = plugins
|
|
98
25
|
|
|
99
|
-
self.script
|
|
100
|
-
self.
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
def initialize_preset_overrides(self, overrides: Overrides) -> "VariableValidation":
|
|
104
|
-
"""
|
|
105
|
-
Do some gymnastics to initialize the Overrides script.
|
|
106
|
-
"""
|
|
107
|
-
override_variables = set(list(overrides.initial_variables().keys()))
|
|
108
|
-
|
|
109
|
-
# Set resolved variables as all entry + override variables
|
|
110
|
-
# at this point to generate every possible added/modified variable
|
|
111
|
-
self.resolved_variables = set(_DUMMY_ENTRY_VARIABLES.keys()) | override_variables
|
|
112
|
-
plugin_variables: Set[str] = set()
|
|
113
|
-
|
|
114
|
-
for (
|
|
115
|
-
plugin_options,
|
|
116
|
-
added_variables,
|
|
117
|
-
modified_variables,
|
|
118
|
-
) in _get_added_and_modified_variables(
|
|
119
|
-
plugins=self.plugins,
|
|
120
|
-
downloader_options=self.downloader_options,
|
|
121
|
-
output_options=self.output_options,
|
|
122
|
-
):
|
|
123
|
-
|
|
124
|
-
for added_variable in added_variables:
|
|
125
|
-
if not overrides.ensure_added_plugin_variable_valid(added_variable=added_variable):
|
|
126
|
-
# pylint: disable=protected-access
|
|
127
|
-
raise plugin_options._validation_exception(
|
|
128
|
-
f"Cannot use the variable name {added_variable} because it exists as a"
|
|
129
|
-
" built-in ytdl-sub variable name."
|
|
130
|
-
)
|
|
131
|
-
# pylint: enable=protected-access
|
|
132
|
-
|
|
133
|
-
# Set unresolved as variables that are added but do not exist as
|
|
134
|
-
# entry/override variables since they are created at run-time
|
|
135
|
-
self.unresolved_variables |= added_variables | modified_variables
|
|
136
|
-
plugin_variables |= added_variables | modified_variables
|
|
137
|
-
|
|
138
|
-
# Then update resolved variables to reflect that
|
|
139
|
-
self.resolved_variables -= self.unresolved_variables
|
|
140
|
-
|
|
141
|
-
# Initialize overrides with unresolved variables + modified variables to throw an error.
|
|
142
|
-
# For modified variables, this is to prevent a resolve(update=True) to setting any
|
|
143
|
-
# dependencies until it has been explicitly added
|
|
144
|
-
overrides = overrides.initialize_script(unresolved_variables=self.unresolved_variables)
|
|
145
|
-
|
|
146
|
-
# copy the script and mock entry variables
|
|
147
|
-
self.script = copy.deepcopy(overrides.script)
|
|
148
|
-
self.script.add(
|
|
149
|
-
variables=_add_dummy_overrides(overrides=overrides)
|
|
150
|
-
| _add_dummy_variables(variables=plugin_variables)
|
|
151
|
-
| _DUMMY_ENTRY_VARIABLES
|
|
26
|
+
self.script = self.overrides.script
|
|
27
|
+
self.unresolved_variables = self.plugins.get_all_variables(
|
|
28
|
+
additional_options=[self.output_options, self.downloader_options]
|
|
152
29
|
)
|
|
153
30
|
|
|
154
|
-
return self
|
|
155
|
-
|
|
156
|
-
def _update_script(self) -> None:
|
|
157
|
-
_ = self.script.resolve(unresolvable=self.unresolved_variables, update=True)
|
|
158
|
-
|
|
159
|
-
def _add_subscription_override_variables(self) -> None:
|
|
160
|
-
"""
|
|
161
|
-
Add dummy subscription variables for script validation
|
|
162
|
-
"""
|
|
163
|
-
self.resolved_variables |= REQUIRED_OVERRIDE_VARIABLE_NAMES
|
|
164
|
-
|
|
165
31
|
def _add_variables(self, plugin_op: PluginOperation, options: OptionsValidator) -> None:
|
|
166
32
|
"""
|
|
167
33
|
Add dummy variables for script validation
|
|
@@ -171,19 +37,17 @@ class VariableValidation:
|
|
|
171
37
|
).get(plugin_op, set())
|
|
172
38
|
modified_variables = options.modified_variables().get(plugin_op, set())
|
|
173
39
|
|
|
174
|
-
|
|
40
|
+
self.unresolved_variables -= added_variables | modified_variables
|
|
175
41
|
|
|
176
|
-
|
|
177
|
-
self.unresolved_variables -= resolved_variables
|
|
178
|
-
|
|
179
|
-
def ensure_proper_usage(self) -> None:
|
|
42
|
+
def ensure_proper_usage(self) -> Dict:
|
|
180
43
|
"""
|
|
181
44
|
Validate variables resolve as plugins are executed, and return
|
|
182
45
|
a mock script which contains actualized added variables from the plugins
|
|
183
46
|
"""
|
|
184
47
|
|
|
48
|
+
resolved_subscription: Dict = {}
|
|
49
|
+
|
|
185
50
|
self._add_variables(PluginOperation.DOWNLOADER, options=self.downloader_options)
|
|
186
|
-
self._add_subscription_override_variables()
|
|
187
51
|
|
|
188
52
|
# Always add output options first
|
|
189
53
|
self._add_variables(PluginOperation.MODIFY_ENTRY_METADATA, options=self.output_options)
|
|
@@ -200,16 +64,28 @@ class VariableValidation:
|
|
|
200
64
|
self._add_variables(PluginOperation.MODIFY_ENTRY, options=plugin_options)
|
|
201
65
|
|
|
202
66
|
# Validate that any formatter in the plugin options can resolve
|
|
203
|
-
validate_formatters(
|
|
67
|
+
resolved_subscription |= validate_formatters(
|
|
204
68
|
script=self.script,
|
|
205
69
|
unresolved_variables=self.unresolved_variables,
|
|
206
70
|
validator=plugin_options,
|
|
207
71
|
)
|
|
208
72
|
|
|
209
|
-
validate_formatters(
|
|
73
|
+
resolved_subscription |= validate_formatters(
|
|
210
74
|
script=self.script,
|
|
211
75
|
unresolved_variables=self.unresolved_variables,
|
|
212
76
|
validator=self.output_options,
|
|
213
77
|
)
|
|
214
78
|
|
|
79
|
+
# TODO: make this a function
|
|
80
|
+
raw_download_output = validate_formatters(
|
|
81
|
+
script=self.script,
|
|
82
|
+
unresolved_variables=self.unresolved_variables,
|
|
83
|
+
validator=self.downloader_options.urls,
|
|
84
|
+
)
|
|
85
|
+
resolved_subscription["download"] = []
|
|
86
|
+
for url_output in raw_download_output["download"]:
|
|
87
|
+
if url_output["url"]:
|
|
88
|
+
resolved_subscription["download"].append(url_output)
|
|
89
|
+
|
|
215
90
|
assert not self.unresolved_variables
|
|
91
|
+
return resolved_subscription
|
|
@@ -21,7 +21,7 @@ class UrlThumbnailValidator(StrictDictValidator):
|
|
|
21
21
|
def __init__(self, name, value):
|
|
22
22
|
super().__init__(name, value)
|
|
23
23
|
|
|
24
|
-
self.
|
|
24
|
+
self._thumb_name = self._validate_key(key="name", validator=StringFormatterValidator)
|
|
25
25
|
self._uid = self._validate_key(key="uid", validator=OverridesStringFormatterValidator)
|
|
26
26
|
|
|
27
27
|
@property
|
|
@@ -29,7 +29,7 @@ class UrlThumbnailValidator(StrictDictValidator):
|
|
|
29
29
|
"""
|
|
30
30
|
File name for the thumbnail
|
|
31
31
|
"""
|
|
32
|
-
return self.
|
|
32
|
+
return self._thumb_name
|
|
33
33
|
|
|
34
34
|
@property
|
|
35
35
|
def uid(self) -> OverridesStringFormatterValidator:
|
|
@@ -11,9 +11,6 @@ from ytdl_sub.entries.script.variable_types import Variable
|
|
|
11
11
|
from ytdl_sub.script.functions import Functions
|
|
12
12
|
from ytdl_sub.script.utils.name_validation import is_valid_name
|
|
13
13
|
|
|
14
|
-
# TODO: use this
|
|
15
|
-
SUBSCRIPTION_ARRAY = "subscription_array"
|
|
16
|
-
|
|
17
14
|
|
|
18
15
|
class SubscriptionVariables:
|
|
19
16
|
@staticmethod
|
|
@@ -163,7 +160,7 @@ class OverrideHelpers:
|
|
|
163
160
|
True if the override name itself is valid. False otherwise.
|
|
164
161
|
"""
|
|
165
162
|
if name.startswith("%"):
|
|
166
|
-
|
|
163
|
+
name = name[1:]
|
|
167
164
|
|
|
168
165
|
return is_valid_name(name=name)
|
|
169
166
|
|
|
@@ -11,8 +11,7 @@ presets:
|
|
|
11
11
|
|
|
12
12
|
# Set the default date_range to 2 months
|
|
13
13
|
overrides:
|
|
14
|
-
|
|
15
|
-
only_recent_date_range: "{date_range}"
|
|
14
|
+
only_recent_date_range: "2months"
|
|
16
15
|
|
|
17
16
|
#############################################################################
|
|
18
17
|
# Only Recent
|