#!/bin/python import os, os.path import sys from shutil import copy2 from tempfile import mkstemp from zlib import crc32 import mutaext import convert from mutaext import SyncFile goodexts = ('.mp3', '.flac', '.ogg') matchtags = ['artist', 'album', 'title', 'tracknumber', 'discnumber'] alltags = [ 'albumartist', 'composer', 'comment', '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) def shouldsync(md): rating = md.get('rating', '') sync = md.get('sync', '') if rating.isnumeric(): rating = int(rating) if sync: sync = sync.lower() return sync == 'yes' or type(rating) == int and rating >= 3 and sync != 'no' and sync != 'space' import re re_digits = re.compile(r'\d+') def tonumber(crap): if crap is None or len(crap) == 0: return 0 nums = re_digits.findall(crap) if len(nums) == 0: return 0 return int(nums[0]) def fixmetadata(md): md['artist'] = md.get('artist', "Unknown Artist") md['album'] = md.get('album', "Unknown Album") md['discnumber'] = str(tonumber(md.get('discnumber', '0'))) md['tracknumber'] = str(tonumber(md.get('tracknumber', '0'))) 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 matchme = [needle[t] for t in matchtags] ismatch = lambda hay: [hay[t] 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'] track = md['tracknumber'] disc = md['discnumber'] fn = "%(disc)s-%(track)s - %(artist)s - %(album)s - %(title)s" % locals() # FAT is a pain to deal with so just use nondescript filenames crc = crc32(fn.encode('utf-8')) & 0xFFFFFFFF 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 tags...") outdir = args[2] for p in paths(outdir): md = SyncFile(p) fixmetadata(md) match = findmatching(tosync, md) if match == None: print("DEL", p) print('was', md['title'], 'by', md['artist']) os.remove(p) elif updatemetadata(md, match): print("UPD", p) md.save() lament("Syncing files...") for md in tosync: fn = makefilename(md) fout = os.path.join(outdir, fn) if md.seen: _from = md.seen _to = fout if _from != _to: print("MOV", _from) os.rename(_from, _to) continue print("ADD", md.path) _, ftemp = mkstemp() try: convert.ogg(md.path, ftemp) mdnew = SyncFile(ftemp) for tag in alltags: if tag in md: mdnew[tag] = md[tag] fixmetadata(mdnew) # redundant? mdnew.save() 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)