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
271 if not self._lock.acquire(block) and not block:
274 esc = {'n': 'TRACKNUMBER',
280 #'z': Used for literal '%'
283 fmt = fmt.replace('%%', '%z')
290 code = fmt[0] and fmt[0][0]
292 fmt[0] = '%' + fmt[0][1:]
293 elif code in esc.keys():
294 fmt[0] = self._tags.get(esc[code], ('',))[0] + fmt[0][1:]
297 raise setup.ErrorBadFormat
303 def __getitem__(self, key, block=True):
305 if not self._lock.acquire(block) and not block:
308 if key == 'GENRE_NAME':
309 if not self._tags.has_key('GENRE'):
312 out = [genrenumbers.get(self._tags['GENRE'][0], self._tags['GENRE'][0])]
315 out = self._tags[key.upper()]
324 def __setitem__(self, key, value, block=True):
326 if not self._lock.acquire(block) and not block:
329 if type(value) != type([]):
331 self._tags[key.upper()] = value
337 def setorappend(self, key, value, block=True):
338 if not self._lock.acquire(block) and not block:
342 if self._tags.has_key(key):
343 self._tags[key].append(value)
351 def __delitem__(self, key, block=True):
352 if not self._lock.acquire(block) and not block:
365 def keys(self, block=True):
366 if not self._lock.acquire(block) and not block:
369 if self._tags.has_key('GENRE'):
370 out = self._tags.keys() + ['GENRE_NAME']
372 out = self._tags.keys()
378 def remove_from_index(trackname, indexname):
379 """Remove an entry from an index file."""
380 if not os.path.exists(indexname):
382 fp = file(indexname, 'r')
383 lines = fp.readlines()
387 while i < len(lines):
388 if lines[i] == trackname + '\n':
401 fp = file(indexname, 'w')
445 'Instrumental': '33',
458 'Instrum. Pop': '46',
459 'Instrum. Rock': '47',
463 'Techno-Indust.': '51',
468 'Southern Rock': '56',
473 'Christian Rap': '61',
476 'Native American': '64',
494 'National Folk': '82',
504 'Progress. Rock': '92',
505 'Psychedel. Rock': '93',
506 'Symphonic Rock': '94',
510 'Easy Listening': '98',
516 'Chamber Music': '104',
521 'Porn Groove': '109',
529 'Power Ballad': '117',
530 'Rhythmic Soul': '118',
539 'Drum & Bass': '127',
548 'Christian Gangsta Rap': '136',
549 'Heavy Metal': '137',
550 'Black Metal': '138',
552 'Contemporary Christian': '140',
553 'Christian Rock': '141',
556 'Thrash Metal': '144',
562 for key, value in genres.items():
563 genrenumbers[value] = key