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:
103 self._tags['ARTIST'] = [eye.getArtist() or '']
104 self._tags['ALBUM'] = [eye.getAlbum() or '']
105 self._tags['TITLE'] = [eye.getTitle() or '']
107 self._tags['GENRE'] = [getattr(eye.getGenre(), 'name', 'Other')]
108 except eyeD3.tag.GenreException:
109 self._tags['GENRE'] = 'Other'
112 except eyeD3.tag.TagException:
114 if HAVE_ID3 and not rok:
115 info = ID3.ID3(self._file, as_tuple=1).as_dict()
116 self._tags = copytags(info)
118 elif self._type == 'ogg' and HAVE_VORBIS:
119 info = ogg.vorbis.VorbisFile(self._file).comment().as_dict()
120 self._tags = copytags(info)
122 elif self._type == 'flac' and HAVE_METAFLAC:
123 cmd = '%s --export-tags-to=- "%s" ' % ('metaflac', self._file)
127 info = map(lambda x: x.split('=', 1), info.split('\n'))
128 info = filter(lambda x: len(x) == 2, info)
129 info = map(lambda x: [x[0].upper(), x[1:]], info)
130 self._tags = dict(info)
131 elif self._type == 'wav':
135 # Try to guess from the file's path - better than nothing
136 path = os.path.splitext(self._file)[0]
137 path = path.replace('_', ' ')
138 for id, depth in [('ARTIST', -3), ('ALBUM', -2), ('TITLE', -1)]:
139 if not id in self._tags or self._tags[id][0] == '':
141 self._tags[id] = [path.split(os.sep)[depth]]
143 self._tags[id] = "%s unknown" % id.lower()
150 def write(self, filename, block=True):
151 if not self._lock.acquire(block) and not block:
154 if not os.path.exists(filename):
156 raise setup.ErrorNoFile
159 for key in self._tags.keys(): # Not self.keys()
160 if key == 'GENRE' and fmt == 'mp3':
161 if type(self._tags[key][0]) is type(1) and 0 <= self._tags[key][0] < 256:
162 d[key] = self._tags[key][0]
163 elif self._tags[key][0] in map(str, range(256)):
164 d[key] = int(self._tags[key][0])
166 d[key] = int(genres.get(self._tags[key][0], '255'))
168 d[key] = self._tags[key][0]
169 # No! don't unlock here dumbass! return from puttags
173 fmt = self._opts.getfiletype(filename)
174 except setup.ErrorUnknownFileType:
179 vf = ogg.vorbis.VorbisFile(filename)
183 out.write_to(filename)
191 out.link(filename, eyeD3.ID3_V1)
192 out.setVersion(eyeD3.ID3_V1)
195 out.setArtist(d['ARTIST'])
196 out.setAlbum(d['ALBUM'])
197 out.setTitle(d['TITLE'])
199 g.setId(d.get('GENRE', 12))
201 if d.has_key('COMMENT'):
202 out.addComment(d['COMMENT'])
206 out = ID3.ID3(filename, as_tuple=1)
211 # Tagging failed, but the file should be okay.
216 raise setup.ErrorUnknownFileType
222 def writeindex(self, filename, indexname, block=True):
223 if not self._lock.acquire(block) and not block:
227 ifile = codecs.open(indexname, 'a', 'utf-8')
228 ifile.write(filename + "\n")
229 keys = self._tags.keys()
232 for q in self._tags[k]:
233 ifile.write("%s: %s\n" % (k, q))
243 def format(self, fmt, block=True):
244 """Pretty print the tag information
246 The following format strings apply:
252 %y Album release year
254 if not self._lock.acquire(block) and not block:
257 esc = {'n': 'TRACKNUMBER',
263 #'z': Used for literal '%'
266 fmt = fmt.replace('%%', '%z')
273 code = fmt[0] and fmt[0][0]
275 fmt[0] = '%' + fmt[0][1:]
276 elif code in esc.keys():
277 fmt[0] = self._tags.get(esc[code], ('',))[0] + fmt[0][1:]
280 raise setup.ErrorBadFormat
286 def __getitem__(self, key, block=True):
288 if not self._lock.acquire(block) and not block:
291 if key == 'GENRE_NAME':
292 if not self._tags.has_key('GENRE'):
295 out = [genrenumbers.get(self._tags['GENRE'][0], self._tags['GENRE'][0])]
298 out = self._tags[key.upper()]
307 def __setitem__(self, key, value, block=True):
309 if not self._lock.acquire(block) and not block:
312 if type(value) != type([]):
314 self._tags[key.upper()] = value
320 def setorappend(self, key, value, block=True):
321 if not self._lock.acquire(block) and not block:
325 if self._tags.has_key(key):
326 self._tags[key].append(value)
334 def __delitem__(self, key, block=True):
335 if not self._lock.acquire(block) and not block:
348 def keys(self, block=True):
349 if not self._lock.acquire(block) and not block:
352 if self._tags.has_key('GENRE'):
353 out = self._tags.keys() + ['GENRE_NAME']
355 out = self._tags.keys()
361 def remove_from_index(trackname, indexname):
362 """Remove an entry from an index file."""
363 if not os.path.exists(indexname):
365 fp = file(indexname, 'r')
366 lines = fp.readlines()
370 while i < len(lines):
371 if lines[i] == trackname + '\n':
384 fp = file(indexname, 'w')
428 'Instrumental': '33',
441 'Instrum. Pop': '46',
442 'Instrum. Rock': '47',
446 'Techno-Indust.': '51',
451 'Southern Rock': '56',
456 'Christian Rap': '61',
459 'Native American': '64',
477 'National Folk': '82',
487 'Progress. Rock': '92',
488 'Psychedel. Rock': '93',
489 'Symphonic Rock': '94',
493 'Easy Listening': '98',
499 'Chamber Music': '104',
504 'Porn Groove': '109',
512 'Power Ballad': '117',
513 'Rhythmic Soul': '118',
522 'Drum & Bass': '127',
531 'Christian Gangsta Rap': '136',
532 'Heavy Metal': '137',
533 'Black Metal': '138',
535 'Contemporary Christian': '140',
536 'Christian Rock': '141',
539 'Thrash Metal': '144',
545 for key, value in genres.items():
546 genrenumbers[value] = key