mpdpopm

Maintain ratings & playcounts for your mpd server..

This manual corresponds to mpdpopm version 0.2.3.

Table of Contents


1 Introduction

mpdpopm provides a companion daemon to MPD that will maintain play counts, ratings, & last-played timestamps for your music library, along with an associated CLI for talking to the companion daemon. Similar to mpdfav, but written in Rust (which I prefer to Go), it will maintain this information in your sticker database. Along the lines of mpdcron, it will also allow you to keep that information up-to-date in your tags or other data stores by invoking external (user-provided & -configured) commands.


2 Getting Started

NB. The project README contains the reference instructions for mpdpopm installation.


2.1 Prerequisites

mpdpopm is distributed in a number of formats. The prerequisites depend on the distribution format, but in any event you need to have MPD installed & running in order to use mpdpopm. See also Setting-up mpd.

The pre-built binary distribution, the Debian package, and the Arch binary package require no other pre-requisites.

Downloading mpdpopm from crates.io requires the Rust toolchain.

Building from source via the Autotools distribution further requires the Gnu Autools.


2.2 Installing mpdpopm

mpdpopm may be installed in a number of ways. Refer to the project README for the most up-to-date instructions.

  1. Pre-built binaries are available for Linux & MacOS.
  2. mpdpopm can be installed from crates.io.
  3. There is a binary package available for x86_64 Debian-based Linux systems.
  4. There is a binary package available for x86_64 Arch-based Linux systems.
  5. There is an Autotools-based distribution available.

2.3 Setting up mpdpopm

mpdpopm provides two programs:

  1. mppopmd is the companion daemon process to MPD
  2. mppopm is the command-line interface to mppopmd

Both programs make use of the MPD protocol, a simple text-based protocol by which clients can communicate with the MPD daemon and with one another. From the perspective of MPD, mppopm & mppopmd are just new clients. Via this protocol mppopmd will monitor MPD for song playback & note when songs complete; this is how it knows to increment the playcount & update the last played timestamp for each song to which you listen.

Also via the protocol, MPD clients can communicate with one another by registering "channels" and subscribing to them; one client can send a message to a given channel, and all other clients subscribed to that channel will receive that message. In particular, if an MPD client sends the "rating" command to the mppopmd commands channel (the channel name is configurable, but defaults to "unwoundstack.com:commands"), mppopmd will set the rating for the given track.

The mechanism by which mppopmd records this information (i.e play counts, last played and ratings) is MPD stickers. A sticker is a little bit of textual information which clients can attach to songs in the form of a name-value pair. mpdpopm defines a new sticker name (again configurable) for each of these items & udpates the values for each song when & as requested.

Of course, other MPD clients will not, in general, be aware of mppopmd, its command channel, or the stickers it sets: you the user will have to bridge that gap. You could of course just fire-up netcat & start sending commands, but that’s not particularly convenient. That’s where mppopm comes in. It is a small command-line tool for controlling the mppopmd daemon; you could for instance say mppopm set-rating '*****' to rate the current track at five stars. Under the hood, it connects to the MPD daemon, sends a "rating" message to the mppopmd commands channel, and mppopmd, in turn, tells MPD to set the rating sticker for the curren track to 255 (more on the rating system below).


2.3.1 Setting-up mpd

Configuring & setting up MPD is beyond the scope of this manual; see the MPD manual here.

One note, however: in order for mpdpopm to function, MPD must be configured to enable the sticker database. This is done via the sticker_file configuration item. The user as whom mppopmd is run will need access to the file named therein and its parent directories. See here for details.


2.3.2 Setting up mppopmd

mppopmd is the companion daemon to MPD. Once installed, most configuration items have sensible defaults, but there are a few that will need to be customized to the user’s particular taste (mostly having to do with their MPD configuration).

It is recommended that it be run in the foreground initially (using the -F flag) for purposes of trouble-shooting, but after that the user will likely want to run it as a daemon.


2.3.2.1 mppopmd Configuration

The configuration file’s location is specified on the mppopmd command-line (using the -c flag). It takes the form of a LISP S-expression in which each top-level configuration item is a cons cell. LISP comments will be ignored.

