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 '']
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'
116 except eyeD3.tag.TagException:
118 if HAVE_ID3 and not rok:
119 info = ID3.ID3(self._file, as_tuple=1).as_dict()
120 self._tags = copytags(info)
122 elif self._type == 'ogg' and HAVE_VORBIS:
123 info = ogg.vorbis.VorbisFile(self._file).comment().as_dict()
124 self._tags = copytags(info)
126 elif self._type == 'flac' and HAVE_METAFLAC:
127 cmd = '%s --export-tags-to=- "%s" ' % ('metaflac', self._file)
131 info = map(lambda x: x.split('=', 1), info.split('\n'))
132 info = filter(lambda x: len(x) == 2, info)
133 info = map(lambda x: [x[0].upper(), x[1:]], info)
134 self._tags = dict(info)
135 elif self._type == 'wav':
139 # Try to guess from the file's path - better than nothing
140 path = os.path.splitext(self._file)[0]
141 path = path.replace('_', ' ')
142 for id, depth in [('ARTIST', -3), ('ALBUM', -2), ('TITLE', -1)]:
143 if not id in self._tags or self._tags[id][0] == '':
145 self._tags[id] = [path.split(os.sep)[depth]]
147 self._tags[id] = "%s unknown" % id.lower()
154 def write(self, filename, block=True):
155 if not self._lock.acquire(block) and not block:
158 if not os.path.exists(filename):
160 raise setup.ErrorNoFile
163 for key in self._tags.keys(): # Not self.keys()
164 if key == 'GENRE' and fmt == 'mp3':
165 if type(self._tags[key][0]) is type(1) and 0 <= self._tags[key][0] < 256:
166 d[key] = self._tags[key][0]
167 elif self._tags[key][0] in map(str, range(256)):
168 d[key] = int(self._tags[key][0])
171 d[key] = int(genres.get(self._tags[key][0], '255'))
175 d[key] = self._tags[key][0]
176 # No! don't unlock here dumbass! return from puttags
180 fmt = self._opts.getfiletype(filename)
181 except setup.ErrorUnknownFileType:
186 vf = ogg.vorbis.VorbisFile(filename)
190 out.write_to(filename)
198 out.link(filename, eyeD3.ID3_V1)
199 out.setVersion(eyeD3.ID3_V1)
202 out.setArtist(d['ARTIST'])
203 out.setAlbum(d['ALBUM'])
204 out.setTitle(d['TITLE'])
206 g.setId(d.get('GENRE', 12))
208 if d.has_key('DATE') and int(d['DATE']) > 1600 and int(d['DATE']) < 3000:
209 out.setDate(d['DATE'])
212 if d.has_key('TRACKNUMBER') and d['TRACKNUMBER']:
214 out.setTrackNum((int(d['TRACKNUMBER']), None))
218 if d.has_key('COMMENT'):
219 out.addComment(d['COMMENT'])
223 out = ID3.ID3(filename, as_tuple=1)
228 # Tagging failed, but the file should be okay.
233 raise setup.ErrorUnknownFileType
239 def writeindex(self, filename, indexname, block=True):
240 if not self._lock.acquire(block) and not block:
244 ifile = codecs.open(indexname, 'a', 'utf-8')
245 ifile.write(filename + "\n")
246 keys = self._tags.keys()
249 for q in self._tags[k]:
250 ifile.write("%s: %s\n" % (k, q))
260 def format(self, fmt, block=True):
261 """Pretty print the tag information
263 The following format strings apply:
269 %y Album release year
272 %x File extension """
273 if not self._lock.acquire(block) and not block:
276 esc = {'n': 'TRACKNUMBER',
284 #'z': Used for literal '%'
287 fmt = fmt.replace('%%', '%z')
294 code = fmt[0] and fmt[0][0]
296 fmt[0] = '%' + fmt[0][1:]
298 fmt[0] = self._file + fmt[0][1:]
300 fmt[0] = self._type + fmt[0][1:]
301 elif code in esc.keys():
302 fmt[0] = self._tags.get(esc[code], ('',))[0] + fmt[0][1:]
305 raise setup.ErrorBadFormat
311 def __getitem__(self, key, block=True):
313 if not self._lock.acquire(block) and not block:
316 if key == 'GENRE_NAME':
317 if not self._tags.has_key('GENRE'):
320 out = [genrenumbers.get(self._tags['GENRE'][0], self._tags['GENRE'][0])]
323 out = self._tags[key.upper()]
332 def __setitem__(self, key, value, block=True):
334 if not self._lock.acquire(block) and not block:
337 if type(value) != type([]):
339 self._tags[key.upper()] = value
345 def setorappend(self, key, value, block=True):
346 if not self._lock.acquire(block) and not block:
350 if self._tags.has_key(key):
351 self._tags[key].append(value)
359 def __delitem__(self, key, block=True):
360 if not self._lock.acquire(block) and not block:
373 def keys(self, block=True):
374 if not self._lock.acquire(block) and not block:
377 if self._tags.has_key('GENRE'):
378 out = self._tags.keys() + ['GENRE_NAME']
380 out = self._tags.keys()
386 def remove_from_index(trackname, indexname):
387 """Remove an entry from an index file."""
388 if not os.path.exists(indexname):
390 fp = file(indexname, 'r')
391 lines = fp.readlines()
395 while i < len(lines):
396 if lines[i] == trackname + '\n':
409 fp = file(indexname, 'w')
453 'Instrumental': '33',
466 'Instrum. Pop': '46',
467 'Instrum. Rock': '47',
471 'Techno-Indust.': '51',
476 'Southern Rock': '56',
481 'Christian Rap': '61',
484 'Native American': '64',
502 'National Folk': '82',
512 'Progress. Rock': '92',
513 'Psychedel. Rock': '93',
514 'Symphonic Rock': '94',
518 'Easy Listening': '98',
524 'Chamber Music': '104',
529 'Porn Groove': '109',
537 'Power Ballad': '117',
538 'Rhythmic Soul': '118',
547 'Drum & Bass': '127',
556 'Christian Gangsta Rap': '136',
557 'Heavy Metal': '137',
558 'Black Metal': '138',
560 'Contemporary Christian': '140',
561 'Christian Rock': '141',
564 'Thrash Metal': '144',
570 for key, value in genres.items():
571 genrenumbers[value] = key