blob: 60c0b98319ffe0fb0dde4a8b935ce1cec6f6870b [file] [log] [blame]
Guido van Rossume7b146f2000-02-04 15:28:42 +00001"""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 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)
32The position returned by tell() and the position given to setpos()
Thomas Wouters7e474022000-07-16 12:04:32 +000033are compatible and have nothing to do with the actual position in the
Guido van Rossume7b146f2000-02-04 15:28:42 +000034file.
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('') or
69close() to patch up the sizes in the header.
70The close() method is called automatically when the class instance
71is destroyed.
72"""
Guido van Rossum3ed23cc1994-02-15 15:57:15 +000073
74import __builtin__
75
76Error = 'wave.Error'
77
78WAVE_FORMAT_PCM = 0x0001
79
80_array_fmts = None, 'b', 'h', None, 'l'
81
Guido van Rossumebb9c921999-02-05 22:28:17 +000082# Determine endian-ness
83import struct
84if struct.pack("h", 1) == "\000\001":
Guido van Rossume7b146f2000-02-04 15:28:42 +000085 big_endian = 1
Guido van Rossumebb9c921999-02-05 22:28:17 +000086else:
Guido van Rossume7b146f2000-02-04 15:28:42 +000087 big_endian = 0
Guido van Rossumebb9c921999-02-05 22:28:17 +000088
Guido van Rossum3601e881999-08-26 15:50:43 +000089from chunk import Chunk
Guido van Rossum3ed23cc1994-02-15 15:57:15 +000090
91class Wave_read:
Guido van Rossume7b146f2000-02-04 15:28:42 +000092 """Variables used in this class:
Guido van Rossum3ed23cc1994-02-15 15:57:15 +000093
Guido van Rossume7b146f2000-02-04 15:28:42 +000094 These variables are available to the user though appropriate
95 methods of this class:
96 _file -- the open file with methods read(), close(), and seek()
97 set through the __init__() method
98 _nchannels -- the number of audio channels
99 available through the getnchannels() method
100 _nframes -- the number of audio frames
101 available through the getnframes() method
102 _sampwidth -- the number of bytes per audio sample
103 available through the getsampwidth() method
104 _framerate -- the sampling frequency
105 available through the getframerate() method
106 _comptype -- the AIFF-C compression type ('NONE' if AIFF)
107 available through the getcomptype() method
108 _compname -- the human-readable AIFF-C compression type
109 available through the getcomptype() method
110 _soundpos -- the position in the audio stream
111 available through the tell() method, set through the
112 setpos() method
Guido van Rossum3ed23cc1994-02-15 15:57:15 +0000113
Guido van Rossume7b146f2000-02-04 15:28:42 +0000114 These variables are used internally only:
115 _fmt_chunk_read -- 1 iff the FMT chunk has been read
116 _data_seek_needed -- 1 iff positioned correctly in audio
117 file for readframes()
118 _data_chunk -- instantiation of a chunk class for the DATA chunk
119 _framesize -- size of one frame in the file
120 """
Guido van Rossum3ed23cc1994-02-15 15:57:15 +0000121
Guido van Rossume7b146f2000-02-04 15:28:42 +0000122 def initfp(self, file):
123 self._convert = None
124 self._soundpos = 0
125 self._file = Chunk(file, bigendian = 0)
126 if self._file.getname() != 'RIFF':
127 raise Error, 'file does not start with RIFF id'
128 if self._file.read(4) != 'WAVE':
129 raise Error, 'not a WAVE file'
130 self._fmt_chunk_read = 0
131 self._data_chunk = None
132 while 1:
133 self._data_seek_needed = 1
134 try:
135 chunk = Chunk(self._file, bigendian = 0)
136 except EOFError:
137 break
138 chunkname = chunk.getname()
139 if chunkname == 'fmt ':
140 self._read_fmt_chunk(chunk)
141 self._fmt_chunk_read = 1
142 elif chunkname == 'data':
143 if not self._fmt_chunk_read:
144 raise Error, 'data chunk before fmt chunk'
145 self._data_chunk = chunk
146 self._nframes = chunk.chunksize / self._framesize
147 self._data_seek_needed = 0
148 break
149 chunk.skip()
150 if not self._fmt_chunk_read or not self._data_chunk:
151 raise Error, 'fmt chunk and/or data chunk missing'
Guido van Rossum3ed23cc1994-02-15 15:57:15 +0000152
Guido van Rossume7b146f2000-02-04 15:28:42 +0000153 def __init__(self, f):
154 if type(f) == type(''):
155 f = __builtin__.open(f, 'rb')
156 # else, assume it is an open file object already
157 self.initfp(f)
Guido van Rossum3ed23cc1994-02-15 15:57:15 +0000158
Guido van Rossume7b146f2000-02-04 15:28:42 +0000159 #
160 # User visible methods.
161 #
162 def getfp(self):
163 return self._file
Guido van Rossum3ed23cc1994-02-15 15:57:15 +0000164
Guido van Rossume7b146f2000-02-04 15:28:42 +0000165 def rewind(self):
166 self._data_seek_needed = 1
167 self._soundpos = 0
Guido van Rossum3ed23cc1994-02-15 15:57:15 +0000168
Guido van Rossume7b146f2000-02-04 15:28:42 +0000169 def close(self):
170 self._file = None
Guido van Rossum3ed23cc1994-02-15 15:57:15 +0000171
Guido van Rossume7b146f2000-02-04 15:28:42 +0000172 def tell(self):
173 return self._soundpos
Guido van Rossum3ed23cc1994-02-15 15:57:15 +0000174
Guido van Rossume7b146f2000-02-04 15:28:42 +0000175 def getnchannels(self):
176 return self._nchannels
Guido van Rossum3ed23cc1994-02-15 15:57:15 +0000177
Guido van Rossume7b146f2000-02-04 15:28:42 +0000178 def getnframes(self):
179 return self._nframes
Guido van Rossum3ed23cc1994-02-15 15:57:15 +0000180
Guido van Rossume7b146f2000-02-04 15:28:42 +0000181 def getsampwidth(self):
182 return self._sampwidth
Guido van Rossum3ed23cc1994-02-15 15:57:15 +0000183
Guido van Rossume7b146f2000-02-04 15:28:42 +0000184 def getframerate(self):
185 return self._framerate
Guido van Rossum3ed23cc1994-02-15 15:57:15 +0000186
Guido van Rossume7b146f2000-02-04 15:28:42 +0000187 def getcomptype(self):
188 return self._comptype
Guido van Rossum3ed23cc1994-02-15 15:57:15 +0000189
Guido van Rossume7b146f2000-02-04 15:28:42 +0000190 def getcompname(self):
191 return self._compname
Guido van Rossum3ed23cc1994-02-15 15:57:15 +0000192
Guido van Rossume7b146f2000-02-04 15:28:42 +0000193 def getparams(self):
194 return self.getnchannels(), self.getsampwidth(), \
195 self.getframerate(), self.getnframes(), \
196 self.getcomptype(), self.getcompname()
Guido van Rossum3ed23cc1994-02-15 15:57:15 +0000197
Guido van Rossume7b146f2000-02-04 15:28:42 +0000198 def getmarkers(self):
199 return None
Guido van Rossum3ed23cc1994-02-15 15:57:15 +0000200
Guido van Rossume7b146f2000-02-04 15:28:42 +0000201 def getmark(self, id):
202 raise Error, 'no marks'
Guido van Rossum3ed23cc1994-02-15 15:57:15 +0000203
Guido van Rossume7b146f2000-02-04 15:28:42 +0000204 def setpos(self, pos):
205 if pos < 0 or pos > self._nframes:
206 raise Error, 'position not in range'
207 self._soundpos = pos
208 self._data_seek_needed = 1
Guido van Rossum3ed23cc1994-02-15 15:57:15 +0000209
Guido van Rossume7b146f2000-02-04 15:28:42 +0000210 def readframes(self, nframes):
211 if self._data_seek_needed:
212 self._data_chunk.seek(0, 0)
213 pos = self._soundpos * self._framesize
214 if pos:
215 self._data_chunk.seek(pos, 0)
216 self._data_seek_needed = 0
217 if nframes == 0:
218 return ''
219 if self._sampwidth > 1 and big_endian:
220 # unfortunately the fromfile() method does not take
221 # something that only looks like a file object, so
222 # we have to reach into the innards of the chunk object
223 import array
224 chunk = self._data_chunk
225 data = array.array(_array_fmts[self._sampwidth])
226 nitems = nframes * self._nchannels
227 if nitems * self._sampwidth > chunk.chunksize - chunk.size_read:
228 nitems = (chunk.chunksize - chunk.size_read) / self._sampwidth
229 data.fromfile(chunk.file.file, nitems)
230 # "tell" data chunk how much was read
231 chunk.size_read = chunk.size_read + nitems * self._sampwidth
232 # do the same for the outermost chunk
233 chunk = chunk.file
234 chunk.size_read = chunk.size_read + nitems * self._sampwidth
235 data.byteswap()
236 data = data.tostring()
237 else:
238 data = self._data_chunk.read(nframes * self._framesize)
239 if self._convert and data:
240 data = self._convert(data)
241 self._soundpos = self._soundpos + len(data) / (self._nchannels * self._sampwidth)
242 return data
243
244 #
245 # Internal methods.
246 #
247
248 def _read_fmt_chunk(self, chunk):
249 wFormatTag, self._nchannels, self._framerate, dwAvgBytesPerSec, wBlockAlign = struct.unpack('<hhllh', chunk.read(14))
250 if wFormatTag == WAVE_FORMAT_PCM:
251 sampwidth = struct.unpack('<h', chunk.read(2))[0]
252 self._sampwidth = (sampwidth + 7) / 8
253 else:
254 raise Error, 'unknown format: ' + `wFormatTag`
255 self._framesize = self._nchannels * self._sampwidth
256 self._comptype = 'NONE'
257 self._compname = 'not compressed'
Guido van Rossum3ed23cc1994-02-15 15:57:15 +0000258
259class Wave_write:
Guido van Rossume7b146f2000-02-04 15:28:42 +0000260 """Variables used in this class:
Guido van Rossum3ed23cc1994-02-15 15:57:15 +0000261
Guido van Rossume7b146f2000-02-04 15:28:42 +0000262 These variables are user settable through appropriate methods
263 of this class:
264 _file -- the open file with methods write(), close(), tell(), seek()
265 set through the __init__() method
266 _comptype -- the AIFF-C compression type ('NONE' in AIFF)
267 set through the setcomptype() or setparams() method
268 _compname -- the human-readable AIFF-C compression type
269 set through the setcomptype() or setparams() method
270 _nchannels -- the number of audio channels
271 set through the setnchannels() or setparams() method
272 _sampwidth -- the number of bytes per audio sample
273 set through the setsampwidth() or setparams() method
274 _framerate -- the sampling frequency
275 set through the setframerate() or setparams() method
276 _nframes -- the number of audio frames written to the header
277 set through the setnframes() or setparams() method
Guido van Rossum3ed23cc1994-02-15 15:57:15 +0000278
Guido van Rossume7b146f2000-02-04 15:28:42 +0000279 These variables are used internally only:
280 _datalength -- the size of the audio samples written to the header
281 _nframeswritten -- the number of frames actually written
282 _datawritten -- the size of the audio samples actually written
283 """
Guido van Rossum3ed23cc1994-02-15 15:57:15 +0000284
Guido van Rossume7b146f2000-02-04 15:28:42 +0000285 def __init__(self, f):
286 if type(f) == type(''):
287 f = __builtin__.open(f, 'wb')
288 self.initfp(f)
Guido van Rossum3ed23cc1994-02-15 15:57:15 +0000289
Guido van Rossume7b146f2000-02-04 15:28:42 +0000290 def initfp(self, file):
291 self._file = file
292 self._convert = None
293 self._nchannels = 0
294 self._sampwidth = 0
295 self._framerate = 0
296 self._nframes = 0
297 self._nframeswritten = 0
298 self._datawritten = 0
299 self._datalength = 0
Guido van Rossum3ed23cc1994-02-15 15:57:15 +0000300
Guido van Rossume7b146f2000-02-04 15:28:42 +0000301 def __del__(self):
302 if self._file:
303 self.close()
Guido van Rossum3ed23cc1994-02-15 15:57:15 +0000304
Guido van Rossume7b146f2000-02-04 15:28:42 +0000305 #
306 # User visible methods.
307 #
308 def setnchannels(self, nchannels):
309 if self._datawritten:
310 raise Error, 'cannot change parameters after starting to write'
311 if nchannels < 1:
312 raise Error, 'bad # of channels'
313 self._nchannels = nchannels
Guido van Rossum3ed23cc1994-02-15 15:57:15 +0000314
Guido van Rossume7b146f2000-02-04 15:28:42 +0000315 def getnchannels(self):
316 if not self._nchannels:
317 raise Error, 'number of channels not set'
318 return self._nchannels
Guido van Rossum3ed23cc1994-02-15 15:57:15 +0000319
Guido van Rossume7b146f2000-02-04 15:28:42 +0000320 def setsampwidth(self, sampwidth):
321 if self._datawritten:
322 raise Error, 'cannot change parameters after starting to write'
323 if sampwidth < 1 or sampwidth > 4:
324 raise Error, 'bad sample width'
325 self._sampwidth = sampwidth
Guido van Rossum3ed23cc1994-02-15 15:57:15 +0000326
Guido van Rossume7b146f2000-02-04 15:28:42 +0000327 def getsampwidth(self):
328 if not self._sampwidth:
329 raise Error, 'sample width not set'
330 return self._sampwidth
Guido van Rossum3ed23cc1994-02-15 15:57:15 +0000331
Guido van Rossume7b146f2000-02-04 15:28:42 +0000332 def setframerate(self, framerate):
333 if self._datawritten:
334 raise Error, 'cannot change parameters after starting to write'
335 if framerate <= 0:
336 raise Error, 'bad frame rate'
337 self._framerate = framerate
Guido van Rossum3ed23cc1994-02-15 15:57:15 +0000338
Guido van Rossume7b146f2000-02-04 15:28:42 +0000339 def getframerate(self):
340 if not self._framerate:
341 raise Error, 'frame rate not set'
342 return self._framerate
Guido van Rossum3ed23cc1994-02-15 15:57:15 +0000343
Guido van Rossume7b146f2000-02-04 15:28:42 +0000344 def setnframes(self, nframes):
345 if self._datawritten:
346 raise Error, 'cannot change parameters after starting to write'
347 self._nframes = nframes
Guido van Rossum3ed23cc1994-02-15 15:57:15 +0000348
Guido van Rossume7b146f2000-02-04 15:28:42 +0000349 def getnframes(self):
350 return self._nframeswritten
Guido van Rossum3ed23cc1994-02-15 15:57:15 +0000351
Guido van Rossume7b146f2000-02-04 15:28:42 +0000352 def setcomptype(self, comptype, compname):
353 if self._datawritten:
354 raise Error, 'cannot change parameters after starting to write'
355 if comptype not in ('NONE',):
356 raise Error, 'unsupported compression type'
357 self._comptype = comptype
358 self._compname = compname
Guido van Rossum3ed23cc1994-02-15 15:57:15 +0000359
Guido van Rossume7b146f2000-02-04 15:28:42 +0000360 def getcomptype(self):
361 return self._comptype
Guido van Rossum3ed23cc1994-02-15 15:57:15 +0000362
Guido van Rossume7b146f2000-02-04 15:28:42 +0000363 def getcompname(self):
364 return self._compname
Guido van Rossum3ed23cc1994-02-15 15:57:15 +0000365
Guido van Rossume7b146f2000-02-04 15:28:42 +0000366 def setparams(self, (nchannels, sampwidth, framerate, nframes, comptype, compname)):
367 if self._datawritten:
368 raise Error, 'cannot change parameters after starting to write'
369 self.setnchannels(nchannels)
370 self.setsampwidth(sampwidth)
371 self.setframerate(framerate)
372 self.setnframes(nframes)
373 self.setcomptype(comptype, compname)
Guido van Rossum3ed23cc1994-02-15 15:57:15 +0000374
Guido van Rossume7b146f2000-02-04 15:28:42 +0000375 def getparams(self):
376 if not self._nchannels or not self._sampwidth or not self._framerate:
377 raise Error, 'not all parameters set'
378 return self._nchannels, self._sampwidth, self._framerate, \
379 self._nframes, self._comptype, self._compname
Guido van Rossum3ed23cc1994-02-15 15:57:15 +0000380
Guido van Rossume7b146f2000-02-04 15:28:42 +0000381 def setmark(self, id, pos, name):
382 raise Error, 'setmark() not supported'
Guido van Rossum3ed23cc1994-02-15 15:57:15 +0000383
Guido van Rossume7b146f2000-02-04 15:28:42 +0000384 def getmark(self, id):
385 raise Error, 'no marks'
Guido van Rossum3ed23cc1994-02-15 15:57:15 +0000386
Guido van Rossume7b146f2000-02-04 15:28:42 +0000387 def getmarkers(self):
388 return None
389
390 def tell(self):
391 return self._nframeswritten
Guido van Rossum3ed23cc1994-02-15 15:57:15 +0000392
Guido van Rossume7b146f2000-02-04 15:28:42 +0000393 def writeframesraw(self, data):
394 self._ensure_header_written(len(data))
395 nframes = len(data) / (self._sampwidth * self._nchannels)
396 if self._convert:
397 data = self._convert(data)
398 if self._sampwidth > 1 and big_endian:
399 import array
400 data = array.array(_array_fmts[self._sampwidth], data)
401 data.byteswap()
402 data.tofile(self._file)
403 self._datawritten = self._datawritten + len(data) * self._sampwidth
404 else:
405 self._file.write(data)
406 self._datawritten = self._datawritten + len(data)
407 self._nframeswritten = self._nframeswritten + nframes
Guido van Rossum3ed23cc1994-02-15 15:57:15 +0000408
Guido van Rossume7b146f2000-02-04 15:28:42 +0000409 def writeframes(self, data):
410 self.writeframesraw(data)
411 if self._datalength != self._datawritten:
412 self._patchheader()
Guido van Rossum3ed23cc1994-02-15 15:57:15 +0000413
Guido van Rossume7b146f2000-02-04 15:28:42 +0000414 def close(self):
415 self._ensure_header_written(0)
416 if self._datalength != self._datawritten:
417 self._patchheader()
418 self._file.flush()
419 self._file = None
Guido van Rossum3ed23cc1994-02-15 15:57:15 +0000420
Guido van Rossume7b146f2000-02-04 15:28:42 +0000421 #
422 # Internal methods.
423 #
Guido van Rossum3ed23cc1994-02-15 15:57:15 +0000424
Guido van Rossume7b146f2000-02-04 15:28:42 +0000425 def _ensure_header_written(self, datasize):
426 if not self._datawritten:
427 if not self._nchannels:
428 raise Error, '# channels not specified'
429 if not self._sampwidth:
430 raise Error, 'sample width not specified'
431 if not self._framerate:
432 raise Error, 'sampling rate not specified'
433 self._write_header(datasize)
434
435 def _write_header(self, initlength):
436 self._file.write('RIFF')
437 if not self._nframes:
438 self._nframes = initlength / (self._nchannels * self._sampwidth)
439 self._datalength = self._nframes * self._nchannels * self._sampwidth
440 self._form_length_pos = self._file.tell()
441 self._file.write(struct.pack('<lsslhhllhhs',
442 36 + self._datalength, 'WAVE', 'fmt ', 16,
443 WAVE_FORMAT_PCM, self._nchannels, self._framerate,
444 self._nchannels * self._framerate * self._sampwidth,
445 self._nchannels * self._sampwidth,
446 self._sampwidth * 8, 'data'))
447 self._data_length_pos = self._file.tell()
448 self._file.write(struct.pack('<l', self._datalength))
449
450 def _patchheader(self):
451 if self._datawritten == self._datalength:
452 return
453 curpos = self._file.tell()
454 self._file.seek(self._form_length_pos, 0)
455 self._file.write(struct.pack('<l', 36 + self._datawritten))
456 self._file.seek(self._data_length_pos, 0)
457 self._file.write(struct.pack('<l', self._datawritten))
458 self._file.seek(curpos, 0)
459 self._datalength = self._datawritten
Guido van Rossum3ed23cc1994-02-15 15:57:15 +0000460
Fred Drakef9607821999-06-17 15:18:47 +0000461def open(f, mode=None):
Guido van Rossume7b146f2000-02-04 15:28:42 +0000462 if mode is None:
463 if hasattr(f, 'mode'):
464 mode = f.mode
465 else:
466 mode = 'rb'
467 if mode in ('r', 'rb'):
468 return Wave_read(f)
469 elif mode in ('w', 'wb'):
470 return Wave_write(f)
471 else:
472 raise Error, "mode must be 'r', 'rb', 'w', or 'wb'"
Guido van Rossum3ed23cc1994-02-15 15:57:15 +0000473
474openfp = open # B/W compatibility