#!/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 safetydance(d, k): if k in d: return d[k] def shouldsync(md): rating = -1 sync = u'' if safetydance(md, 'rating'): try: rating = int(md['rating'][0]) except ValueError: pass if safetydance(md, 'sync'): sync = md['sync'][0].lower() if sync == u'no' or sync == u'space': return False if sync == u'yes' or sync == u'share': return True if rating >= 3: return True return False def findmatching(haystack, needle): artist = safetydance(needle, 'artist') album = safetydance(needle, 'album') title = safetydance(needle, 'title') for match in haystack: if artist == safetydance(match, 'artist') \ and album == safetydance(match, 'album') \ and title == safetydance(match, 'title'): return match def run(args): if not len(args) in (2, 3): print("I need a path or two!", file=sys.stderr) sys.exit(1) inonly = len(args) == 2 # BUG: doesn't work with my .m4a files? goodexts = ('.mp3', '.m4a', '.flac', '.ogg') tosync = [] indir = args[1] walker = os.walk(indir) # TODO: abstract to mdloop(callback) for root, _, files in walker: for f in files: ext = os.path.splitext(f)[1].lower() if not ext in goodexts: continue path = os.path.join(root, f) md = mutagen.File(path, easy=True) if shouldsync(md): if inonly: print(path) else: tosync.append(md) if inonly: return print("Beginning matching...", file=sys.stderr) outdir = args[2] walker = os.walk(outdir) for root, _, files in walker: for f in files: ext = os.path.splitext(f)[1].lower() if not ext in goodexts: continue path = os.path.join(root, f) md = mutagen.File(path, 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", path) else: # TODO: just delete missing ones here print("MISSING", path) try: run(sys.argv) except KeyboardInterrupt: sys.exit(1)