Module fileinfo
[hide private]
[frames] | no frames]

Source Code for Module fileinfo

  1  ### BITPIM 
  2  ### 
  3  ### Copyright (C) 2005 Roger Binns <rogerb@rogerbinns.com> 
  4  ### 
  5  ### This program is free software; you can redistribute it and/or modify 
  6  ### it under the terms of the BitPim license as detailed in the LICENSE file. 
  7  ### 
  8  ### $Id: fileinfo.py 4731 2009-03-06 03:28:04Z hjelmn $ 
  9   
 10  "Returns information about files" 
 11   
 12  import os 
 13  import struct 
 14   
 15  import common 
 16   
 17  import midifile 
 18  import wma_file 
 19  import mp4_file 
 20   
21 -class FailedFile:
22 data=""
23 - def GetBytes(*args): return None
24 - def GetLSBUint32(*args): return None
25 - def GetLSBUint16(*args): return None
26 - def GetByte(*args): return None
27 - def GetMSBUint32(*args): return None
28 - def GetMSBUint16(*args): return None
29
30 -class SafeFileWrapper:
31 """Wraps a file object letting you get various parts without exceptions""" 32 33 READAHEAD=1024 34
35 - def __init__(self, filename):
36 try: 37 self.file=open(filename, "rb") 38 self.size=os.stat(filename).st_size 39 self.data=self.file.read(self.READAHEAD) 40 except (OSError,IOError): 41 # change our class 42 self.size=-1 43 self.__class__=FailedFile
44
45 - def GetBytes(self, offset, length):
46 if offset+length<len(self.data): 47 return self.data[offset:offset+length] 48 if offset+length>self.size: 49 return None 50 self.file.seek(offset) 51 res=self.file.read(length) 52 if len(res)<length: return None 53 return res
54
55 - def GetLSBUint32(self, offset):
56 v=self.GetBytes(offset, 4) 57 if v is None: return v 58 return struct.unpack('<L', v)[0]
59
60 - def GetLSBUint16(self, offset):
61 v=self.GetBytes(offset, 2) 62 if v is None: return v 63 return struct.unpack('<H', v)[0]
64
65 - def GetMSBUint32(self, offset):
66 v=self.GetBytes(offset, 4) 67 if v is None: return v 68 return struct.unpack('>L', v)[0]
69
70 - def GetMSBUint16(self, offset):
71 v=self.GetBytes(offset, 2) 72 if v is None: return v 73 return struct.unpack('>H', v)[0]
74
75 - def GetByte(self, offset):
76 v=self.GetBytes(offset,1) 77 if v is None: return v 78 return ord(v)
79
80 -class SafeStringWrapper(SafeFileWrapper):
81 """ 82 Wraps a string object letting you get various parts w/o exceptions. 83 Mainly used by the com_* modules as part of writing media to the phone. 84 """
85 - def __init__(self, string):
86 if isinstance(string, str): 87 self.file=None 88 self.data=string 89 self.size=len(string) 90 else: 91 self.size=-1 92 self.__class__=FailedFile
93
94 - def GetBytes(self, offset, length):
95 if (offset+length)<=self.size: 96 return self.data[offset:offset+length]
97
98 -class ImgFileInfo:
99 "Wraps information about an image file" 100 101 # These will always be present 102 attrnames=("width", "height", "format", "bpp", "size", "MAXSIZE") 103
104 - def __init__(self, f, **kwds):
105 for a in self.attrnames: 106 setattr(self, a, None) 107 self.mimetypes=[] 108 self.size=f.size 109 self.__dict__.update(kwds)
110
111 - def shortdescription(self):
112 v=getattr(self, "_shortdescription", None) 113 if v is not None: 114 return v(self) 115 res=[] 116 if self.width is not None and self.height is not None: 117 res.append( "%d x %d" % (self.width, self.height) ) 118 if self.format is not None: 119 res.append( self.format) 120 if self.bpp is not None: 121 res.append( "%d bpp" % (self.bpp,)) 122 123 if len(res): 124 return " ".join(res) 125 return "Unknown format"
126
127 - def longdescription(self):
128 v=getattr(self, "_longdescription", None) 129 if v is not None: 130 return v(self) 131 return self.shortdescription()
132
133 -def idimg_BMP(f):
134 "Identify a Windows bitmap" 135 # 40 is header size for windows bmp, different numbers are used by OS/2 136 if f.GetBytes(0,2)=="BM" and f.GetLSBUint16(14)==40: 137 d={'format': "BMP"} 138 d['width']=f.GetLSBUint32(18) 139 d['height']=f.GetLSBUint32(22) 140 d['bpp']=f.GetLSBUint16(28) 141 d['compression']=f.GetLSBUint32(30) 142 d['ncolours']=f.GetLSBUint32(46) 143 d['nimportantcolours']=f.GetLSBUint32(50) 144 d['_longdescription']=fmt_BMP 145 d['mimetypes']=['image/bmp', 'image/x-bmp'] 146 for i in d.itervalues(): 147 if i is None: return None 148 ifi=ImgFileInfo(f,**d) 149 return ifi 150 return None
151
152 -def fmt_BMP(ifi):
153 "Long description for BMP" 154 res=[ifi.shortdescription()] 155 if ifi.compression==0: 156 res.append("No compression") 157 elif ifi.compression==1: 158 res.append("8 bit run length encoding") 159 elif ifi.compression==2: 160 res.append("4 bit run length encoding") 161 elif ifi.compression==3: 162 res.append("RGB bitmap with mask") 163 else: 164 res.append("Unknown compression "+`ifi.compression`) 165 if ifi.ncolours: 166 res.append("%d colours" % (ifi.ncolours,)) 167 if ifi.nimportantcolours: 168 res[-1]=res[-1]+(" (%d important)" % (ifi.nimportantcolours,)) 169 return "\n".join(res)
170
171 -def idimg_PNG(f):
172 "Identify a PNG" 173 if f.GetBytes(0,8)=="\x89PNG\r\n\x1a\n" and f.GetBytes(12,4)=="IHDR": 174 d={'format': "PNG"} 175 d['width']=f.GetMSBUint32(16) 176 d['height']=f.GetMSBUint32(20) 177 d['bitdepth']=f.GetByte(24) 178 d['colourtype']=f.GetByte(25) 179 d['compression']=f.GetByte(26) 180 d['filter']=f.GetByte(27) 181 d['interlace']=f.GetByte(28) 182 d['_shortdescription']=fmts_PNG 183 d['_longdescription']=fmt_PNG 184 d['mimetypes']=['image/png', 'image/x-png'] 185 for i in d.itervalues(): 186 if i is None: return None 187 ifi=ImgFileInfo(f,**d) 188 return ifi 189 return None
190
191 -def fmts_PNG(ifi, short=True):
192 res=[] 193 res.append( "%d x %d" % (ifi.width, ifi.height) ) 194 res.append( ifi.format) 195 if ifi.colourtype in (0,4): 196 res.append("%d bit grayscale" % (ifi.bitdepth,)) 197 elif ifi.colourtype in (2,6): 198 res.append("truecolour (%d bit)" % (ifi.bitdepth*3,)) 199 elif ifi.colourtype==3: 200 res.append("%d colours" % (2**ifi.bitdepth,)) 201 if not short and ifi.colourtype in (4,6): 202 res.append("with transparency") 203 return " ".join(res)
204
205 -def fmt_PNG(ifi):
206 "Long description for PNG" 207 res=[fmts_PNG(ifi, False)] 208 209 if ifi.compression==0: 210 res.append("Deflate compressed") 211 else: 212 res.append("Unknown compression "+`ifi.compression`) 213 214 if ifi.filter==0: 215 res.append("Adaptive filtering") 216 else: 217 res.append("Unknown filtering "+`ifi.filter`) 218 219 if ifi.interlace==0: 220 res.append("No interlacing") 221 elif ifi.interlace==1: 222 res.append("Adam7 interlacing") 223 else: 224 res.append("Unknown interlacing "+`ifi.interlace`) 225 return "\n".join(res)
226
227 -def idimg_BCI(f):
228 "Identify a Brew Compressed Image" 229 if f.GetBytes(0,4)=="BCI\x00": 230 d={'format': "BCI"} 231 d['width']=f.GetLSBUint16(0x0e) 232 d['height']=f.GetLSBUint16(0x10) 233 d['bpp']=8 234 d['ncolours']=f.GetLSBUint16(0x1a) 235 d['_longdescription']=fmt_BCI 236 d['mimetypes']=['image/x-brewcompressedimage'] 237 for i in d.itervalues(): 238 if i is None: return None 239 ifi=ImgFileInfo(f,**d) 240 return ifi 241 return None
242
243 -def fmt_BCI(ifi):
244 "Long description for BCI" 245 res=[ifi.shortdescription()] 246 res.append("%d colour palette" % (ifi.ncolours,)) 247 return "\n".join(res)
248
249 -def idimg_JPG(f):
250 "Identify a JPEG image" 251 # The people who did jpeg decided to see just how complicated an image 252 # format they could make. 253 if f.GetBytes(0,2)=="\xff\xd8": 254 # in theory we could also parse EXIF information 255 offset=2 256 while True: 257 # we just skip the segments until we find SOF0 (0xc0) 258 # I can't figure out from the docs if we should also care about SOF1/SOF2 etc 259 if f.GetByte(offset)!=0xff: 260 return None 261 id=f.GetByte(offset+1) 262 offset+=2 263 seglen=f.GetMSBUint16(offset) 264 if seglen is None or id is None: return None 265 if id!=0xc0: 266 offset+=seglen 267 continue 268 offset+=2 269 d={'format': 'JPEG'} 270 d['bpp']=3*f.GetByte(offset) 271 d['height']=f.GetMSBUint16(offset+1) 272 d['width']=f.GetMSBUint16(offset+3) 273 d['components']=f.GetByte(offset+5) 274 d['_shortdescription']=fmts_JPG 275 d['mimetypes']=['image/jpg', 'image/jpeg', 'image/x-jpg', 'image/x-jpeg'] 276 for i in d.itervalues(): 277 if i is None: return None 278 ifi=ImgFileInfo(f,**d) 279 return ifi 280 return None
281
282 -def fmts_JPG(ifi):
283 res=[] 284 res.append( "%d x %d" % (ifi.width, ifi.height) ) 285 res.append( ifi.format) 286 if ifi.components==1: 287 res.append("(greyscale)") 288 elif ifi.components==3: 289 res.append("(RGB)") # technically it is YcbCr ... 290 elif ifi.components==4: 291 res.append("(CMYK)") 292 else: 293 res.append("Unknown components "+`ifi.components`) 294 return " ".join(res)
295
296 -def idimg_GIF(f):
297 "Identify a GIF image" 298 if f.GetBytes(0, 3)!='GIF': 299 # not a GIF image 300 return None 301 d={ 'format': 'GIF' } 302 d['version']=f.GetBytes(3, 3) 303 d['width']=f.GetLSBUint16(6) 304 d['height']=f.GetLSBUint16(8) 305 d['_shortdescription']=fmts_GIF 306 d['mimetypes']=['image/gif', 'image/x-gif'] 307 ofs=13 308 i=f.GetByte(10) 309 if (i&0x80): 310 # there's a global color table, skip it 311 bpp=(i&0x7)+1 312 d['bpp']=bpp 313 ofs+=3*(2**bpp) 314 # check for data block 315 i=f.GetByte(ofs) 316 if i!=0x2c: 317 # not an image data block 318 if d['version']=='89a' and i==0x21: 319 # extension block, just return what we have so far 320 return ImgFileInfo(f, **d) 321 else: 322 # unknown block, bail 323 return None 324 # get local data 325 d['width']=f.GetLSBUint16(ofs+5) 326 d['height']=f.GetLSBUint16(ofs+7) 327 i=f.GetByte(ofs+9) 328 if (i&0x80): 329 d['bpp']=(i&0xf)+1 330 return ImgFileInfo(f, **d)
331
332 -def fmts_GIF(ifi):
333 res=[] 334 res.append( "%d x %d" % (ifi.width, ifi.height) ) 335 res.append( '%s%s'%(ifi.format, ifi.version)) 336 if ifi.bpp is not None: 337 res.append( '%d BPP'%ifi.bpp) 338 return ' '.join(res)
339
340 -def idimg_AVI(f):
341 "identify an AVI file format" 342 if f.GetBytes(0, 4)!='RIFF' or f.GetBytes(8, 8)!='AVI LIST' or\ 343 f.GetBytes(20, 8)!='hdrlavih': 344 # not an AVI file 345 return None 346 d={ 'format': 'AVI' } 347 d['duration']=float(f.GetLSBUint32(0x20))*f.GetLSBUint32(0x30)/1000000.0 348 d['width']=f.GetLSBUint32(0x40) 349 d['height']=f.GetLSBUint32(0x44) 350 d['_shortdescription']=fmts_AVI 351 d['mimetypes']=['video/avi', 'video/msvideo', 'video/x-msvideo', 352 'video/quicktime' ] 353 return ImgFileInfo(f, **d)
354
355 -def fmts_AVI(ifi):
356 res=['%d x %d' % (ifi.width, ifi.height)] 357 res.append('%.1fs video'%ifi.duration) 358 return ' '.join(res)
359
360 -def idimg_LGBIT(f):
361 "Identify a LGBIT image (LG phone proprietary image format)" 362 # Profoundly lean and mean image format 363 # width/height/image-data 364 # This is for the LG-VX3200. Image data is 16 bit, R5G6B5. 365 width=f.GetLSBUint16(0x00) 366 height=f.GetLSBUint16(0x02) 367 if width is None or height is None: 368 return None 369 if f.size==(width*height*2+4): 370 d={'format': "LGBIT"} 371 d['width']=width 372 d['height']=height 373 d['bpp']=16 374 d['_shortdescription']=fmts_LGBIT 375 for i in d.itervalues(): 376 if i is None: return None 377 ifi=ImgFileInfo(f,**d) 378 return ifi 379 return None
380
381 -def fmts_LGBIT(ifi):
382 res=[] 383 res.append( "%d x %d" % (ifi.width, ifi.height) ) 384 res.append( '%s'%(ifi.format)) 385 if ifi.bpp is not None: 386 res.append( '%d BPP'%ifi.bpp) 387 return ' '.join(res)
388
389 -def idimg_3GPP2(f):
390 "Identify a 3GPP2(3g2) file" 391 if f.GetBytes(4, 8)!='ftyp3g2a': 392 # Not a 3GPP2 file 393 return None 394 d={ 'format': '3GPP2', 395 'mimetypes': ['video/3gpp2'] } 396 return ImgFileInfo(f, **d)
397 398 imageids=[globals()[f] for f in dir() if f.startswith("idimg_")]
399 -def identify_imagefile(filename):
400 v=thefileinfocache.get(filename) 401 if v is not None: return v 402 fo=SafeFileWrapper(filename) 403 for f in imageids: 404 obj=f(fo) 405 if obj is not None: 406 return thefileinfocache.set(filename,obj) 407 return thefileinfocache.set(filename,ImgFileInfo(fo))
408
409 -def identify_imagestring(string):
410 # identify an image format based on the image data string 411 fo=SafeStringWrapper(string) 412 for f in imageids: 413 obj=f(fo) 414 if obj is not None: 415 return obj
416
417 -class AudioFileInfo:
418 "Wraps information about an audio file" 419 420 # These will always be present 421 attrnames=("format", "size", "duration", "MAXSIZE") 422
423 - def __init__(self, f, **kwds):
424 for a in self.attrnames: 425 setattr(self, a, None) 426 self.mimetypes=[] 427 self.size=f.size 428 self.__dict__.update(kwds)
429
430 - def shortdescription(self):
431 v=getattr(self, "_shortdescription", None) 432 if v is not None: 433 return v(self) 434 res=[] 435 if self.format is not None: 436 res.append( self.format) 437 if self.duration is not None: 438 res.append( "%d seconds" % (self.duration,)) 439 440 if len(res): 441 return " ".join(res) 442 return "Unknown format"
443
444 - def longdescription(self):
445 v=getattr(self, "_longdescription", None) 446 if v is not None: 447 return v(self) 448 return self.shortdescription()
449
450 -def idaudio_MIDI(f):
451 "Identify a midi file" 452 # http://www.borg.com/~jglatt/tech/midifile.htm 453 # 454 # You can't work out the length without working out 455 # which track is the longest which you have to do by 456 # parsing each note. 457 m=midifile.MIDIFile(f) 458 if not m.valid: 459 return None 460 d={'format': "MIDI"} 461 d['type']=m.type 462 d['numtracks']=m.num_tracks 463 d['duration']=m.duration 464 d['_shortdescription']=fmts_MIDI 465 d['mimetypes']=['audio/x-midi', 'audio/midi'] 466 for i in d.itervalues(): 467 if i is None: return None 468 afi=AudioFileInfo(f,**d) 469 return afi
470
471 -def fmts_MIDI(afi):
472 res=[] 473 res.append( afi.format) 474 res.append( "type "+`afi.type`) 475 if afi.type!=0 and afi.numtracks>1: 476 res.append("(%d tracks)" % (afi.numtracks,)) 477 res.append('%0.1f seconds'%afi.duration) 478 # res.append("%04x" % (afi.division,)) 479 return " ".join(res)
480
481 -def _getbits(start, length, value):
482 assert length>0 483 return (value>>(start-length+1)) & ((2**length)-1)
484
485 -def getmp3fileinfo(filename):
486 f=SafeFileWrapper(filename) 487 return idaudio_zzMP3(f, True)
488
489 -def getmp4fileinfo(filename):
490 f=SafeFileWrapper(filename) 491 return idaudio_MP4(f)
492 493 494 twooheightzeros="\x00"*208 495 # want to make sure this gets evaluated last
496 -def idaudio_zzMP3(f, returnframes=False):
497 # http://mpgedit.org/mpgedit/mpeg_format/mpeghdr.htm 498 try: 499 idv3present=False 500 id3v1present=False 501 502 header=f.GetMSBUint32(0) 503 504 # there may be ffmpeg output with 208 leading zeros for no apparent reason 505 if header==0 and f.data.startswith(twooheightzeros): 506 offset=208 507 # there may be an id3 header at the begining 508 elif header==0x49443303: 509 sz=[f.GetByte(x) for x in range(6,10)] 510 if len([zz for zz in sz if zz<0 or zz>=0x80]): 511 return None 512 sz=(sz[0]<<21)+(sz[1]<<14)+(sz[2]<<7)+sz[3] 513 offset=10+sz 514 idv3present=True 515 header=f.GetMSBUint32(offset) 516 elif header is None: 517 return None 518 else: 519 offset=0 520 521 # locate the 1st sync frame 522 while True: 523 v=f.GetMSBUint16(offset) 524 if v is None: return None 525 if v&0xffe0==0xffe0: 526 break 527 offset=f.data.find("\xff", offset+1) 528 if offset<0: 529 # not an mp3 file or sync is way later in 530 return None 531 532 frames=[] 533 while offset<f.size: 534 if offset==f.size-128 and f.GetBytes(offset,3)=="TAG": 535 offset+=128 536 id3v1present=True 537 continue 538 frame=MP3Frame(f, offset) 539 if not frame.OK or frame.nextoffset>f.size: break 540 offset=frame.nextoffset 541 frames.append(frame) 542 543 if len(frames)==0: return 544 545 if offset!=f.size: 546 print "MP3 offset is",offset,"size is",f.size 547 548 # copy some information from the first frame 549 f0=frames[0] 550 d={'format': 'MP3', 551 'id3v1present': id3v1present, # badly named ... 552 'idv3present': idv3present, 553 'unrecognisedframes': offset!=f.size, 554 'version': f0.version, 555 'layer': f0.layer, 556 'bitrate': f0.bitrate, 557 'samplerate': f0.samplerate, 558 'channels': f0.channels, 559 'copyright': f0.copyright, 560 'original': f0.original, 561 'mimetypes': ['audio/x-mp3', 'audio/mpeg', 'audio/x-mpeg']} 562 563 duration=f0.duration 564 vbrmin=vbrmax=f0.bitrate 565 566 for frame in frames[1:]: 567 duration+=frame.duration 568 if frame.bitrate!=f0.bitrate: 569 d['bitrate']=0 570 if frame.samplerate!=f0.samplerate: 571 d['samplerate']=0 572 vbrmin=min(frame.bitrate,vbrmin) 573 vbrmax=max(frame.bitrate,vbrmax) 574 if frame.channels!=f0.channels: 575 d['channels']=0 576 577 d['duration']=duration 578 d['vbrmin']=vbrmin 579 d['vbrmax']=vbrmax 580 d['_longdescription']=fmt_MP3 581 d['_shortdescription']=fmts_MP3 582 583 if returnframes: 584 d['frames']=frames 585 586 return AudioFileInfo(f, **d) 587 except: 588 return None
589
590 -def fmt_MP3(afi):
591 res=[] 592 res.append("MP3 (Mpeg Version %d Layer %d)" % (afi.version, afi.layer)) 593 res.append("%s %.1f Khz %0.1f seconds" % (["Variable!!", "Mono", "Stereo"][afi.channels], afi.samplerate/1000.0, afi.duration,)) 594 if afi.bitrate: 595 res.append(`afi.bitrate`+" kbps") 596 else: 597 res.append("VBR (min %d kbps, max %d kbps)" % (afi.vbrmin, afi.vbrmax)) 598 if afi.unrecognisedframes: 599 res.append("There are unrecognised frames in this file") 600 if afi.idv3present: 601 res.append("IDV3 tag present at begining of file") 602 if afi.id3v1present: 603 res.append("IDV3.1 tag present at end of file") 604 if afi.copyright: 605 res.append("Marked as copyrighted") 606 if afi.original: 607 res.append("Marked as the original") 608 609 return "\n".join(res)
610
611 -def fmts_MP3(afi):
612 return "MP3 %s %dKhz %d sec" % (["Variable!!", "Mono", "Stereo"][afi.channels], afi.samplerate/1000.0, afi.duration,)
613 614
615 -class MP3Frame:
616 617 bitrates={ 618 # (version, layer): bitrate mapping 619 (1, 1): [None, 32, 64, 96, 128, 160, 192, 224, 256, 288, 320, 352, 384, 416, 448, None], 620 (1, 2): [None, 32, 48, 56, 64, 80, 96, 112, 128, 160, 192, 224, 256, 320, 384, None], 621 (1, 3): [None, 32, 40, 48, 56, 64, 80, 96, 112, 128, 160, 192, 224, 256, 320, None], 622 (2, 1): [None, 32, 48, 56, 64, 80, 96, 112, 128, 144, 160, 176, 192, 224, 256, None], 623 (2, 2): [None, 8, 16, 24, 32, 40, 48, 56, 64, 80, 96, 112, 128, 144, 160, None], 624 (2, 3): [None, 8, 16, 24, 32, 40, 48, 56, 64, 80, 96, 112, 128, 144, 160, None], 625 } 626 627 samplerates={ 628 1: [44100, 48000, 32000, None], 629 2: [22050, 24000, 16000, None] 630 } 631
632 - def __init__(self, f, offset):
633 self.OK=False 634 header=f.GetMSBUint32(offset) 635 if header is None: return 636 # first 11 buts must all be set 637 if _getbits(31,11, header)!=2047: 638 return 639 self.header=header 640 # Version 641 version=_getbits(20,2,header) 642 if version not in (2,3): # we don't support 'reserved' or version 2.5 643 return 644 if version==3: # yes, version 1 is encoded as 3 645 version=1 646 self.version=version 647 # Layer 648 layer=_getbits(18,2,header) 649 if layer==0: return # reserved which we don't support 650 if layer==1: 651 self.layer=3 652 elif layer==2: 653 self.layer=2 654 elif layer==3: 655 self.layer=1 656 self.crc=_getbits(16,1,header) 657 self.bitrate=self.bitrates[(self.version, self.layer)][_getbits(15,4,header)] 658 self.samplerate=self.samplerates[self.version][_getbits(11,2,header)] 659 self.padding=_getbits(9,1,header) 660 if self.layer==1: 661 self.framelength=(12000*self.bitrate/self.samplerate+self.padding)*4 662 else: 663 if self.version==1: 664 self.framelength=144000*self.bitrate/self.samplerate+self.padding 665 else: 666 self.framelength=72000*self.bitrate/self.samplerate+self.padding 667 self.duration=self.framelength*8*1.0/(self.bitrate*1000) 668 self.private=_getbits(8,1,header) 669 self.channelmode=_getbits(7,2,header) 670 if self.channelmode in (0,1,2): 671 self.channels=2 672 else: 673 self.channels=1 674 675 self.modeextenstion=_getbits(5,2,header) 676 self.copyright=_getbits(3,1,header) 677 self.original=_getbits(2,1, header) 678 self.emphasis=_getbits(1,2, header) 679 680 self.offset=offset 681 self.nextoffset=offset+self.framelength 682 self.OK=True
683
684 -def idaudio_QCP(f):
685 "Identify a Qualcomm Purevoice file" 686 # http://www.faqs.org/rfcs/rfc3625.html 687 # 688 # Sigh, another format where you have no hope of being able to work out the length 689 if f.GetBytes(0,4)=="RIFF" and f.GetBytes(8,4)=="QLCM": 690 d={'format': "QCP"} 691 692 # fmt section 693 if f.GetBytes(12,4)!="fmt ": 694 return None 695 # chunksize is at 16, len 4 696 d['qcpmajor']=f.GetByte(20) 697 d['qcpminor']=f.GetByte(21) 698 # guid is at 22 699 d['codecguid']=(f.GetLSBUint32(22), f.GetLSBUint16(26), f.GetLSBUint16(28), f.GetMSBUint16(30), (long(f.GetMSBUint16(32))<<32)+f.GetMSBUint32(34)) 700 d['codecversion']=f.GetLSBUint16(38) 701 name=f.GetBytes(40,80) 702 zero=name.find('\x00') 703 if zero>=0: 704 name=name[:zero] 705 d['codecname']=name 706 d['averagebps']=f.GetLSBUint16(120) 707 # packetsize is at 122, len 2 708 # block size is at 124, len 2 709 d['samplingrate']=f.GetLSBUint16(126) 710 d['samplesize']=f.GetLSBUint16(128) 711 d['_longdescription']=fmt_QCP 712 713 if d['codecguid']==( 0x5e7f6d41, 0xb115, 0x11d0, 0xba91, 0x00805fb4b97eL ) or \ 714 d['codecguid']==( 0x5e7f6d42, 0xb115, 0x11d0, 0xba91, 0x00805fb4b97eL ): 715 d['mimetypes']=['audio/qcelp'] # in theory audio/vnd.qcelp could also be used but is deprecated 716 elif d['codecguid']==( 0xe689d48dL, 0x9076, 0x46b5, 0x91ef, 0x736a5100ceb4L ): 717 d['mimetypes']=['audio/evrc-qcp'] 718 elif d['codecguid']==( 0x8d7c2b75L, 0xa797, 0xed49, 0x985e, 0xd53c8cc75f84L ): 719 d['mimetypes']=['audio/smv-qcp'] 720 721 for i in d.itervalues(): 722 if i is None: return None 723 afi=AudioFileInfo(f,**d) 724 return afi 725 return None
726
727 -def fmt_QCP(afi):
728 res=["QCP %s" % (afi.codecname,)] 729 res.append("%d bps %d Hz %d bits/sample" % (afi.averagebps, afi.samplingrate, afi.samplesize)) 730 codecguid=afi.codecguid 731 if codecguid==( 0x5e7f6d41, 0xb115, 0x11d0, 0xba91, 0x00805fb4b97eL ): 732 res.append("QCELP-13K V"+`afi.codecversion` + " (guid 1)") 733 elif codecguid==( 0x5e7f6d42, 0xb115, 0x11d0, 0xba91, 0x00805fb4b97eL ): 734 res.append("QCELP-13K V"+`afi.codecversion` + " (guid 2)") 735 elif codecguid==( 0xe689d48dL, 0x9076, 0x46b5, 0x91ef, 0x736a5100ceb4L ): 736 res.append("EVRC V"+`afi.codecversion`) 737 elif codecguid==( 0x8d7c2b75L, 0xa797, 0xed49, 0x985e, 0xd53c8cc75f84L ): 738 res.append("SMV V"+`afi.codecversion`) 739 else: 740 res.append("Codec Guid {%08X-%04X-%04X-%04X-%012X} V%d" % (afi.codecguid+(afi.codecversion,))) 741 res.append("QCP File Version %d.%d" % (afi.qcpmajor, afi.qcpminor)) 742 743 return "\n".join(res)
744
745 -def idaudio_PMD(f):
746 "Identify a PMD/CMX file" 747 # There are no specs for this file format. From 10 minutes of eyeballing, it seems like below. 748 # Each section is a null terminated string followed by a byte saying how long the data is. 749 # The length is probably some sort of variable length encoding such as the high bit indicating 750 # the last byte and using 7 bits. 751 # 752 # offset contents -- comment 753 # 0 cmid -- file type id 754 # 4 \0\0 -- no idea 755 # 6 7*? -- file lengths and pointers 756 # 13 vers\0 -- version section 757 # 18 \x04 -- length of version section 758 # 19 "string" -- a version number that has some correlation with the pmd version number 759 # 760 # Various other sections that cover the contents that don't matter for identification 761 try: 762 if f.GetBytes(0, 4)!='cmid': 763 return None 764 d={ 'format': 'PMD' } 765 hdr_len=f.GetMSBUint16(8) 766 i=f.GetByte(10) 767 d['content']=['Unknown', 'Ringer', 'Pictures&Audio'][i] 768 d['numtracks']=f.GetByte(12) 769 ofs=13 770 ofs_end=hdr_len+10 771 while ofs<ofs_end: 772 s=f.GetBytes(ofs, 4) 773 i=f.GetMSBUint16(ofs+4) 774 ofs+=6 775 if i==0: 776 continue 777 if s=='vers': 778 d['version']=f.GetBytes(ofs, i) 779 elif s=='titl': 780 d['title']=f.GetBytes(ofs, i) 781 elif s=='cnts': 782 d['media']=f.GetBytes(ofs, i) 783 ofs+=i 784 d['_longdescription']=fmt_PMD 785 d['_shortdescription']=fmts_PMD 786 return AudioFileInfo(f, **d) 787 except: 788 return None
789
790 -def fmts_PMD(afi):
791 return 'PMD/CMF %s'% afi.content
792
793 -def fmt_PMD(afi):
794 res=['PMD/CMF'] 795 if hasattr(afi, 'version'): 796 res.append('Version: '+afi.version) 797 res.append('Content: '+afi.content) 798 res.append('%d Tracks'%afi.numtracks) 799 if hasattr(afi, 'title'): 800 res.append('Title: '+afi.title) 801 if hasattr(afi, 'media'): 802 res.append('Media: '+afi.media) 803 return '\n'.join(res)
804
805 -def idaudio_PCM(f):
806 "Identify a PCM/WAV file" 807 try: 808 if f.GetBytes(0, 4)!='RIFF' or f.GetBytes(8, 4)!='WAVE' or \ 809 f.GetBytes(12, 4)!='fmt ' or f.GetLSBUint16(20)!=1: 810 return None 811 d={ 'format': 'PCM', 812 'mimetypes': ['audio/wav', 'audio/x-wav', 813 'audio/wave', 'audio/x-pn-wav'] } 814 d['numchannels']=f.GetLSBUint16(22) 815 d['samplerate']=f.GetLSBUint32(24) 816 d['byterate']=f.GetLSBUint32(28) 817 d['blockalign']=f.GetLSBUint16(32) 818 d['bitspersample']=f.GetLSBUint16(34) 819 # compute the approximate duration 820 subchunk1size=f.GetLSBUint32(16) 821 datasize=f.GetLSBUint32(20+subchunk1size+4) 822 d['duration']=float(datasize)/(d['blockalign']*d['samplerate']) 823 d['_longdescription']=fmt_PCM 824 d['_shortdescription']=fmts_PCM 825 return AudioFileInfo(f, **d) 826 except: 827 return None
828
829 -def fmts_PCM(afi):
830 return afi.format
831
832 -def fmt_PCM(afi):
833 res=['PCM/WAV'] 834 res.append('%d KHz %d bits %s'%\ 835 (afi.samplerate/1000, afi.bitspersample,\ 836 ['None', 'Mono', 'Stereo'][afi.numchannels])) 837 return '\n'.join(res)
838
839 -def getpcmfileinfo(filename):
840 f=SafeFileWrapper(filename) 841 return idaudio_PCM(f)
842 843 # WMA file support
844 -def idaudio_WMA(f):
845 "Identify a WMA file" 846 try: 847 _wma=wma_file.WMA_File(f) 848 if not _wma.valid: 849 return None 850 d={ 'format': 'WMA', 851 'mimetypes': ['audio/x-ms-wma'], 852 'duration': _wma.play_duration, 853 'title': _wma.title, 854 'artist': _wma.author, 855 'album': _wma.album, 856 'genre': _wma.genre, 857 '_longdescription': fmt_WMA, 858 } 859 return AudioFileInfo(f, **d) 860 except: 861 if __debug__: 862 raise
863
864 -def fmt_WMA(afi):
865 res=['WMA', 'Duration: %.2f'%afi.duration ] 866 if afi.title: 867 res.append('Title: %s'%afi.title) 868 if afi.artist: 869 res.append('Artist: %s'%afi.artist) 870 if afi.album: 871 res.append('Album: %s'%afi.album) 872 if afi.genre: 873 res.append('Genre: %s'%afi.genre) 874 return '\n'.join(res)
875 876 # MPEG4 file support
877 -def idaudio_MP4(f):
878 "Identify a MP4 file" 879 try: 880 _mp4=mp4_file.MP4_File(f) 881 if not _mp4.valid: 882 return None 883 d={ 'format': 'MP4', 884 'mimetypes': ['audio/mpeg4'], 885 'duration': _mp4.duration, 886 'title': _mp4.title, 887 'artist': _mp4.artist, 888 'album': _mp4.album, 889 'bitrate': _mp4.bitrate, 890 'samplerate': _mp4.samplerate, 891 '_longdescription': fmt_MP4, 892 } 893 return AudioFileInfo(f, **d) 894 except: 895 if __debug__: 896 raise
897 898
899 -def fmt_MP4(afi):
900 res=[] 901 res.append("MPEG4") 902 res.append("%.1f Khz %0.1f seconds" % (afi.samplerate/1000.0, afi.duration,)) 903 if afi.bitrate: 904 res.append(`afi.bitrate`+" kbps") 905 if afi.title: 906 res.append('Title: %s'%afi.title) 907 if afi.artist: 908 res.append('Artist: %s'%afi.artist) 909 if afi.album: 910 res.append('Album: %s'%afi.album) 911 912 return "\n".join(res)
913 914 audioids=[globals()[f] for f in dir() if f.startswith("idaudio_")]
915 -def identify_audiofile(filename):
916 v=thefileinfocache.get(filename) 917 if v is not None: return v 918 fo=SafeFileWrapper(filename) 919 for f in audioids: 920 obj=f(fo) 921 if obj is not None: 922 return thefileinfocache.set(filename, obj) 923 return thefileinfocache.set(filename, AudioFileInfo(fo))
924
925 -def identify_audiostring(string):
926 # identify an audio format based on the audio data string 927 fo=SafeStringWrapper(string) 928 for f in audioids: 929 obj=f(fo) 930 if obj is not None: 931 return obj
932 ### 933 ### caches for audio/image id 934 ### 935 936 thefileinfocache=common.FileCache(lowwater=100,hiwater=140) 937