PyXR

c:\python24\lib \ wave.py



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
SourceForge.net Logo