~ubuntu-branches/ubuntu/karmic/pypy/karmic

« back to all changes in this revision

Viewing changes to lib-python/2.4.1/wave.py

  • Committer: Bazaar Package Importer
  • Author(s): Alexandre Fayolle
  • Date: 2007-04-13 09:33:09 UTC
  • Revision ID: james.westby@ubuntu.com-20070413093309-yoojh4jcoocu2krz
Tags: upstream-1.0.0
ImportĀ upstreamĀ versionĀ 1.0.0

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
"""Stuff to parse WAVE files.
 
2
 
 
3
Usage.
 
4
 
 
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.
 
11
 
 
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 position in the
 
34
file.
 
35
The close() method is called automatically when the class instance
 
36
is destroyed.
 
37
 
 
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().
 
43
 
 
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
"""
 
73
 
 
74
import __builtin__
 
75
 
 
76
__all__ = ["open", "openfp", "Error"]
 
77
 
 
78
class Error(Exception):
 
79
    pass
 
80
 
 
81
WAVE_FORMAT_PCM = 0x0001
 
82
 
 
83
_array_fmts = None, 'b', 'h', None, 'l'
 
84
 
 
85
# Determine endian-ness
 
86
import struct
 
87
if struct.pack("h", 1) == "\000\001":
 
88
    big_endian = 1
 
89
else:
 
90
    big_endian = 0
 
91
 
 
92
from chunk import Chunk
 
93
 
 
94
class Wave_read:
 
95
    """Variables used in this class:
 
96
 
 
97
    These variables are available to the user though appropriate
 
98
    methods of this class:
 
99
    _file -- the open file with methods read(), close(), and seek()
 
100
              set through the __init__() method
 
101
    _nchannels -- the number of audio channels
 
102
              available through the getnchannels() method
 
103
    _nframes -- the number of audio frames
 
104
              available through the getnframes() method
 
105
    _sampwidth -- the number of bytes per audio sample
 
106
              available through the getsampwidth() method
 
107
    _framerate -- the sampling frequency
 
108
              available through the getframerate() method
 
109
    _comptype -- the AIFF-C compression type ('NONE' if AIFF)
 
110
              available through the getcomptype() method
 
111
    _compname -- the human-readable AIFF-C compression type
 
112
              available through the getcomptype() method
 
113
    _soundpos -- the position in the audio stream
 
114
              available through the tell() method, set through the
 
115
              setpos() method
 
116
 
 
117
    These variables are used internally only:
 
118
    _fmt_chunk_read -- 1 iff the FMT chunk has been read
 
119
    _data_seek_needed -- 1 iff positioned correctly in audio
 
120
              file for readframes()
 
121
    _data_chunk -- instantiation of a chunk class for the DATA chunk
 
122
    _framesize -- size of one frame in the file
 
123
    """
 
124
 
 
125
    def initfp(self, file):
 
126
        self._convert = None
 
127
        self._soundpos = 0
 
128
        self._file = Chunk(file, bigendian = 0)
 
129
        if self._file.getname() != 'RIFF':
 
130
            raise Error, 'file does not start with RIFF id'
 
131
        if self._file.read(4) != 'WAVE':
 
132
            raise Error, 'not a WAVE file'
 
133
        self._fmt_chunk_read = 0
 
134
        self._data_chunk = None
 
135
        while 1:
 
136
            self._data_seek_needed = 1
 
137
            try:
 
138
                chunk = Chunk(self._file, bigendian = 0)
 
139
            except EOFError:
 
140
                break
 
141
            chunkname = chunk.getname()
 
142
            if chunkname == 'fmt ':
 
143
                self._read_fmt_chunk(chunk)
 
144
                self._fmt_chunk_read = 1
 
145
            elif chunkname == 'data':
 
146
                if not self._fmt_chunk_read:
 
147
                    raise Error, 'data chunk before fmt chunk'
 
148
                self._data_chunk = chunk
 
149
                self._nframes = chunk.chunksize // self._framesize
 
150
                self._data_seek_needed = 0
 
151
                break
 
152
            chunk.skip()
 
153
        if not self._fmt_chunk_read or not self._data_chunk:
 
154
            raise Error, 'fmt chunk and/or data chunk missing'
 
155
 
 
156
    def __init__(self, f):
 
157
        self._i_opened_the_file = None
 
158
        if isinstance(f, basestring):
 
159
            f = __builtin__.open(f, 'rb')
 
160
            self._i_opened_the_file = f
 
161
        # else, assume it is an open file object already
 
