Home | History | Annotate | Download | only in Lib
      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 def _read_long(file):
    148     try:
    149         return struct.unpack('>l', file.read(4))[0]
    150     except struct.error:
    151         raise EOFError
    152 
    153 def _read_ulong(file):
    154     try:
    155         return struct.unpack('>L', file.read(4))[0]
    156     except struct.error:
    157         raise EOFError
    158 
    159 def _read_short(file):
    160     try:
    161         return struct.unpack('>h', file.read(2))[0]
    162     except struct.error:
    163         raise EOFError
    164 
    165 def _read_string(file):
    166     length = ord(file.read(1))
    167     if length == 0:
    168         data = ''
    169     else:
    170         data = file.read(length)
    171     if length & 1 == 0:
    172         dummy = file.read(1)
    173     return data
    174 
    175 _HUGE_VAL = 1.79769313486231e+308 # See <limits.h>

    176 
    177 def _read_float(f): # 10 bytes

    178     expon = _read_short(f) # 2 bytes

    179     sign = 1
    180     if expon < 0:
    181         sign = -1
    182         expon = expon + 0x8000
    183     himant = _read_ulong(f) # 4 bytes

    184     lomant = _read_ulong(f) # 4 bytes

    185     if expon == himant == lomant == 0:
    186         f = 0.0
    187     elif expon == 0x7FFF:
    188         f = _HUGE_VAL
    189     else:
    190         expon = expon - 16383
    191         f = (himant * 0x100000000L + lomant) * pow(2.0, expon - 63)
    192     return sign * f
    193 
    194 def _write_short(f, x):
    195     f.write(struct.pack('>h', x))
    196 
    197 def _write_long(f, x):
    198     f.write(struct.pack('>L', x))
    199 
    200 def _write_string(f, s):
    201     if len(s) > 255:
    202         raise ValueError("string exceeds maximum pstring length")
    203     f.write(chr(len(s)))
    204     f.write(s)
    205     if len(s) & 1 == 0:
    206         f.write(chr(0))
    207 
    208 def _write_float(f, x):
    209     import math
    210     if x < 0:
    211         sign = 0x8000
    212         x = x * -1
    213     else:
    214         sign = 0
    215     if x == 0:
    216         expon = 0
    217         himant = 0
    218         lomant = 0
    219     else:
    220         fmant, expon = math.frexp(x)
    221         if expon > 16384 or fmant >= 1:     # Infinity or NaN

    222             expon = sign|0x7FFF
    223             himant = 0
    224             lomant = 0
    225         else:                   # Finite

    226             expon = expon + 16382
    227             if expon < 0:           # denormalized

    228                 fmant = math.ldexp(fmant, expon)
    229                 expon = 0
    230             expon = expon | sign
    231             fmant = math.ldexp(fmant, 32)
    232             fsmant = math.floor(fmant)
    233             himant = long(fsmant)
    234             fmant = math.ldexp(fmant - fsmant, 32)
    235             fsmant = math.floor(fmant)
    236             lomant = long(fsmant)
    237     _write_short(f, expon)
    238     _write_long(f, himant)
    239     _write_long(f, lomant)
    240 
    241 from chunk import Chunk
    242 
    243 class Aifc_read:
    244     # Variables used in this class:

    245     #

    246     # These variables are available to the user though appropriate

    247     # methods of this class:

    248     # _file -- the open file with methods read(), close(), and seek()

    249     #       set through the __init__() method

    250     # _nchannels -- the number of audio channels

    251     #       available through the getnchannels() method

    252     # _nframes -- the number of audio frames

    253     #       available through the getnframes() method

    254     # _sampwidth -- the number of bytes per audio sample

    255     #       available through the getsampwidth() method

    256     # _framerate -- the sampling frequency

    257     #       available through the getframerate() method

    258     # _comptype -- the AIFF-C compression type ('NONE' if AIFF)

    259     #       available through the getcomptype() method

    260     # _compname -- the human-readable AIFF-C compression type

    261     #       available through the getcomptype() method

    262     # _markers -- the marks in the audio file

    263     #       available through the getmarkers() and getmark()

    264     #       methods

    265     # _soundpos -- the position in the audio stream

    266     #       available through the tell() method, set through the

    267     #       setpos() method

    268     #

    269     # These variables are used internally only:

    270     # _version -- the AIFF-C version number

    271     # _decomp -- the decompressor from builtin module cl

    272     # _comm_chunk_read -- 1 iff the COMM chunk has been read

    273     # _aifc -- 1 iff reading an AIFF-C file

    274     # _ssnd_seek_needed -- 1 iff positioned correctly in audio

    275     #       file for readframes()

    276     # _ssnd_chunk -- instantiation of a chunk class for the SSND chunk

    277     # _framesize -- size of one frame in the file

    278 
    279     def initfp(self, file):
    280         self._version = 0
    281         self._decomp = None
    282         self._convert = None
    283         self._markers = []
    284         self._soundpos = 0
    285         self._file = file
    286         chunk = Chunk(file)
    287         if chunk.getname() != 'FORM':
    288             raise Error, 'file does not start with FORM id'
    289         formdata = chunk.read(4)
    290         if formdata == 'AIFF':
    291             self._aifc = 0
    292         elif formdata == 'AIFC':
    293             self._aifc = 1
    294         else:
    295             raise Error, 'not an AIFF or AIFF-C file'
    296         self._comm_chunk_read = 0
    297         while 1:
    298             self._ssnd_seek_needed = 1
    299             try:
    300                 chunk = Chunk(self._file)
    301             except EOFError:
    302                 break
    303             chunkname = chunk.getname()
    304             if chunkname == 'COMM':
    305                 self._read_comm_chunk(chunk)
    306                 self._comm_chunk_read = 1
    307             elif chunkname == 'SSND':
    308                 self._ssnd_chunk = chunk
    309                 dummy = chunk.read(8)
    310                 self._ssnd_seek_needed = 0
    311             elif chunkname == 'FVER':
    312                 self._version = _read_ulong(chunk)
    313             elif chunkname == 'MARK':
    314                 self._readmark(chunk)
    315             chunk.skip()
    316         if not self._comm_chunk_read or not self._ssnd_chunk:
    317             raise Error, 'COMM chunk and/or SSND chunk missing'
    318         if self._aifc and self._decomp:
    319             import cl
    320             params = [cl.ORIGINAL_FORMAT, 0,
    321                   cl.BITS_PER_COMPONENT, self._sampwidth * 8,
    322                   cl.FRAME_RATE, self._framerate]
    323             if self._nchannels == 1:
    324                 params[1] = cl.MONO
    325             elif self._nchannels == 2:
    326                 params[1] = cl.STEREO_INTERLEAVED
    327             else:
    328                 raise Error, 'cannot compress more than 2 channels'
    329             self._decomp.SetParams(params)
    330 
    331     def __init__(self, f):
    332         if type(f) == type(''):
    333             f = __builtin__.open(f, 'rb')
    334         # else, assume it is an open file object already

    335         self.initfp(f)
    336 
    337     #

    338     # User visible methods.

    339     #

    340     def getfp(self):
    341         return self._file
    342 
    343     def rewind(self):
    344         self._ssnd_seek_needed = 1
    345         self._soundpos = 0
    346 
    347     def close(self):
    348         if self._decomp:
    349             self._decomp.CloseDecompressor()
    350             self._decomp = None
    351         self._file.close()
    352 
    353     def tell(self):
    354         return self._soundpos
    355 
    356     def getnchannels(self):
    357         return self._nchannels
    358 
    359     def getnframes(self):
    360         return self._nframes
    361 
    362     def getsampwidth(self):
    363         return self._sampwidth
    364 
    365     def getframerate(self):
    366         return self._framerate
    367 
    368     def getcomptype(self):
    369         return self._comptype
    370 
    371     def getcompname(self):
    372         return self._compname
    373 
    374 ##  def getversion(self):

    375 ##      return self._version

    376 
    377     def getparams(self):
    378         return self.getnchannels(), self.getsampwidth(), \
    379               self.getframerate(), self.getnframes(), \
    380               self.getcomptype(), self.getcompname()
    381 
    382     def getmarkers(self):
    383         if len(self._markers) == 0:
    384             return None
    385         return self._markers
    386 
    387     def getmark(self, id):
    388         for marker in self._markers:
    389             if id == marker[0]:
    390                 return marker
    391         raise Error, 'marker %r does not exist' % (id,)
    392 
    393     def setpos(self, pos):
    394         if pos < 0 or pos > self._nframes:
    395             raise Error, 'position not in range'
    396         self._soundpos = pos
    397         self._ssnd_seek_needed = 1
    398 
    399     def readframes(self, nframes):
    400         if self._ssnd_seek_needed:
    401             self._ssnd_chunk.seek(0)
    402             dummy = self._ssnd_chunk.read(8)
    403             pos = self._soundpos * self._framesize
    404             if pos:
    405                 self._ssnd_chunk.seek(pos + 8)
    406             self._ssnd_seek_needed = 0
    407         if nframes == 0:
    408             return ''
    409         data = self._ssnd_chunk.read(nframes * self._framesize)
    410         if self._convert and data:
    411             data = self._convert(data)
    412         self._soundpos = self._soundpos + len(data) // (self._nchannels * self._sampwidth)
    413         return data
    414 
    415     #

    416     # Internal methods.

    417     #

    418 
    419     def _decomp_data(self, data):
    420         import cl
    421         dummy = self._decomp.SetParam(cl.FRAME_BUFFER_SIZE,
    422                           len(data) * 2)
    423         return self._decomp.Decompress(len(data) // self._nchannels,
    424                            data)
    425 
    426     def _ulaw2lin(self, data):
    427         import audioop
    428         return audioop.ulaw2lin(data, 2)
    429 
    430     def _adpcm2lin(self, data):
    431         import audioop
    432         if not hasattr(self, '_adpcmstate'):
    433             # first time

    434             self._adpcmstate = None
    435         data, self._adpcmstate = audioop.adpcm2lin(data, 2,
    436                                self._adpcmstate)
    437         return data
    438 
    439     def _read_comm_chunk(self, chunk):
    440         self._nchannels = _read_short(chunk)
    441         self._nframes = _read_long(chunk)
    442         self._sampwidth = (_read_short(chunk) + 7) // 8
    443         self._framerate = int(_read_float(chunk))
    444         self._framesize = self._nchannels * self._sampwidth
    445         if self._aifc:
    446             #DEBUG: SGI's soundeditor produces a bad size :-(

    447             kludge = 0
    448             if chunk.chunksize == 18:
    449                 kludge = 1
    450                 print 'Warning: bad COMM chunk size'
    451                 chunk.chunksize = 23
    452             #DEBUG end

    453             self._comptype = chunk.read(4)
    454             #DEBUG start

    455             if kludge:
    456                 length = ord(chunk.file.read(1))
    457                 if length & 1 == 0:
    458                     length = length + 1
    459                 chunk.chunksize = chunk.chunksize + length
    460                 chunk.file.seek(-1, 1)
    461             #DEBUG end

    462             self._compname = _read_string(chunk)
    463             if self._comptype != 'NONE':
    464                 if self._comptype == 'G722':
    465                     try:
    466                         import audioop
    467                     except ImportError:
    468                         pass
    469                     else:
    470                         self._convert = self._adpcm2lin
    471                         self._framesize = self._framesize // 4
    472                         return
    473                 # for ULAW and ALAW try Compression Library

    474                 try:
    475                     import cl
    476                 except ImportError:
    477                     if self._comptype == 'ULAW':
    478                         try:
    479                             import audioop
    480                             self._convert = self._ulaw2lin
    481                             self._framesize = self._framesize // 2
    482                             return
    483                         except ImportError:
    484                             pass
    485                     raise Error, 'cannot read compressed AIFF-C files'
    486                 if self._comptype == 'ULAW':
    487                     scheme = cl.G711_ULAW
    488                     self._framesize = self._framesize // 2
    489                 elif self._comptype == 'ALAW':
    490                     scheme = cl.G711_ALAW
    491                     self._framesize = self._framesize // 2
    492                 else:
    493                     raise Error, 'unsupported compression type'
    494                 self._decomp = cl.OpenDecompressor(scheme)
    495                 self._convert = self._decomp_data
    496         else:
    497             self._comptype = 'NONE'
    498             self._compname = 'not compressed'
    499 
    500     def _readmark(self, chunk):
    501         nmarkers = _read_short(chunk)
    502         # Some files appear to contain invalid counts.

    503         # Cope with this by testing for EOF.

    504         try:
    505             for i in range(nmarkers):
    506                 id = _read_short(chunk)
    507                 pos = _read_long(chunk)
    508                 name = _read_string(chunk)
    509                 if pos or name:
    510                     # some files appear to have

    511                     # dummy markers consisting of

    512                     # a position 0 and name ''

    513                     self._markers.append((id, pos, name))
    514         except EOFError:
    515             print 'Warning: MARK chunk contains only',
    516             print len(self._markers),
    517             if len(self._markers) == 1: print 'marker',
    518             else: print 'markers',
    519             print 'instead of', nmarkers
    520 
    521 class Aifc_write:
    522     # Variables used in this class:

    523     #

    524     # These variables are user settable through appropriate methods

    525     # of this class:

    526     # _file -- the open file with methods write(), close(), tell(), seek()

    527     #       set through the __init__() method

    528     # _comptype -- the AIFF-C compression type ('NONE' in AIFF)

    529     #       set through the setcomptype() or setparams() method

    530     # _compname -- the human-readable AIFF-C compression type

    531     #       set through the setcomptype() or setparams() method

    532     # _nchannels -- the number of audio channels

    533     #       set through the setnchannels() or setparams() method

    534     # _sampwidth -- the number of bytes per audio sample

    535     #       set through the setsampwidth() or setparams() method

    536     # _framerate -- the sampling frequency

    537     #       set through the setframerate() or setparams() method

    538     # _nframes -- the number of audio frames written to the header

    539     #       set through the setnframes() or setparams() method

    540     # _aifc -- whether we're writing an AIFF-C file or an AIFF file

    541     #       set through the aifc() method, reset through the

    542     #       aiff() method

    543     #

    544     # These variables are used internally only:

    545     # _version -- the AIFF-C version number

    546     # _comp -- the compressor from builtin module cl

    547     # _nframeswritten -- the number of audio frames actually written

    548     # _datalength -- the size of the audio samples written to the header

    549     # _datawritten -- the size of the audio samples actually written

    550 
    551     def __init__(self, f):
    552         if type(f) == type(''):
    553             filename = f
    554             f = __builtin__.open(f, 'wb')
    555         else:
    556             # else, assume it is an open file object already

    557             filename = '???'
    558         self.initfp(f)
    559         if filename[-5:] == '.aiff':
    560             self._aifc = 0
    561         else:
    562             self._aifc = 1
    563 
    564     def initfp(self, file):
    565         self._file = file
    566         self._version = _AIFC_version
    567         self._comptype = 'NONE'
    568         self._compname = 'not compressed'
    569         self._comp = None
    570         self._convert = None
    571         self._nchannels = 0
    572         self._sampwidth = 0
    573         self._framerate = 0
    574         self._nframes = 0
    575         self._nframeswritten = 0
    576         self._datawritten = 0
    577         self._datalength = 0
    578         self._markers = []
    579         self._marklength = 0
    580         self._aifc = 1      # AIFF-C is default

    581 
    582     def __del__(self):
    583         if self._file:
    584             self.close()
    585 
    586     #

    587     # User visible methods.

    588     #

    589     def aiff(self):
    590         if self._nframeswritten:
    591             raise Error, 'cannot change parameters after starting to write'
    592         self._aifc = 0
    593 
    594     def aifc(self):
    595         if self._nframeswritten:
    596             raise Error, 'cannot change parameters after starting to write'
    597         self._aifc = 1
    598 
    599     def setnchannels(self, nchannels):
    600         if self._nframeswritten:
    601             raise Error, 'cannot change parameters after starting to write'
    602         if nchannels < 1:
    603             raise Error, 'bad # of channels'
    604         self._nchannels = nchannels
    605 
    606     def getnchannels(self):
    607         if not self._nchannels:
    608             raise Error, 'number of channels not set'
    609         return self._nchannels
    610 
    611     def setsampwidth(self, sampwidth):
    612         if self._nframeswritten:
    613             raise Error, 'cannot change parameters after starting to write'
    614         if sampwidth < 1 or sampwidth > 4:
    615             raise Error, 'bad sample width'
    616         self._sampwidth = sampwidth
    617 
    618     def getsampwidth(self):
    619         if not self._sampwidth:
    620             raise Error, 'sample width not set'
    621         return self._sampwidth
    622 
    623     def setframerate(self, framerate):
    624         if self._nframeswritten:
    625             raise Error, 'cannot change parameters after starting to write'
    626         if framerate <= 0:
    627             raise Error, 'bad frame rate'
    628         self._framerate = framerate
    629 
    630     def getframerate(self):
    631         if not self._framerate:
    632             raise Error, 'frame rate not set'
    633         return self._framerate
    634 
    635     def setnframes(self, nframes):
    636         if self._nframeswritten:
    637             raise Error, 'cannot change parameters after starting to write'
    638         self._nframes = nframes
    639 
    640     def getnframes(self):
    641         return self._nframeswritten
    642 
    643     def setcomptype(self, comptype, compname):
    644         if self._nframeswritten:
    645             raise Error, 'cannot change parameters after starting to write'
    646         if comptype not in ('NONE', 'ULAW', 'ALAW', 'G722'):
    647             raise Error, 'unsupported compression type'
    648         self._comptype = comptype
    649         self._compname = compname
    650 
    651     def getcomptype(self):
    652         return self._comptype
    653 
    654     def getcompname(self):
    655         return self._compname
    656 
    657 ##  def setversion(self, version):

    658 ##      if self._nframeswritten:

    659 ##          raise Error, 'cannot change parameters after starting to write'

    660 ##      self._version = version

    661 
    662     def setparams(self, info):
    663         nchannels, sampwidth, framerate, nframes, comptype, compname = info
    664         if self._nframeswritten:
    665             raise Error, 'cannot change parameters after starting to write'
    666         if comptype not in ('NONE', 'ULAW', 'ALAW', 'G722'):
    667             raise Error, 'unsupported compression type'
    668         self.setnchannels(nchannels)
    669         self.setsampwidth(sampwidth)
    670         self.setframerate(framerate)
    671         self.setnframes(nframes)
    672         self.setcomptype(comptype, compname)
    673 
    674     def getparams(self):
    675         if not self._nchannels or not self._sampwidth or not self._framerate:
    676             raise Error, 'not all parameters set'
    677         return self._nchannels, self._sampwidth, self._framerate, \
    678               self._nframes, self._comptype, self._compname
    679 
    680     def setmark(self, id, pos, name):
    681         if id <= 0:
    682             raise Error, 'marker ID must be > 0'
    683         if pos < 0:
    684             raise Error, 'marker position must be >= 0'
    685         if type(name) != type(''):
    686             raise Error, 'marker name must be a string'
    687         for i in range(len(self._markers)):
    688             if id == self._markers[i][0]:
    689                 self._markers[i] = id, pos, name
    690                 return
    691         self._markers.append((id, pos, name))
    692 
    693     def getmark(self, id):
    694         for marker in self._markers:
    695             if id == marker[0]:
    696                 return marker
    697         raise Error, 'marker %r does not exist' % (id,)
    698 
    699     def getmarkers(self):
    700         if len(self._markers) == 0:
    701             return None
    702         return self._markers
    703 
    704     def tell(self):
    705         return self._nframeswritten
    706 
    707     def writeframesraw(self, data):
    708         self._ensure_header_written(len(data))
    709         nframes = len(data) // (self._sampwidth * self._nchannels)
    710         if self._convert:
    711             data = self._convert(data)
    712         self._file.write(data)
    713         self._nframeswritten = self._nframeswritten + nframes
    714         self._datawritten = self._datawritten + len(data)
    715 
    716     def writeframes(self, data):
    717         self.writeframesraw(data)
    718         if self._nframeswritten != self._nframes or \
    719               self._datalength != self._datawritten:
    720             self._patchheader()
    721 
    722     def close(self):
    723         self._ensure_header_written(0)
    724         if self._datawritten & 1:
    725             # quick pad to even size

    726             self._file.write(chr(0))
    727             self._datawritten = self._datawritten + 1
    728         self._writemarkers()
    729         if self._nframeswritten != self._nframes or \
    730               self._datalength != self._datawritten or \
    731               self._marklength:
    732             self._patchheader()
    733         if self._comp:
    734             self._comp.CloseCompressor()
    735             self._comp = None
    736         # Prevent ref cycles

    737         self._convert = None
    738         self._file.close()
    739 
    740     #

    741     # Internal methods.

    742     #

    743 
    744     def _comp_data(self, data):
    745         import cl
    746         dummy = self._comp.SetParam(cl.FRAME_BUFFER_SIZE, len(data))
    747         dummy = self._comp.SetParam(cl.COMPRESSED_BUFFER_SIZE, len(data))
    748         return self._comp.Compress(self._nframes, data)
    749 
    750     def _lin2ulaw(self, data):
    751         import audioop
    752         return audioop.lin2ulaw(data, 2)
    753 
    754     def _lin2adpcm(self, data):
    755         import audioop
    756         if not hasattr(self, '_adpcmstate'):
    757             self._adpcmstate = None
    758         data, self._adpcmstate = audioop.lin2adpcm(data, 2,
    759                                self._adpcmstate)
    760         return data
    761 
    762     def _ensure_header_written(self, datasize):
    763         if not self._nframeswritten:
    764             if self._comptype in ('ULAW', 'ALAW'):
    765                 if not self._sampwidth:
    766                     self._sampwidth = 2
    767                 if self._sampwidth != 2:
    768                     raise Error, 'sample width must be 2 when compressing with ULAW or ALAW'
    769             if self._comptype == 'G722':
    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 G7.22 (ADPCM)'
    774             if not self._nchannels:
    775                 raise Error, '# channels not specified'
    776             if not self._sampwidth:
    777                 raise Error, 'sample width not specified'
    778             if not self._framerate:
    779                 raise Error, 'sampling rate not specified'
    780             self._write_header(datasize)
    781 
    782     def _init_compression(self):
    783         if self._comptype == 'G722':
    784             self._convert = self._lin2adpcm
    785             return
    786         try:
    787             import cl
    788         except ImportError:
    789             if self._comptype == 'ULAW':
    790                 try:
    791                     import audioop
    792                     self._convert = self._lin2ulaw
    793                     return
    794                 except ImportError:
    795                     pass
    796             raise Error, 'cannot write compressed AIFF-C files'
    797         if self._comptype == 'ULAW':
    798             scheme = cl.G711_ULAW
    799         elif self._comptype == 'ALAW':
    800             scheme = cl.G711_ALAW
    801         else:
    802             raise Error, 'unsupported compression type'
    803         self._comp = cl.OpenCompressor(scheme)
    804         params = [cl.ORIGINAL_FORMAT, 0,
    805               cl.BITS_PER_COMPONENT, self._sampwidth * 8,
    806               cl.FRAME_RATE, self._framerate,
    807               cl.FRAME_BUFFER_SIZE, 100,
    808               cl.COMPRESSED_BUFFER_SIZE, 100]
    809         if self._nchannels == 1:
    810             params[1] = cl.MONO
    811         elif self._nchannels == 2:
    812             params[1] = cl.STEREO_INTERLEAVED
    813         else:
    814             raise Error, 'cannot compress more than 2 channels'
    815         self._comp.SetParams(params)
    816         # the compressor produces a header which we ignore

    817         dummy = self._comp.Compress(0, '')
    818         self._convert = self._comp_data
    819 
    820     def _write_header(self, initlength):
    821         if self._aifc and self._comptype != 'NONE':
    822             self._init_compression()
    823         self._file.write('FORM')
    824         if not self._nframes:
    825             self._nframes = initlength // (self._nchannels * self._sampwidth)
    826         self._datalength = self._nframes * self._nchannels * self._sampwidth
    827         if self._datalength & 1:
    828             self._datalength = self._datalength + 1
    829         if self._aifc:
    830             if self._comptype in ('ULAW', 'ALAW'):
    831                 self._datalength = self._datalength // 2
    832                 if self._datalength & 1:
    833                     self._datalength = self._datalength + 1
    834             elif self._comptype == 'G722':
    835                 self._datalength = (self._datalength + 3) // 4
    836                 if self._datalength & 1:
    837                     self._datalength = self._datalength + 1
    838         self._form_length_pos = self._file.tell()
    839         commlength = self._write_form_length(self._datalength)
    840         if self._aifc:
    841             self._file.write('AIFC')
    842             self._file.write('FVER')
    843             _write_long(self._file, 4)
    844             _write_long(self._file, self._version)
    845         else:
    846             self._file.write('AIFF')
    847         self._file.write('COMM')
    848         _write_long(self._file, commlength)
    849         _write_short(self._file, self._nchannels)
    850         self._nframes_pos = self._file.tell()
    851         _write_long(self._file, self._nframes)
    852         _write_short(self._file, self._sampwidth * 8)
    853         _write_float(self._file, self._framerate)
    854         if self._aifc:
    855             self._file.write(self._comptype)
    856             _write_string(self._file, self._compname)
    857         self._file.write('SSND')
    858         self._ssnd_length_pos = self._file.tell()
    859         _write_long(self._file, self._datalength + 8)
    860         _write_long(self._file, 0)
    861         _write_long(self._file, 0)
    862 
    863     def _write_form_length(self, datalength):
    864         if self._aifc:
    865             commlength = 18 + 5 + len(self._compname)
    866             if commlength & 1:
    867                 commlength = commlength + 1
    868             verslength = 12
    869         else:
    870             commlength = 18
    871             verslength = 0
    872         _write_long(self._file, 4 + verslength + self._marklength + \
    873                     8 + commlength + 16 + datalength)
    874         return commlength
    875 
    876     def _patchheader(self):
    877         curpos = self._file.tell()
    878         if self._datawritten & 1:
    879             datalength = self._datawritten + 1
    880             self._file.write(chr(0))
    881         else:
    882             datalength = self._datawritten
    883         if datalength == self._datalength and \
    884               self._nframes == self._nframeswritten and \
    885               self._marklength == 0:
    886             self._file.seek(curpos, 0)
    887             return
    888         self._file.seek(self._form_length_pos, 0)
    889         dummy = self._write_form_length(datalength)
    890         self._file.seek(self._nframes_pos, 0)
    891         _write_long(self._file, self._nframeswritten)
    892         self._file.seek(self._ssnd_length_pos, 0)
    893         _write_long(self._file, datalength + 8)
    894         self._file.seek(curpos, 0)
    895         self._nframes = self._nframeswritten
    896         self._datalength = datalength
    897 
    898     def _writemarkers(self):
    899         if len(self._markers) == 0:
    900             return
    901         self._file.write('MARK')
    902         length = 2
    903         for marker in self._markers:
    904             id, pos, name = marker
    905             length = length + len(name) + 1 + 6
    906             if len(name) & 1 == 0:
    907                 length = length + 1
    908         _write_long(self._file, length)
    909         self._marklength = length + 8
    910         _write_short(self._file, len(self._markers))
    911         for marker in self._markers:
    912             id, pos, name = marker
    913             _write_short(self._file, id)
    914             _write_long(self._file, pos)
    915             _write_string(self._file, name)
    916 
    917 def open(f, mode=None):
    918     if mode is None:
    919         if hasattr(f, 'mode'):
    920             mode = f.mode
    921         else:
    922             mode = 'rb'
    923     if mode in ('r', 'rb'):
    924         return Aifc_read(f)
    925     elif mode in ('w', 'wb'):
    926         return Aifc_write(f)
    927     else:
    928         raise Error, "mode must be 'r', 'rb', 'w', or 'wb'"
    929 
    930 openfp = open # B/W compatibility

    931 
    932 if __name__ == '__main__':
    933     import sys
    934     if not sys.argv[1:]:
    935         sys.argv.append('/usr/demos/data/audio/bach.aiff')
    936     fn = sys.argv[1]
    937     f = open(fn, 'r')
    938     print "Reading", fn
    939     print "nchannels =", f.getnchannels()
    940     print "nframes   =", f.getnframes()
    941     print "sampwidth =", f.getsampwidth()
    942     print "framerate =", f.getframerate()
    943     print "comptype  =", f.getcomptype()
    944     print "compname  =", f.getcompname()
    945     if sys.argv[2:]:
    946         gn = sys.argv[2]
    947         print "Writing", gn
    948         g = open(gn, 'w')
    949         g.setparams(f.getparams())
    950         while 1:
    951             data = f.readframes(1024)
    952             if not data:
    953                 break
    954             g.writeframes(data)
    955         g.close()
    956         f.close()
    957         print "Done."
    958