~malept/ubuntu/lucid/python2.6/dev-dependency-fix

« back to all changes in this revision

Viewing changes to Lib/aifc.py

  • Committer: Bazaar Package Importer
  • Author(s): Matthias Klose
  • Date: 2009-02-13 12:51:00 UTC
  • Revision ID: james.westby@ubuntu.com-20090213125100-uufgcb9yeqzujpqw
Tags: upstream-2.6.1
ImportĀ upstreamĀ versionĀ 2.6.1

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
"""Stuff to parse AIFF-C and AIFF files.
 
2
 
 
3
Unless explicitly stated otherwise, the description below is true
 
4
both for AIFF-C files and AIFF files.
 
5
 
 
6
An AIFF-C file has the following structure.
 
7
 
 
8
  +-----------------+
 
9
  | FORM            |
 
10
  +-----------------+
 
11
  | <size>          |
 
12
  +----+------------+
 
13
  |    | AIFC       |
 
14
  |    +------------+
 
15
  |    | <chunks>   |
 
16
  |    |    .       |
 
17
  |    |    .       |
 
18
  |    |    .       |
 
19
  +----+------------+
 
20
 
 
21
An AIFF file has the string "AIFF" instead of "AIFC".
 
22
 
 
23
A chunk consists of an identifier (4 bytes) followed by a size (4 bytes,
 
24
big endian order), followed by the data.  The size field does not include
 
25
the size of the 8 byte header.
 
26
 
 
27
The following chunk types are recognized.
 
28
 
 
29
  FVER
 
30
      <version number of AIFF-C defining document> (AIFF-C only).
 
31
  MARK
 
32
      <# of markers> (2 bytes)
 
33
      list of markers:
 
34
          <marker ID> (2 bytes, must be > 0)
 
35
          <position> (4 bytes)
 
36
          <marker name> ("pstring")
 
37
  COMM
 
38
      <# of channels> (2 bytes)
 
39
      <# of sound frames> (4 bytes)
 
40
      <size of the samples> (2 bytes)
 
41
      <sampling frequency> (10 bytes, IEEE 80-bit extended
 
42
          floating point)
 
43
      in AIFF-C files only:
 
44
      <compression type> (4 bytes)
 
45
      <human-readable version of compression type> ("pstring")
 
46
  SSND
 
47
      <offset> (4 bytes, not used by this program)
 
48
      <blocksize> (4 bytes, not used by this program)
 
49
      <sound data>
 
50
 
 
51
A pstring consists of 1 byte length, a string of characters, and 0 or 1
 
52
byte pad to make the total length even.
 
53
 
 
54
Usage.
 
55
 
 
56
Reading AIFF files:
 
57
  f = aifc.open(file, 'r')
 
58
where file is either the name of a file or an open file pointer.
 
59
The open file pointer must have methods read(), seek(), and close().
 
60
In some types of audio files, if the setpos() method is not used,
 
61
the seek() method is not necessary.
 
62
 
 
63
This returns an instance of a class with the following public methods:
 
64
  getnchannels()  -- returns number of audio channels (1 for
 
65
             mono, 2 for stereo)
 
66
  getsampwidth()  -- returns sample width in bytes
 
67
  getframerate()  -- returns sampling frequency
 
68
  getnframes()    -- returns number of audio frames
 
69
  getcomptype()   -- returns compression type ('NONE' for AIFF files)
 
70
  getcompname()   -- returns human-readable version of
 
71
             compression type ('not compressed' for AIFF files)
 
72
  getparams() -- returns a tuple consisting of all of the
 
73
             above in the above order
 
74
  getmarkers()    -- get the list of marks in the audio file or None
 
75
             if there are no marks
 
76
  getmark(id) -- get mark with the specified id (raises an error
 
77
             if the mark does not exist)
 
78
  readframes(n)   -- returns at most n frames of audio
 
79
  rewind()    -- rewind to the beginning of the audio stream
 
80
  setpos(pos) -- seek to the specified position
 
81
  tell()      -- return the current position
 
82
  close()     -- close the instance (make it unusable)
 
83
The position returned by tell(), the position given to setpos() and
 
84
the position of marks are all compatible and have nothing to do with
 
85
the actual position in the file.
 
86
The close() method is called automatically when the class instance
 
87
is destroyed.
 
88
 
 
89
Writing AIFF files:
 
90
  f = aifc.open(file, 'w')
 
91
where file is either the name of a file or an open file pointer.
 
92
The open file pointer must have methods write(), tell(), seek(), and
 
93
close().
 
94
 
 
95
This returns an instance of a class with the following public methods:
 
96
  aiff()      -- create an AIFF file (AIFF-C default)
 
97
  aifc()      -- create an AIFF-C file
 
98
  setnchannels(n) -- set the number of channels
 
99
  setsampwidth(n) -- set the sample width
 
100
  setframerate(n) -- set the frame rate
 
101
  setnframes(n)   -- set the number of frames
 
102
  setcomptype(type, name)
 
103
          -- set the compression type and the
 
104
             human-readable compression type
 
105
  setparams(tuple)
 
106
          -- set all parameters at once
 
107
  setmark(id, pos, name)
 
108
          -- add specified mark to the list of marks
 
109
  tell()      -- return current position in output file (useful
 
110
             in combination with setmark())
 
111
  writeframesraw(data)
 
112
          -- write audio frames without pathing up the
 
113
             file header
 
114
  writeframes(data)
 
115
          -- write audio frames and patch up the file header
 
116
  close()     -- patch up the file header and close the
 
117
             output file
 
118
You should set the parameters before the first writeframesraw or
 
119
writeframes.  The total number of frames does not need to be set,
 
120
but when it is set to the correct value, the header does not have to
 
121
be patched up.
 
122
It is best to first set all parameters, perhaps possibly the
 
123
compression type, and then write audio frames using writeframesraw.
 
124
When all frames have been written, either call writeframes('') or
 
125
close() to patch up the sizes in the header.
 
126
Marks can be added anytime.  If there are any marks, ypu must call
 
127
close() after all frames have been written.
 
128
The close() method is called automatically when the class instance
 
129
is destroyed.
 
130
 
 
131
When a file is opened with the extension '.aiff', an AIFF file is
 
132
written, otherwise an AIFF-C file is written.  This default can be
 
133
changed by calling aiff() or aifc() before the first writeframes or
 
134
writeframesraw.
 
135
"""
 
