The 0.5 release happened on 2/15, not on 2/14. :-)
[python/dscho.git] / Lib / wave.py
blob8bb9ff760ea7720d71fc9c114954fefc642b0d2d
1 """Stuff to parse WAVE files.
3 Usage.
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.
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 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)
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.
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().
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(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
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 """
74 import __builtin__
76 Error = 'wave.Error'
78 WAVE_FORMAT_PCM = 0x0001
80 _array_fmts = None, 'b', 'h', None, 'l'
82 # Determine endian-ness
83 import struct
84 if struct.pack("h", 1) == "\000\001":
85 big_endian = 1
86 else:
87 big_endian = 0
89 from chunk import Chunk
91 class Wave_read:
92 """Variables used in this class:
94 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
114 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
122 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'
153 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)
160 # User visible methods.
162 def getfp(self):
163 return self._file
165 def rewind(self):
166 self._data_seek_needed = 1
167 self._soundpos = 0
169 def close(self):
170 self._file = None
172 def tell(self):
173 return self._soundpos
175 def getnchannels(self):
176 return self._nchannels
178 def getnframes(self):
179 return self._nframes
181 def getsampwidth(self):
182 return self._sampwidth
184 def getframerate(self):
185 return self._framerate
187 def getcomptype(self):
188 return self._comptype
190 def getcompname(self):
191 return self._compname
193 def getparams(self):
194 return self.getnchannels(), self.getsampwidth(), \
195 self.getframerate(), self.getnframes(), \
196 self.getcomptype(), self.getcompname()
198 def getmarkers(self):
199 return None
201 def getmark(self, id):
202 raise Error, 'no marks'
204 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
210 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
245 # Internal methods.
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'
259 class Wave_write:
260 """Variables used in this class:
262 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
279 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
285 def __init__(self, f):
286 if type(f) == type(''):
287 f = __builtin__.open(f, 'wb')
288 self.initfp(f)
290 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
301 def __del__(self):
302 if self._file:
303 self.close()
306 # User visible methods.
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
315 def getnchannels(self):
316 if not self._nchannels:
317 raise Error, 'number of channels not set'
318 return self._nchannels
320 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
327 def getsampwidth(self):
328 if not self._sampwidth:
329 raise Error, 'sample width not set'
330 return self._sampwidth
332 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
339 def getframerate(self):
340 if not self._framerate:
341 raise Error, 'frame rate not set'
342 return self._framerate
344 def setnframes(self, nframes):
345 if self._datawritten:
346 raise Error, 'cannot change parameters after starting to write'
347 self._nframes = nframes
349 def getnframes(self):
350 return self._nframeswritten
352 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
360 def getcomptype(self):
361 return self._comptype
363 def getcompname(self):
364 return self._compname
366 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)
375 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
381 def setmark(self, id, pos, name):
382 raise Error, 'setmark() not supported'
384 def getmark(self, id):
385 raise Error, 'no marks'
387 def getmarkers(self):
388 return None
390 def tell(self):
391 return self._nframeswritten
393 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
409 def writeframes(self, data):
410 self.writeframesraw(data)
411 if self._datalength != self._datawritten:
412 self._patchheader()
414 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
422 # Internal methods.
425 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)
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))
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
461 def open(f, mode=None):
462 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'"
474 openfp = open # B/W compatibility