General-purpose items:

  • host The network address on which the MPD daemon is listening. It should match bind_to_address in your MPD configuration file. The default value is "localhost".
  • port The TCP port on which the MPD daemon is listening. This should match the port setting in your MPD configuration file. The default value is 6600.
  • local_music_dir The root of your MPD music directory, as seen from this host. This will be used to evaluate the %full-file and %current-file replacement parameters when executing commands (on which more see below).
  • log The file to which the daemon will log when running in the background (if you specify the -F flag, to run it in the foreground, it will log to stdout); no log rotation is provided, so setup logrotate, run it under systemd (see see below) or keep an eye on it so it doesn’t grow too large.
  • commands_chan The name of the MPD channel on which mppopmd shall listen for commands ("unwoundstack.com:commands" by default).

Configuration items related to maintaining play counts & last-played timestamps:

  • playcount_sticker The sticker name mpdpopm will use to store play counts (defaults to "unwoundstack.com:playcount").
  • lastplayed_sticker The sticker name mpdpopm will use to store the last played timestamp (defaults to "unwoundstack.com:lastplayed")
  • played_thresh The percentage of a song’s duration that must be played back in order for it to be considered as "played", expressed as number between 0 & 1 (defaults to 0.6).
  • poll_interval_ms The amount of time, in milliseconds, between polls of MPD by mppopmd to check on playback progress. This is the error bound on detection of song having been “played” in that if a poll is performed the instant before the current song’s playback reaches played_thresh, it may be up to this many milliseconds before mppopmd tries again and belatedly determines that the current track has been “played” (and hence updates the playcount, last played time &c). Since this is not, in general, a time-sensitive operation, the default setting is 5000 (i.e. five seconds) to keep network traffic down.
  • playcount_command An optional name of a program to run when the play count is incremented; this should be an absolute path. Use playcount_command_args to specify arguments to this command. The intent behind this feature is to enable the user to keep other data stores (such as ID3 tags) up-to-date with play count information (on which more see below)
  • playcount_command_args An array of arguments to specify to the playcount command; arguments may contain replacement parameters that will be filled in at the time of execution.

Configuration items related to ratings:

  • rating_sticker The sticker name mppopmd will use to store the rating (defaults to "unwoundstack.com:rating").

    ratings_command An optional name of a program to run when the rating is set; this should be an absolute path. Use ratings_command_args to specify arguments to this command. The intent behind this feature is to enable the user to keep other data stores (such as ID3 tags) up-to-date with play count information (on which more see below).

    ratings_command_args An array of arguments to specify to the ratings command; arguments may contain replacement parameters that will be filled in at the time of execution.

Finally, the configuration for generalized commands is not described here, but rather under Generalized Commands.

mpdpopm ships with a minimal sample configuration file. A more expansive sample appears at the end of this manual (see Sample Configuration File).


2.3.2.2 mppopmd as a Daemon

Once you’ve got your configuration file prepared, you should probably start the daemon in the foreground for ease of trouble-shooting. I suggest starting it with the -v flag ("verbose") the first time, as well (there’s also a -d flag which will produce more copious debug output). You should expect to see something like this:

mppopmd -v -F -c <path to configuration file>
[2020-12-12T15:26:19.620806454-08:00][mppopmd] mppopmd 0.2.2 logging at level Debug.
[2020-12-12T15:26:19.621395828-08:00][mpdpopm] mpdpopm 0.2.2 beginning.
[2020-12-12T15:26:19.621998677-08:00][mpdpopm::clients] Connected 0.22.0.
[2020-12-12T15:26:19.623398521-08:00][mpdpopm::clients] Connected 0.22.0.
[2020-12-12T15:26:19.623874861-08:00][mpdpopm::clients] Sent subscribe message for unwoundstack.com:commands; got `OK
'.
[2020-12-12T15:26:19.623888424-08:00][mpdpopm::clients] Subscribed to unwoundstack.com:commands.
[2020-12-12T15:26:19.624985027-08:00][mpdpopm] selecting...
[2020-12-12T15:26:19.628412738-08:00][mpdpopm] output status is Ok(
    Output {
        status: ExitStatus(
            ExitStatus(
                0,
            ),
        ),
        stdout: "/home/sp1ff\n",
        stderr: "",
    },
)
[2020-12-12T15:26:19.628778521-08:00][mpdpopm] No database update needed
[2020-12-12T15:26:19.628817190-08:00][mpdpopm] No more commands to process.

At this point mppopmd is just sitting around, waiting for something to happen. Bring up your favorite MPD client & start playing a track. That should induce some activity:

[2020-12-12T15:26:29.522581696-08:00][mpdpopm::clients] Sent idle message; got `changed: player
OK
'.
[2020-12-12T15:26:29.522756287-08:00][mpdpopm] subsystem Player changed
[2020-12-12T15:26:29.527064915-08:00][mpdpopm::playcounts] Updating status: 0.000% complete.
...
[2020-12-12T15:28:19.653519123-08:00][mpdpopm::playcounts] Updating status: 60.698% complete.
[2020-12-12T15:28:19.653569350-08:00][mpdpopm::playcounts] Increment play count for 'M/Miles Davis - Boplicity.mp3' (songid: 262) at 0.6069790770994554 played.
[2020-12-12T15:28:19.661696678-08:00][mpdpopm::clients] Sent message `sticker get song "M/Miles Davis - Boplicity.mp3" "unwoundstack.com:playcount"'; got `sticker: unwoundstack.com:playcount=3
OK
'
[2020-12-12T15:28:19.661743547-08:00][mpdpopm::playcounts] Current PC is 3.
[2020-12-12T15:28:19.770956673-08:00][mpdpopm::clients] Sent `sticker set song "M/Miles Davis - Boplicity.mp3" "unwoundstack.com:lastplayed" "1607815699"'; got `OK
'
[2020-12-12T15:28:19.868244915-08:00][mpdpopm::clients] Sent `sticker set song "M/Miles Davis - Boplicity.mp3" "unwoundstack.com:playcount" "4"'; got `OK
'
...

