#!/bin/python2 # only using python2 because mutagen from __future__ import print_function import os import os.path import sys import mutagen import mutagen.id3 import mutagen.easyid3 def popm_any(id3): for k, v in id3.iteritems(): if k.startswith('POPM'): return v def byte2rating(b): if b >= 224: return 5 if b >= 160: return 4 if b >= 96: return 3 if b >= 32: return 2 if b >= 1: return 1 return 0 def rating_get(id3, key): try: return list(id3['TXXX:RATING']) except KeyError: try: return list(unicode(byte2rating(popm_any(id3).rating))) except AttributeError: return def rating_set(id3, key, val): # TODO: test try: frame = id3["TXXX:RATING"] except KeyError: id3.add(mutagen.id3.TXXX(encoding=3, desc=u'RATING', text=val)) else: frame.encoding = 3 frame.text = val def rating_delete(id3, key): # TODO: delete POPM too? del(id3["TXXX:RATING"]) mutagen.easyid3.EasyID3.RegisterTXXXKey('sync', 'SYNC') mutagen.easyid3.EasyID3.RegisterKey('rating', rating_get, rating_set, rating_delete) 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') if rating != None: rating = int(rating[0]) 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 rating != None and rating >= 3: return True return False def findmatching(haystack, needle): artist = G(needle, 'artist') album = G(needle, 'album') title = G(needle, 'title') for match in haystack: if artist == G(match, 'artist') \ and album == G(match, 'album') \ and title == G(match, 'title'): return match 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: tosync.append(md) if inonly: return 0 print("Beginning matching...", file=sys.stderr) outdir = args[2] for p in paths(outdir): md = mutagen.File(p, easy=True) match = findmatching(tosync, md) # TODO: don't print anything # TODO: don't match mismatched lengths (Xing?) # TODO: update important tags on mostly matching files # TODO: don't sync files that wouldn't match! # TODO: convert files in loop that works on altered tosync if match: print("MATCHING", p) else: # TODO: just delete missing ones here print("MISSING", p) return 0 ret = 0 try: ret = run(sys.argv) except KeyboardInterrupt: sys.exit(1) sys.exit(ret)