An API to facilitate communication with an MPD server in Tcl. At least, that's the goal. Initially, the focus will be on implementing the bare minimum of features to allow for basic remote control. I'm basing this off of the documentation here: https://www.musicpd.org/doc/protocol/
This was initially based off of some old, really bad code I wrote a while ago. As with many hobby projects, this is one I was working on sporadically, whenever the mood struck me. I recently picked it up again and had several "what was I thinking?" moments.
My goal is to keep this library relatively simple/thin. One with a familiarity with the MPD protocol should be able to intuit what one of these library functions done with a high degree of accuracy. At least, that's the idea. Care was taken try and not overcomplicate things.
There are exceptions to the above, however. In a couple places, the behaviour of the Tcl procedures does not match the MPD command of a matching name; most notably, 'playlist load'. In this particular case, the MPD load command appends a playlist to the queue. In Tcl, we're used to append, lappend, etc. As such, 'playlist append' is more consistent with the language. As I said earlier, I tried to avoid doing this as much as possible. This project is intended to make writing an MPD client in Tcl easier, not confuse anyone using it.
Ultimately, I'd like to base a minimal Tk-based client off of this library, if only as an overkill-level test suite of the API.
Because.
- Tcl 8.5+ (uses a bunch of newer language features, like {*}, dicts, etc.)
After writing the base bits for communicating with MPD, I started with the low-hanging fruit. I intend to continue down this line, slowly implementing features that require more effort. As I implement functions, they'll end up under the "Complete/Working heading below".
Standard Tcl data structures, such as lists and dicts, are used throughout this project. Building on these, a few common data models are used for representing things like tracks (and their associated properties). This section is dedicated to outlining these formats.
Tracks are represented as trackInfo dicts. Each top-level key in the dict represents a separate track (in this way we can juggle multiple tracks easily). The track key is an integer, typically representing the track's position in the playlist or album. NOTE: the order is completely dependant on the order in which MPD sends us the tracks. In most cases, you should rely on the track number and position subkeys.
If we were to show this structure as a bulleted list, the structure would be similar to the following:
- id
- file (this is the uri)
- Last-Modified
- Time
- duration
- Title
- Album
- Artist
- Genre
- AlbumArtist
- Date
- Track
- id #2
- file (this is the uri)
- ...
- Track
- ...
To demonstrate this in Tcl, have a look at the following code:
set counter 0
foreach album $albums {
puts "\t[incr counter]. $album"
# Get the list of tracks for this album
set tracks [mpd db find Artist $artist Album $album]
# Loop through the tracks for each album, printing the properties as we go
dict for {id track} $tracks {
dict with track {
puts "\t\t$Track. $Title"
foreach prop [dict keys $track *] {
puts "\t\t\t$prop >>> [set $prop]"
}
}
}
}
Similar to trackInfo, decoderInfo dicts are keyed on the name of the decoder.
Like its trackInfo and decoderInfo cousins, outputInfo dicts are keyed on the outputid. The output ID is included as a property, as well.
Ditto for playlists. Playlist name is the key. This may or may not contain track info in the Tracks key for each playlist, depending on where you get your info. If you call 'playlist info', then you won't have track info. To get track info, call 'playlist allInfo'.
The two different commands were written to give the client some flexibility in scenarios where it doesn't need to have all of this info, and maybe shouldn't (ex. when the MPD DB has a ton of playlists with a ton of tracks).
This script implements the mpd command namespace. Everything will be implemented by sub-commands off of mpd.
Arguments:
- on - boolean: 0 to resume, 1 to pause
Plays/pauses playback, depending on the state of on.
Toggle playback.
Play the next track in the queue.
Play the previous track from the queue.
Start playing song at songpos in the playback queue.
Stops playback.
Arguments:
- s - seconds, "fractions allowed"
Seek forward or backward in the current song. This follows the rules of MPD's seekcur: you can seek to an absolute or relative position by changing what you pass as 's'. To seek to an absolute position, pass a straight double or int. To seek to a relative position, prefix your double/int with a plus ("-") or minus ("-") sign.
Common variables:
- on - boolean: on=1, off=0
Enables/disables consume mode.
Enable or disable random playback mode.
Enable or disable repeat mode.
When on, stop playback after the current track. If repeat is on, repeat the current track.
Arguments:
- vol - Output volume, from 0-100
Set the output volume.
Arguments:
- s - seconds
Set the crossfade duration.
Arguments:
- state - Replaygain state, where state=off,track,album,auto
Change the state of Replaygain.
Arguments:
- uri - File/URL/Directory path
Add the song(s) at uri to the playback queue. If the passed uri is a directory, adds tracks recursively.
Arguments:
- uri - File path
- pos - Queue position for insertion
Inserts the song at uri into position pos in the playback queue.
Arguments:
- pos - location of song in queue
Remove the song at pos from the queue.
Arguments:
- id - song ID
Remove the song via ID from the queue.
Clear the playback queue.
Returns a list lists containing info for the tracks in the play queue
Shuffle the entire playlist
Arguments:
- server - IP/hostname of an server
- port - Port to connect
Creates an active connection to an MPD server and stashes the socket somewhere for future use. Currently, this only supports connecting over an IP socket. Port is specified as a separate argument to make it easier to handle the address (ex. don't have to split on a colon).
Disconnects from MPD. This will also destroy the socket.
A basic test to confirm we have a working connection established with an MPD server. Returns 1 if everything is OK; otherwise, returns 0.
This could be expanded to be more robust, but, currently, it only checks the socket, then sends a ping command to the server and waits for a response.
Returns a key-value list of information for the current song. This info is useful for showing a "Now Playing" kind of display. Note: while this returns things like the rough length of the file (to the nearest second) and location in the queue, it will not give you anything about playback. For things like bit rate, song position, or playback state, use info status.
Returned keys include:
- Album
- AlbumArtist
- Artist
- Date
- Genre
- Id
- Last-Modified
- Pos
- Time
- Title
- Track
- duration
- file
Returns a key-value list of information about the current state of MPD. This includes things like playback state (paused, playing, etc.), repeat, random, bit rate, etc.
Returned keys include:
- audio
- bitrate
- consume
- duration
- elapsed
- mixrampdb
- nextsong
- nextsongid
- playlist
- playlistlength
- random
- repeat
- single
- song
- songid
- state
- time
- volume
Retuns a key-value list of pertinent server stats.
Returned keys include:
- albums
- artists
- db_playtime
- db_update
- playtime
- songs
- uptime
Returns a key-value list of decoders supported by the MPD server, their filename suffixes, and associated mime types.
Returns the current state of Replaygain.
Returns a key-value list of commands that are allowed to be run by the client and commands that are not allowed (as returned by the MPD directives "commands" and "notcommands", respectively). The key is the command name; value either "allow" or "deny".
These are helper functions to make writing if statements easier.
Returns 1 when MPD is playing; 0 otherwise.
Returns 1 when MPD is stopped; 0 otherwise.
Perform a case-sensitive search of the MPD DB. The contents of args are passed, verbatim. to the message sent to MPD.
Perform a case-insensitive search of the MPD DB. The contents of args are passed, verbatim. to the message sent to MPD.
Lists objects by passed tag criteria. The contents of args are passed, verbatim. to the message sent to MPD.
This proc has limited support for grouping. If you pass a single group directive, as in "Album group Artist", you will get a dict, keyed on whatever you passed for group. In this example, you would get a dict of lists of Albums keyed on Artist.
For the sake of your sanity, if you want to assemble complicated structures of DB data, keep the arguments you pass to this proc simple and call it several times.
Scan for modified files and update the DB. Scope of the scan is controlled by args: if nothing is passed, everything is scanned. Pass a file or directory to scan a fragment of the library.
Scan all files and update the DB. Scope of the scan is controlled by args: if nothing is passed, everything is scanned. Pass a file or directory to scan a fragment of the library.
Convenience procedure to perform a search of the MPD DB.
Convenience procedure to perform a search of the MPD DB.
Common variables:
- id - Output ID
Disable the passed output.
Enable the passed output.
Toggle the passed output.
Return a list of all available outputs.
Common variables:
- name - Playlist name
Returns a playlistInfo dict of all playlists. At the time of writing, only playlist and Last-Modified are returned. For track info as well, see allInfo.
Checks to see if the passed playlist exists.
Nuke the passed playlist.
Save the playlist.
Returns a trackInfo dict for the passed playlist, a la "listplaylistinfo".
Returns a playlistInfo dict containing trackInfo dicts in the Tracks key.
Blank the contents of the passed playlist.
Rename the playlist.
Load the passed playlist into the playback queue, wiping the existing queue.
Appends the contents of the playlist to the queue.
Common variables:
- name - Playlist name
Add the track specified by uri to the playlist.
Delete the track at songpos from the passed playlist.
Sets track priority
Swap a and b in the queue
Returns the album art binary blog from the MPD DB.
Load the contents of the passed playlist into the playback queue, wiping the existing queue.
These are some things that I am intentionally ignoring until I get round one complete.
- Mounts/neighbour stuff
- Partitions
- Stickers
- Client-to-client communication