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 '']
104 self._tags['GENRE'] = [eye.getGenre().name or 'Other']
107 info = ID3.ID3(self._file, as_tuple=1).as_dict()
108 self._tags = copytags(info)
110 elif self._type == 'ogg' and HAVE_VORBIS:
111 info = ogg.vorbis.VorbisFile(self._file).comment().as_dict()
112 self._tags = copytags(info)
114 elif self._type == 'flac' and HAVE_METAFLAC:
115 cmd = '%s --export-tags-to=- "%s" ' % ('metaflac', self._file)
119 info = map(lambda x: x.split('=', 1), info.split('\n'))
120 info = filter(lambda x: len(x) == 2, info)
121 info = map(lambda x: [x[0].upper(), x[1:]], info)
122 self._tags = dict(info)
123 elif self._type == 'wav':
127 # Try to guess from the file's path - better than nothing
128 path = os.path.splitext(self._file)[0]
129 path = path.replace('_', ' ')
130 for id, depth in [('ARTIST', -3), ('ALBUM', -2), ('TITLE', -1)]:
131 if not id in self._tags or self._tags[id][0] == '':
133 self._tags[id] = [path.split(os.sep)[depth]]
135 self._tags[id] = "%s unknown" % id.lower()
142 def write(self, filename, block=True):
143 if not self._lock.acquire(block) and not block:
146 if not os.path.exists(filename):
148 raise setup.ErrorNoFile
151 for key in self._tags.keys(): # Not self.keys()
152 if key == 'GENRE' and fmt == 'mp3':
153 if type(self._tags[key][0]) is type(1) and 0 <= self._tags[key][0] < 256:
154 d[key] = self._tags[key][0]
155 elif self._tags[key][0] in map(str, range(256)):
156 d[key] = int(self._tags[key][0])
158 d[key] = int(genres.get(self._tags[key][0], '255'))
160 d[key] = self._tags[key][0]
161 # No! don't unlock here dumbass! return from puttags
165 fmt = self._opts.getfiletype(filename)
166 except setup.ErrorUnknownFileType:
171 vf = ogg.vorbis.VorbisFile(filename)
175 out.write_to(filename)
183 out.link(filename, eyeD3.ID3_V1)
184 out.setVersion(eyeD3.ID3_V1)
187 out.setArtist(d['ARTIST'])
188 out.setAlbum(d['ALBUM'])
189 out.setTitle(d['TITLE'])
193 if d.has_key('COMMENT'):
194 out.addComment(d['COMMENT'])
198 out = ID3.ID3(filename, as_tuple=1)
203 # Tagging failed, but the file should be okay.
208 raise setup.ErrorUnknownFileType
214 def writeindex(self, filename, indexname, block=True):
215 if not self._lock.acquire(block) and not block:
219 ifile = codecs.open(indexname, 'a', 'utf-8')
220 ifile.write(filename + "\n")
221 keys = self._tags.keys()
224 for q in self._tags[k]:
225 ifile.write("%s: %s\n" % (k, q))
235 def format(self, fmt, block=True):
236 """Pretty print the tag information
238 The following format strings apply:
244 %y Album release year
246 if not self._lock.acquire(block) and not block:
249 esc = {'n': 'TRACKNUMBER',
255 #'z': Used for literal '%'
258 fmt = fmt.replace('%%', '%z')
265 code = fmt[0] and fmt[0][0]
267 fmt[0] = '%' + fmt[0][1:]
268 elif code in esc.keys():
269 fmt[0] = self._tags.get(esc[code], ('',))[0] + fmt[0][1:]
272 raise setup.ErrorBadFormat
278 def __getitem__(self, key, block=True):
280 if not self._lock.acquire(block) and not block:
283 if key == 'GENRE_NAME':
284 if not self._tags.has_key('GENRE'):
287 out = [genrenumbers.get(self._tags['GENRE'][0], self._tags['GENRE'][0])]
290 out = self._tags[key.upper()]
299 def __setitem__(self, key, value, block=True):
301 if not self._lock.acquire(block) and not block:
304 if type(value) != type([]):
306 self._tags[key.upper()] = value
312 def setorappend(self, key, value, block=True):
313 if not self._lock.acquire(block) and not block:
317 if self._tags.has_key(key):
318 self._tags[key].append(value)
326 def __delitem__(self, key, block=True):
327 if not self._lock.acquire(block) and not block:
340 def keys(self, block=True):
341 if not self._lock.acquire(block) and not block:
344 if self._tags.has_key('GENRE'):
345 out = self._tags.keys() + ['GENRE_NAME']
347 out = self._tags.keys()
353 def remove_from_index(trackname, indexname):
354 """Remove an entry from an index file."""
355 if not os.path.exists(indexname):
357 fp = file(indexname, 'r')
358 lines = fp.readlines()
362 while i < len(lines):
363 if lines[i] == trackname + '\n':
376 fp = file(indexname, 'w')
420 'Instrumental': '33',
433 'Instrum. Pop': '46',
434 'Instrum. Rock': '47',
438 'Techno-Indust.': '51',
443 'Southern Rock': '56',
448 'Christian Rap': '61',
451 'Native American': '64',
469 'National Folk': '82',
479 'Progress. Rock': '92',
480 'Psychedel. Rock': '93',
481 'Symphonic Rock': '94',
485 'Easy Listening': '98',
491 'Chamber Music': '104',
496 'Porn Groove': '109',
504 'Power Ballad': '117',
505 'Rhythmic Soul': '118',
514 'Drum & Bass': '127',
523 'Christian Gangsta Rap': '136',
524 'Heavy Metal': '137',
525 'Black Metal': '138',
527 'Contemporary Christian': '140',
528 'Christian Rock': '141',
531 'Thrash Metal': '144',
537 for key, value in genres.items():
538 genrenumbers[value] = key