In this example, mppopmd noticed that "Boplicity" by Miles Davis started playing; when it was played 60% of the way through, the daemon updated the play count from 3 to 4 & set the last played timestamp.

Once things seem to be working, you might consider removing the -F flag & running mppopmd as a proper daemon. mpdopm ships with a sample systemd unit.


2.3.3 Setting up mppopm

On it’s own, mppopmd will happily sit in the background, monitoring playback & keeping play counts & last-played timestamps up-to-date. In order to rate a song, or indeed to do anything with the information that’s being accumulated, the user will need to induce their favorite MPD client to send the relevant commands (see mppopmd Commands) to the mppopmd commands channel (see mppopmd Configuration), or fire-up something like netcat and start entering the commands manually.

Since neither of those options are likely to be convenient, mpdpopm ships with a small, purpose-built MPD client: mppopm (see mppopm Commands).


2.3.3.1 mppopm Configuration

The mppopm CLI needs to know where to find the MPD daemon, as well as the names of the mpdpopm commands channel & sticker names. These configuration items all have default values. They may also be specified on the command line, a configuration file, or an environment variable. The order of presendence is:

  1. command-line arguments
  2. the configuration file
  3. environment

The configuration file defaults to ~/.mppopm, but may be specified on the command line. Like that of mppopmd, it is a LISP S-expression, where each item is a cons cell.

The mppopm configuration items:

  • host: The host on which MPD is listening; defaults to “localhost”, command-line option -H, environment variable MPD_HOST
  • port: The port at which MPD is listening; defaults to 6600, command-line option -p, environment variable MPD_PORT
  • commands_chan: The commands channel to which your mppopmd daemon is subscribed (i.e. should match the commands_chan setting in your daemon’s configuration); defaults to “unwoundstack.com:commands”. No command-line option or environment variable.
  • playcount_sticker: The sticker name being used by your mppopmd daemon to store playcounts (i.e. should match the daemon’s configuration item of the same name); defaults to “unwoundstack.com:playcount”. No command-line option or environment variable.
  • lastplayed_sticker: The sticker name being used by your mppopmd daemon to store last-played timestamps (i.e. should match the daemon’s configuration item of the same name); defaults to “unwoundstack.com:lastplayed”. No command-line option or environment variable.
  • rating_sticker: The sticker name being used by your mppopmd daemon to store ratings (i.e. should match the daemon’s configuration item of the same name); defaults to “unwoundstack.com:rating. No command-line option or environment variable.

3 The commands


3.1 mppopm Commands

mppopm supports a number of sub-commands for carrying out various operations related to mpdpopm. You can always see the complete list of sub-commands your particular build of mppopm supports by saying mppopm --help, and get detailed help on a particular sub-command by saying mppopm SUB-COMMAND --help.


3.1.1 mppopm Rating Commands

mppopm can be used to set & retrieve ratings for your music library. It can also be used to filter on the basis of rating mppopm Filter Commands.

