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