symbian-qemu-0.9.1-12/python-win32-2.6.1/lib/wave.py
changeset 1 2fb8b9db1c86
equal deleted inserted replaced
0:ffa851df0825 1:2fb8b9db1c86
       
     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 tuple 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('') 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 __builtin__
       
    75 
       
    76 __all__ = ["open", "openfp", "Error"]
       
    77 
       
    78 class Error(Exception):
       
    79     pass
       
    80 
       
    81 WAVE_FORMAT_PCM = 0x0001
       
    82 
       
    83 _array_fmts = None, 'b', 'h', None, 'l'
       
    84 
       
    85 # Determine endian-ness
       
    86 import struct
       
    87 if struct.pack("h", 1) == "\000\001":
       
    88     big_endian = 1
       
    89 else:
       
    90     big_endian = 0
       
    91 
       
    92 from chunk import Chunk
       
    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() != 'RIFF':
       
   130             raise Error, 'file does not start with RIFF id'
       
   131         if self._file.read(4) != '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 == 'fmt ':
       
   143                 self._read_fmt_chunk(chunk)
       
   144                 self._fmt_chunk_read = 1
       
   145             elif chunkname == '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, basestring):
       
   159             f = __builtin__.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     # User visible methods.
       
   173     #
       
   174     def getfp(self):
       
   175         return self._file
       
   176 
       
   177     def rewind(self):
       
   178         self._data_seek_needed = 1
       
   179         self._soundpos = 0
       
   180 
       
   181     def close(self):
       
   182         if self._i_opened_the_file:
       
   183             self._i_opened_the_file.close()
       
   184             self._i_opened_the_file = None
       
   185         self._file = None
       
   186 
       
   187     def tell(self):
       
   188         return self._soundpos
       
   189 
       
   190     def getnchannels(self):
       
   191         return self._nchannels
       
   192 
       
   193     def getnframes(self):
       
   194         return self._nframes
       
   195 
       
   196     def getsampwidth(self):
       
   197         return self._sampwidth
       
   198 
       
   199     def getframerate(self):
       
   200         return self._framerate
       
   201 
       
   202     def getcomptype(self):
       
   203         return self._comptype
       
   204 
       
   205     def getcompname(self):
       
   206         return self._compname
       
   207 
       
   208     def getparams(self):
       
   209         return self.getnchannels(), self.getsampwidth(), \
       
   210                self.getframerate(), self.getnframes(), \
       
   211                self.getcomptype(), self.getcompname()
       
   212 
       
   213     def getmarkers(self):
       
   214         return None
       
   215 
       
   216     def getmark(self, id):
       
   217         raise Error, 'no marks'
       
   218 
       
   219     def setpos(self, pos):
       
   220         if pos < 0 or pos > self._nframes:
       
   221             raise Error, 'position not in range'
       
   222         self._soundpos = pos
       
   223         self._data_seek_needed = 1
       
   224 
       
   225     def readframes(self, nframes):
       
   226         if self._data_seek_needed:
       
   227             self._data_chunk.seek(0, 0)
       
   228             pos = self._soundpos * self._framesize
       
   229             if pos:
       
   230                 self._data_chunk.seek(pos, 0)
       
   231             self._data_seek_needed = 0
       
   232         if nframes == 0:
       
   233             return ''
       
   234         if self._sampwidth > 1 and big_endian:
       
   235             # unfortunately the fromfile() method does not take
       
   236             # something that only looks like a file object, so
       
   237             # we have to reach into the innards of the chunk object
       
   238             import array
       
   239             chunk = self._data_chunk
       
   240             data = array.array(_array_fmts[self._sampwidth])
       
   241             nitems = nframes * self._nchannels
       
   242             if nitems * self._sampwidth > chunk.chunksize - chunk.size_read:
       
   243                 nitems = (chunk.chunksize - chunk.size_read) / self._sampwidth
       
   244             data.fromfile(chunk.file.file, nitems)
       
   245             # "tell" data chunk how much was read
       
   246             chunk.size_read = chunk.size_read + nitems * self._sampwidth
       
   247             # do the same for the outermost chunk
       
   248             chunk = chunk.file
       
   249             chunk.size_read = chunk.size_read + nitems * self._sampwidth
       
   250             data.byteswap()
       
   251             data = data.tostring()
       
   252         else:
       
   253             data = self._data_chunk.read(nframes * self._framesize)
       
   254         if self._convert and data:
       
   255             data = self._convert(data)
       
   256         self._soundpos = self._soundpos + len(data) // (self._nchannels * self._sampwidth)
       
   257         return data
       
   258 
       
   259     #
       
   260     # Internal methods.
       
   261     #
       
   262 
       
   263     def _read_fmt_chunk(self, chunk):
       
   264         wFormatTag, self._nchannels, self._framerate, dwAvgBytesPerSec, wBlockAlign = struct.unpack('<hhllh', chunk.read(14))
       
   265         if wFormatTag == WAVE_FORMAT_PCM:
       
   266             sampwidth = struct.unpack('<h', chunk.read(2))[0]
       
   267             self._sampwidth = (sampwidth + 7) // 8
       
   268         else:
       
   269             raise Error, 'unknown format: %r' % (wFormatTag,)
       
   270         self._framesize = self._nchannels * self._sampwidth
       
   271         self._comptype = 'NONE'
       
   272         self._compname = 'not compressed'
       
   273 
       
   274 class Wave_write:
       
   275     """Variables used in this class:
       
   276 
       
   277     These variables are user settable through appropriate methods
       
   278     of this class:
       
   279     _file -- the open file with methods write(), close(), tell(), seek()
       
   280               set through the __init__() method
       
   281     _comptype -- the AIFF-C compression type ('NONE' in AIFF)
       
   282               set through the setcomptype() or setparams() method
       
   283     _compname -- the human-readable AIFF-C compression type
       
   284               set through the setcomptype() or setparams() method
       
   285     _nchannels -- the number of audio channels
       
   286               set through the setnchannels() or setparams() method
       
   287     _sampwidth -- the number of bytes per audio sample
       
   288               set through the setsampwidth() or setparams() method
       
   289     _framerate -- the sampling frequency
       
   290               set through the setframerate() or setparams() method
       
   291     _nframes -- the number of audio frames written to the header
       
   292               set through the setnframes() or setparams() method
       
   293 
       
   294     These variables are used internally only:
       
   295     _datalength -- the size of the audio samples written to the header
       
   296     _nframeswritten -- the number of frames actually written
       
   297     _datawritten -- the size of the audio samples actually written
       
   298     """
       
   299 
       
   300     def __init__(self, f):
       
   301         self._i_opened_the_file = None
       
   302         if isinstance(f, basestring):
       
   303             f = __builtin__.open(f, 'wb')
       
   304             self._i_opened_the_file = f
       
   305         try:
       
   306             self.initfp(f)
       
   307         except:
       
   308             if self._i_opened_the_file:
       
   309                 f.close()
       
   310             raise
       
   311 
       
   312     def initfp(self, file):
       
   313         self._file = file
       
   314         self._convert = None
       
   315         self._nchannels = 0
       
   316         self._sampwidth = 0
       
   317         self._framerate = 0
       
   318         self._nframes = 0
       
   319         self._nframeswritten = 0
       
   320         self._datawritten = 0
       
   321         self._datalength = 0
       
   322 
       
   323     def __del__(self):
       
   324         self.close()
       
   325 
       
   326     #
       
   327     # User visible methods.
       
   328     #
       
   329     def setnchannels(self, nchannels):
       
   330         if self._datawritten:
       
   331             raise Error, 'cannot change parameters after starting to write'
       
   332         if nchannels < 1:
       
   333             raise Error, 'bad # of channels'
       
   334         self._nchannels = nchannels
       
   335 
       
   336     def getnchannels(self):
       
   337         if not self._nchannels:
       
   338             raise Error, 'number of channels not set'
       
   339         return self._nchannels
       
   340 
       
   341     def setsampwidth(self, sampwidth):
       
   342         if self._datawritten:
       
   343             raise Error, 'cannot change parameters after starting to write'
       
   344         if sampwidth < 1 or sampwidth > 4:
       
   345             raise Error, 'bad sample width'
       
   346         self._sampwidth = sampwidth
       
   347 
       
   348     def getsampwidth(self):
       
   349         if not self._sampwidth:
       
   350             raise Error, 'sample width not set'
       
   351         return self._sampwidth
       
   352 
       
   353     def setframerate(self, framerate):
       
   354         if self._datawritten:
       
   355             raise Error, 'cannot change parameters after starting to write'
       
   356         if framerate <= 0:
       
   357             raise Error, 'bad frame rate'
       
   358         self._framerate = framerate
       
   359 
       
   360     def getframerate(self):
       
   361         if not self._framerate:
       
   362             raise Error, 'frame rate not set'
       
   363         return self._framerate
       
   364 
       
   365     def setnframes(self, nframes):
       
   366         if self._datawritten:
       
   367             raise Error, 'cannot change parameters after starting to write'
       
   368         self._nframes = nframes
       
   369 
       
   370     def getnframes(self):
       
   371         return self._nframeswritten
       
   372 
       
   373     def setcomptype(self, comptype, compname):
       
   374         if self._datawritten:
       
   375             raise Error, 'cannot change parameters after starting to write'
       
   376         if comptype not in ('NONE',):
       
   377             raise Error, 'unsupported compression type'
       
   378         self._comptype = comptype
       
   379         self._compname = compname
       
   380 
       
   381     def getcomptype(self):
       
   382         return self._comptype
       
   383 
       
   384     def getcompname(self):
       
   385         return self._compname
       
   386 
       
   387     def setparams(self, (nchannels, sampwidth, framerate, nframes, comptype, compname)):
       
   388         if self._datawritten:
       
   389             raise Error, 'cannot change parameters after starting to write'
       
   390         self.setnchannels(nchannels)
       
   391         self.setsampwidth(sampwidth)
       
   392         self.setframerate(framerate)
       
   393         self.setnframes(nframes)
       
   394         self.setcomptype(comptype, compname)
       
   395 
       
   396     def getparams(self):
       
   397         if not self._nchannels or not self._sampwidth or not self._framerate:
       
   398             raise Error, 'not all parameters set'
       
   399         return self._nchannels, self._sampwidth, self._framerate, \
       
   400               self._nframes, self._comptype, self._compname
       
   401 
       
   402     def setmark(self, id, pos, name):
       
   403         raise Error, 'setmark() not supported'
       
   404 
       
   405     def getmark(self, id):
       
   406         raise Error, 'no marks'
       
   407 
       
   408     def getmarkers(self):
       
   409         return None
       
   410 
       
   411     def tell(self):
       
   412         return self._nframeswritten
       
   413 
       
   414     def writeframesraw(self, data):
       
   415         self._ensure_header_written(len(data))
       
   416         nframes = len(data) // (self._sampwidth * self._nchannels)
       
   417         if self._convert:
       
   418             data = self._convert(data)
       
   419         if self._sampwidth > 1 and big_endian:
       
   420             import array
       
   421             data = array.array(_array_fmts[self._sampwidth], data)
       
   422             data.byteswap()
       
   423             data.tofile(self._file)
       
   424             self._datawritten = self._datawritten + len(data) * self._sampwidth
       
   425         else:
       
   426             self._file.write(data)
       
   427             self._datawritten = self._datawritten + len(data)
       
   428         self._nframeswritten = self._nframeswritten + nframes
       
   429 
       
   430     def writeframes(self, data):
       
   431         self.writeframesraw(data)
       
   432         if self._datalength != self._datawritten:
       
   433             self._patchheader()
       
   434 
       
   435     def close(self):
       
   436         if self._file:
       
   437             self._ensure_header_written(0)
       
   438             if self._datalength != self._datawritten:
       
   439                 self._patchheader()
       
   440             self._file.flush()
       
   441             self._file = None
       
   442         if self._i_opened_the_file:
       
   443             self._i_opened_the_file.close()
       
   444             self._i_opened_the_file = None
       
   445 
       
   446     #
       
   447     # Internal methods.
       
   448     #
       
   449 
       
   450     def _ensure_header_written(self, datasize):
       
   451         if not self._datawritten:
       
   452             if not self._nchannels:
       
   453                 raise Error, '# channels not specified'
       
   454             if not self._sampwidth:
       
   455                 raise Error, 'sample width not specified'
       
   456             if not self._framerate:
       
   457                 raise Error, 'sampling rate not specified'
       
   458             self._write_header(datasize)
       
   459 
       
   460     def _write_header(self, initlength):
       
   461         self._file.write('RIFF')
       
   462         if not self._nframes:
       
   463             self._nframes = initlength / (self._nchannels * self._sampwidth)
       
   464         self._datalength = self._nframes * self._nchannels * self._sampwidth
       
   465         self._form_length_pos = self._file.tell()
       
   466         self._file.write(struct.pack('<l4s4slhhllhh4s',
       
   467             36 + self._datalength, 'WAVE', 'fmt ', 16,
       
   468             WAVE_FORMAT_PCM, self._nchannels, self._framerate,
       
   469             self._nchannels * self._framerate * self._sampwidth,
       
   470             self._nchannels * self._sampwidth,
       
   471             self._sampwidth * 8, 'data'))
       
   472         self._data_length_pos = self._file.tell()
       
   473         self._file.write(struct.pack('<l', self._datalength))
       
   474 
       
   475     def _patchheader(self):
       
   476         if self._datawritten == self._datalength:
       
   477             return
       
   478         curpos = self._file.tell()
       
   479         self._file.seek(self._form_length_pos, 0)
       
   480         self._file.write(struct.pack('<l', 36 + self._datawritten))
       
   481         self._file.seek(self._data_length_pos, 0)
       
   482         self._file.write(struct.pack('<l', self._datawritten))
       
   483         self._file.seek(curpos, 0)
       
   484         self._datalength = self._datawritten
       
   485 
       
   486 def open(f, mode=None):
       
   487     if mode is None:
       
   488         if hasattr(f, 'mode'):
       
   489             mode = f.mode
       
   490         else:
       
   491             mode = 'rb'
       
   492     if mode in ('r', 'rb'):
       
   493         return Wave_read(f)
       
   494     elif mode in ('w', 'wb'):
       
   495         return Wave_write(f)
       
   496     else:
       
   497         raise Error, "mode must be 'r', 'rb', 'w', or 'wb'"
       
   498 
       
   499 openfp = open # B/W compatibility