#changed all email address to go through python.org
[python/dscho.git] / Lib / sunau.py
blob677b298998b102d6bf50d9b0f0c1fe5d98d7d9e9
1 # Stuff to parse Sun and NeXT audio files.
3 # An audio consists of a header followed by the data. The structure
4 # of the header is as follows.
6 # +---------------+
7 # | magic word |
8 # +---------------+
9 # | header size |
10 # +---------------+
11 # | data size |
12 # +---------------+
13 # | encoding |
14 # +---------------+
15 # | sample rate |
16 # +---------------+
17 # | # of channels |
18 # +---------------+
19 # | info |
20 # | |
21 # +---------------+
23 # The magic word consists of the 4 characters '.snd'. Apart from the
24 # info field, all header fields are 4 bytes in size. They are all
25 # 32-bit unsigned integers encoded in big-endian byte order.
27 # The header size really gives the start of the data.
28 # The data size is the physical size of the data. From the other
29 # parameter the number of frames can be calculated.
30 # The encoding gives the way in which audio samples are encoded.
31 # Possible values are listed below.
32 # The info field currently consists of an ASCII string giving a
33 # human-readable description of the audio file. The info field is
34 # padded with NUL bytes to the header size.
36 # Usage.
38 # Reading audio files:
39 # f = sunau.open(file, 'r')
40 # where file is either the name of a file or an open file pointer.
41 # The open file pointer must have methods read(), seek(), and close().
42 # When the setpos() and rewind() methods are not used, the seek()
43 # method is not necessary.
45 # This returns an instance of a class with the following public methods:
46 # getnchannels() -- returns number of audio channels (1 for
47 # mono, 2 for stereo)
48 # getsampwidth() -- returns sample width in bytes
49 # getframerate() -- returns sampling frequency
50 # getnframes() -- returns number of audio frames
51 # getcomptype() -- returns compression type ('NONE' for AIFF files)
52 # getcompname() -- returns human-readable version of
53 # compression type ('not compressed' for AIFF files)
54 # getparams() -- returns a tuple consisting of all of the
55 # above in the above order
56 # getmarkers() -- returns None (for compatibility with the
57 # aifc module)
58 # getmark(id) -- raises an error since the mark does not
59 # exist (for compatibility with the aifc module)
60 # readframes(n) -- returns at most n frames of audio
61 # rewind() -- rewind to the beginning of the audio stream
62 # setpos(pos) -- seek to the specified position
63 # tell() -- return the current position
64 # close() -- close the instance (make it unusable)
65 # The position returned by tell() and the position given to setpos()
66 # are compatible and have nothing to do with the actual postion in the
67 # file.
68 # The close() method is called automatically when the class instance
69 # is destroyed.
71 # Writing audio files:
72 # f = sunau.open(file, 'w')
73 # where file is either the name of a file or an open file pointer.
74 # The open file pointer must have methods write(), tell(), seek(), and
75 # close().
77 # This returns an instance of a class with the following public methods:
78 # setnchannels(n) -- set the number of channels
79 # setsampwidth(n) -- set the sample width
80 # setframerate(n) -- set the frame rate
81 # setnframes(n) -- set the number of frames
82 # setcomptype(type, name)
83 # -- set the compression type and the
84 # human-readable compression type
85 # setparams(nchannels, sampwidth, framerate, nframes, comptype, compname)
86 # -- set all parameters at once
87 # tell() -- return current position in output file
88 # writeframesraw(data)
89 # -- write audio frames without pathing up the
90 # file header
91 # writeframes(data)
92 # -- write audio frames and patch up the file header
93 # close() -- patch up the file header and close the
94 # output file
95 # You should set the parameters before the first writeframesraw or
96 # writeframes. The total number of frames does not need to be set,
97 # but when it is set to the correct value, the header does not have to
98 # be patched up.
99 # It is best to first set all parameters, perhaps possibly the
100 # compression type, and then write audio frames using writeframesraw.
101 # When all frames have been written, either call writeframes('') or
102 # close() to patch up the sizes in the header.
103 # The close() method is called automatically when the class instance
104 # is destroyed.
106 # from <multimedia/audio_filehdr.h>
107 AUDIO_FILE_MAGIC = 0x2e736e64
108 AUDIO_FILE_ENCODING_MULAW_8 = 1
109 AUDIO_FILE_ENCODING_LINEAR_8 = 2
110 AUDIO_FILE_ENCODING_LINEAR_16 = 3
111 AUDIO_FILE_ENCODING_LINEAR_24 = 4
112 AUDIO_FILE_ENCODING_LINEAR_32 = 5
113 AUDIO_FILE_ENCODING_FLOAT = 6
114 AUDIO_FILE_ENCODING_DOUBLE = 7
115 AUDIO_FILE_ENCODING_ADPCM_G721 = 23
116 AUDIO_FILE_ENCODING_ADPCM_G722 = 24
117 AUDIO_FILE_ENCODING_ADPCM_G723_3 = 25
118 AUDIO_FILE_ENCODING_ADPCM_G723_5 = 26
119 AUDIO_FILE_ENCODING_ALAW_8 = 27
121 # from <multimedia/audio_hdr.h>
122 AUDIO_UNKNOWN_SIZE = 0xFFFFFFFFL # ((unsigned)(~0))
124 _simple_encodings = [AUDIO_FILE_ENCODING_MULAW_8,
125 AUDIO_FILE_ENCODING_LINEAR_8,
126 AUDIO_FILE_ENCODING_LINEAR_16,
127 AUDIO_FILE_ENCODING_LINEAR_24,
128 AUDIO_FILE_ENCODING_LINEAR_32,
129 AUDIO_FILE_ENCODING_ALAW_8]
131 def _read_u32(file):
132 x = 0L
133 for i in range(4):
134 byte = file.read(1)
135 if byte == '':
136 raise EOFError
137 x = x*256 + ord(byte)
138 return x
140 def _write_u32(file, x):
141 data = []
142 for i in range(4):
143 d, m = divmod(x, 256)
144 data.insert(0, m)
145 x = d
146 for i in range(4):
147 file.write(chr(int(data[i])))
149 class Au_read:
150 access _file, _soundpos, _hdr_size, _data_size, _encoding, \
151 _sampwidth, _framesize, _framerate, _nchannels, \
152 _info: private
154 def __init__(self, f):
155 if type(f) == type(''):
156 import __builtin__
157 f = __builtin__.open(f, 'r')
158 self.initfp(f)
160 def __del__(self):
161 if self._file:
162 self.close()
164 def initfp(self, file):
165 self._file = file
166 self._soundpos = 0
167 magic = int(_read_u32(file))
168 if magic != AUDIO_FILE_MAGIC:
169 raise Error, 'bad magic number'
170 self._hdr_size = int(_read_u32(file))
171 if self._hdr_size < 24:
172 raise Error, 'header size too small'
173 if self._hdr_size > 100:
174 raise Error, 'header size rediculously large'
175 self._data_size = _read_u32(file)
176 if self._data_size != AUDIO_UNKNOWN_SIZE:
177 self._data_size = int(self._data_size)
178 self._encoding = int(_read_u32(file))
179 if self._encoding not in _simple_encodings:
180 raise Error, 'encoding not (yet) supported'
181 if self._encoding in (AUDIO_FILE_ENCODING_MULAW_8,
182 AUDIO_FILE_ENCODING_LINEAR_8,
183 AUDIO_FILE_ENCODING_ALAW_8):
184 self._sampwidth = 2
185 self._framesize = 1
186 elif self._encoding == AUDIO_FILE_ENCODING_LINEAR_16:
187 self._framesize = self._sampwidth = 2
188 elif self._encoding == AUDIO_FILE_ENCODING_LINEAR_24:
189 self._framesize = self._sampwidth = 3
190 elif self._encoding == AUDIO_FILE_ENCODING_LINEAR_32:
191 self._framesize = self._sampwidth = 4
192 else:
193 raise Error, 'unknown encoding'
194 self._framerate = int(_read_u32(file))
195 self._nchannels = int(_read_u32(file))
196 self._framesize = self._framesize * self._nchannels
197 if self._hdr_size > 24:
198 self._info = file.read(self._hdr_size - 24)
199 for i in range(len(self._info)):
200 if self._info[i] == '\0':
201 self._info = self._info[:i]
202 break
203 else:
204 self._info = ''
206 def getfp(self):
207 return self._file
209 def getnchannels(self):
210 return self._nchannels
212 def getsampwidth(self):
213 return self._sampwidth
215 def getframerate(self):
216 return self._framerate
218 def getnframes(self):
219 if self._data_size == AUDIO_UNKNOWN_SIZE:
220 return AUDIO_UNKNOWN_SIZE
221 if self._encoding in _simple_encodings:
222 return self._data_size / self._framesize
223 return 0 # XXX--must do some arithmetic here
225 def getcomptype(self):
226 if self._encoding == AUDIO_FILE_ENCODING_MULAW_8:
227 return 'ULAW'
228 elif self._encoding == AUDIO_FILE_ENCODING_ALAW_8:
229 return 'ALAW'
230 else:
231 return 'NONE'
233 def getcompname(self):
234 if self._encoding == AUDIO_FILE_ENCODING_MULAW_8:
235 return 'CCITT G.711 u-law'
236 elif self._encoding == AUDIO_FILE_ENCODING_ALAW_8:
237 return 'CCITT G.711 A-law'
238 else:
239 return 'not compressed'
241 def getparams(self):
242 return self.getnchannels(), self.getsampwidth(), \
243 self.getframerate(), self.getnframes(), \
244 self.getcomptype(), self.getcompname()
246 def getmarkers(self):
247 return None
249 def getmark(self, id):
250 raise Error, 'no marks'
252 def readframes(self, nframes):
253 if self._encoding in _simple_encodings:
254 if nframes == AUDIO_UNKNOWN_SIZE:
255 data = self._file.read()
256 else:
257 data = self._file.read(nframes * self._framesize * self._nchannels)
258 if self._encoding == AUDIO_FILE_ENCODING_MULAW_8:
259 import audioop
260 data = audioop.ulaw2lin(data, self._sampwidth)
261 return data
262 return None # XXX--not implemented yet
264 def rewind(self):
265 self._soundpos = 0
266 self._file.seek(self._hdr_size)
268 def tell(self):
269 return self._soundpos
271 def setpos(self, pos):
272 if pos < 0 or pos > self.getnframes():
273 raise Error, 'position not in range'
274 self._file.seek(pos * self._framesize + self._hdr_size)
275 self._soundpos = pos
277 def close(self):
278 self._file = None
280 class Au_write:
281 access _file, _framerate, _nchannels, _sampwidth, _framesize, \
282 _nframes, _nframeswritten, _datawritten, _info, \
283 _comptype: private
285 def __init__(self, f):
286 if type(f) == type(''):
287 import __builtin__
288 f = __builtin__.open(f, 'w')
289 self.initfp(f)
291 def __del__(self):
292 if self._file:
293 self.close()
295 def initfp(self, file):
296 self._file = file
297 self._framerate = 0
298 self._nchannels = 0
299 self._sampwidth = 0
300 self._framesize = 0
301 self._nframes = AUDIO_UNKNOWN_SIZE
302 self._nframeswritten = 0
303 self._datawritten = 0
304 self._datalength = 0
305 self._info = ''
306 self._comptype = 'ULAW' # default is U-law
308 def setnchannels(self, nchannels):
309 if self._nframeswritten:
310 raise Error, 'cannot change parameters after starting to write'
311 if nchannels not in (1, 2, 4):
312 raise Error, 'only 1, 2, or 4 channels supported'
313 self._nchannels = nchannels
315 def getnchannels(self):
316 if not self._nchannels:
317 raise Error, 'number of channels not set'
318 return self._nchannels
320 def setsampwidth(self, sampwidth):
321 if self._nframeswritten:
322 raise Error, 'cannot change parameters after starting to write'
323 if sampwidth not in (1, 2, 4):
324 raise Error, 'bad sample width'
325 self._sampwidth = sampwidth
327 def getsampwidth(self):
328 if not self._framerate:
329 raise Error, 'sample width not specified'
330 return self._sampwidth
332 def setframerate(self, framerate):
333 if self._nframeswritten:
334 raise Error, 'cannot change parameters after starting to write'
335 self._framerate = framerate
337 def getframerate(self):
338 if not self._framerate:
339 raise Error, 'frame rate not set'
340 return self._framerate
342 def setnframes(self, nframes):
343 if self._nframeswritten:
344 raise Error, 'cannot change parameters after starting to write'
345 if nframes < 0:
346 raise Error, '# of frames cannot be negative'
347 self._nframes = nframes
349 def getnframes(self):
350 return self._nframeswritten
352 def setcomptype(self, type, name):
353 if type in ('NONE', 'ULAW'):
354 self._comptype = type
355 else:
356 raise Error, 'unknown compression type'
358 def getcomptype(self):
359 return self._comptype
361 def getcompname(self):
362 if self._comptype == 'ULAW':
363 return 'CCITT G.711 u-law'
364 elif self._comptype == 'ALAW':
365 return 'CCITT G.711 A-law'
366 else:
367 return 'not compressed'
369 def setparams(self, (nchannels, sampwidth, framerate, nframes, comptype, compname)):
370 self.setnchannels(nchannels)
371 self.setsampwidth(sampwidth)
372 self.setframerate(framerate)
373 self.setnframes(nframes)
374 self.setcomptype(comptype, compname)
376 def getparams(self):
377 return self.getnchannels(), self.getsampwidth(), \
378 self.getframerate(), self.getnframes(), \
379 self.getcomptype(), self.getcompname()
381 def tell(self):
382 return self._nframeswritten
384 def writeframesraw(self, data):
385 self._ensure_header_written()
386 nframes = len(data) / self._framesize
387 if self._comptype == 'ULAW':
388 import audioop
389 data = audioop.lin2ulaw(data, self._sampwidth)
390 self._file.write(data)
391 self._nframeswritten = self._nframeswritten + nframes
392 self._datawritten = self._datawritten + len(data)
394 def writeframes(self, data):
395 self.writeframesraw(data)
396 if self._nframeswritten != self._nframes or \
397 self._datalength != self._datawritten:
398 self._patchheader()
400 def close(self):
401 self._ensure_header_written()
402 if self._nframeswritten != self._nframes or \
403 self._datalength != self._datawritten:
404 self._patchheader()
405 self._file.flush()
406 self._file = None
409 # private methods
411 access *: private
413 def _ensure_header_written(self):
414 if not self._nframeswritten:
415 if not self._nchannels:
416 raise Error, '# of channels not specified'
417 if not self._sampwidth:
418 raise Error, 'sample width not specified'
419 if not self._framerate:
420 raise Error, 'frame rate not specified'
421 self._write_header()
423 def _write_header(self):
424 if self._comptype == 'NONE':
425 if self._sampwidth == 1:
426 encoding = AUDIO_FILE_ENCODING_LINEAR_8
427 self._framesize = 1
428 elif self._sampwidth == 2:
429 encoding = AUDIO_FILE_ENCODING_LINEAR_16
430 self._framesize = 2
431 elif self._sampwidth == 4:
432 encoding = AUDIO_FILE_ENCODING_LINEAR_32
433 self._framesize = 4
434 else:
435 raise Error, 'internal error'
436 elif self._comptype == 'ULAW':
437 encoding = AUDIO_FILE_ENCODING_MULAW_8
438 self._framesize = 1
439 else:
440 raise Error, 'internal error'
441 self._framesize = self._framesize * self._nchannels
442 _write_u32(self._file, AUDIO_FILE_MAGIC)
443 header_size = 25 + len(self._info)
444 header_size = (header_size + 7) & ~7
445 _write_u32(self._file, header_size)
446 if self._nframes == AUDIO_UNKNOWN_SIZE:
447 length = AUDIO_UNKNOWN_SIZE
448 else:
449 length = self._nframes * self._framesize
450 _write_u32(self._file, length)
451 self._datalength = length
452 _write_u32(self._file, encoding)
453 _write_u32(self._file, self._framerate)
454 _write_u32(self._file, self._nchannels)
455 self._file.write(self._info)
456 self._file.write('\0'*(header_size - len(self._info) - 24))
458 def _patchheader(self):
459 self._file.seek(8)
460 _write_u32(self._file, self._datawritten)
461 self._datalength = self._datawritten
462 self._file.seek(0, 2)
464 def open(f, mode):
465 if mode == 'r':
466 return Au_read(f)
467 elif mode == 'w':
468 return Au_write(f)
469 else:
470 raise Error, "mode must be 'r' or 'w'"
472 openfp = open