blob: 28acaa6cb43ec838d548618d6ec9fb1fde200af4 [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):
183 self._file = None
Serhiy Storchaka1aa2c0f2015-04-10 13:24:10 +0300184 file = self._i_opened_the_file
185 if file:
186 self._i_opened_the_file = None
187 file.close()
Guido van Rossum3ed23cc1994-02-15 15:57:15 +0000188
Guido van Rossume7b146f2000-02-04 15:28:42 +0000189 def tell(self):
190 return self._soundpos
Guido van Rossum3ed23cc1994-02-15 15:57:15 +0000191
Guido van Rossume7b146f2000-02-04 15:28:42 +0000192 def getnchannels(self):
193 return self._nchannels
Guido van Rossum3ed23cc1994-02-15 15:57:15 +0000194
Guido van Rossume7b146f2000-02-04 15:28:42 +0000195 def getnframes(self):
196 return self._nframes
Guido van Rossum3ed23cc1994-02-15 15:57:15 +0000197
Guido van Rossume7b146f2000-02-04 15:28:42 +0000198 def getsampwidth(self):
199 return self._sampwidth
Guido van Rossum3ed23cc1994-02-15 15:57:15 +0000200
Guido van Rossume7b146f2000-02-04 15:28:42 +0000201 def getframerate(self):
202 return self._framerate
Guido van Rossum3ed23cc1994-02-15 15:57:15 +0000203
Guido van Rossume7b146f2000-02-04 15:28:42 +0000204 def getcomptype(self):
205 return self._comptype
Guido van Rossum3ed23cc1994-02-15 15:57:15 +0000206
Guido van Rossume7b146f2000-02-04 15:28:42 +0000207 def getcompname(self):
208 return self._compname
Guido van Rossum3ed23cc1994-02-15 15:57:15 +0000209
Guido van Rossume7b146f2000-02-04 15:28:42 +0000210 def getparams(self):
211 return self.getnchannels(), self.getsampwidth(), \
212 self.getframerate(), self.getnframes(), \
213 self.getcomptype(), self.getcompname()
Guido van Rossum3ed23cc1994-02-15 15:57:15 +0000214
Guido van Rossume7b146f2000-02-04 15:28:42 +0000215 def getmarkers(self):
216 return None
Guido van Rossum3ed23cc1994-02-15 15:57:15 +0000217
Guido van Rossume7b146f2000-02-04 15:28:42 +0000218 def getmark(self, id):
219 raise Error, 'no marks'
Guido van Rossum3ed23cc1994-02-15 15:57:15 +0000220
Guido van Rossume7b146f2000-02-04 15:28:42 +0000221 def setpos(self, pos):
222 if pos < 0 or pos > self._nframes:
223 raise Error, 'position not in range'
224 self._soundpos = pos
225 self._data_seek_needed = 1
Guido van Rossum3ed23cc1994-02-15 15:57:15 +0000226
Guido van Rossume7b146f2000-02-04 15:28:42 +0000227 def readframes(self, nframes):
228 if self._data_seek_needed:
229 self._data_chunk.seek(0, 0)
230 pos = self._soundpos * self._framesize
231 if pos:
232 self._data_chunk.seek(pos, 0)
233 self._data_seek_needed = 0
234 if nframes == 0:
235 return ''
Serhiy Storchakac47d7232013-11-09 23:09:44 +0200236 if self._sampwidth in (2, 4) and sys.byteorder == 'big':
Guido van Rossume7b146f2000-02-04 15:28:42 +0000237 # unfortunately the fromfile() method does not take
238 # something that only looks like a file object, so
239 # we have to reach into the innards of the chunk object
240 import array
241 chunk = self._data_chunk
242 data = array.array(_array_fmts[self._sampwidth])
Serhiy Storchaka71378032013-10-17 23:03:48 +0300243 assert data.itemsize == self._sampwidth
Guido van Rossume7b146f2000-02-04 15:28:42 +0000244 nitems = nframes * self._nchannels
245 if nitems * self._sampwidth > chunk.chunksize - chunk.size_read:
Serhiy Storchakaf8cc2872016-10-25 09:51:38 +0300246 nitems = (chunk.chunksize - chunk.size_read) // self._sampwidth
Guido van Rossume7b146f2000-02-04 15:28:42 +0000247 data.fromfile(chunk.file.file, nitems)
248 # "tell" data chunk how much was read
249 chunk.size_read = chunk.size_read + nitems * self._sampwidth
250 # do the same for the outermost chunk
251 chunk = chunk.file
252 chunk.size_read = chunk.size_read + nitems * self._sampwidth
253 data.byteswap()
254 data = data.tostring()
255 else:
256 data = self._data_chunk.read(nframes * self._framesize)
Serhiy Storchakac47d7232013-11-09 23:09:44 +0200257 if self._sampwidth == 3 and sys.byteorder == 'big':
258 data = _byteswap3(data)
Guido van Rossume7b146f2000-02-04 15:28:42 +0000259 if self._convert and data:
260 data = self._convert(data)
Guido van Rossum54e54c62001-09-04 19:14:14 +0000261 self._soundpos = self._soundpos + len(data) // (self._nchannels * self._sampwidth)
Guido van Rossume7b146f2000-02-04 15:28:42 +0000262 return data
263
264 #
265 # Internal methods.
266 #
267
268 def _read_fmt_chunk(self, chunk):
Jesus Ceaf34e4de2012-11-17 03:38:17 +0100269 wFormatTag, self._nchannels, self._framerate, dwAvgBytesPerSec, wBlockAlign = struct.unpack('<HHLLH', chunk.read(14))
Guido van Rossume7b146f2000-02-04 15:28:42 +0000270 if wFormatTag == WAVE_FORMAT_PCM:
Jesus Ceaf34e4de2012-11-17 03:38:17 +0100271 sampwidth = struct.unpack('<H', chunk.read(2))[0]
Guido van Rossum54e54c62001-09-04 19:14:14 +0000272 self._sampwidth = (sampwidth + 7) // 8
Guido van Rossume7b146f2000-02-04 15:28:42 +0000273 else:
Walter Dörwald70a6b492004-02-12 17:35:32 +0000274 raise Error, 'unknown format: %r' % (wFormatTag,)
Guido van Rossume7b146f2000-02-04 15:28:42 +0000275 self._framesize = self._nchannels * self._sampwidth
276 self._comptype = 'NONE'
277 self._compname = 'not compressed'
Guido van Rossum3ed23cc1994-02-15 15:57:15 +0000278
279class Wave_write:
Guido van Rossume7b146f2000-02-04 15:28:42 +0000280 """Variables used in this class:
Guido van Rossum3ed23cc1994-02-15 15:57:15 +0000281
Guido van Rossume7b146f2000-02-04 15:28:42 +0000282 These variables are user settable through appropriate methods
283 of this class:
284 _file -- the open file with methods write(), close(), tell(), seek()
285 set through the __init__() method
286 _comptype -- the AIFF-C compression type ('NONE' in AIFF)
287 set through the setcomptype() or setparams() method
288 _compname -- the human-readable AIFF-C compression type
289 set through the setcomptype() or setparams() method
290 _nchannels -- the number of audio channels
291 set through the setnchannels() or setparams() method
292 _sampwidth -- the number of bytes per audio sample
293 set through the setsampwidth() or setparams() method
294 _framerate -- the sampling frequency
295 set through the setframerate() or setparams() method
296 _nframes -- the number of audio frames written to the header
297 set through the setnframes() or setparams() method
Guido van Rossum3ed23cc1994-02-15 15:57:15 +0000298
Guido van Rossume7b146f2000-02-04 15:28:42 +0000299 These variables are used internally only:
300 _datalength -- the size of the audio samples written to the header
301 _nframeswritten -- the number of frames actually written
302 _datawritten -- the size of the audio samples actually written
303 """
Guido van Rossum3ed23cc1994-02-15 15:57:15 +0000304
Guido van Rossume7b146f2000-02-04 15:28:42 +0000305 def __init__(self, f):
Tim Peterscfc41782000-10-09 23:43:55 +0000306 self._i_opened_the_file = None
Neal Norwitz0e67fd42004-08-01 22:48:06 +0000307 if isinstance(f, basestring):
Guido van Rossume7b146f2000-02-04 15:28:42 +0000308 f = __builtin__.open(f, 'wb')
Tim Peterscfc41782000-10-09 23:43:55 +0000309 self._i_opened_the_file = f
Georg Brandlab1f4672007-03-15 07:41:30 +0000310 try:
311 self.initfp(f)
312 except:
313 if self._i_opened_the_file:
314 f.close()
315 raise
Guido van Rossum3ed23cc1994-02-15 15:57:15 +0000316
Guido van Rossume7b146f2000-02-04 15:28:42 +0000317 def initfp(self, file):
318 self._file = file
319 self._convert = None
320 self._nchannels = 0
321 self._sampwidth = 0
322 self._framerate = 0
323 self._nframes = 0
324 self._nframeswritten = 0
325 self._datawritten = 0
326 self._datalength = 0
Georg Brandlef805a62010-11-20 11:29:09 +0000327 self._headerwritten = False
Guido van Rossum3ed23cc1994-02-15 15:57:15 +0000328
Guido van Rossume7b146f2000-02-04 15:28:42 +0000329 def __del__(self):
Tim Peterscfc41782000-10-09 23:43:55 +0000330 self.close()
Guido van Rossum3ed23cc1994-02-15 15:57:15 +0000331
Guido van Rossume7b146f2000-02-04 15:28:42 +0000332 #
333 # User visible methods.
334 #
335 def setnchannels(self, nchannels):
336 if self._datawritten:
337 raise Error, 'cannot change parameters after starting to write'
338 if nchannels < 1:
339 raise Error, 'bad # of channels'
340 self._nchannels = nchannels
Guido van Rossum3ed23cc1994-02-15 15:57:15 +0000341
Guido van Rossume7b146f2000-02-04 15:28:42 +0000342 def getnchannels(self):
343 if not self._nchannels:
344 raise Error, 'number of channels not set'
345 return self._nchannels
Guido van Rossum3ed23cc1994-02-15 15:57:15 +0000346
Guido van Rossume7b146f2000-02-04 15:28:42 +0000347 def setsampwidth(self, sampwidth):
348 if self._datawritten:
349 raise Error, 'cannot change parameters after starting to write'
350 if sampwidth < 1 or sampwidth > 4:
351 raise Error, 'bad sample width'
352 self._sampwidth = sampwidth
Guido van Rossum3ed23cc1994-02-15 15:57:15 +0000353
Guido van Rossume7b146f2000-02-04 15:28:42 +0000354 def getsampwidth(self):
355 if not self._sampwidth:
356 raise Error, 'sample width not set'
357 return self._sampwidth
Guido van Rossum3ed23cc1994-02-15 15:57:15 +0000358
Guido van Rossume7b146f2000-02-04 15:28:42 +0000359 def setframerate(self, framerate):
360 if self._datawritten:
361 raise Error, 'cannot change parameters after starting to write'
362 if framerate <= 0:
363 raise Error, 'bad frame rate'
364 self._framerate = framerate
Guido van Rossum3ed23cc1994-02-15 15:57:15 +0000365
Guido van Rossume7b146f2000-02-04 15:28:42 +0000366 def getframerate(self):
367 if not self._framerate:
368 raise Error, 'frame rate not set'
369 return self._framerate
Guido van Rossum3ed23cc1994-02-15 15:57:15 +0000370
Guido van Rossume7b146f2000-02-04 15:28:42 +0000371 def setnframes(self, nframes):
372 if self._datawritten:
373 raise Error, 'cannot change parameters after starting to write'
374 self._nframes = nframes
Guido van Rossum3ed23cc1994-02-15 15:57:15 +0000375
Guido van Rossume7b146f2000-02-04 15:28:42 +0000376 def getnframes(self):
377 return self._nframeswritten
Guido van Rossum3ed23cc1994-02-15 15:57:15 +0000378
Guido van Rossume7b146f2000-02-04 15:28:42 +0000379 def setcomptype(self, comptype, compname):
380 if self._datawritten:
381 raise Error, 'cannot change parameters after starting to write'
382 if comptype not in ('NONE',):
383 raise Error, 'unsupported compression type'
384 self._comptype = comptype
385 self._compname = compname
Guido van Rossum3ed23cc1994-02-15 15:57:15 +0000386
Guido van Rossume7b146f2000-02-04 15:28:42 +0000387 def getcomptype(self):
388 return self._comptype
Guido van Rossum3ed23cc1994-02-15 15:57:15 +0000389
Guido van Rossume7b146f2000-02-04 15:28:42 +0000390 def getcompname(self):
391 return self._compname
Guido van Rossum3ed23cc1994-02-15 15:57:15 +0000392
Antoine Pitroub9d49632010-01-04 23:22:44 +0000393 def setparams(self, params):
394 nchannels, sampwidth, framerate, nframes, comptype, compname = params
Guido van Rossume7b146f2000-02-04 15:28:42 +0000395 if self._datawritten:
396 raise Error, 'cannot change parameters after starting to write'
397 self.setnchannels(nchannels)
398 self.setsampwidth(sampwidth)
399 self.setframerate(framerate)
400 self.setnframes(nframes)
401 self.setcomptype(comptype, compname)
Guido van Rossum3ed23cc1994-02-15 15:57:15 +0000402
Guido van Rossume7b146f2000-02-04 15:28:42 +0000403 def getparams(self):
404 if not self._nchannels or not self._sampwidth or not self._framerate:
405 raise Error, 'not all parameters set'
406 return self._nchannels, self._sampwidth, self._framerate, \
407 self._nframes, self._comptype, self._compname
Guido van Rossum3ed23cc1994-02-15 15:57:15 +0000408
Guido van Rossume7b146f2000-02-04 15:28:42 +0000409 def setmark(self, id, pos, name):
410 raise Error, 'setmark() not supported'
Guido van Rossum3ed23cc1994-02-15 15:57:15 +0000411
Guido van Rossume7b146f2000-02-04 15:28:42 +0000412 def getmark(self, id):
413 raise Error, 'no marks'
Guido van Rossum3ed23cc1994-02-15 15:57:15 +0000414
Guido van Rossume7b146f2000-02-04 15:28:42 +0000415 def getmarkers(self):
416 return None
Tim Peterse1190062001-01-15 03:34:38 +0000417
Guido van Rossume7b146f2000-02-04 15:28:42 +0000418 def tell(self):
419 return self._nframeswritten
Guido van Rossum3ed23cc1994-02-15 15:57:15 +0000420
Guido van Rossume7b146f2000-02-04 15:28:42 +0000421 def writeframesraw(self, data):
422 self._ensure_header_written(len(data))
Guido van Rossum54e54c62001-09-04 19:14:14 +0000423 nframes = len(data) // (self._sampwidth * self._nchannels)
Guido van Rossume7b146f2000-02-04 15:28:42 +0000424 if self._convert:
425 data = self._convert(data)
Serhiy Storchakac47d7232013-11-09 23:09:44 +0200426 if self._sampwidth in (2, 4) and sys.byteorder == 'big':
Guido van Rossume7b146f2000-02-04 15:28:42 +0000427 import array
Serhiy Storchaka5397c972013-11-21 11:04:37 +0200428 a = array.array(_array_fmts[self._sampwidth])
429 a.fromstring(data)
430 data = a
Serhiy Storchaka71378032013-10-17 23:03:48 +0300431 assert data.itemsize == self._sampwidth
Guido van Rossume7b146f2000-02-04 15:28:42 +0000432 data.byteswap()
433 data.tofile(self._file)
434 self._datawritten = self._datawritten + len(data) * self._sampwidth
435 else:
Serhiy Storchakac47d7232013-11-09 23:09:44 +0200436 if self._sampwidth == 3 and sys.byteorder == 'big':
437 data = _byteswap3(data)
Guido van Rossume7b146f2000-02-04 15:28:42 +0000438 self._file.write(data)
439 self._datawritten = self._datawritten + len(data)
440 self._nframeswritten = self._nframeswritten + nframes
Guido van Rossum3ed23cc1994-02-15 15:57:15 +0000441
Guido van Rossume7b146f2000-02-04 15:28:42 +0000442 def writeframes(self, data):
443 self.writeframesraw(data)
444 if self._datalength != self._datawritten:
445 self._patchheader()
Guido van Rossum3ed23cc1994-02-15 15:57:15 +0000446
Guido van Rossume7b146f2000-02-04 15:28:42 +0000447 def close(self):
Serhiy Storchaka1aa2c0f2015-04-10 13:24:10 +0300448 try:
449 if self._file:
Serhiy Storchaka499d8212013-10-12 21:35:33 +0300450 self._ensure_header_written(0)
451 if self._datalength != self._datawritten:
452 self._patchheader()
453 self._file.flush()
Serhiy Storchaka1aa2c0f2015-04-10 13:24:10 +0300454 finally:
455 self._file = None
456 file = self._i_opened_the_file
457 if file:
458 self._i_opened_the_file = None
459 file.close()
Guido van Rossum3ed23cc1994-02-15 15:57:15 +0000460
Guido van Rossume7b146f2000-02-04 15:28:42 +0000461 #
462 # Internal methods.
463 #
Guido van Rossum3ed23cc1994-02-15 15:57:15 +0000464
Guido van Rossume7b146f2000-02-04 15:28:42 +0000465 def _ensure_header_written(self, datasize):
Georg Brandlef805a62010-11-20 11:29:09 +0000466 if not self._headerwritten:
Guido van Rossume7b146f2000-02-04 15:28:42 +0000467 if not self._nchannels:
468 raise Error, '# channels not specified'
469 if not self._sampwidth:
470 raise Error, 'sample width not specified'
471 if not self._framerate:
472 raise Error, 'sampling rate not specified'
473 self._write_header(datasize)
474
475 def _write_header(self, initlength):
Georg Brandlef805a62010-11-20 11:29:09 +0000476 assert not self._headerwritten
Guido van Rossume7b146f2000-02-04 15:28:42 +0000477 self._file.write('RIFF')
478 if not self._nframes:
479 self._nframes = initlength / (self._nchannels * self._sampwidth)
480 self._datalength = self._nframes * self._nchannels * self._sampwidth
481 self._form_length_pos = self._file.tell()
Jesus Ceaf34e4de2012-11-17 03:38:17 +0100482 self._file.write(struct.pack('<L4s4sLHHLLHH4s',
Guido van Rossume7b146f2000-02-04 15:28:42 +0000483 36 + self._datalength, 'WAVE', 'fmt ', 16,
484 WAVE_FORMAT_PCM, self._nchannels, self._framerate,
485 self._nchannels * self._framerate * self._sampwidth,
486 self._nchannels * self._sampwidth,
487 self._sampwidth * 8, 'data'))
488 self._data_length_pos = self._file.tell()
Jesus Ceaf34e4de2012-11-17 03:38:17 +0100489 self._file.write(struct.pack('<L', self._datalength))
Georg Brandlef805a62010-11-20 11:29:09 +0000490 self._headerwritten = True
Guido van Rossume7b146f2000-02-04 15:28:42 +0000491
492 def _patchheader(self):
Georg Brandlef805a62010-11-20 11:29:09 +0000493 assert self._headerwritten
Guido van Rossume7b146f2000-02-04 15:28:42 +0000494 if self._datawritten == self._datalength:
495 return
496 curpos = self._file.tell()
497 self._file.seek(self._form_length_pos, 0)
Jesus Ceaf34e4de2012-11-17 03:38:17 +0100498 self._file.write(struct.pack('<L', 36 + self._datawritten))
Guido van Rossume7b146f2000-02-04 15:28:42 +0000499 self._file.seek(self._data_length_pos, 0)
Jesus Ceaf34e4de2012-11-17 03:38:17 +0100500 self._file.write(struct.pack('<L', self._datawritten))
Guido van Rossume7b146f2000-02-04 15:28:42 +0000501 self._file.seek(curpos, 0)
502 self._datalength = self._datawritten
Guido van Rossum3ed23cc1994-02-15 15:57:15 +0000503
Fred Drakef9607821999-06-17 15:18:47 +0000504def open(f, mode=None):
Guido van Rossume7b146f2000-02-04 15:28:42 +0000505 if mode is None:
506 if hasattr(f, 'mode'):
507 mode = f.mode
508 else:
509 mode = 'rb'
510 if mode in ('r', 'rb'):
511 return Wave_read(f)
512 elif mode in ('w', 'wb'):
513 return Wave_write(f)
514 else:
515 raise Error, "mode must be 'r', 'rb', 'w', or 'wb'"
Guido van Rossum3ed23cc1994-02-15 15:57:15 +0000516
517openfp = open # B/W compatibility