gists/unsync.py

154 lines
3.9 KiB
Python
Executable file

#!/bin/python2
# only using python2 because mutagen
from __future__ import print_function
import os
import os.path
import sys
import mutagen
import mutaext
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:
rating = 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 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]
# TODO: attempt to infer trackNum/discNum from fn
md['title'] = fn
def findmatching(haystack, needle):
# TODO: don't match mismatched lengths (Xing?)
artist = G(needle, 'artist')
album = G(needle, 'album')
title = G(needle, 'title')
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
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)
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:
# 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
print("Beginning matching...", file=sys.stderr)
outdir = args[2]
for p in paths(outdir):
md = mutagen.File(p, easy=True)
match = findmatching(tosync, md)
if match:
if updatemetadata(md, match):
print("UPD", p)
# save here
else:
print("DEL", p)
# delete files here
for md in tosync:
if md.seen:
continue
print("ADD", md.path)
return 0
ret = 0
try:
ret = run(sys.argv)
except KeyboardInterrupt:
sys.exit(1)
sys.exit(ret)