136
 
 
137
import struct
 
138
import __builtin__
 
139
 
 
140
__all__ = ["Error","open","openfp"]
 
141
 
 
142
class Error(Exception):
 
143
    pass
 
144
 
 
145
_AIFC_version = 0xA2805140L     # Version 1 of AIFF-C
 
146
 
 
147
_skiplist = 'COMT', 'INST', 'MIDI', 'AESD', \
 
148
      'APPL', 'NAME', 'AUTH', '(c) ', 'ANNO'
 
149
 
 
150
def _read_long(file):
 
151
    try:
 
152
        return struct.unpack('>l', file.read(4))[0]
 
153
    except struct.error:
 
154
        raise EOFError
 
155
 
 
156
def _read_ulong(file):
 
157
    try:
 
158
        return struct.unpack('>L', file.read(4))[0]
 
159
    except struct.error:
 
160
        raise EOFError
 
161
 
 
162
def _read_short(file):
 
163
    try:
 
164
        return struct.unpack('>h', file.read(2))[0]
 
165
    except struct.error:
 
166
        raise EOFError
 
167
 
 
168
def _read_string(file):
 
169
    length = ord(file.read(1))
 
170
    if length == 0:
 
171
        data = ''
 
172
    else:
 
173
        data = file.read(length)
 
174
    if length & 1 == 0:
 
175
        dummy = file.read(1)
 
176
    return data
 
177
 
 
178
_HUGE_VAL = 1.79769313486231e+308 # See <limits.h>
 
179
 
 
180
def _read_float(f): # 10 bytes
 
181
    expon = _read_short(f) # 2 bytes
 
182
    sign = 1
 
183
    if expon < 0:
 
184
        sign = -1
 
185
        expon = expon + 0x8000
 
186
    himant = _read_ulong(f) # 4 bytes
 
187
    lomant = _read_ulong(f) # 4 bytes
 
188
    if expon == himant == lomant == 0:
 
189
        f = 0.0
 
190
    elif expon == 0x7FFF:
 
191
        f = _HUGE_VAL
 
192
    else:
 
193
        expon = expon - 16383
 
194
        f = (himant * 0x100000000L + lomant) * pow(2.0, expon - 63)
 
195
    return sign * f
 
196
 
 
197
def _write_short(f, x):
 
198
    f.write(struct.pack('>h', x))
 
199
 
 
200
def _write_long(f, x):
 
201
    f.write(struct.pack('>L', x))
 
202
 
 
203
def _write_string(f, s):
 
204
    if len(s) > 255:
 
205
        raise ValueError("string exceeds maximum pstring length")
 
206
    f.write(chr(len(s)))
 
207
    f.write(s)
 
208
    if len(s) & 1 == 0:
 
209
        f.write(chr(0))
 
210
 
 
211
def _write_float(f, x):
 
212
    import math
 
213
    if x < 0:
 
214
        sign = 0x8000
 
215
        x = x * -1
 
216
    else:
 
217
        sign = 0
 
218
    if x == 0:
 
219
        expon = 0
 
220
        himant = 0
 
221
        lomant = 0
 
222
    else:
 
223
        fmant, expon = math.frexp(x)
 
224
        if expon > 16384 or fmant >= 1:     # Infinity or NaN
 
225
            expon = sign|0x7FFF
 
226
            himant = 0
 
227
            lomant = 0
 
228
        else:                   # Finite
 
229
            expon = expon + 16382
 
230
            if expon < 0:           # denormalized
 
231
                fmant = math.ldexp(fmant, expon)
 
232
                expon = 0
 
233
            expon = expon | sign
 
234
            fmant = math.ldexp(fmant, 32)
 
235
            fsmant = math.floor(fmant)
 
236
            himant = long(fsmant)
 
