Merge lp:~eh/coverlovin/mutagen into lp: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
Reviewer Review Type Date Requested Status
James Stewart Approve
Review via email: mp+102965@code.launchpad.net

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.

Subscribers

People subscribed via source and target branches

to all changes: