133 lines
3.6 KiB
Python
133 lines
3.6 KiB
Python
|
#!/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)
|