Skip to content

Managing Music from the Command Line

Okay. I’ve been curious for a while about trying to manage my music from the command line. All the media managers I have used so far have been a bit too bloated for my tastes. Because of this I decided to try out MPD (Music Player Daemon). It looked pretty cool, but I didn’t have a good way to quickly queue up a bunch of songs I wanted to listen to. After trying out a few solutions by others, I wrote two of my own solutions.

The first was a fuzzy matching system, and the second was a “blob” matching system. Both allow me to queue up an entire album by typing something short.

Because I haven’t finished tweaking the first solution, and the second one is working impressively, I’ll be posting about it only.

What You’ll Need:

  • mpd
  • mpc
  • Python 2.6

Here is the code:

?View Code PYTHON
#!/usr/bin/python
from subprocess import Popen, PIPE
import sys
 
class Database:
	def __init__(self, terms=None):
		self.terms = set()
		self.tree = (set(), dict())
		if terms is not None:
			self.populate(terms)
 
	def populate(self, terms):
		for term in terms:
			self.add(term)
 
	def variations(self, word):
		result = set([word])
		if word.startswith("0") and word != "0":
			#This allows "02" to be found as "2"
			result.add(word)
		elif word in frozenset(["i", "ii", "iii"]):
			#This allows "II" to be found as "2"
			result.add(str(len(word)))
		return result
 
	def words(self, term):
		term = term.lower()
		words = set()
		buf = ""
		for char in term:
			if char.isalnum():
				buf += char
			else:
				if buf != "":
					words = words.union(self.variations(buf))
					buf = ""
		if buf != "":
			words = words.union(self.variations(buf))
		return words
 
	def add(self, term):
		self.terms.add(term)
		for word in self.words(term):
			current = self.tree
			for char in word:
				if char not in current[1]:
					current[1][char] = (set(), dict())
				current = current[1][char]
			current[0].add(term)
 
	def search(self, blob, union=False):
		#This is for excluding words from a search
		if "~" in blob:
			#Excludes titles with ANY words from exclude
			blob, remove = blob.lower().split("~")
			mode = True
		elif "#" in blob:
			#Excludes titles with ALL words from exclude
			blob, remove = blob.lower().split("#")
			mode = False
		else:
			remove = None
			blob = blob.lower()
		found = dict()
		current = self.tree
		i = 0
		for char in blob:
			if char in current[1]:
				current = current[1][char]
				i += 1
				if len(current[0]) > 0:
					found[blob[i:]] = current[0]
			else:
				break
		final = set()
		for k, v in found.items():
			if k == "":
				final |= v
			else:
				if union:
					final |= v | self.search(k)
				else:
					final |= v & self.search(k)
		if remove is not None:
			remove = self.search(remove, mode)
			final -= remove
		return final
 
	def __contains__(self, other):
		return other in self.terms
 
#Initialize and populate album database
db = Database()
#with open("/var/lib/mpd/tag_cache") as f:
#	for line in f:
#		if line.startswith("Album: "):
#			album = line[7:-1]
#			if album not in db:
#				db.add(album)
#We'll get the album list from mpc instead of the tag_cache file. I haven't done a benchmark.
p1 = Popen(["mpc", "list", "album"], stdout=PIPE)
p1.wait()
with p1.stdout as f:
	for line in f:
		album = line[:-1]
		if album != "":
			db.add(album)
 
#Parse arguments
if len(sys.argv) == 2:
	searches = [sys.argv[1]]
else:
	print "No album specified. Exiting..."
	sys.exit()
 
#Perform search
r = db.search(searches[0])
r = list(r)
 
#If more than one album is returned, ask the user to choose which one we will play.
if len(r) > 1:
	for i in xrange(len(r)):
		print " " + str(i+1) + ")", r[i]
	r = r[int(raw_input("Enter a number: ")) - 1]
elif len(r) == 1:
	r = r[0]
else:
	print "No albums found. Exiting..."
	sys.exit()
 
print "Playing album '" + r + "'"
 
#Find the tracks in the album
p1 = Popen(['mpc', 'find', 'album', r], stdout=PIPE)
p1.wait()
 
#Clear the current playlist
Popen(['mpc', 'clear'], stdout=PIPE).wait()
 
#Queue up all the tracks in the album
with p1.stdout as f:
	for line in f:
		Popen(['mpc', 'add', line[:-1]], stdout=PIPE).wait()
 
#Let MPC print a list of the tracks we have queued
Popen(['mpc', 'playlist']).wait()
 
#Start playback
Popen(['mpc', 'play'], stdout=PIPE).wait()

Save that as “album” or whatever you would like. Put it in your home directory or wherever you would like. Finally, open ~/.bashrc (for Ubuntu users, at least), and add an alias pointing to the script. I use “alias album=’~/album’”.

Make sure you set the script to executable.

Finally, because I enjoy having my listening history available on last.fm, I also installed lastmp to scrobble songs played with MPD.

Oh yeah. I almost forgot usage instructions. With the setup described above, one could just type “album wolfsrain” to play the Wolf’s Rain OST (the script would prompt you to select either the first or second volume) or “album fiction” to play Yuki Kajiura’s solo album “Fiction”.

I call this album search method a “blob search” because you can type any words from an album title, in any order and without spaces, and still find the album. For instance, both “album wishyou” and “album hereyouwish” will start the playback of Wish You Were Here in my library.

{ 2 } Comments

  1. Vasudev | July 1, 2009 at 21:00:20 | Permalink

    Have you tried MOC(Music on Console player)?

  2. meyermagic | July 1, 2009 at 21:07:24 | Permalink

    Nope. I just looked it up though, it looks pretty cool. I’ll try it out.

Post a Comment

Your email is never published nor shared. Required fields are marked *