diff --git a/mutaext.py b/mutaext.py new file mode 100644 index 0000000..799580e --- /dev/null +++ b/mutaext.py @@ -0,0 +1,84 @@ +import mutagen +import mutagen.id3 +import mutagen.easyid3 + +# TODO: custom interface to tracknumber/tracktotal/disc... + +def popms(id3): + for k, v in id3.iteritems(): + if k.startswith('POPM'): + yield k, 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 rating2byte(r): + if r == 5: return 256 + if r == 4: return 192 + if r == 3: return 128 + if r == 2: return 64 + if r == 1: return 1 + return 0 + +def rating_get(id3, key): + try: + rating = id3['TXXX:RATING'] + except KeyError: + try: + _, popm = popms(id3).next() + except StopIteration: + return [] + else: + return [byte2rating(popm.rating)] + else: + return list(rating.text) + +def _canconv(r): + try: + if int(r) != str(int(r)): + return False + return int(r) >= 1 and int(r) <= 5 + except ValueError, TypeError: + return False + +def rating_set(id3, key, val): + rating_delete(id3, key) + if _canconv(val): + popm = mutagen.id3.POPM() + popm.email = "Windows Media Player 9 Series" + popm.count = 0 + popm.rating = rating2byte(int(val)) + id3.add(popm) + else: + if 'TXXX:RATING' in id3: + del(id3['TXXX:RATING']) + id3.add(mutagen.id3.TXXX(encoding=3, desc='RATING', text=unicode(val))) + +def rating_delete(id3, key): + for k, v in popms(id3): + del(id3[k]) + if 'TXXX:RATING' in id3: + del(id3['TXXX:RATING']) + +def tracknumber_get(id3, key): + # TODO: use for both track and disc + pass + +def tracknumber_set(id3, key, val): + pass + +def tracknumber_delete(id3, key): + pass + +replaygain_tags = ('replaygain_album_gain', 'replaygain_album_peak', \ + 'replaygain_track_gain', 'replaygain_track_peak') + +for tag in replaygain_tags: + mutagen.easyid3.EasyID3.RegisterTXXXKey(tag, tag) +mutagen.easyid3.EasyID3.RegisterTXXXKey('sync', 'SYNC') +mutagen.easyid3.EasyID3.RegisterKey('rating', rating_get, rating_set, rating_delete) diff --git a/unsync.py b/unsync.py index 72e986b..2cc1ffe 100755 --- a/unsync.py +++ b/unsync.py @@ -7,45 +7,7 @@ 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) +import mutaext def G(d, k): try: @@ -67,11 +29,8 @@ def filterext(paths, exts): def shouldsync(md): rating = G(md, 'rating') sync = G(md, 'sync') - if rating != None: - try: - rating = int(rating[0]) - except ValueError: - rating = None + if rating: + rating = rating[0] if sync: sync = sync[0].lower() else: @@ -93,7 +52,7 @@ def fixmetadata(md): if 'title' not in md: fn = os.path.basename(md.path) fn = os.path.splitext(fn)[0] - # TODO: attempt to infer trackNum/diskNum from fn + # TODO: attempt to infer trackNum/discNum from fn md['title'] = fn def findmatching(haystack, needle): @@ -116,6 +75,28 @@ def findmatching(haystack, needle): break return match +def updatemetadata(mdold, mdnew): + modified = False + updatabletags = ('rating', 'sync', 'comment') + # TODO: + # composer : probably safe + # genre : might need deep equal? + # trackNum : TRCK with optional /[total] + # discNum : TPOS " " " + # total tracks : ? + # total discs : ? + # [replaygain] : TXXX:* in id3 + # albumArtist : TPE2 in id3 + for tag in updatabletags: + if tag in mdnew: + if not tag in mdold or mdnew[tag] != mdold[tag]: + mdold[tag] = mdnew[tag] + modified = True + elif tag in mdold: + del mdold[tag] + modified = True + return modified + def run(args): if not len(args) in (2, 3): print("I need a path or two!", file=sys.stderr) @@ -151,8 +132,9 @@ def run(args): md = mutagen.File(p, easy=True) match = findmatching(tosync, md) if match: - print("UPD", p) - # update tags here if updatemetadata returns true + if updatemetadata(md, match): + print("UPD", p) + # save here else: print("DEL", p) # delete files here