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:
95 if self._type == 'mp3' and HAVE_ID3:
99 self._tags['ARTIST'] = [eye.getArtist() or '']
100 self._tags['ALBUM'] = [eye.getAlbum() or '']
101 self._tags['TITLE'] = [eye.getTitle() or '']
102 self._tags['GENRE'] = [eye.getGenre().name or '']
105 info = ID3.ID3(self._file, as_tuple=1).as_dict()
106 self._tags = copytags(info)
108 elif self._type == 'ogg' and HAVE_VORBIS:
109 info = ogg.vorbis.VorbisFile(self._file).comment().as_dict()
110 self._tags = copytags(info)
112 elif self._type == 'flac' and HAVE_METAFLAC:
113 cmd = '%s --export-tags-to=- "%s" ' % ('metaflac', self._file)
117 info = map(lambda x: x.split('=', 1), info.split('\n'))
118 info = filter(lambda x: len(x) == 2, info)
119 info = map(lambda x: [x[0].upper(), x[1:]], info)
120 self._tags = dict(info)
121 elif self._type == 'wav':
125 # Try to guess from the file's path - better than nothing
126 path = os.path.splitext(self._file)[0]
127 path = path.replace('_', ' ')
128 for id, depth in [('ARTIST', -3), ('ALBUM', -2), ('TITLE', -1)]:
129 if not id in self._tags or self._tags[id][0] == '':
131 self._tags[id] = [path.split(os.sep)[depth]]
133 self._tags[id] = "%s unknown" % id.lower()
140 def write(self, filename, block=True):
141 if not self._lock.acquire(block) and not block:
144 if not os.path.exists(filename):
146 raise setup.ErrorNoFile
149 for key in self._tags.keys(): # Not self.keys()
150 if key == 'GENRE' and fmt == 'mp3':
151 if type(self._tags[key][0]) is type(1) and 0 <= self._tags[key][0] < 256:
152 d[key] = self._tags[key][0]
153 elif self._tags[key][0] in map(str, range(256)):
154 d[key] = int(self._tags[key][0])
156 d[key] = int(genres.get(self._tags[key][0], '255'))
158 d[key] = self._tags[key][0]
159 # No! don't unlock here dumbass! return from puttags
163 fmt = self._opts.getfiletype(filename)
164 except setup.ErrorUnknownFileType:
169 vf = ogg.vorbis.VorbisFile(filename)
173 out.write_to(filename)
181 out.link(filename, eyeD3.ID3_V1)
182 out.setVersion(eyeD3.ID3_V1)
185 out.setArtist(d['ARTIST'])
186 out.setAlbum(d['ALBUM'])
187 out.setTitle(d['TITLE'])
191 if d.has_key('COMMENT'):
192 out.addComment(d['COMMENT'])
196 out = ID3.ID3(filename, as_tuple=1)
201 # Tagging failed, but the file should be okay.
206 raise setup.ErrorUnknownFileType
212 def writeindex(self, filename, indexname, block=True):
213 if not self._lock.acquire(block) and not block:
217 ifile = codecs.open(indexname, 'a', 'utf-8')
218 ifile.write(filename + "\n")
219 keys = self._tags.keys()
222 for q in self._tags[k]:
223 ifile.write("%s: %s\n" % (k, q))
233 def format(self, fmt, block=True):
234 """Pretty print the tag information
236 The following format strings apply:
242 %y Album release year
244 if not self._lock.acquire(block) and not block:
247 esc = {'n': 'TRACKNUMBER',
253 #'z': Used for literal '%'
256 fmt = fmt.replace('%%', '%z')
263 code = fmt[0] and fmt[0][0]
265 fmt[0] = '%' + fmt[0][1:]
266 elif code in esc.keys():
267 fmt[0] = self._tags.get(esc[code], ('',))[0] + fmt[0][1:]
270 raise setup.ErrorBadFormat
276 def __getitem__(self, key, block=True):
278 if not self._lock.acquire(block) and not block:
281 if key == 'GENRE_NAME':
282 if not self._tags.has_key('GENRE'):
285 out = [genrenumbers.get(self._tags['GENRE'][0], self._tags['GENRE'][0])]
288 out = self._tags[key.upper()]
297 def __setitem__(self, key, value, block=True):
299 if not self._lock.acquire(block) and not block:
302 if type(value) != type([]):
304 self._tags[key.upper()] = value
310 def setorappend(self, key, value, block=True):
311 if not self._lock.acquire(block) and not block:
315 if self._tags.has_key(key):
316 self._tags[key].append(value)
324 def __delitem__(self, key, block=True):
325 if not self._lock.acquire(block) and not block:
338 def keys(self, block=True):
339 if not self._lock.acquire(block) and not block:
342 if self._tags.has_key('GENRE'):
343 out = self._tags.keys() + ['GENRE_NAME']
345 out = self._tags.keys()
351 def remove_from_index(trackname, indexname):
352 """Remove an entry from an index file."""
353 if not os.path.exists(indexname):
355 fp = file(indexname, 'r')
356 lines = fp.readlines()
360 while i < len(lines):
361 if lines[i] == trackname + '\n':
374 fp = file(indexname, 'w')
418 'Instrumental': '33',
431 'Instrum. Pop': '46',
432 'Instrum. Rock': '47',
436 'Techno-Indust.': '51',
441 'Southern Rock': '56',
446 'Christian Rap': '61',
449 'Native American': '64',
467 'National Folk': '82',
477 'Progress. Rock': '92',
478 'Psychedel. Rock': '93',
479 'Symphonic Rock': '94',
483 'Easy Listening': '98',
489 'Chamber Music': '104',
494 'Porn Groove': '109',
502 'Power Ballad': '117',
503 'Rhythmic Soul': '118',
512 'Drum & Bass': '127',
521 'Christian Gangsta Rap': '136',
522 'Heavy Metal': '137',
523 'Black Metal': '138',
525 'Contemporary Christian': '140',
526 'Christian Rock': '141',
529 'Thrash Metal': '144',
535 for key, value in genres.items():
536 genrenumbers[value] = key