blob: ad4f29aae840436f4e32cd17582d3e0bafc97a9d [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):
Tim Peterscfc41782000-10-09 23:43:55 +0000155 self._i_opened_the_file = None
Guido van Rossume7b146f2000-02-04 15:28:42 +0000156 if type(f) == type(''):
157 f = __builtin__.open(f, 'rb')
Tim Peterscfc41782000-10-09 23:43:55 +0000158 self._i_opened_the_file = f
Guido van Rossume7b146f2000-02-04 15:28:42 +0000159 # else, assume it is an open file object already
160 self.initfp(f)
Guido van Rossum3ed23cc1994-02-15 15:57:15 +0000161
Tim Peterscfc41782000-10-09 23:43:55 +0000162 def __del__(self):
163 self.close()
Guido van Rossume7b146f2000-02-04 15:28:42 +0000164 #
165 # User visible methods.
166 #
167 def getfp(self):
168 return self._file
Guido van Rossum3ed23cc1994-02-15 15:57:15 +0000169
Guido van Rossume7b146f2000-02-04 15:28:42 +0000170 def rewind(self):
171 self._data_seek_needed = 1
172 self._soundpos = 0
Guido van Rossum3ed23cc1994-02-15 15:57:15 +0000173
Guido van Rossume7b146f2000-02-04 15:28:42 +0000174 def close(self):
Tim Peterscfc41782000-10-09 23:43:55 +0000175 if self._i_opened_the_file:
176 self._i_opened_the_file.close()
177 self._i_opened_the_file = None
Guido van Rossume7b146f2000-02-04 15:28:42 +0000178 self._file = None
Guido van Rossum3ed23cc1994-02-15 15:57:15 +0000179
Guido van Rossume7b146f2000-02-04 15:28:42 +0000180 def tell(self):
181 return self._soundpos
Guido van Rossum3ed23cc1994-02-15 15:57:15 +0000182
Guido van Rossume7b146f2000-02-04 15:28:42 +0000183 def getnchannels(self):
184 return self._nchannels
Guido van Rossum3ed23cc1994-02-15 15:57:15 +0000185
Guido van Rossume7b146f2000-02-04 15:28:42 +0000186 def getnframes(self):
187 return self._nframes
Guido van Rossum3ed23cc1994-02-15 15:57:15 +0000188
Guido van Rossume7b146f2000-02-04 15:28:42 +0000189 def getsampwidth(self):
190 return self._sampwidth
Guido van Rossum3ed23cc1994-02-15 15:57:15 +0000191
Guido van Rossume7b146f2000-02-04 15:28:42 +0000192 def getframerate(self):
193 return self._framerate
Guido van Rossum3ed23cc1994-02-15 15:57:15 +0000194
Guido van Rossume7b146f2000-02-04 15:28:42 +0000195 def getcomptype(self):
196 return self._comptype
Guido van Rossum3ed23cc1994-02-15 15:57:15 +0000197
Guido van Rossume7b146f2000-02-04 15:28:42 +0000198 def getcompname(self):
199 return self._compname
Guido van Rossum3ed23cc1994-02-15 15:57:15 +0000200
Guido van Rossume7b146f2000-02-04 15:28:42 +0000201 def getparams(self):
202 return self.getnchannels(), self.getsampwidth(), \
203 self.getframerate(), self.getnframes(), \
204 self.getcomptype(), self.getcompname()
Guido van Rossum3ed23cc1994-02-15 15:57:15 +0000205
Guido van Rossume7b146f2000-02-04 15:28:42 +0000206 def getmarkers(self):
207 return None
Guido van Rossum3ed23cc1994-02-15 15:57:15 +0000208
Guido van Rossume7b146f2000-02-04 15:28:42 +0000209 def getmark(self, id):
210 raise Error, 'no marks'
Guido van Rossum3ed23cc1994-02-15 15:57:15 +0000211
Guido van Rossume7b146f2000-02-04 15:28:42 +0000212 def setpos(self, pos):
213 if pos < 0 or pos > self._nframes:
214 raise Error, 'position not in range'
215 self._soundpos = pos
216 self._data_seek_needed = 1
Guido van Rossum3ed23cc1994-02-15 15:57:15 +0000217
Guido van Rossume7b146f2000-02-04 15:28:42 +0000218 def readframes(self, nframes):
219 if self._data_seek_needed:
220 self._data_chunk.seek(0, 0)
221 pos = self._soundpos * self._framesize
222 if pos:
223 self._data_chunk.seek(pos, 0)
224 self._data_seek_needed = 0
225 if nframes == 0:
226 return ''
227 if self._sampwidth > 1 and big_endian:
228 # unfortunately the fromfile() method does not take
229 # something that only looks like a file object, so
230 # we have to reach into the innards of the chunk object
231 import array
232 chunk = self._data_chunk
233 data = array.array(_array_fmts[self._sampwidth])
234 nitems = nframes * self._nchannels
235 if nitems * self._sampwidth > chunk.chunksize - chunk.size_read:
236 nitems = (chunk.chunksize - chunk.size_read) / self._sampwidth
237 data.fromfile(chunk.file.file, nitems)
238 # "tell" data chunk how much was read
239 chunk.size_read = chunk.size_read + nitems * self._sampwidth
240 # do the same for the outermost chunk
241 chunk = chunk.file
242 chunk.size_read = chunk.size_read + nitems * self._sampwidth
243 data.byteswap()
244 data = data.tostring()
245 else:
246 data = self._data_chunk.read(nframes * self._framesize)
247 if self._convert and data:
248 data = self._convert(data)
249 self._soundpos = self._soundpos + len(data) / (self._nchannels * self._sampwidth)
250 return data
251
252 #
253 # Internal methods.
254 #
255
256 def _read_fmt_chunk(self, chunk):
257 wFormatTag, self._nchannels, self._framerate, dwAvgBytesPerSec, wBlockAlign = struct.unpack('<hhllh', chunk.read(14))
258 if wFormatTag == WAVE_FORMAT_PCM:
259 sampwidth = struct.unpack('<h', chunk.read(2))[0]
260 self._sampwidth = (sampwidth + 7) / 8
261 else:
262 raise Error, 'unknown format: ' + `wFormatTag`
263 self._framesize = self._nchannels * self._sampwidth
264 self._comptype = 'NONE'
265 self._compname = 'not compressed'
Guido van Rossum3ed23cc1994-02-15 15:57:15 +0000266
267class Wave_write:
Guido van Rossume7b146f2000-02-04 15:28:42 +0000268 """Variables used in this class:
Guido van Rossum3ed23cc1994-02-15 15:57:15 +0000269
Guido van Rossume7b146f2000-02-04 15:28:42 +0000270 These variables are user settable through appropriate methods
271 of this class:
272 _file -- the open file with methods write(), close(), tell(), seek()
273 set through the __init__() method
274 _comptype -- the AIFF-C compression type ('NONE' in AIFF)
275 set through the setcomptype() or setparams() method
276 _compname -- the human-readable AIFF-C compression type
277 set through the setcomptype() or setparams() method
278 _nchannels -- the number of audio channels
279 set through the setnchannels() or setparams() method
280 _sampwidth -- the number of bytes per audio sample
281 set through the setsampwidth() or setparams() method
282 _framerate -- the sampling frequency
283 set through the setframerate() or setparams() method
284 _nframes -- the number of audio frames written to the header
285 set through the setnframes() or setparams() method
Guido van Rossum3ed23cc1994-02-15 15:57:15 +0000286
Guido van Rossume7b146f2000-02-04 15:28:42 +0000287 These variables are used internally only:
288 _datalength -- the size of the audio samples written to the header
289 _nframeswritten -- the number of frames actually written
290 _datawritten -- the size of the audio samples actually written
291 """
Guido van Rossum3ed23cc1994-02-15 15:57:15 +0000292
Guido van Rossume7b146f2000-02-04 15:28:42 +0000293 def __init__(self, f):
Tim Peterscfc41782000-10-09 23:43:55 +0000294 self._i_opened_the_file = None
Guido van Rossume7b146f2000-02-04 15:28:42 +0000295 if type(f) == type(''):
296 f = __builtin__.open(f, 'wb')
Tim Peterscfc41782000-10-09 23:43:55 +0000297 self._i_opened_the_file = f
Guido van Rossume7b146f2000-02-04 15:28:42 +0000298 self.initfp(f)
Guido van Rossum3ed23cc1994-02-15 15:57:15 +0000299
Guido van Rossume7b146f2000-02-04 15:28:42 +0000300 def initfp(self, file):
301 self._file = file
302 self._convert = None
303 self._nchannels = 0
304 self._sampwidth = 0
305 self._framerate = 0
306 self._nframes = 0
307 self._nframeswritten = 0
308 self._datawritten = 0
309 self._datalength = 0
Guido van Rossum3ed23cc1994-02-15 15:57:15 +0000310
Guido van Rossume7b146f2000-02-04 15:28:42 +0000311 def __del__(self):
Tim Peterscfc41782000-10-09 23:43:55 +0000312 self.close()
Guido van Rossum3ed23cc1994-02-15 15:57:15 +0000313
Guido van Rossume7b146f2000-02-04 15:28:42 +0000314 #
315 # User visible methods.
316 #
317 def setnchannels(self, nchannels):
318 if self._datawritten:
319 raise Error, 'cannot change parameters after starting to write'
320 if nchannels < 1:
321 raise Error, 'bad # of channels'
322 self._nchannels = nchannels
Guido van Rossum3ed23cc1994-02-15 15:57:15 +0000323
Guido van Rossume7b146f2000-02-04 15:28:42 +0000324 def getnchannels(self):
325 if not self._nchannels:
326 raise Error, 'number of channels not set'
327 return self._nchannels
Guido van Rossum3ed23cc1994-02-15 15:57:15 +0000328
Guido van Rossume7b146f2000-02-04 15:28:42 +0000329 def setsampwidth(self, sampwidth):
330 if self._datawritten:
331 raise Error, 'cannot change parameters after starting to write'
332 if sampwidth < 1 or sampwidth > 4:
333 raise Error, 'bad sample width'
334 self._sampwidth = sampwidth
Guido van Rossum3ed23cc1994-02-15 15:57:15 +0000335
Guido van Rossume7b146f2000-02-04 15:28:42 +0000336 def getsampwidth(self):
337 if not self._sampwidth:
338 raise Error, 'sample width not set'
339 return self._sampwidth
Guido van Rossum3ed23cc1994-02-15 15:57:15 +0000340
Guido van Rossume7b146f2000-02-04 15:28:42 +0000341 def setframerate(self, framerate):
342 if self._datawritten:
343 raise Error, 'cannot change parameters after starting to write'
344 if framerate <= 0:
345 raise Error, 'bad frame rate'
346 self._framerate = framerate
Guido van Rossum3ed23cc1994-02-15 15:57:15 +0000347
Guido van Rossume7b146f2000-02-04 15:28:42 +0000348 def getframerate(self):
349 if not self._framerate:
350 raise Error, 'frame rate not set'
351 return self._framerate
Guido van Rossum3ed23cc1994-02-15 15:57:15 +0000352
Guido van Rossume7b146f2000-02-04 15:28:42 +0000353 def setnframes(self, nframes):
354 if self._datawritten:
355 raise Error, 'cannot change parameters after starting to write'
356 self._nframes = nframes
Guido van Rossum3ed23cc1994-02-15 15:57:15 +0000357
Guido van Rossume7b146f2000-02-04 15:28:42 +0000358 def getnframes(self):
359 return self._nframeswritten
Guido van Rossum3ed23cc1994-02-15 15:57:15 +0000360
Guido van Rossume7b146f2000-02-04 15:28:42 +0000361 def setcomptype(self, comptype, compname):
362 if self._datawritten:
363 raise Error, 'cannot change parameters after starting to write'
364 if comptype not in ('NONE',):
365 raise Error, 'unsupported compression type'
366 self._comptype = comptype
367 self._compname = compname
Guido van Rossum3ed23cc1994-02-15 15:57:15 +0000368
Guido van Rossume7b146f2000-02-04 15:28:42 +0000369 def getcomptype(self):
370 return self._comptype
Guido van Rossum3ed23cc1994-02-15 15:57:15 +0000371
Guido van Rossume7b146f2000-02-04 15:28:42 +0000372 def getcompname(self):
373 return self._compname
Guido van Rossum3ed23cc1994-02-15 15:57:15 +0000374
Guido van Rossume7b146f2000-02-04 15:28:42 +0000375 def setparams(self, (nchannels, sampwidth, framerate, nframes, comptype, compname)):
376 if self._datawritten:
377 raise Error, 'cannot change parameters after starting to write'
378 self.setnchannels(nchannels)
379 self.setsampwidth(sampwidth)
380 self.setframerate(framerate)
381 self.setnframes(nframes)
382 self.setcomptype(comptype, compname)
Guido van Rossum3ed23cc1994-02-15 15:57:15 +0000383
Guido van Rossume7b146f2000-02-04 15:28:42 +0000384 def getparams(self):
385 if not self._nchannels or not self._sampwidth or not self._framerate:
386 raise Error, 'not all parameters set'
387 return self._nchannels, self._sampwidth, self._framerate, \
388 self._nframes, self._comptype, self._compname
Guido van Rossum3ed23cc1994-02-15 15:57:15 +0000389
Guido van Rossume7b146f2000-02-04 15:28:42 +0000390 def setmark(self, id, pos, name):
391 raise Error, 'setmark() not supported'
Guido van Rossum3ed23cc1994-02-15 15:57:15 +0000392
Guido van Rossume7b146f2000-02-04 15:28:42 +0000393 def getmark(self, id):
394 raise Error, 'no marks'
Guido van Rossum3ed23cc1994-02-15 15:57:15 +0000395
Guido van Rossume7b146f2000-02-04 15:28:42 +0000396 def getmarkers(self):
397 return None
Tim Peterse1190062001-01-15 03:34:38 +0000398
Guido van Rossume7b146f2000-02-04 15:28:42 +0000399 def tell(self):
400 return self._nframeswritten
Guido van Rossum3ed23cc1994-02-15 15:57:15 +0000401
Guido van Rossume7b146f2000-02-04 15:28:42 +0000402 def writeframesraw(self, data):
403 self._ensure_header_written(len(data))
404 nframes = len(data) / (self._sampwidth * self._nchannels)
405 if self._convert:
406 data = self._convert(data)
407 if self._sampwidth > 1 and big_endian:
408 import array
409 data = array.array(_array_fmts[self._sampwidth], data)
410 data.byteswap()
411 data.tofile(self._file)
412 self._datawritten = self._datawritten + len(data) * self._sampwidth
413 else:
414 self._file.write(data)
415 self._datawritten = self._datawritten + len(data)
416 self._nframeswritten = self._nframeswritten + nframes
Guido van Rossum3ed23cc1994-02-15 15:57:15 +0000417
Guido van Rossume7b146f2000-02-04 15:28:42 +0000418 def writeframes(self, data):
419 self.writeframesraw(data)
420 if self._datalength != self._datawritten:
421 self._patchheader()
Guido van Rossum3ed23cc1994-02-15 15:57:15 +0000422
Guido van Rossume7b146f2000-02-04 15:28:42 +0000423 def close(self):
Tim Peterscfc41782000-10-09 23:43:55 +0000424 if self._file:
425 self._ensure_header_written(0)
426 if self._datalength != self._datawritten:
427 self._patchheader()
428 self._file.flush()
429 self._file = None
430 if self._i_opened_the_file:
431 self._i_opened_the_file.close()
432 self._i_opened_the_file = None
Guido van Rossum3ed23cc1994-02-15 15:57:15 +0000433
Guido van Rossume7b146f2000-02-04 15:28:42 +0000434 #
435 # Internal methods.
436 #
Guido van Rossum3ed23cc1994-02-15 15:57:15 +0000437
Guido van Rossume7b146f2000-02-04 15:28:42 +0000438 def _ensure_header_written(self, datasize):
439 if not self._datawritten:
440 if not self._nchannels:
441 raise Error, '# channels not specified'
442 if not self._sampwidth:
443 raise Error, 'sample width not specified'
444 if not self._framerate:
445 raise Error, 'sampling rate not specified'
446 self._write_header(datasize)
447
448 def _write_header(self, initlength):
449 self._file.write('RIFF')
450 if not self._nframes:
451 self._nframes = initlength / (self._nchannels * self._sampwidth)
452 self._datalength = self._nframes * self._nchannels * self._sampwidth
453 self._form_length_pos = self._file.tell()
Guido van Rossumeca576c2000-10-09 20:01:53 +0000454 self._file.write(struct.pack('<l4s4slhhllhh4s',
Guido van Rossume7b146f2000-02-04 15:28:42 +0000455 36 + self._datalength, 'WAVE', 'fmt ', 16,
456 WAVE_FORMAT_PCM, self._nchannels, self._framerate,
457 self._nchannels * self._framerate * self._sampwidth,
458 self._nchannels * self._sampwidth,
459 self._sampwidth * 8, 'data'))
460 self._data_length_pos = self._file.tell()
461 self._file.write(struct.pack('<l', self._datalength))
462
463 def _patchheader(self):
464 if self._datawritten == self._datalength:
465 return
466 curpos = self._file.tell()
467 self._file.seek(self._form_length_pos, 0)
468 self._file.write(struct.pack('<l', 36 + self._datawritten))
469 self._file.seek(self._data_length_pos, 0)
470 self._file.write(struct.pack('<l', self._datawritten))
471 self._file.seek(curpos, 0)
472 self._datalength = self._datawritten
Guido van Rossum3ed23cc1994-02-15 15:57:15 +0000473
Fred Drakef9607821999-06-17 15:18:47 +0000474def open(f, mode=None):
Guido van Rossume7b146f2000-02-04 15:28:42 +0000475 if mode is None:
476 if hasattr(f, 'mode'):
477 mode = f.mode
478 else:
479 mode = 'rb'
480 if mode in ('r', 'rb'):
481 return Wave_read(f)
482 elif mode in ('w', 'wb'):
483 return Wave_write(f)
484 else:
485 raise Error, "mode must be 'r', 'rb', 'w', or 'wb'"
Guido van Rossum3ed23cc1994-02-15 15:57:15 +0000486
487openfp = open # B/W compatibility