Commit f83867bb authored by Olivier Aubert's avatar Olivier Aubert

New python bindings, using ctypes, automatically generated from include files.

parent c3f18518
MODULE_NAME=vlc.py
all: $(MODULE_NAME)
$(MODULE_NAME): generate.py header.py override.py ../../include/vlc/*.h
./generate.py ../../include/vlc/*.h > $@
doc: $(MODULE_NAME)
-epydoc -v -o doc $<
clean:
-$(RM) $(MODULE_NAME)
* Python ctypes-based bindings
The bindings use ctypes to directly call the libvlc dynamic lib, and
the code is generated from the include files defining the public API.
** Building
To generate the vlc.py module and its documentation, use
make
Documentation building needs epydoc.
** Layout
The module offers two ways of accessing the API - a raw access to all
exported methods, and more convenient wrapper classes :
- Raw access: methods are available as attributes of the vlc
module. Use their docstring (introspective shells like ipython are
your friends) to explore them.
- Wrapper classes: most major structures of the libvlc API (Instance,
Media, MediaPlayer, etc) are wrapped as classes, with shorter method
names.
** Using the module
On win32, the simplest way is to put the vlc.py file in the same
directory as the libvlc.dll file (standard location:
c:\Program Files\VideoLAN\VLC ).
- Using raw access:
>>> import vlc
>>> vlc.libvlc_get_version()
'1.0.0 Goldeneye'
>>> e=vlc.VLCException()
>>> i=vlc.libvlc_new(0, [], e)
>>> i
<vlc.Instance object at 0x8384a4c>
>>> vlc.libvlc_audio_get_volume(i,e)
50
- Using wrapper classes:
>>> i=vlc.Instance.new()
>>> i.audio_get_volume()
50
>>> m=i.media_new('/tmp/foo.avi')
>>> m.get_mrl()
'/tmp/foo.avi'
* Check cross-platform (win32, MacOSX) support
* Investigate memory management
* Find how to properly define enums
* Autogenerate enums from include files
* Implement event callbacks
* Write a test suite
#! /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
#! /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
class Instance:
@staticmethod
def new(*p):
"""Create a new Instance.
"""
e=VLCException()
return libvlc_new(len(p), p, e)
class MediaControl:
@staticmethod
def new(*p):
"""Create a new MediaControl
"""
e=MediaControlException()
return mediacontrol_new(len(p), p, e)
@staticmethod
def new_from_instance(i):
"""Create a new MediaControl from an existing Instance.
"""
e=MediaControlException()
return mediacontrol_new_from_instance(i, e)
class MediaList:
def __len__(self):
e=VLCException()
return libvlc_media_list_count(self, e)
def __getitem__(self, i):
e=VLCException()
return libvlc_media_list_item_at_index(self, i, e)
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment