source: titan/mediathek/localhoster/lib/python2.7/aifc.py

Last change on this file was 40094, checked in by obi, 5 years ago

tithek add yoztube-dl support

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