2 # This file is part of mp3togo
4 # Convert audio files to play on a mp3 player
5 # Manage program options
7 # (c) Simeon Veldstra 2004, 2006 <reallifesim@gmail.com>
9 # This software is free.
11 # This program is distributed in the hope that it will be useful,
12 # but WITHOUT ANY WARRANTY; without even the implied warranty of
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 # GNU General Public License for more details.
16 # You may redistribute this program under the terms of the
17 # GNU General Public Licence version 2
18 # Available in this package or at http://www.fsf.org
25 import mp3togo.conf as setup
27 # This mess needs to be fixed,
28 # Tags() is just going to have to
29 # take an Options() instance.
54 for path in os.environ['PATH'].split(':'):
55 if os.path.exists(os.path.join(path, 'metaflac')):
60 class Tags(UserDict.DictMixin):
61 """Manage per track metadata.
63 Provide a filename in the constructor
64 Call read() to read in the tags
65 modify tags if necessary
66 Call write(name) with the output file the tags
68 Tags are available through a dictionary interface
69 format(fmt) returns a formatted string of metadata
72 def __init__(self, filename, opts):
73 if not os.path.exists(filename):
74 raise setup.ErrorNoFile
77 self._type = opts.getfiletype(filename)
79 self._readok = threading.Event()
81 self._lock = threading.Lock()
84 def read(self, block=True):
85 if not self._lock.acquire(block) and not block:
93 if not o.has_key('GENRE'):
97 if self._type == 'mp3':
103 self._tags['ARTIST'] = [eye.getArtist() or '']
104 self._tags['ALBUM'] = [eye.getAlbum() or '']
105 self._tags['TITLE'] = [eye.getTitle() or '']
106 if eye.getYear() and eye.getYear() > 1600 and eye.getYear() < 3000:
107 self._tags['DATE'] = [eye.getYear()]
108 if eye.getTrackNum():
109 self._tags['TRACKNUMBER'] = [str(eye.getTrackNum()[0])]
111 self._tags['GENRE'] = [getattr(eye.getGenre(), 'name', 'Other')]
112 except eyeD3.tag.GenreException:
113 self._tags['GENRE'] = 'Other'
115 except eyeD3.tag.TagException:
118 info = ID3.ID3(self._file, as_tuple=1).as_dict()
119 self._tags = copytags(info)
121 elif self._type == 'ogg' and HAVE_VORBIS:
122 info = ogg.vorbis.VorbisFile(self._file).comment().as_dict()
123 self._tags = copytags(info)
125 elif self._type == 'flac' and HAVE_METAFLAC:
126 cmd = '%s --export-tags-to=- "%s" ' % ('metaflac', self._file)
130 info = map(lambda x: x.split('=', 1), info.split('\n'))
131 info = filter(lambda x: len(x) == 2, info)
132 info = map(lambda x: [x[0].upper(), x[1:]], info)
133 self._tags = dict(info)
134 elif self._type == 'wav':
138 # Try to guess from the file's path - better than nothing
139 path = os.path.splitext(self._file)[0]
140 path = path.replace('_', ' ')
141 for id, depth in [('ARTIST', -3), ('ALBUM', -2), ('TITLE', -1)]:
142 if not id in self._tags or self._tags[id][0] == '':
143 if self._opts['noguesstags']:
144 self._tags[id] = ['']
147 self._tags[id] = [path.split(os.sep)[depth]]
148 self._opts.log(1, "Missing %s tag, guessing %s from file name." %
149 (id, self._tags[id][0]))
151 self._tags[id] = "%s unknown" % id.lower()
158 def write(self, filename, block=True):
159 if not self._lock.acquire(block) and not block:
162 if not os.path.exists(filename):
164 raise setup.ErrorNoFile
167 for key in self._tags.keys(): # Not self.keys()
168 if key == 'GENRE' and fmt == 'mp3':
169 if type(self._tags[key][0]) is type(1) and 0 <= self._tags[key][0] < 256:
170 d[key] = self._tags[key][0]
171 elif self._tags[key][0] in map(str, range(256)):
172 d[key] = int(self._tags[key][0])
175 d[key] = int(genres.get(self._tags[key][0], '255'))
179 d[key] = self._tags[key][0]
180 # No! don't unlock here dumbass! return from puttags
184 fmt = self._opts.getfiletype(filename)
185 except setup.ErrorUnknownFileType:
190 vf = ogg.vorbis.VorbisFile(filename)
194 out.write_to(filename)
202 out.link(filename, eyeD3.ID3_V1)
203 out.setVersion(eyeD3.ID3_V1)
206 out.setArtist(d['ARTIST'])
207 out.setAlbum(d['ALBUM'])
208 out.setTitle(d['TITLE'])
210 g.setId(d.get('GENRE', 12))
212 if d.has_key('DATE') and int(d['DATE']) > 1600 and int(d['DATE']) < 3000:
213 out.setDate(d['DATE'])
216 if d.has_key('TRACKNUMBER') and d['TRACKNUMBER']:
218 out.setTrackNum((int(d['TRACKNUMBER']), None))
222 if d.has_key('COMMENT'):
223 out.addComment(d['COMMENT'])
227 out = ID3.ID3(filename, as_tuple=1)
232 # Tagging failed, but the file should be okay.
237 raise setup.ErrorUnknownFileType
243 def writeindex(self, filename, indexname, block=True):
244 if not self._lock.acquire(block) and not block:
248 ifile = codecs.open(indexname, 'a', 'utf-8')
249 ifile.write(filename + "\n")
250 keys = self._tags.keys()
253 for q in self._tags[k]:
254 ifile.write("%s: %s\n" % (k, q))
264 def format(self, fmt, block=True):
265 """Pretty print the tag information
267 The following format strings apply:
273 %y Album release year
276 %x File extension """
277 if not self._lock.acquire(block) and not block:
280 esc = {'n': 'TRACKNUMBER',
288 #'z': Used for literal '%'
291 fmt = fmt.replace('%%', '%z')
298 code = fmt[0] and fmt[0][0]
300 fmt[0] = '%' + fmt[0][1:]
302 fmt[0] = self._file + fmt[0][1:]
304 fmt[0] = self._type + fmt[0][1:]
305 elif code in esc.keys():
306 fmt[0] = self._tags.get(esc[code], ('',))[0] + fmt[0][1:]
309 raise setup.ErrorBadFormat
315 def __getitem__(self, key, block=True):
317 if not self._lock.acquire(block) and not block:
320 if key == 'GENRE_NAME':
321 if not self._tags.has_key('GENRE'):
324 out = [genrenumbers.get(self._tags['GENRE'][0], self._tags['GENRE'][0])]
327 out = self._tags[key.upper()]
336 def __setitem__(self, key, value, block=True):
338 if not self._lock.acquire(block) and not block:
341 if type(value) != type([]):
343 self._tags[key.upper()] = value
349 def setorappend(self, key, value, block=True):
350 if not self._lock.acquire(block) and not block:
354 if self._tags.has_key(key):
355 self._tags[key].append(value)
363 def __delitem__(self, key, block=True):
364 if not self._lock.acquire(block) and not block:
377 def keys(self, block=True):
378 if not self._lock.acquire(block) and not block:
381 if self._tags.has_key('GENRE'):
382 out = self._tags.keys() + ['GENRE_NAME']
384 out = self._tags.keys()
390 def remove_from_index(trackname, indexname):
391 """Remove an entry from an index file."""
392 if not os.path.exists(indexname):
394 fp = file(indexname, 'r')
395 lines = fp.readlines()
399 while i < len(lines):
400 if lines[i] == trackname + '\n':
413 fp = file(indexname, 'w')
457 'Instrumental': '33',
470 'Instrum. Pop': '46',
471 'Instrum. Rock': '47',
475 'Techno-Indust.': '51',
480 'Southern Rock': '56',
485 'Christian Rap': '61',
488 'Native American': '64',
506 'National Folk': '82',
516 'Progress. Rock': '92',
517 'Psychedel. Rock': '93',
518 'Symphonic Rock': '94',
522 'Easy Listening': '98',
528 'Chamber Music': '104',
533 'Porn Groove': '109',
541 'Power Ballad': '117',
542 'Rhythmic Soul': '118',
551 'Drum & Bass': '127',
560 'Christian Gangsta Rap': '136',
561 'Heavy Metal': '137',
562 'Black Metal': '138',
564 'Contemporary Christian': '140',
565 'Christian Rock': '141',
568 'Thrash Metal': '144',
574 for key, value in genres.items():
575 genrenumbers[value] = key