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