237
            fmant = math.ldexp(fmant - fsmant, 32)
 
238
            fsmant = math.floor(fmant)
 
239
            lomant = long(fsmant)
 
240
    _write_short(f, expon)
 
241
    _write_long(f, himant)
 
242
    _write_long(f, lomant)
 
243
 
 
244
from chunk import Chunk
 
245
 
 
246
class Aifc_read:
 
247
    # Variables used in this class:
 
248
    #
 
249
    # These variables are available to the user though appropriate
 
250
    # methods of this class:
 
251
    # _file -- the open file with methods read(), close(), and seek()
 
252
    #       set through the __init__() method
 
253
    # _nchannels -- the number of audio channels
 
254
    #       available through the getnchannels() method
 
255
    # _nframes -- the number of audio frames
 
256
    #       available through the getnframes() method
 
257
    # _sampwidth -- the number of bytes per audio sample
 
258
    #       available through the getsampwidth() method
 
259
    # _framerate -- the sampling frequency
 
260
    #       available through the getframerate() method
 
261
    # _comptype -- the AIFF-C compression type ('NONE' if AIFF)
 
262
    #       available through the getcomptype() method
 
263
    # _compname -- the human-readable AIFF-C compression type
 
264
    #       available through the getcomptype() method
 
265
    # _markers -- the marks in the audio file
 
266
    #       available through the getmarkers() and getmark()
 
267
    #       methods
 
268
    # _soundpos -- the position in the audio stream
 
269
    #       available through the tell() method, set through the
 
270
    #       setpos() method
 
271
    #
 
272
    # These variables are used internally only:
 
273
    # _version -- the AIFF-C version number
 
274
    # _decomp -- the decompressor from builtin module cl
 
275
    # _comm_chunk_read -- 1 iff the COMM chunk has been read
 
276
    # _aifc -- 1 iff reading an AIFF-C file
 
277
    # _ssnd_seek_needed -- 1 iff positioned correctly in audio
 
278
    #       file for readframes()
 
279
    # _ssnd_chunk -- instantiation of a chunk class for the SSND chunk
 
280
    # _framesize -- size of one frame in the file
 
281
 
 
282
    def initfp(self, file):
 
283
        self._version = 0
 
284
        self._decomp = None
 
285
        self._convert = None
 
286
        self._markers = []
 
287
        self._soundpos = 0
 
288
        self._file = Chunk(file)
 
289
        if self._file.getname() != 'FORM':
 
290
            raise Error, 'file does not start with FORM id'
 
291
        formdata = self._file.read(4)
 
292
        if formdata == 'AIFF':
 
293
            self._aifc = 0
 
294
        elif formdata == 'AIFC':
 
295
            self._aifc = 1
 
296
        else:
 
297
            raise Error, 'not an AIFF or AIFF-C file'
 
298
        self._comm_chunk_read = 0
 
299
        while 1:
 
300
            self._ssnd_seek_needed = 1
 
301
            try:
 
302
                chunk = Chunk(self._file)
 
303
            except EOFError:
 
304
                break
 
305
            chunkname = chunk.getname()
 
306
            if chunkname == 'COMM':
 
307
                self._read_comm_chunk(chunk)
 
308
                self._comm_chunk_read = 1
 
309
            elif chunkname == 'SSND':
 
310
                self._ssnd_chunk = chunk
 
311
                dummy = chunk.read(8)
 
312
                self._ssnd_seek_needed = 0
 
313
            elif chunkname == 'FVER':
 
314
                self._version = _read_ulong(chunk)
 
315
            elif chunkname == 'MARK':
 
316
                self._readmark(chunk)
 
317
            elif chunkname in _skiplist:
 
318
                pass
 
319
            else:
 
320
                raise Error, 'unrecognized chunk type '+chunk.chunkname
 
321
            chunk.skip()
 
322
        if not self._comm_chunk_read or not self._ssnd_chunk:
 
323
            raise Error, 'COMM chunk and/or SSND chunk missing'
 
324
        if self._aifc and self._decomp:
 
325
            import cl
 
326
            params = [cl.ORIGINAL_FORMAT, 0,
 
327
                  cl.BITS_PER_COMPONENT, self._sampwidth * 8,
 
328
                  cl.FRAME_RATE, self._framerate]
 
329
            if self._nchannels == 1:
 
330
                params[1] = cl.MONO
 
331
            elif self._nchannels == 2:
 
332
                params[1] = cl.STEREO_INTERLEAVED
 
333
            else:
 
334
                raise Error, 'cannot compress more than 2 channels'
 
335
            self._decomp.SetParams(params)
 
336
 
 
337
    def __init__(self, f):
 
338
        if type(f) == type(''):
 
339
            f = __builtin__.open(f, 'rb')
 
340
        # else, assume it is an open file object already
 
341
        self.initfp(f)
 
342
 
 
343
    #
 
344
    # User visible methods.
 
345
    #
 
346
    def getfp(self):
 
347
        return self._file
 
348
 
 
349
    def rewind(self):
 
350
        self._ssnd_seek_needed = 1
 
