• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1"""Stuff to parse WAVE files.
2
3Usage.
4
5Reading WAVE files:
6      f = wave.open(file, 'r')
7where file is either the name of a file or an open file pointer.
8The open file pointer must have methods read(), seek(), and close().
9When the setpos() and rewind() methods are not used, the seek()
10method is not  necessary.
11
12This 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)
32The position returned by tell() and the position given to setpos()
33are compatible and have nothing to do with the actual position in the
34file.
35The close() method is called automatically when the class instance
36is destroyed.
37
38Writing WAVE files:
39      f = wave.open(file, 'w')
40where file is either the name of a file or an open file pointer.
41The open file pointer must have methods write(), tell(), seek(), and
42close().
43
44This 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
62You should set the parameters before the first writeframesraw or
63writeframes.  The total number of frames does not need to be set,
64but when it is set to the correct value, the header does not have to
65be patched up.
66It is best to first set all parameters, perhaps possibly the
67compression type, and then write audio frames using writeframesraw.
68When all frames have been written, either call writeframes(b'') or
69close() to patch up the sizes in the header.
70The close() method is called automatically when the class instance
71is destroyed.
72"""
73
74import builtins
75
76__all__ = ["open", "openfp", "Error", "Wave_read", "Wave_write"]
77
78class Error(Exception):
79    pass
80
81WAVE_FORMAT_PCM = 0x0001
82
83_array_fmts = None, 'b', 'h', None, 'i'
84
85import audioop
86import struct
87import sys
88from chunk import Chunk
89from collections import namedtuple
90
91_wave_params = namedtuple('_wave_params',
92                     'nchannels sampwidth framerate nframes comptype compname')
93
94class 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
265class 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
492def 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
505openfp = open # B/W compatibility
506