1 """Stuff to parse WAVE files. 2 3 Usage. 4 5 Reading WAVE files: 6 f = wave.open(file, 'r') 7 where file is either the name of a file or an open file pointer. 8 The open file pointer must have methods read(), seek(), and close(). 9 When the setpos() and rewind() methods are not used, the seek() 10 method is not necessary. 11 12 This returns an instance of a class with the following public methods: 13 getnchannels() -- returns number of audio channels (1 for 14 mono, 2 for stereo) 15 getsampwidth() -- returns sample width in bytes 16 getframerate() -- returns sampling frequency 17 getnframes() -- returns number of audio frames 18 getcomptype() -- returns compression type ('NONE' for linear samples) 19 getcompname() -- returns human-readable version of 20 compression type ('not compressed' linear samples) 21 getparams() -- returns a namedtuple consisting of all of the 22 above in the above order 23 getmarkers() -- returns None (for compatibility with the 24 aifc module) 25 getmark(id) -- raises an error since the mark does not 26 exist (for compatibility with the aifc module) 27 readframes(n) -- returns at most n frames of audio 28 rewind() -- rewind to the beginning of the audio stream 29 setpos(pos) -- seek to the specified position 30 tell() -- return the current position 31 close() -- close the instance (make it unusable) 32 The position returned by tell() and the position given to setpos() 33 are compatible and have nothing to do with the actual position in the 34 file. 35 The close() method is called automatically when the class instance 36 is destroyed. 37 38 Writing WAVE files: 39 f = wave.open(file, 'w') 40 where file is either the name of a file or an open file pointer. 41 The open file pointer must have methods write(), tell(), seek(), and 42 close(). 43 44 This returns an instance of a class with the following public methods: 45 setnchannels(n) -- set the number of channels 46 setsampwidth(n) -- set the sample width 47 setframerate(n) -- set the frame rate 48 setnframes(n) -- set the number of frames 49 setcomptype(type, name) 50 -- set the compression type and the 51 human-readable compression type 52 setparams(tuple) 53 -- set all parameters at once 54 tell() -- return current position in output file 55 writeframesraw(data) 56 -- write audio frames without pathing up the 57 file header 58 writeframes(data) 59 -- write audio frames and patch up the file header 60 close() -- patch up the file header and close the 61 output file 62 You should set the parameters before the first writeframesraw or 63 writeframes. The total number of frames does not need to be set, 64 but when it is set to the correct value, the header does not have to 65 be patched up. 66 It is best to first set all parameters, perhaps possibly the 67 compression type, and then write audio frames using writeframesraw. 68 When all frames have been written, either call writeframes(b'') or 69 close() to patch up the sizes in the header. 70 The close() method is called automatically when the class instance 71 is destroyed. 72 """ 73 74 import builtins 75 76 __all__ = ["open", "openfp", "Error", "Wave_read", "Wave_write"] 77 78 class Error(Exception): 79 pass 80 81 WAVE_FORMAT_PCM = 0x0001 82 83 _array_fmts = None, 'b', 'h', None, 'i' 84 85 import audioop 86 import struct 87 import sys 88 from chunk import Chunk 89 from collections import namedtuple 90 91 _wave_params = namedtuple('_wave_params', 92 'nchannels sampwidth framerate nframes comptype compname') 93 94 class Wave_read: 95 """Variables used in this class: 96 97 These variables are available to the user though appropriate 98 methods of this class: 99 _file -- the open file with methods read(), close(), and seek() 100 set through the __init__() method 101 _nchannels -- the number of audio channels 102 available through the getnchannels() method 103 _nframes -- the number of audio frames 104 available through the getnframes() method 105 _sampwidth -- the number of bytes per audio sample 106 available through the getsampwidth() method 107 _framerate -- the sampling frequency 108 available through the getframerate() method 109 _comptype -- the AIFF-C compression type ('NONE' if AIFF) 110 available through the getcomptype() method 111 _compname -- the human-readable AIFF-C compression type 112 available through the getcomptype() method 113 _soundpos -- the position in the audio stream 114 available through the tell() method, set through the 115 setpos() method 116 117 These variables are used internally only: 118 _fmt_chunk_read -- 1 iff the FMT chunk has been read 119 _data_seek_needed -- 1 iff positioned correctly in audio 120 file for readframes() 121 _data_chunk -- instantiation of a chunk class for the DATA chunk 122 _framesize -- size of one frame in the file 123 """ 124 125 def initfp(self, file): 126 self._convert = None 127 self._soundpos = 0 128 self._file = Chunk(file, bigendian = 0) 129 if self._file.getname() != b'RIFF': 130 raise Error('file does not start with RIFF id') 131 if self._file.read(4) != b'WAVE': 132 raise Error('not a WAVE file') 133 self._fmt_chunk_read = 0 134 self._data_chunk = None 135 while 1: 136 self._data_seek_needed = 1 137 try: 138 chunk = Chunk(self._file, bigendian = 0) 139 except EOFError: 140 break 141 chunkname = chunk.getname() 142 if chunkname == b'fmt ': 143 self._read_fmt_chunk(chunk) 144 self._fmt_chunk_read = 1 145 elif chunkname == b'data': 146 if not self._fmt_chunk_read: 147 raise Error('data chunk before fmt chunk') 148 self._data_chunk = chunk 149 self._nframes = chunk.chunksize // self._framesize 150 self._data_seek_needed = 0 151 break 152 chunk.skip() 153 if not self._fmt_chunk_read or not self._data_chunk: 154 raise Error('fmt chunk and/or data chunk missing') 155 156 def __init__(self, f): 157 self._i_opened_the_file = None 158 if isinstance(f, str): 159 f = builtins.open(f, 'rb') 160 self._i_opened_the_file = f 161 # else, assume it is an open file object already 162 try: 163 self.initfp(f) 164 except: 165 if self._i_opened_the_file: 166 f.close() 167 raise 168 169 def __del__(self): 170 self.close() 171 172 def __enter__(self): 173 return self 174 175 def __exit__(self, *args): 176 self.close() 177 178 # 179 # User visible methods. 180 # 181 def getfp(self): 182 return self._file 183 184 def rewind(self): 185 self._data_seek_needed = 1 186 self._soundpos = 0 187 188 def close(self): 189 self._file = None 190 file = self._i_opened_the_file 191 if file: 192 self._i_opened_the_file = None 193 file.close() 194 195 def tell(self): 196 return self._soundpos 197 198 def getnchannels(self): 199 return self._nchannels 200 201 def getnframes(self): 202 return self._nframes 203 204 def getsampwidth(self): 205 return self._sampwidth 206 207 def getframerate(self): 208 return self._framerate 209 210 def getcomptype(self): 211 return self._comptype 212 213 def getcompname(self): 214 return self._compname 215 216 def getparams(self): 217 return _wave_params(self.getnchannels(), self.getsampwidth(), 218 self.getframerate(), self.getnframes(), 219 self.getcomptype(), self.getcompname()) 220 221 def getmarkers(self): 222 return None 223 224 def getmark(self, id): 225 raise Error('no marks') 226 227 def setpos(self, pos): 228 if pos < 0 or pos > self._nframes: 229 raise Error('position not in range') 230 self._soundpos = pos 231 self._data_seek_needed = 1 232 233 def readframes(self, nframes): 234 if self._data_seek_needed: 235 self._data_chunk.seek(0, 0) 236 pos = self._soundpos * self._framesize 237 if pos: 238 self._data_chunk.seek(pos, 0) 239 self._data_seek_needed = 0 240 if nframes == 0: 241 return b'' 242 data = self._data_chunk.read(nframes * self._framesize) 243 if self._sampwidth != 1 and sys.byteorder == 'big': 244 data = audioop.byteswap(data, self._sampwidth) 245 if self._convert and data: 246 data = self._convert(data) 247 self._soundpos = self._soundpos + len(data) // (self._nchannels * self._sampwidth) 248 return data 249 250 # 251 # Internal methods. 252 # 253 254 def _read_fmt_chunk(self, chunk): 255 wFormatTag, self._nchannels, self._framerate, dwAvgBytesPerSec, wBlockAlign = struct.unpack_from('<HHLLH', chunk.read(14)) 256 if wFormatTag == WAVE_FORMAT_PCM: 257 sampwidth = struct.unpack_from('<H', chunk.read(2))[0] 258 self._sampwidth = (sampwidth + 7) // 8 259 else: 260 raise Error('unknown format: %r' % (wFormatTag,)) 261 self._framesize = self._nchannels * self._sampwidth 262 self._comptype = 'NONE' 263 self._compname = 'not compressed' 264 265 class Wave_write: 266 """Variables used in this class: 267 268 These variables are user settable through appropriate methods 269 of this class: 270 _file -- the open file with methods write(), close(), tell(), seek() 271 set through the __init__() method 272 _comptype -- the AIFF-C compression type ('NONE' in AIFF) 273 set through the setcomptype() or setparams() method 274 _compname -- the human-readable AIFF-C compression type 275 set through the setcomptype() or setparams() method 276 _nchannels -- the number of audio channels 277 set through the setnchannels() or setparams() method 278 _sampwidth -- the number of bytes per audio sample 279 set through the setsampwidth() or setparams() method 280 _framerate -- the sampling frequency 281 set through the setframerate() or setparams() method 282 _nframes -- the number of audio frames written to the header 283 set through the setnframes() or setparams() method 284 285 These variables are used internally only: 286 _datalength -- the size of the audio samples written to the header 287 _nframeswritten -- the number of frames actually written 288 _datawritten -- the size of the audio samples actually written 289 """ 290 291 def __init__(self, f): 292 self._i_opened_the_file = None 293 if isinstance(f, str): 294 f = builtins.open(f, 'wb') 295 self._i_opened_the_file = f 296 try: 297 self.initfp(f) 298 except: 299 if self._i_opened_the_file: 300 f.close() 301 raise 302 303 def initfp(self, file): 304 self._file = file 305 self._convert = None 306 self._nchannels = 0 307 self._sampwidth = 0 308 self._framerate = 0 309 self._nframes = 0 310 self._nframeswritten = 0 311 self._datawritten = 0 312 self._datalength = 0 313 self._headerwritten = False 314 315 def __del__(self): 316 self.close() 317 318 def __enter__(self): 319 return self 320 321 def __exit__(self, *args): 322 self.close() 323 324 # 325 # User visible methods. 326 # 327 def setnchannels(self, nchannels): 328 if self._datawritten: 329 raise Error('cannot change parameters after starting to write') 330 if nchannels < 1: 331 raise Error('bad # of channels') 332 self._nchannels = nchannels 333 334 def getnchannels(self): 335 if not self._nchannels: 336 raise Error('number of channels not set') 337 return self._nchannels 338 339 def setsampwidth(self, sampwidth): 340 if self._datawritten: 341 raise Error('cannot change parameters after starting to write') 342 if sampwidth < 1 or sampwidth > 4: 343 raise Error('bad sample width') 344 self._sampwidth = sampwidth 345 346 def getsampwidth(self): 347 if not self._sampwidth: 348 raise Error('sample width not set') 349 return self._sampwidth 350 351 def setframerate(self, framerate): 352 if self._datawritten: 353 raise Error('cannot change parameters after starting to write') 354 if framerate <= 0: 355 raise Error('bad frame rate') 356 self._framerate = int(round(framerate)) 357 358 def getframerate(self): 359 if not self._framerate: 360 raise Error('frame rate not set') 361 return self._framerate 362 363 def setnframes(self, nframes): 364 if self._datawritten: 365 raise Error('cannot change parameters after starting to write') 366 self._nframes = nframes 367 368 def getnframes(self): 369 return self._nframeswritten 370 371 def setcomptype(self, comptype, compname): 372 if self._datawritten: 373 raise Error('cannot change parameters after starting to write') 374 if comptype not in ('NONE',): 375 raise Error('unsupported compression type') 376 self._comptype = comptype 377 self._compname = compname 378 379 def getcomptype(self): 380 return self._comptype 381 382 def getcompname(self): 383 return self._compname 384 385 def setparams(self, params): 386 nchannels, sampwidth, framerate, nframes, comptype, compname = params 387 if self._datawritten: 388 raise Error('cannot change parameters after starting to write') 389 self.setnchannels(nchannels) 390 self.setsampwidth(sampwidth) 391 self.setframerate(framerate) 392 self.setnframes(nframes) 393 self.setcomptype(comptype, compname) 394 395 def getparams(self): 396 if not self._nchannels or not self._sampwidth or not self._framerate: 397 raise Error('not all parameters set') 398 return _wave_params(self._nchannels, self._sampwidth, self._framerate, 399 self._nframes, self._comptype, self._compname) 400 401 def setmark(self, id, pos, name): 402 raise Error('setmark() not supported') 403 404 def getmark(self, id): 405 raise Error('no marks') 406 407 def getmarkers(self): 408 return None 409 410 def tell(self): 411 return self._nframeswritten 412 413 def writeframesraw(self, data): 414 if not isinstance(data, (bytes, bytearray)): 415 data = memoryview(data).cast('B') 416 self._ensure_header_written(len(data)) 417 nframes = len(data) // (self._sampwidth * self._nchannels) 418 if self._convert: 419 data = self._convert(data) 420 if self._sampwidth != 1 and sys.byteorder == 'big': 421 data = audioop.byteswap(data, self._sampwidth) 422 self._file.write(data) 423 self._datawritten += len(data) 424 self._nframeswritten = self._nframeswritten + nframes 425 426 def writeframes(self, data): 427 self.writeframesraw(data) 428 if self._datalength != self._datawritten: 429 self._patchheader() 430 431 def close(self): 432 try: 433 if self._file: 434 self._ensure_header_written(0) 435 if self._datalength != self._datawritten: 436 self._patchheader() 437 self._file.flush() 438 finally: 439 self._file = None 440 file = self._i_opened_the_file 441 if file: 442 self._i_opened_the_file = None 443 file.close() 444 445 # 446 # Internal methods. 447 # 448 449 def _ensure_header_written(self, datasize): 450 if not self._headerwritten: 451 if not self._nchannels: 452 raise Error('# channels not specified') 453 if not self._sampwidth: 454 raise Error('sample width not specified') 455 if not self._framerate: 456 raise Error('sampling rate not specified') 457 self._write_header(datasize) 458 459 def _write_header(self, initlength): 460 assert not self._headerwritten 461 self._file.write(b'RIFF') 462 if not self._nframes: 463 self._nframes = initlength // (self._nchannels * self._sampwidth) 464 self._datalength = self._nframes * self._nchannels * self._sampwidth 465 try: 466 self._form_length_pos = self._file.tell() 467 except (AttributeError, OSError): 468 self._form_length_pos = None 469 self._file.write(struct.pack('<L4s4sLHHLLHH4s', 470 36 + self._datalength, b'WAVE', b'fmt ', 16, 471 WAVE_FORMAT_PCM, self._nchannels, self._framerate, 472 self._nchannels * self._framerate * self._sampwidth, 473 self._nchannels * self._sampwidth, 474 self._sampwidth * 8, b'data')) 475 if self._form_length_pos is not None: 476 self._data_length_pos = self._file.tell() 477 self._file.write(struct.pack('<L', self._datalength)) 478 self._headerwritten = True 479 480 def _patchheader(self): 481 assert self._headerwritten 482 if self._datawritten == self._datalength: 483 return 484 curpos = self._file.tell() 485 self._file.seek(self._form_length_pos, 0) 486 self._file.write(struct.pack('<L', 36 + self._datawritten)) 487 self._file.seek(self._data_length_pos, 0) 488 self._file.write(struct.pack('<L', self._datawritten)) 489 self._file.seek(curpos, 0) 490 self._datalength = self._datawritten 491 492 def open(f, mode=None): 493 if mode is None: 494 if hasattr(f, 'mode'): 495 mode = f.mode 496 else: 497 mode = 'rb' 498 if mode in ('r', 'rb'): 499 return Wave_read(f) 500 elif mode in ('w', 'wb'): 501 return Wave_write(f) 502 else: 503 raise Error("mode must be 'r', 'rb', 'w', or 'wb'") 504 505 openfp = open # B/W compatibility 506