1 """Stuff to parse AIFF-C and AIFF files. 2 3 Unless explicitly stated otherwise, the description below is true 4 both for AIFF-C files and AIFF files. 5 6 An AIFF-C file has the following structure. 7 8 +-----------------+ 9 | FORM | 10 +-----------------+ 11 | <size> | 12 +----+------------+ 13 | | AIFC | 14 | +------------+ 15 | | <chunks> | 16 | | . | 17 | | . | 18 | | . | 19 +----+------------+ 20 21 An AIFF file has the string "AIFF" instead of "AIFC". 22 23 A chunk consists of an identifier (4 bytes) followed by a size (4 bytes, 24 big endian order), followed by the data. The size field does not include 25 the size of the 8 byte header. 26 27 The following chunk types are recognized. 28 29 FVER 30 <version number of AIFF-C defining document> (AIFF-C only). 31 MARK 32 <# of markers> (2 bytes) 33 list of markers: 34 <marker ID> (2 bytes, must be > 0) 35 <position> (4 bytes) 36 <marker name> ("pstring") 37 COMM 38 <# of channels> (2 bytes) 39 <# of sound frames> (4 bytes) 40 <size of the samples> (2 bytes) 41 <sampling frequency> (10 bytes, IEEE 80-bit extended 42 floating point) 43 in AIFF-C files only: 44 <compression type> (4 bytes) 45 <human-readable version of compression type> ("pstring") 46 SSND 47 <offset> (4 bytes, not used by this program) 48 <blocksize> (4 bytes, not used by this program) 49 <sound data> 50 51 A pstring consists of 1 byte length, a string of characters, and 0 or 1 52 byte pad to make the total length even. 53 54 Usage. 55 56 Reading AIFF files: 57 f = aifc.open(file, 'r') 58 where file is either the name of a file or an open file pointer. 59 The open file pointer must have methods read(), seek(), and close(). 60 In some types of audio files, if the setpos() method is not used, 61 the seek() method is not necessary. 62 63 This returns an instance of a class with the following public methods: 64 getnchannels() -- returns number of audio channels (1 for 65 mono, 2 for stereo) 66 getsampwidth() -- returns sample width in bytes 67 getframerate() -- returns sampling frequency 68 getnframes() -- returns number of audio frames 69 getcomptype() -- returns compression type ('NONE' for AIFF files) 70 getcompname() -- returns human-readable version of 71 compression type ('not compressed' for AIFF files) 72 getparams() -- returns a tuple consisting of all of the 73 above in the above order 74 getmarkers() -- get the list of marks in the audio file or None 75 if there are no marks 76 getmark(id) -- get mark with the specified id (raises an error 77 if the mark does not exist) 78 readframes(n) -- returns at most n frames of audio 79 rewind() -- rewind to the beginning of the audio stream 80 setpos(pos) -- seek to the specified position 81 tell() -- return the current position 82 close() -- close the instance (make it unusable) 83 The position returned by tell(), the position given to setpos() and 84 the position of marks are all compatible and have nothing to do with 85 the actual position in the file. 86 The close() method is called automatically when the class instance 87 is destroyed. 88 89 Writing AIFF files: 90 f = aifc.open(file, 'w') 91 where file is either the name of a file or an open file pointer. 92 The open file pointer must have methods write(), tell(), seek(), and 93 close(). 94 95 This returns an instance of a class with the following public methods: 96 aiff() -- create an AIFF file (AIFF-C default) 97 aifc() -- create an AIFF-C file 98 setnchannels(n) -- set the number of channels 99 setsampwidth(n) -- set the sample width 100 setframerate(n) -- set the frame rate 101 setnframes(n) -- set the number of frames 102 setcomptype(type, name) 103 -- set the compression type and the 104 human-readable compression type 105 setparams(tuple) 106 -- set all parameters at once 107 setmark(id, pos, name) 108 -- add specified mark to the list of marks 109 tell() -- return current position in output file (useful 110 in combination with setmark()) 111 writeframesraw(data) 112 -- write audio frames without pathing up the 113 file header 114 writeframes(data) 115 -- write audio frames and patch up the file header 116 close() -- patch up the file header and close the 117 output file 118 You should set the parameters before the first writeframesraw or 119 writeframes. The total number of frames does not need to be set, 120 but when it is set to the correct value, the header does not have to 121 be patched up. 122 It is best to first set all parameters, perhaps possibly the 123 compression type, and then write audio frames using writeframesraw. 124 When all frames have been written, either call writeframes('') or 125 close() to patch up the sizes in the header. 126 Marks can be added anytime. If there are any marks, ypu must call 127 close() after all frames have been written. 128 The close() method is called automatically when the class instance 129 is destroyed. 130 131 When a file is opened with the extension '.aiff', an AIFF file is 132 written, otherwise an AIFF-C file is written. This default can be 133 changed by calling aiff() or aifc() before the first writeframes or 134 writeframesraw. 135 """ 136 137 import struct 138 import __builtin__ 139 140 __all__ = ["Error","open","openfp"] 141 142 class Error(Exception): 143 pass 144 145 _AIFC_version = 0xA2805140L # Version 1 of AIFF-C 146 147 def _read_long(file): 148 try: 149 return struct.unpack('>l', file.read(4))[0] 150 except struct.error: 151 raise EOFError 152 153 def _read_ulong(file): 154 try: 155 return struct.unpack('>L', file.read(4))[0] 156 except struct.error: 157 raise EOFError 158 159 def _read_short(file): 160 try: 161 return struct.unpack('>h', file.read(2))[0] 162 except struct.error: 163 raise EOFError 164 165 def _read_ushort(file): 166 try: 167 return struct.unpack('>H', file.read(2))[0] 168 except struct.error: 169 raise EOFError 170 171 def _read_string(file): 172 length = ord(file.read(1)) 173 if length == 0: 174 data = '' 175 else: 176 data = file.read(length) 177 if length & 1 == 0: 178 dummy = file.read(1) 179 return data 180 181 _HUGE_VAL = 1.79769313486231e+308 # See <limits.h> 182 183 def _read_float(f): # 10 bytes 184 expon = _read_short(f) # 2 bytes 185 sign = 1 186 if expon < 0: 187 sign = -1 188 expon = expon + 0x8000 189 himant = _read_ulong(f) # 4 bytes 190 lomant = _read_ulong(f) # 4 bytes 191 if expon == himant == lomant == 0: 192 f = 0.0 193 elif expon == 0x7FFF: 194 f = _HUGE_VAL 195 else: 196 expon = expon - 16383 197 f = (himant * 0x100000000L + lomant) * pow(2.0, expon - 63) 198 return sign * f 199 200 def _write_short(f, x): 201 f.write(struct.pack('>h', x)) 202 203 def _write_ushort(f, x): 204 f.write(struct.pack('>H', x)) 205 206 def _write_long(f, x): 207 f.write(struct.pack('>l', x)) 208 209 def _write_ulong(f, x): 210 f.write(struct.pack('>L', x)) 211 212 def _write_string(f, s): 213 if len(s) > 255: 214 raise ValueError("string exceeds maximum pstring length") 215 f.write(struct.pack('B', len(s))) 216 f.write(s) 217 if len(s) & 1 == 0: 218 f.write(chr(0)) 219 220 def _write_float(f, x): 221 import math 222 if x < 0: 223 sign = 0x8000 224 x = x * -1 225 else: 226 sign = 0 227 if x == 0: 228 expon = 0 229 himant = 0 230 lomant = 0 231 else: 232 fmant, expon = math.frexp(x) 233 if expon > 16384 or fmant >= 1 or fmant != fmant: # Infinity or NaN 234 expon = sign|0x7FFF 235 himant = 0 236 lomant = 0 237 else: # Finite 238 expon = expon + 16382 239 if expon < 0: # denormalized 240 fmant = math.ldexp(fmant, expon) 241 expon = 0 242 expon = expon | sign 243 fmant = math.ldexp(fmant, 32) 244 fsmant = math.floor(fmant) 245 himant = long(fsmant) 246 fmant = math.ldexp(fmant - fsmant, 32) 247 fsmant = math.floor(fmant) 248 lomant = long(fsmant) 249 _write_ushort(f, expon) 250 _write_ulong(f, himant) 251 _write_ulong(f, lomant) 252 253 from chunk import Chunk 254 255 class Aifc_read: 256 # Variables used in this class: 257 # 258 # These variables are available to the user though appropriate 259 # methods of this class: 260 # _file -- the open file with methods read(), close(), and seek() 261 # set through the __init__() method 262 # _nchannels -- the number of audio channels 263 # available through the getnchannels() method 264 # _nframes -- the number of audio frames 265 # available through the getnframes() method 266 # _sampwidth -- the number of bytes per audio sample 267 # available through the getsampwidth() method 268 # _framerate -- the sampling frequency 269 # available through the getframerate() method 270 # _comptype -- the AIFF-C compression type ('NONE' if AIFF) 271 # available through the getcomptype() method 272 # _compname -- the human-readable AIFF-C compression type 273 # available through the getcomptype() method 274 # _markers -- the marks in the audio file 275 # available through the getmarkers() and getmark() 276 # methods 277 # _soundpos -- the position in the audio stream 278 # available through the tell() method, set through the 279 # setpos() method 280 # 281 # These variables are used internally only: 282 # _version -- the AIFF-C version number 283 # _decomp -- the decompressor from builtin module cl 284 # _comm_chunk_read -- 1 iff the COMM chunk has been read 285 # _aifc -- 1 iff reading an AIFF-C file 286 # _ssnd_seek_needed -- 1 iff positioned correctly in audio 287 # file for readframes() 288 # _ssnd_chunk -- instantiation of a chunk class for the SSND chunk 289 # _framesize -- size of one frame in the file 290 291 def initfp(self, file): 292 self._version = 0 293 self._decomp = None 294 self._convert = None 295 self._markers = [] 296 self._soundpos = 0 297 self._file = file 298 chunk = Chunk(file) 299 if chunk.getname() != 'FORM': 300 raise Error, 'file does not start with FORM id' 301 formdata = chunk.read(4) 302 if formdata == 'AIFF': 303 self._aifc = 0 304 elif formdata == 'AIFC': 305 self._aifc = 1 306 else: 307 raise Error, 'not an AIFF or AIFF-C file' 308 self._comm_chunk_read = 0 309 while 1: 310 self._ssnd_seek_needed = 1 311 try: 312 chunk = Chunk(self._file) 313 except EOFError: 314 break 315 chunkname = chunk.getname() 316 if chunkname == 'COMM': 317 self._read_comm_chunk(chunk) 318 self._comm_chunk_read = 1 319 elif chunkname == 'SSND': 320 self._ssnd_chunk = chunk 321 dummy = chunk.read(8) 322 self._ssnd_seek_needed = 0 323 elif chunkname == 'FVER': 324 self._version = _read_ulong(chunk) 325 elif chunkname == 'MARK': 326 self._readmark(chunk) 327 chunk.skip() 328 if not self._comm_chunk_read or not self._ssnd_chunk: 329 raise Error, 'COMM chunk and/or SSND chunk missing' 330 if self._aifc and self._decomp: 331 import cl 332 params = [cl.ORIGINAL_FORMAT, 0, 333 cl.BITS_PER_COMPONENT, self._sampwidth * 8, 334 cl.FRAME_RATE, self._framerate] 335 if self._nchannels == 1: 336 params[1] = cl.MONO 337 elif self._nchannels == 2: 338 params[1] = cl.STEREO_INTERLEAVED 339 else: 340 raise Error, 'cannot compress more than 2 channels' 341 self._decomp.SetParams(params) 342 343 def __init__(self, f): 344 if type(f) == type(''): 345 f = __builtin__.open(f, 'rb') 346 # else, assume it is an open file object already 347 self.initfp(f) 348 349 # 350 # User visible methods. 351 # 352 def getfp(self): 353 return self._file 354 355 def rewind(self): 356 self._ssnd_seek_needed = 1 357 self._soundpos = 0 358 359 def close(self): 360 if self._decomp: 361 self._decomp.CloseDecompressor() 362 self._decomp = None 363 self._file.close() 364 365 def tell(self): 366 return self._soundpos 367 368 def getnchannels(self): 369 return self._nchannels 370 371 def getnframes(self): 372 return self._nframes 373 374 def getsampwidth(self): 375 return self._sampwidth 376 377 def getframerate(self): 378 return self._framerate 379 380 def getcomptype(self): 381 return self._comptype 382 383 def getcompname(self): 384 return self._compname 385 386 ## def getversion(self): 387 ## return self._version 388 389 def getparams(self): 390 return self.getnchannels(), self.getsampwidth(), \ 391 self.getframerate(), self.getnframes(), \ 392 self.getcomptype(), self.getcompname() 393 394 def getmarkers(self): 395 if len(self._markers) == 0: 396 return None 397 return self._markers 398 399 def getmark(self, id): 400 for marker in self._markers: 401 if id == marker[0]: 402 return marker 403 raise Error, 'marker %r does not exist' % (id,) 404 405 def setpos(self, pos): 406 if pos < 0 or pos > self._nframes: 407 raise Error, 'position not in range' 408 self._soundpos = pos 409 self._ssnd_seek_needed = 1 410 411 def readframes(self, nframes): 412 if self._ssnd_seek_needed: 413 self._ssnd_chunk.seek(0) 414 dummy = self._ssnd_chunk.read(8) 415 pos = self._soundpos * self._framesize 416 if pos: 417 self._ssnd_chunk.seek(pos + 8) 418 self._ssnd_seek_needed = 0 419 if nframes == 0: 420 return '' 421 data = self._ssnd_chunk.read(nframes * self._framesize) 422 if self._convert and data: 423 data = self._convert(data) 424 self._soundpos = self._soundpos + len(data) // (self._nchannels * self._sampwidth) 425 return data 426 427 # 428 # Internal methods. 429 # 430 431 def _decomp_data(self, data): 432 import cl 433 dummy = self._decomp.SetParam(cl.FRAME_BUFFER_SIZE, 434 len(data) * 2) 435 return self._decomp.Decompress(len(data) // self._nchannels, 436 data) 437 438 def _ulaw2lin(self, data): 439 import audioop 440 return audioop.ulaw2lin(data, 2) 441 442 def _adpcm2lin(self, data): 443 import audioop 444 if not hasattr(self, '_adpcmstate'): 445 # first time 446 self._adpcmstate = None 447 data, self._adpcmstate = audioop.adpcm2lin(data, 2, 448 self._adpcmstate) 449 return data 450 451 def _read_comm_chunk(self, chunk): 452 self._nchannels = _read_short(chunk) 453 self._nframes = _read_long(chunk) 454 self._sampwidth = (_read_short(chunk) + 7) // 8 455 self._framerate = int(_read_float(chunk)) 456 self._framesize = self._nchannels * self._sampwidth 457 if self._aifc: 458 #DEBUG: SGI's soundeditor produces a bad size :-( 459 kludge = 0 460 if chunk.chunksize == 18: 461 kludge = 1 462 print 'Warning: bad COMM chunk size' 463 chunk.chunksize = 23 464 #DEBUG end 465 self._comptype = chunk.read(4) 466 #DEBUG start 467 if kludge: 468 length = ord(chunk.file.read(1)) 469 if length & 1 == 0: 470 length = length + 1 471 chunk.chunksize = chunk.chunksize + length 472 chunk.file.seek(-1, 1) 473 #DEBUG end 474 self._compname = _read_string(chunk) 475 if self._comptype != 'NONE': 476 if self._comptype == 'G722': 477 try: 478 import audioop 479 except ImportError: 480 pass 481 else: 482 self._convert = self._adpcm2lin 483 self._framesize = self._framesize // 4 484 return 485 # for ULAW and ALAW try Compression Library 486 try: 487 import cl 488 except ImportError: 489 if self._comptype == 'ULAW': 490 try: 491 import audioop 492 self._convert = self._ulaw2lin 493 self._framesize = self._framesize // 2 494 return 495 except ImportError: 496 pass 497 raise Error, 'cannot read compressed AIFF-C files' 498 if self._comptype == 'ULAW': 499 scheme = cl.G711_ULAW 500 self._framesize = self._framesize // 2 501 elif self._comptype == 'ALAW': 502 scheme = cl.G711_ALAW 503 self._framesize = self._framesize // 2 504 else: 505 raise Error, 'unsupported compression type' 506 self._decomp = cl.OpenDecompressor(scheme) 507 self._convert = self._decomp_data 508 else: 509 self._comptype = 'NONE' 510 self._compname = 'not compressed' 511 512 def _readmark(self, chunk): 513 nmarkers = _read_short(chunk) 514 # Some files appear to contain invalid counts. 515 # Cope with this by testing for EOF. 516 try: 517 for i in range(nmarkers): 518 id = _read_short(chunk) 519 pos = _read_long(chunk) 520 name = _read_string(chunk) 521 if pos or name: 522 # some files appear to have 523 # dummy markers consisting of 524 # a position 0 and name '' 525 self._markers.append((id, pos, name)) 526 except EOFError: 527 print 'Warning: MARK chunk contains only', 528 print len(self._markers), 529 if len(self._markers) == 1: print 'marker', 530 else: print 'markers', 531 print 'instead of', nmarkers 532 533 class Aifc_write: 534 # Variables used in this class: 535 # 536 # These variables are user settable through appropriate methods 537 # of this class: 538 # _file -- the open file with methods write(), close(), tell(), seek() 539 # set through the __init__() method 540 # _comptype -- the AIFF-C compression type ('NONE' in AIFF) 541 # set through the setcomptype() or setparams() method 542 # _compname -- the human-readable AIFF-C compression type 543 # set through the setcomptype() or setparams() method 544 # _nchannels -- the number of audio channels 545 # set through the setnchannels() or setparams() method 546 # _sampwidth -- the number of bytes per audio sample 547 # set through the setsampwidth() or setparams() method 548 # _framerate -- the sampling frequency 549 # set through the setframerate() or setparams() method 550 # _nframes -- the number of audio frames written to the header 551 # set through the setnframes() or setparams() method 552 # _aifc -- whether we're writing an AIFF-C file or an AIFF file 553 # set through the aifc() method, reset through the 554 # aiff() method 555 # 556 # These variables are used internally only: 557 # _version -- the AIFF-C version number 558 # _comp -- the compressor from builtin module cl 559 # _nframeswritten -- the number of audio frames actually written 560 # _datalength -- the size of the audio samples written to the header 561 # _datawritten -- the size of the audio samples actually written 562 563 def __init__(self, f): 564 if type(f) == type(''): 565 filename = f 566 f = __builtin__.open(f, 'wb') 567 else: 568 # else, assume it is an open file object already 569 filename = '???' 570 self.initfp(f) 571 if filename[-5:] == '.aiff': 572 self._aifc = 0 573 else: 574 self._aifc = 1 575 576 def initfp(self, file): 577 self._file = file 578 self._version = _AIFC_version 579 self._comptype = 'NONE' 580 self._compname = 'not compressed' 581 self._comp = None 582 self._convert = None 583 self._nchannels = 0 584 self._sampwidth = 0 585 self._framerate = 0 586 self._nframes = 0 587 self._nframeswritten = 0 588 self._datawritten = 0 589 self._datalength = 0 590 self._markers = [] 591 self._marklength = 0 592 self._aifc = 1 # AIFF-C is default 593 594 def __del__(self): 595 if self._file: 596 self.close() 597 598 # 599 # User visible methods. 600 # 601 def aiff(self): 602 if self._nframeswritten: 603 raise Error, 'cannot change parameters after starting to write' 604 self._aifc = 0 605 606 def aifc(self): 607 if self._nframeswritten: 608 raise Error, 'cannot change parameters after starting to write' 609 self._aifc = 1 610 611 def setnchannels(self, nchannels): 612 if self._nframeswritten: 613 raise Error, 'cannot change parameters after starting to write' 614 if nchannels < 1: 615 raise Error, 'bad # of channels' 616 self._nchannels = nchannels 617 618 def getnchannels(self): 619 if not self._nchannels: 620 raise Error, 'number of channels not set' 621 return self._nchannels 622 623 def setsampwidth(self, sampwidth): 624 if self._nframeswritten: 625 raise Error, 'cannot change parameters after starting to write' 626 if sampwidth < 1 or sampwidth > 4: 627 raise Error, 'bad sample width' 628 self._sampwidth = sampwidth 629 630 def getsampwidth(self): 631 if not self._sampwidth: 632 raise Error, 'sample width not set' 633 return self._sampwidth 634 635 def setframerate(self, framerate): 636 if self._nframeswritten: 637 raise Error, 'cannot change parameters after starting to write' 638 if framerate <= 0: 639 raise Error, 'bad frame rate' 640 self._framerate = framerate 641 642 def getframerate(self): 643 if not self._framerate: 644 raise Error, 'frame rate not set' 645 return self._framerate 646 647 def setnframes(self, nframes): 648 if self._nframeswritten: 649 raise Error, 'cannot change parameters after starting to write' 650 self._nframes = nframes 651 652 def getnframes(self): 653 return self._nframeswritten 654 655 def setcomptype(self, comptype, compname): 656 if self._nframeswritten: 657 raise Error, 'cannot change parameters after starting to write' 658 if comptype not in ('NONE', 'ULAW', 'ALAW', 'G722'): 659 raise Error, 'unsupported compression type' 660 self._comptype = comptype 661 self._compname = compname 662 663 def getcomptype(self): 664 return self._comptype 665 666 def getcompname(self): 667 return self._compname 668 669 ## def setversion(self, version): 670 ## if self._nframeswritten: 671 ## raise Error, 'cannot change parameters after starting to write' 672 ## self._version = version 673 674 def setparams(self, info): 675 nchannels, sampwidth, framerate, nframes, comptype, compname = info 676 if self._nframeswritten: 677 raise Error, 'cannot change parameters after starting to write' 678 if comptype not in ('NONE', 'ULAW', 'ALAW', 'G722'): 679 raise Error, 'unsupported compression type' 680 self.setnchannels(nchannels) 681 self.setsampwidth(sampwidth) 682 self.setframerate(framerate) 683 self.setnframes(nframes) 684 self.setcomptype(comptype, compname) 685 686 def getparams(self): 687 if not self._nchannels or not self._sampwidth or not self._framerate: 688 raise Error, 'not all parameters set' 689 return self._nchannels, self._sampwidth, self._framerate, \ 690 self._nframes, self._comptype, self._compname 691 692 def setmark(self, id, pos, name): 693 if id <= 0: 694 raise Error, 'marker ID must be > 0' 695 if pos < 0: 696 raise Error, 'marker position must be >= 0' 697 if type(name) != type(''): 698 raise Error, 'marker name must be a string' 699 for i in range(len(self._markers)): 700 if id == self._markers[i][0]: 701 self._markers[i] = id, pos, name 702 return 703 self._markers.append((id, pos, name)) 704 705 def getmark(self, id): 706 for marker in self._markers: 707 if id == marker[0]: 708 return marker 709 raise Error, 'marker %r does not exist' % (id,) 710 711 def getmarkers(self): 712 if len(self._markers) == 0: 713 return None 714 return self._markers 715 716 def tell(self): 717 return self._nframeswritten 718 719 def writeframesraw(self, data): 720 self._ensure_header_written(len(data)) 721 nframes = len(data) // (self._sampwidth * self._nchannels) 722 if self._convert: 723 data = self._convert(data) 724 self._file.write(data) 725 self._nframeswritten = self._nframeswritten + nframes 726 self._datawritten = self._datawritten + len(data) 727 728 def writeframes(self, data): 729 self.writeframesraw(data) 730 if self._nframeswritten != self._nframes or \ 731 self._datalength != self._datawritten: 732 self._patchheader() 733 734 def close(self): 735 if self._file is None: 736 return 737 try: 738 self._ensure_header_written(0) 739 if self._datawritten & 1: 740 # quick pad to even size 741 self._file.write(chr(0)) 742 self._datawritten = self._datawritten + 1 743 self._writemarkers() 744 if self._nframeswritten != self._nframes or \ 745 self._datalength != self._datawritten or \ 746 self._marklength: 747 self._patchheader() 748 if self._comp: 749 self._comp.CloseCompressor() 750 self._comp = None 751 finally: 752 # Prevent ref cycles 753 self._convert = None 754 f = self._file 755 self._file = None 756 f.close() 757 758 # 759 # Internal methods. 760 # 761 762 def _comp_data(self, data): 763 import cl 764 dummy = self._comp.SetParam(cl.FRAME_BUFFER_SIZE, len(data)) 765 dummy = self._comp.SetParam(cl.COMPRESSED_BUFFER_SIZE, len(data)) 766 return self._comp.Compress(self._nframes, data) 767 768 def _lin2ulaw(self, data): 769 import audioop 770 return audioop.lin2ulaw(data, 2) 771 772 def _lin2adpcm(self, data): 773 import audioop 774 if not hasattr(self, '_adpcmstate'): 775 self._adpcmstate = None 776 data, self._adpcmstate = audioop.lin2adpcm(data, 2, 777 self._adpcmstate) 778 return data 779 780 def _ensure_header_written(self, datasize): 781 if not self._nframeswritten: 782 if self._comptype in ('ULAW', 'ALAW'): 783 if not self._sampwidth: 784 self._sampwidth = 2 785 if self._sampwidth != 2: 786 raise Error, 'sample width must be 2 when compressing with ULAW or ALAW' 787 if self._comptype == 'G722': 788 if not self._sampwidth: 789 self._sampwidth = 2 790 if self._sampwidth != 2: 791 raise Error, 'sample width must be 2 when compressing with G7.22 (ADPCM)' 792 if not self._nchannels: 793 raise Error, '# channels not specified' 794 if not self._sampwidth: 795 raise Error, 'sample width not specified' 796 if not self._framerate: 797 raise Error, 'sampling rate not specified' 798 self._write_header(datasize) 799 800 def _init_compression(self): 801 if self._comptype == 'G722': 802 self._convert = self._lin2adpcm 803 return 804 try: 805 import cl 806 except ImportError: 807 if self._comptype == 'ULAW': 808 try: 809 import audioop 810 self._convert = self._lin2ulaw 811 return 812 except ImportError: 813 pass 814 raise Error, 'cannot write compressed AIFF-C files' 815 if self._comptype == 'ULAW': 816 scheme = cl.G711_ULAW 817 elif self._comptype == 'ALAW': 818 scheme = cl.G711_ALAW 819 else: 820 raise Error, 'unsupported compression type' 821 self._comp = cl.OpenCompressor(scheme) 822 params = [cl.ORIGINAL_FORMAT, 0, 823 cl.BITS_PER_COMPONENT, self._sampwidth * 8, 824 cl.FRAME_RATE, self._framerate, 825 cl.FRAME_BUFFER_SIZE, 100, 826 cl.COMPRESSED_BUFFER_SIZE, 100] 827 if self._nchannels == 1: 828 params[1] = cl.MONO 829 elif self._nchannels == 2: 830 params[1] = cl.STEREO_INTERLEAVED 831 else: 832 raise Error, 'cannot compress more than 2 channels' 833 self._comp.SetParams(params) 834 # the compressor produces a header which we ignore 835 dummy = self._comp.Compress(0, '') 836 self._convert = self._comp_data 837 838 def _write_header(self, initlength): 839 if self._aifc and self._comptype != 'NONE': 840 self._init_compression() 841 self._file.write('FORM') 842 if not self._nframes: 843 self._nframes = initlength // (self._nchannels * self._sampwidth) 844 self._datalength = self._nframes * self._nchannels * self._sampwidth 845 if self._datalength & 1: 846 self._datalength = self._datalength + 1 847 if self._aifc: 848 if self._comptype in ('ULAW', 'ALAW'): 849 self._datalength = self._datalength // 2 850 if self._datalength & 1: 851 self._datalength = self._datalength + 1 852 elif self._comptype == 'G722': 853 self._datalength = (self._datalength + 3) // 4 854 if self._datalength & 1: 855 self._datalength = self._datalength + 1 856 self._form_length_pos = self._file.tell() 857 commlength = self._write_form_length(self._datalength) 858 if self._aifc: 859 self._file.write('AIFC') 860 self._file.write('FVER') 861 _write_ulong(self._file, 4) 862 _write_ulong(self._file, self._version) 863 else: 864 self._file.write('AIFF') 865 self._file.write('COMM') 866 _write_ulong(self._file, commlength) 867 _write_short(self._file, self._nchannels) 868 self._nframes_pos = self._file.tell() 869 _write_ulong(self._file, self._nframes) 870 _write_short(self._file, self._sampwidth * 8) 871 _write_float(self._file, self._framerate) 872 if self._aifc: 873 self._file.write(self._comptype) 874 _write_string(self._file, self._compname) 875 self._file.write('SSND') 876 self._ssnd_length_pos = self._file.tell() 877 _write_ulong(self._file, self._datalength + 8) 878 _write_ulong(self._file, 0) 879 _write_ulong(self._file, 0) 880 881 def _write_form_length(self, datalength): 882 if self._aifc: 883 commlength = 18 + 5 + len(self._compname) 884 if commlength & 1: 885 commlength = commlength + 1 886 verslength = 12 887 else: 888 commlength = 18 889 verslength = 0 890 _write_ulong(self._file, 4 + verslength + self._marklength + \ 891 8 + commlength + 16 + datalength) 892 return commlength 893 894 def _patchheader(self): 895 curpos = self._file.tell() 896 if self._datawritten & 1: 897 datalength = self._datawritten + 1 898 self._file.write(chr(0)) 899 else: 900 datalength = self._datawritten 901 if datalength == self._datalength and \ 902 self._nframes == self._nframeswritten and \ 903 self._marklength == 0: 904 self._file.seek(curpos, 0) 905 return 906 self._file.seek(self._form_length_pos, 0) 907 dummy = self._write_form_length(datalength) 908 self._file.seek(self._nframes_pos, 0) 909 _write_ulong(self._file, self._nframeswritten) 910 self._file.seek(self._ssnd_length_pos, 0) 911 _write_ulong(self._file, datalength + 8) 912 self._file.seek(curpos, 0) 913 self._nframes = self._nframeswritten 914 self._datalength = datalength 915 916 def _writemarkers(self): 917 if len(self._markers) == 0: 918 return 919 self._file.write('MARK') 920 length = 2 921 for marker in self._markers: 922 id, pos, name = marker 923 length = length + len(name) + 1 + 6 924 if len(name) & 1 == 0: 925 length = length + 1 926 _write_ulong(self._file, length) 927 self._marklength = length + 8 928 _write_short(self._file, len(self._markers)) 929 for marker in self._markers: 930 id, pos, name = marker 931 _write_short(self._file, id) 932 _write_ulong(self._file, pos) 933 _write_string(self._file, name) 934 935 def open(f, mode=None): 936 if mode is None: 937 if hasattr(f, 'mode'): 938 mode = f.mode 939 else: 940 mode = 'rb' 941 if mode in ('r', 'rb'): 942 return Aifc_read(f) 943 elif mode in ('w', 'wb'): 944 return Aifc_write(f) 945 else: 946 raise Error, "mode must be 'r', 'rb', 'w', or 'wb'" 947 948 openfp = open # B/W compatibility 949 950 if __name__ == '__main__': 951 import sys 952 if not sys.argv[1:]: 953 sys.argv.append('/usr/demos/data/audio/bach.aiff') 954 fn = sys.argv[1] 955 f = open(fn, 'r') 956 print "Reading", fn 957 print "nchannels =", f.getnchannels() 958 print "nframes =", f.getnframes() 959 print "sampwidth =", f.getsampwidth() 960 print "framerate =", f.getframerate() 961 print "comptype =", f.getcomptype() 962 print "compname =", f.getcompname() 963 if sys.argv[2:]: 964 gn = sys.argv[2] 965 print "Writing", gn 966 g = open(gn, 'w') 967 g.setparams(f.getparams()) 968 while 1: 969 data = f.readframes(1024) 970 if not data: 971 break 972 g.writeframes(data) 973 g.close() 974 f.close() 975 print "Done." 976