blob: dbad3db8392d9576b9a4feceac910eb78993d22e [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))
211 self._framesize = self._framesize * self._nchannels
212 if self._hdr_size > 24:
213 self._info = file.read(self._hdr_size - 24)
Serhiy Storchaka74a49ac2015-03-20 16:46:19 +0200214 self._info, _, _ = self._info.partition(b'\0')
Tim Peters495ad3c2001-01-15 01:36:40 +0000215 else:
Serhiy Storchaka74a49ac2015-03-20 16:46:19 +0200216 self._info = b''
Serhiy Storchaka0300a8d2013-09-28 21:21:39 +0300217 try:
218 self._data_pos = file.tell()
219 except (AttributeError, OSError):
220 self._data_pos = None
Sjoerd Mullender43bf0bc1993-12-13 11:42:39 +0000221
Tim Peters495ad3c2001-01-15 01:36:40 +0000222 def getfp(self):
223 return self._file
Sjoerd Mullender43bf0bc1993-12-13 11:42:39 +0000224
Tim Peters495ad3c2001-01-15 01:36:40 +0000225 def getnchannels(self):
226 return self._nchannels
Sjoerd Mullender43bf0bc1993-12-13 11:42:39 +0000227
Tim Peters495ad3c2001-01-15 01:36:40 +0000228 def getsampwidth(self):
229 return self._sampwidth
Sjoerd Mullender43bf0bc1993-12-13 11:42:39 +0000230
Tim Peters495ad3c2001-01-15 01:36:40 +0000231 def getframerate(self):
232 return self._framerate
Sjoerd Mullender43bf0bc1993-12-13 11:42:39 +0000233
Tim Peters495ad3c2001-01-15 01:36:40 +0000234 def getnframes(self):
235 if self._data_size == AUDIO_UNKNOWN_SIZE:
236 return AUDIO_UNKNOWN_SIZE
237 if self._encoding in _simple_encodings:
Serhiy Storchaka0300a8d2013-09-28 21:21:39 +0300238 return self._data_size // self._framesize
Tim Peters495ad3c2001-01-15 01:36:40 +0000239 return 0 # XXX--must do some arithmetic here
Sjoerd Mullender43bf0bc1993-12-13 11:42:39 +0000240
Tim Peters495ad3c2001-01-15 01:36:40 +0000241 def getcomptype(self):
242 if self._encoding == AUDIO_FILE_ENCODING_MULAW_8:
243 return 'ULAW'
244 elif self._encoding == AUDIO_FILE_ENCODING_ALAW_8:
245 return 'ALAW'
246 else:
247 return 'NONE'
Sjoerd Mullender43bf0bc1993-12-13 11:42:39 +0000248
Tim Peters495ad3c2001-01-15 01:36:40 +0000249 def getcompname(self):
250 if self._encoding == AUDIO_FILE_ENCODING_MULAW_8:
251 return 'CCITT G.711 u-law'
252 elif self._encoding == AUDIO_FILE_ENCODING_ALAW_8:
253 return 'CCITT G.711 A-law'
254 else:
255 return 'not compressed'
Sjoerd Mullender43bf0bc1993-12-13 11:42:39 +0000256
Tim Peters495ad3c2001-01-15 01:36:40 +0000257 def getparams(self):
Serhiy Storchakae06a8962013-09-04 00:43:03 +0300258 return _sunau_params(self.getnchannels(), self.getsampwidth(),
259 self.getframerate(), self.getnframes(),
260 self.getcomptype(), self.getcompname())
Sjoerd Mullender43bf0bc1993-12-13 11:42:39 +0000261
Tim Peters495ad3c2001-01-15 01:36:40 +0000262 def getmarkers(self):
263 return None
Sjoerd Mullender43bf0bc1993-12-13 11:42:39 +0000264
Tim Peters495ad3c2001-01-15 01:36:40 +0000265 def getmark(self, id):
Collin Winterce36ad82007-08-30 01:19:48 +0000266 raise Error('no marks')
Sjoerd Mullender43bf0bc1993-12-13 11:42:39 +0000267
Tim Peters495ad3c2001-01-15 01:36:40 +0000268 def readframes(self, nframes):
269 if self._encoding in _simple_encodings:
270 if nframes == AUDIO_UNKNOWN_SIZE:
271 data = self._file.read()
272 else:
Serhiy Storchaka0300a8d2013-09-28 21:21:39 +0300273 data = self._file.read(nframes * self._framesize)
274 self._soundpos += len(data) // self._framesize
Tim Peters495ad3c2001-01-15 01:36:40 +0000275 if self._encoding == AUDIO_FILE_ENCODING_MULAW_8:
276 import audioop
277 data = audioop.ulaw2lin(data, self._sampwidth)
278 return data
279 return None # XXX--not implemented yet
Sjoerd Mullender43bf0bc1993-12-13 11:42:39 +0000280
Tim Peters495ad3c2001-01-15 01:36:40 +0000281 def rewind(self):
Serhiy Storchaka0300a8d2013-09-28 21:21:39 +0300282 if self._data_pos is None:
283 raise OSError('cannot seek')
284 self._file.seek(self._data_pos)
Tim Peters495ad3c2001-01-15 01:36:40 +0000285 self._soundpos = 0
Sjoerd Mullender43bf0bc1993-12-13 11:42:39 +0000286
Tim Peters495ad3c2001-01-15 01:36:40 +0000287 def tell(self):
288 return self._soundpos
Sjoerd Mullender43bf0bc1993-12-13 11:42:39 +0000289
Tim Peters495ad3c2001-01-15 01:36:40 +0000290 def setpos(self, pos):
291 if pos < 0 or pos > self.getnframes():
Collin Winterce36ad82007-08-30 01:19:48 +0000292 raise Error('position not in range')
Serhiy Storchaka0300a8d2013-09-28 21:21:39 +0300293 if self._data_pos is None:
294 raise OSError('cannot seek')
295 self._file.seek(self._data_pos + pos * self._framesize)
Tim Peters495ad3c2001-01-15 01:36:40 +0000296 self._soundpos = pos
Sjoerd Mullender43bf0bc1993-12-13 11:42:39 +0000297
Tim Peters495ad3c2001-01-15 01:36:40 +0000298 def close(self):
Serhiy Storchaka7e7a3db2015-04-10 13:24:41 +0300299 file = self._file
300 if file:
301 self._file = None
302 if self._opened:
303 file.close()
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:
Serhiy Storchaka7e7a3db2015-04-10 13:24:41 +0300444 file = self._file
Serhiy Storchaka34d20132013-09-05 17:01:53 +0300445 self._file = None
Serhiy Storchaka7e7a3db2015-04-10 13:24:41 +0300446 if self._opened:
447 file.close()
Sjoerd Mullender43bf0bc1993-12-13 11:42:39 +0000448
Tim Peters495ad3c2001-01-15 01:36:40 +0000449 #
450 # private methods
451 #
Sjoerd Mullender2a451411993-12-20 09:36:01 +0000452
Tim Peters495ad3c2001-01-15 01:36:40 +0000453 def _ensure_header_written(self):
454 if not self._nframeswritten:
455 if not self._nchannels:
Collin Winterce36ad82007-08-30 01:19:48 +0000456 raise Error('# of channels not specified')
Tim Peters495ad3c2001-01-15 01:36:40 +0000457 if not self._sampwidth:
Collin Winterce36ad82007-08-30 01:19:48 +0000458 raise Error('sample width not specified')
Tim Peters495ad3c2001-01-15 01:36:40 +0000459 if not self._framerate:
Collin Winterce36ad82007-08-30 01:19:48 +0000460 raise Error('frame rate not specified')
Tim Peters495ad3c2001-01-15 01:36:40 +0000461 self._write_header()
Sjoerd Mullender43bf0bc1993-12-13 11:42:39 +0000462
Tim Peters495ad3c2001-01-15 01:36:40 +0000463 def _write_header(self):
464 if self._comptype == 'NONE':
465 if self._sampwidth == 1:
466 encoding = AUDIO_FILE_ENCODING_LINEAR_8
467 self._framesize = 1
468 elif self._sampwidth == 2:
469 encoding = AUDIO_FILE_ENCODING_LINEAR_16
470 self._framesize = 2
Serhiy Storchaka81895f82013-11-10 21:02:53 +0200471 elif self._sampwidth == 3:
472 encoding = AUDIO_FILE_ENCODING_LINEAR_24
473 self._framesize = 3
Tim Peters495ad3c2001-01-15 01:36:40 +0000474 elif self._sampwidth == 4:
475 encoding = AUDIO_FILE_ENCODING_LINEAR_32
476 self._framesize = 4
477 else:
Collin Winterce36ad82007-08-30 01:19:48 +0000478 raise Error('internal error')
Tim Peters495ad3c2001-01-15 01:36:40 +0000479 elif self._comptype == 'ULAW':
480 encoding = AUDIO_FILE_ENCODING_MULAW_8
481 self._framesize = 1
482 else:
Collin Winterce36ad82007-08-30 01:19:48 +0000483 raise Error('internal error')
Tim Peters495ad3c2001-01-15 01:36:40 +0000484 self._framesize = self._framesize * self._nchannels
485 _write_u32(self._file, AUDIO_FILE_MAGIC)
486 header_size = 25 + len(self._info)
487 header_size = (header_size + 7) & ~7
488 _write_u32(self._file, header_size)
489 if self._nframes == AUDIO_UNKNOWN_SIZE:
490 length = AUDIO_UNKNOWN_SIZE
491 else:
492 length = self._nframes * self._framesize
Serhiy Storchaka0300a8d2013-09-28 21:21:39 +0300493 try:
494 self._form_length_pos = self._file.tell()
495 except (AttributeError, OSError):
496 self._form_length_pos = None
Tim Peters495ad3c2001-01-15 01:36:40 +0000497 _write_u32(self._file, length)
498 self._datalength = length
499 _write_u32(self._file, encoding)
500 _write_u32(self._file, self._framerate)
501 _write_u32(self._file, self._nchannels)
502 self._file.write(self._info)
Antoine Pitrou7a5dc752008-08-17 00:38:32 +0000503 self._file.write(b'\0'*(header_size - len(self._info) - 24))
Sjoerd Mullender43bf0bc1993-12-13 11:42:39 +0000504
Tim Peters495ad3c2001-01-15 01:36:40 +0000505 def _patchheader(self):
Serhiy Storchaka0300a8d2013-09-28 21:21:39 +0300506 if self._form_length_pos is None:
507 raise OSError('cannot seek')
508 self._file.seek(self._form_length_pos)
Tim Peters495ad3c2001-01-15 01:36:40 +0000509 _write_u32(self._file, self._datawritten)
510 self._datalength = self._datawritten
511 self._file.seek(0, 2)
Sjoerd Mullender43bf0bc1993-12-13 11:42:39 +0000512
Fred Drake43161351999-06-22 21:23:23 +0000513def open(f, mode=None):
Tim Peters495ad3c2001-01-15 01:36:40 +0000514 if mode is None:
515 if hasattr(f, 'mode'):
516 mode = f.mode
517 else:
518 mode = 'rb'
519 if mode in ('r', 'rb'):
520 return Au_read(f)
521 elif mode in ('w', 'wb'):
522 return Au_write(f)
523 else:
Collin Winterce36ad82007-08-30 01:19:48 +0000524 raise Error("mode must be 'r', 'rb', 'w', or 'wb'")
Sjoerd Mullender43bf0bc1993-12-13 11:42:39 +0000525
Brian Curtin9f914a02017-11-10 11:38:25 -0500526def openfp(f, mode=None):
527 warnings.warn("sunau.openfp is deprecated since Python 3.7. "
528 "Use sunau.open instead.", DeprecationWarning, stacklevel=2)
529 return open(f, mode=mode)