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
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:
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()
101 _nchannels -- the number of audio channels
103 _nframes -- the number of audio frames
105 _sampwidth -- the number of bytes per audio sample
107 _framerate -- the sampling frequency
109 _comptype -- the AIFF-C compression type (
'NONE' if AIFF)
111 _compname -- the human-readable AIFF-C compression type
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
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 type(f) == type(''):
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: ' + `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()
276 _comptype -- the AIFF-C compression type (
'NONE' in AIFF)
278 _compname -- the human-readable AIFF-C compression type
280 _nchannels -- the number of audio channels
282 _sampwidth -- the number of bytes per audio sample
284 _framerate -- the sampling frequency
286 _nframes -- the number of audio frames written to the header
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 type(f) == type(''):
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