Merge lp:~eh/coverlovin/mutagen into lp:coverlovin
- mutagen
- Merge into coverlovin
Proposed by
Eetu Huisman
Status: | Merged |
---|---|
Merged at revision: | 7 |
Proposed branch: | lp:~eh/coverlovin/mutagen |
Merge into: | lp:coverlovin |
Diff against target: |
670 lines (+10/-632) 2 files modified
coverlovin.py (+10/-5) id3reader.py (+0/-627) |
To merge this branch: | bzr merge lp:~eh/coverlovin/mutagen |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
James Stewart | Approve | ||
Review via email: mp+102965@code.launchpad.net |
Commit message
Description of the change
Added support for other types besides mp3 by replacing id3reader with a dependency to Mutagen.
To post a comment you must log in.
Revision history for this message
James Stewart (amorphic) : | # |
review:
Approve
Preview Diff
[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1 | === modified file 'coverlovin.py' |
2 | --- coverlovin.py 2012-02-06 14:08:17 +0000 |
3 | +++ coverlovin.py 2012-04-21 08:59:19 +0000 |
4 | @@ -10,7 +10,7 @@ |
5 | import os, sys |
6 | import urllib, urllib2 |
7 | import simplejson |
8 | -import id3reader |
9 | +import mutagen |
10 | import logging |
11 | from optparse import OptionParser |
12 | |
13 | @@ -147,16 +147,21 @@ |
14 | fileFullPath = os.path.join(thisDir, file) |
15 | if os.path.getsize(fileFullPath) > 128: |
16 | |
17 | - # check file for id3 tag info |
18 | + # check file for tag info |
19 | try: |
20 | - id3r = id3reader.Reader(fileFullPath) |
21 | + fileInfo = mutagen.File(fileFullPath, easy=True) |
22 | except: |
23 | log.info("Skipping " + file + " due to unicode error") |
24 | continue |
25 | |
26 | - artist = id3r.getValue('performer') |
27 | - album = id3r.getValue('album') |
28 | + try: |
29 | + artist = fileInfo["artist"][0] |
30 | + album = fileInfo["album"][0] |
31 | + except: |
32 | + log.debug("Artist or album of " + file + " not found, continuing") |
33 | + continue |
34 | |
35 | + log.info("artist: " + artist + ", album: " + album) |
36 | # sanitise None values |
37 | if artist == None: artist = '' |
38 | if album == None: album = '' |
39 | |
40 | === removed file 'id3reader.py' |
41 | --- id3reader.py 2011-02-06 03:46:54 +0000 |
42 | +++ id3reader.py 1970-01-01 00:00:00 +0000 |
43 | @@ -1,627 +0,0 @@ |
44 | -""" Read ID3 tags from a file. |
45 | - Ned Batchelder, http://nedbatchelder.com/code/modules/id3reader.html |
46 | -""" |
47 | - |
48 | -__version__ = '1.53.20070415' # History at the end of the file. |
49 | - |
50 | -# ID3 specs: http://www.id3.org/develop.html |
51 | - |
52 | -import struct, sys, zlib |
53 | - |
54 | -# These are the text encodings, indexed by the first byte of a text value. |
55 | -_encodings = ['iso8859-1', 'utf-16', 'utf-16be', 'utf-8'] |
56 | - |
57 | -# Simple pseudo-id's, mapped to their various representations. |
58 | -# Use these ids with getValue, and you don't need to know what |
59 | -# version of ID3 the file contains. |
60 | -_simpleDataMapping = { |
61 | - 'album': ('TALB', 'TAL', 'v1album', 'TOAL'), |
62 | - 'performer': ('TPE1', 'TP1', 'v1performer', 'TOPE'), |
63 | - 'title': ('TIT2', 'TT2', 'v1title'), |
64 | - 'track': ('TRCK', 'TRK', 'v1track'), |
65 | - 'year': ('TYER', 'TYE', 'v1year'), |
66 | - 'genre': ('TCON', 'TCO', 'v1genre'), |
67 | - 'comment': ('COMM', 'COM', 'v1comment'), |
68 | -} |
69 | - |
70 | -# Provide booleans for older Pythons. |
71 | -try: |
72 | - True, False |
73 | -except NameError: |
74 | - True, False = 1==1, 1==0 |
75 | - |
76 | -# Tracing |
77 | -_t = False |
78 | -def _trace(msg): |
79 | - print msg |
80 | - |
81 | -# Coverage |
82 | -_c = False |
83 | -_features = {} |
84 | -def _coverage(feat): |
85 | - #if _t: _trace('feature '+feat) |
86 | - _features[feat] = _features.setdefault(feat, 0)+1 |
87 | - |
88 | -def _safestr(s): |
89 | - """ Get a good string for printing, that won't throw exceptions, |
90 | - no matter what's in it. |
91 | - """ |
92 | - try: |
93 | - return unicode(s).encode(sys.getdefaultencoding()) |
94 | - except UnicodeError: |
95 | - return '?: '+repr(s) |
96 | - |
97 | -# Can I just say that I think the whole concept of genres is bogus, |
98 | -# since they are so subjective? And the idea of letting someone else pick |
99 | -# one of these things and then have it affect the categorization of my music |
100 | -# is extra bogus. And the list itself is absurd. Polsk Punk? |
101 | -_genres = [ |
102 | - # 0-19 |
103 | - 'Blues', 'Classic Rock', 'Country', 'Dance', 'Disco', 'Funk', 'Grunge', 'Hip - Hop', 'Jazz', 'Metal', |
104 | - 'New Age', 'Oldies', 'Other', 'Pop', 'R&B', 'Rap', 'Reggae', 'Rock', 'Techno', 'Industrial', |
105 | - # 20-39 |
106 | - 'Alternative', 'Ska', 'Death Metal', 'Pranks', 'Soundtrack', 'Euro - Techno', 'Ambient', 'Trip - Hop', 'Vocal', 'Jazz + Funk', |
107 | - 'Fusion', 'Trance', 'Classical', 'Instrumental', 'Acid', 'House', 'Game', 'Sound Clip', 'Gospel', 'Noise', |
108 | - # 40-59 |
109 | - 'Alt Rock', 'Bass', 'Soul', 'Punk', 'Space', 'Meditative', 'Instrumental Pop', 'Instrumental Rock', 'Ethnic', 'Gothic', |
110 | - 'Darkwave', 'Techno - Industrial', 'Electronic', 'Pop - Folk', 'Eurodance', 'Dream', 'Southern Rock', 'Comedy', 'Cult', 'Gangsta Rap', |
111 | - # 60-79 |
112 | - 'Top 40', 'Christian Rap', 'Pop / Funk', 'Jungle', 'Native American', 'Cabaret', 'New Wave', 'Psychedelic', 'Rave', 'Showtunes', |
113 | - 'Trailer', 'Lo - Fi', 'Tribal', 'Acid Punk', 'Acid Jazz', 'Polka', 'Retro', 'Musical', 'Rock & Roll', 'Hard Rock', |
114 | - # 80-99 |
115 | - 'Folk', 'Folk / Rock', 'National Folk', 'Swing', 'Fast - Fusion', 'Bebob', 'Latin', 'Revival', 'Celtic', 'Bluegrass', |
116 | - 'Avantgarde', 'Gothic Rock', 'Progressive Rock', 'Psychedelic Rock', 'Symphonic Rock', 'Slow Rock', 'Big Band', 'Chorus', 'Easy Listening', 'Acoustic', |
117 | - # 100-119 |
118 | - 'Humour', 'Speech', 'Chanson', 'Opera', 'Chamber Music', 'Sonata', 'Symphony', 'Booty Bass', 'Primus', 'Porn Groove', |
119 | - 'Satire', 'Slow Jam', 'Club', 'Tango', 'Samba', 'Folklore', 'Ballad', 'Power Ballad', 'Rhythmic Soul', 'Freestyle', |
120 | - # 120-139 |
121 | - 'Duet', 'Punk Rock', 'Drum Solo', 'A Cappella', 'Euro - House', 'Dance Hall', 'Goa', 'Drum & Bass', 'Club - House', 'Hardcore', |
122 | - 'Terror', 'Indie', 'BritPop', 'Negerpunk', 'Polsk Punk', 'Beat', 'Christian Gangsta Rap', 'Heavy Metal', 'Black Metal', 'Crossover', |
123 | - # 140-147 |
124 | - 'Contemporary Christian', 'Christian Rock', 'Merengue', 'Salsa', 'Thrash Metal', 'Anime', 'JPop', 'Synthpop' |
125 | - ] |
126 | - |
127 | -class Id3Error(Exception): |
128 | - """ An exception caused by id3reader properly handling a bad ID3 tag. |
129 | - """ |
130 | - pass |
131 | - |
132 | -class _Header: |
133 | - """ Represent the ID3 header in a tag. |
134 | - """ |
135 | - def __init__(self): |
136 | - self.majorVersion = 0 |
137 | - self.revision = 0 |
138 | - self.flags = 0 |
139 | - self.size = 0 |
140 | - self.bUnsynchronized = False |
141 | - self.bExperimental = False |
142 | - self.bFooter = False |
143 | - |
144 | - def __str__(self): |
145 | - return str(self.__dict__) |
146 | - |
147 | -class _Frame: |
148 | - """ Represent an ID3 frame in a tag. |
149 | - """ |
150 | - def __init__(self): |
151 | - self.id = '' |
152 | - self.size = 0 |
153 | - self.flags = 0 |
154 | - self.rawData = '' |
155 | - self.bTagAlterPreserve = False |
156 | - self.bFileAlterPreserve = False |
157 | - self.bReadOnly = False |
158 | - self.bCompressed = False |
159 | - self.bEncrypted = False |
160 | - self.bInGroup = False |
161 | - |
162 | - def __str__(self): |
163 | - return str(self.__dict__) |
164 | - |
165 | - def __repr__(self): |
166 | - return str(self.__dict__) |
167 | - |
168 | - def _interpret(self): |
169 | - """ Examine self.rawData and create a self.value from it. |
170 | - """ |
171 | - if len(self.rawData) == 0: |
172 | - # This is counter to the spec, but seems harmless enough. |
173 | - #if _c: _coverage('zero data') |
174 | - return |
175 | - |
176 | - if self.bCompressed: |
177 | - # Decompress the compressed data. |
178 | - self.rawData = zlib.decompress(self.rawData) |
179 | - |
180 | - if self.id[0] == 'T': |
181 | - # Text fields start with T |
182 | - encoding = ord(self.rawData[0]) |
183 | - if 0 <= encoding < len(_encodings): |
184 | - #if _c: _coverage('encoding%d' % encoding) |
185 | - value = self.rawData[1:].decode(_encodings[encoding]) |
186 | - else: |
187 | - #if _c: _coverage('bad encoding') |
188 | - value = self.rawData[1:] |
189 | - # Don't let trailing zero bytes fool you. |
190 | - if value: |
191 | - value = value.strip('\0') |
192 | - # The value can actually be a list. |
193 | - if '\0' in value: |
194 | - value = value.split('\0') |
195 | - #if _c: _coverage('textlist') |
196 | - self.value = value |
197 | - elif self.id[0] == 'W': |
198 | - # URL fields start with W |
199 | - self.value = self.rawData.strip('\0') |
200 | - if self.id == 'WXXX': |
201 | - self.value = self.value.split('\0') |
202 | - elif self.id == 'CDM': |
203 | - # ID3v2.2.1 Compressed Data Metaframe |
204 | - if self.rawData[0] == 'z': |
205 | - self.rawData = zlib.decompress(self.rawData[5:]) |
206 | - else: |
207 | - #if _c: _coverage('badcdm!') |
208 | - raise Id3Error, 'Unknown CDM compression: %02x' % self.rawData[0] |
209 | - #@TODO: re-interpret the decompressed frame. |
210 | - |
211 | - elif self.id in _simpleDataMapping['comment']: |
212 | - # comment field |
213 | - |
214 | - # In limited testing a typical comment looks like |
215 | - # '\x00XXXID3v1 Comment\x00comment test' so in this |
216 | - # case we need to find the second \x00 to know where |
217 | - # where we start for a comment. In case we only find |
218 | - # one \x00, lets just start at the beginning for the |
219 | - # value |
220 | - s = str(self.rawData) |
221 | - |
222 | - pos = 0 |
223 | - count = 0 |
224 | - while pos < len(s) and count < 2: |
225 | - if ord(s[pos]) == 0: |
226 | - count = count + 1 |
227 | - pos = pos + 1 |
228 | - if count < 2: |
229 | - pos = 1 |
230 | - |
231 | - if pos > 0 and pos < len(s): |
232 | - s = s[pos:] |
233 | - if ord(s[-1]) == 0: |
234 | - s = s[:-1] |
235 | - |
236 | - self.value = s |
237 | - |
238 | -class Reader: |
239 | - """ An ID3 reader. |
240 | - Create one on a file object, and then use getValue('TIT2') (for example) |
241 | - to pull values. |
242 | - """ |
243 | - def __init__(self, file): |
244 | - """ Create a reader from a file or filename. """ |
245 | - self.file = file |
246 | - self.header = None |
247 | - self.frames = {} |
248 | - self.allFrames = [] |
249 | - self.bytesLeft = 0 |
250 | - self.padbytes = '' |
251 | - |
252 | - bCloseFile = False |
253 | - # If self.file is a string of some sort, then open it to get a file. |
254 | - if isinstance(self.file, (type(''), type(u''))): |
255 | - self.file = open(self.file, 'rb') |
256 | - bCloseFile = True |
257 | - |
258 | - self._readId3() |
259 | - |
260 | - if bCloseFile: |
261 | - self.file.close() |
262 | - |
263 | - def _readBytes(self, num, desc=''): |
264 | - """ Read some bytes from the file. |
265 | - This method implements the "unsynchronization" scheme, |
266 | - where 0xFF bytes may have had 0x00 bytes stuffed after |
267 | - them. These zero bytes have to be removed transparently. |
268 | - """ |
269 | - #if _t: _trace("ask %d (%s)" % (num,desc)) |
270 | - if num > self.bytesLeft: |
271 | - #if _c: _coverage('long!') |
272 | - raise Id3Error, 'Long read (%s): (%d > %d)' % (desc, num, self.bytesLeft) |
273 | - bytes = self.file.read(num) |
274 | - self.bytesLeft -= num |
275 | - |
276 | - if len(bytes) < num: |
277 | - #if _t: _trace("short read with %d left, %d total" % (self.bytesLeft, self.header.size)) |
278 | - #if _c: _coverage('short!') |
279 | - raise Id3Error, 'Short read (%s): (%d < %d)' % (desc, len(bytes), num) |
280 | - |
281 | - if self.header.bUnsynchronized: |
282 | - nUnsync = 0 |
283 | - i = 0 |
284 | - while True: |
285 | - i = bytes.find('\xFF\x00', i) |
286 | - if i == -1: |
287 | - break |
288 | - #if _t: _trace("unsync at %d" % (i+1)) |
289 | - #if _c: _coverage('unsyncbyte') |
290 | - nUnsync += 1 |
291 | - # This is a stuffed byte to remove |
292 | - bytes = bytes[:i+1] + bytes[i+2:] |
293 | - # Have to read one more byte from the file to adjust |
294 | - bytes += self.file.read(1) |
295 | - self.bytesLeft -= 1 |
296 | - i += 1 |
297 | - #if _t: _trace("unsync'ed %d" % (nUnsync)) |
298 | - |
299 | - return bytes |
300 | - |
301 | - def _unreadBytes(self, num): |
302 | - self.file.seek(-num, 1) |
303 | - self.bytesLeft += num |
304 | - |
305 | - def _getSyncSafeInt(self, bytes): |
306 | - assert len(bytes) == 4 |
307 | - if type(bytes) == type(''): |
308 | - bytes = [ ord(c) for c in bytes ] |
309 | - return (bytes[0] << 21) + (bytes[1] << 14) + (bytes[2] << 7) + bytes[3] |
310 | - |
311 | - def _getInteger(self, bytes): |
312 | - i = 0; |
313 | - if type(bytes) == type(''): |
314 | - bytes = [ ord(c) for c in bytes ] |
315 | - for b in bytes: |
316 | - i = i*256+b |
317 | - return i |
318 | - |
319 | - def _addV1Frame(self, id, rawData): |
320 | - if id == 'v1genre': |
321 | - assert len(rawData) == 1 |
322 | - nGenre = ord(rawData) |
323 | - try: |
324 | - value = _genres[nGenre] |
325 | - except IndexError: |
326 | - value = "(%d)" % nGenre |
327 | - else: |
328 | - value = rawData.strip(' \t\r\n').split('\0')[0] |
329 | - if value: |
330 | - frame = _Frame() |
331 | - frame.id = id |
332 | - frame.rawData = rawData |
333 | - frame.value = value |
334 | - self.frames[id] = frame |
335 | - self.allFrames.append(frame) |
336 | - |
337 | - def _pass(self): |
338 | - """ Do nothing, for when we need to plug in a no-op function. |
339 | - """ |
340 | - pass |
341 | - |
342 | - def _readId3(self): |
343 | - header = self.file.read(10) |
344 | - if len(header) < 10: |
345 | - return |
346 | - hstuff = struct.unpack('!3sBBBBBBB', header) |
347 | - if hstuff[0] != "ID3": |
348 | - # Doesn't look like an ID3v2 tag, |
349 | - # Try reading an ID3v1 tag. |
350 | - self._readId3v1() |
351 | - return |
352 | - |
353 | - self.header = _Header() |
354 | - self.header.majorVersion = hstuff[1] |
355 | - self.header.revision = hstuff[2] |
356 | - self.header.flags = hstuff[3] |
357 | - self.header.size = self._getSyncSafeInt(hstuff[4:8]) |
358 | - |
359 | - self.bytesLeft = self.header.size |
360 | - |
361 | - self._readExtHeader = self._pass |
362 | - |
363 | - if self.header.majorVersion == 2: |
364 | - #if _c: _coverage('id3v2.2.%d' % self.header.revision) |
365 | - self._readFrame = self._readFrame_rev2 |
366 | - elif self.header.majorVersion == 3: |
367 | - #if _c: _coverage('id3v2.3.%d' % self.header.revision) |
368 | - self._readFrame = self._readFrame_rev3 |
369 | - elif self.header.majorVersion == 4: |
370 | - #if _c: _coverage('id3v2.4.%d' % self.header.revision) |
371 | - self._readFrame = self._readFrame_rev4 |
372 | - else: |
373 | - #if _c: _coverage('badmajor!') |
374 | - raise Id3Error, "Unsupported major version: %d" % self.header.majorVersion |
375 | - |
376 | - # Interpret the flags |
377 | - self._interpretFlags() |
378 | - |
379 | - # Read any extended header |
380 | - self._readExtHeader() |
381 | - |
382 | - # Read the frames |
383 | - while self.bytesLeft > 0: |
384 | - frame = self._readFrame() |
385 | - if frame: |
386 | - frame._interpret() |
387 | - self.frames[frame.id] = frame |
388 | - self.allFrames.append(frame) |
389 | - else: |
390 | - #if _c: _coverage('padding') |
391 | - break |
392 | - |
393 | - def _interpretFlags(self): |
394 | - """ Interpret ID3v2.x flags. |
395 | - """ |
396 | - if self.header.flags & 0x80: |
397 | - self.header.bUnsynchronized = True |
398 | - #if _c: _coverage('unsynctag') |
399 | - |
400 | - if self.header.majorVersion == 2: |
401 | - if self.header.flags & 0x40: |
402 | - #if _c: _coverage('compressed') |
403 | - # "Since no compression scheme has been decided yet, |
404 | - # the ID3 decoder (for now) should just ignore the entire |
405 | - # tag if the compression bit is set." |
406 | - self.header.bCompressed = True |
407 | - |
408 | - if self.header.majorVersion >= 3: |
409 | - if self.header.flags & 0x40: |
410 | - #if _c: _coverage('extheader') |
411 | - if self.header.majorVersion == 3: |
412 | - self._readExtHeader = self._readExtHeader_rev3 |
413 | - else: |
414 | - self._readExtHeader = self._readExtHeader_rev4 |
415 | - if self.header.flags & 0x20: |
416 | - #if _c: _coverage('experimental') |
417 | - self.header.bExperimental = True |
418 | - |
419 | - if self.header.majorVersion >= 4: |
420 | - if self.header.flags & 0x10: |
421 | - #if _c: _coverage('footer') |
422 | - self.header.bFooter = True |
423 | - |
424 | - def _readExtHeader_rev3(self): |
425 | - """ Read the ID3v2.3 extended header. |
426 | - """ |
427 | - # We don't interpret this yet, just eat the bytes. |
428 | - size = self._getInteger(self._readBytes(4, 'rev3ehlen')) |
429 | - self._readBytes(size, 'rev3ehdata') |
430 | - |
431 | - def _readExtHeader_rev4(self): |
432 | - """ Read the ID3v2.4 extended header. |
433 | - """ |
434 | - # We don't interpret this yet, just eat the bytes. |
435 | - size = self._getSyncSafeInt(self._readBytes(4, 'rev4ehlen')) |
436 | - self._readBytes(size-4, 'rev4ehdata') |
437 | - |
438 | - def _readId3v1(self): |
439 | - """ Read the ID3v1 tag. |
440 | - spec: http://www.id3.org/id3v1.html |
441 | - """ |
442 | - self.file.seek(-128, 2) |
443 | - tag = self.file.read(128) |
444 | - if len(tag) != 128: |
445 | - return |
446 | - if tag[0:3] != 'TAG': |
447 | - return |
448 | - self.header = _Header() |
449 | - self.header.majorVersion = 1 |
450 | - self.header.revision = 0 |
451 | - |
452 | - self._addV1Frame('v1title', tag[3:33]) |
453 | - self._addV1Frame('v1performer', tag[33:63]) |
454 | - self._addV1Frame('v1album', tag[63:93]) |
455 | - self._addV1Frame('v1year', tag[93:97]) |
456 | - self._addV1Frame('v1comment', tag[97:127]) |
457 | - self._addV1Frame('v1genre', tag[127]) |
458 | - if tag[125] == '\0' and tag[126] != '\0': |
459 | - #if _c: _coverage('id3v1.1') |
460 | - self.header.revision = 1 |
461 | - self._addV1Frame('v1track', str(ord(tag[126]))) |
462 | - else: |
463 | - #if _c: _coverage('id3v1.0') |
464 | - pass |
465 | - return |
466 | - |
467 | - _validIdChars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789' |
468 | - |
469 | - def _isValidId(self, id): |
470 | - """ Determine if the id bytes make a valid ID3 id. |
471 | - """ |
472 | - for c in id: |
473 | - if not c in self._validIdChars: |
474 | - #if _c: _coverage('bad id') |
475 | - return False |
476 | - #if _c: _coverage('id '+id) |
477 | - return True |
478 | - |
479 | - def _readFrame_rev2(self): |
480 | - """ Read a frame for ID3v2.2: three-byte ids and lengths. |
481 | - spec: http://www.id3.org/id3v2-00.txt |
482 | - """ |
483 | - if self.bytesLeft < 6: |
484 | - return None |
485 | - id = self._readBytes(3, 'rev2id') |
486 | - if len(id) < 3 or not self._isValidId(id): |
487 | - self._unreadBytes(len(id)) |
488 | - return None |
489 | - hstuff = struct.unpack('!BBB', self._readBytes(3, 'rev2len')) |
490 | - frame = _Frame() |
491 | - frame.id = id |
492 | - frame.size = self._getInteger(hstuff[0:3]) |
493 | - frame.rawData = self._readBytes(frame.size, 'rev2data') |
494 | - return frame |
495 | - |
496 | - def _readFrame_rev3(self): |
497 | - """ Read a frame for ID3v2.3: four-byte ids and lengths. |
498 | - """ |
499 | - if self.bytesLeft < 10: |
500 | - return None |
501 | - id = self._readBytes(4,'rev3id') |
502 | - if len(id) < 4 or not self._isValidId(id): |
503 | - self._unreadBytes(len(id)) |
504 | - return None |
505 | - hstuff = struct.unpack('!BBBBh', self._readBytes(6,'rev3head')) |
506 | - frame = _Frame() |
507 | - frame.id = id |
508 | - frame.size = self._getInteger(hstuff[0:4]) |
509 | - cbData = frame.size |
510 | - frame.flags = hstuff[4] |
511 | - #if _t: _trace('flags = %x' % frame.flags) |
512 | - frame.bTagAlterPreserve = (frame.flags & 0x8000 != 0) |
513 | - frame.bFileAlterPreserve = (frame.flags & 0x4000 != 0) |
514 | - frame.bReadOnly = (frame.flags & 0x2000 != 0) |
515 | - frame.bCompressed = (frame.flags & 0x0080 != 0) |
516 | - if frame.bCompressed: |
517 | - frame.decompressedSize = self._getInteger(self._readBytes(4, 'decompsize')) |
518 | - cbData -= 4 |
519 | - #if _c: _coverage('compress') |
520 | - frame.bEncrypted = (frame.flags & 0x0040 != 0) |
521 | - if frame.bEncrypted: |
522 | - frame.encryptionMethod = self._readBytes(1, 'encrmethod') |
523 | - cbData -= 1 |
524 | - #if _c: _coverage('encrypt') |
525 | - frame.bInGroup = (frame.flags & 0x0020 != 0) |
526 | - if frame.bInGroup: |
527 | - frame.groupid = self._readBytes(1, 'groupid') |
528 | - cbData -= 1 |
529 | - #if _c: _coverage('groupid') |
530 | - |
531 | - frame.rawData = self._readBytes(cbData, 'rev3data') |
532 | - return frame |
533 | - |
534 | - def _readFrame_rev4(self): |
535 | - """ Read a frame for ID3v2.4: four-byte ids and lengths. |
536 | - """ |
537 | - if self.bytesLeft < 10: |
538 | - return None |
539 | - id = self._readBytes(4,'rev4id') |
540 | - if len(id) < 4 or not self._isValidId(id): |
541 | - self._unreadBytes(len(id)) |
542 | - return None |
543 | - hstuff = struct.unpack('!BBBBh', self._readBytes(6,'rev4head')) |
544 | - frame = _Frame() |
545 | - frame.id = id |
546 | - frame.size = self._getSyncSafeInt(hstuff[0:4]) |
547 | - cbData = frame.size |
548 | - frame.flags = hstuff[4] |
549 | - frame.bTagAlterPreserve = (frame.flags & 0x4000 != 0) |
550 | - frame.bFileAlterPreserve = (frame.flags & 0x2000 != 0) |
551 | - frame.bReadOnly = (frame.flags & 0x1000 != 0) |
552 | - frame.bInGroup = (frame.flags & 0x0040 != 0) |
553 | - if frame.bInGroup: |
554 | - frame.groupid = self._readBytes(1, 'groupid') |
555 | - cbData -= 1 |
556 | - #if _c: _coverage('groupid') |
557 | - |
558 | - frame.bCompressed = (frame.flags & 0x0008 != 0) |
559 | - if frame.bCompressed: |
560 | - #if _c: _coverage('compress') |
561 | - pass |
562 | - frame.bEncrypted = (frame.flags & 0x0004 != 0) |
563 | - if frame.bEncrypted: |
564 | - frame.encryptionMethod = self._readBytes(1, 'encrmethod') |
565 | - cbData -= 1 |
566 | - #if _c: _coverage('encrypt') |
567 | - frame.bUnsynchronized = (frame.flags & 0x0002 != 0) |
568 | - if frame.bUnsynchronized: |
569 | - #if _c: _coverage('unsyncframe') |
570 | - pass |
571 | - if frame.flags & 0x0001: |
572 | - frame.datalen = self._getSyncSafeInt(self._readBytes(4, 'datalen')) |
573 | - cbData -= 4 |
574 | - #if _c: _coverage('datalenindic') |
575 | - |
576 | - frame.rawData = self._readBytes(cbData, 'rev3data') |
577 | - |
578 | - return frame |
579 | - |
580 | - def getValue(self, id): |
581 | - """ Return the value for an ID3 tag id, or for a |
582 | - convenience label ('title', 'performer', ...), |
583 | - or return None if there is no such value. |
584 | - """ |
585 | - if self.frames.has_key(id): |
586 | - if hasattr(self.frames[id], 'value'): |
587 | - return self.frames[id].value |
588 | - if _simpleDataMapping.has_key(id): |
589 | - for id2 in _simpleDataMapping[id]: |
590 | - v = self.getValue(id2) |
591 | - if v: |
592 | - return v |
593 | - return None |
594 | - |
595 | - def getRawData(self, id): |
596 | - if self.frames.has_key(id): |
597 | - return self.frames[id].rawData |
598 | - return None |
599 | - |
600 | - def dump(self): |
601 | - import pprint |
602 | - print "Header:" |
603 | - print self.header |
604 | - print "Frames:" |
605 | - for fr in self.allFrames: |
606 | - if len(fr.rawData) > 30: |
607 | - fr.rawData = fr.rawData[:30] |
608 | - pprint.pprint(self.allFrames) |
609 | - for fr in self.allFrames: |
610 | - if hasattr(fr, 'value'): |
611 | - print '%s: %s' % (fr.id, _safestr(fr.value)) |
612 | - else: |
613 | - print '%s= %s' % (fr.id, _safestr(fr.rawData)) |
614 | - for label in _simpleDataMapping.keys(): |
615 | - v = self.getValue(label) |
616 | - if v: |
617 | - print 'Label %s: %s' % (label, _safestr(v)) |
618 | - |
619 | - def dumpCoverage(self): |
620 | - feats = _features.keys() |
621 | - feats.sort() |
622 | - for feat in feats: |
623 | - print "Feature %-12s: %d" % (feat, _features[feat]) |
624 | - |
625 | -if __name__ == '__main__': |
626 | - if len(sys.argv) < 2 or '-?' in sys.argv: |
627 | - print "Give me a filename" |
628 | - else: |
629 | - id3 = Reader(sys.argv[1]) |
630 | - id3.dump() |
631 | - #if _c: id3.dumpCoverage() |
632 | - |
633 | -# History: |
634 | -# 20040104: Created. |
635 | -# 20040105: Two bugs: didn't read v1 properly, and didn't like empty strings in values. |
636 | -# |
637 | -# 20040109: Properly reads v2.3 properly (4-byte lens, but not synchsafe) |
638 | -# Handles unsynchronized tags properly. |
639 | -# |
640 | -# 20040110: Total length was wrong for unsynchronized tags. |
641 | -# Treat input filename better so path module can be used. |
642 | -# Frame ids are more closely scrutinized for validity. |
643 | -# Errors are now thrown as our own exception. |
644 | -# Pad bytes aren't retained any more. |
645 | -# Frame.value is not set if there is no interpretation performed. |
646 | -# |
647 | -# 20040111: Tracing and code coverage more formalized. |
648 | -# Exceptions are now all Id3Error. |
649 | -# Zero-length data in frames is handled pleasantly. |
650 | -# Compressed frames are decompressed. |
651 | -# Extended headers are read (but uninterpreted). |
652 | -# Non-zero pad bytes are handled. |
653 | -# Frame flags are read and interpreted. |
654 | -# W*** frames are interpreted. |
655 | -# Multi-string frames set .value to a list of strings. |
656 | -# |
657 | -# 20040113: Strip all trailing zero bytes from text strings. |
658 | -# If we opened the file, we should close the file. |
659 | -# |
660 | -# 20040205: Do a better job printing strings without throwing. |
661 | -# Support genre information, even if it is stupid. |
662 | -# |
663 | -# 20040913: When dumping strings, be more robust when trying to print |
664 | -# non-character data. Thanks to Duane Harkness for the fix. |
665 | -# |
666 | -# 20061230: Fix ommission of self. in a few places. |
667 | -# |
668 | -# 20070415: Extended headers in ID3v2.4 weren't skipped properly, throwing |
669 | -# everything out of whack. |
670 | -# Be more generous about finding album and performer names in the tag. |