2014-04-26 14:34:13 -07:00
|
|
|
#!/bin/python2
|
|
|
|
|
|
|
|
# only using python2 because mutagen
|
|
|
|
from __future__ import print_function
|
|
|
|
|
|
|
|
import os
|
|
|
|
import os.path
|
|
|
|
import sys
|
|
|
|
import mutagen
|
2014-04-27 12:03:52 -07:00
|
|
|
import mutaext
|
2014-04-26 14:34:13 -07:00
|
|
|
|
2014-04-27 07:17:32 -07:00
|
|
|
def G(d, k):
|
|
|
|
try:
|
|
|
|
return d[k]
|
|
|
|
except KeyError:
|
|
|
|
return None
|
2014-04-26 14:34:13 -07:00
|
|
|
|
2014-04-27 06:46:24 -07:00
|
|
|
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
|
|
|
|
|
2014-04-26 14:34:13 -07:00
|
|
|
def shouldsync(md):
|
2014-04-27 07:17:32 -07:00
|
|
|
rating = G(md, 'rating')
|
|
|
|
sync = G(md, 'sync')
|
2014-04-27 12:03:52 -07:00
|
|
|
if rating:
|
|
|
|
rating = rating[0]
|
2014-04-27 07:05:51 -07:00
|
|
|
if sync:
|
|
|
|
sync = sync[0].lower()
|
|
|
|
else:
|
|
|
|
sync = u''
|
2014-04-26 14:34:13 -07:00
|
|
|
|
|
|
|
if sync == u'no' or sync == u'space':
|
|
|
|
return False
|
|
|
|
if sync == u'yes' or sync == u'share':
|
|
|
|
return True
|
2014-04-27 07:05:51 -07:00
|
|
|
if rating != None and rating >= 3:
|
2014-04-26 14:34:13 -07:00
|
|
|
return True
|
|
|
|
return False
|
|
|
|
|
2014-04-27 09:36:14 -07:00
|
|
|
def fixmetadata(md):
|
|
|
|
if 'artist' not in md:
|
|
|
|
md['artist'] = "Unknown Artist"
|
|
|
|
if 'album' not in md:
|
|
|
|
md['album'] = "Unknown Album"
|
|
|
|
if 'title' not in md:
|
|
|
|
fn = os.path.basename(md.path)
|
|
|
|
fn = os.path.splitext(fn)[0]
|
2014-04-27 12:03:52 -07:00
|
|
|
# TODO: attempt to infer trackNum/discNum from fn
|
2014-04-27 09:36:14 -07:00
|
|
|
md['title'] = fn
|
|
|
|
|
2014-04-26 14:34:13 -07:00
|
|
|
def findmatching(haystack, needle):
|
2014-04-27 09:36:14 -07:00
|
|
|
# TODO: don't match mismatched lengths (Xing?)
|
2014-04-27 07:17:32 -07:00
|
|
|
artist = G(needle, 'artist')
|
|
|
|
album = G(needle, 'album')
|
|
|
|
title = G(needle, 'title')
|
2014-04-27 09:36:14 -07:00
|
|
|
match = None
|
|
|
|
for hay in haystack:
|
|
|
|
if artist == G(hay, 'artist') \
|
|
|
|
and album == G(hay, 'album') \
|
|
|
|
and title == G(hay, 'title'):
|
|
|
|
match = hay
|
|
|
|
if match.seen:
|
|
|
|
# TODO: check other tags and filename and such?
|
|
|
|
print("Warning: duplicate match found:", file=sys.stderr)
|
|
|
|
print("{0} by {1} from {2}".format(artist,album,title), file=sys.stderr)
|
|
|
|
else:
|
|
|
|
match.seen = True
|
|
|
|
break
|
|
|
|
return match
|
2014-04-26 14:34:13 -07:00
|
|
|
|
2014-04-27 12:03:52 -07:00
|
|
|
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
|
|
|
|
|
2014-04-26 14:34:13 -07:00
|
|
|
def run(args):
|
|
|
|
if not len(args) in (2, 3):
|
|
|
|
print("I need a path or two!", file=sys.stderr)
|
2014-04-27 07:05:51 -07:00
|
|
|
return 1
|
2014-04-26 14:34:13 -07:00
|
|
|
|
|
|
|
inonly = len(args) == 2
|
|
|
|
|
|
|
|
# BUG: doesn't work with my .m4a files?
|
|
|
|
goodexts = ('.mp3', '.m4a', '.flac', '.ogg')
|
|
|
|
tosync = []
|
|
|
|
indir = args[1]
|
2014-04-27 06:46:24 -07:00
|
|
|
paths = lambda dir: filterext(walkfiles(os.walk(dir)), goodexts)
|
2014-04-26 14:34:13 -07:00
|
|
|
|
2014-04-27 06:46:24 -07:00
|
|
|
for p in paths(indir):
|
|
|
|
md = mutagen.File(p, easy=True)
|
|
|
|
if shouldsync(md):
|
2014-04-27 09:36:14 -07:00
|
|
|
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
|
|
|
|
# TODO: don't print anything
|
2014-04-26 14:34:13 -07:00
|
|
|
print("Beginning matching...", file=sys.stderr)
|
|
|
|
|
|
|
|
outdir = args[2]
|
2014-04-27 06:46:24 -07:00
|
|
|
for p in paths(outdir):
|
|
|
|
md = mutagen.File(p, easy=True)
|
|
|
|
match = findmatching(tosync, md)
|
|
|
|
if match:
|
2014-04-27 12:03:52 -07:00
|
|
|
if updatemetadata(md, match):
|
|
|
|
print("UPD", p)
|
|
|
|
# save here
|
2014-04-27 06:46:24 -07:00
|
|
|
else:
|
2014-04-27 09:36:14 -07:00
|
|
|
print("DEL", p)
|
|
|
|
# delete files here
|
|
|
|
|
|
|
|
for md in tosync:
|
|
|
|
if md.seen:
|
|
|
|
continue
|
|
|
|
print("ADD", md.path)
|
2014-04-26 14:34:13 -07:00
|
|
|
|
2014-04-27 07:05:51 -07:00
|
|
|
return 0
|
|
|
|
|
|
|
|
ret = 0
|
2014-04-26 14:34:13 -07:00
|
|
|
try:
|
2014-04-27 07:05:51 -07:00
|
|
|
ret = run(sys.argv)
|
2014-04-26 14:34:13 -07:00
|
|
|
except KeyboardInterrupt:
|
|
|
|
sys.exit(1)
|
2014-04-27 07:05:51 -07:00
|
|
|
sys.exit(ret)
|