mppopm get-rating will retrieve the current song’s rating & print it on stdout. mppopm get-rating URI will interpret “URI” as an MPD song URI & display it’s rating on stdout. mppopm get-rating URI... will do the same for multiple songs. Ratings are expressed as an integer between 0 & 255 inclusive, with the convention that a rating of zero denotes “un-rated”.

mppopm set-rating ARG will set the current song’s rating to “ARG”. “ARG” may be an integer between 0 & 255, or it may be expressed as one to five “stars”: “*” deontes one star, “**” two stars and so forth. “Stars” are mapped to numeric ratings as per Winamp:

*

1

**

64

***

128

****

196

*****

255

Finally, mppopm set-rating ARG SONG will interpret “SONG” as a song URI & set that song’s rating to “ARG”.


3.1.2 mppopm Play Count Commands

mppopm get-pc will print the play count of the current song on stdout. mppopm get-pc ARG will interpret “ARG” as a song URI & print that song’s play count to stdout. mppopm get-pc ARG... will print the play count for each argument on stdout.

mppopm set-pc ARG will set the play count for the current song to “ARG”, where “ARG” is an unsigned integer. mppopm set-pc ARG SONG will set the play count for “SONG” to “ARG”.


3.1.3 mppopm Last Played Commands

mppopm get-lp will print the last played time of the current song in seconds since Unix epoch on stdout. mppopm get-lp ARG will interpret “ARG” as a song URI & print that song’s last played timestamp to stdout. mppopm get-lp ARG... will print the last played timestamp for each argument on stdout.

mppopm set-lp ARG will set the last played timestamp for the current song to “ARG”, where “ARG” is an unsigned integer interpreted as seconds since Unix epoch. mppopm set-lp ARG SONG will set the last played timestamp for “SONG” to “ARG”.


3.1.4 mppopm Filter Commands

Having accumulated play counts, ratings &c in one’s sticker database one can now begin acting on that information. For instance, here is a bash shell script to add all un-rated songs to the play queue:

exec 3<>/dev/tcp/localhost/6600;
head -1 <&3

declare -A rated
last_file=""
echo "sticker find song \"\" unwoundstack.com:rating" >&3
while true; do
    read -r -u 3 line
    case $line in
        OK*)
            break;;
        ACK*)
            echo "$line" >&2;
            exit 1;;
        file:*)
            last_file="${line:6}";;
        sticker:*)
            rated["$last_file"]="${line:33}";
            last_file="";;
        *)
            echo "error: $line" >&2;
            exit 1;;
    esac
done

exec 4<>/dev/tcp/localhost/6600;
head -1 <&4
echo "command_list_begin" >&4

echo "listall" >&3
while true; do
    read -r -u 3 line
    case $line in
        OK*)
            break;;
        ACK*)
            echo "$line" >&2;
            exit 1;;
        file:*)
            if test -z "${rated[${line:6}]}" -o "${rated[${line:6}]}" == "0"; then
                echo "adding ${line:6}"
                echo "add \"${line:6}\"" >&4
            fi;;
    esac
done

exec 3<&-
echo "command_list_end" >&4
head -1 <&4
exec 4<&-

In search of a more ergonomic way to make use of this information, mpdpopm now has support for filters. Filters are an MPD feature for searching one’s song database; one can select songs for various operations by filters such as “(artist =~ ’pogues’)”, for instance. mpdpopm’s contribution is to extend the MPD filter syntax to add ratings, play counts & last-played timestamps (“rating”, “playcount” & “lastplayed” resp.) to the filter syntax.

The first two filter-related commands added to mppopm are searchadd and findadd. Each accepts a filter as an argument and adds the selected songs to the the play queue. The only difference is that searchadd works case-insensitively & findadd respects case.

