4 HELLO_PREFIX = "OK MPD "
10 class MPDError(Exception):
13 class ProtocolError(MPDError):
16 class CommandError(MPDError):
19 class CommandListError(MPDError):
23 class MPDClient(object):
29 "disableoutput": self._getnone,
30 "enableoutput": self._getnone,
32 "update": self._getitem,
33 # Informational Commands
34 "status": self._getobject,
35 "stats": self._getobject,
36 "outputs": self._getoutputs,
37 "commands": self._getlist,
38 "notcommands": self._getlist,
39 "tagtypes": self._getlist,
40 "urlhandlers": self._getlist,
42 "find": self._getsongs,
43 "list": self._getlist,
44 "listall": self._getdatabase,
45 "listallinfo": self._getdatabase,
46 "lsinfo": self._getdatabase,
47 "search": self._getsongs,
48 "count": self._getobject,
51 "addid": self._getitem,
52 "clear": self._getnone,
53 "currentsong": self._getobject,
54 "delete": self._getnone,
55 "deleteid": self._getnone,
56 "load": self._getnone,
57 "rename": self._getnone,
58 "move": self._getnone,
59 "moveid": self._getnone,
60 "playlist": self._getplaylist,
61 "playlistinfo": self._getsongs,
62 "playlistid": self._getsongs,
63 "plchanges": self._getsongs,
64 "plchangesposid": self._getchanges,
66 "save": self._getnone,
67 "shuffle": self._getnone,
68 "swap": self._getnone,
69 "swapid": self._getnone,
70 "listplaylist": self._getlist,
71 "listplaylistinfo": self._getsongs,
72 "playlistadd": self._getnone,
73 "playlistclear": self._getnone,
74 "playlistdelete": self._getnone,
75 "playlistmove": self._getnone,
76 "playlistfind": self._getsongs,
77 "playlistsearch": self._getsongs,
79 "crossfade": self._getnone,
80 "next": self._getnone,
81 "pause": self._getnone,
82 "play": self._getnone,
83 "playid": self._getnone,
84 "previous": self._getnone,
85 "random": self._getnone,
86 "repeat": self._getnone,
87 "seek": self._getnone,
88 "seekid": self._getnone,
89 "setvol": self._getnone,
90 "stop": self._getnone,
91 "volume": self._getnone,
92 # Miscellaneous Commands
93 "clearerror": self._getnone,
95 "password": self._getnone,
96 "ping": self._getnone,
99 def __getattr__(self, attr):
101 retval = self._commands[attr]
103 raise AttributeError, "'%s' object has no attribute '%s'" % \
104 (self.__class__.__name__, attr)
105 return lambda *args: self._docommand(attr, args, retval)
107 def _docommand(self, command, args, retval):
108 if self._commandlist is not None and not callable(retval):
109 raise CommandListError, "%s not allowed in command list" % command
110 self._writecommand(command, args)
111 if self._commandlist is None:
115 self._commandlist.append(retval)
117 def _writeline(self, line):
118 self._sockfile.write("%s\n" % line)
119 self._sockfile.flush()
121 def _writecommand(self, command, args=[]):
124 parts.append('"%s"' % escape(str(arg)))
125 self._writeline(" ".join(parts))
128 line = self._sockfile.readline().rstrip("\n")
129 if line.startswith(ERROR_PREFIX):
130 error = line[len(ERROR_PREFIX):].strip()
131 raise CommandError, error
132 if self._commandlist is not None:
136 raise ProtocolError, "Got unexpected '%s'" % SUCCESS
137 elif line == SUCCESS:
141 def _readitem(self, separator):
142 line = self._readline()
145 item = line.split(separator, 1)
147 raise ProtocolError, "Could not parse item: '%s'" % line
150 def _readitems(self, separator=": "):
151 item = self._readitem(separator)
154 item = self._readitem(separator)
159 for key, value in self._readitems():
162 raise ProtocolError, "Expected key '%s', got '%s'" % \
168 def _readplaylist(self):
169 for key, value in self._readitems(":"):
173 def _readobjects(self, delimiters=[]):
175 for key, value in self._readitems():
178 if key in delimiters:
181 elif obj.has_key(key):
182 if not isinstance(obj[key], list):
183 obj[key] = [obj[key], value]
185 obj[key].append(value)
192 def _readcommandlist(self):
193 for retval in self._commandlist:
195 self._commandlist = None
199 def _wrapiterator(self, iterator):
201 return list(iterator)
205 line = self._readline()
207 raise ProtocolError, "Got unexpected return value: '%s'" % line
210 items = list(self._readitems())
212 raise ProtocolError, "Expected 1 item, got %i" % len(items)
216 return self._wrapiterator(self._readlist())
218 def _getplaylist(self):
219 return self._wrapiterator(self._readplaylist())
221 def _getobject(self):
222 objs = list(self._readobjects())
227 def _getobjects(self, delimiters):
228 return self._wrapiterator(self._readobjects(delimiters))
231 return self._getobjects(["file"])
233 def _getdatabase(self):
234 return self._getobjects(["file", "directory", "playlist"])
236 def _getoutputs(self):
237 return self._getobjects(["outputid"])
239 def _getchanges(self):
240 return self._getobjects(["cpos"])
242 def _getcommandlist(self):
243 return self._wrapiterator(self._readcommandlist())
246 line = self._sockfile.readline().rstrip("\n")
247 if not line.startswith(HELLO_PREFIX):
248 raise ProtocolError, "Got invalid MPD hello: '%s'" % line
249 self.mpd_version = line[len(HELLO_PREFIX):].strip()
252 self.mpd_version = None
253 self._commandlist = None
254 self._sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
255 self._sockfile = self._sock.makefile("rb+")
257 def connect(self, host, port):
259 self._sock.connect((host, port))
262 def disconnect(self):
263 self._sockfile.close()
267 def command_list_ok_begin(self):
268 if self._commandlist is not None:
269 raise CommandListError, "Already in command list"
270 self._writecommand("command_list_ok_begin")
271 self._commandlist = []
273 def command_list_end(self):
274 if self._commandlist is None:
275 raise CommandListError, "Not in command list"
276 self._writecommand("command_list_end")
277 return self._getcommandlist()
281 return text.replace("\\", "\\\\").replace('"', '\\"')
284 # vim: set expandtab shiftwidth=4 softtabstop=4 textwidth=79: