1"""Stuff to parse AIFF-C and AIFF files. 2 3Unless explicitly stated otherwise, the description below is true 4both for AIFF-C files and AIFF files. 5 6An 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 21An AIFF file has the string "AIFF" instead of "AIFC". 22 23A chunk consists of an identifier (4 bytes) followed by a size (4 bytes, 24big endian order), followed by the data. The size field does not include 25the size of the 8 byte header. 26 27The 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 51A pstring consists of 1 byte length, a string of characters, and 0 or 1 52byte pad to make the total length even. 53 54Usage. 55 56Reading AIFF files: 57 f = aifc.open(file, 'r') 58where file is either the name of a file or an open file pointer. 59The open file pointer must have methods read(), seek(), and close(). 60In some types of audio files, if the setpos() method is not used, 61the seek() method is not necessary. 62 63This 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) 83The position returned by tell(), the position given to setpos() and 84the position of marks are all compatible and have nothing to do with 85the actual position in the file. 86The close() method is called automatically when the class instance 87is destroyed. 88 89Writing AIFF files: 90 f = aifc.open(file, 'w') 91where file is either the name of a file or an open file pointer. 92The open file pointer must have methods write(), tell(), seek(), and 93close(). 94 95This 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 118You should set the parameters before the first writeframesraw or 119writeframes. The total number of frames does not need to be set, 120but when it is set to the correct value, the header does not have to 121be patched up. 122It is best to first set all parameters, perhaps possibly the 123compression type, and then write audio frames using writeframesraw. 124When all frames have been written, either call writeframes('') or 125close() to patch up the sizes in the header. 126Marks can be added anytime. If there are any marks, you must call 127close() after all frames have been written. 128The close() method is called automatically when the class instance 129is destroyed. 130 131When a file is opened with the extension '.aiff', an AIFF file is 132written, otherwise an AIFF-C file is written. This default can be 133changed by calling aiff() or aifc() before the first writeframes or 134writeframesraw. 135""" 136 137import struct 138import __builtin__ 139 140__all__ = ["Error","open","openfp"] 141 142class Error(Exception): 143 pass 144 145_AIFC_version = 0xA2805140L # Version 1 of AIFF-C 146 147def _read_long(file): 148 try: 149 return struct.unpack('>l', file.read(4))[0] 150 except struct.error: 151 raise EOFError 152 153def _read_ulong(file): 154 try: 155 return struct.unpack('>L', file.read(4))[0] 156 except struct.error: 157 raise EOFError 158 159def _read_short(file): 160 try: 161 return struct.unpack('>h', file.read(2))[0] 162 except struct.error: 163 raise EOFError 164 165def _read_ushort(file): 166 try: 167 return struct.unpack('>H', file.read(2))[0] 168 except struct.error: 169 raise EOFError 170 171def _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 183def _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 200def _write_short(f, x): 201 f.write(struct.pack('>h', x)) 202 203def _write_ushort(f, x): 204 f.write(struct.pack('>H', x)) 205 206def _write_long(f, x): 207 f.write(struct.pack('>l', x)) 208 209def _write_ulong(f, x): 210 f.write(struct.pack('>L', x)) 211 212def _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 220def _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 253from chunk import Chunk 254 255class 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 decomp = self._decomp 361 try: 362 if decomp: 363 self._decomp = None 364 decomp.CloseDecompressor() 365 finally: 366 self._file.close() 367 368 def tell(self): 369 return self._soundpos 370 371 def getnchannels(self): 372 return self._nchannels 373 374 def getnframes(self): 375 return self._nframes 376 377 def getsampwidth(self): 378 return self._sampwidth 379 380 def getframerate(self): 381 return self._framerate 382 383 def getcomptype(self): 384 return self._comptype 385 386 def getcompname(self): 387 return self._compname 388 389## def getversion(self): 390## return self._version 391 392 def getparams(self): 393 return self.getnchannels(), self.getsampwidth(), \ 394 self.getframerate(), self.getnframes(), \ 395 self.getcomptype(), self.getcompname() 396 397 def getmarkers(self): 398 if len(self._markers) == 0: 399 return None 400 return self._markers 401 402 def getmark(self, id): 403 for marker in self._markers: 404 if id == marker[0]: 405 return marker 406 raise Error, 'marker %r does not exist' % (id,) 407 408 def setpos(self, pos): 409 if pos < 0 or pos > self._nframes: 410 raise Error, 'position not in range' 411 self._soundpos = pos 412 self._ssnd_seek_needed = 1 413 414 def readframes(self, nframes): 415 if self._ssnd_seek_needed: 416 self._ssnd_chunk.seek(0) 417 dummy = self._ssnd_chunk.read(8) 418 pos = self._soundpos * self._framesize 419 if pos: 420 self._ssnd_chunk.seek(pos + 8) 421 self._ssnd_seek_needed = 0 422 if nframes == 0: 423 return '' 424 data = self._ssnd_chunk.read(nframes * self._framesize) 425 if self._convert and data: 426 data = self._convert(data) 427 self._soundpos = self._soundpos + len(data) // (self._nchannels * self._sampwidth) 428 return data 429 430 # 431 # Internal methods. 432 # 433 434 def _decomp_data(self, data): 435 import cl 436 dummy = self._decomp.SetParam(cl.FRAME_BUFFER_SIZE, 437 len(data) * 2) 438 return self._decomp.Decompress(len(data) // self._nchannels, 439 data) 440 441 def _ulaw2lin(self, data): 442 import audioop 443 return audioop.ulaw2lin(data, 2) 444 445 def _adpcm2lin(self, data): 446 import audioop 447 if not hasattr(self, '_adpcmstate'): 448 # first time 449 self._adpcmstate = None 450 data, self._adpcmstate = audioop.adpcm2lin(data, 2, 451 self._adpcmstate) 452 return data 453 454 def _read_comm_chunk(self, chunk): 455 self._nchannels = _read_short(chunk) 456 self._nframes = _read_long(chunk) 457 self._sampwidth = (_read_short(chunk) + 7) // 8 458 self._framerate = int(_read_float(chunk)) 459 self._framesize = self._nchannels * self._sampwidth 460 if self._aifc: 461 #DEBUG: SGI's soundeditor produces a bad size :-( 462 kludge = 0 463 if chunk.chunksize == 18: 464 kludge = 1 465 print 'Warning: bad COMM chunk size' 466 chunk.chunksize = 23 467 #DEBUG end 468 self._comptype = chunk.read(4) 469 #DEBUG start 470 if kludge: 471 length = ord(chunk.file.read(1)) 472 if length & 1 == 0: 473 length = length + 1 474 chunk.chunksize = chunk.chunksize + length 475 chunk.file.seek(-1, 1) 476 #DEBUG end 477 self._compname = _read_string(chunk) 478 if self._comptype != 'NONE': 479 if self._comptype == 'G722': 480 try: 481 import audioop 482 except ImportError: 483 pass 484 else: 485 self._convert = self._adpcm2lin 486 self._sampwidth = 2 487 return 488 # for ULAW and ALAW try Compression Library 489 try: 490 import cl 491 except ImportError: 492 if self._comptype in ('ULAW', 'ulaw'): 493 try: 494 import audioop 495 self._convert = self._ulaw2lin 496 self._sampwidth = 2 497 return 498 except ImportError: 499 pass 500 raise Error, 'cannot read compressed AIFF-C files' 501 if self._comptype in ('ULAW', 'ulaw'): 502 scheme = cl.G711_ULAW 503 elif self._comptype in ('ALAW', 'alaw'): 504 scheme = cl.G711_ALAW 505 else: 506 raise Error, 'unsupported compression type' 507 self._decomp = cl.OpenDecompressor(scheme) 508 self._convert = self._decomp_data 509 self._sampwidth = 2 510 else: 511 self._comptype = 'NONE' 512 self._compname = 'not compressed' 513 514 def _readmark(self, chunk): 515 nmarkers = _read_short(chunk) 516 # Some files appear to contain invalid counts. 517 # Cope with this by testing for EOF. 518 try: 519 for i in range(nmarkers): 520 id = _read_short(chunk) 521 pos = _read_long(chunk) 522 name = _read_string(chunk) 523 if pos or name: 524 # some files appear to have 525 # dummy markers consisting of 526 # a position 0 and name '' 527 self._markers.append((id, pos, name)) 528 except EOFError: 529 print 'Warning: MARK chunk contains only', 530 print len(self._markers), 531 if len(self._markers) == 1: print 'marker', 532 else: print 'markers', 533 print 'instead of', nmarkers 534 535class Aifc_write: 536 # Variables used in this class: 537 # 538 # These variables are user settable through appropriate methods 539 # of this class: 540 # _file -- the open file with methods write(), close(), tell(), seek() 541 # set through the __init__() method 542 # _comptype -- the AIFF-C compression type ('NONE' in AIFF) 543 # set through the setcomptype() or setparams() method 544 # _compname -- the human-readable AIFF-C compression type 545 # set through the setcomptype() or setparams() method 546 # _nchannels -- the number of audio channels 547 # set through the setnchannels() or setparams() method 548 # _sampwidth -- the number of bytes per audio sample 549 # set through the setsampwidth() or setparams() method 550 # _framerate -- the sampling frequency 551 # set through the setframerate() or setparams() method 552 # _nframes -- the number of audio frames written to the header 553 # set through the setnframes() or setparams() method 554 # _aifc -- whether we're writing an AIFF-C file or an AIFF file 555 # set through the aifc() method, reset through the 556 # aiff() method 557 # 558 # These variables are used internally only: 559 # _version -- the AIFF-C version number 560 # _comp -- the compressor from builtin module cl 561 # _nframeswritten -- the number of audio frames actually written 562 # _datalength -- the size of the audio samples written to the header 563 # _datawritten -- the size of the audio samples actually written 564 565 def __init__(self, f): 566 if type(f) == type(''): 567 filename = f 568 f = __builtin__.open(f, 'wb') 569 else: 570 # else, assume it is an open file object already 571 filename = '???' 572 self.initfp(f) 573 if filename[-5:] == '.aiff': 574 self._aifc = 0 575 else: 576 self._aifc = 1 577 578 def initfp(self, file): 579 self._file = file 580 self._version = _AIFC_version 581 self._comptype = 'NONE' 582 self._compname = 'not compressed' 583 self._comp = None 584 self._convert = None 585 self._nchannels = 0 586 self._sampwidth = 0 587 self._framerate = 0 588 self._nframes = 0 589 self._nframeswritten = 0 590 self._datawritten = 0 591 self._datalength = 0 592 self._markers = [] 593 self._marklength = 0 594 self._aifc = 1 # AIFF-C is default 595 596 def __del__(self): 597 if self._file: 598 self.close() 599 600 # 601 # User visible methods. 602 # 603 def aiff(self): 604 if self._nframeswritten: 605 raise Error, 'cannot change parameters after starting to write' 606 self._aifc = 0 607 608 def aifc(self): 609 if self._nframeswritten: 610 raise Error, 'cannot change parameters after starting to write' 611 self._aifc = 1 612 613 def setnchannels(self, nchannels): 614 if self._nframeswritten: 615 raise Error, 'cannot change parameters after starting to write' 616 if nchannels < 1: 617 raise Error, 'bad # of channels' 618 self._nchannels = nchannels 619 620 def getnchannels(self): 621 if not self._nchannels: 622 raise Error, 'number of channels not set' 623 return self._nchannels 624 625 def setsampwidth(self, sampwidth): 626 if self._nframeswritten: 627 raise Error, 'cannot change parameters after starting to write' 628 if sampwidth < 1 or sampwidth > 4: 629 raise Error, 'bad sample width' 630 self._sampwidth = sampwidth 631 632 def getsampwidth(self): 633 if not self._sampwidth: 634 raise Error, 'sample width not set' 635 return self._sampwidth 636 637 def setframerate(self, framerate): 638 if self._nframeswritten: 639 raise Error, 'cannot change parameters after starting to write' 640 if framerate <= 0: 641 raise Error, 'bad frame rate' 642 self._framerate = framerate 643 644 def getframerate(self): 645 if not self._framerate: 646 raise Error, 'frame rate not set' 647 return self._framerate 648 649 def setnframes(self, nframes): 650 if self._nframeswritten: 651 raise Error, 'cannot change parameters after starting to write' 652 self._nframes = nframes 653 654 def getnframes(self): 655 return self._nframeswritten 656 657 def setcomptype(self, comptype, compname): 658 if self._nframeswritten: 659 raise Error, 'cannot change parameters after starting to write' 660 if comptype not in ('NONE', 'ULAW', 'ulaw', 'ALAW', 'alaw', 'G722'): 661 raise Error, 'unsupported compression type' 662 self._comptype = comptype 663 self._compname = compname 664 665 def getcomptype(self): 666 return self._comptype 667 668 def getcompname(self): 669 return self._compname 670 671## def setversion(self, version): 672## if self._nframeswritten: 673## raise Error, 'cannot change parameters after starting to write' 674## self._version = version 675 676 def setparams(self, info): 677 nchannels, sampwidth, framerate, nframes, comptype, compname = info 678 if self._nframeswritten: 679 raise Error, 'cannot change parameters after starting to write' 680 if comptype not in ('NONE', 'ULAW', 'ulaw', 'ALAW', 'alaw', 'G722'): 681 raise Error, 'unsupported compression type' 682 self.setnchannels(nchannels) 683 self.setsampwidth(sampwidth) 684 self.setframerate(framerate) 685 self.setnframes(nframes) 686 self.setcomptype(comptype, compname) 687 688 def getparams(self): 689 if not self._nchannels or not self._sampwidth or not self._framerate: 690 raise Error, 'not all parameters set' 691 return self._nchannels, self._sampwidth, self._framerate, \ 692 self._nframes, self._comptype, self._compname 693 694 def setmark(self, id, pos, name): 695 if id <= 0: 696 raise Error, 'marker ID must be > 0' 697 if pos < 0: 698 raise Error, 'marker position must be >= 0' 699 if type(name) != type(''): 700 raise Error, 'marker name must be a string' 701 for i in range(len(self._markers)): 702 if id == self._markers[i][0]: 703 self._markers[i] = id, pos, name 704 return 705 self._markers.append((id, pos, name)) 706 707 def getmark(self, id): 708 for marker in self._markers: 709 if id == marker[0]: 710 return marker 711 raise Error, 'marker %r does not exist' % (id,) 712 713 def getmarkers(self): 714 if len(self._markers) == 0: 715 return None 716 return self._markers 717 718 def tell(self): 719 return self._nframeswritten 720 721 def writeframesraw(self, data): 722 self._ensure_header_written(len(data)) 723 nframes = len(data) // (self._sampwidth * self._nchannels) 724 if self._convert: 725 data = self._convert(data) 726 self._file.write(data) 727 self._nframeswritten = self._nframeswritten + nframes 728 self._datawritten = self._datawritten + len(data) 729 730 def writeframes(self, data): 731 self.writeframesraw(data) 732 if self._nframeswritten != self._nframes or \ 733 self._datalength != self._datawritten: 734 self._patchheader() 735 736 def close(self): 737 if self._file is None: 738 return 739 try: 740 self._ensure_header_written(0) 741 if self._datawritten & 1: 742 # quick pad to even size 743 self._file.write(chr(0)) 744 self._datawritten = self._datawritten + 1 745 self._writemarkers() 746 if self._nframeswritten != self._nframes or \ 747 self._datalength != self._datawritten or \ 748 self._marklength: 749 self._patchheader() 750 if self._comp: 751 self._comp.CloseCompressor() 752 self._comp = None 753 finally: 754 # Prevent ref cycles 755 self._convert = None 756 f = self._file 757 self._file = None 758 f.close() 759 760 # 761 # Internal methods. 762 # 763 764 def _comp_data(self, data): 765 import cl 766 dummy = self._comp.SetParam(cl.FRAME_BUFFER_SIZE, len(data)) 767 dummy = self._comp.SetParam(cl.COMPRESSED_BUFFER_SIZE, len(data)) 768 return self._comp.Compress(self._nframes, data) 769 770 def _lin2ulaw(self, data): 771 import audioop 772 return audioop.lin2ulaw(data, 2) 773 774 def _lin2adpcm(self, data): 775 import audioop 776 if not hasattr(self, '_adpcmstate'): 777 self._adpcmstate = None 778 data, self._adpcmstate = audioop.lin2adpcm(data, 2, 779 self._adpcmstate) 780 return data 781 782 def _ensure_header_written(self, datasize): 783 if not self._nframeswritten: 784 if self._comptype in ('ULAW', 'ulaw', 'ALAW', 'alaw'): 785 if not self._sampwidth: 786 self._sampwidth = 2 787 if self._sampwidth != 2: 788 raise Error, 'sample width must be 2 when compressing with ULAW or ALAW' 789 if self._comptype == 'G722': 790 if not self._sampwidth: 791 self._sampwidth = 2 792 if self._sampwidth != 2: 793 raise Error, 'sample width must be 2 when compressing with G7.22 (ADPCM)' 794 if not self._nchannels: 795 raise Error, '# channels not specified' 796 if not self._sampwidth: 797 raise Error, 'sample width not specified' 798 if not self._framerate: 799 raise Error, 'sampling rate not specified' 800 self._write_header(datasize) 801 802 def _init_compression(self): 803 if self._comptype == 'G722': 804 self._convert = self._lin2adpcm 805 return 806 try: 807 import cl 808 except ImportError: 809 if self._comptype in ('ULAW', 'ulaw'): 810 try: 811 import audioop 812 self._convert = self._lin2ulaw 813 return 814 except ImportError: 815 pass 816 raise Error, 'cannot write compressed AIFF-C files' 817 if self._comptype in ('ULAW', 'ulaw'): 818 scheme = cl.G711_ULAW 819 elif self._comptype in ('ALAW', 'alaw'): 820 scheme = cl.G711_ALAW 821 else: 822 raise Error, 'unsupported compression type' 823 self._comp = cl.OpenCompressor(scheme) 824 params = [cl.ORIGINAL_FORMAT, 0, 825 cl.BITS_PER_COMPONENT, self._sampwidth * 8, 826 cl.FRAME_RATE, self._framerate, 827 cl.FRAME_BUFFER_SIZE, 100, 828 cl.COMPRESSED_BUFFER_SIZE, 100] 829 if self._nchannels == 1: 830 params[1] = cl.MONO 831 elif self._nchannels == 2: 832 params[1] = cl.STEREO_INTERLEAVED 833 else: 834 raise Error, 'cannot compress more than 2 channels' 835 self._comp.SetParams(params) 836 # the compressor produces a header which we ignore 837 dummy = self._comp.Compress(0, '') 838 self._convert = self._comp_data 839 840 def _write_header(self, initlength): 841 if self._aifc and self._comptype != 'NONE': 842 self._init_compression() 843 self._file.write('FORM') 844 if not self._nframes: 845 self._nframes = initlength // (self._nchannels * self._sampwidth) 846 self._datalength = self._nframes * self._nchannels * self._sampwidth 847 if self._datalength & 1: 848 self._datalength = self._datalength + 1 849 if self._aifc: 850 if self._comptype in ('ULAW', 'ulaw', 'ALAW', 'alaw'): 851 self._datalength = self._datalength // 2 852 if self._datalength & 1: 853 self._datalength = self._datalength + 1 854 elif self._comptype == 'G722': 855 self._datalength = (self._datalength + 3) // 4 856 if self._datalength & 1: 857 self._datalength = self._datalength + 1 858 try: 859 self._form_length_pos = self._file.tell() 860 except (AttributeError, IOError): 861 self._form_length_pos = None 862 commlength = self._write_form_length(self._datalength) 863 if self._aifc: 864 self._file.write('AIFC') 865 self._file.write('FVER') 866 _write_ulong(self._file, 4) 867 _write_ulong(self._file, self._version) 868 else: 869 self._file.write('AIFF') 870 self._file.write('COMM') 871 _write_ulong(self._file, commlength) 872 _write_short(self._file, self._nchannels) 873 if self._form_length_pos is not None: 874 self._nframes_pos = self._file.tell() 875 _write_ulong(self._file, self._nframes) 876 if self._comptype in ('ULAW', 'ulaw', 'ALAW', 'alaw', 'G722'): 877 _write_short(self._file, 8) 878 else: 879 _write_short(self._file, self._sampwidth * 8) 880 _write_float(self._file, self._framerate) 881 if self._aifc: 882 self._file.write(self._comptype) 883 _write_string(self._file, self._compname) 884 self._file.write('SSND') 885 if self._form_length_pos is not None: 886 self._ssnd_length_pos = self._file.tell() 887 _write_ulong(self._file, self._datalength + 8) 888 _write_ulong(self._file, 0) 889 _write_ulong(self._file, 0) 890 891 def _write_form_length(self, datalength): 892 if self._aifc: 893 commlength = 18 + 5 + len(self._compname) 894 if commlength & 1: 895 commlength = commlength + 1 896 verslength = 12 897 else: 898 commlength = 18 899 verslength = 0 900 _write_ulong(self._file, 4 + verslength + self._marklength + \ 901 8 + commlength + 16 + datalength) 902 return commlength 903 904 def _patchheader(self): 905 curpos = self._file.tell() 906 if self._datawritten & 1: 907 datalength = self._datawritten + 1 908 self._file.write(chr(0)) 909 else: 910 datalength = self._datawritten 911 if datalength == self._datalength and \ 912 self._nframes == self._nframeswritten and \ 913 self._marklength == 0: 914 self._file.seek(curpos, 0) 915 return 916 self._file.seek(self._form_length_pos, 0) 917 dummy = self._write_form_length(datalength) 918 self._file.seek(self._nframes_pos, 0) 919 _write_ulong(self._file, self._nframeswritten) 920 self._file.seek(self._ssnd_length_pos, 0) 921 _write_ulong(self._file, datalength + 8) 922 self._file.seek(curpos, 0) 923 self._nframes = self._nframeswritten 924 self._datalength = datalength 925 926 def _writemarkers(self): 927 if len(self._markers) == 0: 928 return 929 self._file.write('MARK') 930 length = 2 931 for marker in self._markers: 932 id, pos, name = marker 933 length = length + len(name) + 1 + 6 934 if len(name) & 1 == 0: 935 length = length + 1 936 _write_ulong(self._file, length) 937 self._marklength = length + 8 938 _write_short(self._file, len(self._markers)) 939 for marker in self._markers: 940 id, pos, name = marker 941 _write_short(self._file, id) 942 _write_ulong(self._file, pos) 943 _write_string(self._file, name) 944 945def open(f, mode=None): 946 if mode is None: 947 if hasattr(f, 'mode'): 948 mode = f.mode 949 else: 950 mode = 'rb' 951 if mode in ('r', 'rb'): 952 return Aifc_read(f) 953 elif mode in ('w', 'wb'): 954 return Aifc_write(f) 955 else: 956 raise Error, "mode must be 'r', 'rb', 'w', or 'wb'" 957 958openfp = open # B/W compatibility 959 960if __name__ == '__main__': 961 import sys 962 if not sys.argv[1:]: 963 sys.argv.append('/usr/demos/data/audio/bach.aiff') 964 fn = sys.argv[1] 965 f = open(fn, 'r') 966 try: 967 print "Reading", fn 968 print "nchannels =", f.getnchannels() 969 print "nframes =", f.getnframes() 970 print "sampwidth =", f.getsampwidth() 971 print "framerate =", f.getframerate() 972 print "comptype =", f.getcomptype() 973 print "compname =", f.getcompname() 974 if sys.argv[2:]: 975 gn = sys.argv[2] 976 print "Writing", gn 977 g = open(gn, 'w') 978 try: 979 g.setparams(f.getparams()) 980 while 1: 981 data = f.readframes(1024) 982 if not data: 983 break 984 g.writeframes(data) 985 finally: 986 g.close() 987 print "Done." 988 finally: 989 f.close() 990