Consequently, the shell script above can now be replaced by the command mppopm findadd ``(rating == 0)''. NB. filter syntax requires textual values to be quoted, so you’ll need to escape them for the shell. For instance, to select songs with an artist like “pogues”, the filter syntax would be (artist =~ “pogues”), so to specify this on the command line, you would have to escape the double quotes: mppopm searchadd ``(artist =~ \"pogues\")''.

See below for a detailed description of filter syntax & quoting issues.


3.1.4.1 mppopm Filter Syntax

The mpdpopm filter syntax is a (small) superset of the MPD syntax. The following is a grammar for filter expressions in modified BNF:

<expr> ::= <term> | <negation> | <conjunction> | <disjunction>
           
<term> ::= "(" <comparison> ")"

<negation> ::= "(" "!" <expr> ")"

<conjunction> ::= "(" <conj-list> ")"

<conj-list> ::= <expr> "AND" <expr> |
                <expr> "AND" <conj-list>

<disjunction> ::= "(" <disj-list> ")"

<disj-list> ::= <expr> "OR" <expr> |
                <expr> "OR" <disj-list>

<comparison> ::= <tag> <op> <qtext>                             |
                 "file" "==" <qtext>                            |
                 "base" <qtext>                                 |
                 "modified-since" <qtext>                       |
                 "AudioFormat" <audio-format-op> <audio-format> |
                 <mpdpopm-numeric-tag> <numeric-op> <number>    |
                 "lastplayed" <numeric-op> <date-or-epoch>

<tag> ::= "artist" | "artistsort" | "album" | "albumsort" |
          "albumartist" | "albumartistsort" | "title" | "track" |
          "name" | "genre" | "date" | "originaldate" | "composer" |
          "performer" | "conductor" | "work | "grouping" | "comment" |
          "disc" | "label" | "musicbrainz_artistid" |
          "musicbrainz_albumid" | "musicbrainz_albumartistid" |
          "musicbrainz_trackid" | "musicbrainz_releasetrackid" |
          "musicbrainz_workid"

<op> ::= "==" | "!=" | "contains" | "=~" | "!~"

<audio-format-op> ::= "==" | "=~"

<audio-format> ::= "'" <samplerate>:<bits>:<channels> "'"

<mpdpopm-numeric-tag> ::= "rating" | "playcount"

<numeric-op> ::= "==" | "!=" | "<" | "<=" | ">" | ">="

<number> ::= [0-9]+

<qtext> ::= text enclosed in either single- or double-quotes; if the text
            contains single- or double-quotes, or backslashes, they shall
            be backslash-escaped

<date-or-epoch> ::= either <number> or a single- or double-quoted ISO
                    8601 timestamp

In the above grammar, the non-terminals <samplerate>, <bits>, and <channels> are undefined; please see the MPD manual for details on how to specify them.

Note that tags are matched without regard to case; for consistency’s sake, mpdpopm-defined attributes are also matched case-insensitively.

For a detailed description of how tags are matched, also please see the MPD manual, since mpdpopm delegates the evaluation of all terms already supported by MPD to thereto.


3.1.4.2 mppopm Filter Quoting

The non-terminal qtext appears frequently in the mpdpopm filter grammar (see mppopm Filter Syntax). These textual tokens are required to be enclosed in quotation marks, either by single-quote or a double-quote characters (ASCII characters 39 or 34, respectively; cf. ExpectQuoted in the MPD codebase) This brings up the question of how to represent single- or double-quotes inside the quoted text. MPD employs the time-honored tradition of “escaping” them with a backslash character (ASCII character 92). This then implies that backslashes in the quoted text must themselves be backslash-escaped.

For example, to test against the text

foo\bar''

using double-quotes, we would enclose the text in " characters, and backslash-escape any ", ', and \ characters therein, to obtain

"foo\\bar\""

We could also choose to quote the text using single-quotes in the same manner:

'foo\\bar\"'

Tragically, to use this text in a filter in the shell with mppopm, we will need to again quote the text in order to protect special characters therein from the shell.

Let us suppose we had songs by the pathologically named artist foo\bar”. A properly quoted filter expression would be:

(artist == "foo\\bar\"")

To invoke this on the command line, however, we would need to quote the filter itself (to make it into a single argument for mppopm), which would require quoting the entire filter:

mppopm findadd "(artist =~ \"foo\\\\bar\\\"\")"

In this particular case, quoting the filter using single-quotes might be simpler

mppopm findadd '(artist == "foo\\bar\"")'

If the artist name contained a single-quote, e.g.

foo'bar

We could quote it using "s

(artist =~ "foo\'bar")

but to express this in the shell we would need to protect the single-quote from bash:

mppopm findadd '(artist =~ "foo\'"'"'bar")'

Quoting the artist name using 's, and protecting that from the shell is left as an exercise for the reader.


3.2 mppopmd Commands

This section discusses mppopmd command syntax & will be of interest only to authors of MPD clients that want to communicate with mppopmd.

Clients can invoke mppopmd commands via the sendmessage MPD command to the mppopmd commands channel, e.g.

