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, you 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_ushort(file):
    166     try:
    167         return struct.unpack('>H', file.read(2))[0]
    168     except struct.error:
    169         raise EOFError
    170 
    171 def _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 
    183 def _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 
    200 def _write_short(f, x):
    201     f.write(struct.pack('>h', x))
    202 
    203 def _write_ushort(f, x):
    204     f.write(struct.pack('>H', x))
    205 
    206 def _write_long(f, x):
    207     f.write(struct.pack('>l', x))
    208 
    209 def _write_ulong(f, x):
    210     f.write(struct.pack('>L', x))
    211 
    212 def _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 
    220 def _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 
    253 from chunk import Chunk
    254 
    255 class 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 
    535 class 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 
    945 def 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 
    958 openfp = open # B/W compatibility
    959 
    960 if __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()
    990