1# Copyright (c) 2012-2021 Fredrik Mellbin 2# 3# This file is part of VapourSynth. 4# 5# VapourSynth is free software; you can redistribute it and/or 6# modify it under the terms of the GNU Lesser General Public 7# License as published by the Free Software Foundation; either 8# version 2.1 of the License, or (at your option) any later version. 9# 10# VapourSynth is distributed in the hope that it will be useful, 11# but WITHOUT ANY WARRANTY; without even the implied warranty of 12# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 13# Lesser General Public License for more details. 14# 15# You should have received a copy of the GNU Lesser General Public 16# License along with VapourSynth; if not, write to the Free Software 17# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 18""" This is the VapourSynth module implementing the Python bindings. """ 19 20cimport vapoursynth 21cimport cython.parallel 22from cython cimport view, final 23from libc.stdint cimport intptr_t, uint16_t, uint32_t 24from cpython.buffer cimport (PyBUF_WRITABLE, PyBUF_FORMAT, PyBUF_STRIDES, 25 PyBUF_F_CONTIGUOUS) 26from cpython.ref cimport Py_INCREF, Py_DECREF 27import os 28import ctypes 29import threading 30import traceback 31import gc 32import sys 33import inspect 34import weakref 35import atexit 36import contextlib 37from threading import local as ThreadLocal, Lock 38from types import MappingProxyType 39from collections import namedtuple 40from collections.abc import Iterable, Mapping 41from fractions import Fraction 42 43# Ensure that the import doesn't fail 44# if typing is not available on the python installation. 45try: 46 import typing 47except ImportError as e: 48 typing = None 49 50__all__ = [ 51 'COMPAT', 52 'COMPATBGR32', 'COMPATYUY2', 53 'GRAY', 54 'GRAY16', 'GRAY8', 'GRAYH', 'GRAYS', 55 'RGB', 56 'RGB24', 'RGB27', 'RGB30', 'RGB48', 'RGBH', 'RGBS', 57 'YCOCG', 'YUV', 58 'YUV410P8', 'YUV411P8', 'YUV420P10', 'YUV420P12', 59 'YUV420P14', 'YUV420P16', 'YUV420P8', 'YUV420P9', 60 'YUV422P10', 'YUV422P12', 'YUV422P14', 'YUV422P16', 61 'YUV422P8', 'YUV422P9', 'YUV440P8', 'YUV444P10', 62 'YUV444P12', 'YUV444P14', 'YUV444P16', 'YUV444P8', 63 'YUV444P9', 'YUV444PH', 'YUV444PS', 64 'NONE', 65 'FLOAT', 'INTEGER', 66 67 'get_output', 'get_outputs', 68 'clear_output', 'clear_outputs', 69 70 'core', 71] 72 73__version__ = namedtuple("VapourSynthVersion", "release_major release_minor")(54, 0) 74__api_version__ = namedtuple("VapourSynthAPIVersion", "api_major api_minor")(VAPOURSYNTH_API_MAJOR, VAPOURSYNTH_API_MINOR) 75 76@final 77cdef class EnvironmentData(object): 78 cdef object core 79 cdef dict outputs 80 81 cdef object __weakref__ 82 83 def __init__(self): 84 raise RuntimeError("Cannot directly instantiate this class.") 85 86 87class EnvironmentPolicy(object): 88 89 def on_policy_registered(self, special_api): 90 pass 91 92 def on_policy_cleared(self): 93 pass 94 95 def get_current_environment(self): 96 raise NotImplementedError 97 98 def set_environment(self, environment): 99 raise NotImplementedError 100 101 def is_active(self, environment): 102 raise NotImplementedError 103 104 105@final 106cdef class StandaloneEnvironmentPolicy: 107 cdef EnvironmentData _environment 108 109 cdef object __weakref__ 110 111 def __init__(self): 112 raise RuntimeError("Cannot directly instantiate this class.") 113 114 def on_policy_registered(self, api): 115 self._environment = api.create_environment() 116 117 def on_policy_cleared(self): 118 self._environment = None 119 120 def get_current_environment(self): 121 return self._environment 122 123 def set_environment(self, environment): 124 return self._environment 125 126 def is_alive(self, environment): 127 return environment is self._environment 128 129# This flag is kept for backwards compatibility 130# I suggest deleting it sometime after R51 131_using_vsscript = False 132 133# Internal holder of the current policy. 134cdef object _policy = None 135 136 137cdef object _message_handler = None 138cdef const VSAPI *_vsapi = NULL 139 140 141@final 142cdef class EnvironmentPolicyAPI: 143 # This must be a weak-ref to prevent a cyclic dependency that happens if the API 144 # is stored within an EnvironmentPolicy-instance. 145 cdef object _target_policy 146 147 def __init__(self): 148 raise RuntimeError("Cannot directly instantiate this class.") 149 150 cdef ensure_policy_matches(self): 151 if _policy is not self._target_policy(): 152 raise ValueError("The currently activated policy does not match the bound policy. Was the environment unregistered?") 153 154 def wrap_environment(self, environment_data): 155 self.ensure_policy_matches() 156 if not isinstance(environment_data, EnvironmentData): 157 raise ValueError("environment_data must be an EnvironmentData instance.") 158 return use_environment(<EnvironmentData>environment_data, direct=False) 159 160 def create_environment(self): 161 self.ensure_policy_matches() 162 163 cdef EnvironmentData env = EnvironmentData.__new__(EnvironmentData) 164 env.core = None 165 env.outputs = {} 166 167 return env 168 169 def unregister_policy(self): 170 self.ensure_policy_matches() 171 clear_policy() 172 173 def __repr__(self): 174 target = self._target_policy() 175 if target is None: 176 return f"<EnvironmentPolicyAPI bound to <garbage collected> (unregistered)" 177 elif _policy is not target: 178 return f"<EnvironmentPolicyAPI bound to {target!r} (unregistered)>" 179 else: 180 return f"<EnvironmentPolicyAPI bound to {target!r}>" 181 182 183def register_policy(policy): 184 global _policy, _using_vsscript 185 if _policy is not None: 186 raise RuntimeError("There is already a policy registered.") 187 _policy = policy 188 189 # Expose Additional API-calls to the newly registered Environment-policy. 190 cdef EnvironmentPolicyAPI _api = EnvironmentPolicyAPI.__new__(EnvironmentPolicyAPI) 191 _api._target_policy = weakref.ref(_policy) 192 _policy.on_policy_registered(_api) 193 194 if not isinstance(policy, StandaloneEnvironmentPolicy): 195 # Older script had to use this flag to determine if it ran in 196 # Multi-VSCore-Environments. 197 # 198 # We will just assume that this is the case if we register a custom 199 # policy. 200 _using_vsscript = True 201 202 203## DO NOT EXPOSE THIS FUNCTION TO PYTHON-LAND! 204cdef get_policy(): 205 global _policy 206 if _policy is None: 207 standalone_policy = StandaloneEnvironmentPolicy.__new__(StandaloneEnvironmentPolicy) 208 register_policy(standalone_policy) 209 210 return _policy 211 212def has_policy(): 213 return _policy is not None 214 215cdef clear_policy(): 216 global _policy, _using_vsscript 217 old_policy = _policy 218 _policy = None 219 if old_policy is not None: 220 old_policy.on_policy_cleared() 221 _using_vsscript = False 222 return old_policy 223 224cdef EnvironmentData _env_current(): 225 return get_policy().get_current_environment() 226 227 228# Make sure the policy is cleared at exit. 229atexit.register(lambda: clear_policy()) 230 231 232@final 233cdef class _FastManager(object): 234 cdef EnvironmentData target 235 cdef EnvironmentData previous 236 237 def __init__(self): 238 raise RuntimeError("Cannot directly instantiate this class.") 239 240 def __enter__(self): 241 if self.target is not None: 242 self.previous = get_policy().set_environment(self.target) 243 self.target = None 244 else: 245 self.previous = get_policy().get_current_environment() 246 247 def __exit__(self, *_): 248 get_policy().set_environment(self.previous) 249 self.previous = None 250 251 252cdef object _tl_env_stack = ThreadLocal() 253cdef class Environment(object): 254 cdef readonly object env 255 cdef bint use_stack 256 257 def __init__(self): 258 raise Error('Class cannot be instantiated directly') 259 260 @property 261 def alive(self): 262 env = self.get_env() 263 if env is None: 264 return False 265 return get_policy().is_alive(env) 266 267 @property 268 def single(self): 269 return self.is_single() 270 271 @classmethod 272 def is_single(self): 273 return not has_policy() or isinstance(_policy, StandaloneEnvironmentPolicy) 274 275 @property 276 def env_id(self): 277 if self.single: 278 return -1 279 return id(self.env) 280 281 cdef EnvironmentData get_env(self): 282 return self.env() 283 284 @property 285 def active(self): 286 env = self.get_env() 287 if env is None: 288 return None 289 return get_policy().get_current_environment() is env 290 291 def copy(self): 292 cdef Environment env = Environment.__new__(Environment) 293 env.env = self.env 294 env.use_stack = False 295 return env 296 297 def use(self): 298 env = self.get_env() 299 if env is None: 300 raise RuntimeError("The environment is dead.") 301 302 cdef _FastManager ctx = _FastManager.__new__(_FastManager) 303 ctx.target = env 304 ctx.previous = None 305 return ctx 306 307 cdef _get_stack(self): 308 if not self.use_stack: 309 raise RuntimeError("You cannot directly use the environment as a context-manager. Use Environment.use instead.") 310 _tl_env_stack.stack = getattr(_tl_env_stack, "stack", []) 311 return _tl_env_stack.stack 312 313 def __enter__(self): 314 if not self.alive: 315 raise RuntimeError("The environment has died.") 316 317 env = self.get_env() 318 stack = self._get_stack() 319 stack.append(get_policy().set_environment(env)) 320 if len(stack) > 1: 321 import warnings 322 warnings.warn("Using the environment as a context-manager is not reentrant. Expect undefined behaviour. Use Environment.use instead.", RuntimeWarning) 323 324 return self 325 326 def __exit__(self, *_): 327 stack = self._get_stack() 328 if not stack: 329 import warnings 330 warnings.warn("Exiting while the stack is empty. Was the frame suspended during the with-statement?", RuntimeWarning) 331 return 332 333 env = stack.pop() 334 old = get_policy().set_environment(env) 335 336 # We exited with a different environment. This is not good. Automatically revert this change. 337 if old is not self.get_env(): 338 import warnings 339 warnings.warn("The exited environment did not match the managed environment. Was the frame suspended during the with-statement?", RuntimeWarning) 340 341 def __eq__(self, other): 342 return other.env_id == self.env_id 343 344 def __repr__(self): 345 if self.single: 346 return "<Environment (default)>" 347 348 return f"<Environment {id(self.env)} ({('active' if self.active else 'alive') if self.alive else 'dead'})>" 349 350 351cdef Environment use_environment(EnvironmentData env, bint direct = False): 352 if id is None: raise ValueError("id may not be None.") 353 354 cdef Environment instance = Environment.__new__(Environment) 355 instance.env = weakref.ref(env) 356 instance.use_stack = direct 357 358 return instance 359 360 361def vpy_current_environment(): 362 import warnings 363 warnings.warn("This function is deprecated and might cause unexpected behaviour. Use get_current_environment() instead.", DeprecationWarning) 364 365 env = get_policy().get_current_environment() 366 if env is None: 367 raise RuntimeError("We are not running inside an environment.") 368 369 vsscript_get_core_internal(env) # Make sure a core is defined 370 return use_environment(env, direct=True) 371 372def get_current_environment(): 373 env = get_policy().get_current_environment() 374 if env is None: 375 raise RuntimeError("We are not running inside an environment.") 376 377 vsscript_get_core_internal(env) # Make sure a core is defined 378 return use_environment(env, direct=False) 379 380# Create an empty list whose instance will represent a not passed value. 381_EMPTY = [] 382 383AlphaOutputTuple = namedtuple("AlphaOutputTuple", "clip alpha") 384 385def _construct_parameter(signature): 386 name,type,*opt = signature.split(":") 387 388 # Handle Arrays. 389 if type.endswith("[]"): 390 array = True 391 type = type[:-2] 392 else: 393 array = False 394 395 # Handle types 396 if type == "clip": 397 type = vapoursynth.VideoNode 398 elif type == "frame": 399 type = vapoursynth.VideoFrame 400 elif type == "func": 401 type = typing.Union[vapoursynth.Func, typing.Callable] 402 elif type == "int": 403 type = int 404 elif type == "float": 405 type = float 406 elif type == "data": 407 type = typing.Union[str, bytes, bytearray] 408 else: 409 type = typing.Any 410 411 # Make the type a sequence. 412 if array: 413 type = typing.Union[type, typing.Sequence[type]] 414 415 # Mark an optional type 416 if opt: 417 type = typing.Optional[type] 418 opt = None 419 else: 420 opt = inspect.Parameter.empty 421 422 423 return inspect.Parameter( 424 name, inspect.Parameter.POSITIONAL_OR_KEYWORD, 425 default=opt, annotation=type 426 ) 427 428def construct_signature(signature, injected=None): 429 if typing is None: 430 raise RuntimeError("At least Python 3.5 is required to use type-hinting") 431 432 if isinstance(signature, vapoursynth.Function): 433 signature = signature.signature 434 435 params = list( 436 _construct_parameter(param) 437 for param in signature.split(";") 438 if param 439 ) 440 441 if injected: 442 del params[0] 443 444 return inspect.Signature(tuple(params), return_annotation=vapoursynth.VideoNode) 445 446 447class Error(Exception): 448 def __init__(self, value): 449 self.value = value 450 451 def __str__(self): 452 return str(self.value) 453 454 def __repr__(self): 455 return repr(self.value) 456 457cdef void __stdcall message_handler_wrapper(int msgType, const char *msg, void *userData) nogil: 458 with gil: 459 global _message_handler 460 _message_handler(msgType, msg.decode('utf-8')) 461 462def set_message_handler(handler_func): 463 cdef const VSAPI *funcs 464 global _message_handler 465 funcs = getVapourSynthAPI(VAPOURSYNTH_API_VERSION) 466 if funcs == NULL: 467 raise Error('Failed to obtain VapourSynth API pointer. Is the Python module and loaded core library mismatched?') 468 if handler_func is None: 469 _message_handler = None 470 funcs.setMessageHandler(NULL, NULL) 471 else: 472 handler_func(vapoursynth.mtDebug, 'New message handler installed from python') 473 _message_handler = handler_func 474 funcs.setMessageHandler(message_handler_wrapper, NULL) 475 476cdef _get_output_dict(funcname="this function"): 477 cdef EnvironmentData env = _env_current() 478 if env is None: 479 raise Error('Internal environment id not set. %s called from a filter callback?'%funcname) 480 return env.outputs 481 482def clear_output(int index = 0): 483 cdef dict outputs = _get_output_dict("clear_output") 484 try: 485 del outputs[index] 486 except KeyError: 487 pass 488 489def clear_outputs(): 490 cdef dict outputs = _get_output_dict("clear_outputs") 491 outputs.clear() 492 493def get_outputs(): 494 cdef dict outputs = _get_output_dict("get_outputs") 495 return MappingProxyType(outputs) 496 497def get_output(int index = 0): 498 return _get_output_dict("get_output")[index] 499 500cdef class FuncData(object): 501 cdef object func 502 cdef VSCore *core 503 cdef EnvironmentData env 504 505 def __init__(self): 506 raise Error('Class cannot be instantiated directly') 507 508 def __call__(self, **kwargs): 509 return self.func(**kwargs) 510 511cdef FuncData createFuncData(object func, VSCore *core, EnvironmentData env): 512 cdef FuncData instance = FuncData.__new__(FuncData) 513 instance.func = func 514 instance.core = core 515 instance.env = env 516 return instance 517 518cdef class Func(object): 519 cdef const VSAPI *funcs 520 cdef VSFuncRef *ref 521 522 def __init__(self): 523 raise Error('Class cannot be instantiated directly') 524 525 def __dealloc__(self): 526 if self.funcs: 527 self.funcs.freeFunc(self.ref) 528 529 def __call__(self, **kwargs): 530 cdef VSMap *outm 531 cdef VSMap *inm 532 cdef const VSAPI *vsapi 533 cdef const char *error 534 vsapi = vpy_getVSApi(); 535 outm = self.funcs.createMap() 536 inm = self.funcs.createMap() 537 try: 538 dictToMap(kwargs, inm, NULL, vsapi) 539 self.funcs.callFunc(self.ref, inm, outm, NULL, NULL) 540 error = self.funcs.getError(outm) 541 if error: 542 raise Error(error.decode('utf-8')) 543 return mapToDict(outm, True, False, NULL, vsapi) 544 finally: 545 vsapi.freeMap(outm) 546 vsapi.freeMap(inm) 547 548cdef Func createFuncPython(object func, VSCore *core, const VSAPI *funcs): 549 cdef Func instance = Func.__new__(Func) 550 instance.funcs = funcs 551 552 cdef EnvironmentData env = _env_current() 553 if env is None: 554 raise Error('Internal environment id not set. Did the environment die?') 555 fdata = createFuncData(func, core, env) 556 557 Py_INCREF(fdata) 558 instance.ref = instance.funcs.createFunc(publicFunction, <void *>fdata, freeFunc, core, funcs) 559 return instance 560 561cdef Func createFuncRef(VSFuncRef *ref, const VSAPI *funcs): 562 cdef Func instance = Func.__new__(Func) 563 instance.funcs = funcs 564 instance.ref = ref 565 return instance 566 567cdef class RawCallbackData(object): 568 cdef const VSAPI *funcs 569 cdef object callback 570 571 cdef VideoNode node 572 573 cdef object wrap_cb 574 cdef object future 575 cdef EnvironmentData env 576 577 def __init__(self, VideoNode node, EnvironmentData env, object callback = None): 578 self.node = node 579 self.callback = callback 580 581 self.future = None 582 self.wrap_cb = None 583 self.env = env 584 585 def for_future(self, object future, object wrap_call=None): 586 if wrap_call is None: 587 wrap_call = lambda func, *args, **kwargs: func(*args, **kwargs) 588 self.callback = self.handle_future 589 self.future = future 590 self.wrap_cb = wrap_call 591 592 def handle_future(self, node, n, result): 593 if isinstance(result, Error): 594 func = self.future.set_exception 595 else: 596 func = self.future.set_result 597 598 with use_environment(self.env).use(): 599 self.wrap_cb(func, result) 600 601 def receive(self, n, result): 602 self.callback(self.node, n, result) 603 604 605cdef createRawCallbackData(const VSAPI* funcs, VideoNode videonode, object cb, object wrap_call=None): 606 cbd = RawCallbackData(videonode, _env_current(), cb) 607 if not callable(cb): 608 cbd.for_future(cb, wrap_call) 609 cbd.funcs = funcs 610 return cbd 611 612cdef class CallbackData(object): 613 cdef VideoNode node 614 cdef const VSAPI *funcs 615 cdef object fileobj 616 cdef int output 617 cdef int requested 618 cdef int completed 619 cdef int total 620 cdef int num_planes 621 cdef bint y4m 622 cdef dict reorder 623 cdef object condition 624 cdef object progress_update 625 cdef str error 626 627 def __init__(self, fileobj, requested, total, num_planes, y4m, node, progress_update): 628 self.fileobj = fileobj 629 self.output = 0 630 self.requested = requested 631 self.completed = 0 632 self.total = total 633 self.num_planes = num_planes 634 self.y4m = y4m 635 self.condition = threading.Condition() 636 self.node = node 637 self.progress_update = progress_update 638 self.funcs = (<VideoNode>node).funcs 639 self.reorder = {} 640 641 642cdef class FramePtr(object): 643 cdef const VSFrameRef *f 644 cdef const VSAPI *funcs 645 646 def __init__(self): 647 raise Error('Class cannot be instantiated directly') 648 649 def __dealloc__(self): 650 if self.funcs: 651 self.funcs.freeFrame(self.f) 652 653cdef FramePtr createFramePtr(const VSFrameRef *f, const VSAPI *funcs): 654 cdef FramePtr instance = FramePtr.__new__(FramePtr) 655 instance.f = f 656 instance.funcs = funcs 657 return instance 658 659cdef void __stdcall frameDoneCallbackRaw(void *data, const VSFrameRef *f, int n, VSNodeRef *node, const char *errormsg) nogil: 660 with gil: 661 d = <RawCallbackData>data 662 if f == NULL: 663 result = '' 664 if errormsg != NULL: 665 result = errormsg.decode('utf-8') 666 result = Error(result) 667 668 else: 669 result = createConstVideoFrame(f, d.funcs, d.node.core.core) 670 671 try: 672 d.receive(n, result) 673 except: 674 import traceback 675 traceback.print_exc() 676 finally: 677 Py_DECREF(d) 678 679 680cdef void __stdcall frameDoneCallbackOutput(void *data, const VSFrameRef *f, int n, VSNodeRef *node, const char *errormsg) with gil: 681 cdef VideoFrame frame_obj 682 cdef VideoPlane plane 683 cdef int x 684 685 cdef CallbackData d = <CallbackData>data 686 d.completed += 1 687 688 if f == NULL: 689 d.total = d.requested 690 if errormsg == NULL: 691 d.error = 'Failed to retrieve frame ' + str(n) 692 else: 693 d.error = 'Failed to retrieve frame ' + str(n) + ' with error: ' + errormsg.decode('utf-8') 694 d.output += 1 695 else: 696 d.reorder[n] = createConstVideoFrame(f, d.funcs, d.node.core.core) 697 698 while d.output in d.reorder: 699 frame_obj = <VideoFrame>d.reorder[d.output] 700 bytes_per_sample = frame_obj.format.bytes_per_sample 701 try: 702 if d.y4m: 703 d.fileobj.write(b'FRAME\n') 704 for x in range(frame_obj.format.num_planes): 705 plane = VideoPlane.__new__(VideoPlane, frame_obj, x) 706 707 # This is a quick fix. 708 # Calling bytes(VideoPlane) should make the buffer continuous by 709 # copying the frame to a continous buffer 710 # if the stride does not match the width*bytes_per_sample. 711 if frame_obj.get_stride(x) != plane.width*bytes_per_sample: 712 d.fileobj.write(bytes(plane)) 713 else: 714 d.fileobj.write(plane) 715 716 except BaseException as e: 717 d.error = 'File write call returned an error: ' + str(e) 718 d.total = d.requested 719 720 del d.reorder[d.output] 721 d.output += 1 722 723 if d.progress_update is not None: 724 try: 725 d.progress_update(d.completed, d.total) 726 except BaseException as e: 727 d.error = 'Progress update caused an exception: ' + str(e) 728 d.total = d.requested 729 730 if d.requested < d.total: 731 d.node.funcs.getFrameAsync(d.requested, d.node.node, frameDoneCallbackOutput, data) 732 d.requested += 1 733 734 d.condition.acquire() 735 d.condition.notify() 736 d.condition.release() 737 738 739cdef object mapToDict(const VSMap *map, bint flatten, bint add_cache, VSCore *core, const VSAPI *funcs): 740 cdef int numKeys = funcs.propNumKeys(map) 741 retdict = {} 742 cdef const char *retkey 743 cdef char proptype 744 745 for x in range(numKeys): 746 retkey = funcs.propGetKey(map, x) 747 proptype = funcs.propGetType(map, retkey) 748 749 for y in range(funcs.propNumElements(map, retkey)): 750 if proptype == 'i': 751 newval = funcs.propGetInt(map, retkey, y, NULL) 752 elif proptype == 'f': 753 newval = funcs.propGetFloat(map, retkey, y, NULL) 754 elif proptype == 's': 755 newval = funcs.propGetData(map, retkey, y, NULL) 756 elif proptype =='c': 757 c = _get_core() 758 newval = createVideoNode(funcs.propGetNode(map, retkey, y, NULL), funcs, c) 759 760 if add_cache and not (newval.flags & vapoursynth.nfNoCache): 761 newval = c.std.Cache(clip=newval) 762 763 if isinstance(newval, dict): 764 newval = newval['dict'] 765 elif proptype =='v': 766 newval = createConstVideoFrame(funcs.propGetFrame(map, retkey, y, NULL), funcs, core) 767 elif proptype =='m': 768 newval = createFuncRef(funcs.propGetFunc(map, retkey, y, NULL), funcs) 769 770 if y == 0: 771 vval = newval 772 elif y == 1: 773 vval = [vval, newval] 774 else: 775 vval.append(newval) 776 retdict[retkey.decode('utf-8')] = vval 777 778 if not flatten: 779 return retdict 780 elif len(retdict) == 0: 781 return None 782 elif len(retdict) == 1: 783 a, b = retdict.popitem() 784 return b 785 else: 786 return retdict 787 788cdef void dictToMap(dict ndict, VSMap *inm, VSCore *core, const VSAPI *funcs) except *: 789 for key in ndict: 790 ckey = key.encode('utf-8') 791 val = ndict[key] 792 793 if isinstance(val, (str, bytes, bytearray, VideoNode)): 794 val = [val] 795 else: 796 try: 797 iter(val) 798 except: 799 val = [val] 800 801 for v in val: 802 if isinstance(v, VideoNode): 803 if funcs.propSetNode(inm, ckey, (<VideoNode>v).node, 1) != 0: 804 raise Error('not all values are of the same type in ' + key) 805 elif isinstance(v, VideoFrame): 806 if funcs.propSetFrame(inm, ckey, (<VideoFrame>v).constf, 1) != 0: 807 raise Error('not all values are of the same type in ' + key) 808 elif isinstance(v, Func): 809 if funcs.propSetFunc(inm, ckey, (<Func>v).ref, 1) != 0: 810 raise Error('not all values are of the same type in ' + key) 811 elif callable(v): 812 tf = createFuncPython(v, core, funcs) 813 814 if funcs.propSetFunc(inm, ckey, tf.ref, 1) != 0: 815 raise Error('not all values are of the same type in ' + key) 816 elif isinstance(v, int): 817 if funcs.propSetInt(inm, ckey, int(v), 1) != 0: 818 raise Error('not all values are of the same type in ' + key) 819 elif isinstance(v, float): 820 if funcs.propSetFloat(inm, ckey, float(v), 1) != 0: 821 raise Error('not all values are of the same type in ' + key) 822 elif isinstance(v, str): 823 s = str(v).encode('utf-8') 824 825 if funcs.propSetData(inm, ckey, s, -1, 1) != 0: 826 raise Error('not all values are of the same type in ' + key) 827 elif isinstance(v, (bytes, bytearray)): 828 if funcs.propSetData(inm, ckey, v, <int>len(v), 1) != 0: 829 raise Error('not all values are of the same type in ' + key) 830 else: 831 raise Error('argument ' + key + ' was passed an unsupported type (' + type(v).__name__ + ')') 832 833 834cdef void typedDictToMap(dict ndict, dict atypes, VSMap *inm, VSCore *core, const VSAPI *funcs) except *: 835 for key in ndict: 836 ckey = key.encode('utf-8') 837 val = ndict[key] 838 if val is None: 839 continue 840 841 if isinstance(val, (str, bytes, bytearray, VideoNode)) or not isinstance(val, Iterable): 842 val = [val] 843 844 for v in val: 845 if atypes[key][:4] == 'clip' and isinstance(v, VideoNode): 846 if funcs.propSetNode(inm, ckey, (<VideoNode>v).node, 1) != 0: 847 raise Error('not all values are of the same type in ' + key) 848 elif atypes[key][:5] == 'frame' and isinstance(v, VideoFrame): 849 if funcs.propSetFrame(inm, ckey, (<VideoFrame>v).constf, 1) != 0: 850 raise Error('not all values are of the same type in ' + key) 851 elif atypes[key][:4] == 'func' and isinstance(v, Func): 852 if funcs.propSetFunc(inm, ckey, (<Func>v).ref, 1) != 0: 853 raise Error('not all values are of the same type in ' + key) 854 elif atypes[key][:4] == 'func' and callable(v): 855 tf = createFuncPython(v, core, funcs) 856 if funcs.propSetFunc(inm, ckey, tf.ref, 1) != 0: 857 raise Error('not all values are of the same type in ' + key) 858 elif atypes[key][:3] == 'int': 859 if funcs.propSetInt(inm, ckey, int(v), 1) != 0: 860 raise Error('not all values are of the same type in ' + key) 861 elif atypes[key][:5] == 'float': 862 if funcs.propSetFloat(inm, ckey, float(v), 1) != 0: 863 raise Error('not all values are of the same type in ' + key) 864 elif atypes[key][:4] == 'data': 865 if not isinstance(v, (str, bytes, bytearray)): 866 v = str(v) 867 if isinstance(v, str): 868 s = v.encode('utf-8') 869 else: 870 s = v 871 if funcs.propSetData(inm, ckey, s, <int>len(s), 1) != 0: 872 raise Error('not all values are of the same type in ' + key) 873 else: 874 raise Error('argument ' + key + ' was passed an unsupported type (expected ' + atypes[key] + ' compatible type but got ' + type(v).__name__ + ')') 875 if len(val) == 0: 876 # set an empty key if it's an empty array 877 if atypes[key][:4] == 'clip': 878 funcs.propSetNode(inm, ckey, NULL, 2) 879 elif atypes[key][:5] == 'frame': 880 funcs.propSetFrame(inm, ckey, NULL, 2) 881 elif atypes[key][:4] == 'func': 882 funcs.propSetFunc(inm, ckey, NULL, 2) 883 elif atypes[key][:3] == 'int': 884 funcs.propSetInt(inm, ckey, 0, 2) 885 elif atypes[key][:5] == 'float': 886 funcs.propSetFloat(inm, ckey, 0, 2) 887 elif atypes[key][:4] == 'data': 888 funcs.propSetData(inm, ckey, NULL, 0, 2) 889 else: 890 raise Error('argument ' + key + ' has an unknown type: ' + atypes[key]) 891 892cdef class Format(object): 893 cdef readonly int id 894 cdef readonly str name 895 cdef readonly object color_family 896 cdef readonly object sample_type 897 cdef readonly int bits_per_sample 898 cdef readonly int bytes_per_sample 899 cdef readonly int subsampling_w 900 cdef readonly int subsampling_h 901 cdef readonly int num_planes 902 903 def __init__(self): 904 raise Error('Class cannot be instantiated directly') 905 906 def _as_dict(self): 907 return { 908 'color_family': self.color_family, 909 'sample_type': self.sample_type, 910 'bits_per_sample': self.bits_per_sample, 911 'subsampling_w': self.subsampling_w, 912 'subsampling_h': self.subsampling_h 913 } 914 915 def replace(self, **kwargs): 916 core = kwargs.pop("core", None) or _get_core() 917 vals = self._as_dict() 918 vals.update(**kwargs) 919 return core.register_format(**vals) 920 921 def __eq__(self, other): 922 if not isinstance(other, Format): 923 return False 924 return other.id == self.id 925 926 def __int__(self): 927 return self.id 928 929 def __str__(self): 930 return ('Format Descriptor\n' 931 f'\tId: {self.id:d}\n' 932 f'\tName: {self.name}\n' 933 f'\tColor Family: {self.color_family.name}\n' 934 f'\tSample Type: {self.sample_type.name.capitalize()}\n' 935 f'\tBits Per Sample: {self.bits_per_sample:d}\n' 936 f'\tBytes Per Sample: {self.bytes_per_sample:d}\n' 937 f'\tPlanes: {self.num_planes:d}\n' 938 f'\tSubsampling W: {self.subsampling_w:d}\n' 939 f'\tSubsampling H: {self.subsampling_h:d}\n') 940 941cdef Format createFormat(const VSFormat *f): 942 cdef Format instance = Format.__new__(Format) 943 instance.id = f.id 944 instance.name = (<const char *>f.name).decode('utf-8') 945 instance.color_family = ColorFamily(f.colorFamily) 946 instance.sample_type = SampleType(f.sampleType) 947 instance.bits_per_sample = f.bitsPerSample 948 instance.bytes_per_sample = f.bytesPerSample 949 instance.subsampling_w = f.subSamplingW 950 instance.subsampling_h = f.subSamplingH 951 instance.num_planes = f.numPlanes 952 return instance 953 954cdef class VideoProps(object): 955 cdef const VSFrameRef *constf 956 cdef VSFrameRef *f 957 cdef VSCore *core 958 cdef const VSAPI *funcs 959 cdef bint readonly 960 961 def __init__(self): 962 raise Error('Class cannot be instantiated directly') 963 964 def __dealloc__(self): 965 if self.funcs: 966 self.funcs.freeFrame(self.constf) 967 968 def __contains__(self, str name): 969 cdef const VSMap *m = self.funcs.getFramePropsRO(self.constf) 970 cdef bytes b = name.encode('utf-8') 971 cdef int numelem = self.funcs.propNumElements(m, b) 972 return numelem > 0 973 974 def __getitem__(self, str name): 975 cdef const VSMap *m = self.funcs.getFramePropsRO(self.constf) 976 cdef bytes b = name.encode('utf-8') 977 cdef list ol = [] 978 cdef int numelem = self.funcs.propNumElements(m, b) 979 cdef const int64_t *intArray 980 cdef const double *floatArray 981 cdef const char *data 982 983 if numelem < 0: 984 raise KeyError('No key named ' + name + ' exists') 985 cdef char t = self.funcs.propGetType(m, b) 986 if t == 'i': 987 if numelem > 0: 988 intArray = self.funcs.propGetIntArray(m, b, NULL) 989 for i in range(numelem): 990 ol.append(intArray[i]) 991 elif t == 'f': 992 if numelem > 0: 993 floatArray = self.funcs.propGetFloatArray(m, b, NULL) 994 for i in range(numelem): 995 ol.append(floatArray[i]) 996 elif t == 's': 997 for i in range(numelem): 998 data = self.funcs.propGetData(m, b, i, NULL) 999 ol.append(data[:self.funcs.propGetDataSize(m, b, i, NULL)]) 1000 elif t == 'c': 1001 for i in range(numelem): 1002 ol.append(createVideoNode(self.funcs.propGetNode(m, b, i, NULL), self.funcs, _get_core())) 1003 elif t == 'v': 1004 for i in range(numelem): 1005 ol.append(createConstVideoFrame(self.funcs.propGetFrame(m, b, i, NULL), self.funcs, self.core)) 1006 elif t == 'm': 1007 for i in range(numelem): 1008 ol.append(createFuncRef(self.funcs.propGetFunc(m, b, i, NULL), self.funcs)) 1009 1010 if len(ol) == 1: 1011 return ol[0] 1012 else: 1013 return ol 1014 1015 def __setitem__(self, str name, value): 1016 if self.readonly: 1017 raise Error('Cannot delete properties of a read only object') 1018 cdef VSMap *m = self.funcs.getFramePropsRW(self.f) 1019 cdef bytes b = name.encode('utf-8') 1020 cdef const VSAPI *funcs = self.funcs 1021 val = value 1022 if isinstance(val, (str, bytes, bytearray, VideoNode)): 1023 val = [val] 1024 else: 1025 try: 1026 iter(val) 1027 except: 1028 val = [val] 1029 self.__delitem__(name) 1030 try: 1031 for v in val: 1032 if isinstance(v, VideoNode): 1033 if funcs.propSetNode(m, b, (<VideoNode>v).node, 1) != 0: 1034 raise Error('Not all values are of the same type') 1035 elif isinstance(v, VideoFrame): 1036 if funcs.propSetFrame(m, b, (<VideoFrame>v).constf, 1) != 0: 1037 raise Error('Not all values are of the same type') 1038 elif isinstance(v, Func): 1039 if funcs.propSetFunc(m, b, (<Func>v).ref, 1) != 0: 1040 raise Error('Not all values are of the same type') 1041 elif callable(v): 1042 tf = createFuncPython(v, self.core, self.funcs) 1043 if funcs.propSetFunc(m, b, tf.ref, 1) != 0: 1044 raise Error('Not all values are of the same type') 1045 elif isinstance(v, int): 1046 if funcs.propSetInt(m, b, int(v), 1) != 0: 1047 raise Error('Not all values are of the same type') 1048 elif isinstance(v, float): 1049 if funcs.propSetFloat(m, b, float(v), 1) != 0: 1050 raise Error('Not all values are of the same type') 1051 elif isinstance(v, str): 1052 s = str(v).encode('utf-8') 1053 if funcs.propSetData(m, b, s, -1, 1) != 0: 1054 raise Error('Not all values are of the same type') 1055 elif isinstance(v, (bytes, bytearray)): 1056 if funcs.propSetData(m, b, v, <int>len(v), 1) != 0: 1057 raise Error('Not all values are of the same type') 1058 else: 1059 raise Error('Setter was passed an unsupported type (' + type(v).__name__ + ')') 1060 except Error: 1061 self.__delitem__(name) 1062 raise 1063 1064 def __delitem__(self, str name): 1065 if self.readonly: 1066 raise Error('Cannot delete properties of a read only object') 1067 cdef VSMap *m = self.funcs.getFramePropsRW(self.f) 1068 cdef bytes b = name.encode('utf-8') 1069 self.funcs.propDeleteKey(m, b) 1070 1071 def __setattr__(self, name, value): 1072 self[name] = value 1073 1074 def __delattr__(self, name): 1075 del self[name] 1076 1077 # Only the methods __getattr__ and keys are required for the support of 1078 # >>> dict(frame.props) 1079 # this can be shown at Objects/dictobject.c:static int dict_merge(PyObject *, PyObject *, int) 1080 # in the generic code path. 1081 1082 def __getattr__(self, name): 1083 try: 1084 return self[name] 1085 except KeyError as e: 1086 raise AttributeError from e 1087 1088 def keys(self): 1089 cdef const VSMap *m = self.funcs.getFramePropsRO(self.constf) 1090 cdef int numkeys = self.funcs.propNumKeys(m) 1091 result = set() 1092 for i in range(numkeys): 1093 result.add(self.funcs.propGetKey(m, i).decode('utf-8')) 1094 return result 1095 1096 def values(self): 1097 return {self[key] for key in self.keys()} 1098 1099 def items(self): 1100 return {(key, self[key]) for key in self.keys()} 1101 1102 def get(self, key, default=None): 1103 if key in self: 1104 return self[key] 1105 return default 1106 1107 def pop(self, key, default=_EMPTY): 1108 if key in self: 1109 value = self[key] 1110 del self[key] 1111 return value 1112 1113 # The is-operator is required to ensure that 1114 # we have actually passed the _EMPTY list instead any other list with length zero. 1115 if default is _EMPTY: 1116 raise KeyError 1117 1118 return default 1119 1120 def popitem(self): 1121 if len(self) <= 0: 1122 raise KeyError 1123 key = next(iter(self.keys())) 1124 return (key, self.pop(key)) 1125 1126 def setdefault(self, key, default=0): 1127 """ 1128 Behaves like the dict.setdefault function but since setting None is not supported, 1129 it will default to zero. 1130 """ 1131 if key not in self: 1132 self[key] = default 1133 return self[key] 1134 1135 def update(self, *args, **kwargs): 1136 # This code converts the positional argument into a dict which we then can update 1137 # with the kwargs. 1138 if 0 < len(args) < 2: 1139 args = args[0] 1140 if not isinstance(args, dict): 1141 args = dict(args) 1142 elif len(args) > 1: 1143 raise TypeError("update takes 1 positional argument but %d was given" % len(args)) 1144 else: 1145 args = {} 1146 1147 args.update(kwargs) 1148 1149 for k, v in args.items(): 1150 self[k] = v 1151 1152 def clear(self): 1153 for _ in range(len(self)): 1154 self.popitem() 1155 1156 def copy(self): 1157 """ 1158 We can't copy VideoFrames directly, so we're just gonna return a real dictionary. 1159 """ 1160 return dict(self) 1161 1162 def __iter__(self): 1163 yield from self.keys() 1164 1165 def __len__(self): 1166 cdef const VSMap *m = self.funcs.getFramePropsRO(self.constf) 1167 return self.funcs.propNumKeys(m) 1168 1169 def __dir__(self): 1170 return super(VideoProps, self).__dir__() + list(self.keys()) 1171 1172 def __repr__(self): 1173 return "<vapoursynth.VideoProps %r>" % dict(self) 1174 1175cdef VideoProps createVideoProps(VideoFrame f): 1176 cdef VideoProps instance = VideoProps.__new__(VideoProps) 1177# since the vsapi only returns const refs when cloning a VSFrameRef it is safe to cast away the const here 1178 instance.constf = f.funcs.cloneFrameRef(f.constf) 1179 instance.f = NULL 1180 instance.funcs = f.funcs 1181 instance.core = f.core 1182 instance.readonly = f.readonly 1183 if not instance.readonly: 1184 instance.f = <VSFrameRef *>instance.constf 1185 return instance 1186 1187# Make sure the VideoProps-Object quacks like a Mapping. 1188Mapping.register(VideoProps) 1189 1190 1191cdef class VideoFrame(object): 1192 cdef const VSFrameRef *constf 1193 cdef VSFrameRef *f 1194 cdef VSCore *core 1195 cdef const VSAPI *funcs 1196 cdef readonly Format format 1197 cdef readonly int width 1198 cdef readonly int height 1199 cdef readonly bint readonly 1200 cdef readonly VideoProps props 1201 1202 cdef object __weakref__ 1203 1204 def __init__(self): 1205 raise Error('Class cannot be instantiated directly') 1206 1207 def __dealloc__(self): 1208 if self.funcs: 1209 self.funcs.freeFrame(self.constf) 1210 1211 def copy(self): 1212 return createVideoFrame(self.funcs.copyFrame(self.constf, self.core), self.funcs, self.core) 1213 1214 def get_read_ptr(self, int plane): 1215 if plane < 0 or plane >= self.format.num_planes: 1216 raise IndexError('Specified plane index out of range') 1217 cdef const uint8_t *d = self.funcs.getReadPtr(self.constf, plane) 1218 return ctypes.c_void_p(<uintptr_t>d) 1219 1220 def get_read_array(self, int plane): 1221 if plane < 0 or plane >= self.format.num_planes: 1222 raise IndexError('Specified plane index out of range') 1223 cdef const uint8_t *d = self.funcs.getReadPtr(self.constf, plane) 1224 stride = self.get_stride(plane) // self.format.bytes_per_sample 1225 width = self.width 1226 height = self.height 1227 if plane is not 0: 1228 height >>= self.format.subsampling_h 1229 width >>= self.format.subsampling_w 1230 array = None 1231 if self.format.sample_type == INTEGER: 1232 if self.format.bytes_per_sample == 1: 1233 array = <uint8_t[:height, :stride]> d 1234 elif self.format.bytes_per_sample == 2: 1235 array = <uint16_t[:height, :stride]> (<uint16_t*>d) 1236 elif self.format.bytes_per_sample == 4: 1237 array = <uint32_t[:height, :stride]> (<uint32_t*>d) 1238 elif self.format.sample_type == FLOAT: 1239 array = <float[:height, :stride]> (<float*>d) 1240 if array is not None: 1241 return array[:height, :width] 1242 return None 1243 1244 def get_write_ptr(self, int plane): 1245 if self.readonly: 1246 raise Error('Cannot obtain write pointer to read only frame') 1247 if plane < 0 or plane >= self.format.num_planes: 1248 raise IndexError('Specified plane index out of range') 1249 cdef uint8_t *d = self.funcs.getWritePtr(self.f, plane) 1250 return ctypes.c_void_p(<uintptr_t>d) 1251 1252 def get_write_array(self, int plane): 1253 if self.readonly: 1254 raise Error('Cannot obtain write pointer to read only frame') 1255 if plane < 0 or plane >= self.format.num_planes: 1256 raise IndexError('Specified plane index out of range') 1257 cdef uint8_t *d = self.funcs.getWritePtr(self.f, plane) 1258 stride = self.get_stride(plane) // self.format.bytes_per_sample 1259 width = self.width 1260 height = self.height 1261 if plane is not 0: 1262 height >>= self.format.subsampling_h 1263 width >>= self.format.subsampling_w 1264 array = None 1265 if self.format.sample_type == INTEGER: 1266 if self.format.bytes_per_sample == 1: 1267 array = <uint8_t[:height, :stride]> d 1268 elif self.format.bytes_per_sample == 2: 1269 array = <uint16_t[:height, :stride]> (<uint16_t*>d) 1270 elif self.format.bytes_per_sample == 4: 1271 array = <uint32_t[:height, :stride]> (<uint32_t*>d) 1272 elif self.format.sample_type == FLOAT: 1273 array = <float[:height, :stride]> (<float*>d) 1274 if array is not None: 1275 return array[:height, :width] 1276 return None 1277 1278 def get_stride(self, int plane): 1279 if plane < 0 or plane >= self.format.num_planes: 1280 raise IndexError('Specified plane index out of range') 1281 return self.funcs.getStride(self.constf, plane) 1282 1283 def planes(self): 1284 cdef int x 1285 for x in range(self.format.num_planes): 1286 yield VideoPlane.__new__(VideoPlane, self, x) 1287 1288 def __str__(self): 1289 cdef str s = 'VideoFrame\n' 1290 s += '\tFormat: ' + self.format.name + '\n' 1291 s += '\tWidth: ' + str(self.width) + '\n' 1292 s += '\tHeight: ' + str(self.height) + '\n' 1293 return s 1294 1295 1296cdef VideoFrame createConstVideoFrame(const VSFrameRef *constf, const VSAPI *funcs, VSCore *core): 1297 cdef VideoFrame instance = VideoFrame.__new__(VideoFrame) 1298 instance.constf = constf 1299 instance.f = NULL 1300 instance.funcs = funcs 1301 instance.core = core 1302 instance.readonly = True 1303 instance.format = createFormat(funcs.getFrameFormat(constf)) 1304 instance.width = funcs.getFrameWidth(constf, 0) 1305 instance.height = funcs.getFrameHeight(constf, 0) 1306 instance.props = createVideoProps(instance) 1307 return instance 1308 1309 1310cdef VideoFrame createVideoFrame(VSFrameRef *f, const VSAPI *funcs, VSCore *core): 1311 cdef VideoFrame instance = VideoFrame.__new__(VideoFrame) 1312 instance.constf = f 1313 instance.f = f 1314 instance.funcs = funcs 1315 instance.core = core 1316 instance.readonly = False 1317 instance.format = createFormat(funcs.getFrameFormat(f)) 1318 instance.width = funcs.getFrameWidth(f, 0) 1319 instance.height = funcs.getFrameHeight(f, 0) 1320 instance.props = createVideoProps(instance) 1321 return instance 1322 1323 1324cdef class VideoPlane: 1325 cdef VideoFrame frame 1326 cdef int plane 1327 cdef Py_ssize_t shape[2] 1328 cdef Py_ssize_t strides[2] 1329 cdef char* format 1330 1331 def __cinit__(self, VideoFrame frame, int plane): 1332 cdef Py_ssize_t itemsize 1333 1334 if not (0 <= plane < frame.format.num_planes): 1335 raise IndexError("specified plane index out of range") 1336 1337 self.shape[1] = <Py_ssize_t> frame.width 1338 self.shape[0] = <Py_ssize_t> frame.height 1339 if plane: 1340 self.shape[1] >>= <Py_ssize_t> frame.format.subsampling_w 1341 self.shape[0] >>= <Py_ssize_t> frame.format.subsampling_h 1342 1343 self.strides[1] = itemsize = <Py_ssize_t> frame.format.bytes_per_sample 1344 self.strides[0] = <Py_ssize_t> frame.funcs.getStride(frame.constf, plane) 1345 1346 if frame.format.sample_type == INTEGER: 1347 if itemsize == 1: 1348 self.format = b'B' 1349 elif itemsize == 2: 1350 self.format = b'H' 1351 elif itemsize == 4: 1352 self.format = b'I' 1353 elif frame.format.sample_type == FLOAT: 1354 if itemsize == 2: 1355 self.format = b'e' 1356 elif itemsize == 4: 1357 self.format = b'f' 1358 1359 self.frame = frame 1360 self.plane = plane 1361 1362 @property 1363 def width(self): 1364 """Plane's pixel width.""" 1365 if self.plane: 1366 return self.frame.width >> self.frame.format.subsampling_w 1367 return self.frame.width 1368 1369 @property 1370 def height(self): 1371 """Plane's pixel height.""" 1372 if self.plane: 1373 return self.frame.height >> self.frame.format.subsampling_h 1374 return self.frame.height 1375 1376 def __getbuffer__(self, Py_buffer* view, int flags): 1377 if (flags & PyBUF_F_CONTIGUOUS) == PyBUF_F_CONTIGUOUS: 1378 raise BufferError("C-contiguous buffer only.") 1379 1380 if self.frame.readonly: 1381 if flags & PyBUF_WRITABLE: 1382 raise BufferError("Object is not writable.") 1383 view.buf = (<void*> self.frame.funcs.getReadPtr(self.frame.constf, self.plane)) 1384 else: 1385 view.buf = (<void*> self.frame.funcs.getWritePtr(self.frame.f, self.plane)) 1386 1387 if flags & PyBUF_STRIDES: 1388 view.shape = self.shape 1389 view.strides = self.strides 1390 else: 1391 view.shape = NULL 1392 view.strides = NULL 1393 1394 if flags & PyBUF_FORMAT: 1395 view.format = self.format 1396 else: 1397 view.format = NULL 1398 1399 view.obj = self 1400 view.len = self.shape[0] * self.shape[1] * self.strides[1] 1401 view.readonly = self.frame.readonly 1402 view.itemsize = self.strides[1] 1403 view.ndim = 2 1404 view.suboffsets = NULL 1405 view.internal = NULL 1406 1407 1408cdef class VideoNode(object): 1409 cdef VSNodeRef *node 1410 cdef const VSAPI *funcs 1411 cdef Core core 1412 cdef const VSVideoInfo *vi 1413 cdef readonly Format format 1414 cdef readonly int width 1415 cdef readonly int height 1416 cdef readonly int num_frames 1417 cdef readonly int64_t fps_num 1418 cdef readonly int64_t fps_den 1419 cdef readonly object fps 1420 cdef readonly int flags 1421 1422 cdef object __weakref__ 1423 1424 def __init__(self): 1425 raise Error('Class cannot be instantiated directly') 1426 1427 def __dealloc__(self): 1428 if self.funcs: 1429 self.funcs.freeNode(self.node) 1430 1431 def __getattr__(self, name): 1432 err = False 1433 try: 1434 obj = self.core.__getattr__(name) 1435 if isinstance(obj, Plugin): 1436 (<Plugin>obj).injected_arg = self 1437 return obj 1438 except AttributeError: 1439 err = True 1440 if err: 1441 raise AttributeError('There is no attribute or namespace named ' + name) 1442 1443 cdef ensure_valid_frame_number(self, int n): 1444 if n < 0: 1445 raise ValueError('Requesting negative frame numbers not allowed') 1446 if (self.num_frames > 0) and (n >= self.num_frames): 1447 raise ValueError('Requesting frame number is beyond the last frame') 1448 1449 def get_frame(self, int n): 1450 cdef char errorMsg[512] 1451 cdef char *ep = errorMsg 1452 cdef const VSFrameRef *f 1453 self.ensure_valid_frame_number(n) 1454 1455 with nogil: 1456 f = self.funcs.getFrame(n, self.node, errorMsg, 500) 1457 if f == NULL: 1458 if (errorMsg[0]): 1459 raise Error(ep.decode('utf-8')) 1460 else: 1461 raise Error('Internal error - no error given') 1462 else: 1463 return createConstVideoFrame(f, self.funcs, self.core.core) 1464 1465 def get_frame_async_raw(self, int n, object cb, object future_wrapper=None): 1466 self.ensure_valid_frame_number(n) 1467 1468 data = createRawCallbackData(self.funcs, self, cb, future_wrapper) 1469 Py_INCREF(data) 1470 with nogil: 1471 self.funcs.getFrameAsync(n, self.node, frameDoneCallbackRaw, <void *>data) 1472 1473 def get_frame_async(self, int n): 1474 from concurrent.futures import Future 1475 fut = Future() 1476 fut.set_running_or_notify_cancel() 1477 1478 try: 1479 self.get_frame_async_raw(n, fut) 1480 except Exception as e: 1481 fut.set_exception(e) 1482 1483 return fut 1484 1485 def set_output(self, int index = 0, VideoNode alpha = None): 1486 cdef const VSFormat *aformat = NULL 1487 clip = self 1488 if alpha is not None: 1489 if (self.vi.width != alpha.vi.width) or (self.vi.height != alpha.vi.height): 1490 raise Error('Alpha clip dimensions must match the main video') 1491 if (self.num_frames != alpha.num_frames): 1492 raise Error('Alpha clip length must match the main video') 1493 if (self.vi.format) and (alpha.vi.format): 1494 if (alpha.vi.format != self.funcs.registerFormat(GRAY, self.vi.format.sampleType, self.vi.format.bitsPerSample, 0, 0, self.core.core)): 1495 raise Error('Alpha clip format must match the main video') 1496 elif (self.vi.format) or (alpha.vi.format): 1497 raise Error('Format must be either known or unknown for both alpha and main clip') 1498 1499 clip = AlphaOutputTuple(self, alpha) 1500 1501 _get_output_dict("set_output")[index] = clip 1502 1503 def output(self, object fileobj not None, bint y4m = False, object progress_update = None, int prefetch = 0): 1504 if prefetch < 1: 1505 prefetch = self.core.num_threads 1506 1507 # stdout usually isn't in binary mode so let's automatically compensate for that 1508 if fileobj == sys.stdout: 1509 fileobj = sys.stdout.buffer 1510 1511 cdef CallbackData d = CallbackData(fileobj, min(prefetch, self.num_frames), self.num_frames, self.format.num_planes, y4m, self, progress_update) 1512 1513 # this is also an implicit test that the progress_update callback at least vaguely matches the requirements 1514 if (progress_update is not None): 1515 progress_update(0, d.total) 1516 1517 if (self.format is None or self.format.color_family not in (YUV, GRAY)) and y4m: 1518 raise Error('Can only apply y4m headers to YUV and Gray format clips') 1519 1520 y4mformat = '' 1521 numbits = '' 1522 1523 if y4m: 1524 if self.format.color_family == GRAY: 1525 y4mformat = 'mono' 1526 if self.format.bits_per_sample > 8: 1527 y4mformat = y4mformat + str(self.format.bits_per_sample) 1528 elif self.format.color_family == YUV: 1529 if self.format.subsampling_w == 1 and self.format.subsampling_h == 1: 1530 y4mformat = '420' 1531 elif self.format.subsampling_w == 1 and self.format.subsampling_h == 0: 1532 y4mformat = '422' 1533 elif self.format.subsampling_w == 0 and self.format.subsampling_h == 0: 1534 y4mformat = '444' 1535 elif self.format.subsampling_w == 2 and self.format.subsampling_h == 2: 1536 y4mformat = '410' 1537 elif self.format.subsampling_w == 2 and self.format.subsampling_h == 0: 1538 y4mformat = '411' 1539 elif self.format.subsampling_w == 0 and self.format.subsampling_h == 1: 1540 y4mformat = '440' 1541 if self.format.bits_per_sample > 8: 1542 y4mformat = y4mformat + 'p' + str(self.format.bits_per_sample) 1543 1544 if len(y4mformat) > 0: 1545 y4mformat = 'C' + y4mformat + ' ' 1546 1547 cdef str header = 'YUV4MPEG2 ' + y4mformat + 'W' + str(self.width) + ' H' + str(self.height) + ' F' + str(self.fps_num) + ':' + str(self.fps_den) + ' Ip A0:0\n' 1548 if y4m: 1549 fileobj.write(header.encode('utf-8')) 1550 d.condition.acquire() 1551 1552 for n in range(min(prefetch, d.total)): 1553 self.funcs.getFrameAsync(n, self.node, frameDoneCallbackOutput, <void *>d) 1554 1555 stored_exception = None 1556 while d.total != d.completed: 1557 try: 1558 d.condition.wait() 1559 except BaseException, e: 1560 d.total = d.requested 1561 stored_exception = e 1562 d.condition.release() 1563 1564 if stored_exception is not None: 1565 raise stored_exception 1566 1567 if d.error: 1568 raise Error(d.error) 1569 1570 def __add__(x, y): 1571 if not isinstance(x, VideoNode) or not isinstance(y, VideoNode): 1572 return NotImplemented 1573 return (<VideoNode>x).core.std.Splice(clips=[x, y]) 1574 1575 def __mul__(a, b): 1576 if isinstance(a, VideoNode): 1577 node = a 1578 val = b 1579 else: 1580 node = b 1581 val = a 1582 1583 if not isinstance(val, int): 1584 raise TypeError('Clips may only be repeated by integer factors') 1585 if val <= 0: 1586 raise ValueError('Loop count must be one or bigger') 1587 return (<VideoNode>node).core.std.Loop(clip=node, times=val) 1588 1589 def __getitem__(self, val): 1590 if isinstance(val, slice): 1591 if val.step is not None and val.step == 0: 1592 raise ValueError('Slice step cannot be zero') 1593 1594 indices = val.indices(self.num_frames) 1595 1596 step = indices[2] 1597 1598 if step > 0: 1599 start = indices[0] 1600 stop = indices[1] 1601 else: 1602 start = indices[1] 1603 stop = indices[0] 1604 1605 ret = self 1606 1607 if step > 0 and stop is not None: 1608 stop -= 1 1609 if step < 0 and start is not None: 1610 start += 1 1611 1612 if start is not None and stop is not None: 1613 ret = self.core.std.Trim(clip=ret, first=start, last=stop) 1614 elif start is not None: 1615 ret = self.core.std.Trim(clip=ret, first=start) 1616 elif stop is not None: 1617 ret = self.core.std.Trim(clip=ret, last=stop) 1618 1619 if step < 0: 1620 ret = self.core.std.Reverse(clip=ret) 1621 1622 if abs(step) != 1: 1623 ret = self.core.std.SelectEvery(clip=ret, cycle=abs(step), offsets=[0]) 1624 1625 return ret 1626 elif isinstance(val, int): 1627 if val < 0: 1628 n = self.num_frames + val 1629 else: 1630 n = val 1631 if n < 0 or (self.num_frames > 0 and n >= self.num_frames): 1632 raise IndexError('List index out of bounds') 1633 return self.core.std.Trim(clip=self, first=n, length=1) 1634 else: 1635 raise TypeError("index must be int or slice") 1636 1637 def frames(self): 1638 for frameno in range(len(self)): 1639 yield self.get_frame(frameno) 1640 1641 def __dir__(self): 1642 plugins = [plugin["namespace"] for plugin in self.core.get_plugins().values()] 1643 return super(VideoNode, self).__dir__() + plugins 1644 1645 def __len__(self): 1646 return self.num_frames 1647 1648 def __str__(self): 1649 cdef str s = 'VideoNode\n' 1650 1651 if self.format: 1652 s += '\tFormat: ' + self.format.name + '\n' 1653 else: 1654 s += '\tFormat: dynamic\n' 1655 1656 if not self.width or not self.height: 1657 s += '\tWidth: dynamic\n' 1658 s += '\tHeight: dynamic\n' 1659 else: 1660 s += '\tWidth: ' + str(self.width) + '\n' 1661 s += '\tHeight: ' + str(self.height) + '\n' 1662 1663 s += '\tNum Frames: ' + str(self.num_frames) + '\n' 1664 1665 s += <str>f"\tFPS: {self.fps or 'dynamic'}\n" 1666 1667 if self.flags: 1668 s += '\tFlags:' 1669 if (self.flags & vapoursynth.nfNoCache): 1670 s += ' NoCache' 1671 if (self.flags & vapoursynth.nfIsCache): 1672 s += ' IsCache' 1673 if (self.flags & vapoursynth.nfMakeLinear): 1674 s += ' MakeLinear' 1675 s += '\n' 1676 else: 1677 s += '\tFlags: None\n' 1678 1679 return s 1680 1681cdef VideoNode createVideoNode(VSNodeRef *node, const VSAPI *funcs, Core core): 1682 cdef VideoNode instance = VideoNode.__new__(VideoNode) 1683 instance.core = core 1684 instance.node = node 1685 instance.funcs = funcs 1686 instance.vi = funcs.getVideoInfo(node) 1687 1688 if (instance.vi.format): 1689 instance.format = createFormat(instance.vi.format) 1690 else: 1691 instance.format = None 1692 1693 instance.width = instance.vi.width 1694 instance.height = instance.vi.height 1695 instance.num_frames = instance.vi.numFrames 1696 instance.fps_num = <int64_t>instance.vi.fpsNum 1697 instance.fps_den = <int64_t>instance.vi.fpsDen 1698 if instance.vi.fpsDen: 1699 instance.fps = Fraction( 1700 <int64_t> instance.vi.fpsNum, <int64_t> instance.vi.fpsDen) 1701 else: 1702 instance.fps = Fraction(0, 1) 1703 instance.flags = instance.vi.flags 1704 return instance 1705 1706cdef class Core(object): 1707 cdef VSCore *core 1708 cdef const VSAPI *funcs 1709 cdef public bint add_cache 1710 1711 cdef object __weakref__ 1712 1713 def __init__(self): 1714 raise Error('Class cannot be instantiated directly') 1715 1716 def __dealloc__(self): 1717 if self.funcs: 1718 self.funcs.freeCore(self.core) 1719 1720 property num_threads: 1721 def __get__(self): 1722 cdef VSCoreInfo v 1723 self.funcs.getCoreInfo2(self.core, &v) 1724 return v.numThreads 1725 1726 def __set__(self, int value): 1727 self.funcs.setThreadCount(value, self.core) 1728 1729 property max_cache_size: 1730 def __get__(self): 1731 cdef VSCoreInfo v 1732 self.funcs.getCoreInfo2(self.core, &v) 1733 cdef int64_t current_size = <int64_t>v.maxFramebufferSize 1734 current_size = current_size + 1024 * 1024 - 1 1735 current_size = current_size // <int64_t>(1024 * 1024) 1736 return current_size 1737 1738 def __set__(self, int mb): 1739 if mb <= 0: 1740 raise ValueError('Maximum cache size must be a positive number') 1741 cdef int64_t new_size = mb 1742 new_size = new_size * 1024 * 1024 1743 self.funcs.setMaxCacheSize(new_size, self.core) 1744 1745 def __getattr__(self, name): 1746 cdef VSPlugin *plugin 1747 tname = name.encode('utf-8') 1748 cdef const char *cname = tname 1749 plugin = self.funcs.getPluginByNs(cname, self.core) 1750 1751 if plugin: 1752 return createPlugin(plugin, name, self.funcs, self) 1753 else: 1754 raise AttributeError('No attribute with the name ' + name + ' exists. Did you mistype a plugin namespace?') 1755 1756 def set_max_cache_size(self, int mb): 1757 self.max_cache_size = mb 1758 return self.max_cache_size 1759 1760 def get_plugins(self): 1761 cdef VSMap *m = self.funcs.getPlugins(self.core) 1762 cdef VSMap *n 1763 cdef bytes b 1764 cdef dict sout = {} 1765 1766 for i in range(self.funcs.propNumKeys(m)): 1767 a = self.funcs.propGetData(m, self.funcs.propGetKey(m, i), 0, NULL) 1768 a = a.decode('utf-8') 1769 a = a.split(';', 2) 1770 1771 plugin_dict = {} 1772 plugin_dict['namespace'] = a[0] 1773 plugin_dict['identifier'] = a[1] 1774 plugin_dict['name'] = a[2] 1775 1776 function_dict = {} 1777 1778 b = a[1].encode('utf-8') 1779 n = self.funcs.getFunctions(self.funcs.getPluginById(b, self.core)) 1780 1781 for j in range(self.funcs.propNumKeys(n)): 1782 c = self.funcs.propGetData(n, self.funcs.propGetKey(n, j), 0, NULL) 1783 c = c.decode('utf-8') 1784 c = c.split(';', 1) 1785 function_dict[c[0]] = c[1] 1786 1787 plugin_dict['functions'] = function_dict 1788 sout[plugin_dict['identifier']] = plugin_dict 1789 self.funcs.freeMap(n) 1790 1791 self.funcs.freeMap(m) 1792 return sout 1793 1794 def list_functions(self): 1795 sout = "" 1796 plugins = self.get_plugins() 1797 for plugin in sorted(plugins.keys()): 1798 sout += 'name: ' + plugins[plugin]['name'] + '\n' 1799 sout += 'namespace: ' + plugins[plugin]['namespace'] + '\n' 1800 sout += 'identifier: ' + plugins[plugin]['identifier'] + '\n' 1801 for function in sorted(plugins[plugin]['functions'].keys()): 1802 line = '\t' + function + '(' + plugins[plugin]['functions'][function].replace(';', '; ') + ')\n' 1803 sout += line.replace('; )', ')') 1804 return sout 1805 1806 def register_format(self, int color_family, int sample_type, int bits_per_sample, int subsampling_w, int subsampling_h): 1807 cdef const VSFormat *fmt = self.funcs.registerFormat(color_family, sample_type, bits_per_sample, subsampling_w, subsampling_h, self.core) 1808 if fmt == NULL: 1809 raise Error('Invalid format specified') 1810 return createFormat(fmt) 1811 1812 def get_format(self, int id): 1813 cdef const VSFormat *f = self.funcs.getFormatPreset(id, self.core) 1814 1815 if f == NULL: 1816 raise Error('Format not registered') 1817 else: 1818 return createFormat(f) 1819 1820 def version(self): 1821 cdef VSCoreInfo v 1822 self.funcs.getCoreInfo2(self.core, &v) 1823 return (<const char *>v.versionString).decode('utf-8') 1824 1825 def version_number(self): 1826 cdef VSCoreInfo v 1827 self.funcs.getCoreInfo2(self.core, &v) 1828 return v.core 1829 1830 def __dir__(self): 1831 plugins = [plugin["namespace"] for plugin in self.get_plugins().values()] 1832 return super(Core, self).__dir__() + plugins 1833 1834 def __str__(self): 1835 cdef str s = 'Core\n' 1836 s += self.version() + '\n' 1837 s += '\tNumber of Threads: ' + str(self.num_threads) + '\n' 1838 s += '\tAdd Cache: ' + str(self.add_cache) + '\n' 1839 return s 1840 1841cdef Core createCore(): 1842 cdef Core instance = Core.__new__(Core) 1843 instance.funcs = getVapourSynthAPI(VAPOURSYNTH_API_VERSION) 1844 if instance.funcs == NULL: 1845 raise Error('Failed to obtain VapourSynth API pointer. System does not support SSE2 or is the Python module and loaded core library mismatched?') 1846 instance.core = instance.funcs.createCore(0) 1847 instance.add_cache = True 1848 return instance 1849 1850def _get_core(threads = None, add_cache = None): 1851 env = _env_current() 1852 if env is None: 1853 raise Error('Internal environment id not set. Was get_core() called from a filter callback?') 1854 1855 return vsscript_get_core_internal(env) 1856 1857def get_core(threads = None, add_cache = None): 1858 import warnings 1859 warnings.warn("get_core() is deprecated. Use \"vapoursynth.core\" instead.", DeprecationWarning) 1860 1861 ret_core = _get_core() 1862 if ret_core is not None: 1863 if threads is not None: 1864 ret_core.num_threads = threads 1865 if add_cache is not None: 1866 ret_core.add_cache = add_cache 1867 return ret_core 1868 1869cdef Core vsscript_get_core_internal(EnvironmentData env): 1870 if env.core is None: 1871 env.core = createCore() 1872 return env.core 1873 1874cdef class _CoreProxy(object): 1875 1876 def __init__(self): 1877 raise Error('Class cannot be instantiated directly') 1878 1879 @property 1880 def core(self): 1881 return _get_core() 1882 1883 def __dir__(self): 1884 d = dir(self.core) 1885 if 'core' not in d: 1886 d += ['core'] 1887 1888 return d 1889 1890 def __getattr__(self, name): 1891 return getattr(self.core, name) 1892 1893 def __setattr__(self, name, value): 1894 setattr(self.core, name, value) 1895 1896core = _CoreProxy.__new__(_CoreProxy) 1897 1898 1899cdef class Plugin(object): 1900 cdef Core core 1901 cdef VSPlugin *plugin 1902 cdef const VSAPI *funcs 1903 cdef object injected_arg 1904 cdef readonly str namespace 1905 cdef readonly str __doc__ 1906 1907 def __init__(self): 1908 raise Error('Class cannot be instantiated directly') 1909 1910 1911 def __getattr__(self, name): 1912 tname = name.encode('utf-8') 1913 cdef const char *cname = tname 1914 cdef VSMap *m = self.funcs.getFunctions(self.plugin) 1915 match = False 1916 1917 for i in range(self.funcs.propNumKeys(m)): 1918 cname = self.funcs.propGetKey(m, i) 1919 orig_name = cname.decode('utf-8') 1920 1921 if orig_name == name: 1922 match = True 1923 break 1924 1925 if match: 1926 signature = self.funcs.propGetData(m, cname, 0, NULL).decode('utf-8') 1927 signature = signature.split(';', 1) 1928 self.funcs.freeMap(m) 1929 return createFunction(orig_name, signature[1], self, self.funcs) 1930 else: 1931 self.funcs.freeMap(m) 1932 raise AttributeError('There is no function named ' + name) 1933 1934 def get_functions(self): 1935 cdef VSMap *n 1936 cdef bytes b 1937 cdef dict sout = {} 1938 1939 n = self.funcs.getFunctions(self.plugin) 1940 1941 for j in range(self.funcs.propNumKeys(n)): 1942 c = self.funcs.propGetData(n, self.funcs.propGetKey(n, j), 0, NULL) 1943 c = c.decode('utf-8') 1944 c = c.split(';', 1) 1945 sout[c[0]] = c[1]; 1946 1947 self.funcs.freeMap(n) 1948 return sout 1949 1950 def list_functions(self): 1951 sout = "" 1952 functions = self.get_functions() 1953 for key in sorted(functions.keys()): 1954 sout += key + '(' + functions[key].replace(';', '; ') + ')\n' 1955 return sout.replace('; )', ')') 1956 1957 def __dir__(self): 1958 attrs = [] 1959 functions = self.get_functions() 1960 for key in functions: 1961 attrs.append(key) 1962 return attrs 1963 1964cdef Plugin createPlugin(VSPlugin *plugin, str namespace, const VSAPI *funcs, Core core): 1965 cdef Plugin instance = Plugin.__new__(Plugin) 1966 instance.core = core 1967 instance.plugin = plugin 1968 instance.funcs = funcs 1969 instance.injected_arg = None 1970 instance.namespace = namespace 1971 1972 for plugin_dict in core.get_plugins().values(): 1973 if plugin_dict['namespace'] == namespace: 1974 instance.__doc__ = plugin_dict['name'] 1975 break 1976 1977 return instance 1978 1979cdef class Function(object): 1980 cdef const VSAPI *funcs 1981 cdef readonly Plugin plugin 1982 cdef readonly str name 1983 cdef readonly str signature 1984 1985 @property 1986 def __signature__(self): 1987 if typing is None: 1988 return None 1989 return construct_signature(self.signature, injected=self.plugin.injected_arg) 1990 1991 def __init__(self): 1992 raise Error('Class cannot be instantiated directly') 1993 1994 def __call__(self, *args, **kwargs): 1995 cdef VSMap *inm 1996 cdef VSMap *outm 1997 cdef char *cname 1998 arglist = list(args) 1999 if self.plugin.injected_arg is not None: 2000 arglist.insert(0, self.plugin.injected_arg) 2001 ndict = {} 2002 processed = {} 2003 atypes = {} 2004 # remove _ from all args 2005 for key in kwargs: 2006 if key[0] == '_': 2007 nkey = key[1:] 2008 # PEP8 tells us single_trailing_underscore_ for collisions with Python-keywords. 2009 elif key[-1] == "_": 2010 nkey = key[:-1] 2011 else: 2012 nkey = key 2013 ndict[nkey] = kwargs[key] 2014 2015 # match up unnamed arguments to the first unused name in order 2016 sigs = self.signature.split(';') 2017 2018 for sig in sigs: 2019 if sig == '': 2020 continue 2021 parts = sig.split(':') 2022 # store away the types for later use 2023 key = parts[0] 2024 atypes[key] = parts[1] 2025 2026 # the name has already been specified 2027 if key in ndict: 2028 processed[key] = ndict[key] 2029 del ndict[key] 2030 else: 2031 # fill in with the first unnamed arg until they run out 2032 if len(arglist) > 0: 2033 processed[key] = arglist[0] 2034 del arglist[0] 2035 2036 if len(arglist) > 0: 2037 raise Error(self.name + ': Too many unnamed arguments specified') 2038 2039 if len(ndict) > 0: 2040 raise Error(self.name + ': Function does not take argument(s) named ' + ', '.join(ndict.keys())) 2041 2042 inm = self.funcs.createMap() 2043 2044 dtomsuccess = True 2045 dtomexceptmsg = '' 2046 try: 2047 typedDictToMap(processed, atypes, inm, self.plugin.core.core, self.funcs) 2048 except Error as e: 2049 self.funcs.freeMap(inm) 2050 dtomsuccess = False 2051 dtomexceptmsg = str(e) 2052 2053 if dtomsuccess == False: 2054 raise Error(self.name + ': ' + dtomexceptmsg) 2055 2056 tname = self.name.encode('utf-8') 2057 cname = tname 2058 with nogil: 2059 outm = self.funcs.invoke(self.plugin.plugin, cname, inm) 2060 self.funcs.freeMap(inm) 2061 cdef const char *err = self.funcs.getError(outm) 2062 cdef bytes emsg 2063 2064 if err: 2065 emsg = err 2066 self.funcs.freeMap(outm) 2067 raise Error(emsg.decode('utf-8')) 2068 2069 retdict = mapToDict(outm, True, self.plugin.core.add_cache, self.plugin.core.core, self.funcs) 2070 self.funcs.freeMap(outm) 2071 return retdict 2072 2073cdef Function createFunction(str name, str signature, Plugin plugin, const VSAPI *funcs): 2074 cdef Function instance = Function.__new__(Function) 2075 instance.name = name 2076 instance.signature = signature 2077 instance.plugin = plugin 2078 instance.funcs = funcs 2079 return instance 2080 2081# for python functions being executed by vs 2082 2083cdef void __stdcall freeFunc(void *pobj) nogil: 2084 with gil: 2085 fobj = <FuncData>pobj 2086 Py_DECREF(fobj) 2087 fobj = None 2088 2089 2090cdef void __stdcall publicFunction(const VSMap *inm, VSMap *outm, void *userData, VSCore *core, const VSAPI *vsapi) nogil: 2091 with gil: 2092 d = <FuncData>userData 2093 try: 2094 with use_environment(d.env).use(): 2095 m = mapToDict(inm, False, False, core, vsapi) 2096 ret = d(**m) 2097 if not isinstance(ret, dict): 2098 if ret is None: 2099 ret = 0 2100 ret = {'val':ret} 2101 dictToMap(ret, outm, core, vsapi) 2102 except BaseException, e: 2103 emsg = str(e).encode('utf-8') 2104 vsapi.setError(outm, emsg) 2105 2106 2107@final 2108cdef class VSScriptEnvironmentPolicy: 2109 cdef dict _env_map 2110 cdef dict _known_environments 2111 2112 cdef object _stack 2113 cdef object _lock 2114 cdef EnvironmentPolicyAPI _api 2115 2116 cdef object __weakref__ 2117 2118 def __init__(self): 2119 raise RuntimeError("Cannot instantiate this class directly.") 2120 2121 def on_policy_registered(self, policy_api): 2122 self._env_map = {} 2123 self._known_environments = {} 2124 self._stack = ThreadLocal() 2125 self._lock = Lock() 2126 self._api = policy_api 2127 2128 def on_policy_cleared(self): 2129 with self._lock: 2130 self._known_environments = None 2131 self._env_map = None 2132 self._stack = None 2133 self._lock = None 2134 2135 cdef EnvironmentData get_environment(self, id): 2136 return self._env_map.get(id, None) 2137 2138 def get_current_environment(self): 2139 return getattr(self._stack, "stack", None) 2140 2141 def set_environment(self, environment): 2142 if not self.is_alive(environment): 2143 environment = None 2144 2145 previous = getattr(self._stack, "stack", None) 2146 self._stack.stack = environment 2147 return previous 2148 2149 cdef EnvironmentData _make_environment(self, int script_id): 2150 env = self._api.create_environment() 2151 self._env_map[script_id] = env 2152 self._known_environments[id(env)] = env 2153 return env 2154 2155 cdef has_environment(self, int script_id): 2156 return script_id in self._env_map 2157 2158 cdef _free_environment(self, int script_id): 2159 env = self._env_map.pop(script_id, None) 2160 if env is not None: 2161 self._known_environments.pop(id(env)) 2162 2163 def is_alive(self, env): 2164 return id(env) in self._known_environments 2165 2166 2167cdef VSScriptEnvironmentPolicy _get_vsscript_policy(): 2168 if not isinstance(_policy, VSScriptEnvironmentPolicy): 2169 raise RuntimeError("This is not a VSScript-Policy.") 2170 return <VSScriptEnvironmentPolicy>_policy 2171 2172 2173cdef object _vsscript_use_environment(int id): 2174 return use_environment(_get_vsscript_policy().get_environment(id)) 2175 2176 2177cdef object _vsscript_use_or_create_environment(int id): 2178 cdef VSScriptEnvironmentPolicy policy = _get_vsscript_policy() 2179 if not policy.has_environment(id): 2180 policy._make_environment(id) 2181 return use_environment(policy.get_environment(id)) 2182 2183 2184# for whole script evaluation and export 2185cdef public struct VPYScriptExport: 2186 void *pyenvdict 2187 void *errstr 2188 int id 2189 2190 2191cdef public api int vpy_createScript(VPYScriptExport *se) nogil: 2192 with gil: 2193 try: 2194 evaldict = {} 2195 Py_INCREF(evaldict) 2196 se.pyenvdict = <void *>evaldict 2197 2198 _get_vsscript_policy()._make_environment(<int>se.id) 2199 2200 except: 2201 errstr = 'Unspecified Python exception' + '\n\n' + traceback.format_exc() 2202 errstr = errstr.encode('utf-8') 2203 Py_INCREF(errstr) 2204 se.errstr = <void *>errstr 2205 return 1 2206 return 0 2207 2208cdef public api int vpy_evaluateScript(VPYScriptExport *se, const char *script, const char *scriptFilename, int flags) nogil: 2209 with gil: 2210 orig_path = None 2211 try: 2212 evaldict = {} 2213 if se.pyenvdict: 2214 evaldict = <dict>se.pyenvdict 2215 else: 2216 Py_INCREF(evaldict) 2217 se.pyenvdict = <void *>evaldict 2218 2219 _get_vsscript_policy().get_environment(se.id).outputs.clear() 2220 2221 fn = scriptFilename.decode('utf-8') 2222 2223 # don't set a filename if NULL is passed 2224 if fn != '<string>': 2225 abspath = os.path.abspath(fn) 2226 evaldict['__file__'] = abspath 2227 if flags & 1: 2228 orig_path = os.getcwd() 2229 os.chdir(os.path.dirname(abspath)) 2230 2231 evaldict['__name__'] = "__vapoursynth__" 2232 2233 if se.errstr: 2234 errstr = <bytes>se.errstr 2235 se.errstr = NULL 2236 Py_DECREF(errstr) 2237 errstr = None 2238 2239 comp = compile(script.decode('utf-8-sig'), fn, 'exec') 2240 2241 # Change the environment now. 2242 with _vsscript_use_or_create_environment(se.id).use(): 2243 exec(comp) in evaldict 2244 2245 except BaseException, e: 2246 errstr = 'Python exception: ' + str(e) + '\n\n' + traceback.format_exc() 2247 errstr = errstr.encode('utf-8') 2248 Py_INCREF(errstr) 2249 se.errstr = <void *>errstr 2250 return 2 2251 except: 2252 errstr = 'Unspecified Python exception' + '\n\n' + traceback.format_exc() 2253 errstr = errstr.encode('utf-8') 2254 Py_INCREF(errstr) 2255 se.errstr = <void *>errstr 2256 return 1 2257 finally: 2258 if orig_path is not None: 2259 os.chdir(orig_path) 2260 return 0 2261 2262cdef public api int vpy_evaluateFile(VPYScriptExport *se, const char *scriptFilename, int flags) nogil: 2263 with gil: 2264 if not se.pyenvdict: 2265 evaldict = {} 2266 Py_INCREF(evaldict) 2267 se.pyenvdict = <void *>evaldict 2268 _get_vsscript_policy().get_environment(se.id).outputs.clear() 2269 try: 2270 with open(scriptFilename.decode('utf-8'), 'rb') as f: 2271 script = f.read(1024*1024*16) 2272 return vpy_evaluateScript(se, script, scriptFilename, flags) 2273 except BaseException, e: 2274 errstr = 'File reading exception:\n' + str(e) 2275 errstr = errstr.encode('utf-8') 2276 Py_INCREF(errstr) 2277 se.errstr = <void *>errstr 2278 return 2 2279 except: 2280 errstr = 'Unspecified file reading exception' 2281 errstr = errstr.encode('utf-8') 2282 Py_INCREF(errstr) 2283 se.errstr = <void *>errstr 2284 return 1 2285 2286cdef public api void vpy_freeScript(VPYScriptExport *se) nogil: 2287 with gil: 2288 vpy_clearEnvironment(se) 2289 if se.pyenvdict: 2290 evaldict = <dict>se.pyenvdict 2291 evaldict.clear() 2292 se.pyenvdict = NULL 2293 Py_DECREF(evaldict) 2294 evaldict = None 2295 2296 if se.errstr: 2297 errstr = <bytes>se.errstr 2298 se.errstr = NULL 2299 Py_DECREF(errstr) 2300 errstr = None 2301 2302 try: 2303 _get_vsscript_policy()._free_environment(se.id) 2304 except: 2305 pass 2306 2307 gc.collect() 2308 2309cdef public api char *vpy_getError(VPYScriptExport *se) nogil: 2310 if not se.errstr: 2311 return NULL 2312 with gil: 2313 errstr = <bytes>se.errstr 2314 return errstr 2315 2316cdef public api VSNodeRef *vpy_getOutput(VPYScriptExport *se, int index) nogil: 2317 with gil: 2318 evaldict = <dict>se.pyenvdict 2319 node = None 2320 try: 2321 node = _get_vsscript_policy().get_environment(se.id).outputs[index] 2322 except: 2323 return NULL 2324 2325 if isinstance(node, AlphaOutputTuple): 2326 node = node[0] 2327 2328 if isinstance(node, VideoNode): 2329 return (<VideoNode>node).funcs.cloneNodeRef((<VideoNode>node).node) 2330 else: 2331 return NULL 2332 2333cdef public api VSNodeRef *vpy_getOutput2(VPYScriptExport *se, int index, VSNodeRef **alpha) nogil: 2334 if (alpha != NULL): 2335 alpha[0] = NULL 2336 with gil: 2337 evaldict = <dict>se.pyenvdict 2338 node = None 2339 try: 2340 node = _get_vsscript_policy().get_environment(se.id).outputs[index] 2341 except: 2342 return NULL 2343 2344 if isinstance(node, AlphaOutputTuple): 2345 if (isinstance(node[1], VideoNode) and (alpha != NULL)): 2346 alpha[0] = (<VideoNode>(node[1])).funcs.cloneNodeRef((<VideoNode>(node[1])).node) 2347 return (<VideoNode>(node[0])).funcs.cloneNodeRef((<VideoNode>(node[0])).node) 2348 elif isinstance(node, VideoNode): 2349 return (<VideoNode>node).funcs.cloneNodeRef((<VideoNode>node).node) 2350 else: 2351 return NULL 2352 2353cdef public api int vpy_clearOutput(VPYScriptExport *se, int index) nogil: 2354 with gil: 2355 try: 2356 del _get_vsscript_policy().get_environment(se.id).outputs[index] 2357 except: 2358 return 1 2359 return 0 2360 2361cdef public api VSCore *vpy_getCore(VPYScriptExport *se) nogil: 2362 with gil: 2363 try: 2364 core = vsscript_get_core_internal(_get_vsscript_policy().get_environment(se.id)) 2365 if core is not None: 2366 return (<Core>core).core 2367 else: 2368 return NULL 2369 except: 2370 return NULL 2371 2372cdef public api const VSAPI *vpy_getVSApi() nogil: 2373 global _vsapi 2374 if _vsapi == NULL: 2375 _vsapi = getVapourSynthAPI(VAPOURSYNTH_API_VERSION) 2376 return _vsapi 2377 2378cdef public api const VSAPI *vpy_getVSApi2(int version) nogil: 2379 return getVapourSynthAPI(version) 2380 2381cdef public api int vpy_getVariable(VPYScriptExport *se, const char *name, VSMap *dst) nogil: 2382 with gil: 2383 with _vsscript_use_environment(se.id).use(): 2384 if vpy_getVSApi() == NULL: 2385 return 1 2386 evaldict = <dict>se.pyenvdict 2387 try: 2388 dname = name.decode('utf-8') 2389 read_var = { dname:evaldict[dname]} 2390 core = vsscript_get_core_internal(_get_vsscript_policy().get_environment(se.id)) 2391 dictToMap(read_var, dst, core.core, vpy_getVSApi()) 2392 return 0 2393 except: 2394 return 1 2395 2396cdef public api int vpy_setVariable(VPYScriptExport *se, const VSMap *vars) nogil: 2397 with gil: 2398 with _vsscript_use_environment(se.id).use(): 2399 if vpy_getVSApi() == NULL: 2400 return 1 2401 evaldict = <dict>se.pyenvdict 2402 try: 2403 core = vsscript_get_core_internal(_get_vsscript_policy().get_environment(se.id)) 2404 new_vars = mapToDict(vars, False, False, core.core, vpy_getVSApi()) 2405 for key in new_vars: 2406 evaldict[key] = new_vars[key] 2407 return 0 2408 except: 2409 return 1 2410 2411cdef public api int vpy_clearVariable(VPYScriptExport *se, const char *name) nogil: 2412 with gil: 2413 evaldict = <dict>se.pyenvdict 2414 try: 2415 del evaldict[name.decode('utf-8')] 2416 except: 2417 return 1 2418 return 0 2419 2420cdef public api void vpy_clearEnvironment(VPYScriptExport *se) nogil: 2421 with gil: 2422 evaldict = <dict>se.pyenvdict 2423 for key in evaldict: 2424 evaldict[key] = None 2425 evaldict.clear() 2426 try: 2427 _get_vsscript_policy().get_environment(se.id).outputs.clear() 2428 _get_vsscript_policy().get_environment(se.id).core = None 2429 except: 2430 pass 2431 gc.collect() 2432 2433cdef public api int vpy_initVSScript() nogil: 2434 with gil: 2435 if vpy_getVSApi() == NULL: 2436 return 1 2437 if has_policy(): 2438 return 1 2439 2440 vsscript = VSScriptEnvironmentPolicy.__new__(VSScriptEnvironmentPolicy) 2441 register_policy(vsscript) 2442 return 0 2443