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