]> diplodocus.org Git - flac-archive/blob - rewrite-tags
fix for flacs with no embedded art
[flac-archive] / rewrite-tags
1 #! /usr/bin/python
2
3 import os, sys
4 import subprocess
5 import tempfile
6 from errno import EEXIST
7
8 from flac_archive.tags import Tags
9
10 class SubprocessError(Exception):
11 def __init__(self, status, command=None, stderr=None):
12 if command is None:
13 command_msg = None
14 else:
15 command_msg = ': ' + ' '.join(command)
16 if status < 0:
17 msg = 'exited due to signal %d%s'
18 else:
19 msg = 'exit status %d%s'
20 Exception.__init__(self, msg % (abs(status), command_msg))
21 self.status = status
22 self.command = command
23 self.stderr = ''.join(stderr)
24
25 def get_tags(fn):
26 tags = Tags()
27
28 command = ['metaflac', '--no-utf8-convert', '--export-tags-to=-', fn]
29 p = subprocess.Popen(command, stdout=subprocess.PIPE,
30 stderr=subprocess.STDOUT)
31 tags.load(p.stdout)
32
33 status = p.wait()
34 if status != 0:
35 raise SubprocessError(status, command=command, stderr=p.stdout)
36
37 return tags
38
39 def rewrite_track_tags(track, tags, fn, tmp):
40 tmp.write('\n'.join(tags.track(track)) + '\n')
41 tmp.close()
42 command = ['metaflac', '--no-utf8-convert', '--dont-use-padding',
43 '--remove-all-tags', '--import-tags-from', tmp.name, fn]
44 p = subprocess.Popen(command, stdout=subprocess.PIPE,
45 stderr=subprocess.STDOUT)
46 status = p.wait()
47 if status != 0:
48 raise SubprocessError(status, command=command, stderr=p.stdout)
49
50 def do_read(filenames):
51 # Use this mapping of tag names to sets of tag values to detect global tags.
52 all_tags = {}
53 # Build the collated result in this Tags object.
54 coll_tags = Tags()
55 # XXX The Tags interface is horrible. It's gotta be almost 10 years since
56 # I wrote it, so not surprising...
57 for fn in filenames:
58 tags = get_tags(fn)
59 track_tags = tags.get('TRACKNUMBER')
60 # this check belongs in Tags
61 if len(track_tags) != 1:
62 sys.stderr.write('bogus TRACKNUMBER %s: %s\n' % (track_tags, fn))
63 return 4
64 track = int(track_tags[0])
65 for tag, values in tags._global.iteritems():
66 # Makes no sense to save TRACKNUMBER in coll_tags.
67 if tag == 'TRACKNUMBER':
68 continue
69 for value in values:
70 if tag in all_tags:
71 all_tags[tag].add(value)
72 else:
73 all_tags[tag] = set([value])
74 coll_tags.set(tag, value, track)
75 for tag, values in all_tags.iteritems():
76 if len(values) == 1:
77 # Only one value for this tag, but does that one value appear on
78 # all tracks?
79 for track, tags in coll_tags._tracks.iteritems():
80 if not tag in tags:
81 # Nope.
82 break
83 else:
84 # Yep, so add it to global tags.
85 coll_tags.set(tag, list(values)[0])
86 # And now remove it from each track tags.
87 for track, tags in coll_tags._tracks.iteritems():
88 del tags[tag]
89 print '\n'.join(coll_tags.all())
90 return 0
91
92 def do_write(args):
93 tags = Tags()
94 tags.load(open(args.pop(0)))
95 if len(args) != len(tags):
96 sys.stderr.write('expected %d flac files, got %d\n'
97 % (len(tags), len(args)))
98 return 2
99 artist = tags.get_path_safe('ALBUMARTIST')
100 if not artist:
101 artist = tags.get_path_safe('ARTIST')
102 album = tags.get_path_safe('ALBUM')
103 if not album:
104 album = tags.get_path_safe('ALBUM', track=1)
105 try:
106 os.mkdir(artist)
107 except OSError, e:
108 if e.errno != EEXIST:
109 raise
110 album_path = artist + '/' + album
111 try:
112 os.mkdir(album_path)
113 except OSError, e:
114 if e.errno != EEXIST:
115 raise
116 for i, old_fn in enumerate(args):
117 track = i + 1
118 fn = '%s/%s/%s.flac' % (artist, album, tags.make_filename(track))
119 if fn != old_fn:
120 os.rename(old_fn, fn)
121 tmp = tempfile.NamedTemporaryFile(delete=False)
122 try:
123 rewrite_track_tags(track, tags, fn, tmp)
124 finally:
125 try:
126 os.unlink(tmp.name)
127 except:
128 pass
129 return 0
130
131 def main(args):
132 if len(args) < 3:
133 return usage()
134 try:
135 if args[1] == 'read':
136 return do_read(args[2:])
137 if args[1] == 'write':
138 return do_write(args[2:])
139 except SubprocessError, e:
140 sys.stderr.write('%s\n%s\n' % (e.stderr, e))
141 return 3
142 return usage()
143
144 def usage():
145 return 2
146
147 if __name__ == '__main__':
148 sys.exit(main(sys.argv))
149