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.
45 for path in os.environ['PATH'].split(':'):
46 if os.path.exists(os.path.join(path, 'metaflac')):
51 class Tags(UserDict.DictMixin):
52 """Manage per track metadata.
54 Provide a filename in the constructor
55 Call read() to read in the tags
56 modify tags if necessary
57 Call write(name) with the output file the tags
59 Tags are available through a dictionary interface
60 format(fmt) returns a formatted string of metadata
63 def __init__(self, filename):
64 if not os.path.exists(filename):
65 raise setup.ErrorNoFile
67 self._type = setup.getfiletype(filename)
69 self._readok = threading.Event()
71 self._lock = threading.Lock()
74 def read(self, block=True):
75 if not self._lock.acquire(block) and not block:
85 if self._type == 'mp3' and HAVE_ID3:
86 info = ID3.ID3(self._file, as_tuple=1).as_dict()
87 self._tags = copytags(info)
89 elif self._type == 'ogg' and HAVE_VORBIS:
90 info = ogg.vorbis.VorbisFile(self._file).comment().as_dict()
91 self._tags = copytags(info)
93 elif self._type == 'flac' and HAVE_METAFLAC:
94 cmd = '%s --export-tags-to=- "%s" ' % ('metaflac', self._file)
98 info = map(lambda x: x.split('=', 1), info.split('\n'))
99 info = filter(lambda x: len(x) == 2, info)
100 info = map(lambda x: [x[0].upper(), x[1:]], info)
101 self._tags = dict(info)
102 elif self._type == 'wav':
106 # Try to guess from the file's path - better than nothing
107 path = os.path.splitext(self._file)[0]
108 path = path.replace('_', ' ')
109 for id, depth in [('ARTIST', -3), ('ALBUM', -2), ('TITLE', -1)]:
110 if not id in self._tags or self._tags[id][0] == '':
112 self._tags[id] = [path.split(os.sep)[depth]]
114 self._tags[id] = "%s unknown" % id.lower()
121 def write(self, filename, block=True):
122 if not self._lock.acquire(block) and not block:
125 if not os.path.exists(filename):
127 raise setup.ErrorNoFile
130 for key in self._tags.keys(): # Not self.keys()
131 if key == 'GENRE' and fmt == 'mp3':
132 if type(self._tags[key][0]) is type(1) and 0 <= self._tags[key][0] < 256:
133 d[key] = self._tags[key][0]
134 elif self._tags[key][0] in map(str, range(256)):
135 d[key] = int(self._tags[key][0])
137 d[key] = int(genres.get(self._tags[key][0], '255'))
139 d[key] = self._tags[key][0]
140 # No! don't unlock here dumbass! return from puttags
144 fmt = setup.getfiletype(filename)
145 except setup.ErrorUnknownFileType:
150 vf = ogg.vorbis.VorbisFile(filename)
154 out.write_to(filename)
160 out = ID3.ID3(filename, as_tuple=1)
165 # Tagging failed, but the file should be okay.
170 raise setup.ErrorUnknownFileType
176 def writeindex(self, filename, indexname, block=True):
177 if not self._lock.acquire(block) and not block:
181 ifile = file(indexname, 'a')
182 ifile.write(filename + "\n")
183 keys = self._tags.keys()
186 for q in self._tags[k]:
187 ifile.write("%s: %s\n" % (k, q))
197 def format(self, fmt, block=True):
198 """Pretty print the tag information
200 The following format strings apply:
206 %y Album release year
208 if not self._lock.acquire(block) and not block:
211 esc = {'n': 'TRACKNUMBER',
217 #'z': Used for literal '%'
220 fmt = fmt.replace('%%', '%z')
227 code = fmt[0] and fmt[0][0]
229 fmt[0] = '%' + fmt[0][1:]
230 elif code in esc.keys():
231 fmt[0] = self._tags.get(esc[code], ('',))[0] + fmt[0][1:]
234 raise setup.ErrorBadFormat
240 def __getitem__(self, key, block=True):
242 if not self._lock.acquire(block) and not block:
245 if key == 'GENRE_NAME':
246 if not self._tags.has_key('GENRE'):
249 out = [genrenumbers.get(self._tags['GENRE'][0], self._tags['GENRE'][0])]
252 out = self._tags[key.upper()]
261 def __setitem__(self, key, value, block=True):
263 if not self._lock.acquire(block) and not block:
266 if type(value) != type([]):
268 self._tags[key.upper()] = value
274 def setorappend(self, key, value, block=True):
275 if not self._lock.acquire(block) and not block:
279 if self._tags.has_key(key):
280 self._tags[key].append(value)
288 def __delitem__(self, key, block=True):
289 if not self._lock.acquire(block) and not block:
302 def keys(self, block=True):
303 if not self._lock.acquire(block) and not block:
306 if self._tags.has_key('GENRE'):
307 out = self._tags.keys() + ['GENRE_NAME']
309 out = self._tags.keys()
315 def remove_from_index(trackname, indexname):
316 """Remove an entry from an index file."""
317 if not os.path.exists(indexname):
319 fp = file(indexname, 'r')
320 lines = fp.readlines()
324 while i < len(lines):
325 if lines[i] == trackname + '\n':
338 fp = file(indexname, 'w')
382 'Instrumental': '33',
395 'Instrum. Pop': '46',
396 'Instrum. Rock': '47',
400 'Techno-Indust.': '51',
405 'Southern Rock': '56',
410 'Christian Rap': '61',
413 'Native American': '64',
431 'National Folk': '82',
441 'Progress. Rock': '92',
442 'Psychedel. Rock': '93',
443 'Symphonic Rock': '94',
447 'Easy Listening': '98',
453 'Chamber Music': '104',
458 'Porn Groove': '109',
466 'Power Ballad': '117',
467 'Rhythmic Soul': '118',
476 'Drum & Bass': '127',
485 'Christian Gangsta Rap': '136',
486 'Heavy Metal': '137',
487 'Black Metal': '138',
489 'Contemporary Christian': '140',
490 'Christian Rock': '141',
493 'Thrash Metal': '144',
499 for key, value in genres.items():
500 genrenumbers[value] = key