PyXR

c:\python24\lib \ aifc.py



0001 """Stuff to parse AIFF-C and AIFF files.
0002 
0003 Unless explicitly stated otherwise, the description below is true
0004 both for AIFF-C files and AIFF files.
0005 
0006 An AIFF-C file has the following structure.
0007 
0008   +-----------------+
0009   | FORM            |
0010   +-----------------+
0011   | <size>          |
0012   +----+------------+
0013   |    | AIFC       |
0014   |    +------------+
0015   |    | <chunks>   |
0016   |    |    .       |
0017   |    |    .       |
0018   |    |    .       |
0019   +----+------------+
0020 
0021 An AIFF file has the string "AIFF" instead of "AIFC".
0022 
0023 A chunk consists of an identifier (4 bytes) followed by a size (4 bytes,
0024 big endian order), followed by the data.  The size field does not include
0025 the size of the 8 byte header.
0026 
0027 The following chunk types are recognized.
0028 
0029   FVER
0030       <version number of AIFF-C defining document> (AIFF-C only).
0031   MARK
0032       <# of markers> (2 bytes)
0033       list of markers:
0034           <marker ID> (2 bytes, must be > 0)
0035           <position> (4 bytes)
0036           <marker name> ("pstring")
0037   COMM
0038       <# of channels> (2 bytes)
0039       <# of sound frames> (4 bytes)
0040       <size of the samples> (2 bytes)
0041       <sampling frequency> (10 bytes, IEEE 80-bit extended
0042           floating point)
0043       in AIFF-C files only:
0044       <compression type> (4 bytes)
0045       <human-readable version of compression type> ("pstring")
0046   SSND
0047       <offset> (4 bytes, not used by this program)
0048       <blocksize> (4 bytes, not used by this program)
0049       <sound data>
0050 
0051 A pstring consists of 1 byte length, a string of characters, and 0 or 1
0052 byte pad to make the total length even.
0053 
0054 Usage.
0055 
0056 Reading AIFF files:
0057   f = aifc.open(file, 'r')
0058 where file is either the name of a file or an open file pointer.
0059 The open file pointer must have methods read(), seek(), and close().
0060 In some types of audio files, if the setpos() method is not used,
0061 the seek() method is not necessary.
0062 
0063 This returns an instance of a class with the following public methods:
0064   getnchannels()  -- returns number of audio channels (1 for
0065              mono, 2 for stereo)
0066   getsampwidth()  -- returns sample width in bytes
0067   getframerate()  -- returns sampling frequency
0068   getnframes()    -- returns number of audio frames
0069   getcomptype()   -- returns compression type ('NONE' for AIFF files)
0070   getcompname()   -- returns human-readable version of
0071              compression type ('not compressed' for AIFF files)
0072   getparams() -- returns a tuple consisting of all of the
0073              above in the above order
0074   getmarkers()    -- get the list of marks in the audio file or None
0075              if there are no marks
0076   getmark(id) -- get mark with the specified id (raises an error
0077              if the mark does not exist)
0078   readframes(n)   -- returns at most n frames of audio
0079   rewind()    -- rewind to the beginning of the audio stream
0080   setpos(pos) -- seek to the specified position
0081   tell()      -- return the current position
0082   close()     -- close the instance (make it unusable)
0083 The position returned by tell(), the position given to setpos() and
0084 the position of marks are all compatible and have nothing to do with
0085 the actual position in the file.
0086 The close() method is called automatically when the class instance
0087 is destroyed.
0088 
0089 Writing AIFF files:
0090   f = aifc.open(file, 'w')
0091 where file is either the name of a file or an open file pointer.
0092 The open file pointer must have methods write(), tell(), seek(), and
0093 close().
0094 
0095 This returns an instance of a class with the following public methods:
0096   aiff()      -- create an AIFF file (AIFF-C default)
0097   aifc()      -- create an AIFF-C file
0098   setnchannels(n) -- set the number of channels
0099   setsampwidth(n) -- set the sample width
0100   setframerate(n) -- set the frame rate
0101   setnframes(n)   -- set the number of frames
0102   setcomptype(type, name)
0103           -- set the compression type and the
0104              human-readable compression type
0105   setparams(tuple)
0106           -- set all parameters at once
0107   setmark(id, pos, name)
0108           -- add specified mark to the list of marks
0109   tell()      -- return current position in output file (useful
0110              in combination with setmark())
0111   writeframesraw(data)
0112           -- write audio frames without pathing up the
0113              file header
0114   writeframes(data)
0115           -- write audio frames and patch up the file header
0116   close()     -- patch up the file header and close the
0117              output file
0118 You should set the parameters before the first writeframesraw or
0119 writeframes.  The total number of frames does not need to be set,
0120 but when it is set to the correct value, the header does not have to
0121 be patched up.
0122 It is best to first set all parameters, perhaps possibly the
0123 compression type, and then write audio frames using writeframesraw.
0124 When all frames have been written, either call writeframes('') or
0125 close() to patch up the sizes in the header.
0126 Marks can be added anytime.  If there are any marks, ypu must call
0127 close() after all frames have been written.
0128 The close() method is called automatically when the class instance
0129 is destroyed.
0130 
0131 When a file is opened with the extension '.aiff', an AIFF file is
0132 written, otherwise an AIFF-C file is written.  This default can be
0133 changed by calling aiff() or aifc() before the first writeframes or
0134 writeframesraw.
0135 """
0136 
0137 import struct
0138 import __builtin__
0139 
0140 __all__ = ["Error","open","openfp"]
0141 
0142 class Error(Exception):
0143     pass
0144 
0145 _AIFC_version = 0xA2805140L     # Version 1 of AIFF-C
0146 
0147 _skiplist = 'COMT', 'INST', 'MIDI', 'AESD', \
0148       'APPL', 'NAME', 'AUTH', '(c) ', 'ANNO'
0149 
0150 def _read_long(file):
0151     try:
0152         return struct.unpack('>l', file.read(4))[0]
0153     except struct.error:
0154         raise EOFError
0155 
0156 def _read_ulong(file):
0157     try:
0158         return struct.unpack('>L', file.read(4))[0]
0159     except struct.error:
0160         raise EOFError
0161 
0162 def _read_short(file):
0163     try:
0164         return struct.unpack('>h', file.read(2))[0]
0165     except struct.error:
0166         raise EOFError
0167 
0168 def _read_string(file):
0169     length = ord(file.read(1))
0170     if length == 0:
0171         data = ''
0172     else:
0173         data = file.read(length)
0174     if length & 1 == 0:
0175         dummy = file.read(1)
0176     return data
0177 
0178 _HUGE_VAL = 1.79769313486231e+308 # See <limits.h>
0179 
0180 def _read_float(f): # 10 bytes
0181     expon = _read_short(f) # 2 bytes
0182     sign = 1
0183     if expon < 0:
0184         sign = -1
0185         expon = expon + 0x8000
0186     himant = _read_ulong(f) # 4 bytes
0187     lomant = _read_ulong(f) # 4 bytes
0188     if expon == himant == lomant == 0:
0189         f = 0.0
0190     elif expon == 0x7FFF:
0191         f = _HUGE_VAL
0192     else:
0193         expon = expon - 16383
0194         f = (himant * 0x100000000L + lomant) * pow(2.0, expon - 63)
0195     return sign * f
0196 
0197 def _write_short(f, x):
0198     f.write(struct.pack('>h', x))
0199 
0200 def _write_long(f, x):
0201     f.write(struct.pack('>L', x))
0202 
0203 def _write_string(f, s):
0204     f.write(chr(len(s)))
0205     f.write(s)
0206     if len(s) & 1 == 0:
0207         f.write(chr(0))
0208 
0209 def _write_float(f, x):
0210     import math
0211     if x < 0:
0212         sign = 0x8000
0213         x = x * -1
0214     else:
0215         sign = 0
0216     if x == 0:
0217         expon = 0
0218         himant = 0
0219         lomant = 0
0220     else:
0221         fmant, expon = math.frexp(x)
0222         if expon > 16384 or fmant >= 1:     # Infinity or NaN
0223             expon = sign|0x7FFF
0224             himant = 0
0225             lomant = 0
0226         else:                   # Finite
0227             expon = expon + 16382
0228             if expon < 0:           # denormalized
0229                 fmant = math.ldexp(fmant, expon)
0230                 expon = 0
0231             expon = expon | sign
0232             fmant = math.ldexp(fmant, 32)
0233             fsmant = math.floor(fmant)
0234             himant = long(fsmant)
0235             fmant = math.ldexp(fmant - fsmant, 32)
0236             fsmant = math.floor(fmant)
0237             lomant = long(fsmant)
0238     _write_short(f, expon)
0239     _write_long(f, himant)
0240     _write_long(f, lomant)
0241 
0242 from chunk import Chunk
0243 
0244 class Aifc_read:
0245     # Variables used in this class:
0246     #
0247     # These variables are available to the user though appropriate
0248     # methods of this class:
0249     # _file -- the open file with methods read(), close(), and seek()
0250     #       set through the __init__() method
0251     # _nchannels -- the number of audio channels
0252     #       available through the getnchannels() method
0253     # _nframes -- the number of audio frames
0254     #       available through the getnframes() method
0255     # _sampwidth -- the number of bytes per audio sample
0256     #       available through the getsampwidth() method
0257     # _framerate -- the sampling frequency
0258     #       available through the getframerate() method
0259     # _comptype -- the AIFF-C compression type ('NONE' if AIFF)
0260     #       available through the getcomptype() method
0261     # _compname -- the human-readable AIFF-C compression type
0262     #       available through the getcomptype() method
0263     # _markers -- the marks in the audio file
0264     #       available through the getmarkers() and getmark()
0265     #       methods
0266     # _soundpos -- the position in the audio stream
0267     #       available through the tell() method, set through the
0268     #       setpos() method
0269     #
0270     # These variables are used internally only:
0271     # _version -- the AIFF-C version number
0272     # _decomp -- the decompressor from builtin module cl
0273     # _comm_chunk_read -- 1 iff the COMM chunk has been read
0274     # _aifc -- 1 iff reading an AIFF-C file
0275     # _ssnd_seek_needed -- 1 iff positioned correctly in audio
0276     #       file for readframes()
0277     # _ssnd_chunk -- instantiation of a chunk class for the SSND chunk
0278     # _framesize -- size of one frame in the file
0279 
0280     def initfp(self, file):
0281         self._version = 0
0282         self._decomp = None
0283         self._convert = None
0284         self._markers = []
0285         self._soundpos = 0
0286         self._file = Chunk(file)
0287         if self._file.getname() != 'FORM':
0288             raise Error, 'file does not start with FORM id'
0289         formdata = self._file.read(4)
0290         if formdata == 'AIFF':
0291             self._aifc = 0
0292         elif formdata == 'AIFC':
0293             self._aifc = 1
0294         else:
0295             raise Error, 'not an AIFF or AIFF-C file'
0296         self._comm_chunk_read = 0
0297         while 1:
0298             self._ssnd_seek_needed = 1
0299             try:
0300                 chunk = Chunk(self._file)
0301             except EOFError:
0302                 break
0303             chunkname = chunk.getname()
0304             if chunkname == 'COMM':
0305                 self._read_comm_chunk(chunk)
0306                 self._comm_chunk_read = 1
0307             elif chunkname == 'SSND':
0308                 self._ssnd_chunk = chunk
0309                 dummy = chunk.read(8)
0310                 self._ssnd_seek_needed = 0
0311             elif chunkname == 'FVER':
0312                 self._version = _read_ulong(chunk)
0313             elif chunkname == 'MARK':
0314                 self._readmark(chunk)
0315             elif chunkname in _skiplist:
0316                 pass
0317             else:
0318                 raise Error, 'unrecognized chunk type '+chunk.chunkname
0319             chunk.skip()
0320         if not self._comm_chunk_read or not self._ssnd_chunk:
0321             raise Error, 'COMM chunk and/or SSND chunk missing'
0322         if self._aifc and self._decomp:
0323             import cl
0324             params = [cl.ORIGINAL_FORMAT, 0,
0325                   cl.BITS_PER_COMPONENT, self._sampwidth * 8,
0326                   cl.FRAME_RATE, self._framerate]
0327             if self._nchannels == 1:
0328                 params[1] = cl.MONO
0329             elif self._nchannels == 2:
0330                 params[1] = cl.STEREO_INTERLEAVED
0331             else:
0332                 raise Error, 'cannot compress more than 2 channels'
0333             self._decomp.SetParams(params)
0334 
0335     def __init__(self, f):
0336         if type(f) == type(''):
0337             f = __builtin__.open(f, 'rb')
0338         # else, assume it is an open file object already
0339         self.initfp(f)
0340 
0341     #
0342     # User visible methods.
0343     #
0344     def getfp(self):
0345         return self._file
0346 
0347     def rewind(self):
0348         self._ssnd_seek_needed = 1
0349         self._soundpos = 0
0350 
0351     def close(self):
0352         if self._decomp:
0353             self._decomp.CloseDecompressor()
0354             self._decomp = None
0355         self._file = None
0356 
0357     def tell(self):
0358         return self._soundpos
0359 
0360     def getnchannels(self):
0361         return self._nchannels
0362 
0363     def getnframes(self):
0364         return self._nframes
0365 
0366     def getsampwidth(self):
0367         return self._sampwidth
0368 
0369     def getframerate(self):
0370         return self._framerate
0371 
0372     def getcomptype(self):
0373         return self._comptype
0374 
0375     def getcompname(self):
0376         return self._compname
0377 
0378 ##  def getversion(self):
0379 ##      return self._version
0380 
0381     def getparams(self):
0382         return self.getnchannels(), self.getsampwidth(), \
0383               self.getframerate(), self.getnframes(), \
0384               self.getcomptype(), self.getcompname()
0385 
0386     def getmarkers(self):
0387         if len(self._markers) == 0:
0388             return None
0389         return self._markers
0390 
0391     def getmark(self, id):
0392         for marker in self._markers:
0393             if id == marker[0]:
0394                 return marker
0395         raise Error, 'marker %r does not exist' % (id,)
0396 
0397     def setpos(self, pos):
0398         if pos < 0 or pos > self._nframes:
0399             raise Error, 'position not in range'
0400         self._soundpos = pos
0401         self._ssnd_seek_needed = 1
0402 
0403     def readframes(self, nframes):
0404         if self._ssnd_seek_needed:
0405             self._ssnd_chunk.seek(0)
0406             dummy = self._ssnd_chunk.read(8)
0407             pos = self._soundpos * self._framesize
0408             if pos:
0409                 self._ssnd_chunk.seek(pos + 8)
0410             self._ssnd_seek_needed = 0
0411         if nframes == 0:
0412             return ''
0413         data = self._ssnd_chunk.read(nframes * self._framesize)
0414         if self._convert and data:
0415             data = self._convert(data)
0416         self._soundpos = self._soundpos + len(data) / (self._nchannels * self._sampwidth)
0417         return data
0418 
0419     #
0420     # Internal methods.
0421     #
0422 
0423     def _decomp_data(self, data):
0424         import cl
0425         dummy = self._decomp.SetParam(cl.FRAME_BUFFER_SIZE,
0426                           len(data) * 2)
0427         return self._decomp.Decompress(len(data) / self._nchannels,
0428                            data)
0429 
0430     def _ulaw2lin(self, data):
0431         import audioop
0432         return audioop.ulaw2lin(data, 2)
0433 
0434     def _adpcm2lin(self, data):
0435         import audioop
0436         if not hasattr(self, '_adpcmstate'):
0437             # first time
0438             self._adpcmstate = None
0439         data, self._adpcmstate = audioop.adpcm2lin(data, 2,
0440                                self._adpcmstate)
0441         return data
0442 
0443     def _read_comm_chunk(self, chunk):
0444         self._nchannels = _read_short(chunk)
0445         self._nframes = _read_long(chunk)
0446         self._sampwidth = (_read_short(chunk) + 7) / 8
0447         self._framerate = int(_read_float(chunk))
0448         self._framesize = self._nchannels * self._sampwidth
0449         if self._aifc:
0450             #DEBUG: SGI's soundeditor produces a bad size :-(
0451             kludge = 0
0452             if chunk.chunksize == 18:
0453                 kludge = 1
0454                 print 'Warning: bad COMM chunk size'
0455                 chunk.chunksize = 23
0456             #DEBUG end
0457             self._comptype = chunk.read(4)
0458             #DEBUG start
0459             if kludge:
0460                 length = ord(chunk.file.read(1))
0461                 if length & 1 == 0:
0462                     length = length + 1
0463                 chunk.chunksize = chunk.chunksize + length
0464                 chunk.file.seek(-1, 1)
0465             #DEBUG end
0466             self._compname = _read_string(chunk)
0467             if self._comptype != 'NONE':
0468                 if self._comptype == 'G722':
0469                     try:
0470                         import audioop
0471                     except ImportError:
0472                         pass
0473                     else:
0474                         self._convert = self._adpcm2lin
0475                         self._framesize = self._framesize / 4
0476                         return
0477                 # for ULAW and ALAW try Compression Library
0478                 try:
0479                     import cl
0480                 except ImportError:
0481                     if self._comptype == 'ULAW':
0482                         try:
0483                             import audioop
0484                             self._convert = self._ulaw2lin
0485                             self._framesize = self._framesize / 2
0486                             return
0487                         except ImportError:
0488                             pass
0489                     raise Error, 'cannot read compressed AIFF-C files'
0490                 if self._comptype == 'ULAW':
0491                     scheme = cl.G711_ULAW
0492                     self._framesize = self._framesize / 2
0493                 elif self._comptype == 'ALAW':
0494                     scheme = cl.G711_ALAW
0495                     self._framesize = self._framesize / 2
0496                 else:
0497                     raise Error, 'unsupported compression type'
0498                 self._decomp = cl.OpenDecompressor(scheme)
0499                 self._convert = self._decomp_data
0500         else:
0501             self._comptype = 'NONE'
0502             self._compname = 'not compressed'
0503 
0504     def _readmark(self, chunk):
0505         nmarkers = _read_short(chunk)
0506         # Some files appear to contain invalid counts.
0507         # Cope with this by testing for EOF.
0508         try:
0509             for i in range(nmarkers):
0510                 id = _read_short(chunk)
0511                 pos = _read_long(chunk)
0512                 name = _read_string(chunk)
0513                 if pos or name:
0514                     # some files appear to have
0515                     # dummy markers consisting of
0516                     # a position 0 and name ''
0517                     self._markers.append((id, pos, name))
0518         except EOFError:
0519             print 'Warning: MARK chunk contains only',
0520             print len(self._markers),
0521             if len(self._markers) == 1: print 'marker',
0522             else: print 'markers',
0523             print 'instead of', nmarkers
0524 
0525 class Aifc_write:
0526     # Variables used in this class:
0527     #
0528     # These variables are user settable through appropriate methods
0529     # of this class:
0530     # _file -- the open file with methods write(), close(), tell(), seek()
0531     #       set through the __init__() method
0532     # _comptype -- the AIFF-C compression type ('NONE' in AIFF)
0533     #       set through the setcomptype() or setparams() method
0534     # _compname -- the human-readable AIFF-C compression type
0535     #       set through the setcomptype() or setparams() method
0536     # _nchannels -- the number of audio channels
0537     #       set through the setnchannels() or setparams() method
0538     # _sampwidth -- the number of bytes per audio sample
0539     #       set through the setsampwidth() or setparams() method
0540     # _framerate -- the sampling frequency
0541     #       set through the setframerate() or setparams() method
0542     # _nframes -- the number of audio frames written to the header
0543     #       set through the setnframes() or setparams() method
0544     # _aifc -- whether we're writing an AIFF-C file or an AIFF file
0545     #       set through the aifc() method, reset through the
0546     #       aiff() method
0547     #
0548     # These variables are used internally only:
0549     # _version -- the AIFF-C version number
0550     # _comp -- the compressor from builtin module cl
0551     # _nframeswritten -- the number of audio frames actually written
0552     # _datalength -- the size of the audio samples written to the header
0553     # _datawritten -- the size of the audio samples actually written
0554 
0555     def __init__(self, f):
0556         if type(f) == type(''):
0557             filename = f
0558             f = __builtin__.open(f, 'wb')
0559         else:
0560             # else, assume it is an open file object already
0561             filename = '???'
0562         self.initfp(f)
0563         if filename[-5:] == '.aiff':
0564             self._aifc = 0
0565         else:
0566             self._aifc = 1
0567 
0568     def initfp(self, file):
0569         self._file = file
0570         self._version = _AIFC_version
0571         self._comptype = 'NONE'
0572         self._compname = 'not compressed'
0573         self._comp = None
0574         self._convert = None
0575         self._nchannels = 0
0576         self._sampwidth = 0
0577         self._framerate = 0
0578         self._nframes = 0
0579         self._nframeswritten = 0
0580         self._datawritten = 0
0581         self._datalength = 0
0582         self._markers = []
0583         self._marklength = 0
0584         self._aifc = 1      # AIFF-C is default
0585 
0586     def __del__(self):
0587         if self._file:
0588             self.close()
0589 
0590     #
0591     # User visible methods.
0592     #
0593     def aiff(self):
0594         if self._nframeswritten:
0595             raise Error, 'cannot change parameters after starting to write'
0596         self._aifc = 0
0597 
0598     def aifc(self):
0599         if self._nframeswritten:
0600             raise Error, 'cannot change parameters after starting to write'
0601         self._aifc = 1
0602 
0603     def setnchannels(self, nchannels):
0604         if self._nframeswritten:
0605             raise Error, 'cannot change parameters after starting to write'
0606         if nchannels < 1:
0607             raise Error, 'bad # of channels'
0608         self._nchannels = nchannels
0609 
0610     def getnchannels(self):
0611         if not self._nchannels:
0612             raise Error, 'number of channels not set'
0613         return self._nchannels
0614 
0615     def setsampwidth(self, sampwidth):
0616         if self._nframeswritten:
0617             raise Error, 'cannot change parameters after starting to write'
0618         if sampwidth < 1 or sampwidth > 4:
0619             raise Error, 'bad sample width'
0620         self._sampwidth = sampwidth
0621 
0622     def getsampwidth(self):
0623         if not self._sampwidth:
0624             raise Error, 'sample width not set'
0625         return self._sampwidth
0626 
0627     def setframerate(self, framerate):
0628         if self._nframeswritten:
0629             raise Error, 'cannot change parameters after starting to write'
0630         if framerate <= 0:
0631             raise Error, 'bad frame rate'
0632         self._framerate = framerate
0633 
0634     def getframerate(self):
0635         if not self._framerate:
0636             raise Error, 'frame rate not set'
0637         return self._framerate
0638 
0639     def setnframes(self, nframes):
0640         if self._nframeswritten:
0641             raise Error, 'cannot change parameters after starting to write'
0642         self._nframes = nframes
0643 
0644     def getnframes(self):
0645         return self._nframeswritten
0646 
0647     def setcomptype(self, comptype, compname):
0648         if self._nframeswritten:
0649             raise Error, 'cannot change parameters after starting to write'
0650         if comptype not in ('NONE', 'ULAW', 'ALAW', 'G722'):
0651             raise Error, 'unsupported compression type'
0652         self._comptype = comptype
0653         self._compname = compname
0654 
0655     def getcomptype(self):
0656         return self._comptype
0657 
0658     def getcompname(self):
0659         return self._compname
0660 
0661 ##  def setversion(self, version):
0662 ##      if self._nframeswritten:
0663 ##          raise Error, 'cannot change parameters after starting to write'
0664 ##      self._version = version
0665 
0666     def setparams(self, (nchannels, sampwidth, framerate, nframes, comptype, compname)):
0667         if self._nframeswritten:
0668             raise Error, 'cannot change parameters after starting to write'
0669         if comptype not in ('NONE', 'ULAW', 'ALAW', 'G722'):
0670             raise Error, 'unsupported compression type'
0671         self.setnchannels(nchannels)
0672         self.setsampwidth(sampwidth)
0673         self.setframerate(framerate)
0674         self.setnframes(nframes)
0675         self.setcomptype(comptype, compname)
0676 
0677     def getparams(self):
0678         if not self._nchannels or not self._sampwidth or not self._framerate:
0679             raise Error, 'not all parameters set'
0680         return self._nchannels, self._sampwidth, self._framerate, \
0681               self._nframes, self._comptype, self._compname
0682 
0683     def setmark(self, id, pos, name):
0684         if id <= 0:
0685             raise Error, 'marker ID must be > 0'
0686         if pos < 0:
0687             raise Error, 'marker position must be >= 0'
0688         if type(name) != type(''):
0689             raise Error, 'marker name must be a string'
0690         for i in range(len(self._markers)):
0691             if id == self._markers[i][0]:
0692                 self._markers[i] = id, pos, name
0693                 return
0694         self._markers.append((id, pos, name))
0695 
0696     def getmark(self, id):
0697         for marker in self._markers:
0698             if id == marker[0]:
0699                 return marker
0700         raise Error, 'marker %r does not exist' % (id,)
0701 
0702     def getmarkers(self):
0703         if len(self._markers) == 0:
0704             return None
0705         return self._markers
0706 
0707     def tell(self):
0708         return self._nframeswritten
0709 
0710     def writeframesraw(self, data):
0711         self._ensure_header_written(len(data))
0712         nframes = len(data) / (self._sampwidth * self._nchannels)
0713         if self._convert:
0714             data = self._convert(data)
0715         self._file.write(data)
0716         self._nframeswritten = self._nframeswritten + nframes
0717         self._datawritten = self._datawritten + len(data)
0718 
0719     def writeframes(self, data):
0720         self.writeframesraw(data)
0721         if self._nframeswritten != self._nframes or \
0722               self._datalength != self._datawritten:
0723             self._patchheader()
0724 
0725     def close(self):
0726         self._ensure_header_written(0)
0727         if self._datawritten & 1:
0728             # quick pad to even size
0729             self._file.write(chr(0))
0730             self._datawritten = self._datawritten + 1
0731         self._writemarkers()
0732         if self._nframeswritten != self._nframes or \
0733               self._datalength != self._datawritten or \
0734               self._marklength:
0735             self._patchheader()
0736         if self._comp:
0737             self._comp.CloseCompressor()
0738             self._comp = None
0739         self._file.flush()
0740         self._file = None
0741 
0742     #
0743     # Internal methods.
0744     #
0745 
0746     def _comp_data(self, data):
0747         import cl
0748         dummy = self._comp.SetParam(cl.FRAME_BUFFER_SIZE, len(data))
0749         dummy = self._comp.SetParam(cl.COMPRESSED_BUFFER_SIZE, len(data))
0750         return self._comp.Compress(self._nframes, data)
0751 
0752     def _lin2ulaw(self, data):
0753         import audioop
0754         return audioop.lin2ulaw(data, 2)
0755 
0756     def _lin2adpcm(self, data):
0757         import audioop
0758         if not hasattr(self, '_adpcmstate'):
0759             self._adpcmstate = None
0760         data, self._adpcmstate = audioop.lin2adpcm(data, 2,
0761                                self._adpcmstate)
0762         return data
0763 
0764     def _ensure_header_written(self, datasize):
0765         if not self._nframeswritten:
0766             if self._comptype in ('ULAW', 'ALAW'):
0767                 if not self._sampwidth:
0768                     self._sampwidth = 2
0769                 if self._sampwidth != 2:
0770                     raise Error, 'sample width must be 2 when compressing with ULAW or ALAW'
0771             if self._comptype == 'G722':
0772                 if not self._sampwidth:
0773                     self._sampwidth = 2
0774                 if self._sampwidth != 2:
0775                     raise Error, 'sample width must be 2 when compressing with G7.22 (ADPCM)'
0776             if not self._nchannels:
0777                 raise Error, '# channels not specified'
0778             if not self._sampwidth:
0779                 raise Error, 'sample width not specified'
0780             if not self._framerate:
0781                 raise Error, 'sampling rate not specified'
0782             self._write_header(datasize)
0783 
0784     def _init_compression(self):
0785         if self._comptype == 'G722':
0786             self._convert = self._lin2adpcm
0787             return
0788         try:
0789             import cl
0790         except ImportError:
0791             if self._comptype == 'ULAW':
0792                 try:
0793                     import audioop
0794                     self._convert = self._lin2ulaw
0795                     return
0796                 except ImportError:
0797                     pass
0798             raise Error, 'cannot write compressed AIFF-C files'
0799         if self._comptype == 'ULAW':
0800             scheme = cl.G711_ULAW
0801         elif self._comptype == 'ALAW':
0802             scheme = cl.G711_ALAW
0803         else:
0804             raise Error, 'unsupported compression type'
0805         self._comp = cl.OpenCompressor(scheme)
0806         params = [cl.ORIGINAL_FORMAT, 0,
0807               cl.BITS_PER_COMPONENT, self._sampwidth * 8,
0808               cl.FRAME_RATE, self._framerate,
0809               cl.FRAME_BUFFER_SIZE, 100,
0810               cl.COMPRESSED_BUFFER_SIZE, 100]
0811         if self._nchannels == 1:
0812             params[1] = cl.MONO
0813         elif self._nchannels == 2:
0814             params[1] = cl.STEREO_INTERLEAVED
0815         else:
0816             raise Error, 'cannot compress more than 2 channels'
0817         self._comp.SetParams(params)
0818         # the compressor produces a header which we ignore
0819         dummy = self._comp.Compress(0, '')
0820         self._convert = self._comp_data
0821 
0822     def _write_header(self, initlength):
0823         if self._aifc and self._comptype != 'NONE':
0824             self._init_compression()
0825         self._file.write('FORM')
0826         if not self._nframes:
0827             self._nframes = initlength / (self._nchannels * self._sampwidth)
0828         self._datalength = self._nframes * self._nchannels * self._sampwidth
0829         if self._datalength & 1:
0830             self._datalength = self._datalength + 1
0831         if self._aifc:
0832             if self._comptype in ('ULAW', 'ALAW'):
0833                 self._datalength = self._datalength / 2
0834                 if self._datalength & 1:
0835                     self._datalength = self._datalength + 1
0836             elif self._comptype == 'G722':
0837                 self._datalength = (self._datalength + 3) / 4
0838                 if self._datalength & 1:
0839                     self._datalength = self._datalength + 1
0840         self._form_length_pos = self._file.tell()
0841         commlength = self._write_form_length(self._datalength)
0842         if self._aifc:
0843             self._file.write('AIFC')
0844             self._file.write('FVER')
0845             _write_long(self._file, 4)
0846             _write_long(self._file, self._version)
0847         else:
0848             self._file.write('AIFF')
0849         self._file.write('COMM')
0850         _write_long(self._file, commlength)
0851         _write_short(self._file, self._nchannels)
0852         self._nframes_pos = self._file.tell()
0853         _write_long(self._file, self._nframes)
0854         _write_short(self._file, self._sampwidth * 8)
0855         _write_float(self._file, self._framerate)
0856         if self._aifc:
0857             self._file.write(self._comptype)
0858             _write_string(self._file, self._compname)
0859         self._file.write('SSND')
0860         self._ssnd_length_pos = self._file.tell()
0861         _write_long(self._file, self._datalength + 8)
0862         _write_long(self._file, 0)
0863         _write_long(self._file, 0)
0864 
0865     def _write_form_length(self, datalength):
0866         if self._aifc:
0867             commlength = 18 + 5 + len(self._compname)
0868             if commlength & 1:
0869                 commlength = commlength + 1
0870             verslength = 12
0871         else:
0872             commlength = 18
0873             verslength = 0
0874         _write_long(self._file, 4 + verslength + self._marklength + \
0875                     8 + commlength + 16 + datalength)
0876         return commlength
0877 
0878     def _patchheader(self):
0879         curpos = self._file.tell()
0880         if self._datawritten & 1:
0881             datalength = self._datawritten + 1
0882             self._file.write(chr(0))
0883         else:
0884             datalength = self._datawritten
0885         if datalength == self._datalength and \
0886               self._nframes == self._nframeswritten and \
0887               self._marklength == 0:
0888             self._file.seek(curpos, 0)
0889             return
0890         self._file.seek(self._form_length_pos, 0)
0891         dummy = self._write_form_length(datalength)
0892         self._file.seek(self._nframes_pos, 0)
0893         _write_long(self._file, self._nframeswritten)
0894         self._file.seek(self._ssnd_length_pos, 0)
0895         _write_long(self._file, datalength + 8)
0896         self._file.seek(curpos, 0)
0897         self._nframes = self._nframeswritten
0898         self._datalength = datalength
0899 
0900     def _writemarkers(self):
0901         if len(self._markers) == 0:
0902             return
0903         self._file.write('MARK')
0904         length = 2
0905         for marker in self._markers:
0906             id, pos, name = marker
0907             length = length + len(name) + 1 + 6
0908             if len(name) & 1 == 0:
0909                 length = length + 1
0910         _write_long(self._file, length)
0911         self._marklength = length + 8
0912         _write_short(self._file, len(self._markers))
0913         for marker in self._markers:
0914             id, pos, name = marker
0915             _write_short(self._file, id)
0916             _write_long(self._file, pos)
0917             _write_string(self._file, name)
0918 
0919 def open(f, mode=None):
0920     if mode is None:
0921         if hasattr(f, 'mode'):
0922             mode = f.mode
0923         else:
0924             mode = 'rb'
0925     if mode in ('r', 'rb'):
0926         return Aifc_read(f)
0927     elif mode in ('w', 'wb'):
0928         return Aifc_write(f)
0929     else:
0930         raise Error, "mode must be 'r', 'rb', 'w', or 'wb'"
0931 
0932 openfp = open # B/W compatibility
0933 
0934 if __name__ == '__main__':
0935     import sys
0936     if not sys.argv[1:]:
0937         sys.argv.append('/usr/demos/data/audio/bach.aiff')
0938     fn = sys.argv[1]
0939     f = open(fn, 'r')
0940     print "Reading", fn
0941     print "nchannels =", f.getnchannels()
0942     print "nframes   =", f.getnframes()
0943     print "sampwidth =", f.getsampwidth()
0944     print "framerate =", f.getframerate()
0945     print "comptype  =", f.getcomptype()
0946     print "compname  =", f.getcompname()
0947     if sys.argv[2:]:
0948         gn = sys.argv[2]
0949         print "Writing", gn
0950         g = open(gn, 'w')
0951         g.setparams(f.getparams())
0952         while 1:
0953             data = f.readframes(1024)
0954             if not data:
0955                 break
0956             g.writeframes(data)
0957         g.close()
0958         f.close()
0959         print "Done."
0960 

Generated by PyXR 0.9.4
SourceForge.net Logo