<commit_before>#  Copyright (c) 2012 <NAME>
#
#  This file is part of VapourSynth.
#
#  VapourSynth is free software: you can redistribute it and/or modify
#  it under the terms of the GNU Lesser General Public License as
#  published by the Free Software Foundation, either version 3 of the
#  License, or (at your option) any later version.
#
#  VapourSynth is distributed in the hope that it will be useful,
#  but WITHOUT ANY WARRANTY; without even the implied warranty of
#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#  GNU General Public License for more details.
#
#  You should have received a copy of the GNU General Public License
#  along with VapourSynth.  If not, see <http://www.gnu.org/licenses/>.

cimport windows
cimport libinclude
cimport vapoursynth
cimport cython.parallel
from libc.stdlib cimport malloc, free
from cpython.ref cimport Py_INCREF, Py_DECREF
import ctypes
import msvcrt
import threading

GRAY  = vapoursynth.cmGray
RGB   = vapoursynth.cmRGB
YUV   = vapoursynth.cmYUV
YCOCG = vapoursynth.cmYCoCg
COMPAT= vapoursynth.cmCompat

GRAY8 = vapoursynth.pfGray8
GRAY16 = vapoursynth.pfGray16

YUV420P8 = vapoursynth.pfYUV420P8
YUV422P8 = vapoursynth.pfYUV422P8
YUV444P8 = vapoursynth.pfYUV444P8
YUV410P8 = vapoursynth.pfYUV410P8
YUV411P8 = vapoursynth.pfYUV411P8
YUV440P8 = vapoursynth.pfYUV440P8

YUV420P9 = vapoursynth.pfYUV420P9
YUV422P9 = vapoursynth.pfYUV422P9
YUV444P9 = vapoursynth.pfYUV444P9

YUV420P10 = vapoursynth.pfYUV420P10
YUV422P10 = vapoursynth.pfYUV422P10
YUV444P10 = vapoursynth.pfYUV444P10

YUV420P16 = vapoursynth.pfYUV420P16
YUV422P16 = vapoursynth.pfYUV422P16
YUV444P16 = vapoursynth.pfYUV444P16

RGB24 = vapoursynth.pfRGB24
RGB27 = vapoursynth.pfRGB27
RGB30 = vapoursynth.pfRGB30
RGB48 = vapoursynth.pfRGB48

COMPATBGR32 = vapoursynth.pfCompatBGR32
COMPATYUY2 = vapoursynth.pfCompatYUY2

class Error(Exception):
    def __init__(self, value):
        self.value = value

    def __str__(self):
        return repr(self.value)

cdef class Link(object):
    def __init__(self, val, prop):
        self.val = val
        self.prop = prop

# fixme, make it possible for this to call functions not defined in python
cdef class Func(object):
    cdef Core core
    cdef object func
    cdef VSFuncRef *ref

    def __init__(self, object func, Core core):
        self.core = core
        self.func = func
        self.ref = core.funcs.createFunc(publicFunction, <void *>self, freeFunc)
        Py_INCREF(self)

    def __deinit__(self):
        self.core.funcs.freeFunc(self.ref)

    def __call__(self, dict args):
        return self.func(args)

cdef Plugin createFunc(VSFuncRef *ref, Core core):
    cdef Func instance = Func.__new__(Func)
    instance.core = core
    instance.func = None
    instance.ref = ref
    return instance

cdef class CallbackData(object):
    cdef VideoNode node
    cdef VSAPI *funcs
    cdef int handle
    cdef int output
    cdef int requested
    cdef int completed
    cdef int total
    cdef int num_planes
    cdef bint y4m
    cdef dict reorder
    cdef object condition
    cdef object progress_update
    cdef str error

    def __init__(self, handle, requested, total, num_planes, y4m, node, progress_update):
        self.handle = handle
        self.output = 0
        self.requested = requested
        self.completed = 0
        self.total = total
        self.num_planes = num_planes
        self.y4m = y4m
        self.condition = threading.Condition()
        self.node = node
        self.progress_update = progress_update
        self.funcs = (<VideoNode>node).funcs
        self.reorder = {}

cdef class FramePtr(object):
    cdef VSFrameRef *f
    cdef VSAPI *funcs

    def __init__(self):
        raise Error('Class cannon be instantiated directly')

    def __dealloc__(self):
        self.funcs.freeFrame(self.f)

cdef FramePtr createFramePtr(vapoursynth.VSFrameRef *f, vapoursynth.VSAPI *funcs):
    cdef FramePtr instance = FramePtr.__new__(FramePtr)    
    instance.f = f
    instance.funcs = funcs
    return instance

cdef void __stdcall callback(void *data, VSFrameRef *f, int n, VSNodeRef *node, char *errormsg) nogil:
    cdef int pitch
    cdef uint8_t *readptr
    cdef VSFormat *fi
    cdef int row_size
    cdef int height
    cdef char err[512]
    cdef char *header = 'FRAME\n'
    cdef int dummy = 0
    cdef int p
    cdef int y

    with gil:
        d = <CallbackData>data

        if f == NULL:
            d.total = d.requested
            d.error = 'Failed to retrieve frame ' + n + ' with error: ' + errormsg.decode('utf-8')

        d.reorder[n] = createFramePtr(f, d.funcs)

        while d.output in d.reorder:
            frame_obj = <VideoFrame>d.reorder[d.output]
            windows.WriteFile(d.handle, header, 6, &dummy, NULL)
            p = 0
            fi = d.funcs.getFrameFormat(frame_obj.f)

            while p < d.num_planes:
                pitch = d.funcs.getStride(frame_obj.f, p)
                readptr = d.funcs.getReadPtr(frame_obj.f, p)
                row_size = d.funcs.getFrameWidth(frame_obj.f, p) * fi.bytesPerSample
                height = d.funcs.getFrameHeight(frame_obj.f, p)
                y = 0

                while y < height:

                    if not windows.WriteFile(d.handle, readptr, row_size, &dummy, NULL):
                        d.total = d.requested
                        d.error = 'WriteFile() call returned false'

                    readptr += pitch
                    y = y + 1

                p = p + 1

            del d.reorder[d.output]
            d.output = d.output + 1
            windows.FlushFileBuffers(d.handle)

        d.completed = d.completed + 1

        if (d.progress_update is not None):
            d.progress_update(d.completed, d.total)

        if d.requested < d.total:
            d.node.funcs.getFrameAsync(d.requested, d.node.node, callback, data)
            d.requested = d.requested + 1

        if d.total == d.completed:
            d.condition.acquire()
            d.condition.notify()
            d.condition.release()