162
        self.initfp(f)
 
163
 
 
164
    def __del__(self):
 
165
        self.close()
 
166
    #
 
167
    # User visible methods.
 
168
    #
 
169
    def getfp(self):
 
170
        return self._file
 
171
 
 
172
    def rewind(self):
 
173
        self._data_seek_needed = 1
 
174
        self._soundpos = 0
 
175
 
 
176
    def close(self):
 
177
        if self._i_opened_the_file:
 
178
            self._i_opened_the_file.close()
 
179
            self._i_opened_the_file = None
 
180
        self._file = None
 
181
 
 
182
    def tell(self):
 
183
        return self._soundpos
 
184
 
 
185
    def getnchannels(self):
 
186
        return self._nchannels
 
187
 
 
188
    def getnframes(self):
 
189
        return self._nframes
 
190
 
 
191
    def getsampwidth(self):
 
192
        return self._sampwidth
 
193
 
 
194
    def getframerate(self):
 
195
        return self._framerate
 
196
 
 
197
    def getcomptype(self):
 
198
        return self._comptype
 
199
 
 
200
    def getcompname(self):
 
201
        return self._compname
 
202
 
 
203
    def getparams(self):
 
204
        return self.getnchannels(), self.getsampwidth(), \
 
205
               self.getframerate(), self.getnframes(), \
 
206
               self.getcomptype(), self.getcompname()
 
207
 
 
208
    def getmarkers(self):
 
209
        return None
 
210
 
 
211
    def getmark(self, id):
 
212
        raise Error, 'no marks'
 
213
 
 
214
    def setpos(self, pos):
 
215
        if pos < 0 or pos > self._nframes:
 
216
            raise Error, 'position not in range'
 
217
        self._soundpos = pos
 
218
        self._data_seek_needed = 1
 
219
 
 
220
    def readframes(self, nframes):
 
221
        if self._data_seek_needed:
 
222
            self._data_chunk.seek(0, 0)
 
223
            pos = self._soundpos * self._framesize
 
224
            if pos:
 
225
                self._data_chunk.seek(pos, 0)
 
226
            self._data_seek_needed = 0
 
227
        if nframes == 0:
 
228
            return ''
 
229
        if self._sampwidth > 1 and big_endian:
 
230
            # unfortunately the fromfile() method does not take
 
231
            # something that only looks like a file object, so
 
232
            # we have to reach into the innards of the chunk object
 
233
            import array
 
234
            chunk = self._data_chunk
 
235
            data = array.array(_array_fmts[self._sampwidth])
 
236
            nitems = nframes * self._nchannels
 
237
            if nitems * self._sampwidth > chunk.chunksize - chunk.size_read:
 
238
                nitems = (chunk.chunksize - chunk.size_read) / self._sampwidth
 
239
            data.fromfile(chunk.file.file, nitems)
 
240
            # "tell" data chunk how much was read
 
241
            chunk.size_read = chunk.size_read + nitems * self._sampwidth
 
242
            # do the same for the outermost chunk
 
243
            chunk = chunk.file
 
244
            chunk.size_read = chunk.size_read + nitems * self._sampwidth
 
245
            data.byteswap()
 
246
            data = data.tostring()
 
247
        else:
 
248
            data = self._data_chunk.read(nframes * self._framesize)
 
249
        if self._convert and data:
 
250
            data = self._convert(data)
 
251
        self._soundpos = self._soundpos + len(data) // (self._nchannels * self._sampwidth)
 
252
        return data
 
253
 
 
254
    #
 
255
    # Internal methods.
 
256
    #
 
257
 
 
258
    def _read_fmt_chunk(self, chunk):
 
259
        wFormatTag, self._nchannels, self._framerate, dwAvgBytesPerSec, wBlockAlign = struct.unpack('<hhllh', chunk.read(14))
 
260
        if wFormatTag == WAVE_FORMAT_PCM:
 
261
            sampwidth = struct.unpack('<h', chunk.read(2))[0]
 
262
            self._sampwidth = (sampwidth + 7) // 8
 
263
        else:
 
264
            raise Error, 'unknown format: %r' % (wFormatTag,)
 
265
        self._framesize = self._nchannels * self._sampwidth
 
266
        self._comptype = 'NONE'
 
267
        self._compname = 'not compressed'
 
268
 
 
269
class Wave_write:
 
