nothing@1: """Stuff to parse WAVE files. nothing@1: nothing@1: Usage. nothing@1: nothing@1: Reading WAVE files: nothing@1: f = wave.open(file, 'r') nothing@1: where file is either the name of a file or an open file pointer. nothing@1: The open file pointer must have methods read(), seek(), and close(). nothing@1: When the setpos() and rewind() methods are not used, the seek() nothing@1: method is not necessary. nothing@1: nothing@1: This returns an instance of a class with the following public methods: nothing@1: getnchannels() -- returns number of audio channels (1 for nothing@1: mono, 2 for stereo) nothing@1: getsampwidth() -- returns sample width in bytes nothing@1: getframerate() -- returns sampling frequency nothing@1: getnframes() -- returns number of audio frames nothing@1: getcomptype() -- returns compression type ('NONE' for linear samples) nothing@1: getcompname() -- returns human-readable version of nothing@1: compression type ('not compressed' linear samples) nothing@1: getparams() -- returns a tuple consisting of all of the nothing@1: above in the above order nothing@1: getmarkers() -- returns None (for compatibility with the nothing@1: aifc module) nothing@1: getmark(id) -- raises an error since the mark does not nothing@1: exist (for compatibility with the aifc module) nothing@1: readframes(n) -- returns at most n frames of audio nothing@1: rewind() -- rewind to the beginning of the audio stream nothing@1: setpos(pos) -- seek to the specified position nothing@1: tell() -- return the current position nothing@1: close() -- close the instance (make it unusable) nothing@1: The position returned by tell() and the position given to setpos() nothing@1: are compatible and have nothing to do with the actual position in the nothing@1: file. nothing@1: The close() method is called automatically when the class instance nothing@1: is destroyed. nothing@1: nothing@1: Writing WAVE files: nothing@1: f = wave.open(file, 'w') nothing@1: where file is either the name of a file or an open file pointer. nothing@1: The open file pointer must have methods write(), tell(), seek(), and nothing@1: close(). nothing@1: nothing@1: This returns an instance of a class with the following public methods: nothing@1: setnchannels(n) -- set the number of channels nothing@1: setsampwidth(n) -- set the sample width nothing@1: setframerate(n) -- set the frame rate nothing@1: setnframes(n) -- set the number of frames nothing@1: setcomptype(type, name) nothing@1: -- set the compression type and the nothing@1: human-readable compression type nothing@1: setparams(tuple) nothing@1: -- set all parameters at once nothing@1: tell() -- return current position in output file nothing@1: writeframesraw(data) nothing@1: -- write audio frames without pathing up the nothing@1: file header nothing@1: writeframes(data) nothing@1: -- write audio frames and patch up the file header nothing@1: close() -- patch up the file header and close the nothing@1: output file nothing@1: You should set the parameters before the first writeframesraw or nothing@1: writeframes. The total number of frames does not need to be set, nothing@1: but when it is set to the correct value, the header does not have to nothing@1: be patched up. nothing@1: It is best to first set all parameters, perhaps possibly the nothing@1: compression type, and then write audio frames using writeframesraw. nothing@1: When all frames have been written, either call writeframes('') or nothing@1: close() to patch up the sizes in the header. nothing@1: The close() method is called automatically when the class instance nothing@1: is destroyed. nothing@1: """ nothing@1: nothing@1: import __builtin__ nothing@1: nothing@1: __all__ = ["open", "openfp", "Error"] nothing@1: nothing@1: class Error(Exception): nothing@1: pass nothing@1: nothing@1: WAVE_FORMAT_PCM = 0x0001 nothing@1: nothing@1: _array_fmts = None, 'b', 'h', None, 'i' nothing@1: nothing@1: import struct nothing@1: import sys nothing@1: from chunk import Chunk nothing@1: nothing@1: def _byteswap3(data): nothing@1: ba = bytearray(data) nothing@1: ba[::3] = data[2::3] nothing@1: ba[2::3] = data[::3] nothing@1: return bytes(ba) nothing@1: nothing@1: class Wave_read: nothing@1: """Variables used in this class: nothing@1: nothing@1: These variables are available to the user though appropriate nothing@1: methods of this class: nothing@1: _file -- the open file with methods read(), close(), and seek() nothing@1: set through the __init__() method nothing@1: _nchannels -- the number of audio channels nothing@1: available through the getnchannels() method nothing@1: _nframes -- the number of audio frames nothing@1: available through the getnframes() method nothing@1: _sampwidth -- the number of bytes per audio sample nothing@1: available through the getsampwidth() method nothing@1: _framerate -- the sampling frequency nothing@1: available through the getframerate() method nothing@1: _comptype -- the AIFF-C compression type ('NONE' if AIFF) nothing@1: available through the getcomptype() method nothing@1: _compname -- the human-readable AIFF-C compression type nothing@1: available through the getcomptype() method nothing@1: _soundpos -- the position in the audio stream nothing@1: available through the tell() method, set through the nothing@1: setpos() method nothing@1: nothing@1: These variables are used internally only: nothing@1: _fmt_chunk_read -- 1 iff the FMT chunk has been read nothing@1: _data_seek_needed -- 1 iff positioned correctly in audio nothing@1: file for readframes() nothing@1: _data_chunk -- instantiation of a chunk class for the DATA chunk nothing@1: _framesize -- size of one frame in the file nothing@1: """ nothing@1: nothing@1: def initfp(self, file): nothing@1: self._convert = None nothing@1: self._soundpos = 0 nothing@1: self._file = Chunk(file, bigendian = 0) nothing@1: if self._file.getname() != 'RIFF': nothing@1: raise Error, 'file does not start with RIFF id' nothing@1: if self._file.read(4) != 'WAVE': nothing@1: raise Error, 'not a WAVE file' nothing@1: self._fmt_chunk_read = 0 nothing@1: self._data_chunk = None nothing@1: while 1: nothing@1: self._data_seek_needed = 1 nothing@1: try: nothing@1: chunk = Chunk(self._file, bigendian = 0) nothing@1: except EOFError: nothing@1: break nothing@1: chunkname = chunk.getname() nothing@1: if chunkname == 'fmt ': nothing@1: self._read_fmt_chunk(chunk) nothing@1: self._fmt_chunk_read = 1 nothing@1: elif chunkname == 'data': nothing@1: if not self._fmt_chunk_read: nothing@1: raise Error, 'data chunk before fmt chunk' nothing@1: self._data_chunk = chunk nothing@1: self._nframes = chunk.chunksize // self._framesize nothing@1: self._data_seek_needed = 0 nothing@1: break nothing@1: chunk.skip() nothing@1: if not self._fmt_chunk_read or not self._data_chunk: nothing@1: raise Error, 'fmt chunk and/or data chunk missing' nothing@1: nothing@1: def __init__(self, f): nothing@1: self._i_opened_the_file = None nothing@1: if isinstance(f, basestring): nothing@1: f = __builtin__.open(f, 'rb') nothing@1: self._i_opened_the_file = f nothing@1: # else, assume it is an open file object already nothing@1: try: nothing@1: self.initfp(f) nothing@1: except: nothing@1: if self._i_opened_the_file: nothing@1: f.close() nothing@1: raise nothing@1: nothing@1: def __del__(self): nothing@1: self.close() nothing@1: # nothing@1: # User visible methods. nothing@1: # nothing@1: def getfp(self): nothing@1: return self._file nothing@1: nothing@1: def rewind(self): nothing@1: self._data_seek_needed = 1 nothing@1: self._soundpos = 0 nothing@1: nothing@1: def close(self): nothing@1: self._file = None nothing@1: file = self._i_opened_the_file nothing@1: if file: nothing@1: self._i_opened_the_file = None nothing@1: file.close() nothing@1: nothing@1: def tell(self): nothing@1: return self._soundpos nothing@1: nothing@1: def getnchannels(self): nothing@1: return self._nchannels nothing@1: nothing@1: def getnframes(self): nothing@1: return self._nframes nothing@1: nothing@1: def getsampwidth(self): nothing@1: return self._sampwidth nothing@1: nothing@1: def getframerate(self): nothing@1: return self._framerate nothing@1: nothing@1: def getcomptype(self): nothing@1: return self._comptype nothing@1: nothing@1: def getcompname(self): nothing@1: return self._compname nothing@1: nothing@1: def getparams(self): nothing@1: return self.getnchannels(), self.getsampwidth(), \ nothing@1: self.getframerate(), self.getnframes(), \ nothing@1: self.getcomptype(), self.getcompname() nothing@1: nothing@1: def getmarkers(self): nothing@1: return None nothing@1: nothing@1: def getmark(self, id): nothing@1: raise Error, 'no marks' nothing@1: nothing@1: def setpos(self, pos): nothing@1: if pos < 0 or pos > self._nframes: nothing@1: raise Error, 'position not in range' nothing@1: self._soundpos = pos nothing@1: self._data_seek_needed = 1 nothing@1: nothing@1: def readframes(self, nframes): nothing@1: if self._data_seek_needed: nothing@1: self._data_chunk.seek(0, 0) nothing@1: pos = self._soundpos * self._framesize nothing@1: if pos: nothing@1: self._data_chunk.seek(pos, 0) nothing@1: self._data_seek_needed = 0 nothing@1: if nframes == 0: nothing@1: return '' nothing@1: if self._sampwidth in (2, 4) and sys.byteorder == 'big': nothing@1: # unfortunately the fromfile() method does not take nothing@1: # something that only looks like a file object, so nothing@1: # we have to reach into the innards of the chunk object nothing@1: import array nothing@1: chunk = self._data_chunk nothing@1: data = array.array(_array_fmts[self._sampwidth]) nothing@1: assert data.itemsize == self._sampwidth nothing@1: nitems = nframes * self._nchannels nothing@1: if nitems * self._sampwidth > chunk.chunksize - chunk.size_read: nothing@1: nitems = (chunk.chunksize - chunk.size_read) // self._sampwidth nothing@1: data.fromfile(chunk.file.file, nitems) nothing@1: # "tell" data chunk how much was read nothing@1: chunk.size_read = chunk.size_read + nitems * self._sampwidth nothing@1: # do the same for the outermost chunk nothing@1: chunk = chunk.file nothing@1: chunk.size_read = chunk.size_read + nitems * self._sampwidth nothing@1: data.byteswap() nothing@1: data = data.tostring() nothing@1: else: nothing@1: data = self._data_chunk.read(nframes * self._framesize) nothing@1: if self._sampwidth == 3 and sys.byteorder == 'big': nothing@1: data = _byteswap3(data) nothing@1: if self._convert and data: nothing@1: data = self._convert(data) nothing@1: self._soundpos = self._soundpos + len(data) // (self._nchannels * self._sampwidth) nothing@1: return data nothing@1: nothing@1: # nothing@1: # Internal methods. nothing@1: # nothing@1: nothing@1: def _read_fmt_chunk(self, chunk): nothing@1: wFormatTag, self._nchannels, self._framerate, dwAvgBytesPerSec, wBlockAlign = struct.unpack(' 4: nothing@1: raise Error, 'bad sample width' nothing@1: self._sampwidth = sampwidth nothing@1: nothing@1: def getsampwidth(self): nothing@1: if not self._sampwidth: nothing@1: raise Error, 'sample width not set' nothing@1: return self._sampwidth nothing@1: nothing@1: def setframerate(self, framerate): nothing@1: if self._datawritten: nothing@1: raise Error, 'cannot change parameters after starting to write' nothing@1: if framerate <= 0: nothing@1: raise Error, 'bad frame rate' nothing@1: self._framerate = framerate nothing@1: nothing@1: def getframerate(self): nothing@1: if not self._framerate: nothing@1: raise Error, 'frame rate not set' nothing@1: return self._framerate nothing@1: nothing@1: def setnframes(self, nframes): nothing@1: if self._datawritten: nothing@1: raise Error, 'cannot change parameters after starting to write' nothing@1: self._nframes = nframes nothing@1: nothing@1: def getnframes(self): nothing@1: return self._nframeswritten nothing@1: nothing@1: def setcomptype(self, comptype, compname): nothing@1: if self._datawritten: nothing@1: raise Error, 'cannot change parameters after starting to write' nothing@1: if comptype not in ('NONE',): nothing@1: raise Error, 'unsupported compression type' nothing@1: self._comptype = comptype nothing@1: self._compname = compname nothing@1: nothing@1: def getcomptype(self): nothing@1: return self._comptype nothing@1: nothing@1: def getcompname(self): nothing@1: return self._compname nothing@1: nothing@1: def setparams(self, params): nothing@1: nchannels, sampwidth, framerate, nframes, comptype, compname = params nothing@1: if self._datawritten: nothing@1: raise Error, 'cannot change parameters after starting to write' nothing@1: self.setnchannels(nchannels) nothing@1: self.setsampwidth(sampwidth) nothing@1: self.setframerate(framerate) nothing@1: self.setnframes(nframes) nothing@1: self.setcomptype(comptype, compname) nothing@1: nothing@1: def getparams(self): nothing@1: if not self._nchannels or not self._sampwidth or not self._framerate: nothing@1: raise Error, 'not all parameters set' nothing@1: return self._nchannels, self._sampwidth, self._framerate, \ nothing@1: self._nframes, self._comptype, self._compname nothing@1: nothing@1: def setmark(self, id, pos, name): nothing@1: raise Error, 'setmark() not supported' nothing@1: nothing@1: def getmark(self, id): nothing@1: raise Error, 'no marks' nothing@1: nothing@1: def getmarkers(self): nothing@1: return None nothing@1: nothing@1: def tell(self): nothing@1: return self._nframeswritten nothing@1: nothing@1: def writeframesraw(self, data): nothing@1: self._ensure_header_written(len(data)) nothing@1: nframes = len(data) // (self._sampwidth * self._nchannels) nothing@1: if self._convert: nothing@1: data = self._convert(data) nothing@1: if self._sampwidth in (2, 4) and sys.byteorder == 'big': nothing@1: import array nothing@1: a = array.array(_array_fmts[self._sampwidth]) nothing@1: a.fromstring(data) nothing@1: data = a nothing@1: assert data.itemsize == self._sampwidth nothing@1: data.byteswap() nothing@1: data.tofile(self._file) nothing@1: self._datawritten = self._datawritten + len(data) * self._sampwidth nothing@1: else: nothing@1: if self._sampwidth == 3 and sys.byteorder == 'big': nothing@1: data = _byteswap3(data) nothing@1: self._file.write(data) nothing@1: self._datawritten = self._datawritten + len(data) nothing@1: self._nframeswritten = self._nframeswritten + nframes nothing@1: nothing@1: def writeframes(self, data): nothing@1: self.writeframesraw(data) nothing@1: if self._datalength != self._datawritten: nothing@1: self._patchheader() nothing@1: nothing@1: def close(self): nothing@1: try: nothing@1: if self._file: nothing@1: self._ensure_header_written(0) nothing@1: if self._datalength != self._datawritten: nothing@1: self._patchheader() nothing@1: self._file.flush() nothing@1: finally: nothing@1: self._file = None nothing@1: file = self._i_opened_the_file nothing@1: if file: nothing@1: self._i_opened_the_file = None nothing@1: file.close() nothing@1: nothing@1: # nothing@1: # Internal methods. nothing@1: # nothing@1: nothing@1: def _ensure_header_written(self, datasize): nothing@1: if not self._headerwritten: nothing@1: if not self._nchannels: nothing@1: raise Error, '# channels not specified' nothing@1: if not self._sampwidth: nothing@1: raise Error, 'sample width not specified' nothing@1: if not self._framerate: nothing@1: raise Error, 'sampling rate not specified' nothing@1: self._write_header(datasize) nothing@1: nothing@1: def _write_header(self, initlength): nothing@1: assert not self._headerwritten nothing@1: self._file.write('RIFF') nothing@1: if not self._nframes: nothing@1: self._nframes = initlength / (self._nchannels * self._sampwidth) nothing@1: self._datalength = self._nframes * self._nchannels * self._sampwidth nothing@1: self._form_length_pos = self._file.tell() nothing@1: self._file.write(struct.pack('