0001 ### BITPIM 0002 ### 0003 ### Copyright (C) 2005 Roger Binns <rogerb@rogerbinns.com> 0004 ### 0005 ### This program is free software; you can redistribute it and/or modify 0006 ### it under the terms of the BitPim license as detailed in the LICENSE file. 0007 ### 0008 ### $Id: fileinfo.py 3608 2006-10-06 02:51:56Z djpham $ 0009 0010 "Returns information about files" 0011 0012 import os 0013 import struct 0014 0015 import common 0016 0017 import midifile 0018 import wma_file 0019 0020 class FailedFile: 0021 data="" 0022 def GetBytes(*args): return None 0023 def GetLSBUint32(*args): return None 0024 def GetLSBUint16(*args): return None 0025 def GetByte(*args): return None 0026 def GetMSBUint32(*args): return None 0027 def GetMSBUint16(*args): return None 0028 0029 class SafeFileWrapper: 0030 """Wraps a file object letting you get various parts without exceptions""" 0031 0032 READAHEAD=1024 0033 0034 def __init__(self, filename): 0035 try: 0036 self.file=open(filename, "rb") 0037 self.size=os.stat(filename).st_size 0038 self.data=self.file.read(self.READAHEAD) 0039 except (OSError,IOError): 0040 # change our class 0041 self.size=-1 0042 self.__class__=FailedFile 0043 0044 def GetBytes(self, offset, length): 0045 if offset+length<len(self.data): 0046 return self.data[offset:offset+length] 0047 if offset+length>self.size: 0048 return None 0049 self.file.seek(offset) 0050 res=self.file.read(length) 0051 if len(res)<length: return None 0052 return res 0053 0054 def GetLSBUint32(self, offset): 0055 v=self.GetBytes(offset, 4) 0056 if v is None: return v 0057 return struct.unpack('<L', v)[0] 0058 0059 def GetLSBUint16(self, offset): 0060 v=self.GetBytes(offset, 2) 0061 if v is None: return v 0062 return struct.unpack('<H', v)[0] 0063 0064 def GetMSBUint32(self, offset): 0065 v=self.GetBytes(offset, 4) 0066 if v is None: return v 0067 return struct.unpack('>L', v)[0] 0068 0069 def GetMSBUint16(self, offset): 0070 v=self.GetBytes(offset, 2) 0071 if v is None: return v 0072 return struct.unpack('>H', v)[0] 0073 0074 def GetByte(self, offset): 0075 v=self.GetBytes(offset,1) 0076 if v is None: return v 0077 return ord(v) 0078 0079 class SafeStringWrapper(SafeFileWrapper): 0080 """ 0081 Wraps a string object letting you get various parts w/o exceptions. 0082 Mainly used by the com_* modules as part of writing media to the phone. 0083 """ 0084 def __init__(self, string): 0085 if isinstance(string, str): 0086 self.file=None 0087 self.data=string 0088 self.size=len(string) 0089 else: 0090 self.size=-1 0091 self.__class__=FailedFile 0092 0093 def GetBytes(self, offset, length): 0094 if (offset+length)<=self.size: 0095 return self.data[offset:offset+length] 0096 0097 class ImgFileInfo: 0098 "Wraps information about an image file" 0099 0100 # These will always be present 0101 attrnames=("width", "height", "format", "bpp", "size", "MAXSIZE") 0102 0103 def __init__(self, f, **kwds): 0104 for a in self.attrnames: 0105 setattr(self, a, None) 0106 self.mimetypes=[] 0107 self.size=f.size 0108 self.__dict__.update(kwds) 0109 0110 def shortdescription(self): 0111 v=getattr(self, "_shortdescription", None) 0112 if v is not None: 0113 return v(self) 0114 res=[] 0115 if self.width is not None and self.height is not None: 0116 res.append( "%d x %d" % (self.width, self.height) ) 0117 if self.format is not None: 0118 res.append( self.format) 0119 if self.bpp is not None: 0120 res.append( "%d bpp" % (self.bpp,)) 0121 0122 if len(res): 0123 return " ".join(res) 0124 return "Unknown format" 0125 0126 def longdescription(self): 0127 v=getattr(self, "_longdescription", None) 0128 if v is not None: 0129 return v(self) 0130 return self.shortdescription() 0131 0132 def idimg_BMP(f): 0133 "Identify a Windows bitmap" 0134 # 40 is header size for windows bmp, different numbers are used by OS/2 0135 if f.GetBytes(0,2)=="BM" and f.GetLSBUint16(14)==40: 0136 d={'format': "BMP"} 0137 d['width']=f.GetLSBUint32(18) 0138 d['height']=f.GetLSBUint32(22) 0139 d['bpp']=f.GetLSBUint16(28) 0140 d['compression']=f.GetLSBUint32(30) 0141 d['ncolours']=f.GetLSBUint32(46) 0142 d['nimportantcolours']=f.GetLSBUint32(50) 0143 d['_longdescription']=fmt_BMP 0144 d['mimetypes']=['image/bmp', 'image/x-bmp'] 0145 for i in d.itervalues(): 0146 if i is None: return None 0147 ifi=ImgFileInfo(f,**d) 0148 return ifi 0149 return None 0150 0151 def fmt_BMP(ifi): 0152 "Long description for BMP" 0153 res=[ifi.shortdescription()] 0154 if ifi.compression==0: 0155 res.append("No compression") 0156 elif ifi.compression==1: 0157 res.append("8 bit run length encoding") 0158 elif ifi.compression==2: 0159 res.append("4 bit run length encoding") 0160 elif ifi.compression==3: 0161 res.append("RGB bitmap with mask") 0162 else: 0163 res.append("Unknown compression "+`ifi.compression`) 0164 if ifi.ncolours: 0165 res.append("%d colours" % (ifi.ncolours,)) 0166 if ifi.nimportantcolours: 0167 res[-1]=res[-1]+(" (%d important)" % (ifi.nimportantcolours,)) 0168 return "\n".join(res) 0169 0170 def idimg_PNG(f): 0171 "Identify a PNG" 0172 if f.GetBytes(0,8)=="\x89PNG\r\n\x1a\n" and f.GetBytes(12,4)=="IHDR": 0173 d={'format': "PNG"} 0174 d['width']=f.GetMSBUint32(16) 0175 d['height']=f.GetMSBUint32(20) 0176 d['bitdepth']=f.GetByte(24) 0177 d['colourtype']=f.GetByte(25) 0178 d['compression']=f.GetByte(26) 0179 d['filter']=f.GetByte(27) 0180 d['interlace']=f.GetByte(28) 0181 d['_shortdescription']=fmts_PNG 0182 d['_longdescription']=fmt_PNG 0183 d['mimetypes']=['image/png', 'image/x-png'] 0184 for i in d.itervalues(): 0185 if i is None: return None 0186 ifi=ImgFileInfo(f,**d) 0187 return ifi 0188 return None 0189 0190 def fmts_PNG(ifi, short=True): 0191 res=[] 0192 res.append( "%d x %d" % (ifi.width, ifi.height) ) 0193 res.append( ifi.format) 0194 if ifi.colourtype in (0,4): 0195 res.append("%d bit grayscale" % (ifi.bitdepth,)) 0196 elif ifi.colourtype in (2,6): 0197 res.append("truecolour (%d bit)" % (ifi.bitdepth*3,)) 0198 elif ifi.colourtype==3: 0199 res.append("%d colours" % (2**ifi.bitdepth,)) 0200 if not short and ifi.colourtype in (4,6): 0201 res.append("with transparency") 0202 return " ".join(res) 0203 0204 def fmt_PNG(ifi): 0205 "Long description for PNG" 0206 res=[fmts_PNG(ifi, False)] 0207 0208 if ifi.compression==0: 0209 res.append("Deflate compressed") 0210 else: 0211 res.append("Unknown compression "+`ifi.compression`) 0212 0213 if ifi.filter==0: 0214 res.append("Adaptive filtering") 0215 else: 0216 res.append("Unknown filtering "+`ifi.filter`) 0217 0218 if ifi.interlace==0: 0219 res.append("No interlacing") 0220 elif ifi.interlace==1: 0221 res.append("Adam7 interlacing") 0222 else: 0223 res.append("Unknown interlacing "+`ifi.interlace`) 0224 return "\n".join(res) 0225 0226 def idimg_BCI(f): 0227 "Identify a Brew Compressed Image" 0228 if f.GetBytes(0,4)=="BCI\x00": 0229 d={'format': "BCI"} 0230 d['width']=f.GetLSBUint16(0x0e) 0231 d['height']=f.GetLSBUint16(0x10) 0232 d['bpp']=8 0233 d['ncolours']=f.GetLSBUint16(0x1a) 0234 d['_longdescription']=fmt_BCI 0235 d['mimetypes']=['image/x-brewcompressedimage'] 0236 for i in d.itervalues(): 0237 if i is None: return None 0238 ifi=ImgFileInfo(f,**d) 0239 return ifi 0240 return None 0241 0242 def fmt_BCI(ifi): 0243 "Long description for BCI" 0244 res=[ifi.shortdescription()] 0245 res.append("%d colour palette" % (ifi.ncolours,)) 0246 return "\n".join(res) 0247 0248 def idimg_JPG(f): 0249 "Identify a JPEG image" 0250 # The people who did jpeg decided to see just how complicated an image 0251 # format they could make. 0252 if f.GetBytes(0,2)=="\xff\xd8": 0253 # in theory we could also parse EXIF information 0254 offset=2 0255 while True: 0256 # we just skip the segments until we find SOF0 (0xc0) 0257 # I can't figure out from the docs if we should also care about SOF1/SOF2 etc 0258 if f.GetByte(offset)!=0xff: 0259 return None 0260 id=f.GetByte(offset+1) 0261 offset+=2 0262 seglen=f.GetMSBUint16(offset) 0263 if seglen is None or id is None: return None 0264 if id!=0xc0: 0265 offset+=seglen 0266 continue 0267 offset+=2 0268 d={'format': 'JPEG'} 0269 d['bpp']=3*f.GetByte(offset) 0270 d['height']=f.GetMSBUint16(offset+1) 0271 d['width']=f.GetMSBUint16(offset+3) 0272 d['components']=f.GetByte(offset+5) 0273 d['_shortdescription']=fmts_JPG 0274 d['mimetypes']=['image/jpg', 'image/jpeg', 'image/x-jpg', 'image/x-jpeg'] 0275 for i in d.itervalues(): 0276 if i is None: return None 0277 ifi=ImgFileInfo(f,**d) 0278 return ifi 0279 return None 0280 0281 def fmts_JPG(ifi): 0282 res=[] 0283 res.append( "%d x %d" % (ifi.width, ifi.height) ) 0284 res.append( ifi.format) 0285 if ifi.components==1: 0286 res.append("(greyscale)") 0287 elif ifi.components==3: 0288 res.append("(RGB)") # technically it is YcbCr ... 0289 elif ifi.components==4: 0290 res.append("(CMYK)") 0291 else: 0292 res.append("Unknown components "+`ifi.components`) 0293 return " ".join(res) 0294 0295 def idimg_GIF(f): 0296 "Identify a GIF image" 0297 if f.GetBytes(0, 3)!='GIF': 0298 # not a GIF image 0299 return None 0300 d={ 'format': 'GIF' } 0301 d['version']=f.GetBytes(3, 3) 0302 d['width']=f.GetLSBUint16(6) 0303 d['height']=f.GetLSBUint16(8) 0304 d['_shortdescription']=fmts_GIF 0305 d['mimetypes']=['image/gif', 'image/x-gif'] 0306 ofs=13 0307 i=f.GetByte(10) 0308 if (i&0x80): 0309 # there's a global color table, skip it 0310 bpp=(i&0x7)+1 0311 d['bpp']=bpp 0312 ofs+=3*(2**bpp) 0313 # check for data block 0314 i=f.GetByte(ofs) 0315 if i!=0x2c: 0316 # not an image data block 0317 if d['version']=='89a' and i==0x21: 0318 # extension block, just return what we have so far 0319 return ImgFileInfo(f, **d) 0320 else: 0321 # unknown block, bail 0322 return None 0323 # get local data 0324 d['width']=f.GetLSBUint16(ofs+5) 0325 d['height']=f.GetLSBUint16(ofs+7) 0326 i=f.GetByte(ofs+9) 0327 if (i&0x80): 0328 d['bpp']=(i&0xf)+1 0329 return ImgFileInfo(f, **d) 0330 0331 def fmts_GIF(ifi): 0332 res=[] 0333 res.append( "%d x %d" % (ifi.width, ifi.height) ) 0334 res.append( '%s%s'%(ifi.format, ifi.version)) 0335 if ifi.bpp is not None: 0336 res.append( '%d BPP'%ifi.bpp) 0337 return ' '.join(res) 0338 0339 def idimg_AVI(f): 0340 "identify an AVI file format" 0341 if f.GetBytes(0, 4)!='RIFF' or f.GetBytes(8, 8)!='AVI LIST' or\ 0342 f.GetBytes(20, 8)!='hdrlavih': 0343 # not an AVI file 0344 return None 0345 d={ 'format': 'AVI' } 0346 d['duration']=float(f.GetLSBUint32(0x20))*f.GetLSBUint32(0x30)/1000000.0 0347 d['width']=f.GetLSBUint32(0x40) 0348 d['height']=f.GetLSBUint32(0x44) 0349 d['_shortdescription']=fmts_AVI 0350 d['mimetypes']=['video/avi', 'video/msvideo', 'video/x-msvideo', 0351 'video/quicktime' ] 0352 return ImgFileInfo(f, **d) 0353 0354 def fmts_AVI(ifi): 0355 res=['%d x %d' % (ifi.width, ifi.height)] 0356 res.append('%.1fs video'%ifi.duration) 0357 return ' '.join(res) 0358 0359 def idimg_LGBIT(f): 0360 "Identify a LGBIT image (LG phone proprietary image format)" 0361 # Profoundly lean and mean image format 0362 # width/height/image-data 0363 # This is for the LG-VX3200. Image data is 16 bit, R5G6B5. 0364 width=f.GetLSBUint16(0x00) 0365 height=f.GetLSBUint16(0x02) 0366 if width is None or height is None: 0367 return None 0368 if f.size==(width*height*2+4): 0369 d={'format': "LGBIT"} 0370 d['width']=width 0371 d['height']=height 0372 d['bpp']=16 0373 d['_shortdescription']=fmts_LGBIT 0374 for i in d.itervalues(): 0375 if i is None: return None 0376 ifi=ImgFileInfo(f,**d) 0377 return ifi 0378 return None 0379 0380 def fmts_LGBIT(ifi): 0381 res=[] 0382 res.append( "%d x %d" % (ifi.width, ifi.height) ) 0383 res.append( '%s'%(ifi.format)) 0384 if ifi.bpp is not None: 0385 res.append( '%d BPP'%ifi.bpp) 0386 return ' '.join(res) 0387 0388 def idimg_3GPP2(f): 0389 "Identify a 3GPP2(3g2) file" 0390 if f.GetBytes(4, 8)!='ftyp3g2a': 0391 # Not a 3GPP2 file 0392 return None 0393 d={ 'format': '3GPP2', 0394 'mimetypes': ['video/3gpp2'] } 0395 return ImgFileInfo(f, **d) 0396 0397 imageids=[globals()[f] for f in dir() if f.startswith("idimg_")] 0398 def identify_imagefile(filename): 0399 v=thefileinfocache.get(filename) 0400 if v is not None: return v 0401 fo=SafeFileWrapper(filename) 0402 for f in imageids: 0403 obj=f(fo) 0404 if obj is not None: 0405 return thefileinfocache.set(filename,obj) 0406 return thefileinfocache.set(filename,ImgFileInfo(fo)) 0407 0408 def identify_imagestring(string): 0409 # identify an image format based on the image data string 0410 fo=SafeStringWrapper(string) 0411 for f in imageids: 0412 obj=f(fo) 0413 if obj is not None: 0414 return obj 0415 0416 class AudioFileInfo: 0417 "Wraps information about an audio file" 0418 0419 # These will always be present 0420 attrnames=("format", "size", "duration", "MAXSIZE") 0421 0422 def __init__(self, f, **kwds): 0423 for a in self.attrnames: 0424 setattr(self, a, None) 0425 self.mimetypes=[] 0426 self.size=f.size 0427 self.__dict__.update(kwds) 0428 0429 def shortdescription(self): 0430 v=getattr(self, "_shortdescription", None) 0431 if v is not None: 0432 return v(self) 0433 res=[] 0434 if self.format is not None: 0435 res.append( self.format) 0436 if self.duration is not None: 0437 res.append( "%d seconds" % (self.duration,)) 0438 0439 if len(res): 0440 return " ".join(res) 0441 return "Unknown format" 0442 0443 def longdescription(self): 0444 v=getattr(self, "_longdescription", None) 0445 if v is not None: 0446 return v(self) 0447 return self.shortdescription() 0448 0449 def idaudio_MIDI(f): 0450 "Identify a midi file" 0451 # http://www.borg.com/~jglatt/tech/midifile.htm 0452 # 0453 # You can't work out the length without working out 0454 # which track is the longest which you have to do by 0455 # parsing each note. 0456 m=midifile.MIDIFile(f) 0457 if not m.valid: 0458 return None 0459 d={'format': "MIDI"} 0460 d['type']=m.type 0461 d['numtracks']=m.num_tracks 0462 d['duration']=m.duration 0463 d['_shortdescription']=fmts_MIDI 0464 d['mimetypes']=['audio/x-midi', 'audio/midi'] 0465 for i in d.itervalues(): 0466 if i is None: return None 0467 afi=AudioFileInfo(f,**d) 0468 return afi 0469 0470 def fmts_MIDI(afi): 0471 res=[] 0472 res.append( afi.format) 0473 res.append( "type "+`afi.type`) 0474 if afi.type!=0 and afi.numtracks>1: 0475 res.append("(%d tracks)" % (afi.numtracks,)) 0476 res.append('%0.1f seconds'%afi.duration) 0477 # res.append("%04x" % (afi.division,)) 0478 return " ".join(res) 0479 0480 def _getbits(start, length, value): 0481 assert length>0 0482 return (value>>(start-length+1)) & ((2**length)-1) 0483 0484 def getmp3fileinfo(filename): 0485 f=SafeFileWrapper(filename) 0486 return idaudio_zzMP3(f, True) 0487 0488 0489 twooheightzeros="\x00"*208 0490 # want to make sure this gets evaluated last 0491 def idaudio_zzMP3(f, returnframes=False): 0492 # http://mpgedit.org/mpgedit/mpeg_format/mpeghdr.htm 0493 try: 0494 idv3present=False 0495 id3v1present=False 0496 0497 header=f.GetMSBUint32(0) 0498 0499 # there may be ffmpeg output with 208 leading zeros for no apparent reason 0500 if header==0 and f.data.startswith(twooheightzeros): 0501 offset=208 0502 # there may be an id3 header at the begining 0503 elif header==0x49443303: 0504 sz=[f.GetByte(x) for x in range(6,10)] 0505 if len([zz for zz in sz if zz<0 or zz>=0x80]): 0506 return None 0507 sz=(sz[0]<<21)+(sz[1]<<14)+(sz[2]<<7)+sz[3] 0508 offset=10+sz 0509 idv3present=True 0510 header=f.GetMSBUint32(offset) 0511 elif header is None: 0512 return None 0513 else: 0514 offset=0 0515 0516 # locate the 1st sync frame 0517 while True: 0518 v=f.GetMSBUint16(offset) 0519 if v is None: return None 0520 if v&0xffe0==0xffe0: 0521 break 0522 offset=f.data.find("\xff", offset+1) 0523 if offset<0: 0524 # not an mp3 file or sync is way later in 0525 return None 0526 0527 frames=[] 0528 while offset<f.size: 0529 if offset==f.size-128 and f.GetBytes(offset,3)=="TAG": 0530 offset+=128 0531 id3v1present=True 0532 continue 0533 frame=MP3Frame(f, offset) 0534 if not frame.OK or frame.nextoffset>f.size: break 0535 offset=frame.nextoffset 0536 frames.append(frame) 0537 0538 if len(frames)==0: return 0539 0540 if offset!=f.size: 0541 print "MP3 offset is",offset,"size is",f.size 0542 0543 # copy some information from the first frame 0544 f0=frames[0] 0545 d={'format': 'MP3', 0546 'id3v1present': id3v1present, # badly named ... 0547 'idv3present': idv3present, 0548 'unrecognisedframes': offset!=f.size, 0549 'version': f0.version, 0550 'layer': f0.layer, 0551 'bitrate': f0.bitrate, 0552 'samplerate': f0.samplerate, 0553 'channels': f0.channels, 0554 'copyright': f0.copyright, 0555 'original': f0.original, 0556 'mimetypes': ['audio/x-mp3', 'audio/mpeg', 'audio/x-mpeg']} 0557 0558 duration=f0.duration 0559 vbrmin=vbrmax=f0.bitrate 0560 0561 for frame in frames[1:]: 0562 duration+=frame.duration 0563 if frame.bitrate!=f0.bitrate: 0564 d['bitrate']=0 0565 if frame.samplerate!=f0.samplerate: 0566 d['samplerate']=0 0567 vbrmin=min(frame.bitrate,vbrmin) 0568 vbrmax=max(frame.bitrate,vbrmax) 0569 if frame.channels!=f0.channels: 0570 d['channels']=0 0571 0572 d['duration']=duration 0573 d['vbrmin']=vbrmin 0574 d['vbrmax']=vbrmax 0575 d['_longdescription']=fmt_MP3 0576 d['_shortdescription']=fmts_MP3 0577 0578 if returnframes: 0579 d['frames']=frames 0580 0581 return AudioFileInfo(f, **d) 0582 except: 0583 return None 0584 0585 def fmt_MP3(afi): 0586 res=[] 0587 res.append("MP3 (Mpeg Version %d Layer %d)" % (afi.version, afi.layer)) 0588 res.append("%s %.1f Khz %0.1f seconds" % (["Variable!!", "Mono", "Stereo"][afi.channels], afi.samplerate/1000.0, afi.duration,)) 0589 if afi.bitrate: 0590 res.append(`afi.bitrate`+" kbps") 0591 else: 0592 res.append("VBR (min %d kbps, max %d kbps)" % (afi.vbrmin, afi.vbrmax)) 0593 if afi.unrecognisedframes: 0594 res.append("There are unrecognised frames in this file") 0595 if afi.idv3present: 0596 res.append("IDV3 tag present at begining of file") 0597 if afi.id3v1present: 0598 res.append("IDV3.1 tag present at end of file") 0599 if afi.copyright: 0600 res.append("Marked as copyrighted") 0601 if afi.original: 0602 res.append("Marked as the original") 0603 0604 return "\n".join(res) 0605 0606 def fmts_MP3(afi): 0607 return "MP3 %s %dKhz %d sec" % (["Variable!!", "Mono", "Stereo"][afi.channels], afi.samplerate/1000.0, afi.duration,) 0608 0609 0610 class MP3Frame: 0611 0612 bitrates={ 0613 # (version, layer): bitrate mapping 0614 (1, 1): [None, 32, 64, 96, 128, 160, 192, 224, 256, 288, 320, 352, 384, 416, 448, None], 0615 (1, 2): [None, 32, 48, 56, 64, 80, 96, 112, 128, 160, 192, 224, 256, 320, 384, None], 0616 (1, 3): [None, 32, 40, 48, 56, 64, 80, 96, 112, 128, 160, 192, 224, 256, 320, None], 0617 (2, 1): [None, 32, 48, 56, 64, 80, 96, 112, 128, 144, 160, 176, 192, 224, 256, None], 0618 (2, 2): [None, 8, 16, 24, 32, 40, 48, 56, 64, 80, 96, 112, 128, 144, 160, None], 0619 (2, 3): [None, 8, 16, 24, 32, 40, 48, 56, 64, 80, 96, 112, 128, 144, 160, None], 0620 } 0621 0622 samplerates={ 0623 1: [44100, 48000, 32000, None], 0624 2: [22050, 24000, 16000, None] 0625 } 0626 0627 def __init__(self, f, offset): 0628 self.OK=False 0629 header=f.GetMSBUint32(offset) 0630 if header is None: return 0631 # first 11 buts must all be set 0632 if _getbits(31,11, header)!=2047: 0633 return 0634 self.header=header 0635 # Version 0636 version=_getbits(20,2,header) 0637 if version not in (2,3): # we don't support 'reserved' or version 2.5 0638 return 0639 if version==3: # yes, version 1 is encoded as 3 0640 version=1 0641 self.version=version 0642 # Layer 0643 layer=_getbits(18,2,header) 0644 if layer==0: return # reserved which we don't support 0645 if layer==1: 0646 self.layer=3 0647 elif layer==2: 0648 self.layer=2 0649 elif layer==3: 0650 self.layer=1 0651 self.crc=_getbits(16,1,header) 0652 self.bitrate=self.bitrates[(self.version, self.layer)][_getbits(15,4,header)] 0653 self.samplerate=self.samplerates[self.version][_getbits(11,2,header)] 0654 self.padding=_getbits(9,1,header) 0655 if self.layer==1: 0656 self.framelength=(12000*self.bitrate/self.samplerate+self.padding)*4 0657 else: 0658 if self.version==1: 0659 self.framelength=144000*self.bitrate/self.samplerate+self.padding 0660 else: 0661 self.framelength=72000*self.bitrate/self.samplerate+self.padding 0662 self.duration=self.framelength*8*1.0/(self.bitrate*1000) 0663 self.private=_getbits(8,1,header) 0664 self.channelmode=_getbits(7,2,header) 0665 if self.channelmode in (0,1,2): 0666 self.channels=2 0667 else: 0668 self.channels=1 0669 0670 self.modeextenstion=_getbits(5,2,header) 0671 self.copyright=_getbits(3,1,header) 0672 self.original=_getbits(2,1, header) 0673 self.emphasis=_getbits(1,2, header) 0674 0675 self.offset=offset 0676 self.nextoffset=offset+self.framelength 0677 self.OK=True 0678 0679 def idaudio_QCP(f): 0680 "Identify a Qualcomm Purevoice file" 0681 # http://www.faqs.org/rfcs/rfc3625.html 0682 # 0683 # Sigh, another format where you have no hope of being able to work out the length 0684 if f.GetBytes(0,4)=="RIFF" and f.GetBytes(8,4)=="QLCM": 0685 d={'format': "QCP"} 0686 0687 # fmt section 0688 if f.GetBytes(12,4)!="fmt ": 0689 return None 0690 # chunksize is at 16, len 4 0691 d['qcpmajor']=f.GetByte(20) 0692 d['qcpminor']=f.GetByte(21) 0693 # guid is at 22 0694 d['codecguid']=(f.GetLSBUint32(22), f.GetLSBUint16(26), f.GetLSBUint16(28), f.GetMSBUint16(30), (long(f.GetMSBUint16(32))<<32)+f.GetMSBUint32(34)) 0695 d['codecversion']=f.GetLSBUint16(38) 0696 name=f.GetBytes(40,80) 0697 zero=name.find('\x00') 0698 if zero>=0: 0699 name=name[:zero] 0700 d['codecname']=name 0701 d['averagebps']=f.GetLSBUint16(120) 0702 # packetsize is at 122, len 2 0703 # block size is at 124, len 2 0704 d['samplingrate']=f.GetLSBUint16(126) 0705 d['samplesize']=f.GetLSBUint16(128) 0706 d['_longdescription']=fmt_QCP 0707 0708 if d['codecguid']==( 0x5e7f6d41, 0xb115, 0x11d0, 0xba91, 0x00805fb4b97eL ) or \ 0709 d['codecguid']==( 0x5e7f6d42, 0xb115, 0x11d0, 0xba91, 0x00805fb4b97eL ): 0710 d['mimetypes']=['audio/qcelp'] # in theory audio/vnd.qcelp could also be used but is deprecated 0711 elif d['codecguid']==( 0xe689d48dL, 0x9076, 0x46b5, 0x91ef, 0x736a5100ceb4L ): 0712 d['mimetypes']=['audio/evrc-qcp'] 0713 elif d['codecguid']==( 0x8d7c2b75L, 0xa797, 0xed49, 0x985e, 0xd53c8cc75f84L ): 0714 d['mimetypes']=['audio/smv-qcp'] 0715 0716 for i in d.itervalues(): 0717 if i is None: return None 0718 afi=AudioFileInfo(f,**d) 0719 return afi 0720 return None 0721 0722 def fmt_QCP(afi): 0723 res=["QCP %s" % (afi.codecname,)] 0724 res.append("%d bps %d Hz %d bits/sample" % (afi.averagebps, afi.samplingrate, afi.samplesize)) 0725 codecguid=afi.codecguid 0726 if codecguid==( 0x5e7f6d41, 0xb115, 0x11d0, 0xba91, 0x00805fb4b97eL ): 0727 res.append("QCELP-13K V"+`afi.codecversion` + " (guid 1)") 0728 elif codecguid==( 0x5e7f6d42, 0xb115, 0x11d0, 0xba91, 0x00805fb4b97eL ): 0729 res.append("QCELP-13K V"+`afi.codecversion` + " (guid 2)") 0730 elif codecguid==( 0xe689d48dL, 0x9076, 0x46b5, 0x91ef, 0x736a5100ceb4L ): 0731 res.append("EVRC V"+`afi.codecversion`) 0732 elif codecguid==( 0x8d7c2b75L, 0xa797, 0xed49, 0x985e, 0xd53c8cc75f84L ): 0733 res.append("SMV V"+`afi.codecversion`) 0734 else: 0735 res.append("Codec Guid {%08X-%04X-%04X-%04X-%012X} V%d" % (afi.codecguid+(afi.codecversion,))) 0736 res.append("QCP File Version %d.%d" % (afi.qcpmajor, afi.qcpminor)) 0737 0738 return "\n".join(res) 0739 0740 def idaudio_PMD(f): 0741 "Identify a PMD/CMX file" 0742 # There are no specs for this file format. From 10 minutes of eyeballing, it seems like below. 0743 # Each section is a null terminated string followed by a byte saying how long the data is. 0744 # The length is probably some sort of variable length encoding such as the high bit indicating 0745 # the last byte and using 7 bits. 0746 # 0747 # offset contents -- comment 0748 # 0 cmid -- file type id 0749 # 4 \0\0 -- no idea 0750 # 6 7*? -- file lengths and pointers 0751 # 13 vers\0 -- version section 0752 # 18 \x04 -- length of version section 0753 # 19 "string" -- a version number that has some correlation with the pmd version number 0754 # 0755 # Various other sections that cover the contents that don't matter for identification 0756 try: 0757 if f.GetBytes(0, 4)!='cmid': 0758 return None 0759 d={ 'format': 'PMD' } 0760 hdr_len=f.GetMSBUint16(8) 0761 i=f.GetByte(10) 0762 d['content']=['Unknown', 'Ringer', 'Pictures&Audio'][i] 0763 d['numtracks']=f.GetByte(12) 0764 ofs=13 0765 ofs_end=hdr_len+10 0766 while ofs<ofs_end: 0767 s=f.GetBytes(ofs, 4) 0768 i=f.GetMSBUint16(ofs+4) 0769 ofs+=6 0770 if i==0: 0771 continue 0772 if s=='vers': 0773 d['version']=f.GetBytes(ofs, i) 0774 elif s=='titl': 0775 d['title']=f.GetBytes(ofs, i) 0776 elif s=='cnts': 0777 d['media']=f.GetBytes(ofs, i) 0778 ofs+=i 0779 d['_longdescription']=fmt_PMD 0780 d['_shortdescription']=fmts_PMD 0781 return AudioFileInfo(f, **d) 0782 except: 0783 return None 0784 0785 def fmts_PMD(afi): 0786 return 'PMD/CMF %s'% afi.content 0787 0788 def fmt_PMD(afi): 0789 res=['PMD/CMF'] 0790 if hasattr(afi, 'version'): 0791 res.append('Version: '+afi.version) 0792 res.append('Content: '+afi.content) 0793 res.append('%d Tracks'%afi.numtracks) 0794 if hasattr(afi, 'title'): 0795 res.append('Title: '+afi.title) 0796 if hasattr(afi, 'media'): 0797 res.append('Media: '+afi.media) 0798 return '\n'.join(res) 0799 0800 def idaudio_PCM(f): 0801 "Identify a PCM/WAV file" 0802 try: 0803 if f.GetBytes(0, 4)!='RIFF' or f.GetBytes(8, 4)!='WAVE' or \ 0804 f.GetBytes(12, 4)!='fmt ' or f.GetLSBUint16(20)!=1: 0805 return None 0806 d={ 'format': 'PCM', 0807 'mimetypes': ['audio/wav', 'audio/x-wav', 0808 'audio/wave', 'audio/x-pn-wav'] } 0809 d['numchannels']=f.GetLSBUint16(22) 0810 d['samplerate']=f.GetLSBUint32(24) 0811 d['byterate']=f.GetLSBUint32(28) 0812 d['blockalign']=f.GetLSBUint16(32) 0813 d['bitspersample']=f.GetLSBUint16(34) 0814 # compute the approximate duration 0815 subchunk1size=f.GetLSBUint32(16) 0816 datasize=f.GetLSBUint32(20+subchunk1size+4) 0817 d['duration']=float(datasize)/(d['blockalign']*d['samplerate']) 0818 d['_longdescription']=fmt_PCM 0819 d['_shortdescription']=fmts_PCM 0820 return AudioFileInfo(f, **d) 0821 except: 0822 return None 0823 0824 def fmts_PCM(afi): 0825 return afi.format 0826 0827 def fmt_PCM(afi): 0828 res=['PCM/WAV'] 0829 res.append('%d KHz %d bits %s'%\ 0830 (afi.samplerate/1000, afi.bitspersample,\ 0831 ['None', 'Mono', 'Stereo'][afi.numchannels])) 0832 return '\n'.join(res) 0833 0834 def getpcmfileinfo(filename): 0835 f=SafeFileWrapper(filename) 0836 return idaudio_PCM(f) 0837 0838 # WMA file support 0839 def idaudio_WMA(f): 0840 "Identify a WMA file" 0841 try: 0842 _wma=wma_file.WMA_File(f) 0843 if not _wma.valid: 0844 return None 0845 d={ 'format': 'WMA', 0846 'mimetypes': ['audio/x-ms-wma'], 0847 'duration': _wma.play_duration, 0848 'title': _wma.title, 0849 'artist': _wma.author, 0850 'album': _wma.album, 0851 'genre': _wma.genre, 0852 '_longdescription': fmt_WMA, 0853 } 0854 return AudioFileInfo(f, **d) 0855 except: 0856 if __debug__: 0857 raise 0858 0859 def fmt_WMA(afi): 0860 res=['WMA', 'Duration: %.2f'%afi.duration ] 0861 if afi.title: 0862 res.append('Title: %s'%afi.title) 0863 if afi.artist: 0864 res.append('Artist: %s'%afi.artist) 0865 if afi.album: 0866 res.append('Album: %s'%afi.album) 0867 if afi.genre: 0868 res.append('Genre: %s'%afi.genre) 0869 return '\n'.join(res) 0870 0871 audioids=[globals()[f] for f in dir() if f.startswith("idaudio_")] 0872 def identify_audiofile(filename): 0873 v=thefileinfocache.get(filename) 0874 if v is not None: return v 0875 fo=SafeFileWrapper(filename) 0876 for f in audioids: 0877 obj=f(fo) 0878 if obj is not None: 0879 return thefileinfocache.set(filename, obj) 0880 return thefileinfocache.set(filename, AudioFileInfo(fo)) 0881 0882 def identify_audiostring(string): 0883 # identify an audio format based on the audio data string 0884 fo=SafeStringWrapper(string) 0885 for f in audioids: 0886 obj=f(fo) 0887 if obj is not None: 0888 return obj 0889 ### 0890 ### caches for audio/image id 0891 ### 0892 0893 thefileinfocache=common.FileCache(lowwater=100,hiwater=140) 0894
Generated by PyXR 0.9.4