cdef object mapToDict(VSMap *map, bint flatten, bint addcache, Core core, VSAPI *funcs):
    cdef int numKeys = funcs.propNumKeys(map)
    retdict = {}
    cdef char *retkey
    cdef char proptype
    cdef VSMap *tempmap

    for x in range(numKeys):
        retkey = funcs.propGetKey(map, x)
        proptype = funcs.propGetType(map, retkey)

        for y in range(funcs.propNumElements(map, retkey)):
            if proptype == 'i':
                newval = funcs.propGetInt(map, retkey, 0, NULL)
            elif proptype == 'f':
                newval = funcs.propGetFloat(map, retkey, 0, NULL)
            elif proptype == 's':
                newval = funcs.propGetData(map, retkey, 0, NULL).decode('utf-8')
            elif proptype =='c':
                newval = createVideoNode(funcs.propGetNode(map, retkey, 0, NULL), funcs, core)

                if addcache and not newval.flags:
                    newval = core.std.Cache(clip=newval)

                    if type(newval) == dict:
                        newval = newval['dict']
            elif proptype =='v':
                newval = createVideoFrame(funcs.propGetFrame(map, retkey, 0, NULL), funcs, core)
            elif proptype =='m':
                newval = createFunc(funcs.propGetFunc(map, retkey, 0, NULL), core)

            if y == 0:
                vval = newval
            elif y == 1:
                vval = [vval, newval]
            else:
                vval.append(newval)
        retdict[retkey.decode('utf-8')] = vval

    if not flatten:
        return retdict
    elif len(retdict) == 0:
        return None
    elif len(retdict) == 1:
        a, b = retdict.popitem()
        return b
    else:
        return retdict

cdef void dictToMap(dict ndict, VSMap *inm, Core core, VSAPI *funcs):
    for key in ndict:
        ckey = key.encode('utf-8')
        val = ndict[key]

        if not isinstance(val, list):
            val = [val]

        for v in val:
            if isinstance(v, VideoNode):
                if funcs.propSetNode(inm, ckey, (<VideoNode>v).node, 1) != 0:
                    raise Error('not all values are of the same type in ' + key)
            elif isinstance(v, VideoFrame):
                if funcs.propSetFrame(inm, ckey, (<VideoFrame>v).f, 1) != 0:
                    raise Error('not all values are of the same type in ' + key)
            elif isinstance(v, Func):
                if funcs.propSetFunc(inm, ckey, (<Func>v).ref, 1) != 0:
                    raise Error('not all values are of the same type in ' + key)
            elif callable(v):
                tf = Func(v, core)

                if funcs.propSetFunc(inm, ckey, tf.ref, 1) != 0:
                    raise Error('not all values are of the same type in ' + key)
            elif type(v) == int or type(v) == long or type(v) == bool:
                if funcs.propSetInt(inm, ckey, int(v), 1) != 0:
                    raise Error('not all values are of the same type in ' + key)
            elif type(v) == float:
                if funcs.propSetFloat(inm, ckey, float(v), 1) != 0:
                    raise Error('not all values are of the same type in ' + key)
            elif type(v) == str:
                s = str(v).encode('utf-8')

                if funcs.propSetData(inm, ckey, s, -1, 1) != 0:
                    raise Error('not all values are of the same type in ' + key)
            elif type(v) == bytes:
                if funcs.propSetData(inm, ckey, v, len(v), 1) != 0:
                    raise Error('not all values are of the same type in ' + key)
            else:
                raise Error('argument ' + key + ' was passed an unsupported type')


cdef class Format(object):
    cdef vapoursynth.VSFormat *f
    cdef readonly int id
    cdef readonly str name
    cdef readonly int color_family
    cdef readonly int sample_type
    cdef readonly int bits_per_sample
    cdef readonly int bytes_per_sample
    cdef readonly int subsampling_w
    cdef readonly int subsampling_h
    cdef readonly int num_planes

    def __init__(self):
        raise Error('Class cannon be instantiated directly')

    def __str__(self):
        cdef dict color_stuff = dict({GRAY:'Gray', RGB:'RGB', YUV:'YUV', YCOCG:'YCoCg', COMPAT:'Compat'})
        cdef str s = ''
        s += 'Format Descriptor\n'
        s += '\tId: ' + str(self.id) + '\n'
        s += '\tName: ' + self.name + '\n'
        s += '\tColor Family: ' + color_stuff[self.color_family] + '\n'

        if self.sample_type == stInteger:
            s += '\tSample Type: Integral\n'
        else:
            s += '\tSample Type: Float\n'

        s += '\tBits Per Sample: ' + str(self.bits_per_sample) + '\n'
        s += '\tBytes Per Sample: ' + str(self.bytes_per_sample) + '\n'
        s += '\tPlanes: ' + str(self.num_planes) + '\n'
        s += '\tSubsampling W: ' + str(self.subsampling_w) + '\n'
        s += '\tSubsampling H: ' + str(self.subsampling_h) + '\n'
        return s

cdef Format createFormat(vapoursynth.VSFormat *f):
    cdef Format instance = Format.__new__(Format)
    instance.f = f
    instance.id = f.id
    instance.name = f.name.decode('utf-8')
    instance.color_family = f.colorFamily
    instance.sample_type = f.sampleType
    instance.bits_per_sample = f.bitsPerSample
    instance.bytes_per_sample = f.bytesPerSample
    instance.subsampling_w = f.subSamplingW
    instance.subsampling_h = f.subSamplingH
    instance.num_planes = f.numPlanes
    return instance

cdef class VideoFrame(object):
    cdef VSFrameRef *f
    cdef Core core
    cdef vapoursynth.VSAPI *funcs
    cdef readonly Format format
    cdef readonly int width
    cdef readonly int height

    def __init__(self):
        raise Error('Class cannon be instantiated directly')

    def __dealloc__(self):
        self.funcs.freeFrame(self.f)

    def get_props(self):
        retdict = mapToDict(self.funcs.getFramePropsRO(self.f), False, False, self.core, self.funcs)

    def get_data(self, int plane):
        cdef uint8_t *d = self.funcs.getReadPtr(self.f, plane)
        return ctypes.c_void_p(<int64_t>d)

    def get_stride(self, int plane):
        return self.funcs.getStride(self.f, plane)

    def __str__(self):
        cdef str s = 'VideoFrame\n'
        s += '\tFormat: ' + self.format.name + '\n'
        s += '\tWidth: ' + str(self.width) + '\n'
        s += '\tHeight: ' + str(self.height) + '\n'
        return s

cdef VideoFrame createVideoFrame(vapoursynth.VSFrameRef *f, vapoursynth.VSAPI *funcs, Core core):
    cdef VideoFrame instance = VideoFrame.__new__(VideoFrame)    
    instance.f = f
    instance.funcs = funcs
    instance.core = core
    instance.format = createFormat(funcs.getFrameFormat(f))
    instance.width = funcs.getFrameWidth(f, 0)
    instance.height = funcs.getFrameHeight(f, 0)
    return instance