270
    """Variables used in this class:
 
271
 
 
272
    These variables are user settable through appropriate methods
 
273
    of this class:
 
274
    _file -- the open file with methods write(), close(), tell(), seek()
 
275
              set through the __init__() method
 
276
    _comptype -- the AIFF-C compression type ('NONE' in AIFF)
 
277
              set through the setcomptype() or setparams() method
 
278
    _compname -- the human-readable AIFF-C compression type
 
279
              set through the setcomptype() or setparams() method
 
280
    _nchannels -- the number of audio channels
 
281
              set through the setnchannels() or setparams() method
 
282
    _sampwidth -- the number of bytes per audio sample
 
283
              set through the setsampwidth() or setparams() method
 
284
    _framerate -- the sampling frequency
 
285
              set through the setframerate() or setparams() method
 
286
    _nframes -- the number of audio frames written to the header
 
287
              set through the setnframes() or setparams() method
 
288
 
 
289
    These variables are used internally only:
 
290
    _datalength -- the size of the audio samples written to the header
 
291
    _nframeswritten -- the number of frames actually written
 
292
    _datawritten -- the size of the audio samples actually written
 
293
    """
 
294
 
 
295
    def __init__(self, f):
 
296
        self._i_opened_the_file = None
 
297
        if isinstance(f, basestring):
 
298
            f = __builtin__.open(f, 'wb')
 
299
            self._i_opened_the_file = f
 
300
        self.initfp(f)
 
301
 
 
302
    def initfp(self, file):
 
303
        self._file = file
 
304
        self._convert = None
 
305
        self._nchannels = 0
 
306
        self._sampwidth = 0
 
307
        self._framerate = 0
 
308
        self._nframes = 0
 
309
        self._nframeswritten = 0
 
310
        self._datawritten = 0
 
311
        self._datalength = 0
 
312
 
 
313
    def __del__(self):
 
314
        self.close()
 
315
 
 
316
    #
 
317
    # User visible methods.
 
318
    #
 
319
    def setnchannels(self, nchannels):
 
320
        if self._datawritten:
 
321
            raise Error, 'cannot change parameters after starting to write'
 
322
        if nchannels < 1:
 
323
            raise Error, 'bad # of channels'
 
324
        self._nchannels = nchannels
 
325
 
 
326
    def getnchannels(self):
 
327
        if not self._nchannels:
 
328
            raise Error, 'number of channels not set'
 
329
        return self._nchannels
 
330
 
 
331
    def setsampwidth(self, sampwidth):
 
332
        if self._datawritten:
 
333
            raise Error, 'cannot change parameters after starting to write'
 
334
        if sampwidth < 1 or sampwidth > 4:
 
335
            raise Error, 'bad sample width'
 
336
        self._sampwidth = sampwidth
 
337
 
 
338
    def getsampwidth(self):
 
339
        if not self._sampwidth:
 
340
            raise Error, 'sample width not set'
 
341
        return self._sampwidth
 
342
 
 
343
    def setframerate(self, framerate):
 
344
        if self._datawritten:
 
345
            raise Error, 'cannot change parameters after starting to write'
 
346
        if framerate <= 0:
 
347
            raise Error, 'bad frame rate'
 
348
        self._framerate = framerate
 
349
 
 
350
    def getframerate(self):
 
351
        if not self._framerate:
 
352
            raise Error, 'frame rate not set'
 
353
        return self._framerate
 
354
 
 
355
    def setnframes(self, nframes):
 
356
        if self._datawritten:
 
357
            raise Error, 'cannot change parameters after starting to write'
 
358
        self._nframes = nframes
 
359
 
 
360
    def getnframes(self):
 
361
        return self._nframeswritten
 
362
 
 
363
    def setcomptype(self, comptype, compname):
 
364
        if self._datawritten:
 
365
            raise Error, 'cannot change parameters after starting to write'
 
366
        if comptype not in ('NONE',):
 
367
            raise Error, 'unsupported compression type'
 
368
        self._comptype = comptype
 
369
        self._compname = compname
 
370
 
 
371
    def getcomptype(self):
 
372
        return self._comptype
 
373
 
 
374
    def getcompname(self):
 
375
        return self._compname
 
376
 
 
377
    def setparams(self, (nchannels, sampwidth, framerate, nframes, comptype, compname)):
 
378
        if self._datawritten:
 
379
            raise Error, 'cannot change parameters after starting to write'
 
380
        self.setnchannels(nchannels)
 
381
        self.setsampwidth(sampwidth)
 
382
        self.setframerate(framerate)
 
383
        self.setnframes(nframes)
 
384
        self.setcomptype(comptype, compname)
 
