ViewVC Help
View File | Revision Log | Show Annotations | View Changeset | Root Listing
root/ns_dev/Python/NinoCode/Active_prgs/mp3info.py
Revision: 8
Committed: Sat May 5 04:21:19 2012 UTC (13 years, 10 months ago) by ninoborges
Content type: text/x-python
File size: 19756 byte(s)
Log Message:
Initial Import

File Contents

# User Rev Content
1 ninoborges 8 #!/usr/bin/env python
2     #
3     # Copyright (c) 2002 Vivake Gupta (vivakeATomniscia.org). All rights reserved.
4     #
5     # This program is free software; you can redistribute it and/or
6     # modify it under the terms of the GNU General Public License as
7     # published by the Free Software Foundation; either version 2 of the
8     # License, or (at your option) any later version.
9     #
10     # This program is distributed in the hope that it will be useful,
11     # but WITHOUT ANY WARRANTY; without even the implied warranty of
12     # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13     # GNU General Public License for more details.
14     #
15     # You should have received a copy of the GNU General Public License
16     # along with this program; if not, write to the Free Software
17     # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
18     # USA
19     #
20     # This software is maintained by Vivake (vivakeATomniscia.org) and is available at:
21     # http://www.omniscia.org/~vivake/python/MP3Info.py
22    
23     import struct
24     import string
25    
26     def _from_synch_safe(synchsafe):
27     if isinstance(synchsafe, type(1)):
28     (b3, b2, b1, b0) = struct.unpack('!4b', struct.pack('!1i', synchsafe))
29     else:
30     while len(synchsafe) < 4:
31     synchsafe = (0,) + synchsafe
32     (b3, b2, b1, b0) = synchsafe
33    
34     x = 256
35     return (((b3 * x + b2) * x + b1) * x + b0)
36    
37     def _strip_zero(s):
38     start = 0
39     while start < len(s) and (s[start] == '\0' or s[start] == ' '):
40     start = start + 1
41    
42     end = len(s) - 1
43     while end >= 0 and (s[end] == '\0' or s[end] == ' '):
44     end = end - 1
45    
46     return s[start:end+1]
47    
48     class ID3v2Frame:
49     def __init__(self, file, version):
50     self.name = ""
51     self.version = 0
52     self.padding = 0
53     self.size = 0
54     self.data = ""
55    
56     self.flags = {}
57     self.f_tag_alter_preservation = 0
58     self.f_file_alter_preservation = 0
59     self.f_read_only = 0
60     self.f_compression = 0
61     self.f_encryption = 0
62     self.f_grouping_identity = 0
63     self.f_unsynchronization = 0
64     self.f_data_length_indicator = 0
65    
66     if version == 2:
67     nameSize = 3
68     else:
69     nameSize = 4
70     self.name = file.read(nameSize)
71    
72     self.version = version
73    
74     if self.name == nameSize * '\0':
75     self.padding = 1
76     return
77    
78     if self.name[0] < 'A' or self.name[0] > 'Z':
79     self.padding = 1
80     return
81    
82     size = ()
83     if version == 2:
84     size = struct.unpack('!3b', file.read(3))
85     elif version == 3 or version == 4:
86     size = struct.unpack('!4b', file.read(4))
87    
88     if version == 3: # abc00000 def00000
89     (flags,) = struct.unpack('!1b', file.read(1))
90     self.f_tag_alter_preservation = flags >> 7 & 1 #a
91     self.f_file_alter_preservation = flags >> 6 & 1 #b
92     self.f_read_only = flags >> 5 & 1 #c
93     (flags,) = struct.unpack('!1b', file.read(1))
94     self.f_compression = flags >> 7 & 1 #d
95     self.f_encryption = flags >> 6 & 1 #e
96     self.f_grouping_identity = flags >> 5 & 1 #f
97     elif version == 4: # 0abc0000 0h00kmnp
98     (flags,) = struct.unpack('!1b', file.read(1))
99     self.f_tag_alter_preservation = flags >> 6 & 1 #a
100     self.f_file_alter_preservation = flags >> 5 & 1 #b
101     self.f_read_only = flags >> 4 & 1 #c
102     (flags,) = struct.unpack('!1b', file.read(1))
103     self.f_grouping_identity = flags >> 6 & 1 #h
104     self.f_compression = flags >> 3 & 1 #k
105     self.f_encryption = flags >> 2 & 1 #m
106     self.f_unsynchronization = flags >> 1 & 1 #n
107     self.f_data_length_indicator = flags >> 0 & 1 #p
108    
109     self.size = _from_synch_safe(size)
110     self.data = _strip_zero(file.read(self.size))
111    
112     _genres = [
113     "Blues", "Classic Rock", "Country", "Dance", "Disco", "Funk", "Grunge",
114     "Hip-Hop", "Jazz", "Metal", "New Age", "Oldies", "Other", "Pop", "R&B",
115     "Rap", "Reggae", "Rock", "Techno", "Industrial", "Alternative", "Ska",
116     "Death Metal", "Pranks", "Soundtrack", "Euro-Techno", "Ambient", "Trip-Hop",
117     "Vocal", "Jazz+Funk", "Fusion", "Trance", "Classical", "Instrumental",
118     "Acid", "House", "Game", "Sound Clip", "Gospel", "Noise", "AlternRock",
119     "Bass", "Soul", "Punk", "Space", "Meditative", "Instrumental Pop",
120     "Instrumental Rock", "Ethnic", "Gothic", "Darkwave", "Techno-industrial",
121     "Electronic", "Pop-Folk", "Eurodance", "Dream", "Southern Rock", "Comedy",
122     "Cult", "Gangsta", "Top 40", "Christian Rap", "Pop/Funk", "Jungle",
123     "Native American", "Cabaret", "New Wave", "Psychadelic", "Rave",
124     "Showtunes", "Trailer", "Lo-Fi", "Tribal", "Acid Punk", "Acid Jazz",
125     "Polka", "Retro", "Musical", "Rock & Roll", "Hard Rock", "Folk",
126     "Folk/Rock", "National Folk", "Swing", "Fast-Fusion", "Bebob", "Latin",
127     "Revival", "Celtic", "Bluegrass", "Avantegarde", "Gothic Rock",
128     "Progressive Rock", "Psychedelic Rock", "Symphonic Rock", "Slow Rock",
129     "Big Band", "Chorus", "Easy Listening", "Acoustic", "Humour", "Speech",
130     "Chanson", "Opera", "Chamber Music", "Sonata", "Symphony", "Booty Bass",
131     "Primus", "Porn Groove", "Satire", "Slow Jam", "Club", "Tango", "Samba",
132     "Folklore", "Ballad", "Power Ballad", "Rythmic Soul", "Freestyle", "Duet",
133     "Punk Rock", "Drum Solo", "A capella", "Euro-House", "Dance Hall", "Goa",
134     "Drum & Bass", "Club House", "Hardcore", "Terror", "Indie", "BritPop",
135     "NegerPunk", "Polsk Punk", "Beat", "Christian Gangsta", "Heavy Metal",
136     "Black Metal", "Crossover", "Contemporary C", "Christian Rock", "Merengue",
137     "Salsa", "Thrash Metal", "Anime", "JPop", "SynthPop",
138     ]
139    
140     class ID3v1:
141     def __init__(self, file):
142     self.valid = 0
143    
144     self.tags = { }
145    
146     try:
147     file.seek(-128, 2)
148     except IOError:
149     pass
150    
151     data = file.read(128)
152     if data[0:3] != 'TAG':
153     return
154     else:
155     self.valid = 1
156    
157     self.tags['TT2'] = _strip_zero(data[ 3: 33])
158     self.tags['TP1'] = _strip_zero(data[33: 63])
159     self.tags['TAL'] = _strip_zero(data[63: 93])
160     self.tags['TYE'] = _strip_zero(data[93: 97])
161     self.tags['COM'] = _strip_zero(data[97:125])
162    
163     if data[125] == '\0':
164     self.tags['TRK'] = ord(data[126])
165    
166     try:
167     self.tags['TCO'] = _genres[ord(data[127])]
168     except IndexError:
169     self.tags['TCO'] = "(%i)" % ord(data[127])
170    
171    
172     class ID3v2:
173     def __init__(self, file):
174     self.valid = 0
175    
176     self.tags = { }
177    
178     self.header_size = 0
179    
180     self.major_version = 0
181     self.minor_version = 0
182    
183     self.f_unsynchronization = 0
184     self.f_extended_header = 0
185     self.f_experimental = 0
186     self.f_footer = 0
187    
188     self.f_extended_header_zie = 0
189     self.f_extended_num_flag_bytes = 0
190    
191     self.ef_update = 0
192     self.ef_crc = 0
193     self.ef_restrictions = 0
194    
195     self.crc = 0
196     self.restrictions = 0
197    
198     self.frames = []
199     self.tags = {}
200    
201     file.seek(0, 0)
202     if file.read(3) != "ID3":
203     return
204     else:
205     self.valid = 1
206    
207     (self.major_version, self.minor_version) = struct.unpack('!2b', file.read(2))
208    
209     # abcd 0000
210     (flags,) = struct.unpack('!1b', file.read(1))
211     self.f_unsynchronization = flags >> 7 & 1 # a
212     self.f_extended_header = flags >> 6 & 1 # b
213     self.f_experimental = flags >> 5 & 1 # c
214     self.f_footer = flags >> 4 & 1 # d
215    
216     self.header_size = _from_synch_safe(struct.unpack('!4b', file.read(4)))
217    
218     while 1:
219     if file.tell() >= self.header_size:
220     break
221     frame = ID3v2Frame(file, self.major_version)
222     if frame.padding:
223     file.seek(self.header_size)
224     break
225    
226     self.frames = self.frames + [frame]
227     self.tags[frame.name] = frame.data
228    
229     _bitrates = [
230     [ # MPEG-2 & 2.5
231     [0,32,48,56, 64, 80, 96,112,128,144,160,176,192,224,256,None], # Layer 1
232     [0, 8,16,24, 32, 40, 48, 56, 64, 80, 96,112,128,144,160,None], # Layer 2
233     [0, 8,16,24, 32, 40, 48, 56, 64, 80, 96,112,128,144,160,None] # Layer 3
234     ],
235    
236     [ # MPEG-1
237     [0,32,64,96,128,160,192,224,256,288,320,352,384,416,448,None], # Layer 1
238     [0,32,48,56, 64, 80, 96,112,128,160,192,224,256,320,384,None], # Layer 2
239     [0,32,40,48, 56, 64, 80, 96,112,128,160,192,224,256,320,None] # Layer 3
240     ]
241     ]
242    
243     _samplerates = [
244     [ 11025, 12000, 8000, None], # MPEG-2.5
245     [ None, None, None, None], # reserved
246     [ 22050, 24000, 16000, None], # MPEG-2
247     [ 44100, 48000, 32000, None], # MPEG-1
248     ]
249    
250     _modes = [ "stereo", "joint stereo", "dual channel", "mono" ]
251    
252     _mode_extensions = [
253     [ "4-31", "8-31", "12-31", "16-31" ],
254     [ "4-31", "8-31", "12-31", "16-31" ],
255     [ "", "IS", "MS", "IS+MS" ]
256     ]
257    
258     _emphases = [ "none", "50/15 ms", "reserved", "CCIT J.17" ]
259    
260     _MP3_HEADER_SEEK_LIMIT = 8192
261    
262     class MPEG:
263     def __init__(self, file):
264     self.valid = 0
265    
266     file.seek(0, 2)
267     self.filesize = file.tell()
268     file.seek(0, 0)
269    
270     self.version = 0
271     self.layer = 0
272     self.protection = 0
273     self.bitrate = 0
274     self.samplerate = 0
275     self.padding = 0
276     self.private = 0
277     self.mode = ""
278     self.mode_extension = ""
279     self.copyright = 0
280     self.original = 0
281     self.emphasis = ""
282     self.length = 0
283    
284     offset, header = self._find_header(file)
285     if offset == -1 or header is None:
286     return
287    
288     self._parse_header(header)
289     ### offset + framelength will find another header. verify??
290     if not self.valid:
291     return
292    
293     self._parse_xing(file)
294    
295    
296     def _find_header(self, file):
297     file.seek(0, 0)
298     amount_read = 0
299    
300     # see if we get lucky with the first four bytes
301     amt = 4
302    
303     while amount_read < _MP3_HEADER_SEEK_LIMIT:
304     header = file.read(amt)
305     if len(header) < amt:
306     # awfully short file. just give up.
307     return -1, None
308    
309     amount_read = amount_read + len(header)
310    
311     # on the next read, grab a lot more
312     amt = 500
313    
314     # look for the sync byte
315     offset = string.find(header, chr(255))
316     if offset == -1:
317     continue
318     ### maybe verify more sync bits in next byte?
319    
320     if offset + 4 > len(header):
321     more = file.read(4)
322     if len(more) < 4:
323     # end of file. can't find a header
324     return -1, None
325     amount_read = amount_read + 4
326     header = header + more
327     return amount_read - len(header) + offset, header[offset:offset+4]
328    
329     # couldn't find the header
330     return -1, None
331    
332     def _parse_header(self, header):
333     # AAAAAAAA AAABBCCD EEEEFFGH IIJJKLMM
334     (bytes,) = struct.unpack('>i', header)
335     mpeg_version = (bytes >> 19) & 3 # BB 00 = MPEG2.5, 01 = res, 10 = MPEG2, 11 = MPEG1
336     layer = (bytes >> 17) & 3 # CC 00 = res, 01 = Layer 3, 10 = Layer 2, 11 = Layer 1
337     protection_bit = (bytes >> 16) & 1 # D 0 = protected, 1 = not protected
338     bitrate = (bytes >> 12) & 15 # EEEE 0000 = free, 1111 = bad
339     samplerate = (bytes >> 10) & 3 # F 11 = res
340     padding_bit = (bytes >> 9) & 1 # G 0 = not padded, 1 = padded
341     private_bit = (bytes >> 8) & 1 # H
342     mode = (bytes >> 6) & 3 # II 00 = stereo, 01 = joint stereo, 10 = dual channel, 11 = mono
343     mode_extension = (bytes >> 4) & 3 # JJ
344     copyright = (bytes >> 3) & 1 # K 00 = not copyrighted, 01 = copyrighted
345     original = (bytes >> 2) & 1 # L 00 = copy, 01 = original
346     emphasis = (bytes >> 0) & 3 # MM 00 = none, 01 = 50/15 ms, 10 = res, 11 = CCIT J.17
347    
348     if mpeg_version == 0:
349     self.version = 2.5
350     elif mpeg_version == 2:
351     self.version = 2
352     elif mpeg_version == 3:
353     self.version = 1
354     else:
355     return
356    
357     if layer > 0:
358     self.layer = 4 - layer
359     else:
360     return
361    
362     self.protection = protection_bit
363    
364     self.bitrate = _bitrates[mpeg_version & 1][self.layer - 1][bitrate]
365     self.samplerate = _samplerates[mpeg_version][samplerate]
366    
367     if self.bitrate is None or self.samplerate is None:
368     return
369    
370     self.padding = padding_bit
371     self.private = private_bit
372     self.mode = _modes[mode]
373     self.mode_extension = _mode_extensions[self.layer - 1][mode_extension]
374     self.copyright = copyright
375     self.original = original
376     self.emphasis = _emphases[emphasis]
377    
378     if self.layer == 1:
379     self.framelength = (( 12 * (self.bitrate * 1000.0)/self.samplerate) + padding_bit) * 4
380     self.samplesperframe = 384.0
381     else:
382     self.framelength = ( 144 * (self.bitrate * 1000.0)/self.samplerate) + padding_bit
383     self.samplesperframe = 1152.0
384     self.length = int(round((self.filesize / self.framelength) * (self.samplesperframe / self.samplerate)))
385    
386     self.valid = 1
387    
388     def _parse_xing(self, file):
389     """Parse the Xing-specific header.
390    
391     For variable-bitrate (VBR) MPEG files, Xing includes a header which
392     can be used to approximate the (average) bitrate and the duration
393     of the file.
394     """
395     file.seek(0, 0)
396     header = file.read(128)
397    
398     i = string.find(header, 'Xing')
399     if i > 0:
400     (flags,) = struct.unpack('>i', header[i+4:i+8])
401     if flags & 3:
402     # flags says "frames" and "bytes" are present. use them.
403     (frames,) = struct.unpack('>i', header[i+8:i+12])
404     (bytes,) = struct.unpack('>i', header[i+12:i+16])
405    
406     if self.samplerate:
407     self.length = int(round(frames * self.samplesperframe / self.samplerate))
408     self.bitrate = ((bytes * 8.0 / self.length) / 1000)
409    
410     class MP3Info:
411     def __init__(self, file):
412     self.valid = 0
413    
414     self.id3 = None
415     self.mpeg = None
416    
417     id3 = ID3v1(file)
418     if id3.valid:
419     self.id3 = id3
420    
421     id3 = ID3v2(file)
422     if id3.valid:
423     self.id3 = id3
424    
425     self.mpeg = MPEG(file)
426    
427    
428     if self.id3 is None:
429     return
430    
431     for tag in self.id3.tags.keys():
432     if tag == 'TT2' or tag == 'TIT2':
433     self.title = self.id3.tags[tag]
434     elif tag == 'TP1' or tag == 'TPE1':
435     self.artist = self.id3.tags[tag]
436     elif tag == 'TRK' or tag == 'TRCK':
437     self.track = self.id3.tags[tag]
438     elif tag == 'TYE' or tag == 'TYER':
439     self.year = self.id3.tags[tag]
440     elif tag == 'COM' or tag == 'COMM':
441     self.comment = self.id3.tags[tag]
442     elif tag == 'TCM':
443     self.composer = self.id3.tags[tag]
444     elif tag == 'TAL' or tag == 'TALB':
445     self.album = self.id3.tags[tag]
446     elif tag == 'TPA':
447     self.disc = self.id3.tags[tag]
448     elif tag == 'TCO' or tag == 'TCON':
449     self.genre = self.id3.tags[tag]
450     if self.genre and self.genre[0] == '(' and self.genre[-1] == ')':
451     try:
452     self.genre = _genres[int(self.genre[1:-1])]
453     except IndexError:
454     self.genre = ""
455     elif tag == 'TEN' or tag == 'TENC':
456     self.encoder = self.id3.tags[tag]
457    
458     if __name__ == '__main__':
459     import sys
460     i = MP3Info(open(sys.argv[1], 'rb'))
461     print i.id3.tags
462