I may have mentioned before: I'm not very good at listening to music.  Or rather, I'm not very good at choosing what to put on.

This is partly due to having a large collection and mainly due to indecisiveness.  It is compounded quite heavily by my media set-up, which doesn't make life easy for me to browse and choose.

Today, we'll have a look at solving this heinous First World Problem by tackling it with laziness.  Or rather, the pursuit of laziness.   

The ultimate aim is to be able to choose music to listen to from my phone with as little effort as possible.

My music is played from my little headless media server connected to an amp.  It uses a lovely little program called MPD - Music Player Daemon - which is designed perfectly for use on headless computers.  It can be remote-controlled from anywhere, and I use a little phone app to do all the usual things, including building playlists.  This bit is a bit cumbersome though.

You may have come across a nice little music player before called "Plait".  It is a very simple contraption which, when given a search-word, creates a random playlist including all tracks which mention that word anywhere in their name or path.  So, you can search by Album, Artist, Track etc - if you've got your music collection properly sorted.  For sorting it nicely and almost automatically, I would recommend "MusicBrainz Picard".  But that's a whole other topic.

So today, we're going to integrate the ideas of Plait in to MPD - so that from my phone, I can create a "random" playlist which MPD will let me listen to.

MPD playlists...

First off, we need to know where and how MPD stores its playlists - what format are they in?  Quite simply, they are in /var/lib/mpd/playlists and are m3u format.  This is nice, because m3u is pretty flexible and forgiving, and lets you simply pop file paths in, one line at a time.  The only caveat is that the paths must be relative to your configured "music root" in MPD.  Mine is /mnt/bulk/media/audio.

Let's make an index of all the music and try to build an MPD playlist.

For simplicity, let's put the index with mpd.  Later on, we'll worry about a better place and file permissions as we build this in to something useable.  For now:

find /mnt/bulk/media/audio/Albums -type f -name "*.mp3" -or -name "*.ogg" -or -name "*.flac" > /var/lib/mpd/musicIndex.m3u

Now, let's create a random playlist from a magic word and see if it plays:

grep -i vibert /var/lib/mpd/musicIndex.m3u | sort -R | head -n 50 > /var/lib/mpd/playlists/random.m3u

This will find all my Vibert tracks, give them a quick shuffle and pop the first 50 in to our playlist.  Now in your MPD client, simply select the "random" playlist in the list of "server playlists".  

So far so good - now we know it is do-able.  Time to automate.


Re-indexing on demand...

You may, quite frequently, add new music to your collection, so musicIndex.m3u will need rebuilding.  But you only want to rebuild it when it changes, not on some kind of schedule.  For this, there's a very useful tool we can use called "incron".

Incron is designed to be a little like "cron", except instead of running a task at a certain time, it runs it when certain filesystem events occur.  Install it from the "incron" debian package and have a look at the manual page:

apt-get install incron

man 5 incrontab

Let's configure an incron watcher.  As root, edit /etc/incron.d/mpdReindex, put in this line and save:

/mnt/bulk/media/audio/Albums IN_CREATE /usr/local/bin/mpdReindex

This creates a watcher for files and directories being created against the music directory, and will run our reindexer script for each change.  Let's have a look at the script:

#!/bin/bash

set -e


# Reindex mpd/plait 

pid=$$

kill $(pgrep mpdReindex | grep -v $pid) || /bin/true

if [ -z "$1" ]; then

 sleep 60

fi


find /mnt/bulk/media/audio -type f -name "*.mp3" -or -name "*.ogg" -or -name "*.flac" -or -name "*.m4a" > /var/lib/mpd/musicIndex.m3u


# Tell MPD to update its DB

mpc update

This is simply the same "find" command to build the playlist as above, but with a small hack: it will first kill any other processes of the same name, excluding our own PID.  Then it waits for 60 seconds.  This means that it should run a single time just after the last file / directory is created.  It's a bit hacky and not 100% guaranteed to work if your audio files are really huge and take over a minute each to copy, but it will suffice for now.

What about triggering the actual Plait-style playlist generation?  How about a nice little web page?


The Frontend...

<?php
$words = trim(isset($_POST['words'])?$_POST['words']:'');
$rand = trim(isset($_POST['rand'])?$_POST['rand']:'N');

if( $words == '' )
{
    $x = explode(" ", file_get_contents("./playword"));
    $rand = array_shift($x);
    $words = implode(" ", $x);
}

$words = preg_replace("/[^A-Za-z0-9 ]/", "", $words);
$rand = preg_replace("/[^RN]/", "", $rand);
$chk=$rand=='R'?'checked="CHECKED"':'';

$h = <<<EOT
<html>
<head>
<meta name="viewport" content="width=device-width" />
<style type='text/css'>
<!--
input {display: block; width: 80%; margin: 0 auto 5px auto; border:1px solid #888888; color: #888888; padding: 4px;}
body {padding-top: 10px;background: #e8ffe0;
      font-family: helvetica;font-size:12pt;}
label {display: block; margin: 0 auto 0 auto; text-align: center;}
label>input {display: inline; width: auto;vertical-align: middle;}
input[type='submit'] {color: #444444; margin-top: 10px;}
-->
</style>
</head>
<body>
<form method='POST' action=''>
<input type='text' name='words' value='{$words}'/>
<label for='rand'>Randomise playlist?
 <input type='checkbox' name='rand' value='R' {$chk}/>
</label>
<input type='submit' name='doit' value='Go for it'/>
</body>
</html>
EOT;

if( isset($_POST['doit']) )
    file_put_contents("./playword", "{$rand} {$words}");

die($h);
?>

In the same directory, do this:

touch playword; chmod 777 playword

So now from your mobile, you can simply tap in a new word.  But how to build the playlist when this happens?  That's right, more incron magic:

/home/web/playword IN_CLOSE_WRITE /usr/local/bin/mpdNewPlaylist

So when the word file is written and closed, a script runs to do something about it.  It does it as root*, which lets us do what we need to do to the files, but securely because it is decoupled from the webserver.  Notice in the php script above, the preg_replace - that has sanitised the inputs to be only letters, numbers and spaces, so our root-run script should be safe from injection attacks.

* All the script run through incron run as root.  If you are implementing this yourself, I would suggest creating a new user with no shell access and using "sudo" in the incrontabs to run the scripts as this user.  You'll also need to set permissions appropriately on the files the scripts need to touch.  In the interest of clarity, I have left this out of this howto.


Playlist creation...

Now for one last script to create the playlist... /usr/local/bin/mpdNewPlaylist.  This is quite a simple one: It is pretty much just the grep from earlier, with some sugar-coating:

#!/bin/bash

# Build the new playlist - randomise if requested

f=$(cat /home/web/playword)

rand=$(echo $f|cut -d' ' -f1);

word=$(echo $f | cut -d' ' -f2- | sed -r 's/[^A-Za-z0-9]//');

out="/var/lib/mpd/playlists/random.m3u"

idx="/var/lib/mpd/musicIndex.m3u"

opts='';

if [ "$rand" = 'R' ]; then

 opts="-R"

fi

grep -i "$word" "$idx" | sort $opts > "$out"


# Load it in and gogogo!

mpc clear

mpc load random 

mpc play

And the cherry on the cake - use mpc to clear the current playlist, load the new one and set it going!

Enjoy.







...Click for More
Article
incron
IT
MPD
Music
Plait