#!/usr/bin/env python # -*- Mode: python -*- # # Copyright (c) 2002-2003 Vivake Gupta (vivakeATomniscia.org). All rights reserved. # Modified by Hyriand (2003, 2004) # Modified 2004 by Marek Hnilica # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License as # published by the Free Software Foundation; either version 2 of the # License, or (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 # USA # # This software is maintained by Vivake (vivakeATomniscia.org) and is available at: # http://www.omniscia.org/~vivake/python/MP3Info.py # # July 2003 - Incorporated various changes from Stan Seibert # for more robust MP3 detection. Includes looking for all 11 sync bits # and limits on how far to look for sync bits depending on presence of # ID3v2 headers. import struct,string,ID3V2 class Error(Exception): pass _bitrates = [ [ # MPEG-2 & 2.5 [0,32,48,56, 64, 80, 96,112,128,144,160,176,192,224,256,None], # Layer 1 [0, 8,16,24, 32, 40, 48, 56, 64, 80, 96,112,128,144,160,None], # Layer 2 [0, 8,16,24, 32, 40, 48, 56, 64, 80, 96,112,128,144,160,None] # Layer 3 ], [ # MPEG-1 [0,32,64,96,128,160,192,224,256,288,320,352,384,416,448,None], # Layer 1 [0,32,48,56, 64, 80, 96,112,128,160,192,224,256,320,384,None], # Layer 2 [0,32,40,48, 56, 64, 80, 96,112,128,160,192,224,256,320,None] # Layer 3 ] ] _samplerates = [ [ 11025, 12000, 8000, None], # MPEG-2.5 [ None, None, None, None], # reserved [ 22050, 24000, 16000, None], # MPEG-2 [ 44100, 48000, 32000, None], # MPEG-1 ] _modes = [ "stereo", "joint stereo", "dual channel", "mono" ] _mode_extensions = [ [ "4-31", "8-31", "12-31", "16-31" ], [ "4-31", "8-31", "12-31", "16-31" ], [ "", "IS", "MS", "IS+MS" ] ] _emphases = [ "none", "50/15 ms", "reserved", "CCIT J.17" ] _MP3_HEADER_SEEK_LIMIT = 1024*256 class MPEG: def __init__(self, file, seeklimit=_MP3_HEADER_SEEK_LIMIT, seekstart=0): self.valid = 0 file.seek(0, 2) self.filesize = file.tell() self.FileName=file.name ID3File=open(self.FileName,'rb') self.ID3v1Size=0 try: ID3File.seek(-128, 2) except: pass if ID3File.read(3) == 'TAG': self.ID3v1Size=128 ID3File.close() self.version = 0 self.layer = 0 self.protection = 0 self.bitrate = 0 self.samplerate = 0 self.padding = 0 self.private = 0 self.mode = "" self.mode_extension = "" self.copyright = 0 self.original = 0 self.emphasis = "" self.length = 0 self.vbr = 0 offset, header = self._find_header(file, seeklimit, seekstart) if offset == -1 or header is None: raise Error("Could not find MPEG header") self._parse_header(header) ### offset + framelength will find another header. verify?? if not self.valid: raise Error("MPEG header not valid") self._parse_xing(file, seekstart, seeklimit) self.file=file def head_check(self, head): if ((head & 0xffe00000L) != 0xffe00000L): return 0 if (not ((head >> 17) & 3)): return 0 if (((head >> 12) & 0xf) == 0xf): return 0 if ( not ((head >> 12) & 0xf)): return 0 if (((head >> 10) & 0x3) == 0x3): return 0 if (((head >> 19) & 1) == 1 and ((head >> 17) & 3) == 3 and ((head >> 16) & 1) == 1): return 0 if ((head & 0xffff0000L) == 0xfffe0000L): return 0 return 1 def _find_header(self, file, seeklimit=_MP3_HEADER_SEEK_LIMIT, seekstart=0): file.seek(seekstart, 0) header = file.read(4) # see if we get lucky with the first four bytes curr_pos = 0 amt = 1024 while len(header) <= seeklimit: # look for the sync byte offset = string.find(header, chr(255), curr_pos) if offset == -1: curr_pos = len(header) # Header after everything so far elif offset + 4 > len(header): curr_pos = offset # Need to read more, jump back here later elif self.head_check(struct.unpack(">I", header[offset:offset+4])[0]): return seekstart+offset, header[offset:offset+4] else: curr_pos = offset+2 # Gotta be after the 2 bytes we looked at chunk = file.read(amt) # Read bigger chunks header += chunk if len(chunk) == 0: # no more to read, give up return -1, None # couldn't find the header return -1, None def _parse_header(self, header): # AAAAAAAA AAABBCCD EEEEFFGH IIJJKLMM (bytes,) = struct.unpack('>i', header) mpeg_version = (bytes >> 19) & 3 # BB 00 = MPEG2.5, 01 = res, 10 = MPEG2, 11 = MPEG1 layer = (bytes >> 17) & 3 # CC 00 = res, 01 = Layer 3, 10 = Layer 2, 11 = Layer 1 protection_bit = (bytes >> 16) & 1 # D 0 = protected, 1 = not protected bitrate = (bytes >> 12) & 15 # EEEE 0000 = free, 1111 = bad samplerate = (bytes >> 10) & 3 # F 11 = res padding_bit = (bytes >> 9) & 1 # G 0 = not padded, 1 = padded private_bit = (bytes >> 8) & 1 # H mode = (bytes >> 6) & 3 # II 00 = stereo, 01 = joint stereo, 10 = dual channel, 11 = mono mode_extension = (bytes >> 4) & 3 # JJ copyright = (bytes >> 3) & 1 # K 00 = not copyrighted, 01 = copyrighted original = (bytes >> 2) & 1 # L 00 = copy, 01 = original emphasis = (bytes >> 0) & 3 # MM 00 = none, 01 = 50/15 ms, 10 = res, 11 = CCIT J.17 if mpeg_version == 0: self.version = 2.5 elif mpeg_version == 2: self.version = 2 elif mpeg_version == 3: self.version = 1 else: return if layer > 0: self.layer = 4 - layer else: return self.protection = protection_bit self.bitrate = _bitrates[mpeg_version & 1][self.layer - 1][bitrate] self.samplerate = _samplerates[mpeg_version][samplerate] if self.bitrate is None or self.samplerate is None: return self.padding = padding_bit self.private = private_bit self.mode = _modes[mode] self.mode_extension = _mode_extensions[self.layer - 1][mode_extension] self.copyright = copyright self.original = original self.emphasis = _emphases[emphasis] try: if self.layer == 1: self.framelength = (( 12 * (self.bitrate * 1000.0)/self.samplerate) + padding_bit) * 4 self.samplesperframe = 384.0 else: self.framelength = ( 144 * (self.bitrate * 1000.0)/self.samplerate) + padding_bit self.samplesperframe = 1152.0 Size=ID3V2.ID3v2(self.FileName) Size=Size.TagSize+self.ID3v1Size self.filesize=self.filesize-Size self.length=int(float(self.filesize)/(self.bitrate*1000.0)*8.0) except ZeroDivisionError: return # Division by zero means the header is bad self.valid = 1 def _parse_xing(self, file, seekstart=0, seeklimit=_MP3_HEADER_SEEK_LIMIT): """Parse the Xing-specific header. For variable-bitrate (VBR) MPEG files, Xing includes a header which can be used to approximate the (average) bitrate and the duration of the file. """ file.seek(seekstart, 0) header = file.read(seeklimit) try: i = string.find(header, 'Xing') if i > 0: header += file.read(128) (flags,) = struct.unpack('>i', header[i+4:i+8]) if flags & 3: # flags says "frames" and "bytes" are present. use them. (frames,) = struct.unpack('>i', header[i+8:i+12]) (bytes,) = struct.unpack('>i', header[i+12:i+16]) if self.samplerate: self.vbr = 1 bitrate = ((bytes * 8 / ((frames * self.samplesperframe) / self.samplerate)) / 1000) length=int(float(self.filesize)/(bitrate*1000)*8) self.length = length self.bitrate = int(bitrate) return except ZeroDivisionError: pass # This header is bad except struct.error: pass # This header is bad # If we made it here, the header wasn't any good. Try at the beginning # now just in case if seekstart != 0: self._parse_xing(file, 0, seeklimit) def detect_mp3(path): try: f = open(path, "rb") m = MPEG(f) f.close() except: return 0 else: return { "bitrate": m.bitrate, "vbr": m.vbr, "time": m.length, "vbrrate":m.bitrate, 'layer':m.layer, 'version':m.version, 'samplerate':m.samplerate }