351
        self._soundpos = 0
 
352
 
 
353
    def close(self):
 
354
        if self._decomp:
 
355
            self._decomp.CloseDecompressor()
 
356
            self._decomp = None
 
357
        self._file = None
 
358
 
 
359
    def tell(self):
 
360
        return self._soundpos
 
361
 
 
362
    def getnchannels(self):
 
363
        return self._nchannels
 
364
 
 
365
    def getnframes(self):
 
366
        return self._nframes
 
367
 
 
368
    def getsampwidth(self):
 
369
        return self._sampwidth
 
370
 
 
371
    def getframerate(self):
 
372
        return self._framerate
 
373
 
 
374
    def getcomptype(self):
 
375
        return self._comptype
 
376
 
 
377
    def getcompname(self):
 
378
        return self._compname
 
379
 
 
380
##  def getversion(self):
 
381
##      return self._version
 
382
 
 
383
    def getparams(self):
 
384
        return self.getnchannels(), self.getsampwidth(), \
 
385
              self.getframerate(), self.getnframes(), \
 
386
              self.getcomptype(), self.getcompname()
 
387
 
 
388
    def getmarkers(self):
 
389
        if len(self._markers) == 0:
 
390
            return None
 
391
        return self._markers
 
392
 
 
393
    def getmark(self, id):
 
394
        for marker in self._markers:
 
395
            if id == marker[0]:
 
396
                return marker
 
397
        raise Error, 'marker %r does not exist' % (id,)
 
398
 
 
399
    def setpos(self, pos):
 
400
        if pos < 0 or pos > self._nframes:
 
401
            raise Error, 'position not in range'
 
402
        self._soundpos = pos
 
403
        self._ssnd_seek_needed = 1
 
404
 
 
405
    def readframes(self, nframes):
 
406
        if self._ssnd_seek_needed:
 
407
            self._ssnd_chunk.seek(0)
 
408
            dummy = self._ssnd_chunk.read(8)
 
409
            pos = self._soundpos * self._framesize
 
410
            if pos:
 
411
                self._ssnd_chunk.seek(pos + 8)
 
412
            self._ssnd_seek_needed = 0
 
413
        if nframes == 0:
 
414
            return ''
 
415
        data = self._ssnd_chunk.read(nframes * self._framesize)
 
416
        if self._convert and data:
 
417
            data = self._convert(data)
 
418
        self._soundpos = self._soundpos + len(data) / (self._nchannels * self._sampwidth)
 
419
        return data
 
420
 
 
421
    #
 
422
    # Internal methods.
 
423
    #
 
424
 
 
425
    def _decomp_data(self, data):
 
426
        import cl
 
427
        dummy = self._decomp.SetParam(cl.FRAME_BUFFER_SIZE,
 
428
                          len(data) * 2)
 
429
        return self._decomp.Decompress(len(data) / self._nchannels,
 
430
                           data)
 
431
 
 
432
    def _ulaw2lin(self, data):
 
433
        import audioop
 
434
        return audioop.ulaw2lin(data, 2)
 
435
 
 
436
    def _adpcm2lin(self, data):
 
437
        import audioop
 
438
        if not hasattr(self, '_adpcmstate'):
 
439
            # first time
 
440
            self._adpcmstate = None
 
441
        data, self._adpcmstate = audioop.adpcm2lin(data, 2,
 
442
                               self._adpcmstate)
 
443
        return data
 
444
 
 
445
    def _read_comm_chunk(self, chunk):
 
446
        self._nchannels = _read_short(chunk)
 
447
        self._nframes = _read_long(chunk)
 
448
        self._sampwidth = (_read_short(chunk) + 7) / 8
 
449
        self._framerate = int(_read_float(chunk))
 
450
        self._framesize = self._nchannels * self._sampwidth
 
451
        if self._aifc:
 
452
            #DEBUG: SGI's soundeditor produces a bad size :-(
 
453
            kludge = 0
 
454
            if chunk.chunksize == 18:
 
455
                kludge = 1
 
456
                print 'Warning: bad COMM chunk size'
 
457
                chunk.chunksize = 23
 
458
            #DEBUG end
 
459
            self._comptype = chunk.read(4)
 
460
            #DEBUG start
 
461
            if kludge:
 
462
                length = ord(chunk.file.read(1))
 
463
                if length & 1 == 0:
 
464
                    length = length + 1
 
465
                chunk.chunksize = chunk.chunksize + length
 
466
                chunk.file.seek(-1, 1)
 
467
            #DEBUG end
 
468
            self._compname = _read_string(chunk)
 
469
            if self._comptype != 'NONE':
 
470
                if self._comptype == 'G722':
 
471
                    try:
 
472
                        import audioop
 
473
                    except ImportError:
 
474
                        pass
 
475
                    else:
 
476
                        self._convert = self._adpcm2lin
 
477
                        self._framesize = self._framesize / 4
 
478
                        return
 
479
                # for ULAW and ALAW try Compression Library
 
480
                try:
 
481
                    import cl
 
482
                except ImportError:
 
483
                    if self._comptype == 'ULAW':
 
