Author: duncan
Date: Sun Jan 6 16:45:25 2008
New Revision: 10263
Log:
[ 1865008 ] replacement MPD plugin
New plug-in from Graham Billiau added
Added:
branches/rel-1-7/freevo/src/audio/plugins/mpd_playlist.py (contents, props
changed)
branches/rel-1-7/freevo/src/audio/plugins/mpd_status.py (contents, props
changed)
branches/rel-1-7/freevo/src/audio/plugins/mpdclient2.py (contents, props
changed)
branches/rel-1/freevo/src/audio/plugins/mpd_playlist.py (contents, props
changed)
branches/rel-1/freevo/src/audio/plugins/mpd_status.py (contents, props
changed)
branches/rel-1/freevo/src/audio/plugins/mpdclient2.py (contents, props
changed)
Modified:
branches/rel-1-7/freevo/ChangeLog
branches/rel-1/freevo/ChangeLog
Modified: branches/rel-1-7/freevo/ChangeLog
==============================================================================
--- branches/rel-1-7/freevo/ChangeLog (original)
+++ branches/rel-1-7/freevo/ChangeLog Sun Jan 6 16:45:25 2008
@@ -16,6 +16,7 @@
== Release 1.7.6 (2008-??-??) ==
--------------------------------
+ * New audio MPD plug-in replacement (F#1865008)
* New DirecTV plug-in to send to DirecTV receivers (F#1858775)
* Updated cdbackup plug-in with CD_RIP_FMT to set the encoding format
(F#1857460)
* Updated recordings manager plug-in to add thumbnail support (F#1857397)
Added: branches/rel-1-7/freevo/src/audio/plugins/mpd_playlist.py
==============================================================================
--- (empty file)
+++ branches/rel-1-7/freevo/src/audio/plugins/mpd_playlist.py Sun Jan 6
16:45:25 2008
@@ -0,0 +1,96 @@
+# This is a replacement for the previous mpd plugin
+# By Graham Billiau <[EMAIL PROTECTED]> (DrLizAu's code monkey)
+# This code is released under the GPL
+
+# There are two parts to this plugin, an addition to the audio item menu to
queue the item in mpd's playlist and a
+# status display in the audio menu
+
+# TODO:
+# add code to cope if the mpd server crashes
+# add code to enqueue an entire directory & sub-directories
+# add code to enqueue an existing playlist
+# modify code to support localisation
+# investigate having the mpd connection managed by another class
+
+# Advantages of this over the previous mpd plugin:
+# The code is a lot cleaner and more robust.
+# Faster (talks to mpd directly, rather than calling other programs).
+# Allows you to modify the playlist within freevo.
+
+# This only works if the music to be played is part of the filesystem avalible
to mpd and also avalible to freevo
+# so both on the same computer, or exported using samba or nfs
+
+# This code uses the following options in local_conf.py:
+# MPD_SERVER_HOST='localhost' # the host running the mpd server
+# MPD_SERVER_PORT='6600' # the port the server is listening on
+# MPD_SERVER_PASSWORD=None # the password to access the mpd server
+# MPD_MUSIC_BASE_PATH='/mnt/music/' # the local path to where the music
is stored, must have trailing slash
+# MPD_EXTERNAL_CLIENT='/usr/bin/pympd' # the location of the external
client you want to use, or None
+# MPD_EXTERNAL_CLIENT_ARGS='' # arguments to be passed to the external
client, or None, obsolete
+
+# This is the mpd playlist plugin.
+# using this you can add the currently selected song to the mpd playlist
+
+import plugin
+import config
+
+import mpdclient2
+
+class PluginInterface (plugin.ItemPlugin):
+ """This plugin adds a 'enqueue in MPD' option to audio files"""
+
+
+ def __init__(self):
+ """open the connection to the mpd server and keep it alive
+ assume that the plugin is loaded once, then kept in memory"""
+ plugin.ItemPlugin.__init__(self)
+ self.conn = mpdclient2.Thread_MPD_Connection(config.MPD_SERVER_HOST,
config.MPD_SERVER_PORT, True,
+ config.MPD_SERVER_PASSWORD)
+
+ # ensure there is a trailing slash on config.MPD_MUSIC_BASE_PATH
+ if not config.MPD_MUSIC_BASE_PATH.endswith('/'):
+ config.MPD_MUSIC_BASE_PATH = config.MPD_MUSIC_BASE_PATH + '/'
+
+
+ def config(self):
+ return [ ('MPD_SERVER_HOST', 'localhost', 'the host running the mpd
server'),
+ ('MPD_SERVER_PORT', 6600, 'the port the server is listening
on'),
+ ('MPD_SERVER_PASSWORD', None, 'the password to access the mpd
server'),
+ ('MPD_MUSIC_BASE_PATH', '/mnt/music/', 'the local path to
where the music is stored') ]
+
+
+ def shutdown(self):
+ """close the connection to the mpd server"""
+ try:
+ # this always throws EOFError, even though there isn't really an
error
+ self.conn.close()
+ except EOFError:
+ pass
+ return
+
+
+ def actions (self, item):
+ """add the option for all music that is in the mpd library"""
+ self.item = item
+ # check to see if item is a FileItem
+ if (item.type == 'file'):
+ # check to see if item is in mpd's library
+ if (item.filename.startswith(config.MPD_MUSIC_BASE_PATH)):
+ # can query mpd to see if the file is in it's ibrary
+ return [ (self.enqueue_file, 'Add to MPD playlist') ]
+ #elif (item.type == 'dir'):
+ #elif (item.type == 'playlist'):
+ return []
+
+
+ def enqueue_file(self, arg=None, menuw=None):
+ self.conn.add(self.item.filename[len(config.MPD_MUSIC_BASE_PATH):])
+ if menuw is not None:
+ menuw.delete_menu(arg, menuw)
+ return
+
+
+ #def enqueue_dir(self, arg=None, menuw=None):
+
+
+ #def enqueue_playlist(self, arg=None, menuw=None):
\ No newline at end of file
Added: branches/rel-1-7/freevo/src/audio/plugins/mpd_status.py
==============================================================================
--- (empty file)
+++ branches/rel-1-7/freevo/src/audio/plugins/mpd_status.py Sun Jan 6
16:45:25 2008
@@ -0,0 +1,242 @@
+# This is a replacement for the previous mpd plugin
+# By Graham Billiau <[EMAIL PROTECTED]> (DrLizAu's code monkey)
+# This code is released under the GPL
+
+# There are two parts to this plugin, an addition to the audio item menu to
queue the item in mpd's playlist and a
+# status display in the audio menu
+
+# Advantages of this over the previous mpd plugin:
+# The code is a lot cleaner and more robust.
+# Faster (talks to mpd directly, rather than calling other programs).
+# Allows you to modify the playlist within freevo.
+
+# TODO
+# enable localisation
+# investigate having the mpd connection managed by another class
+# pretty GUI
+# have status/everything update periodicly
+# add a playlist browser
+# code to handle when the mpd server is down
+# show the currently playing song in the status view
+
+# This code uses the following options in local_conf.py:
+# MPD_SERVER_HOST='localhost' # the host running the mpd server
+# MPD_SERVER_PORT='6600' # the port the server is listening on
+# MPD_SERVER_PASSWORD=None # the password to access the mpd server
+# MPD_MUSIC_BASE_PATH='/mnt/music' # the local path to where the music is
stored
+# MPD_EXTERNAL_CLIENT='/usr/bin/pympd' # the location of the external
client you want to use, or None
+# MPD_EXTERNAL_CLIENT_ARGS='' # arguments to be passed to the external
client, or None, obsolete
+
+# This is the status display.
+# using this you can see the currently playing track, play, pause, stop, skip
forward, skip back, toggle repeat, toggle
+# shuffle, clear playlist, open external playlist editor, change volume
+
+import plugin
+import config
+from gui import PopupBox
+import menu
+import childapp
+
+import os
+import sys
+
+import mpdclient2
+
+class PluginInterface(plugin.MainMenuPlugin):
+ """This creates a new item in the audio menu
+ Which allows you to view and modify the MPD settings"""
+
+ def __init__(self):
+ """ initilise the plugin"""
+ plugin.MainMenuPlugin.__init__(self)
+ self.show_item = menu.MenuItem('MPD status', action=self.show_menu)
+ self.show_item.type = 'audio'
+ plugin.register(self, 'audio.MPD_status')
+ # connect to the server
+ self.conn = mpdclient2.Thread_MPD_Connection(config.MPD_SERVER_HOST,
config.MPD_SERVER_PORT, True,
+ config.MPD_SERVER_PASSWORD)
+
+ # items to go in the mpd menu
+ self.item_play = menu.MenuItem('play', self.mpd_play)#, parent=self)
+ self.item_status = menu.MenuItem('status', self.mpd_status)#,
parent=self)
+ self.item_pause = menu.MenuItem('pause', self.mpd_pause)#, parent=self)
+ self.item_stop = menu.MenuItem('stop', self.mpd_stop)#, parent=self)
+ self.item_next = menu.MenuItem('next track', self.mpd_next)#,
parent=self)
+ self.item_previous = menu.MenuItem('previous track',
self.mpd_previous)#, parent=self)
+ self.item_clear = menu.MenuItem('clear playlist', self.mpd_clear)#,
parent=self)
+ self.item_shuffle = menu.MenuItem('shuffle playlist',
self.mpd_shuffle)#, parent=self)
+ self.item_random = menu.MenuItem('toggle random',
self.mpd_toggle_random)#, parent=self)
+ self.item_repeat = menu.MenuItem('toggle repeat',
self.mpd_toggle_repeat)#, parent=self)
+ self.item_extern = menu.MenuItem('open external mpd client',
self.start_external)#, parent=self)
+
+
+
+ def shutdown(self):
+ """close the connection to the MPD server"""
+ try:
+ self.conn.close()
+ except EOFError:
+ # this always happens
+ pass
+
+
+ def config(self):
+ """returns the config variables used by this plugin"""
+ return [ ('MPD_SERVER_HOST', 'localhost', 'the host running the mpd
server'),
+ ('MPD_SERVER_PORT', 6600, 'the port the server is listening
on'),
+ ('MPD_SERVER_PASSWORD', None, 'the password to access the mpd
server'),
+ ('MPD_EXTERNAL_CLIENT', None,'the location of the external
client you want to use') ]
+ #('MPD_EXTERNAL_CLIENT_ARGS', '','arguments to be passed to
the external client') ]
+
+
+ def items(self, parent):
+ """returns the options to add to the main menu"""
+ return [ self.show_item ]
+
+
+ def actions(self):
+ """extra options for this menu"""
+ return [ ]
+
+
+ def create_menu(self):
+ """this creates the menu"""
+
+ menu_items = []
+ stat = self.conn.status()
+
+ # populate the menu
+ menu_items.append(self.item_status)
+ if (stat['state'] == 'play'):
+ menu_items.append(self.item_pause)
+ menu_items.append(self.item_stop)
+ elif (stat['state'] == 'pause'):
+ menu_items.append(self.item_play)
+ menu_items.append(self.item_stop)
+ else:
+ menu_items.append(self.item_play)
+ menu_items.append(self.item_next)
+ menu_items.append(self.item_previous)
+ menu_items.append(self.item_repeat)
+ menu_items.append(self.item_random)
+ menu_items.append(self.item_shuffle)
+ menu_items.append(self.item_clear)
+ if not (config.MPD_EXTERNAL_CLIENT is None or
config.MPD_EXTERNAL_CLIENT == ''):
+ menu_items.append(self.item_extern)
+
+ return menu.Menu('MPD status', menu_items,
reload_func=self.create_menu)
+
+
+ def show_menu(self, arg=None, menuw=None):
+ """this displays the menu"""
+
+ # display the menu
+ mpd_menu = self.create_menu()
+ menuw.pushmenu(mpd_menu)
+ menuw.refresh()
+
+
+ def mpd_play(self, arg=None, menuw=None):
+ """send the play command to the mpd server"""
+ self.conn.play()
+ # force the menu to reload
+ menuw.delete_menu()
+ self.show_menu(arg, menuw)
+
+
+ def mpd_next(self, arg=None, menuw=None):
+ """send the play command to the mpd server"""
+ self.conn.next()
+
+
+ def mpd_previous(self, arg=None, menuw=None):
+ """send the play command to the mpd server"""
+ self.conn.previous()
+
+
+ def mpd_stop(self, arg=None, menuw=None):
+ """send the stop command to the mpd server"""
+ self.conn.stop()
+ # force the menu to reload
+ menuw.delete_menu()
+ self.show_menu(arg, menuw)
+
+
+ def mpd_pause(self, arg=None, menuw=None):
+ """send the pause command to the mpd server"""
+ self.conn.pause(1)
+ # force the menu to reload
+ menuw.delete_menu()
+ self.show_menu(arg, menuw)
+
+
+ def mpd_shuffle(self, arg=None, menuw=None):
+ """send the shuffle command to the mpd server"""
+ self.conn.shuffle()
+
+
+ def mpd_toggle_random(self, arg=None, menuw=None):
+ """toggle random on/off to the mpd server"""
+ stat = self.conn.status()
+ if (stat['random'] == '0'):
+ self.conn.random(1)
+ else:
+ self.conn.random(0)
+
+
+ def mpd_toggle_repeat(self, arg=None, menuw=None):
+ """toggle repeat on/off to the mpd server"""
+ stat = self.conn.status()
+ if (stat['repeat'] == '0'):
+ self.conn.repeat(1)
+ else:
+ self.conn.repeat(0)
+
+
+ def mpd_clear(self, arg=None, menuw=None):
+ """send the clear command to the mpd server"""
+ self.conn.clear()
+
+
+ def start_external(self, arg=None, menuw=None):
+ """start the external browser in the config file"""
+ if (config.MPD_EXTERNAL_CLIENT is None or config.MPD_EXTERNAL_CLIENT
== ''):
+ return
+
+ #if (config.MPD_EXTERNAL_CLIENT_ARGS is None or
config.MPD_EXTERNAL_CLIENT_ARGS == ''):
+ #args = None
+ #else:
+ #args = config.MPD_EXTERNAL_CLIENT_ARGS.split()
+
+ try:
+ #os,spawnv(os.P_NOWAIT, config.MPD_EXTERNAL_CLIENT, args)
+ childapp.ChildApp(config.MPD_EXTERNAL_CLIENT)
+ except:
+ text = "Error starting external client\n%s\n%s" %sys.exc_info()[:2]
+ pop = PopupBox(text)
+ pop.show()
+
+
+ def mpd_status(self, arg=None, menuw=None):
+ """bring up a dialog showing mpd's current status"""
+ stat = self.conn.status()
+
+ text = "status: %s\n" %(stat['state'])
+ if (stat['state'] != 'stop'):
+ # how do i get the song name?
+ text += "track: %s\\%s\n" %(int(stat['song']) + 1,
stat['playlistlength'])
+ text += "time: %s\n" %(stat['time'])
+ if (stat['repeat'] == '1'):
+ text += "repeat: on\n"
+ else:
+ text += "repeat: off\n"
+ if (stat['random'] == '1'):
+ text += "random: on\n"
+ else:
+ text += "random: off\n"
+ text += "volume: %s" %(stat['volume'])
+
+ pop = PopupBox(text)
+ pop.show()
+
+
\ No newline at end of file
Added: branches/rel-1-7/freevo/src/audio/plugins/mpdclient2.py
==============================================================================
--- (empty file)
+++ branches/rel-1-7/freevo/src/audio/plugins/mpdclient2.py Sun Jan 6
16:45:25 2008
@@ -0,0 +1,370 @@
+#!/usr/bin/env python
+
+# py-libmpdclient2 is written by Nick Welch <[EMAIL PROTECTED]>, 2005.
+#
+# This software is released to the public
+# domain and is provided AS IS, with NO WARRANTY.
+
+import socket
+from time import sleep
+import thread
+
+# a line is either:
+#
+# key:val pair
+# OK
+# ACK
+
+class socket_talker(object):
+
+ def __init__(self, host, port):
+ self.host = host
+ self.port = port
+ self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
+ self.sock.connect((host, port))
+ self.file = self.sock.makefile("rb+")
+ self.current_line = ''
+ self.ack = ''
+ self.done = True
+
+ # this SUCKS
+
+ def get_line(self):
+ if not self.current_line:
+ self.current_line = self.file.readline().rstrip("\n")
+ if not self.current_line:
+ raise EOFError
+ if self.current_line == "OK" or self.current_line.startswith("ACK"):
+ self.done = True
+ return self.current_line
+
+ def putline(self, line):
+ self.file.write("%s\n" % line)
+ self.file.flush()
+ self.done = False
+
+ def get_pair(self):
+ line = self.get_line()
+ self.ack = ''
+
+ if self.done:
+ if line.startswith("ACK"):
+ self.ack = line.split(None, 1)[1]
+ return ()
+
+ pair = line.split(": ", 1)
+ if len(pair) != 2:
+ raise RuntimeError("bogus response: ``%s''" % line)
+
+ return pair
+
+ZERO = 0
+ONE = 1
+MANY = 2
+
+plitem_delim = ["file", "directory", "playlist"]
+
+commands = {
+ # (name, nargs): (format string, nresults, results_type_name,
delimiter_key)
+
+ # delimiter key is for commands that return multiple results. we use this
+ # string to detect the beginning of a new result object.
+
+ # if results_type_name is empty, the result object's .type will be set to
+ # the key of the first key/val pair in it; otherwise, it will be set to
+ # results_type_name.
+
+ ("kill", 0): ('%s', ZERO, '', []),
+ ("outputs", 0): ('%s', MANY, 'outputs', ['outputid']),
+ ("clear", 0): ('%s', ZERO, '', []),
+ ("currentsong", 0): ('%s', ONE, '', []),
+ ("shuffle", 0): ('%s', ZERO, '', []),
+ ("next", 0): ('%s', ZERO, '', []),
+ ("previous", 0): ('%s', ZERO, '', []),
+ ("stop", 0): ('%s', ZERO, '', []),
+ ("clearerror", 0): ('%s', ZERO, '', []),
+ ("close", 0): ('%s', ZERO, '', []),
+ ("commands", 0): ('%s', MANY, 'commands', ['command']),
+ ("notcommands", 0): ('%s', MANY, 'notcommands', ['command']),
+ ("ping", 0): ('%s', ZERO, '', []),
+ ("stats", 0): ('%s', ONE, 'stats', []),
+ ("status", 0): ('%s', ONE, 'status', []),
+ ("play", 0): ('%s', ZERO, '', []),
+ ("playlistinfo", 0): ('%s', MANY, '', plitem_delim),
+ ("playlistid", 0): ('%s', MANY, '', plitem_delim),
+ ("lsinfo", 0): ('%s', MANY, '', plitem_delim),
+ ("update", 0): ('%s', ZERO, '', []),
+ ("listall", 0): ('%s', MANY, '', plitem_delim),
+ ("listallinfo", 0): ('%s', MANY, '', plitem_delim),
+
+ ("disableoutput", 1): ("%s %d", ZERO, '', []),
+ ("enableoutput", 1): ("%s %d", ZERO, '', []),
+ ("delete", 1): ('%s %d', ZERO, '', []), # <int song>
+ ("deleteid", 1): ('%s %d', ZERO, '', []), # <int songid>
+ ("playlistinfo", 1): ('%s %d', MANY, '', plitem_delim), # <int song>
+ ("playlistid", 1): ('%s %d', MANY, '', plitem_delim), # <int songid>
+ ("crossfade", 1): ('%s %d', ZERO, '', []), # <int seconds>
+ ("play", 1): ('%s %d', ZERO, '', []), # <int song>
+ ("playid", 1): ('%s %d', ZERO, '', []), # <int songid>
+ ("random", 1): ('%s %d', ZERO, '', []), # <int state>
+ ("repeat", 1): ('%s %d', ZERO, '', []), # <int state>
+ ("setvol", 1): ('%s %d', ZERO, '', []), # <int vol>
+ ("plchanges", 1): ('%s %d', MANY, '', plitem_delim), # <playlist
version>
+ ("pause", 1): ('%s %d', ZERO, '', []), # <bool pause>
+
+ ("update", 1): ('%s "%s"', ONE, 'update', []), # <string path>
+ ("listall", 1): ('%s "%s"', MANY, '', plitem_delim), # <string path>
+ ("listallinfo", 1): ('%s "%s"', MANY, '', plitem_delim), # <string path>
+ ("lsinfo", 1): ('%s "%s"', MANY, '', plitem_delim), # <string
directory>
+ ("add", 1): ('%s "%s"', ZERO, '', []), # <string>
+ ("load", 1): ('%s "%s"', ZERO, '', []), # <string name>
+ ("rm", 1): ('%s "%s"', ZERO, '', []), # <string name>
+ ("save", 1): ('%s "%s"', ZERO, '', []), # <string playlist name>
+ ("password", 1): ('%s "%s"', ZERO, '', []), # <string password>
+
+ ("move", 2): ("%s %d %d", ZERO, '', []), # <int from> <int to>
+ ("moveid", 2): ("%s %d %d", ZERO, '', []), # <int songid from> <int to>
+ ("swap", 2): ("%s %d %d", ZERO, '', []), # <int song1> <int song2>
+ ("swapid", 2): ("%s %d %d", ZERO, '', []), # <int songid1> <int songid2>
+ ("seek", 2): ("%s %d %d", ZERO, '', []), # <int song> <int time>
+ ("seekid", 2): ("%s %d %d", ZERO, '', []), # <int songid> <int time>
+
+ # <string type> <string what>
+ ("find", 2): ('%s "%s" "%s"', MANY, '', plitem_delim),
+
+ # <string type> <string what>
+ ("search", 2): ('%s "%s" "%s"', MANY, '', plitem_delim),
+
+ # list <metadata arg1> [<metadata arg2> <search term>]
+
+ # <metadata arg1>
+ ("list", 1): ('%s "%s"', MANY, '', plitem_delim),
+
+ # <metadata arg1> <metadata arg2> <search term>
+ ("list", 3): ('%s "%s" "%s" "%s"', MANY, '', plitem_delim),
+}
+
+def is_command(cmd):
+ return cmd in [ k[0] for k in commands.keys() ]
+
+def escape(text):
+ # join/split is faster than replace
+ text = '\\\\'.join(text.split('\\')) # \ -> \\
+ text = '\\"'.join(text.split('"')) # " -> \"
+ return text
+
+def get_command(cmd, args):
+ try:
+ return commands[(cmd, len(args))]
+ except KeyError:
+ raise RuntimeError("no such command: %s (%d args)" % (cmd, len(args)))
+
+def send_command(talker, cmd, args):
+ args = list(args[:])
+ for i, arg in enumerate(args):
+ if not isinstance(arg, int):
+ args[i] = escape(str(arg))
+ format = get_command(cmd, args)[0]
+ talker.putline(format % tuple([cmd] + list(args)))
+
+class sender_n_fetcher(object):
+ def __init__(self, sender, fetcher):
+ self.sender = sender
+ self.fetcher = fetcher
+ self.iterate = False
+
+ def __getattr__(self, cmd):
+ return lambda *args: self.send_n_fetch(cmd, args)
+
+ def send_n_fetch(self, cmd, args):
+ getattr(self.sender, cmd)(*args)
+ junk, howmany, type, keywords = get_command(cmd, args)
+
+ if howmany == ZERO:
+ self.fetcher.clear()
+ return
+
+ if howmany == ONE:
+ return self.fetcher.one_object(keywords, type)
+
+ assert howmany == MANY
+ result = self.fetcher.all_objects(keywords, type)
+
+ if not self.iterate:
+ result = list(result)
+ self.fetcher.clear()
+ return result
+
+ # stupid hack because you apparently can't return non-None and yield
+ # within the same function
+ def yield_then_clear(it):
+ for x in it:
+ yield x
+ self.fetcher.clear()
+ return yield_then_clear(result)
+
+
+class command_sender(object):
+ def __init__(self, talker):
+ self.talker = talker
+ def __getattr__(self, cmd):
+ return lambda *args: send_command(self.talker, cmd, args)
+
+class response_fetcher(object):
+ def __init__(self, talker):
+ self.talker = talker
+ self.converters = {}
+
+ def clear(self):
+ while not self.talker.done:
+ self.talker.current_line = ''
+ self.talker.get_line()
+ self.talker.current_line = ''
+
+ def one_object(self, keywords, type):
+ # if type isn't empty, then the object's type is set to it. otherwise
+ # the type is set to the key of the first key/val pair.
+
+ # keywords lists the keys that indicate a new object -- like for the
+ # 'outputs' command, keywords would be ['outputid'].
+
+ entity = dictobj()
+ if type:
+ entity['type'] = type
+
+ while not self.talker.done:
+ self.talker.get_line()
+ pair = self.talker.get_pair()
+
+ if not pair:
+ self.talker.current_line = ''
+ return entity
+
+ key, val = pair
+ key = key.lower()
+
+ if key in keywords and key in entity.keys():
+ return entity
+
+ if not type and 'type' not in entity.keys():
+ entity['type'] = key
+
+ entity[key] = self.convert(entity['type'], key, val)
+ self.talker.current_line = ''
+
+ return entity
+
+ def all_objects(self, keywords, type):
+ while 1:
+ obj = self.one_object(keywords, type)
+ if not obj:
+ raise StopIteration
+ yield obj
+ if self.talker.done:
+ raise StopIteration
+
+ def convert(self, cmd, key, val):
+ # if there's a converter, convert it, otherwise return it the same
+ return self.converters.get(cmd, {}).get(key, lambda x: x)(val)
+
+class dictobj(dict):
+ def __getattr__(self, attr):
+ return self[attr]
+ def __repr__(self):
+ # <mpdclient2.dictobj at 0x12345678 ..
+ # {
+ # key: val,
+ # key2: val2
+ # }>
+ return (object.__repr__(self).rstrip('>') + ' ..\n' +
+ ' {\n ' +
+ ',\n '.join([ '%s: %s' % (k, v) for k, v in self.items() ])
+
+ '\n }>')
+
+class mpd_connection(object):
+ def __init__(self, host, port):
+ self.talker = socket_talker(host, port)
+ self.send = command_sender(self.talker)
+ self.fetch = response_fetcher(self.talker)
+ self.do = sender_n_fetcher(self.send, self.fetch)
+
+ self._hello()
+
+ def _hello(self):
+ line = self.talker.get_line()
+ if not line.startswith("OK MPD "):
+ raise RuntimeError("this ain't mpd")
+ self.mpd_version = line[len("OK MPD "):].strip()
+ self.talker.current_line = ''
+
+ # conn.foo() is equivalent to conn.do.foo(), but nicer
+ def __getattr__(self, attr):
+ if is_command(attr):
+ return getattr(self.do, attr)
+ raise AttributeError(attr)
+
+def parse_host(host):
+ if '@' in host:
+ return host.split('@', 1)
+ return '', host
+
+def connect(**kw):
+ import os
+
+ port = int(os.environ.get('MPD_PORT', 6600))
+ password, host = parse_host(os.environ.get('MPD_HOST', 'localhost'))
+
+ kw_port = kw.get('port', 0)
+ kw_password = kw.get('password', '')
+ kw_host = kw.get('host', '')
+
+ if kw_port:
+ port = kw_port
+ if kw_password:
+ password = kw_password
+ if kw_host:
+ host = kw_host
+
+ conn = mpd_connection(host, port)
+ if password:
+ conn.password(password)
+ return conn
+
+
+#
+# # Thread safe extenstion added by Graham Billiau <[EMAIL PROTECTED]>
+#
+
+# TODO:
+# sometimes this hangs, probably a problem with the locking
+
+def MPD_Ping_Thread(conn):
+ """This should be run as a thread
+ It constantly loops, sending keepalive packets to the mpd server
+ it exits cleanly after the connection is closed"""
+ while True:
+ sleep(10)
+ try:
+ conn.ping()
+ except socket.error:
+ return
+
+class Thread_MPD_Connection:
+ """This is a wrapper around the mpdclient2 library to make it thread
safe"""
+ #conn
+ def __init__ (self, host, port, keepalive=False, pword = None):
+ """create the connection and locks,
+ if keepalive is True the connection will not time out and must be
explcitly closed"""
+ self.conn = mpd_connection(host, port)
+ if (pword is not None):
+ self.conn.password(pword)
+ self.lock = thread.allocate_lock()
+ if (keepalive == True):
+ thread.start_new_thread(MPD_Ping_Thread, (self, ))
+
+ def __getattr__(self, attr):
+ """pass the request on to the connection object, while ensuring no
conflicts"""
+ self.lock.acquire()
+ funct = self.conn.__getattr__(attr)
+ self.lock.release()
+ return funct
Modified: branches/rel-1/freevo/ChangeLog
==============================================================================
--- branches/rel-1/freevo/ChangeLog (original)
+++ branches/rel-1/freevo/ChangeLog Sun Jan 6 16:45:25 2008
@@ -19,6 +19,7 @@
== Release 1.7.6 (2008-??-??) ==
--------------------------------
+ * New audio MPD plug-in replacement (F#1865008)
* New DirecTV plug-in to send to DirecTV receivers (F#1858775)
* Updated cdbackup plug-in with CD_RIP_FMT to set the encoding format
(F#1857460)
* Updated recordings manager plug-in to add thumbnail support (F#1857397)
Added: branches/rel-1/freevo/src/audio/plugins/mpd_playlist.py
==============================================================================
--- (empty file)
+++ branches/rel-1/freevo/src/audio/plugins/mpd_playlist.py Sun Jan 6
16:45:25 2008
@@ -0,0 +1,96 @@
+# This is a replacement for the previous mpd plugin
+# By Graham Billiau <[EMAIL PROTECTED]> (DrLizAu's code monkey)
+# This code is released under the GPL
+
+# There are two parts to this plugin, an addition to the audio item menu to
queue the item in mpd's playlist and a
+# status display in the audio menu
+
+# TODO:
+# add code to cope if the mpd server crashes
+# add code to enqueue an entire directory & sub-directories
+# add code to enqueue an existing playlist
+# modify code to support localisation
+# investigate having the mpd connection managed by another class
+
+# Advantages of this over the previous mpd plugin:
+# The code is a lot cleaner and more robust.
+# Faster (talks to mpd directly, rather than calling other programs).
+# Allows you to modify the playlist within freevo.
+
+# This only works if the music to be played is part of the filesystem avalible
to mpd and also avalible to freevo
+# so both on the same computer, or exported using samba or nfs
+
+# This code uses the following options in local_conf.py:
+# MPD_SERVER_HOST='localhost' # the host running the mpd server
+# MPD_SERVER_PORT='6600' # the port the server is listening on
+# MPD_SERVER_PASSWORD=None # the password to access the mpd server
+# MPD_MUSIC_BASE_PATH='/mnt/music/' # the local path to where the music
is stored, must have trailing slash
+# MPD_EXTERNAL_CLIENT='/usr/bin/pympd' # the location of the external
client you want to use, or None
+# MPD_EXTERNAL_CLIENT_ARGS='' # arguments to be passed to the external
client, or None, obsolete
+
+# This is the mpd playlist plugin.
+# using this you can add the currently selected song to the mpd playlist
+
+import plugin
+import config
+
+import mpdclient2
+
+class PluginInterface (plugin.ItemPlugin):
+ """This plugin adds a 'enqueue in MPD' option to audio files"""
+
+
+ def __init__(self):
+ """open the connection to the mpd server and keep it alive
+ assume that the plugin is loaded once, then kept in memory"""
+ plugin.ItemPlugin.__init__(self)
+ self.conn = mpdclient2.Thread_MPD_Connection(config.MPD_SERVER_HOST,
config.MPD_SERVER_PORT, True,
+ config.MPD_SERVER_PASSWORD)
+
+ # ensure there is a trailing slash on config.MPD_MUSIC_BASE_PATH
+ if not config.MPD_MUSIC_BASE_PATH.endswith('/'):
+ config.MPD_MUSIC_BASE_PATH = config.MPD_MUSIC_BASE_PATH + '/'
+
+
+ def config(self):
+ return [ ('MPD_SERVER_HOST', 'localhost', 'the host running the mpd
server'),
+ ('MPD_SERVER_PORT', 6600, 'the port the server is listening
on'),
+ ('MPD_SERVER_PASSWORD', None, 'the password to access the mpd
server'),
+ ('MPD_MUSIC_BASE_PATH', '/mnt/music/', 'the local path to
where the music is stored') ]
+
+
+ def shutdown(self):
+ """close the connection to the mpd server"""
+ try:
+ # this always throws EOFError, even though there isn't really an
error
+ self.conn.close()
+ except EOFError:
+ pass
+ return
+
+
+ def actions (self, item):
+ """add the option for all music that is in the mpd library"""
+ self.item = item
+ # check to see if item is a FileItem
+ if (item.type == 'file'):
+ # check to see if item is in mpd's library
+ if (item.filename.startswith(config.MPD_MUSIC_BASE_PATH)):
+ # can query mpd to see if the file is in it's ibrary
+ return [ (self.enqueue_file, 'Add to MPD playlist') ]
+ #elif (item.type == 'dir'):
+ #elif (item.type == 'playlist'):
+ return []
+
+
+ def enqueue_file(self, arg=None, menuw=None):
+ self.conn.add(self.item.filename[len(config.MPD_MUSIC_BASE_PATH):])
+ if menuw is not None:
+ menuw.delete_menu(arg, menuw)
+ return
+
+
+ #def enqueue_dir(self, arg=None, menuw=None):
+
+
+ #def enqueue_playlist(self, arg=None, menuw=None):
\ No newline at end of file
Added: branches/rel-1/freevo/src/audio/plugins/mpd_status.py
==============================================================================
--- (empty file)
+++ branches/rel-1/freevo/src/audio/plugins/mpd_status.py Sun Jan 6
16:45:25 2008
@@ -0,0 +1,242 @@
+# This is a replacement for the previous mpd plugin
+# By Graham Billiau <[EMAIL PROTECTED]> (DrLizAu's code monkey)
+# This code is released under the GPL
+
+# There are two parts to this plugin, an addition to the audio item menu to
queue the item in mpd's playlist and a
+# status display in the audio menu
+
+# Advantages of this over the previous mpd plugin:
+# The code is a lot cleaner and more robust.
+# Faster (talks to mpd directly, rather than calling other programs).
+# Allows you to modify the playlist within freevo.
+
+# TODO
+# enable localisation
+# investigate having the mpd connection managed by another class
+# pretty GUI
+# have status/everything update periodicly
+# add a playlist browser
+# code to handle when the mpd server is down
+# show the currently playing song in the status view
+
+# This code uses the following options in local_conf.py:
+# MPD_SERVER_HOST='localhost' # the host running the mpd server
+# MPD_SERVER_PORT='6600' # the port the server is listening on
+# MPD_SERVER_PASSWORD=None # the password to access the mpd server
+# MPD_MUSIC_BASE_PATH='/mnt/music' # the local path to where the music is
stored
+# MPD_EXTERNAL_CLIENT='/usr/bin/pympd' # the location of the external
client you want to use, or None
+# MPD_EXTERNAL_CLIENT_ARGS='' # arguments to be passed to the external
client, or None, obsolete
+
+# This is the status display.
+# using this you can see the currently playing track, play, pause, stop, skip
forward, skip back, toggle repeat, toggle
+# shuffle, clear playlist, open external playlist editor, change volume
+
+import plugin
+import config
+from gui import PopupBox
+import menu
+import childapp
+
+import os
+import sys
+
+import mpdclient2
+
+class PluginInterface(plugin.MainMenuPlugin):
+ """This creates a new item in the audio menu
+ Which allows you to view and modify the MPD settings"""
+
+ def __init__(self):
+ """ initilise the plugin"""
+ plugin.MainMenuPlugin.__init__(self)
+ self.show_item = menu.MenuItem('MPD status', action=self.show_menu)
+ self.show_item.type = 'audio'
+ plugin.register(self, 'audio.MPD_status')
+ # connect to the server
+ self.conn = mpdclient2.Thread_MPD_Connection(config.MPD_SERVER_HOST,
config.MPD_SERVER_PORT, True,
+ config.MPD_SERVER_PASSWORD)
+
+ # items to go in the mpd menu
+ self.item_play = menu.MenuItem('play', self.mpd_play)#, parent=self)
+ self.item_status = menu.MenuItem('status', self.mpd_status)#,
parent=self)
+ self.item_pause = menu.MenuItem('pause', self.mpd_pause)#, parent=self)
+ self.item_stop = menu.MenuItem('stop', self.mpd_stop)#, parent=self)
+ self.item_next = menu.MenuItem('next track', self.mpd_next)#,
parent=self)
+ self.item_previous = menu.MenuItem('previous track',
self.mpd_previous)#, parent=self)
+ self.item_clear = menu.MenuItem('clear playlist', self.mpd_clear)#,
parent=self)
+ self.item_shuffle = menu.MenuItem('shuffle playlist',
self.mpd_shuffle)#, parent=self)
+ self.item_random = menu.MenuItem('toggle random',
self.mpd_toggle_random)#, parent=self)
+ self.item_repeat = menu.MenuItem('toggle repeat',
self.mpd_toggle_repeat)#, parent=self)
+ self.item_extern = menu.MenuItem('open external mpd client',
self.start_external)#, parent=self)
+
+
+
+ def shutdown(self):
+ """close the connection to the MPD server"""
+ try:
+ self.conn.close()
+ except EOFError:
+ # this always happens
+ pass
+
+
+ def config(self):
+ """returns the config variables used by this plugin"""
+ return [ ('MPD_SERVER_HOST', 'localhost', 'the host running the mpd
server'),
+ ('MPD_SERVER_PORT', 6600, 'the port the server is listening
on'),
+ ('MPD_SERVER_PASSWORD', None, 'the password to access the mpd
server'),
+ ('MPD_EXTERNAL_CLIENT', None,'the location of the external
client you want to use') ]
+ #('MPD_EXTERNAL_CLIENT_ARGS', '','arguments to be passed to
the external client') ]
+
+
+ def items(self, parent):
+ """returns the options to add to the main menu"""
+ return [ self.show_item ]
+
+
+ def actions(self):
+ """extra options for this menu"""
+ return [ ]
+
+
+ def create_menu(self):
+ """this creates the menu"""
+
+ menu_items = []
+ stat = self.conn.status()
+
+ # populate the menu
+ menu_items.append(self.item_status)
+ if (stat['state'] == 'play'):
+ menu_items.append(self.item_pause)
+ menu_items.append(self.item_stop)
+ elif (stat['state'] == 'pause'):
+ menu_items.append(self.item_play)
+ menu_items.append(self.item_stop)
+ else:
+ menu_items.append(self.item_play)
+ menu_items.append(self.item_next)
+ menu_items.append(self.item_previous)
+ menu_items.append(self.item_repeat)
+ menu_items.append(self.item_random)
+ menu_items.append(self.item_shuffle)
+ menu_items.append(self.item_clear)
+ if not (config.MPD_EXTERNAL_CLIENT is None or
config.MPD_EXTERNAL_CLIENT == ''):
+ menu_items.append(self.item_extern)
+
+ return menu.Menu('MPD status', menu_items,
reload_func=self.create_menu)
+
+
+ def show_menu(self, arg=None, menuw=None):
+ """this displays the menu"""
+
+ # display the menu
+ mpd_menu = self.create_menu()
+ menuw.pushmenu(mpd_menu)
+ menuw.refresh()
+
+
+ def mpd_play(self, arg=None, menuw=None):
+ """send the play command to the mpd server"""
+ self.conn.play()
+ # force the menu to reload
+ menuw.delete_menu()
+ self.show_menu(arg, menuw)
+
+
+ def mpd_next(self, arg=None, menuw=None):
+ """send the play command to the mpd server"""
+ self.conn.next()
+
+
+ def mpd_previous(self, arg=None, menuw=None):
+ """send the play command to the mpd server"""
+ self.conn.previous()
+
+
+ def mpd_stop(self, arg=None, menuw=None):
+ """send the stop command to the mpd server"""
+ self.conn.stop()
+ # force the menu to reload
+ menuw.delete_menu()
+ self.show_menu(arg, menuw)
+
+
+ def mpd_pause(self, arg=None, menuw=None):
+ """send the pause command to the mpd server"""
+ self.conn.pause(1)
+ # force the menu to reload
+ menuw.delete_menu()
+ self.show_menu(arg, menuw)
+
+
+ def mpd_shuffle(self, arg=None, menuw=None):
+ """send the shuffle command to the mpd server"""
+ self.conn.shuffle()
+
+
+ def mpd_toggle_random(self, arg=None, menuw=None):
+ """toggle random on/off to the mpd server"""
+ stat = self.conn.status()
+ if (stat['random'] == '0'):
+ self.conn.random(1)
+ else:
+ self.conn.random(0)
+
+
+ def mpd_toggle_repeat(self, arg=None, menuw=None):
+ """toggle repeat on/off to the mpd server"""
+ stat = self.conn.status()
+ if (stat['repeat'] == '0'):
+ self.conn.repeat(1)
+ else:
+ self.conn.repeat(0)
+
+
+ def mpd_clear(self, arg=None, menuw=None):
+ """send the clear command to the mpd server"""
+ self.conn.clear()
+
+
+ def start_external(self, arg=None, menuw=None):
+ """start the external browser in the config file"""
+ if (config.MPD_EXTERNAL_CLIENT is None or config.MPD_EXTERNAL_CLIENT
== ''):
+ return
+
+ #if (config.MPD_EXTERNAL_CLIENT_ARGS is None or
config.MPD_EXTERNAL_CLIENT_ARGS == ''):
+ #args = None
+ #else:
+ #args = config.MPD_EXTERNAL_CLIENT_ARGS.split()
+
+ try:
+ #os,spawnv(os.P_NOWAIT, config.MPD_EXTERNAL_CLIENT, args)
+ childapp.ChildApp(config.MPD_EXTERNAL_CLIENT)
+ except:
+ text = "Error starting external client\n%s\n%s" %sys.exc_info()[:2]
+ pop = PopupBox(text)
+ pop.show()
+
+
+ def mpd_status(self, arg=None, menuw=None):
+ """bring up a dialog showing mpd's current status"""
+ stat = self.conn.status()
+
+ text = "status: %s\n" %(stat['state'])
+ if (stat['state'] != 'stop'):
+ # how do i get the song name?
+ text += "track: %s\\%s\n" %(int(stat['song']) + 1,
stat['playlistlength'])
+ text += "time: %s\n" %(stat['time'])
+ if (stat['repeat'] == '1'):
+ text += "repeat: on\n"
+ else:
+ text += "repeat: off\n"
+ if (stat['random'] == '1'):
+ text += "random: on\n"
+ else:
+ text += "random: off\n"
+ text += "volume: %s" %(stat['volume'])
+
+ pop = PopupBox(text)
+ pop.show()
+
+
\ No newline at end of file
Added: branches/rel-1/freevo/src/audio/plugins/mpdclient2.py
==============================================================================
--- (empty file)
+++ branches/rel-1/freevo/src/audio/plugins/mpdclient2.py Sun Jan 6
16:45:25 2008
@@ -0,0 +1,370 @@
+#!/usr/bin/env python
+
+# py-libmpdclient2 is written by Nick Welch <[EMAIL PROTECTED]>, 2005.
+#
+# This software is released to the public
+# domain and is provided AS IS, with NO WARRANTY.
+
+import socket
+from time import sleep
+import thread
+
+# a line is either:
+#
+# key:val pair
+# OK
+# ACK
+
+class socket_talker(object):
+
+ def __init__(self, host, port):
+ self.host = host
+ self.port = port
+ self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
+ self.sock.connect((host, port))
+ self.file = self.sock.makefile("rb+")
+ self.current_line = ''
+ self.ack = ''
+ self.done = True
+
+ # this SUCKS
+
+ def get_line(self):
+ if not self.current_line:
+ self.current_line = self.file.readline().rstrip("\n")
+ if not self.current_line:
+ raise EOFError
+ if self.current_line == "OK" or self.current_line.startswith("ACK"):
+ self.done = True
+ return self.current_line
+
+ def putline(self, line):
+ self.file.write("%s\n" % line)
+ self.file.flush()
+ self.done = False
+
+ def get_pair(self):
+ line = self.get_line()
+ self.ack = ''
+
+ if self.done:
+ if line.startswith("ACK"):
+ self.ack = line.split(None, 1)[1]
+ return ()
+
+ pair = line.split(": ", 1)
+ if len(pair) != 2:
+ raise RuntimeError("bogus response: ``%s''" % line)
+
+ return pair
+
+ZERO = 0
+ONE = 1
+MANY = 2
+
+plitem_delim = ["file", "directory", "playlist"]
+
+commands = {
+ # (name, nargs): (format string, nresults, results_type_name,
delimiter_key)
+
+ # delimiter key is for commands that return multiple results. we use this
+ # string to detect the beginning of a new result object.
+
+ # if results_type_name is empty, the result object's .type will be set to
+ # the key of the first key/val pair in it; otherwise, it will be set to
+ # results_type_name.
+
+ ("kill", 0): ('%s', ZERO, '', []),
+ ("outputs", 0): ('%s', MANY, 'outputs', ['outputid']),
+ ("clear", 0): ('%s', ZERO, '', []),
+ ("currentsong", 0): ('%s', ONE, '', []),
+ ("shuffle", 0): ('%s', ZERO, '', []),
+ ("next", 0): ('%s', ZERO, '', []),
+ ("previous", 0): ('%s', ZERO, '', []),
+ ("stop", 0): ('%s', ZERO, '', []),
+ ("clearerror", 0): ('%s', ZERO, '', []),
+ ("close", 0): ('%s', ZERO, '', []),
+ ("commands", 0): ('%s', MANY, 'commands', ['command']),
+ ("notcommands", 0): ('%s', MANY, 'notcommands', ['command']),
+ ("ping", 0): ('%s', ZERO, '', []),
+ ("stats", 0): ('%s', ONE, 'stats', []),
+ ("status", 0): ('%s', ONE, 'status', []),
+ ("play", 0): ('%s', ZERO, '', []),
+ ("playlistinfo", 0): ('%s', MANY, '', plitem_delim),
+ ("playlistid", 0): ('%s', MANY, '', plitem_delim),
+ ("lsinfo", 0): ('%s', MANY, '', plitem_delim),
+ ("update", 0): ('%s', ZERO, '', []),
+ ("listall", 0): ('%s', MANY, '', plitem_delim),
+ ("listallinfo", 0): ('%s', MANY, '', plitem_delim),
+
+ ("disableoutput", 1): ("%s %d", ZERO, '', []),
+ ("enableoutput", 1): ("%s %d", ZERO, '', []),
+ ("delete", 1): ('%s %d', ZERO, '', []), # <int song>
+ ("deleteid", 1): ('%s %d', ZERO, '', []), # <int songid>
+ ("playlistinfo", 1): ('%s %d', MANY, '', plitem_delim), # <int song>
+ ("playlistid", 1): ('%s %d', MANY, '', plitem_delim), # <int songid>
+ ("crossfade", 1): ('%s %d', ZERO, '', []), # <int seconds>
+ ("play", 1): ('%s %d', ZERO, '', []), # <int song>
+ ("playid", 1): ('%s %d', ZERO, '', []), # <int songid>
+ ("random", 1): ('%s %d', ZERO, '', []), # <int state>
+ ("repeat", 1): ('%s %d', ZERO, '', []), # <int state>
+ ("setvol", 1): ('%s %d', ZERO, '', []), # <int vol>
+ ("plchanges", 1): ('%s %d', MANY, '', plitem_delim), # <playlist
version>
+ ("pause", 1): ('%s %d', ZERO, '', []), # <bool pause>
+
+ ("update", 1): ('%s "%s"', ONE, 'update', []), # <string path>
+ ("listall", 1): ('%s "%s"', MANY, '', plitem_delim), # <string path>
+ ("listallinfo", 1): ('%s "%s"', MANY, '', plitem_delim), # <string path>
+ ("lsinfo", 1): ('%s "%s"', MANY, '', plitem_delim), # <string
directory>
+ ("add", 1): ('%s "%s"', ZERO, '', []), # <string>
+ ("load", 1): ('%s "%s"', ZERO, '', []), # <string name>
+ ("rm", 1): ('%s "%s"', ZERO, '', []), # <string name>
+ ("save", 1): ('%s "%s"', ZERO, '', []), # <string playlist name>
+ ("password", 1): ('%s "%s"', ZERO, '', []), # <string password>
+
+ ("move", 2): ("%s %d %d", ZERO, '', []), # <int from> <int to>
+ ("moveid", 2): ("%s %d %d", ZERO, '', []), # <int songid from> <int to>
+ ("swap", 2): ("%s %d %d", ZERO, '', []), # <int song1> <int song2>
+ ("swapid", 2): ("%s %d %d", ZERO, '', []), # <int songid1> <int songid2>
+ ("seek", 2): ("%s %d %d", ZERO, '', []), # <int song> <int time>
+ ("seekid", 2): ("%s %d %d", ZERO, '', []), # <int songid> <int time>
+
+ # <string type> <string what>
+ ("find", 2): ('%s "%s" "%s"', MANY, '', plitem_delim),
+
+ # <string type> <string what>
+ ("search", 2): ('%s "%s" "%s"', MANY, '', plitem_delim),
+
+ # list <metadata arg1> [<metadata arg2> <search term>]
+
+ # <metadata arg1>
+ ("list", 1): ('%s "%s"', MANY, '', plitem_delim),
+
+ # <metadata arg1> <metadata arg2> <search term>
+ ("list", 3): ('%s "%s" "%s" "%s"', MANY, '', plitem_delim),
+}
+
+def is_command(cmd):
+ return cmd in [ k[0] for k in commands.keys() ]
+
+def escape(text):
+ # join/split is faster than replace
+ text = '\\\\'.join(text.split('\\')) # \ -> \\
+ text = '\\"'.join(text.split('"')) # " -> \"
+ return text
+
+def get_command(cmd, args):
+ try:
+ return commands[(cmd, len(args))]
+ except KeyError:
+ raise RuntimeError("no such command: %s (%d args)" % (cmd, len(args)))
+
+def send_command(talker, cmd, args):
+ args = list(args[:])
+ for i, arg in enumerate(args):
+ if not isinstance(arg, int):
+ args[i] = escape(str(arg))
+ format = get_command(cmd, args)[0]
+ talker.putline(format % tuple([cmd] + list(args)))
+
+class sender_n_fetcher(object):
+ def __init__(self, sender, fetcher):
+ self.sender = sender
+ self.fetcher = fetcher
+ self.iterate = False
+
+ def __getattr__(self, cmd):
+ return lambda *args: self.send_n_fetch(cmd, args)
+
+ def send_n_fetch(self, cmd, args):
+ getattr(self.sender, cmd)(*args)
+ junk, howmany, type, keywords = get_command(cmd, args)
+
+ if howmany == ZERO:
+ self.fetcher.clear()
+ return
+
+ if howmany == ONE:
+ return self.fetcher.one_object(keywords, type)
+
+ assert howmany == MANY
+ result = self.fetcher.all_objects(keywords, type)
+
+ if not self.iterate:
+ result = list(result)
+ self.fetcher.clear()
+ return result
+
+ # stupid hack because you apparently can't return non-None and yield
+ # within the same function
+ def yield_then_clear(it):
+ for x in it:
+ yield x
+ self.fetcher.clear()
+ return yield_then_clear(result)
+
+
+class command_sender(object):
+ def __init__(self, talker):
+ self.talker = talker
+ def __getattr__(self, cmd):
+ return lambda *args: send_command(self.talker, cmd, args)
+
+class response_fetcher(object):
+ def __init__(self, talker):
+ self.talker = talker
+ self.converters = {}
+
+ def clear(self):
+ while not self.talker.done:
+ self.talker.current_line = ''
+ self.talker.get_line()
+ self.talker.current_line = ''
+
+ def one_object(self, keywords, type):
+ # if type isn't empty, then the object's type is set to it. otherwise
+ # the type is set to the key of the first key/val pair.
+
+ # keywords lists the keys that indicate a new object -- like for the
+ # 'outputs' command, keywords would be ['outputid'].
+
+ entity = dictobj()
+ if type:
+ entity['type'] = type
+
+ while not self.talker.done:
+ self.talker.get_line()
+ pair = self.talker.get_pair()
+
+ if not pair:
+ self.talker.current_line = ''
+ return entity
+
+ key, val = pair
+ key = key.lower()
+
+ if key in keywords and key in entity.keys():
+ return entity
+
+ if not type and 'type' not in entity.keys():
+ entity['type'] = key
+
+ entity[key] = self.convert(entity['type'], key, val)
+ self.talker.current_line = ''
+
+ return entity
+
+ def all_objects(self, keywords, type):
+ while 1:
+ obj = self.one_object(keywords, type)
+ if not obj:
+ raise StopIteration
+ yield obj
+ if self.talker.done:
+ raise StopIteration
+
+ def convert(self, cmd, key, val):
+ # if there's a converter, convert it, otherwise return it the same
+ return self.converters.get(cmd, {}).get(key, lambda x: x)(val)
+
+class dictobj(dict):
+ def __getattr__(self, attr):
+ return self[attr]
+ def __repr__(self):
+ # <mpdclient2.dictobj at 0x12345678 ..
+ # {
+ # key: val,
+ # key2: val2
+ # }>
+ return (object.__repr__(self).rstrip('>') + ' ..\n' +
+ ' {\n ' +
+ ',\n '.join([ '%s: %s' % (k, v) for k, v in self.items() ])
+
+ '\n }>')
+
+class mpd_connection(object):
+ def __init__(self, host, port):
+ self.talker = socket_talker(host, port)
+ self.send = command_sender(self.talker)
+ self.fetch = response_fetcher(self.talker)
+ self.do = sender_n_fetcher(self.send, self.fetch)
+
+ self._hello()
+
+ def _hello(self):
+ line = self.talker.get_line()
+ if not line.startswith("OK MPD "):
+ raise RuntimeError("this ain't mpd")
+ self.mpd_version = line[len("OK MPD "):].strip()
+ self.talker.current_line = ''
+
+ # conn.foo() is equivalent to conn.do.foo(), but nicer
+ def __getattr__(self, attr):
+ if is_command(attr):
+ return getattr(self.do, attr)
+ raise AttributeError(attr)
+
+def parse_host(host):
+ if '@' in host:
+ return host.split('@', 1)
+ return '', host
+
+def connect(**kw):
+ import os
+
+ port = int(os.environ.get('MPD_PORT', 6600))
+ password, host = parse_host(os.environ.get('MPD_HOST', 'localhost'))
+
+ kw_port = kw.get('port', 0)
+ kw_password = kw.get('password', '')
+ kw_host = kw.get('host', '')
+
+ if kw_port:
+ port = kw_port
+ if kw_password:
+ password = kw_password
+ if kw_host:
+ host = kw_host
+
+ conn = mpd_connection(host, port)
+ if password:
+ conn.password(password)
+ return conn
+
+
+#
+# # Thread safe extenstion added by Graham Billiau <[EMAIL PROTECTED]>
+#
+
+# TODO:
+# sometimes this hangs, probably a problem with the locking
+
+def MPD_Ping_Thread(conn):
+ """This should be run as a thread
+ It constantly loops, sending keepalive packets to the mpd server
+ it exits cleanly after the connection is closed"""
+ while True:
+ sleep(10)
+ try:
+ conn.ping()
+ except socket.error:
+ return
+
+class Thread_MPD_Connection:
+ """This is a wrapper around the mpdclient2 library to make it thread
safe"""
+ #conn
+ def __init__ (self, host, port, keepalive=False, pword = None):
+ """create the connection and locks,
+ if keepalive is True the connection will not time out and must be
explcitly closed"""
+ self.conn = mpd_connection(host, port)
+ if (pword is not None):
+ self.conn.password(pword)
+ self.lock = thread.allocate_lock()
+ if (keepalive == True):
+ thread.start_new_thread(MPD_Ping_Thread, (self, ))
+
+ def __getattr__(self, attr):
+ """pass the request on to the connection object, while ensuring no
conflicts"""
+ self.lock.acquire()
+ funct = self.conn.__getattr__(attr)
+ self.lock.release()
+ return funct
-------------------------------------------------------------------------
This SF.net email is sponsored by: Microsoft
Defy all challenges. Microsoft(R) Visual Studio 2005.
http://clk.atdmt.com/MRT/go/vse0120000070mrt/direct/01/
_______________________________________________
Freevo-cvslog mailing list
[email protected]
https://lists.sourceforge.net/lists/listinfo/freevo-cvslog