cdef class VideoNode(object):
    cdef vapoursynth.VSNodeRef *node
    cdef vapoursynth.VSAPI *funcs    
    cdef Core core
    cdef vapoursynth.VSVideoInfo *vi
    cdef readonly Format format
    cdef readonly int width
    cdef readonly int height
    cdef readonly int num_frames
    cdef readonly int fps_num
    cdef readonly int fps_den
    cdef readonly int flags

    def __init__(self):
        raise Error('Class cannon be instantiated directly')

    def __dealloc__(self):
        self.funcs.freeNode(self.node)

    def get_frame(self, int n):
        cdef char errorMsg[512]
        cdef VSFrameRef *f
        f = self.funcs.getFrame(n, self.node, errorMsg, 500)
        if f == NULL:
            raise Error('Internal error')
        else:
            return createVideoFrame(f, self.funcs, self.core) 

    def output(self, object fileobj not None, bint y4m = False, int lookahead = 10, object progress_update = None):
        cdef CallbackData d = CallbackData(msvcrt.get_osfhandle(fileobj.fileno()), min(lookahead, self.num_frames), self.num_frames, self.format.num_planes, y4m, self, progress_update)

        if d.total <= 0:
            raise Error('Cannot output unknown length clip')

        # this is also an implicit test that the progress_update callback at least vaguely matches the requirements
        if (progress_update is not None):
            progress_update(0, d.total)

        if (self.format is None or self.format.color_family != YUV) and y4m:
            raise Error('Cannot apply y4m headers to non-yuv/unknown formats')

        y4mformat = ''
        numbits = ''

        if self.format is not None:
            if self.format.subsampling_w == 1 and self.format.subsampling_h == 1:
                y4mformat = 'C420'
            elif self.format.subsampling_w == 1 and self.format.subsampling_h == 0:
                y4mformat = 'C422'
            elif self.format.subsampling_w == 0 and self.format.subsampling_h == 0:
                y4mformat = 'C444'
            elif self.format.subsampling_w == 2 and self.format.subsampling_h == 2:
                y4mformat = 'C410'
            elif self.format.subsampling_w == 2 and self.format.subsampling_h == 0:
                y4mformat = 'C411'
            elif self.format.subsampling_w == 0 and self.format.subsampling_h == 1:
                y4mformat = 'C440'

            numbits = 'B' + str(self.format.bits_per_sample) + ' '

        if len(y4mformat) > 0:
            y4mformat = y4mformat + ' '

        cdef str header = 'YUV4MPEG2 ' + y4mformat + numbits + 'W' + str(self.width) + ' H' + str(self.height) + ' F' + str(self.fps_num) + ':' + str(self.fps_den) + ' Ip A0:0\n'
        cdef bytes b = header.encode('utf-8')
        cdef int dummy = 0
        windows.WriteFile(d.handle, <char*>b, len(b), &dummy, NULL)
        d.condition.acquire()

        for n in range(min(lookahead, d.total)):
            self.funcs.getFrameAsync(n, self.node, callback, <void *>d)

        d.condition.wait()
        d.condition.release()

        if d.error:
            raise Error(d.error)

    def __str__(self):
        cdef str s = 'VideoNode\n'

        if self.format:
            s += '\tFormat: ' + self.format.name + '\n'
        else:
            s += '\tFormat: dynamic\n'

        if not self.width or not self.height:
            s += '\tWidth: dynamic\n'
            s += '\tHeight: dynamic\n'
        else:
            s += '\tWidth: ' + str(self.width) + '\n'
            s += '\tHeight: ' + str(self.height) + '\n'

        if not self.num_frames:
            s += '\tNum Frames: unknown\n'
        else:
            s += '\tNum Frames: ' + str(self.num_frames) + '\n'

        if not self.fps_num or not self.fps_den:
            s += '\tFPS Num: dynamic\n'
            s += '\tFPS Den: dynamic\n'
        else:
            s += '\tFPS Num: ' + str(self.fps_num) + '\n'
            s += '\tFPS Den: ' + str(self.fps_den) + '\n'

        if self.flags == vapoursynth.nfNoCache:
            s += '\tFlags: No Cache\n'
        else:
            s += '\tFlags: None\n'

        return s

cdef VideoNode createVideoNode(vapoursynth.VSNodeRef *node, vapoursynth.VSAPI *funcs, Core core):
    cdef VideoNode instance = VideoNode.__new__(VideoNode)    
    instance.core = core
    instance.node = node
    instance.funcs = funcs
    instance.vi = funcs.getVideoInfo(node)

    if (instance.vi.format):
        instance.format = createFormat(instance.vi.format)
    else:
        instance.format = None

    instance.width = instance.vi.width
    instance.height = instance.vi.height
    instance.num_frames = instance.vi.numFrames
    instance.fps_num = instance.vi.fpsNum
    instance.fps_den = instance.vi.fpsDen
    instance.flags = instance.vi.flags
    return instance

cdef class Core(object):
    cdef vapoursynth.VSCore *core
    cdef vapoursynth.VSAPI *funcs
    cdef bint flatten
    cdef bint addcache
    cdef bint accept_lowercase

    def __cinit__(self, flatten = True, addcache = True, int threads = 0, bint accept_lowercase = False):
        self.funcs = vapoursynth.getVapourSynthAPI(1)
        self.core = self.funcs.createVSCore(threads)
        self.flatten = flatten
        self.addcache = addcache
        self.accept_lowercase = accept_lowercase

    def __dealloc__(self):
        self.funcs.freeVSCore(self.core)

    def __getattr__(self, name):
        cdef vapoursynth.VSPlugin *plugin
        tname = name.encode('utf-8')
        cdef char *cname = tname
        plugin = self.funcs.getPluginNs(cname, self.core)

        if plugin:
            return createPlugin(plugin, self.funcs, self)
        else:
            raise Error('No attribute with the name ' + name + ' exists. Did you mistype a plugin namespace?')

    def list_functions(self):
        cdef VSMap *m = self.funcs.getPlugins(self.core)
        cdef VSMap *n
        cdef bytes b
        cdef str sout = ''

        for i in range(self.funcs.propNumKeys(m)):
            a = self.funcs.propGetData(m, self.funcs.propGetKey(m, i), 0, NULL)
            a = a.decode('utf-8')
            a = a.split(';', 2)
            sout += a[2] + '\n'
            sout += '\tnamespace:\t' + a[0] + '\n'
            sout += '\tidentifier:\t' + a[1] + '\n'
            b = a[1].encode('utf-8')
            n = self.funcs.getFunctions(self.funcs.getPluginId(b, self.core))

            for j in range(self.funcs.propNumKeys(n)):
                c = self.funcs.propGetData(n, self.funcs.propGetKey(n, j), 0, NULL)
                c = c.decode('utf-8')
                c = c.split(';', 1)
                sout += '\t\t' + c[0] + '(' + c[1] +')\n'

            self.funcs.freeMap(n)

        self.funcs.freeMap(m)
        return sout

    def register_format(self, int color_family, int sample_type, int bits_per_sample, int subsampling_w, int subsampling_h):
        return createFormat(self.funcs.registerFormat(color_family, sample_type, bits_per_sample, subsampling_w, subsampling_h, self.core))

    def get_format(self, int id):
        cdef VSFormat *f = self.funcs.getFormatPreset(id, self.core)

        if f == NULL:
            raise Error('Internal error')
        else:
            return createFormat(f)

    def version(self):
        cdef VSVersion *v = self.funcs.getVersion()
        return v.versionString.decode('utf-8')

    def __str__(self):
        cdef str s = 'Core\n'
        s += self.version() + '\n'
        s += '\tFlatten: ' + str(self.flatten) + '\n'
        s += '\tAdd Caches: ' + str(self.addcache) + '\n'
        s += '\tAccept Lowercase: ' + str(self.accept_lowercase) + '\n'
        return s

cdef class Plugin(object):
    cdef Core core
    cdef VSPlugin *plugin
    cdef vapoursynth.VSAPI *funcs

    def __init__(self):
        raise Error('Class cannon be instantiated directly')

    def __getattr__(self, name):
        tname = name.encode('utf-8')
        cdef char *cname = tname
        cdef VSMap *m = self.funcs.getFunctions(self.plugin)
        lc_match = False
        cs_match = False

        for i in range(self.funcs.propNumKeys(m)):
            cname = self.funcs.propGetKey(m, i)
            orig_name = cname.decode('utf-8')
            lc_name = orig_name.lower()

            if orig_name == name:
                cs_match = True
                break

            if lc_name == name:
                lc_match = True
                break

        if cs_match or (lc_match and self.core.accept_lowercase):
            signature = self.funcs.propGetData(m, cname, 0, NULL).decode('utf-8').split(';', 1)
            self.funcs.freeMap(m)
            return createFunction(orig_name, signature[1], self, self.funcs)
        else:
            self.funcs.freeMap(m)
            raise Error('There is no function named ' + name)

cdef Plugin createPlugin(vapoursynth.VSPlugin *plugin, vapoursynth.VSAPI *funcs, Core core):
    cdef Plugin instance = Plugin.__new__(Plugin)    
    instance.core = core
    instance.plugin = plugin
    instance.funcs = funcs
    return instance

cdef class Function(object):
    cdef Plugin plugin
    cdef str name
    cdef str signature
    cdef vapoursynth.VSAPI *funcs

    def __init__(self):
        raise Error('Class cannon be instantiated directly')

    def __call__(self, *args, **kwargs):
        cdef VSMap *inm
        cdef VSMap *outm
        cdef char *cname
        ndict = {}

        # naively insert named arguments
        for key in kwargs:
            if isinstance(kwargs[key], Link):
                ndict[key + '_prop'] = kwargs[key].prop
                ndict[key] = kwargs[key].val
            else:
                ndict[key] = kwargs[key]

        # match up unnamed arguments to the first unused name in order
        sigs = self.signature.split(';')
        csig = 0
        numsig = len(sigs) 

        for arg in args:
            key = sigs[csig].split(':', 1)
            key = key[0]

            while key in ndict:
                csig = csig + 1

                if csig >= numsig:
                    raise Error('There are more unnamed arguments given than unspecified arguments to match')

                key = sigs[csig].split(':', 1)
                key = key[0]
            if isinstance(arg, Link):
                ndict[key + '_prop'] = arg.prop
                ndict[key] = arg.val
            else:
                ndict[key] = arg

        inm = self.funcs.newMap()

        try:
            dictToMap(ndict, inm, self.plugin.core, self.funcs)
        except Error as e:
            self.funcs.freeMap(inm)
            raise Error(self.name + ': ' + str(e))

        tname = self.name.encode('utf-8')
        cname = tname
        outm = self.funcs.invoke(self.plugin.plugin, cname, inm)
        self.funcs.freeMap(inm)
        cdef char *err = self.funcs.getError(outm)
        cdef bytes emsg

        if err:
            emsg = err
            self.funcs.freeMap(outm)
            raise Error(emsg.decode('utf-8'))

        retdict = mapToDict(outm, self.plugin.core.flatten, self.plugin.core.addcache, self.plugin.core, self.funcs)
        self.funcs.freeMap(outm)
        return retdict

cdef Function createFunction(str name, str signature, Plugin plugin, vapoursynth.VSAPI *funcs):
    cdef Function instance = Function.__new__(Function)    
    instance.name = name
    instance.signature = signature
    instance.plugin = plugin
    instance.funcs = funcs
    return instance

# for python functions being executed by vs

cdef void __stdcall freeFunc(void *pobj) nogil:
    with gil:
        Py_DECREF(<Func>pobj)

cdef void __stdcall publicFunction(VSMap *inm, VSMap *outm, void *userData, VSCore *core, VSAPI *vsapi) nogil:
    with gil:
        d = <Func>userData

        try:
            m = mapToDict(inm, False, False, d.core, vsapi)
            ret = d(m)
            dictToMap(ret, outm, d.core, vsapi)
        except Error as e:
            emsg = str(e).encode('utf-8')
            vsapi.setError(outm, emsg)

# for whole script evaluation and export

cdef public struct ScriptExport:
    void *pynode
    VSNodeRef *node
    char *error

cdef public api int __stdcall evaluate_script(char *fn, ScriptExport *extp) nogil:
    extp.node = NULL
    extp.pynode = NULL
    extp.error = NULL

    with gil:
        try:
            evaldict = {}
            comp = compile(open(fn.decode('utf-8')).read(), fn.decode('utf-8'), 'exec')
            exec(comp) in evaldict
            node = evaldict['last']

            if isinstance(node, VideoNode):
                Py_INCREF(node)
                extp.pynode = <void *>node
                extp.node = (<VideoNode>node).node
            else:
                return 3
        except Error, e:
            return 1
        except:
            return 2
        return 0

cdef public api int __stdcall free_script(ScriptExport *extp) nogil:
    with gil:
        node = <VideoNode>extp.pynode
        Py_DECREF(node)
<commit_msg>Add #ifdefs to cython stuff<commit_after>#  Copyright (c) 2012 <NAME>
#
#  This file is part of VapourSynth.
#
#  VapourSynth is free software: you can redistribute it and/or modify
#  it under the terms of the GNU Lesser General Public License as
#  published by the Free Software Foundation, either version 3 of the
#  License, or (at your option) any later version.
#
#  VapourSynth is distributed in the hope that it will be useful,
#  but WITHOUT ANY WARRANTY; without even the implied warranty of
#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#  GNU General Public License for more details.
#
#  You should have received a copy of the GNU General Public License
#  along with VapourSynth.  If not, see <http://www.gnu.org/licenses/>.

