blob: 5e8ac327728a1dec25b03af1dfc156bae2f19afb [file] [log] [blame]
Guido van Rossume7b146f2000-02-04 15:28:42 +00001"""Stuff to parse Sun and NeXT audio files.
2
3An audio consists of a header followed by the data. The structure
4of the header is as follows.
5
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 +---------------+
22
23The magic word consists of the 4 characters '.snd'. Apart from the
24info field, all header fields are 4 bytes in size. They are all
2532-bit unsigned integers encoded in big-endian byte order.
26
27The header size really gives the start of the data.
28The data size is the physical size of the data. From the other
29parameter the number of frames can be calculated.
30The encoding gives the way in which audio samples are encoded.
31Possible values are listed below.
32The info field currently consists of an ASCII string giving a
33human-readable description of the audio file. The info field is
34padded with NUL bytes to the header size.
35
36Usage.
37
38Reading audio files:
39 f = sunau.open(file, 'r')
40where file is either the name of a file or an open file pointer.
41The open file pointer must have methods read(), seek(), and close().
42When the setpos() and rewind() methods are not used, the seek()
43method is not necessary.
44
45This 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' or 'ULAW')
52 getcompname() -- returns human-readable version of
53 compression type ('not compressed' matches 'NONE')
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)
65The position returned by tell() and the position given to setpos()
66are compatible and have nothing to do with the actual postion in the
67file.
68The close() method is called automatically when the class instance
69is destroyed.
70
71Writing audio files:
72 f = sunau.open(file, 'w')
73where file is either the name of a file or an open file pointer.
74The open file pointer must have methods write(), tell(), seek(), and
75close().
76
77This 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(tuple)-- set all parameters at once
86 tell() -- return current position in output file
87 writeframesraw(data)
88 -- write audio frames without pathing up the
89 file header
90 writeframes(data)
91 -- write audio frames and patch up the file header
92 close() -- patch up the file header and close the
93 output file
94You should set the parameters before the first writeframesraw or
95writeframes. The total number of frames does not need to be set,
96but when it is set to the correct value, the header does not have to
97be patched up.
98It is best to first set all parameters, perhaps possibly the
99compression type, and then write audio frames using writeframesraw.
100When all frames have been written, either call writeframes('') or
101close() to patch up the sizes in the header.
102The close() method is called automatically when the class instance
103is destroyed.
104"""
Sjoerd Mullender43bf0bc1993-12-13 11:42:39 +0000105
106# from <multimedia/audio_filehdr.h>
107AUDIO_FILE_MAGIC = 0x2e736e64
108AUDIO_FILE_ENCODING_MULAW_8 = 1
109AUDIO_FILE_ENCODING_LINEAR_8 = 2
110AUDIO_FILE_ENCODING_LINEAR_16 = 3
111AUDIO_FILE_ENCODING_LINEAR_24 = 4
112AUDIO_FILE_ENCODING_LINEAR_32 = 5
113AUDIO_FILE_ENCODING_FLOAT = 6
114AUDIO_FILE_ENCODING_DOUBLE = 7
115AUDIO_FILE_ENCODING_ADPCM_G721 = 23
116AUDIO_FILE_ENCODING_ADPCM_G722 = 24
117AUDIO_FILE_ENCODING_ADPCM_G723_3 = 25
118AUDIO_FILE_ENCODING_ADPCM_G723_5 = 26
119AUDIO_FILE_ENCODING_ALAW_8 = 27
120
121# from <multimedia/audio_hdr.h>
122AUDIO_UNKNOWN_SIZE = 0xFFFFFFFFL # ((unsigned)(~0))
123
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]
130
Sjoerd Mullenderf33a69f1995-08-14 07:49:51 +0000131Error = 'sunau.Error'
132
Sjoerd Mullender43bf0bc1993-12-13 11:42:39 +0000133def _read_u32(file):
134 x = 0L
135 for i in range(4):
136 byte = file.read(1)
137 if byte == '':
138 raise EOFError
139 x = x*256 + ord(byte)
140 return x
141
142def _write_u32(file, x):
143 data = []
144 for i in range(4):
145 d, m = divmod(x, 256)
146 data.insert(0, m)
147 x = d
148 for i in range(4):
149 file.write(chr(int(data[i])))
150
151class Au_read:
Sjoerd Mullender2a451411993-12-20 09:36:01 +0000152
153 def __init__(self, f):
154 if type(f) == type(''):
Guido van Rossum3db6ebc1994-01-28 09:59:35 +0000155 import __builtin__
Guido van Rossum2013ba41999-02-05 20:55:16 +0000156 f = __builtin__.open(f, 'rb')
Sjoerd Mullender2a451411993-12-20 09:36:01 +0000157 self.initfp(f)
158
159 def __del__(self):
160 if self._file:
161 self.close()
162
Sjoerd Mullender43bf0bc1993-12-13 11:42:39 +0000163 def initfp(self, file):
164 self._file = file
165 self._soundpos = 0
166 magic = int(_read_u32(file))
167 if magic != AUDIO_FILE_MAGIC:
168 raise Error, 'bad magic number'
169 self._hdr_size = int(_read_u32(file))
170 if self._hdr_size < 24:
171 raise Error, 'header size too small'
172 if self._hdr_size > 100:
173 raise Error, 'header size rediculously large'
174 self._data_size = _read_u32(file)
175 if self._data_size != AUDIO_UNKNOWN_SIZE:
176 self._data_size = int(self._data_size)
177 self._encoding = int(_read_u32(file))
178 if self._encoding not in _simple_encodings:
179 raise Error, 'encoding not (yet) supported'
180 if self._encoding in (AUDIO_FILE_ENCODING_MULAW_8,
Sjoerd Mullender43bf0bc1993-12-13 11:42:39 +0000181 AUDIO_FILE_ENCODING_ALAW_8):
182 self._sampwidth = 2
183 self._framesize = 1
Guido van Rossum5ebeea01999-02-05 19:59:27 +0000184 elif self._encoding == AUDIO_FILE_ENCODING_LINEAR_8:
185 self._framesize = self._sampwidth = 1
Sjoerd Mullender43bf0bc1993-12-13 11:42:39 +0000186 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 = ''
Sjoerd Mullender43bf0bc1993-12-13 11:42:39 +0000205
Sjoerd Mullender43bf0bc1993-12-13 11:42:39 +0000206 def getfp(self):
207 return self._file
208
209 def getnchannels(self):
210 return self._nchannels
211
212 def getsampwidth(self):
213 return self._sampwidth
214
215 def getframerate(self):
216 return self._framerate
217
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
224
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'
232
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'
240
241 def getparams(self):
242 return self.getnchannels(), self.getsampwidth(), \
243 self.getframerate(), self.getnframes(), \
244 self.getcomptype(), self.getcompname()
245
246 def getmarkers(self):
247 return None
248
249 def getmark(self, id):
250 raise Error, 'no marks'
251
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:
Sjoerd Mullenderffe94901994-01-28 09:56:05 +0000257 data = self._file.read(nframes * self._framesize * self._nchannels)
Sjoerd Mullender43bf0bc1993-12-13 11:42:39 +0000258 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
263
264 def rewind(self):
265 self._soundpos = 0
266 self._file.seek(self._hdr_size)
267
268 def tell(self):
269 return self._soundpos
270
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
276
277 def close(self):
Sjoerd Mullender43bf0bc1993-12-13 11:42:39 +0000278 self._file = None
279
280class Au_write:
Sjoerd Mullender2a451411993-12-20 09:36:01 +0000281
Guido van Rossum7bc817d1993-12-17 15:25:27 +0000282 def __init__(self, f):
283 if type(f) == type(''):
Guido van Rossum3db6ebc1994-01-28 09:59:35 +0000284 import __builtin__
Guido van Rossum2013ba41999-02-05 20:55:16 +0000285 f = __builtin__.open(f, 'wb')
Guido van Rossum7bc817d1993-12-17 15:25:27 +0000286 self.initfp(f)
Sjoerd Mullender43bf0bc1993-12-13 11:42:39 +0000287
Sjoerd Mullender2a451411993-12-20 09:36:01 +0000288 def __del__(self):
289 if self._file:
290 self.close()
291
Sjoerd Mullender43bf0bc1993-12-13 11:42:39 +0000292 def initfp(self, file):
293 self._file = file
294 self._framerate = 0
295 self._nchannels = 0
296 self._sampwidth = 0
297 self._framesize = 0
298 self._nframes = AUDIO_UNKNOWN_SIZE
299 self._nframeswritten = 0
300 self._datawritten = 0
301 self._datalength = 0
302 self._info = ''
303 self._comptype = 'ULAW' # default is U-law
Sjoerd Mullender43bf0bc1993-12-13 11:42:39 +0000304
Sjoerd Mullender43bf0bc1993-12-13 11:42:39 +0000305 def setnchannels(self, nchannels):
306 if self._nframeswritten:
307 raise Error, 'cannot change parameters after starting to write'
308 if nchannels not in (1, 2, 4):
309 raise Error, 'only 1, 2, or 4 channels supported'
310 self._nchannels = nchannels
311
312 def getnchannels(self):
313 if not self._nchannels:
314 raise Error, 'number of channels not set'
315 return self._nchannels
316
317 def setsampwidth(self, sampwidth):
318 if self._nframeswritten:
319 raise Error, 'cannot change parameters after starting to write'
320 if sampwidth not in (1, 2, 4):
321 raise Error, 'bad sample width'
322 self._sampwidth = sampwidth
323
324 def getsampwidth(self):
325 if not self._framerate:
326 raise Error, 'sample width not specified'
327 return self._sampwidth
328
329 def setframerate(self, framerate):
330 if self._nframeswritten:
331 raise Error, 'cannot change parameters after starting to write'
332 self._framerate = framerate
333
334 def getframerate(self):
335 if not self._framerate:
336 raise Error, 'frame rate not set'
337 return self._framerate
338
339 def setnframes(self, nframes):
340 if self._nframeswritten:
341 raise Error, 'cannot change parameters after starting to write'
342 if nframes < 0:
343 raise Error, '# of frames cannot be negative'
344 self._nframes = nframes
345
346 def getnframes(self):
347 return self._nframeswritten
348
349 def setcomptype(self, type, name):
350 if type in ('NONE', 'ULAW'):
351 self._comptype = type
352 else:
353 raise Error, 'unknown compression type'
354
355 def getcomptype(self):
356 return self._comptype
357
358 def getcompname(self):
359 if self._comptype == 'ULAW':
360 return 'CCITT G.711 u-law'
361 elif self._comptype == 'ALAW':
362 return 'CCITT G.711 A-law'
363 else:
364 return 'not compressed'
365
366 def setparams(self, (nchannels, sampwidth, framerate, nframes, comptype, compname)):
367 self.setnchannels(nchannels)
368 self.setsampwidth(sampwidth)
369 self.setframerate(framerate)
370 self.setnframes(nframes)
371 self.setcomptype(comptype, compname)
372
373 def getparams(self):
374 return self.getnchannels(), self.getsampwidth(), \
375 self.getframerate(), self.getnframes(), \
376 self.getcomptype(), self.getcompname()
377
378 def tell(self):
379 return self._nframeswritten
380
381 def writeframesraw(self, data):
382 self._ensure_header_written()
383 nframes = len(data) / self._framesize
384 if self._comptype == 'ULAW':
385 import audioop
386 data = audioop.lin2ulaw(data, self._sampwidth)
387 self._file.write(data)
388 self._nframeswritten = self._nframeswritten + nframes
389 self._datawritten = self._datawritten + len(data)
390
391 def writeframes(self, data):
392 self.writeframesraw(data)
393 if self._nframeswritten != self._nframes or \
394 self._datalength != self._datawritten:
395 self._patchheader()
396
397 def close(self):
398 self._ensure_header_written()
399 if self._nframeswritten != self._nframes or \
400 self._datalength != self._datawritten:
401 self._patchheader()
Sjoerd Mullenderad7324c1993-12-16 14:02:44 +0000402 self._file.flush()
Sjoerd Mullender43bf0bc1993-12-13 11:42:39 +0000403 self._file = None
404
405 #
406 # private methods
407 #
Sjoerd Mullender2a451411993-12-20 09:36:01 +0000408
Sjoerd Mullender43bf0bc1993-12-13 11:42:39 +0000409 def _ensure_header_written(self):
410 if not self._nframeswritten:
411 if not self._nchannels:
412 raise Error, '# of channels not specified'
413 if not self._sampwidth:
414 raise Error, 'sample width not specified'
415 if not self._framerate:
416 raise Error, 'frame rate not specified'
417 self._write_header()
418
419 def _write_header(self):
420 if self._comptype == 'NONE':
421 if self._sampwidth == 1:
422 encoding = AUDIO_FILE_ENCODING_LINEAR_8
423 self._framesize = 1
424 elif self._sampwidth == 2:
425 encoding = AUDIO_FILE_ENCODING_LINEAR_16
426 self._framesize = 2
427 elif self._sampwidth == 4:
428 encoding = AUDIO_FILE_ENCODING_LINEAR_32
429 self._framesize = 4
430 else:
431 raise Error, 'internal error'
432 elif self._comptype == 'ULAW':
433 encoding = AUDIO_FILE_ENCODING_MULAW_8
434 self._framesize = 1
435 else:
436 raise Error, 'internal error'
437 self._framesize = self._framesize * self._nchannels
438 _write_u32(self._file, AUDIO_FILE_MAGIC)
439 header_size = 25 + len(self._info)
440 header_size = (header_size + 7) & ~7
441 _write_u32(self._file, header_size)
442 if self._nframes == AUDIO_UNKNOWN_SIZE:
443 length = AUDIO_UNKNOWN_SIZE
444 else:
445 length = self._nframes * self._framesize
446 _write_u32(self._file, length)
447 self._datalength = length
448 _write_u32(self._file, encoding)
449 _write_u32(self._file, self._framerate)
450 _write_u32(self._file, self._nchannels)
451 self._file.write(self._info)
452 self._file.write('\0'*(header_size - len(self._info) - 24))
453
454 def _patchheader(self):
455 self._file.seek(8)
456 _write_u32(self._file, self._datawritten)
457 self._datalength = self._datawritten
458 self._file.seek(0, 2)
459
Fred Drake43161351999-06-22 21:23:23 +0000460def open(f, mode=None):
461 if mode is None:
462 if hasattr(f, 'mode'):
463 mode = f.mode
464 else:
465 mode = 'rb'
466 if mode in ('r', 'rb'):
Guido van Rossum7bc817d1993-12-17 15:25:27 +0000467 return Au_read(f)
Fred Drake43161351999-06-22 21:23:23 +0000468 elif mode in ('w', 'wb'):
Guido van Rossum7bc817d1993-12-17 15:25:27 +0000469 return Au_write(f)
Sjoerd Mullender43bf0bc1993-12-13 11:42:39 +0000470 else:
Fred Drake43161351999-06-22 21:23:23 +0000471 raise Error, "mode must be 'r', 'rb', 'w', or 'wb'"
Sjoerd Mullender43bf0bc1993-12-13 11:42:39 +0000472
Guido van Rossum7bc817d1993-12-17 15:25:27 +0000473openfp = open