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'] = [getattr(eye.getGenre(), 'name', '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
247 %x File extension """
248 if not self._lock.acquire(block) and not block:
251 esc = {'n': 'TRACKNUMBER',
259 #'z': Used for literal '%'
262 fmt = fmt.replace('%%', '%z')
269 code = fmt[0] and fmt[0][0]
271 fmt[0] = '%' + fmt[0][1:]
273 fmt[0] = self._file + fmt[0][1:]
275 fmt[0] = self._type + 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