blob: f709d888f5758c6ed386e7b17bb06d42626a0d5d [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
Fred Drake227b1202000-08-17 05:06:49 +0000140class Error(Exception):
141 pass
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000142
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000143_AIFC_version = 0xA2805140 # Version 1 of AIFF-C
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000144
145_skiplist = 'COMT', 'INST', 'MIDI', 'AESD', \
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000146 'APPL', 'NAME', 'AUTH', '(c) ', 'ANNO'
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000147
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000148def _read_long(file):
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000149 try:
150 return struct.unpack('>l', file.read(4))[0]
151 except struct.error:
152 raise EOFError
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000153
154def _read_ulong(file):
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000155 try:
156 return struct.unpack('>L', file.read(4))[0]
157 except struct.error:
158 raise EOFError
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000159
160def _read_short(file):
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000161 try:
162 return struct.unpack('>h', file.read(2))[0]
163 except struct.error:
164 raise EOFError
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000165
166def _read_string(file):
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000167 length = ord(file.read(1))
168 if length == 0:
169 data = ''
170 else:
171 data = file.read(length)
172 if length & 1 == 0:
173 dummy = file.read(1)
174 return data
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000175
176_HUGE_VAL = 1.79769313486231e+308 # See <limits.h>
177
178def _read_float(f): # 10 bytes
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000179 import math
180 expon = _read_short(f) # 2 bytes
181 sign = 1
182 if expon < 0:
183 sign = -1
184 expon = expon + 0x8000
185 himant = _read_ulong(f) # 4 bytes
186 lomant = _read_ulong(f) # 4 bytes
187 if expon == himant == lomant == 0:
188 f = 0.0
189 elif expon == 0x7FFF:
190 f = _HUGE_VAL
191 else:
192 expon = expon - 16383
193 f = (himant * 0x100000000L + lomant) * pow(2.0, expon - 63)
194 return sign * f
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000195
196def _write_short(f, x):
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000197 f.write(struct.pack('>h', x))
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000198
199def _write_long(f, x):
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000200 f.write(struct.pack('>L', x))
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000201
202def _write_string(f, s):
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':
311 self._version = _read_long(chunk)
312 elif chunkname == 'MARK':
313 self._readmark(chunk)
314 elif chunkname in _skiplist:
315 pass
316 else:
317 raise Error, 'unrecognized chunk type '+chunk.chunkname
318 chunk.skip()
319 if not self._comm_chunk_read or not self._ssnd_chunk:
320 raise Error, 'COMM chunk and/or SSND chunk missing'
321 if self._aifc and self._decomp:
322 import cl
323 params = [cl.ORIGINAL_FORMAT, 0,
324 cl.BITS_PER_COMPONENT, self._sampwidth * 8,
325 cl.FRAME_RATE, self._framerate]
326 if self._nchannels == 1:
327 params[1] = cl.MONO
328 elif self._nchannels == 2:
329 params[1] = cl.STEREO_INTERLEAVED
330 else:
331 raise Error, 'cannot compress more than 2 channels'
332 self._decomp.SetParams(params)
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000333
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000334 def __init__(self, f):
335 if type(f) == type(''):
336 f = __builtin__.open(f, 'rb')
337 # else, assume it is an open file object already
338 self.initfp(f)
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000339
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000340 #
341 # User visible methods.
342 #
343 def getfp(self):
344 return self._file
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000345
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000346 def rewind(self):
347 self._ssnd_seek_needed = 1
348 self._soundpos = 0
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000349
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000350 def close(self):
351 if self._decomp:
352 self._decomp.CloseDecompressor()
353 self._decomp = None
354 self._file = None
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000355
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000356 def tell(self):
357 return self._soundpos
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000358
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000359 def getnchannels(self):
360 return self._nchannels
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000361
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000362 def getnframes(self):
363 return self._nframes
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000364
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000365 def getsampwidth(self):
366 return self._sampwidth
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000367
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000368 def getframerate(self):
369 return self._framerate
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000370
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000371 def getcomptype(self):
372 return self._comptype
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000373
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000374 def getcompname(self):
375 return self._compname
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000376
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000377## def getversion(self):
378## return self._version
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000379
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000380 def getparams(self):
381 return self.getnchannels(), self.getsampwidth(), \
382 self.getframerate(), self.getnframes(), \
383 self.getcomptype(), self.getcompname()
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000384
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000385 def getmarkers(self):
386 if len(self._markers) == 0:
387 return None
388 return self._markers
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000389
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000390 def getmark(self, id):
391 for marker in self._markers:
392 if id == marker[0]:
393 return marker
394 raise Error, 'marker ' + `id` + ' does not exist'
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000395
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000396 def setpos(self, pos):
397 if pos < 0 or pos > self._nframes:
398 raise Error, 'position not in range'
399 self._soundpos = pos
400 self._ssnd_seek_needed = 1
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000401
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000402 def readframes(self, nframes):
403 if self._ssnd_seek_needed:
404 self._ssnd_chunk.seek(0)
405 dummy = self._ssnd_chunk.read(8)
406 pos = self._soundpos * self._framesize
407 if pos:
Guido van Rossum2663c132000-03-07 15:19:31 +0000408 self._ssnd_chunk.seek(pos + 8)
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000409 self._ssnd_seek_needed = 0
410 if nframes == 0:
411 return ''
412 data = self._ssnd_chunk.read(nframes * self._framesize)
413 if self._convert and data:
414 data = self._convert(data)
415 self._soundpos = self._soundpos + len(data) / (self._nchannels * self._sampwidth)
416 return data
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000417
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000418 #
419 # Internal methods.
420 #
Sjoerd Mullender2a451411993-12-20 09:36:01 +0000421
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000422 def _decomp_data(self, data):
423 import cl
424 dummy = self._decomp.SetParam(cl.FRAME_BUFFER_SIZE,
425 len(data) * 2)
426 return self._decomp.Decompress(len(data) / self._nchannels,
427 data)
Sjoerd Mullender43bf0bc1993-12-13 11:42:39 +0000428
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000429 def _ulaw2lin(self, data):
430 import audioop
431 return audioop.ulaw2lin(data, 2)
Sjoerd Mullender43bf0bc1993-12-13 11:42:39 +0000432
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000433 def _adpcm2lin(self, data):
434 import audioop
435 if not hasattr(self, '_adpcmstate'):
436 # first time
437 self._adpcmstate = None
438 data, self._adpcmstate = audioop.adpcm2lin(data, 2,
439 self._adpcmstate)
440 return data
Sjoerd Mullender1f057541994-09-06 16:17:51 +0000441
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000442 def _read_comm_chunk(self, chunk):
443 self._nchannels = _read_short(chunk)
444 self._nframes = _read_long(chunk)
445 self._sampwidth = (_read_short(chunk) + 7) / 8
446 self._framerate = int(_read_float(chunk))
447 self._framesize = self._nchannels * self._sampwidth
448 if self._aifc:
449 #DEBUG: SGI's soundeditor produces a bad size :-(
450 kludge = 0
451 if chunk.chunksize == 18:
452 kludge = 1
453 print 'Warning: bad COMM chunk size'
454 chunk.chunksize = 23
455 #DEBUG end
456 self._comptype = chunk.read(4)
457 #DEBUG start
458 if kludge:
459 length = ord(chunk.file.read(1))
460 if length & 1 == 0:
461 length = length + 1
462 chunk.chunksize = chunk.chunksize + length
463 chunk.file.seek(-1, 1)
464 #DEBUG end
465 self._compname = _read_string(chunk)
466 if self._comptype != 'NONE':
467 if self._comptype == 'G722':
468 try:
469 import audioop
470 except ImportError:
471 pass
472 else:
473 self._convert = self._adpcm2lin
474 self._framesize = self._framesize / 4
475 return
476 # for ULAW and ALAW try Compression Library
477 try:
478 import cl
479 except ImportError:
480 if self._comptype == 'ULAW':
481 try:
482 import audioop
483 self._convert = self._ulaw2lin
484 self._framesize = self._framesize / 2
485 return
486 except ImportError:
487 pass
488 raise Error, 'cannot read compressed AIFF-C files'
489 if self._comptype == 'ULAW':
490 scheme = cl.G711_ULAW
491 self._framesize = self._framesize / 2
492 elif self._comptype == 'ALAW':
493 scheme = cl.G711_ALAW
494 self._framesize = self._framesize / 2
495 else:
496 raise Error, 'unsupported compression type'
497 self._decomp = cl.OpenDecompressor(scheme)
498 self._convert = self._decomp_data
499 else:
500 self._comptype = 'NONE'
501 self._compname = 'not compressed'
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000502
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000503 def _readmark(self, chunk):
504 nmarkers = _read_short(chunk)
505 # Some files appear to contain invalid counts.
506 # Cope with this by testing for EOF.
507 try:
508 for i in range(nmarkers):
509 id = _read_short(chunk)
510 pos = _read_long(chunk)
511 name = _read_string(chunk)
512 if pos or name:
513 # some files appear to have
514 # dummy markers consisting of
515 # a position 0 and name ''
516 self._markers.append((id, pos, name))
517 except EOFError:
518 print 'Warning: MARK chunk contains only',
519 print len(self._markers),
520 if len(self._markers) == 1: print 'marker',
521 else: print 'markers',
522 print 'instead of', nmarkers
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000523
Guido van Rossumd3166071993-05-24 14:16:22 +0000524class Aifc_write:
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000525 # Variables used in this class:
526 #
527 # These variables are user settable through appropriate methods
528 # of this class:
529 # _file -- the open file with methods write(), close(), tell(), seek()
530 # set through the __init__() method
531 # _comptype -- the AIFF-C compression type ('NONE' in AIFF)
532 # set through the setcomptype() or setparams() method
533 # _compname -- the human-readable AIFF-C compression type
534 # set through the setcomptype() or setparams() method
535 # _nchannels -- the number of audio channels
536 # set through the setnchannels() or setparams() method
537 # _sampwidth -- the number of bytes per audio sample
538 # set through the setsampwidth() or setparams() method
539 # _framerate -- the sampling frequency
540 # set through the setframerate() or setparams() method
541 # _nframes -- the number of audio frames written to the header
542 # set through the setnframes() or setparams() method
543 # _aifc -- whether we're writing an AIFF-C file or an AIFF file
544 # set through the aifc() method, reset through the
545 # aiff() method
546 #
547 # These variables are used internally only:
548 # _version -- the AIFF-C version number
549 # _comp -- the compressor from builtin module cl
550 # _nframeswritten -- the number of audio frames actually written
551 # _datalength -- the size of the audio samples written to the header
552 # _datawritten -- the size of the audio samples actually written
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000553
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000554 def __init__(self, f):
555 if type(f) == type(''):
556 filename = f
557 f = __builtin__.open(f, 'wb')
558 else:
559 # else, assume it is an open file object already
560 filename = '???'
561 self.initfp(f)
562 if filename[-5:] == '.aiff':
563 self._aifc = 0
564 else:
565 self._aifc = 1
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000566
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000567 def initfp(self, file):
568 self._file = file
569 self._version = _AIFC_version
570 self._comptype = 'NONE'
571 self._compname = 'not compressed'
572 self._comp = None
573 self._convert = None
574 self._nchannels = 0
575 self._sampwidth = 0
576 self._framerate = 0
577 self._nframes = 0
578 self._nframeswritten = 0
579 self._datawritten = 0
580 self._datalength = 0
581 self._markers = []
582 self._marklength = 0
583 self._aifc = 1 # AIFF-C is default
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000584
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000585 def __del__(self):
586 if self._file:
587 self.close()
Sjoerd Mullender43bf0bc1993-12-13 11:42:39 +0000588
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000589 #
590 # User visible methods.
591 #
592 def aiff(self):
593 if self._nframeswritten:
594 raise Error, 'cannot change parameters after starting to write'
595 self._aifc = 0
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000596
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000597 def aifc(self):
598 if self._nframeswritten:
599 raise Error, 'cannot change parameters after starting to write'
600 self._aifc = 1
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000601
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000602 def setnchannels(self, nchannels):
603 if self._nframeswritten:
604 raise Error, 'cannot change parameters after starting to write'
605 if nchannels < 1:
606 raise Error, 'bad # of channels'
607 self._nchannels = nchannels
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000608
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000609 def getnchannels(self):
610 if not self._nchannels:
611 raise Error, 'number of channels not set'
612 return self._nchannels
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000613
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000614 def setsampwidth(self, sampwidth):
615 if self._nframeswritten:
616 raise Error, 'cannot change parameters after starting to write'
617 if sampwidth < 1 or sampwidth > 4:
618 raise Error, 'bad sample width'
619 self._sampwidth = sampwidth
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000620
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000621 def getsampwidth(self):
622 if not self._sampwidth:
623 raise Error, 'sample width not set'
624 return self._sampwidth
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000625
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000626 def setframerate(self, framerate):
627 if self._nframeswritten:
628 raise Error, 'cannot change parameters after starting to write'
629 if framerate <= 0:
630 raise Error, 'bad frame rate'
631 self._framerate = framerate
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000632
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000633 def getframerate(self):
634 if not self._framerate:
635 raise Error, 'frame rate not set'
636 return self._framerate
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000637
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000638 def setnframes(self, nframes):
639 if self._nframeswritten:
640 raise Error, 'cannot change parameters after starting to write'
641 self._nframes = nframes
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000642
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000643 def getnframes(self):
644 return self._nframeswritten
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000645
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000646 def setcomptype(self, comptype, compname):
647 if self._nframeswritten:
648 raise Error, 'cannot change parameters after starting to write'
649 if comptype not in ('NONE', 'ULAW', 'ALAW', 'G722'):
650 raise Error, 'unsupported compression type'
651 self._comptype = comptype
652 self._compname = compname
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000653
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000654 def getcomptype(self):
655 return self._comptype
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000656
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000657 def getcompname(self):
658 return self._compname
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000659
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000660## def setversion(self, version):
661## if self._nframeswritten:
662## raise Error, 'cannot change parameters after starting to write'
663## self._version = version
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000664
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000665 def setparams(self, (nchannels, sampwidth, framerate, nframes, comptype, compname)):
666 if self._nframeswritten:
667 raise Error, 'cannot change parameters after starting to write'
668 if comptype not in ('NONE', 'ULAW', 'ALAW', 'G722'):
669 raise Error, 'unsupported compression type'
670 self.setnchannels(nchannels)
671 self.setsampwidth(sampwidth)
672 self.setframerate(framerate)
673 self.setnframes(nframes)
674 self.setcomptype(comptype, compname)
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000675
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000676 def getparams(self):
677 if not self._nchannels or not self._sampwidth or not self._framerate:
678 raise Error, 'not all parameters set'
679 return self._nchannels, self._sampwidth, self._framerate, \
680 self._nframes, self._comptype, self._compname
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000681
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000682 def setmark(self, id, pos, name):
683 if id <= 0:
684 raise Error, 'marker ID must be > 0'
685 if pos < 0:
686 raise Error, 'marker position must be >= 0'
687 if type(name) != type(''):
688 raise Error, 'marker name must be a string'
689 for i in range(len(self._markers)):
690 if id == self._markers[i][0]:
691 self._markers[i] = id, pos, name
692 return
693 self._markers.append((id, pos, name))
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000694
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000695 def getmark(self, id):
696 for marker in self._markers:
697 if id == marker[0]:
698 return marker
699 raise Error, 'marker ' + `id` + ' does not exist'
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000700
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000701 def getmarkers(self):
702 if len(self._markers) == 0:
703 return None
704 return self._markers
705
706 def tell(self):
707 return self._nframeswritten
Sjoerd Mullender43bf0bc1993-12-13 11:42:39 +0000708
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000709 def writeframesraw(self, data):
710 self._ensure_header_written(len(data))
711 nframes = len(data) / (self._sampwidth * self._nchannels)
712 if self._convert:
713 data = self._convert(data)
714 self._file.write(data)
715 self._nframeswritten = self._nframeswritten + nframes
716 self._datawritten = self._datawritten + len(data)
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000717
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000718 def writeframes(self, data):
719 self.writeframesraw(data)
720 if self._nframeswritten != self._nframes or \
721 self._datalength != self._datawritten:
722 self._patchheader()
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000723
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000724 def close(self):
725 self._ensure_header_written(0)
726 if self._datawritten & 1:
727 # quick pad to even size
728 self._file.write(chr(0))
729 self._datawritten = self._datawritten + 1
730 self._writemarkers()
731 if self._nframeswritten != self._nframes or \
732 self._datalength != self._datawritten or \
733 self._marklength:
734 self._patchheader()
735 if self._comp:
736 self._comp.CloseCompressor()
737 self._comp = None
738 self._file.flush()
739 self._file = None
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000740
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000741 #
742 # Internal methods.
743 #
Sjoerd Mullender2a451411993-12-20 09:36:01 +0000744
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000745 def _comp_data(self, data):
746 import cl
747 dum = self._comp.SetParam(cl.FRAME_BUFFER_SIZE, len(data))
748 dum = self._comp.SetParam(cl.COMPRESSED_BUFFER_SIZE, len(data))
749 return self._comp.Compress(self._nframes, data)
Sjoerd Mullender43bf0bc1993-12-13 11:42:39 +0000750
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000751 def _lin2ulaw(self, data):
752 import audioop
753 return audioop.lin2ulaw(data, 2)
Sjoerd Mullender43bf0bc1993-12-13 11:42:39 +0000754
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000755 def _lin2adpcm(self, data):
756 import audioop
757 if not hasattr(self, '_adpcmstate'):
758 self._adpcmstate = None
759 data, self._adpcmstate = audioop.lin2adpcm(data, 2,
760 self._adpcmstate)
761 return data
Sjoerd Mullender1f057541994-09-06 16:17:51 +0000762
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000763 def _ensure_header_written(self, datasize):
764 if not self._nframeswritten:
765 if self._comptype in ('ULAW', 'ALAW'):
766 if not self._sampwidth:
767 self._sampwidth = 2
768 if self._sampwidth != 2:
769 raise Error, 'sample width must be 2 when compressing with ULAW or ALAW'
770 if self._comptype == 'G722':
771 if not self._sampwidth:
772 self._sampwidth = 2
773 if self._sampwidth != 2:
774 raise Error, 'sample width must be 2 when compressing with G7.22 (ADPCM)'
775 if not self._nchannels:
776 raise Error, '# channels not specified'
777 if not self._sampwidth:
778 raise Error, 'sample width not specified'
779 if not self._framerate:
780 raise Error, 'sampling rate not specified'
781 self._write_header(datasize)
Guido van Rossum52fc1f61993-06-17 12:38:10 +0000782
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000783 def _init_compression(self):
784 if self._comptype == 'G722':
785 import audioop
786 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."