blob: 3da41b75e4eb1cbbef0ea2f50a6f2d0e7d9e1057 [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)
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):
Antoine Pitrou4d984892010-10-31 21:27:04 +0000298 if self._opened and self._file:
299 self._file.close()
Tim Peters495ad3c2001-01-15 01:36:40 +0000300 self._file = None
Sjoerd Mullender43bf0bc1993-12-13 11:42:39 +0000301
302class Au_write:
Sjoerd Mullender2a451411993-12-20 09:36:01 +0000303
Tim Peters495ad3c2001-01-15 01:36:40 +0000304 def __init__(self, f):
305 if type(f) == type(''):
Georg Brandl1a3284e2007-12-02 09:40:06 +0000306 import builtins
307 f = builtins.open(f, 'wb')
Antoine Pitrou4d984892010-10-31 21:27:04 +0000308 self._opened = True
309 else:
310 self._opened = False
Tim Peters495ad3c2001-01-15 01:36:40 +0000311 self.initfp(f)
Sjoerd Mullender43bf0bc1993-12-13 11:42:39 +0000312
Tim Peters495ad3c2001-01-15 01:36:40 +0000313 def __del__(self):
314 if self._file:
315 self.close()
Antoine Pitrou4d984892010-10-31 21:27:04 +0000316 self._file = None
Sjoerd Mullender2a451411993-12-20 09:36:01 +0000317
Serhiy Storchaka34d20132013-09-05 17:01:53 +0300318 def __enter__(self):
319 return self
320
321 def __exit__(self, *args):
322 self.close()
323
Tim Peters495ad3c2001-01-15 01:36:40 +0000324 def initfp(self, file):
325 self._file = file
326 self._framerate = 0
327 self._nchannels = 0
328 self._sampwidth = 0
329 self._framesize = 0
330 self._nframes = AUDIO_UNKNOWN_SIZE
331 self._nframeswritten = 0
332 self._datawritten = 0
333 self._datalength = 0
Victor Stinner7f3652e2010-06-07 20:14:04 +0000334 self._info = b''
Tim Peters495ad3c2001-01-15 01:36:40 +0000335 self._comptype = 'ULAW' # default is U-law
Sjoerd Mullender43bf0bc1993-12-13 11:42:39 +0000336
Tim Peters495ad3c2001-01-15 01:36:40 +0000337 def setnchannels(self, nchannels):
338 if self._nframeswritten:
Collin Winterce36ad82007-08-30 01:19:48 +0000339 raise Error('cannot change parameters after starting to write')
Tim Peters495ad3c2001-01-15 01:36:40 +0000340 if nchannels not in (1, 2, 4):
Collin Winterce36ad82007-08-30 01:19:48 +0000341 raise Error('only 1, 2, or 4 channels supported')
Tim Peters495ad3c2001-01-15 01:36:40 +0000342 self._nchannels = nchannels
Sjoerd Mullender43bf0bc1993-12-13 11:42:39 +0000343
Tim Peters495ad3c2001-01-15 01:36:40 +0000344 def getnchannels(self):
345 if not self._nchannels:
Collin Winterce36ad82007-08-30 01:19:48 +0000346 raise Error('number of channels not set')
Tim Peters495ad3c2001-01-15 01:36:40 +0000347 return self._nchannels
Sjoerd Mullender43bf0bc1993-12-13 11:42:39 +0000348
Tim Peters495ad3c2001-01-15 01:36:40 +0000349 def setsampwidth(self, sampwidth):
350 if self._nframeswritten:
Collin Winterce36ad82007-08-30 01:19:48 +0000351 raise Error('cannot change parameters after starting to write')
Serhiy Storchaka81895f82013-11-10 21:02:53 +0200352 if sampwidth not in (1, 2, 3, 4):
Collin Winterce36ad82007-08-30 01:19:48 +0000353 raise Error('bad sample width')
Tim Peters495ad3c2001-01-15 01:36:40 +0000354 self._sampwidth = sampwidth
Sjoerd Mullender43bf0bc1993-12-13 11:42:39 +0000355
Tim Peters495ad3c2001-01-15 01:36:40 +0000356 def getsampwidth(self):
357 if not self._framerate:
Collin Winterce36ad82007-08-30 01:19:48 +0000358 raise Error('sample width not specified')
Tim Peters495ad3c2001-01-15 01:36:40 +0000359 return self._sampwidth
Sjoerd Mullender43bf0bc1993-12-13 11:42:39 +0000360
Tim Peters495ad3c2001-01-15 01:36:40 +0000361 def setframerate(self, framerate):
362 if self._nframeswritten:
Collin Winterce36ad82007-08-30 01:19:48 +0000363 raise Error('cannot change parameters after starting to write')
Tim Peters495ad3c2001-01-15 01:36:40 +0000364 self._framerate = framerate
Sjoerd Mullender43bf0bc1993-12-13 11:42:39 +0000365
Tim Peters495ad3c2001-01-15 01:36:40 +0000366 def getframerate(self):
367 if not self._framerate:
Collin Winterce36ad82007-08-30 01:19:48 +0000368 raise Error('frame rate not set')
Tim Peters495ad3c2001-01-15 01:36:40 +0000369 return self._framerate
Sjoerd Mullender43bf0bc1993-12-13 11:42:39 +0000370
Tim Peters495ad3c2001-01-15 01:36:40 +0000371 def setnframes(self, nframes):
372 if self._nframeswritten:
Collin Winterce36ad82007-08-30 01:19:48 +0000373 raise Error('cannot change parameters after starting to write')
Tim Peters495ad3c2001-01-15 01:36:40 +0000374 if nframes < 0:
Collin Winterce36ad82007-08-30 01:19:48 +0000375 raise Error('# of frames cannot be negative')
Tim Peters495ad3c2001-01-15 01:36:40 +0000376 self._nframes = nframes
Sjoerd Mullender43bf0bc1993-12-13 11:42:39 +0000377
Tim Peters495ad3c2001-01-15 01:36:40 +0000378 def getnframes(self):
379 return self._nframeswritten
Sjoerd Mullender43bf0bc1993-12-13 11:42:39 +0000380
Tim Peters495ad3c2001-01-15 01:36:40 +0000381 def setcomptype(self, type, name):
382 if type in ('NONE', 'ULAW'):
383 self._comptype = type
384 else:
Collin Winterce36ad82007-08-30 01:19:48 +0000385 raise Error('unknown compression type')
Sjoerd Mullender43bf0bc1993-12-13 11:42:39 +0000386
Tim Peters495ad3c2001-01-15 01:36:40 +0000387 def getcomptype(self):
388 return self._comptype
Sjoerd Mullender43bf0bc1993-12-13 11:42:39 +0000389
Tim Peters495ad3c2001-01-15 01:36:40 +0000390 def getcompname(self):
391 if self._comptype == 'ULAW':
392 return 'CCITT G.711 u-law'
393 elif self._comptype == 'ALAW':
394 return 'CCITT G.711 A-law'
395 else:
396 return 'not compressed'
Sjoerd Mullender43bf0bc1993-12-13 11:42:39 +0000397
Guido van Rossum1bc535d2007-05-15 18:46:22 +0000398 def setparams(self, params):
399 nchannels, sampwidth, framerate, nframes, comptype, compname = params
Tim Peters495ad3c2001-01-15 01:36:40 +0000400 self.setnchannels(nchannels)
401 self.setsampwidth(sampwidth)
402 self.setframerate(framerate)
403 self.setnframes(nframes)
404 self.setcomptype(comptype, compname)
Sjoerd Mullender43bf0bc1993-12-13 11:42:39 +0000405
Tim Peters495ad3c2001-01-15 01:36:40 +0000406 def getparams(self):
Serhiy Storchakaaf722bf2013-09-04 14:30:16 +0300407 return _sunau_params(self.getnchannels(), self.getsampwidth(),
Serhiy Storchakae06a8962013-09-04 00:43:03 +0300408 self.getframerate(), self.getnframes(),
409 self.getcomptype(), self.getcompname())
Sjoerd Mullender43bf0bc1993-12-13 11:42:39 +0000410
Tim Peters495ad3c2001-01-15 01:36:40 +0000411 def tell(self):
412 return self._nframeswritten
Sjoerd Mullender43bf0bc1993-12-13 11:42:39 +0000413
Tim Peters495ad3c2001-01-15 01:36:40 +0000414 def writeframesraw(self, data):
Serhiy Storchaka452bab42013-11-16 14:01:31 +0200415 if not isinstance(data, (bytes, bytearray)):
416 data = memoryview(data).cast('B')
Tim Peters495ad3c2001-01-15 01:36:40 +0000417 self._ensure_header_written()
Tim Peters495ad3c2001-01-15 01:36:40 +0000418 if self._comptype == 'ULAW':
419 import audioop
420 data = audioop.lin2ulaw(data, self._sampwidth)
Serhiy Storchaka0300a8d2013-09-28 21:21:39 +0300421 nframes = len(data) // self._framesize
Tim Peters495ad3c2001-01-15 01:36:40 +0000422 self._file.write(data)
423 self._nframeswritten = self._nframeswritten + nframes
424 self._datawritten = self._datawritten + len(data)
Sjoerd Mullender43bf0bc1993-12-13 11:42:39 +0000425
Tim Peters495ad3c2001-01-15 01:36:40 +0000426 def writeframes(self, data):
427 self.writeframesraw(data)
428 if self._nframeswritten != self._nframes or \
429 self._datalength != self._datawritten:
430 self._patchheader()
Sjoerd Mullender43bf0bc1993-12-13 11:42:39 +0000431
Tim Peters495ad3c2001-01-15 01:36:40 +0000432 def close(self):
Serhiy Storchaka34d20132013-09-05 17:01:53 +0300433 if self._file:
434 try:
435 self._ensure_header_written()
436 if self._nframeswritten != self._nframes or \
437 self._datalength != self._datawritten:
438 self._patchheader()
439 self._file.flush()
440 finally:
441 if self._opened and self._file:
442 self._file.close()
443 self._file = None
Sjoerd Mullender43bf0bc1993-12-13 11:42:39 +0000444
Tim Peters495ad3c2001-01-15 01:36:40 +0000445 #
446 # private methods
447 #
Sjoerd Mullender2a451411993-12-20 09:36:01 +0000448
Tim Peters495ad3c2001-01-15 01:36:40 +0000449 def _ensure_header_written(self):
450 if not self._nframeswritten:
451 if not self._nchannels:
Collin Winterce36ad82007-08-30 01:19:48 +0000452 raise Error('# of channels not specified')
Tim Peters495ad3c2001-01-15 01:36:40 +0000453 if not self._sampwidth:
Collin Winterce36ad82007-08-30 01:19:48 +0000454 raise Error('sample width not specified')
Tim Peters495ad3c2001-01-15 01:36:40 +0000455 if not self._framerate:
Collin Winterce36ad82007-08-30 01:19:48 +0000456 raise Error('frame rate not specified')
Tim Peters495ad3c2001-01-15 01:36:40 +0000457 self._write_header()
Sjoerd Mullender43bf0bc1993-12-13 11:42:39 +0000458
Tim Peters495ad3c2001-01-15 01:36:40 +0000459 def _write_header(self):
460 if self._comptype == 'NONE':
461 if self._sampwidth == 1:
462 encoding = AUDIO_FILE_ENCODING_LINEAR_8
463 self._framesize = 1
464 elif self._sampwidth == 2:
465 encoding = AUDIO_FILE_ENCODING_LINEAR_16
466 self._framesize = 2
Serhiy Storchaka81895f82013-11-10 21:02:53 +0200467 elif self._sampwidth == 3:
468 encoding = AUDIO_FILE_ENCODING_LINEAR_24
469 self._framesize = 3
Tim Peters495ad3c2001-01-15 01:36:40 +0000470 elif self._sampwidth == 4:
471 encoding = AUDIO_FILE_ENCODING_LINEAR_32
472 self._framesize = 4
473 else:
Collin Winterce36ad82007-08-30 01:19:48 +0000474 raise Error('internal error')
Tim Peters495ad3c2001-01-15 01:36:40 +0000475 elif self._comptype == 'ULAW':
476 encoding = AUDIO_FILE_ENCODING_MULAW_8
477 self._framesize = 1
478 else:
Collin Winterce36ad82007-08-30 01:19:48 +0000479 raise Error('internal error')
Tim Peters495ad3c2001-01-15 01:36:40 +0000480 self._framesize = self._framesize * self._nchannels
481 _write_u32(self._file, AUDIO_FILE_MAGIC)
482 header_size = 25 + len(self._info)
483 header_size = (header_size + 7) & ~7
484 _write_u32(self._file, header_size)
485 if self._nframes == AUDIO_UNKNOWN_SIZE:
486 length = AUDIO_UNKNOWN_SIZE
487 else:
488 length = self._nframes * self._framesize
Serhiy Storchaka0300a8d2013-09-28 21:21:39 +0300489 try:
490 self._form_length_pos = self._file.tell()
491 except (AttributeError, OSError):
492 self._form_length_pos = None
Tim Peters495ad3c2001-01-15 01:36:40 +0000493 _write_u32(self._file, length)
494 self._datalength = length
495 _write_u32(self._file, encoding)
496 _write_u32(self._file, self._framerate)
497 _write_u32(self._file, self._nchannels)
498 self._file.write(self._info)
Antoine Pitrou7a5dc752008-08-17 00:38:32 +0000499 self._file.write(b'\0'*(header_size - len(self._info) - 24))
Sjoerd Mullender43bf0bc1993-12-13 11:42:39 +0000500
Tim Peters495ad3c2001-01-15 01:36:40 +0000501 def _patchheader(self):
Serhiy Storchaka0300a8d2013-09-28 21:21:39 +0300502 if self._form_length_pos is None:
503 raise OSError('cannot seek')
504 self._file.seek(self._form_length_pos)
Tim Peters495ad3c2001-01-15 01:36:40 +0000505 _write_u32(self._file, self._datawritten)
506 self._datalength = self._datawritten
507 self._file.seek(0, 2)
Sjoerd Mullender43bf0bc1993-12-13 11:42:39 +0000508
Fred Drake43161351999-06-22 21:23:23 +0000509def open(f, mode=None):
Tim Peters495ad3c2001-01-15 01:36:40 +0000510 if mode is None:
511 if hasattr(f, 'mode'):
512 mode = f.mode
513 else:
514 mode = 'rb'
515 if mode in ('r', 'rb'):
516 return Au_read(f)
517 elif mode in ('w', 'wb'):
518 return Au_write(f)
519 else:
Collin Winterce36ad82007-08-30 01:19:48 +0000520 raise Error("mode must be 'r', 'rb', 'w', or 'wb'")
Sjoerd Mullender43bf0bc1993-12-13 11:42:39 +0000521
Guido van Rossum7bc817d1993-12-17 15:25:27 +0000522openfp = open