PyXR

c:\projects\bitpim\src \ fileinfo.py



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