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