-
-
Notifications
You must be signed in to change notification settings - Fork 1
/
flac2mp3.py
executable file
·174 lines (153 loc) · 7.83 KB
/
flac2mp3.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
#!/usr/bin/env python2.5
from mutagen.id3 import ID3, ID3NoHeaderError, TALB, TPE1, TPE2, TBPM, COMM, TCMP, TCOM, TPE3, TDRC, TPOS, TCON, TSRC, TEXT, TPUB, TIT2, TRCK, UFID, TXXX, TSOP, TSO2, APIC, TSOT, TSOA
from mutagen.flac import FLAC
import string
import sys
import os.path
from subprocess import Popen, PIPE
def one_to_one_conversion(flac_frame_name, frame_class):
return (flac_frame_name, lambda mp3, flac: mp3.text[0] == flac, lambda str:[frame_class(encoding=3, text=str)])
def one_to_one_conversion_txxx(flac_frame_name, desc):
return (flac_frame_name, lambda mp3, flac: mp3.text[0] == flac, lambda str:[TXXX(encoding=3, desc=desc, text=str)])
mp3_flac_dict = {
'TDRC': (u'$DATE', lambda mp3, flac: mp3.text[0].text == flac, lambda str: [TDRC(encoding=3, text=str)]),
'UFID:http://musicbrainz.org': (u'$MUSICBRAINZ_TRACKID', lambda mp3, flac: mp3.data == flac, lambda str: [UFID(encoding=3, owner=u'http://musicbrainz.org', data=str)]),
'TALB': one_to_one_conversion(u'$ALBUM', TALB),
'TPE1': one_to_one_conversion(u'$ARTIST', TPE1),
'TPE2': one_to_one_conversion(u'$BAND', TPE2),
'TBPM': one_to_one_conversion(u'$BPM', TBPM),
'COMM': one_to_one_conversion(u'$COMMENT', COMM),
'TCMP': one_to_one_conversion(u'$COMPILATION', TCMP),
'TCOM': one_to_one_conversion(u'$COMPOSER', TCOM),
'TPE3': one_to_one_conversion(u'$CONDUCTOR', TPE3),
'TPOS': one_to_one_conversion(u'$DISCNUMBER/$TOTALDISCS', TPOS),
'TCON': one_to_one_conversion(u'$GENRE', TCON),
'TSRC': one_to_one_conversion(u'$ISRC', TSRC),
'TEXT': one_to_one_conversion(u'$LYRICIST', TEXT),
'TPUB': one_to_one_conversion(u'$PUBLISHER', TPUB),
'TIT2': one_to_one_conversion(u'$TITLE', TIT2),
'TRCK': one_to_one_conversion(u'$TRACKNUMBER/$TOTALTRACKS', TRCK),
'TSOP': one_to_one_conversion(u'$ARTISTSORT', TSOP),
'TSO2': one_to_one_conversion(u'$ALBUMARTISTSORT', TSO2),
'TSOT': one_to_one_conversion(u'$TITLESORT', TSOT),
'TSOA': one_to_one_conversion(u'$ALBUMSORT', TSOA),
'TXXX:MusicBrainz Album Id': one_to_one_conversion_txxx(u'$MUSICBRAINZ_ALBUMID', 'MusicBrainz Album Id'),
'TXXX:MusicBrainz Album Status': one_to_one_conversion_txxx(u'$MUSICBRAINZ_ALBUMSTATUS', 'MusicBrainz Album Status'),
'TXXX:MusicBrainz Album Artist Id': one_to_one_conversion_txxx(u'$MUSICBRAINZ_ALBUMARTISTID', 'MusicBrainz Album Artist Id'),
'TXXX:MusicBrainz Album Type': one_to_one_conversion_txxx(u'$MUSICBRAINZ_ALBUMTYPE', 'MusicBrainz Album Type'),
'TXXX:MusicBrainz Artist Id': one_to_one_conversion_txxx(u'$MUSICBRAINZ_ARTISTID', 'MusicBrainz Artist Id'),
'TXXX:MusicBrainz Sortname': one_to_one_conversion_txxx(u'$MUSICBRAINZ_SORTNAME', 'MusicBrainz Sortname'),
'TXXX:MusicBrainz TRM Id': one_to_one_conversion_txxx(u'$MUSICBRAINZ_TRMID', 'MusicBrainz TRM Id'),
'TXXX:MD5': one_to_one_conversion_txxx(u'$MD5', 'MD5'),
'TXXX:ALBUMARTISTSORT': one_to_one_conversion_txxx(u'$ALBUMARTISTSORT', 'ALBUMARTISTSORT'),
}
status_printed=False
def flac_tag_dict(flac):
ret = {}
for key in flac.tags.as_dict().keys():
ret[key.upper()] = flac.tags[key][0]
ret['MD5'] = ('%x' % flac.info.md5_signature)
if ret.has_key('TRACKTOTAL'):
ret['TOTALTRACKS'] = ret['TRACKTOTAL']
if ret.has_key('DISCTOTAL'):
ret['TOTALDISCS'] = ret['DISCTOTAL']
if not ret.has_key('TOTALTRACKS'):
ret['TOTALTRACKS'] = ''
if not ret.has_key('TOTALDISCS'):
ret['TOTALDISCS'] = ''
return ret
def encode_file(flac_name, mp3_name):
# We need to pass --tl (or any tag option) to ensure Mutagen can read the file afterwards.
flac = Popen(["flac", "--decode", "--silent", "--stdout", flac_name], stdout=PIPE)
lame = Popen(["lame", "--vbr-new", "-V2", "--quiet", "--noreplaygain", "--tl", "placeholder", "-", mp3_name], stdin=flac.stdout)
lame.communicate()
lame.wait()
flac.wait()
def print_status(file_name, pos, status):
global status_printed
if not status_printed:
print file_name
for i in range(pos):
sys.stdout.write(" ")
status_printed = True
sys.stdout.write(status)
sys.stdout.flush
def maybe_encode_file(flac_name, mp3_name):
if os.path.isfile(mp3_name):
if os.path.getmtime(mp3_name) >= os.path.getmtime(flac_name):
return
# Need to check md5 to make sure they're the same:
flac = FLAC(flac_name)
mp3 = ID3(mp3_name)
try:
if (len(mp3.getall('TXXX:MD5')) == 0) or ('%x' % flac.info.md5_signature) != mp3['TXXX:MD5'].text[0]:
try:
mp3_sig = mp3['TXXX:MD5'].text[0]
except KeyError:
mp3_sig = 'None'
print_status(mp3_name, 0, "R")
encode_file(flac_name, mp3_name)
except ID3NoHeaderError:
print_status(mp3_name, 0, "I")
encode_file(flac_name, mp3_name)
else:
print_status(mp3_name, 0, "E")
encode_file(flac_name, mp3_name)
tag_sync(flac_name, mp3_name)
def tag_sync(flac_name, mp3_name):
mp3 = ID3(mp3_name)
flac = FLAC(flac_name)
flactags = flac_tag_dict(flac)
tag_differences = {}
tag_index = 1
# First, check whether the tags that can easily be translated match:
for frame in mp3_flac_dict.keys():
mp3_has_frame = True
flac_has_frame = True
frames_differ = False
mp3_value = None
flac_value = None
format, comparator, id3_generator = mp3_flac_dict[frame]
try:
mp3_value = mp3[frame]
except KeyError:
mp3_has_frame = False
try:
flac_value = string.Template(format).substitute(flactags)
except KeyError:
flac_has_frame = False
if flac_has_frame and mp3_has_frame:
frames_differ = not comparator(mp3_value, flac_value)
if (flac_has_frame and not mp3_has_frame) or frames_differ:
#print "%s differs: %s <> %s" % (frame, mp3_value, flac_value)
tag_differences[frame] = id3_generator(flac_value)
print_status(mp3_name, tag_index, ".")
elif not flac_has_frame and mp3_has_frame:
tag_differences[frame] = None
print_status(mp3_name, tag_index, "X")
else:
print_status(mp3_name, tag_index, " ")
tag_index += 1
# Now, check pictures:
for picture in flac.pictures:
have_picture = False
for apic in mp3.getall('APIC'):
if apic.mime == picture.mime and apic.type == picture.type and apic.data == picture.data:
have_picture = True
if not have_picture:
if not tag_differences.has_key('APIC:'):
tag_differences['APIC:'] = []
tag_differences['APIC:'].append(APIC(encoding=3, desc=u'', type=picture.type, data=picture.data, mime=picture.mime))
print_status(mp3_name, tag_index, "P")
print ""
# And now push the changed tags to the MP3.
for frame in tag_differences.keys():
if tag_differences[frame] == None:
mp3.delall(frame)
else:
mp3.setall(frame, tag_differences[frame])
if len(tag_differences.keys()) > 0:
mp3.save(mp3_name, v1=1)
else:
os.utime(mp3_name, None)
maybe_encode_file(sys.argv[1], sys.argv[2])