blob: 3c244925d1e8f7edfe42d64e5a890b5a1069dd5d [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):
Serhiy Storchaka452bab42013-11-16 14:01:31 +0200418 if not isinstance(data, (bytes, bytearray)):
419 data = memoryview(data).cast('B')
Tim Peters495ad3c2001-01-15 01:36:40 +0000420 self._ensure_header_written()
Tim Peters495ad3c2001-01-15 01:36:40 +0000421 if self._comptype == 'ULAW':
422 import audioop
423 data = audioop.lin2ulaw(data, self._sampwidth)
Serhiy Storchaka0300a8d2013-09-28 21:21:39 +0300424 nframes = len(data) // self._framesize
Tim Peters495ad3c2001-01-15 01:36:40 +0000425 self._file.write(data)
426 self._nframeswritten = self._nframeswritten + nframes
427 self._datawritten = self._datawritten + len(data)
Sjoerd Mullender43bf0bc1993-12-13 11:42:39 +0000428
Tim Peters495ad3c2001-01-15 01:36:40 +0000429 def writeframes(self, data):
430 self.writeframesraw(data)
431 if self._nframeswritten != self._nframes or \
432 self._datalength != self._datawritten:
433 self._patchheader()
Sjoerd Mullender43bf0bc1993-12-13 11:42:39 +0000434
Tim Peters495ad3c2001-01-15 01:36:40 +0000435 def close(self):
Serhiy Storchaka34d20132013-09-05 17:01:53 +0300436 if self._file:
437 try:
438 self._ensure_header_written()
439 if self._nframeswritten != self._nframes or \
440 self._datalength != self._datawritten:
441 self._patchheader()
442 self._file.flush()
443 finally:
444 if self._opened and self._file:
445 self._file.close()
446 self._file = None
Sjoerd Mullender43bf0bc1993-12-13 11:42:39 +0000447
Tim Peters495ad3c2001-01-15 01:36:40 +0000448 #
449 # private methods
450 #
Sjoerd Mullender2a451411993-12-20 09:36:01 +0000451
Tim Peters495ad3c2001-01-15 01:36:40 +0000452 def _ensure_header_written(self):
453 if not self._nframeswritten:
454 if not self._nchannels:
Collin Winterce36ad82007-08-30 01:19:48 +0000455 raise Error('# of channels not specified')
Tim Peters495ad3c2001-01-15 01:36:40 +0000456 if not self._sampwidth:
Collin Winterce36ad82007-08-30 01:19:48 +0000457 raise Error('sample width not specified')
Tim Peters495ad3c2001-01-15 01:36:40 +0000458 if not self._framerate:
Collin Winterce36ad82007-08-30 01:19:48 +0000459 raise Error('frame rate not specified')
Tim Peters495ad3c2001-01-15 01:36:40 +0000460 self._write_header()
Sjoerd Mullender43bf0bc1993-12-13 11:42:39 +0000461
Tim Peters495ad3c2001-01-15 01:36:40 +0000462 def _write_header(self):
463 if self._comptype == 'NONE':
464 if self._sampwidth == 1:
465 encoding = AUDIO_FILE_ENCODING_LINEAR_8
466 self._framesize = 1
467 elif self._sampwidth == 2:
468 encoding = AUDIO_FILE_ENCODING_LINEAR_16
469 self._framesize = 2
Serhiy Storchaka81895f82013-11-10 21:02:53 +0200470 elif self._sampwidth == 3:
471 encoding = AUDIO_FILE_ENCODING_LINEAR_24
472 self._framesize = 3
Tim Peters495ad3c2001-01-15 01:36:40 +0000473 elif self._sampwidth == 4:
474 encoding = AUDIO_FILE_ENCODING_LINEAR_32
475 self._framesize = 4
476 else:
Collin Winterce36ad82007-08-30 01:19:48 +0000477 raise Error('internal error')
Tim Peters495ad3c2001-01-15 01:36:40 +0000478 elif self._comptype == 'ULAW':
479 encoding = AUDIO_FILE_ENCODING_MULAW_8
480 self._framesize = 1
481 else:
Collin Winterce36ad82007-08-30 01:19:48 +0000482 raise Error('internal error')
Tim Peters495ad3c2001-01-15 01:36:40 +0000483 self._framesize = self._framesize * self._nchannels
484 _write_u32(self._file, AUDIO_FILE_MAGIC)
485 header_size = 25 + len(self._info)
486 header_size = (header_size + 7) & ~7
487 _write_u32(self._file, header_size)
488 if self._nframes == AUDIO_UNKNOWN_SIZE:
489 length = AUDIO_UNKNOWN_SIZE
490 else:
491 length = self._nframes * self._framesize
Serhiy Storchaka0300a8d2013-09-28 21:21:39 +0300492 try:
493 self._form_length_pos = self._file.tell()
494 except (AttributeError, OSError):
495 self._form_length_pos = None
Tim Peters495ad3c2001-01-15 01:36:40 +0000496 _write_u32(self._file, length)
497 self._datalength = length
498 _write_u32(self._file, encoding)
499 _write_u32(self._file, self._framerate)
500 _write_u32(self._file, self._nchannels)
501 self._file.write(self._info)
Antoine Pitrou7a5dc752008-08-17 00:38:32 +0000502 self._file.write(b'\0'*(header_size - len(self._info) - 24))
Sjoerd Mullender43bf0bc1993-12-13 11:42:39 +0000503
Tim Peters495ad3c2001-01-15 01:36:40 +0000504 def _patchheader(self):
Serhiy Storchaka0300a8d2013-09-28 21:21:39 +0300505 if self._form_length_pos is None:
506 raise OSError('cannot seek')
507 self._file.seek(self._form_length_pos)
Tim Peters495ad3c2001-01-15 01:36:40 +0000508 _write_u32(self._file, self._datawritten)
509 self._datalength = self._datawritten
510 self._file.seek(0, 2)
Sjoerd Mullender43bf0bc1993-12-13 11:42:39 +0000511
Fred Drake43161351999-06-22 21:23:23 +0000512def open(f, mode=None):
Tim Peters495ad3c2001-01-15 01:36:40 +0000513 if mode is None:
514 if hasattr(f, 'mode'):
515 mode = f.mode
516 else:
517 mode = 'rb'
518 if mode in ('r', 'rb'):
519 return Au_read(f)
520 elif mode in ('w', 'wb'):
521 return Au_write(f)
522 else:
Collin Winterce36ad82007-08-30 01:19:48 +0000523 raise Error("mode must be 'r', 'rb', 'w', or 'wb'")
Sjoerd Mullender43bf0bc1993-12-13 11:42:39 +0000524
Guido van Rossum7bc817d1993-12-17 15:25:27 +0000525openfp = open