$>: nc localhost 6600
OK MPD 0.22.0
sendmessage unwoundstack.com:commands "rate 255"
OK

3.2.1 Quoting

The sendmessage command takes two string arguments: the channel & the message. Both must be quoted according to the rules of the MPD protocol. Per the MPD manual, “If arguments contain spaces, they should be surrounded by double quotation marks”.

More generally, in the MPD protocol:

  1. an un-quoted token may contain any printable ASCII character except space, tab, ' & "
  2. to include spaces, tabs, '-s or "-s, the token must be enclosed in "-s, and any "-s or \-s therein must be backslash escaped. NB. 's need not be escaped, if they occur within a quoted string (although doing so is harmless)

Since mppopmd commands generally take arguments, they will contain spaces, and hence need to be quoted. For instance, the “rate” command above takes at least one argument (the rating) and so the text “rate 255” had to be enclosed in double-quotes.

Commands that take filters as arguments will not only contain spaces, but also "s and/or 's and possible backslashes, so quoting will be more involved. Cf. libmpdclient for a reference implemenation of the quoting algorithm.

As a worked example, suppose we wanted to send the findadd command with the (filter) argument of:

(artist =~ 'foo\'bar')

The filter itself needs to be quoted to make it a single parameter:

"(artist =~ \'foo\\\'bar\')"

Giving us a command of

findadd "(artist =~ \'foo\\\'bar\')"

This contains spaces, so we wrap it in double-quotes and escape all special characters therein, giving us the final message as it would appear on the wire:

sendmessage unwoundstack.com:commands "findadd \"(artist =~ \\\'foo\\\\\\\'bar\\\')\""

See also here for the MPD parsing logic.


3.2.2 Supported Commands

mppopmd supports the following commands:

  • rate: “rate RATING( TRACK)?” set the rating to RATING for TRACK if it is given, or the current track if it is not. If TRACK is given, it will be interpreted as a song URI.
  • setpc: “setpc PC( TRACK)?” set the play count to PC for TRACK if it is given, or the current track if it is not. If TRACK is given, it will be interpreted as a song URI.
  • setlp: “setlp LP( TRACK)?” set the last-played timestamp to LP for TRACK if it is given, or the current track if it is not. If TRACK is given, it will be interpreted as a song URI.
  • findadd: “findadd FILTER” apply FILTER in a case-sensitive manner and add all matching songs to the play queue
  • searchadd: “searchadd FILTER” apply FILTER in a case-insensitive manner and add all matching songs to the play queue

4 Advanced Usage


4.1 Keeping Your Tags Up-to-date

It may be that the information managed by mpdpopm is replicated in other locations. For instance, the author has play counts also recorded in the PCNT & POPM frames of the ID3 tags attached to his music files, and ratings in the POPM frame, as well. For that reason, mppopmd has the ability to run arbitrary commands after updating stickers. This feature was developed for the purpose of keeping ID3 tags up-to-date, but one could do anything (pop up a notification, update a database &c).

The operations of rating a song and of updating its play count (and associated last played timestamp) may be configured to run an arbitrary program whenever they are carried out. The two salient options are rating_command and playcount_command, respectively. If present in the mppopmd configuration, they should be the absolute path to the program to be executed.

Each has a partner option specifying the parameters that will be supplied to the program when it is run (rating_command_args and playcount_command_args, respectively). These options shall be lists of command-line arguments to be provided, specified as strings. Each may specify an argument of “%full-file” which will, on command execution, be expanded by mppopmd to the absolute path of the song being operated upon.

rating_command_args may also make use of the “%rating” replacement parameter which will be expanded to the rating being applied (expressed as an integer between 0 & 255 inclusive expressed in base 10).

playcount_command_args may also employ the “%playcount” replacement parameter which will be the new play count (again expressed as an integer in base 10).

For example, the author has written an ID3 tagging utility called scribbu which he uses to keep his ID3 tags up-to-date. The following two lines, placed in the mppopmd configuration file, will cause it to invoke the command /home/sp1ff/bin/scribbu popm -a -A -b -o sp1ff@pobox.com -C %playcount %full-file every time it updates the play count sticker:

(playcount_command . "/home/sp1ff/bin/scribbu")
(playcount_command_args . ("popm" "-a" "-A" "-b" "-o" "sp1ff@pobox.com" "-C" "%playcount" "%full-file"))

