0001 """Stuff to parse WAVE files. 0002 0003 Usage. 0004 0005 Reading WAVE files: 0006 f = wave.open(file, 'r') 0007 where file is either the name of a file or an open file pointer. 0008 The open file pointer must have methods read(), seek(), and close(). 0009 When the setpos() and rewind() methods are not used, the seek() 0010 method is not necessary. 0011 0012 This returns an instance of a class with the following public methods: 0013 getnchannels() -- returns number of audio channels (1 for 0014 mono, 2 for stereo) 0015 getsampwidth() -- returns sample width in bytes 0016 getframerate() -- returns sampling frequency 0017 getnframes() -- returns number of audio frames 0018 getcomptype() -- returns compression type ('NONE' for linear samples) 0019 getcompname() -- returns human-readable version of 0020 compression type ('not compressed' linear samples) 0021 getparams() -- returns a tuple consisting of all of the 0022 above in the above order 0023 getmarkers() -- returns None (for compatibility with the 0024 aifc module) 0025 getmark(id) -- raises an error since the mark does not 0026 exist (for compatibility with the aifc module) 0027 readframes(n) -- returns at most n frames of audio 0028 rewind() -- rewind to the beginning of the audio stream 0029 setpos(pos) -- seek to the specified position 0030 tell() -- return the current position 0031 close() -- close the instance (make it unusable) 0032 The position returned by tell() and the position given to setpos() 0033 are compatible and have nothing to do with the actual position in the 0034 file. 0035 The close() method is called automatically when the class instance 0036 is destroyed. 0037 0038 Writing WAVE files: 0039 f = wave.open(file, 'w') 0040 where file is either the name of a file or an open file pointer. 0041 The open file pointer must have methods write(), tell(), seek(), and 0042 close(). 0043 0044 This returns an instance of a class with the following public methods: 0045 setnchannels(n) -- set the number of channels 0046 setsampwidth(n) -- set the sample width 0047 setframerate(n) -- set the frame rate 0048 setnframes(n) -- set the number of frames 0049 setcomptype(type, name) 0050 -- set the compression type and the 0051 human-readable compression type 0052 setparams(tuple) 0053 -- set all parameters at once 0054 tell() -- return current position in output file 0055 writeframesraw(data) 0056 -- write audio frames without pathing up the 0057 file header 0058 writeframes(data) 0059 -- write audio frames and patch up the file header 0060 close() -- patch up the file header and close the 0061 output file 0062 You should set the parameters before the first writeframesraw or 0063 writeframes. The total number of frames does not need to be set, 0064 but when it is set to the correct value, the header does not have to 0065 be patched up. 0066 It is best to first set all parameters, perhaps possibly the 0067 compression type, and then write audio frames using writeframesraw. 0068 When all frames have been written, either call writeframes('') or 0069 close() to patch up the sizes in the header. 0070 The close() method is called automatically when the class instance 0071 is destroyed. 0072 """ 0073 0074 import __builtin__ 0075 0076 __all__ = ["open", "openfp", "Error"] 0077 0078 class Error(Exception): 0079 pass 0080 0081 WAVE_FORMAT_PCM = 0x0001 0082 0083 _array_fmts = None, 'b', 'h', None, 'l' 0084 0085 # Determine endian-ness 0086 import struct 0087 if struct.pack("h", 1) == "\000\001": 0088 big_endian = 1 0089 else: 0090 big_endian = 0 0091 0092 from chunk import Chunk 0093 0094 class Wave_read: 0095 """Variables used in this class: 0096 0097 These variables are available to the user though appropriate 0098 methods of this class: 0099 _file -- the open file with methods read(), close(), and seek() 0100 set through the __init__() method 0101 _nchannels -- the number of audio channels 0102 available through the getnchannels() method 0103 _nframes -- the number of audio frames 0104 available through the getnframes() method 0105 _sampwidth -- the number of bytes per audio sample 0106 available through the getsampwidth() method 0107 _framerate -- the sampling frequency 0108 available through the getframerate() method 0109 _comptype -- the AIFF-C compression type ('NONE' if AIFF) 0110 available through the getcomptype() method 0111 _compname -- the human-readable AIFF-C compression type 0112 available through the getcomptype() method 0113 _soundpos -- the position in the audio stream 0114 available through the tell() method, set through the 0115 setpos() method 0116 0117 These variables are used internally only: 0118 _fmt_chunk_read -- 1 iff the FMT chunk has been read 0119 _data_seek_needed -- 1 iff positioned correctly in audio 0120 file for readframes() 0121 _data_chunk -- instantiation of a chunk class for the DATA chunk 0122 _framesize -- size of one frame in the file 0123 """ 0124 0125 def initfp(self, file): 0126 self._convert = None 0127 self._soundpos = 0 0128 self._file = Chunk(file, bigendian = 0) 0129 if self._file.getname() != 'RIFF': 0130 raise Error, 'file does not start with RIFF id' 0131 if self._file.read(4) != 'WAVE': 0132 raise Error, 'not a WAVE file' 0133 self._fmt_chunk_read = 0 0134 self._data_chunk = None 0135 while 1: 0136 self._data_seek_needed = 1 0137 try: 0138 chunk = Chunk(self._file, bigendian = 0) 0139 except EOFError: 0140 break 0141 chunkname = chunk.getname() 0142 if chunkname == 'fmt ': 0143 self._read_fmt_chunk(chunk) 0144 self._fmt_chunk_read = 1 0145 elif chunkname == 'data': 0146 if not self._fmt_chunk_read: 0147 raise Error, 'data chunk before fmt chunk' 0148 self._data_chunk = chunk 0149 self._nframes = chunk.chunksize // self._framesize 0150 self._data_seek_needed = 0 0151 break 0152 chunk.skip() 0153 if not self._fmt_chunk_read or not self._data_chunk: 0154 raise Error, 'fmt chunk and/or data chunk missing' 0155 0156 def __init__(self, f): 0157 self._i_opened_the_file = None 0158 if isinstance(f, basestring): 0159 f = __builtin__.open(f, 'rb') 0160 self._i_opened_the_file = f 0161 # else, assume it is an open file object already 0162 self.initfp(f) 0163 0164 def __del__(self): 0165 self.close() 0166 # 0167 # User visible methods. 0168 # 0169 def getfp(self): 0170 return self._file 0171 0172 def rewind(self): 0173 self._data_seek_needed = 1 0174 self._soundpos = 0 0175 0176 def close(self): 0177 if self._i_opened_the_file: 0178 self._i_opened_the_file.close() 0179 self._i_opened_the_file = None 0180 self._file = None 0181 0182 def tell(self): 0183 return self._soundpos 0184 0185 def getnchannels(self): 0186 return self._nchannels 0187 0188 def getnframes(self): 0189 return self._nframes 0190 0191 def getsampwidth(self): 0192 return self._sampwidth 0193 0194 def getframerate(self): 0195 return self._framerate 0196 0197 def getcomptype(self): 0198 return self._comptype 0199 0200 def getcompname(self): 0201 return self._compname 0202 0203 def getparams(self): 0204 return self.getnchannels(), self.getsampwidth(), \ 0205 self.getframerate(), self.getnframes(), \ 0206 self.getcomptype(), self.getcompname() 0207 0208 def getmarkers(self): 0209 return None 0210 0211 def getmark(self, id): 0212 raise Error, 'no marks' 0213 0214 def setpos(self, pos): 0215 if pos < 0 or pos > self._nframes: 0216 raise Error, 'position not in range' 0217 self._soundpos = pos 0218 self._data_seek_needed = 1 0219 0220 def readframes(self, nframes): 0221 if self._data_seek_needed: 0222 self._data_chunk.seek(0, 0) 0223 pos = self._soundpos * self._framesize 0224 if pos: 0225 self._data_chunk.seek(pos, 0) 0226 self._data_seek_needed = 0 0227 if nframes == 0: 0228 return '' 0229 if self._sampwidth > 1 and big_endian: 0230 # unfortunately the fromfile() method does not take 0231 # something that only looks like a file object, so 0232 # we have to reach into the innards of the chunk object 0233 import array 0234 chunk = self._data_chunk 0235 data = array.array(_array_fmts[self._sampwidth]) 0236 nitems = nframes * self._nchannels 0237 if nitems * self._sampwidth > chunk.chunksize - chunk.size_read: 0238 nitems = (chunk.chunksize - chunk.size_read) / self._sampwidth 0239 data.fromfile(chunk.file.file, nitems) 0240 # "tell" data chunk how much was read 0241 chunk.size_read = chunk.size_read + nitems * self._sampwidth 0242 # do the same for the outermost chunk 0243 chunk = chunk.file 0244 chunk.size_read = chunk.size_read + nitems * self._sampwidth 0245 data.byteswap() 0246 data = data.tostring() 0247 else: 0248 data = self._data_chunk.read(nframes * self._framesize) 0249 if self._convert and data: 0250 data = self._convert(data) 0251 self._soundpos = self._soundpos + len(data) // (self._nchannels * self._sampwidth) 0252 return data 0253 0254 # 0255 # Internal methods. 0256 # 0257 0258 def _read_fmt_chunk(self, chunk): 0259 wFormatTag, self._nchannels, self._framerate, dwAvgBytesPerSec, wBlockAlign = struct.unpack('<hhllh', chunk.read(14)) 0260 if wFormatTag == WAVE_FORMAT_PCM: 0261 sampwidth = struct.unpack('<h', chunk.read(2))[0] 0262 self._sampwidth = (sampwidth + 7) // 8 0263 else: 0264 raise Error, 'unknown format: %r' % (wFormatTag,) 0265 self._framesize = self._nchannels * self._sampwidth 0266 self._comptype = 'NONE' 0267 self._compname = 'not compressed' 0268 0269 class Wave_write: 0270 """Variables used in this class: 0271 0272 These variables are user settable through appropriate methods 0273 of this class: 0274 _file -- the open file with methods write(), close(), tell(), seek() 0275 set through the __init__() method 0276 _comptype -- the AIFF-C compression type ('NONE' in AIFF) 0277 set through the setcomptype() or setparams() method 0278 _compname -- the human-readable AIFF-C compression type 0279 set through the setcomptype() or setparams() method 0280 _nchannels -- the number of audio channels 0281 set through the setnchannels() or setparams() method 0282 _sampwidth -- the number of bytes per audio sample 0283 set through the setsampwidth() or setparams() method 0284 _framerate -- the sampling frequency 0285 set through the setframerate() or setparams() method 0286 _nframes -- the number of audio frames written to the header 0287 set through the setnframes() or setparams() method 0288 0289 These variables are used internally only: 0290 _datalength -- the size of the audio samples written to the header 0291 _nframeswritten -- the number of frames actually written 0292 _datawritten -- the size of the audio samples actually written 0293 """ 0294 0295 def __init__(self, f): 0296 self._i_opened_the_file = None 0297 if isinstance(f, basestring): 0298 f = __builtin__.open(f, 'wb') 0299 self._i_opened_the_file = f 0300 self.initfp(f) 0301 0302 def initfp(self, file): 0303 self._file = file 0304 self._convert = None 0305 self._nchannels = 0 0306 self._sampwidth = 0 0307 self._framerate = 0 0308 self._nframes = 0 0309 self._nframeswritten = 0 0310 self._datawritten = 0 0311 self._datalength = 0 0312 0313 def __del__(self): 0314 self.close() 0315 0316 # 0317 # User visible methods. 0318 # 0319 def setnchannels(self, nchannels): 0320 if self._datawritten: 0321 raise Error, 'cannot change parameters after starting to write' 0322 if nchannels < 1: 0323 raise Error, 'bad # of channels' 0324 self._nchannels = nchannels 0325 0326 def getnchannels(self): 0327 if not self._nchannels: 0328 raise Error, 'number of channels not set' 0329 return self._nchannels 0330 0331 def setsampwidth(self, sampwidth): 0332 if self._datawritten: 0333 raise Error, 'cannot change parameters after starting to write' 0334 if sampwidth < 1 or sampwidth > 4: 0335 raise Error, 'bad sample width' 0336 self._sampwidth = sampwidth 0337 0338 def getsampwidth(self): 0339 if not self._sampwidth: 0340 raise Error, 'sample width not set' 0341 return self._sampwidth 0342 0343 def setframerate(self, framerate): 0344 if self._datawritten: 0345 raise Error, 'cannot change parameters after starting to write' 0346 if framerate <= 0: 0347 raise Error, 'bad frame rate' 0348 self._framerate = framerate 0349 0350 def getframerate(self): 0351 if not self._framerate: 0352 raise Error, 'frame rate not set' 0353 return self._framerate 0354 0355 def setnframes(self, nframes): 0356 if self._datawritten: 0357 raise Error, 'cannot change parameters after starting to write' 0358 self._nframes = nframes 0359 0360 def getnframes(self): 0361 return self._nframeswritten 0362 0363 def setcomptype(self, comptype, compname): 0364 if self._datawritten: 0365 raise Error, 'cannot change parameters after starting to write' 0366 if comptype not in ('NONE',): 0367 raise Error, 'unsupported compression type' 0368 self._comptype = comptype 0369 self._compname = compname 0370 0371 def getcomptype(self): 0372 return self._comptype 0373 0374 def getcompname(self): 0375 return self._compname 0376 0377 def setparams(self, (nchannels, sampwidth, framerate, nframes, comptype, compname)): 0378 if self._datawritten: 0379 raise Error, 'cannot change parameters after starting to write' 0380 self.setnchannels(nchannels) 0381 self.setsampwidth(sampwidth) 0382 self.setframerate(framerate) 0383 self.setnframes(nframes) 0384 self.setcomptype(comptype, compname) 0385 0386 def getparams(self): 0387 if not self._nchannels or not self._sampwidth or not self._framerate: 0388 raise Error, 'not all parameters set' 0389 return self._nchannels, self._sampwidth, self._framerate, \ 0390 self._nframes, self._comptype, self._compname 0391 0392 def setmark(self, id, pos, name): 0393 raise Error, 'setmark() not supported' 0394 0395 def getmark(self, id): 0396 raise Error, 'no marks' 0397 0398 def getmarkers(self): 0399 return None 0400 0401 def tell(self): 0402 return self._nframeswritten 0403 0404 def writeframesraw(self, data): 0405 self._ensure_header_written(len(data)) 0406 nframes = len(data) // (self._sampwidth * self._nchannels) 0407 if self._convert: 0408 data = self._convert(data) 0409 if self._sampwidth > 1 and big_endian: 0410 import array 0411 data = array.array(_array_fmts[self._sampwidth], data) 0412 data.byteswap() 0413 data.tofile(self._file) 0414 self._datawritten = self._datawritten + len(data) * self._sampwidth 0415 else: 0416 self._file.write(data) 0417 self._datawritten = self._datawritten + len(data) 0418 self._nframeswritten = self._nframeswritten + nframes 0419 0420 def writeframes(self, data): 0421 self.writeframesraw(data) 0422 if self._datalength != self._datawritten: 0423 self._patchheader() 0424 0425 def close(self): 0426 if self._file: 0427 self._ensure_header_written(0) 0428 if self._datalength != self._datawritten: 0429 self._patchheader() 0430 self._file.flush() 0431 self._file = None 0432 if self._i_opened_the_file: 0433 self._i_opened_the_file.close() 0434 self._i_opened_the_file = None 0435 0436 # 0437 # Internal methods. 0438 # 0439 0440 def _ensure_header_written(self, datasize): 0441 if not self._datawritten: 0442 if not self._nchannels: 0443 raise Error, '# channels not specified' 0444 if not self._sampwidth: 0445 raise Error, 'sample width not specified' 0446 if not self._framerate: 0447 raise Error, 'sampling rate not specified' 0448 self._write_header(datasize) 0449 0450 def _write_header(self, initlength): 0451 self._file.write('RIFF') 0452 if not self._nframes: 0453 self._nframes = initlength / (self._nchannels * self._sampwidth) 0454 self._datalength = self._nframes * self._nchannels * self._sampwidth 0455 self._form_length_pos = self._file.tell() 0456 self._file.write(struct.pack('<l4s4slhhllhh4s', 0457 36 + self._datalength, 'WAVE', 'fmt ', 16, 0458 WAVE_FORMAT_PCM, self._nchannels, self._framerate, 0459 self._nchannels * self._framerate * self._sampwidth, 0460 self._nchannels * self._sampwidth, 0461 self._sampwidth * 8, 'data')) 0462 self._data_length_pos = self._file.tell() 0463 self._file.write(struct.pack('<l', self._datalength)) 0464 0465 def _patchheader(self): 0466 if self._datawritten == self._datalength: 0467 return 0468 curpos = self._file.tell() 0469 self._file.seek(self._form_length_pos, 0) 0470 self._file.write(struct.pack('<l', 36 + self._datawritten)) 0471 self._file.seek(self._data_length_pos, 0) 0472 self._file.write(struct.pack('<l', self._datawritten)) 0473 self._file.seek(curpos, 0) 0474 self._datalength = self._datawritten 0475 0476 def open(f, mode=None): 0477 if mode is None: 0478 if hasattr(f, 'mode'): 0479 mode = f.mode 0480 else: 0481 mode = 'rb' 0482 if mode in ('r', 'rb'): 0483 return Wave_read(f) 0484 elif mode in ('w', 'wb'): 0485 return Wave_write(f) 0486 else: 0487 raise Error, "mode must be 'r', 'rb', 'w', or 'wb'" 0488 0489 openfp = open # B/W compatibility 0490
Generated by PyXR 0.9.4