blob: aec9bdf81b0e5774082785e05695ed623e51d668 [file] [log] [blame]
Guido van Rossum3ed23cc1994-02-15 15:57:15 +00001# Stuff to parse WAVE files.
2#
3# Usage.
4#
5# Reading 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.
11#
12# This 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
Guido van Rossumbb189db1998-04-23 21:40:02 +000018# getcomptype() -- returns compression type ('NONE' for linear samples)
Guido van Rossum3ed23cc1994-02-15 15:57:15 +000019# getcompname() -- returns human-readable version of
Guido van Rossumbb189db1998-04-23 21:40:02 +000020# compression type ('not compressed' linear samples)
Guido van Rossum3ed23cc1994-02-15 15:57:15 +000021# 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)
32# The position returned by tell() and the position given to setpos()
33# are compatible and have nothing to do with the actual postion in the
34# file.
35# The close() method is called automatically when the class instance
36# is destroyed.
37#
38# Writing WAVE files:
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
42# close().
43#
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
Guido van Rossumbb189db1998-04-23 21:40:02 +000052# setparams(tuple)
Guido van Rossum3ed23cc1994-02-15 15:57:15 +000053# -- 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
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
65# be patched up.
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
71# is destroyed.
72
73import __builtin__
74
75Error = 'wave.Error'
76
77WAVE_FORMAT_PCM = 0x0001
78
79_array_fmts = None, 'b', 'h', None, 'l'
80
Guido van Rossumebb9c921999-02-05 22:28:17 +000081# Determine endian-ness
82import struct
83if struct.pack("h", 1) == "\000\001":
84 big_endian = 1
85else:
86 big_endian = 0
87
Guido van Rossum3601e881999-08-26 15:50:43 +000088from chunk import Chunk
Guido van Rossum3ed23cc1994-02-15 15:57:15 +000089
90class Wave_read:
91 # Variables used in this class:
92 #
93 # These variables are available to the user though appropriate
94 # methods of this class:
95 # _file -- the open file with methods read(), close(), and seek()
96 # set through the __init__() method
97 # _nchannels -- the number of audio channels
98 # available through the getnchannels() method
99 # _nframes -- the number of audio frames
100 # available through the getnframes() method
101 # _sampwidth -- the number of bytes per audio sample
102 # available through the getsampwidth() method
103 # _framerate -- the sampling frequency
104 # available through the getframerate() method
105 # _comptype -- the AIFF-C compression type ('NONE' if AIFF)
106 # available through the getcomptype() method
107 # _compname -- the human-readable AIFF-C compression type
108 # available through the getcomptype() method
109 # _soundpos -- the position in the audio stream
110 # available through the tell() method, set through the
111 # setpos() method
112 #
113 # These variables are used internally only:
114 # _fmt_chunk_read -- 1 iff the FMT chunk has been read
115 # _data_seek_needed -- 1 iff positioned correctly in audio
116 # file for readframes()
117 # _data_chunk -- instantiation of a chunk class for the DATA chunk
118 # _framesize -- size of one frame in the file
119
Guido van Rossum3ed23cc1994-02-15 15:57:15 +0000120 def initfp(self, file):
Guido van Rossum3ed23cc1994-02-15 15:57:15 +0000121 self._convert = None
122 self._soundpos = 0
Guido van Rossum3601e881999-08-26 15:50:43 +0000123 self._file = Chunk(file, bigendian = 0)
124 if self._file.getname() != 'RIFF':
Guido van Rossum3ed23cc1994-02-15 15:57:15 +0000125 raise Error, 'file does not start with RIFF id'
Guido van Rossum3601e881999-08-26 15:50:43 +0000126 if self._file.read(4) != 'WAVE':
Guido van Rossum3ed23cc1994-02-15 15:57:15 +0000127 raise Error, 'not a WAVE file'
128 self._fmt_chunk_read = 0
Guido van Rossum3601e881999-08-26 15:50:43 +0000129 self._data_chunk = None
130 while 1:
Guido van Rossum3ed23cc1994-02-15 15:57:15 +0000131 self._data_seek_needed = 1
Guido van Rossum3601e881999-08-26 15:50:43 +0000132 try:
133 chunk = Chunk(self._file, bigendian = 0)
134 except EOFError:
135 break
136 chunkname = chunk.getname()
137 if chunkname == 'fmt ':
Guido van Rossum3ed23cc1994-02-15 15:57:15 +0000138 self._read_fmt_chunk(chunk)
139 self._fmt_chunk_read = 1
Guido van Rossum3601e881999-08-26 15:50:43 +0000140 elif chunkname == 'data':
Guido van Rossum3ed23cc1994-02-15 15:57:15 +0000141 if not self._fmt_chunk_read:
142 raise Error, 'data chunk before fmt chunk'
143 self._data_chunk = chunk
144 self._nframes = chunk.chunksize / self._framesize
145 self._data_seek_needed = 0
Guido van Rossum3601e881999-08-26 15:50:43 +0000146 break
147 chunk.skip()
Guido van Rossum3ed23cc1994-02-15 15:57:15 +0000148 if not self._fmt_chunk_read or not self._data_chunk:
149 raise Error, 'fmt chunk and/or data chunk missing'
150
151 def __init__(self, f):
152 if type(f) == type(''):
Fred Drakeac36c641998-04-16 16:44:33 +0000153 f = __builtin__.open(f, 'rb')
Guido van Rossum3ed23cc1994-02-15 15:57:15 +0000154 # else, assume it is an open file object already
155 self.initfp(f)
156
Guido van Rossum3ed23cc1994-02-15 15:57:15 +0000157 #
158 # User visible methods.
159 #
160 def getfp(self):
161 return self._file
162
163 def rewind(self):
164 self._data_seek_needed = 1
165 self._soundpos = 0
166
167 def close(self):
168 self._file = None
169
170 def tell(self):
171 return self._soundpos
172
173 def getnchannels(self):
174 return self._nchannels
175
176 def getnframes(self):
177 return self._nframes
178
179 def getsampwidth(self):
180 return self._sampwidth
181
182 def getframerate(self):
183 return self._framerate
184
185 def getcomptype(self):
186 return self._comptype
187
188 def getcompname(self):
189 return self._compname
190
191 def getparams(self):
192 return self.getnchannels(), self.getsampwidth(), \
193 self.getframerate(), self.getnframes(), \
194 self.getcomptype(), self.getcompname()
195
196 def getmarkers(self):
197 return None
198
199 def getmark(self, id):
200 raise Error, 'no marks'
201
202 def setpos(self, pos):
203 if pos < 0 or pos > self._nframes:
204 raise Error, 'position not in range'
205 self._soundpos = pos
206 self._data_seek_needed = 1
207
208 def readframes(self, nframes):
209 if self._data_seek_needed:
Guido van Rossum3601e881999-08-26 15:50:43 +0000210 self._data_chunk.seek(0, 0)
Guido van Rossum3ed23cc1994-02-15 15:57:15 +0000211 pos = self._soundpos * self._framesize
212 if pos:
Guido van Rossum3601e881999-08-26 15:50:43 +0000213 self._data_chunk.seek(pos, 0)
Guido van Rossum3ed23cc1994-02-15 15:57:15 +0000214 self._data_seek_needed = 0
215 if nframes == 0:
216 return ''
Guido van Rossumd42e46e1999-02-05 22:32:11 +0000217 if self._sampwidth > 1 and big_endian:
Guido van Rossum3ed23cc1994-02-15 15:57:15 +0000218 # unfortunately the fromfile() method does not take
219 # something that only looks like a file object, so
220 # we have to reach into the innards of the chunk object
221 import array
Guido van Rossum3601e881999-08-26 15:50:43 +0000222 chunk = self._data_chunk
Guido van Rossum3ed23cc1994-02-15 15:57:15 +0000223 data = array.array(_array_fmts[self._sampwidth])
224 nitems = nframes * self._nchannels
Guido van Rossum3601e881999-08-26 15:50:43 +0000225 if nitems * self._sampwidth > chunk.chunksize - chunk.size_read:
226 nitems = (chunk.chunksize - chunk.size_read) / self._sampwidth
227 data.fromfile(chunk.file.file, nitems)
228 # "tell" data chunk how much was read
229 chunk.size_read = chunk.size_read + nitems * self._sampwidth
230 # do the same for the outermost chunk
231 chunk = chunk.file
232 chunk.size_read = chunk.size_read + nitems * self._sampwidth
Guido van Rossumd42e46e1999-02-05 22:32:11 +0000233 data.byteswap()
Guido van Rossum3ed23cc1994-02-15 15:57:15 +0000234 data = data.tostring()
235 else:
236 data = self._data_chunk.read(nframes * self._framesize)
237 if self._convert and data:
238 data = self._convert(data)
239 self._soundpos = self._soundpos + len(data) / (self._nchannels * self._sampwidth)
240 return data
241
242 #
243 # Internal methods.
244 #
Guido van Rossum3ed23cc1994-02-15 15:57:15 +0000245
246 def _read_fmt_chunk(self, chunk):
Guido van Rossum3601e881999-08-26 15:50:43 +0000247 wFormatTag, self._nchannels, self._framerate, dwAvgBytesPerSec, wBlockAlign = struct.unpack('<hhllh', chunk.read(14))
Guido van Rossum3ed23cc1994-02-15 15:57:15 +0000248 if wFormatTag == WAVE_FORMAT_PCM:
Guido van Rossum3601e881999-08-26 15:50:43 +0000249 sampwidth = struct.unpack('<h', chunk.read(2))[0]
250 self._sampwidth = (sampwidth + 7) / 8
Guido van Rossum3ed23cc1994-02-15 15:57:15 +0000251 else:
Guido van Rossumbb189db1998-04-23 21:40:02 +0000252 raise Error, 'unknown format: ' + `wFormatTag`
Guido van Rossum3ed23cc1994-02-15 15:57:15 +0000253 self._framesize = self._nchannels * self._sampwidth
254 self._comptype = 'NONE'
255 self._compname = 'not compressed'
256
257class Wave_write:
258 # Variables used in this class:
259 #
260 # These variables are user settable through appropriate methods
261 # of this class:
262 # _file -- the open file with methods write(), close(), tell(), seek()
263 # set through the __init__() method
264 # _comptype -- the AIFF-C compression type ('NONE' in AIFF)
265 # set through the setcomptype() or setparams() method
266 # _compname -- the human-readable AIFF-C compression type
267 # set through the setcomptype() or setparams() method
268 # _nchannels -- the number of audio channels
269 # set through the setnchannels() or setparams() method
270 # _sampwidth -- the number of bytes per audio sample
271 # set through the setsampwidth() or setparams() method
272 # _framerate -- the sampling frequency
273 # set through the setframerate() or setparams() method
274 # _nframes -- the number of audio frames written to the header
275 # set through the setnframes() or setparams() method
276 #
277 # These variables are used internally only:
278 # _datalength -- the size of the audio samples written to the header
279 # _nframeswritten -- the number of frames actually written
280 # _datawritten -- the size of the audio samples actually written
281
Guido van Rossum3ed23cc1994-02-15 15:57:15 +0000282 def __init__(self, f):
283 if type(f) == type(''):
Fred Drakeac36c641998-04-16 16:44:33 +0000284 f = __builtin__.open(f, 'wb')
Guido van Rossum3ed23cc1994-02-15 15:57:15 +0000285 self.initfp(f)
286
287 def initfp(self, file):
288 self._file = file
289 self._convert = None
290 self._nchannels = 0
291 self._sampwidth = 0
292 self._framerate = 0
293 self._nframes = 0
294 self._nframeswritten = 0
295 self._datawritten = 0
296 self._datalength = 0
297
298 def __del__(self):
299 if self._file:
300 self.close()
301
302 #
303 # User visible methods.
304 #
305 def setnchannels(self, nchannels):
306 if self._datawritten:
307 raise Error, 'cannot change parameters after starting to write'
308 if nchannels < 1:
309 raise Error, 'bad # of channels'
310 self._nchannels = nchannels
311
312 def getnchannels(self):
313 if not self._nchannels:
314 raise Error, 'number of channels not set'
315 return self._nchannels
316
317 def setsampwidth(self, sampwidth):
318 if self._datawritten:
319 raise Error, 'cannot change parameters after starting to write'
320 if sampwidth < 1 or sampwidth > 4:
321 raise Error, 'bad sample width'
322 self._sampwidth = sampwidth
323
324 def getsampwidth(self):
325 if not self._sampwidth:
326 raise Error, 'sample width not set'
327 return self._sampwidth
328
329 def setframerate(self, framerate):
330 if self._datawritten:
331 raise Error, 'cannot change parameters after starting to write'
332 if framerate <= 0:
333 raise Error, 'bad frame rate'
334 self._framerate = framerate
335
336 def getframerate(self):
337 if not self._framerate:
338 raise Error, 'frame rate not set'
339 return self._framerate
340
341 def setnframes(self, nframes):
342 if self._datawritten:
343 raise Error, 'cannot change parameters after starting to write'
344 self._nframes = nframes
345
346 def getnframes(self):
347 return self._nframeswritten
348
349 def setcomptype(self, comptype, compname):
350 if self._datawritten:
351 raise Error, 'cannot change parameters after starting to write'
352 if comptype not in ('NONE',):
353 raise Error, 'unsupported compression type'
354 self._comptype = comptype
355 self._compname = compname
356
357 def getcomptype(self):
358 return self._comptype
359
360 def getcompname(self):
361 return self._compname
362
363 def setparams(self, (nchannels, sampwidth, framerate, nframes, comptype, compname)):
364 if self._datawritten:
365 raise Error, 'cannot change parameters after starting to write'
366 self.setnchannels(nchannels)
367 self.setsampwidth(sampwidth)
368 self.setframerate(framerate)
369 self.setnframes(nframes)
370 self.setcomptype(comptype, compname)
371
372 def getparams(self):
373 if not self._nchannels or not self._sampwidth or not self._framerate:
374 raise Error, 'not all parameters set'
375 return self._nchannels, self._sampwidth, self._framerate, \
376 self._nframes, self._comptype, self._compname
377
378 def setmark(self, id, pos, name):
379 raise Error, 'setmark() not supported'
380
381 def getmark(self, id):
382 raise Error, 'no marks'
383
384 def getmarkers(self):
385 return None
386
387 def tell(self):
388 return self._nframeswritten
389
390 def writeframesraw(self, data):
391 self._ensure_header_written(len(data))
392 nframes = len(data) / (self._sampwidth * self._nchannels)
393 if self._convert:
394 data = self._convert(data)
Guido van Rossumd42e46e1999-02-05 22:32:11 +0000395 if self._sampwidth > 1 and big_endian:
Guido van Rossum3ed23cc1994-02-15 15:57:15 +0000396 import array
397 data = array.array(_array_fmts[self._sampwidth], data)
Guido van Rossumd42e46e1999-02-05 22:32:11 +0000398 data.byteswap()
Guido van Rossum3ed23cc1994-02-15 15:57:15 +0000399 data.tofile(self._file)
400 self._datawritten = self._datawritten + len(data) * self._sampwidth
401 else:
402 self._file.write(data)
403 self._datawritten = self._datawritten + len(data)
404 self._nframeswritten = self._nframeswritten + nframes
405
406 def writeframes(self, data):
407 self.writeframesraw(data)
408 if self._datalength != self._datawritten:
409 self._patchheader()
410
411 def close(self):
412 self._ensure_header_written(0)
413 if self._datalength != self._datawritten:
414 self._patchheader()
415 self._file.flush()
416 self._file = None
417
418 #
419 # Internal methods.
420 #
Guido van Rossum3ed23cc1994-02-15 15:57:15 +0000421
422 def _ensure_header_written(self, datasize):
423 if not self._datawritten:
424 if not self._nchannels:
425 raise Error, '# channels not specified'
426 if not self._sampwidth:
427 raise Error, 'sample width not specified'
428 if not self._framerate:
429 raise Error, 'sampling rate not specified'
430 self._write_header(datasize)
431
432 def _write_header(self, initlength):
433 self._file.write('RIFF')
434 if not self._nframes:
435 self._nframes = initlength / (self._nchannels * self._sampwidth)
436 self._datalength = self._nframes * self._nchannels * self._sampwidth
437 self._form_length_pos = self._file.tell()
Guido van Rossum3601e881999-08-26 15:50:43 +0000438 self._file.write(struct.pack('<lsslhhllhhs',
439 36 + self._datalength, 'WAVE', 'fmt ', 16,
440 WAVE_FORMAT_PCM, self._nchannels, self._framerate,
441 self._nchannels * self._framerate * self._sampwidth,
442 self._nchannels * self._sampwidth,
443 self._sampwidth * 8, 'data'))
Guido van Rossum3ed23cc1994-02-15 15:57:15 +0000444 self._data_length_pos = self._file.tell()
Guido van Rossum3601e881999-08-26 15:50:43 +0000445 self._file.write(struct.pack('<l', self._datalength))
Guido van Rossum3ed23cc1994-02-15 15:57:15 +0000446
447 def _patchheader(self):
448 if self._datawritten == self._datalength:
449 return
450 curpos = self._file.tell()
451 self._file.seek(self._form_length_pos, 0)
Guido van Rossum3601e881999-08-26 15:50:43 +0000452 self._file.write(struct.pack('<l', 36 + self._datawritten))
Guido van Rossum3ed23cc1994-02-15 15:57:15 +0000453 self._file.seek(self._data_length_pos, 0)
Guido van Rossum3601e881999-08-26 15:50:43 +0000454 self._file.write(struct.pack('<l', self._datawritten))
Guido van Rossum3ed23cc1994-02-15 15:57:15 +0000455 self._file.seek(curpos, 0)
456 self._datalength = self._datawritten
457
Fred Drakef9607821999-06-17 15:18:47 +0000458def open(f, mode=None):
459 if mode is None:
460 if hasattr(f, 'mode'):
461 mode = f.mode
462 else:
463 mode = 'rb'
Fred Drakeac36c641998-04-16 16:44:33 +0000464 if mode in ('r', 'rb'):
Guido van Rossum3ed23cc1994-02-15 15:57:15 +0000465 return Wave_read(f)
Fred Drakeac36c641998-04-16 16:44:33 +0000466 elif mode in ('w', 'wb'):
Guido van Rossum3ed23cc1994-02-15 15:57:15 +0000467 return Wave_write(f)
468 else:
Fred Drakeac36c641998-04-16 16:44:33 +0000469 raise Error, "mode must be 'r', 'rb', 'w', or 'wb'"
Guido van Rossum3ed23cc1994-02-15 15:57:15 +0000470
471openfp = open # B/W compatibility