Home · Linux · Vim · Programming · Trees · Quests

Linux Music Server

Update - October 2020

I recently discovered iBroadcast. It is the hosted music service I’ve been looking for. Here’s what you get:

That’s kind of all I need. It’s not fancy, it’s not complicated. I love it.

Currently, the service is free while the company presumably decides how to monetize. I’ll take it, but would be happy to pay for this. I currently pay about $10/mo to store my collection in the cloud, so I’d happily pay iBroadcast instead if they could convince me they’d keep my data safe.

The Linux laptop discussed below is still setup but currently just turned off. We’ll see what lies in store for it in the future. I’ve been wanting to try out a BSD but they don’t seem to like VirtualBox, so maybe that.


(Previous Version)

I collect digital music. Years ago, I converted my large CD collection into high quality MP3 (no, I can’t tell the difference), and have digitized the small vinyl collection I’ve got so I can enjoy it, bumps and all, away from home and without fussing with the turntable. I’ve currently got about 90GB of music and have struggled with how to keep it backed up, synchronized across all my devices and available while at home and away.

I use aws-cli to backup the collection to an S3 bucket that maintains a version history to avoid accidental deletions. I can pull that down from any computer I want to install the collection on. For mobile access, I have a script that synchronizes the collection to a 128GB SD card in my phone. As you can imagine, trying to keep 3-5 synchronized copies of a 90GB collection that I’m frequently adding to is a pain. And using up 90GB of storage on so many devices feels unnecessarily wasteful.

When I recently purchased a new Thinkpad T490, I opted for a smaller 256GB SSD as I figured it’s an inexpensive upgrade down the road if I’m feeling squeezed for space. That motivated me to start thinking about how to avoid using up more than 1/3 of my new laptop drive with the music collection.

The plan I hatched was to convert my trusty old Thinkpad E430 into a dedicated music server. After looking at and fairly quickly dismissing Ampache (it has an ugly UI, no mobile support and doesn’t seem to be actively maintained), I landed on Plex Media Server. A coworker mentioned using Plex a while back for his large movie collection, but I hadn’t realized it was also a streaming music server.

While it’s not perfect, Plex just worked out of the box and does a few important things very well:

That last one is key, since the T430 is connected to the stereo amplifier and bookshelf speakers in my office. If I have a browser open to Plex on the T430, I can play music through the amp from a remote computer. It’s kind of like Chromecast, but isn’t.

I opted to support Plex development with a $5/mo subscription, which offers a few minor benefits. My only complaints with Plex is its burying my music server in its own navigation. I have zero interest in the content Plex is trying to push at me, and it takes a few clicks to get to my personal music collection. Easy enough to bookmark, but still, a little obnoxious.

In addition to Plex, I wanted to be able to play music both remotely and through the stereo system via the Linux terminal. For this, I’m using a combination of mpd and sshfs.

mpd is a music server that can be remotely controlled. By running mpd on the music server, I can connect to it with lots of different clients either via an SSH session on the music server or remotely over the network. After trying a few command line mpd clients I landed on mpc for one-off playing and ncmpcpp (took me a while to memorize that alphabet soup) for a more interactive experience.

For playing locally on my laptop, I am using sshfs to mount the music folder on the server to a local folder on my laptop. It’s fast enough to use a modern music player like Lollypop (my favorite Linux GUI player) and of course works great for terminal players. So if I’m using Windows (which I do for work) I can listen to my collection via the Plex web interface. And at home (running Linux) - or even via the Ubuntu WSSL command line at work - I can use the sshfs mount.

I’m still backing up to S3, but the expense of that is wearing on me, so I’m considering just using flash drives.

mpd Setup

mpd is really cool, and the Arch wiki coverage of it is worth a read: https://wiki.archlinux.org/index.php/Music_Player_Daemon

To set things up, just make a copy of the config file and edit as needed. It’s well documented.

$ mkdir ~/.config/mpd
$ cp /usr/share/doc/mpd/mpdconf.example ~/.config/mpd/mpd.conf

Enable and start the service:

$ systemctl --user enable mpd.service
$ systemctl --user start mpd.service

ncmpcpp - use “a” to add current song to playlist

Playlist Synchronization Between mpd and Plex

I was hoping to share playlists between mpd and Plex, even if it took a bit of coding. Unfortunately, Plex stores its playlists in a rather opaque sqlite database. I tried poking around in there and could not find my playlist data. It appears to be using some kind of ORM that creates a highly abstracted schema - an unfortunate design choice. I’m a firm believer that data persistence should be human readable and easily accessible unless there are very good reasons to do otherwise - and I don’t think there are in this case.

After some Googling, I found that there is a limited XML web API in Plex. Though not well documented at all, Plex does point you in the right direction from https://support.plex.tv/articles/201638786-plex-media-server-url-commands/.

From that, I found my local server’s playlist data at: http://[MACHINE_IP]:32400/playlists/[PLAYLIST_ID]/items?X-Plex-Token=[LOGIN_TOKEN].

That got me to a repeatable XML export of a Plex playlist. I then wrote a small ruby script to extract an .m3u style playlist file from the Plex XML format after pulling it down, and then a small shell script to combine and dedupe that list with a playlist managed by mpd.

Here’s the shell script:

# output line count for current playlist
wc -l playlists/favorites.m3u
# make a backup of current playlist
cp playlists/favorites.m3u playlists/favorites.m3u.bak
# invoke ruby script to pull down XML from Plex and convert to m3u
ruby favorites.rb > /tmp/plex.m3u
# combine both playlists into a single file
cat /tmp/plex.m3u  playlists/favorites.m3u >> /tmp/all.m3u
# sort the file (required for deduping)
sort /tmp/all.m3u > /tmp/sorted.m3u
# dedupe the file
uniq /tmp/sorted.m3u > /tmp/uniq.m3u
# re-shuffle lines in the file
shuf /tmp/uniq.m3u > playlists/favorites.m3u
# output the new line count
wc -l playlists/favorites.m3u

And here’s the favorites.rb ruby script:

require 'rexml/document'
# REXML is Ruby's XML parser
include REXML
# Use wget to pull down the playlist XML export
`wget -o /dev/null -O playlists/favorites.xml http://[IP_OMITTED]:32400/playlists/7535/items?X-Plex-Token=[TOKEN_OMITTED]`
# Parse the XML file into a Ruby object
xmlfile = File.new('playlists/favorites.xml')
xmldoc = Document.new(xmlfile)
root = xmldoc.root
# Iterate through the XML nodes, each representing a single playlist entry
root.elements.each("Track/Media/Part") { |part| 
  path = part.attributes['file']
  # remove Plex server specific portion of the path to the file
  path = path.sub('/usr/data/Plex/','')
  # output corrected local path
  puts path 
}

What’s Next

Next step on this is to use the Plex Python API to do a proper two-way sync of playlists. That will be a fun programming project for another day.

I also need to setup dynamic DNS to one of my Route53-hosted domains so I can use sshfs from outside my home network. As I write this during COVID-19 work-from-home, this is not a pressing need.