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:
- Python 2.6
Here is the code:
#!/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: current[char] = (set(), dict()) current = current[char] current.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: current = current[char] i += 1 if len(current) > 0: found[blob[i:]] = current 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] else: print "No album specified. Exiting..." sys.exit() #Perform search r = db.search(searches) 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 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.