test.py should do a decent job of showing how the module works -- there's not a lot to it -- but if you have more questions, your best bet is to get a hold of me (mackstann) in #mpd on irc.freenode.net (not there so much anymore). Or you can email me at mack@incise.org .
Since the library isn't widely packaged, it is probably easiest for you to just include mpdclient2.py in your own client's codebase. And since it is public domain, you have no licensing issues to worry about whatsoever. The code belongs to you no less than it belongs to me.
So ok, let's fix it. Basic ideas:
MPD's protocol is line-based (linefeed (\n) delimited).
Every line sent from the client to the server is a command that elicits a response, with the exception of command lists. A command list begins with command_list_begin, followed by an arbitrary number of commands, followed by command_list_end. The entire command list gets one response -- OK if all commands succeeded, ACK if any failed, and no commands after the failed command will have been executed.
A command is a single word followed by a space, followed by space-delimited arguments. Arguments with embedded spaces or double quotes should be enclosed within double quotes. Double quotes and backslashes within the quoted string should be escaped with backslashes in the typical way:
A safe implementation is just as simple as that.
escaped = text.replace('\\', '\\\\').replace('"', '\\"')
somecommand "hey i have \"embedded quotes\" in my string" ACK what's somecommand? you're dumb somecommand "i have backslashes too. a backslash is \"\\\"." ACK what's somecommand? you're dumb
Every line that comes from the server is one of:
The OK MPD is sent to the client when it first connects. Some commands receive a list of key/value pairs. All commands get either an OK or ACK response (after the key/value pairs, if any).
todo: how's the format of the errors work, with all of the funky [foo@bar] {} business?
Examples:
% telnet localhost 6600
Trying 127.0.0.1...
Connected to localhost.localdomain.
Escape character is '^]'.
OK MPD 0.12.0
pause
OK
pause
OK
outputs
outputid: 0
outputname: my ALSA device
outputenabled: 1
OK
command_list_begin
pause
pause
command_list_end
OK
command_list_begin
foo
bar
command_list_end
ACK [5@0] {} unknown command "foo"
status
volume: 79
repeat: 1
random: 1
playlist: 27287
playlistlength: 3036
xfade: 0
state: pause
song: 554
songid: 24793
time: 3:319
bitrate: 192
audio: 44100:16:2
OK
todo: command_list_ok_begin adds some more exceptions to the protocol
Check out the status command above. We can take all of its key/val pairs and throw them into a dict (hash) - voila. But some commands return multiple "sets" of the same key/val pairs. For example, the outputs command could return:
outputid: 0 outputname: my ALSA device outputenabled: 1 outputid: 1 outputname: lalala outputenabled: 1
We now need to break this into two different dicts. We need to know which keys to consider "beginning" keys -- keys that indicate a new set of pairs; an independent entity in the results.
We also should give each of these entities a type, so we know what it is. This is necessary for the playlist commands which return arbitrarily ordered lists of playlists, files, and directories. For example, listallinfo might give us:
directory: music directory: music/deftones file: music/deftones/deftones - around the fur.mp3 Time: 212 Artist: deftones Title: around the fur Album: deftones file: music/deftones/deftones - be quiet and drive (far away).mp3 Time: 308 Artist: deftones Title: be quiet and drive (far away) Album: deftones
So we'll give our dicts a single extra key, "type". For the above output, we'd have four dicts:
{'directory': 'music', 'type': 'directory'}
{'directory': 'music/deftones', 'type': 'directory'}
{'Album': 'deftones', 'Title': 'around the fur', 'Artist': 'deftones', 'file': 'music/deftones/deftones - around the fur.mp3', 'Time': '212', 'type': 'file'}
{'Album': 'deftones', 'Title': 'be quiet and drive (far away)', 'Artist': 'deftones', 'file': 'music/deftones/deftones - be quiet and drive (far away).mp3', 'Time': '308', 'type': 'file'}
The "type" to use for each entity would be its first key for playlist items, and for other commands we'll just use the name of the command -- after all, it would make no sense for a status object's type to be "volume".
It's also a lot nicer to just do foo.type, foo.directory, etc., instead of foo['type'] and foo['directory'], so instead of just using a dict, we'll subclass it and implement __getattr__ to look up members in the dict for reading.
I mentioned earlier that hard-coding the various keys that go into a given object (like a status object, or a directory object) basically sucks, but the one thing that it allows us to do is convert things to integers where appropriate. I would also like to convert things like "44100:16:2" to a tuple (44100, 16, 2). Unfortunately you can't really do this in a generic way. Imagine that some band has an album called 5. It would automatically get converted to an int, which wouldn't make sense. We'd have to keep track of which fields should be integers or sets of integers -- and then what happens when a new integer field is added to some command's output? Ah, the library is now out of sync. So I really don't want to do that. Bummer.
todo: explain converters
todo: explain the layout of the command dict, and the independent send/fetch/do components