#ifdef _WIN32
cimport windows
cimport libinclude
#else
cimport posix.unistd
#endif
cimport vapoursynth
cimport cython.parallel
from cpython.ref cimport Py_INCREF, Py_DECREF
import ctypes
#ifdef _WIN32
import msvcrt
#endif
import threading

GRAY  = vapoursynth.cmGray
RGB   = vapoursynth.cmRGB
YUV   = vapoursynth.cmYUV
YCOCG = vapoursynth.cmYCoCg
COMPAT= vapoursynth.cmCompat

GRAY8 = vapoursynth.pfGray8
GRAY16 = vapoursynth.pfGray16

YUV420P8 = vapoursynth.pfYUV420P8
YUV422P8 = vapoursynth.pfYUV422P8
YUV444P8 = vapoursynth.pfYUV444P8
YUV410P8 = vapoursynth.pfYUV410P8
YUV411P8 = vapoursynth.pfYUV411P8
YUV440P8 = vapoursynth.pfYUV440P8

YUV420P9 = vapoursynth.pfYUV420P9
YUV422P9 = vapoursynth.pfYUV422P9
YUV444P9 = vapoursynth.pfYUV444P9

YUV420P10 = vapoursynth.pfYUV420P10
YUV422P10 = vapoursynth.pfYUV422P10
YUV444P10 = vapoursynth.pfYUV444P10

YUV420P16 = vapoursynth.pfYUV420P16
YUV422P16 = vapoursynth.pfYUV422P16
YUV444P16 = vapoursynth.pfYUV444P16

RGB24 = vapoursynth.pfRGB24
RGB27 = vapoursynth.pfRGB27
RGB30 = vapoursynth.pfRGB30
RGB48 = vapoursynth.pfRGB48

COMPATBGR32 = vapoursynth.pfCompatBGR32
COMPATYUY2 = vapoursynth.pfCompatYUY2

class Error(Exception):
    def __init__(self, value):
        self.value = value

    def __str__(self):
        return repr(self.value)

cdef class Link(object):
    def __init__(self, val, prop):
        self.val = val
        self.prop = prop

# fixme, make it possible for this to call functions not defined in python
cdef class Func(object):
    cdef Core core
    cdef object func
    cdef VSFuncRef *ref

    def __init__(self, object func, Core core):
        self.core = core
        self.func = func
        self.ref = core.funcs.createFunc(publicFunction, <void *>self, freeFunc)
        Py_INCREF(self)

    def __deinit__(self):
        self.core.funcs.freeFunc(self.ref)

    def __call__(self, dict args):
        return self.func(args)

cdef Plugin createFunc(VSFuncRef *ref, Core core):
    cdef Func instance = Func.__new__(Func)
    instance.core = core
    instance.func = None
    instance.ref = ref
    return instance

cdef class CallbackData(object):
    cdef VideoNode node
    cdef VSAPI *funcs
    cdef int handle
    cdef int output
    cdef int requested
    cdef int completed
    cdef int total
    cdef int num_planes
    cdef bint y4m
    cdef dict reorder
    cdef object condition
    cdef object progress_update
    cdef str error

    def __init__(self, handle, requested, total, num_planes, y4m, node, progress_update):
        self.handle = handle
        self.output = 0
        self.requested = requested
        self.completed = 0
        self.total = total
        self.num_planes = num_planes
        self.y4m = y4m
        self.condition = threading.Condition()
        self.node = node
        self.progress_update = progress_update
        self.funcs = (<VideoNode>node).funcs
        self.reorder = {}

cdef class FramePtr(object):
    cdef VSFrameRef *f
    cdef VSAPI *funcs

    def __init__(self):
        raise Error('Class cannon be instantiated directly')

    def __dealloc__(self):
        self.funcs.freeFrame(self.f)

cdef FramePtr createFramePtr(vapoursynth.VSFrameRef *f, vapoursynth.VSAPI *funcs):
    cdef FramePtr instance = FramePtr.__new__(FramePtr)    
    instance.f = f
    instance.funcs = funcs
    return instance

cdef void __stdcall callback(void *data, VSFrameRef *f, int n, VSNodeRef *node, char *errormsg) nogil:
    cdef int pitch
    cdef uint8_t *readptr
    cdef VSFormat *fi
    cdef int row_size
    cdef int height
    cdef char err[512]
    cdef char *header = 'FRAME\n'
    cdef int dummy = 0
    cdef int p
    cdef int y

    with gil:
        d = <CallbackData>data

        if f == NULL:
            d.total = d.requested
            d.error = 'Failed to retrieve frame ' + n + ' with error: ' + errormsg.decode('utf-8')

        d.reorder[n] = createFramePtr(f, d.funcs)

        while d.output in d.reorder:
            frame_obj = <VideoFrame>d.reorder[d.output]
#ifdef _WIN32
            windows.WriteFile(d.handle, header, 6, &dummy, NULL)
#else
            posix.unistd.write(d.handle, header, 6)
#endif
            
            p = 0
            fi = d.funcs.getFrameFormat(frame_obj.f)

            while p < d.num_planes:
                pitch = d.funcs.getStride(frame_obj.f, p)
                readptr = d.funcs.getReadPtr(frame_obj.f, p)
                row_size = d.funcs.getFrameWidth(frame_obj.f, p) * fi.bytesPerSample
                height = d.funcs.getFrameHeight(frame_obj.f, p)
                y = 0

                while y < height:
#ifdef _WIN32
                    if not windows.WriteFile(d.handle, readptr, row_size, &dummy, NULL):
#else
                    if not posix.unistd.write(d.handle, readptr, row_size):
#endif
                        d.total = d.requested
                        d.error = 'WriteFile() call returned false'

                    readptr += pitch
                    y = y + 1

                p = p + 1

            del d.reorder[d.output]
            d.output = d.output + 1
#ifdef _WIN32
            windows.FlushFileBuffers(d.handle)
#endif

        d.completed = d.completed + 1

        if (d.progress_update is not None):
            d.progress_update(d.completed, d.total)

        if d.requested < d.total:
            d.node.funcs.getFrameAsync(d.requested, d.node.node, callback, data)
            d.requested = d.requested + 1

        if d.total == d.completed:
            d.condition.acquire()
            d.condition.notify()
            d.condition.release()

