blob: 5d1a1c16299b4d82e4577f277f9b752c19a20012 [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
18# getcomptype() -- returns compression type ('NONE' for AIFF files)
19# getcompname() -- returns human-readable version of
20# compression type ('not compressed' for AIFF files)
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)
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
52# setparams(nchannels, sampwidth, framerate, nframes, comptype, compname)
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
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
81def _read_long(file):
82 x = 0L
83 for i in range(4):
84 byte = file.read(1)
85 if byte == '':
86 raise EOFError
87 x = x + (ord(byte) << (8 * i))
88 if x >= 0x80000000L:
89 x = x - 0x100000000L
90 return int(x)
91
92def _read_ulong(file):
93 x = 0L
94 for i in range(4):
95 byte = file.read(1)
96 if byte == '':
97 raise EOFError
98 x = x + (ord(byte) << (8 * i))
99 return x
100
101def _read_short(file):
102 x = 0
103 for i in range(2):
104 byte = file.read(1)
105 if byte == '':
106 raise EOFError
107 x = x + (ord(byte) << (8 * i))
108 if x >= 0x8000:
109 x = x - 0x10000
110 return x
111
112def _write_short(f, x):
113 d, m = divmod(x, 256)
114 f.write(chr(m))
115 f.write(chr(d))
116
117def _write_long(f, x):
118 if x < 0:
119 x = x + 0x100000000L
120 for i in range(4):
121 d, m = divmod(x, 256)
122 f.write(chr(int(m)))
123 x = d
124
125class Chunk:
126 def __init__(self, file):
127 self.file = file
128 self.chunkname = self.file.read(4)
129 if len(self.chunkname) < 4:
130 raise EOFError
131 self.chunksize = _read_long(self.file)
132 self.size_read = 0
133 self.offset = self.file.tell()
134
135 def rewind(self):
136 self.file.seek(self.offset, 0)
137 self.size_read = 0
138
139 def setpos(self, pos):
140 if pos < 0 or pos > self.chunksize:
141 raise RuntimeError
142 self.file.seek(self.offset + pos, 0)
143 self.size_read = pos
144
145 def read(self, length):
146 if self.size_read >= self.chunksize:
147 return ''
148 if length > self.chunksize - self.size_read:
149 length = self.chunksize - self.size_read
150 data = self.file.read(length)
151 self.size_read = self.size_read + len(data)
152 return data
153
154 def skip(self):
155 try:
156 self.file.seek(self.chunksize - self.size_read, 1)
157 except RuntimeError:
158 while self.size_read < self.chunksize:
159 dummy = self.read(8192)
160 if not dummy:
161 raise EOFError
162
163class Wave_read:
164 # Variables used in this class:
165 #
166 # These variables are available to the user though appropriate
167 # methods of this class:
168 # _file -- the open file with methods read(), close(), and seek()
169 # set through the __init__() method
170 # _nchannels -- the number of audio channels
171 # available through the getnchannels() method
172 # _nframes -- the number of audio frames
173 # available through the getnframes() method
174 # _sampwidth -- the number of bytes per audio sample
175 # available through the getsampwidth() method
176 # _framerate -- the sampling frequency
177 # available through the getframerate() method
178 # _comptype -- the AIFF-C compression type ('NONE' if AIFF)
179 # available through the getcomptype() method
180 # _compname -- the human-readable AIFF-C compression type
181 # available through the getcomptype() method
182 # _soundpos -- the position in the audio stream
183 # available through the tell() method, set through the
184 # setpos() method
185 #
186 # These variables are used internally only:
187 # _fmt_chunk_read -- 1 iff the FMT chunk has been read
188 # _data_seek_needed -- 1 iff positioned correctly in audio
189 # file for readframes()
190 # _data_chunk -- instantiation of a chunk class for the DATA chunk
191 # _framesize -- size of one frame in the file
192
193 access _file, _nchannels, _nframes, _sampwidth, _framerate, \
194 _comptype, _compname, _soundpos, \
195 _fmt_chunk_read, _data_seek_needed, \
196 _data_chunk, _framesize: private
197
198 def initfp(self, file):
199 self._file = file
200 self._convert = None
201 self._soundpos = 0
202 form = self._file.read(4)
203 if form != 'RIFF':
204 raise Error, 'file does not start with RIFF id'
205 formlength = _read_long(self._file)
206 if formlength <= 0:
207 raise Error, 'invalid FORM chunk data size'
208 formdata = self._file.read(4)
209 formlength = formlength - 4
210 if formdata != 'WAVE':
211 raise Error, 'not a WAVE file'
212 self._fmt_chunk_read = 0
213 while formlength > 0:
214 self._data_seek_needed = 1
215 chunk = Chunk(self._file)
216 if chunk.chunkname == 'fmt ':
217 self._read_fmt_chunk(chunk)
218 self._fmt_chunk_read = 1
219 elif chunk.chunkname == 'data':
220 if not self._fmt_chunk_read:
221 raise Error, 'data chunk before fmt chunk'
222 self._data_chunk = chunk
223 self._nframes = chunk.chunksize / self._framesize
224 self._data_seek_needed = 0
225 formlength = formlength - 8 - chunk.chunksize
226 if formlength > 0:
227 chunk.skip()
228 if not self._fmt_chunk_read or not self._data_chunk:
229 raise Error, 'fmt chunk and/or data chunk missing'
230
231 def __init__(self, f):
232 if type(f) == type(''):
233 f = __builtin__.open(f, 'r')
234 # else, assume it is an open file object already
235 self.initfp(f)
236
237 def __del__(self):
238 if self._file:
239 self.close()
240
241 #
242 # User visible methods.
243 #
244 def getfp(self):
245 return self._file
246
247 def rewind(self):
248 self._data_seek_needed = 1
249 self._soundpos = 0
250
251 def close(self):
252 self._file = None
253
254 def tell(self):
255 return self._soundpos
256
257 def getnchannels(self):
258 return self._nchannels
259
260 def getnframes(self):
261 return self._nframes
262
263 def getsampwidth(self):
264 return self._sampwidth
265
266 def getframerate(self):
267 return self._framerate
268
269 def getcomptype(self):
270 return self._comptype
271
272 def getcompname(self):
273 return self._compname
274
275 def getparams(self):
276 return self.getnchannels(), self.getsampwidth(), \
277 self.getframerate(), self.getnframes(), \
278 self.getcomptype(), self.getcompname()
279
280 def getmarkers(self):
281 return None
282
283 def getmark(self, id):
284 raise Error, 'no marks'
285
286 def setpos(self, pos):
287 if pos < 0 or pos > self._nframes:
288 raise Error, 'position not in range'
289 self._soundpos = pos
290 self._data_seek_needed = 1
291
292 def readframes(self, nframes):
293 if self._data_seek_needed:
294 self._data_chunk.rewind()
295 pos = self._soundpos * self._framesize
296 if pos:
297 self._data_chunk.setpos(pos)
298 self._data_seek_needed = 0
299 if nframes == 0:
300 return ''
301 if self._sampwidth > 1:
302 # unfortunately the fromfile() method does not take
303 # something that only looks like a file object, so
304 # we have to reach into the innards of the chunk object
305 import array
306 data = array.array(_array_fmts[self._sampwidth])
307 nitems = nframes * self._nchannels
308 if nitems * self._sampwidth > self._data_chunk.chunksize - self._data_chunk.size_read:
309 nitems = (self._data_chunk.chunksize - self._data_chunk.size_read) / self._sampwidth
310 data.fromfile(self._data_chunk.file, nitems)
311 self._data_chunk.size_read = self._data_chunk.size_read + nitems * self._sampwidth
312 data.byteswap()
313 data = data.tostring()
314 else:
315 data = self._data_chunk.read(nframes * self._framesize)
316 if self._convert and data:
317 data = self._convert(data)
318 self._soundpos = self._soundpos + len(data) / (self._nchannels * self._sampwidth)
319 return data
320
321 #
322 # Internal methods.
323 #
324 access *: private
325
326 def _read_fmt_chunk(self, chunk):
327 wFormatTag = _read_short(chunk)
328 self._nchannels = _read_short(chunk)
329 self._framerate = _read_long(chunk)
330 dwAvgBytesPerSec = _read_long(chunk)
331 wBlockAlign = _read_short(chunk)
332 if wFormatTag == WAVE_FORMAT_PCM:
333 self._sampwidth = (_read_short(chunk) + 7) / 8
334 else:
335 raise Error, 'unknown format'
336 self._framesize = self._nchannels * self._sampwidth
337 self._comptype = 'NONE'
338 self._compname = 'not compressed'
339
340class Wave_write:
341 # Variables used in this class:
342 #
343 # These variables are user settable through appropriate methods
344 # of this class:
345 # _file -- the open file with methods write(), close(), tell(), seek()
346 # set through the __init__() method
347 # _comptype -- the AIFF-C compression type ('NONE' in AIFF)
348 # set through the setcomptype() or setparams() method
349 # _compname -- the human-readable AIFF-C compression type
350 # set through the setcomptype() or setparams() method
351 # _nchannels -- the number of audio channels
352 # set through the setnchannels() or setparams() method
353 # _sampwidth -- the number of bytes per audio sample
354 # set through the setsampwidth() or setparams() method
355 # _framerate -- the sampling frequency
356 # set through the setframerate() or setparams() method
357 # _nframes -- the number of audio frames written to the header
358 # set through the setnframes() or setparams() method
359 #
360 # These variables are used internally only:
361 # _datalength -- the size of the audio samples written to the header
362 # _nframeswritten -- the number of frames actually written
363 # _datawritten -- the size of the audio samples actually written
364
365 access _file, _comptype, _compname, _nchannels, _sampwidth, \
366 _framerate, _nframes, _nframeswritten, \
367 _datalength, _datawritten: private
368
369 def __init__(self, f):
370 if type(f) == type(''):
371 f = __builtin__.open(f, 'w')
372 self.initfp(f)
373
374 def initfp(self, file):
375 self._file = file
376 self._convert = None
377 self._nchannels = 0
378 self._sampwidth = 0
379 self._framerate = 0
380 self._nframes = 0
381 self._nframeswritten = 0
382 self._datawritten = 0
383 self._datalength = 0
384
385 def __del__(self):
386 if self._file:
387 self.close()
388
389 #
390 # User visible methods.
391 #
392 def setnchannels(self, nchannels):
393 if self._datawritten:
394 raise Error, 'cannot change parameters after starting to write'
395 if nchannels < 1:
396 raise Error, 'bad # of channels'
397 self._nchannels = nchannels
398
399 def getnchannels(self):
400 if not self._nchannels:
401 raise Error, 'number of channels not set'
402 return self._nchannels
403
404 def setsampwidth(self, sampwidth):
405 if self._datawritten:
406 raise Error, 'cannot change parameters after starting to write'
407 if sampwidth < 1 or sampwidth > 4:
408 raise Error, 'bad sample width'
409 self._sampwidth = sampwidth
410
411 def getsampwidth(self):
412 if not self._sampwidth:
413 raise Error, 'sample width not set'
414 return self._sampwidth
415
416 def setframerate(self, framerate):
417 if self._datawritten:
418 raise Error, 'cannot change parameters after starting to write'
419 if framerate <= 0:
420 raise Error, 'bad frame rate'
421 self._framerate = framerate
422
423 def getframerate(self):
424 if not self._framerate:
425 raise Error, 'frame rate not set'
426 return self._framerate
427
428 def setnframes(self, nframes):
429 if self._datawritten:
430 raise Error, 'cannot change parameters after starting to write'
431 self._nframes = nframes
432
433 def getnframes(self):
434 return self._nframeswritten
435
436 def setcomptype(self, comptype, compname):
437 if self._datawritten:
438 raise Error, 'cannot change parameters after starting to write'
439 if comptype not in ('NONE',):
440 raise Error, 'unsupported compression type'
441 self._comptype = comptype
442 self._compname = compname
443
444 def getcomptype(self):
445 return self._comptype
446
447 def getcompname(self):
448 return self._compname
449
450 def setparams(self, (nchannels, sampwidth, framerate, nframes, comptype, compname)):
451 if self._datawritten:
452 raise Error, 'cannot change parameters after starting to write'
453 self.setnchannels(nchannels)
454 self.setsampwidth(sampwidth)
455 self.setframerate(framerate)
456 self.setnframes(nframes)
457 self.setcomptype(comptype, compname)
458
459 def getparams(self):
460 if not self._nchannels or not self._sampwidth or not self._framerate:
461 raise Error, 'not all parameters set'
462 return self._nchannels, self._sampwidth, self._framerate, \
463 self._nframes, self._comptype, self._compname
464
465 def setmark(self, id, pos, name):
466 raise Error, 'setmark() not supported'
467
468 def getmark(self, id):
469 raise Error, 'no marks'
470
471 def getmarkers(self):
472 return None
473
474 def tell(self):
475 return self._nframeswritten
476
477 def writeframesraw(self, data):
478 self._ensure_header_written(len(data))
479 nframes = len(data) / (self._sampwidth * self._nchannels)
480 if self._convert:
481 data = self._convert(data)
482 if self._sampwidth > 1:
483 import array
484 data = array.array(_array_fmts[self._sampwidth], data)
485 data.byteswap()
486 data.tofile(self._file)
487 self._datawritten = self._datawritten + len(data) * self._sampwidth
488 else:
489 self._file.write(data)
490 self._datawritten = self._datawritten + len(data)
491 self._nframeswritten = self._nframeswritten + nframes
492
493 def writeframes(self, data):
494 self.writeframesraw(data)
495 if self._datalength != self._datawritten:
496 self._patchheader()
497
498 def close(self):
499 self._ensure_header_written(0)
500 if self._datalength != self._datawritten:
501 self._patchheader()
502 self._file.flush()
503 self._file = None
504
505 #
506 # Internal methods.
507 #
508 access *: private
509
510 def _ensure_header_written(self, datasize):
511 if not self._datawritten:
512 if not self._nchannels:
513 raise Error, '# channels not specified'
514 if not self._sampwidth:
515 raise Error, 'sample width not specified'
516 if not self._framerate:
517 raise Error, 'sampling rate not specified'
518 self._write_header(datasize)
519
520 def _write_header(self, initlength):
521 self._file.write('RIFF')
522 if not self._nframes:
523 self._nframes = initlength / (self._nchannels * self._sampwidth)
524 self._datalength = self._nframes * self._nchannels * self._sampwidth
525 self._form_length_pos = self._file.tell()
526 _write_long(self._file, 36 + self._datalength)
527 self._file.write('WAVE')
528 self._file.write('fmt ')
529 _write_long(self._file, 16)
530 _write_short(self._file, WAVE_FORMAT_PCM)
531 _write_short(self._file, self._nchannels)
532 _write_long(self._file, self._framerate)
533 _write_long(self._file, self._nchannels * self._framerate * self._sampwidth)
534 _write_short(self._file, self._nchannels * self._sampwidth)
535 _write_short(self._file, self._sampwidth * 8)
536 self._file.write('data')
537 self._data_length_pos = self._file.tell()
538 _write_long(self._file, self._datalength)
539
540 def _patchheader(self):
541 if self._datawritten == self._datalength:
542 return
543 curpos = self._file.tell()
544 self._file.seek(self._form_length_pos, 0)
545 _write_long(36 + self._datawritten)
546 self._file.seek(self._data_length_pos, 0)
547 _write_long(self._file, self._datawritten)
548 self._file.seek(curpos, 0)
549 self._datalength = self._datawritten
550
551def open(f, mode):
552 if mode == 'r':
553 return Wave_read(f)
554 elif mode == 'w':
555 return Wave_write(f)
556 else:
557 raise Error, "mode must be 'r' or 'w'"
558
559openfp = open # B/W compatibility