385
 
 
386
    def getparams(self):
 
387
        if not self._nchannels or not self._sampwidth or not self._framerate:
 
388
            raise Error, 'not all parameters set'
 
389
        return self._nchannels, self._sampwidth, self._framerate, \
 
390
              self._nframes, self._comptype, self._compname
 
391
 
 
392
    def setmark(self, id, pos, name):
 
393
        raise Error, 'setmark() not supported'
 
394
 
 
395
    def getmark(self, id):
 
396
        raise Error, 'no marks'
 
397
 
 
398
    def getmarkers(self):
 
399
        return None
 
400
 
 
401
    def tell(self):
 
402
        return self._nframeswritten
 
403
 
 
404
    def writeframesraw(self, data):
 
405
        self._ensure_header_written(len(data))
 
406
        nframes = len(data) // (self._sampwidth * self._nchannels)
 
407
        if self._convert:
 
408
            data = self._convert(data)
 
409
        if self._sampwidth > 1 and big_endian:
 
410
            import array
 
411
            data = array.array(_array_fmts[self._sampwidth], data)
 
412
            data.byteswap()
 
413
            data.tofile(self._file)
 
414
            self._datawritten = self._datawritten + len(data) * self._sampwidth
 
415
        else:
 
416
            self._file.write(data)
 
417
            self._datawritten = self._datawritten + len(data)
 
418
        self._nframeswritten = self._nframeswritten + nframes
 
419
 
 
420
    def writeframes(self, data):
 
421
        self.writeframesraw(data)
 
422
        if self._datalength != self._datawritten:
 
423
            self._patchheader()
 
424
 
 
425
    def close(self):
 
426
        if self._file:
 
427
            self._ensure_header_written(0)
 
428
            if self._datalength != self._datawritten:
 
429
                self._patchheader()
 
430
            self._file.flush()
 
431
            self._file = None
 
432
        if self._i_opened_the_file:
 
433
            self._i_opened_the_file.close()
 
434
            self._i_opened_the_file = None
 
435
 
 
436
    #
 
437
    # Internal methods.
 
438
    #
 
439
 
 
440
    def _ensure_header_written(self, datasize):
 
441
        if not self._datawritten:
 
442
            if not self._nchannels:
 
443
                raise Error, '# channels not specified'
 
444
            if not self._sampwidth:
 
445
                raise Error, 'sample width not specified'
 
446
            if not self._framerate:
 
447
                raise Error, 'sampling rate not specified'
 
448
            self._write_header(datasize)
 
449
 
 
450
    def _write_header(self, initlength):
 
451
        self._file.write('RIFF')
 
452
        if not self._nframes:
 
453
            self._nframes = initlength / (self._nchannels * self._sampwidth)
 
454
        self._datalength = self._nframes * self._nchannels * self._sampwidth
 
455
        self._form_length_pos = self._file.tell()
 
456
        self._file.write(struct.pack('<l4s4slhhllhh4s',
 
457
            36 + self._datalength, 'WAVE', 'fmt ', 16,
 
458
            WAVE_FORMAT_PCM, self._nchannels, self._framerate,
 
459
            self._nchannels * self._framerate * self._sampwidth,
 
460
            self._nchannels * self._sampwidth,
 
461
            self._sampwidth * 8, 'data'))
 
462
        self._data_length_pos = self._file.tell()
 
463
        self._file.write(struct.pack('<l', self._datalength))
 
464
 
 
465
    def _patchheader(self):
 
466
        if self._datawritten == self._datalength:
 
467
            return
 
468
        curpos = self._file.tell()
 
469
        self._file.seek(self._form_length_pos, 0)
 
470
        self._file.write(struct.pack('<l', 36 + self._datawritten))
 
471
        self._file.seek(self._data_length_pos, 0)
 
472
        self._file.write(struct.pack('<l', self._datawritten))
 
473
        self._file.seek(curpos, 0)
 
474
        self._datalength = self._datawritten
 
475
 
 
476
def open(f, mode=None):
 
477
    if mode is None:
 
478
        if hasattr(f, 'mode'):
 
479
            mode = f.mode
 
480
        else:
 
481
            mode = 'rb'
 
482
    if mode in ('r', 'rb'):
 
483
        return Wave_read(f)
 
484
    elif mode in ('w', 'wb'):
 
485
        return Wave_write(f)
 
486
    else:
 
487
        raise Error, "mode must be 'r', 'rb', 'w', or 'wb'"
 
488
 
 
489
openfp = open # B/W compatibility