cdef object mapToDict(VSMap *map, bint flatten, bint addcache, Core core, VSAPI *funcs):
    cdef int numKeys = funcs.propNumKeys(map)
    retdict = {}
    cdef char *retkey
    cdef char proptype
    cdef VSMap *tempmap

    for x in range(numKeys):
        retkey = funcs.propGetKey(map, x)
        proptype = funcs.propGetType(map, retkey)

        for y in range(funcs.propNumElements(map, retkey)):
            if proptype == 'i':
                newval = funcs.propGetInt(map, retkey, 0, NULL)
            elif proptype == 'f':
                newval = funcs.propGetFloat(map, retkey, 0, NULL)
            elif proptype == 's':
                newval = funcs.propGetData(map, retkey, 0, NULL).decode('utf-8')
            elif proptype =='c':
                newval = createVideoNode(funcs.propGetNode(map, retkey, 0, NULL), funcs, core)

                if addcache and not newval.flags:
                    newval = core.std.Cache(clip=newval)

                    if type(newval) == dict:
                        newval = newval['dict']
            elif proptype =='v':
                newval = createVideoFrame(funcs.propGetFrame(map, retkey, 0, NULL), funcs, core)
            elif proptype =='m':
                newval = createFunc(funcs.propGetFunc(map, retkey, 0, NULL), core)

            if y == 0:
                vval = newval
            elif y == 1:
                vval = [vval, newval]
            else:
                vval.append(newval)
        retdict[retkey.decode('utf-8')] = vval

    if not flatten:
        return retdict
    elif len(retdict) == 0:
        return None
    elif len(retdict) == 1:
        a, b = retdict.popitem()
        return b
    else:
        return retdict

cdef void dictToMap(dict ndict, VSMap *inm, Core core, VSAPI *funcs):
    for key in ndict:
        ckey = key.encode('utf-8')
        val = ndict[key]

        if not isinstance(val, list):
            val = [val]

        for v in val:
            if isinstance(v, VideoNode):
                if funcs.propSetNode(inm, ckey, (<VideoNode>v).node, 1) != 0:
                    raise Error('not all values are of the same type in ' + key)
            elif isinstance(v, VideoFrame):
                if funcs.propSetFrame(inm, ckey, (<VideoFrame>v).f, 1) != 0:
                    raise Error('not all values are of the same type in ' + key)
            elif isinstance(v, Func):
                if funcs.propSetFunc(inm, ckey, (<Func>v).ref, 1) != 0:
                    raise Error('not all values are of the same type in ' + key)
            elif callable(v):
                tf = Func(v, core)

                if funcs.propSetFunc(inm, ckey, tf.ref, 1) != 0:
                    raise Error('not all values are of the same type in ' + key)
            elif type(v) == int or type(v) == long or type(v) == bool:
                if funcs.propSetInt(inm, ckey, int(v), 1) != 0:
                    raise Error('not all values are of the same type in ' + key)
            elif type(v) == float:
                if funcs.propSetFloat(inm, ckey, float(v), 1) != 0:
                    raise Error('not all values are of the same type in ' + key)
            elif type(v) == str:
                s = str(v).encode('utf-8')

                if funcs.propSetData(inm, ckey, s, -1, 1) != 0:
                    raise Error('not all values are of the same type in ' + key)
            elif type(v) == bytes:
                if funcs.propSetData(inm, ckey, v, len(v), 1) != 0:
                    raise Error('not all values are of the same type in ' + key)
            else:
                raise Error('argument ' + key + ' was passed an unsupported type')


cdef class Format(object):
    cdef vapoursynth.VSFormat *f
    cdef readonly int id
    cdef readonly str name
    cdef readonly int color_family
    cdef readonly int sample_type
    cdef readonly int bits_per_sample
    cdef readonly int bytes_per_sample
    cdef readonly int subsampling_w
    cdef readonly int subsampling_h
    cdef readonly int num_planes

    def __init__(self):
        raise Error('Class cannon be instantiated directly')

    def __str__(self):
        cdef dict color_stuff = dict({GRAY:'Gray', RGB:'RGB', YUV:'YUV', YCOCG:'YCoCg', COMPAT:'Compat'})
        cdef str s = ''
        s += 'Format Descriptor\n'
        s += '\tId: ' + str(self.id) + '\n'
        s += '\tName: ' + self.name + '\n'
        s += '\tColor Family: ' + color_stuff[self.color_family] + '\n'

        if self.sample_type == stInteger:
            s += '\tSample Type: Integral\n'
        else:
            s += '\tSample Type: Float\n'

        s += '\tBits Per Sample: ' + str(self.bits_per_sample) + '\n'
        s += '\tBytes Per Sample: ' + str(self.bytes_per_sample) + '\n'
        s += '\tPlanes: ' + str(self.num_planes) + '\n'
        s += '\tSubsampling W: ' + str(self.subsampling_w) + '\n'
        s += '\tSubsampling H: ' + str(self.subsampling_h) + '\n'
        return s

cdef Format createFormat(vapoursynth.VSFormat *f):
    cdef Format instance = Format.__new__(Format)
    instance.f = f
    instance.id = f.id
    instance.name = f.name.decode('utf-8')
    instance.color_family = f.colorFamily
    instance.sample_type = f.sampleType
    instance.bits_per_sample = f.bitsPerSample
    instance.bytes_per_sample = f.bytesPerSample
    instance.subsampling_w = f.subSamplingW
    instance.subsampling_h = f.subSamplingH
    instance.num_planes = f.numPlanes
    return instance

cdef class VideoFrame(object):
    cdef VSFrameRef *f
    cdef Core core
    cdef vapoursynth.VSAPI *funcs
    cdef readonly Format format
    cdef readonly int width
    cdef readonly int height

    def __init__(self):
        raise Error('Class cannon be instantiated directly')

    def __dealloc__(self):
        self.funcs.freeFrame(self.f)

    def get_props(self):
        retdict = mapToDict(self.funcs.getFramePropsRO(self.f), False, False, self.core, self.funcs)

    def get_data(self, int plane):
        cdef uint8_t *d = self.funcs.getReadPtr(self.f, plane)
        return ctypes.c_void_p(<int64_t>d)

    def get_stride(self, int plane):
        return self.funcs.getStride(self.f, plane)

    def __str__(self):
        cdef str s = 'VideoFrame\n'
        s += '\tFormat: ' + self.format.name + '\n'
        s += '\tWidth: ' + str(self.width) + '\n'
        s += '\tHeight: ' + str(self.height) + '\n'
        return s

cdef VideoFrame createVideoFrame(vapoursynth.VSFrameRef *f, vapoursynth.VSAPI *funcs, Core core):
    cdef VideoFrame instance = VideoFrame.__new__(VideoFrame)    
    instance.f = f
    instance.funcs = funcs
    instance.core = core
    instance.format = createFormat(funcs.getFrameFormat(f))
    instance.width = funcs.getFrameWidth(f, 0)
    instance.height = funcs.getFrameHeight(f, 0)
    return instance

