0001 """Stuff to parse AIFF-C and AIFF files. 0002 0003 Unless explicitly stated otherwise, the description below is true 0004 both for AIFF-C files and AIFF files. 0005 0006 An AIFF-C file has the following structure. 0007 0008 +-----------------+ 0009 | FORM | 0010 +-----------------+ 0011 | <size> | 0012 +----+------------+ 0013 | | AIFC | 0014 | +------------+ 0015 | | <chunks> | 0016 | | . | 0017 | | . | 0018 | | . | 0019 +----+------------+ 0020 0021 An AIFF file has the string "AIFF" instead of "AIFC". 0022 0023 A chunk consists of an identifier (4 bytes) followed by a size (4 bytes, 0024 big endian order), followed by the data. The size field does not include 0025 the size of the 8 byte header. 0026 0027 The following chunk types are recognized. 0028 0029 FVER 0030 <version number of AIFF-C defining document> (AIFF-C only). 0031 MARK 0032 <# of markers> (2 bytes) 0033 list of markers: 0034 <marker ID> (2 bytes, must be > 0) 0035 <position> (4 bytes) 0036 <marker name> ("pstring") 0037 COMM 0038 <# of channels> (2 bytes) 0039 <# of sound frames> (4 bytes) 0040 <size of the samples> (2 bytes) 0041 <sampling frequency> (10 bytes, IEEE 80-bit extended 0042 floating point) 0043 in AIFF-C files only: 0044 <compression type> (4 bytes) 0045 <human-readable version of compression type> ("pstring") 0046 SSND 0047 <offset> (4 bytes, not used by this program) 0048 <blocksize> (4 bytes, not used by this program) 0049 <sound data> 0050 0051 A pstring consists of 1 byte length, a string of characters, and 0 or 1 0052 byte pad to make the total length even. 0053 0054 Usage. 0055 0056 Reading AIFF files: 0057 f = aifc.open(file, 'r') 0058 where file is either the name of a file or an open file pointer. 0059 The open file pointer must have methods read(), seek(), and close(). 0060 In some types of audio files, if the setpos() method is not used, 0061 the seek() method is not necessary. 0062 0063 This returns an instance of a class with the following public methods: 0064 getnchannels() -- returns number of audio channels (1 for 0065 mono, 2 for stereo) 0066 getsampwidth() -- returns sample width in bytes 0067 getframerate() -- returns sampling frequency 0068 getnframes() -- returns number of audio frames 0069 getcomptype() -- returns compression type ('NONE' for AIFF files) 0070 getcompname() -- returns human-readable version of 0071 compression type ('not compressed' for AIFF files) 0072 getparams() -- returns a tuple consisting of all of the 0073 above in the above order 0074 getmarkers() -- get the list of marks in the audio file or None 0075 if there are no marks 0076 getmark(id) -- get mark with the specified id (raises an error 0077 if the mark does not exist) 0078 readframes(n) -- returns at most n frames of audio 0079 rewind() -- rewind to the beginning of the audio stream 0080 setpos(pos) -- seek to the specified position 0081 tell() -- return the current position 0082 close() -- close the instance (make it unusable) 0083 The position returned by tell(), the position given to setpos() and 0084 the position of marks are all compatible and have nothing to do with 0085 the actual position in the file. 0086 The close() method is called automatically when the class instance 0087 is destroyed. 0088 0089 Writing AIFF files: 0090 f = aifc.open(file, 'w') 0091 where file is either the name of a file or an open file pointer. 0092 The open file pointer must have methods write(), tell(), seek(), and 0093 close(). 0094 0095 This returns an instance of a class with the following public methods: 0096 aiff() -- create an AIFF file (AIFF-C default) 0097 aifc() -- create an AIFF-C file 0098 setnchannels(n) -- set the number of channels 0099 setsampwidth(n) -- set the sample width 0100 setframerate(n) -- set the frame rate 0101 setnframes(n) -- set the number of frames 0102 setcomptype(type, name) 0103 -- set the compression type and the 0104 human-readable compression type 0105 setparams(tuple) 0106 -- set all parameters at once 0107 setmark(id, pos, name) 0108 -- add specified mark to the list of marks 0109 tell() -- return current position in output file (useful 0110 in combination with setmark()) 0111 writeframesraw(data) 0112 -- write audio frames without pathing up the 0113 file header 0114 writeframes(data) 0115 -- write audio frames and patch up the file header 0116 close() -- patch up the file header and close the 0117 output file 0118 You should set the parameters before the first writeframesraw or 0119 writeframes. The total number of frames does not need to be set, 0120 but when it is set to the correct value, the header does not have to 0121 be patched up. 0122 It is best to first set all parameters, perhaps possibly the 0123 compression type, and then write audio frames using writeframesraw. 0124 When all frames have been written, either call writeframes('') or 0125 close() to patch up the sizes in the header. 0126 Marks can be added anytime. If there are any marks, ypu must call 0127 close() after all frames have been written. 0128 The close() method is called automatically when the class instance 0129 is destroyed. 0130 0131 When a file is opened with the extension '.aiff', an AIFF file is 0132 written, otherwise an AIFF-C file is written. This default can be 0133 changed by calling aiff() or aifc() before the first writeframes or 0134 writeframesraw. 0135 """ 0136 0137 import struct 0138 import __builtin__ 0139 0140 __all__ = ["Error","open","openfp"] 0141 0142 class Error(Exception): 0143 pass 0144 0145 _AIFC_version = 0xA2805140L # Version 1 of AIFF-C 0146 0147 _skiplist = 'COMT', 'INST', 'MIDI', 'AESD', \ 0148 'APPL', 'NAME', 'AUTH', '(c) ', 'ANNO' 0149 0150 def _read_long(file): 0151 try: 0152 return struct.unpack('>l', file.read(4))[0] 0153 except struct.error: 0154 raise EOFError 0155 0156 def _read_ulong(file): 0157 try: 0158 return struct.unpack('>L', file.read(4))[0] 0159 except struct.error: 0160 raise EOFError 0161 0162 def _read_short(file): 0163 try: 0164 return struct.unpack('>h', file.read(2))[0] 0165 except struct.error: 0166 raise EOFError 0167 0168 def _read_string(file): 0169 length = ord(file.read(1)) 0170 if length == 0: 0171 data = '' 0172 else: 0173 data = file.read(length) 0174 if length & 1 == 0: 0175 dummy = file.read(1) 0176 return data 0177 0178 _HUGE_VAL = 1.79769313486231e+308 # See <limits.h> 0179 0180 def _read_float(f): # 10 bytes 0181 expon = _read_short(f) # 2 bytes 0182 sign = 1 0183 if expon < 0: 0184 sign = -1 0185 expon = expon + 0x8000 0186 himant = _read_ulong(f) # 4 bytes 0187 lomant = _read_ulong(f) # 4 bytes 0188 if expon == himant == lomant == 0: 0189 f = 0.0 0190 elif expon == 0x7FFF: 0191 f = _HUGE_VAL 0192 else: 0193 expon = expon - 16383 0194 f = (himant * 0x100000000L + lomant) * pow(2.0, expon - 63) 0195 return sign * f 0196 0197 def _write_short(f, x): 0198 f.write(struct.pack('>h', x)) 0199 0200 def _write_long(f, x): 0201 f.write(struct.pack('>L', x)) 0202 0203 def _write_string(f, s): 0204 f.write(chr(len(s))) 0205 f.write(s) 0206 if len(s) & 1 == 0: 0207 f.write(chr(0)) 0208 0209 def _write_float(f, x): 0210 import math 0211 if x < 0: 0212 sign = 0x8000 0213 x = x * -1 0214 else: 0215 sign = 0 0216 if x == 0: 0217 expon = 0 0218 himant = 0 0219 lomant = 0 0220 else: 0221 fmant, expon = math.frexp(x) 0222 if expon > 16384 or fmant >= 1: # Infinity or NaN 0223 expon = sign|0x7FFF 0224 himant = 0 0225 lomant = 0 0226 else: # Finite 0227 expon = expon + 16382 0228 if expon < 0: # denormalized 0229 fmant = math.ldexp(fmant, expon) 0230 expon = 0 0231 expon = expon | sign 0232 fmant = math.ldexp(fmant, 32) 0233 fsmant = math.floor(fmant) 0234 himant = long(fsmant) 0235 fmant = math.ldexp(fmant - fsmant, 32) 0236 fsmant = math.floor(fmant) 0237 lomant = long(fsmant) 0238 _write_short(f, expon) 0239 _write_long(f, himant) 0240 _write_long(f, lomant) 0241 0242 from chunk import Chunk 0243 0244 class Aifc_read: 0245 # Variables used in this class: 0246 # 0247 # These variables are available to the user though appropriate 0248 # methods of this class: 0249 # _file -- the open file with methods read(), close(), and seek() 0250 # set through the __init__() method 0251 # _nchannels -- the number of audio channels 0252 # available through the getnchannels() method 0253 # _nframes -- the number of audio frames 0254 # available through the getnframes() method 0255 # _sampwidth -- the number of bytes per audio sample 0256 # available through the getsampwidth() method 0257 # _framerate -- the sampling frequency 0258 # available through the getframerate() method 0259 # _comptype -- the AIFF-C compression type ('NONE' if AIFF) 0260 # available through the getcomptype() method 0261 # _compname -- the human-readable AIFF-C compression type 0262 # available through the getcomptype() method 0263 # _markers -- the marks in the audio file 0264 # available through the getmarkers() and getmark() 0265 # methods 0266 # _soundpos -- the position in the audio stream 0267 # available through the tell() method, set through the 0268 # setpos() method 0269 # 0270 # These variables are used internally only: 0271 # _version -- the AIFF-C version number 0272 # _decomp -- the decompressor from builtin module cl 0273 # _comm_chunk_read -- 1 iff the COMM chunk has been read 0274 # _aifc -- 1 iff reading an AIFF-C file 0275 # _ssnd_seek_needed -- 1 iff positioned correctly in audio 0276 # file for readframes() 0277 # _ssnd_chunk -- instantiation of a chunk class for the SSND chunk 0278 # _framesize -- size of one frame in the file 0279 0280 def initfp(self, file): 0281 self._version = 0 0282 self._decomp = None 0283 self._convert = None 0284 self._markers = [] 0285 self._soundpos = 0 0286 self._file = Chunk(file) 0287 if self._file.getname() != 'FORM': 0288 raise Error, 'file does not start with FORM id' 0289 formdata = self._file.read(4) 0290 if formdata == 'AIFF': 0291 self._aifc = 0 0292 elif formdata == 'AIFC': 0293 self._aifc = 1 0294 else: 0295 raise Error, 'not an AIFF or AIFF-C file' 0296 self._comm_chunk_read = 0 0297 while 1: 0298 self._ssnd_seek_needed = 1 0299 try: 0300 chunk = Chunk(self._file) 0301 except EOFError: 0302 break 0303 chunkname = chunk.getname() 0304 if chunkname == 'COMM': 0305 self._read_comm_chunk(chunk) 0306 self._comm_chunk_read = 1 0307 elif chunkname == 'SSND': 0308 self._ssnd_chunk = chunk 0309 dummy = chunk.read(8) 0310 self._ssnd_seek_needed = 0 0311 elif chunkname == 'FVER': 0312 self._version = _read_ulong(chunk) 0313 elif chunkname == 'MARK': 0314 self._readmark(chunk) 0315 elif chunkname in _skiplist: 0316 pass 0317 else: 0318 raise Error, 'unrecognized chunk type '+chunk.chunkname 0319 chunk.skip() 0320 if not self._comm_chunk_read or not self._ssnd_chunk: 0321 raise Error, 'COMM chunk and/or SSND chunk missing' 0322 if self._aifc and self._decomp: 0323 import cl 0324 params = [cl.ORIGINAL_FORMAT, 0, 0325 cl.BITS_PER_COMPONENT, self._sampwidth * 8, 0326 cl.FRAME_RATE, self._framerate] 0327 if self._nchannels == 1: 0328 params[1] = cl.MONO 0329 elif self._nchannels == 2: 0330 params[1] = cl.STEREO_INTERLEAVED 0331 else: 0332 raise Error, 'cannot compress more than 2 channels' 0333 self._decomp.SetParams(params) 0334 0335 def __init__(self, f): 0336 if type(f) == type(''): 0337 f = __builtin__.open(f, 'rb') 0338 # else, assume it is an open file object already 0339 self.initfp(f) 0340 0341 # 0342 # User visible methods. 0343 # 0344 def getfp(self): 0345 return self._file 0346 0347 def rewind(self): 0348 self._ssnd_seek_needed = 1 0349 self._soundpos = 0 0350 0351 def close(self): 0352 if self._decomp: 0353 self._decomp.CloseDecompressor() 0354 self._decomp = None 0355 self._file = None 0356 0357 def tell(self): 0358 return self._soundpos 0359 0360 def getnchannels(self): 0361 return self._nchannels 0362 0363 def getnframes(self): 0364 return self._nframes 0365 0366 def getsampwidth(self): 0367 return self._sampwidth 0368 0369 def getframerate(self): 0370 return self._framerate 0371 0372 def getcomptype(self): 0373 return self._comptype 0374 0375 def getcompname(self): 0376 return self._compname 0377 0378 ## def getversion(self): 0379 ## return self._version 0380 0381 def getparams(self): 0382 return self.getnchannels(), self.getsampwidth(), \ 0383 self.getframerate(), self.getnframes(), \ 0384 self.getcomptype(), self.getcompname() 0385 0386 def getmarkers(self): 0387 if len(self._markers) == 0: 0388 return None 0389 return self._markers 0390 0391 def getmark(self, id): 0392 for marker in self._markers: 0393 if id == marker[0]: 0394 return marker 0395 raise Error, 'marker %r does not exist' % (id,) 0396 0397 def setpos(self, pos): 0398 if pos < 0 or pos > self._nframes: 0399 raise Error, 'position not in range' 0400 self._soundpos = pos 0401 self._ssnd_seek_needed = 1 0402 0403 def readframes(self, nframes): 0404 if self._ssnd_seek_needed: 0405 self._ssnd_chunk.seek(0) 0406 dummy = self._ssnd_chunk.read(8) 0407 pos = self._soundpos * self._framesize 0408 if pos: 0409 self._ssnd_chunk.seek(pos + 8) 0410 self._ssnd_seek_needed = 0 0411 if nframes == 0: 0412 return '' 0413 data = self._ssnd_chunk.read(nframes * self._framesize) 0414 if self._convert and data: 0415 data = self._convert(data) 0416 self._soundpos = self._soundpos + len(data) / (self._nchannels * self._sampwidth) 0417 return data 0418 0419 # 0420 # Internal methods. 0421 # 0422 0423 def _decomp_data(self, data): 0424 import cl 0425 dummy = self._decomp.SetParam(cl.FRAME_BUFFER_SIZE, 0426 len(data) * 2) 0427 return self._decomp.Decompress(len(data) / self._nchannels, 0428 data) 0429 0430 def _ulaw2lin(self, data): 0431 import audioop 0432 return audioop.ulaw2lin(data, 2) 0433 0434 def _adpcm2lin(self, data): 0435 import audioop 0436 if not hasattr(self, '_adpcmstate'): 0437 # first time 0438 self._adpcmstate = None 0439 data, self._adpcmstate = audioop.adpcm2lin(data, 2, 0440 self._adpcmstate) 0441 return data 0442 0443 def _read_comm_chunk(self, chunk): 0444 self._nchannels = _read_short(chunk) 0445 self._nframes = _read_long(chunk) 0446 self._sampwidth = (_read_short(chunk) + 7) / 8 0447 self._framerate = int(_read_float(chunk)) 0448 self._framesize = self._nchannels * self._sampwidth 0449 if self._aifc: 0450 #DEBUG: SGI's soundeditor produces a bad size :-( 0451 kludge = 0 0452 if chunk.chunksize == 18: 0453 kludge = 1 0454 print 'Warning: bad COMM chunk size' 0455 chunk.chunksize = 23 0456 #DEBUG end 0457 self._comptype = chunk.read(4) 0458 #DEBUG start 0459 if kludge: 0460 length = ord(chunk.file.read(1)) 0461 if length & 1 == 0: 0462 length = length + 1 0463 chunk.chunksize = chunk.chunksize + length 0464 chunk.file.seek(-1, 1) 0465 #DEBUG end 0466 self._compname = _read_string(chunk) 0467 if self._comptype != 'NONE': 0468 if self._comptype == 'G722': 0469 try: 0470 import audioop 0471 except ImportError: 0472 pass 0473 else: 0474 self._convert = self._adpcm2lin 0475 self._framesize = self._framesize / 4 0476 return 0477 # for ULAW and ALAW try Compression Library 0478 try: 0479 import cl 0480 except ImportError: 0481 if self._comptype == 'ULAW': 0482 try: 0483 import audioop 0484 self._convert = self._ulaw2lin 0485 self._framesize = self._framesize / 2 0486 return 0487 except ImportError: 0488 pass 0489 raise Error, 'cannot read compressed AIFF-C files' 0490 if self._comptype == 'ULAW': 0491 scheme = cl.G711_ULAW 0492 self._framesize = self._framesize / 2 0493 elif self._comptype == 'ALAW': 0494 scheme = cl.G711_ALAW 0495 self._framesize = self._framesize / 2 0496 else: 0497 raise Error, 'unsupported compression type' 0498 self._decomp = cl.OpenDecompressor(scheme) 0499 self._convert = self._decomp_data 0500 else: 0501 self._comptype = 'NONE' 0502 self._compname = 'not compressed' 0503 0504 def _readmark(self, chunk): 0505 nmarkers = _read_short(chunk) 0506 # Some files appear to contain invalid counts. 0507 # Cope with this by testing for EOF. 0508 try: 0509 for i in range(nmarkers): 0510 id = _read_short(chunk) 0511 pos = _read_long(chunk) 0512 name = _read_string(chunk) 0513 if pos or name: 0514 # some files appear to have 0515 # dummy markers consisting of 0516 # a position 0 and name '' 0517 self._markers.append((id, pos, name)) 0518 except EOFError: 0519 print 'Warning: MARK chunk contains only', 0520 print len(self._markers), 0521 if len(self._markers) == 1: print 'marker', 0522 else: print 'markers', 0523 print 'instead of', nmarkers 0524 0525 class Aifc_write: 0526 # Variables used in this class: 0527 # 0528 # These variables are user settable through appropriate methods 0529 # of this class: 0530 # _file -- the open file with methods write(), close(), tell(), seek() 0531 # set through the __init__() method 0532 # _comptype -- the AIFF-C compression type ('NONE' in AIFF) 0533 # set through the setcomptype() or setparams() method 0534 # _compname -- the human-readable AIFF-C compression type 0535 # set through the setcomptype() or setparams() method 0536 # _nchannels -- the number of audio channels 0537 # set through the setnchannels() or setparams() method 0538 # _sampwidth -- the number of bytes per audio sample 0539 # set through the setsampwidth() or setparams() method 0540 # _framerate -- the sampling frequency 0541 # set through the setframerate() or setparams() method 0542 # _nframes -- the number of audio frames written to the header 0543 # set through the setnframes() or setparams() method 0544 # _aifc -- whether we're writing an AIFF-C file or an AIFF file 0545 # set through the aifc() method, reset through the 0546 # aiff() method 0547 # 0548 # These variables are used internally only: 0549 # _version -- the AIFF-C version number 0550 # _comp -- the compressor from builtin module cl 0551 # _nframeswritten -- the number of audio frames actually written 0552 # _datalength -- the size of the audio samples written to the header 0553 # _datawritten -- the size of the audio samples actually written 0554 0555 def __init__(self, f): 0556 if type(f) == type(''): 0557 filename = f 0558 f = __builtin__.open(f, 'wb') 0559 else: 0560 # else, assume it is an open file object already 0561 filename = '???' 0562 self.initfp(f) 0563 if filename[-5:] == '.aiff': 0564 self._aifc = 0 0565 else: 0566 self._aifc = 1 0567 0568 def initfp(self, file): 0569 self._file = file 0570 self._version = _AIFC_version 0571 self._comptype = 'NONE' 0572 self._compname = 'not compressed' 0573 self._comp = None 0574 self._convert = None 0575 self._nchannels = 0 0576 self._sampwidth = 0 0577 self._framerate = 0 0578 self._nframes = 0 0579 self._nframeswritten = 0 0580 self._datawritten = 0 0581 self._datalength = 0 0582 self._markers = [] 0583 self._marklength = 0 0584 self._aifc = 1 # AIFF-C is default 0585 0586 def __del__(self): 0587 if self._file: 0588 self.close() 0589 0590 # 0591 # User visible methods. 0592 # 0593 def aiff(self): 0594 if self._nframeswritten: 0595 raise Error, 'cannot change parameters after starting to write' 0596 self._aifc = 0 0597 0598 def aifc(self): 0599 if self._nframeswritten: 0600 raise Error, 'cannot change parameters after starting to write' 0601 self._aifc = 1 0602 0603 def setnchannels(self, nchannels): 0604 if self._nframeswritten: 0605 raise Error, 'cannot change parameters after starting to write' 0606 if nchannels < 1: 0607 raise Error, 'bad # of channels' 0608 self._nchannels = nchannels 0609 0610 def getnchannels(self): 0611 if not self._nchannels: 0612 raise Error, 'number of channels not set' 0613 return self._nchannels 0614 0615 def setsampwidth(self, sampwidth): 0616 if self._nframeswritten: 0617 raise Error, 'cannot change parameters after starting to write' 0618 if sampwidth < 1 or sampwidth > 4: 0619 raise Error, 'bad sample width' 0620 self._sampwidth = sampwidth 0621 0622 def getsampwidth(self): 0623 if not self._sampwidth: 0624 raise Error, 'sample width not set' 0625 return self._sampwidth 0626 0627 def setframerate(self, framerate): 0628 if self._nframeswritten: 0629 raise Error, 'cannot change parameters after starting to write' 0630 if framerate <= 0: 0631 raise Error, 'bad frame rate' 0632 self._framerate = framerate 0633 0634 def getframerate(self): 0635 if not self._framerate: 0636 raise Error, 'frame rate not set' 0637 return self._framerate 0638 0639 def setnframes(self, nframes): 0640 if self._nframeswritten: 0641 raise Error, 'cannot change parameters after starting to write' 0642 self._nframes = nframes 0643 0644 def getnframes(self): 0645 return self._nframeswritten 0646 0647 def setcomptype(self, comptype, compname): 0648 if self._nframeswritten: 0649 raise Error, 'cannot change parameters after starting to write' 0650 if comptype not in ('NONE', 'ULAW', 'ALAW', 'G722'): 0651 raise Error, 'unsupported compression type' 0652 self._comptype = comptype 0653 self._compname = compname 0654 0655 def getcomptype(self): 0656 return self._comptype 0657 0658 def getcompname(self): 0659 return self._compname 0660 0661 ## def setversion(self, version): 0662 ## if self._nframeswritten: 0663 ## raise Error, 'cannot change parameters after starting to write' 0664 ## self._version = version 0665 0666 def setparams(self, (nchannels, sampwidth, framerate, nframes, comptype, compname)): 0667 if self._nframeswritten: 0668 raise Error, 'cannot change parameters after starting to write' 0669 if comptype not in ('NONE', 'ULAW', 'ALAW', 'G722'): 0670 raise Error, 'unsupported compression type' 0671 self.setnchannels(nchannels) 0672 self.setsampwidth(sampwidth) 0673 self.setframerate(framerate) 0674 self.setnframes(nframes) 0675 self.setcomptype(comptype, compname) 0676 0677 def getparams(self): 0678 if not self._nchannels or not self._sampwidth or not self._framerate: 0679 raise Error, 'not all parameters set' 0680 return self._nchannels, self._sampwidth, self._framerate, \ 0681 self._nframes, self._comptype, self._compname 0682 0683 def setmark(self, id, pos, name): 0684 if id <= 0: 0685 raise Error, 'marker ID must be > 0' 0686 if pos < 0: 0687 raise Error, 'marker position must be >= 0' 0688 if type(name) != type(''): 0689 raise Error, 'marker name must be a string' 0690 for i in range(len(self._markers)): 0691 if id == self._markers[i][0]: 0692 self._markers[i] = id, pos, name 0693 return 0694 self._markers.append((id, pos, name)) 0695 0696 def getmark(self, id): 0697 for marker in self._markers: 0698 if id == marker[0]: 0699 return marker 0700 raise Error, 'marker %r does not exist' % (id,) 0701 0702 def getmarkers(self): 0703 if len(self._markers) == 0: 0704 return None 0705 return self._markers 0706 0707 def tell(self): 0708 return self._nframeswritten 0709 0710 def writeframesraw(self, data): 0711 self._ensure_header_written(len(data)) 0712 nframes = len(data) / (self._sampwidth * self._nchannels) 0713 if self._convert: 0714 data = self._convert(data) 0715 self._file.write(data) 0716 self._nframeswritten = self._nframeswritten + nframes 0717 self._datawritten = self._datawritten + len(data) 0718 0719 def writeframes(self, data): 0720 self.writeframesraw(data) 0721 if self._nframeswritten != self._nframes or \ 0722 self._datalength != self._datawritten: 0723 self._patchheader() 0724 0725 def close(self): 0726 self._ensure_header_written(0) 0727 if self._datawritten & 1: 0728 # quick pad to even size 0729 self._file.write(chr(0)) 0730 self._datawritten = self._datawritten + 1 0731 self._writemarkers() 0732 if self._nframeswritten != self._nframes or \ 0733 self._datalength != self._datawritten or \ 0734 self._marklength: 0735 self._patchheader() 0736 if self._comp: 0737 self._comp.CloseCompressor() 0738 self._comp = None 0739 self._file.flush() 0740 self._file = None 0741 0742 # 0743 # Internal methods. 0744 # 0745 0746 def _comp_data(self, data): 0747 import cl 0748 dummy = self._comp.SetParam(cl.FRAME_BUFFER_SIZE, len(data)) 0749 dummy = self._comp.SetParam(cl.COMPRESSED_BUFFER_SIZE, len(data)) 0750 return self._comp.Compress(self._nframes, data) 0751 0752 def _lin2ulaw(self, data): 0753 import audioop 0754 return audioop.lin2ulaw(data, 2) 0755 0756 def _lin2adpcm(self, data): 0757 import audioop 0758 if not hasattr(self, '_adpcmstate'): 0759 self._adpcmstate = None 0760 data, self._adpcmstate = audioop.lin2adpcm(data, 2, 0761 self._adpcmstate) 0762 return data 0763 0764 def _ensure_header_written(self, datasize): 0765 if not self._nframeswritten: 0766 if self._comptype in ('ULAW', 'ALAW'): 0767 if not self._sampwidth: 0768 self._sampwidth = 2 0769 if self._sampwidth != 2: 0770 raise Error, 'sample width must be 2 when compressing with ULAW or ALAW' 0771 if self._comptype == 'G722': 0772 if not self._sampwidth: 0773 self._sampwidth = 2 0774 if self._sampwidth != 2: 0775 raise Error, 'sample width must be 2 when compressing with G7.22 (ADPCM)' 0776 if not self._nchannels: 0777 raise Error, '# channels not specified' 0778 if not self._sampwidth: 0779 raise Error, 'sample width not specified' 0780 if not self._framerate: 0781 raise Error, 'sampling rate not specified' 0782 self._write_header(datasize) 0783 0784 def _init_compression(self): 0785 if self._comptype == 'G722': 0786 self._convert = self._lin2adpcm 0787 return 0788 try: 0789 import cl 0790 except ImportError: 0791 if self._comptype == 'ULAW': 0792 try: 0793 import audioop 0794 self._convert = self._lin2ulaw 0795 return 0796 except ImportError: 0797 pass 0798 raise Error, 'cannot write compressed AIFF-C files' 0799 if self._comptype == 'ULAW': 0800 scheme = cl.G711_ULAW 0801 elif self._comptype == 'ALAW': 0802 scheme = cl.G711_ALAW 0803 else: 0804 raise Error, 'unsupported compression type' 0805 self._comp = cl.OpenCompressor(scheme) 0806 params = [cl.ORIGINAL_FORMAT, 0, 0807 cl.BITS_PER_COMPONENT, self._sampwidth * 8, 0808 cl.FRAME_RATE, self._framerate, 0809 cl.FRAME_BUFFER_SIZE, 100, 0810 cl.COMPRESSED_BUFFER_SIZE, 100] 0811 if self._nchannels == 1: 0812 params[1] = cl.MONO 0813 elif self._nchannels == 2: 0814 params[1] = cl.STEREO_INTERLEAVED 0815 else: 0816 raise Error, 'cannot compress more than 2 channels' 0817 self._comp.SetParams(params) 0818 # the compressor produces a header which we ignore 0819 dummy = self._comp.Compress(0, '') 0820 self._convert = self._comp_data 0821 0822 def _write_header(self, initlength): 0823 if self._aifc and self._comptype != 'NONE': 0824 self._init_compression() 0825 self._file.write('FORM') 0826 if not self._nframes: 0827 self._nframes = initlength / (self._nchannels * self._sampwidth) 0828 self._datalength = self._nframes * self._nchannels * self._sampwidth 0829 if self._datalength & 1: 0830 self._datalength = self._datalength + 1 0831 if self._aifc: 0832 if self._comptype in ('ULAW', 'ALAW'): 0833 self._datalength = self._datalength / 2 0834 if self._datalength & 1: 0835 self._datalength = self._datalength + 1 0836 elif self._comptype == 'G722': 0837 self._datalength = (self._datalength + 3) / 4 0838 if self._datalength & 1: 0839 self._datalength = self._datalength + 1 0840 self._form_length_pos = self._file.tell() 0841 commlength = self._write_form_length(self._datalength) 0842 if self._aifc: 0843 self._file.write('AIFC') 0844 self._file.write('FVER') 0845 _write_long(self._file, 4) 0846 _write_long(self._file, self._version) 0847 else: 0848 self._file.write('AIFF') 0849 self._file.write('COMM') 0850 _write_long(self._file, commlength) 0851 _write_short(self._file, self._nchannels) 0852 self._nframes_pos = self._file.tell() 0853 _write_long(self._file, self._nframes) 0854 _write_short(self._file, self._sampwidth * 8) 0855 _write_float(self._file, self._framerate) 0856 if self._aifc: 0857 self._file.write(self._comptype) 0858 _write_string(self._file, self._compname) 0859 self._file.write('SSND') 0860 self._ssnd_length_pos = self._file.tell() 0861 _write_long(self._file, self._datalength + 8) 0862 _write_long(self._file, 0) 0863 _write_long(self._file, 0) 0864 0865 def _write_form_length(self, datalength): 0866 if self._aifc: 0867 commlength = 18 + 5 + len(self._compname) 0868 if commlength & 1: 0869 commlength = commlength + 1 0870 verslength = 12 0871 else: 0872 commlength = 18 0873 verslength = 0 0874 _write_long(self._file, 4 + verslength + self._marklength + \ 0875 8 + commlength + 16 + datalength) 0876 return commlength 0877 0878 def _patchheader(self): 0879 curpos = self._file.tell() 0880 if self._datawritten & 1: 0881 datalength = self._datawritten + 1 0882 self._file.write(chr(0)) 0883 else: 0884 datalength = self._datawritten 0885 if datalength == self._datalength and \ 0886 self._nframes == self._nframeswritten and \ 0887 self._marklength == 0: 0888 self._file.seek(curpos, 0) 0889 return 0890 self._file.seek(self._form_length_pos, 0) 0891 dummy = self._write_form_length(datalength) 0892 self._file.seek(self._nframes_pos, 0) 0893 _write_long(self._file, self._nframeswritten) 0894 self._file.seek(self._ssnd_length_pos, 0) 0895 _write_long(self._file, datalength + 8) 0896 self._file.seek(curpos, 0) 0897 self._nframes = self._nframeswritten 0898 self._datalength = datalength 0899 0900 def _writemarkers(self): 0901 if len(self._markers) == 0: 0902 return 0903 self._file.write('MARK') 0904 length = 2 0905 for marker in self._markers: 0906 id, pos, name = marker 0907 length = length + len(name) + 1 + 6 0908 if len(name) & 1 == 0: 0909 length = length + 1 0910 _write_long(self._file, length) 0911 self._marklength = length + 8 0912 _write_short(self._file, len(self._markers)) 0913 for marker in self._markers: 0914 id, pos, name = marker 0915 _write_short(self._file, id) 0916 _write_long(self._file, pos) 0917 _write_string(self._file, name) 0918 0919 def open(f, mode=None): 0920 if mode is None: 0921 if hasattr(f, 'mode'): 0922 mode = f.mode 0923 else: 0924 mode = 'rb' 0925 if mode in ('r', 'rb'): 0926 return Aifc_read(f) 0927 elif mode in ('w', 'wb'): 0928 return Aifc_write(f) 0929 else: 0930 raise Error, "mode must be 'r', 'rb', 'w', or 'wb'" 0931 0932 openfp = open # B/W compatibility 0933 0934 if __name__ == '__main__': 0935 import sys 0936 if not sys.argv[1:]: 0937 sys.argv.append('/usr/demos/data/audio/bach.aiff') 0938 fn = sys.argv[1] 0939 f = open(fn, 'r') 0940 print "Reading", fn 0941 print "nchannels =", f.getnchannels() 0942 print "nframes =", f.getnframes() 0943 print "sampwidth =", f.getsampwidth() 0944 print "framerate =", f.getframerate() 0945 print "comptype =", f.getcomptype() 0946 print "compname =", f.getcompname() 0947 if sys.argv[2:]: 0948 gn = sys.argv[2] 0949 print "Writing", gn 0950 g = open(gn, 'w') 0951 g.setparams(f.getparams()) 0952 while 1: 0953 data = f.readframes(1024) 0954 if not data: 0955 break 0956 g.writeframes(data) 0957 g.close() 0958 f.close() 0959 print "Done." 0960
Generated by PyXR 0.9.4