484
                        try:
 
485
                            import audioop
 
486
                            self._convert = self._ulaw2lin
 
487
                            self._framesize = self._framesize / 2
 
488
                            return
 
489
                        except ImportError:
 
490
                            pass
 
491
                    raise Error, 'cannot read compressed AIFF-C files'
 
492
                if self._comptype == 'ULAW':
 
493
                    scheme = cl.G711_ULAW
 
494
                    self._framesize = self._framesize / 2
 
495
                elif self._comptype == 'ALAW':
 
496
                    scheme = cl.G711_ALAW
 
497
                    self._framesize = self._framesize / 2
 
498
                else:
 
499
                    raise Error, 'unsupported compression type'
 
500
                self._decomp = cl.OpenDecompressor(scheme)
 
501
                self._convert = self._decomp_data
 
502
        else:
 
503
            self._comptype = 'NONE'
 
504
            self._compname = 'not compressed'
 
505
 
 
506
    def _readmark(self, chunk):
 
507
        nmarkers = _read_short(chunk)
 
508
        # Some files appear to contain invalid counts.
 
509
        # Cope with this by testing for EOF.
 
510
        try:
 
511
            for i in range(nmarkers):
 
512
                id = _read_short(chunk)
 
513
                pos = _read_long(chunk)
 
514
                name = _read_string(chunk)
 
515
                if pos or name:
 
516
                    # some files appear to have
 
517
                    # dummy markers consisting of
 
518
                    # a position 0 and name ''
 
519
                    self._markers.append((id, pos, name))
 
520
        except EOFError:
 
521
            print 'Warning: MARK chunk contains only',
 
522
            print len(self._markers),
 
523
            if len(self._markers) == 1: print 'marker',
 
524
            else: print 'markers',
 
525
            print 'instead of', nmarkers
 
526
 
 
527
class Aifc_write:
 
528
    # Variables used in this class:
 
529
    #
 
530
    # These variables are user settable through appropriate methods
 
531
    # of this class:
 
532
    # _file -- the open file with methods write(), close(), tell(), seek()
 
533
    #       set through the __init__() method
 
534
    # _comptype -- the AIFF-C compression type ('NONE' in AIFF)
 
535
    #       set through the setcomptype() or setparams() method
 
536
    # _compname -- the human-readable AIFF-C compression type
 
537
    #       set through the setcomptype() or setparams() method
 
538
    # _nchannels -- the number of audio channels
 
539
    #       set through the setnchannels() or setparams() method
 
540
    # _sampwidth -- the number of bytes per audio sample
 
541
    #       set through the setsampwidth() or setparams() method
 
542
    # _framerate -- the sampling frequency
 
543
    #       set through the setframerate() or setparams() method
 
544
    # _nframes -- the number of audio frames written to the header
 
545
    #       set through the setnframes() or setparams() method
 
546
    # _aifc -- whether we're writing an AIFF-C file or an AIFF file
 
547
    #       set through the aifc() method, reset through the
 
548
    #       aiff() method
 
549
    #
 
550
    # These variables are used internally only:
 
551
    # _version -- the AIFF-C version number
 
552
    # _comp -- the compressor from builtin module cl
 
553
    # _nframeswritten -- the number of audio frames actually written
 
554
    # _datalength -- the size of the audio samples written to the header
 
555
    # _datawritten -- the size of the audio samples actually written
 
556
 
 
557
    def __init__(self, f):
 
558
        if type(f) == type(''):
 
559
            filename = f
 
560
            f = __builtin__.open(f, 'wb')
 
561
        else:
 
562
            # else, assume it is an open file object already
 
563
            filename = '???'
 
564
        self.initfp(f)
 
565
        if filename[-5:] == '.aiff':
 
566
            self._aifc = 0
 
567
        else:
 
568
            self._aifc = 1
 
569
 
 
570
    def initfp(self, file):
 
571
        self._file = file
 
572
        self._version = _AIFC_version
 
573
        self._comptype = 'NONE'
 
574
        self._compname = 'not compressed'
 
575
        self._comp = None
 
576
        self._convert = None
 
577
        self._nchannels = 0
 
578
        self._sampwidth = 0
 
579
        self._framerate = 0
 
580
        self._nframes = 0
 
581
        self._nframeswritten = 0
 
582
        self._datawritten = 0
 
583
        self._datalength = 0
 
584
        self._markers = []
 
585
        self._marklength = 0
 
586
        self._aifc = 1      # AIFF-C is default
 
587
 
 
588
    def __del__(self):
 
589
        if self._file:
 
590
            self.close()
 
591
 
 
592
    #
 
593
    # User visible methods.
 
594
    #
 
595
    def aiff(self):
 
596
        if self._nframeswritten:
 
597
            raise Error, 'cannot change parameters after starting to write'
 
598
        self._aifc = 0
 
599
 
 
600
    def aifc(self):
 
601
        if self._nframeswritten:
 
602
            raise Error, 'cannot change parameters after starting to write'
 
603
        self._aifc = 1
 
604
 
 
605
    def setnchannels(self, nchannels):
 
