blob: 0275e4272080675b02f9ca37ee414e40f66487db [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
147_skiplist = 'COMT', 'INST', 'MIDI', 'AESD', \
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000148 'APPL', 'NAME', 'AUTH', '(c) ', 'ANNO'
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000149
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000150def _read_long(file):
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000151 try:
152 return struct.unpack('>l', file.read(4))[0]
153 except struct.error:
154 raise EOFError
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000155
156def _read_ulong(file):
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000157 try:
158 return struct.unpack('>L', file.read(4))[0]
159 except struct.error:
160 raise EOFError
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000161
162def _read_short(file):
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000163 try:
164 return struct.unpack('>h', file.read(2))[0]
165 except struct.error:
166 raise EOFError
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000167
168def _read_string(file):
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000169 length = ord(file.read(1))
170 if length == 0:
171 data = ''
172 else:
173 data = file.read(length)
174 if length & 1 == 0:
175 dummy = file.read(1)
176 return data
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000177
178_HUGE_VAL = 1.79769313486231e+308 # See <limits.h>
179
180def _read_float(f): # 10 bytes
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000181 expon = _read_short(f) # 2 bytes
182 sign = 1
183 if expon < 0:
184 sign = -1
185 expon = expon + 0x8000
186 himant = _read_ulong(f) # 4 bytes
187 lomant = _read_ulong(f) # 4 bytes
188 if expon == himant == lomant == 0:
189 f = 0.0
190 elif expon == 0x7FFF:
191 f = _HUGE_VAL
192 else:
193 expon = expon - 16383
194 f = (himant * 0x100000000L + lomant) * pow(2.0, expon - 63)
195 return sign * f
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000196
197def _write_short(f, x):
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000198 f.write(struct.pack('>h', x))
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000199
200def _write_long(f, x):
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000201 f.write(struct.pack('>L', x))
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000202
203def _write_string(f, s):
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000204 f.write(chr(len(s)))
205 f.write(s)
206 if len(s) & 1 == 0:
207 f.write(chr(0))
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000208
209def _write_float(f, x):
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000210 import math
211 if x < 0:
212 sign = 0x8000
213 x = x * -1
214 else:
215 sign = 0
216 if x == 0:
217 expon = 0
218 himant = 0
219 lomant = 0
220 else:
221 fmant, expon = math.frexp(x)
222 if expon > 16384 or fmant >= 1: # Infinity or NaN
223 expon = sign|0x7FFF
224 himant = 0
225 lomant = 0
226 else: # Finite
227 expon = expon + 16382
228 if expon < 0: # denormalized
229 fmant = math.ldexp(fmant, expon)
230 expon = 0
231 expon = expon | sign
232 fmant = math.ldexp(fmant, 32)
233 fsmant = math.floor(fmant)
234 himant = long(fsmant)
235 fmant = math.ldexp(fmant - fsmant, 32)
236 fsmant = math.floor(fmant)
237 lomant = long(fsmant)
238 _write_short(f, expon)
239 _write_long(f, himant)
240 _write_long(f, lomant)
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000241
Guido van Rossum8ea7bb81999-06-09 13:32:28 +0000242from chunk import Chunk
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000243
Guido van Rossumd3166071993-05-24 14:16:22 +0000244class Aifc_read:
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000245 # Variables used in this class:
246 #
247 # These variables are available to the user though appropriate
248 # methods of this class:
249 # _file -- the open file with methods read(), close(), and seek()
250 # set through the __init__() method
251 # _nchannels -- the number of audio channels
252 # available through the getnchannels() method
253 # _nframes -- the number of audio frames
254 # available through the getnframes() method
255 # _sampwidth -- the number of bytes per audio sample
256 # available through the getsampwidth() method
257 # _framerate -- the sampling frequency
258 # available through the getframerate() method
259 # _comptype -- the AIFF-C compression type ('NONE' if AIFF)
260 # available through the getcomptype() method
261 # _compname -- the human-readable AIFF-C compression type
262 # available through the getcomptype() method
263 # _markers -- the marks in the audio file
264 # available through the getmarkers() and getmark()
265 # methods
266 # _soundpos -- the position in the audio stream
267 # available through the tell() method, set through the
268 # setpos() method
269 #
270 # These variables are used internally only:
271 # _version -- the AIFF-C version number
272 # _decomp -- the decompressor from builtin module cl
273 # _comm_chunk_read -- 1 iff the COMM chunk has been read
274 # _aifc -- 1 iff reading an AIFF-C file
275 # _ssnd_seek_needed -- 1 iff positioned correctly in audio
276 # file for readframes()
277 # _ssnd_chunk -- instantiation of a chunk class for the SSND chunk
278 # _framesize -- size of one frame in the file
Sjoerd Mullender2a451411993-12-20 09:36:01 +0000279
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000280 def initfp(self, file):
281 self._version = 0
282 self._decomp = None
283 self._convert = None
284 self._markers = []
285 self._soundpos = 0
286 self._file = Chunk(file)
287 if self._file.getname() != 'FORM':
288 raise Error, 'file does not start with FORM id'
289 formdata = self._file.read(4)
290 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)
315 elif chunkname in _skiplist:
316 pass
317 else:
318 raise Error, 'unrecognized chunk type '+chunk.chunkname
319 chunk.skip()
320 if not self._comm_chunk_read or not self._ssnd_chunk:
321 raise Error, 'COMM chunk and/or SSND chunk missing'
322 if self._aifc and self._decomp:
323 import cl
324 params = [cl.ORIGINAL_FORMAT, 0,
325 cl.BITS_PER_COMPONENT, self._sampwidth * 8,
326 cl.FRAME_RATE, self._framerate]
327 if self._nchannels == 1:
328 params[1] = cl.MONO
329 elif self._nchannels == 2:
330 params[1] = cl.STEREO_INTERLEAVED
331 else:
332 raise Error, 'cannot compress more than 2 channels'
333 self._decomp.SetParams(params)
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000334
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000335 def __init__(self, f):
336 if type(f) == type(''):
337 f = __builtin__.open(f, 'rb')
338 # else, assume it is an open file object already
339 self.initfp(f)
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000340
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000341 #
342 # User visible methods.
343 #
344 def getfp(self):
345 return self._file
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000346
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000347 def rewind(self):
348 self._ssnd_seek_needed = 1
349 self._soundpos = 0
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000350
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000351 def close(self):
352 if self._decomp:
353 self._decomp.CloseDecompressor()
354 self._decomp = None
355 self._file = None
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000356
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000357 def tell(self):
358 return self._soundpos
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000359
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000360 def getnchannels(self):
361 return self._nchannels
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000362
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000363 def getnframes(self):
364 return self._nframes
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000365
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000366 def getsampwidth(self):
367 return self._sampwidth
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000368
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000369 def getframerate(self):
370 return self._framerate
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000371
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000372 def getcomptype(self):
373 return self._comptype
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000374
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000375 def getcompname(self):
376 return self._compname
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000377
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000378## def getversion(self):
379## return self._version
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000380
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000381 def getparams(self):
382 return self.getnchannels(), self.getsampwidth(), \
383 self.getframerate(), self.getnframes(), \
384 self.getcomptype(), self.getcompname()
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000385
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000386 def getmarkers(self):
387 if len(self._markers) == 0:
388 return None
389 return self._markers
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000390
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000391 def getmark(self, id):
392 for marker in self._markers:
393 if id == marker[0]:
394 return marker
395 raise Error, 'marker ' + `id` + ' does not exist'
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000396
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000397 def setpos(self, pos):
398 if pos < 0 or pos > self._nframes:
399 raise Error, 'position not in range'
400 self._soundpos = pos
401 self._ssnd_seek_needed = 1
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000402
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000403 def readframes(self, nframes):
404 if self._ssnd_seek_needed:
405 self._ssnd_chunk.seek(0)
406 dummy = self._ssnd_chunk.read(8)
407 pos = self._soundpos * self._framesize
408 if pos:
Guido van Rossum2663c132000-03-07 15:19:31 +0000409 self._ssnd_chunk.seek(pos + 8)
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000410 self._ssnd_seek_needed = 0
411 if nframes == 0:
412 return ''
413 data = self._ssnd_chunk.read(nframes * self._framesize)
414 if self._convert and data:
415 data = self._convert(data)
416 self._soundpos = self._soundpos + len(data) / (self._nchannels * self._sampwidth)
417 return data
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000418
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000419 #
420 # Internal methods.
421 #
Sjoerd Mullender2a451411993-12-20 09:36:01 +0000422
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000423 def _decomp_data(self, data):
424 import cl
425 dummy = self._decomp.SetParam(cl.FRAME_BUFFER_SIZE,
426 len(data) * 2)
427 return self._decomp.Decompress(len(data) / self._nchannels,
428 data)
Sjoerd Mullender43bf0bc1993-12-13 11:42:39 +0000429
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000430 def _ulaw2lin(self, data):
431 import audioop
432 return audioop.ulaw2lin(data, 2)
Sjoerd Mullender43bf0bc1993-12-13 11:42:39 +0000433
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000434 def _adpcm2lin(self, data):
435 import audioop
436 if not hasattr(self, '_adpcmstate'):
437 # first time
438 self._adpcmstate = None
439 data, self._adpcmstate = audioop.adpcm2lin(data, 2,
440 self._adpcmstate)
441 return data
Sjoerd Mullender1f057541994-09-06 16:17:51 +0000442
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000443 def _read_comm_chunk(self, chunk):
444 self._nchannels = _read_short(chunk)
445 self._nframes = _read_long(chunk)
446 self._sampwidth = (_read_short(chunk) + 7) / 8
447 self._framerate = int(_read_float(chunk))
448 self._framesize = self._nchannels * self._sampwidth
449 if self._aifc:
450 #DEBUG: SGI's soundeditor produces a bad size :-(
451 kludge = 0
452 if chunk.chunksize == 18:
453 kludge = 1
454 print 'Warning: bad COMM chunk size'
455 chunk.chunksize = 23
456 #DEBUG end
457 self._comptype = chunk.read(4)
458 #DEBUG start
459 if kludge:
460 length = ord(chunk.file.read(1))
461 if length & 1 == 0:
462 length = length + 1
463 chunk.chunksize = chunk.chunksize + length
464 chunk.file.seek(-1, 1)
465 #DEBUG end
466 self._compname = _read_string(chunk)
467 if self._comptype != 'NONE':
468 if self._comptype == 'G722':
469 try:
470 import audioop
471 except ImportError:
472 pass
473 else:
474 self._convert = self._adpcm2lin
475 self._framesize = self._framesize / 4
476 return
477 # for ULAW and ALAW try Compression Library
478 try:
479 import cl
480 except ImportError:
481 if self._comptype == 'ULAW':
482 try:
483 import audioop
484 self._convert = self._ulaw2lin
485 self._framesize = self._framesize / 2
486 return
487 except ImportError:
488 pass
489 raise Error, 'cannot read compressed AIFF-C files'
490 if self._comptype == 'ULAW':
491 scheme = cl.G711_ULAW
492 self._framesize = self._framesize / 2
493 elif self._comptype == 'ALAW':
494 scheme = cl.G711_ALAW
495 self._framesize = self._framesize / 2
496 else:
497 raise Error, 'unsupported compression type'
498 self._decomp = cl.OpenDecompressor(scheme)
499 self._convert = self._decomp_data
500 else:
501 self._comptype = 'NONE'
502 self._compname = 'not compressed'
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000503
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000504 def _readmark(self, chunk):
505 nmarkers = _read_short(chunk)
506 # Some files appear to contain invalid counts.
507 # Cope with this by testing for EOF.
508 try:
509 for i in range(nmarkers):
510 id = _read_short(chunk)
511 pos = _read_long(chunk)
512 name = _read_string(chunk)
513 if pos or name:
514 # some files appear to have
515 # dummy markers consisting of
516 # a position 0 and name ''
517 self._markers.append((id, pos, name))
518 except EOFError:
519 print 'Warning: MARK chunk contains only',
520 print len(self._markers),
521 if len(self._markers) == 1: print 'marker',
522 else: print 'markers',
523 print 'instead of', nmarkers
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000524
Guido van Rossumd3166071993-05-24 14:16:22 +0000525class Aifc_write:
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000526 # Variables used in this class:
527 #
528 # These variables are user settable through appropriate methods
529 # of this class:
530 # _file -- the open file with methods write(), close(), tell(), seek()
531 # set through the __init__() method
532 # _comptype -- the AIFF-C compression type ('NONE' in AIFF)
533 # set through the setcomptype() or setparams() method
534 # _compname -- the human-readable AIFF-C compression type
535 # set through the setcomptype() or setparams() method
536 # _nchannels -- the number of audio channels
537 # set through the setnchannels() or setparams() method
538 # _sampwidth -- the number of bytes per audio sample
539 # set through the setsampwidth() or setparams() method
540 # _framerate -- the sampling frequency
541 # set through the setframerate() or setparams() method
542 # _nframes -- the number of audio frames written to the header
543 # set through the setnframes() or setparams() method
544 # _aifc -- whether we're writing an AIFF-C file or an AIFF file
545 # set through the aifc() method, reset through the
546 # aiff() method
547 #
548 # These variables are used internally only:
549 # _version -- the AIFF-C version number
550 # _comp -- the compressor from builtin module cl
551 # _nframeswritten -- the number of audio frames actually written
552 # _datalength -- the size of the audio samples written to the header
553 # _datawritten -- the size of the audio samples actually written
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000554
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000555 def __init__(self, f):
556 if type(f) == type(''):
557 filename = f
558 f = __builtin__.open(f, 'wb')
559 else:
560 # else, assume it is an open file object already
561 filename = '???'
562 self.initfp(f)
563 if filename[-5:] == '.aiff':
564 self._aifc = 0
565 else:
566 self._aifc = 1
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000567
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000568 def initfp(self, file):
569 self._file = file
570 self._version = _AIFC_version
571 self._comptype = 'NONE'
572 self._compname = 'not compressed'
573 self._comp = None
574 self._convert = None
575 self._nchannels = 0
576 self._sampwidth = 0
577 self._framerate = 0
578 self._nframes = 0
579 self._nframeswritten = 0
580 self._datawritten = 0
581 self._datalength = 0
582 self._markers = []
583 self._marklength = 0
584 self._aifc = 1 # AIFF-C is default
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000585
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000586 def __del__(self):
587 if self._file:
588 self.close()
Sjoerd Mullender43bf0bc1993-12-13 11:42:39 +0000589
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000590 #
591 # User visible methods.
592 #
593 def aiff(self):
594 if self._nframeswritten:
595 raise Error, 'cannot change parameters after starting to write'
596 self._aifc = 0
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000597
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000598 def aifc(self):
599 if self._nframeswritten:
600 raise Error, 'cannot change parameters after starting to write'
601 self._aifc = 1
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000602
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000603 def setnchannels(self, nchannels):
604 if self._nframeswritten:
605 raise Error, 'cannot change parameters after starting to write'
606 if nchannels < 1:
607 raise Error, 'bad # of channels'
608 self._nchannels = nchannels
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000609
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000610 def getnchannels(self):
611 if not self._nchannels:
612 raise Error, 'number of channels not set'
613 return self._nchannels
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000614
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000615 def setsampwidth(self, sampwidth):
616 if self._nframeswritten:
617 raise Error, 'cannot change parameters after starting to write'
618 if sampwidth < 1 or sampwidth > 4:
619 raise Error, 'bad sample width'
620 self._sampwidth = sampwidth
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000621
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000622 def getsampwidth(self):
623 if not self._sampwidth:
624 raise Error, 'sample width not set'
625 return self._sampwidth
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000626
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000627 def setframerate(self, framerate):
628 if self._nframeswritten:
629 raise Error, 'cannot change parameters after starting to write'
630 if framerate <= 0:
631 raise Error, 'bad frame rate'
632 self._framerate = framerate
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000633
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000634 def getframerate(self):
635 if not self._framerate:
636 raise Error, 'frame rate not set'
637 return self._framerate
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000638
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000639 def setnframes(self, nframes):
640 if self._nframeswritten:
641 raise Error, 'cannot change parameters after starting to write'
642 self._nframes = nframes
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000643
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000644 def getnframes(self):
645 return self._nframeswritten
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000646
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000647 def setcomptype(self, comptype, compname):
648 if self._nframeswritten:
649 raise Error, 'cannot change parameters after starting to write'
650 if comptype not in ('NONE', 'ULAW', 'ALAW', 'G722'):
651 raise Error, 'unsupported compression type'
652 self._comptype = comptype
653 self._compname = compname
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000654
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000655 def getcomptype(self):
656 return self._comptype
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000657
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000658 def getcompname(self):
659 return self._compname
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000660
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000661## def setversion(self, version):
662## if self._nframeswritten:
663## raise Error, 'cannot change parameters after starting to write'
664## self._version = version
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000665
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000666 def setparams(self, (nchannels, sampwidth, framerate, nframes, comptype, compname)):
667 if self._nframeswritten:
668 raise Error, 'cannot change parameters after starting to write'
669 if comptype not in ('NONE', 'ULAW', 'ALAW', 'G722'):
670 raise Error, 'unsupported compression type'
671 self.setnchannels(nchannels)
672 self.setsampwidth(sampwidth)
673 self.setframerate(framerate)
674 self.setnframes(nframes)
675 self.setcomptype(comptype, compname)
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000676
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000677 def getparams(self):
678 if not self._nchannels or not self._sampwidth or not self._framerate:
679 raise Error, 'not all parameters set'
680 return self._nchannels, self._sampwidth, self._framerate, \
681 self._nframes, self._comptype, self._compname
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000682
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000683 def setmark(self, id, pos, name):
684 if id <= 0:
685 raise Error, 'marker ID must be > 0'
686 if pos < 0:
687 raise Error, 'marker position must be >= 0'
688 if type(name) != type(''):
689 raise Error, 'marker name must be a string'
690 for i in range(len(self._markers)):
691 if id == self._markers[i][0]:
692 self._markers[i] = id, pos, name
693 return
694 self._markers.append((id, pos, name))
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000695
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000696 def getmark(self, id):
697 for marker in self._markers:
698 if id == marker[0]:
699 return marker
700 raise Error, 'marker ' + `id` + ' does not exist'
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000701
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000702 def getmarkers(self):
703 if len(self._markers) == 0:
704 return None
705 return self._markers
Tim Peters146965a2001-01-14 18:09:23 +0000706
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000707 def tell(self):
708 return self._nframeswritten
Sjoerd Mullender43bf0bc1993-12-13 11:42:39 +0000709
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000710 def writeframesraw(self, data):
711 self._ensure_header_written(len(data))
712 nframes = len(data) / (self._sampwidth * self._nchannels)
713 if self._convert:
714 data = self._convert(data)
715 self._file.write(data)
716 self._nframeswritten = self._nframeswritten + nframes
717 self._datawritten = self._datawritten + len(data)
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000718
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000719 def writeframes(self, data):
720 self.writeframesraw(data)
721 if self._nframeswritten != self._nframes or \
722 self._datalength != self._datawritten:
723 self._patchheader()
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000724
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000725 def close(self):
726 self._ensure_header_written(0)
727 if self._datawritten & 1:
728 # quick pad to even size
729 self._file.write(chr(0))
730 self._datawritten = self._datawritten + 1
731 self._writemarkers()
732 if self._nframeswritten != self._nframes or \
733 self._datalength != self._datawritten or \
734 self._marklength:
735 self._patchheader()
736 if self._comp:
737 self._comp.CloseCompressor()
738 self._comp = None
739 self._file.flush()
740 self._file = None
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000741
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000742 #
743 # Internal methods.
744 #
Sjoerd Mullender2a451411993-12-20 09:36:01 +0000745
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000746 def _comp_data(self, data):
747 import cl
Neal Norwitz086ac002002-02-11 17:56:27 +0000748 dummy = self._comp.SetParam(cl.FRAME_BUFFER_SIZE, len(data))
749 dummy = self._comp.SetParam(cl.COMPRESSED_BUFFER_SIZE, len(data))
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000750 return self._comp.Compress(self._nframes, data)
Sjoerd Mullender43bf0bc1993-12-13 11:42:39 +0000751
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000752 def _lin2ulaw(self, data):
753 import audioop
754 return audioop.lin2ulaw(data, 2)
Sjoerd Mullender43bf0bc1993-12-13 11:42:39 +0000755
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000756 def _lin2adpcm(self, data):
757 import audioop
758 if not hasattr(self, '_adpcmstate'):
759 self._adpcmstate = None
760 data, self._adpcmstate = audioop.lin2adpcm(data, 2,
761 self._adpcmstate)
762 return data
Sjoerd Mullender1f057541994-09-06 16:17:51 +0000763
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000764 def _ensure_header_written(self, datasize):
765 if not self._nframeswritten:
766 if self._comptype in ('ULAW', 'ALAW'):
767 if not self._sampwidth:
768 self._sampwidth = 2
769 if self._sampwidth != 2:
770 raise Error, 'sample width must be 2 when compressing with ULAW or ALAW'
771 if self._comptype == 'G722':
772 if not self._sampwidth:
773 self._sampwidth = 2
774 if self._sampwidth != 2:
775 raise Error, 'sample width must be 2 when compressing with G7.22 (ADPCM)'
776 if not self._nchannels:
777 raise Error, '# channels not specified'
778 if not self._sampwidth:
779 raise Error, 'sample width not specified'
780 if not self._framerate:
781 raise Error, 'sampling rate not specified'
782 self._write_header(datasize)
Guido van Rossum52fc1f61993-06-17 12:38:10 +0000783
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000784 def _init_compression(self):
785 if self._comptype == 'G722':
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000786 self._convert = self._lin2adpcm
787 return
788 try:
789 import cl
790 except ImportError:
791 if self._comptype == 'ULAW':
792 try:
793 import audioop
794 self._convert = self._lin2ulaw
795 return
796 except ImportError:
797 pass
798 raise Error, 'cannot write compressed AIFF-C files'
799 if self._comptype == 'ULAW':
800 scheme = cl.G711_ULAW
801 elif self._comptype == 'ALAW':
802 scheme = cl.G711_ALAW
803 else:
804 raise Error, 'unsupported compression type'
805 self._comp = cl.OpenCompressor(scheme)
806 params = [cl.ORIGINAL_FORMAT, 0,
807 cl.BITS_PER_COMPONENT, self._sampwidth * 8,
808 cl.FRAME_RATE, self._framerate,
809 cl.FRAME_BUFFER_SIZE, 100,
810 cl.COMPRESSED_BUFFER_SIZE, 100]
811 if self._nchannels == 1:
812 params[1] = cl.MONO
813 elif self._nchannels == 2:
814 params[1] = cl.STEREO_INTERLEAVED
815 else:
816 raise Error, 'cannot compress more than 2 channels'
817 self._comp.SetParams(params)
818 # the compressor produces a header which we ignore
819 dummy = self._comp.Compress(0, '')
820 self._convert = self._comp_data
Sjoerd Mullender43bf0bc1993-12-13 11:42:39 +0000821
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000822 def _write_header(self, initlength):
823 if self._aifc and self._comptype != 'NONE':
824 self._init_compression()
825 self._file.write('FORM')
826 if not self._nframes:
827 self._nframes = initlength / (self._nchannels * self._sampwidth)
828 self._datalength = self._nframes * self._nchannels * self._sampwidth
829 if self._datalength & 1:
830 self._datalength = self._datalength + 1
831 if self._aifc:
832 if self._comptype in ('ULAW', 'ALAW'):
833 self._datalength = self._datalength / 2
834 if self._datalength & 1:
835 self._datalength = self._datalength + 1
836 elif self._comptype == 'G722':
837 self._datalength = (self._datalength + 3) / 4
838 if self._datalength & 1:
839 self._datalength = self._datalength + 1
840 self._form_length_pos = self._file.tell()
841 commlength = self._write_form_length(self._datalength)
842 if self._aifc:
843 self._file.write('AIFC')
844 self._file.write('FVER')
845 _write_long(self._file, 4)
846 _write_long(self._file, self._version)
847 else:
848 self._file.write('AIFF')
849 self._file.write('COMM')
850 _write_long(self._file, commlength)
851 _write_short(self._file, self._nchannels)
852 self._nframes_pos = self._file.tell()
853 _write_long(self._file, self._nframes)
854 _write_short(self._file, self._sampwidth * 8)
855 _write_float(self._file, self._framerate)
856 if self._aifc:
857 self._file.write(self._comptype)
858 _write_string(self._file, self._compname)
859 self._file.write('SSND')
860 self._ssnd_length_pos = self._file.tell()
861 _write_long(self._file, self._datalength + 8)
862 _write_long(self._file, 0)
863 _write_long(self._file, 0)
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000864
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000865 def _write_form_length(self, datalength):
866 if self._aifc:
867 commlength = 18 + 5 + len(self._compname)
868 if commlength & 1:
869 commlength = commlength + 1
870 verslength = 12
871 else:
872 commlength = 18
873 verslength = 0
874 _write_long(self._file, 4 + verslength + self._marklength + \
875 8 + commlength + 16 + datalength)
876 return commlength
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000877
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000878 def _patchheader(self):
879 curpos = self._file.tell()
880 if self._datawritten & 1:
881 datalength = self._datawritten + 1
882 self._file.write(chr(0))
883 else:
884 datalength = self._datawritten
885 if datalength == self._datalength and \
886 self._nframes == self._nframeswritten and \
887 self._marklength == 0:
888 self._file.seek(curpos, 0)
889 return
890 self._file.seek(self._form_length_pos, 0)
891 dummy = self._write_form_length(datalength)
892 self._file.seek(self._nframes_pos, 0)
893 _write_long(self._file, self._nframeswritten)
894 self._file.seek(self._ssnd_length_pos, 0)
895 _write_long(self._file, datalength + 8)
896 self._file.seek(curpos, 0)
897 self._nframes = self._nframeswritten
898 self._datalength = datalength
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000899
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000900 def _writemarkers(self):
901 if len(self._markers) == 0:
902 return
903 self._file.write('MARK')
904 length = 2
905 for marker in self._markers:
906 id, pos, name = marker
907 length = length + len(name) + 1 + 6
908 if len(name) & 1 == 0:
909 length = length + 1
910 _write_long(self._file, length)
911 self._marklength = length + 8
912 _write_short(self._file, len(self._markers))
913 for marker in self._markers:
914 id, pos, name = marker
915 _write_short(self._file, id)
916 _write_long(self._file, pos)
917 _write_string(self._file, name)
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000918
Fred Drake43161351999-06-22 21:23:23 +0000919def open(f, mode=None):
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000920 if mode is None:
921 if hasattr(f, 'mode'):
922 mode = f.mode
923 else:
924 mode = 'rb'
925 if mode in ('r', 'rb'):
926 return Aifc_read(f)
927 elif mode in ('w', 'wb'):
928 return Aifc_write(f)
929 else:
930 raise Error, "mode must be 'r', 'rb', 'w', or 'wb'"
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000931
Guido van Rossum7bc817d1993-12-17 15:25:27 +0000932openfp = open # B/W compatibility
Guido van Rossum36bb1811996-12-31 05:57:34 +0000933
934if __name__ == '__main__':
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000935 import sys
936 if not sys.argv[1:]:
937 sys.argv.append('/usr/demos/data/audio/bach.aiff')
938 fn = sys.argv[1]
939 f = open(fn, 'r')
940 print "Reading", fn
941 print "nchannels =", f.getnchannels()
942 print "nframes =", f.getnframes()
943 print "sampwidth =", f.getsampwidth()
944 print "framerate =", f.getframerate()
945 print "comptype =", f.getcomptype()
946 print "compname =", f.getcompname()
947 if sys.argv[2:]:
948 gn = sys.argv[2]
949 print "Writing", gn
950 g = open(gn, 'w')
951 g.setparams(f.getparams())
952 while 1:
953 data = f.readframes(1024)
954 if not data:
955 break
956 g.writeframes(data)
957 g.close()
958 f.close()
959 print "Done."