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