#!/bin/python2 # only using python2 because mutagen from __future__ import print_function import os import os.path import sys import shutil import mutagen import mutaext import convert updatabletags = [\ 'albumartist', 'composer', 'comment' \ 'tracknumber', 'discnumber', \ 'genre', 'date', \ ] updatabletags.extend(mutaext.replaygain_tags) updatabletags.extend(mutaext.extra_tags) alltags = list(updatabletags) alltags.extend(['artist', 'album', 'title']) def G(d, k): try: return d[k] except KeyError: return None def walkfiles(walker): for root, _, files in walker: for f in files: yield os.path.join(root, f) def filterext(paths, exts): for p in paths: ext = os.path.splitext(p)[1].lower() if ext in exts: yield p def shouldsync(md): rating = G(md, 'rating') sync = G(md, 'sync') try: rating = int(rating[0]) except: pass if sync: sync = sync[0].lower() else: sync = u'' if sync == u'no' or sync == u'space': return False if sync == u'yes' or sync == u'share': return True if type(rating) == int and rating >= 3: return True return False def fixmetadata(md): if 'artist' not in md: md['artist'] = "Unknown Artist" if 'album' not in md: md['album'] = "Unknown Album" if 'title' not in md: fn = os.path.basename(md.path) fn = os.path.splitext(fn)[0] # TODO: attempt to infer trackNum/discNum from fn md['title'] = fn def findmatching(haystack, needle): # TODO: don't match mismatched lengths (Xing?) artist = G(needle, 'artist') album = G(needle, 'album') title = G(needle, 'title') match = None for hay in haystack: if artist == G(hay, 'artist') \ and album == G(hay, 'album') \ and title == G(hay, 'title'): match = hay if match.seen: # TODO: check other tags and filename and such? print("Warning: duplicate match found:", file=sys.stderr) print("{0} by {1} from {2}".format(artist,album,title), file=sys.stderr) else: match.seen = True break return match def updatemetadata(mdold, mdnew): modified = False # TODO: ensure genre and date work properly (special cases in EasyID3) for tag in updatabletags: if tag in mdnew: if not tag in mdold: #print("not in old:",tag) mdold[tag] = mdnew[tag] elif mdnew[tag][0] != mdold[tag][0]: #print("in old:",tag) mdold[tag] = mdnew[tag] modified = True elif tag in mdold: del mdold[tag] modified = True return modified def makefilename(md): fn = "" title = md['title'][0] artist = md['artist'][0] album = md['album'][0] fn = u"{1} - {2} - {0}".format(title,artist,album) fn += ".ogg" return fn def run(args): if not len(args) in (2, 3): print("I need a path or two!", file=sys.stderr) return 1 inonly = len(args) == 2 # BUG: doesn't work with my .m4a files? goodexts = ('.mp3', '.m4a', '.flac', '.ogg') tosync = [] indir = args[1] paths = lambda dir: filterext(walkfiles(os.walk(dir)), goodexts) for p in paths(indir): md = mutagen.File(p, easy=True) if shouldsync(md): if inonly: print(p) else: # TODO: don't use custom members on external metadata class md.path = p md.seen = False fixmetadata(md) tosync.append(md) if inonly: return 0 print("Matching...", file=sys.stderr) outdir = args[2] for p in paths(outdir): md = mutagen.File(p, easy=True) match = findmatching(tosync, md) if match: if updatemetadata(md, match): print("UPD", p) md.save() else: print("DEL", p) os.remove(p) for md in tosync: if md.seen == False: print("ADD", md.path) outf = os.path.join(outdir, makefilename(md)) #print("TO", outf) temppath = convert.ogg(md.path) mdnew = mutagen.File(temppath, easy=True) for tag in alltags: if tag in md: mdnew[tag] = md[tag] mdnew.save() shutil.move(temppath, outf) return 0 ret = 0 try: ret = run(sys.argv) except KeyboardInterrupt: sys.exit(1) sys.exit(ret)