blob: 1880a01d1a232d45cb98eaecf8538cbfb8795c31 [file] [log] [blame]
Guido van Rossume7b146f2000-02-04 15:28:42 +00001"""Stuff to parse Sun and NeXT audio files.
2
Fred Drake24c532a2000-10-06 20:28:46 +00003An audio file consists of a header followed by the data. The structure
Guido van Rossume7b146f2000-02-04 15:28:42 +00004of 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
Fred Drake24c532a2000-10-06 20:28:46 +000029parameters the number of frames can be calculated.
Guido van Rossume7b146f2000-02-04 15:28:42 +000030The 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:
Tim Peters495ad3c2001-01-15 01:36:40 +000046 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')
Serhiy Storchakae06a8962013-09-04 00:43:03 +030054 getparams() -- returns a namedtuple consisting of all of the
Tim Peters495ad3c2001-01-15 01:36:40 +000055 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)
Guido van Rossume7b146f2000-02-04 15:28:42 +000065The position returned by tell() and the position given to setpos()
Thomas Wouters7e474022000-07-16 12:04:32 +000066are compatible and have nothing to do with the actual position in the
Guido van Rossume7b146f2000-02-04 15:28:42 +000067file.
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:
Tim Peters495ad3c2001-01-15 01:36:40 +000078 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
Guido van Rossume7b146f2000-02-04 15:28:42 +000082 setcomptype(type, name)
Tim Peters495ad3c2001-01-15 01:36:40 +000083 -- set the compression type and the
84 human-readable compression type
Guido van Rossume7b146f2000-02-04 15:28:42 +000085 setparams(tuple)-- set all parameters at once
Tim Peters495ad3c2001-01-15 01:36:40 +000086 tell() -- return current position in output file
Guido van Rossume7b146f2000-02-04 15:28:42 +000087 writeframesraw(data)
Tim Peters495ad3c2001-01-15 01:36:40 +000088 -- write audio frames without pathing up the
89 file header
Guido van Rossume7b146f2000-02-04 15:28:42 +000090 writeframes(data)
Tim Peters495ad3c2001-01-15 01:36:40 +000091 -- write audio frames and patch up the file header
92 close() -- patch up the file header and close the
93 output file
Guido van Rossume7b146f2000-02-04 15:28:42 +000094You 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
Serhiy Storchakae06a8962013-09-04 00:43:03 +0300106from collections import namedtuple
107
108_sunau_params = namedtuple('_sunau_params',
109 'nchannels sampwidth framerate nframes comptype compname')
110
Sjoerd Mullender43bf0bc1993-12-13 11:42:39 +0000111# from <multimedia/audio_filehdr.h>
112AUDIO_FILE_MAGIC = 0x2e736e64
113AUDIO_FILE_ENCODING_MULAW_8 = 1
114AUDIO_FILE_ENCODING_LINEAR_8 = 2
115AUDIO_FILE_ENCODING_LINEAR_16 = 3
116AUDIO_FILE_ENCODING_LINEAR_24 = 4
117AUDIO_FILE_ENCODING_LINEAR_32 = 5
118AUDIO_FILE_ENCODING_FLOAT = 6
119AUDIO_FILE_ENCODING_DOUBLE = 7
120AUDIO_FILE_ENCODING_ADPCM_G721 = 23
121AUDIO_FILE_ENCODING_ADPCM_G722 = 24
122AUDIO_FILE_ENCODING_ADPCM_G723_3 = 25
123AUDIO_FILE_ENCODING_ADPCM_G723_5 = 26
124AUDIO_FILE_ENCODING_ALAW_8 = 27
125
126# from <multimedia/audio_hdr.h>
Guido van Rossume2a383d2007-01-15 16:59:06 +0000127AUDIO_UNKNOWN_SIZE = 0xFFFFFFFF # ((unsigned)(~0))
Sjoerd Mullender43bf0bc1993-12-13 11:42:39 +0000128
129_simple_encodings = [AUDIO_FILE_ENCODING_MULAW_8,
Tim Peters495ad3c2001-01-15 01:36:40 +0000130 AUDIO_FILE_ENCODING_LINEAR_8,
131 AUDIO_FILE_ENCODING_LINEAR_16,
132 AUDIO_FILE_ENCODING_LINEAR_24,
133 AUDIO_FILE_ENCODING_LINEAR_32,
134 AUDIO_FILE_ENCODING_ALAW_8]
Sjoerd Mullender43bf0bc1993-12-13 11:42:39 +0000135
Fred Drake9b8d8012000-08-17 04:45:13 +0000136class Error(Exception):
Tim Peters495ad3c2001-01-15 01:36:40 +0000137 pass
Sjoerd Mullenderf33a69f1995-08-14 07:49:51 +0000138
Sjoerd Mullender43bf0bc1993-12-13 11:42:39 +0000139def _read_u32(file):
Guido van Rossume2a383d2007-01-15 16:59:06 +0000140 x = 0
Tim Peters495ad3c2001-01-15 01:36:40 +0000141 for i in range(4):
142 byte = file.read(1)
Antoine Pitrou7a5dc752008-08-17 00:38:32 +0000143 if not byte:
Tim Peters495ad3c2001-01-15 01:36:40 +0000144 raise EOFError
145 x = x*256 + ord(byte)
146 return x
Sjoerd Mullender43bf0bc1993-12-13 11:42:39 +0000147
148def _write_u32(file, x):
Tim Peters495ad3c2001-01-15 01:36:40 +0000149 data = []
150 for i in range(4):
151 d, m = divmod(x, 256)
Antoine Pitrou7a5dc752008-08-17 00:38:32 +0000152 data.insert(0, int(m))
Tim Peters495ad3c2001-01-15 01:36:40 +0000153 x = d
Antoine Pitrou7a5dc752008-08-17 00:38:32 +0000154 file.write(bytes(data))
Sjoerd Mullender43bf0bc1993-12-13 11:42:39 +0000155
156class Au_read:
Sjoerd Mullender2a451411993-12-20 09:36:01 +0000157
Tim Peters495ad3c2001-01-15 01:36:40 +0000158 def __init__(self, f):
159 if type(f) == type(''):
Georg Brandl1a3284e2007-12-02 09:40:06 +0000160 import builtins
161 f = builtins.open(f, 'rb')
Antoine Pitrou4d984892010-10-31 21:27:04 +0000162 self._opened = True
163 else:
164 self._opened = False
Tim Peters495ad3c2001-01-15 01:36:40 +0000165 self.initfp(f)
Sjoerd Mullender2a451411993-12-20 09:36:01 +0000166
Tim Peters495ad3c2001-01-15 01:36:40 +0000167 def __del__(self):
168 if self._file:
169 self.close()
Sjoerd Mullender2a451411993-12-20 09:36:01 +0000170
Serhiy Storchaka34d20132013-09-05 17:01:53 +0300171 def __enter__(self):
172 return self
173
174 def __exit__(self, *args):
175 self.close()
176
Tim Peters495ad3c2001-01-15 01:36:40 +0000177 def initfp(self, file):
178 self._file = file
179 self._soundpos = 0
180 magic = int(_read_u32(file))
181 if magic != AUDIO_FILE_MAGIC:
Collin Winterce36ad82007-08-30 01:19:48 +0000182 raise Error('bad magic number')
Tim Peters495ad3c2001-01-15 01:36:40 +0000183 self._hdr_size = int(_read_u32(file))
184 if self._hdr_size < 24:
Collin Winterce36ad82007-08-30 01:19:48 +0000185 raise Error('header size too small')
Tim Peters495ad3c2001-01-15 01:36:40 +0000186 if self._hdr_size > 100:
Collin Winterce36ad82007-08-30 01:19:48 +0000187 raise Error('header size ridiculously large')
Tim Peters495ad3c2001-01-15 01:36:40 +0000188 self._data_size = _read_u32(file)
189 if self._data_size != AUDIO_UNKNOWN_SIZE:
190 self._data_size = int(self._data_size)
191 self._encoding = int(_read_u32(file))
192 if self._encoding not in _simple_encodings:
Collin Winterce36ad82007-08-30 01:19:48 +0000193 raise Error('encoding not (yet) supported')
Tim Peters495ad3c2001-01-15 01:36:40 +0000194 if self._encoding in (AUDIO_FILE_ENCODING_MULAW_8,
195 AUDIO_FILE_ENCODING_ALAW_8):
196 self._sampwidth = 2
197 self._framesize = 1
198 elif self._encoding == AUDIO_FILE_ENCODING_LINEAR_8:
199 self._framesize = self._sampwidth = 1
200 elif self._encoding == AUDIO_FILE_ENCODING_LINEAR_16:
201 self._framesize = self._sampwidth = 2
202 elif self._encoding == AUDIO_FILE_ENCODING_LINEAR_24:
203 self._framesize = self._sampwidth = 3
204 elif self._encoding == AUDIO_FILE_ENCODING_LINEAR_32:
205 self._framesize = self._sampwidth = 4
206 else:
Collin Winterce36ad82007-08-30 01:19:48 +0000207 raise Error('unknown encoding')
Tim Peters495ad3c2001-01-15 01:36:40 +0000208 self._framerate = int(_read_u32(file))
209 self._nchannels = int(_read_u32(file))
210 self._framesize = self._framesize * self._nchannels
211 if self._hdr_size > 24:
212 self._info = file.read(self._hdr_size - 24)
213 for i in range(len(self._info)):
Antoine Pitrou7a5dc752008-08-17 00:38:32 +0000214 if self._info[i] == b'\0':
Tim Peters495ad3c2001-01-15 01:36:40 +0000215 self._info = self._info[:i]
216 break
217 else:
218 self._info = ''
Serhiy Storchaka0300a8d2013-09-28 21:21:39 +0300219 try:
220 self._data_pos = file.tell()
221 except (AttributeError, OSError):
222 self._data_pos = None
Sjoerd Mullender43bf0bc1993-12-13 11:42:39 +0000223
Tim Peters495ad3c2001-01-15 01:36:40 +0000224 def getfp(self):
225 return self._file
Sjoerd Mullender43bf0bc1993-12-13 11:42:39 +0000226
Tim Peters495ad3c2001-01-15 01:36:40 +0000227 def getnchannels(self):
228 return self._nchannels
Sjoerd Mullender43bf0bc1993-12-13 11:42:39 +0000229
Tim Peters495ad3c2001-01-15 01:36:40 +0000230 def getsampwidth(self):
231 return self._sampwidth
Sjoerd Mullender43bf0bc1993-12-13 11:42:39 +0000232
Tim Peters495ad3c2001-01-15 01:36:40 +0000233 def getframerate(self):
234 return self._framerate
Sjoerd Mullender43bf0bc1993-12-13 11:42:39 +0000235
Tim Peters495ad3c2001-01-15 01:36:40 +0000236 def getnframes(self):
237 if self._data_size == AUDIO_UNKNOWN_SIZE:
238 return AUDIO_UNKNOWN_SIZE
239 if self._encoding in _simple_encodings:
Serhiy Storchaka0300a8d2013-09-28 21:21:39 +0300240 return self._data_size // self._framesize
Tim Peters495ad3c2001-01-15 01:36:40 +0000241 return 0 # XXX--must do some arithmetic here
Sjoerd Mullender43bf0bc1993-12-13 11:42:39 +0000242
Tim Peters495ad3c2001-01-15 01:36:40 +0000243 def getcomptype(self):
244 if self._encoding == AUDIO_FILE_ENCODING_MULAW_8:
245 return 'ULAW'
246 elif self._encoding == AUDIO_FILE_ENCODING_ALAW_8:
247 return 'ALAW'
248 else:
249 return 'NONE'
Sjoerd Mullender43bf0bc1993-12-13 11:42:39 +0000250
Tim Peters495ad3c2001-01-15 01:36:40 +0000251 def getcompname(self):
252 if self._encoding == AUDIO_FILE_ENCODING_MULAW_8:
253 return 'CCITT G.711 u-law'
254 elif self._encoding == AUDIO_FILE_ENCODING_ALAW_8:
255 return 'CCITT G.711 A-law'
256 else:
257 return 'not compressed'
Sjoerd Mullender43bf0bc1993-12-13 11:42:39 +0000258
Tim Peters495ad3c2001-01-15 01:36:40 +0000259 def getparams(self):
Serhiy Storchakae06a8962013-09-04 00:43:03 +0300260 return _sunau_params(self.getnchannels(), self.getsampwidth(),
261 self.getframerate(), self.getnframes(),
262 self.getcomptype(), self.getcompname())
Sjoerd Mullender43bf0bc1993-12-13 11:42:39 +0000263
Tim Peters495ad3c2001-01-15 01:36:40 +0000264 def getmarkers(self):
265 return None
Sjoerd Mullender43bf0bc1993-12-13 11:42:39 +0000266
Tim Peters495ad3c2001-01-15 01:36:40 +0000267 def getmark(self, id):
Collin Winterce36ad82007-08-30 01:19:48 +0000268 raise Error('no marks')
Sjoerd Mullender43bf0bc1993-12-13 11:42:39 +0000269
Tim Peters495ad3c2001-01-15 01:36:40 +0000270 def readframes(self, nframes):
271 if self._encoding in _simple_encodings:
272 if nframes == AUDIO_UNKNOWN_SIZE:
273 data = self._file.read()
274 else:
Serhiy Storchaka0300a8d2013-09-28 21:21:39 +0300275 data = self._file.read(nframes * self._framesize)
276 self._soundpos += len(data) // self._framesize
Tim Peters495ad3c2001-01-15 01:36:40 +0000277 if self._encoding == AUDIO_FILE_ENCODING_MULAW_8:
278 import audioop
279 data = audioop.ulaw2lin(data, self._sampwidth)
280 return data
281 return None # XXX--not implemented yet
Sjoerd Mullender43bf0bc1993-12-13 11:42:39 +0000282
Tim Peters495ad3c2001-01-15 01:36:40 +0000283 def rewind(self):
Serhiy Storchaka0300a8d2013-09-28 21:21:39 +0300284 if self._data_pos is None:
285 raise OSError('cannot seek')
286 self._file.seek(self._data_pos)
Tim Peters495ad3c2001-01-15 01:36:40 +0000287 self._soundpos = 0
Sjoerd Mullender43bf0bc1993-12-13 11:42:39 +0000288
Tim Peters495ad3c2001-01-15 01:36:40 +0000289 def tell(self):
290 return self._soundpos
Sjoerd Mullender43bf0bc1993-12-13 11:42:39 +0000291
Tim Peters495ad3c2001-01-15 01:36:40 +0000292 def setpos(self, pos):
293 if pos < 0 or pos > self.getnframes():
Collin Winterce36ad82007-08-30 01:19:48 +0000294 raise Error('position not in range')
Serhiy Storchaka0300a8d2013-09-28 21:21:39 +0300295 if self._data_pos is None:
296 raise OSError('cannot seek')
297 self._file.seek(self._data_pos + pos * self._framesize)
Tim Peters495ad3c2001-01-15 01:36:40 +0000298 self._soundpos = pos
Sjoerd Mullender43bf0bc1993-12-13 11:42:39 +0000299
Tim Peters495ad3c2001-01-15 01:36:40 +0000300 def close(self):
Antoine Pitrou4d984892010-10-31 21:27:04 +0000301 if self._opened and self._file:
302 self._file.close()
Tim Peters495ad3c2001-01-15 01:36:40 +0000303 self._file = None
Sjoerd Mullender43bf0bc1993-12-13 11:42:39 +0000304
305class Au_write:
Sjoerd Mullender2a451411993-12-20 09:36:01 +0000306
Tim Peters495ad3c2001-01-15 01:36:40 +0000307 def __init__(self, f):
308 if type(f) == type(''):
Georg Brandl1a3284e2007-12-02 09:40:06 +0000309 import builtins
310 f = builtins.open(f, 'wb')
Antoine Pitrou4d984892010-10-31 21:27:04 +0000311 self._opened = True
312 else:
313 self._opened = False
Tim Peters495ad3c2001-01-15 01:36:40 +0000314 self.initfp(f)
Sjoerd Mullender43bf0bc1993-12-13 11:42:39 +0000315
Tim Peters495ad3c2001-01-15 01:36:40 +0000316 def __del__(self):
317 if self._file:
318 self.close()
Antoine Pitrou4d984892010-10-31 21:27:04 +0000319 self._file = None
Sjoerd Mullender2a451411993-12-20 09:36:01 +0000320
Serhiy Storchaka34d20132013-09-05 17:01:53 +0300321 def __enter__(self):
322 return self
323
324 def __exit__(self, *args):
325 self.close()
326
Tim Peters495ad3c2001-01-15 01:36:40 +0000327 def initfp(self, file):
328 self._file = file
329 self._framerate = 0
330 self._nchannels = 0
331 self._sampwidth = 0
332 self._framesize = 0
333 self._nframes = AUDIO_UNKNOWN_SIZE
334 self._nframeswritten = 0
335 self._datawritten = 0
336 self._datalength = 0
Victor Stinner7f3652e2010-06-07 20:14:04 +0000337 self._info = b''
Tim Peters495ad3c2001-01-15 01:36:40 +0000338 self._comptype = 'ULAW' # default is U-law
Sjoerd Mullender43bf0bc1993-12-13 11:42:39 +0000339
Tim Peters495ad3c2001-01-15 01:36:40 +0000340 def setnchannels(self, nchannels):
341 if self._nframeswritten:
Collin Winterce36ad82007-08-30 01:19:48 +0000342 raise Error('cannot change parameters after starting to write')
Tim Peters495ad3c2001-01-15 01:36:40 +0000343 if nchannels not in (1, 2, 4):
Collin Winterce36ad82007-08-30 01:19:48 +0000344 raise Error('only 1, 2, or 4 channels supported')
Tim Peters495ad3c2001-01-15 01:36:40 +0000345 self._nchannels = nchannels
Sjoerd Mullender43bf0bc1993-12-13 11:42:39 +0000346
Tim Peters495ad3c2001-01-15 01:36:40 +0000347 def getnchannels(self):
348 if not self._nchannels:
Collin Winterce36ad82007-08-30 01:19:48 +0000349 raise Error('number of channels not set')
Tim Peters495ad3c2001-01-15 01:36:40 +0000350 return self._nchannels
Sjoerd Mullender43bf0bc1993-12-13 11:42:39 +0000351
Tim Peters495ad3c2001-01-15 01:36:40 +0000352 def setsampwidth(self, sampwidth):
353 if self._nframeswritten:
Collin Winterce36ad82007-08-30 01:19:48 +0000354 raise Error('cannot change parameters after starting to write')
Serhiy Storchaka81895f82013-11-10 21:02:53 +0200355 if sampwidth not in (1, 2, 3, 4):
Collin Winterce36ad82007-08-30 01:19:48 +0000356 raise Error('bad sample width')
Tim Peters495ad3c2001-01-15 01:36:40 +0000357 self._sampwidth = sampwidth
Sjoerd Mullender43bf0bc1993-12-13 11:42:39 +0000358
Tim Peters495ad3c2001-01-15 01:36:40 +0000359 def getsampwidth(self):
360 if not self._framerate:
Collin Winterce36ad82007-08-30 01:19:48 +0000361 raise Error('sample width not specified')
Tim Peters495ad3c2001-01-15 01:36:40 +0000362 return self._sampwidth
Sjoerd Mullender43bf0bc1993-12-13 11:42:39 +0000363
Tim Peters495ad3c2001-01-15 01:36:40 +0000364 def setframerate(self, framerate):
365 if self._nframeswritten:
Collin Winterce36ad82007-08-30 01:19:48 +0000366 raise Error('cannot change parameters after starting to write')
Tim Peters495ad3c2001-01-15 01:36:40 +0000367 self._framerate = framerate
Sjoerd Mullender43bf0bc1993-12-13 11:42:39 +0000368
Tim Peters495ad3c2001-01-15 01:36:40 +0000369 def getframerate(self):
370 if not self._framerate:
Collin Winterce36ad82007-08-30 01:19:48 +0000371 raise Error('frame rate not set')
Tim Peters495ad3c2001-01-15 01:36:40 +0000372 return self._framerate
Sjoerd Mullender43bf0bc1993-12-13 11:42:39 +0000373
Tim Peters495ad3c2001-01-15 01:36:40 +0000374 def setnframes(self, nframes):
375 if self._nframeswritten:
Collin Winterce36ad82007-08-30 01:19:48 +0000376 raise Error('cannot change parameters after starting to write')
Tim Peters495ad3c2001-01-15 01:36:40 +0000377 if nframes < 0:
Collin Winterce36ad82007-08-30 01:19:48 +0000378 raise Error('# of frames cannot be negative')
Tim Peters495ad3c2001-01-15 01:36:40 +0000379 self._nframes = nframes
Sjoerd Mullender43bf0bc1993-12-13 11:42:39 +0000380
Tim Peters495ad3c2001-01-15 01:36:40 +0000381 def getnframes(self):
382 return self._nframeswritten
Sjoerd Mullender43bf0bc1993-12-13 11:42:39 +0000383
Tim Peters495ad3c2001-01-15 01:36:40 +0000384 def setcomptype(self, type, name):
385 if type in ('NONE', 'ULAW'):
386 self._comptype = type
387 else:
Collin Winterce36ad82007-08-30 01:19:48 +0000388 raise Error('unknown compression type')
Sjoerd Mullender43bf0bc1993-12-13 11:42:39 +0000389
Tim Peters495ad3c2001-01-15 01:36:40 +0000390 def getcomptype(self):
391 return self._comptype
Sjoerd Mullender43bf0bc1993-12-13 11:42:39 +0000392
Tim Peters495ad3c2001-01-15 01:36:40 +0000393 def getcompname(self):
394 if self._comptype == 'ULAW':
395 return 'CCITT G.711 u-law'
396 elif self._comptype == 'ALAW':
397 return 'CCITT G.711 A-law'
398 else:
399 return 'not compressed'
Sjoerd Mullender43bf0bc1993-12-13 11:42:39 +0000400
Guido van Rossum1bc535d2007-05-15 18:46:22 +0000401 def setparams(self, params):
402 nchannels, sampwidth, framerate, nframes, comptype, compname = params
Tim Peters495ad3c2001-01-15 01:36:40 +0000403 self.setnchannels(nchannels)
404 self.setsampwidth(sampwidth)
405 self.setframerate(framerate)
406 self.setnframes(nframes)
407 self.setcomptype(comptype, compname)
Sjoerd Mullender43bf0bc1993-12-13 11:42:39 +0000408
Tim Peters495ad3c2001-01-15 01:36:40 +0000409 def getparams(self):
Serhiy Storchakaaf722bf2013-09-04 14:30:16 +0300410 return _sunau_params(self.getnchannels(), self.getsampwidth(),
Serhiy Storchakae06a8962013-09-04 00:43:03 +0300411 self.getframerate(), self.getnframes(),
412 self.getcomptype(), self.getcompname())
Sjoerd Mullender43bf0bc1993-12-13 11:42:39 +0000413
Tim Peters495ad3c2001-01-15 01:36:40 +0000414 def tell(self):
415 return self._nframeswritten
Sjoerd Mullender43bf0bc1993-12-13 11:42:39 +0000416
Tim Peters495ad3c2001-01-15 01:36:40 +0000417 def writeframesraw(self, data):
418 self._ensure_header_written()
Tim Peters495ad3c2001-01-15 01:36:40 +0000419 if self._comptype == 'ULAW':
420 import audioop
421 data = audioop.lin2ulaw(data, self._sampwidth)
Serhiy Storchaka0300a8d2013-09-28 21:21:39 +0300422 nframes = len(data) // self._framesize
Tim Peters495ad3c2001-01-15 01:36:40 +0000423 self._file.write(data)
424 self._nframeswritten = self._nframeswritten + nframes
425 self._datawritten = self._datawritten + len(data)
Sjoerd Mullender43bf0bc1993-12-13 11:42:39 +0000426
Tim Peters495ad3c2001-01-15 01:36:40 +0000427 def writeframes(self, data):
428 self.writeframesraw(data)
429 if self._nframeswritten != self._nframes or \
430 self._datalength != self._datawritten:
431 self._patchheader()
Sjoerd Mullender43bf0bc1993-12-13 11:42:39 +0000432
Tim Peters495ad3c2001-01-15 01:36:40 +0000433 def close(self):
Serhiy Storchaka34d20132013-09-05 17:01:53 +0300434 if self._file:
435 try:
436 self._ensure_header_written()
437 if self._nframeswritten != self._nframes or \
438 self._datalength != self._datawritten:
439 self._patchheader()
440 self._file.flush()
441 finally:
442 if self._opened and self._file:
443 self._file.close()
444 self._file = None
Sjoerd Mullender43bf0bc1993-12-13 11:42:39 +0000445
Tim Peters495ad3c2001-01-15 01:36:40 +0000446 #
447 # private methods
448 #
Sjoerd Mullender2a451411993-12-20 09:36:01 +0000449
Tim Peters495ad3c2001-01-15 01:36:40 +0000450 def _ensure_header_written(self):
451 if not self._nframeswritten:
452 if not self._nchannels:
Collin Winterce36ad82007-08-30 01:19:48 +0000453 raise Error('# of channels not specified')
Tim Peters495ad3c2001-01-15 01:36:40 +0000454 if not self._sampwidth:
Collin Winterce36ad82007-08-30 01:19:48 +0000455 raise Error('sample width not specified')
Tim Peters495ad3c2001-01-15 01:36:40 +0000456 if not self._framerate:
Collin Winterce36ad82007-08-30 01:19:48 +0000457 raise Error('frame rate not specified')
Tim Peters495ad3c2001-01-15 01:36:40 +0000458 self._write_header()
Sjoerd Mullender43bf0bc1993-12-13 11:42:39 +0000459
Tim Peters495ad3c2001-01-15 01:36:40 +0000460 def _write_header(self):
461 if self._comptype == 'NONE':
462 if self._sampwidth == 1:
463 encoding = AUDIO_FILE_ENCODING_LINEAR_8
464 self._framesize = 1
465 elif self._sampwidth == 2:
466 encoding = AUDIO_FILE_ENCODING_LINEAR_16
467 self._framesize = 2
Serhiy Storchaka81895f82013-11-10 21:02:53 +0200468 elif self._sampwidth == 3:
469 encoding = AUDIO_FILE_ENCODING_LINEAR_24
470 self._framesize = 3
Tim Peters495ad3c2001-01-15 01:36:40 +0000471 elif self._sampwidth == 4:
472 encoding = AUDIO_FILE_ENCODING_LINEAR_32
473 self._framesize = 4
474 else:
Collin Winterce36ad82007-08-30 01:19:48 +0000475 raise Error('internal error')
Tim Peters495ad3c2001-01-15 01:36:40 +0000476 elif self._comptype == 'ULAW':
477 encoding = AUDIO_FILE_ENCODING_MULAW_8
478 self._framesize = 1
479 else:
Collin Winterce36ad82007-08-30 01:19:48 +0000480 raise Error('internal error')
Tim Peters495ad3c2001-01-15 01:36:40 +0000481 self._framesize = self._framesize * self._nchannels
482 _write_u32(self._file, AUDIO_FILE_MAGIC)
483 header_size = 25 + len(self._info)
484 header_size = (header_size + 7) & ~7
485 _write_u32(self._file, header_size)
486 if self._nframes == AUDIO_UNKNOWN_SIZE:
487 length = AUDIO_UNKNOWN_SIZE
488 else:
489 length = self._nframes * self._framesize
Serhiy Storchaka0300a8d2013-09-28 21:21:39 +0300490 try:
491 self._form_length_pos = self._file.tell()
492 except (AttributeError, OSError):
493 self._form_length_pos = None
Tim Peters495ad3c2001-01-15 01:36:40 +0000494 _write_u32(self._file, length)
495 self._datalength = length
496 _write_u32(self._file, encoding)
497 _write_u32(self._file, self._framerate)
498 _write_u32(self._file, self._nchannels)
499 self._file.write(self._info)
Antoine Pitrou7a5dc752008-08-17 00:38:32 +0000500 self._file.write(b'\0'*(header_size - len(self._info) - 24))
Sjoerd Mullender43bf0bc1993-12-13 11:42:39 +0000501
Tim Peters495ad3c2001-01-15 01:36:40 +0000502 def _patchheader(self):
Serhiy Storchaka0300a8d2013-09-28 21:21:39 +0300503 if self._form_length_pos is None:
504 raise OSError('cannot seek')
505 self._file.seek(self._form_length_pos)
Tim Peters495ad3c2001-01-15 01:36:40 +0000506 _write_u32(self._file, self._datawritten)
507 self._datalength = self._datawritten
508 self._file.seek(0, 2)
Sjoerd Mullender43bf0bc1993-12-13 11:42:39 +0000509
Fred Drake43161351999-06-22 21:23:23 +0000510def open(f, mode=None):
Tim Peters495ad3c2001-01-15 01:36:40 +0000511 if mode is None:
512 if hasattr(f, 'mode'):
513 mode = f.mode
514 else:
515 mode = 'rb'
516 if mode in ('r', 'rb'):
517 return Au_read(f)
518 elif mode in ('w', 'wb'):
519 return Au_write(f)
520 else:
Collin Winterce36ad82007-08-30 01:19:48 +0000521 raise Error("mode must be 'r', 'rb', 'w', or 'wb'")
Sjoerd Mullender43bf0bc1993-12-13 11:42:39 +0000522
Guido van Rossum7bc817d1993-12-17 15:25:27 +0000523openfp = open