cdef class VideoNode(object):
    cdef vapoursynth.VSNodeRef *node
    cdef vapoursynth.VSAPI *funcs    
    cdef Core core
    cdef vapoursynth.VSVideoInfo *vi
    cdef readonly Format format
    cdef readonly int width
    cdef readonly int height
    cdef readonly int num_frames
    cdef readonly int fps_num
    cdef readonly int fps_den
    cdef readonly int flags

    def __init__(self):
        raise Error('Class cannon be instantiated directly')

    def __dealloc__(self):
        self.funcs.freeNode(self.node)

    def get_frame(self, int n):
        cdef char errorMsg[512]
        cdef VSFrameRef *f
        f = self.funcs.getFrame(n, self.node, errorMsg, 500)
        if f == NULL:
            raise Error('Internal error')
        else:
            return createVideoFrame(f, self.funcs, self.core) 

    def output(self, object fileobj not None, bint y4m = False, int lookahead = 10, object progress_update = None):
#ifdef _WIN32
        cdef CallbackData d = CallbackData(msvcrt.get_osfhandle(fileobj.fileno()), min(lookahead, self.num_frames), self.num_frames, self.format.num_planes, y4m, self, progress_update)
#else
        cdef CallbackData d = CallbackData(fileobj.fileno(), min(lookahead, self.num_frames), self.num_frames, self.format.num_planes, y4m, self, progress_update)
#endif
        if d.total <= 0:
            raise Error('Cannot output unknown length clip')

        # this is also an implicit test that the progress_update callback at least vaguely matches the requirements
        if (progress_update is not None):
            progress_update(0, d.total)

        if (self.format is None or self.format.color_family != YUV) and y4m:
            raise Error('Cannot apply y4m headers to non-yuv/unknown formats')

        y4mformat = ''
        numbits = ''

        if self.format is not None:
            if self.format.subsampling_w == 1 and self.format.subsampling_h == 1:
                y4mformat = 'C420'
            elif self.format.subsampling_w == 1 and self.format.subsampling_h == 0:
                y4mformat = 'C422'
            elif self.format.subsampling_w == 0 and self.format.subsampling_h == 0:
                y4mformat = 'C444'
            elif self.format.subsampling_w == 2 and self.format.subsampling_h == 2:
                y4mformat = 'C410'
            elif self.format.subsampling_w == 2 and self.format.subsampling_h == 0:
                y4mformat = 'C411'
            elif self.format.subsampling_w == 0 and self.format.subsampling_h == 1:
                y4mformat = 'C440'

            numbits = 'B' + str(self.format.bits_per_sample) + ' '

        if len(y4mformat) > 0:
            y4mformat = y4mformat + ' '

        cdef str header = 'YUV4MPEG2 ' + y4mformat + numbits + 'W' + str(self.width) + ' H' + str(self.height) + ' F' + str(self.fps_num) + ':' + str(self.fps_den) + ' Ip A0:0\n'
        cdef bytes b = header.encode('utf-8')
        cdef int dummy = 0
#ifdef _WIN32
        windows.WriteFile(d.handle, <char*>b, len(b), &dummy, NULL)
#else
        posix.unistd.write(d.handle, <char*>b, len(b))
#endif
        d.condition.acquire()

        for n in range(min(lookahead, d.total)):
            self.funcs.getFrameAsync(n, self.node, callback, <void *>d)

        d.condition.wait()
        d.condition.release()

        if d.error:
            raise Error(d.error)

    def __str__(self):
        cdef str s = 'VideoNode\n'

        if self.format:
            s += '\tFormat: ' + self.format.name + '\n'
        else:
            s += '\tFormat: dynamic\n'

        if not self.width or not self.height:
            s += '\tWidth: dynamic\n'
            s += '\tHeight: dynamic\n'
        else:
            s += '\tWidth: ' + str(self.width) + '\n'
            s += '\tHeight: ' + str(self.height) + '\n'

        if not self.num_frames:
            s += '\tNum Frames: unknown\n'
        else:
            s += '\tNum Frames: ' + str(self.num_frames) + '\n'

        if not self.fps_num or not self.fps_den:
            s += '\tFPS Num: dynamic\n'
            s += '\tFPS Den: dynamic\n'
        else:
            s += '\tFPS Num: ' + str(self.fps_num) + '\n'
            s += '\tFPS Den: ' + str(self.fps_den) + '\n'

        if self.flags == vapoursynth.nfNoCache:
            s += '\tFlags: No Cache\n'
        else:
            s += '\tFlags: None\n'

        return s

cdef VideoNode createVideoNode(vapoursynth.VSNodeRef *node, vapoursynth.VSAPI *funcs, Core core):
    cdef VideoNode instance = VideoNode.__new__(VideoNode)    
    instance.core = core
    instance.node = node
    instance.funcs = funcs
    instance.vi = funcs.getVideoInfo(node)

    if (instance.vi.format):
        instance.format = createFormat(instance.vi.format)
    else:
        instance.format = None

    instance.width = instance.vi.width
    instance.height = instance.vi.height
    instance.num_frames = instance.vi.numFrames
    instance.fps_num = instance.vi.fpsNum
    instance.fps_den = instance.vi.fpsDen
    instance.flags = instance.vi.flags
    return instance

cdef class Core(object):
    cdef vapoursynth.VSCore *core
    cdef vapoursynth.VSAPI *funcs
    cdef bint flatten
    cdef bint addcache
    cdef bint accept_lowercase

    def __cinit__(self, flatten = True, addcache = True, int threads = 0, bint accept_lowercase = False):
        self.funcs = vapoursynth.getVapourSynthAPI(1)
        self.core = self.funcs.createVSCore(threads)
        self.flatten = flatten
        self.addcache = addcache
        self.accept_lowercase = accept_lowercase

    def __dealloc__(self):
        self.funcs.freeVSCore(self.core)

    def __getattr__(self, name):
        cdef vapoursynth.VSPlugin *plugin
        tname = name.encode('utf-8')
        cdef char *cname = tname
        plugin = self.funcs.getPluginNs(cname, self.core)

        if plugin:
            return createPlugin(plugin, self.funcs, self)
        else:
            raise Error('No attribute with the name ' + name + ' exists. Did you mistype a plugin namespace?')

    def list_functions(self):
        cdef VSMap *m = self.funcs.getPlugins(self.core)
        cdef VSMap *n
        cdef bytes b
        cdef str sout = ''

        for i in range(self.funcs.propNumKeys(m)):
            a = self.funcs.propGetData(m, self.funcs.propGetKey(m, i), 0, NULL)
            a = a.decode('utf-8')
            a = a.split(';', 2)
            sout += a[2] + '\n'
            sout += '\tnamespace:\t' + a[0] + '\n'
            sout += '\tidentifier:\t' + a[1] + '\n'
            b = a[1].encode('utf-8')
            n = self.funcs.getFunctions(self.funcs.getPluginId(b, self.core))

            for j in range(self.funcs.propNumKeys(n)):
                c = self.funcs.propGetData(n, self.funcs.propGetKey(n, j), 0, NULL)
                c = c.decode('utf-8')
                c = c.split(';', 1)
                sout += '\t\t' + c[0] + '(' + c[1] +')\n'

            self.funcs.freeMap(n)

        self.funcs.freeMap(m)
        return sout

    def register_format(self, int color_family, int sample_type, int bits_per_sample, int subsampling_w, int subsampling_h):
        return createFormat(self.funcs.registerFormat(color_family, sample_type, bits_per_sample, subsampling_w, subsampling_h, self.core))

    def get_format(self, int id):
        cdef VSFormat *f = self.funcs.getFormatPreset(id, self.core)

        if f == NULL:
            raise Error('Internal error')
        else:
            return createFormat(f)

    def version(self):
        cdef VSVersion *v = self.funcs.getVersion()
        return v.versionString.decode('utf-8')

    def __str__(self):
        cdef str s = 'Core\n'
        s += self.version() + '\n'
        s += '\tFlatten: ' + str(self.flatten) + '\n'
        s += '\tAdd Caches: ' + str(self.addcache) + '\n'
        s += '\tAccept Lowercase: ' + str(self.accept_lowercase) + '\n'
        return s

