+#!/bin/zsh
+#
+# Collection of shell functions for manipulating MPD. Functions prefixed with
+# __ are intended for use in these scripts, whereas functions prefixed with
+# "mpd" are intended to be called by users.
+#
+# This file may be sourced by your shell startup scripts in order to load all
+# the functions into the environment, or it may be invoked as an executable.
+# The first argument to the script designates the function, with or without
+# the leading "mpd". Subsequent arguments are passed to the called function.
+#
+# For example, you might run:
+# `source mpdscripts.sh' followed by `mpdlist mycoolsong'
+# or equivalently:
+# `./mpdscripts.sh list mycoolsong'.
+# The latter form is useful when not invoking the scripts from a shell.
+#
+# This version specifies the functions:
+# mpdnext, mpdbetter, mpdlist, mpdalbum, and mpdshow.
+#
+# See the comments above each function for more details.
+#
+# Requires mpc, GNU grep and GNU sed.
+# Works with zsh or bash.
+#
+# Send questions or comments to nbowler@draconx.ca, or find me on IRC as
+# Draconx on irc.freenode.net.
+#
+# Licensed under the terms of the Do What The Fuck You Want To Public License.
+#
+##############################################################################
+
+__mpd_playlist()
+{
+ mpc --format='%position%) %artist% - %title%' playlist
+}
+
+# __mpd_trackid [regexp]
+# With a regular expression as an argument, search the MPD playlist for the
+# track matching the expression and print the index of that track. Otherwise,
+# print the index of the currently playing track.
+__mpd_trackid()
+{
+ local id
+
+ if [ -z "$1" ]; then
+ id=`mpc | sed -n "2{s/.*#//;s/\/.*//p}"`
+ else
+ id=`__mpd_playlist | sed -n \
+ "/${1//\//\\\/}/I{s/^[^[:digit:]]//;s/).*//p;q}"`
+ fi
+
+ [ -z "$id" ] && return 1
+
+ echo "$id"
+ return 0
+}
+
+# __mpd_trackname [id]
+# With a track ID as an argument, print the title of that track. Otherwise,
+# print the title of the currently playing track.
+__mpd_trackname()
+{
+ local title
+
+ if [ -z "$1" ]; then
+ title=`mpc | sed -n "1h;2{g;p;q}"`
+ else
+ title=`__mpd_playlist | sed -n \
+ "/^[^[:digit:]]*$1)/s/^.[0-9]*) //p"`
+ fi
+
+ [ -z "$title" ] && return 1
+
+ echo "$title"
+ return 0
+}
+
+# mpdnext <regexp> [advance]
+# Searches the playlist for a track matching <regexp>, then moves it to the
+# position following the currently playing track. If [advance] is a non-empty
+# string, also run `mpc next' to play the moved track immediately.
+mpdnext()
+{
+ local index target name
+
+ if [ -z "$1" ]; then
+ echo "usage: mpdnext <regexp>"
+ return 1
+ fi
+
+ if ! index=`__mpd_trackid`; then
+ echo "mpdnext: MPD is currently stopped."
+ return 1
+ fi
+
+ if ! target=`__mpd_trackid "$1"`; then
+ echo "mpdnext: target not found."
+ return 1
+ fi
+
+ name=`__mpd_trackname $((target))`
+
+ if [ $target -lt $index ]; then
+ mpc move $target $index
+ elif [ $target -gt $index ]; then
+ mpc move $target $((index+1))
+ else
+ echo "mpdnext: selected the playing track"
+ return 1
+ fi
+
+ echo "mpdnext: $name moved."
+
+ # Play track if desired
+ [ -n "$2" ] && mpc next
+
+ return 0
+}
+
+# mpdbetter [sedscript]
+# Processes the title of the currently playing song through the sed program
+# specified by [sedscript] (or ~/.mpdbetter if not specified). If the
+# resulting title is different, attempt to find it in the playlist and use
+# mpdnext to play it immediately.
+mpdbetter()
+{
+ local title better script="$HOME/.mpdbetter"
+ [ -n "$1" ] && script="$1"
+
+ if ! title=`__mpd_trackname`; then
+ echo "mpdbetter: MPD is currently stopped."
+ return 1
+ fi
+
+ if ! better=`echo "$title" | sed -f "$script"`; then
+ echo "mpdbetter: error in script: \`$script'"
+ return 1
+ fi
+
+ if [ "$better" = "$title" ]; then
+ echo "mpdbetter: nothing to be done."
+ return 1
+ fi
+
+ mpdnext ") $better\$" next
+}
+
+# mpdlist [regexp]
+# Simply search the playlist for tracks matching [regexp], or print the entire
+# playlist if [regexp] is not specified.
+mpdlist()
+{
+ if [ -z "$1" ]; then
+ __mpd_playlist
+ else
+ __mpd_playlist | grep --color=auto -i -- "$1"
+ fi
+}
+
+# mpdshow
+# Display the currently playing track as well as the part of the playlist
+# surrounding that track.
+mpdshow()
+{
+ local index
+
+ if ! index=`__mpd_trackid`; then
+ echo "mpdshow: MPD is currently stopped."
+ return 1
+ fi
+
+ mpc
+ echo
+ __mpd_playlist | grep -C 5 --color=auto "^$index)"
+}
+
+# mpdalbum <regexp>
+# Search the playlist for tracks belonging to an album matching <regexp>.
+# Print those tracks grouped by album and sorted by track number.
+mpdalbum()
+{
+ local current list
+
+ if [ -z "$1" ]; then
+ echo "usage: mpdalbum <regexp>"
+ return 1
+ fi
+
+ # This horrible hack grabs the relevant playlist entries and separates
+ # desired fields with @!@ and !@!.
+ list="`mpc --format $'\n%album%\n%track%\n%artist% - %title%' playlist \
+ | sed -n "
+ /^.[0-9]*) $/{
+ h;n
+ /${1//\//\\\/}/I{
+ x;G;s/$/@!@/
+ N;s/$/!@!/
+ N;s/\n//g
+ p
+ }
+ }
+ "
+ `"
+
+# list="`mpc --format '%album%@!@%track%!@!%artist% - %title%' playlist |\
+# sed -n 'h;s/@!@.*//;'"/${1//\//\\\/}/I{g;p}" | sort -k 2`"
+
+ if [ -z "$list" ]; then
+ echo "mpdalbum: no matches for \`$1'"
+ return 1
+ fi
+
+ echo "$list" | while read i; do
+ local album="`echo $i | sed 's/.[0-9]*) \(.*\)@!@.*/\1/'`"
+ local index="`echo $i | sed 's/[# >]\?\([0-9]*\)).*/\1/'`"
+ local title="`echo $i | sed 's/.*!@!//'`"
+
+ if [ "$album" != "$current" ]; then
+ [ -n "$current" ] && echo
+ echo "$album"
+ current="$album"
+ fi
+
+ echo -e " $index)\t$title"
+ done
+}
+
+# For executable-like invocation...
+
+# It would be nice if we could detect whether we are invoked as an executable
+# and print a usage message if not given any arguments. I have yet to find
+# a clean way to do this, however.
+
+if [ $# -gt 0 ]; then
+ func="mpd${1#mpd}"
+ shift
+ "$func" $@
+fi