Skip to content

Commit

Permalink
Make python3 compatible and update README.md
Browse files Browse the repository at this point in the history
  • Loading branch information
pafcu committed Dec 19, 2017
1 parent 58fa976 commit d4fd518
Show file tree
Hide file tree
Showing 3 changed files with 58 additions and 29 deletions.
34 changes: 23 additions & 11 deletions README → README.md
Original file line number Diff line number Diff line change
@@ -1,36 +1,48 @@
pymplb (PYthonMPLayerBindings) is a library that can be used to play media using an external MPlayer process.
To use pymplb you must have Python 2.6 or newer and MPlayer installed.
To use pymplb you must have Python 2.6 or newer (Python3 should also work) and MPlayer installed.
The library runs the MPlayer binary in slave mode as a subprocess and then sends slave-mode commands to the process.
Commands are mapped to class methods and properties to class properties (by default prefixed with 'p_').
Commands are mapped to class methods and properties to class properties (by default prefixed with 'p\_').
Commands are discovered at runtime and thus these bindings should automatically also support any new commands added to MPlayer in the future.
An example:

>>> import pymplb;import sys
>>> player = pymplb.MPlayer(fs=True,speed=2.0)
```
>>> import pymplb
>>> player = pymplb.MPlayer()
>>> player.loadfile('test.ogv')
>>> player.p_filename
'test.ogv'
```

All available commands and properties can be used in this way. To see a complete list you can use the python interactive help. All methods take an additional keyword argument 'pausing'. This can be one if 'pausing', 'pausing_keep', 'pausing_toggle', and 'pausing_keep_force'. These affect how playback is affected by the command.
>>> player.get_property('filename',pausing='pausing')
All available commands and properties can be used in this way. To see a complete list you can use the python interactive help. All methods take an additional keyword argument 'pausing'. This can be one of '', 'pausing', 'pausing\_keep', 'pausing\_toggle', and 'pausing\_keep\_force'. These affect how playback is affected by the command.

```
>>> player.get_property('filename', pausing='pausing')
'test.ogv'
```

