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 _file = None # Set here since __del__ checks it 292 293 def initfp(self, file): 294 self._version = 0 295 self._decomp = None 296 self._convert = None 297 self._markers = [] 298 self._soundpos = 0 299 self._file = file 300 chunk = Chunk(file) 301 if chunk.getname() != 'FORM': 302 raise Error, 'file does not start with FORM id' 303 formdata = chunk.read(4) 304 if formdata == 'AIFF': 305 self._aifc = 0 306 elif formdata == 'AIFC': 307 self._aifc = 1 308 else: 309 raise Error, 'not an AIFF or AIFF-C file' 310 self._comm_chunk_read = 0 311 self._ssnd_chunk = None 312 while 1: 313 self._ssnd_seek_needed = 1 314 try: 315 chunk = Chunk(self._file) 316 except EOFError: 317 break 318 chunkname = chunk.getname() 319 if chunkname == 'COMM': 320 self._read_comm_chunk(chunk) 321 self._comm_chunk_read = 1 322 elif chunkname == 'SSND': 323 self._ssnd_chunk = chunk 324 dummy = chunk.read(8) 325 self._ssnd_seek_needed = 0 326 elif chunkname == 'FVER': 327 self._version = _read_ulong(chunk) 328 elif chunkname == 'MARK': 329 self._readmark(chunk) 330 chunk.skip() 331 if not self._comm_chunk_read or not self._ssnd_chunk: 332 raise Error, 'COMM chunk and/or SSND chunk missing' 333 if self._aifc and self._decomp: 334 import cl 335 params = [cl.ORIGINAL_FORMAT, 0, 336 cl.BITS_PER_COMPONENT, self._sampwidth * 8, 337 cl.FRAME_RATE, self._framerate] 338 if self._nchannels == 1: 339 params[1] = cl.MONO 340 elif self._nchannels == 2: 341 params[1] = cl.STEREO_INTERLEAVED 342 else: 343 raise Error, 'cannot compress more than 2 channels' 344 self._decomp.SetParams(params) 345 346 def __init__(self, f): 347 if isinstance(f, basestring): 348 f = __builtin__.open(f, 'rb') 349 try: 350 self.initfp(f) 351 except: 352 f.close() 353 raise 354 else: 355 # assume it is an open file object already 356 self.initfp(f) 357 358 # 359 # User visible methods. 360 # 361 def getfp(self): 362 return self._file 363 364 def rewind(self): 365 self._ssnd_seek_needed = 1 366 self._soundpos = 0 367 368 def close(self): 369 decomp = self._decomp 370 try: 371 if decomp: 372 self._decomp = None 373 decomp.CloseDecompressor() 374 finally: 375 self._file.close() 376 377 def tell(self): 378 return self._soundpos 379 380 def getnchannels(self): 381 return self._nchannels 382 383 def getnframes(self): 384 return self._nframes 385 386 def getsampwidth(self): 387 return self._sampwidth 388 389 def getframerate(self): 390 return self._framerate 391 392 def getcomptype(self): 393 return self._comptype 394 395 def getcompname(self): 396 return self._compname 397 398## def getversion(self): 399## return self._version 400 401 def getparams(self): 402 return self.getnchannels(), self.getsampwidth(), \ 403 self.getframerate(), self.getnframes(), \ 404 self.getcomptype(), self.getcompname() 405 406 def getmarkers(self): 407 if len(self._markers) == 0: 408 return None 409 return self._markers 410 411 def getmark(self, id): 412 for marker in self._markers: 413 if id == marker[0]: 414 return marker 415 raise Error, 'marker %r does not exist' % (id,) 416 417 def setpos(self, pos): 418 if pos < 0 or pos > self._nframes: 419 raise Error, 'position not in range' 420 self._soundpos = pos 421 self._ssnd_seek_needed = 1 422 423 def readframes(self, nframes): 424 if self._ssnd_seek_needed: 425 self._ssnd_chunk.seek(0) 426 dummy = self._ssnd_chunk.read(8) 427 pos = self._soundpos * self._framesize 428 if pos: 429 self._ssnd_chunk.seek(pos + 8) 430 self._ssnd_seek_needed = 0 431 if nframes == 0: 432 return '' 433 data = self._ssnd_chunk.read(nframes * self._framesize) 434 if self._convert and data: 435 data = self._convert(data) 436 self._soundpos = self._soundpos + len(data) // (self._nchannels * self._sampwidth) 437 return data 438 439 # 440 # Internal methods. 441 # 442 443 def _decomp_data(self, data): 444 import cl 445 dummy = self._decomp.SetParam(cl.FRAME_BUFFER_SIZE, 446 len(data) * 2) 447 return self._decomp.Decompress(len(data) // self._nchannels, 448 data) 449 450 def _ulaw2lin(self, data): 451 import audioop 452 return audioop.ulaw2lin(data, 2) 453 454 def _adpcm2lin(self, data): 455 import audioop 456 if not hasattr(self, '_adpcmstate'): 457 # first time 458 self._adpcmstate = None 459 data, self._adpcmstate = audioop.adpcm2lin(data, 2, 460 self._adpcmstate) 461 return data 462 463 def _read_comm_chunk(self, chunk): 464 self._nchannels = _read_short(chunk) 465 self._nframes = _read_long(chunk) 466 self._sampwidth = (_read_short(chunk) + 7) // 8 467 self._framerate = int(_read_float(chunk)) 468 self._framesize = self._nchannels * self._sampwidth 469 if self._aifc: 470 #DEBUG: SGI's soundeditor produces a bad size :-( 471 kludge = 0 472 if chunk.chunksize == 18: 473 kludge = 1 474 print 'Warning: bad COMM chunk size' 475 chunk.chunksize = 23 476 #DEBUG end 477 self._comptype = chunk.read(4) 478 #DEBUG start 479 if kludge: 480 length = ord(chunk.file.read(1)) 481 if length & 1 == 0: 482 length = length + 1 483 chunk.chunksize = chunk.chunksize + length 484 chunk.file.seek(-1, 1) 485 #DEBUG end 486 self._compname = _read_string(chunk) 487 if self._comptype != 'NONE': 488 if self._comptype == 'G722': 489 try: 490 import audioop 491 except ImportError: 492 pass 493 else: 494 self._convert = self._adpcm2lin 495 self._sampwidth = 2 496 return 497 # for ULAW and ALAW try Compression Library 498 try: 499 import cl 500 except ImportError: 501 if self._comptype in ('ULAW', 'ulaw'): 502 try: 503 import audioop 504 self._convert = self._ulaw2lin 505 self._sampwidth = 2 506 return 507 except ImportError: 508 pass 509 raise Error, 'cannot read compressed AIFF-C files' 510 if self._comptype in ('ULAW', 'ulaw'): 511 scheme = cl.G711_ULAW 512 elif self._comptype in ('ALAW', 'alaw'): 513 scheme = cl.G711_ALAW 514 else: 515 raise Error, 'unsupported compression type' 516 self._decomp = cl.OpenDecompressor(scheme) 517 self._convert = self._decomp_data 518 self._sampwidth = 2 519 else: 520 self._comptype = 'NONE' 521 self._compname = 'not compressed' 522 523 def _readmark(self, chunk): 524 nmarkers = _read_short(chunk) 525 # Some files appear to contain invalid counts. 526 # Cope with this by testing for EOF. 527 try: 528 for i in range(nmarkers): 529 id = _read_short(chunk) 530 pos = _read_long(chunk) 531 name = _read_string(chunk) 532 if pos or name: 533 # some files appear to have 534 # dummy markers consisting of 535 # a position 0 and name '' 536 self._markers.append((id, pos, name)) 537 except EOFError: 538 print 'Warning: MARK chunk contains only', 539 print len(self._markers), 540 if len(self._markers) == 1: print 'marker', 541 else: print 'markers', 542 print 'instead of', nmarkers 543 544class Aifc_write: 545 # Variables used in this class: 546 # 547 # These variables are user settable through appropriate methods 548 # of this class: 549 # _file -- the open file with methods write(), close(), tell(), seek() 550 # set through the __init__() method 551 # _comptype -- the AIFF-C compression type ('NONE' in AIFF) 552 # set through the setcomptype() or setparams() method 553 # _compname -- the human-readable AIFF-C compression type 554 # set through the setcomptype() or setparams() method 555 # _nchannels -- the number of audio channels 556 # set through the setnchannels() or setparams() method 557 # _sampwidth -- the number of bytes per audio sample 558 # set through the setsampwidth() or setparams() method 559 # _framerate -- the sampling frequency 560 # set through the setframerate() or setparams() method 561 # _nframes -- the number of audio frames written to the header 562 # set through the setnframes() or setparams() method 563 # _aifc -- whether we're writing an AIFF-C file or an AIFF file 564 # set through the aifc() method, reset through the 565 # aiff() method 566 # 567 # These variables are used internally only: 568 # _version -- the AIFF-C version number 569 # _comp -- the compressor from builtin module cl 570 # _nframeswritten -- the number of audio frames actually written 571 # _datalength -- the size of the audio samples written to the header 572 # _datawritten -- the size of the audio samples actually written 573 574 _file = None # Set here since __del__ checks it 575 576 def __init__(self, f): 577 if isinstance(f, basestring): 578 filename = f 579 f = __builtin__.open(f, 'wb') 580 else: 581 # else, assume it is an open file object already 582 filename = '???' 583 self.initfp(f) 584 if filename[-5:] == '.aiff': 585 self._aifc = 0 586 else: 587 self._aifc = 1 588 589 def initfp(self, file): 590 self._file = file 591 self._version = _AIFC_version 592 self._comptype = 'NONE' 593 self._compname = 'not compressed' 594 self._comp = None 595 self._convert = None 596 self._nchannels = 0 597 self._sampwidth = 0 598 self._framerate = 0 599 self._nframes = 0 600 self._nframeswritten = 0 601 self._datawritten = 0 602 self._datalength = 0 603 self._markers = [] 604 self._marklength = 0 605 self._aifc = 1 # AIFF-C is default 606 607 def __del__(self): 608 if self._file: 609 self.close() 610 611 # 612 # User visible methods. 613 # 614 def aiff(self): 615 if self._nframeswritten: 616 raise Error, 'cannot change parameters after starting to write' 617 self._aifc = 0 618 619 def aifc(self): 620 if self._nframeswritten: 621 raise Error, 'cannot change parameters after starting to write' 622 self._aifc = 1 623 624 def setnchannels(self, nchannels): 625 if self._nframeswritten: 626 raise Error, 'cannot change parameters after starting to write' 627 if nchannels < 1: 628 raise Error, 'bad # of channels' 629 self._nchannels = nchannels 630 631 def getnchannels(self): 632 if not self._nchannels: 633 raise Error, 'number of channels not set' 634 return self._nchannels 635 636 def setsampwidth(self, sampwidth): 637 if self._nframeswritten: 638 raise Error, 'cannot change parameters after starting to write' 639 if sampwidth < 1 or sampwidth > 4: 640 raise Error, 'bad sample width' 641 self._sampwidth = sampwidth 642 643 def getsampwidth(self): 644 if not self._sampwidth: 645 raise Error, 'sample width not set' 646 return self._sampwidth 647 648 def setframerate(self, framerate): 649 if self._nframeswritten: 650 raise Error, 'cannot change parameters after starting to write' 651 if framerate <= 0: 652 raise Error, 'bad frame rate' 653 self._framerate = framerate 654 655 def getframerate(self): 656 if not self._framerate: 657 raise Error, 'frame rate not set' 658 return self._framerate 659 660 def setnframes(self, nframes): 661 if self._nframeswritten: 662 raise Error, 'cannot change parameters after starting to write' 663 self._nframes = nframes 664 665 def getnframes(self): 666 return self._nframeswritten 667 668 def setcomptype(self, comptype, compname): 669 if self._nframeswritten: 670 raise Error, 'cannot change parameters after starting to write' 671 if comptype not in ('NONE', 'ULAW', 'ulaw', 'ALAW', 'alaw', 'G722'): 672 raise Error, 'unsupported compression type' 673 self._comptype = comptype 674 self._compname = compname 675 676 def getcomptype(self): 677 return self._comptype 678 679 def getcompname(self): 680 return self._compname 681 682## def setversion(self, version): 683## if self._nframeswritten: 684## raise Error, 'cannot change parameters after starting to write' 685## self._version = version 686 687 def setparams(self, info): 688 nchannels, sampwidth, framerate, nframes, comptype, compname = info 689 if self._nframeswritten: 690 raise Error, 'cannot change parameters after starting to write' 691 if comptype not in ('NONE', 'ULAW', 'ulaw', 'ALAW', 'alaw', 'G722'): 692 raise Error, 'unsupported compression type' 693 self.setnchannels(nchannels) 694 self.setsampwidth(sampwidth) 695 self.setframerate(framerate) 696 self.setnframes(nframes) 697 self.setcomptype(comptype, compname) 698 699 def getparams(self): 700 if not self._nchannels or not self._sampwidth or not self._framerate: 701 raise Error, 'not all parameters set' 702 return self._nchannels, self._sampwidth, self._framerate, \ 703 self._nframes, self._comptype, self._compname 704 705 def setmark(self, id, pos, name): 706 if id <= 0: 707 raise Error, 'marker ID must be > 0' 708 if pos < 0: 709 raise Error, 'marker position must be >= 0' 710 if type(name) != type(''): 711 raise Error, 'marker name must be a string' 712 for i in range(len(self._markers)): 713 if id == self._markers[i][0]: 714 self._markers[i] = id, pos, name 715 return 716 self._markers.append((id, pos, name)) 717 718 def getmark(self, id): 719 for marker in self._markers: 720 if id == marker[0]: 721 return marker 722 raise Error, 'marker %r does not exist' % (id,) 723 724 def getmarkers(self): 725 if len(self._markers) == 0: 726 return None 727 return self._markers 728 729 def tell(self): 730 return self._nframeswritten 731 732 def writeframesraw(self, data): 733 self._ensure_header_written(len(data)) 734 nframes = len(data) // (self._sampwidth * self._nchannels) 735 if self._convert: 736 data = self._convert(data) 737 self._file.write(data) 738 self._nframeswritten = self._nframeswritten + nframes 739 self._datawritten = self._datawritten + len(data) 740 741 def writeframes(self, data): 742 self.writeframesraw(data) 743 if self._nframeswritten != self._nframes or \ 744 self._datalength != self._datawritten: 745 self._patchheader() 746 747 def close(self): 748 if self._file is None: 749 return 750 try: 751 self._ensure_header_written(0) 752 if self._datawritten & 1: 753 # quick pad to even size 754 self._file.write(chr(0)) 755 self._datawritten = self._datawritten + 1 756 self._writemarkers() 757 if self._nframeswritten != self._nframes or \ 758 self._datalength != self._datawritten or \ 759 self._marklength: 760 self._patchheader() 761 if self._comp: 762 self._comp.CloseCompressor() 763 self._comp = None 764 finally: 765 # Prevent ref cycles 766 self._convert = None 767 f = self._file 768 self._file = None 769 f.close() 770 771 # 772 # Internal methods. 773 # 774 775 def _comp_data(self, data): 776 import cl 777 dummy = self._comp.SetParam(cl.FRAME_BUFFER_SIZE, len(data)) 778 dummy = self._comp.SetParam(cl.COMPRESSED_BUFFER_SIZE, len(data)) 779 return self._comp.Compress(self._nframes, data) 780 781 def _lin2ulaw(self, data): 782 import audioop 783 return audioop.lin2ulaw(data, 2) 784 785 def _lin2adpcm(self, data): 786 import audioop 787 if not hasattr(self, '_adpcmstate'): 788 self._adpcmstate = None 789 data, self._adpcmstate = audioop.lin2adpcm(data, 2, 790 self._adpcmstate) 791 return data 792 793 def _ensure_header_written(self, datasize): 794 if not self._nframeswritten: 795 if self._comptype in ('ULAW', 'ulaw', 'ALAW', 'alaw'): 796 if not self._sampwidth: 797 self._sampwidth = 2 798 if self._sampwidth != 2: 799 raise Error, 'sample width must be 2 when compressing with ULAW or ALAW' 800 if self._comptype == 'G722': 801 if not self._sampwidth: 802 self._sampwidth = 2 803 if self._sampwidth != 2: 804 raise Error, 'sample width must be 2 when compressing with G7.22 (ADPCM)' 805 if not self._nchannels: 806 raise Error, '# channels not specified' 807 if not self._sampwidth: 808 raise Error, 'sample width not specified' 809 if not self._framerate: 810 raise Error, 'sampling rate not specified' 811 self._write_header(datasize) 812 813 def _init_compression(self): 814 if self._comptype == 'G722': 815 self._convert = self._lin2adpcm 816 return 817 try: 818 import cl 819 except ImportError: 820 if self._comptype in ('ULAW', 'ulaw'): 821 try: 822 import audioop 823 self._convert = self._lin2ulaw 824 return 825 except ImportError: 826 pass 827 raise Error, 'cannot write compressed AIFF-C files' 828 if self._comptype in ('ULAW', 'ulaw'): 829 scheme = cl.G711_ULAW 830 elif self._comptype in ('ALAW', 'alaw'): 831 scheme = cl.G711_ALAW 832 else: 833 raise Error, 'unsupported compression type' 834 self._comp = cl.OpenCompressor(scheme) 835 params = [cl.ORIGINAL_FORMAT, 0, 836 cl.BITS_PER_COMPONENT, self._sampwidth * 8, 837 cl.FRAME_RATE, self._framerate, 838 cl.FRAME_BUFFER_SIZE, 100, 839 cl.COMPRESSED_BUFFER_SIZE, 100] 840 if self._nchannels == 1: 841 params[1] = cl.MONO 842 elif self._nchannels == 2: 843 params[1] = cl.STEREO_INTERLEAVED 844 else: 845 raise Error, 'cannot compress more than 2 channels' 846 self._comp.SetParams(params) 847 # the compressor produces a header which we ignore 848 dummy = self._comp.Compress(0, '') 849 self._convert = self._comp_data 850 851 def _write_header(self, initlength): 852 if self._aifc and self._comptype != 'NONE': 853 self._init_compression() 854 self._file.write('FORM') 855 if not self._nframes: 856 self._nframes = initlength // (self._nchannels * self._sampwidth) 857 self._datalength = self._nframes * self._nchannels * self._sampwidth 858 if self._datalength & 1: 859 self._datalength = self._datalength + 1 860 if self._aifc: 861 if self._comptype in ('ULAW', 'ulaw', 'ALAW', 'alaw'): 862 self._datalength = self._datalength // 2 863 if self._datalength & 1: 864 self._datalength = self._datalength + 1 865 elif self._comptype == 'G722': 866 self._datalength = (self._datalength + 3) // 4 867 if self._datalength & 1: 868 self._datalength = self._datalength + 1 869 try: 870 self._form_length_pos = self._file.tell() 871 except (AttributeError, IOError): 872 self._form_length_pos = None 873 commlength = self._write_form_length(self._datalength) 874 if self._aifc: 875 self._file.write('AIFC') 876 self._file.write('FVER') 877 _write_ulong(self._file, 4) 878 _write_ulong(self._file, self._version) 879 else: 880 self._file.write('AIFF') 881 self._file.write('COMM') 882 _write_ulong(self._file, commlength) 883 _write_short(self._file, self._nchannels) 884 if self._form_length_pos is not None: 885 self._nframes_pos = self._file.tell() 886 _write_ulong(self._file, self._nframes) 887 if self._comptype in ('ULAW', 'ulaw', 'ALAW', 'alaw', 'G722'): 888 _write_short(self._file, 8) 889 else: 890 _write_short(self._file, self._sampwidth * 8) 891 _write_float(self._file, self._framerate) 892 if self._aifc: 893 self._file.write(self._comptype) 894 _write_string(self._file, self._compname) 895 self._file.write('SSND') 896 if self._form_length_pos is not None: 897 self._ssnd_length_pos = self._file.tell() 898 _write_ulong(self._file, self._datalength + 8) 899 _write_ulong(self._file, 0) 900 _write_ulong(self._file, 0) 901 902 def _write_form_length(self, datalength): 903 if self._aifc: 904 commlength = 18 + 5 + len(self._compname) 905 if commlength & 1: 906 commlength = commlength + 1 907 verslength = 12 908 else: 909 commlength = 18 910 verslength = 0 911 _write_ulong(self._file, 4 + verslength + self._marklength + \ 912 8 + commlength + 16 + datalength) 913 return commlength 914 915 def _patchheader(self): 916 curpos = self._file.tell() 917 if self._datawritten & 1: 918 datalength = self._datawritten + 1 919 self._file.write(chr(0)) 920 else: 921 datalength = self._datawritten 922 if datalength == self._datalength and \ 923 self._nframes == self._nframeswritten and \ 924 self._marklength == 0: 925 self._file.seek(curpos, 0) 926 return 927 self._file.seek(self._form_length_pos, 0) 928 dummy = self._write_form_length(datalength) 929 self._file.seek(self._nframes_pos, 0) 930 _write_ulong(self._file, self._nframeswritten) 931 self._file.seek(self._ssnd_length_pos, 0) 932 _write_ulong(self._file, datalength + 8) 933 self._file.seek(curpos, 0) 934 self._nframes = self._nframeswritten 935 self._datalength = datalength 936 937 def _writemarkers(self): 938 if len(self._markers) == 0: 939 return 940 self._file.write('MARK') 941 length = 2 942 for marker in self._markers: 943 id, pos, name = marker 944 length = length + len(name) + 1 + 6 945 if len(name) & 1 == 0: 946 length = length + 1 947 _write_ulong(self._file, length) 948 self._marklength = length + 8 949 _write_short(self._file, len(self._markers)) 950 for marker in self._markers: 951 id, pos, name = marker 952 _write_short(self._file, id) 953 _write_ulong(self._file, pos) 954 _write_string(self._file, name) 955 956def open(f, mode=None): 957 if mode is None: 958 if hasattr(f, 'mode'): 959 mode = f.mode 960 else: 961 mode = 'rb' 962 if mode in ('r', 'rb'): 963 return Aifc_read(f) 964 elif mode in ('w', 'wb'): 965 return Aifc_write(f) 966 else: 967 raise Error, "mode must be 'r', 'rb', 'w', or 'wb'" 968 969openfp = open # B/W compatibility 970 971if __name__ == '__main__': 972 import sys 973 if not sys.argv[1:]: 974 sys.argv.append('/usr/demos/data/audio/bach.aiff') 975 fn = sys.argv[1] 976 f = open(fn, 'r') 977 try: 978 print "Reading", fn 979 print "nchannels =", f.getnchannels() 980 print "nframes =", f.getnframes() 981 print "sampwidth =", f.getsampwidth() 982 print "framerate =", f.getframerate() 983 print "comptype =", f.getcomptype() 984 print "compname =", f.getcompname() 985 if sys.argv[2:]: 986 gn = sys.argv[2] 987 print "Writing", gn 988 g = open(gn, 'w') 989 try: 990 g.setparams(f.getparams()) 991 while 1: 992 data = f.readframes(1024) 993 if not data: 994 break 995 g.writeframes(data) 996 finally: 997 g.close() 998 print "Done." 999 finally: 1000 f.close() 1001