#!/bin/python2 # only using python2 because mutagen from __future__ import print_function import os import os.path import sys import shutil import tempfile import mutagen import mutaext import convert # BUG: doesn't work with my .m4a files? goodexts = ('.mp3', '.m4a', '.flac', '.ogg') matchtags = ['artist', 'album', 'title'] updatabletags = [\ 'albumartist', 'composer', 'comment' \ 'tracknumber', 'discnumber', \ 'genre', 'date', \ ] updatabletags.extend(mutaext.replaygain_tags) updatabletags.extend(mutaext.extra_tags) alltags = list(updatabletags) alltags.extend(matchtags) lament = lambda *args, **kwargs: print(*args, file=sys.stderr, **kwargs) walkfiles = lambda w: (os.path.join(r, f) for r, _, fs in w for f in fs) extof = lambda p: os.path.splitext(p)[1].lower() filterext = lambda ps, es: (p for p in ps if extof(p) in es) def shouldsync(md): rating = md.get('rating') sync = md.get('sync', u'') try: rating = int(rating[0]) except (IndexError, ValueError): pass if sync: sync = sync[0].lower() return sync == 'yes' or type(rating) == int and rating >= 3 and sync != 'no' and sync != 'space' def fixmetadata(md): md['artist'] = md.get('artist', u"Unknown Artist") md['album'] = md.get('album', u"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'] = unicode(fn) def findmatching(haystack, needle): matchme = [needle[t] for t in matchtags] ismatch = lambda hay: [hay[t] for t in matchtags] == matchme for match in (hay for hay in haystack if ismatch(hay)): if match.seen: # TODO: check other tags and filename and such? lament("Warning: duplicate match found:") lament(u"%(title)s by %(artist)s from %(album)s" % locals()) match.seen = True return match def updatemetadata(mdold, mdnew): modified = False for tag in updatabletags: # checking for length b/c sometimes (ID3 genre) exists but empty if tag in mdnew and len(mdnew[tag]): if not tag in mdold or mdnew[tag][0] != mdold[tag][0]: mdold[tag] = mdnew[tag] modified = True elif tag in mdold: del mdold[tag] modified = True return modified def makefilename(md): title = md['title'][0] artist = md['artist'][0] album = md['album'][0] return u"%(artist)s - %(album)s - %(title)s.ogg" % locals() def run(args): if not len(args) in (2, 3): lament("I need a path or two!") return 1 inonly = len(args) == 2 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 lament("Matching...") outdir = args[2] for p in paths(outdir): md = mutagen.File(p, easy=True) match = findmatching(tosync, md) if not match: print("DEL", p) os.remove(p) elif updatemetadata(md, match): print("UPD", p) md.save() for md in tosync: if md.seen: continue print("ADD", md.path) fout = os.path.join(outdir, makefilename(md)) _, ftemp = tempfile.mkstemp() try: convert.ogg(md.path, ftemp) mdnew = mutagen.File(ftemp, easy=True) for tag in alltags: if tag in md: mdnew[tag] = md[tag] mdnew.save() shutil.copy2(ftemp, fout) finally: os.remove(ftemp) return 0 ret = 0 try: ret = run(sys.argv) except KeyboardInterrupt: sys.exit(1) sys.exit(ret)