symbian-qemu-0.9.1-12/python-2.6.1/Lib/aifc.py
changeset 1 2fb8b9db1c86
equal deleted inserted replaced
0:ffa851df0825 1:2fb8b9db1c86
       
     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, info):
       
   669         nchannels, sampwidth, framerate, nframes, comptype, compname = info
       
   670         if self._nframeswritten:
       
   671             raise Error, 'cannot change parameters after starting to write'
       
   672         if comptype not in ('NONE', 'ULAW', 'ALAW', 'G722'):
       
   673             raise Error, 'unsupported compression type'
       
   674         self.setnchannels(nchannels)
       
   675         self.setsampwidth(sampwidth)
       
   676         self.setframerate(framerate)
       
   677         self.setnframes(nframes)
       
   678         self.setcomptype(comptype, compname)
       
   679 
       
   680     def getparams(self):
       
   681         if not self._nchannels or not self._sampwidth or not self._framerate:
       
   682             raise Error, 'not all parameters set'
       
   683         return self._nchannels, self._sampwidth, self._framerate, \
       
   684               self._nframes, self._comptype, self._compname
       
   685 
       
   686     def setmark(self, id, pos, name):
       
   687         if id <= 0:
       
   688             raise Error, 'marker ID must be > 0'
       
   689         if pos < 0:
       
   690             raise Error, 'marker position must be >= 0'
       
   691         if type(name) != type(''):
       
   692             raise Error, 'marker name must be a string'
       
   693         for i in range(len(self._markers)):
       
   694             if id == self._markers[i][0]:
       
   695                 self._markers[i] = id, pos, name
       
   696                 return
       
   697         self._markers.append((id, pos, name))
       
   698 
       
   699     def getmark(self, id):
       
   700         for marker in self._markers:
       
   701             if id == marker[0]:
       
   702                 return marker
       
   703         raise Error, 'marker %r does not exist' % (id,)
       
   704 
       
   705     def getmarkers(self):
       
   706         if len(self._markers) == 0:
       
   707             return None
       
   708         return self._markers
       
   709 
       
   710     def tell(self):
       
   711         return self._nframeswritten
       
   712 
       
   713     def writeframesraw(self, data):
       
   714         self._ensure_header_written(len(data))
       
   715         nframes = len(data) / (self._sampwidth * self._nchannels)
       
   716         if self._convert:
       
   717             data = self._convert(data)
       
   718         self._file.write(data)
       
   719         self._nframeswritten = self._nframeswritten + nframes
       
   720         self._datawritten = self._datawritten + len(data)
       
   721 
       
   722     def writeframes(self, data):
       
   723         self.writeframesraw(data)
       
   724         if self._nframeswritten != self._nframes or \
       
   725               self._datalength != self._datawritten:
       
   726             self._patchheader()
       
   727 
       
   728     def close(self):
       
   729         self._ensure_header_written(0)
       
   730         if self._datawritten & 1:
       
   731             # quick pad to even size
       
   732             self._file.write(chr(0))
       
   733             self._datawritten = self._datawritten + 1
       
   734         self._writemarkers()
       
   735         if self._nframeswritten != self._nframes or \
       
   736               self._datalength != self._datawritten or \
       
   737               self._marklength:
       
   738             self._patchheader()
       
   739         if self._comp:
       
   740             self._comp.CloseCompressor()
       
   741             self._comp = None
       
   742         self._file.flush()
       
   743         self._file = None
       
   744 
       
   745     #
       
   746     # Internal methods.
       
   747     #
       
   748 
       
   749     def _comp_data(self, data):
       
   750         import cl
       
   751         dummy = self._comp.SetParam(cl.FRAME_BUFFER_SIZE, len(data))
       
   752         dummy = self._comp.SetParam(cl.COMPRESSED_BUFFER_SIZE, len(data))
       
   753         return self._comp.Compress(self._nframes, data)
       
   754 
       
   755     def _lin2ulaw(self, data):
       
   756         import audioop
       
   757         return audioop.lin2ulaw(data, 2)
       
   758 
       
   759     def _lin2adpcm(self, data):
       
   760         import audioop
       
   761         if not hasattr(self, '_adpcmstate'):
       
   762             self._adpcmstate = None
       
   763         data, self._adpcmstate = audioop.lin2adpcm(data, 2,
       
   764                                self._adpcmstate)
       
   765         return data
       
   766 
       
   767     def _ensure_header_written(self, datasize):
       
   768         if not self._nframeswritten:
       
   769             if self._comptype in ('ULAW', 'ALAW'):
       
   770                 if not self._sampwidth:
       
   771                     self._sampwidth = 2
       
   772                 if self._sampwidth != 2:
       
   773                     raise Error, 'sample width must be 2 when compressing with ULAW or ALAW'
       
   774             if self._comptype == 'G722':
       
   775                 if not self._sampwidth:
       
   776                     self._sampwidth = 2
       
   777                 if self._sampwidth != 2:
       
   778                     raise Error, 'sample width must be 2 when compressing with G7.22 (ADPCM)'
       
   779             if not self._nchannels:
       
   780                 raise Error, '# channels not specified'
       
   781             if not self._sampwidth:
       
   782                 raise Error, 'sample width not specified'
       
   783             if not self._framerate:
       
   784                 raise Error, 'sampling rate not specified'
       
   785             self._write_header(datasize)
       
   786 
       
   787     def _init_compression(self):
       
   788         if self._comptype == 'G722':
       
   789             self._convert = self._lin2adpcm
       
   790             return
       
   791         try:
       
   792             import cl
       
   793         except ImportError:
       
   794             if self._comptype == 'ULAW':
       
   795                 try:
       
   796                     import audioop
       
   797                     self._convert = self._lin2ulaw
       
   798                     return
       
   799                 except ImportError:
       
   800                     pass
       
   801             raise Error, 'cannot write compressed AIFF-C files'
       
   802         if self._comptype == 'ULAW':
       
   803             scheme = cl.G711_ULAW
       
   804         elif self._comptype == 'ALAW':
       
   805             scheme = cl.G711_ALAW
       
   806         else:
       
   807             raise Error, 'unsupported compression type'
       
   808         self._comp = cl.OpenCompressor(scheme)
       
   809         params = [cl.ORIGINAL_FORMAT, 0,
       
   810               cl.BITS_PER_COMPONENT, self._sampwidth * 8,
       
   811               cl.FRAME_RATE, self._framerate,
       
   812               cl.FRAME_BUFFER_SIZE, 100,
       
   813               cl.COMPRESSED_BUFFER_SIZE, 100]
       
   814         if self._nchannels == 1:
       
   815             params[1] = cl.MONO
       
   816         elif self._nchannels == 2:
       
   817             params[1] = cl.STEREO_INTERLEAVED
       
   818         else:
       
   819             raise Error, 'cannot compress more than 2 channels'
       
   820         self._comp.SetParams(params)
       
   821         # the compressor produces a header which we ignore
       
   822         dummy = self._comp.Compress(0, '')
       
   823         self._convert = self._comp_data
       
   824 
       
   825     def _write_header(self, initlength):
       
   826         if self._aifc and self._comptype != 'NONE':
       
   827             self._init_compression()
       
   828         self._file.write('FORM')
       
   829         if not self._nframes:
       
   830             self._nframes = initlength / (self._nchannels * self._sampwidth)
       
   831         self._datalength = self._nframes * self._nchannels * self._sampwidth
       
   832         if self._datalength & 1:
       
   833             self._datalength = self._datalength + 1
       
   834         if self._aifc:
       
   835             if self._comptype in ('ULAW', 'ALAW'):
       
   836                 self._datalength = self._datalength / 2
       
   837                 if self._datalength & 1:
       
   838                     self._datalength = self._datalength + 1
       
   839             elif self._comptype == 'G722':
       
   840                 self._datalength = (self._datalength + 3) / 4
       
   841                 if self._datalength & 1:
       
   842                     self._datalength = self._datalength + 1
       
   843         self._form_length_pos = self._file.tell()
       
   844         commlength = self._write_form_length(self._datalength)
       
   845         if self._aifc:
       
   846             self._file.write('AIFC')
       
   847             self._file.write('FVER')
       
   848             _write_long(self._file, 4)
       
   849             _write_long(self._file, self._version)
       
   850         else:
       
   851             self._file.write('AIFF')
       
   852         self._file.write('COMM')
       
   853         _write_long(self._file, commlength)
       
   854         _write_short(self._file, self._nchannels)
       
   855         self._nframes_pos = self._file.tell()
       
   856         _write_long(self._file, self._nframes)
       
   857         _write_short(self._file, self._sampwidth * 8)
       
   858         _write_float(self._file, self._framerate)
       
   859         if self._aifc:
       
   860             self._file.write(self._comptype)
       
   861             _write_string(self._file, self._compname)
       
   862         self._file.write('SSND')
       
   863         self._ssnd_length_pos = self._file.tell()
       
   864         _write_long(self._file, self._datalength + 8)
       
   865         _write_long(self._file, 0)
       
   866         _write_long(self._file, 0)
       
   867 
       
   868     def _write_form_length(self, datalength):
       
   869         if self._aifc:
       
   870             commlength = 18 + 5 + len(self._compname)
       
   871             if commlength & 1:
       
   872                 commlength = commlength + 1
       
   873             verslength = 12
       
   874         else:
       
   875             commlength = 18
       
   876             verslength = 0
       
   877         _write_long(self._file, 4 + verslength + self._marklength + \
       
   878                     8 + commlength + 16 + datalength)
       
   879         return commlength
       
   880 
       
   881     def _patchheader(self):
       
   882         curpos = self._file.tell()
       
   883         if self._datawritten & 1:
       
   884             datalength = self._datawritten + 1
       
   885             self._file.write(chr(0))
       
   886         else:
       
   887             datalength = self._datawritten
       
   888         if datalength == self._datalength and \
       
   889               self._nframes == self._nframeswritten and \
       
   890               self._marklength == 0:
       
   891             self._file.seek(curpos, 0)
       
   892             return
       
   893         self._file.seek(self._form_length_pos, 0)
       
   894         dummy = self._write_form_length(datalength)
       
   895         self._file.seek(self._nframes_pos, 0)
       
   896         _write_long(self._file, self._nframeswritten)
       
   897         self._file.seek(self._ssnd_length_pos, 0)
       
   898         _write_long(self._file, datalength + 8)
       
   899         self._file.seek(curpos, 0)
       
   900         self._nframes = self._nframeswritten
       
   901         self._datalength = datalength
       
   902 
       
   903     def _writemarkers(self):
       
   904         if len(self._markers) == 0:
       
   905             return
       
   906         self._file.write('MARK')
       
   907         length = 2
       
   908         for marker in self._markers:
       
   909             id, pos, name = marker
       
   910             length = length + len(name) + 1 + 6
       
   911             if len(name) & 1 == 0:
       
   912                 length = length + 1
       
   913         _write_long(self._file, length)
       
   914         self._marklength = length + 8
       
   915         _write_short(self._file, len(self._markers))
       
   916         for marker in self._markers:
       
   917             id, pos, name = marker
       
   918             _write_short(self._file, id)
       
   919             _write_long(self._file, pos)
       
   920             _write_string(self._file, name)
       
   921 
       
   922 def open(f, mode=None):
       
   923     if mode is None:
       
   924         if hasattr(f, 'mode'):
       
   925             mode = f.mode
       
   926         else:
       
   927             mode = 'rb'
       
   928     if mode in ('r', 'rb'):
       
   929         return Aifc_read(f)
       
   930     elif mode in ('w', 'wb'):
       
   931         return Aifc_write(f)
       
   932     else:
       
   933         raise Error, "mode must be 'r', 'rb', 'w', or 'wb'"
       
   934 
       
   935 openfp = open # B/W compatibility
       
   936 
       
   937 if __name__ == '__main__':
       
   938     import sys
       
   939     if not sys.argv[1:]:
       
   940         sys.argv.append('/usr/demos/data/audio/bach.aiff')
       
   941     fn = sys.argv[1]
       
   942     f = open(fn, 'r')
       
   943     print "Reading", fn
       
   944     print "nchannels =", f.getnchannels()
       
   945     print "nframes   =", f.getnframes()
       
   946     print "sampwidth =", f.getsampwidth()
       
   947     print "framerate =", f.getframerate()
       
   948     print "comptype  =", f.getcomptype()
       
   949     print "compname  =", f.getcompname()
       
   950     if sys.argv[2:]:
       
   951         gn = sys.argv[2]
       
   952         print "Writing", gn
       
   953         g = open(gn, 'w')
       
   954         g.setparams(f.getparams())
       
   955         while 1:
       
   956             data = f.readframes(1024)
       
   957             if not data:
       
   958                 break
       
   959             g.writeframes(data)
       
   960         g.close()
       
   961         f.close()
       
   962         print "Done."