gists/unsync.py
Connor Olding 2bbe301cdc switch to mutagenx and upgrade to python 3
mutagenx is about 13 times faster than mutagen!
2014-11-02 12:31:39 -08:00

169 lines
4.4 KiB
Python
Executable file

#!/bin/python
import os
import os.path
import sys
import shutil
import tempfile
from zlib import crc32
import mutaext
import convert
from mutaext import SyncFile
# BUG: doesn't work with my .m4a files?
#goodexts = ('.mp3', '.mp2', '.m4a', '.flac', '.ogg')
goodexts = ('.mp3', '.flac', '.ogg')
matchtags = ['artist', 'album', 'title']
alltags = [\
'albumartist', 'composer', 'comment' \
'tracknumber', 'discnumber', \
'genre', 'date', \
]
alltags.extend(mutaext.replaygain_tags)
alltags.extend(mutaext.extra_tags)
alltags.extend(matchtags)
lament = lambda *args, **kwargs: print(*args, file=sys.stderr, **kwargs)
walkfiles = lambda w: (os.path.join(r, f) for r, _, fs in w for f in fs)
extof = lambda p: os.path.splitext(p)[1].lower()
filterext = lambda ps, es: (p for p in ps if extof(p) in es)
ansty = lambda u: str(u.decode('ascii', errors='replace').replace('\ufffd', '?'))
def shouldsync(md):
rating = md.get('rating', '')
sync = md.get('sync', '')
try:
rating = int(rating)
except ValueError:
pass
if sync:
sync = sync.lower()
return sync == 'yes' or type(rating) == int and rating >= 3 and sync != 'no' and sync != 'space'
def fixmetadata(md):
md['artist'] = md.get('artist', "Unknown Artist")
md['album'] = md.get('album', "Unknown Album")
if 'title' not in md:
fn = os.path.basename(md.path)
fn = os.path.splitext(fn)[0]
md['title'] = str(fn)
def findmatching(haystack, needle):
matchme = [needle[t].lower() for t in matchtags]
ismatch = lambda hay: [hay[t].lower() for t in matchtags] == matchme
for match in (hay for hay in haystack if ismatch(hay)):
if match.seen:
# TODO: check other tags too?
lament("Duplicate")
return None
match.seen = needle.path
return match
def updatemetadata(mdold, mdnew):
modified = False
for tag in alltags:
if tag in mdnew:
if tag not 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 makefilename(md):
title = md['title']
artist = md['artist']
album = md['album']
fn = "%(artist)s - %(album)s - %(title)s" % locals()
crc = crc32(fn.encode('utf-8')) & 0xFFFFFFFF
# FAT is a pain to deal with so just use nondescript filenames
fn = '{:08X}.ogg'.format(crc)
return fn
def run(args):
if not len(args) in (2, 3):
lament("I need a path or two!")
return 1
inonly = len(args) == 2
tosync = []
indir = args[1]
paths = lambda dir: filterext(walkfiles(os.walk(dir)), goodexts)
for p in paths(indir):
md = SyncFile(p)
if shouldsync(md):
if inonly:
print(p)
else:
fixmetadata(md)
tosync.append(md)
if inonly:
return 0
lament("Matching...")
outdir = args[2]
for p in paths(outdir):
md = SyncFile(p)
match = findmatching(tosync, md)
if match == None:
print("DEL", p)
os.remove(p)
elif updatemetadata(md, match):
print("UPD", p)
md.md.save()
lament("[beginning tosync]")
for md in tosync:
#print("SYN", md.path)
fn = makefilename(md)
fout = os.path.join(outdir, fn)
if md.seen:
try:
_from = md.seen
_to = fout
if type(_from) != type(_to):
raise TypeError
if _from != _to:
print("MOV", _from)
os.rename(_from, _to)
continue
except:
lament(type(_from), type(_to))
lament("_from:", [_from])
lament("_to:", [_to])
raise
print("ADD", md.path)
_, ftemp = tempfile.mkstemp()
try:
convert.ogg(md.path, ftemp)
mdnew = SyncFile(ftemp)
for tag in alltags:
if tag in md:
mdnew[tag] = md[tag]
mdnew.md.save()
shutil.copy2(ftemp, fout)
finally:
os.remove(ftemp)
return 0
if __name__ == '__main__':
ret = 0
try:
ret = run(sys.argv)
except KeyboardInterrupt:
sys.exit(1)
sys.exit(ret)