blob: 7d7cd6c6886f603c39869c17534f8a0adb67d168 [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
Tim Peters495ad3c2001-01-15 01:36:40 +0000171 def initfp(self, file):
172 self._file = file
173 self._soundpos = 0
174 magic = int(_read_u32(file))
175 if magic != AUDIO_FILE_MAGIC:
Collin Winterce36ad82007-08-30 01:19:48 +0000176 raise Error('bad magic number')
Tim Peters495ad3c2001-01-15 01:36:40 +0000177 self._hdr_size = int(_read_u32(file))
178 if self._hdr_size < 24:
Collin Winterce36ad82007-08-30 01:19:48 +0000179 raise Error('header size too small')
Tim Peters495ad3c2001-01-15 01:36:40 +0000180 if self._hdr_size > 100:
Collin Winterce36ad82007-08-30 01:19:48 +0000181 raise Error('header size ridiculously large')
Tim Peters495ad3c2001-01-15 01:36:40 +0000182 self._data_size = _read_u32(file)
183 if self._data_size != AUDIO_UNKNOWN_SIZE:
184 self._data_size = int(self._data_size)
185 self._encoding = int(_read_u32(file))
186 if self._encoding not in _simple_encodings:
Collin Winterce36ad82007-08-30 01:19:48 +0000187 raise Error('encoding not (yet) supported')
Tim Peters495ad3c2001-01-15 01:36:40 +0000188 if self._encoding in (AUDIO_FILE_ENCODING_MULAW_8,
189 AUDIO_FILE_ENCODING_ALAW_8):
190 self._sampwidth = 2
191 self._framesize = 1
192 elif self._encoding == AUDIO_FILE_ENCODING_LINEAR_8:
193 self._framesize = self._sampwidth = 1
194 elif self._encoding == AUDIO_FILE_ENCODING_LINEAR_16:
195 self._framesize = self._sampwidth = 2
196 elif self._encoding == AUDIO_FILE_ENCODING_LINEAR_24:
197 self._framesize = self._sampwidth = 3
198 elif self._encoding == AUDIO_FILE_ENCODING_LINEAR_32:
199 self._framesize = self._sampwidth = 4
200 else:
Collin Winterce36ad82007-08-30 01:19:48 +0000201 raise Error('unknown encoding')
Tim Peters495ad3c2001-01-15 01:36:40 +0000202 self._framerate = int(_read_u32(file))
203 self._nchannels = int(_read_u32(file))
204 self._framesize = self._framesize * self._nchannels
205 if self._hdr_size > 24:
206 self._info = file.read(self._hdr_size - 24)
207 for i in range(len(self._info)):
Antoine Pitrou7a5dc752008-08-17 00:38:32 +0000208 if self._info[i] == b'\0':
Tim Peters495ad3c2001-01-15 01:36:40 +0000209 self._info = self._info[:i]
210 break
211 else:
212 self._info = ''
Sjoerd Mullender43bf0bc1993-12-13 11:42:39 +0000213
Tim Peters495ad3c2001-01-15 01:36:40 +0000214 def getfp(self):
215 return self._file
Sjoerd Mullender43bf0bc1993-12-13 11:42:39 +0000216
Tim Peters495ad3c2001-01-15 01:36:40 +0000217 def getnchannels(self):
218 return self._nchannels
Sjoerd Mullender43bf0bc1993-12-13 11:42:39 +0000219
Tim Peters495ad3c2001-01-15 01:36:40 +0000220 def getsampwidth(self):
221 return self._sampwidth
Sjoerd Mullender43bf0bc1993-12-13 11:42:39 +0000222
Tim Peters495ad3c2001-01-15 01:36:40 +0000223 def getframerate(self):
224 return self._framerate
Sjoerd Mullender43bf0bc1993-12-13 11:42:39 +0000225
Tim Peters495ad3c2001-01-15 01:36:40 +0000226 def getnframes(self):
227 if self._data_size == AUDIO_UNKNOWN_SIZE:
228 return AUDIO_UNKNOWN_SIZE
229 if self._encoding in _simple_encodings:
230 return self._data_size / self._framesize
231 return 0 # XXX--must do some arithmetic here
Sjoerd Mullender43bf0bc1993-12-13 11:42:39 +0000232
Tim Peters495ad3c2001-01-15 01:36:40 +0000233 def getcomptype(self):
234 if self._encoding == AUDIO_FILE_ENCODING_MULAW_8:
235 return 'ULAW'
236 elif self._encoding == AUDIO_FILE_ENCODING_ALAW_8:
237 return 'ALAW'
238 else:
239 return 'NONE'
Sjoerd Mullender43bf0bc1993-12-13 11:42:39 +0000240
Tim Peters495ad3c2001-01-15 01:36:40 +0000241 def getcompname(self):
242 if self._encoding == AUDIO_FILE_ENCODING_MULAW_8:
243 return 'CCITT G.711 u-law'
244 elif self._encoding == AUDIO_FILE_ENCODING_ALAW_8:
245 return 'CCITT G.711 A-law'
246 else:
247 return 'not compressed'
Sjoerd Mullender43bf0bc1993-12-13 11:42:39 +0000248
Tim Peters495ad3c2001-01-15 01:36:40 +0000249 def getparams(self):
Serhiy Storchakae06a8962013-09-04 00:43:03 +0300250 return _sunau_params(self.getnchannels(), self.getsampwidth(),
251 self.getframerate(), self.getnframes(),
252 self.getcomptype(), self.getcompname())
Sjoerd Mullender43bf0bc1993-12-13 11:42:39 +0000253
Tim Peters495ad3c2001-01-15 01:36:40 +0000254 def getmarkers(self):
255 return None
Sjoerd Mullender43bf0bc1993-12-13 11:42:39 +0000256
Tim Peters495ad3c2001-01-15 01:36:40 +0000257 def getmark(self, id):
Collin Winterce36ad82007-08-30 01:19:48 +0000258 raise Error('no marks')
Sjoerd Mullender43bf0bc1993-12-13 11:42:39 +0000259
Tim Peters495ad3c2001-01-15 01:36:40 +0000260 def readframes(self, nframes):
261 if self._encoding in _simple_encodings:
262 if nframes == AUDIO_UNKNOWN_SIZE:
263 data = self._file.read()
264 else:
265 data = self._file.read(nframes * self._framesize * self._nchannels)
266 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):
273 self._soundpos = 0
274 self._file.seek(self._hdr_size)
Sjoerd Mullender43bf0bc1993-12-13 11:42:39 +0000275
Tim Peters495ad3c2001-01-15 01:36:40 +0000276 def tell(self):
277 return self._soundpos
Sjoerd Mullender43bf0bc1993-12-13 11:42:39 +0000278
Tim Peters495ad3c2001-01-15 01:36:40 +0000279 def setpos(self, pos):
280 if pos < 0 or pos > self.getnframes():
Collin Winterce36ad82007-08-30 01:19:48 +0000281 raise Error('position not in range')
Tim Peters495ad3c2001-01-15 01:36:40 +0000282 self._file.seek(pos * self._framesize + self._hdr_size)
283 self._soundpos = pos
Sjoerd Mullender43bf0bc1993-12-13 11:42:39 +0000284
Tim Peters495ad3c2001-01-15 01:36:40 +0000285 def close(self):
Antoine Pitrou4d984892010-10-31 21:27:04 +0000286 if self._opened and self._file:
287 self._file.close()
Tim Peters495ad3c2001-01-15 01:36:40 +0000288 self._file = None
Sjoerd Mullender43bf0bc1993-12-13 11:42:39 +0000289
290class Au_write:
Sjoerd Mullender2a451411993-12-20 09:36:01 +0000291
Tim Peters495ad3c2001-01-15 01:36:40 +0000292 def __init__(self, f):
293 if type(f) == type(''):
Georg Brandl1a3284e2007-12-02 09:40:06 +0000294 import builtins
295 f = builtins.open(f, 'wb')
Antoine Pitrou4d984892010-10-31 21:27:04 +0000296 self._opened = True
297 else:
298 self._opened = False
Tim Peters495ad3c2001-01-15 01:36:40 +0000299 self.initfp(f)
Sjoerd Mullender43bf0bc1993-12-13 11:42:39 +0000300
Tim Peters495ad3c2001-01-15 01:36:40 +0000301 def __del__(self):
302 if self._file:
303 self.close()
Antoine Pitrou4d984892010-10-31 21:27:04 +0000304 self._file = None
Sjoerd Mullender2a451411993-12-20 09:36:01 +0000305
Tim Peters495ad3c2001-01-15 01:36:40 +0000306 def initfp(self, file):
307 self._file = file
308 self._framerate = 0
309 self._nchannels = 0
310 self._sampwidth = 0
311 self._framesize = 0
312 self._nframes = AUDIO_UNKNOWN_SIZE
313 self._nframeswritten = 0
314 self._datawritten = 0
315 self._datalength = 0
Victor Stinner7f3652e2010-06-07 20:14:04 +0000316 self._info = b''
Tim Peters495ad3c2001-01-15 01:36:40 +0000317 self._comptype = 'ULAW' # default is U-law
Sjoerd Mullender43bf0bc1993-12-13 11:42:39 +0000318
Tim Peters495ad3c2001-01-15 01:36:40 +0000319 def setnchannels(self, nchannels):
320 if self._nframeswritten:
Collin Winterce36ad82007-08-30 01:19:48 +0000321 raise Error('cannot change parameters after starting to write')
Tim Peters495ad3c2001-01-15 01:36:40 +0000322 if nchannels not in (1, 2, 4):
Collin Winterce36ad82007-08-30 01:19:48 +0000323 raise Error('only 1, 2, or 4 channels supported')
Tim Peters495ad3c2001-01-15 01:36:40 +0000324 self._nchannels = nchannels
Sjoerd Mullender43bf0bc1993-12-13 11:42:39 +0000325
Tim Peters495ad3c2001-01-15 01:36:40 +0000326 def getnchannels(self):
327 if not self._nchannels:
Collin Winterce36ad82007-08-30 01:19:48 +0000328 raise Error('number of channels not set')
Tim Peters495ad3c2001-01-15 01:36:40 +0000329 return self._nchannels
Sjoerd Mullender43bf0bc1993-12-13 11:42:39 +0000330
Tim Peters495ad3c2001-01-15 01:36:40 +0000331 def setsampwidth(self, sampwidth):
332 if self._nframeswritten:
Collin Winterce36ad82007-08-30 01:19:48 +0000333 raise Error('cannot change parameters after starting to write')
Tim Peters495ad3c2001-01-15 01:36:40 +0000334 if sampwidth not in (1, 2, 4):
Collin Winterce36ad82007-08-30 01:19:48 +0000335 raise Error('bad sample width')
Tim Peters495ad3c2001-01-15 01:36:40 +0000336 self._sampwidth = sampwidth
Sjoerd Mullender43bf0bc1993-12-13 11:42:39 +0000337
Tim Peters495ad3c2001-01-15 01:36:40 +0000338 def getsampwidth(self):
339 if not self._framerate:
Collin Winterce36ad82007-08-30 01:19:48 +0000340 raise Error('sample width not specified')
Tim Peters495ad3c2001-01-15 01:36:40 +0000341 return self._sampwidth
Sjoerd Mullender43bf0bc1993-12-13 11:42:39 +0000342
Tim Peters495ad3c2001-01-15 01:36:40 +0000343 def setframerate(self, framerate):
344 if self._nframeswritten:
Collin Winterce36ad82007-08-30 01:19:48 +0000345 raise Error('cannot change parameters after starting to write')
Tim Peters495ad3c2001-01-15 01:36:40 +0000346 self._framerate = framerate
Sjoerd Mullender43bf0bc1993-12-13 11:42:39 +0000347
Tim Peters495ad3c2001-01-15 01:36:40 +0000348 def getframerate(self):
349 if not self._framerate:
Collin Winterce36ad82007-08-30 01:19:48 +0000350 raise Error('frame rate not set')
Tim Peters495ad3c2001-01-15 01:36:40 +0000351 return self._framerate
Sjoerd Mullender43bf0bc1993-12-13 11:42:39 +0000352
Tim Peters495ad3c2001-01-15 01:36:40 +0000353 def setnframes(self, nframes):
354 if self._nframeswritten:
Collin Winterce36ad82007-08-30 01:19:48 +0000355 raise Error('cannot change parameters after starting to write')
Tim Peters495ad3c2001-01-15 01:36:40 +0000356 if nframes < 0:
Collin Winterce36ad82007-08-30 01:19:48 +0000357 raise Error('# of frames cannot be negative')
Tim Peters495ad3c2001-01-15 01:36:40 +0000358 self._nframes = nframes
Sjoerd Mullender43bf0bc1993-12-13 11:42:39 +0000359
Tim Peters495ad3c2001-01-15 01:36:40 +0000360 def getnframes(self):
361 return self._nframeswritten
Sjoerd Mullender43bf0bc1993-12-13 11:42:39 +0000362
Tim Peters495ad3c2001-01-15 01:36:40 +0000363 def setcomptype(self, type, name):
364 if type in ('NONE', 'ULAW'):
365 self._comptype = type
366 else:
Collin Winterce36ad82007-08-30 01:19:48 +0000367 raise Error('unknown compression type')
Sjoerd Mullender43bf0bc1993-12-13 11:42:39 +0000368
Tim Peters495ad3c2001-01-15 01:36:40 +0000369 def getcomptype(self):
370 return self._comptype
Sjoerd Mullender43bf0bc1993-12-13 11:42:39 +0000371
Tim Peters495ad3c2001-01-15 01:36:40 +0000372 def getcompname(self):
373 if self._comptype == 'ULAW':
374 return 'CCITT G.711 u-law'
375 elif self._comptype == 'ALAW':
376 return 'CCITT G.711 A-law'
377 else:
378 return 'not compressed'
Sjoerd Mullender43bf0bc1993-12-13 11:42:39 +0000379
Guido van Rossum1bc535d2007-05-15 18:46:22 +0000380 def setparams(self, params):
381 nchannels, sampwidth, framerate, nframes, comptype, compname = params
Tim Peters495ad3c2001-01-15 01:36:40 +0000382 self.setnchannels(nchannels)
383 self.setsampwidth(sampwidth)
384 self.setframerate(framerate)
385 self.setnframes(nframes)
386 self.setcomptype(comptype, compname)
Sjoerd Mullender43bf0bc1993-12-13 11:42:39 +0000387
Tim Peters495ad3c2001-01-15 01:36:40 +0000388 def getparams(self):
Serhiy Storchakae06a8962013-09-04 00:43:03 +0300389 return _sunau_getparams(self.getnchannels(), self.getsampwidth(),
390 self.getframerate(), self.getnframes(),
391 self.getcomptype(), self.getcompname())
Sjoerd Mullender43bf0bc1993-12-13 11:42:39 +0000392
Tim Peters495ad3c2001-01-15 01:36:40 +0000393 def tell(self):
394 return self._nframeswritten
Sjoerd Mullender43bf0bc1993-12-13 11:42:39 +0000395
Tim Peters495ad3c2001-01-15 01:36:40 +0000396 def writeframesraw(self, data):
397 self._ensure_header_written()
398 nframes = len(data) / self._framesize
399 if self._comptype == 'ULAW':
400 import audioop
401 data = audioop.lin2ulaw(data, self._sampwidth)
402 self._file.write(data)
403 self._nframeswritten = self._nframeswritten + nframes
404 self._datawritten = self._datawritten + len(data)
Sjoerd Mullender43bf0bc1993-12-13 11:42:39 +0000405
Tim Peters495ad3c2001-01-15 01:36:40 +0000406 def writeframes(self, data):
407 self.writeframesraw(data)
408 if self._nframeswritten != self._nframes or \
409 self._datalength != self._datawritten:
410 self._patchheader()
Sjoerd Mullender43bf0bc1993-12-13 11:42:39 +0000411
Tim Peters495ad3c2001-01-15 01:36:40 +0000412 def close(self):
413 self._ensure_header_written()
414 if self._nframeswritten != self._nframes or \
415 self._datalength != self._datawritten:
416 self._patchheader()
417 self._file.flush()
Antoine Pitrou4d984892010-10-31 21:27:04 +0000418 if self._opened and self._file:
419 self._file.close()
Tim Peters495ad3c2001-01-15 01:36:40 +0000420 self._file = None
Sjoerd Mullender43bf0bc1993-12-13 11:42:39 +0000421
Tim Peters495ad3c2001-01-15 01:36:40 +0000422 #
423 # private methods
424 #
Sjoerd Mullender2a451411993-12-20 09:36:01 +0000425
Tim Peters495ad3c2001-01-15 01:36:40 +0000426 def _ensure_header_written(self):
427 if not self._nframeswritten:
428 if not self._nchannels:
Collin Winterce36ad82007-08-30 01:19:48 +0000429 raise Error('# of channels not specified')
Tim Peters495ad3c2001-01-15 01:36:40 +0000430 if not self._sampwidth:
Collin Winterce36ad82007-08-30 01:19:48 +0000431 raise Error('sample width not specified')
Tim Peters495ad3c2001-01-15 01:36:40 +0000432 if not self._framerate:
Collin Winterce36ad82007-08-30 01:19:48 +0000433 raise Error('frame rate not specified')
Tim Peters495ad3c2001-01-15 01:36:40 +0000434 self._write_header()
Sjoerd Mullender43bf0bc1993-12-13 11:42:39 +0000435
Tim Peters495ad3c2001-01-15 01:36:40 +0000436 def _write_header(self):
437 if self._comptype == 'NONE':
438 if self._sampwidth == 1:
439 encoding = AUDIO_FILE_ENCODING_LINEAR_8
440 self._framesize = 1
441 elif self._sampwidth == 2:
442 encoding = AUDIO_FILE_ENCODING_LINEAR_16
443 self._framesize = 2
444 elif self._sampwidth == 4:
445 encoding = AUDIO_FILE_ENCODING_LINEAR_32
446 self._framesize = 4
447 else:
Collin Winterce36ad82007-08-30 01:19:48 +0000448 raise Error('internal error')
Tim Peters495ad3c2001-01-15 01:36:40 +0000449 elif self._comptype == 'ULAW':
450 encoding = AUDIO_FILE_ENCODING_MULAW_8
451 self._framesize = 1
452 else:
Collin Winterce36ad82007-08-30 01:19:48 +0000453 raise Error('internal error')
Tim Peters495ad3c2001-01-15 01:36:40 +0000454 self._framesize = self._framesize * self._nchannels
455 _write_u32(self._file, AUDIO_FILE_MAGIC)
456 header_size = 25 + len(self._info)
457 header_size = (header_size + 7) & ~7
458 _write_u32(self._file, header_size)
459 if self._nframes == AUDIO_UNKNOWN_SIZE:
460 length = AUDIO_UNKNOWN_SIZE
461 else:
462 length = self._nframes * self._framesize
463 _write_u32(self._file, length)
464 self._datalength = length
465 _write_u32(self._file, encoding)
466 _write_u32(self._file, self._framerate)
467 _write_u32(self._file, self._nchannels)
468 self._file.write(self._info)
Antoine Pitrou7a5dc752008-08-17 00:38:32 +0000469 self._file.write(b'\0'*(header_size - len(self._info) - 24))
Sjoerd Mullender43bf0bc1993-12-13 11:42:39 +0000470
Tim Peters495ad3c2001-01-15 01:36:40 +0000471 def _patchheader(self):
472 self._file.seek(8)
473 _write_u32(self._file, self._datawritten)
474 self._datalength = self._datawritten
475 self._file.seek(0, 2)
Sjoerd Mullender43bf0bc1993-12-13 11:42:39 +0000476
Fred Drake43161351999-06-22 21:23:23 +0000477def open(f, mode=None):
Tim Peters495ad3c2001-01-15 01:36:40 +0000478 if mode is None:
479 if hasattr(f, 'mode'):
480 mode = f.mode
481 else:
482 mode = 'rb'
483 if mode in ('r', 'rb'):
484 return Au_read(f)
485 elif mode in ('w', 'wb'):
486 return Au_write(f)
487 else:
Collin Winterce36ad82007-08-30 01:19:48 +0000488 raise Error("mode must be 'r', 'rb', 'w', or 'wb'")
Sjoerd Mullender43bf0bc1993-12-13 11:42:39 +0000489
Guido van Rossum7bc817d1993-12-17 15:25:27 +0000490openfp = open