1
"""Stuff to parse 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.
12
This returns an instance of a class with the following public methods:
13
getnchannels() -- returns number of audio channels (1 for
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
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
35
The close() method is called automatically when the class instance
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
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
53
-- set all parameters at once
54
tell() -- return current position in output file
56
-- write audio frames without pathing up the
59
-- write audio frames and patch up the file header
60
close() -- patch up the file header and close the
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
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
76
__all__ = ["open", "openfp", "Error"]
78
class Error(Exception):
81
WAVE_FORMAT_PCM = 0x0001
83
_array_fmts = None, 'b', 'h', None, 'l'
85
# Determine endian-ness
87
if struct.pack("h", 1) == b"\000\001":
92
from chunk import Chunk
95
"""Variables used in this class:
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
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
125
def initfp(self, file):
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
136
self._data_seek_needed = 1
138
chunk = Chunk(self._file, bigendian = 0)
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
153
if not self._fmt_chunk_read or not self._data_chunk:
154
raise Error('fmt chunk and/or data chunk missing')
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
165
if self._i_opened_the_file:
172
# User visible methods.
178
self._data_seek_needed = 1
182
if self._i_opened_the_file:
183
self._i_opened_the_file.close()
184
self._i_opened_the_file = None
188
return self._soundpos
190
def getnchannels(self):
191
return self._nchannels
193
def getnframes(self):
196
def getsampwidth(self):
197
return self._sampwidth
199
def getframerate(self):
200
return self._framerate
202
def getcomptype(self):
203
return self._comptype
205
def getcompname(self):
206
return self._compname
209
return self.getnchannels(), self.getsampwidth(), \
210
self.getframerate(), self.getnframes(), \
211
self.getcomptype(), self.getcompname()
213
def getmarkers(self):
216
def getmark(self, id):
217
raise Error('no marks')
219
def setpos(self, pos):
220
if pos < 0 or pos > self._nframes:
221
raise Error('position not in range')
223
self._data_seek_needed = 1
225
def readframes(self, nframes):
226
if self._data_seek_needed:
227
self._data_chunk.seek(0, 0)
228
pos = self._soundpos * self._framesize
230
self._data_chunk.seek(pos, 0)
231
self._data_seek_needed = 0
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
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
249
chunk.size_read = chunk.size_read + nitems * self._sampwidth
251
data = data.tostring()
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)
263
def _read_fmt_chunk(self, chunk):
264
wFormatTag, self._nchannels, self._framerate, dwAvgBytesPerSec, wBlockAlign = struct.unpack_from('<hhllh', chunk.read(14))
265
if wFormatTag == WAVE_FORMAT_PCM:
266
sampwidth = struct.unpack_from('<h', chunk.read(2))[0]
267
self._sampwidth = (sampwidth + 7) // 8
269
raise Error('unknown format: %r' % (wFormatTag,))
270
self._framesize = self._nchannels * self._sampwidth
271
self._comptype = 'NONE'
272
self._compname = 'not compressed'
275
"""Variables used in this class:
277
These variables are user settable through appropriate methods
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
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
300
def __init__(self, f):
301
self._i_opened_the_file = None
302
if isinstance(f, str):
303
f = builtins.open(f, 'wb')
304
self._i_opened_the_file = f
308
if self._i_opened_the_file:
312
def initfp(self, file):
319
self._nframeswritten = 0
320
self._datawritten = 0
327
# User visible methods.
329
def setnchannels(self, nchannels):
330
if self._datawritten:
331
raise Error('cannot change parameters after starting to write')
333
raise Error('bad # of channels')
334
self._nchannels = nchannels
336
def getnchannels(self):
337
if not self._nchannels:
338
raise Error('number of channels not set')
339
return self._nchannels
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
348
def getsampwidth(self):
349
if not self._sampwidth:
350
raise Error('sample width not set')
351
return self._sampwidth
353
def setframerate(self, framerate):
354
if self._datawritten:
355
raise Error('cannot change parameters after starting to write')
357
raise Error('bad frame rate')
358
self._framerate = framerate
360
def getframerate(self):
361
if not self._framerate:
362
raise Error('frame rate not set')
363
return self._framerate
365
def setnframes(self, nframes):
366
if self._datawritten:
367
raise Error('cannot change parameters after starting to write')
368
self._nframes = nframes
370
def getnframes(self):
371
return self._nframeswritten
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
381
def getcomptype(self):
382
return self._comptype
384
def getcompname(self):
385
return self._compname
387
def setparams(self, params):
388
nchannels, sampwidth, framerate, nframes, comptype, compname = params
389
if self._datawritten:
390
raise Error('cannot change parameters after starting to write')
391
self.setnchannels(nchannels)
392
self.setsampwidth(sampwidth)
393
self.setframerate(framerate)
394
self.setnframes(nframes)
395
self.setcomptype(comptype, compname)
398
if not self._nchannels or not self._sampwidth or not self._framerate:
399
raise Error('not all parameters set')
400
return self._nchannels, self._sampwidth, self._framerate, \
401
self._nframes, self._comptype, self._compname
403
def setmark(self, id, pos, name):
404
raise Error('setmark() not supported')
406
def getmark(self, id):
407
raise Error('no marks')
409
def getmarkers(self):
413
return self._nframeswritten
415
def writeframesraw(self, data):
416
self._ensure_header_written(len(data))
417
nframes = len(data) // (self._sampwidth * self._nchannels)
419
data = self._convert(data)
420
if self._sampwidth > 1 and big_endian:
422
data = array.array(_array_fmts[self._sampwidth], data)
424
data.tofile(self._file)
425
self._datawritten = self._datawritten + len(data) * self._sampwidth
427
self._file.write(data)
428
self._datawritten = self._datawritten + len(data)
429
self._nframeswritten = self._nframeswritten + nframes
431
def writeframes(self, data):
432
self.writeframesraw(data)
433
if self._datalength != self._datawritten:
438
self._ensure_header_written(0)
439
if self._datalength != self._datawritten:
443
if self._i_opened_the_file:
444
self._i_opened_the_file.close()
445
self._i_opened_the_file = None
451
def _ensure_header_written(self, datasize):
452
if not self._datawritten:
453
if not self._nchannels:
454
raise Error('# channels not specified')
455
if not self._sampwidth:
456
raise Error('sample width not specified')
457
if not self._framerate:
458
raise Error('sampling rate not specified')
459
self._write_header(datasize)
461
def _write_header(self, initlength):
462
self._file.write(b'RIFF')
463
if not self._nframes:
464
self._nframes = initlength / (self._nchannels * self._sampwidth)
465
self._datalength = self._nframes * self._nchannels * self._sampwidth
466
self._form_length_pos = self._file.tell()
467
self._file.write(struct.pack('<l4s4slhhllhh4s',
468
36 + self._datalength, 'WAVE', 'fmt ', 16,
469
WAVE_FORMAT_PCM, self._nchannels, self._framerate,
470
self._nchannels * self._framerate * self._sampwidth,
471
self._nchannels * self._sampwidth,
472
self._sampwidth * 8, 'data'))
473
self._data_length_pos = self._file.tell()
474
self._file.write(struct.pack('<l', self._datalength))
476
def _patchheader(self):
477
if self._datawritten == self._datalength:
479
curpos = self._file.tell()
480
self._file.seek(self._form_length_pos, 0)
481
self._file.write(struct.pack('<l', 36 + self._datawritten))
482
self._file.seek(self._data_length_pos, 0)
483
self._file.write(struct.pack('<l', self._datawritten))
484
self._file.seek(curpos, 0)
485
self._datalength = self._datawritten
487
def open(f, mode=None):
489
if hasattr(f, 'mode'):
493
if mode in ('r', 'rb'):
495
elif mode in ('w', 'wb'):
498
raise Error("mode must be 'r', 'rb', 'w', or 'wb'")
500
openfp = open # B/W compatibility