home *** CD-ROM | disk | FTP | other *** search
/ OS/2 Shareware BBS: 10 Tools / 10-Tools.zip / pyos2bin.zip / Lib / aifc.py < prev    next >
Text File  |  1997-01-11  |  30KB  |  1,021 lines

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