blob: f9db3f483555e23aaa9cf6093903070b343cd225 [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
285 self._file = Chunk(file)
286 if self._file.getname() != 'FORM':
287 raise Error, 'file does not start with FORM id'
288 formdata = self._file.read(4)
289 if formdata == 'AIFF':
290 self._aifc = 0
291 elif formdata == 'AIFC':
292 self._aifc = 1
293 else:
294 raise Error, 'not an AIFF or AIFF-C file'
295 self._comm_chunk_read = 0
296 while 1:
297 self._ssnd_seek_needed = 1
298 try:
299 chunk = Chunk(self._file)
300 except EOFError:
301 break
302 chunkname = chunk.getname()
303 if chunkname == 'COMM':
304 self._read_comm_chunk(chunk)
305 self._comm_chunk_read = 1
306 elif chunkname == 'SSND':
307 self._ssnd_chunk = chunk
308 dummy = chunk.read(8)
309 self._ssnd_seek_needed = 0
310 elif chunkname == 'FVER':
Guido van Rossum820819c2002-08-12 22:11:28 +0000311 self._version = _read_ulong(chunk)
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000312 elif chunkname == 'MARK':
313 self._readmark(chunk)
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000314 chunk.skip()
315 if not self._comm_chunk_read or not self._ssnd_chunk:
316 raise Error, 'COMM chunk and/or SSND chunk missing'
317 if self._aifc and self._decomp:
318 import cl
319 params = [cl.ORIGINAL_FORMAT, 0,
320 cl.BITS_PER_COMPONENT, self._sampwidth * 8,
321 cl.FRAME_RATE, self._framerate]
322 if self._nchannels == 1:
323 params[1] = cl.MONO
324 elif self._nchannels == 2:
325 params[1] = cl.STEREO_INTERLEAVED
326 else:
327 raise Error, 'cannot compress more than 2 channels'
328 self._decomp.SetParams(params)
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000329
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000330 def __init__(self, f):
331 if type(f) == type(''):
332 f = __builtin__.open(f, 'rb')
333 # else, assume it is an open file object already
334 self.initfp(f)
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000335
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000336 #
337 # User visible methods.
338 #
339 def getfp(self):
340 return self._file
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000341
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000342 def rewind(self):
343 self._ssnd_seek_needed = 1
344 self._soundpos = 0
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000345
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000346 def close(self):
347 if self._decomp:
348 self._decomp.CloseDecompressor()
349 self._decomp = None
350 self._file = None
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000351
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000352 def tell(self):
353 return self._soundpos
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000354
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000355 def getnchannels(self):
356 return self._nchannels
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000357
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000358 def getnframes(self):
359 return self._nframes
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000360
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000361 def getsampwidth(self):
362 return self._sampwidth
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000363
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000364 def getframerate(self):
365 return self._framerate
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000366
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000367 def getcomptype(self):
368 return self._comptype
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000369
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000370 def getcompname(self):
371 return self._compname
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000372
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000373## def getversion(self):
374## return self._version
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000375
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000376 def getparams(self):
377 return self.getnchannels(), self.getsampwidth(), \
378 self.getframerate(), self.getnframes(), \
379 self.getcomptype(), self.getcompname()
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000380
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000381 def getmarkers(self):
382 if len(self._markers) == 0:
383 return None
384 return self._markers
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000385
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000386 def getmark(self, id):
387 for marker in self._markers:
388 if id == marker[0]:
389 return marker
Walter Dörwald70a6b492004-02-12 17:35:32 +0000390 raise Error, 'marker %r does not exist' % (id,)
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000391
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000392 def setpos(self, pos):
393 if pos < 0 or pos > self._nframes:
394 raise Error, 'position not in range'
395 self._soundpos = pos
396 self._ssnd_seek_needed = 1
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000397
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000398 def readframes(self, nframes):
399 if self._ssnd_seek_needed:
400 self._ssnd_chunk.seek(0)
401 dummy = self._ssnd_chunk.read(8)
402 pos = self._soundpos * self._framesize
403 if pos:
Guido van Rossum2663c132000-03-07 15:19:31 +0000404 self._ssnd_chunk.seek(pos + 8)
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000405 self._ssnd_seek_needed = 0
406 if nframes == 0:
407 return ''
408 data = self._ssnd_chunk.read(nframes * self._framesize)
409 if self._convert and data:
410 data = self._convert(data)
411 self._soundpos = self._soundpos + len(data) / (self._nchannels * self._sampwidth)
412 return data
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000413
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000414 #
415 # Internal methods.
416 #
Sjoerd Mullender2a451411993-12-20 09:36:01 +0000417
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000418 def _decomp_data(self, data):
419 import cl
420 dummy = self._decomp.SetParam(cl.FRAME_BUFFER_SIZE,
421 len(data) * 2)
422 return self._decomp.Decompress(len(data) / self._nchannels,
423 data)
Sjoerd Mullender43bf0bc1993-12-13 11:42:39 +0000424
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000425 def _ulaw2lin(self, data):
426 import audioop
427 return audioop.ulaw2lin(data, 2)
Sjoerd Mullender43bf0bc1993-12-13 11:42:39 +0000428
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000429 def _adpcm2lin(self, data):
430 import audioop
431 if not hasattr(self, '_adpcmstate'):
432 # first time
433 self._adpcmstate = None
434 data, self._adpcmstate = audioop.adpcm2lin(data, 2,
435 self._adpcmstate)
436 return data
Sjoerd Mullender1f057541994-09-06 16:17:51 +0000437
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000438 def _read_comm_chunk(self, chunk):
439 self._nchannels = _read_short(chunk)
440 self._nframes = _read_long(chunk)
441 self._sampwidth = (_read_short(chunk) + 7) / 8
442 self._framerate = int(_read_float(chunk))
443 self._framesize = self._nchannels * self._sampwidth
444 if self._aifc:
445 #DEBUG: SGI's soundeditor produces a bad size :-(
446 kludge = 0
447 if chunk.chunksize == 18:
448 kludge = 1
449 print 'Warning: bad COMM chunk size'
450 chunk.chunksize = 23
451 #DEBUG end
452 self._comptype = chunk.read(4)
453 #DEBUG start
454 if kludge:
455 length = ord(chunk.file.read(1))
456 if length & 1 == 0:
457 length = length + 1
458 chunk.chunksize = chunk.chunksize + length
459 chunk.file.seek(-1, 1)
460 #DEBUG end
461 self._compname = _read_string(chunk)
462 if self._comptype != 'NONE':
463 if self._comptype == 'G722':
464 try:
465 import audioop
466 except ImportError:
467 pass
468 else:
469 self._convert = self._adpcm2lin
470 self._framesize = self._framesize / 4
471 return
472 # for ULAW and ALAW try Compression Library
473 try:
474 import cl
475 except ImportError:
476 if self._comptype == 'ULAW':
477 try:
478 import audioop
479 self._convert = self._ulaw2lin
480 self._framesize = self._framesize / 2
481 return
482 except ImportError:
483 pass
484 raise Error, 'cannot read compressed AIFF-C files'
485 if self._comptype == 'ULAW':
486 scheme = cl.G711_ULAW
487 self._framesize = self._framesize / 2
488 elif self._comptype == 'ALAW':
489 scheme = cl.G711_ALAW
490 self._framesize = self._framesize / 2
491 else:
492 raise Error, 'unsupported compression type'
493 self._decomp = cl.OpenDecompressor(scheme)
494 self._convert = self._decomp_data
495 else:
496 self._comptype = 'NONE'
497 self._compname = 'not compressed'
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000498
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000499 def _readmark(self, chunk):
500 nmarkers = _read_short(chunk)
501 # Some files appear to contain invalid counts.
502 # Cope with this by testing for EOF.
503 try:
504 for i in range(nmarkers):
505 id = _read_short(chunk)
506 pos = _read_long(chunk)
507 name = _read_string(chunk)
508 if pos or name:
509 # some files appear to have
510 # dummy markers consisting of
511 # a position 0 and name ''
512 self._markers.append((id, pos, name))
513 except EOFError:
514 print 'Warning: MARK chunk contains only',
515 print len(self._markers),
516 if len(self._markers) == 1: print 'marker',
517 else: print 'markers',
518 print 'instead of', nmarkers
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000519
Guido van Rossumd3166071993-05-24 14:16:22 +0000520class Aifc_write:
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000521 # Variables used in this class:
522 #
523 # These variables are user settable through appropriate methods
524 # of this class:
525 # _file -- the open file with methods write(), close(), tell(), seek()
526 # set through the __init__() method
527 # _comptype -- the AIFF-C compression type ('NONE' in AIFF)
528 # set through the setcomptype() or setparams() method
529 # _compname -- the human-readable AIFF-C compression type
530 # set through the setcomptype() or setparams() method
531 # _nchannels -- the number of audio channels
532 # set through the setnchannels() or setparams() method
533 # _sampwidth -- the number of bytes per audio sample
534 # set through the setsampwidth() or setparams() method
535 # _framerate -- the sampling frequency
536 # set through the setframerate() or setparams() method
537 # _nframes -- the number of audio frames written to the header
538 # set through the setnframes() or setparams() method
539 # _aifc -- whether we're writing an AIFF-C file or an AIFF file
540 # set through the aifc() method, reset through the
541 # aiff() method
542 #
543 # These variables are used internally only:
544 # _version -- the AIFF-C version number
545 # _comp -- the compressor from builtin module cl
546 # _nframeswritten -- the number of audio frames actually written
547 # _datalength -- the size of the audio samples written to the header
548 # _datawritten -- the size of the audio samples actually written
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000549
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000550 def __init__(self, f):
551 if type(f) == type(''):
552 filename = f
553 f = __builtin__.open(f, 'wb')
554 else:
555 # else, assume it is an open file object already
556 filename = '???'
557 self.initfp(f)
558 if filename[-5:] == '.aiff':
559 self._aifc = 0
560 else:
561 self._aifc = 1
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000562
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000563 def initfp(self, file):
564 self._file = file
565 self._version = _AIFC_version
566 self._comptype = 'NONE'
567 self._compname = 'not compressed'
568 self._comp = None
569 self._convert = None
570 self._nchannels = 0
571 self._sampwidth = 0
572 self._framerate = 0
573 self._nframes = 0
574 self._nframeswritten = 0
575 self._datawritten = 0
576 self._datalength = 0
577 self._markers = []
578 self._marklength = 0
579 self._aifc = 1 # AIFF-C is default
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000580
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000581 def __del__(self):
582 if self._file:
583 self.close()
Sjoerd Mullender43bf0bc1993-12-13 11:42:39 +0000584
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000585 #
586 # User visible methods.
587 #
588 def aiff(self):
589 if self._nframeswritten:
590 raise Error, 'cannot change parameters after starting to write'
591 self._aifc = 0
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000592
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000593 def aifc(self):
594 if self._nframeswritten:
595 raise Error, 'cannot change parameters after starting to write'
596 self._aifc = 1
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000597
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000598 def setnchannels(self, nchannels):
599 if self._nframeswritten:
600 raise Error, 'cannot change parameters after starting to write'
601 if nchannels < 1:
602 raise Error, 'bad # of channels'
603 self._nchannels = nchannels
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000604
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000605 def getnchannels(self):
606 if not self._nchannels:
607 raise Error, 'number of channels not set'
608 return self._nchannels
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000609
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000610 def setsampwidth(self, sampwidth):
611 if self._nframeswritten:
612 raise Error, 'cannot change parameters after starting to write'
613 if sampwidth < 1 or sampwidth > 4:
614 raise Error, 'bad sample width'
615 self._sampwidth = sampwidth
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000616
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000617 def getsampwidth(self):
618 if not self._sampwidth:
619 raise Error, 'sample width not set'
620 return self._sampwidth
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000621
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000622 def setframerate(self, framerate):
623 if self._nframeswritten:
624 raise Error, 'cannot change parameters after starting to write'
625 if framerate <= 0:
626 raise Error, 'bad frame rate'
627 self._framerate = framerate
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000628
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000629 def getframerate(self):
630 if not self._framerate:
631 raise Error, 'frame rate not set'
632 return self._framerate
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000633
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000634 def setnframes(self, nframes):
635 if self._nframeswritten:
636 raise Error, 'cannot change parameters after starting to write'
637 self._nframes = nframes
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000638
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000639 def getnframes(self):
640 return self._nframeswritten
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000641
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000642 def setcomptype(self, comptype, compname):
643 if self._nframeswritten:
644 raise Error, 'cannot change parameters after starting to write'
645 if comptype not in ('NONE', 'ULAW', 'ALAW', 'G722'):
646 raise Error, 'unsupported compression type'
647 self._comptype = comptype
648 self._compname = compname
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000649
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000650 def getcomptype(self):
651 return self._comptype
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000652
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000653 def getcompname(self):
654 return self._compname
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000655
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000656## def setversion(self, version):
657## if self._nframeswritten:
658## raise Error, 'cannot change parameters after starting to write'
659## self._version = version
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000660
Brett Cannond13e4ba2008-08-04 21:33:00 +0000661 def setparams(self, info):
662 nchannels, sampwidth, framerate, nframes, comptype, compname = info
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000663 if self._nframeswritten:
664 raise Error, 'cannot change parameters after starting to write'
665 if comptype not in ('NONE', 'ULAW', 'ALAW', 'G722'):
666 raise Error, 'unsupported compression type'
667 self.setnchannels(nchannels)
668 self.setsampwidth(sampwidth)
669 self.setframerate(framerate)
670 self.setnframes(nframes)
671 self.setcomptype(comptype, compname)
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000672
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000673 def getparams(self):
674 if not self._nchannels or not self._sampwidth or not self._framerate:
675 raise Error, 'not all parameters set'
676 return self._nchannels, self._sampwidth, self._framerate, \
677 self._nframes, self._comptype, self._compname
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000678
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000679 def setmark(self, id, pos, name):
680 if id <= 0:
681 raise Error, 'marker ID must be > 0'
682 if pos < 0:
683 raise Error, 'marker position must be >= 0'
684 if type(name) != type(''):
685 raise Error, 'marker name must be a string'
686 for i in range(len(self._markers)):
687 if id == self._markers[i][0]:
688 self._markers[i] = id, pos, name
689 return
690 self._markers.append((id, pos, name))
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000691
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000692 def getmark(self, id):
693 for marker in self._markers:
694 if id == marker[0]:
695 return marker
Walter Dörwald70a6b492004-02-12 17:35:32 +0000696 raise Error, 'marker %r does not exist' % (id,)
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000697
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000698 def getmarkers(self):
699 if len(self._markers) == 0:
700 return None
701 return self._markers
Tim Peters146965a2001-01-14 18:09:23 +0000702
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000703 def tell(self):
704 return self._nframeswritten
Sjoerd Mullender43bf0bc1993-12-13 11:42:39 +0000705
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000706 def writeframesraw(self, data):
707 self._ensure_header_written(len(data))
708 nframes = len(data) / (self._sampwidth * self._nchannels)
709 if self._convert:
710 data = self._convert(data)
711 self._file.write(data)
712 self._nframeswritten = self._nframeswritten + nframes
713 self._datawritten = self._datawritten + len(data)
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000714
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000715 def writeframes(self, data):
716 self.writeframesraw(data)
717 if self._nframeswritten != self._nframes or \
718 self._datalength != self._datawritten:
719 self._patchheader()
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000720
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000721 def close(self):
722 self._ensure_header_written(0)
723 if self._datawritten & 1:
724 # quick pad to even size
725 self._file.write(chr(0))
726 self._datawritten = self._datawritten + 1
727 self._writemarkers()
728 if self._nframeswritten != self._nframes or \
729 self._datalength != self._datawritten or \
730 self._marklength:
731 self._patchheader()
732 if self._comp:
733 self._comp.CloseCompressor()
734 self._comp = None
Benjamin Peterson2518d3c2009-04-30 00:23:11 +0000735 # Prevent ref cycles
736 self._convert = None
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000737 self._file.flush()
738 self._file = None
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000739
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000740 #
741 # Internal methods.
742 #
Sjoerd Mullender2a451411993-12-20 09:36:01 +0000743
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000744 def _comp_data(self, data):
745 import cl
Neal Norwitz086ac002002-02-11 17:56:27 +0000746 dummy = self._comp.SetParam(cl.FRAME_BUFFER_SIZE, len(data))
747 dummy = self._comp.SetParam(cl.COMPRESSED_BUFFER_SIZE, len(data))
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000748 return self._comp.Compress(self._nframes, data)
Sjoerd Mullender43bf0bc1993-12-13 11:42:39 +0000749
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000750 def _lin2ulaw(self, data):
751 import audioop
752 return audioop.lin2ulaw(data, 2)
Sjoerd Mullender43bf0bc1993-12-13 11:42:39 +0000753
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000754 def _lin2adpcm(self, data):
755 import audioop
756 if not hasattr(self, '_adpcmstate'):
757 self._adpcmstate = None
758 data, self._adpcmstate = audioop.lin2adpcm(data, 2,
759 self._adpcmstate)
760 return data
Sjoerd Mullender1f057541994-09-06 16:17:51 +0000761
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000762 def _ensure_header_written(self, datasize):
763 if not self._nframeswritten:
764 if self._comptype in ('ULAW', 'ALAW'):
765 if not self._sampwidth:
766 self._sampwidth = 2
767 if self._sampwidth != 2:
768 raise Error, 'sample width must be 2 when compressing with ULAW or ALAW'
769 if self._comptype == 'G722':
770 if not self._sampwidth:
771 self._sampwidth = 2
772 if self._sampwidth != 2:
773 raise Error, 'sample width must be 2 when compressing with G7.22 (ADPCM)'
774 if not self._nchannels:
775 raise Error, '# channels not specified'
776 if not self._sampwidth:
777 raise Error, 'sample width not specified'
778 if not self._framerate:
779 raise Error, 'sampling rate not specified'
780 self._write_header(datasize)
Guido van Rossum52fc1f61993-06-17 12:38:10 +0000781
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000782 def _init_compression(self):
783 if self._comptype == 'G722':
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000784 self._convert = self._lin2adpcm
785 return
786 try:
787 import cl
788 except ImportError:
789 if self._comptype == 'ULAW':
790 try:
791 import audioop
792 self._convert = self._lin2ulaw
793 return
794 except ImportError:
795 pass
796 raise Error, 'cannot write compressed AIFF-C files'
797 if self._comptype == 'ULAW':
798 scheme = cl.G711_ULAW
799 elif self._comptype == 'ALAW':
800 scheme = cl.G711_ALAW
801 else:
802 raise Error, 'unsupported compression type'
803 self._comp = cl.OpenCompressor(scheme)
804 params = [cl.ORIGINAL_FORMAT, 0,
805 cl.BITS_PER_COMPONENT, self._sampwidth * 8,
806 cl.FRAME_RATE, self._framerate,
807 cl.FRAME_BUFFER_SIZE, 100,
808 cl.COMPRESSED_BUFFER_SIZE, 100]
809 if self._nchannels == 1:
810 params[1] = cl.MONO
811 elif self._nchannels == 2:
812 params[1] = cl.STEREO_INTERLEAVED
813 else:
814 raise Error, 'cannot compress more than 2 channels'
815 self._comp.SetParams(params)
816 # the compressor produces a header which we ignore
817 dummy = self._comp.Compress(0, '')
818 self._convert = self._comp_data
Sjoerd Mullender43bf0bc1993-12-13 11:42:39 +0000819
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000820 def _write_header(self, initlength):
821 if self._aifc and self._comptype != 'NONE':
822 self._init_compression()
823 self._file.write('FORM')
824 if not self._nframes:
825 self._nframes = initlength / (self._nchannels * self._sampwidth)
826 self._datalength = self._nframes * self._nchannels * self._sampwidth
827 if self._datalength & 1:
828 self._datalength = self._datalength + 1
829 if self._aifc:
830 if self._comptype in ('ULAW', 'ALAW'):
831 self._datalength = self._datalength / 2
832 if self._datalength & 1:
833 self._datalength = self._datalength + 1
834 elif self._comptype == 'G722':
835 self._datalength = (self._datalength + 3) / 4
836 if self._datalength & 1:
837 self._datalength = self._datalength + 1
838 self._form_length_pos = self._file.tell()
839 commlength = self._write_form_length(self._datalength)
840 if self._aifc:
841 self._file.write('AIFC')
842 self._file.write('FVER')
843 _write_long(self._file, 4)
844 _write_long(self._file, self._version)
845 else:
846 self._file.write('AIFF')
847 self._file.write('COMM')
848 _write_long(self._file, commlength)
849 _write_short(self._file, self._nchannels)
850 self._nframes_pos = self._file.tell()
851 _write_long(self._file, self._nframes)
852 _write_short(self._file, self._sampwidth * 8)
853 _write_float(self._file, self._framerate)
854 if self._aifc:
855 self._file.write(self._comptype)
856 _write_string(self._file, self._compname)
857 self._file.write('SSND')
858 self._ssnd_length_pos = self._file.tell()
859 _write_long(self._file, self._datalength + 8)
860 _write_long(self._file, 0)
861 _write_long(self._file, 0)
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000862
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000863 def _write_form_length(self, datalength):
864 if self._aifc:
865 commlength = 18 + 5 + len(self._compname)
866 if commlength & 1:
867 commlength = commlength + 1
868 verslength = 12
869 else:
870 commlength = 18
871 verslength = 0
872 _write_long(self._file, 4 + verslength + self._marklength + \
873 8 + commlength + 16 + datalength)
874 return commlength
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000875
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000876 def _patchheader(self):
877 curpos = self._file.tell()
878 if self._datawritten & 1:
879 datalength = self._datawritten + 1
880 self._file.write(chr(0))
881 else:
882 datalength = self._datawritten
883 if datalength == self._datalength and \
884 self._nframes == self._nframeswritten and \
885 self._marklength == 0:
886 self._file.seek(curpos, 0)
887 return
888 self._file.seek(self._form_length_pos, 0)
889 dummy = self._write_form_length(datalength)
890 self._file.seek(self._nframes_pos, 0)
891 _write_long(self._file, self._nframeswritten)
892 self._file.seek(self._ssnd_length_pos, 0)
893 _write_long(self._file, datalength + 8)
894 self._file.seek(curpos, 0)
895 self._nframes = self._nframeswritten
896 self._datalength = datalength
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000897
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000898 def _writemarkers(self):
899 if len(self._markers) == 0:
900 return
901 self._file.write('MARK')
902 length = 2
903 for marker in self._markers:
904 id, pos, name = marker
905 length = length + len(name) + 1 + 6
906 if len(name) & 1 == 0:
907 length = length + 1
908 _write_long(self._file, length)
909 self._marklength = length + 8
910 _write_short(self._file, len(self._markers))
911 for marker in self._markers:
912 id, pos, name = marker
913 _write_short(self._file, id)
914 _write_long(self._file, pos)
915 _write_string(self._file, name)
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000916
Fred Drake43161351999-06-22 21:23:23 +0000917def open(f, mode=None):
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000918 if mode is None:
919 if hasattr(f, 'mode'):
920 mode = f.mode
921 else:
922 mode = 'rb'
923 if mode in ('r', 'rb'):
924 return Aifc_read(f)
925 elif mode in ('w', 'wb'):
926 return Aifc_write(f)
927 else:
928 raise Error, "mode must be 'r', 'rb', 'w', or 'wb'"
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000929
Guido van Rossum7bc817d1993-12-17 15:25:27 +0000930openfp = open # B/W compatibility
Guido van Rossum36bb1811996-12-31 05:57:34 +0000931
932if __name__ == '__main__':
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000933 import sys
934 if not sys.argv[1:]:
935 sys.argv.append('/usr/demos/data/audio/bach.aiff')
936 fn = sys.argv[1]
937 f = open(fn, 'r')
938 print "Reading", fn
939 print "nchannels =", f.getnchannels()
940 print "nframes =", f.getnframes()
941 print "sampwidth =", f.getsampwidth()
942 print "framerate =", f.getframerate()
943 print "comptype =", f.getcomptype()
944 print "compname =", f.getcompname()
945 if sys.argv[2:]:
946 gn = sys.argv[2]
947 print "Writing", gn
948 g = open(gn, 'w')
949 g.setparams(f.getparams())
950 while 1:
951 data = f.readframes(1024)
952 if not data:
953 break
954 g.writeframes(data)
955 g.close()
956 f.close()
957 print "Done."