5 # Code generator for python ctypes bindings for VLC
6 # Copyright (C) 2009 the VideoLAN team
9 # Authors: Olivier Aubert <olivier.aubert at liris.cnrs.fr>
11 # This program is free software; you can redistribute it and/or modify
12 # it under the terms of the GNU General Public License as published by
13 # the Free Software Foundation; either version 2 of the License, or
14 # (at your option) any later version.
16 # This program is distributed in the hope that it will be useful,
17 # but WITHOUT ANY WARRANTY; without even the implied warranty of
18 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
19 # GNU General Public License for more details.
21 # You should have received a copy of the GNU General Public License
22 # along with this program; if not, write to the Free Software
23 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
26 """This module parses VLC public API include files and generates
27 corresponding python/ctypes code. Moreover, it generates class
28 wrappers for most methods.
36 from optparse import OptionParser
38 # DefaultDict from ASPN python cookbook
40 class DefaultDict(dict):
41 """Dictionary with a default value for unknown keys."""
42 def __init__(self, default=None, **items):
43 dict.__init__(self, **items)
44 self.default = default
46 def __getitem__(self, key):
50 ## Need copy in case self.default is something like []
51 return self.setdefault(key, copy.deepcopy(self.default))
54 return DefaultDict(self.default, **self)
56 # Methods not decorated/not referenced
58 "libvlc_exception_raise",
59 "libvlc_exception_raised",
60 "libvlc_exception_get_message",
61 "libvlc_get_vlc_instance",
63 "libvlc_media_list_view_index_of_item",
64 "libvlc_media_list_view_insert_at_index",
65 "libvlc_media_list_view_remove_at_index",
66 "libvlc_media_list_view_add_item",
68 # In svn but not in current 1.0.0.
69 #"libvlc_media_add_option_flag",
70 #'libvlc_video_set_deinterlace',
71 #'libvlc_video_get_marquee_option_as_int',
72 #'libvlc_video_get_marquee_option_as_string',
73 #'libvlc_video_set_marquee_option_as_int',
74 #'libvlc_video_set_marquee_option_as_string',
75 #'libvlc_vlm_get_event_manager',
76 #"libvlc_media_list_player_event_manager",
77 #'libvlc_media_player_next_frame',
79 'mediacontrol_PlaylistSeq__free',
83 api_re=re.compile('VLC_PUBLIC_API\s+(\S+\s+.+?)\s*\(\s*(.+?)\s*\)')
84 param_re=re.compile('\s*(const\s*|unsigned\s*|struct\s*)?(\S+\s*\**)\s+(.+)')
85 paramlist_re=re.compile('\s*,\s*')
86 comment_re=re.compile('\\param\s+(\S+)')
87 python_param_re=re.compile('(@param\s+\S+)(.+)')
88 forward_re=re.compile('.+\(\s*(.+?)\s*\)(\s*\S+)')
89 enum_re=re.compile('typedef\s+(enum)\s*(\S+\s*)?\{\s*(.+)\s*\}\s*(\S+);')
90 special_enum_re=re.compile('^(enum)\s*(\S+\s*)?\{\s*(.+)\s*\};')
92 # Definition of parameter passing mode for types. This should not be
93 # hardcoded this way, but works alright ATM.
94 parameter_passing=DefaultDict(default=1)
95 parameter_passing['libvlc_exception_t*']=3
98 def __init__(self, list_of_files):
102 for name in list_of_files:
103 self.enums.extend(self.parse_typedef(name))
104 self.methods.extend(self.parse_include(name))
106 def parse_param(self, s):
107 """Parse a C parameter expression.
109 It is used to parse both the type/name for methods, and type/name
110 for their parameters.
112 It returns a tuple (type, name).
115 s=s.replace('const', '')
116 if 'VLC_FORWARD' in s:
117 m=forward_re.match(s)
118 s=m.group(1)+m.group(2)
121 const, typ, name=m.groups()
122 while name.startswith('*'):
126 # K&R definition: const char * const*
128 typ=typ.replace(' ', '')
131 # K&R definition: only type
132 return s.replace(' ', ''), ''
134 def parse_typedef(self, name):
135 """Parse include file for typedef expressions.
137 This generates a tuple for each typedef:
138 (type, name, value_list, comment)
139 with type == 'enum' (for the moment) and value_list being a list of (name, value)
140 Note that values are string, since this is intended for code generation.
145 # Note: lstrip() should not be necessary, but there is 1 badly
146 # formatted comment in vlc1.0.0 includes
147 if l.lstrip().startswith('/**'):
150 elif l.startswith(' * '):
151 comment = comment + l[3:]
155 if l.startswith('/*') or l.endswith('*/'):
158 if (l.startswith('typedef enum') or l.startswith('enum')) and not l.endswith(';'):
159 # Multiline definition. Accumulate until end of definition
163 accumulator=" ".join( (accumulator, l) )
172 (typ, dummy, data, name)=m.groups()
173 for i, l in enumerate(paramlist_re.split(data)):
175 if l.startswith('/*'):
178 # A value was specified. Use it.
179 values.append(re.split('\s*=\s*', l))
182 values.append( (l, str(i)) )
183 comment=comment.replace('@{', '').replace('@see', 'See').replace('\ingroup', '')
184 yield (typ, name.strip(), values, comment)
188 # Special case, used only for libvlc_events.h
189 m=special_enum_re.match(l)
192 (typ, name, data)=m.groups()
193 for i, l in enumerate(paramlist_re.split(data)):
195 if l.startswith('/*') or l.startswith('#'):
198 # A value was specified. Use it.
199 values.append(re.split('\s*=\s*', l))
202 values.append( (l, str(i)) )
203 comment=comment.replace('@{', '').replace('@see', 'See').replace('\ingroup', '')
204 yield (typ, name.strip(), values, comment)
208 def parse_include(self, name):
209 """Parse include file.
211 This generates a tuple for each function:
212 (return_type, method_name, parameter_list, comment)
213 with parameter_list being a list of tuples (parameter_type, parameter_name).
219 # Note: lstrip() should not be necessary, but there is 1 badly
220 # formatted comment in vlc1.0.0 includes
221 if l.lstrip().startswith('/**'):
224 elif l.startswith(' * '):
225 comment = comment + l[3:]
231 accumulator=" ".join( (accumulator, l) )
236 elif l.startswith('VLC_PUBLIC_API') and not l.endswith(');'):
237 # Multiline definition. Accumulate until end of definition
243 (ret, param)=m.groups()
245 rtype, method=self.parse_param(ret)
248 for p in paramlist_re.split(param):
249 params.append( self.parse_param(p) )
251 if len(params) == 1 and params[0][0] == 'void':
252 # Empty parameter list
255 if list(p for p in params if not p[1]):
256 # Empty parameter names. Have to poke into comment.
257 names=comment_re.findall(comment)
258 if len(names) < len(params):
259 # Bad description: all parameters are not specified.
260 # Generate default parameter names
261 badnames=[ "param%d" % i for i in xrange(len(params)) ]
262 # Put in the existing ones
263 for (i, p) in enumerate(names):
266 print "### Error ###"
267 print "### Cannot get parameter names from comment for %s: %s" % (method, comment.replace("\n", ' '))
268 # Note: this was previously
269 # raise Exception("Cannot get parameter names from comment for %s: %s" % (method, comment))
270 # but it prevented code generation for a minor detail (some bad descriptions).
271 params=[ (p[0], names[i]) for (i, p) in enumerate(params) ]
273 # Transform Doxygen syntax into epydoc syntax
274 comment=comment.replace('\\param', '@param').replace('\\return', '@return')
277 print '********************'
280 print "%s (%s)" % (method, rtype)
281 for typ, name in params:
282 print " %s (%s)" % (name, typ)
283 print '********************'
290 def dump_methods(self):
291 print "** Defined functions **"
292 for (rtype, name, params, comment) in self.methods:
293 print "%(name)s (%(rtype)s):" % locals()
295 print " %(n)s (%(t)s)" % locals()
297 def dump_enums(self):
298 print "** Defined enums **"
299 for (typ, name, values, comment) in self.enums:
300 print "%(name)s (%(typ)s):" % locals()
302 print " %(k)s=%(v)s" % locals()
304 class PythonGenerator(object):
305 # C-type to ctypes/python type conversion.
306 # Note that enum types conversions are generated (cf convert_enum_names)
308 'libvlc_exception_t*': 'ctypes.POINTER(VLCException)',
310 'libvlc_media_player_t*': 'MediaPlayer',
311 'libvlc_instance_t*': 'Instance',
312 'libvlc_media_t*': 'Media',
313 'libvlc_log_t*': 'Log',
314 'libvlc_log_iterator_t*': 'LogIterator',
315 'libvlc_log_message_t*': 'LogMessage',
316 'libvlc_event_type_t': 'ctypes.c_uint',
317 'libvlc_event_manager_t*': 'EventManager',
318 'libvlc_media_discoverer_t*': 'MediaDiscoverer',
319 'libvlc_media_library_t*': 'MediaLibrary',
320 'libvlc_media_list_t*': 'MediaList',
321 'libvlc_media_list_player_t*': 'MediaListPlayer',
322 'libvlc_media_list_view_t*': 'MediaListView',
323 'libvlc_track_description_t*': 'TrackDescription',
324 'libvlc_audio_output_t*': 'AudioOutput',
326 'mediacontrol_Instance*': 'MediaControl',
327 'mediacontrol_Exception*': 'MediaControlException',
328 'mediacontrol_RGBPicture*': 'ctypes.POINTER(RGBPicture)',
329 'mediacontrol_PlaylistSeq*': 'MediaControlPlaylistSeq',
330 'mediacontrol_Position*': 'ctypes.POINTER(MediaControlPosition)',
331 'mediacontrol_StreamInformation*': 'ctypes.POINTER(MediaControlStreamInformation)',
332 'WINDOWHANDLE': 'ctypes.c_ulong',
335 'void*': 'ctypes.c_void_p',
336 'short': 'ctypes.c_short',
337 'char*': 'ctypes.c_char_p',
338 'char**': 'ListPOINTER(ctypes.c_char_p)',
339 'uint32_t': 'ctypes.c_uint',
340 'float': 'ctypes.c_float',
341 'unsigned': 'ctypes.c_uint',
342 'int': 'ctypes.c_int',
343 '...': 'FIXMEva_list',
344 'libvlc_callback_t': 'ctypes.c_void_p',
345 'libvlc_time_t': 'ctypes.c_longlong',
348 # Defined python classes, i.e. classes for which we want to generate
349 # class wrappers around libvlc functions
367 def __init__(self, parser=None):
370 # Generate python names for enums
371 self.type2class.update(self.convert_enum_names(parser.enums))
374 # Definition of prefixes that we can strip from method names when
375 # wrapping them into class methods
376 self.prefixes=dict( (v, k[:-2])
377 for (k, v) in self.type2class.iteritems()
378 if v in self.defined_classes )
379 self.prefixes['MediaControl']='mediacontrol_'
381 def save(self, filename=None):
382 if filename is None or filename == '-':
385 self.fd=open(filename, 'w')
387 self.insert_code('header.py')
388 self.generate_enums(self.parser.enums)
389 wrapped_methods=self.generate_wrappers(self.parser.methods)
390 for l in self.parser.methods:
391 self.output_ctypes(*l)
392 self.insert_code('footer.py')
394 all_methods=set( t[1] for t in self.parser.methods )
395 not_wrapped=all_methods.difference(wrapped_methods)
396 self.output("# Not wrapped methods:")
397 for m in not_wrapped:
400 if self.fd != sys.stdout:
403 def output(self, *p):
404 self.fd.write(" ".join(p))
407 def check_types(self):
408 """Make sure that all types are properly translated.
410 This method must be called *after* convert_enum_names, since
411 the latter populates type2class with converted enum names.
413 for (rt, met, params, c) in self.parser.methods:
414 for typ, name in params:
415 if not typ in self.type2class:
416 raise Exception("No conversion for %s (from %s:%s)" % (typ, met, name))
418 def insert_code(self, filename):
419 """Generate header/footer code.
421 f=open(filename, 'r')
423 if 'build_date' in l:
424 self.output('build_date="%s"' % time.ctime())
426 self.output(l.rstrip())
429 def convert_enum_names(self, enums):
431 for (typ, name, values, comment) in enums:
433 raise Exception('This method only handles enums')
434 pyname=re.findall('(libvlc|mediacontrol)_(.+?)(_t)?$', name)[0][1]
436 pyname=pyname.title().replace('_', '')
437 elif not pyname[0].isupper():
438 pyname=pyname.capitalize()
442 def generate_enums(self, enums):
443 for (typ, name, values, comment) in enums:
445 raise Exception('This method only handles enums')
446 pyname=self.type2class[name]
448 self.output("class %s(ctypes.c_uint):" % pyname)
449 self.output(' """%s\n """' % comment)
452 # Convert symbol names
456 # Single character. Some symbols use 1_1, 5_1, etc.
457 n="_".join( k.split('_')[-2:] )
458 if re.match('^[0-9]', n):
459 # Cannot start an identifier with a number
464 self.output(" %s=%s" % (conv[k], v))
466 self.output(" _names={")
468 self.output(" %s: '%s'," % (v, conv[k]))
473 return ".".join((self.__class__.__module__, self.__class__.__name__, self._names[self.value]))
476 def output_ctypes(self, rtype, method, params, comment):
477 """Output ctypes decorator for the given method.
479 if method in blacklist:
483 self.output("""if hasattr(dll, '%s'):""" % method)
485 self.output(" prototype=ctypes.CFUNCTYPE(%s, %s)" % (self.type2class.get(rtype, 'FIXME_%s' % rtype),
486 ",".join( self.type2class[p[0]] for p in params )))
488 self.output(" prototype=ctypes.CFUNCTYPE(%s)" % self.type2class.get(rtype, 'FIXME_%s' % rtype))
492 flags=' paramflags= tuple()'
493 elif len(params) == 1:
494 flags=" paramflags=( (%d, ), )" % parameter_passing[params[0][0]]
496 flags=" paramflags=%s" % ",".join( '(%d,)' % parameter_passing[p[0]] for p in params )
498 self.output(' %s = prototype( ("%s", dll), paramflags )' % (method, method))
500 # A VLCException is present. Process it.
501 self.output(" %s.errcheck = check_vlc_exception" % method)
502 self.output(' %s.__doc__ = """%s"""' % (method, comment))
505 def parse_override(self, name):
506 """Parse override definitions file.
508 It is possible to override methods definitions in classes.
511 (code, overriden_methods, docstring)
519 m=re.match('class (\S+):', l)
522 if current is not None:
523 code[current]="".join(data)
528 code[current]="".join(data)
532 for k, v in code.iteritems():
533 if v.lstrip().startswith('"""'):
534 # Starting comment. Use it as docstring.
535 dummy, docstring[k], code[k]=v.split('"""', 2)
537 # Not robust wrt. internal methods, but this works for the moment.
538 overridden_methods=dict( (k, re.findall('^\s+def\s+(\w+)', v, re.MULTILINE)) for (k, v) in code.iteritems() )
540 return code, overridden_methods, docstring
542 def fix_python_comment(self, c):
543 """Fix comment by removing first and last parameters (self and exception)
545 data=c.replace('@{', '').replace('@see', 'See').splitlines()
546 body=itertools.takewhile(lambda l: not '@param' in l and not '@return' in l, data)
547 param=[ python_param_re.sub('\\1:\\2', l) for l in itertools.ifilter(lambda l: '@param' in l, data) ]
548 ret=[ l.replace('@return', '@return:') for l in itertools.ifilter(lambda l: '@return' in l, data) ]
552 elif len(param) == 1:
555 return "\n".join(itertools.chain(body, param, ret))
557 def generate_wrappers(self, methods):
558 """Generate class wrappers for all appropriate methods.
560 @return: the set of wrapped method names
563 # Sort methods against the element they apply to.
564 elements=sorted( ( (self.type2class.get(params[0][0]), rt, met, params, c)
565 for (rt, met, params, c) in methods
566 if params and self.type2class.get(params[0][0], '_') in self.defined_classes
568 key=operator.itemgetter(0))
570 overrides, overriden_methods, docstring=self.parse_override('override.py')
572 for classname, el in itertools.groupby(elements, key=operator.itemgetter(0)):
573 self.output("""class %(name)s(object):""" % {'name': classname})
574 if classname in docstring:
575 self.output(' """%s\n """' % docstring[classname])
578 def __new__(cls, pointer=None):
579 '''Internal method used for instanciating wrappers from ctypes.
582 raise Exception("Internal method. Surely this class cannot be instanciated by itself.")
586 o=object.__new__(cls)
587 o._as_parameter_=ctypes.c_void_p(pointer)
592 '''(INTERNAL) ctypes parameter conversion method.
594 return arg._as_parameter_
595 """ % {'name': classname})
597 if classname in overrides:
598 self.output(overrides[classname])
600 prefix=self.prefixes.get(classname, '')
602 for cl, rtype, method, params, comment in el:
603 if method in blacklist:
606 name=method.replace(prefix, '').replace('libvlc_', '')
608 if name in overriden_methods.get(cl, []):
609 # Method already defined in override.py
613 params[0]=(params[0][0], 'self')
614 if params and params[-1][0] in ('libvlc_exception_t*', 'mediacontrol_Exception*'):
615 args=", ".join( p[1] for p in params[:-1] )
617 args=", ".join( p[1] for p in params )
619 self.output(" if hasattr(dll, '%s'):" % method)
620 self.output(" def %s(%s):" % (name, args))
621 self.output(' """%s\n """' % self.fix_python_comment(comment))
622 if params and params[-1][0] == 'libvlc_exception_t*':
624 self.output(" e=VLCException()")
625 self.output(" return %s(%s, e)" % (method, args))
626 elif params and params[-1][0] == 'mediacontrol_Exception*':
628 self.output(" e=MediaControlException()")
629 self.output(" return %s(%s, e)" % (method, args))
631 self.output(" return %s(%s)" % (method, args))
634 # Check for standard methods
636 # There is a count method. Generate a __len__ one.
637 self.output(""" def __len__(self):
641 elif name.endswith('item_at_index'):
642 # Indexable (and thus iterable)"
643 self.output(""" def __getitem__(self, i):
645 return %s(self, i, e)
649 for i in xrange(len(self)):
654 def process(output, list_of_includes):
655 p=Parser(list_of_includes)
659 if __name__ == '__main__':
660 opt=OptionParser(usage="""Parse VLC include files and generate bindings code.
661 %prog [options] include_file.h [...]""")
663 opt.add_option("-d", "--debug", dest="debug", action="store_true",
667 opt.add_option("-o", "--output", dest="output", action="store",
668 type="str", default="-",
669 help="Output filename")
671 (options, args) = opt.parse_args()
685 g.save(options.output)