#!/usr/bin/python """ NAME playlist.py: Output album entries matching category criteria SYNOPSIS playlist.py [-a] [-x cat[,cat ...]] [-l] [-f album-list] [-d dir] [-U] [-n] [-c cat[,cat ...]] [cat cat ...] Switches: -a output all album entries -x exclude categories -l list all categories defined in album-list file -f read albums and categories from album-list file; default is album.list in the working directory -d path location for all album entry names; default is /rep/music/mp3 -U update album list with any new directories found in the mp3 directory, as specified by the -d switch -n don't prompt for categories when adding new albums via the -U switch; a default category will be assigned (NEW). -c use category list as default when adding new albums via the -U switch One more more categories may be provided. Categories are implicitly or'ed together. To 'and' categories (i.e select those albums that have been tagged with all the categories given, use the plus (+) character to join categories. DESCRIPTION playlist.py is driven by the album-list file, which consists of album directory names, one per line, followed by a comma-separated list of assigned categories. Categories are separated from the album directory name by a colon. For example: various_-_stuff:prog,rock The program will return a list of album directories which match the desired catagories, each album directory prefixed by the -d dir argument (or /rep/music/mp3 if -d dir is not given). This list is designed to be used by find in order to create a playlist of song files. The -x argument may be used to exclude certain categories, e.g. "playlist.py -a -x rock" will omit rock genre music from an otherwise complete playlist. As another example, "playlist.py -x funk jazz" will return all jazz music that is not also categorised as funk. If -U is specified, any playlist options and categories are ignored. By default, the -U switch will cause playlist.py to prompt for the category to assign for each new album located in the directory identified by the -d switch. This prompting may be suppressed using the -n switch, in which case new entries are given the category NEW. This default may be modified by providing a preferred default category (or list) using -c. EXAMPLES Generate complete playlist: find `playlist.py -a` -type f -name "*.mp3" -print >playlist Generate 'pure' jazz playlist: find `playlist.py -x rock,funk,world jazz` \ -type f -name "*.mp3" -print >jazz.pl Generate jazz rock playlist: find `playlist.py -x world,funk jazz+rock` \ -type f -name "*.mp3" -print >jazzrock.pl Update album.list file with new album entries in the mp3 directory, do not prompt for categories but assign the category of "rock": python playlist.py -U -n -c rock """ import sys import getopt import os def build_lists(file_name): "Build and return dictionary of category lists." meta = dict() try: for line in open(file_name): toks = line.strip().split(":") cats = toks[1].split(",") for cat in cats: if cat in meta.keys(): meta[cat].append(toks[0]) else: meta[cat] = [toks[0],] except IOError,e: print >>sys.stderr,"%s: unable to process album file: %s" %\ (sys.argv[0],e) sys.exit(1) return meta def read_album_list(file_name): "Build in-memory version of album-list file." albums = dict() try: for line in open(file_name): toks = line.strip().split(":") albums[toks[0]] = toks[1] except IOError,e: print >>sys.stderr,"%s: unable to process album file: %s" %\ (sys.argv[0],e) sys.exit(1) return albums def write_album_list(albums,file_name): "Write in-memory version of album-list to file." f = open(file_name,"w") for album in sorted(albums.keys()): f.write("%s:%s\n"%(album,albums[album])) f.close() return def add_from_dir(albums,album_dir,ask,default_cat): "Add categories to new directory entries; prompt user if requested." count = 0 dirs = os.listdir(album_dir) for dir in dirs: if dir not in albums: if ask: albums[dir] = get_input("%s: "%(dir,)) else: albums[dir] = default_cat count += 1 return count def get_input(prompt): "Get category from user." try: cat = raw_input(prompt) if cat == "q": sys.exit(1) except EOFError: print >>sys.stderr,"End of file reading from stdin." sys.exit(1) return cat def parse_catands(album_cats,cat_string): "Return sequence of albums that match anded categories." cats = cat_string.split('+') if len(cats) <= 1: print >>sys.stderr,"%s: '%s' is not a catand - internal error." % \ (sys.argv[0],cat_string) sys.exit(1) cat = cats[0] try: s = set(album_cats[cat]) for cat in cats[1:]: s = s.intersection(set(album_cats[cat])) except KeyError: print >>sys.stderr,"%s: no such category as %s"% \ (sys.argv[0],cat) sys.exit(1) return s def process_cats(meta_list,cats,exclude_list,root_dir,all): "Generate list of albums as determined by arguments." names = dict() if all: for albums in meta_list.values(): for album in albums: names[album] = 0 else: # split category arg list into two: those with # ands (+) and ors (implicit) catands = list() cators = list() for cat in cats: if cat.find('+') > 0: catands.append(cat) else: cators.append(cat) # get all anded categories first for catand in catands: k = parse_catands(meta_list,catand) for i in k: names[i] = 0 try: for cat in cators: for album in meta_list[cat]: names[album] = 0 if exclude_list: excludes = exclude_list.strip().split(",") for cat in excludes: for album in meta_list[cat]: try: del names[album] except: continue except KeyError: print >>sys.stderr,"%s: no such category as %s" % (sys.argv[0],cat) sys.exit(1) for album in names.keys(): print "%s/%s"%(root_dir,album) return ################################################################### # program starts here # ################################################################### ls = False all = False album_file = "album.list" exclude_list = None root_dir = "/rep/music/mp3" update_list = False ask = True default_new = "NEW" try: opts,args = getopt.getopt(sys.argv[1:],'arx:lfUnc:') for o,v in opts: if o == '-l': ls = True elif o == '-a': all = True elif o == '-f': album_file = v elif o == '-x': exclude_list = v elif o == '-d': root_dir = v elif o == "-U": update_list = True elif o == "-n": ask = False elif o == "-c": default_new = v except getopt.GetoptError,e: print >>sys.stderr,"%s: illegal argument -%s" % (sys.argv[0],e.opt) sys.exit(1) # if update of album list file required, that's all we'll do if update_list: albums = read_album_list(album_file) added = add_from_dir(albums,root_dir,ask,default_new) write_album_list(albums,album_file) print "Added",added,"albums." else: # read in album file and build category lists meta_list = build_lists(album_file) # if a list of the categories desired, that's it. if ls: for cat in meta_list.keys(): print cat else: process_cats(meta_list,args,exclude_list,root_dir,all)