blob: efdc146095a0f57fd3fe6882fecd767a6b25e788 [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)
213 for i in range(len(self._info)):
Antoine Pitrou7a5dc752008-08-17 00:38:32 +0000214 if self._info[i] == b'\0':
Tim Peters495ad3c2001-01-15 01:36:40 +0000215 self._info = self._info[:i]
216 break
217 else:
218 self._info = ''
Sjoerd Mullender43bf0bc1993-12-13 11:42:39 +0000219
Tim Peters495ad3c2001-01-15 01:36:40 +0000220 def getfp(self):
221 return self._file
Sjoerd Mullender43bf0bc1993-12-13 11:42:39 +0000222
Tim Peters495ad3c2001-01-15 01:36:40 +0000223 def getnchannels(self):
224 return self._nchannels
Sjoerd Mullender43bf0bc1993-12-13 11:42:39 +0000225
Tim Peters495ad3c2001-01-15 01:36:40 +0000226 def getsampwidth(self):
227 return self._sampwidth
Sjoerd Mullender43bf0bc1993-12-13 11:42:39 +0000228
Tim Peters495ad3c2001-01-15 01:36:40 +0000229 def getframerate(self):
230 return self._framerate
Sjoerd Mullender43bf0bc1993-12-13 11:42:39 +0000231
Tim Peters495ad3c2001-01-15 01:36:40 +0000232 def getnframes(self):
233 if self._data_size == AUDIO_UNKNOWN_SIZE:
234 return AUDIO_UNKNOWN_SIZE
235 if self._encoding in _simple_encodings:
236 return self._data_size / self._framesize
237 return 0 # XXX--must do some arithmetic here
Sjoerd Mullender43bf0bc1993-12-13 11:42:39 +0000238
Tim Peters495ad3c2001-01-15 01:36:40 +0000239 def getcomptype(self):
240 if self._encoding == AUDIO_FILE_ENCODING_MULAW_8:
241 return 'ULAW'
242 elif self._encoding == AUDIO_FILE_ENCODING_ALAW_8:
243 return 'ALAW'
244 else:
245 return 'NONE'
Sjoerd Mullender43bf0bc1993-12-13 11:42:39 +0000246
Tim Peters495ad3c2001-01-15 01:36:40 +0000247 def getcompname(self):
248 if self._encoding == AUDIO_FILE_ENCODING_MULAW_8:
249 return 'CCITT G.711 u-law'
250 elif self._encoding == AUDIO_FILE_ENCODING_ALAW_8:
251 return 'CCITT G.711 A-law'
252 else:
253 return 'not compressed'
Sjoerd Mullender43bf0bc1993-12-13 11:42:39 +0000254
Tim Peters495ad3c2001-01-15 01:36:40 +0000255 def getparams(self):
Serhiy Storchakae06a8962013-09-04 00:43:03 +0300256 return _sunau_params(self.getnchannels(), self.getsampwidth(),
257 self.getframerate(), self.getnframes(),
258 self.getcomptype(), self.getcompname())
Sjoerd Mullender43bf0bc1993-12-13 11:42:39 +0000259
Tim Peters495ad3c2001-01-15 01:36:40 +0000260 def getmarkers(self):
261 return None
Sjoerd Mullender43bf0bc1993-12-13 11:42:39 +0000262
Tim Peters495ad3c2001-01-15 01:36:40 +0000263 def getmark(self, id):
Collin Winterce36ad82007-08-30 01:19:48 +0000264 raise Error('no marks')
Sjoerd Mullender43bf0bc1993-12-13 11:42:39 +0000265
Tim Peters495ad3c2001-01-15 01:36:40 +0000266 def readframes(self, nframes):
267 if self._encoding in _simple_encodings:
268 if nframes == AUDIO_UNKNOWN_SIZE:
269 data = self._file.read()
270 else:
271 data = self._file.read(nframes * self._framesize * self._nchannels)
272 if self._encoding == AUDIO_FILE_ENCODING_MULAW_8:
273 import audioop
274 data = audioop.ulaw2lin(data, self._sampwidth)
275 return data
276 return None # XXX--not implemented yet
Sjoerd Mullender43bf0bc1993-12-13 11:42:39 +0000277
Tim Peters495ad3c2001-01-15 01:36:40 +0000278 def rewind(self):
279 self._soundpos = 0
280 self._file.seek(self._hdr_size)
Sjoerd Mullender43bf0bc1993-12-13 11:42:39 +0000281
Tim Peters495ad3c2001-01-15 01:36:40 +0000282 def tell(self):
283 return self._soundpos
Sjoerd Mullender43bf0bc1993-12-13 11:42:39 +0000284
Tim Peters495ad3c2001-01-15 01:36:40 +0000285 def setpos(self, pos):
286 if pos < 0 or pos > self.getnframes():
Collin Winterce36ad82007-08-30 01:19:48 +0000287 raise Error('position not in range')
Tim Peters495ad3c2001-01-15 01:36:40 +0000288 self._file.seek(pos * self._framesize + self._hdr_size)
289 self._soundpos = pos
Sjoerd Mullender43bf0bc1993-12-13 11:42:39 +0000290
Tim Peters495ad3c2001-01-15 01:36:40 +0000291 def close(self):
Antoine Pitrou4d984892010-10-31 21:27:04 +0000292 if self._opened and self._file:
293 self._file.close()
Tim Peters495ad3c2001-01-15 01:36:40 +0000294 self._file = None
Sjoerd Mullender43bf0bc1993-12-13 11:42:39 +0000295
296class Au_write:
Sjoerd Mullender2a451411993-12-20 09:36:01 +0000297
Tim Peters495ad3c2001-01-15 01:36:40 +0000298 def __init__(self, f):
299 if type(f) == type(''):
Georg Brandl1a3284e2007-12-02 09:40:06 +0000300 import builtins
301 f = builtins.open(f, 'wb')
Antoine Pitrou4d984892010-10-31 21:27:04 +0000302 self._opened = True
303 else:
304 self._opened = False
Tim Peters495ad3c2001-01-15 01:36:40 +0000305 self.initfp(f)
Sjoerd Mullender43bf0bc1993-12-13 11:42:39 +0000306
Tim Peters495ad3c2001-01-15 01:36:40 +0000307 def __del__(self):
308 if self._file:
309 self.close()
Antoine Pitrou4d984892010-10-31 21:27:04 +0000310 self._file = None
Sjoerd Mullender2a451411993-12-20 09:36:01 +0000311
Serhiy Storchaka34d20132013-09-05 17:01:53 +0300312 def __enter__(self):
313 return self
314
315 def __exit__(self, *args):
316 self.close()
317
Tim Peters495ad3c2001-01-15 01:36:40 +0000318 def initfp(self, file):
319 self._file = file
320 self._framerate = 0
321 self._nchannels = 0
322 self._sampwidth = 0
323 self._framesize = 0
324 self._nframes = AUDIO_UNKNOWN_SIZE
325 self._nframeswritten = 0
326 self._datawritten = 0
327 self._datalength = 0
Victor Stinner7f3652e2010-06-07 20:14:04 +0000328 self._info = b''
Tim Peters495ad3c2001-01-15 01:36:40 +0000329 self._comptype = 'ULAW' # default is U-law
Sjoerd Mullender43bf0bc1993-12-13 11:42:39 +0000330
Tim Peters495ad3c2001-01-15 01:36:40 +0000331 def setnchannels(self, nchannels):
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 nchannels not in (1, 2, 4):
Collin Winterce36ad82007-08-30 01:19:48 +0000335 raise Error('only 1, 2, or 4 channels supported')
Tim Peters495ad3c2001-01-15 01:36:40 +0000336 self._nchannels = nchannels
Sjoerd Mullender43bf0bc1993-12-13 11:42:39 +0000337
Tim Peters495ad3c2001-01-15 01:36:40 +0000338 def getnchannels(self):
339 if not self._nchannels:
Collin Winterce36ad82007-08-30 01:19:48 +0000340 raise Error('number of channels not set')
Tim Peters495ad3c2001-01-15 01:36:40 +0000341 return self._nchannels
Sjoerd Mullender43bf0bc1993-12-13 11:42:39 +0000342
Tim Peters495ad3c2001-01-15 01:36:40 +0000343 def setsampwidth(self, sampwidth):
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 if sampwidth not in (1, 2, 4):
Collin Winterce36ad82007-08-30 01:19:48 +0000347 raise Error('bad sample width')
Tim Peters495ad3c2001-01-15 01:36:40 +0000348 self._sampwidth = sampwidth
Sjoerd Mullender43bf0bc1993-12-13 11:42:39 +0000349
Tim Peters495ad3c2001-01-15 01:36:40 +0000350 def getsampwidth(self):
351 if not self._framerate:
Collin Winterce36ad82007-08-30 01:19:48 +0000352 raise Error('sample width not specified')
Tim Peters495ad3c2001-01-15 01:36:40 +0000353 return self._sampwidth
Sjoerd Mullender43bf0bc1993-12-13 11:42:39 +0000354
Tim Peters495ad3c2001-01-15 01:36:40 +0000355 def setframerate(self, framerate):
356 if self._nframeswritten:
Collin Winterce36ad82007-08-30 01:19:48 +0000357 raise Error('cannot change parameters after starting to write')
Tim Peters495ad3c2001-01-15 01:36:40 +0000358 self._framerate = framerate
Sjoerd Mullender43bf0bc1993-12-13 11:42:39 +0000359
Tim Peters495ad3c2001-01-15 01:36:40 +0000360 def getframerate(self):
361 if not self._framerate:
Collin Winterce36ad82007-08-30 01:19:48 +0000362 raise Error('frame rate not set')
Tim Peters495ad3c2001-01-15 01:36:40 +0000363 return self._framerate
Sjoerd Mullender43bf0bc1993-12-13 11:42:39 +0000364
Tim Peters495ad3c2001-01-15 01:36:40 +0000365 def setnframes(self, nframes):
366 if self._nframeswritten:
Collin Winterce36ad82007-08-30 01:19:48 +0000367 raise Error('cannot change parameters after starting to write')
Tim Peters495ad3c2001-01-15 01:36:40 +0000368 if nframes < 0:
Collin Winterce36ad82007-08-30 01:19:48 +0000369 raise Error('# of frames cannot be negative')
Tim Peters495ad3c2001-01-15 01:36:40 +0000370 self._nframes = nframes
Sjoerd Mullender43bf0bc1993-12-13 11:42:39 +0000371
Tim Peters495ad3c2001-01-15 01:36:40 +0000372 def getnframes(self):
373 return self._nframeswritten
Sjoerd Mullender43bf0bc1993-12-13 11:42:39 +0000374
Tim Peters495ad3c2001-01-15 01:36:40 +0000375 def setcomptype(self, type, name):
376 if type in ('NONE', 'ULAW'):
377 self._comptype = type
378 else:
Collin Winterce36ad82007-08-30 01:19:48 +0000379 raise Error('unknown compression type')
Sjoerd Mullender43bf0bc1993-12-13 11:42:39 +0000380
Tim Peters495ad3c2001-01-15 01:36:40 +0000381 def getcomptype(self):
382 return self._comptype
Sjoerd Mullender43bf0bc1993-12-13 11:42:39 +0000383
Tim Peters495ad3c2001-01-15 01:36:40 +0000384 def getcompname(self):
385 if self._comptype == 'ULAW':
386 return 'CCITT G.711 u-law'
387 elif self._comptype == 'ALAW':
388 return 'CCITT G.711 A-law'
389 else:
390 return 'not compressed'
Sjoerd Mullender43bf0bc1993-12-13 11:42:39 +0000391
Guido van Rossum1bc535d2007-05-15 18:46:22 +0000392 def setparams(self, params):
393 nchannels, sampwidth, framerate, nframes, comptype, compname = params
Tim Peters495ad3c2001-01-15 01:36:40 +0000394 self.setnchannels(nchannels)
395 self.setsampwidth(sampwidth)
396 self.setframerate(framerate)
397 self.setnframes(nframes)
398 self.setcomptype(comptype, compname)
Sjoerd Mullender43bf0bc1993-12-13 11:42:39 +0000399
Tim Peters495ad3c2001-01-15 01:36:40 +0000400 def getparams(self):
Serhiy Storchakaaf722bf2013-09-04 14:30:16 +0300401 return _sunau_params(self.getnchannels(), self.getsampwidth(),
Serhiy Storchakae06a8962013-09-04 00:43:03 +0300402 self.getframerate(), self.getnframes(),
403 self.getcomptype(), self.getcompname())
Sjoerd Mullender43bf0bc1993-12-13 11:42:39 +0000404
Tim Peters495ad3c2001-01-15 01:36:40 +0000405 def tell(self):
406 return self._nframeswritten
Sjoerd Mullender43bf0bc1993-12-13 11:42:39 +0000407
Tim Peters495ad3c2001-01-15 01:36:40 +0000408 def writeframesraw(self, data):
409 self._ensure_header_written()
410 nframes = len(data) / self._framesize
411 if self._comptype == 'ULAW':
412 import audioop
413 data = audioop.lin2ulaw(data, self._sampwidth)
414 self._file.write(data)
415 self._nframeswritten = self._nframeswritten + nframes
416 self._datawritten = self._datawritten + len(data)
Sjoerd Mullender43bf0bc1993-12-13 11:42:39 +0000417
Tim Peters495ad3c2001-01-15 01:36:40 +0000418 def writeframes(self, data):
419 self.writeframesraw(data)
420 if self._nframeswritten != self._nframes or \
421 self._datalength != self._datawritten:
422 self._patchheader()
Sjoerd Mullender43bf0bc1993-12-13 11:42:39 +0000423
Tim Peters495ad3c2001-01-15 01:36:40 +0000424 def close(self):
Serhiy Storchaka34d20132013-09-05 17:01:53 +0300425 if self._file:
426 try:
427 self._ensure_header_written()
428 if self._nframeswritten != self._nframes or \
429 self._datalength != self._datawritten:
430 self._patchheader()
431 self._file.flush()
432 finally:
433 if self._opened and self._file:
434 self._file.close()
435 self._file = None
Sjoerd Mullender43bf0bc1993-12-13 11:42:39 +0000436
Tim Peters495ad3c2001-01-15 01:36:40 +0000437 #
438 # private methods
439 #
Sjoerd Mullender2a451411993-12-20 09:36:01 +0000440
Tim Peters495ad3c2001-01-15 01:36:40 +0000441 def _ensure_header_written(self):
442 if not self._nframeswritten:
443 if not self._nchannels:
Collin Winterce36ad82007-08-30 01:19:48 +0000444 raise Error('# of channels not specified')
Tim Peters495ad3c2001-01-15 01:36:40 +0000445 if not self._sampwidth:
Collin Winterce36ad82007-08-30 01:19:48 +0000446 raise Error('sample width not specified')
Tim Peters495ad3c2001-01-15 01:36:40 +0000447 if not self._framerate:
Collin Winterce36ad82007-08-30 01:19:48 +0000448 raise Error('frame rate not specified')
Tim Peters495ad3c2001-01-15 01:36:40 +0000449 self._write_header()
Sjoerd Mullender43bf0bc1993-12-13 11:42:39 +0000450
Tim Peters495ad3c2001-01-15 01:36:40 +0000451 def _write_header(self):
452 if self._comptype == 'NONE':
453 if self._sampwidth == 1:
454 encoding = AUDIO_FILE_ENCODING_LINEAR_8
455 self._framesize = 1
456 elif self._sampwidth == 2:
457 encoding = AUDIO_FILE_ENCODING_LINEAR_16
458 self._framesize = 2
459 elif self._sampwidth == 4:
460 encoding = AUDIO_FILE_ENCODING_LINEAR_32
461 self._framesize = 4
462 else:
Collin Winterce36ad82007-08-30 01:19:48 +0000463 raise Error('internal error')
Tim Peters495ad3c2001-01-15 01:36:40 +0000464 elif self._comptype == 'ULAW':
465 encoding = AUDIO_FILE_ENCODING_MULAW_8
466 self._framesize = 1
467 else:
Collin Winterce36ad82007-08-30 01:19:48 +0000468 raise Error('internal error')
Tim Peters495ad3c2001-01-15 01:36:40 +0000469 self._framesize = self._framesize * self._nchannels
470 _write_u32(self._file, AUDIO_FILE_MAGIC)
471 header_size = 25 + len(self._info)
472 header_size = (header_size + 7) & ~7
473 _write_u32(self._file, header_size)
474 if self._nframes == AUDIO_UNKNOWN_SIZE:
475 length = AUDIO_UNKNOWN_SIZE
476 else:
477 length = self._nframes * self._framesize
478 _write_u32(self._file, length)
479 self._datalength = length
480 _write_u32(self._file, encoding)
481 _write_u32(self._file, self._framerate)
482 _write_u32(self._file, self._nchannels)
483 self._file.write(self._info)
Antoine Pitrou7a5dc752008-08-17 00:38:32 +0000484 self._file.write(b'\0'*(header_size - len(self._info) - 24))
Sjoerd Mullender43bf0bc1993-12-13 11:42:39 +0000485
Tim Peters495ad3c2001-01-15 01:36:40 +0000486 def _patchheader(self):
487 self._file.seek(8)
488 _write_u32(self._file, self._datawritten)
489 self._datalength = self._datawritten
490 self._file.seek(0, 2)
Sjoerd Mullender43bf0bc1993-12-13 11:42:39 +0000491
Fred Drake43161351999-06-22 21:23:23 +0000492def open(f, mode=None):
Tim Peters495ad3c2001-01-15 01:36:40 +0000493 if mode is None:
494 if hasattr(f, 'mode'):
495 mode = f.mode
496 else:
497 mode = 'rb'
498 if mode in ('r', 'rb'):
499 return Au_read(f)
500 elif mode in ('w', 'wb'):
501 return Au_write(f)
502 else:
Collin Winterce36ad82007-08-30 01:19:48 +0000503 raise Error("mode must be 'r', 'rb', 'w', or 'wb'")
Sjoerd Mullender43bf0bc1993-12-13 11:42:39 +0000504
Guido van Rossum7bc817d1993-12-17 15:25:27 +0000505openfp = open