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' and HAVE_ID3:
101 self._tags['ARTIST'] = [eye.getArtist() or '']
102 self._tags['ALBUM'] = [eye.getAlbum() or '']
103 self._tags['TITLE'] = [eye.getTitle() or '']
105 self._tags['GENRE'] = [getattr(eye.getGenre(), 'name', 'Other')]
106 except eyeD3.tag.GenreException:
107 self._tags['GENRE'] = 'Other'
110 info = ID3.ID3(self._file, as_tuple=1).as_dict()
111 self._tags = copytags(info)
113 elif self._type == 'ogg' and HAVE_VORBIS:
114 info = ogg.vorbis.VorbisFile(self._file).comment().as_dict()
115 self._tags = copytags(info)
117 elif self._type == 'flac' and HAVE_METAFLAC:
118 cmd = '%s --export-tags-to=- "%s" ' % ('metaflac', self._file)
122 info = map(lambda x: x.split('=', 1), info.split('\n'))
123 info = filter(lambda x: len(x) == 2, info)
124 info = map(lambda x: [x[0].upper(), x[1:]], info)
125 self._tags = dict(info)
126 elif self._type == 'wav':
130 # Try to guess from the file's path - better than nothing
131 path = os.path.splitext(self._file)[0]
132 path = path.replace('_', ' ')
133 for id, depth in [('ARTIST', -3), ('ALBUM', -2), ('TITLE', -1)]:
134 if not id in self._tags or self._tags[id][0] == '':
136 self._tags[id] = [path.split(os.sep)[depth]]
138 self._tags[id] = "%s unknown" % id.lower()
145 def write(self, filename, block=True):
146 if not self._lock.acquire(block) and not block:
149 if not os.path.exists(filename):
151 raise setup.ErrorNoFile
154 for key in self._tags.keys(): # Not self.keys()
155 if key == 'GENRE' and fmt == 'mp3':
156 if type(self._tags[key][0]) is type(1) and 0 <= self._tags[key][0] < 256:
157 d[key] = self._tags[key][0]
158 elif self._tags[key][0] in map(str, range(256)):
159 d[key] = int(self._tags[key][0])
161 d[key] = int(genres.get(self._tags[key][0], '255'))
163 d[key] = self._tags[key][0]
164 # No! don't unlock here dumbass! return from puttags
168 fmt = self._opts.getfiletype(filename)
169 except setup.ErrorUnknownFileType:
174 vf = ogg.vorbis.VorbisFile(filename)
178 out.write_to(filename)
186 out.link(filename, eyeD3.ID3_V1)
187 out.setVersion(eyeD3.ID3_V1)
190 out.setArtist(d['ARTIST'])
191 out.setAlbum(d['ALBUM'])
192 out.setTitle(d['TITLE'])
194 g.setId(d.get('GENRE', 12))
196 if d.has_key('COMMENT'):
197 out.addComment(d['COMMENT'])
201 out = ID3.ID3(filename, as_tuple=1)
206 # Tagging failed, but the file should be okay.
211 raise setup.ErrorUnknownFileType
217 def writeindex(self, filename, indexname, block=True):
218 if not self._lock.acquire(block) and not block:
222 ifile = codecs.open(indexname, 'a', 'utf-8')
223 ifile.write(filename + "\n")
224 keys = self._tags.keys()
227 for q in self._tags[k]:
228 ifile.write("%s: %s\n" % (k, q))
238 def format(self, fmt, block=True):
239 """Pretty print the tag information
241 The following format strings apply:
247 %y Album release year
250 %x File extension """
251 if not self._lock.acquire(block) and not block:
254 esc = {'n': 'TRACKNUMBER',
262 #'z': Used for literal '%'
265 fmt = fmt.replace('%%', '%z')
272 code = fmt[0] and fmt[0][0]
274 fmt[0] = '%' + fmt[0][1:]
276 fmt[0] = self._file + fmt[0][1:]
278 fmt[0] = self._type + fmt[0][1:]
279 elif code in esc.keys():
280 fmt[0] = self._tags.get(esc[code], ('',))[0] + fmt[0][1:]
283 raise setup.ErrorBadFormat
289 def __getitem__(self, key, block=True):
291 if not self._lock.acquire(block) and not block:
294 if key == 'GENRE_NAME':
295 if not self._tags.has_key('GENRE'):
298 out = [genrenumbers.get(self._tags['GENRE'][0], self._tags['GENRE'][0])]
301 out = self._tags[key.upper()]
310 def __setitem__(self, key, value, block=True):
312 if not self._lock.acquire(block) and not block:
315 if type(value) != type([]):
317 self._tags[key.upper()] = value
323 def setorappend(self, key, value, block=True):
324 if not self._lock.acquire(block) and not block:
328 if self._tags.has_key(key):
329 self._tags[key].append(value)
337 def __delitem__(self, key, block=True):
338 if not self._lock.acquire(block) and not block:
351 def keys(self, block=True):
352 if not self._lock.acquire(block) and not block:
355 if self._tags.has_key('GENRE'):
356 out = self._tags.keys() + ['GENRE_NAME']
358 out = self._tags.keys()
364 def remove_from_index(trackname, indexname):
365 """Remove an entry from an index file."""
366 if not os.path.exists(indexname):
368 fp = file(indexname, 'r')
369 lines = fp.readlines()
373 while i < len(lines):
374 if lines[i] == trackname + '\n':
387 fp = file(indexname, 'w')
431 'Instrumental': '33',
444 'Instrum. Pop': '46',
445 'Instrum. Rock': '47',
449 'Techno-Indust.': '51',
454 'Southern Rock': '56',
459 'Christian Rap': '61',
462 'Native American': '64',
480 'National Folk': '82',
490 'Progress. Rock': '92',
491 'Psychedel. Rock': '93',
492 'Symphonic Rock': '94',
496 'Easy Listening': '98',
502 'Chamber Music': '104',
507 'Porn Groove': '109',
515 'Power Ballad': '117',
516 'Rhythmic Soul': '118',
525 'Drum & Bass': '127',
534 'Christian Gangsta Rap': '136',
535 'Heavy Metal': '137',
536 'Black Metal': '138',
538 'Contemporary Christian': '140',
539 'Christian Rock': '141',
542 'Thrash Metal': '144',
548 for key, value in genres.items():
549 genrenumbers[value] = key