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.
37 # DefaultDict from ASPN python cookbook
39 class DefaultDict(dict):
40 """Dictionary with a default value for unknown keys."""
41 def __init__(self, default=None, **items):
42 dict.__init__(self, **items)
43 self.default = default
45 def __getitem__(self, key):
49 ## Need copy in case self.default is something like []
50 return self.setdefault(key, copy.deepcopy(self.default))
53 return DefaultDict(self.default, **self)
55 # Methods not decorated/not referenced
57 "libvlc_exception_raise",
58 "libvlc_exception_raised",
59 "libvlc_exception_get_message",
60 "libvlc_get_vlc_instance",
62 "libvlc_media_add_option_flag",
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_video_set_deinterlace',
70 'libvlc_video_get_marquee_option_as_int',
71 'libvlc_video_get_marquee_option_as_string',
72 'libvlc_video_set_marquee_option_as_int',
73 'libvlc_video_set_marquee_option_as_string',
74 'libvlc_vlm_get_event_manager',
76 'mediacontrol_PlaylistSeq__free',
79 "libvlc_event_detach",
80 "libvlc_event_attach",
84 api_re=re.compile('VLC_PUBLIC_API\s+(\S+\s+.+?)\s*\(\s*(.+?)\s*\)')
85 param_re=re.compile('\s*(const\s*|unsigned\s*|struct\s*)?(\S+\s*\**)\s+(.+)')
86 paramlist_re=re.compile('\s*,\s*')
87 comment_re=re.compile('\\param\s+(\S+)')
88 python_param_re=re.compile('(@param\s+\S+)(.+)')
89 forward_re=re.compile('.+\(\s*(.+?)\s*\)(\s*\S+)')
90 enum_re=re.compile('typedef\s+(enum)\s*(\S+\s*)?\{\s*(.+)\s*\}\s*(\S+);')
91 special_enum_re=re.compile('^(enum)\s*(\S+\s*)?\{\s*(.+)\s*\};')
93 # Definition of parameter passing mode for types. This should not be
94 # hardcoded this way, but works alright ATM.
95 parameter_passing=DefaultDict(default=1)
96 parameter_passing['libvlc_exception_t*']=3
98 # C-type to ctypes/python type conversion.
99 # Note that enum types conversions are generated (cf convert_enum_names)
101 'libvlc_exception_t*': 'ctypes.POINTER(VLCException)',
103 'libvlc_media_player_t*': 'MediaPlayer',
104 'libvlc_instance_t*': 'Instance',
105 'libvlc_media_t*': 'Media',
106 'libvlc_log_t*': 'Log',
107 'libvlc_log_iterator_t*': 'LogIterator',
108 'libvlc_log_message_t*': 'LogMessage',
109 'libvlc_event_type_t': 'EventType',
110 'libvlc_event_manager_t*': 'EventManager',
111 'libvlc_media_discoverer_t*': 'MediaDiscoverer',
112 'libvlc_media_library_t*': 'MediaLibrary',
113 'libvlc_media_list_t*': 'MediaList',
114 'libvlc_media_list_player_t*': 'MediaListPlayer',
115 'libvlc_media_list_view_t*': 'MediaListView',
116 'libvlc_track_description_t*': 'TrackDescription',
117 'libvlc_audio_output_t*': 'AudioOutput',
119 'mediacontrol_Instance*': 'MediaControl',
120 'mediacontrol_Exception*': 'MediaControlException',
121 'mediacontrol_RGBPicture*': 'RGBPicture',
122 'mediacontrol_PlaylistSeq*': 'MediaControlPlaylistSeq',
123 'mediacontrol_Position*': 'MediaControlPosition',
124 'mediacontrol_StreamInformation*': 'MediaControlStreamInformation',
125 'WINDOWHANDLE': 'ctypes.c_ulong',
128 'void*': 'ctypes.c_void_p',
129 'short': 'ctypes.c_short',
130 'char*': 'ctypes.c_char_p',
131 'char**': 'ListPOINTER(ctypes.c_char_p)',
132 'uint32_t': 'ctypes.c_uint',
133 'float': 'ctypes.c_float',
134 'unsigned': 'ctypes.c_uint',
135 'int': 'ctypes.c_int',
136 '...': 'FIXMEva_list',
137 'libvlc_callback_t': 'FIXMEcallback',
138 'libvlc_time_t': 'ctypes.c_longlong',
141 # Defined python classes, i.e. classes for which we want to generate
142 # class wrappers around libvlc functions
161 #'MediaControlPosition',
162 #'MediaControlStreamInformation',
165 # Definition of prefixes that we can strip from method names when
166 # wrapping them into class methods
167 prefixes=dict( (v, k[:-2]) for (k, v) in typ2class.iteritems() if v in defined_classes )
168 prefixes['MediaControl']='mediacontrol_'
171 """Parse a C parameter expression.
173 It is used to parse both the type/name for methods, and type/name
174 for their parameters.
176 It returns a tuple (type, name).
179 s=s.replace('const', '')
180 if 'VLC_FORWARD' in s:
181 m=forward_re.match(s)
182 s=m.group(1)+m.group(2)
185 const, typ, name=m.groups()
186 while name.startswith('*'):
190 # K&R definition: const char * const*
192 typ=typ.replace(' ', '')
195 # K&R definition: only type
196 return s.replace(' ', ''), ''
198 def generate_header(classes=None):
199 """Generate header code.
201 f=open('header.py', 'r')
203 if 'build_date' in l:
204 print 'build_date="%s"' % time.ctime()
209 def convert_enum_names(enums):
211 for (typ, name, values, comment) in enums:
213 raise Exception('This method only handles enums')
214 pyname=re.findall('(libvlc|mediacontrol)_(.+?)(_t)?$', name)[0][1]
216 pyname=pyname.title().replace('_', '')
218 pyname=pyname.capitalize()
222 def generate_enums(enums):
223 for (typ, name, values, comment) in enums:
225 raise Exception('This method only handles enums')
226 pyname=typ2class[name]
228 print "class %s(ctypes.c_uint):" % pyname
229 print ' """%s\n """' % comment
232 # Convert symbol names
236 # Single character. Some symbols use 1_1, 5_1, etc.
237 n="_".join( k.split('_')[-2:] )
238 if re.match('^[0-9]', n):
239 # Cannot start an identifier with a number
244 print " %s=%s" % (conv[k], v)
248 print " %s: '%s'," % (v, conv[k])
253 return ".".join((self.__class__.__module__, self.__class__.__name__, self._names[self.value]))
256 def parse_typedef(name):
257 """Parse include file for typedef expressions.
259 This generates a tuple for each typedef:
260 (type, name, value_list, comment)
261 with type == 'enum' (for the moment) and value_list being a list of (name, value)
262 Note that values are string, since this is intended for code generation.
267 # Note: lstrip() should not be necessary, but there is 1 badly
268 # formatted comment in vlc1.0.0 includes
269 if l.lstrip().startswith('/**'):
272 elif l.startswith(' * '):
273 comment = comment + l[3:]
277 if l.startswith('/*') or l.endswith('*/'):
280 if (l.startswith('typedef enum') or l.startswith('enum')) and not l.endswith(';'):
281 # Multiline definition. Accumulate until end of definition
285 accumulator=" ".join( (accumulator, l) )
294 (typ, dummy, data, name)=m.groups()
295 for i, l in enumerate(paramlist_re.split(data)):
297 if l.startswith('/*'):
300 # A value was specified. Use it.
301 values.append(re.split('\s*=\s*', l))
304 values.append( (l, str(i)) )
305 comment=comment.replace('@{', '').replace('@see', 'See').replace('\ingroup', '')
306 yield (typ, name, values, comment)
310 # Special case, used only for libvlc_events.h
311 m=special_enum_re.match(l)
314 (typ, name, data)=m.groups()
315 for i, l in enumerate(paramlist_re.split(data)):
317 if l.startswith('/*') or l.startswith('#'):
320 # A value was specified. Use it.
321 values.append(re.split('\s*=\s*', l))
324 values.append( (l, str(i)) )
325 comment=comment.replace('@{', '').replace('@see', 'See').replace('\ingroup', '')
326 yield (typ, name, values, comment)
330 def parse_include(name):
331 """Parse include file.
333 This generates a tuple for each function:
334 (return_type, method_name, parameter_list, comment)
335 with parameter_list being a list of tuples (parameter_type, parameter_name).
341 # Note: lstrip() should not be necessary, but there is 1 badly
342 # formatted comment in vlc1.0.0 includes
343 if l.lstrip().startswith('/**'):
346 elif l.startswith(' * '):
347 comment = comment + l[3:]
353 accumulator=" ".join( (accumulator, l) )
358 elif l.startswith('VLC_PUBLIC_API') and not l.endswith(');'):
359 # Multiline definition. Accumulate until end of definition
365 (ret, param)=m.groups()
367 rtype, method=parse_param(ret)
370 for p in paramlist_re.split(param):
371 params.append( parse_param(p) )
373 if len(params) == 1 and params[0][0] == 'void':
374 # Empty parameter list
377 if list(p for p in params if not p[1]):
378 # Empty parameter names. Have to poke into comment.
379 names=comment_re.findall(comment)
380 if len(names) < len(params):
381 # Bad description: all parameters are not specified.
382 # Generate default parameter names
383 badnames=[ "param%d" % i for i in xrange(len(params)) ]
384 # Put in the existing ones
385 for (i, p) in enumerate(names):
388 print "### Error ###"
389 print "### Cannot get parameter names from comment for %s: %s" % (method, comment.replace("\n", ' '))
390 # Note: this was previously
391 # raise Exception("Cannot get parameter names from comment for %s: %s" % (method, comment))
392 # but it prevented code generation for a minor detail (some bad descriptions).
393 params=[ (p[0], names[i]) for (i, p) in enumerate(params) ]
395 for typ, name in params:
396 if not typ in typ2class:
397 raise Exception("No conversion for %s (from %s:%s)" % (typ, method, name))
399 # Transform Doxygen syntax into epydoc syntax
400 comment=comment.replace('\\param', '@param').replace('\\return', '@return')
403 print '********************'
406 print "%s (%s)" % (method, rtype)
407 for typ, name in params:
408 print " %s (%s)" % (name, typ)
409 print '********************'
416 def output_ctypes(rtype, method, params, comment):
417 """Output ctypes decorator for the given method.
419 if method in blacklist:
424 print "prototype=ctypes.CFUNCTYPE(%s, %s)" % (typ2class.get(rtype, 'FIXME_%s' % rtype),
425 ",".join( typ2class[p[0]] for p in params ))
427 print "prototype=ctypes.CFUNCTYPE(%s)" % typ2class.get(rtype, 'FIXME_%s' % rtype)
431 flags='paramflags= tuple()'
432 elif len(params) == 1:
433 flags="paramflags=( (%d, ), )" % parameter_passing[params[0][0]]
435 flags="paramflags=%s" % ",".join( '(%d,)' % parameter_passing[p[0]] for p in params )
437 print '%s = prototype( ("%s", dll), paramflags )' % (method, method)
439 # A VLCException is present. Process it.
440 print "%s.errcheck = check_vlc_exception" % method
441 print '%s.__doc__ = """%s"""' % (method, comment)
444 def parse_override(name):
445 """Parse override definitions file.
447 It is possible to override methods definitions in classes.
450 (code, overriden_methods, docstring)
458 m=re.match('class (\S+):', l)
461 if current is not None:
462 code[current]="".join(data)
467 code[current]="".join(data)
471 for k, v in code.iteritems():
472 if v.lstrip().startswith('"""'):
473 # Starting comment. Use it as docstring.
474 dummy, docstring[k], code[k]=v.split('"""', 2)
476 # Not robust wrt. internal methods, but this works for the moment.
477 overridden_methods=dict( (k, re.findall('^\s+def\s+(\w+)', v, re.MULTILINE)) for (k, v) in code.iteritems() )
479 return code, overridden_methods, docstring
481 def fix_python_comment(c):
482 """Fix comment by removing first and last parameters (self and exception)
484 data=c.replace('@{', '').replace('@see', 'See').splitlines()
485 body=itertools.takewhile(lambda l: not '@param' in l and not '@return' in l, data)
486 param=[ python_param_re.sub('\\1:\\2', l) for l in itertools.ifilter(lambda l: '@param' in l, data) ]
487 ret=[ l.replace('@return', '@return:') for l in itertools.ifilter(lambda l: '@return' in l, data) ]
491 elif len(param) == 1:
494 return "\n".join(itertools.chain(body, param, ret))
496 def generate_wrappers(methods):
497 """Generate class wrappers for all appropriate methods.
499 @return: the set of wrapped method names
502 # Sort methods against the element they apply to.
503 elements=sorted( ( (typ2class.get(params[0][0]), rt, met, params, c)
504 for (rt, met, params, c) in methods
505 if params and typ2class.get(params[0][0], '_') in defined_classes
507 key=operator.itemgetter(0))
509 overrides, overriden_methods, docstring=parse_override('override.py')
511 for classname, el in itertools.groupby(elements, key=operator.itemgetter(0)):
512 print """class %(name)s(object):""" % {'name': classname}
513 if classname in docstring:
514 print ' """%s\n """' % docstring[classname]
517 def __new__(cls, pointer=None):
518 '''Internal method used for instanciating wrappers from ctypes.
521 raise Exception("Internal method. You should instanciate objects through other class methods (probably named 'new' or ending with 'new')")
525 o=object.__new__(cls)
526 o._as_parameter_=ctypes.c_void_p(pointer)
531 '''(INTERNAL) ctypes parameter conversion method.
533 return arg._as_parameter_
534 """ % {'name': classname}
536 if classname in overrides:
537 print overrides[classname]
539 prefix=prefixes.get(classname, '')
541 for cl, rtype, method, params, comment in el:
542 if method in blacklist:
545 name=method.replace(prefix, '').replace('libvlc_', '')
547 if name in overriden_methods.get(cl, []):
548 # Method already defined in override.py
552 params[0]=(params[0][0], 'self')
553 if params and params[-1][0] in ('libvlc_exception_t*', 'mediacontrol_Exception*'):
554 args=", ".join( p[1] for p in params[:-1] )
556 args=", ".join( p[1] for p in params )
558 print " def %s(%s):" % (name, args)
559 print ' """%s\n """' % fix_python_comment(comment)
560 if params and params[-1][0] == 'libvlc_exception_t*':
562 print " e=VLCException()"
563 print " return %s(%s, e)" % (method, args)
564 elif params and params[-1][0] == 'mediacontrol_Exception*':
566 print " e=MediaControlException()"
567 print " return %s(%s, e)" % (method, args)
569 print " return %s(%s)" % (method, args)
572 # Check for standard methods
574 # There is a count method. Generate a __len__ one.
575 print " def __len__(self):"
576 print " e=VLCException()"
577 print " return %s(self, e)" % method
579 elif name.endswith('item_at_index'):
580 # Indexable (and thus iterable)"
581 print " def __getitem__(self, i):"
582 print " e=VLCException()"
583 print " return %s(self, i, e)" % method
585 print " def __iter__(self):"
586 print " e=VLCException()"
587 print " for i in xrange(len(self)):"
588 print " yield self[i]"
593 if __name__ == '__main__':
595 for name in sys.argv[1:]:
596 enums.extend(list(parse_typedef(name)))
597 # Generate python names for enums
598 typ2class.update(convert_enum_names(enums))
601 for name in sys.argv[1:]:
602 methods.extend(list(parse_include(name)))
607 generate_enums(enums)
608 wrapped=generate_wrappers(methods)
612 all=set( t[1] for t in methods )
613 not_wrapped=all.difference(wrapped)
614 print "# Not wrapped methods:"
615 for m in not_wrapped: