--- /dev/null
+#! /usr/bin/python
+debug=False
+
+#
+# Code generator for python ctypes bindings for VLC
+# Copyright (C) 2009 the VideoLAN team
+# $Id: $
+#
+# Authors: Olivier Aubert <olivier.aubert at liris.cnrs.fr>
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program 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 this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
+#
+
+"""This module parses VLC public API include files and generates
+corresponding python/ctypes code. Moreover, it generates class
+wrappers for most methods.
+"""
+
+import sys
+import re
+import time
+import operator
+import itertools
+
+# DefaultDict from ASPN python cookbook
+import copy
+class DefaultDict(dict):
+ """Dictionary with a default value for unknown keys."""
+ def __init__(self, default=None, **items):
+ dict.__init__(self, **items)
+ self.default = default
+
+ def __getitem__(self, key):
+ if key in self:
+ return self.get(key)
+ else:
+ ## Need copy in case self.default is something like []
+ return self.setdefault(key, copy.deepcopy(self.default))
+
+ def __copy__(self):
+ return DefaultDict(self.default, **self)
+
+# Methods not decorated/not referenced
+blacklist=[
+ "libvlc_exception_raise",
+ "libvlc_exception_raised",
+ "libvlc_exception_get_message",
+ "libvlc_get_vlc_instance",
+
+ "libvlc_media_add_option_flag",
+ "libvlc_media_list_view_index_of_item",
+ "libvlc_media_list_view_insert_at_index",
+ "libvlc_media_list_view_remove_at_index",
+ "libvlc_media_list_view_add_item",
+
+ # In svn but not in current 1.0.0
+ 'libvlc_video_set_deinterlace',
+ 'libvlc_video_get_marquee_option_as_int',
+ 'libvlc_video_get_marquee_option_as_string',
+ 'libvlc_video_set_marquee_option_as_int',
+ 'libvlc_video_set_marquee_option_as_string',
+ 'libvlc_vlm_get_event_manager',
+
+ 'mediacontrol_PlaylistSeq__free',
+
+ # TODO
+ "libvlc_event_detach",
+ "libvlc_event_attach",
+ ]
+
+# Precompiled regexps
+api_re=re.compile('VLC_PUBLIC_API\s+(\S+\s+.+?)\s*\(\s*(.+?)\s*\)')
+param_re=re.compile('\s*(const\s*|unsigned\s*|struct\s*)?(\S+\s*\**)\s+(.+)')
+paramlist_re=re.compile('\s*,\s*')
+comment_re=re.compile('\\param\s+(\S+)')
+python_param_re=re.compile('(@param\s+\S+)(.+)')
+forward_re=re.compile('.+\(\s*(.+?)\s*\)(\s*\S+)')
+
+# Definition of parameter passing mode for types. This should not be
+# hardcoded this way, but works alright ATM.
+parameter_passing=DefaultDict(default=1)
+parameter_passing['libvlc_exception_t*']=3
+
+# C-type to ctypes/python type conversion
+typ2class={
+ 'libvlc_exception_t*': 'ctypes.POINTER(VLCException)',
+
+ 'libvlc_media_player_t*': 'MediaPlayer',
+ 'libvlc_instance_t*': 'Instance',
+ 'libvlc_media_t*': 'Media',
+ 'libvlc_log_t*': 'Log',
+ 'libvlc_log_iterator_t*': 'LogIterator',
+ 'libvlc_log_message_t*': 'LogMessage',
+ 'libvlc_event_type_t': 'EventType',
+ 'libvlc_event_manager_t*': 'EventManager',
+ 'libvlc_media_discoverer_t*': 'MediaDiscoverer',
+ 'libvlc_media_library_t*': 'MediaLibrary',
+ 'libvlc_media_list_t*': 'MediaList',
+ 'libvlc_media_list_player_t*': 'MediaListPlayer',
+ 'libvlc_media_list_view_t*': 'MediaListView',
+ 'libvlc_track_description_t*': 'TrackDescription',
+ 'libvlc_audio_output_t*': 'AudioOutput',
+
+ 'mediacontrol_Instance*': 'MediaControl',
+ 'mediacontrol_Exception*': 'MediaControlException',
+ 'mediacontrol_RGBPicture*': 'RGBPicture',
+ 'mediacontrol_PlaylistSeq*': 'MediaControlPlaylistSeq',
+ 'mediacontrol_Position*': 'MediaControlPosition',
+ 'mediacontrol_StreamInformation*': 'MediaControlStreamInformation',
+ 'mediacontrol_PositionOrigin': 'ctypes.c_uint',
+ 'mediacontrol_PositionKey': 'ctypes.c_uint',
+ 'WINDOWHANDLE': 'ctypes.c_ulong',
+
+ 'short': 'ctypes.c_short',
+ 'char*': 'ctypes.c_char_p',
+ 'char**': 'ListPOINTER(ctypes.c_char_p)',
+ 'uint32_t': 'ctypes.c_uint',
+ 'float': 'ctypes.c_float',
+ 'unsigned': 'ctypes.c_uint',
+ 'void': 'None',
+ 'void*': 'ctypes.c_void_p',
+ 'int': 'ctypes.c_int',
+ '...': 'FIXMEva_list',
+ 'libvlc_callback_t': 'FIXMEcallback',
+ 'libvlc_time_t': 'ctypes.c_longlong',
+ 'libvlc_video_marquee_int_option_t': 'ctypes.c_int',
+ 'libvlc_video_marquee_string_option_t': 'ctypes.c_char_p',
+ # FIXME: enums -> to be processed
+ 'libvlc_media_option_t': 'ctypes.c_uint',
+ 'libvlc_meta_t': 'ctypes.c_uint',
+ 'libvlc_state_t': 'State',
+ }
+
+# Defined python classes, i.e. classes for which we want to generate
+# class wrappers around libvlc functions
+defined_classes=(
+ 'MediaPlayer',
+ 'Instance',
+ 'Media',
+ 'Log',
+ 'LogIterator',
+ #'LogMessage',
+ 'EventType',
+ 'EventManager',
+ 'MediaDiscoverer',
+ 'MediaLibrary',
+ 'MediaList',
+ 'MediaListPlayer',
+ 'MediaListView',
+ 'TrackDescription',
+ 'AudioOutput',
+ 'MediaControl',
+ #'RGBPicture',
+ #'MediaControlPosition',
+ #'MediaControlStreamInformation',
+ )
+
+# Definition of prefixes that we can strip from method names when
+# wrapping them into class methods
+prefixes=dict( (v, k[:-2]) for (k, v) in typ2class.iteritems() if v in defined_classes )
+prefixes['MediaControl']='mediacontrol_'
+
+def parse_param(s):
+ """Parse a C parameter expression.
+
+ It is used to parse both the type/name for methods, and type/name
+ for their parameters.
+
+ It returns a tuple (type, name).
+ """
+ s=s.strip()
+ s=s.replace('const', '')
+ if 'VLC_FORWARD' in s:
+ m=forward_re.match(s)
+ s=m.group(1)+m.group(2)
+ m=param_re.search(s)
+ if m:
+ const, typ, name=m.groups()
+ while name.startswith('*'):
+ typ += '*'
+ name=name[1:]
+ if name == 'const*':
+ # K&R definition: const char * const*
+ name=''
+ typ=typ.replace(' ', '')
+ return typ, name
+ else:
+ # K&R definition: only type
+ return s.replace(' ', ''), ''
+
+def generate_header(classes=None):
+ """Generate header code.
+ """
+ f=open('header.py', 'r')
+ for l in f:
+ if 'build_date' in l:
+ print 'build_date="%s"' % time.ctime()
+ else:
+ print l,
+ f.close()
+
+def parse_include(name):
+ """Parse include file.
+
+ This generates a tuple for each function:
+ (return_type, method_name, parameter_list, comment)
+ with parameter_list being a list of tuples (parameter_type, parameter_name).
+ """
+ f=open(name, 'r')
+ accumulator=''
+ comment=''
+ for l in f:
+ # Note: lstrip() should not be necessary, but there is 1 badly
+ # formatted comment in vlc1.0.0 includes
+ if l.lstrip().startswith('/**'):
+ comment=''
+ continue
+ elif l.startswith(' * '):
+ comment = comment + l[3:]
+ continue
+
+ l=l.strip()
+
+ if accumulator:
+ accumulator=" ".join( (accumulator, l) )
+ if l.endswith(');'):
+ # End of definition
+ l=accumulator
+ accumulator=''
+ elif l.startswith('VLC_PUBLIC_API') and not l.endswith(');'):
+ # Multiline definition. Accumulate until end of definition
+ accumulator=l
+ continue
+
+ m=api_re.match(l)
+ if m:
+ (ret, param)=m.groups()
+
+ rtype, method=parse_param(ret)
+
+ params=[]
+ for p in paramlist_re.split(param):
+ params.append( parse_param(p) )
+
+ if len(params) == 1 and params[0][0] == 'void':
+ # Empty parameter list
+ params=[]
+
+ if list(p for p in params if not p[1]):
+ # Empty parameter names. Have to poke into comment.
+ names=comment_re.findall(comment)
+ if len(names) < len(params):
+ # Bad description: all parameters are not specified.
+ # Generate default parameter names
+ badnames=[ "param%d" % i for i in xrange(len(params)) ]
+ # Put in the existing ones
+ for (i, p) in enumerate(names):
+ badnames[i]=names[i]
+ names=badnames
+ print "### Error ###"
+ print "### Cannot get parameter names from comment for %s: %s" % (method, comment.replace("\n", ' '))
+ # Note: this was previously
+ # raise Exception("Cannot get parameter names from comment for %s: %s" % (method, comment))
+ # but it prevented code generation for a minor detail (some bad descriptions).
+ params=[ (p[0], names[i]) for (i, p) in enumerate(params) ]
+
+ for typ, name in params:
+ if not typ in typ2class:
+ raise Exception("No conversion for %s (from %s:%s)" % (typ, method, name))
+
+ # Transform Doxygen syntax into epydoc syntax
+ comment=comment.replace('\\param', '@param').replace('\\return', '@return')
+
+ if debug:
+ print '********************'
+ print l
+ print '-------->'
+ print "%s (%s)" % (method, rtype)
+ for typ, name in params:
+ print " %s (%s)" % (name, typ)
+ print '********************'
+ yield (rtype,
+ method,
+ params,
+ comment)
+
+def output_ctypes(rtype, method, params, comment):
+ """Output ctypes decorator for the given method.
+ """
+ if method in blacklist:
+ # FIXME
+ return
+
+ if params:
+ print "prototype=ctypes.CFUNCTYPE(%s, %s)" % (typ2class.get(rtype, 'FIXME_%s' % rtype),
+ ",".join( typ2class[p[0]] for p in params ))
+ else:
+ print "prototype=ctypes.CFUNCTYPE(%s)" % typ2class.get(rtype, 'FIXME_%s' % rtype)
+
+
+ if not params:
+ flags='paramflags= tuple()'
+ elif len(params) == 1:
+ flags="paramflags=( (%d, ), )" % parameter_passing[params[0][0]]
+ else:
+ flags="paramflags=%s" % ",".join( '(%d,)' % parameter_passing[p[0]] for p in params )
+ print flags
+ print '%s = prototype( ("%s", dll), paramflags )' % (method, method)
+ if '3' in flags:
+ # A VLCException is present. Process it.
+ print "%s.errcheck = check_vlc_exception" % method
+ print '%s.__doc__ = """%s"""' % (method, comment)
+ print
+
+def parse_override(name):
+ """Parse override definitions file.
+
+ It is possible to override methods definitions in classes.
+ """
+ res={}
+
+ data=[]
+ current=None
+ f=open(name, 'r')
+ for l in f:
+ m=re.match('class (\S+):', l)
+ if m:
+ # Dump old data
+ if current is not None:
+ res[current]="\n".join(data)
+ current=m.group(1)
+ data=[]
+ continue
+ data.append(l)
+ res[current]="\n".join(data)
+ f.close()
+ return res
+
+def fix_python_comment(c):
+ """Fix comment by removing first and last parameters (self and exception)
+ """
+ data=c.splitlines()
+ body=itertools.takewhile(lambda l: not '@param' in l, data)
+ param=[ python_param_re.sub('\\1:\\2', l) for l in itertools.ifilter(lambda l: '@param' in l, data) ]
+ ret=[ l.replace('@return', '@return:') for l in itertools.ifilter(lambda l: '@return' in l, data) ]
+
+ if len(param) >= 2:
+ param=param[1:-1]
+ elif len(param) == 1:
+ param=[]
+
+ return "\n".join(itertools.chain(body, param, ret))
+
+def generate_wrappers(methods):
+ """Generate class wrappers for all appropriate methods.
+
+ @return: the set of wrapped method names
+ """
+ ret=set()
+ # Sort methods against the element they apply to.
+ elements=sorted( ( (typ2class.get(params[0][0]), rt, met, params, c)
+ for (rt, met, params, c) in methods
+ if params and typ2class.get(params[0][0], '_') in defined_classes
+ ),
+ key=operator.itemgetter(0))
+
+ overrides=parse_override('override.py')
+
+ for classname, el in itertools.groupby(elements, key=operator.itemgetter(0)):
+ print """
+class %(name)s(object):
+ def __init__(self, pointer=None):
+ '''Internal method used for instanciating wrappers from ctypes.
+ '''
+ if pointer is None:
+ raise Exception("Internal method. You should instanciate objects through other class methods (probably named 'new' or ending with 'new')")
+ self._as_parameter_=ctypes.c_void_p(pointer)
+
+ @staticmethod
+ def from_param(arg):
+ '''(INTERNAL) ctypes parameter conversion method.
+ '''
+ return arg._as_parameter_
+""" % {'name': classname}
+
+ if classname in overrides:
+ print overrides[classname]
+
+ prefix=prefixes.get(classname, '')
+
+ for cl, rtype, method, params, comment in el:
+ if method in blacklist:
+ continue
+ # Strip prefix
+ name=method.replace(prefix, '').replace('libvlc_', '')
+ ret.add(method)
+ if params:
+ params[0]=(params[0][0], 'self')
+ if params and params[-1][0] in ('libvlc_exception_t*', 'mediacontrol_Exception*'):
+ args=", ".join( p[1] for p in params[:-1] )
+ else:
+ args=", ".join( p[1] for p in params )
+
+ print " def %s(%s):" % (name, args)
+ print ' """%s\n"""' % fix_python_comment(comment)
+ if params and params[-1][0] == 'libvlc_exception_t*':
+ # Exception handling
+ print " e=VLCException()"
+ print " return %s(%s, e)" % (method, args)
+ elif params and params[-1][0] == 'mediacontrol_Exception*':
+ # Exception handling
+ print " e=MediaControlException()"
+ print " return %s(%s, e)" % (method, args)
+ else:
+ print " return %s(%s)" % (method, args)
+ print
+ return ret
+
+if __name__ == '__main__':
+ methods=[]
+ for name in sys.argv[1:]:
+ methods.extend(list(parse_include(name)))
+ if debug:
+ sys.exit(0)
+
+ generate_header()
+ wrapped=generate_wrappers(methods)
+ for l in methods:
+ output_ctypes(*l)
+
+ all=set( t[1] for t in methods )
+ not_wrapped=all.difference(wrapped)
+ print "# Not wrapped methods:"
+ for m in not_wrapped:
+ print "# ", m
+
--- /dev/null
+#! /usr/bin/python
+
+#
+# Python ctypes bindings for VLC
+# Copyright (C) 2009 the VideoLAN team
+# $Id: $
+#
+# Authors: Olivier Aubert <olivier.aubert at liris.cnrs.fr>
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program 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 this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
+#
+
+import ctypes
+import sys
+
+build_date="This will be replaced by the build date"
+
+if sys.platform == 'linux2':
+ dll=ctypes.CDLL('libvlc.so')
+elif sys.platform == 'win32':
+ dll=ctypes.CDLL('libvlc.dll')
+elif sys.platform == 'darwin':
+ # FIXME: should find a means to configure path
+ dll=ctypes.CDLL('/Applications/VLC.app/Contents/MacOS/lib/libvlc.2.dylib')
+
+class ListPOINTER(object):
+ '''Just like a POINTER but accept a list of ctype as an argument.
+ '''
+ def __init__(self, etype):
+ self.etype = etype
+
+ def from_param(self, param):
+ if isinstance(param, (list,tuple)):
+ return (self.etype * len(param))(*param)
+
+# From libvlc_structures.h
+class VLCException(ctypes.Structure):
+ _fields_= [
+ ('raised', ctypes.c_int),
+ ('code', ctypes.c_int),
+ ('message', ctypes.c_char_p),
+ ]
+ def init(self):
+ libvlc_exception_init(self)
+
+ def clear(self):
+ libvlc_exception_clear(self)
+
+class PlaylistItem(ctypes.Structure):
+ _fields_= [
+ ('id', ctypes.c_int),
+ ('uri', ctypes.c_char_p),
+ ('name', ctypes.c_char_p),
+ ]
+
+class LogMessage(ctypes.Structure):
+ _fields_= [
+ ('size', ctypes.c_uint),
+ ('severity', ctypes.c_int),
+ ('type', ctypes.c_char_p),
+ ('name', ctypes.c_char_p),
+ ('header', ctypes.c_char_p),
+ ('message', ctypes.c_char_p),
+ ]
+
+class MediaControlPosition(ctypes.Structure):
+ _fields_= [
+ ('origin', ctypes.c_ushort),
+ ('key', ctypes.c_ushort),
+ ('value', ctypes.c_longlong),
+ ]
+
+ @staticmethod
+ def from_param(arg):
+ if isinstance(arg, (int, long)):
+ p=MediaControlPosition()
+ p.value=arg
+ p.key=2
+ return p
+ else:
+ return arg
+
+class MediaControlPositionOrigin(ctypes.c_uint):
+ enum=(
+ 'AbsolutePosition',
+ 'RelativePosition',
+ 'ModuloPosition',
+ )
+ def __repr__(self):
+ return self.enum[self.value]
+
+class State(ctypes.c_uint):
+ # FIXME: should be improved (State.NothingSpecial should hold the value)
+ # and maybe auto-generated from typedefs
+ enum=(
+ 'NothingSpecial',
+ 'Opening',
+ 'Buffering',
+ 'Playing',
+ 'Paused',
+ 'Stopped',
+ 'Ended',
+ 'Error',
+ )
+ def __repr__(self):
+ return self.enum[self.value]
+
+class MediaControlException(ctypes.Structure):
+ _fields_= [
+ ('code', ctypes.c_int),
+ ('message', ctypes.c_char_p),
+ ]
+ def init(self):
+ mediacontrol_exception_init(self)
+
+ def clear(self):
+ mediacontrol_exception_free(self)
+
+class MediaControlStreamInformation(ctypes.Structure):
+ _fields_= [
+ ('code', ctypes.c_int),
+ ('message', ctypes.c_char_p),
+ ]
+
+class RGBPicture(ctypes.Structure):
+ _fields_= [
+ ('width', ctypes.c_int),
+ ('height', ctypes.c_int),
+ ('type', ctypes.c_uint32),
+ ('date', ctypes.c_longlong),
+ ('size', ctypes.c_int),
+ ('data', ctypes.c_char_p),
+ ]
+
+ def free(self):
+ mediacontrol_RGBPicture__free(self)
+
+# Decorator for callback methods
+callbackmethod=ctypes.CFUNCTYPE(None, ctypes.c_void_p, ctypes.c_void_p)
+
+def check_vlc_exception(result, func, args):
+ """Error checking method for functions using an exception in/out parameter.
+ """
+ ex=args[-1]
+ # Take into account both VLCException and MediacontrolException
+ c=getattr(ex, 'raised', getattr(ex, 'code', 0))
+ if c:
+ raise Exception(args[-1].message)
+ return result