606
        if self._nframeswritten:
 
607
            raise Error, 'cannot change parameters after starting to write'
 
608
        if nchannels < 1:
 
609
            raise Error, 'bad # of channels'
 
610
        self._nchannels = nchannels
 
611
 
 
612
    def getnchannels(self):
 
613
        if not self._nchannels:
 
614
            raise Error, 'number of channels not set'
 
615
        return self._nchannels
 
616
 
 
617
    def setsampwidth(self, sampwidth):
 
618
        if self._nframeswritten:
 
619
            raise Error, 'cannot change parameters after starting to write'
 
620
        if sampwidth < 1 or sampwidth > 4:
 
621
            raise Error, 'bad sample width'
 
622
        self._sampwidth = sampwidth
 
623
 
 
624
    def getsampwidth(self):
 
625
        if not self._sampwidth:
 
626
            raise Error, 'sample width not set'
 
627
        return self._sampwidth
 
628
 
 
629
    def setframerate(self, framerate):
 
630
        if self._nframeswritten:
 
631
            raise Error, 'cannot change parameters after starting to write'
 
632
        if framerate <= 0:
 
633
            raise Error, 'bad frame rate'
 
634
        self._framerate = framerate
 
635
 
 
636
    def getframerate(self):
 
637
        if not self._framerate:
 
638
            raise Error, 'frame rate not set'
 
639
        return self._framerate
 
640
 
 
641
    def setnframes(self, nframes):
 
642
        if self._nframeswritten:
 
643
            raise Error, 'cannot change parameters after starting to write'
 
644
        self._nframes = nframes
 
645
 
 
646
    def getnframes(self):
 
647
        return self._nframeswritten
 
648
 
 
649
    def setcomptype(self, comptype, compname):
 
650
        if self._nframeswritten:
 
651
            raise Error, 'cannot change parameters after starting to write'
 
652
        if comptype not in ('NONE', 'ULAW', 'ALAW', 'G722'):
 
653
            raise Error, 'unsupported compression type'
 
654
        self._comptype = comptype
 
655
        self._compname = compname
 
656
 
 
657
    def getcomptype(self):
 
658
        return self._comptype
 
659
 
 
660
    def getcompname(self):
 
661
        return self._compname
 
662
 
 
663
##  def setversion(self, version):
 
664
##      if self._nframeswritten:
 
665
##          raise Error, 'cannot change parameters after starting to write'
 
666
##      self._version = version
 
667
 
 
668
    def setparams(self, info):
 
669
        nchannels, sampwidth, framerate, nframes, comptype, compname = info
 
670
        if self._nframeswritten:
 
671
            raise Error, 'cannot change parameters after starting to write'
 
672
        if comptype not in ('NONE', 'ULAW', 'ALAW', 'G722'):
 
673
            raise Error, 'unsupported compression type'
 
674
        self.setnchannels(nchannels)
 
675
        self.setsampwidth(sampwidth)
 
676
        self.setframerate(framerate)
 
677
        self.setnframes(nframes)
 
678
        self.setcomptype(comptype, compname)
 
679
 
 
680
    def getparams(self):
 
681
        if not self._nchannels or not self._sampwidth or not self._framerate:
 
682
            raise Error, 'not all parameters set'
 
683
        return self._nchannels, self._sampwidth, self._framerate, \
 
684
              self._nframes, self._comptype, self._compname
 
685
 
 
686
    def setmark(self, id, pos, name):
 
687
        if id <= 0:
 
688
            raise Error, 'marker ID must be > 0'
 
689
        if pos < 0:
 
690
            raise Error, 'marker position must be >= 0'
 
691
        if type(name) != type(''):
 
692
            raise Error, 'marker name must be a string'
 
693
        for i in range(len(self._markers)):
 
694
            if id == self._markers[i][0]:
 
695
                self._markers[i] = id, pos, name
 
696
                return
 
697
        self._markers.append((id, pos, name))
 
698
 
 
699
    def getmark(self, id):
 
700
        for marker in self._markers:
 
701
            if id == marker[0]:
 
702
                return marker
 
703
        raise Error, 'marker %r does not exist' % (id,)
 
704
 
 
705
    def getmarkers(self):
 
706
        if len(self._markers) == 0:
 
707
            return None
 
708
        return self._markers
 
709
 
 
710
    def tell(self):
 
711
        return self._nframeswritten
 
712
 
 
713
    def writeframesraw(self, data):
 
714
        self._ensure_header_written(len(data))
 
715
        nframes = len(data) / (self._sampwidth * self._nchannels)
 
716
        if self._convert:
 
717
            data = self._convert(data)
 
718
        self._file.write(data)
 
719
        self._nframeswritten = self._nframeswritten + nframes
 
720
        self._datawritten = self._datawritten + len(data)
 
721
 
 
722
    def writeframes(self, data):
 
723
        self.writeframesraw(data)
 
724
        if self._nframeswritten != self._nframes or \
 
725
              self._datalength != self._datawritten:
 
726
            self._patchheader()
 
727
 
 
728
    def close(self):
 
729
        self._ensure_header_written(0)
 
