blob: 0473e9e4ca15cdbc9db8367217a209efd783317d [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.
Serhiy Storchakae0fd7ef2015-07-10 22:13:40 +0300100When all frames have been written, either call writeframes(b'') or
Guido van Rossume7b146f2000-02-04 15:28:42 +0000101close() 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)
Serhiy Storchaka74a49ac2015-03-20 16:46:19 +0200213 self._info, _, _ = self._info.partition(b'\0')
Tim Peters495ad3c2001-01-15 01:36:40 +0000214 else:
Serhiy Storchaka74a49ac2015-03-20 16:46:19 +0200215 self._info = b''
Serhiy Storchaka0300a8d2013-09-28 21:21:39 +0300216 try:
217 self._data_pos = file.tell()
218 except (AttributeError, OSError):
219 self._data_pos = None
Sjoerd Mullender43bf0bc1993-12-13 11:42:39 +0000220
Tim Peters495ad3c2001-01-15 01:36:40 +0000221 def getfp(self):
222 return self._file
Sjoerd Mullender43bf0bc1993-12-13 11:42:39 +0000223
Tim Peters495ad3c2001-01-15 01:36:40 +0000224 def getnchannels(self):
225 return self._nchannels
Sjoerd Mullender43bf0bc1993-12-13 11:42:39 +0000226
Tim Peters495ad3c2001-01-15 01:36:40 +0000227 def getsampwidth(self):
228 return self._sampwidth
Sjoerd Mullender43bf0bc1993-12-13 11:42:39 +0000229
Tim Peters495ad3c2001-01-15 01:36:40 +0000230 def getframerate(self):
231 return self._framerate
Sjoerd Mullender43bf0bc1993-12-13 11:42:39 +0000232
Tim Peters495ad3c2001-01-15 01:36:40 +0000233 def getnframes(self):
234 if self._data_size == AUDIO_UNKNOWN_SIZE:
235 return AUDIO_UNKNOWN_SIZE
236 if self._encoding in _simple_encodings:
Serhiy Storchaka0300a8d2013-09-28 21:21:39 +0300237 return self._data_size // self._framesize
Tim Peters495ad3c2001-01-15 01:36:40 +0000238 return 0 # XXX--must do some arithmetic here
Sjoerd Mullender43bf0bc1993-12-13 11:42:39 +0000239
Tim Peters495ad3c2001-01-15 01:36:40 +0000240 def getcomptype(self):
241 if self._encoding == AUDIO_FILE_ENCODING_MULAW_8:
242 return 'ULAW'
243 elif self._encoding == AUDIO_FILE_ENCODING_ALAW_8:
244 return 'ALAW'
245 else:
246 return 'NONE'
Sjoerd Mullender43bf0bc1993-12-13 11:42:39 +0000247
Tim Peters495ad3c2001-01-15 01:36:40 +0000248 def getcompname(self):
249 if self._encoding == AUDIO_FILE_ENCODING_MULAW_8:
250 return 'CCITT G.711 u-law'
251 elif self._encoding == AUDIO_FILE_ENCODING_ALAW_8:
252 return 'CCITT G.711 A-law'
253 else:
254 return 'not compressed'
Sjoerd Mullender43bf0bc1993-12-13 11:42:39 +0000255
Tim Peters495ad3c2001-01-15 01:36:40 +0000256 def getparams(self):
Serhiy Storchakae06a8962013-09-04 00:43:03 +0300257 return _sunau_params(self.getnchannels(), self.getsampwidth(),
258 self.getframerate(), self.getnframes(),
259 self.getcomptype(), self.getcompname())
Sjoerd Mullender43bf0bc1993-12-13 11:42:39 +0000260
Tim Peters495ad3c2001-01-15 01:36:40 +0000261 def getmarkers(self):
262 return None
Sjoerd Mullender43bf0bc1993-12-13 11:42:39 +0000263
Tim Peters495ad3c2001-01-15 01:36:40 +0000264 def getmark(self, id):
Collin Winterce36ad82007-08-30 01:19:48 +0000265 raise Error('no marks')
Sjoerd Mullender43bf0bc1993-12-13 11:42:39 +0000266
Tim Peters495ad3c2001-01-15 01:36:40 +0000267 def readframes(self, nframes):
268 if self._encoding in _simple_encodings:
269 if nframes == AUDIO_UNKNOWN_SIZE:
270 data = self._file.read()
271 else:
Serhiy Storchaka0300a8d2013-09-28 21:21:39 +0300272 data = self._file.read(nframes * self._framesize)
273 self._soundpos += len(data) // self._framesize
Tim Peters495ad3c2001-01-15 01:36:40 +0000274 if self._encoding == AUDIO_FILE_ENCODING_MULAW_8:
275 import audioop
276 data = audioop.ulaw2lin(data, self._sampwidth)
277 return data
278 return None # XXX--not implemented yet
Sjoerd Mullender43bf0bc1993-12-13 11:42:39 +0000279
Tim Peters495ad3c2001-01-15 01:36:40 +0000280 def rewind(self):
Serhiy Storchaka0300a8d2013-09-28 21:21:39 +0300281 if self._data_pos is None:
282 raise OSError('cannot seek')
283 self._file.seek(self._data_pos)
Tim Peters495ad3c2001-01-15 01:36:40 +0000284 self._soundpos = 0
Sjoerd Mullender43bf0bc1993-12-13 11:42:39 +0000285
Tim Peters495ad3c2001-01-15 01:36:40 +0000286 def tell(self):
287 return self._soundpos
Sjoerd Mullender43bf0bc1993-12-13 11:42:39 +0000288
Tim Peters495ad3c2001-01-15 01:36:40 +0000289 def setpos(self, pos):
290 if pos < 0 or pos > self.getnframes():
Collin Winterce36ad82007-08-30 01:19:48 +0000291 raise Error('position not in range')
Serhiy Storchaka0300a8d2013-09-28 21:21:39 +0300292 if self._data_pos is None:
293 raise OSError('cannot seek')
294 self._file.seek(self._data_pos + pos * self._framesize)
Tim Peters495ad3c2001-01-15 01:36:40 +0000295 self._soundpos = pos
Sjoerd Mullender43bf0bc1993-12-13 11:42:39 +0000296
Tim Peters495ad3c2001-01-15 01:36:40 +0000297 def close(self):
Serhiy Storchaka7e7a3db2015-04-10 13:24:41 +0300298 file = self._file
299 if file:
300 self._file = None
301 if self._opened:
302 file.close()
Sjoerd Mullender43bf0bc1993-12-13 11:42:39 +0000303
304class Au_write:
Sjoerd Mullender2a451411993-12-20 09:36:01 +0000305
Tim Peters495ad3c2001-01-15 01:36:40 +0000306 def __init__(self, f):
307 if type(f) == type(''):
Georg Brandl1a3284e2007-12-02 09:40:06 +0000308 import builtins
309 f = builtins.open(f, 'wb')
Antoine Pitrou4d984892010-10-31 21:27:04 +0000310 self._opened = True
311 else:
312 self._opened = False
Tim Peters495ad3c2001-01-15 01:36:40 +0000313 self.initfp(f)
Sjoerd Mullender43bf0bc1993-12-13 11:42:39 +0000314
Tim Peters495ad3c2001-01-15 01:36:40 +0000315 def __del__(self):
316 if self._file:
317 self.close()
Antoine Pitrou4d984892010-10-31 21:27:04 +0000318 self._file = None
Sjoerd Mullender2a451411993-12-20 09:36:01 +0000319
Serhiy Storchaka34d20132013-09-05 17:01:53 +0300320 def __enter__(self):
321 return self
322
323 def __exit__(self, *args):
324 self.close()
325
Tim Peters495ad3c2001-01-15 01:36:40 +0000326 def initfp(self, file):
327 self._file = file
328 self._framerate = 0
329 self._nchannels = 0
330 self._sampwidth = 0
331 self._framesize = 0
332 self._nframes = AUDIO_UNKNOWN_SIZE
333 self._nframeswritten = 0
334 self._datawritten = 0
335 self._datalength = 0
Victor Stinner7f3652e2010-06-07 20:14:04 +0000336 self._info = b''
Tim Peters495ad3c2001-01-15 01:36:40 +0000337 self._comptype = 'ULAW' # default is U-law
Sjoerd Mullender43bf0bc1993-12-13 11:42:39 +0000338
Tim Peters495ad3c2001-01-15 01:36:40 +0000339 def setnchannels(self, nchannels):
340 if self._nframeswritten:
Collin Winterce36ad82007-08-30 01:19:48 +0000341 raise Error('cannot change parameters after starting to write')
Tim Peters495ad3c2001-01-15 01:36:40 +0000342 if nchannels not in (1, 2, 4):
Collin Winterce36ad82007-08-30 01:19:48 +0000343 raise Error('only 1, 2, or 4 channels supported')
Tim Peters495ad3c2001-01-15 01:36:40 +0000344 self._nchannels = nchannels
Sjoerd Mullender43bf0bc1993-12-13 11:42:39 +0000345
Tim Peters495ad3c2001-01-15 01:36:40 +0000346 def getnchannels(self):
347 if not self._nchannels:
Collin Winterce36ad82007-08-30 01:19:48 +0000348 raise Error('number of channels not set')
Tim Peters495ad3c2001-01-15 01:36:40 +0000349 return self._nchannels
Sjoerd Mullender43bf0bc1993-12-13 11:42:39 +0000350
Tim Peters495ad3c2001-01-15 01:36:40 +0000351 def setsampwidth(self, sampwidth):
352 if self._nframeswritten:
Collin Winterce36ad82007-08-30 01:19:48 +0000353 raise Error('cannot change parameters after starting to write')
Serhiy Storchaka81895f82013-11-10 21:02:53 +0200354 if sampwidth not in (1, 2, 3, 4):
Collin Winterce36ad82007-08-30 01:19:48 +0000355 raise Error('bad sample width')
Tim Peters495ad3c2001-01-15 01:36:40 +0000356 self._sampwidth = sampwidth
Sjoerd Mullender43bf0bc1993-12-13 11:42:39 +0000357
Tim Peters495ad3c2001-01-15 01:36:40 +0000358 def getsampwidth(self):
359 if not self._framerate:
Collin Winterce36ad82007-08-30 01:19:48 +0000360 raise Error('sample width not specified')
Tim Peters495ad3c2001-01-15 01:36:40 +0000361 return self._sampwidth
Sjoerd Mullender43bf0bc1993-12-13 11:42:39 +0000362
Tim Peters495ad3c2001-01-15 01:36:40 +0000363 def setframerate(self, framerate):
364 if self._nframeswritten:
Collin Winterce36ad82007-08-30 01:19:48 +0000365 raise Error('cannot change parameters after starting to write')
Tim Peters495ad3c2001-01-15 01:36:40 +0000366 self._framerate = framerate
Sjoerd Mullender43bf0bc1993-12-13 11:42:39 +0000367
Tim Peters495ad3c2001-01-15 01:36:40 +0000368 def getframerate(self):
369 if not self._framerate:
Collin Winterce36ad82007-08-30 01:19:48 +0000370 raise Error('frame rate not set')
Tim Peters495ad3c2001-01-15 01:36:40 +0000371 return self._framerate
Sjoerd Mullender43bf0bc1993-12-13 11:42:39 +0000372
Tim Peters495ad3c2001-01-15 01:36:40 +0000373 def setnframes(self, nframes):
374 if self._nframeswritten:
Collin Winterce36ad82007-08-30 01:19:48 +0000375 raise Error('cannot change parameters after starting to write')
Tim Peters495ad3c2001-01-15 01:36:40 +0000376 if nframes < 0:
Collin Winterce36ad82007-08-30 01:19:48 +0000377 raise Error('# of frames cannot be negative')
Tim Peters495ad3c2001-01-15 01:36:40 +0000378 self._nframes = nframes
Sjoerd Mullender43bf0bc1993-12-13 11:42:39 +0000379
Tim Peters495ad3c2001-01-15 01:36:40 +0000380 def getnframes(self):
381 return self._nframeswritten
Sjoerd Mullender43bf0bc1993-12-13 11:42:39 +0000382
Tim Peters495ad3c2001-01-15 01:36:40 +0000383 def setcomptype(self, type, name):
384 if type in ('NONE', 'ULAW'):
385 self._comptype = type
386 else:
Collin Winterce36ad82007-08-30 01:19:48 +0000387 raise Error('unknown compression type')
Sjoerd Mullender43bf0bc1993-12-13 11:42:39 +0000388
Tim Peters495ad3c2001-01-15 01:36:40 +0000389 def getcomptype(self):
390 return self._comptype
Sjoerd Mullender43bf0bc1993-12-13 11:42:39 +0000391
Tim Peters495ad3c2001-01-15 01:36:40 +0000392 def getcompname(self):
393 if self._comptype == 'ULAW':
394 return 'CCITT G.711 u-law'
395 elif self._comptype == 'ALAW':
396 return 'CCITT G.711 A-law'
397 else:
398 return 'not compressed'
Sjoerd Mullender43bf0bc1993-12-13 11:42:39 +0000399
Guido van Rossum1bc535d2007-05-15 18:46:22 +0000400 def setparams(self, params):
401 nchannels, sampwidth, framerate, nframes, comptype, compname = params
Tim Peters495ad3c2001-01-15 01:36:40 +0000402 self.setnchannels(nchannels)
403 self.setsampwidth(sampwidth)
404 self.setframerate(framerate)
405 self.setnframes(nframes)
406 self.setcomptype(comptype, compname)
Sjoerd Mullender43bf0bc1993-12-13 11:42:39 +0000407
Tim Peters495ad3c2001-01-15 01:36:40 +0000408 def getparams(self):
Serhiy Storchakaaf722bf2013-09-04 14:30:16 +0300409 return _sunau_params(self.getnchannels(), self.getsampwidth(),
Serhiy Storchakae06a8962013-09-04 00:43:03 +0300410 self.getframerate(), self.getnframes(),
411 self.getcomptype(), self.getcompname())
Sjoerd Mullender43bf0bc1993-12-13 11:42:39 +0000412
Tim Peters495ad3c2001-01-15 01:36:40 +0000413 def tell(self):
414 return self._nframeswritten
Sjoerd Mullender43bf0bc1993-12-13 11:42:39 +0000415
Tim Peters495ad3c2001-01-15 01:36:40 +0000416 def writeframesraw(self, data):
Serhiy Storchaka452bab42013-11-16 14:01:31 +0200417 if not isinstance(data, (bytes, bytearray)):
418 data = memoryview(data).cast('B')
Tim Peters495ad3c2001-01-15 01:36:40 +0000419 self._ensure_header_written()
Tim Peters495ad3c2001-01-15 01:36:40 +0000420 if self._comptype == 'ULAW':
421 import audioop
422 data = audioop.lin2ulaw(data, self._sampwidth)
Serhiy Storchaka0300a8d2013-09-28 21:21:39 +0300423 nframes = len(data) // self._framesize
Tim Peters495ad3c2001-01-15 01:36:40 +0000424 self._file.write(data)
425 self._nframeswritten = self._nframeswritten + nframes
426 self._datawritten = self._datawritten + len(data)
Sjoerd Mullender43bf0bc1993-12-13 11:42:39 +0000427
Tim Peters495ad3c2001-01-15 01:36:40 +0000428 def writeframes(self, data):
429 self.writeframesraw(data)
430 if self._nframeswritten != self._nframes or \
431 self._datalength != self._datawritten:
432 self._patchheader()
Sjoerd Mullender43bf0bc1993-12-13 11:42:39 +0000433
Tim Peters495ad3c2001-01-15 01:36:40 +0000434 def close(self):
Serhiy Storchaka34d20132013-09-05 17:01:53 +0300435 if self._file:
436 try:
437 self._ensure_header_written()
438 if self._nframeswritten != self._nframes or \
439 self._datalength != self._datawritten:
440 self._patchheader()
441 self._file.flush()
442 finally:
Serhiy Storchaka7e7a3db2015-04-10 13:24:41 +0300443 file = self._file
Serhiy Storchaka34d20132013-09-05 17:01:53 +0300444 self._file = None
Serhiy Storchaka7e7a3db2015-04-10 13:24:41 +0300445 if self._opened:
446 file.close()
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