The tokens %playcount & %full-file will be replaced with the new play count and the absolute path to the file backing the current song, respectively.


4.2 Generalized Commands

The idea of executing arbitrary commands proved useful enough that mpdpopm generalized it: the user can configure arbitrary server-side commands in response to messages received on the mppopmd commands channel. As an example, an entry in the configuration file like this:

  (gen_cmds .
            (((name . "set-genre")
              (formal_parameters . (Literal Track))
              (default_after . 1)
              (cmd . "/home/sp1ff/bin/scribbu")
              (args . ("genre" "-a" "-C" "-g" "%1" "%full-file"))
              (update . TrackOnly)))

will define a new command “set-genre”, with two parameters, the second of which can be omitted (it will default to the current track). When =mppopmd= receives this command (i.e. when a client says something like:

sendmessage unwoundstack.com:commands "set-genre Rock"

mppopmd will invoke scribbu like so:

/home/sp1ff/scribbu genre -a -C -g Rock "${music-dir}/${song URI}"

where the "psuedo-variables" music-dir and song URI above will be replaced with the configured music directory and the current song’s URI.

The configuration is perforce more complex because we have to, at configuration-time, define a mapping between the actual parameters supplied by a client in the message to “unwoundstack.com:commands” and the replacement parameters used in the command arguments. The command’s replacement parameters are defined by a simple list, given in formal_parameters, of parameter types. At this time, there are only two formal parameter types:

  1. Literal: the actual parameter shall be copied verbatim into the replacement parameters, under the name “%i” where i is the one-based index of this formal parameter, expressed in base ten
  2. Track: the actual parameter will be interpreted as a song URI; it again may be referred in the replacement parameters as “%i”. Only one Track argument may appear in the list of formal parameters.

Actual parameters after index default_after (counting from one) are optional; if not specified the replacement parameter will take the default value for its type (the empty string for literals, the currently playing song’s URI for tracks).

Two additional parameters are available:

  1. current-file:: the absolute path to the currently playing track (if any)
  2. full-file :: if the list of formal parameters contains a Track argument in slot i, the actual parameter will be interpreted as a song URI, “%i” will return the absolute path to that file (i.e. with music_dir prepended to the actual argument), as will %full-file

Finally, some commands (such as set-genre, above) may change your music collection in such a way as to necessitate an MPD database update after they complete. The update configuration item governs that: it may be set to "NoUpdate", "TrackOnly", or "FullDatabase" to indicate that this command will require no update, an update to the song named by the Track parameter only, or the full database, respectively.


5 Sample Configuration File

((log . "/home/mgh/var/log/mppopmd.log")
 (host . "192.168.1.6")
 (port . 6600)
 (local_music_dir . "/mnt/Took-Hall/mp3")
 (playcount_sticker . "unwoundstack.com:playcount")
 (lastplayed_sticker . "unwoundstack.com:lastplayed")
 (played_thresh . 0.6)
 (poll_interval_ms . 5000)
 (playcount_command . "/usr/local/bin/scribbu")
 (playcount_command_args . ("popm" "-v" "-a" "-A" "-b" "-o" "sp1ff@pobox.com" "-C" "%playcount" "%full-file"))
 (commands_chan . "unwoundstack.com:commands")
 (rating_sticker . "unwoundstack.com:rating")
 (ratings_command . "/usr/local/bin/scribbu")
 (ratings_command_args . ("popm" "-v" "-a" "-A" "-b" "-o" "sp1ff@pobox.com" "-r" "%rating" "%full-file"))
 (gen_cmds .
           (((name . "set-genre")
             (formal_parameters . (Literal Track))
             (default_after . 1)
             (cmd . "/usr/local/bin/scribbu")
             (args . ("genre" "-a" "-C" "-g" "%1" "%full-file"))
             (update . TrackOnly))
            ((name . "set-xtag")
             (formal_parameters . (Literal Track))
             (default_after . 1)
             (cmd . "/usr/local/bin/scribbu")
             (args . ("xtag" "-a" "-o" "sp1ff@pobox.com" "-T" "%1" "%full-file"))
             (update . TrackOnly))
            ((name . "merge-xtag")
             (formal_parameters . (Literal Track))
             (default_after . 1)
             (cmd . "/usr/local/bin/scribbu")
             (args . ("xtag" "-m" "-o" "sp1ff@pobox.com" "-T" "%1" "%full-file"))
             (update . TrackOnly)))))


Index