#! /usr/bin/env python import sys import os import os.path import re import tagpy import tagpy.mpeg import tagpy.ogg from optparse import OptionParser songs = sys.argv[1:] patterns = [ "${artist:7}-${album:7}/${track}-${title}.${ext}", "${artist}/${album}/${track}-${title}.${ext}", ] mix_pattern = "AAAMIX/${artist:7}-${title}.${ext}" opt_parse = OptionParser(usage="%prog [options] root_path") opt_parse.add_option("-p", "--pattern", dest="pattern", help="Use PATTERN as the rename pattern") opt_parse.add_option("-m", "--use-mix", action="store_true", dest="use_mix", help="Use aaamix directory") opt_parse.add_option("-M", "--no-use-mix", action="store_false", dest="use_mix", help="Do not use aaamix directory") (options, remaining_args) = opt_parse.parse_args() if options.pattern is not None: pattern = patterns[int(options.pattern)] else: for i, p in enumerate(patterns): print i, p print pattern = patterns[int(raw_input("Select pattern: "))] if options.use_mix is not None: use_mix = options.use_mix else: use_mix = raw_input("Use AAAMIX directory? [n]y?") == "y" def canonical_ext(song): if isinstance(song, tagpy.mpeg.File): aprops = song.audioProperties() if aprops is None: raise ValueError, "MPEG track %s has no audio properties" % song.name() return "mp%d" % aprops.layer elif isinstance(song, tagpy.ogg.File): return "ogg" else: raise ValueError, "unknown track type" def is_directory(name): dirstat = os.stat(name) import stat return stat.S_ISDIR(dirstat.st_mode) def force_us_ascii(str): result = "" for i in str: if 32 <= ord(i) < 128: result += chr(ord(i)) else: result += "_" return result def makedirs(name, mode=0777): # stolen and modified from os """makedirs(path [, mode=0777]) Super-mkdir; create a leaf directory and all intermediate ones. Works like mkdir, except that any intermediate path segment (not just the rightmost) will be created if it does not exist. This is recursive. """ head, tail = os.path.split(name) while not tail: head, tail = os.path.split(head) if head and tail and not os.path.exists(head): makedirs(head, mode) try: os.mkdir(name, mode) except OSError: pass def make_fn_suitable(str): return force_us_ascii(str .replace("/", "_") .replace("?", "_") .replace("*", "_") .replace("&", "_") .replace(":", "_") .replace("\"", "_") .replace("\'", "_") .replace("<", "_") .replace(">", "_") ) def expand_pattern(pattern, song): tag = song.tag() if tag is None: return None expr = re.compile(r"\$\{([a-z]+)(\:[0-9]+)?\}") def variable_function(match): meta_id = match.group(1) if meta_id == "artist": result = tag.artist elif meta_id == "album": result = tag.album elif meta_id == "title": result = tag.title elif meta_id == "ext": result = canonical_ext(song) elif meta_id == "track": result = "%02d" % tag.track else: raise ValueError, "invalid variable in pattern: "+meta_id if len(match.groups()) == 2 and match.group(2): result = result[:int(match.group(2)[1:])] return make_fn_suitable(result).strip() return expr.sub(variable_function, pattern) def get_new_dir_and_name(pattern, song): dest_name = expand_pattern(pattern, song) if dest_name is None: return None dest_dir = os.path.dirname(dest_name).lower() dest_basename = os.path.basename(dest_name) return dest_dir, os.path.join(dest_dir, dest_basename) # walk directory tree all_songs = [] for dirpath, dirnames, filenames in os.walk(remaining_args[0]): all_songs += [os.path.join(dirpath, filename) for filename in filenames] # find out all "canonical" renames, i.e. without mix directory dest_dir_counts = {} renames = [] for source_name in all_songs: try: song = tagpy.FileRef(source_name).file() except ValueError: print "WARNING: Not a media file:\n %s" % source_name continue dir_and_name = get_new_dir_and_name(pattern, song) if dir_and_name is not None: dest_dir, dest_name = get_new_dir_and_name(pattern, song) renames.append((source_name, dest_name, dest_dir)) dest_dir_counts[dest_dir] = 1 + dest_dir_counts.setdefault(dest_dir, 0) # judge mix directory and perform moves for source_name, dest_name, dest_dir in renames: if use_mix and dest_dir_counts[dest_dir] == 1: song = tagpy.FileRef(source_name).file() dest_dir, dest_name = get_new_dir_and_name(mix_pattern, song) try: if not is_directory(dest_dir): print "WARNING: %s exists, but is not a directory, skipping\n %s" % \ (dest_dir, dest_name) continue except OSError: makedirs(dest_dir) try: os.rename(source_name, dest_name) except OSError, e: print "Couldn't rename `%s' to `%s': %s" % (source_name, dest_name, e)