PyXR

c:\python24\lib \ sunau.py



0001 """Stuff to parse Sun and NeXT audio files.
0002 
0003 An audio file consists of a header followed by the data.  The structure
0004 of the header is as follows.
0005 
0006         +---------------+
0007         | magic word    |
0008         +---------------+
0009         | header size   |
0010         +---------------+
0011         | data size     |
0012         +---------------+
0013         | encoding      |
0014         +---------------+
0015         | sample rate   |
0016         +---------------+
0017         | # of channels |
0018         +---------------+
0019         | info          |
0020         |               |
0021         +---------------+
0022 
0023 The magic word consists of the 4 characters '.snd'.  Apart from the
0024 info field, all header fields are 4 bytes in size.  They are all
0025 32-bit unsigned integers encoded in big-endian byte order.
0026 
0027 The header size really gives the start of the data.
0028 The data size is the physical size of the data.  From the other
0029 parameters the number of frames can be calculated.
0030 The encoding gives the way in which audio samples are encoded.
0031 Possible values are listed below.
0032 The info field currently consists of an ASCII string giving a
0033 human-readable description of the audio file.  The info field is
0034 padded with NUL bytes to the header size.
0035 
0036 Usage.
0037 
0038 Reading audio files:
0039         f = sunau.open(file, 'r')
0040 where file is either the name of a file or an open file pointer.
0041 The open file pointer must have methods read(), seek(), and close().
0042 When the setpos() and rewind() methods are not used, the seek()
0043 method is not  necessary.
0044 
0045 This returns an instance of a class with the following public methods:
0046         getnchannels()  -- returns number of audio channels (1 for
0047                            mono, 2 for stereo)
0048         getsampwidth()  -- returns sample width in bytes
0049         getframerate()  -- returns sampling frequency
0050         getnframes()    -- returns number of audio frames
0051         getcomptype()   -- returns compression type ('NONE' or 'ULAW')
0052         getcompname()   -- returns human-readable version of
0053                            compression type ('not compressed' matches 'NONE')
0054         getparams()     -- returns a tuple consisting of all of the
0055                            above in the above order
0056         getmarkers()    -- returns None (for compatibility with the
0057                            aifc module)
0058         getmark(id)     -- raises an error since the mark does not
0059                            exist (for compatibility with the aifc module)
0060         readframes(n)   -- returns at most n frames of audio
0061         rewind()        -- rewind to the beginning of the audio stream
0062         setpos(pos)     -- seek to the specified position
0063         tell()          -- return the current position
0064         close()         -- close the instance (make it unusable)
0065 The position returned by tell() and the position given to setpos()
0066 are compatible and have nothing to do with the actual position in the
0067 file.
0068 The close() method is called automatically when the class instance
0069 is destroyed.
0070 
0071 Writing audio files:
0072         f = sunau.open(file, 'w')
0073 where file is either the name of a file or an open file pointer.
0074 The open file pointer must have methods write(), tell(), seek(), and
0075 close().
0076 
0077 This returns an instance of a class with the following public methods:
0078         setnchannels(n) -- set the number of channels
0079         setsampwidth(n) -- set the sample width
0080         setframerate(n) -- set the frame rate
0081         setnframes(n)   -- set the number of frames
0082         setcomptype(type, name)
0083                         -- set the compression type and the
0084                            human-readable compression type
0085         setparams(tuple)-- set all parameters at once
0086         tell()          -- return current position in output file
0087         writeframesraw(data)
0088                         -- write audio frames without pathing up the
0089                            file header
0090         writeframes(data)
0091                         -- write audio frames and patch up the file header
0092         close()         -- patch up the file header and close the
0093                            output file
0094 You should set the parameters before the first writeframesraw or
0095 writeframes.  The total number of frames does not need to be set,
0096 but when it is set to the correct value, the header does not have to
0097 be patched up.
0098 It is best to first set all parameters, perhaps possibly the
0099 compression type, and then write audio frames using writeframesraw.
0100 When all frames have been written, either call writeframes('') or
0101 close() to patch up the sizes in the header.
0102 The close() method is called automatically when the class instance
0103 is destroyed.
0104 """
0105 
0106 # from <multimedia/audio_filehdr.h>
0107 AUDIO_FILE_MAGIC = 0x2e736e64
0108 AUDIO_FILE_ENCODING_MULAW_8 = 1
0109 AUDIO_FILE_ENCODING_LINEAR_8 = 2
0110 AUDIO_FILE_ENCODING_LINEAR_16 = 3
0111 AUDIO_FILE_ENCODING_LINEAR_24 = 4
0112 AUDIO_FILE_ENCODING_LINEAR_32 = 5
0113 AUDIO_FILE_ENCODING_FLOAT = 6
0114 AUDIO_FILE_ENCODING_DOUBLE = 7
0115 AUDIO_FILE_ENCODING_ADPCM_G721 = 23
0116 AUDIO_FILE_ENCODING_ADPCM_G722 = 24
0117 AUDIO_FILE_ENCODING_ADPCM_G723_3 = 25
0118 AUDIO_FILE_ENCODING_ADPCM_G723_5 = 26
0119 AUDIO_FILE_ENCODING_ALAW_8 = 27
0120 
0121 # from <multimedia/audio_hdr.h>
0122 AUDIO_UNKNOWN_SIZE = 0xFFFFFFFFL        # ((unsigned)(~0))
0123 
0124 _simple_encodings = [AUDIO_FILE_ENCODING_MULAW_8,
0125                      AUDIO_FILE_ENCODING_LINEAR_8,
0126                      AUDIO_FILE_ENCODING_LINEAR_16,
0127                      AUDIO_FILE_ENCODING_LINEAR_24,
0128                      AUDIO_FILE_ENCODING_LINEAR_32,
0129                      AUDIO_FILE_ENCODING_ALAW_8]
0130 
0131 class Error(Exception):
0132     pass
0133 
0134 def _read_u32(file):
0135     x = 0L
0136     for i in range(4):
0137         byte = file.read(1)
0138         if byte == '':
0139             raise EOFError
0140         x = x*256 + ord(byte)
0141     return x
0142 
0143 def _write_u32(file, x):
0144     data = []
0145     for i in range(4):
0146         d, m = divmod(x, 256)
0147         data.insert(0, m)
0148         x = d
0149     for i in range(4):
0150         file.write(chr(int(data[i])))
0151 
0152 class Au_read:
0153 
0154     def __init__(self, f):
0155         if type(f) == type(''):
0156             import __builtin__
0157             f = __builtin__.open(f, 'rb')
0158         self.initfp(f)
0159 
0160     def __del__(self):
0161         if self._file:
0162             self.close()
0163 
0164     def initfp(self, file):
0165         self._file = file
0166         self._soundpos = 0
0167         magic = int(_read_u32(file))
0168         if magic != AUDIO_FILE_MAGIC:
0169             raise Error, 'bad magic number'
0170         self._hdr_size = int(_read_u32(file))
0171         if self._hdr_size < 24:
0172             raise Error, 'header size too small'
0173         if self._hdr_size > 100:
0174             raise Error, 'header size ridiculously large'
0175         self._data_size = _read_u32(file)
0176         if self._data_size != AUDIO_UNKNOWN_SIZE:
0177             self._data_size = int(self._data_size)
0178         self._encoding = int(_read_u32(file))
0179         if self._encoding not in _simple_encodings:
0180             raise Error, 'encoding not (yet) supported'
0181         if self._encoding in (AUDIO_FILE_ENCODING_MULAW_8,
0182                   AUDIO_FILE_ENCODING_ALAW_8):
0183             self._sampwidth = 2
0184             self._framesize = 1
0185         elif self._encoding == AUDIO_FILE_ENCODING_LINEAR_8:
0186             self._framesize = self._sampwidth = 1
0187         elif self._encoding == AUDIO_FILE_ENCODING_LINEAR_16:
0188             self._framesize = self._sampwidth = 2
0189         elif self._encoding == AUDIO_FILE_ENCODING_LINEAR_24:
0190             self._framesize = self._sampwidth = 3
0191         elif self._encoding == AUDIO_FILE_ENCODING_LINEAR_32:
0192             self._framesize = self._sampwidth = 4
0193         else:
0194             raise Error, 'unknown encoding'
0195         self._framerate = int(_read_u32(file))
0196         self._nchannels = int(_read_u32(file))
0197         self._framesize = self._framesize * self._nchannels
0198         if self._hdr_size > 24:
0199             self._info = file.read(self._hdr_size - 24)
0200             for i in range(len(self._info)):
0201                 if self._info[i] == '\0':
0202                     self._info = self._info[:i]
0203                     break
0204         else:
0205             self._info = ''
0206 
0207     def getfp(self):
0208         return self._file
0209 
0210     def getnchannels(self):
0211         return self._nchannels
0212 
0213     def getsampwidth(self):
0214         return self._sampwidth
0215 
0216     def getframerate(self):
0217         return self._framerate
0218 
0219     def getnframes(self):
0220         if self._data_size == AUDIO_UNKNOWN_SIZE:
0221             return AUDIO_UNKNOWN_SIZE
0222         if self._encoding in _simple_encodings:
0223             return self._data_size / self._framesize
0224         return 0                # XXX--must do some arithmetic here
0225 
0226     def getcomptype(self):
0227         if self._encoding == AUDIO_FILE_ENCODING_MULAW_8:
0228             return 'ULAW'
0229         elif self._encoding == AUDIO_FILE_ENCODING_ALAW_8:
0230             return 'ALAW'
0231         else:
0232             return 'NONE'
0233 
0234     def getcompname(self):
0235         if self._encoding == AUDIO_FILE_ENCODING_MULAW_8:
0236             return 'CCITT G.711 u-law'
0237         elif self._encoding == AUDIO_FILE_ENCODING_ALAW_8:
0238             return 'CCITT G.711 A-law'
0239         else:
0240             return 'not compressed'
0241 
0242     def getparams(self):
0243         return self.getnchannels(), self.getsampwidth(), \
0244                   self.getframerate(), self.getnframes(), \
0245                   self.getcomptype(), self.getcompname()
0246 
0247     def getmarkers(self):
0248         return None
0249 
0250     def getmark(self, id):
0251         raise Error, 'no marks'
0252 
0253     def readframes(self, nframes):
0254         if self._encoding in _simple_encodings:
0255             if nframes == AUDIO_UNKNOWN_SIZE:
0256                 data = self._file.read()
0257             else:
0258                 data = self._file.read(nframes * self._framesize * self._nchannels)
0259             if self._encoding == AUDIO_FILE_ENCODING_MULAW_8:
0260                 import audioop
0261                 data = audioop.ulaw2lin(data, self._sampwidth)
0262             return data
0263         return None             # XXX--not implemented yet
0264 
0265     def rewind(self):
0266         self._soundpos = 0
0267         self._file.seek(self._hdr_size)
0268 
0269     def tell(self):
0270         return self._soundpos
0271 
0272     def setpos(self, pos):
0273         if pos < 0 or pos > self.getnframes():
0274             raise Error, 'position not in range'
0275         self._file.seek(pos * self._framesize + self._hdr_size)
0276         self._soundpos = pos
0277 
0278     def close(self):
0279         self._file = None
0280 
0281 class Au_write:
0282 
0283     def __init__(self, f):
0284         if type(f) == type(''):
0285             import __builtin__
0286             f = __builtin__.open(f, 'wb')
0287         self.initfp(f)
0288 
0289     def __del__(self):
0290         if self._file:
0291             self.close()
0292 
0293     def initfp(self, file):
0294         self._file = file
0295         self._framerate = 0
0296         self._nchannels = 0
0297         self._sampwidth = 0
0298         self._framesize = 0
0299         self._nframes = AUDIO_UNKNOWN_SIZE
0300         self._nframeswritten = 0
0301         self._datawritten = 0
0302         self._datalength = 0
0303         self._info = ''
0304         self._comptype = 'ULAW' # default is U-law
0305 
0306     def setnchannels(self, nchannels):
0307         if self._nframeswritten:
0308             raise Error, 'cannot change parameters after starting to write'
0309         if nchannels not in (1, 2, 4):
0310             raise Error, 'only 1, 2, or 4 channels supported'
0311         self._nchannels = nchannels
0312 
0313     def getnchannels(self):
0314         if not self._nchannels:
0315             raise Error, 'number of channels not set'
0316         return self._nchannels
0317 
0318     def setsampwidth(self, sampwidth):
0319         if self._nframeswritten:
0320             raise Error, 'cannot change parameters after starting to write'
0321         if sampwidth not in (1, 2, 4):
0322             raise Error, 'bad sample width'
0323         self._sampwidth = sampwidth
0324 
0325     def getsampwidth(self):
0326         if not self._framerate:
0327             raise Error, 'sample width not specified'
0328         return self._sampwidth
0329 
0330     def setframerate(self, framerate):
0331         if self._nframeswritten:
0332             raise Error, 'cannot change parameters after starting to write'
0333         self._framerate = framerate
0334 
0335     def getframerate(self):
0336         if not self._framerate:
0337             raise Error, 'frame rate not set'
0338         return self._framerate
0339 
0340     def setnframes(self, nframes):
0341         if self._nframeswritten:
0342             raise Error, 'cannot change parameters after starting to write'
0343         if nframes < 0:
0344             raise Error, '# of frames cannot be negative'
0345         self._nframes = nframes
0346 
0347     def getnframes(self):
0348         return self._nframeswritten
0349 
0350     def setcomptype(self, type, name):
0351         if type in ('NONE', 'ULAW'):
0352             self._comptype = type
0353         else:
0354             raise Error, 'unknown compression type'
0355 
0356     def getcomptype(self):
0357         return self._comptype
0358 
0359     def getcompname(self):
0360         if self._comptype == 'ULAW':
0361             return 'CCITT G.711 u-law'
0362         elif self._comptype == 'ALAW':
0363             return 'CCITT G.711 A-law'
0364         else:
0365             return 'not compressed'
0366 
0367     def setparams(self, (nchannels, sampwidth, framerate, nframes, comptype, compname)):
0368         self.setnchannels(nchannels)
0369         self.setsampwidth(sampwidth)
0370         self.setframerate(framerate)
0371         self.setnframes(nframes)
0372         self.setcomptype(comptype, compname)
0373 
0374     def getparams(self):
0375         return self.getnchannels(), self.getsampwidth(), \
0376                   self.getframerate(), self.getnframes(), \
0377                   self.getcomptype(), self.getcompname()
0378 
0379     def tell(self):
0380         return self._nframeswritten
0381 
0382     def writeframesraw(self, data):
0383         self._ensure_header_written()
0384         nframes = len(data) / self._framesize
0385         if self._comptype == 'ULAW':
0386             import audioop
0387             data = audioop.lin2ulaw(data, self._sampwidth)
0388         self._file.write(data)
0389         self._nframeswritten = self._nframeswritten + nframes
0390         self._datawritten = self._datawritten + len(data)
0391 
0392     def writeframes(self, data):
0393         self.writeframesraw(data)
0394         if self._nframeswritten != self._nframes or \
0395                   self._datalength != self._datawritten:
0396             self._patchheader()
0397 
0398     def close(self):
0399         self._ensure_header_written()
0400         if self._nframeswritten != self._nframes or \
0401                   self._datalength != self._datawritten:
0402             self._patchheader()
0403         self._file.flush()
0404         self._file = None
0405 
0406     #
0407     # private methods
0408     #
0409 
0410     def _ensure_header_written(self):
0411         if not self._nframeswritten:
0412             if not self._nchannels:
0413                 raise Error, '# of channels not specified'
0414             if not self._sampwidth:
0415                 raise Error, 'sample width not specified'
0416             if not self._framerate:
0417                 raise Error, 'frame rate not specified'
0418             self._write_header()
0419 
0420     def _write_header(self):
0421         if self._comptype == 'NONE':
0422             if self._sampwidth == 1:
0423                 encoding = AUDIO_FILE_ENCODING_LINEAR_8
0424                 self._framesize = 1
0425             elif self._sampwidth == 2:
0426                 encoding = AUDIO_FILE_ENCODING_LINEAR_16
0427                 self._framesize = 2
0428             elif self._sampwidth == 4:
0429                 encoding = AUDIO_FILE_ENCODING_LINEAR_32
0430                 self._framesize = 4
0431             else:
0432                 raise Error, 'internal error'
0433         elif self._comptype == 'ULAW':
0434             encoding = AUDIO_FILE_ENCODING_MULAW_8
0435             self._framesize = 1
0436         else:
0437             raise Error, 'internal error'
0438         self._framesize = self._framesize * self._nchannels
0439         _write_u32(self._file, AUDIO_FILE_MAGIC)
0440         header_size = 25 + len(self._info)
0441         header_size = (header_size + 7) & ~7
0442         _write_u32(self._file, header_size)
0443         if self._nframes == AUDIO_UNKNOWN_SIZE:
0444             length = AUDIO_UNKNOWN_SIZE
0445         else:
0446             length = self._nframes * self._framesize
0447         _write_u32(self._file, length)
0448         self._datalength = length
0449         _write_u32(self._file, encoding)
0450         _write_u32(self._file, self._framerate)
0451         _write_u32(self._file, self._nchannels)
0452         self._file.write(self._info)
0453         self._file.write('\0'*(header_size - len(self._info) - 24))
0454 
0455     def _patchheader(self):
0456         self._file.seek(8)
0457         _write_u32(self._file, self._datawritten)
0458         self._datalength = self._datawritten
0459         self._file.seek(0, 2)
0460 
0461 def open(f, mode=None):
0462     if mode is None:
0463         if hasattr(f, 'mode'):
0464             mode = f.mode
0465         else:
0466             mode = 'rb'
0467     if mode in ('r', 'rb'):
0468         return Au_read(f)
0469     elif mode in ('w', 'wb'):
0470         return Au_write(f)
0471     else:
0472         raise Error, "mode must be 'r', 'rb', 'w', or 'wb'"
0473 
0474 openfp = open
0475 

Generated by PyXR 0.9.4
SourceForge.net Logo