#! /usr/bin/python import os, sys import subprocess import tempfile from errno import EEXIST from flac_archive.tags import Tags class SubprocessError(Exception): def __init__(self, status, command=None, stderr=None): if command is None: command_msg = None else: command_msg = ': ' + ' '.join(command) if status < 0: msg = 'exited due to signal %d%s' else: msg = 'exit status %d%s' Exception.__init__(self, msg % (abs(status), command_msg)) self.status = status self.command = command self.stderr = ''.join(stderr) def get_tags(fn): tags = Tags() command = ['metaflac', '--no-utf8-convert', '--export-tags-to=-', fn] p = subprocess.Popen(command, stdout=subprocess.PIPE, stderr=subprocess.STDOUT) tags.load(p.stdout) status = p.wait() if status != 0: raise SubprocessError(status, command=command, stderr=p.stdout) return tags def rewrite_track_tags(track, tags, fn, tmp): tmp.write('\n'.join(tags.track(track)) + '\n') tmp.close() command = ['metaflac', '--no-utf8-convert', '--dont-use-padding', '--remove-all-tags', '--import-tags-from', tmp.name, fn] p = subprocess.Popen(command, stdout=subprocess.PIPE, stderr=subprocess.STDOUT) status = p.wait() if status != 0: raise SubprocessError(status, command=command, stderr=p.stdout) def do_read(filenames): # Use this mapping of tag names to sets of tag values to detect global tags. all_tags = {} # Build the collated result in this Tags object. coll_tags = Tags() # XXX The Tags interface is horrible. It's gotta be almost 10 years since # I wrote it, so not surprising... for fn in filenames: tags = get_tags(fn) track_tags = tags.get('TRACKNUMBER') # this check belongs in Tags if len(track_tags) != 1: sys.stderr.write('bogus TRACKNUMBER %s: %s\n' % (track_tags, fn)) return 4 track = int(track_tags[0]) for tag, values in tags._global.iteritems(): # Makes no sense to save TRACKNUMBER in coll_tags. if tag == 'TRACKNUMBER': continue for value in values: if tag in all_tags: all_tags[tag].add(value) else: all_tags[tag] = set([value]) coll_tags.set(tag, value, track) for tag, values in all_tags.iteritems(): if len(values) == 1: # Only one value for this tag, but does that one value appear on # all tracks? for track, tags in coll_tags._tracks.iteritems(): if not tag in tags: # Nope. break else: # Yep, so add it to global tags. coll_tags.set(tag, list(values)[0]) # And now remove it from each track tags. for track, tags in coll_tags._tracks.iteritems(): del tags[tag] print '\n'.join(coll_tags.all()) return 0 def do_write(args): tags = Tags() tags.load(open(args.pop(0))) if len(args) != len(tags): sys.stderr.write('expected %d flac files, got %d\n' % (len(tags), len(args))) return 2 artist = tags.get_path_safe('ALBUMARTIST') if not artist: artist = tags.get_path_safe('ARTIST') album = tags.get_path_safe('ALBUM') if not album: album = tags.get_path_safe('ALBUM', track=1) try: os.mkdir(artist) except OSError, e: if e.errno != EEXIST: raise album_path = artist + '/' + album try: os.mkdir(album_path) except OSError, e: if e.errno != EEXIST: raise for i, old_fn in enumerate(args): track = i + 1 fn = '%s/%s/%s.flac' % (artist, album, tags.make_filename(track)) if fn != old_fn: os.rename(old_fn, fn) tmp = tempfile.NamedTemporaryFile(delete=False) try: rewrite_track_tags(track, tags, fn, tmp) finally: try: os.unlink(tmp.name) except: pass return 0 def main(args): if len(args) < 3: return usage() try: if args[1] == 'read': return do_read(args[2:]) if args[1] == 'write': return do_write(args[2:]) except SubprocessError, e: sys.stderr.write('%s\n%s\n' % (e.stderr, e)) return 3 return usage() def usage(): return 2 if __name__ == '__main__': sys.exit(main(sys.argv))