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) == "\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() != '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
136
self._data_seek_needed = 1
138
chunk = Chunk(self._file, bigendian = 0)
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
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, basestring):
159
f = __builtin__.open(f, 'rb')
160
self._i_opened_the_file = f
161
# else, assume it is an open file object already
167
# User visible methods.
173
self._data_seek_needed = 1
177
if self._i_opened_the_file:
178
self._i_opened_the_file.close()
179
self._i_opened_the_file = None
183
return self._soundpos
185
def getnchannels(self):
186
return self._nchannels
188
def getnframes(self):
191
def getsampwidth(self):
192
return self._sampwidth
194
def getframerate(self):
195
return self._framerate
197
def getcomptype(self):
198
return self._comptype
200
def getcompname(self):
201
return self._compname
204
return self.getnchannels(), self.getsampwidth(), \
205
self.getframerate(), self.getnframes(), \
206
self.getcomptype(), self.getcompname()
208
def getmarkers(self):
211
def getmark(self, id):
212
raise Error, 'no marks'
214
def setpos(self, pos):
215
if pos < 0 or pos > self._nframes:
216
raise Error, 'position not in range'
218
self._data_seek_needed = 1
220
def readframes(self, nframes):
221
if self._data_seek_needed:
222
self._data_chunk.seek(0, 0)
223
pos = self._soundpos * self._framesize
225
self._data_chunk.seek(pos, 0)
226
self._data_seek_needed = 0
229
if self._sampwidth > 1 and big_endian:
230
# unfortunately the fromfile() method does not take
231
# something that only looks like a file object, so
232
# we have to reach into the innards of the chunk object
234
chunk = self._data_chunk
235
data = array.array(_array_fmts[self._sampwidth])
236
nitems = nframes * self._nchannels
237
if nitems * self._sampwidth > chunk.chunksize - chunk.size_read:
238
nitems = (chunk.chunksize - chunk.size_read) / self._sampwidth
239
data.fromfile(chunk.file.file, nitems)
240
# "tell" data chunk how much was read
241
chunk.size_read = chunk.size_read + nitems * self._sampwidth
242
# do the same for the outermost chunk
244
chunk.size_read = chunk.size_read + nitems * self._sampwidth
246
data = data.tostring()
248
data = self._data_chunk.read(nframes * self._framesize)
249
if self._convert and data:
250
data = self._convert(data)
251
self._soundpos = self._soundpos + len(data) // (self._nchannels * self._sampwidth)
258
def _read_fmt_chunk(self, chunk):
259
wFormatTag, self._nchannels, self._framerate, dwAvgBytesPerSec, wBlockAlign = struct.unpack('<hhllh', chunk.read(14))
260
if wFormatTag == WAVE_FORMAT_PCM:
261
sampwidth = struct.unpack('<h', chunk.read(2))[0]
262
self._sampwidth = (sampwidth + 7) // 8
264
raise Error, 'unknown format: %r' % (wFormatTag,)
265
self._framesize = self._nchannels * self._sampwidth
266
self._comptype = 'NONE'
267
self._compname = 'not compressed'
270
"""Variables used in this class:
272
These variables are user settable through appropriate methods
274
_file -- the open file with methods write(), close(), tell(), seek()
275
set through the __init__() method
276
_comptype -- the AIFF-C compression type ('NONE' in AIFF)
277
set through the setcomptype() or setparams() method
278
_compname -- the human-readable AIFF-C compression type
279
set through the setcomptype() or setparams() method
280
_nchannels -- the number of audio channels
281
set through the setnchannels() or setparams() method
282
_sampwidth -- the number of bytes per audio sample
283
set through the setsampwidth() or setparams() method
284
_framerate -- the sampling frequency
285
set through the setframerate() or setparams() method
286
_nframes -- the number of audio frames written to the header
287
set through the setnframes() or setparams() method
289
These variables are used internally only:
290
_datalength -- the size of the audio samples written to the header
291
_nframeswritten -- the number of frames actually written
292
_datawritten -- the size of the audio samples actually written
295
def __init__(self, f):
296
self._i_opened_the_file = None
297
if isinstance(f, basestring):
298
f = __builtin__.open(f, 'wb')
299
self._i_opened_the_file = f
302
def initfp(self, file):
309
self._nframeswritten = 0
310
self._datawritten = 0
317
# User visible methods.
319
def setnchannels(self, nchannels):
320
if self._datawritten:
321
raise Error, 'cannot change parameters after starting to write'
323
raise Error, 'bad # of channels'
324
self._nchannels = nchannels
326
def getnchannels(self):
327
if not self._nchannels:
328
raise Error, 'number of channels not set'
329
return self._nchannels
331
def setsampwidth(self, sampwidth):
332
if self._datawritten:
333
raise Error, 'cannot change parameters after starting to write'
334
if sampwidth < 1 or sampwidth > 4:
335
raise Error, 'bad sample width'
336
self._sampwidth = sampwidth
338
def getsampwidth(self):
339
if not self._sampwidth:
340
raise Error, 'sample width not set'
341
return self._sampwidth
343
def setframerate(self, framerate):
344
if self._datawritten:
345
raise Error, 'cannot change parameters after starting to write'
347
raise Error, 'bad frame rate'
348
self._framerate = framerate
350
def getframerate(self):
351
if not self._framerate:
352
raise Error, 'frame rate not set'
353
return self._framerate
355
def setnframes(self, nframes):
356
if self._datawritten:
357
raise Error, 'cannot change parameters after starting to write'
358
self._nframes = nframes
360
def getnframes(self):
361
return self._nframeswritten
363
def setcomptype(self, comptype, compname):
364
if self._datawritten:
365
raise Error, 'cannot change parameters after starting to write'
366
if comptype not in ('NONE',):
367
raise Error, 'unsupported compression type'
368
self._comptype = comptype
369
self._compname = compname
371
def getcomptype(self):
372
return self._comptype
374
def getcompname(self):
375
return self._compname
377
def setparams(self, (nchannels, sampwidth, framerate, nframes, comptype, compname)):
378
if self._datawritten:
379
raise Error, 'cannot change parameters after starting to write'
380
self.setnchannels(nchannels)
381
self.setsampwidth(sampwidth)
382
self.setframerate(framerate)
383
self.setnframes(nframes)
384
self.setcomptype(comptype, compname)
387
if not self._nchannels or not self._sampwidth or not self._framerate:
388
raise Error, 'not all parameters set'
389
return self._nchannels, self._sampwidth, self._framerate, \
390
self._nframes, self._comptype, self._compname
392
def setmark(self, id, pos, name):
393
raise Error, 'setmark() not supported'
395
def getmark(self, id):
396
raise Error, 'no marks'
398
def getmarkers(self):
402
return self._nframeswritten
404
def writeframesraw(self, data):
405
self._ensure_header_written(len(data))
406
nframes = len(data) // (self._sampwidth * self._nchannels)
408
data = self._convert(data)
409
if self._sampwidth > 1 and big_endian:
411
data = array.array(_array_fmts[self._sampwidth], data)
413
data.tofile(self._file)
414
self._datawritten = self._datawritten + len(data) * self._sampwidth
416
self._file.write(data)
417
self._datawritten = self._datawritten + len(data)
418
self._nframeswritten = self._nframeswritten + nframes
420
def writeframes(self, data):
421
self.writeframesraw(data)
422
if self._datalength != self._datawritten:
427
self._ensure_header_written(0)
428
if self._datalength != self._datawritten:
432
if self._i_opened_the_file:
433
self._i_opened_the_file.close()
434
self._i_opened_the_file = None
440
def _ensure_header_written(self, datasize):
441
if not self._datawritten:
442
if not self._nchannels:
443
raise Error, '# channels not specified'
444
if not self._sampwidth:
445
raise Error, 'sample width not specified'
446
if not self._framerate:
447
raise Error, 'sampling rate not specified'
448
self._write_header(datasize)
450
def _write_header(self, initlength):
451
self._file.write('RIFF')
452
if not self._nframes:
453
self._nframes = initlength / (self._nchannels * self._sampwidth)
454
self._datalength = self._nframes * self._nchannels * self._sampwidth
455
self._form_length_pos = self._file.tell()
456
self._file.write(struct.pack('<l4s4slhhllhh4s',
457
36 + self._datalength, 'WAVE', 'fmt ', 16,
458
WAVE_FORMAT_PCM, self._nchannels, self._framerate,
459
self._nchannels * self._framerate * self._sampwidth,
460
self._nchannels * self._sampwidth,
461
self._sampwidth * 8, 'data'))
462
self._data_length_pos = self._file.tell()
463
self._file.write(struct.pack('<l', self._datalength))
465
def _patchheader(self):
466
if self._datawritten == self._datalength:
468
curpos = self._file.tell()
469
self._file.seek(self._form_length_pos, 0)
470
self._file.write(struct.pack('<l', 36 + self._datawritten))
471
self._file.seek(self._data_length_pos, 0)
472
self._file.write(struct.pack('<l', self._datawritten))
473
self._file.seek(curpos, 0)
474
self._datalength = self._datawritten
476
def open(f, mode=None):
478
if hasattr(f, 'mode'):
482
if mode in ('r', 'rb'):
484
elif mode in ('w', 'wb'):
487
raise Error, "mode must be 'r', 'rb', 'w', or 'wb'"
489
openfp = open # B/W compatibility