nothing@1
|
1 """Stuff to parse WAVE files.
|
nothing@1
|
2
|
nothing@1
|
3 Usage.
|
nothing@1
|
4
|
nothing@1
|
5 Reading WAVE files:
|
nothing@1
|
6 f = wave.open(file, 'r')
|
nothing@1
|
7 where file is either the name of a file or an open file pointer.
|
nothing@1
|
8 The open file pointer must have methods read(), seek(), and close().
|
nothing@1
|
9 When the setpos() and rewind() methods are not used, the seek()
|
nothing@1
|
10 method is not necessary.
|
nothing@1
|
11
|
nothing@1
|
12 This returns an instance of a class with the following public methods:
|
nothing@1
|
13 getnchannels() -- returns number of audio channels (1 for
|
nothing@1
|
14 mono, 2 for stereo)
|
nothing@1
|
15 getsampwidth() -- returns sample width in bytes
|
nothing@1
|
16 getframerate() -- returns sampling frequency
|
nothing@1
|
17 getnframes() -- returns number of audio frames
|
nothing@1
|
18 getcomptype() -- returns compression type ('NONE' for linear samples)
|
nothing@1
|
19 getcompname() -- returns human-readable version of
|
nothing@1
|
20 compression type ('not compressed' linear samples)
|
nothing@1
|
21 getparams() -- returns a tuple consisting of all of the
|
nothing@1
|
22 above in the above order
|
nothing@1
|
23 getmarkers() -- returns None (for compatibility with the
|
nothing@1
|
24 aifc module)
|
nothing@1
|
25 getmark(id) -- raises an error since the mark does not
|
nothing@1
|
26 exist (for compatibility with the aifc module)
|
nothing@1
|
27 readframes(n) -- returns at most n frames of audio
|
nothing@1
|
28 rewind() -- rewind to the beginning of the audio stream
|
nothing@1
|
29 setpos(pos) -- seek to the specified position
|
nothing@1
|
30 tell() -- return the current position
|
nothing@1
|
31 close() -- close the instance (make it unusable)
|
nothing@1
|
32 The position returned by tell() and the position given to setpos()
|
nothing@1
|
33 are compatible and have nothing to do with the actual position in the
|
nothing@1
|
34 file.
|
nothing@1
|
35 The close() method is called automatically when the class instance
|
nothing@1
|
36 is destroyed.
|
nothing@1
|
37
|
nothing@1
|
38 Writing WAVE files:
|
nothing@1
|
39 f = wave.open(file, 'w')
|
nothing@1
|
40 where file is either the name of a file or an open file pointer.
|
nothing@1
|
41 The open file pointer must have methods write(), tell(), seek(), and
|
nothing@1
|
42 close().
|
nothing@1
|
43
|
nothing@1
|
44 This returns an instance of a class with the following public methods:
|
nothing@1
|
45 setnchannels(n) -- set the number of channels
|
nothing@1
|
46 setsampwidth(n) -- set the sample width
|
nothing@1
|
47 setframerate(n) -- set the frame rate
|
nothing@1
|
48 setnframes(n) -- set the number of frames
|
nothing@1
|
49 setcomptype(type, name)
|
nothing@1
|
50 -- set the compression type and the
|
nothing@1
|
51 human-readable compression type
|
nothing@1
|
52 setparams(tuple)
|
nothing@1
|
53 -- set all parameters at once
|
nothing@1
|
54 tell() -- return current position in output file
|
nothing@1
|
55 writeframesraw(data)
|
nothing@1
|
56 -- write audio frames without pathing up the
|
nothing@1
|
57 file header
|
nothing@1
|
58 writeframes(data)
|
nothing@1
|
59 -- write audio frames and patch up the file header
|
nothing@1
|
60 close() -- patch up the file header and close the
|
nothing@1
|
61 output file
|
nothing@1
|
62 You should set the parameters before the first writeframesraw or
|
nothing@1
|
63 writeframes. The total number of frames does not need to be set,
|
nothing@1
|
64 but when it is set to the correct value, the header does not have to
|
nothing@1
|
65 be patched up.
|
nothing@1
|
66 It is best to first set all parameters, perhaps possibly the
|
nothing@1
|
67 compression type, and then write audio frames using writeframesraw.
|
nothing@1
|
68 When all frames have been written, either call writeframes('') or
|
nothing@1
|
69 close() to patch up the sizes in the header.
|
nothing@1
|
70 The close() method is called automatically when the class instance
|
nothing@1
|
71 is destroyed.
|
nothing@1
|
72 """
|
nothing@1
|
73
|
nothing@1
|
74 import __builtin__
|
nothing@1
|
75
|
nothing@1
|
76 __all__ = ["open", "openfp", "Error"]
|
nothing@1
|
77
|
nothing@1
|
78 class Error(Exception):
|
nothing@1
|
79 pass
|
nothing@1
|
80
|
nothing@1
|
81 WAVE_FORMAT_PCM = 0x0001
|
nothing@1
|
82
|
nothing@1
|
83 _array_fmts = None, 'b', 'h', None, 'i'
|
nothing@1
|
84
|
nothing@1
|
85 import struct
|
nothing@1
|
86 import sys
|
nothing@1
|
87 from chunk import Chunk
|
nothing@1
|
88
|
nothing@1
|
89 def _byteswap3(data):
|
nothing@1
|
90 ba = bytearray(data)
|
nothing@1
|
91 ba[::3] = data[2::3]
|
nothing@1
|
92 ba[2::3] = data[::3]
|
nothing@1
|
93 return bytes(ba)
|
nothing@1
|
94
|
nothing@1
|
95 class Wave_read:
|
nothing@1
|
96 """Variables used in this class:
|
nothing@1
|
97
|
nothing@1
|
98 These variables are available to the user though appropriate
|
nothing@1
|
99 methods of this class:
|
nothing@1
|
100 _file -- the open file with methods read(), close(), and seek()
|
nothing@1
|
101 set through the __init__() method
|
nothing@1
|
102 _nchannels -- the number of audio channels
|
nothing@1
|
103 available through the getnchannels() method
|
nothing@1
|
104 _nframes -- the number of audio frames
|
nothing@1
|
105 available through the getnframes() method
|
nothing@1
|
106 _sampwidth -- the number of bytes per audio sample
|
nothing@1
|
107 available through the getsampwidth() method
|
nothing@1
|
108 _framerate -- the sampling frequency
|
nothing@1
|
109 available through the getframerate() method
|
nothing@1
|
110 _comptype -- the AIFF-C compression type ('NONE' if AIFF)
|
nothing@1
|
111 available through the getcomptype() method
|
nothing@1
|
112 _compname -- the human-readable AIFF-C compression type
|
nothing@1
|
113 available through the getcomptype() method
|
nothing@1
|
114 _soundpos -- the position in the audio stream
|
nothing@1
|
115 available through the tell() method, set through the
|
nothing@1
|
116 setpos() method
|
nothing@1
|
117
|
nothing@1
|
118 These variables are used internally only:
|
nothing@1
|
119 _fmt_chunk_read -- 1 iff the FMT chunk has been read
|
nothing@1
|
120 _data_seek_needed -- 1 iff positioned correctly in audio
|
nothing@1
|
121 file for readframes()
|
nothing@1
|
122 _data_chunk -- instantiation of a chunk class for the DATA chunk
|
nothing@1
|
123 _framesize -- size of one frame in the file
|
nothing@1
|
124 """
|
nothing@1
|
125
|
nothing@1
|
126 def initfp(self, file):
|
nothing@1
|
127 self._convert = None
|
nothing@1
|
128 self._soundpos = 0
|
nothing@1
|
129 self._file = Chunk(file, bigendian = 0)
|
nothing@1
|
130 if self._file.getname() != 'RIFF':
|
nothing@1
|
131 raise Error, 'file does not start with RIFF id'
|
nothing@1
|
132 if self._file.read(4) != 'WAVE':
|
nothing@1
|
133 raise Error, 'not a WAVE file'
|
nothing@1
|
134 self._fmt_chunk_read = 0
|
nothing@1
|
135 self._data_chunk = None
|
nothing@1
|
136 while 1:
|
nothing@1
|
137 self._data_seek_needed = 1
|
nothing@1
|
138 try:
|
nothing@1
|
139 chunk = Chunk(self._file, bigendian = 0)
|
nothing@1
|
140 except EOFError:
|
nothing@1
|
141 break
|
nothing@1
|
142 chunkname = chunk.getname()
|
nothing@1
|
143 if chunkname == 'fmt ':
|
nothing@1
|
144 self._read_fmt_chunk(chunk)
|
nothing@1
|
145 self._fmt_chunk_read = 1
|
nothing@1
|
146 elif chunkname == 'data':
|
nothing@1
|
147 if not self._fmt_chunk_read:
|
nothing@1
|
148 raise Error, 'data chunk before fmt chunk'
|
nothing@1
|
149 self._data_chunk = chunk
|
nothing@1
|
150 self._nframes = chunk.chunksize // self._framesize
|
nothing@1
|
151 self._data_seek_needed = 0
|
nothing@1
|
152 break
|
nothing@1
|
153 chunk.skip()
|
nothing@1
|
154 if not self._fmt_chunk_read or not self._data_chunk:
|
nothing@1
|
155 raise Error, 'fmt chunk and/or data chunk missing'
|
nothing@1
|
156
|
nothing@1
|
157 def __init__(self, f):
|
nothing@1
|
158 self._i_opened_the_file = None
|
nothing@1
|
159 if isinstance(f, basestring):
|
nothing@1
|
160 f = __builtin__.open(f, 'rb')
|
nothing@1
|
161 self._i_opened_the_file = f
|
nothing@1
|
162 # else, assume it is an open file object already
|
nothing@1
|
163 try:
|
nothing@1
|
164 self.initfp(f)
|
nothing@1
|
165 except:
|
nothing@1
|
166 if self._i_opened_the_file:
|
nothing@1
|
167 f.close()
|
nothing@1
|
168 raise
|
nothing@1
|
169
|
nothing@1
|
170 def __del__(self):
|
nothing@1
|
171 self.close()
|
nothing@1
|
172 #
|
nothing@1
|
173 # User visible methods.
|
nothing@1
|
174 #
|
nothing@1
|
175 def getfp(self):
|
nothing@1
|
176 return self._file
|
nothing@1
|
177
|
nothing@1
|
178 def rewind(self):
|
nothing@1
|
179 self._data_seek_needed = 1
|
nothing@1
|
180 self._soundpos = 0
|
nothing@1
|
181
|
nothing@1
|
182 def close(self):
|
nothing@1
|
183 self._file = None
|
nothing@1
|
184 file = self._i_opened_the_file
|
nothing@1
|
185 if file:
|
nothing@1
|
186 self._i_opened_the_file = None
|
nothing@1
|
187 file.close()
|
nothing@1
|
188
|
nothing@1
|
189 def tell(self):
|
nothing@1
|
190 return self._soundpos
|
nothing@1
|
191
|
nothing@1
|
192 def getnchannels(self):
|
nothing@1
|
193 return self._nchannels
|
nothing@1
|
194
|
nothing@1
|
195 def getnframes(self):
|
nothing@1
|
196 return self._nframes
|
nothing@1
|
197
|
nothing@1
|
198 def getsampwidth(self):
|
nothing@1
|
199 return self._sampwidth
|
nothing@1
|
200
|
nothing@1
|
201 def getframerate(self):
|
nothing@1
|
202 return self._framerate
|
nothing@1
|
203
|
nothing@1
|
204 def getcomptype(self):
|
nothing@1
|
205 return self._comptype
|
nothing@1
|
206
|
nothing@1
|
207 def getcompname(self):
|
nothing@1
|
208 return self._compname
|
nothing@1
|
209
|
nothing@1
|
210 def getparams(self):
|
nothing@1
|
211 return self.getnchannels(), self.getsampwidth(), \
|
nothing@1
|
212 self.getframerate(), self.getnframes(), \
|
nothing@1
|
213 self.getcomptype(), self.getcompname()
|
nothing@1
|
214
|
nothing@1
|
215 def getmarkers(self):
|
nothing@1
|
216 return None
|
nothing@1
|
217
|
nothing@1
|
218 def getmark(self, id):
|
nothing@1
|
219 raise Error, 'no marks'
|
nothing@1
|
220
|
nothing@1
|
221 def setpos(self, pos):
|
nothing@1
|
222 if pos < 0 or pos > self._nframes:
|
nothing@1
|
223 raise Error, 'position not in range'
|
nothing@1
|
224 self._soundpos = pos
|
nothing@1
|
225 self._data_seek_needed = 1
|
nothing@1
|
226
|
nothing@1
|
227 def readframes(self, nframes):
|
nothing@1
|
228 if self._data_seek_needed:
|
nothing@1
|
229 self._data_chunk.seek(0, 0)
|
nothing@1
|
230 pos = self._soundpos * self._framesize
|
nothing@1
|
231 if pos:
|
nothing@1
|
232 self._data_chunk.seek(pos, 0)
|
nothing@1
|
233 self._data_seek_needed = 0
|
nothing@1
|
234 if nframes == 0:
|
nothing@1
|
235 return ''
|
nothing@1
|
236 if self._sampwidth in (2, 4) and sys.byteorder == 'big':
|
nothing@1
|
237 # unfortunately the fromfile() method does not take
|
nothing@1
|
238 # something that only looks like a file object, so
|
nothing@1
|
239 # we have to reach into the innards of the chunk object
|
nothing@1
|
240 import array
|
nothing@1
|
241 chunk = self._data_chunk
|
nothing@1
|
242 data = array.array(_array_fmts[self._sampwidth])
|
nothing@1
|
243 assert data.itemsize == self._sampwidth
|
nothing@1
|
244 nitems = nframes * self._nchannels
|
nothing@1
|
245 if nitems * self._sampwidth > chunk.chunksize - chunk.size_read:
|
nothing@1
|
246 nitems = (chunk.chunksize - chunk.size_read) // self._sampwidth
|
nothing@1
|
247 data.fromfile(chunk.file.file, nitems)
|
nothing@1
|
248 # "tell" data chunk how much was read
|
nothing@1
|
249 chunk.size_read = chunk.size_read + nitems * self._sampwidth
|
nothing@1
|
250 # do the same for the outermost chunk
|
nothing@1
|
251 chunk = chunk.file
|
nothing@1
|
252 chunk.size_read = chunk.size_read + nitems * self._sampwidth
|
nothing@1
|
253 data.byteswap()
|
nothing@1
|
254 data = data.tostring()
|
nothing@1
|
255 else:
|
nothing@1
|
256 data = self._data_chunk.read(nframes * self._framesize)
|
nothing@1
|
257 if self._sampwidth == 3 and sys.byteorder == 'big':
|
nothing@1
|
258 data = _byteswap3(data)
|
nothing@1
|
259 if self._convert and data:
|
nothing@1
|
260 data = self._convert(data)
|
nothing@1
|
261 self._soundpos = self._soundpos + len(data) // (self._nchannels * self._sampwidth)
|
nothing@1
|
262 return data
|
nothing@1
|
263
|
nothing@1
|
264 #
|
nothing@1
|
265 # Internal methods.
|
nothing@1
|
266 #
|
nothing@1
|
267
|
nothing@1
|
268 def _read_fmt_chunk(self, chunk):
|
nothing@1
|
269 wFormatTag, self._nchannels, self._framerate, dwAvgBytesPerSec, wBlockAlign = struct.unpack('<HHLLH', chunk.read(14))
|
nothing@1
|
270 if wFormatTag == WAVE_FORMAT_PCM:
|
nothing@1
|
271 sampwidth = struct.unpack('<H', chunk.read(2))[0]
|
nothing@1
|
272 self._sampwidth = (sampwidth + 7) // 8
|
nothing@1
|
273 else:
|
nothing@1
|
274 raise Error, 'unknown format: %r' % (wFormatTag,)
|
nothing@1
|
275 self._framesize = self._nchannels * self._sampwidth
|
nothing@1
|
276 self._comptype = 'NONE'
|
nothing@1
|
277 self._compname = 'not compressed'
|
nothing@1
|
278
|
nothing@1
|
279 class Wave_write:
|
nothing@1
|
280 """Variables used in this class:
|
nothing@1
|
281
|
nothing@1
|
282 These variables are user settable through appropriate methods
|
nothing@1
|
283 of this class:
|
nothing@1
|
284 _file -- the open file with methods write(), close(), tell(), seek()
|
nothing@1
|
285 set through the __init__() method
|
nothing@1
|
286 _comptype -- the AIFF-C compression type ('NONE' in AIFF)
|
nothing@1
|
287 set through the setcomptype() or setparams() method
|
nothing@1
|
288 _compname -- the human-readable AIFF-C compression type
|
nothing@1
|
289 set through the setcomptype() or setparams() method
|
nothing@1
|
290 _nchannels -- the number of audio channels
|
nothing@1
|
291 set through the setnchannels() or setparams() method
|
nothing@1
|
292 _sampwidth -- the number of bytes per audio sample
|
nothing@1
|
293 set through the setsampwidth() or setparams() method
|
nothing@1
|
294 _framerate -- the sampling frequency
|
nothing@1
|
295 set through the setframerate() or setparams() method
|
nothing@1
|
296 _nframes -- the number of audio frames written to the header
|
nothing@1
|
297 set through the setnframes() or setparams() method
|
nothing@1
|
298
|
nothing@1
|
299 These variables are used internally only:
|
nothing@1
|
300 _datalength -- the size of the audio samples written to the header
|
nothing@1
|
301 _nframeswritten -- the number of frames actually written
|
nothing@1
|
302 _datawritten -- the size of the audio samples actually written
|
nothing@1
|
303 """
|
nothing@1
|
304
|
nothing@1
|
305 def __init__(self, f):
|
nothing@1
|
306 self._i_opened_the_file = None
|
nothing@1
|
307 if isinstance(f, basestring):
|
nothing@1
|
308 f = __builtin__.open(f, 'wb')
|
nothing@1
|
309 self._i_opened_the_file = f
|
nothing@1
|
310 try:
|
nothing@1
|
311 self.initfp(f)
|
nothing@1
|
312 except:
|
nothing@1
|
313 if self._i_opened_the_file:
|
nothing@1
|
314 f.close()
|
nothing@1
|
315 raise
|
nothing@1
|
316
|
nothing@1
|
317 def initfp(self, file):
|
nothing@1
|
318 self._file = file
|
nothing@1
|
319 self._convert = None
|
nothing@1
|
320 self._nchannels = 0
|
nothing@1
|
321 self._sampwidth = 0
|
nothing@1
|
322 self._framerate = 0
|
nothing@1
|
323 self._nframes = 0
|
nothing@1
|
324 self._nframeswritten = 0
|
nothing@1
|
325 self._datawritten = 0
|
nothing@1
|
326 self._datalength = 0
|
nothing@1
|
327 self._headerwritten = False
|
nothing@1
|
328
|
nothing@1
|
329 def __del__(self):
|
nothing@1
|
330 self.close()
|
nothing@1
|
331
|
nothing@1
|
332 #
|
nothing@1
|
333 # User visible methods.
|
nothing@1
|
334 #
|
nothing@1
|
335 def setnchannels(self, nchannels):
|
nothing@1
|
336 if self._datawritten:
|
nothing@1
|
337 raise Error, 'cannot change parameters after starting to write'
|
nothing@1
|
338 if nchannels < 1:
|
nothing@1
|
339 raise Error, 'bad # of channels'
|
nothing@1
|
340 self._nchannels = nchannels
|
nothing@1
|
341
|
nothing@1
|
342 def getnchannels(self):
|
nothing@1
|
343 if not self._nchannels:
|
nothing@1
|
344 raise Error, 'number of channels not set'
|
nothing@1
|
345 return self._nchannels
|
nothing@1
|
346
|
nothing@1
|
347 def setsampwidth(self, sampwidth):
|
nothing@1
|
348 if self._datawritten:
|
nothing@1
|
349 raise Error, 'cannot change parameters after starting to write'
|
nothing@1
|
350 if sampwidth < 1 or sampwidth > 4:
|
nothing@1
|
351 raise Error, 'bad sample width'
|
nothing@1
|
352 self._sampwidth = sampwidth
|
nothing@1
|
353
|
nothing@1
|
354 def getsampwidth(self):
|
nothing@1
|
355 if not self._sampwidth:
|
nothing@1
|
356 raise Error, 'sample width not set'
|
nothing@1
|
357 return self._sampwidth
|
nothing@1
|
358
|
nothing@1
|
359 def setframerate(self, framerate):
|
nothing@1
|
360 if self._datawritten:
|
nothing@1
|
361 raise Error, 'cannot change parameters after starting to write'
|
nothing@1
|
362 if framerate <= 0:
|
nothing@1
|
363 raise Error, 'bad frame rate'
|
nothing@1
|
364 self._framerate = framerate
|
nothing@1
|
365
|
nothing@1
|
366 def getframerate(self):
|
nothing@1
|
367 if not self._framerate:
|
nothing@1
|
368 raise Error, 'frame rate not set'
|
nothing@1
|
369 return self._framerate
|
nothing@1
|
370
|
nothing@1
|
371 def setnframes(self, nframes):
|
nothing@1
|
372 if self._datawritten:
|
nothing@1
|
373 raise Error, 'cannot change parameters after starting to write'
|
nothing@1
|
374 self._nframes = nframes
|
nothing@1
|
375
|
nothing@1
|
376 def getnframes(self):
|
nothing@1
|
377 return self._nframeswritten
|
nothing@1
|
378
|
nothing@1
|
379 def setcomptype(self, comptype, compname):
|
nothing@1
|
380 if self._datawritten:
|
nothing@1
|
381 raise Error, 'cannot change parameters after starting to write'
|
nothing@1
|
382 if comptype not in ('NONE',):
|
nothing@1
|
383 raise Error, 'unsupported compression type'
|
nothing@1
|
384 self._comptype = comptype
|
nothing@1
|
385 self._compname = compname
|
nothing@1
|
386
|
nothing@1
|
387 def getcomptype(self):
|
nothing@1
|
388 return self._comptype
|
nothing@1
|
389
|
nothing@1
|
390 def getcompname(self):
|
nothing@1
|
391 return self._compname
|
nothing@1
|
392
|
nothing@1
|
393 def setparams(self, params):
|
nothing@1
|
394 nchannels, sampwidth, framerate, nframes, comptype, compname = params
|
nothing@1
|
395 if self._datawritten:
|
nothing@1
|
396 raise Error, 'cannot change parameters after starting to write'
|
nothing@1
|
397 self.setnchannels(nchannels)
|
nothing@1
|
398 self.setsampwidth(sampwidth)
|
nothing@1
|
399 self.setframerate(framerate)
|
nothing@1
|
400 self.setnframes(nframes)
|
nothing@1
|
401 self.setcomptype(comptype, compname)
|
nothing@1
|
402
|
nothing@1
|
403 def getparams(self):
|
nothing@1
|
404 if not self._nchannels or not self._sampwidth or not self._framerate:
|
nothing@1
|
405 raise Error, 'not all parameters set'
|
nothing@1
|
406 return self._nchannels, self._sampwidth, self._framerate, \
|
nothing@1
|
407 self._nframes, self._comptype, self._compname
|
nothing@1
|
408
|
nothing@1
|
409 def setmark(self, id, pos, name):
|
nothing@1
|
410 raise Error, 'setmark() not supported'
|
nothing@1
|
411
|
nothing@1
|
412 def getmark(self, id):
|
nothing@1
|
413 raise Error, 'no marks'
|
nothing@1
|
414
|
nothing@1
|
415 def getmarkers(self):
|
nothing@1
|
416 return None
|
nothing@1
|
417
|
nothing@1
|
418 def tell(self):
|
nothing@1
|
419 return self._nframeswritten
|
nothing@1
|
420
|
nothing@1
|
421 def writeframesraw(self, data):
|
nothing@1
|
422 self._ensure_header_written(len(data))
|
nothing@1
|
423 nframes = len(data) // (self._sampwidth * self._nchannels)
|
nothing@1
|
424 if self._convert:
|
nothing@1
|
425 data = self._convert(data)
|
nothing@1
|
426 if self._sampwidth in (2, 4) and sys.byteorder == 'big':
|
nothing@1
|
427 import array
|
nothing@1
|
428 a = array.array(_array_fmts[self._sampwidth])
|
nothing@1
|
429 a.fromstring(data)
|
nothing@1
|
430 data = a
|
nothing@1
|
431 assert data.itemsize == self._sampwidth
|
nothing@1
|
432 data.byteswap()
|
nothing@1
|
433 data.tofile(self._file)
|
nothing@1
|
434 self._datawritten = self._datawritten + len(data) * self._sampwidth
|
nothing@1
|
435 else:
|
nothing@1
|
436 if self._sampwidth == 3 and sys.byteorder == 'big':
|
nothing@1
|
437 data = _byteswap3(data)
|
nothing@1
|
438 self._file.write(data)
|
nothing@1
|
439 self._datawritten = self._datawritten + len(data)
|
nothing@1
|
440 self._nframeswritten = self._nframeswritten + nframes
|
nothing@1
|
441
|
nothing@1
|
442 def writeframes(self, data):
|
nothing@1
|
443 self.writeframesraw(data)
|
nothing@1
|
444 if self._datalength != self._datawritten:
|
nothing@1
|
445 self._patchheader()
|
nothing@1
|
446
|
nothing@1
|
447 def close(self):
|
nothing@1
|
448 try:
|
nothing@1
|
449 if self._file:
|
nothing@1
|
450 self._ensure_header_written(0)
|
nothing@1
|
451 if self._datalength != self._datawritten:
|
nothing@1
|
452 self._patchheader()
|
nothing@1
|
453 self._file.flush()
|
nothing@1
|
454 finally:
|
nothing@1
|
455 self._file = None
|
nothing@1
|
456 file = self._i_opened_the_file
|
nothing@1
|
457 if file:
|
nothing@1
|
458 self._i_opened_the_file = None
|
nothing@1
|
459 file.close()
|
nothing@1
|
460
|
nothing@1
|
461 #
|
nothing@1
|
462 # Internal methods.
|
nothing@1
|
463 #
|
nothing@1
|
464
|
nothing@1
|
465 def _ensure_header_written(self, datasize):
|
nothing@1
|
466 if not self._headerwritten:
|
nothing@1
|
467 if not self._nchannels:
|
nothing@1
|
468 raise Error, '# channels not specified'
|
nothing@1
|
469 if not self._sampwidth:
|
nothing@1
|
470 raise Error, 'sample width not specified'
|
nothing@1
|
471 if not self._framerate:
|
nothing@1
|
472 raise Error, 'sampling rate not specified'
|
nothing@1
|
473 self._write_header(datasize)
|
nothing@1
|
474
|
nothing@1
|
475 def _write_header(self, initlength):
|
nothing@1
|
476 assert not self._headerwritten
|
nothing@1
|
477 self._file.write('RIFF')
|
nothing@1
|
478 if not self._nframes:
|
nothing@1
|
479 self._nframes = initlength / (self._nchannels * self._sampwidth)
|
nothing@1
|
480 self._datalength = self._nframes * self._nchannels * self._sampwidth
|
nothing@1
|
481 self._form_length_pos = self._file.tell()
|
nothing@1
|
482 self._file.write(struct.pack('<L4s4sLHHLLHH4s',
|
nothing@1
|
483 36 + self._datalength, 'WAVE', 'fmt ', 16,
|
nothing@1
|
484 WAVE_FORMAT_PCM, self._nchannels, self._framerate,
|
nothing@1
|
485 self._nchannels * self._framerate * self._sampwidth,
|
nothing@1
|
486 self._nchannels * self._sampwidth,
|
nothing@1
|
487 self._sampwidth * 8, 'data'))
|
nothing@1
|
488 self._data_length_pos = self._file.tell()
|
nothing@1
|
489 self._file.write(struct.pack('<L', self._datalength))
|
nothing@1
|
490 self._headerwritten = True
|
nothing@1
|
491
|
nothing@1
|
492 def _patchheader(self):
|
nothing@1
|
493 assert self._headerwritten
|
nothing@1
|
494 if self._datawritten == self._datalength:
|
nothing@1
|
495 return
|
nothing@1
|
496 curpos = self._file.tell()
|
nothing@1
|
497 self._file.seek(self._form_length_pos, 0)
|
nothing@1
|
498 self._file.write(struct.pack('<L', 36 + self._datawritten))
|
nothing@1
|
499 self._file.seek(self._data_length_pos, 0)
|
nothing@1
|
500 self._file.write(struct.pack('<L', self._datawritten))
|
nothing@1
|
501 self._file.seek(curpos, 0)
|
nothing@1
|
502 self._datalength = self._datawritten
|
nothing@1
|
503
|
nothing@1
|
504 def open(f, mode=None):
|
nothing@1
|
505 if mode is None:
|
nothing@1
|
506 if hasattr(f, 'mode'):
|
nothing@1
|
507 mode = f.mode
|
nothing@1
|
508 else:
|
nothing@1
|
509 mode = 'rb'
|
nothing@1
|
510 if mode in ('r', 'rb'):
|
nothing@1
|
511 return Wave_read(f)
|
nothing@1
|
512 elif mode in ('w', 'wb'):
|
nothing@1
|
513 return Wave_write(f)
|
nothing@1
|
514 else:
|
nothing@1
|
515 raise Error, "mode must be 'r', 'rb', 'w', or 'wb'"
|
nothing@1
|
516
|
nothing@1
|
517 openfp = open # B/W compatibility
|