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
249 if not self._lock.acquire(block) and not block:
252 esc = {'n': 'TRACKNUMBER',
258 #'z': Used for literal '%'
261 fmt = fmt.replace('%%', '%z')
268 code = fmt[0] and fmt[0][0]
270 fmt[0] = '%' + fmt[0][1:]
271 elif code in esc.keys():
272 fmt[0] = self._tags.get(esc[code], ('',))[0] + fmt[0][1:]
275 raise setup.ErrorBadFormat
281 def __getitem__(self, key, block=True):
283 if not self._lock.acquire(block) and not block:
286 if key == 'GENRE_NAME':
287 if not self._tags.has_key('GENRE'):
290 out = [genrenumbers.get(self._tags['GENRE'][0], self._tags['GENRE'][0])]
293 out = self._tags[key.upper()]
302 def __setitem__(self, key, value, block=True):
304 if not self._lock.acquire(block) and not block:
307 if type(value) != type([]):
309 self._tags[key.upper()] = value
315 def setorappend(self, key, value, block=True):
316 if not self._lock.acquire(block) and not block:
320 if self._tags.has_key(key):
321 self._tags[key].append(value)
329 def __delitem__(self, key, block=True):
330 if not self._lock.acquire(block) and not block:
343 def keys(self, block=True):
344 if not self._lock.acquire(block) and not block:
347 if self._tags.has_key('GENRE'):
348 out = self._tags.keys() + ['GENRE_NAME']
350 out = self._tags.keys()
356 def remove_from_index(trackname, indexname):
357 """Remove an entry from an index file."""
358 if not os.path.exists(indexname):
360 fp = file(indexname, 'r')
361 lines = fp.readlines()
365 while i < len(lines):
366 if lines[i] == trackname + '\n':
379 fp = file(indexname, 'w')
423 'Instrumental': '33',
436 'Instrum. Pop': '46',
437 'Instrum. Rock': '47',
441 'Techno-Indust.': '51',
446 'Southern Rock': '56',
451 'Christian Rap': '61',
454 'Native American': '64',
472 'National Folk': '82',
482 'Progress. Rock': '92',
483 'Psychedel. Rock': '93',
484 'Symphonic Rock': '94',
488 'Easy Listening': '98',
494 'Chamber Music': '104',
499 'Porn Groove': '109',
507 'Power Ballad': '117',
508 'Rhythmic Soul': '118',
517 'Drum & Bass': '127',
526 'Christian Gangsta Rap': '136',
527 'Heavy Metal': '137',
528 'Black Metal': '138',
530 'Contemporary Christian': '140',
531 'Christian Rock': '141',
534 'Thrash Metal': '144',
540 for key, value in genres.items():
541 genrenumbers[value] = key