Home | History | Annotate | Download | only in Lib
      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