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     _file = None  # Set here since __del__ checks it
    292 
    293     def initfp(self, file):
    294         self._version = 0
    295         self._decomp = None
    296         self._convert = None
    297         self._markers = []
    298         self._soundpos = 0
    299         self._file = file
    300         chunk = Chunk(file)
    301         if chunk.getname() != 'FORM':
    302             raise Error, 'file does not start with FORM id'
    303         formdata = chunk.read(4)
    304         if formdata == 'AIFF':
    305             self._aifc = 0
    306         elif formdata == 'AIFC':
    307             self._aifc = 1
    308         else:
    309             raise Error, 'not an AIFF or AIFF-C file'
    310         self._comm_chunk_read = 0
    311         self._ssnd_chunk = None
    312         while 1:
    313             self._ssnd_seek_needed = 1
    314             try:
    315                 chunk = Chunk(self._file)
    316             except EOFError:
    317                 break
    318             chunkname = chunk.getname()
    319             if chunkname == 'COMM':
    320                 self._read_comm_chunk(chunk)
    321                 self._comm_chunk_read = 1
    322             elif chunkname == 'SSND':
    323                 self._ssnd_chunk = chunk
    324                 dummy = chunk.read(8)
    325                 self._ssnd_seek_needed = 0
    326             elif chunkname == 'FVER':
    327                 self._version = _read_ulong(chunk)
    328             elif chunkname == 'MARK':
    329                 self._readmark(chunk)
    330             chunk.skip()
    331         if not self._comm_chunk_read or not self._ssnd_chunk:
    332             raise Error, 'COMM chunk and/or SSND chunk missing'
    333         if self._aifc and self._decomp:
    334             import cl
    335             params = [cl.ORIGINAL_FORMAT, 0,
    336                   cl.BITS_PER_COMPONENT, self._sampwidth * 8,
    337                   cl.FRAME_RATE, self._framerate]
    338             if self._nchannels == 1:
    339                 params[1] = cl.MONO
    340             elif self._nchannels == 2:
    341                 params[1] = cl.STEREO_INTERLEAVED
    342             else:
    343                 raise Error, 'cannot compress more than 2 channels'
    344             self._decomp.SetParams(params)
    345 
    346     def __init__(self, f):
    347         if isinstance(f, basestring):
    348             f = __builtin__.open(f, 'rb')
    349             try:
    350                 self.initfp(f)
    351             except:
    352                 f.close()
    353                 raise
    354         else:
    355             # assume it is an open file object already
    356             self.initfp(f)
    357 
    358     #
    359     # User visible methods.
    360     #
    361     def getfp(self):
    362         return self._file
    363 
    364     def rewind(self):
    365         self._ssnd_seek_needed = 1
    366         self._soundpos = 0
    367 
    368     def close(self):
    369         decomp = self._decomp
    370         try:
    371             if decomp:
    372                 self._decomp = None
    373                 decomp.CloseDecompressor()
    374         finally:
    375             self._file.close()
    376 
    377     def tell(self):
    378         return self._soundpos
    379 
    380     def getnchannels(self):
    381         return self._nchannels
    382 
    383     def getnframes(self):
    384         return self._nframes
    385 
    386     def getsampwidth(self):
    387         return self._sampwidth
    388 
    389     def getframerate(self):
    390         return self._framerate
    391 
    392     def getcomptype(self):
    393         return self._comptype
    394 
    395     def getcompname(self):
    396         return self._compname
    397 
    398 ##  def getversion(self):
    399 ##      return self._version
    400 
    401     def getparams(self):
    402         return self.getnchannels(), self.getsampwidth(), \
    403               self.getframerate(), self.getnframes(), \
    404               self.getcomptype(), self.getcompname()
    405 
    406     def getmarkers(self):
    407         if len(self._markers) == 0:
    408             return None
    409         return self._markers
    410 
    411     def getmark(self, id):
    412         for marker in self._markers:
    413             if id == marker[0]:
    414                 return marker
    415         raise Error, 'marker %r does not exist' % (id,)
    416 
    417     def setpos(self, pos):
    418         if pos < 0 or pos > self._nframes:
    419             raise Error, 'position not in range'
    420         self._soundpos = pos
    421         self._ssnd_seek_needed = 1
    422 
    423     def readframes(self, nframes):
    424         if self._ssnd_seek_needed:
    425             self._ssnd_chunk.seek(0)
    426             dummy = self._ssnd_chunk.read(8)
    427             pos = self._soundpos * self._framesize
    428             if pos:
    429                 self._ssnd_chunk.seek(pos + 8)
    430             self._ssnd_seek_needed = 0
    431         if nframes == 0:
    432             return ''
    433         data = self._ssnd_chunk.read(nframes * self._framesize)
    434         if self._convert and data:
    435             data = self._convert(data)
    436         self._soundpos = self._soundpos + len(data) // (self._nchannels * self._sampwidth)
    437         return data
    438 
    439     #
    440     # Internal methods.
    441     #
    442 
    443     def _decomp_data(self, data):
    444         import cl
    445         dummy = self._decomp.SetParam(cl.FRAME_BUFFER_SIZE,
    446                           len(data) * 2)
    447         return self._decomp.Decompress(len(data) // self._nchannels,
    448                            data)
    449 
    450     def _ulaw2lin(self, data):
    451         import audioop
    452         return audioop.ulaw2lin(data, 2)
    453 
    454     def _adpcm2lin(self, data):
    455         import audioop
    456         if not hasattr(self, '_adpcmstate'):
    457             # first time
    458             self._adpcmstate = None
    459         data, self._adpcmstate = audioop.adpcm2lin(data, 2,
    460                                self._adpcmstate)
    461         return data
    462 
    463     def _read_comm_chunk(self, chunk):
    464         self._nchannels = _read_short(chunk)
    465         self._nframes = _read_long(chunk)
    466         self._sampwidth = (_read_short(chunk) + 7) // 8
    467         self._framerate = int(_read_float(chunk))
    468         self._framesize = self._nchannels * self._sampwidth
    469         if self._aifc:
    470             #DEBUG: SGI's soundeditor produces a bad size :-(
    471             kludge = 0
    472             if chunk.chunksize == 18:
    473                 kludge = 1
    474                 print 'Warning: bad COMM chunk size'
    475                 chunk.chunksize = 23
    476             #DEBUG end
    477             self._comptype = chunk.read(4)
    478             #DEBUG start
    479             if kludge:
    480                 length = ord(chunk.file.read(1))
    481                 if length & 1 == 0:
    482                     length = length + 1
    483                 chunk.chunksize = chunk.chunksize + length
    484                 chunk.file.seek(-1, 1)
    485             #DEBUG end
    486             self._compname = _read_string(chunk)
    487             if self._comptype != 'NONE':
    488                 if self._comptype == 'G722':
    489                     try:
    490                         import audioop
    491                     except ImportError:
    492                         pass
    493                     else:
    494                         self._convert = self._adpcm2lin
    495                         self._sampwidth = 2
    496                         return
    497                 # for ULAW and ALAW try Compression Library
    498                 try:
    499                     import cl
    500                 except ImportError:
    501                     if self._comptype in ('ULAW', 'ulaw'):
    502                         try:
    503                             import audioop
    504                             self._convert = self._ulaw2lin
    505                             self._sampwidth = 2
    506                             return
    507                         except ImportError:
    508                             pass
    509                     raise Error, 'cannot read compressed AIFF-C files'
    510                 if self._comptype in ('ULAW', 'ulaw'):
    511                     scheme = cl.G711_ULAW
    512                 elif self._comptype in ('ALAW', 'alaw'):
    513                     scheme = cl.G711_ALAW
    514                 else:
    515                     raise Error, 'unsupported compression type'
    516                 self._decomp = cl.OpenDecompressor(scheme)
    517                 self._convert = self._decomp_data
    518                 self._sampwidth = 2
    519         else:
    520             self._comptype = 'NONE'
    521             self._compname = 'not compressed'
    522 
    523     def _readmark(self, chunk):
    524         nmarkers = _read_short(chunk)
    525         # Some files appear to contain invalid counts.
    526         # Cope with this by testing for EOF.
    527         try:
    528             for i in range(nmarkers):
    529                 id = _read_short(chunk)
    530                 pos = _read_long(chunk)
    531                 name = _read_string(chunk)
    532                 if pos or name:
    533                     # some files appear to have
    534                     # dummy markers consisting of
    535                     # a position 0 and name ''
    536                     self._markers.append((id, pos, name))
    537         except EOFError:
    538             print 'Warning: MARK chunk contains only',
    539             print len(self._markers),
    540             if len(self._markers) == 1: print 'marker',
    541             else: print 'markers',
    542             print 'instead of', nmarkers
    543 
    544 class Aifc_write:
    545     # Variables used in this class:
    546     #
    547     # These variables are user settable through appropriate methods
    548     # of this class:
    549     # _file -- the open file with methods write(), close(), tell(), seek()
    550     #       set through the __init__() method
    551     # _comptype -- the AIFF-C compression type ('NONE' in AIFF)
    552     #       set through the setcomptype() or setparams() method
    553     # _compname -- the human-readable AIFF-C compression type
    554     #       set through the setcomptype() or setparams() method
    555     # _nchannels -- the number of audio channels
    556     #       set through the setnchannels() or setparams() method
    557     # _sampwidth -- the number of bytes per audio sample
    558     #       set through the setsampwidth() or setparams() method
    559     # _framerate -- the sampling frequency
    560     #       set through the setframerate() or setparams() method
    561     # _nframes -- the number of audio frames written to the header
    562     #       set through the setnframes() or setparams() method
    563     # _aifc -- whether we're writing an AIFF-C file or an AIFF file
    564     #       set through the aifc() method, reset through the
    565     #       aiff() method
    566     #
    567     # These variables are used internally only:
    568     # _version -- the AIFF-C version number
    569     # _comp -- the compressor from builtin module cl
    570     # _nframeswritten -- the number of audio frames actually written
    571     # _datalength -- the size of the audio samples written to the header
    572     # _datawritten -- the size of the audio samples actually written
    573 
    574     _file = None  # Set here since __del__ checks it
    575 
    576     def __init__(self, f):
    577         if isinstance(f, basestring):
    578             filename = f
    579             f = __builtin__.open(f, 'wb')
    580         else:
    581             # else, assume it is an open file object already
    582             filename = '???'
    583         self.initfp(f)
    584         if filename[-5:] == '.aiff':
    585             self._aifc = 0
    586         else:
    587             self._aifc = 1
    588 
    589     def initfp(self, file):
    590         self._file = file
    591         self._version = _AIFC_version
    592         self._comptype = 'NONE'
    593         self._compname = 'not compressed'
    594         self._comp = None
    595         self._convert = None
    596         self._nchannels = 0
    597         self._sampwidth = 0
    598         self._framerate = 0
    599         self._nframes = 0
    600         self._nframeswritten = 0
    601         self._datawritten = 0
    602         self._datalength = 0
    603         self._markers = []
    604         self._marklength = 0
    605         self._aifc = 1      # AIFF-C is default
    606 
    607     def __del__(self):
    608         if self._file:
    609             self.close()
    610 
    611     #
    612     # User visible methods.
    613     #
    614     def aiff(self):
    615         if self._nframeswritten:
    616             raise Error, 'cannot change parameters after starting to write'
    617         self._aifc = 0
    618 
    619     def aifc(self):
    620         if self._nframeswritten:
    621             raise Error, 'cannot change parameters after starting to write'
    622         self._aifc = 1
    623 
    624     def setnchannels(self, nchannels):
    625         if self._nframeswritten:
    626             raise Error, 'cannot change parameters after starting to write'
    627         if nchannels < 1:
    628             raise Error, 'bad # of channels'
    629         self._nchannels = nchannels
    630 
    631     def getnchannels(self):
    632         if not self._nchannels:
    633             raise Error, 'number of channels not set'
    634         return self._nchannels
    635 
    636     def setsampwidth(self, sampwidth):
    637         if self._nframeswritten:
    638             raise Error, 'cannot change parameters after starting to write'
    639         if sampwidth < 1 or sampwidth > 4:
    640             raise Error, 'bad sample width'
    641         self._sampwidth = sampwidth
    642 
    643     def getsampwidth(self):
    644         if not self._sampwidth:
    645             raise Error, 'sample width not set'
    646         return self._sampwidth
    647 
    648     def setframerate(self, framerate):
    649         if self._nframeswritten:
    650             raise Error, 'cannot change parameters after starting to write'
    651         if framerate <= 0:
    652             raise Error, 'bad frame rate'
    653         self._framerate = framerate
    654 
    655     def getframerate(self):
    656         if not self._framerate:
    657             raise Error, 'frame rate not set'
    658         return self._framerate
    659 
    660     def setnframes(self, nframes):
    661         if self._nframeswritten:
    662             raise Error, 'cannot change parameters after starting to write'
    663         self._nframes = nframes
    664 
    665     def getnframes(self):
    666         return self._nframeswritten
    667 
    668     def setcomptype(self, comptype, compname):
    669         if self._nframeswritten:
    670             raise Error, 'cannot change parameters after starting to write'
    671         if comptype not in ('NONE', 'ULAW', 'ulaw', 'ALAW', 'alaw', 'G722'):
    672             raise Error, 'unsupported compression type'
    673         self._comptype = comptype
    674         self._compname = compname
    675 
    676     def getcomptype(self):
    677         return self._comptype
    678 
    679     def getcompname(self):
    680         return self._compname
    681 
    682 ##  def setversion(self, version):
    683 ##      if self._nframeswritten:
    684 ##          raise Error, 'cannot change parameters after starting to write'
    685 ##      self._version = version
    686 
    687     def setparams(self, info):
    688         nchannels, sampwidth, framerate, nframes, comptype, compname = info
    689         if self._nframeswritten:
    690             raise Error, 'cannot change parameters after starting to write'
    691         if comptype not in ('NONE', 'ULAW', 'ulaw', 'ALAW', 'alaw', 'G722'):
    692             raise Error, 'unsupported compression type'
    693         self.setnchannels(nchannels)
    694         self.setsampwidth(sampwidth)
    695         self.setframerate(framerate)
    696         self.setnframes(nframes)
    697         self.setcomptype(comptype, compname)
    698 
    699     def getparams(self):
    700         if not self._nchannels or not self._sampwidth or not self._framerate:
    701             raise Error, 'not all parameters set'
    702         return self._nchannels, self._sampwidth, self._framerate, \
    703               self._nframes, self._comptype, self._compname
    704 
    705     def setmark(self, id, pos, name):
    706         if id <= 0:
    707             raise Error, 'marker ID must be > 0'
    708         if pos < 0:
    709             raise Error, 'marker position must be >= 0'
    710         if type(name) != type(''):
    711             raise Error, 'marker name must be a string'
    712         for i in range(len(self._markers)):
    713             if id == self._markers[i][0]:
    714                 self._markers[i] = id, pos, name
    715                 return
    716         self._markers.append((id, pos, name))
    717 
    718     def getmark(self, id):
    719         for marker in self._markers:
    720             if id == marker[0]:
    721                 return marker
    722         raise Error, 'marker %r does not exist' % (id,)
    723 
    724     def getmarkers(self):
    725         if len(self._markers) == 0:
    726             return None
    727         return self._markers
    728 
    729     def tell(self):
    730         return self._nframeswritten
    731 
    732     def writeframesraw(self, data):
    733         self._ensure_header_written(len(data))
    734         nframes = len(data) // (self._sampwidth * self._nchannels)
    735         if self._convert:
    736             data = self._convert(data)
    737         self._file.write(data)
    738         self._nframeswritten = self._nframeswritten + nframes
    739         self._datawritten = self._datawritten + len(data)
    740 
    741     def writeframes(self, data):
    742         self.writeframesraw(data)
    743         if self._nframeswritten != self._nframes or \
    744               self._datalength != self._datawritten:
    745             self._patchheader()
    746 
    747     def close(self):
    748         if self._file is None:
    749             return
    750         try:
    751             self._ensure_header_written(0)
    752             if self._datawritten & 1:
    753                 # quick pad to even size
    754                 self._file.write(chr(0))
    755                 self._datawritten = self._datawritten + 1
    756             self._writemarkers()
    757             if self._nframeswritten != self._nframes or \
    758                   self._datalength != self._datawritten or \
    759                   self._marklength:
    760                 self._patchheader()
    761             if self._comp:
    762                 self._comp.CloseCompressor()
    763                 self._comp = None
    764         finally:
    765             # Prevent ref cycles
    766             self._convert = None
    767             f = self._file
    768             self._file = None
    769             f.close()
    770 
    771     #
    772     # Internal methods.
    773     #
    774 
    775     def _comp_data(self, data):
    776         import cl
    777         dummy = self._comp.SetParam(cl.FRAME_BUFFER_SIZE, len(data))
    778         dummy = self._comp.SetParam(cl.COMPRESSED_BUFFER_SIZE, len(data))
    779         return self._comp.Compress(self._nframes, data)
    780 
    781     def _lin2ulaw(self, data):
    782         import audioop
    783         return audioop.lin2ulaw(data, 2)
    784 
    785     def _lin2adpcm(self, data):
    786         import audioop
    787         if not hasattr(self, '_adpcmstate'):
    788             self._adpcmstate = None
    789         data, self._adpcmstate = audioop.lin2adpcm(data, 2,
    790                                self._adpcmstate)
    791         return data
    792 
    793     def _ensure_header_written(self, datasize):
    794         if not self._nframeswritten:
    795             if self._comptype in ('ULAW', 'ulaw', 'ALAW', 'alaw'):
    796                 if not self._sampwidth:
    797                     self._sampwidth = 2
    798                 if self._sampwidth != 2:
    799                     raise Error, 'sample width must be 2 when compressing with ULAW or ALAW'
    800             if self._comptype == 'G722':
    801                 if not self._sampwidth:
    802                     self._sampwidth = 2
    803                 if self._sampwidth != 2:
    804                     raise Error, 'sample width must be 2 when compressing with G7.22 (ADPCM)'
    805             if not self._nchannels:
    806                 raise Error, '# channels not specified'
    807             if not self._sampwidth:
    808                 raise Error, 'sample width not specified'
    809             if not self._framerate:
    810                 raise Error, 'sampling rate not specified'
    811             self._write_header(datasize)
    812 
    813     def _init_compression(self):
    814         if self._comptype == 'G722':
    815             self._convert = self._lin2adpcm
    816             return
    817         try:
    818             import cl
    819         except ImportError:
    820             if self._comptype in ('ULAW', 'ulaw'):
    821                 try:
    822                     import audioop
    823                     self._convert = self._lin2ulaw
    824                     return
    825                 except ImportError:
    826                     pass
    827             raise Error, 'cannot write compressed AIFF-C files'
    828         if self._comptype in ('ULAW', 'ulaw'):
    829             scheme = cl.G711_ULAW
    830         elif self._comptype in ('ALAW', 'alaw'):
    831             scheme = cl.G711_ALAW
    832         else:
    833             raise Error, 'unsupported compression type'
    834         self._comp = cl.OpenCompressor(scheme)
    835         params = [cl.ORIGINAL_FORMAT, 0,
    836               cl.BITS_PER_COMPONENT, self._sampwidth * 8,
    837               cl.FRAME_RATE, self._framerate,
    838               cl.FRAME_BUFFER_SIZE, 100,
    839               cl.COMPRESSED_BUFFER_SIZE, 100]
    840         if self._nchannels == 1:
    841             params[1] = cl.MONO
    842         elif self._nchannels == 2:
    843             params[1] = cl.STEREO_INTERLEAVED
    844         else:
    845             raise Error, 'cannot compress more than 2 channels'
    846         self._comp.SetParams(params)
    847         # the compressor produces a header which we ignore
    848         dummy = self._comp.Compress(0, '')
    849         self._convert = self._comp_data
    850 
    851     def _write_header(self, initlength):
    852         if self._aifc and self._comptype != 'NONE':
    853             self._init_compression()
    854         self._file.write('FORM')
    855         if not self._nframes:
    856             self._nframes = initlength // (self._nchannels * self._sampwidth)
    857         self._datalength = self._nframes * self._nchannels * self._sampwidth
    858         if self._datalength & 1:
    859             self._datalength = self._datalength + 1
    860         if self._aifc:
    861             if self._comptype in ('ULAW', 'ulaw', 'ALAW', 'alaw'):
    862                 self._datalength = self._datalength // 2
    863                 if self._datalength & 1:
    864                     self._datalength = self._datalength + 1
    865             elif self._comptype == 'G722':
    866                 self._datalength = (self._datalength + 3) // 4
    867                 if self._datalength & 1:
    868                     self._datalength = self._datalength + 1
    869         try:
    870             self._form_length_pos = self._file.tell()
    871         except (AttributeError, IOError):
    872             self._form_length_pos = None
    873         commlength = self._write_form_length(self._datalength)
    874         if self._aifc:
    875             self._file.write('AIFC')
    876             self._file.write('FVER')
    877             _write_ulong(self._file, 4)
    878             _write_ulong(self._file, self._version)
    879         else:
    880             self._file.write('AIFF')
    881         self._file.write('COMM')
    882         _write_ulong(self._file, commlength)
    883         _write_short(self._file, self._nchannels)
    884         if self._form_length_pos is not None:
    885             self._nframes_pos = self._file.tell()
    886         _write_ulong(self._file, self._nframes)
    887         if self._comptype in ('ULAW', 'ulaw', 'ALAW', 'alaw', 'G722'):
    888             _write_short(self._file, 8)
    889         else:
    890             _write_short(self._file, self._sampwidth * 8)
    891         _write_float(self._file, self._framerate)
    892         if self._aifc:
    893             self._file.write(self._comptype)
    894             _write_string(self._file, self._compname)
    895         self._file.write('SSND')
    896         if self._form_length_pos is not None:
    897             self._ssnd_length_pos = self._file.tell()
    898         _write_ulong(self._file, self._datalength + 8)
    899         _write_ulong(self._file, 0)
    900         _write_ulong(self._file, 0)
    901 
    902     def _write_form_length(self, datalength):
    903         if self._aifc:
    904             commlength = 18 + 5 + len(self._compname)
    905             if commlength & 1:
    906                 commlength = commlength + 1
    907             verslength = 12
    908         else:
    909             commlength = 18
    910             verslength = 0
    911         _write_ulong(self._file, 4 + verslength + self._marklength + \
    912                      8 + commlength + 16 + datalength)
    913         return commlength
    914 
    915     def _patchheader(self):
    916         curpos = self._file.tell()
    917         if self._datawritten & 1:
    918             datalength = self._datawritten + 1
    919             self._file.write(chr(0))
    920         else:
    921             datalength = self._datawritten
    922         if datalength == self._datalength and \
    923               self._nframes == self._nframeswritten and \
    924               self._marklength == 0:
    925             self._file.seek(curpos, 0)
    926             return
    927         self._file.seek(self._form_length_pos, 0)
    928         dummy = self._write_form_length(datalength)
    929         self._file.seek(self._nframes_pos, 0)
    930         _write_ulong(self._file, self._nframeswritten)
    931         self._file.seek(self._ssnd_length_pos, 0)
    932         _write_ulong(self._file, datalength + 8)
    933         self._file.seek(curpos, 0)
    934         self._nframes = self._nframeswritten
    935         self._datalength = datalength
    936 
    937     def _writemarkers(self):
    938         if len(self._markers) == 0:
    939             return
    940         self._file.write('MARK')
    941         length = 2
    942         for marker in self._markers:
    943             id, pos, name = marker
    944             length = length + len(name) + 1 + 6
    945             if len(name) & 1 == 0:
    946                 length = length + 1
    947         _write_ulong(self._file, length)
    948         self._marklength = length + 8
    949         _write_short(self._file, len(self._markers))
    950         for marker in self._markers:
    951             id, pos, name = marker
    952             _write_short(self._file, id)
    953             _write_ulong(self._file, pos)
    954             _write_string(self._file, name)
    955 
    956 def open(f, mode=None):
    957     if mode is None:
    958         if hasattr(f, 'mode'):
    959             mode = f.mode
    960         else:
    961             mode = 'rb'
    962     if mode in ('r', 'rb'):
    963         return Aifc_read(f)
    964     elif mode in ('w', 'wb'):
    965         return Aifc_write(f)
    966     else:
    967         raise Error, "mode must be 'r', 'rb', 'w', or 'wb'"
    968 
    969 openfp = open # B/W compatibility
    970 
    971 if __name__ == '__main__':
    972     import sys
    973     if not sys.argv[1:]:
    974         sys.argv.append('/usr/demos/data/audio/bach.aiff')
    975     fn = sys.argv[1]
    976     f = open(fn, 'r')
    977     try:
    978         print "Reading", fn
    979         print "nchannels =", f.getnchannels()
    980         print "nframes   =", f.getnframes()
    981         print "sampwidth =", f.getsampwidth()
    982         print "framerate =", f.getframerate()
    983         print "comptype  =", f.getcomptype()
    984         print "compname  =", f.getcompname()
    985         if sys.argv[2:]:
    986             gn = sys.argv[2]
    987             print "Writing", gn
    988             g = open(gn, 'w')
    989             try:
    990                 g.setparams(f.getparams())
    991                 while 1:
    992                     data = f.readframes(1024)
    993                     if not data:
    994                         break
    995                     g.writeframes(data)
    996             finally:
    997                 g.close()
    998             print "Done."
    999     finally:
   1000         f.close()
   1001