blob: 129502b0b4177ad42f4555004819adb5dd82be3d [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
Brian Curtin9f914a02017-11-10 11:38:25 -0500107import warnings
Serhiy Storchakae06a8962013-09-04 00:43:03 +0300108
109_sunau_params = namedtuple('_sunau_params',
110 'nchannels sampwidth framerate nframes comptype compname')
111
Sjoerd Mullender43bf0bc1993-12-13 11:42:39 +0000112# from <multimedia/audio_filehdr.h>
113AUDIO_FILE_MAGIC = 0x2e736e64
114AUDIO_FILE_ENCODING_MULAW_8 = 1
115AUDIO_FILE_ENCODING_LINEAR_8 = 2
116AUDIO_FILE_ENCODING_LINEAR_16 = 3
117AUDIO_FILE_ENCODING_LINEAR_24 = 4
118AUDIO_FILE_ENCODING_LINEAR_32 = 5
119AUDIO_FILE_ENCODING_FLOAT = 6
120AUDIO_FILE_ENCODING_DOUBLE = 7
121AUDIO_FILE_ENCODING_ADPCM_G721 = 23
122AUDIO_FILE_ENCODING_ADPCM_G722 = 24
123AUDIO_FILE_ENCODING_ADPCM_G723_3 = 25
124AUDIO_FILE_ENCODING_ADPCM_G723_5 = 26
125AUDIO_FILE_ENCODING_ALAW_8 = 27
126
127# from <multimedia/audio_hdr.h>
Guido van Rossume2a383d2007-01-15 16:59:06 +0000128AUDIO_UNKNOWN_SIZE = 0xFFFFFFFF # ((unsigned)(~0))
Sjoerd Mullender43bf0bc1993-12-13 11:42:39 +0000129
130_simple_encodings = [AUDIO_FILE_ENCODING_MULAW_8,
Tim Peters495ad3c2001-01-15 01:36:40 +0000131 AUDIO_FILE_ENCODING_LINEAR_8,
132 AUDIO_FILE_ENCODING_LINEAR_16,
133 AUDIO_FILE_ENCODING_LINEAR_24,
134 AUDIO_FILE_ENCODING_LINEAR_32,
135 AUDIO_FILE_ENCODING_ALAW_8]
Sjoerd Mullender43bf0bc1993-12-13 11:42:39 +0000136
Fred Drake9b8d8012000-08-17 04:45:13 +0000137class Error(Exception):
Tim Peters495ad3c2001-01-15 01:36:40 +0000138 pass
Sjoerd Mullenderf33a69f1995-08-14 07:49:51 +0000139
Sjoerd Mullender43bf0bc1993-12-13 11:42:39 +0000140def _read_u32(file):
Guido van Rossume2a383d2007-01-15 16:59:06 +0000141 x = 0
Tim Peters495ad3c2001-01-15 01:36:40 +0000142 for i in range(4):
143 byte = file.read(1)
Antoine Pitrou7a5dc752008-08-17 00:38:32 +0000144 if not byte:
Tim Peters495ad3c2001-01-15 01:36:40 +0000145 raise EOFError
146 x = x*256 + ord(byte)
147 return x
Sjoerd Mullender43bf0bc1993-12-13 11:42:39 +0000148
149def _write_u32(file, x):
Tim Peters495ad3c2001-01-15 01:36:40 +0000150 data = []
151 for i in range(4):
152 d, m = divmod(x, 256)
Antoine Pitrou7a5dc752008-08-17 00:38:32 +0000153 data.insert(0, int(m))
Tim Peters495ad3c2001-01-15 01:36:40 +0000154 x = d
Antoine Pitrou7a5dc752008-08-17 00:38:32 +0000155 file.write(bytes(data))
Sjoerd Mullender43bf0bc1993-12-13 11:42:39 +0000156
157class Au_read:
Sjoerd Mullender2a451411993-12-20 09:36:01 +0000158
Tim Peters495ad3c2001-01-15 01:36:40 +0000159 def __init__(self, f):
160 if type(f) == type(''):
Georg Brandl1a3284e2007-12-02 09:40:06 +0000161 import builtins
162 f = builtins.open(f, 'rb')
Antoine Pitrou4d984892010-10-31 21:27:04 +0000163 self._opened = True
164 else:
165 self._opened = False
Tim Peters495ad3c2001-01-15 01:36:40 +0000166 self.initfp(f)
Sjoerd Mullender2a451411993-12-20 09:36:01 +0000167
Tim Peters495ad3c2001-01-15 01:36:40 +0000168 def __del__(self):
169 if self._file:
170 self.close()
Sjoerd Mullender2a451411993-12-20 09:36:01 +0000171
Serhiy Storchaka34d20132013-09-05 17:01:53 +0300172 def __enter__(self):
173 return self
174
175 def __exit__(self, *args):
176 self.close()
177
Tim Peters495ad3c2001-01-15 01:36:40 +0000178 def initfp(self, file):
179 self._file = file
180 self._soundpos = 0
181 magic = int(_read_u32(file))
182 if magic != AUDIO_FILE_MAGIC:
Collin Winterce36ad82007-08-30 01:19:48 +0000183 raise Error('bad magic number')
Tim Peters495ad3c2001-01-15 01:36:40 +0000184 self._hdr_size = int(_read_u32(file))
185 if self._hdr_size < 24:
Collin Winterce36ad82007-08-30 01:19:48 +0000186 raise Error('header size too small')
Tim Peters495ad3c2001-01-15 01:36:40 +0000187 if self._hdr_size > 100:
Collin Winterce36ad82007-08-30 01:19:48 +0000188 raise Error('header size ridiculously large')
Tim Peters495ad3c2001-01-15 01:36:40 +0000189 self._data_size = _read_u32(file)
190 if self._data_size != AUDIO_UNKNOWN_SIZE:
191 self._data_size = int(self._data_size)
192 self._encoding = int(_read_u32(file))
193 if self._encoding not in _simple_encodings:
Collin Winterce36ad82007-08-30 01:19:48 +0000194 raise Error('encoding not (yet) supported')
Tim Peters495ad3c2001-01-15 01:36:40 +0000195 if self._encoding in (AUDIO_FILE_ENCODING_MULAW_8,
196 AUDIO_FILE_ENCODING_ALAW_8):
197 self._sampwidth = 2
198 self._framesize = 1
199 elif self._encoding == AUDIO_FILE_ENCODING_LINEAR_8:
200 self._framesize = self._sampwidth = 1
201 elif self._encoding == AUDIO_FILE_ENCODING_LINEAR_16:
202 self._framesize = self._sampwidth = 2
203 elif self._encoding == AUDIO_FILE_ENCODING_LINEAR_24:
204 self._framesize = self._sampwidth = 3
205 elif self._encoding == AUDIO_FILE_ENCODING_LINEAR_32:
206 self._framesize = self._sampwidth = 4
207 else:
Collin Winterce36ad82007-08-30 01:19:48 +0000208 raise Error('unknown encoding')
Tim Peters495ad3c2001-01-15 01:36:40 +0000209 self._framerate = int(_read_u32(file))
210 self._nchannels = int(_read_u32(file))
Serhiy Storchaka134cb012018-03-18 09:55:53 +0200211 if not self._nchannels:
212 raise Error('bad # of channels')
Tim Peters495ad3c2001-01-15 01:36:40 +0000213 self._framesize = self._framesize * self._nchannels
214 if self._hdr_size > 24:
215 self._info = file.read(self._hdr_size - 24)
Serhiy Storchaka74a49ac2015-03-20 16:46:19 +0200216 self._info, _, _ = self._info.partition(b'\0')
Tim Peters495ad3c2001-01-15 01:36:40 +0000217 else:
Serhiy Storchaka74a49ac2015-03-20 16:46:19 +0200218 self._info = b''
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):
Serhiy Storchaka7e7a3db2015-04-10 13:24:41 +0300301 file = self._file
302 if file:
303 self._file = None
304 if self._opened:
305 file.close()
Sjoerd Mullender43bf0bc1993-12-13 11:42:39 +0000306
307class Au_write:
Sjoerd Mullender2a451411993-12-20 09:36:01 +0000308
Tim Peters495ad3c2001-01-15 01:36:40 +0000309 def __init__(self, f):
310 if type(f) == type(''):
Georg Brandl1a3284e2007-12-02 09:40:06 +0000311 import builtins
312 f = builtins.open(f, 'wb')
Antoine Pitrou4d984892010-10-31 21:27:04 +0000313 self._opened = True
314 else:
315 self._opened = False
Tim Peters495ad3c2001-01-15 01:36:40 +0000316 self.initfp(f)
Sjoerd Mullender43bf0bc1993-12-13 11:42:39 +0000317
Tim Peters495ad3c2001-01-15 01:36:40 +0000318 def __del__(self):
319 if self._file:
320 self.close()
Antoine Pitrou4d984892010-10-31 21:27:04 +0000321 self._file = None
Sjoerd Mullender2a451411993-12-20 09:36:01 +0000322
Serhiy Storchaka34d20132013-09-05 17:01:53 +0300323 def __enter__(self):
324 return self
325
326 def __exit__(self, *args):
327 self.close()
328
Tim Peters495ad3c2001-01-15 01:36:40 +0000329 def initfp(self, file):
330 self._file = file
331 self._framerate = 0
332 self._nchannels = 0
333 self._sampwidth = 0
334 self._framesize = 0
335 self._nframes = AUDIO_UNKNOWN_SIZE
336 self._nframeswritten = 0
337 self._datawritten = 0
338 self._datalength = 0
Victor Stinner7f3652e2010-06-07 20:14:04 +0000339 self._info = b''
Tim Peters495ad3c2001-01-15 01:36:40 +0000340 self._comptype = 'ULAW' # default is U-law
Sjoerd Mullender43bf0bc1993-12-13 11:42:39 +0000341
Tim Peters495ad3c2001-01-15 01:36:40 +0000342 def setnchannels(self, nchannels):
343 if self._nframeswritten:
Collin Winterce36ad82007-08-30 01:19:48 +0000344 raise Error('cannot change parameters after starting to write')
Tim Peters495ad3c2001-01-15 01:36:40 +0000345 if nchannels not in (1, 2, 4):
Collin Winterce36ad82007-08-30 01:19:48 +0000346 raise Error('only 1, 2, or 4 channels supported')
Tim Peters495ad3c2001-01-15 01:36:40 +0000347 self._nchannels = nchannels
Sjoerd Mullender43bf0bc1993-12-13 11:42:39 +0000348
Tim Peters495ad3c2001-01-15 01:36:40 +0000349 def getnchannels(self):
350 if not self._nchannels:
Collin Winterce36ad82007-08-30 01:19:48 +0000351 raise Error('number of channels not set')
Tim Peters495ad3c2001-01-15 01:36:40 +0000352 return self._nchannels
Sjoerd Mullender43bf0bc1993-12-13 11:42:39 +0000353
Tim Peters495ad3c2001-01-15 01:36:40 +0000354 def setsampwidth(self, sampwidth):
355 if self._nframeswritten:
Collin Winterce36ad82007-08-30 01:19:48 +0000356 raise Error('cannot change parameters after starting to write')
Serhiy Storchaka81895f82013-11-10 21:02:53 +0200357 if sampwidth not in (1, 2, 3, 4):
Collin Winterce36ad82007-08-30 01:19:48 +0000358 raise Error('bad sample width')
Tim Peters495ad3c2001-01-15 01:36:40 +0000359 self._sampwidth = sampwidth
Sjoerd Mullender43bf0bc1993-12-13 11:42:39 +0000360
Tim Peters495ad3c2001-01-15 01:36:40 +0000361 def getsampwidth(self):
362 if not self._framerate:
Collin Winterce36ad82007-08-30 01:19:48 +0000363 raise Error('sample width not specified')
Tim Peters495ad3c2001-01-15 01:36:40 +0000364 return self._sampwidth
Sjoerd Mullender43bf0bc1993-12-13 11:42:39 +0000365
Tim Peters495ad3c2001-01-15 01:36:40 +0000366 def setframerate(self, framerate):
367 if self._nframeswritten:
Collin Winterce36ad82007-08-30 01:19:48 +0000368 raise Error('cannot change parameters after starting to write')
Tim Peters495ad3c2001-01-15 01:36:40 +0000369 self._framerate = framerate
Sjoerd Mullender43bf0bc1993-12-13 11:42:39 +0000370
Tim Peters495ad3c2001-01-15 01:36:40 +0000371 def getframerate(self):
372 if not self._framerate:
Collin Winterce36ad82007-08-30 01:19:48 +0000373 raise Error('frame rate not set')
Tim Peters495ad3c2001-01-15 01:36:40 +0000374 return self._framerate
Sjoerd Mullender43bf0bc1993-12-13 11:42:39 +0000375
Tim Peters495ad3c2001-01-15 01:36:40 +0000376 def setnframes(self, nframes):
377 if self._nframeswritten:
Collin Winterce36ad82007-08-30 01:19:48 +0000378 raise Error('cannot change parameters after starting to write')
Tim Peters495ad3c2001-01-15 01:36:40 +0000379 if nframes < 0:
Collin Winterce36ad82007-08-30 01:19:48 +0000380 raise Error('# of frames cannot be negative')
Tim Peters495ad3c2001-01-15 01:36:40 +0000381 self._nframes = nframes
Sjoerd Mullender43bf0bc1993-12-13 11:42:39 +0000382
Tim Peters495ad3c2001-01-15 01:36:40 +0000383 def getnframes(self):
384 return self._nframeswritten
Sjoerd Mullender43bf0bc1993-12-13 11:42:39 +0000385
Tim Peters495ad3c2001-01-15 01:36:40 +0000386 def setcomptype(self, type, name):
387 if type in ('NONE', 'ULAW'):
388 self._comptype = type
389 else:
Collin Winterce36ad82007-08-30 01:19:48 +0000390 raise Error('unknown compression type')
Sjoerd Mullender43bf0bc1993-12-13 11:42:39 +0000391
Tim Peters495ad3c2001-01-15 01:36:40 +0000392 def getcomptype(self):
393 return self._comptype
Sjoerd Mullender43bf0bc1993-12-13 11:42:39 +0000394
Tim Peters495ad3c2001-01-15 01:36:40 +0000395 def getcompname(self):
396 if self._comptype == 'ULAW':
397 return 'CCITT G.711 u-law'
398 elif self._comptype == 'ALAW':
399 return 'CCITT G.711 A-law'
400 else:
401 return 'not compressed'
Sjoerd Mullender43bf0bc1993-12-13 11:42:39 +0000402
Guido van Rossum1bc535d2007-05-15 18:46:22 +0000403 def setparams(self, params):
404 nchannels, sampwidth, framerate, nframes, comptype, compname = params
Tim Peters495ad3c2001-01-15 01:36:40 +0000405 self.setnchannels(nchannels)
406 self.setsampwidth(sampwidth)
407 self.setframerate(framerate)
408 self.setnframes(nframes)
409 self.setcomptype(comptype, compname)
Sjoerd Mullender43bf0bc1993-12-13 11:42:39 +0000410
Tim Peters495ad3c2001-01-15 01:36:40 +0000411 def getparams(self):
Serhiy Storchakaaf722bf2013-09-04 14:30:16 +0300412 return _sunau_params(self.getnchannels(), self.getsampwidth(),
Serhiy Storchakae06a8962013-09-04 00:43:03 +0300413 self.getframerate(), self.getnframes(),
414 self.getcomptype(), self.getcompname())
Sjoerd Mullender43bf0bc1993-12-13 11:42:39 +0000415
Tim Peters495ad3c2001-01-15 01:36:40 +0000416 def tell(self):
417 return self._nframeswritten
Sjoerd Mullender43bf0bc1993-12-13 11:42:39 +0000418
Tim Peters495ad3c2001-01-15 01:36:40 +0000419 def writeframesraw(self, data):
Serhiy Storchaka452bab42013-11-16 14:01:31 +0200420 if not isinstance(data, (bytes, bytearray)):
421 data = memoryview(data).cast('B')
Tim Peters495ad3c2001-01-15 01:36:40 +0000422 self._ensure_header_written()
Tim Peters495ad3c2001-01-15 01:36:40 +0000423 if self._comptype == 'ULAW':
424 import audioop
425 data = audioop.lin2ulaw(data, self._sampwidth)
Serhiy Storchaka0300a8d2013-09-28 21:21:39 +0300426 nframes = len(data) // self._framesize
Tim Peters495ad3c2001-01-15 01:36:40 +0000427 self._file.write(data)
428 self._nframeswritten = self._nframeswritten + nframes
429 self._datawritten = self._datawritten + len(data)
Sjoerd Mullender43bf0bc1993-12-13 11:42:39 +0000430
Tim Peters495ad3c2001-01-15 01:36:40 +0000431 def writeframes(self, data):
432 self.writeframesraw(data)
433 if self._nframeswritten != self._nframes or \
434 self._datalength != self._datawritten:
435 self._patchheader()
Sjoerd Mullender43bf0bc1993-12-13 11:42:39 +0000436
Tim Peters495ad3c2001-01-15 01:36:40 +0000437 def close(self):
Serhiy Storchaka34d20132013-09-05 17:01:53 +0300438 if self._file:
439 try:
440 self._ensure_header_written()
441 if self._nframeswritten != self._nframes or \
442 self._datalength != self._datawritten:
443 self._patchheader()
444 self._file.flush()
445 finally:
Serhiy Storchaka7e7a3db2015-04-10 13:24:41 +0300446 file = self._file
Serhiy Storchaka34d20132013-09-05 17:01:53 +0300447 self._file = None
Serhiy Storchaka7e7a3db2015-04-10 13:24:41 +0300448 if self._opened:
449 file.close()
Sjoerd Mullender43bf0bc1993-12-13 11:42:39 +0000450
Tim Peters495ad3c2001-01-15 01:36:40 +0000451 #
452 # private methods
453 #
Sjoerd Mullender2a451411993-12-20 09:36:01 +0000454
Tim Peters495ad3c2001-01-15 01:36:40 +0000455 def _ensure_header_written(self):
456 if not self._nframeswritten:
457 if not self._nchannels:
Collin Winterce36ad82007-08-30 01:19:48 +0000458 raise Error('# of channels not specified')
Tim Peters495ad3c2001-01-15 01:36:40 +0000459 if not self._sampwidth:
Collin Winterce36ad82007-08-30 01:19:48 +0000460 raise Error('sample width not specified')
Tim Peters495ad3c2001-01-15 01:36:40 +0000461 if not self._framerate:
Collin Winterce36ad82007-08-30 01:19:48 +0000462 raise Error('frame rate not specified')
Tim Peters495ad3c2001-01-15 01:36:40 +0000463 self._write_header()
Sjoerd Mullender43bf0bc1993-12-13 11:42:39 +0000464
Tim Peters495ad3c2001-01-15 01:36:40 +0000465 def _write_header(self):
466 if self._comptype == 'NONE':
467 if self._sampwidth == 1:
468 encoding = AUDIO_FILE_ENCODING_LINEAR_8
469 self._framesize = 1
470 elif self._sampwidth == 2:
471 encoding = AUDIO_FILE_ENCODING_LINEAR_16
472 self._framesize = 2
Serhiy Storchaka81895f82013-11-10 21:02:53 +0200473 elif self._sampwidth == 3:
474 encoding = AUDIO_FILE_ENCODING_LINEAR_24
475 self._framesize = 3
Tim Peters495ad3c2001-01-15 01:36:40 +0000476 elif self._sampwidth == 4:
477 encoding = AUDIO_FILE_ENCODING_LINEAR_32
478 self._framesize = 4
479 else:
Collin Winterce36ad82007-08-30 01:19:48 +0000480 raise Error('internal error')
Tim Peters495ad3c2001-01-15 01:36:40 +0000481 elif self._comptype == 'ULAW':
482 encoding = AUDIO_FILE_ENCODING_MULAW_8
483 self._framesize = 1
484 else:
Collin Winterce36ad82007-08-30 01:19:48 +0000485 raise Error('internal error')
Tim Peters495ad3c2001-01-15 01:36:40 +0000486 self._framesize = self._framesize * self._nchannels
487 _write_u32(self._file, AUDIO_FILE_MAGIC)
488 header_size = 25 + len(self._info)
489 header_size = (header_size + 7) & ~7
490 _write_u32(self._file, header_size)
491 if self._nframes == AUDIO_UNKNOWN_SIZE:
492 length = AUDIO_UNKNOWN_SIZE
493 else:
494 length = self._nframes * self._framesize
Serhiy Storchaka0300a8d2013-09-28 21:21:39 +0300495 try:
496 self._form_length_pos = self._file.tell()
497 except (AttributeError, OSError):
498 self._form_length_pos = None
Tim Peters495ad3c2001-01-15 01:36:40 +0000499 _write_u32(self._file, length)
500 self._datalength = length
501 _write_u32(self._file, encoding)
502 _write_u32(self._file, self._framerate)
503 _write_u32(self._file, self._nchannels)
504 self._file.write(self._info)
Antoine Pitrou7a5dc752008-08-17 00:38:32 +0000505 self._file.write(b'\0'*(header_size - len(self._info) - 24))
Sjoerd Mullender43bf0bc1993-12-13 11:42:39 +0000506
Tim Peters495ad3c2001-01-15 01:36:40 +0000507 def _patchheader(self):
Serhiy Storchaka0300a8d2013-09-28 21:21:39 +0300508 if self._form_length_pos is None:
509 raise OSError('cannot seek')
510 self._file.seek(self._form_length_pos)
Tim Peters495ad3c2001-01-15 01:36:40 +0000511 _write_u32(self._file, self._datawritten)
512 self._datalength = self._datawritten
513 self._file.seek(0, 2)
Sjoerd Mullender43bf0bc1993-12-13 11:42:39 +0000514
Fred Drake43161351999-06-22 21:23:23 +0000515def open(f, mode=None):
Tim Peters495ad3c2001-01-15 01:36:40 +0000516 if mode is None:
517 if hasattr(f, 'mode'):
518 mode = f.mode
519 else:
520 mode = 'rb'
521 if mode in ('r', 'rb'):
522 return Au_read(f)
523 elif mode in ('w', 'wb'):
524 return Au_write(f)
525 else:
Collin Winterce36ad82007-08-30 01:19:48 +0000526 raise Error("mode must be 'r', 'rb', 'w', or 'wb'")
Sjoerd Mullender43bf0bc1993-12-13 11:42:39 +0000527
Brian Curtin9f914a02017-11-10 11:38:25 -0500528def openfp(f, mode=None):
529 warnings.warn("sunau.openfp is deprecated since Python 3.7. "
530 "Use sunau.open instead.", DeprecationWarning, stacklevel=2)
531 return open(f, mode=mode)