blob: 15f7b0354b22645fbada5cf3856532e92b2c7781 [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')
54 getparams() -- returns a tuple consisting of all of the
55 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
106# from <multimedia/audio_filehdr.h>
107AUDIO_FILE_MAGIC = 0x2e736e64
108AUDIO_FILE_ENCODING_MULAW_8 = 1
109AUDIO_FILE_ENCODING_LINEAR_8 = 2
110AUDIO_FILE_ENCODING_LINEAR_16 = 3
111AUDIO_FILE_ENCODING_LINEAR_24 = 4
112AUDIO_FILE_ENCODING_LINEAR_32 = 5
113AUDIO_FILE_ENCODING_FLOAT = 6
114AUDIO_FILE_ENCODING_DOUBLE = 7
115AUDIO_FILE_ENCODING_ADPCM_G721 = 23
116AUDIO_FILE_ENCODING_ADPCM_G722 = 24
117AUDIO_FILE_ENCODING_ADPCM_G723_3 = 25
118AUDIO_FILE_ENCODING_ADPCM_G723_5 = 26
119AUDIO_FILE_ENCODING_ALAW_8 = 27
120
121# from <multimedia/audio_hdr.h>
Guido van Rossume2a383d2007-01-15 16:59:06 +0000122AUDIO_UNKNOWN_SIZE = 0xFFFFFFFF # ((unsigned)(~0))
Sjoerd Mullender43bf0bc1993-12-13 11:42:39 +0000123
124_simple_encodings = [AUDIO_FILE_ENCODING_MULAW_8,
Tim Peters495ad3c2001-01-15 01:36:40 +0000125 AUDIO_FILE_ENCODING_LINEAR_8,
126 AUDIO_FILE_ENCODING_LINEAR_16,
127 AUDIO_FILE_ENCODING_LINEAR_24,
128 AUDIO_FILE_ENCODING_LINEAR_32,
129 AUDIO_FILE_ENCODING_ALAW_8]
Sjoerd Mullender43bf0bc1993-12-13 11:42:39 +0000130
Fred Drake9b8d8012000-08-17 04:45:13 +0000131class Error(Exception):
Tim Peters495ad3c2001-01-15 01:36:40 +0000132 pass
Sjoerd Mullenderf33a69f1995-08-14 07:49:51 +0000133
Sjoerd Mullender43bf0bc1993-12-13 11:42:39 +0000134def _read_u32(file):
Guido van Rossume2a383d2007-01-15 16:59:06 +0000135 x = 0
Tim Peters495ad3c2001-01-15 01:36:40 +0000136 for i in range(4):
137 byte = file.read(1)
Antoine Pitrou7a5dc752008-08-17 00:38:32 +0000138 if not byte:
Tim Peters495ad3c2001-01-15 01:36:40 +0000139 raise EOFError
140 x = x*256 + ord(byte)
141 return x
Sjoerd Mullender43bf0bc1993-12-13 11:42:39 +0000142
143def _write_u32(file, x):
Tim Peters495ad3c2001-01-15 01:36:40 +0000144 data = []
145 for i in range(4):
146 d, m = divmod(x, 256)
Antoine Pitrou7a5dc752008-08-17 00:38:32 +0000147 data.insert(0, int(m))
Tim Peters495ad3c2001-01-15 01:36:40 +0000148 x = d
Antoine Pitrou7a5dc752008-08-17 00:38:32 +0000149 file.write(bytes(data))
Sjoerd Mullender43bf0bc1993-12-13 11:42:39 +0000150
151class Au_read:
Sjoerd Mullender2a451411993-12-20 09:36:01 +0000152
Tim Peters495ad3c2001-01-15 01:36:40 +0000153 def __init__(self, f):
154 if type(f) == type(''):
Georg Brandl1a3284e2007-12-02 09:40:06 +0000155 import builtins
156 f = builtins.open(f, 'rb')
Antoine Pitrou4d984892010-10-31 21:27:04 +0000157 self._opened = True
158 else:
159 self._opened = False
Tim Peters495ad3c2001-01-15 01:36:40 +0000160 self.initfp(f)
Sjoerd Mullender2a451411993-12-20 09:36:01 +0000161
Tim Peters495ad3c2001-01-15 01:36:40 +0000162 def __del__(self):
163 if self._file:
164 self.close()
Sjoerd Mullender2a451411993-12-20 09:36:01 +0000165
Tim Peters495ad3c2001-01-15 01:36:40 +0000166 def initfp(self, file):
167 self._file = file
168 self._soundpos = 0
169 magic = int(_read_u32(file))
170 if magic != AUDIO_FILE_MAGIC:
Collin Winterce36ad82007-08-30 01:19:48 +0000171 raise Error('bad magic number')
Tim Peters495ad3c2001-01-15 01:36:40 +0000172 self._hdr_size = int(_read_u32(file))
173 if self._hdr_size < 24:
Collin Winterce36ad82007-08-30 01:19:48 +0000174 raise Error('header size too small')
Tim Peters495ad3c2001-01-15 01:36:40 +0000175 if self._hdr_size > 100:
Collin Winterce36ad82007-08-30 01:19:48 +0000176 raise Error('header size ridiculously large')
Tim Peters495ad3c2001-01-15 01:36:40 +0000177 self._data_size = _read_u32(file)
178 if self._data_size != AUDIO_UNKNOWN_SIZE:
179 self._data_size = int(self._data_size)
180 self._encoding = int(_read_u32(file))
181 if self._encoding not in _simple_encodings:
Collin Winterce36ad82007-08-30 01:19:48 +0000182 raise Error('encoding not (yet) supported')
Tim Peters495ad3c2001-01-15 01:36:40 +0000183 if self._encoding in (AUDIO_FILE_ENCODING_MULAW_8,
184 AUDIO_FILE_ENCODING_ALAW_8):
185 self._sampwidth = 2
186 self._framesize = 1
187 elif self._encoding == AUDIO_FILE_ENCODING_LINEAR_8:
188 self._framesize = self._sampwidth = 1
189 elif self._encoding == AUDIO_FILE_ENCODING_LINEAR_16:
190 self._framesize = self._sampwidth = 2
191 elif self._encoding == AUDIO_FILE_ENCODING_LINEAR_24:
192 self._framesize = self._sampwidth = 3
193 elif self._encoding == AUDIO_FILE_ENCODING_LINEAR_32:
194 self._framesize = self._sampwidth = 4
195 else:
Collin Winterce36ad82007-08-30 01:19:48 +0000196 raise Error('unknown encoding')
Tim Peters495ad3c2001-01-15 01:36:40 +0000197 self._framerate = int(_read_u32(file))
198 self._nchannels = int(_read_u32(file))
199 self._framesize = self._framesize * self._nchannels
200 if self._hdr_size > 24:
201 self._info = file.read(self._hdr_size - 24)
202 for i in range(len(self._info)):
Antoine Pitrou7a5dc752008-08-17 00:38:32 +0000203 if self._info[i] == b'\0':
Tim Peters495ad3c2001-01-15 01:36:40 +0000204 self._info = self._info[:i]
205 break
206 else:
207 self._info = ''
Serhiy Storchaka0300a8d2013-09-28 21:21:39 +0300208 try:
209 self._data_pos = file.tell()
210 except (AttributeError, OSError):
211 self._data_pos = None
Sjoerd Mullender43bf0bc1993-12-13 11:42:39 +0000212
Tim Peters495ad3c2001-01-15 01:36:40 +0000213 def getfp(self):
214 return self._file
Sjoerd Mullender43bf0bc1993-12-13 11:42:39 +0000215
Tim Peters495ad3c2001-01-15 01:36:40 +0000216 def getnchannels(self):
217 return self._nchannels
Sjoerd Mullender43bf0bc1993-12-13 11:42:39 +0000218
Tim Peters495ad3c2001-01-15 01:36:40 +0000219 def getsampwidth(self):
220 return self._sampwidth
Sjoerd Mullender43bf0bc1993-12-13 11:42:39 +0000221
Tim Peters495ad3c2001-01-15 01:36:40 +0000222 def getframerate(self):
223 return self._framerate
Sjoerd Mullender43bf0bc1993-12-13 11:42:39 +0000224
Tim Peters495ad3c2001-01-15 01:36:40 +0000225 def getnframes(self):
226 if self._data_size == AUDIO_UNKNOWN_SIZE:
227 return AUDIO_UNKNOWN_SIZE
228 if self._encoding in _simple_encodings:
Serhiy Storchaka0300a8d2013-09-28 21:21:39 +0300229 return self._data_size // self._framesize
Tim Peters495ad3c2001-01-15 01:36:40 +0000230 return 0 # XXX--must do some arithmetic here
Sjoerd Mullender43bf0bc1993-12-13 11:42:39 +0000231
Tim Peters495ad3c2001-01-15 01:36:40 +0000232 def getcomptype(self):
233 if self._encoding == AUDIO_FILE_ENCODING_MULAW_8:
234 return 'ULAW'
235 elif self._encoding == AUDIO_FILE_ENCODING_ALAW_8:
236 return 'ALAW'
237 else:
238 return 'NONE'
Sjoerd Mullender43bf0bc1993-12-13 11:42:39 +0000239
Tim Peters495ad3c2001-01-15 01:36:40 +0000240 def getcompname(self):
241 if self._encoding == AUDIO_FILE_ENCODING_MULAW_8:
242 return 'CCITT G.711 u-law'
243 elif self._encoding == AUDIO_FILE_ENCODING_ALAW_8:
244 return 'CCITT G.711 A-law'
245 else:
246 return 'not compressed'
Sjoerd Mullender43bf0bc1993-12-13 11:42:39 +0000247
Tim Peters495ad3c2001-01-15 01:36:40 +0000248 def getparams(self):
249 return self.getnchannels(), self.getsampwidth(), \
250 self.getframerate(), self.getnframes(), \
251 self.getcomptype(), self.getcompname()
Sjoerd Mullender43bf0bc1993-12-13 11:42:39 +0000252
Tim Peters495ad3c2001-01-15 01:36:40 +0000253 def getmarkers(self):
254 return None
Sjoerd Mullender43bf0bc1993-12-13 11:42:39 +0000255
Tim Peters495ad3c2001-01-15 01:36:40 +0000256 def getmark(self, id):
Collin Winterce36ad82007-08-30 01:19:48 +0000257 raise Error('no marks')
Sjoerd Mullender43bf0bc1993-12-13 11:42:39 +0000258
Tim Peters495ad3c2001-01-15 01:36:40 +0000259 def readframes(self, nframes):
260 if self._encoding in _simple_encodings:
261 if nframes == AUDIO_UNKNOWN_SIZE:
262 data = self._file.read()
263 else:
Serhiy Storchaka0300a8d2013-09-28 21:21:39 +0300264 data = self._file.read(nframes * self._framesize)
265 self._soundpos += len(data) // self._framesize
Tim Peters495ad3c2001-01-15 01:36:40 +0000266 if self._encoding == AUDIO_FILE_ENCODING_MULAW_8:
267 import audioop
268 data = audioop.ulaw2lin(data, self._sampwidth)
269 return data
270 return None # XXX--not implemented yet
Sjoerd Mullender43bf0bc1993-12-13 11:42:39 +0000271
Tim Peters495ad3c2001-01-15 01:36:40 +0000272 def rewind(self):
Serhiy Storchaka0300a8d2013-09-28 21:21:39 +0300273 if self._data_pos is None:
274 raise OSError('cannot seek')
275 self._file.seek(self._data_pos)
Tim Peters495ad3c2001-01-15 01:36:40 +0000276 self._soundpos = 0
Sjoerd Mullender43bf0bc1993-12-13 11:42:39 +0000277
Tim Peters495ad3c2001-01-15 01:36:40 +0000278 def tell(self):
279 return self._soundpos
Sjoerd Mullender43bf0bc1993-12-13 11:42:39 +0000280
Tim Peters495ad3c2001-01-15 01:36:40 +0000281 def setpos(self, pos):
282 if pos < 0 or pos > self.getnframes():
Collin Winterce36ad82007-08-30 01:19:48 +0000283 raise Error('position not in range')
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 + pos * self._framesize)
Tim Peters495ad3c2001-01-15 01:36:40 +0000287 self._soundpos = pos
Sjoerd Mullender43bf0bc1993-12-13 11:42:39 +0000288
Tim Peters495ad3c2001-01-15 01:36:40 +0000289 def close(self):
Antoine Pitrou4d984892010-10-31 21:27:04 +0000290 if self._opened and self._file:
291 self._file.close()
Tim Peters495ad3c2001-01-15 01:36:40 +0000292 self._file = None
Sjoerd Mullender43bf0bc1993-12-13 11:42:39 +0000293
294class Au_write:
Sjoerd Mullender2a451411993-12-20 09:36:01 +0000295
Tim Peters495ad3c2001-01-15 01:36:40 +0000296 def __init__(self, f):
297 if type(f) == type(''):
Georg Brandl1a3284e2007-12-02 09:40:06 +0000298 import builtins
299 f = builtins.open(f, 'wb')
Antoine Pitrou4d984892010-10-31 21:27:04 +0000300 self._opened = True
301 else:
302 self._opened = False
Tim Peters495ad3c2001-01-15 01:36:40 +0000303 self.initfp(f)
Sjoerd Mullender43bf0bc1993-12-13 11:42:39 +0000304
Tim Peters495ad3c2001-01-15 01:36:40 +0000305 def __del__(self):
306 if self._file:
307 self.close()
Antoine Pitrou4d984892010-10-31 21:27:04 +0000308 self._file = None
Sjoerd Mullender2a451411993-12-20 09:36:01 +0000309
Tim Peters495ad3c2001-01-15 01:36:40 +0000310 def initfp(self, file):
311 self._file = file
312 self._framerate = 0
313 self._nchannels = 0
314 self._sampwidth = 0
315 self._framesize = 0
316 self._nframes = AUDIO_UNKNOWN_SIZE
317 self._nframeswritten = 0
318 self._datawritten = 0
319 self._datalength = 0
Victor Stinner7f3652e2010-06-07 20:14:04 +0000320 self._info = b''
Tim Peters495ad3c2001-01-15 01:36:40 +0000321 self._comptype = 'ULAW' # default is U-law
Sjoerd Mullender43bf0bc1993-12-13 11:42:39 +0000322
Tim Peters495ad3c2001-01-15 01:36:40 +0000323 def setnchannels(self, nchannels):
324 if self._nframeswritten:
Collin Winterce36ad82007-08-30 01:19:48 +0000325 raise Error('cannot change parameters after starting to write')
Tim Peters495ad3c2001-01-15 01:36:40 +0000326 if nchannels not in (1, 2, 4):
Collin Winterce36ad82007-08-30 01:19:48 +0000327 raise Error('only 1, 2, or 4 channels supported')
Tim Peters495ad3c2001-01-15 01:36:40 +0000328 self._nchannels = nchannels
Sjoerd Mullender43bf0bc1993-12-13 11:42:39 +0000329
Tim Peters495ad3c2001-01-15 01:36:40 +0000330 def getnchannels(self):
331 if not self._nchannels:
Collin Winterce36ad82007-08-30 01:19:48 +0000332 raise Error('number of channels not set')
Tim Peters495ad3c2001-01-15 01:36:40 +0000333 return self._nchannels
Sjoerd Mullender43bf0bc1993-12-13 11:42:39 +0000334
Tim Peters495ad3c2001-01-15 01:36:40 +0000335 def setsampwidth(self, sampwidth):
336 if self._nframeswritten:
Collin Winterce36ad82007-08-30 01:19:48 +0000337 raise Error('cannot change parameters after starting to write')
Tim Peters495ad3c2001-01-15 01:36:40 +0000338 if sampwidth not in (1, 2, 4):
Collin Winterce36ad82007-08-30 01:19:48 +0000339 raise Error('bad sample width')
Tim Peters495ad3c2001-01-15 01:36:40 +0000340 self._sampwidth = sampwidth
Sjoerd Mullender43bf0bc1993-12-13 11:42:39 +0000341
Tim Peters495ad3c2001-01-15 01:36:40 +0000342 def getsampwidth(self):
343 if not self._framerate:
Collin Winterce36ad82007-08-30 01:19:48 +0000344 raise Error('sample width not specified')
Tim Peters495ad3c2001-01-15 01:36:40 +0000345 return self._sampwidth
Sjoerd Mullender43bf0bc1993-12-13 11:42:39 +0000346
Tim Peters495ad3c2001-01-15 01:36:40 +0000347 def setframerate(self, framerate):
348 if self._nframeswritten:
Collin Winterce36ad82007-08-30 01:19:48 +0000349 raise Error('cannot change parameters after starting to write')
Tim Peters495ad3c2001-01-15 01:36:40 +0000350 self._framerate = framerate
Sjoerd Mullender43bf0bc1993-12-13 11:42:39 +0000351
Tim Peters495ad3c2001-01-15 01:36:40 +0000352 def getframerate(self):
353 if not self._framerate:
Collin Winterce36ad82007-08-30 01:19:48 +0000354 raise Error('frame rate not set')
Tim Peters495ad3c2001-01-15 01:36:40 +0000355 return self._framerate
Sjoerd Mullender43bf0bc1993-12-13 11:42:39 +0000356
Tim Peters495ad3c2001-01-15 01:36:40 +0000357 def setnframes(self, nframes):
358 if self._nframeswritten:
Collin Winterce36ad82007-08-30 01:19:48 +0000359 raise Error('cannot change parameters after starting to write')
Tim Peters495ad3c2001-01-15 01:36:40 +0000360 if nframes < 0:
Collin Winterce36ad82007-08-30 01:19:48 +0000361 raise Error('# of frames cannot be negative')
Tim Peters495ad3c2001-01-15 01:36:40 +0000362 self._nframes = nframes
Sjoerd Mullender43bf0bc1993-12-13 11:42:39 +0000363
Tim Peters495ad3c2001-01-15 01:36:40 +0000364 def getnframes(self):
365 return self._nframeswritten
Sjoerd Mullender43bf0bc1993-12-13 11:42:39 +0000366
Tim Peters495ad3c2001-01-15 01:36:40 +0000367 def setcomptype(self, type, name):
368 if type in ('NONE', 'ULAW'):
369 self._comptype = type
370 else:
Collin Winterce36ad82007-08-30 01:19:48 +0000371 raise Error('unknown compression type')
Sjoerd Mullender43bf0bc1993-12-13 11:42:39 +0000372
Tim Peters495ad3c2001-01-15 01:36:40 +0000373 def getcomptype(self):
374 return self._comptype
Sjoerd Mullender43bf0bc1993-12-13 11:42:39 +0000375
Tim Peters495ad3c2001-01-15 01:36:40 +0000376 def getcompname(self):
377 if self._comptype == 'ULAW':
378 return 'CCITT G.711 u-law'
379 elif self._comptype == 'ALAW':
380 return 'CCITT G.711 A-law'
381 else:
382 return 'not compressed'
Sjoerd Mullender43bf0bc1993-12-13 11:42:39 +0000383
Guido van Rossum1bc535d2007-05-15 18:46:22 +0000384 def setparams(self, params):
385 nchannels, sampwidth, framerate, nframes, comptype, compname = params
Tim Peters495ad3c2001-01-15 01:36:40 +0000386 self.setnchannels(nchannels)
387 self.setsampwidth(sampwidth)
388 self.setframerate(framerate)
389 self.setnframes(nframes)
390 self.setcomptype(comptype, compname)
Sjoerd Mullender43bf0bc1993-12-13 11:42:39 +0000391
Tim Peters495ad3c2001-01-15 01:36:40 +0000392 def getparams(self):
393 return self.getnchannels(), self.getsampwidth(), \
394 self.getframerate(), self.getnframes(), \
395 self.getcomptype(), self.getcompname()
Sjoerd Mullender43bf0bc1993-12-13 11:42:39 +0000396
Tim Peters495ad3c2001-01-15 01:36:40 +0000397 def tell(self):
398 return self._nframeswritten
Sjoerd Mullender43bf0bc1993-12-13 11:42:39 +0000399
Tim Peters495ad3c2001-01-15 01:36:40 +0000400 def writeframesraw(self, data):
401 self._ensure_header_written()
Tim Peters495ad3c2001-01-15 01:36:40 +0000402 if self._comptype == 'ULAW':
403 import audioop
404 data = audioop.lin2ulaw(data, self._sampwidth)
Serhiy Storchaka0300a8d2013-09-28 21:21:39 +0300405 nframes = len(data) // self._framesize
Tim Peters495ad3c2001-01-15 01:36:40 +0000406 self._file.write(data)
407 self._nframeswritten = self._nframeswritten + nframes
408 self._datawritten = self._datawritten + len(data)
Sjoerd Mullender43bf0bc1993-12-13 11:42:39 +0000409
Tim Peters495ad3c2001-01-15 01:36:40 +0000410 def writeframes(self, data):
411 self.writeframesraw(data)
412 if self._nframeswritten != self._nframes or \
413 self._datalength != self._datawritten:
414 self._patchheader()
Sjoerd Mullender43bf0bc1993-12-13 11:42:39 +0000415
Tim Peters495ad3c2001-01-15 01:36:40 +0000416 def close(self):
Serhiy Storchakae23b2d02013-10-12 21:36:10 +0300417 if self._file:
418 try:
419 self._ensure_header_written()
420 if self._nframeswritten != self._nframes or \
421 self._datalength != self._datawritten:
422 self._patchheader()
423 self._file.flush()
424 if self._opened and self._file:
425 self._file.close()
426 finally:
427 self._file = None
Sjoerd Mullender43bf0bc1993-12-13 11:42:39 +0000428
Tim Peters495ad3c2001-01-15 01:36:40 +0000429 #
430 # private methods
431 #
Sjoerd Mullender2a451411993-12-20 09:36:01 +0000432
Tim Peters495ad3c2001-01-15 01:36:40 +0000433 def _ensure_header_written(self):
434 if not self._nframeswritten:
435 if not self._nchannels:
Collin Winterce36ad82007-08-30 01:19:48 +0000436 raise Error('# of channels not specified')
Tim Peters495ad3c2001-01-15 01:36:40 +0000437 if not self._sampwidth:
Collin Winterce36ad82007-08-30 01:19:48 +0000438 raise Error('sample width not specified')
Tim Peters495ad3c2001-01-15 01:36:40 +0000439 if not self._framerate:
Collin Winterce36ad82007-08-30 01:19:48 +0000440 raise Error('frame rate not specified')
Tim Peters495ad3c2001-01-15 01:36:40 +0000441 self._write_header()
Sjoerd Mullender43bf0bc1993-12-13 11:42:39 +0000442
Tim Peters495ad3c2001-01-15 01:36:40 +0000443 def _write_header(self):
444 if self._comptype == 'NONE':
445 if self._sampwidth == 1:
446 encoding = AUDIO_FILE_ENCODING_LINEAR_8
447 self._framesize = 1
448 elif self._sampwidth == 2:
449 encoding = AUDIO_FILE_ENCODING_LINEAR_16
450 self._framesize = 2
451 elif self._sampwidth == 4:
452 encoding = AUDIO_FILE_ENCODING_LINEAR_32
453 self._framesize = 4
454 else:
Collin Winterce36ad82007-08-30 01:19:48 +0000455 raise Error('internal error')
Tim Peters495ad3c2001-01-15 01:36:40 +0000456 elif self._comptype == 'ULAW':
457 encoding = AUDIO_FILE_ENCODING_MULAW_8
458 self._framesize = 1
459 else:
Collin Winterce36ad82007-08-30 01:19:48 +0000460 raise Error('internal error')
Tim Peters495ad3c2001-01-15 01:36:40 +0000461 self._framesize = self._framesize * self._nchannels
462 _write_u32(self._file, AUDIO_FILE_MAGIC)
463 header_size = 25 + len(self._info)
464 header_size = (header_size + 7) & ~7
465 _write_u32(self._file, header_size)
466 if self._nframes == AUDIO_UNKNOWN_SIZE:
467 length = AUDIO_UNKNOWN_SIZE
468 else:
469 length = self._nframes * self._framesize
Serhiy Storchaka0300a8d2013-09-28 21:21:39 +0300470 try:
471 self._form_length_pos = self._file.tell()
472 except (AttributeError, OSError):
473 self._form_length_pos = None
Tim Peters495ad3c2001-01-15 01:36:40 +0000474 _write_u32(self._file, length)
475 self._datalength = length
476 _write_u32(self._file, encoding)
477 _write_u32(self._file, self._framerate)
478 _write_u32(self._file, self._nchannels)
479 self._file.write(self._info)
Antoine Pitrou7a5dc752008-08-17 00:38:32 +0000480 self._file.write(b'\0'*(header_size - len(self._info) - 24))
Sjoerd Mullender43bf0bc1993-12-13 11:42:39 +0000481
Tim Peters495ad3c2001-01-15 01:36:40 +0000482 def _patchheader(self):
Serhiy Storchaka0300a8d2013-09-28 21:21:39 +0300483 if self._form_length_pos is None:
484 raise OSError('cannot seek')
485 self._file.seek(self._form_length_pos)
Tim Peters495ad3c2001-01-15 01:36:40 +0000486 _write_u32(self._file, self._datawritten)
487 self._datalength = self._datawritten
488 self._file.seek(0, 2)
Sjoerd Mullender43bf0bc1993-12-13 11:42:39 +0000489
Fred Drake43161351999-06-22 21:23:23 +0000490def open(f, mode=None):
Tim Peters495ad3c2001-01-15 01:36:40 +0000491 if mode is None:
492 if hasattr(f, 'mode'):
493 mode = f.mode
494 else:
495 mode = 'rb'
496 if mode in ('r', 'rb'):
497 return Au_read(f)
498 elif mode in ('w', 'wb'):
499 return Au_write(f)
500 else:
Collin Winterce36ad82007-08-30 01:19:48 +0000501 raise Error("mode must be 'r', 'rb', 'w', or 'wb'")
Sjoerd Mullender43bf0bc1993-12-13 11:42:39 +0000502
Guido van Rossum7bc817d1993-12-17 15:25:27 +0000503openfp = open