730
        if self._datawritten & 1:
 
731
            # quick pad to even size
 
732
            self._file.write(chr(0))
 
733
            self._datawritten = self._datawritten + 1
 
734
        self._writemarkers()
 
735
        if self._nframeswritten != self._nframes or \
 
736
              self._datalength != self._datawritten or \
 
737
              self._marklength:
 
738
            self._patchheader()
 
739
        if self._comp:
 
740
            self._comp.CloseCompressor()
 
741
            self._comp = None
 
742
        self._file.flush()
 
743
        self._file = None
 
744
 
 
745
    #
 
746
    # Internal methods.
 
747
    #
 
748
 
 
749
    def _comp_data(self, data):
 
750
        import cl
 
751
        dummy = self._comp.SetParam(cl.FRAME_BUFFER_SIZE, len(data))
 
752
        dummy = self._comp.SetParam(cl.COMPRESSED_BUFFER_SIZE, len(data))
 
753
        return self._comp.Compress(self._nframes, data)
 
754
 
 
755
    def _lin2ulaw(self, data):
 
756
        import audioop
 
757
        return audioop.lin2ulaw(data, 2)
 
758
 
 
759
    def _lin2adpcm(self, data):
 
760
        import audioop
 
761
        if not hasattr(self, '_adpcmstate'):
 
762
            self._adpcmstate = None
 
763
        data, self._adpcmstate = audioop.lin2adpcm(data, 2,
 
764
                               self._adpcmstate)
 
765
        return data
 
766
 
 
767
    def _ensure_header_written(self, datasize):
 
768
        if not self._nframeswritten:
 
769
            if self._comptype in ('ULAW', 'ALAW'):
 
770
                if not self._sampwidth:
 
771
                    self._sampwidth = 2
 
772
                if self._sampwidth != 2:
 
773
                    raise Error, 'sample width must be 2 when compressing with ULAW or ALAW'
 
774
            if self._comptype == 'G722':
 
775
                if not self._sampwidth:
 
776
                    self._sampwidth = 2
 
777
                if self._sampwidth != 2:
 
778
                    raise Error, 'sample width must be 2 when compressing with G7.22 (ADPCM)'
 
779
            if not self._nchannels:
 
780
                raise Error, '# channels not specified'
 
781
            if not self._sampwidth:
 
782
                raise Error, 'sample width not specified'
 
783
            if not self._framerate:
 
784
                raise Error, 'sampling rate not specified'
 
785
            self._write_header(datasize)
 
786
 
 
787
    def _init_compression(self):
 
788
        if self._comptype == 'G722':
 
789
            self._convert = self._lin2adpcm
 
790
            return
 
791
        try:
 
792
            import cl
 
793
        except ImportError:
 
794
            if self._comptype == 'ULAW':
 
795
                try:
 
796
                    import audioop
 
797
                    self._convert = self._lin2ulaw
 
798
                    return
 
799
                except ImportError:
 
800
                    pass
 
801
            raise Error, 'cannot write compressed AIFF-C files'
 
802
        if self._comptype == 'ULAW':
 
803
            scheme = cl.G711_ULAW
 
804
        elif self._comptype == 'ALAW':
 
805
            scheme = cl.G711_ALAW
 
806
        else:
 
807
            raise Error, 'unsupported compression type'
 
808
        self._comp = cl.OpenCompressor(scheme)
 
809
        params = [cl.ORIGINAL_FORMAT, 0,
 
810
              cl.BITS_PER_COMPONENT, self._sampwidth * 8,
 
811
              cl.FRAME_RATE, self._framerate,
 
812
              cl.FRAME_BUFFER_SIZE, 100,
 
813
              cl.COMPRESSED_BUFFER_SIZE, 100]
 
814
        if self._nchannels == 1:
 
815
            params[1] = cl.MONO
 
816
        elif self._nchannels == 2:
 
817
            params[1] = cl.STEREO_INTERLEAVED
 
818
        else:
 
819
            raise Error, 'cannot compress more than 2 channels'
 
820
        self._comp.SetParams(params)
 
821
        # the compressor produces a header which we ignore
 
822
        dummy = self._comp.Compress(0, '')
 
823
        self._convert = self._comp_data
 
824
 
 
825
    def _write_header(self, initlength):
 
826
        if self._aifc and self._comptype != 'NONE':
 
827
            self._init_compression()
 
828
        self._file.write('FORM')
 
829
        if not self._nframes:
 
830
            self._nframes = initlength / (self._nchannels * self._sampwidth)
 
831
        self._datalength = self._nframes * self._nchannels * self._sampwidth
 
832
        if self._datalength & 1:
 
833
            self._datalength = self._datalength + 1
 
834
        if self._aifc:
 
835
            if self._comptype in ('ULAW', 'ALAW'):
 
836
                self._datalength = self._datalength / 2
 
837
                if self._datalength & 1:
 
838
                    self._datalength = self._datalength + 1
 
839
            elif self._comptype == 'G722':
 
840
                self._datalength = (self._datalength + 3) / 4
 
841
                if self._datalength & 1:
 
