blob: e0b76d6dd2fd44ebbc4dd4a39aa24b642fe2a470 [file] [log] [blame]
Guido van Rossum4acc25b2000-02-02 15:10:15 +00001"""Stuff to parse AIFF-C and AIFF files.
2
3Unless explicitly stated otherwise, the description below is true
4both for AIFF-C files and AIFF files.
5
6An AIFF-C file has the following structure.
7
8 +-----------------+
9 | FORM |
10 +-----------------+
11 | <size> |
12 +----+------------+
13 | | AIFC |
14 | +------------+
15 | | <chunks> |
16 | | . |
17 | | . |
18 | | . |
19 +----+------------+
20
21An AIFF file has the string "AIFF" instead of "AIFC".
22
23A chunk consists of an identifier (4 bytes) followed by a size (4 bytes,
24big endian order), followed by the data. The size field does not include
25the size of the 8 byte header.
26
27The following chunk types are recognized.
28
29 FVER
30 <version number of AIFF-C defining document> (AIFF-C only).
31 MARK
32 <# of markers> (2 bytes)
33 list of markers:
34 <marker ID> (2 bytes, must be > 0)
35 <position> (4 bytes)
36 <marker name> ("pstring")
37 COMM
38 <# of channels> (2 bytes)
39 <# of sound frames> (4 bytes)
40 <size of the samples> (2 bytes)
41 <sampling frequency> (10 bytes, IEEE 80-bit extended
42 floating point)
43 in AIFF-C files only:
44 <compression type> (4 bytes)
45 <human-readable version of compression type> ("pstring")
46 SSND
47 <offset> (4 bytes, not used by this program)
48 <blocksize> (4 bytes, not used by this program)
49 <sound data>
50
51A pstring consists of 1 byte length, a string of characters, and 0 or 1
52byte pad to make the total length even.
53
54Usage.
55
56Reading AIFF files:
57 f = aifc.open(file, 'r')
58where file is either the name of a file or an open file pointer.
59The open file pointer must have methods read(), seek(), and close().
60In some types of audio files, if the setpos() method is not used,
61the seek() method is not necessary.
62
63This returns an instance of a class with the following public methods:
64 getnchannels() -- returns number of audio channels (1 for
65 mono, 2 for stereo)
66 getsampwidth() -- returns sample width in bytes
67 getframerate() -- returns sampling frequency
68 getnframes() -- returns number of audio frames
69 getcomptype() -- returns compression type ('NONE' for AIFF files)
70 getcompname() -- returns human-readable version of
71 compression type ('not compressed' for AIFF files)
72 getparams() -- returns a tuple consisting of all of the
73 above in the above order
74 getmarkers() -- get the list of marks in the audio file or None
75 if there are no marks
76 getmark(id) -- get mark with the specified id (raises an error
77 if the mark does not exist)
78 readframes(n) -- returns at most n frames of audio
79 rewind() -- rewind to the beginning of the audio stream
80 setpos(pos) -- seek to the specified position
81 tell() -- return the current position
82 close() -- close the instance (make it unusable)
83The position returned by tell(), the position given to setpos() and
84the position of marks are all compatible and have nothing to do with
Thomas Wouters7e474022000-07-16 12:04:32 +000085the actual position in the file.
Guido van Rossum4acc25b2000-02-02 15:10:15 +000086The close() method is called automatically when the class instance
87is destroyed.
88
89Writing AIFF files:
90 f = aifc.open(file, 'w')
91where file is either the name of a file or an open file pointer.
92The open file pointer must have methods write(), tell(), seek(), and
93close().
94
95This returns an instance of a class with the following public methods:
96 aiff() -- create an AIFF file (AIFF-C default)
97 aifc() -- create an AIFF-C file
98 setnchannels(n) -- set the number of channels
99 setsampwidth(n) -- set the sample width
100 setframerate(n) -- set the frame rate
101 setnframes(n) -- set the number of frames
102 setcomptype(type, name)
103 -- set the compression type and the
104 human-readable compression type
105 setparams(tuple)
106 -- set all parameters at once
107 setmark(id, pos, name)
108 -- add specified mark to the list of marks
109 tell() -- return current position in output file (useful
110 in combination with setmark())
111 writeframesraw(data)
112 -- write audio frames without pathing up the
113 file header
114 writeframes(data)
115 -- write audio frames and patch up the file header
116 close() -- patch up the file header and close the
117 output file
118You should set the parameters before the first writeframesraw or
119writeframes. The total number of frames does not need to be set,
120but when it is set to the correct value, the header does not have to
121be patched up.
122It is best to first set all parameters, perhaps possibly the
123compression type, and then write audio frames using writeframesraw.
124When all frames have been written, either call writeframes('') or
125close() to patch up the sizes in the header.
126Marks can be added anytime. If there are any marks, ypu must call
127close() after all frames have been written.
128The close() method is called automatically when the class instance
129is destroyed.
130
131When a file is opened with the extension '.aiff', an AIFF file is
132written, otherwise an AIFF-C file is written. This default can be
133changed by calling aiff() or aifc() before the first writeframes or
134writeframesraw.
135"""
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000136
Guido van Rossum36bb1811996-12-31 05:57:34 +0000137import struct
Guido van Rossum3db6ebc1994-01-28 09:59:35 +0000138import __builtin__
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000139
Skip Montanaroe99d5ea2001-01-20 19:54:20 +0000140__all__ = ["Error","open","openfp"]
141
Fred Drake227b1202000-08-17 05:06:49 +0000142class Error(Exception):
143 pass
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000144
Guido van Rossum820819c2002-08-12 22:11:28 +0000145_AIFC_version = 0xA2805140L # Version 1 of AIFF-C
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000146
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000147def _read_long(file):
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000148 try:
149 return struct.unpack('>l', file.read(4))[0]
150 except struct.error:
151 raise EOFError
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000152
153def _read_ulong(file):
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000154 try:
155 return struct.unpack('>L', file.read(4))[0]
156 except struct.error:
157 raise EOFError
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000158
159def _read_short(file):
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000160 try:
161 return struct.unpack('>h', file.read(2))[0]
162 except struct.error:
163 raise EOFError
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000164
165def _read_string(file):
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000166 length = ord(file.read(1))
167 if length == 0:
168 data = ''
169 else:
170 data = file.read(length)
171 if length & 1 == 0:
172 dummy = file.read(1)
173 return data
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000174
175_HUGE_VAL = 1.79769313486231e+308 # See <limits.h>
176
177def _read_float(f): # 10 bytes
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000178 expon = _read_short(f) # 2 bytes
179 sign = 1
180 if expon < 0:
181 sign = -1
182 expon = expon + 0x8000
183 himant = _read_ulong(f) # 4 bytes
184 lomant = _read_ulong(f) # 4 bytes
185 if expon == himant == lomant == 0:
186 f = 0.0
187 elif expon == 0x7FFF:
188 f = _HUGE_VAL
189 else:
190 expon = expon - 16383
191 f = (himant * 0x100000000L + lomant) * pow(2.0, expon - 63)
192 return sign * f
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000193
194def _write_short(f, x):
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000195 f.write(struct.pack('>h', x))
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000196
197def _write_long(f, x):
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000198 f.write(struct.pack('>L', x))
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000199
200def _write_string(f, s):
Bob Ippolito6067f202006-05-30 00:26:01 +0000201 if len(s) > 255:
202 raise ValueError("string exceeds maximum pstring length")
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000203 f.write(chr(len(s)))
204 f.write(s)
205 if len(s) & 1 == 0:
206 f.write(chr(0))
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000207
208def _write_float(f, x):
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000209 import math
210 if x < 0:
211 sign = 0x8000
212 x = x * -1
213 else:
214 sign = 0
215 if x == 0:
216 expon = 0
217 himant = 0
218 lomant = 0
219 else:
220 fmant, expon = math.frexp(x)
221 if expon > 16384 or fmant >= 1: # Infinity or NaN
222 expon = sign|0x7FFF
223 himant = 0
224 lomant = 0
225 else: # Finite
226 expon = expon + 16382
227 if expon < 0: # denormalized
228 fmant = math.ldexp(fmant, expon)
229 expon = 0
230 expon = expon | sign
231 fmant = math.ldexp(fmant, 32)
232 fsmant = math.floor(fmant)
233 himant = long(fsmant)
234 fmant = math.ldexp(fmant - fsmant, 32)
235 fsmant = math.floor(fmant)
236 lomant = long(fsmant)
237 _write_short(f, expon)
238 _write_long(f, himant)
239 _write_long(f, lomant)
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000240
Guido van Rossum8ea7bb81999-06-09 13:32:28 +0000241from chunk import Chunk
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000242
Guido van Rossumd3166071993-05-24 14:16:22 +0000243class Aifc_read:
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000244 # Variables used in this class:
245 #
246 # These variables are available to the user though appropriate
247 # methods of this class:
248 # _file -- the open file with methods read(), close(), and seek()
249 # set through the __init__() method
250 # _nchannels -- the number of audio channels
251 # available through the getnchannels() method
252 # _nframes -- the number of audio frames
253 # available through the getnframes() method
254 # _sampwidth -- the number of bytes per audio sample
255 # available through the getsampwidth() method
256 # _framerate -- the sampling frequency
257 # available through the getframerate() method
258 # _comptype -- the AIFF-C compression type ('NONE' if AIFF)
259 # available through the getcomptype() method
260 # _compname -- the human-readable AIFF-C compression type
261 # available through the getcomptype() method
262 # _markers -- the marks in the audio file
263 # available through the getmarkers() and getmark()
264 # methods
265 # _soundpos -- the position in the audio stream
266 # available through the tell() method, set through the
267 # setpos() method
268 #
269 # These variables are used internally only:
270 # _version -- the AIFF-C version number
271 # _decomp -- the decompressor from builtin module cl
272 # _comm_chunk_read -- 1 iff the COMM chunk has been read
273 # _aifc -- 1 iff reading an AIFF-C file
274 # _ssnd_seek_needed -- 1 iff positioned correctly in audio
275 # file for readframes()
276 # _ssnd_chunk -- instantiation of a chunk class for the SSND chunk
277 # _framesize -- size of one frame in the file
Sjoerd Mullender2a451411993-12-20 09:36:01 +0000278
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000279 def initfp(self, file):
280 self._version = 0
281 self._decomp = None
282 self._convert = None
283 self._markers = []
284 self._soundpos = 0
R. David Murrayd0a45392009-05-07 16:45:44 +0000285 self._file = file
286 chunk = Chunk(file)
287 if chunk.getname() != 'FORM':
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000288 raise Error, 'file does not start with FORM id'
R. David Murrayd0a45392009-05-07 16:45:44 +0000289 formdata = chunk.read(4)
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000290 if formdata == 'AIFF':
291 self._aifc = 0
292 elif formdata == 'AIFC':
293 self._aifc = 1
294 else:
295 raise Error, 'not an AIFF or AIFF-C file'
296 self._comm_chunk_read = 0
297 while 1:
298 self._ssnd_seek_needed = 1
299 try:
300 chunk = Chunk(self._file)
301 except EOFError:
302 break
303 chunkname = chunk.getname()
304 if chunkname == 'COMM':
305 self._read_comm_chunk(chunk)
306 self._comm_chunk_read = 1
307 elif chunkname == 'SSND':
308 self._ssnd_chunk = chunk
309 dummy = chunk.read(8)
310 self._ssnd_seek_needed = 0
311 elif chunkname == 'FVER':
Guido van Rossum820819c2002-08-12 22:11:28 +0000312 self._version = _read_ulong(chunk)
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000313 elif chunkname == 'MARK':
314 self._readmark(chunk)
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000315 chunk.skip()
316 if not self._comm_chunk_read or not self._ssnd_chunk:
317 raise Error, 'COMM chunk and/or SSND chunk missing'
318 if self._aifc and self._decomp:
319 import cl
320 params = [cl.ORIGINAL_FORMAT, 0,
321 cl.BITS_PER_COMPONENT, self._sampwidth * 8,
322 cl.FRAME_RATE, self._framerate]
323 if self._nchannels == 1:
324 params[1] = cl.MONO
325 elif self._nchannels == 2:
326 params[1] = cl.STEREO_INTERLEAVED
327 else:
328 raise Error, 'cannot compress more than 2 channels'
329 self._decomp.SetParams(params)
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000330
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000331 def __init__(self, f):
332 if type(f) == type(''):
333 f = __builtin__.open(f, 'rb')
334 # else, assume it is an open file object already
335 self.initfp(f)
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000336
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000337 #
338 # User visible methods.
339 #
340 def getfp(self):
341 return self._file
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000342
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000343 def rewind(self):
344 self._ssnd_seek_needed = 1
345 self._soundpos = 0
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000346
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000347 def close(self):
348 if self._decomp:
349 self._decomp.CloseDecompressor()
350 self._decomp = None
R. David Murrayd0a45392009-05-07 16:45:44 +0000351 self._file.close()
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000352
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000353 def tell(self):
354 return self._soundpos
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000355
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000356 def getnchannels(self):
357 return self._nchannels
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000358
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000359 def getnframes(self):
360 return self._nframes
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000361
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000362 def getsampwidth(self):
363 return self._sampwidth
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000364
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000365 def getframerate(self):
366 return self._framerate
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000367
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000368 def getcomptype(self):
369 return self._comptype
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000370
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000371 def getcompname(self):
372 return self._compname
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000373
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000374## def getversion(self):
375## return self._version
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000376
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000377 def getparams(self):
378 return self.getnchannels(), self.getsampwidth(), \
379 self.getframerate(), self.getnframes(), \
380 self.getcomptype(), self.getcompname()
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000381
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000382 def getmarkers(self):
383 if len(self._markers) == 0:
384 return None
385 return self._markers
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000386
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000387 def getmark(self, id):
388 for marker in self._markers:
389 if id == marker[0]:
390 return marker
Walter Dörwald70a6b492004-02-12 17:35:32 +0000391 raise Error, 'marker %r does not exist' % (id,)
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000392
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000393 def setpos(self, pos):
394 if pos < 0 or pos > self._nframes:
395 raise Error, 'position not in range'
396 self._soundpos = pos
397 self._ssnd_seek_needed = 1
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000398
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000399 def readframes(self, nframes):
400 if self._ssnd_seek_needed:
401 self._ssnd_chunk.seek(0)
402 dummy = self._ssnd_chunk.read(8)
403 pos = self._soundpos * self._framesize
404 if pos:
Guido van Rossum2663c132000-03-07 15:19:31 +0000405 self._ssnd_chunk.seek(pos + 8)
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000406 self._ssnd_seek_needed = 0
407 if nframes == 0:
408 return ''
409 data = self._ssnd_chunk.read(nframes * self._framesize)
410 if self._convert and data:
411 data = self._convert(data)
412 self._soundpos = self._soundpos + len(data) / (self._nchannels * self._sampwidth)
413 return data
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000414
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000415 #
416 # Internal methods.
417 #
Sjoerd Mullender2a451411993-12-20 09:36:01 +0000418
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000419 def _decomp_data(self, data):
420 import cl
421 dummy = self._decomp.SetParam(cl.FRAME_BUFFER_SIZE,
422 len(data) * 2)
423 return self._decomp.Decompress(len(data) / self._nchannels,
424 data)
Sjoerd Mullender43bf0bc1993-12-13 11:42:39 +0000425
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000426 def _ulaw2lin(self, data):
427 import audioop
428 return audioop.ulaw2lin(data, 2)
Sjoerd Mullender43bf0bc1993-12-13 11:42:39 +0000429
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000430 def _adpcm2lin(self, data):
431 import audioop
432 if not hasattr(self, '_adpcmstate'):
433 # first time
434 self._adpcmstate = None
435 data, self._adpcmstate = audioop.adpcm2lin(data, 2,
436 self._adpcmstate)
437 return data
Sjoerd Mullender1f057541994-09-06 16:17:51 +0000438
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000439 def _read_comm_chunk(self, chunk):
440 self._nchannels = _read_short(chunk)
441 self._nframes = _read_long(chunk)
442 self._sampwidth = (_read_short(chunk) + 7) / 8
443 self._framerate = int(_read_float(chunk))
444 self._framesize = self._nchannels * self._sampwidth
445 if self._aifc:
446 #DEBUG: SGI's soundeditor produces a bad size :-(
447 kludge = 0
448 if chunk.chunksize == 18:
449 kludge = 1
450 print 'Warning: bad COMM chunk size'
451 chunk.chunksize = 23
452 #DEBUG end
453 self._comptype = chunk.read(4)
454 #DEBUG start
455 if kludge:
456 length = ord(chunk.file.read(1))
457 if length & 1 == 0:
458 length = length + 1
459 chunk.chunksize = chunk.chunksize + length
460 chunk.file.seek(-1, 1)
461 #DEBUG end
462 self._compname = _read_string(chunk)
463 if self._comptype != 'NONE':
464 if self._comptype == 'G722':
465 try:
466 import audioop
467 except ImportError:
468 pass
469 else:
470 self._convert = self._adpcm2lin
471 self._framesize = self._framesize / 4
472 return
473 # for ULAW and ALAW try Compression Library
474 try:
475 import cl
476 except ImportError:
477 if self._comptype == 'ULAW':
478 try:
479 import audioop
480 self._convert = self._ulaw2lin
481 self._framesize = self._framesize / 2
482 return
483 except ImportError:
484 pass
485 raise Error, 'cannot read compressed AIFF-C files'
486 if self._comptype == 'ULAW':
487 scheme = cl.G711_ULAW
488 self._framesize = self._framesize / 2
489 elif self._comptype == 'ALAW':
490 scheme = cl.G711_ALAW
491 self._framesize = self._framesize / 2
492 else:
493 raise Error, 'unsupported compression type'
494 self._decomp = cl.OpenDecompressor(scheme)
495 self._convert = self._decomp_data
496 else:
497 self._comptype = 'NONE'
498 self._compname = 'not compressed'
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000499
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000500 def _readmark(self, chunk):
501 nmarkers = _read_short(chunk)
502 # Some files appear to contain invalid counts.
503 # Cope with this by testing for EOF.
504 try:
505 for i in range(nmarkers):
506 id = _read_short(chunk)
507 pos = _read_long(chunk)
508 name = _read_string(chunk)
509 if pos or name:
510 # some files appear to have
511 # dummy markers consisting of
512 # a position 0 and name ''
513 self._markers.append((id, pos, name))
514 except EOFError:
515 print 'Warning: MARK chunk contains only',
516 print len(self._markers),
517 if len(self._markers) == 1: print 'marker',
518 else: print 'markers',
519 print 'instead of', nmarkers
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000520
Guido van Rossumd3166071993-05-24 14:16:22 +0000521class Aifc_write:
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000522 # Variables used in this class:
523 #
524 # These variables are user settable through appropriate methods
525 # of this class:
526 # _file -- the open file with methods write(), close(), tell(), seek()
527 # set through the __init__() method
528 # _comptype -- the AIFF-C compression type ('NONE' in AIFF)
529 # set through the setcomptype() or setparams() method
530 # _compname -- the human-readable AIFF-C compression type
531 # set through the setcomptype() or setparams() method
532 # _nchannels -- the number of audio channels
533 # set through the setnchannels() or setparams() method
534 # _sampwidth -- the number of bytes per audio sample
535 # set through the setsampwidth() or setparams() method
536 # _framerate -- the sampling frequency
537 # set through the setframerate() or setparams() method
538 # _nframes -- the number of audio frames written to the header
539 # set through the setnframes() or setparams() method
540 # _aifc -- whether we're writing an AIFF-C file or an AIFF file
541 # set through the aifc() method, reset through the
542 # aiff() method
543 #
544 # These variables are used internally only:
545 # _version -- the AIFF-C version number
546 # _comp -- the compressor from builtin module cl
547 # _nframeswritten -- the number of audio frames actually written
548 # _datalength -- the size of the audio samples written to the header
549 # _datawritten -- the size of the audio samples actually written
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000550
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000551 def __init__(self, f):
552 if type(f) == type(''):
553 filename = f
554 f = __builtin__.open(f, 'wb')
555 else:
556 # else, assume it is an open file object already
557 filename = '???'
558 self.initfp(f)
559 if filename[-5:] == '.aiff':
560 self._aifc = 0
561 else:
562 self._aifc = 1
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000563
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000564 def initfp(self, file):
565 self._file = file
566 self._version = _AIFC_version
567 self._comptype = 'NONE'
568 self._compname = 'not compressed'
569 self._comp = None
570 self._convert = None
571 self._nchannels = 0
572 self._sampwidth = 0
573 self._framerate = 0
574 self._nframes = 0
575 self._nframeswritten = 0
576 self._datawritten = 0
577 self._datalength = 0
578 self._markers = []
579 self._marklength = 0
580 self._aifc = 1 # AIFF-C is default
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000581
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000582 def __del__(self):
583 if self._file:
584 self.close()
Sjoerd Mullender43bf0bc1993-12-13 11:42:39 +0000585
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000586 #
587 # User visible methods.
588 #
589 def aiff(self):
590 if self._nframeswritten:
591 raise Error, 'cannot change parameters after starting to write'
592 self._aifc = 0
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000593
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000594 def aifc(self):
595 if self._nframeswritten:
596 raise Error, 'cannot change parameters after starting to write'
597 self._aifc = 1
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000598
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000599 def setnchannels(self, nchannels):
600 if self._nframeswritten:
601 raise Error, 'cannot change parameters after starting to write'
602 if nchannels < 1:
603 raise Error, 'bad # of channels'
604 self._nchannels = nchannels
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000605
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000606 def getnchannels(self):
607 if not self._nchannels:
608 raise Error, 'number of channels not set'
609 return self._nchannels
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000610
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000611 def setsampwidth(self, sampwidth):
612 if self._nframeswritten:
613 raise Error, 'cannot change parameters after starting to write'
614 if sampwidth < 1 or sampwidth > 4:
615 raise Error, 'bad sample width'
616 self._sampwidth = sampwidth
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000617
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000618 def getsampwidth(self):
619 if not self._sampwidth:
620 raise Error, 'sample width not set'
621 return self._sampwidth
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000622
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000623 def setframerate(self, framerate):
624 if self._nframeswritten:
625 raise Error, 'cannot change parameters after starting to write'
626 if framerate <= 0:
627 raise Error, 'bad frame rate'
628 self._framerate = framerate
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000629
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000630 def getframerate(self):
631 if not self._framerate:
632 raise Error, 'frame rate not set'
633 return self._framerate
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000634
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000635 def setnframes(self, nframes):
636 if self._nframeswritten:
637 raise Error, 'cannot change parameters after starting to write'
638 self._nframes = nframes
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000639
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000640 def getnframes(self):
641 return self._nframeswritten
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000642
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000643 def setcomptype(self, comptype, compname):
644 if self._nframeswritten:
645 raise Error, 'cannot change parameters after starting to write'
646 if comptype not in ('NONE', 'ULAW', 'ALAW', 'G722'):
647 raise Error, 'unsupported compression type'
648 self._comptype = comptype
649 self._compname = compname
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000650
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000651 def getcomptype(self):
652 return self._comptype
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000653
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000654 def getcompname(self):
655 return self._compname
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000656
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000657## def setversion(self, version):
658## if self._nframeswritten:
659## raise Error, 'cannot change parameters after starting to write'
660## self._version = version
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000661
Brett Cannond13e4ba2008-08-04 21:33:00 +0000662 def setparams(self, info):
663 nchannels, sampwidth, framerate, nframes, comptype, compname = info
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000664 if self._nframeswritten:
665 raise Error, 'cannot change parameters after starting to write'
666 if comptype not in ('NONE', 'ULAW', 'ALAW', 'G722'):
667 raise Error, 'unsupported compression type'
668 self.setnchannels(nchannels)
669 self.setsampwidth(sampwidth)
670 self.setframerate(framerate)
671 self.setnframes(nframes)
672 self.setcomptype(comptype, compname)
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000673
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000674 def getparams(self):
675 if not self._nchannels or not self._sampwidth or not self._framerate:
676 raise Error, 'not all parameters set'
677 return self._nchannels, self._sampwidth, self._framerate, \
678 self._nframes, self._comptype, self._compname
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000679
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000680 def setmark(self, id, pos, name):
681 if id <= 0:
682 raise Error, 'marker ID must be > 0'
683 if pos < 0:
684 raise Error, 'marker position must be >= 0'
685 if type(name) != type(''):
686 raise Error, 'marker name must be a string'
687 for i in range(len(self._markers)):
688 if id == self._markers[i][0]:
689 self._markers[i] = id, pos, name
690 return
691 self._markers.append((id, pos, name))
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000692
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000693 def getmark(self, id):
694 for marker in self._markers:
695 if id == marker[0]:
696 return marker
Walter Dörwald70a6b492004-02-12 17:35:32 +0000697 raise Error, 'marker %r does not exist' % (id,)
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000698
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000699 def getmarkers(self):
700 if len(self._markers) == 0:
701 return None
702 return self._markers
Tim Peters146965a2001-01-14 18:09:23 +0000703
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000704 def tell(self):
705 return self._nframeswritten
Sjoerd Mullender43bf0bc1993-12-13 11:42:39 +0000706
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000707 def writeframesraw(self, data):
708 self._ensure_header_written(len(data))
709 nframes = len(data) / (self._sampwidth * self._nchannels)
710 if self._convert:
711 data = self._convert(data)
712 self._file.write(data)
713 self._nframeswritten = self._nframeswritten + nframes
714 self._datawritten = self._datawritten + len(data)
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000715
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000716 def writeframes(self, data):
717 self.writeframesraw(data)
718 if self._nframeswritten != self._nframes or \
719 self._datalength != self._datawritten:
720 self._patchheader()
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000721
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000722 def close(self):
723 self._ensure_header_written(0)
724 if self._datawritten & 1:
725 # quick pad to even size
726 self._file.write(chr(0))
727 self._datawritten = self._datawritten + 1
728 self._writemarkers()
729 if self._nframeswritten != self._nframes or \
730 self._datalength != self._datawritten or \
731 self._marklength:
732 self._patchheader()
733 if self._comp:
734 self._comp.CloseCompressor()
735 self._comp = None
R. David Murrayd0a45392009-05-07 16:45:44 +0000736 self._file.close()
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000737
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000738 #
739 # Internal methods.
740 #
Sjoerd Mullender2a451411993-12-20 09:36:01 +0000741
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000742 def _comp_data(self, data):
743 import cl
Neal Norwitz086ac002002-02-11 17:56:27 +0000744 dummy = self._comp.SetParam(cl.FRAME_BUFFER_SIZE, len(data))
745 dummy = self._comp.SetParam(cl.COMPRESSED_BUFFER_SIZE, len(data))
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000746 return self._comp.Compress(self._nframes, data)
Sjoerd Mullender43bf0bc1993-12-13 11:42:39 +0000747
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000748 def _lin2ulaw(self, data):
749 import audioop
750 return audioop.lin2ulaw(data, 2)
Sjoerd Mullender43bf0bc1993-12-13 11:42:39 +0000751
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000752 def _lin2adpcm(self, data):
753 import audioop
754 if not hasattr(self, '_adpcmstate'):
755 self._adpcmstate = None
756 data, self._adpcmstate = audioop.lin2adpcm(data, 2,
757 self._adpcmstate)
758 return data
Sjoerd Mullender1f057541994-09-06 16:17:51 +0000759
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000760 def _ensure_header_written(self, datasize):
761 if not self._nframeswritten:
762 if self._comptype in ('ULAW', 'ALAW'):
763 if not self._sampwidth:
764 self._sampwidth = 2
765 if self._sampwidth != 2:
766 raise Error, 'sample width must be 2 when compressing with ULAW or ALAW'
767 if self._comptype == 'G722':
768 if not self._sampwidth:
769 self._sampwidth = 2
770 if self._sampwidth != 2:
771 raise Error, 'sample width must be 2 when compressing with G7.22 (ADPCM)'
772 if not self._nchannels:
773 raise Error, '# channels not specified'
774 if not self._sampwidth:
775 raise Error, 'sample width not specified'
776 if not self._framerate:
777 raise Error, 'sampling rate not specified'
778 self._write_header(datasize)
Guido van Rossum52fc1f61993-06-17 12:38:10 +0000779
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000780 def _init_compression(self):
781 if self._comptype == 'G722':
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000782 self._convert = self._lin2adpcm
783 return
784 try:
785 import cl
786 except ImportError:
787 if self._comptype == 'ULAW':
788 try:
789 import audioop
790 self._convert = self._lin2ulaw
791 return
792 except ImportError:
793 pass
794 raise Error, 'cannot write compressed AIFF-C files'
795 if self._comptype == 'ULAW':
796 scheme = cl.G711_ULAW
797 elif self._comptype == 'ALAW':
798 scheme = cl.G711_ALAW
799 else:
800 raise Error, 'unsupported compression type'
801 self._comp = cl.OpenCompressor(scheme)
802 params = [cl.ORIGINAL_FORMAT, 0,
803 cl.BITS_PER_COMPONENT, self._sampwidth * 8,
804 cl.FRAME_RATE, self._framerate,
805 cl.FRAME_BUFFER_SIZE, 100,
806 cl.COMPRESSED_BUFFER_SIZE, 100]
807 if self._nchannels == 1:
808 params[1] = cl.MONO
809 elif self._nchannels == 2:
810 params[1] = cl.STEREO_INTERLEAVED
811 else:
812 raise Error, 'cannot compress more than 2 channels'
813 self._comp.SetParams(params)
814 # the compressor produces a header which we ignore
815 dummy = self._comp.Compress(0, '')
816 self._convert = self._comp_data
Sjoerd Mullender43bf0bc1993-12-13 11:42:39 +0000817
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000818 def _write_header(self, initlength):
819 if self._aifc and self._comptype != 'NONE':
820 self._init_compression()
821 self._file.write('FORM')
822 if not self._nframes:
823 self._nframes = initlength / (self._nchannels * self._sampwidth)
824 self._datalength = self._nframes * self._nchannels * self._sampwidth
825 if self._datalength & 1:
826 self._datalength = self._datalength + 1
827 if self._aifc:
828 if self._comptype in ('ULAW', 'ALAW'):
829 self._datalength = self._datalength / 2
830 if self._datalength & 1:
831 self._datalength = self._datalength + 1
832 elif self._comptype == 'G722':
833 self._datalength = (self._datalength + 3) / 4
834 if self._datalength & 1:
835 self._datalength = self._datalength + 1
836 self._form_length_pos = self._file.tell()
837 commlength = self._write_form_length(self._datalength)
838 if self._aifc:
839 self._file.write('AIFC')
840 self._file.write('FVER')
841 _write_long(self._file, 4)
842 _write_long(self._file, self._version)
843 else:
844 self._file.write('AIFF')
845 self._file.write('COMM')
846 _write_long(self._file, commlength)
847 _write_short(self._file, self._nchannels)
848 self._nframes_pos = self._file.tell()
849 _write_long(self._file, self._nframes)
850 _write_short(self._file, self._sampwidth * 8)
851 _write_float(self._file, self._framerate)
852 if self._aifc:
853 self._file.write(self._comptype)
854 _write_string(self._file, self._compname)
855 self._file.write('SSND')
856 self._ssnd_length_pos = self._file.tell()
857 _write_long(self._file, self._datalength + 8)
858 _write_long(self._file, 0)
859 _write_long(self._file, 0)
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000860
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000861 def _write_form_length(self, datalength):
862 if self._aifc:
863 commlength = 18 + 5 + len(self._compname)
864 if commlength & 1:
865 commlength = commlength + 1
866 verslength = 12
867 else:
868 commlength = 18
869 verslength = 0
870 _write_long(self._file, 4 + verslength + self._marklength + \
871 8 + commlength + 16 + datalength)
872 return commlength
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000873
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000874 def _patchheader(self):
875 curpos = self._file.tell()
876 if self._datawritten & 1:
877 datalength = self._datawritten + 1
878 self._file.write(chr(0))
879 else:
880 datalength = self._datawritten
881 if datalength == self._datalength and \
882 self._nframes == self._nframeswritten and \
883 self._marklength == 0:
884 self._file.seek(curpos, 0)
885 return
886 self._file.seek(self._form_length_pos, 0)
887 dummy = self._write_form_length(datalength)
888 self._file.seek(self._nframes_pos, 0)
889 _write_long(self._file, self._nframeswritten)
890 self._file.seek(self._ssnd_length_pos, 0)
891 _write_long(self._file, datalength + 8)
892 self._file.seek(curpos, 0)
893 self._nframes = self._nframeswritten
894 self._datalength = datalength
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000895
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000896 def _writemarkers(self):
897 if len(self._markers) == 0:
898 return
899 self._file.write('MARK')
900 length = 2
901 for marker in self._markers:
902 id, pos, name = marker
903 length = length + len(name) + 1 + 6
904 if len(name) & 1 == 0:
905 length = length + 1
906 _write_long(self._file, length)
907 self._marklength = length + 8
908 _write_short(self._file, len(self._markers))
909 for marker in self._markers:
910 id, pos, name = marker
911 _write_short(self._file, id)
912 _write_long(self._file, pos)
913 _write_string(self._file, name)
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000914
Fred Drake43161351999-06-22 21:23:23 +0000915def open(f, mode=None):
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000916 if mode is None:
917 if hasattr(f, 'mode'):
918 mode = f.mode
919 else:
920 mode = 'rb'
921 if mode in ('r', 'rb'):
922 return Aifc_read(f)
923 elif mode in ('w', 'wb'):
924 return Aifc_write(f)
925 else:
926 raise Error, "mode must be 'r', 'rb', 'w', or 'wb'"
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000927
Guido van Rossum7bc817d1993-12-17 15:25:27 +0000928openfp = open # B/W compatibility
Guido van Rossum36bb1811996-12-31 05:57:34 +0000929
930if __name__ == '__main__':
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000931 import sys
932 if not sys.argv[1:]:
933 sys.argv.append('/usr/demos/data/audio/bach.aiff')
934 fn = sys.argv[1]
935 f = open(fn, 'r')
936 print "Reading", fn
937 print "nchannels =", f.getnchannels()
938 print "nframes =", f.getnframes()
939 print "sampwidth =", f.getsampwidth()
940 print "framerate =", f.getframerate()
941 print "comptype =", f.getcomptype()
942 print "compname =", f.getcompname()
943 if sys.argv[2:]:
944 gn = sys.argv[2]
945 print "Writing", gn
946 g = open(gn, 'w')
947 g.setparams(f.getparams())
948 while 1:
949 data = f.readframes(1024)
950 if not data:
951 break
952 g.writeframes(data)
953 g.close()
954 f.close()
955 print "Done."