cdef class Plugin(object):
    cdef Core core
    cdef VSPlugin *plugin
    cdef vapoursynth.VSAPI *funcs

    def __init__(self):
        raise Error('Class cannon be instantiated directly')

    def __getattr__(self, name):
        tname = name.encode('utf-8')
        cdef char *cname = tname
        cdef VSMap *m = self.funcs.getFunctions(self.plugin)
        lc_match = False
        cs_match = False

        for i in range(self.funcs.propNumKeys(m)):
            cname = self.funcs.propGetKey(m, i)
            orig_name = cname.decode('utf-8')
            lc_name = orig_name.lower()

            if orig_name == name:
                cs_match = True
                break

            if lc_name == name:
                lc_match = True
                break

        if cs_match or (lc_match and self.core.accept_lowercase):
            signature = self.funcs.propGetData(m, cname, 0, NULL).decode('utf-8').split(';', 1)
            self.funcs.freeMap(m)
            return createFunction(orig_name, signature[1], self, self.funcs)
        else:
            self.funcs.freeMap(m)
            raise Error('There is no function named ' + name)

cdef Plugin createPlugin(vapoursynth.VSPlugin *plugin, vapoursynth.VSAPI *funcs, Core core):
    cdef Plugin instance = Plugin.__new__(Plugin)    
    instance.core = core
    instance.plugin = plugin
    instance.funcs = funcs
    return instance

cdef class Function(object):
    cdef Plugin plugin
    cdef str name
    cdef str signature
    cdef vapoursynth.VSAPI *funcs

    def __init__(self):
        raise Error('Class cannon be instantiated directly')

    def __call__(self, *args, **kwargs):
        cdef VSMap *inm
        cdef VSMap *outm
        cdef char *cname
        ndict = {}

        # naively insert named arguments
        for key in kwargs:
            if isinstance(kwargs[key], Link):
                ndict[key + '_prop'] = kwargs[key].prop
                ndict[key] = kwargs[key].val
            else:
                ndict[key] = kwargs[key]

        # match up unnamed arguments to the first unused name in order
        sigs = self.signature.split(';')
        csig = 0
        numsig = len(sigs) 

        for arg in args:
            key = sigs[csig].split(':', 1)
            key = key[0]

            while key in ndict:
                csig = csig + 1

                if csig >= numsig:
                    raise Error('There are more unnamed arguments given than unspecified arguments to match')

                key = sigs[csig].split(':', 1)
                key = key[0]
            if isinstance(arg, Link):
                ndict[key + '_prop'] = arg.prop
                ndict[key] = arg.val
            else:
                ndict[key] = arg

        inm = self.funcs.newMap()

        try:
            dictToMap(ndict, inm, self.plugin.core, self.funcs)
        except Error as e:
            self.funcs.freeMap(inm)
            raise Error(self.name + ': ' + str(e))

        tname = self.name.encode('utf-8')
        cname = tname
        outm = self.funcs.invoke(self.plugin.plugin, cname, inm)
        self.funcs.freeMap(inm)
        cdef char *err = self.funcs.getError(outm)
        cdef bytes emsg

        if err:
            emsg = err
            self.funcs.freeMap(outm)
            raise Error(emsg.decode('utf-8'))

        retdict = mapToDict(outm, self.plugin.core.flatten, self.plugin.core.addcache, self.plugin.core, self.funcs)
        self.funcs.freeMap(outm)
        return retdict

cdef Function createFunction(str name, str signature, Plugin plugin, vapoursynth.VSAPI *funcs):
    cdef Function instance = Function.__new__(Function)    
    instance.name = name
    instance.signature = signature
    instance.plugin = plugin
    instance.funcs = funcs
    return instance

# for python functions being executed by vs

cdef void __stdcall freeFunc(void *pobj) nogil:
    with gil:
        Py_DECREF(<Func>pobj)

cdef void __stdcall publicFunction(VSMap *inm, VSMap *outm, void *userData, VSCore *core, VSAPI *vsapi) nogil:
    with gil:
        d = <Func>userData

        try:
            m = mapToDict(inm, False, False, d.core, vsapi)
            ret = d(m)
            dictToMap(ret, outm, d.core, vsapi)
        except Error as e:
            emsg = str(e).encode('utf-8')
            vsapi.setError(outm, emsg)

# for whole script evaluation and export

cdef public struct ScriptExport:
    void *pynode
    VSNodeRef *node
    char *error

cdef public api int __stdcall evaluate_script(char *fn, ScriptExport *extp) nogil:
    extp.node = NULL
    extp.pynode = NULL
    extp.error = NULL

    with gil:
        try:
            evaldict = {}
            comp = compile(open(fn.decode('utf-8')).read(), fn.decode('utf-8'), 'exec')
            exec(comp) in evaldict
            node = evaldict['last']

            if isinstance(node, VideoNode):
                Py_INCREF(node)
                extp.pynode = <void *>node
                extp.node = (<VideoNode>node).node
            else:
                return 3
        except Error, e:
            return 1
        except:
            return 2
        return 0

cdef public api int __stdcall free_script(ScriptExport *extp) nogil:
    with gil:
        node = <VideoNode>extp.pynode
        Py_DECREF(node)

{'fraction_non_alphanumeric': 0.10436935121754648, 'fraction_numerical': 0.0126647861378159, 'mean_word_length': 2.115375418460067, 'pattern_counts': {'":': 0, '<': 46, '<?xml version=': 0, '>': 42, 'https://': 0, 'lorem ipsum': 0, 'www.': 2, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '6509379', 'n_tokens_mistral': 17740, 'n_tokens_neox': 16737, 'n_words': 4445}