vsjetengine 1.0.0__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.
- vsengine/__init__.py +28 -0
- vsengine/_futures.py +372 -0
- vsengine/_helpers.py +34 -0
- vsengine/_hospice.py +121 -0
- vsengine/_nodes.py +116 -0
- vsengine/_version.py +2 -0
- vsengine/adapters/__init__.py +6 -0
- vsengine/adapters/asyncio.py +85 -0
- vsengine/adapters/trio.py +107 -0
- vsengine/loops.py +269 -0
- vsengine/policy.py +395 -0
- vsengine/py.typed +0 -0
- vsengine/video.py +180 -0
- vsengine/vpy.py +441 -0
- vsjetengine-1.0.0.dist-info/METADATA +350 -0
- vsjetengine-1.0.0.dist-info/RECORD +18 -0
- vsjetengine-1.0.0.dist-info/WHEEL +4 -0
- vsjetengine-1.0.0.dist-info/licenses/COPYING +287 -0
vsengine/vpy.py
ADDED
|
@@ -0,0 +1,441 @@
|
|
|
1
|
+
# vs-engine
|
|
2
|
+
# Copyright (C) 2022 cid-chan
|
|
3
|
+
# Copyright (C) 2025 Jaded-Encoding-Thaumaturgy
|
|
4
|
+
# This project is licensed under the EUPL-1.2
|
|
5
|
+
# SPDX-License-Identifier: EUPL-1.2
|
|
6
|
+
"""This module provides functions to load and execute VapourSynth scripts (`.vpy` files) or inline code."""
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
import ast
|
|
11
|
+
import io
|
|
12
|
+
import os
|
|
13
|
+
import sys
|
|
14
|
+
from collections.abc import Awaitable, Buffer, Callable, Generator
|
|
15
|
+
from concurrent.futures import Future
|
|
16
|
+
from contextlib import AbstractContextManager
|
|
17
|
+
from types import CodeType, ModuleType, TracebackType
|
|
18
|
+
from typing import Any, Concatenate, Self, overload
|
|
19
|
+
from uuid import uuid4
|
|
20
|
+
|
|
21
|
+
import vapoursynth as vs
|
|
22
|
+
|
|
23
|
+
from ._futures import UnifiedFuture, unified
|
|
24
|
+
from .loops import make_awaitable, to_thread
|
|
25
|
+
from .policy import ManagedEnvironment, Policy
|
|
26
|
+
|
|
27
|
+
__all__ = ["ExecutionError", "Script", "load_code", "load_script"]
|
|
28
|
+
|
|
29
|
+
type Runner[R] = Callable[[Callable[[], R]], Future[R]]
|
|
30
|
+
type Executor[T] = Callable[[WrapAllErrors, ModuleType], T]
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
class ExecutionError(Exception):
|
|
34
|
+
"""
|
|
35
|
+
Exception raised when script execution fails.
|
|
36
|
+
"""
|
|
37
|
+
|
|
38
|
+
parent_error: BaseException
|
|
39
|
+
"""The actual exception that has been raised"""
|
|
40
|
+
|
|
41
|
+
def __init__(self, parent_error: BaseException) -> None:
|
|
42
|
+
"""
|
|
43
|
+
Initialize the ExecutionError exception.
|
|
44
|
+
|
|
45
|
+
:param parent_error: The original exception that occurred.
|
|
46
|
+
"""
|
|
47
|
+
import textwrap
|
|
48
|
+
|
|
49
|
+
msg = textwrap.indent(self.extract_traceback(parent_error), "| ")
|
|
50
|
+
super().__init__(f"An exception was raised while running the script.\n{msg}")
|
|
51
|
+
self.parent_error = parent_error
|
|
52
|
+
|
|
53
|
+
@staticmethod
|
|
54
|
+
def extract_traceback(error: BaseException) -> str:
|
|
55
|
+
"""
|
|
56
|
+
Extract and format the traceback from an exception.
|
|
57
|
+
|
|
58
|
+
:param error: The exception to extract the traceback from.
|
|
59
|
+
:return: A formatted string containing the traceback.
|
|
60
|
+
"""
|
|
61
|
+
import traceback
|
|
62
|
+
|
|
63
|
+
msg = traceback.format_exception(type(error), error, error.__traceback__)
|
|
64
|
+
msg = "".join(msg)
|
|
65
|
+
return msg
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
class WrapAllErrors(AbstractContextManager[None]):
|
|
69
|
+
"""
|
|
70
|
+
Context manager that wraps exceptions in ExecutionError.
|
|
71
|
+
"""
|
|
72
|
+
|
|
73
|
+
def __enter__(self) -> None: ...
|
|
74
|
+
|
|
75
|
+
def __exit__(self, exc: type[BaseException] | None, val: BaseException | None, tb: TracebackType | None) -> None:
|
|
76
|
+
if val is not None:
|
|
77
|
+
raise ExecutionError(val) from None
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
class _TempModule(AbstractContextManager[None]):
|
|
81
|
+
"""
|
|
82
|
+
Temporarily register a module in sys.modules.
|
|
83
|
+
|
|
84
|
+
Ported from runpy.
|
|
85
|
+
That ensures the module is available in sys.modules during execution and restored/cleaned up afterwards.
|
|
86
|
+
"""
|
|
87
|
+
|
|
88
|
+
def __init__(self, mod_name: str, filename: str) -> None:
|
|
89
|
+
self.mod_name = mod_name
|
|
90
|
+
self.module = ModuleType(mod_name)
|
|
91
|
+
self.module.__dict__["__file__"] = filename
|
|
92
|
+
self._saved_module = list[ModuleType | None]()
|
|
93
|
+
|
|
94
|
+
def __enter__(self) -> None:
|
|
95
|
+
mod_name = self.mod_name
|
|
96
|
+
|
|
97
|
+
self._saved_module.append(sys.modules.get(mod_name))
|
|
98
|
+
|
|
99
|
+
sys.modules[mod_name] = self.module
|
|
100
|
+
|
|
101
|
+
def __exit__(self, exc: type[BaseException] | None, val: BaseException | None, tb: TracebackType | None) -> None:
|
|
102
|
+
mod = self._saved_module.pop()
|
|
103
|
+
|
|
104
|
+
if mod:
|
|
105
|
+
sys.modules[self.mod_name] = mod
|
|
106
|
+
else:
|
|
107
|
+
del sys.modules[self.mod_name]
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
def inline_runner[T](func: Callable[[], T]) -> Future[T]:
|
|
111
|
+
"""
|
|
112
|
+
Runs a function inline and returns the result as a Future.
|
|
113
|
+
|
|
114
|
+
:param func: The function to run.
|
|
115
|
+
:return: A future containing the result or exception of the function.
|
|
116
|
+
"""
|
|
117
|
+
fut = Future[T]()
|
|
118
|
+
try:
|
|
119
|
+
result = func()
|
|
120
|
+
except BaseException as e:
|
|
121
|
+
fut.set_exception(e)
|
|
122
|
+
else:
|
|
123
|
+
fut.set_result(result)
|
|
124
|
+
return fut
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
def chdir_runner[**P, R](
|
|
128
|
+
dir: str | os.PathLike[str], parent: Runner[R]
|
|
129
|
+
) -> Callable[Concatenate[Callable[P, R], P], Future[R]]:
|
|
130
|
+
"""
|
|
131
|
+
Wraps a runner to change the current working directory during execution.
|
|
132
|
+
|
|
133
|
+
:param dir: The directory to change to.
|
|
134
|
+
:param parent: The runner to wrap.
|
|
135
|
+
:return: A wrapped runner function.
|
|
136
|
+
"""
|
|
137
|
+
|
|
138
|
+
def runner(func: Callable[P, R], *args: P.args, **kwargs: P.kwargs) -> Future[R]:
|
|
139
|
+
def _wrapped() -> R:
|
|
140
|
+
current = os.getcwd()
|
|
141
|
+
os.chdir(dir)
|
|
142
|
+
|
|
143
|
+
try:
|
|
144
|
+
f = func(*args, **kwargs)
|
|
145
|
+
return f
|
|
146
|
+
except Exception:
|
|
147
|
+
raise
|
|
148
|
+
finally:
|
|
149
|
+
os.chdir(current)
|
|
150
|
+
|
|
151
|
+
return parent(_wrapped)
|
|
152
|
+
|
|
153
|
+
return runner
|
|
154
|
+
|
|
155
|
+
|
|
156
|
+
_missing = object()
|
|
157
|
+
|
|
158
|
+
|
|
159
|
+
class Script[EnvT: (vs.Environment, ManagedEnvironment)](AbstractContextManager["Script[EnvT]"], Awaitable[None]):
|
|
160
|
+
"""VapourSynth script wrapper."""
|
|
161
|
+
|
|
162
|
+
def __init__(self, executor: Executor[None], module: ModuleType, environment: EnvT, runner: Runner[None]) -> None:
|
|
163
|
+
self.executor = executor
|
|
164
|
+
self.environment: EnvT = environment
|
|
165
|
+
self.runner = runner
|
|
166
|
+
self.module = module
|
|
167
|
+
|
|
168
|
+
def __enter__(self) -> Self:
|
|
169
|
+
self.result()
|
|
170
|
+
return self
|
|
171
|
+
|
|
172
|
+
def __exit__(self, exc: type[BaseException] | None, val: BaseException | None, tb: TracebackType | None) -> None:
|
|
173
|
+
self.dispose()
|
|
174
|
+
|
|
175
|
+
def __await__(self) -> Generator[Any, None, None]:
|
|
176
|
+
"""
|
|
177
|
+
Runs the script and waits until the script has completed.
|
|
178
|
+
"""
|
|
179
|
+
return self.run_async().__await__()
|
|
180
|
+
|
|
181
|
+
def run(self) -> Future[None]:
|
|
182
|
+
"""
|
|
183
|
+
Runs the script.
|
|
184
|
+
|
|
185
|
+
It returns a future which completes when the script completes.
|
|
186
|
+
When the script fails, it raises a ExecutionError.
|
|
187
|
+
"""
|
|
188
|
+
self._future: Future[None]
|
|
189
|
+
|
|
190
|
+
if hasattr(self, "_future"):
|
|
191
|
+
return self._future
|
|
192
|
+
|
|
193
|
+
self._future = self.runner(self._run_inline)
|
|
194
|
+
|
|
195
|
+
return self._future
|
|
196
|
+
|
|
197
|
+
async def run_async(self) -> None:
|
|
198
|
+
"""
|
|
199
|
+
Runs the script asynchronously, but it returns a coroutine.
|
|
200
|
+
"""
|
|
201
|
+
return await make_awaitable(self.run())
|
|
202
|
+
|
|
203
|
+
def result(self) -> None:
|
|
204
|
+
"""
|
|
205
|
+
Runs the script and blocks until the script has finished running.
|
|
206
|
+
"""
|
|
207
|
+
return self.run().result()
|
|
208
|
+
|
|
209
|
+
def dispose(self) -> None:
|
|
210
|
+
"""Disposes the managed environment and clears the module globals."""
|
|
211
|
+
self.module.__dict__.clear()
|
|
212
|
+
|
|
213
|
+
if isinstance(self.environment, ManagedEnvironment):
|
|
214
|
+
self.environment.dispose()
|
|
215
|
+
|
|
216
|
+
@overload
|
|
217
|
+
@unified(kind="future")
|
|
218
|
+
def get_variable(self, name: str) -> Future[Any]: ...
|
|
219
|
+
@overload
|
|
220
|
+
@unified(kind="future")
|
|
221
|
+
def get_variable[T](self, name: str, default: T) -> Future[Any | T]: ...
|
|
222
|
+
@unified(kind="future")
|
|
223
|
+
def get_variable(self, name: str, default: Any = _missing) -> Future[Any]:
|
|
224
|
+
"""
|
|
225
|
+
Retrieve a variable from the script's module.
|
|
226
|
+
|
|
227
|
+
:param name: The name of the variable to retrieve.
|
|
228
|
+
:param default: The default value if the variable is not found.
|
|
229
|
+
:return: A future that resolves to the variable's value.
|
|
230
|
+
"""
|
|
231
|
+
return UnifiedFuture[Any].resolve(
|
|
232
|
+
getattr(self.module, name) if default is _missing else getattr(self.module, name, default)
|
|
233
|
+
)
|
|
234
|
+
|
|
235
|
+
def _run_inline(self) -> None:
|
|
236
|
+
with self.environment.use():
|
|
237
|
+
self.executor(WrapAllErrors(), self.module)
|
|
238
|
+
|
|
239
|
+
|
|
240
|
+
@overload
|
|
241
|
+
def load_script(
|
|
242
|
+
script: str | os.PathLike[str],
|
|
243
|
+
environment: vs.Environment | None = None,
|
|
244
|
+
*,
|
|
245
|
+
module: str | ModuleType = "__vapoursynth__",
|
|
246
|
+
inline: bool = True,
|
|
247
|
+
chdir: str | os.PathLike[str] | None = None,
|
|
248
|
+
) -> Script[vs.Environment]: ...
|
|
249
|
+
|
|
250
|
+
|
|
251
|
+
@overload
|
|
252
|
+
def load_script(
|
|
253
|
+
script: str | os.PathLike[str],
|
|
254
|
+
environment: Script[vs.Environment],
|
|
255
|
+
*,
|
|
256
|
+
inline: bool = True,
|
|
257
|
+
chdir: str | os.PathLike[str] | None = None,
|
|
258
|
+
) -> Script[vs.Environment]: ...
|
|
259
|
+
|
|
260
|
+
|
|
261
|
+
@overload
|
|
262
|
+
def load_script(
|
|
263
|
+
script: str | os.PathLike[str],
|
|
264
|
+
environment: Policy | ManagedEnvironment,
|
|
265
|
+
*,
|
|
266
|
+
module: str | ModuleType = "__vapoursynth__",
|
|
267
|
+
inline: bool = True,
|
|
268
|
+
chdir: str | os.PathLike[str] | None = None,
|
|
269
|
+
) -> Script[ManagedEnvironment]: ...
|
|
270
|
+
|
|
271
|
+
|
|
272
|
+
@overload
|
|
273
|
+
def load_script(
|
|
274
|
+
script: str | os.PathLike[str],
|
|
275
|
+
environment: Script[ManagedEnvironment],
|
|
276
|
+
*,
|
|
277
|
+
inline: bool = True,
|
|
278
|
+
chdir: str | os.PathLike[str] | None = None,
|
|
279
|
+
) -> Script[ManagedEnvironment]: ...
|
|
280
|
+
|
|
281
|
+
|
|
282
|
+
def load_script(
|
|
283
|
+
script: str | os.PathLike[str],
|
|
284
|
+
environment: Policy | vs.Environment | ManagedEnvironment | Script[Any] | None = None,
|
|
285
|
+
*,
|
|
286
|
+
module: str | ModuleType = "__vapoursynth__",
|
|
287
|
+
inline: bool = True,
|
|
288
|
+
chdir: str | os.PathLike[str] | None = None,
|
|
289
|
+
) -> Script[Any]:
|
|
290
|
+
"""
|
|
291
|
+
Runs the script at the given path.
|
|
292
|
+
|
|
293
|
+
:param script: The path to the script file to run.
|
|
294
|
+
:param environment: Defines the environment in which the code should run.
|
|
295
|
+
If passed a Policy, it will create a new environment from the policy,
|
|
296
|
+
which can be accessed using the environment attribute.
|
|
297
|
+
:param module: The name the module should get. Defaults to __vapoursynth__.
|
|
298
|
+
:param inline: Run the code inline, e.g. not in a separate thread.
|
|
299
|
+
:param chdir: Change the currently running directory while the script is running.
|
|
300
|
+
This is unsafe when running multiple scripts at once.
|
|
301
|
+
:returns: A script object. The script starts running when you call run() on it, or await it.
|
|
302
|
+
"""
|
|
303
|
+
|
|
304
|
+
def _execute(ctx: WrapAllErrors, module: ModuleType) -> None:
|
|
305
|
+
nonlocal script
|
|
306
|
+
|
|
307
|
+
script = str(script)
|
|
308
|
+
|
|
309
|
+
with ctx, io.open_code(script) as f, _TempModule(module.__name__, script):
|
|
310
|
+
exec(
|
|
311
|
+
compile(f.read(), filename=script, dont_inherit=True, flags=0, mode="exec"),
|
|
312
|
+
module.__dict__,
|
|
313
|
+
module.__dict__,
|
|
314
|
+
)
|
|
315
|
+
|
|
316
|
+
return _load(_execute, environment, module, inline, chdir)
|
|
317
|
+
|
|
318
|
+
|
|
319
|
+
@overload
|
|
320
|
+
def load_code(
|
|
321
|
+
script: str | Buffer | ast.Module | CodeType,
|
|
322
|
+
environment: vs.Environment | None = None,
|
|
323
|
+
*,
|
|
324
|
+
module: str | ModuleType = "__vapoursynth__",
|
|
325
|
+
inline: bool = True,
|
|
326
|
+
chdir: str | os.PathLike[str] | None = None,
|
|
327
|
+
**kwargs: Any,
|
|
328
|
+
) -> Script[vs.Environment]: ...
|
|
329
|
+
|
|
330
|
+
|
|
331
|
+
@overload
|
|
332
|
+
def load_code(
|
|
333
|
+
script: str | Buffer | ast.Module | CodeType,
|
|
334
|
+
environment: Script[vs.Environment],
|
|
335
|
+
*,
|
|
336
|
+
inline: bool = True,
|
|
337
|
+
chdir: str | os.PathLike[str] | None = None,
|
|
338
|
+
**kwargs: Any,
|
|
339
|
+
) -> Script[vs.Environment]: ...
|
|
340
|
+
|
|
341
|
+
|
|
342
|
+
@overload
|
|
343
|
+
def load_code(
|
|
344
|
+
script: str | Buffer | ast.Module | CodeType,
|
|
345
|
+
environment: Policy | ManagedEnvironment,
|
|
346
|
+
*,
|
|
347
|
+
module: str | ModuleType = "__vapoursynth__",
|
|
348
|
+
inline: bool = True,
|
|
349
|
+
chdir: str | os.PathLike[str] | None = None,
|
|
350
|
+
**kwargs: Any,
|
|
351
|
+
) -> Script[ManagedEnvironment]: ...
|
|
352
|
+
|
|
353
|
+
|
|
354
|
+
@overload
|
|
355
|
+
def load_code(
|
|
356
|
+
script: str | Buffer | ast.Module | CodeType,
|
|
357
|
+
environment: Script[ManagedEnvironment],
|
|
358
|
+
*,
|
|
359
|
+
inline: bool = True,
|
|
360
|
+
chdir: str | os.PathLike[str] | None = None,
|
|
361
|
+
**kwargs: Any,
|
|
362
|
+
) -> Script[ManagedEnvironment]: ...
|
|
363
|
+
|
|
364
|
+
|
|
365
|
+
def load_code(
|
|
366
|
+
script: str | Buffer | ast.Module | CodeType,
|
|
367
|
+
environment: Policy | vs.Environment | ManagedEnvironment | Script[Any] | None = None,
|
|
368
|
+
*,
|
|
369
|
+
module: str | ModuleType = "__vapoursynth__",
|
|
370
|
+
inline: bool = True,
|
|
371
|
+
chdir: str | os.PathLike[str] | None = None,
|
|
372
|
+
**kwargs: Any,
|
|
373
|
+
) -> Script[Any]:
|
|
374
|
+
"""
|
|
375
|
+
Runs the given code snippet.
|
|
376
|
+
|
|
377
|
+
:param script: The code to run. Can be a string, bytes, AST, or compiled code.
|
|
378
|
+
:param environment: Defines the environment in which the code should run. If passed a Policy,
|
|
379
|
+
it will create a new environment from the policy,
|
|
380
|
+
which can be accessed using the environment attribute.
|
|
381
|
+
If the environment is another Script, it will take the environment and module of the script.
|
|
382
|
+
:param module: The name the module should get. Defaults to __vapoursynth__.
|
|
383
|
+
:param inline: Run the code inline, e.g. not in a separate thread.
|
|
384
|
+
:param chdir: Change the currently running directory while the script is running.
|
|
385
|
+
This is unsafe when running multiple scripts at once.
|
|
386
|
+
:returns: A script object. The script starts running when you call run() on it, or await it.
|
|
387
|
+
"""
|
|
388
|
+
|
|
389
|
+
def _execute(ctx: WrapAllErrors, module: ModuleType) -> None:
|
|
390
|
+
nonlocal script, kwargs
|
|
391
|
+
|
|
392
|
+
filename = kwargs.pop("filename", f"<runvpy {uuid4().hex[:8]}>")
|
|
393
|
+
|
|
394
|
+
with ctx, _TempModule(module.__name__, filename):
|
|
395
|
+
if isinstance(script, CodeType):
|
|
396
|
+
code = script
|
|
397
|
+
else:
|
|
398
|
+
compile_args: dict[str, Any] = {
|
|
399
|
+
"filename": filename,
|
|
400
|
+
"dont_inherit": True,
|
|
401
|
+
"flags": 0,
|
|
402
|
+
"mode": "exec",
|
|
403
|
+
} | kwargs
|
|
404
|
+
code = compile(script, **compile_args)
|
|
405
|
+
|
|
406
|
+
exec(code, module.__dict__, module.__dict__)
|
|
407
|
+
|
|
408
|
+
return _load(_execute, environment, module, inline, chdir)
|
|
409
|
+
|
|
410
|
+
|
|
411
|
+
def _load(
|
|
412
|
+
executor: Executor[None],
|
|
413
|
+
environment: Policy
|
|
414
|
+
| vs.Environment
|
|
415
|
+
| ManagedEnvironment
|
|
416
|
+
| Script[vs.Environment]
|
|
417
|
+
| Script[ManagedEnvironment]
|
|
418
|
+
| None,
|
|
419
|
+
module: str | ModuleType,
|
|
420
|
+
inline: bool,
|
|
421
|
+
chdir: str | os.PathLike[str] | None,
|
|
422
|
+
) -> Script[Any]:
|
|
423
|
+
runner = inline_runner if inline else to_thread
|
|
424
|
+
|
|
425
|
+
if chdir is not None:
|
|
426
|
+
runner = chdir_runner(chdir, runner)
|
|
427
|
+
|
|
428
|
+
if isinstance(environment, Script):
|
|
429
|
+
module = environment.module
|
|
430
|
+
environment = environment.environment
|
|
431
|
+
elif isinstance(module, str):
|
|
432
|
+
module = ModuleType(module)
|
|
433
|
+
|
|
434
|
+
if environment is None:
|
|
435
|
+
environment = vs.get_current_environment()
|
|
436
|
+
elif isinstance(environment, vs.Environment):
|
|
437
|
+
return Script(executor, module, environment, runner)
|
|
438
|
+
elif isinstance(environment, Policy):
|
|
439
|
+
environment = environment.new_environment()
|
|
440
|
+
|
|
441
|
+
return Script[Any](executor, module, environment, runner)
|