blob: 49e070f8939ea673ee4b3b5b29c083a3aa18143f [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
Fred Drake9b8d8012000-08-17 04:45:13 +000076class Error(Exception):
77 pass
Guido van Rossum3ed23cc1994-02-15 15:57:15 +000078
79WAVE_FORMAT_PCM = 0x0001
80
81_array_fmts = None, 'b', 'h', None, 'l'
82
Guido van Rossumebb9c921999-02-05 22:28:17 +000083# Determine endian-ness
84import struct
85if struct.pack("h", 1) == "\000\001":
Guido van Rossume7b146f2000-02-04 15:28:42 +000086 big_endian = 1
Guido van Rossumebb9c921999-02-05 22:28:17 +000087else:
Guido van Rossume7b146f2000-02-04 15:28:42 +000088 big_endian = 0
Guido van Rossumebb9c921999-02-05 22:28:17 +000089
Guido van Rossum3601e881999-08-26 15:50:43 +000090from chunk import Chunk
Guido van Rossum3ed23cc1994-02-15 15:57:15 +000091
92class Wave_read:
Guido van Rossume7b146f2000-02-04 15:28:42 +000093 """Variables used in this class:
Guido van Rossum3ed23cc1994-02-15 15:57:15 +000094
Guido van Rossume7b146f2000-02-04 15:28:42 +000095 These variables are available to the user though appropriate
96 methods of this class:
97 _file -- the open file with methods read(), close(), and seek()
98 set through the __init__() method
99 _nchannels -- the number of audio channels
100 available through the getnchannels() method
101 _nframes -- the number of audio frames
102 available through the getnframes() method
103 _sampwidth -- the number of bytes per audio sample
104 available through the getsampwidth() method
105 _framerate -- the sampling frequency
106 available through the getframerate() method
107 _comptype -- the AIFF-C compression type ('NONE' if AIFF)
108 available through the getcomptype() method
109 _compname -- the human-readable AIFF-C compression type
110 available through the getcomptype() method
111 _soundpos -- the position in the audio stream
112 available through the tell() method, set through the
113 setpos() method
Guido van Rossum3ed23cc1994-02-15 15:57:15 +0000114
Guido van Rossume7b146f2000-02-04 15:28:42 +0000115 These variables are used internally only:
116 _fmt_chunk_read -- 1 iff the FMT chunk has been read
117 _data_seek_needed -- 1 iff positioned correctly in audio
118 file for readframes()
119 _data_chunk -- instantiation of a chunk class for the DATA chunk
120 _framesize -- size of one frame in the file
121 """
Guido van Rossum3ed23cc1994-02-15 15:57:15 +0000122
Guido van Rossume7b146f2000-02-04 15:28:42 +0000123 def initfp(self, file):
124 self._convert = None
125 self._soundpos = 0
126 self._file = Chunk(file, bigendian = 0)
127 if self._file.getname() != 'RIFF':
128 raise Error, 'file does not start with RIFF id'
129 if self._file.read(4) != 'WAVE':
130 raise Error, 'not a WAVE file'
131 self._fmt_chunk_read = 0
132 self._data_chunk = None
133 while 1:
134 self._data_seek_needed = 1
135 try:
136 chunk = Chunk(self._file, bigendian = 0)
137 except EOFError:
138 break
139 chunkname = chunk.getname()
140 if chunkname == 'fmt ':
141 self._read_fmt_chunk(chunk)
142 self._fmt_chunk_read = 1
143 elif chunkname == 'data':
144 if not self._fmt_chunk_read:
145 raise Error, 'data chunk before fmt chunk'
146 self._data_chunk = chunk
147 self._nframes = chunk.chunksize / self._framesize
148 self._data_seek_needed = 0
149 break
150 chunk.skip()
151 if not self._fmt_chunk_read or not self._data_chunk:
152 raise Error, 'fmt chunk and/or data chunk missing'
Guido van Rossum3ed23cc1994-02-15 15:57:15 +0000153
Guido van Rossume7b146f2000-02-04 15:28:42 +0000154 def __init__(self, f):
155 if type(f) == type(''):
156 f = __builtin__.open(f, 'rb')
157 # else, assume it is an open file object already
158 self.initfp(f)
Guido van Rossum3ed23cc1994-02-15 15:57:15 +0000159
Guido van Rossume7b146f2000-02-04 15:28:42 +0000160 #
161 # User visible methods.
162 #
163 def getfp(self):
164 return self._file
Guido van Rossum3ed23cc1994-02-15 15:57:15 +0000165
Guido van Rossume7b146f2000-02-04 15:28:42 +0000166 def rewind(self):
167 self._data_seek_needed = 1
168 self._soundpos = 0
Guido van Rossum3ed23cc1994-02-15 15:57:15 +0000169
Guido van Rossume7b146f2000-02-04 15:28:42 +0000170 def close(self):
171 self._file = None
Guido van Rossum3ed23cc1994-02-15 15:57:15 +0000172
Guido van Rossume7b146f2000-02-04 15:28:42 +0000173 def tell(self):
174 return self._soundpos
Guido van Rossum3ed23cc1994-02-15 15:57:15 +0000175
Guido van Rossume7b146f2000-02-04 15:28:42 +0000176 def getnchannels(self):
177 return self._nchannels
Guido van Rossum3ed23cc1994-02-15 15:57:15 +0000178
Guido van Rossume7b146f2000-02-04 15:28:42 +0000179 def getnframes(self):
180 return self._nframes
Guido van Rossum3ed23cc1994-02-15 15:57:15 +0000181
Guido van Rossume7b146f2000-02-04 15:28:42 +0000182 def getsampwidth(self):
183 return self._sampwidth
Guido van Rossum3ed23cc1994-02-15 15:57:15 +0000184
Guido van Rossume7b146f2000-02-04 15:28:42 +0000185 def getframerate(self):
186 return self._framerate
Guido van Rossum3ed23cc1994-02-15 15:57:15 +0000187
Guido van Rossume7b146f2000-02-04 15:28:42 +0000188 def getcomptype(self):
189 return self._comptype
Guido van Rossum3ed23cc1994-02-15 15:57:15 +0000190
Guido van Rossume7b146f2000-02-04 15:28:42 +0000191 def getcompname(self):
192 return self._compname
Guido van Rossum3ed23cc1994-02-15 15:57:15 +0000193
Guido van Rossume7b146f2000-02-04 15:28:42 +0000194 def getparams(self):
195 return self.getnchannels(), self.getsampwidth(), \
196 self.getframerate(), self.getnframes(), \
197 self.getcomptype(), self.getcompname()
Guido van Rossum3ed23cc1994-02-15 15:57:15 +0000198
Guido van Rossume7b146f2000-02-04 15:28:42 +0000199 def getmarkers(self):
200 return None
Guido van Rossum3ed23cc1994-02-15 15:57:15 +0000201
Guido van Rossume7b146f2000-02-04 15:28:42 +0000202 def getmark(self, id):
203 raise Error, 'no marks'
Guido van Rossum3ed23cc1994-02-15 15:57:15 +0000204
Guido van Rossume7b146f2000-02-04 15:28:42 +0000205 def setpos(self, pos):
206 if pos < 0 or pos > self._nframes:
207 raise Error, 'position not in range'
208 self._soundpos = pos
209 self._data_seek_needed = 1
Guido van Rossum3ed23cc1994-02-15 15:57:15 +0000210
Guido van Rossume7b146f2000-02-04 15:28:42 +0000211 def readframes(self, nframes):
212 if self._data_seek_needed:
213 self._data_chunk.seek(0, 0)
214 pos = self._soundpos * self._framesize
215 if pos:
216 self._data_chunk.seek(pos, 0)
217 self._data_seek_needed = 0
218 if nframes == 0:
219 return ''
220 if self._sampwidth > 1 and big_endian:
221 # unfortunately the fromfile() method does not take
222 # something that only looks like a file object, so
223 # we have to reach into the innards of the chunk object
224 import array
225 chunk = self._data_chunk
226 data = array.array(_array_fmts[self._sampwidth])
227 nitems = nframes * self._nchannels
228 if nitems * self._sampwidth > chunk.chunksize - chunk.size_read:
229 nitems = (chunk.chunksize - chunk.size_read) / self._sampwidth
230 data.fromfile(chunk.file.file, nitems)
231 # "tell" data chunk how much was read
232 chunk.size_read = chunk.size_read + nitems * self._sampwidth
233 # do the same for the outermost chunk
234 chunk = chunk.file
235 chunk.size_read = chunk.size_read + nitems * self._sampwidth
236 data.byteswap()
237 data = data.tostring()
238 else:
239 data = self._data_chunk.read(nframes * self._framesize)
240 if self._convert and data:
241 data = self._convert(data)
242 self._soundpos = self._soundpos + len(data) / (self._nchannels * self._sampwidth)
243 return data
244
245 #
246 # Internal methods.
247 #
248
249 def _read_fmt_chunk(self, chunk):
250 wFormatTag, self._nchannels, self._framerate, dwAvgBytesPerSec, wBlockAlign = struct.unpack('<hhllh', chunk.read(14))
251 if wFormatTag == WAVE_FORMAT_PCM:
252 sampwidth = struct.unpack('<h', chunk.read(2))[0]
253 self._sampwidth = (sampwidth + 7) / 8
254 else:
255 raise Error, 'unknown format: ' + `wFormatTag`
256 self._framesize = self._nchannels * self._sampwidth
257 self._comptype = 'NONE'
258 self._compname = 'not compressed'
Guido van Rossum3ed23cc1994-02-15 15:57:15 +0000259
260class Wave_write:
Guido van Rossume7b146f2000-02-04 15:28:42 +0000261 """Variables used in this class:
Guido van Rossum3ed23cc1994-02-15 15:57:15 +0000262
Guido van Rossume7b146f2000-02-04 15:28:42 +0000263 These variables are user settable through appropriate methods
264 of this class:
265 _file -- the open file with methods write(), close(), tell(), seek()
266 set through the __init__() method
267 _comptype -- the AIFF-C compression type ('NONE' in AIFF)
268 set through the setcomptype() or setparams() method
269 _compname -- the human-readable AIFF-C compression type
270 set through the setcomptype() or setparams() method
271 _nchannels -- the number of audio channels
272 set through the setnchannels() or setparams() method
273 _sampwidth -- the number of bytes per audio sample
274 set through the setsampwidth() or setparams() method
275 _framerate -- the sampling frequency
276 set through the setframerate() or setparams() method
277 _nframes -- the number of audio frames written to the header
278 set through the setnframes() or setparams() method
Guido van Rossum3ed23cc1994-02-15 15:57:15 +0000279
Guido van Rossume7b146f2000-02-04 15:28:42 +0000280 These variables are used internally only:
281 _datalength -- the size of the audio samples written to the header
282 _nframeswritten -- the number of frames actually written
283 _datawritten -- the size of the audio samples actually written
284 """
Guido van Rossum3ed23cc1994-02-15 15:57:15 +0000285
Guido van Rossume7b146f2000-02-04 15:28:42 +0000286 def __init__(self, f):
287 if type(f) == type(''):
288 f = __builtin__.open(f, 'wb')
289 self.initfp(f)
Guido van Rossum3ed23cc1994-02-15 15:57:15 +0000290
Guido van Rossume7b146f2000-02-04 15:28:42 +0000291 def initfp(self, file):
292 self._file = file
293 self._convert = None
294 self._nchannels = 0
295 self._sampwidth = 0
296 self._framerate = 0
297 self._nframes = 0
298 self._nframeswritten = 0
299 self._datawritten = 0
300 self._datalength = 0
Guido van Rossum3ed23cc1994-02-15 15:57:15 +0000301
Guido van Rossume7b146f2000-02-04 15:28:42 +0000302 def __del__(self):
303 if self._file:
304 self.close()
Guido van Rossum3ed23cc1994-02-15 15:57:15 +0000305
Guido van Rossume7b146f2000-02-04 15:28:42 +0000306 #
307 # User visible methods.
308 #
309 def setnchannels(self, nchannels):
310 if self._datawritten:
311 raise Error, 'cannot change parameters after starting to write'
312 if nchannels < 1:
313 raise Error, 'bad # of channels'
314 self._nchannels = nchannels
Guido van Rossum3ed23cc1994-02-15 15:57:15 +0000315
Guido van Rossume7b146f2000-02-04 15:28:42 +0000316 def getnchannels(self):
317 if not self._nchannels:
318 raise Error, 'number of channels not set'
319 return self._nchannels
Guido van Rossum3ed23cc1994-02-15 15:57:15 +0000320
Guido van Rossume7b146f2000-02-04 15:28:42 +0000321 def setsampwidth(self, sampwidth):
322 if self._datawritten:
323 raise Error, 'cannot change parameters after starting to write'
324 if sampwidth < 1 or sampwidth > 4:
325 raise Error, 'bad sample width'
326 self._sampwidth = sampwidth
Guido van Rossum3ed23cc1994-02-15 15:57:15 +0000327
Guido van Rossume7b146f2000-02-04 15:28:42 +0000328 def getsampwidth(self):
329 if not self._sampwidth:
330 raise Error, 'sample width not set'
331 return self._sampwidth
Guido van Rossum3ed23cc1994-02-15 15:57:15 +0000332
Guido van Rossume7b146f2000-02-04 15:28:42 +0000333 def setframerate(self, framerate):
334 if self._datawritten:
335 raise Error, 'cannot change parameters after starting to write'
336 if framerate <= 0:
337 raise Error, 'bad frame rate'
338 self._framerate = framerate
Guido van Rossum3ed23cc1994-02-15 15:57:15 +0000339
Guido van Rossume7b146f2000-02-04 15:28:42 +0000340 def getframerate(self):
341 if not self._framerate:
342 raise Error, 'frame rate not set'
343 return self._framerate
Guido van Rossum3ed23cc1994-02-15 15:57:15 +0000344
Guido van Rossume7b146f2000-02-04 15:28:42 +0000345 def setnframes(self, nframes):
346 if self._datawritten:
347 raise Error, 'cannot change parameters after starting to write'
348 self._nframes = nframes
Guido van Rossum3ed23cc1994-02-15 15:57:15 +0000349
Guido van Rossume7b146f2000-02-04 15:28:42 +0000350 def getnframes(self):
351 return self._nframeswritten
Guido van Rossum3ed23cc1994-02-15 15:57:15 +0000352
Guido van Rossume7b146f2000-02-04 15:28:42 +0000353 def setcomptype(self, comptype, compname):
354 if self._datawritten:
355 raise Error, 'cannot change parameters after starting to write'
356 if comptype not in ('NONE',):
357 raise Error, 'unsupported compression type'
358 self._comptype = comptype
359 self._compname = compname
Guido van Rossum3ed23cc1994-02-15 15:57:15 +0000360
Guido van Rossume7b146f2000-02-04 15:28:42 +0000361 def getcomptype(self):
362 return self._comptype
Guido van Rossum3ed23cc1994-02-15 15:57:15 +0000363
Guido van Rossume7b146f2000-02-04 15:28:42 +0000364 def getcompname(self):
365 return self._compname
Guido van Rossum3ed23cc1994-02-15 15:57:15 +0000366
Guido van Rossume7b146f2000-02-04 15:28:42 +0000367 def setparams(self, (nchannels, sampwidth, framerate, nframes, comptype, compname)):
368 if self._datawritten:
369 raise Error, 'cannot change parameters after starting to write'
370 self.setnchannels(nchannels)
371 self.setsampwidth(sampwidth)
372 self.setframerate(framerate)
373 self.setnframes(nframes)
374 self.setcomptype(comptype, compname)
Guido van Rossum3ed23cc1994-02-15 15:57:15 +0000375
Guido van Rossume7b146f2000-02-04 15:28:42 +0000376 def getparams(self):
377 if not self._nchannels or not self._sampwidth or not self._framerate:
378 raise Error, 'not all parameters set'
379 return self._nchannels, self._sampwidth, self._framerate, \
380 self._nframes, self._comptype, self._compname
Guido van Rossum3ed23cc1994-02-15 15:57:15 +0000381
Guido van Rossume7b146f2000-02-04 15:28:42 +0000382 def setmark(self, id, pos, name):
383 raise Error, 'setmark() not supported'
Guido van Rossum3ed23cc1994-02-15 15:57:15 +0000384
Guido van Rossume7b146f2000-02-04 15:28:42 +0000385 def getmark(self, id):
386 raise Error, 'no marks'
Guido van Rossum3ed23cc1994-02-15 15:57:15 +0000387
Guido van Rossume7b146f2000-02-04 15:28:42 +0000388 def getmarkers(self):
389 return None
390
391 def tell(self):
392 return self._nframeswritten
Guido van Rossum3ed23cc1994-02-15 15:57:15 +0000393
Guido van Rossume7b146f2000-02-04 15:28:42 +0000394 def writeframesraw(self, data):
395 self._ensure_header_written(len(data))
396 nframes = len(data) / (self._sampwidth * self._nchannels)
397 if self._convert:
398 data = self._convert(data)
399 if self._sampwidth > 1 and big_endian:
400 import array
401 data = array.array(_array_fmts[self._sampwidth], data)
402 data.byteswap()
403 data.tofile(self._file)
404 self._datawritten = self._datawritten + len(data) * self._sampwidth
405 else:
406 self._file.write(data)
407 self._datawritten = self._datawritten + len(data)
408 self._nframeswritten = self._nframeswritten + nframes
Guido van Rossum3ed23cc1994-02-15 15:57:15 +0000409
Guido van Rossume7b146f2000-02-04 15:28:42 +0000410 def writeframes(self, data):
411 self.writeframesraw(data)
412 if self._datalength != self._datawritten:
413 self._patchheader()
Guido van Rossum3ed23cc1994-02-15 15:57:15 +0000414
Guido van Rossume7b146f2000-02-04 15:28:42 +0000415 def close(self):
416 self._ensure_header_written(0)
417 if self._datalength != self._datawritten:
418 self._patchheader()
419 self._file.flush()
420 self._file = None
Guido van Rossum3ed23cc1994-02-15 15:57:15 +0000421
Guido van Rossume7b146f2000-02-04 15:28:42 +0000422 #
423 # Internal methods.
424 #
Guido van Rossum3ed23cc1994-02-15 15:57:15 +0000425
Guido van Rossume7b146f2000-02-04 15:28:42 +0000426 def _ensure_header_written(self, datasize):
427 if not self._datawritten:
428 if not self._nchannels:
429 raise Error, '# channels not specified'
430 if not self._sampwidth:
431 raise Error, 'sample width not specified'
432 if not self._framerate:
433 raise Error, 'sampling rate not specified'
434 self._write_header(datasize)
435
436 def _write_header(self, initlength):
437 self._file.write('RIFF')
438 if not self._nframes:
439 self._nframes = initlength / (self._nchannels * self._sampwidth)
440 self._datalength = self._nframes * self._nchannels * self._sampwidth
441 self._form_length_pos = self._file.tell()
Guido van Rossumeca576c2000-10-09 20:01:53 +0000442 self._file.write(struct.pack('<l4s4slhhllhh4s',
Guido van Rossume7b146f2000-02-04 15:28:42 +0000443 36 + self._datalength, 'WAVE', 'fmt ', 16,
444 WAVE_FORMAT_PCM, self._nchannels, self._framerate,
445 self._nchannels * self._framerate * self._sampwidth,
446 self._nchannels * self._sampwidth,
447 self._sampwidth * 8, 'data'))
448 self._data_length_pos = self._file.tell()
449 self._file.write(struct.pack('<l', self._datalength))
450
451 def _patchheader(self):
452 if self._datawritten == self._datalength:
453 return
454 curpos = self._file.tell()
455 self._file.seek(self._form_length_pos, 0)
456 self._file.write(struct.pack('<l', 36 + self._datawritten))
457 self._file.seek(self._data_length_pos, 0)
458 self._file.write(struct.pack('<l', self._datawritten))
459 self._file.seek(curpos, 0)
460 self._datalength = self._datawritten
Guido van Rossum3ed23cc1994-02-15 15:57:15 +0000461
Fred Drakef9607821999-06-17 15:18:47 +0000462def open(f, mode=None):
Guido van Rossume7b146f2000-02-04 15:28:42 +0000463 if mode is None:
464 if hasattr(f, 'mode'):
465 mode = f.mode
466 else:
467 mode = 'rb'
468 if mode in ('r', 'rb'):
469 return Wave_read(f)
470 elif mode in ('w', 'wb'):
471 return Wave_write(f)
472 else:
473 raise Error, "mode must be 'r', 'rb', 'w', or 'wb'"
Guido van Rossum3ed23cc1994-02-15 15:57:15 +0000474
475openfp = open # B/W compatibility