1 # Stuff to parse 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
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
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
35 # The close() method is called automatically when the class instance
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
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
59 # -- write audio frames and patch up the file header
60 # close() -- patch up the file header and close the
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
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
77 WAVE_FORMAT_PCM
= 0x0001
79 _array_fmts
= None, 'b', 'h', None, 'l'
87 x
= x
+ (ord(byte
) << (8 * i
))
92 def _read_ulong(file):
98 x
= x
+ (ord(byte
) << (8 * i
))
101 def _read_short(file):
107 x
= x
+ (ord(byte
) << (8 * i
))
112 def _write_short(f
, x
):
113 d
, m
= divmod(x
, 256)
117 def _write_long(f
, x
):
121 d
, m
= divmod(x
, 256)
126 def __init__(self
, file):
128 self
.chunkname
= self
.file.read(4)
129 if len(self
.chunkname
) < 4:
131 self
.chunksize
= _read_long(self
.file)
133 self
.offset
= self
.file.tell()
136 self
.file.seek(self
.offset
, 0)
139 def setpos(self
, pos
):
140 if pos
< 0 or pos
> self
.chunksize
:
142 self
.file.seek(self
.offset
+ pos
, 0)
145 def read(self
, length
):
146 if self
.size_read
>= self
.chunksize
:
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
)
156 self
.file.seek(self
.chunksize
- self
.size_read
, 1)
158 while self
.size_read
< self
.chunksize
:
159 dummy
= self
.read(8192)
164 # Variables used in this class:
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
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
193 access _file
, _nchannels
, _nframes
, _sampwidth
, _framerate
, \
194 _comptype
, _compname
, _soundpos
, \
195 _fmt_chunk_read
, _data_seek_needed
, \
196 _data_chunk
, _framesize
: private
198 def initfp(self
, file):
202 form
= self
._file
.read(4)
204 raise Error
, 'file does not start with RIFF id'
205 formlength
= _read_long(self
._file
)
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
228 if not self
._fmt
_chunk
_read
or not self
._data
_chunk
:
229 raise Error
, 'fmt chunk and/or data chunk missing'
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
242 # User visible methods.
248 self
._data
_seek
_needed
= 1
255 return self
._soundpos
257 def getnchannels(self
):
258 return self
._nchannels
260 def getnframes(self
):
263 def getsampwidth(self
):
264 return self
._sampwidth
266 def getframerate(self
):
267 return self
._framerate
269 def getcomptype(self
):
270 return self
._comptype
272 def getcompname(self
):
273 return self
._compname
276 return self
.getnchannels(), self
.getsampwidth(), \
277 self
.getframerate(), self
.getnframes(), \
278 self
.getcomptype(), self
.getcompname()
280 def getmarkers(self
):
283 def getmark(self
, id):
284 raise Error
, 'no marks'
286 def setpos(self
, pos
):
287 if pos
< 0 or pos
> self
._nframes
:
288 raise Error
, 'position not in range'
290 self
._data
_seek
_needed
= 1
292 def readframes(self
, nframes
):
293 if self
._data
_seek
_needed
:
294 self
._data
_chunk
.rewind()
295 pos
= self
._soundpos
* self
._framesize
297 self
._data
_chunk
.setpos(pos
)
298 self
._data
_seek
_needed
= 0
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
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
313 data
= data
.tostring()
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
)
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
335 raise Error
, 'unknown format'
336 self
._framesize
= self
._nchannels
* self
._sampwidth
337 self
._comptype
= 'NONE'
338 self
._compname
= 'not compressed'
341 # Variables used in this class:
343 # These variables are user settable through appropriate methods
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
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
365 access _file
, _comptype
, _compname
, _nchannels
, _sampwidth
, \
366 _framerate
, _nframes
, _nframeswritten
, \
367 _datalength
, _datawritten
: private
369 def __init__(self
, f
):
370 if type(f
) == type(''):
371 f
= __builtin__
.open(f
, 'w')
374 def initfp(self
, file):
381 self
._nframeswritten
= 0
382 self
._datawritten
= 0
390 # User visible methods.
392 def setnchannels(self
, nchannels
):
393 if self
._datawritten
:
394 raise Error
, 'cannot change parameters after starting to write'
396 raise Error
, 'bad # of channels'
397 self
._nchannels
= nchannels
399 def getnchannels(self
):
400 if not self
._nchannels
:
401 raise Error
, 'number of channels not set'
402 return self
._nchannels
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
411 def getsampwidth(self
):
412 if not self
._sampwidth
:
413 raise Error
, 'sample width not set'
414 return self
._sampwidth
416 def setframerate(self
, framerate
):
417 if self
._datawritten
:
418 raise Error
, 'cannot change parameters after starting to write'
420 raise Error
, 'bad frame rate'
421 self
._framerate
= framerate
423 def getframerate(self
):
424 if not self
._framerate
:
425 raise Error
, 'frame rate not set'
426 return self
._framerate
428 def setnframes(self
, nframes
):
429 if self
._datawritten
:
430 raise Error
, 'cannot change parameters after starting to write'
431 self
._nframes
= nframes
433 def getnframes(self
):
434 return self
._nframeswritten
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
444 def getcomptype(self
):
445 return self
._comptype
447 def getcompname(self
):
448 return self
._compname
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
)
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
465 def setmark(self
, id, pos
, name
):
466 raise Error
, 'setmark() not supported'
468 def getmark(self
, id):
469 raise Error
, 'no marks'
471 def getmarkers(self
):
475 return self
._nframeswritten
477 def writeframesraw(self
, data
):
478 self
._ensure
_header
_written
(len(data
))
479 nframes
= len(data
) / (self
._sampwidth
* self
._nchannels
)
481 data
= self
._convert
(data
)
482 if self
._sampwidth
> 1:
484 data
= array
.array(_array_fmts
[self
._sampwidth
], data
)
486 data
.tofile(self
._file
)
487 self
._datawritten
= self
._datawritten
+ len(data
) * self
._sampwidth
489 self
._file
.write(data
)
490 self
._datawritten
= self
._datawritten
+ len(data
)
491 self
._nframeswritten
= self
._nframeswritten
+ nframes
493 def writeframes(self
, data
):
494 self
.writeframesraw(data
)
495 if self
._datalength
!= self
._datawritten
:
499 self
._ensure
_header
_written
(0)
500 if self
._datalength
!= self
._datawritten
:
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
)
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
)
540 def _patchheader(self
):
541 if self
._datawritten
== self
._datalength
:
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
557 raise Error
, "mode must be 'r' or 'w'"
559 openfp
= open # B/W compatibility