blob: 8ff93c37f2025129d573ca9c2c3807bb1c80cd4b [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
Skip Montanaro40fc1602001-03-01 04:27:19 +000076__all__ = ["open", "openfp", "Error"]
77
Fred Drake9b8d8012000-08-17 04:45:13 +000078class Error(Exception):
79 pass
Guido van Rossum3ed23cc1994-02-15 15:57:15 +000080
81WAVE_FORMAT_PCM = 0x0001
82
Serhiy Storchaka71378032013-10-17 23:03:48 +030083_array_fmts = None, 'b', 'h', None, 'i'
Guido van Rossum3ed23cc1994-02-15 15:57:15 +000084
Guido van Rossumebb9c921999-02-05 22:28:17 +000085import struct
Serhiy Storchakac47d7232013-11-09 23:09:44 +020086import sys
Guido van Rossum3601e881999-08-26 15:50:43 +000087from chunk import Chunk
Guido van Rossum3ed23cc1994-02-15 15:57:15 +000088
Serhiy Storchakac47d7232013-11-09 23:09:44 +020089def _byteswap3(data):
90 ba = bytearray(data)
91 ba[::3] = data[2::3]
92 ba[2::3] = data[::3]
93 return bytes(ba)
94
Guido van Rossum3ed23cc1994-02-15 15:57:15 +000095class Wave_read:
Guido van Rossume7b146f2000-02-04 15:28:42 +000096 """Variables used in this class:
Guido van Rossum3ed23cc1994-02-15 15:57:15 +000097
Guido van Rossume7b146f2000-02-04 15:28:42 +000098 These variables are available to the user though appropriate
99 methods of this class:
100 _file -- the open file with methods read(), close(), and seek()
101 set through the __init__() method
102 _nchannels -- the number of audio channels
103 available through the getnchannels() method
104 _nframes -- the number of audio frames
105 available through the getnframes() method
106 _sampwidth -- the number of bytes per audio sample
107 available through the getsampwidth() method
108 _framerate -- the sampling frequency
109 available through the getframerate() method
110 _comptype -- the AIFF-C compression type ('NONE' if AIFF)
111 available through the getcomptype() method
112 _compname -- the human-readable AIFF-C compression type
113 available through the getcomptype() method
114 _soundpos -- the position in the audio stream
115 available through the tell() method, set through the
116 setpos() method
Guido van Rossum3ed23cc1994-02-15 15:57:15 +0000117
Guido van Rossume7b146f2000-02-04 15:28:42 +0000118 These variables are used internally only:
119 _fmt_chunk_read -- 1 iff the FMT chunk has been read
120 _data_seek_needed -- 1 iff positioned correctly in audio
121 file for readframes()
122 _data_chunk -- instantiation of a chunk class for the DATA chunk
123 _framesize -- size of one frame in the file
124 """
Guido van Rossum3ed23cc1994-02-15 15:57:15 +0000125
Guido van Rossume7b146f2000-02-04 15:28:42 +0000126 def initfp(self, file):
127 self._convert = None
128 self._soundpos = 0
129 self._file = Chunk(file, bigendian = 0)
130 if self._file.getname() != 'RIFF':
131 raise Error, 'file does not start with RIFF id'
132 if self._file.read(4) != 'WAVE':
133 raise Error, 'not a WAVE file'
134 self._fmt_chunk_read = 0
135 self._data_chunk = None
136 while 1:
137 self._data_seek_needed = 1
138 try:
139 chunk = Chunk(self._file, bigendian = 0)
140 except EOFError:
141 break
142 chunkname = chunk.getname()
143 if chunkname == 'fmt ':
144 self._read_fmt_chunk(chunk)
145 self._fmt_chunk_read = 1
146 elif chunkname == 'data':
147 if not self._fmt_chunk_read:
148 raise Error, 'data chunk before fmt chunk'
149 self._data_chunk = chunk
Guido van Rossum54e54c62001-09-04 19:14:14 +0000150 self._nframes = chunk.chunksize // self._framesize
Guido van Rossume7b146f2000-02-04 15:28:42 +0000151 self._data_seek_needed = 0
152 break
153 chunk.skip()
154 if not self._fmt_chunk_read or not self._data_chunk:
155 raise Error, 'fmt chunk and/or data chunk missing'
Guido van Rossum3ed23cc1994-02-15 15:57:15 +0000156
Guido van Rossume7b146f2000-02-04 15:28:42 +0000157 def __init__(self, f):
Tim Peterscfc41782000-10-09 23:43:55 +0000158 self._i_opened_the_file = None
Neal Norwitz0e67fd42004-08-01 22:48:06 +0000159 if isinstance(f, basestring):
Guido van Rossume7b146f2000-02-04 15:28:42 +0000160 f = __builtin__.open(f, 'rb')
Tim Peterscfc41782000-10-09 23:43:55 +0000161 self._i_opened_the_file = f
Guido van Rossume7b146f2000-02-04 15:28:42 +0000162 # else, assume it is an open file object already
Georg Brandlab1f4672007-03-15 07:41:30 +0000163 try:
164 self.initfp(f)
165 except:
166 if self._i_opened_the_file:
167 f.close()
168 raise
Guido van Rossum3ed23cc1994-02-15 15:57:15 +0000169
Tim Peterscfc41782000-10-09 23:43:55 +0000170 def __del__(self):
171 self.close()
Guido van Rossume7b146f2000-02-04 15:28:42 +0000172 #
173 # User visible methods.
174 #
175 def getfp(self):
176 return self._file
Guido van Rossum3ed23cc1994-02-15 15:57:15 +0000177
Guido van Rossume7b146f2000-02-04 15:28:42 +0000178 def rewind(self):
179 self._data_seek_needed = 1
180 self._soundpos = 0
Guido van Rossum3ed23cc1994-02-15 15:57:15 +0000181
Guido van Rossume7b146f2000-02-04 15:28:42 +0000182 def close(self):
Tim Peterscfc41782000-10-09 23:43:55 +0000183 if self._i_opened_the_file:
184 self._i_opened_the_file.close()
185 self._i_opened_the_file = None
Guido van Rossume7b146f2000-02-04 15:28:42 +0000186 self._file = None
Guido van Rossum3ed23cc1994-02-15 15:57:15 +0000187
Guido van Rossume7b146f2000-02-04 15:28:42 +0000188 def tell(self):
189 return self._soundpos
Guido van Rossum3ed23cc1994-02-15 15:57:15 +0000190
Guido van Rossume7b146f2000-02-04 15:28:42 +0000191 def getnchannels(self):
192 return self._nchannels
Guido van Rossum3ed23cc1994-02-15 15:57:15 +0000193
Guido van Rossume7b146f2000-02-04 15:28:42 +0000194 def getnframes(self):
195 return self._nframes
Guido van Rossum3ed23cc1994-02-15 15:57:15 +0000196
Guido van Rossume7b146f2000-02-04 15:28:42 +0000197 def getsampwidth(self):
198 return self._sampwidth
Guido van Rossum3ed23cc1994-02-15 15:57:15 +0000199
Guido van Rossume7b146f2000-02-04 15:28:42 +0000200 def getframerate(self):
201 return self._framerate
Guido van Rossum3ed23cc1994-02-15 15:57:15 +0000202
Guido van Rossume7b146f2000-02-04 15:28:42 +0000203 def getcomptype(self):
204 return self._comptype
Guido van Rossum3ed23cc1994-02-15 15:57:15 +0000205
Guido van Rossume7b146f2000-02-04 15:28:42 +0000206 def getcompname(self):
207 return self._compname
Guido van Rossum3ed23cc1994-02-15 15:57:15 +0000208
Guido van Rossume7b146f2000-02-04 15:28:42 +0000209 def getparams(self):
210 return self.getnchannels(), self.getsampwidth(), \
211 self.getframerate(), self.getnframes(), \
212 self.getcomptype(), self.getcompname()
Guido van Rossum3ed23cc1994-02-15 15:57:15 +0000213
Guido van Rossume7b146f2000-02-04 15:28:42 +0000214 def getmarkers(self):
215 return None
Guido van Rossum3ed23cc1994-02-15 15:57:15 +0000216
Guido van Rossume7b146f2000-02-04 15:28:42 +0000217 def getmark(self, id):
218 raise Error, 'no marks'
Guido van Rossum3ed23cc1994-02-15 15:57:15 +0000219
Guido van Rossume7b146f2000-02-04 15:28:42 +0000220 def setpos(self, pos):
221 if pos < 0 or pos > self._nframes:
222 raise Error, 'position not in range'
223 self._soundpos = pos
224 self._data_seek_needed = 1
Guido van Rossum3ed23cc1994-02-15 15:57:15 +0000225
Guido van Rossume7b146f2000-02-04 15:28:42 +0000226 def readframes(self, nframes):
227 if self._data_seek_needed:
228 self._data_chunk.seek(0, 0)
229 pos = self._soundpos * self._framesize
230 if pos:
231 self._data_chunk.seek(pos, 0)
232 self._data_seek_needed = 0
233 if nframes == 0:
234 return ''
Serhiy Storchakac47d7232013-11-09 23:09:44 +0200235 if self._sampwidth in (2, 4) and sys.byteorder == 'big':
Guido van Rossume7b146f2000-02-04 15:28:42 +0000236 # unfortunately the fromfile() method does not take
237 # something that only looks like a file object, so
238 # we have to reach into the innards of the chunk object
239 import array
240 chunk = self._data_chunk
241 data = array.array(_array_fmts[self._sampwidth])
Serhiy Storchaka71378032013-10-17 23:03:48 +0300242 assert data.itemsize == self._sampwidth
Guido van Rossume7b146f2000-02-04 15:28:42 +0000243 nitems = nframes * self._nchannels
244 if nitems * self._sampwidth > chunk.chunksize - chunk.size_read:
245 nitems = (chunk.chunksize - chunk.size_read) / self._sampwidth
246 data.fromfile(chunk.file.file, nitems)
247 # "tell" data chunk how much was read
248 chunk.size_read = chunk.size_read + nitems * self._sampwidth
249 # do the same for the outermost chunk
250 chunk = chunk.file
251 chunk.size_read = chunk.size_read + nitems * self._sampwidth
252 data.byteswap()
253 data = data.tostring()
254 else:
255 data = self._data_chunk.read(nframes * self._framesize)
Serhiy Storchakac47d7232013-11-09 23:09:44 +0200256 if self._sampwidth == 3 and sys.byteorder == 'big':
257 data = _byteswap3(data)
Guido van Rossume7b146f2000-02-04 15:28:42 +0000258 if self._convert and data:
259 data = self._convert(data)
Guido van Rossum54e54c62001-09-04 19:14:14 +0000260 self._soundpos = self._soundpos + len(data) // (self._nchannels * self._sampwidth)
Guido van Rossume7b146f2000-02-04 15:28:42 +0000261 return data
262
263 #
264 # Internal methods.
265 #
266
267 def _read_fmt_chunk(self, chunk):
Jesus Ceaf34e4de2012-11-17 03:38:17 +0100268 wFormatTag, self._nchannels, self._framerate, dwAvgBytesPerSec, wBlockAlign = struct.unpack('<HHLLH', chunk.read(14))
Guido van Rossume7b146f2000-02-04 15:28:42 +0000269 if wFormatTag == WAVE_FORMAT_PCM:
Jesus Ceaf34e4de2012-11-17 03:38:17 +0100270 sampwidth = struct.unpack('<H', chunk.read(2))[0]
Guido van Rossum54e54c62001-09-04 19:14:14 +0000271 self._sampwidth = (sampwidth + 7) // 8
Guido van Rossume7b146f2000-02-04 15:28:42 +0000272 else:
Walter Dörwald70a6b492004-02-12 17:35:32 +0000273 raise Error, 'unknown format: %r' % (wFormatTag,)
Guido van Rossume7b146f2000-02-04 15:28:42 +0000274 self._framesize = self._nchannels * self._sampwidth
275 self._comptype = 'NONE'
276 self._compname = 'not compressed'
Guido van Rossum3ed23cc1994-02-15 15:57:15 +0000277
278class Wave_write:
Guido van Rossume7b146f2000-02-04 15:28:42 +0000279 """Variables used in this class:
Guido van Rossum3ed23cc1994-02-15 15:57:15 +0000280
Guido van Rossume7b146f2000-02-04 15:28:42 +0000281 These variables are user settable through appropriate methods
282 of this class:
283 _file -- the open file with methods write(), close(), tell(), seek()
284 set through the __init__() method
285 _comptype -- the AIFF-C compression type ('NONE' in AIFF)
286 set through the setcomptype() or setparams() method
287 _compname -- the human-readable AIFF-C compression type
288 set through the setcomptype() or setparams() method
289 _nchannels -- the number of audio channels
290 set through the setnchannels() or setparams() method
291 _sampwidth -- the number of bytes per audio sample
292 set through the setsampwidth() or setparams() method
293 _framerate -- the sampling frequency
294 set through the setframerate() or setparams() method
295 _nframes -- the number of audio frames written to the header
296 set through the setnframes() or setparams() method
Guido van Rossum3ed23cc1994-02-15 15:57:15 +0000297
Guido van Rossume7b146f2000-02-04 15:28:42 +0000298 These variables are used internally only:
299 _datalength -- the size of the audio samples written to the header
300 _nframeswritten -- the number of frames actually written
301 _datawritten -- the size of the audio samples actually written
302 """
Guido van Rossum3ed23cc1994-02-15 15:57:15 +0000303
Guido van Rossume7b146f2000-02-04 15:28:42 +0000304 def __init__(self, f):
Tim Peterscfc41782000-10-09 23:43:55 +0000305 self._i_opened_the_file = None
Neal Norwitz0e67fd42004-08-01 22:48:06 +0000306 if isinstance(f, basestring):
Guido van Rossume7b146f2000-02-04 15:28:42 +0000307 f = __builtin__.open(f, 'wb')
Tim Peterscfc41782000-10-09 23:43:55 +0000308 self._i_opened_the_file = f
Georg Brandlab1f4672007-03-15 07:41:30 +0000309 try:
310 self.initfp(f)
311 except:
312 if self._i_opened_the_file:
313 f.close()
314 raise
Guido van Rossum3ed23cc1994-02-15 15:57:15 +0000315
Guido van Rossume7b146f2000-02-04 15:28:42 +0000316 def initfp(self, file):
317 self._file = file
318 self._convert = None
319 self._nchannels = 0
320 self._sampwidth = 0
321 self._framerate = 0
322 self._nframes = 0
323 self._nframeswritten = 0
324 self._datawritten = 0
325 self._datalength = 0
Georg Brandlef805a62010-11-20 11:29:09 +0000326 self._headerwritten = False
Guido van Rossum3ed23cc1994-02-15 15:57:15 +0000327
Guido van Rossume7b146f2000-02-04 15:28:42 +0000328 def __del__(self):
Tim Peterscfc41782000-10-09 23:43:55 +0000329 self.close()
Guido van Rossum3ed23cc1994-02-15 15:57:15 +0000330
Guido van Rossume7b146f2000-02-04 15:28:42 +0000331 #
332 # User visible methods.
333 #
334 def setnchannels(self, nchannels):
335 if self._datawritten:
336 raise Error, 'cannot change parameters after starting to write'
337 if nchannels < 1:
338 raise Error, 'bad # of channels'
339 self._nchannels = nchannels
Guido van Rossum3ed23cc1994-02-15 15:57:15 +0000340
Guido van Rossume7b146f2000-02-04 15:28:42 +0000341 def getnchannels(self):
342 if not self._nchannels:
343 raise Error, 'number of channels not set'
344 return self._nchannels
Guido van Rossum3ed23cc1994-02-15 15:57:15 +0000345
Guido van Rossume7b146f2000-02-04 15:28:42 +0000346 def setsampwidth(self, sampwidth):
347 if self._datawritten:
348 raise Error, 'cannot change parameters after starting to write'
349 if sampwidth < 1 or sampwidth > 4:
350 raise Error, 'bad sample width'
351 self._sampwidth = sampwidth
Guido van Rossum3ed23cc1994-02-15 15:57:15 +0000352
Guido van Rossume7b146f2000-02-04 15:28:42 +0000353 def getsampwidth(self):
354 if not self._sampwidth:
355 raise Error, 'sample width not set'
356 return self._sampwidth
Guido van Rossum3ed23cc1994-02-15 15:57:15 +0000357
Guido van Rossume7b146f2000-02-04 15:28:42 +0000358 def setframerate(self, framerate):
359 if self._datawritten:
360 raise Error, 'cannot change parameters after starting to write'
361 if framerate <= 0:
362 raise Error, 'bad frame rate'
363 self._framerate = framerate
Guido van Rossum3ed23cc1994-02-15 15:57:15 +0000364
Guido van Rossume7b146f2000-02-04 15:28:42 +0000365 def getframerate(self):
366 if not self._framerate:
367 raise Error, 'frame rate not set'
368 return self._framerate
Guido van Rossum3ed23cc1994-02-15 15:57:15 +0000369
Guido van Rossume7b146f2000-02-04 15:28:42 +0000370 def setnframes(self, nframes):
371 if self._datawritten:
372 raise Error, 'cannot change parameters after starting to write'
373 self._nframes = nframes
Guido van Rossum3ed23cc1994-02-15 15:57:15 +0000374
Guido van Rossume7b146f2000-02-04 15:28:42 +0000375 def getnframes(self):
376 return self._nframeswritten
Guido van Rossum3ed23cc1994-02-15 15:57:15 +0000377
Guido van Rossume7b146f2000-02-04 15:28:42 +0000378 def setcomptype(self, comptype, compname):
379 if self._datawritten:
380 raise Error, 'cannot change parameters after starting to write'
381 if comptype not in ('NONE',):
382 raise Error, 'unsupported compression type'
383 self._comptype = comptype
384 self._compname = compname
Guido van Rossum3ed23cc1994-02-15 15:57:15 +0000385
Guido van Rossume7b146f2000-02-04 15:28:42 +0000386 def getcomptype(self):
387 return self._comptype
Guido van Rossum3ed23cc1994-02-15 15:57:15 +0000388
Guido van Rossume7b146f2000-02-04 15:28:42 +0000389 def getcompname(self):
390 return self._compname
Guido van Rossum3ed23cc1994-02-15 15:57:15 +0000391
Antoine Pitroub9d49632010-01-04 23:22:44 +0000392 def setparams(self, params):
393 nchannels, sampwidth, framerate, nframes, comptype, compname = params
Guido van Rossume7b146f2000-02-04 15:28:42 +0000394 if self._datawritten:
395 raise Error, 'cannot change parameters after starting to write'
396 self.setnchannels(nchannels)
397 self.setsampwidth(sampwidth)
398 self.setframerate(framerate)
399 self.setnframes(nframes)
400 self.setcomptype(comptype, compname)
Guido van Rossum3ed23cc1994-02-15 15:57:15 +0000401
Guido van Rossume7b146f2000-02-04 15:28:42 +0000402 def getparams(self):
403 if not self._nchannels or not self._sampwidth or not self._framerate:
404 raise Error, 'not all parameters set'
405 return self._nchannels, self._sampwidth, self._framerate, \
406 self._nframes, self._comptype, self._compname
Guido van Rossum3ed23cc1994-02-15 15:57:15 +0000407
Guido van Rossume7b146f2000-02-04 15:28:42 +0000408 def setmark(self, id, pos, name):
409 raise Error, 'setmark() not supported'
Guido van Rossum3ed23cc1994-02-15 15:57:15 +0000410
Guido van Rossume7b146f2000-02-04 15:28:42 +0000411 def getmark(self, id):
412 raise Error, 'no marks'
Guido van Rossum3ed23cc1994-02-15 15:57:15 +0000413
Guido van Rossume7b146f2000-02-04 15:28:42 +0000414 def getmarkers(self):
415 return None
Tim Peterse1190062001-01-15 03:34:38 +0000416
Guido van Rossume7b146f2000-02-04 15:28:42 +0000417 def tell(self):
418 return self._nframeswritten
Guido van Rossum3ed23cc1994-02-15 15:57:15 +0000419
Guido van Rossume7b146f2000-02-04 15:28:42 +0000420 def writeframesraw(self, data):
421 self._ensure_header_written(len(data))
Guido van Rossum54e54c62001-09-04 19:14:14 +0000422 nframes = len(data) // (self._sampwidth * self._nchannels)
Guido van Rossume7b146f2000-02-04 15:28:42 +0000423 if self._convert:
424 data = self._convert(data)
Serhiy Storchakac47d7232013-11-09 23:09:44 +0200425 if self._sampwidth in (2, 4) and sys.byteorder == 'big':
Guido van Rossume7b146f2000-02-04 15:28:42 +0000426 import array
Serhiy Storchaka5397c972013-11-21 11:04:37 +0200427 a = array.array(_array_fmts[self._sampwidth])
428 a.fromstring(data)
429 data = a
Serhiy Storchaka71378032013-10-17 23:03:48 +0300430 assert data.itemsize == self._sampwidth
Guido van Rossume7b146f2000-02-04 15:28:42 +0000431 data.byteswap()
432 data.tofile(self._file)
433 self._datawritten = self._datawritten + len(data) * self._sampwidth
434 else:
Serhiy Storchakac47d7232013-11-09 23:09:44 +0200435 if self._sampwidth == 3 and sys.byteorder == 'big':
436 data = _byteswap3(data)
Guido van Rossume7b146f2000-02-04 15:28:42 +0000437 self._file.write(data)
438 self._datawritten = self._datawritten + len(data)
439 self._nframeswritten = self._nframeswritten + nframes
Guido van Rossum3ed23cc1994-02-15 15:57:15 +0000440
Guido van Rossume7b146f2000-02-04 15:28:42 +0000441 def writeframes(self, data):
442 self.writeframesraw(data)
443 if self._datalength != self._datawritten:
444 self._patchheader()
Guido van Rossum3ed23cc1994-02-15 15:57:15 +0000445
Guido van Rossume7b146f2000-02-04 15:28:42 +0000446 def close(self):
Tim Peterscfc41782000-10-09 23:43:55 +0000447 if self._file:
Serhiy Storchaka499d8212013-10-12 21:35:33 +0300448 try:
449 self._ensure_header_written(0)
450 if self._datalength != self._datawritten:
451 self._patchheader()
452 self._file.flush()
453 finally:
454 self._file = None
Tim Peterscfc41782000-10-09 23:43:55 +0000455 if self._i_opened_the_file:
456 self._i_opened_the_file.close()
457 self._i_opened_the_file = None
Guido van Rossum3ed23cc1994-02-15 15:57:15 +0000458
Guido van Rossume7b146f2000-02-04 15:28:42 +0000459 #
460 # Internal methods.
461 #
Guido van Rossum3ed23cc1994-02-15 15:57:15 +0000462
Guido van Rossume7b146f2000-02-04 15:28:42 +0000463 def _ensure_header_written(self, datasize):
Georg Brandlef805a62010-11-20 11:29:09 +0000464 if not self._headerwritten:
Guido van Rossume7b146f2000-02-04 15:28:42 +0000465 if not self._nchannels:
466 raise Error, '# channels not specified'
467 if not self._sampwidth:
468 raise Error, 'sample width not specified'
469 if not self._framerate:
470 raise Error, 'sampling rate not specified'
471 self._write_header(datasize)
472
473 def _write_header(self, initlength):
Georg Brandlef805a62010-11-20 11:29:09 +0000474 assert not self._headerwritten
Guido van Rossume7b146f2000-02-04 15:28:42 +0000475 self._file.write('RIFF')
476 if not self._nframes:
477 self._nframes = initlength / (self._nchannels * self._sampwidth)
478 self._datalength = self._nframes * self._nchannels * self._sampwidth
479 self._form_length_pos = self._file.tell()
Jesus Ceaf34e4de2012-11-17 03:38:17 +0100480 self._file.write(struct.pack('<L4s4sLHHLLHH4s',
Guido van Rossume7b146f2000-02-04 15:28:42 +0000481 36 + self._datalength, 'WAVE', 'fmt ', 16,
482 WAVE_FORMAT_PCM, self._nchannels, self._framerate,
483 self._nchannels * self._framerate * self._sampwidth,
484 self._nchannels * self._sampwidth,
485 self._sampwidth * 8, 'data'))
486 self._data_length_pos = self._file.tell()
Jesus Ceaf34e4de2012-11-17 03:38:17 +0100487 self._file.write(struct.pack('<L', self._datalength))
Georg Brandlef805a62010-11-20 11:29:09 +0000488 self._headerwritten = True
Guido van Rossume7b146f2000-02-04 15:28:42 +0000489
490 def _patchheader(self):
Georg Brandlef805a62010-11-20 11:29:09 +0000491 assert self._headerwritten
Guido van Rossume7b146f2000-02-04 15:28:42 +0000492 if self._datawritten == self._datalength:
493 return
494 curpos = self._file.tell()
495 self._file.seek(self._form_length_pos, 0)
Jesus Ceaf34e4de2012-11-17 03:38:17 +0100496 self._file.write(struct.pack('<L', 36 + self._datawritten))
Guido van Rossume7b146f2000-02-04 15:28:42 +0000497 self._file.seek(self._data_length_pos, 0)
Jesus Ceaf34e4de2012-11-17 03:38:17 +0100498 self._file.write(struct.pack('<L', self._datawritten))
Guido van Rossume7b146f2000-02-04 15:28:42 +0000499 self._file.seek(curpos, 0)
500 self._datalength = self._datawritten
Guido van Rossum3ed23cc1994-02-15 15:57:15 +0000501
Fred Drakef9607821999-06-17 15:18:47 +0000502def open(f, mode=None):
Guido van Rossume7b146f2000-02-04 15:28:42 +0000503 if mode is None:
504 if hasattr(f, 'mode'):
505 mode = f.mode
506 else:
507 mode = 'rb'
508 if mode in ('r', 'rb'):
509 return Wave_read(f)
510 elif mode in ('w', 'wb'):
511 return Wave_write(f)
512 else:
513 raise Error, "mode must be 'r', 'rb', 'w', or 'wb'"
Guido van Rossum3ed23cc1994-02-15 15:57:15 +0000514
515openfp = open # B/W compatibility