842
                    self._datalength = self._datalength + 1
 
843
        self._form_length_pos = self._file.tell()
 
844
        commlength = self._write_form_length(self._datalength)
 
845
        if self._aifc:
 
846
            self._file.write('AIFC')
 
847
            self._file.write('FVER')
 
848
            _write_long(self._file, 4)
 
849
            _write_long(self._file, self._version)
 
850
        else:
 
851
            self._file.write('AIFF')
 
852
        self._file.write('COMM')
 
853
        _write_long(self._file, commlength)
 
854
        _write_short(self._file, self._nchannels)
 
855
        self._nframes_pos = self._file.tell()
 
856
        _write_long(self._file, self._nframes)
 
857
        _write_short(self._file, self._sampwidth * 8)
 
858
        _write_float(self._file, self._framerate)
 
859
        if self._aifc:
 
860
            self._file.write(self._comptype)
 
861
            _write_string(self._file, self._compname)
 
862
        self._file.write('SSND')
 
863
        self._ssnd_length_pos = self._file.tell()
 
864
        _write_long(self._file, self._datalength + 8)
 
865
        _write_long(self._file, 0)
 
866
        _write_long(self._file, 0)
 
867
 
 
868
    def _write_form_length(self, datalength):
 
869
        if self._aifc:
 
870
            commlength = 18 + 5 + len(self._compname)
 
871
            if commlength & 1:
 
872
                commlength = commlength + 1
 
873
            verslength = 12
 
874
        else:
 
875
            commlength = 18
 
876
            verslength = 0
 
877
        _write_long(self._file, 4 + verslength + self._marklength + \
 
878
                    8 + commlength + 16 + datalength)
 
879
        return commlength
 
880
 
 
881
    def _patchheader(self):
 
882
        curpos = self._file.tell()
 
883
        if self._datawritten & 1:
 
884
            datalength = self._datawritten + 1
 
885
            self._file.write(chr(0))
 
886
        else:
 
887
            datalength = self._datawritten
 
888
        if datalength == self._datalength and \
 
889
              self._nframes == self._nframeswritten and \
 
890
              self._marklength == 0:
 
891
            self._file.seek(curpos, 0)
 
892
            return
 
893
        self._file.seek(self._form_length_pos, 0)
 
894
        dummy = self._write_form_length(datalength)
 
895
        self._file.seek(self._nframes_pos, 0)
 
896
        _write_long(self._file, self._nframeswritten)
 
897
        self._file.seek(self._ssnd_length_pos, 0)
 
898
        _write_long(self._file, datalength + 8)
 
899
        self._file.seek(curpos, 0)
 
900
        self._nframes = self._nframeswritten
 
901
        self._datalength = datalength
 
902
 
 
903
    def _writemarkers(self):
 
904
        if len(self._markers) == 0:
 
905
            return
 
906
        self._file.write('MARK')
 
907
        length = 2
 
908
        for marker in self._markers:
 
909
            id, pos, name = marker
 
910
            length = length + len(name) + 1 + 6
 
911
            if len(name) & 1 == 0:
 
912
                length = length + 1
 
913
        _write_long(self._file, length)
 
914
        self._marklength = length + 8
 
915
        _write_short(self._file, len(self._markers))
 
916
        for marker in self._markers:
 
917
            id, pos, name = marker
 
918
            _write_short(self._file, id)
 
919
            _write_long(self._file, pos)
 
920
            _write_string(self._file, name)
 
921
 
 
922
def open(f, mode=None):
 
923
    if mode is None:
 
924
        if hasattr(f, 'mode'):
 
925
            mode = f.mode
 
926
        else:
 
927
            mode = 'rb'
 
928
    if mode in ('r', 'rb'):
 
929
        return Aifc_read(f)
 
930
    elif mode in ('w', 'wb'):
 
931
        return Aifc_write(f)
 
932
    else:
 
933
        raise Error, "mode must be 'r', 'rb', 'w', or 'wb'"
 
934
 
 
935
openfp = open # B/W compatibility
 
936
 
 
937
if __name__ == '__main__':
 
938
    import sys
 
939
    if not sys.argv[1:]:
 
940
        sys.argv.append('/usr/demos/data/audio/bach.aiff')
 
941
    fn = sys.argv[1]
 
942
    f = open(fn, 'r')
 
943
    print "Reading", fn
 
944
    print "nchannels =", f.getnchannels()
 
945
    print "nframes   =", f.getnframes()
 
946
    print "sampwidth =", f.getsampwidth()
 
947
    print "framerate =", f.getframerate()
 
948
    print "comptype  =", f.getcomptype()
 
949
    print "compname  =", f.getcompname()
 
950
    if sys.argv[2:]:
 
951
        gn = sys.argv[2]
 
952
        print "Writing", gn
 
953
        g = open(gn, 'w')
 
954
        g.setparams(f.getparams())
 
955
        while 1:
 
956
            data = f.readframes(1024)
 
957
            if not data:
 
958
                break
 
959
            g.writeframes(data)
 
960
        g.close()
 
961
        f.close()
 
962
        print "Done."