See the MPlayer documentation for details on pausing.
See the MPlayer [slave-mode documentation](http://www.mplayerhq.hu/DOCS/tech/slave.txt) for details on pausing.

By default pymplb assumes that the binary "mplayer" is in your path. If this is not the case, or you want to use a differently named binary, you can do

```
>>> MPlayer2 = pymplb.make_mplayer_class(mplayer_bin='/usr/bin/mplayer')
>>> player = MPlayer2()
```

This is especially important when using the Windows OS since most programs are NOT in the path.

The reason why a new class is constructed instead of simply giving the binary path when constructing an MPlayer instance is that introspection capabilities would suffer: it would not be possible to see help for the class until an instance has been constructed. Anyway, this is a minor issue and in general you do not need to worry about this.

To prevent naming collisions between properties and methods the prefix 'p_' is by default prepended to all property names. It is, however, not impossible that future version of MPlayer could have a property called 'foo' and a command called 'p_foo', which would result in a naming conflict in pymplb. Therefore, it is also possible to change the prefix:
>>> MPlayer3 = pymplb.make_mplayer_class(method_prefix='m_',property_prefix='prop_')
To prevent naming collisions between properties and methods the prefix 'p\_' is by default prepended to all property names. It is, however, not impossible that future version of MPlayer could have a property called 'foo' and a command called 'p\_foo', which would result in a naming conflict in pymplb. Therefore, it is also possible to change the prefix:

```
>>> MPlayer3 = pymplb.make_mplayer_class(method_prefix='m_', property_prefix='prop_')
>>> player = MPlayer3()
>>> player.m_loadfile('test.ogv')
>>> player.prop_filename
'test.ogv'

```

Support the developer if you like this project:
bitcoin:1NXsx6mXPNJwnMB2oPpwAdNYGFfMG5WHTd

[![Donate using Liberapay](https://liberapay.com/assets/widgets/donate.svg)](https://liberapay.com/saparvia/donate)
36 changes: 19 additions & 17 deletions pymplb.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ def __init__(self, player_path):

def make_mplayer_class(mplayer_bin='mplayer', method_prefix='', property_prefix='p_'):
"""
Construct a MPlayer class which user mplayer_bin as the platyer binary and prepends the given prefixes to property and method names.
Construct a MPlayer class which user mplayer_bin as the player binary and prepends the given prefixes to property and method names.
Prefixes are needed because some properties and methods have the same name.
You only need to construct a new class if the default values are not suitable (i.e. mplayer is not in your path, or some new commands have been introduced that conflict with the default prefixes.
"""
Expand Down Expand Up @@ -71,11 +71,14 @@ def __init__(self, env=None, mplayer_args_d=None, **mplayer_args):
for (name, func) in self._player_methods.items():
setattr(self, name, partial(func, self.__player))

atexit.register(self.__cleanup) # Make sure subprocess is killed
atexit.register(self.close) # Make sure subprocess is killed

def __cleanup(self):
"""Method that kills the MPlayer subprocess when the application using the library exits"""
self.__player.terminate()
def close(self):
"""Method that kills the MPlayer subprocess"""
try:
self.__player.terminate()
except:
pass

@staticmethod
def _run_player(args, env=None):
Expand All @@ -86,7 +89,7 @@ def _run_player(args, env=None):
if err.errno == 2:
raise PlayerNotFoundException(args[0])
else:
raise err
raise
return player

@classmethod
Expand All @@ -101,7 +104,8 @@ def cmd(name, argtypes, obligatory, player, *args, **kwargs):
for i in range(len(args)):
if type(args[i]) != argtypes[i]:
raise TypeError('Argument %d of %s() has type %s, should be %s'%(i, name, type(args[i]).__name__, argtypes[i].__name__))
pausing = kwargs.get('pausing','pausing_keep')

pausing = kwargs.get('pausing', '')
if pausing != '':
pausing = pausing + ' '

Expand All @@ -113,20 +117,18 @@ def cmd(name, argtypes, obligatory, player, *args, **kwargs):
# Hopefully this is smart enough ...
if name.startswith('get_'):
while True:
line = player.stdout.readline()
line = player.stdout.readline().decode('utf-8')
if line == '': # no more lines
return None

if not line[:3] == 'ANS':
continue

line = str(line.decode('utf-8'))

retval = line.split('=', 2)[1].rstrip()
if retval == 'PROPERTY_UNAVAILABLE':
return None
else:
return retval

return retval

player = cls._run_player([mplayer_bin, '-input', 'cmdlist'])

Expand All @@ -144,7 +146,7 @@ def cmd(name, argtypes, obligatory, player, *args, **kwargs):
continue # Some garbage on the output (version?)

method = partial(cmd, name, argtypes, obligatory)
if len(args) == 0:
if not args:
method.__doc__ = 'Method taking no arguments'
elif len(args) == 1:
method.__doc__ = 'Method taking argument of type %s' % args[0]
Expand Down Expand Up @@ -197,20 +199,19 @@ def set_prop(name, prop_type, islist, prop_min, prop_max, self, value):
raise ValueError('ValueError: %s must be at most %s (<%s)'%(name, prop_max, value))

getattr(self, cls._method_prefix+'set_property')(name, str(value))

player = cls._run_player([mplayer_bin, '-list-properties'])
# Add each property found
for line in player.stdout:
line = str(line.decode('utf-8'))
parts = line.strip().split()
parts = line.strip().decode('utf-8').split()
if not (len(parts) == 4 or (len(parts) == 5 and parts[2] == 'list')):
continue
name = parts[0]
try:
prop_type = cls._arg_types[parts[1]]
except KeyError:
continue

if parts[2] == 'list': # Actually a list
prop_min = parts[3]
prop_max = parts[4]
Expand All @@ -230,6 +231,7 @@ def set_prop(name, prop_type, islist, prop_min, prop_max, self, value):
else:
prop_max = prop_type(prop_max)


getter = partial(get_prop, name, prop_type, islist)
setter = partial(set_prop, name, prop_type, islist, prop_min, prop_max)
setattr(cls, cls._property_prefix+name, property(getter, setter, doc='Property of type %s in range [%s, %s].'%(prop_type.__name__, prop_min, prop_max)))
Expand Down
17 changes: 16 additions & 1 deletion tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,35 +6,41 @@ def testReadProperty(self):
player = pymplb.MPlayer()
r = player.p_loop
self.assertEqual(r,-1)
player.close()

def testWriteProperty(self):
player = pymplb.MPlayer()
player.p_loop = 5
r = player.p_loop
self.assertEqual(r,5)
player.close()

def testMethod(self):
player = pymplb.MPlayer()
player.set_property('loop','1')
r = player.get_property('loop')
self.assertEqual(r,'1')
player.close()

def testListProperty(self):
player = pymplb.MPlayer()
player.loadfile('test.ogv')
r = player.p_metadata
self.assertEqual(type(r),type([]))
player.close()

def testNullProperty(self):
player = pymplb.MPlayer()
r = player.p_filename
self.assertEqual(r,None)
player.close()

def testLoadedFileProperties(self):
player = pymplb.MPlayer(fs=True)
player.loadfile('test.ogv')
r = player.p_filename
self.assertNotEqual(r,None)
player.close()

def testOtherPrefix(self):
player = pymplb.make_mplayer_class(property_prefix='prop_',method_prefix='m_')()
Expand All @@ -43,41 +49,50 @@ def testOtherPrefix(self):
r = player.m_get_property('loop')
self.assertRaises(AttributeError,lambda: player.p_loop)
self.assertRaises(AttributeError,lambda: player.get_property('loop'))
player.close()

def testMethodTooManyArgs(self):
player = pymplb.MPlayer()
self.assertRaises(TypeError,lambda: player.get_property('loop','foo'))
player.close()

def testMethodTooFewArgs(self):
player = pymplb.MPlayer()
self.assertRaises(TypeError,lambda: player.get_property())
player.close()

def testMethodType(self):
player = pymplb.MPlayer()
self.assertRaises(TypeError,lambda:player.get_property(0))
player.close()

def testSetPropertyType(self):
player = pymplb.MPlayer()
def f():
player.p_loop = '0'
self.assertRaises(TypeError,f)
player.close()

def testGetpropertyType(self):
player = pymplb.MPlayer()
r = player.p_loop
self.assertEqual(type(r),type(0))
player.close()

def testInitArgumentsDict(self):
player = pymplb.MPlayer({'fs':True,'speed':2.0})
player = pymplb.MPlayer(mplayer_args_d={'fs':True,'speed':2.0})
player.loadfile('test.ogv')
player.close()

def testInitArgumentsKw(self):
player = pymplb.MPlayer(fs=True,speed=2.0)
player.loadfile('test.ogv')
player.close()

def testPausing(self):
player = pymplb.MPlayer()
player.get_property('loop',pausing='pausing')
player.close()

if __name__ == '__main__':
unittest.main()

0 comments on commit d4fd518

Please sign in to comment.