blob: 0af66228c288629fe314b83492a8c817d1a4bcbf [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
85the actual postion in the file.
86The 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
140Error = 'aifc.Error'
141
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000142_AIFC_version = 0xA2805140 # Version 1 of AIFF-C
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000143
144_skiplist = 'COMT', 'INST', 'MIDI', 'AESD', \
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000145 'APPL', 'NAME', 'AUTH', '(c) ', 'ANNO'
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 import math
179 expon = _read_short(f) # 2 bytes
180 sign = 1
181 if expon < 0:
182 sign = -1
183 expon = expon + 0x8000
184 himant = _read_ulong(f) # 4 bytes
185 lomant = _read_ulong(f) # 4 bytes
186 if expon == himant == lomant == 0:
187 f = 0.0
188 elif expon == 0x7FFF:
189 f = _HUGE_VAL
190 else:
191 expon = expon - 16383
192 f = (himant * 0x100000000L + lomant) * pow(2.0, expon - 63)
193 return sign * f
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000194
195def _write_short(f, x):
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000196 f.write(struct.pack('>h', x))
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000197
198def _write_long(f, x):
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000199 f.write(struct.pack('>L', x))
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000200
201def _write_string(f, s):
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000202 f.write(chr(len(s)))
203 f.write(s)
204 if len(s) & 1 == 0:
205 f.write(chr(0))
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000206
207def _write_float(f, x):
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000208 import math
209 if x < 0:
210 sign = 0x8000
211 x = x * -1
212 else:
213 sign = 0
214 if x == 0:
215 expon = 0
216 himant = 0
217 lomant = 0
218 else:
219 fmant, expon = math.frexp(x)
220 if expon > 16384 or fmant >= 1: # Infinity or NaN
221 expon = sign|0x7FFF
222 himant = 0
223 lomant = 0
224 else: # Finite
225 expon = expon + 16382
226 if expon < 0: # denormalized
227 fmant = math.ldexp(fmant, expon)
228 expon = 0
229 expon = expon | sign
230 fmant = math.ldexp(fmant, 32)
231 fsmant = math.floor(fmant)
232 himant = long(fsmant)
233 fmant = math.ldexp(fmant - fsmant, 32)
234 fsmant = math.floor(fmant)
235 lomant = long(fsmant)
236 _write_short(f, expon)
237 _write_long(f, himant)
238 _write_long(f, lomant)
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000239
Guido van Rossum8ea7bb81999-06-09 13:32:28 +0000240from chunk import Chunk
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000241
Guido van Rossumd3166071993-05-24 14:16:22 +0000242class Aifc_read:
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000243 # Variables used in this class:
244 #
245 # These variables are available to the user though appropriate
246 # methods of this class:
247 # _file -- the open file with methods read(), close(), and seek()
248 # set through the __init__() method
249 # _nchannels -- the number of audio channels
250 # available through the getnchannels() method
251 # _nframes -- the number of audio frames
252 # available through the getnframes() method
253 # _sampwidth -- the number of bytes per audio sample
254 # available through the getsampwidth() method
255 # _framerate -- the sampling frequency
256 # available through the getframerate() method
257 # _comptype -- the AIFF-C compression type ('NONE' if AIFF)
258 # available through the getcomptype() method
259 # _compname -- the human-readable AIFF-C compression type
260 # available through the getcomptype() method
261 # _markers -- the marks in the audio file
262 # available through the getmarkers() and getmark()
263 # methods
264 # _soundpos -- the position in the audio stream
265 # available through the tell() method, set through the
266 # setpos() method
267 #
268 # These variables are used internally only:
269 # _version -- the AIFF-C version number
270 # _decomp -- the decompressor from builtin module cl
271 # _comm_chunk_read -- 1 iff the COMM chunk has been read
272 # _aifc -- 1 iff reading an AIFF-C file
273 # _ssnd_seek_needed -- 1 iff positioned correctly in audio
274 # file for readframes()
275 # _ssnd_chunk -- instantiation of a chunk class for the SSND chunk
276 # _framesize -- size of one frame in the file
Sjoerd Mullender2a451411993-12-20 09:36:01 +0000277
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000278 def initfp(self, file):
279 self._version = 0
280 self._decomp = None
281 self._convert = None
282 self._markers = []
283 self._soundpos = 0
284 self._file = Chunk(file)
285 if self._file.getname() != 'FORM':
286 raise Error, 'file does not start with FORM id'
287 formdata = self._file.read(4)
288 if formdata == 'AIFF':
289 self._aifc = 0
290 elif formdata == 'AIFC':
291 self._aifc = 1
292 else:
293 raise Error, 'not an AIFF or AIFF-C file'
294 self._comm_chunk_read = 0
295 while 1:
296 self._ssnd_seek_needed = 1
297 try:
298 chunk = Chunk(self._file)
299 except EOFError:
300 break
301 chunkname = chunk.getname()
302 if chunkname == 'COMM':
303 self._read_comm_chunk(chunk)
304 self._comm_chunk_read = 1
305 elif chunkname == 'SSND':
306 self._ssnd_chunk = chunk
307 dummy = chunk.read(8)
308 self._ssnd_seek_needed = 0
309 elif chunkname == 'FVER':
310 self._version = _read_long(chunk)
311 elif chunkname == 'MARK':
312 self._readmark(chunk)
313 elif chunkname in _skiplist:
314 pass
315 else:
316 raise Error, 'unrecognized chunk type '+chunk.chunkname
317 chunk.skip()
318 if not self._comm_chunk_read or not self._ssnd_chunk:
319 raise Error, 'COMM chunk and/or SSND chunk missing'
320 if self._aifc and self._decomp:
321 import cl
322 params = [cl.ORIGINAL_FORMAT, 0,
323 cl.BITS_PER_COMPONENT, self._sampwidth * 8,
324 cl.FRAME_RATE, self._framerate]
325 if self._nchannels == 1:
326 params[1] = cl.MONO
327 elif self._nchannels == 2:
328 params[1] = cl.STEREO_INTERLEAVED
329 else:
330 raise Error, 'cannot compress more than 2 channels'
331 self._decomp.SetParams(params)
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000332
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000333 def __init__(self, f):
334 if type(f) == type(''):
335 f = __builtin__.open(f, 'rb')
336 # else, assume it is an open file object already
337 self.initfp(f)
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000338
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000339 #
340 # User visible methods.
341 #
342 def getfp(self):
343 return self._file
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000344
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000345 def rewind(self):
346 self._ssnd_seek_needed = 1
347 self._soundpos = 0
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000348
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000349 def close(self):
350 if self._decomp:
351 self._decomp.CloseDecompressor()
352 self._decomp = None
353 self._file = None
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000354
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000355 def tell(self):
356 return self._soundpos
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000357
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000358 def getnchannels(self):
359 return self._nchannels
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000360
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000361 def getnframes(self):
362 return self._nframes
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000363
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000364 def getsampwidth(self):
365 return self._sampwidth
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000366
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000367 def getframerate(self):
368 return self._framerate
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000369
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000370 def getcomptype(self):
371 return self._comptype
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000372
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000373 def getcompname(self):
374 return self._compname
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000375
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000376## def getversion(self):
377## return self._version
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000378
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000379 def getparams(self):
380 return self.getnchannels(), self.getsampwidth(), \
381 self.getframerate(), self.getnframes(), \
382 self.getcomptype(), self.getcompname()
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000383
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000384 def getmarkers(self):
385 if len(self._markers) == 0:
386 return None
387 return self._markers
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000388
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000389 def getmark(self, id):
390 for marker in self._markers:
391 if id == marker[0]:
392 return marker
393 raise Error, 'marker ' + `id` + ' does not exist'
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000394
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000395 def setpos(self, pos):
396 if pos < 0 or pos > self._nframes:
397 raise Error, 'position not in range'
398 self._soundpos = pos
399 self._ssnd_seek_needed = 1
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000400
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000401 def readframes(self, nframes):
402 if self._ssnd_seek_needed:
403 self._ssnd_chunk.seek(0)
404 dummy = self._ssnd_chunk.read(8)
405 pos = self._soundpos * self._framesize
406 if pos:
Guido van Rossum2663c132000-03-07 15:19:31 +0000407 self._ssnd_chunk.seek(pos + 8)
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000408 self._ssnd_seek_needed = 0
409 if nframes == 0:
410 return ''
411 data = self._ssnd_chunk.read(nframes * self._framesize)
412 if self._convert and data:
413 data = self._convert(data)
414 self._soundpos = self._soundpos + len(data) / (self._nchannels * self._sampwidth)
415 return data
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000416
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000417 #
418 # Internal methods.
419 #
Sjoerd Mullender2a451411993-12-20 09:36:01 +0000420
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000421 def _decomp_data(self, data):
422 import cl
423 dummy = self._decomp.SetParam(cl.FRAME_BUFFER_SIZE,
424 len(data) * 2)
425 return self._decomp.Decompress(len(data) / self._nchannels,
426 data)
Sjoerd Mullender43bf0bc1993-12-13 11:42:39 +0000427
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000428 def _ulaw2lin(self, data):
429 import audioop
430 return audioop.ulaw2lin(data, 2)
Sjoerd Mullender43bf0bc1993-12-13 11:42:39 +0000431
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000432 def _adpcm2lin(self, data):
433 import audioop
434 if not hasattr(self, '_adpcmstate'):
435 # first time
436 self._adpcmstate = None
437 data, self._adpcmstate = audioop.adpcm2lin(data, 2,
438 self._adpcmstate)
439 return data
Sjoerd Mullender1f057541994-09-06 16:17:51 +0000440
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000441 def _read_comm_chunk(self, chunk):
442 self._nchannels = _read_short(chunk)
443 self._nframes = _read_long(chunk)
444 self._sampwidth = (_read_short(chunk) + 7) / 8
445 self._framerate = int(_read_float(chunk))
446 self._framesize = self._nchannels * self._sampwidth
447 if self._aifc:
448 #DEBUG: SGI's soundeditor produces a bad size :-(
449 kludge = 0
450 if chunk.chunksize == 18:
451 kludge = 1
452 print 'Warning: bad COMM chunk size'
453 chunk.chunksize = 23
454 #DEBUG end
455 self._comptype = chunk.read(4)
456 #DEBUG start
457 if kludge:
458 length = ord(chunk.file.read(1))
459 if length & 1 == 0:
460 length = length + 1
461 chunk.chunksize = chunk.chunksize + length
462 chunk.file.seek(-1, 1)
463 #DEBUG end
464 self._compname = _read_string(chunk)
465 if self._comptype != 'NONE':
466 if self._comptype == 'G722':
467 try:
468 import audioop
469 except ImportError:
470 pass
471 else:
472 self._convert = self._adpcm2lin
473 self._framesize = self._framesize / 4
474 return
475 # for ULAW and ALAW try Compression Library
476 try:
477 import cl
478 except ImportError:
479 if self._comptype == 'ULAW':
480 try:
481 import audioop
482 self._convert = self._ulaw2lin
483 self._framesize = self._framesize / 2
484 return
485 except ImportError:
486 pass
487 raise Error, 'cannot read compressed AIFF-C files'
488 if self._comptype == 'ULAW':
489 scheme = cl.G711_ULAW
490 self._framesize = self._framesize / 2
491 elif self._comptype == 'ALAW':
492 scheme = cl.G711_ALAW
493 self._framesize = self._framesize / 2
494 else:
495 raise Error, 'unsupported compression type'
496 self._decomp = cl.OpenDecompressor(scheme)
497 self._convert = self._decomp_data
498 else:
499 self._comptype = 'NONE'
500 self._compname = 'not compressed'
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000501
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000502 def _readmark(self, chunk):
503 nmarkers = _read_short(chunk)
504 # Some files appear to contain invalid counts.
505 # Cope with this by testing for EOF.
506 try:
507 for i in range(nmarkers):
508 id = _read_short(chunk)
509 pos = _read_long(chunk)
510 name = _read_string(chunk)
511 if pos or name:
512 # some files appear to have
513 # dummy markers consisting of
514 # a position 0 and name ''
515 self._markers.append((id, pos, name))
516 except EOFError:
517 print 'Warning: MARK chunk contains only',
518 print len(self._markers),
519 if len(self._markers) == 1: print 'marker',
520 else: print 'markers',
521 print 'instead of', nmarkers
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000522
Guido van Rossumd3166071993-05-24 14:16:22 +0000523class Aifc_write:
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000524 # Variables used in this class:
525 #
526 # These variables are user settable through appropriate methods
527 # of this class:
528 # _file -- the open file with methods write(), close(), tell(), seek()
529 # set through the __init__() method
530 # _comptype -- the AIFF-C compression type ('NONE' in AIFF)
531 # set through the setcomptype() or setparams() method
532 # _compname -- the human-readable AIFF-C compression type
533 # set through the setcomptype() or setparams() method
534 # _nchannels -- the number of audio channels
535 # set through the setnchannels() or setparams() method
536 # _sampwidth -- the number of bytes per audio sample
537 # set through the setsampwidth() or setparams() method
538 # _framerate -- the sampling frequency
539 # set through the setframerate() or setparams() method
540 # _nframes -- the number of audio frames written to the header
541 # set through the setnframes() or setparams() method
542 # _aifc -- whether we're writing an AIFF-C file or an AIFF file
543 # set through the aifc() method, reset through the
544 # aiff() method
545 #
546 # These variables are used internally only:
547 # _version -- the AIFF-C version number
548 # _comp -- the compressor from builtin module cl
549 # _nframeswritten -- the number of audio frames actually written
550 # _datalength -- the size of the audio samples written to the header
551 # _datawritten -- the size of the audio samples actually written
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000552
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000553 def __init__(self, f):
554 if type(f) == type(''):
555 filename = f
556 f = __builtin__.open(f, 'wb')
557 else:
558 # else, assume it is an open file object already
559 filename = '???'
560 self.initfp(f)
561 if filename[-5:] == '.aiff':
562 self._aifc = 0
563 else:
564 self._aifc = 1
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000565
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000566 def initfp(self, file):
567 self._file = file
568 self._version = _AIFC_version
569 self._comptype = 'NONE'
570 self._compname = 'not compressed'
571 self._comp = None
572 self._convert = None
573 self._nchannels = 0
574 self._sampwidth = 0
575 self._framerate = 0
576 self._nframes = 0
577 self._nframeswritten = 0
578 self._datawritten = 0
579 self._datalength = 0
580 self._markers = []
581 self._marklength = 0
582 self._aifc = 1 # AIFF-C is default
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000583
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000584 def __del__(self):
585 if self._file:
586 self.close()
Sjoerd Mullender43bf0bc1993-12-13 11:42:39 +0000587
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000588 #
589 # User visible methods.
590 #
591 def aiff(self):
592 if self._nframeswritten:
593 raise Error, 'cannot change parameters after starting to write'
594 self._aifc = 0
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000595
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000596 def aifc(self):
597 if self._nframeswritten:
598 raise Error, 'cannot change parameters after starting to write'
599 self._aifc = 1
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000600
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000601 def setnchannels(self, nchannels):
602 if self._nframeswritten:
603 raise Error, 'cannot change parameters after starting to write'
604 if nchannels < 1:
605 raise Error, 'bad # of channels'
606 self._nchannels = nchannels
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000607
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000608 def getnchannels(self):
609 if not self._nchannels:
610 raise Error, 'number of channels not set'
611 return self._nchannels
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000612
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000613 def setsampwidth(self, sampwidth):
614 if self._nframeswritten:
615 raise Error, 'cannot change parameters after starting to write'
616 if sampwidth < 1 or sampwidth > 4:
617 raise Error, 'bad sample width'
618 self._sampwidth = sampwidth
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000619
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000620 def getsampwidth(self):
621 if not self._sampwidth:
622 raise Error, 'sample width not set'
623 return self._sampwidth
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000624
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000625 def setframerate(self, framerate):
626 if self._nframeswritten:
627 raise Error, 'cannot change parameters after starting to write'
628 if framerate <= 0:
629 raise Error, 'bad frame rate'
630 self._framerate = framerate
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000631
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000632 def getframerate(self):
633 if not self._framerate:
634 raise Error, 'frame rate not set'
635 return self._framerate
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000636
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000637 def setnframes(self, nframes):
638 if self._nframeswritten:
639 raise Error, 'cannot change parameters after starting to write'
640 self._nframes = nframes
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000641
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000642 def getnframes(self):
643 return self._nframeswritten
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000644
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000645 def setcomptype(self, comptype, compname):
646 if self._nframeswritten:
647 raise Error, 'cannot change parameters after starting to write'
648 if comptype not in ('NONE', 'ULAW', 'ALAW', 'G722'):
649 raise Error, 'unsupported compression type'
650 self._comptype = comptype
651 self._compname = compname
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000652
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000653 def getcomptype(self):
654 return self._comptype
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000655
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000656 def getcompname(self):
657 return self._compname
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000658
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000659## def setversion(self, version):
660## if self._nframeswritten:
661## raise Error, 'cannot change parameters after starting to write'
662## self._version = version
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000663
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000664 def setparams(self, (nchannels, sampwidth, framerate, nframes, comptype, compname)):
665 if self._nframeswritten:
666 raise Error, 'cannot change parameters after starting to write'
667 if comptype not in ('NONE', 'ULAW', 'ALAW', 'G722'):
668 raise Error, 'unsupported compression type'
669 self.setnchannels(nchannels)
670 self.setsampwidth(sampwidth)
671 self.setframerate(framerate)
672 self.setnframes(nframes)
673 self.setcomptype(comptype, compname)
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000674
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000675 def getparams(self):
676 if not self._nchannels or not self._sampwidth or not self._framerate:
677 raise Error, 'not all parameters set'
678 return self._nchannels, self._sampwidth, self._framerate, \
679 self._nframes, self._comptype, self._compname
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000680
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000681 def setmark(self, id, pos, name):
682 if id <= 0:
683 raise Error, 'marker ID must be > 0'
684 if pos < 0:
685 raise Error, 'marker position must be >= 0'
686 if type(name) != type(''):
687 raise Error, 'marker name must be a string'
688 for i in range(len(self._markers)):
689 if id == self._markers[i][0]:
690 self._markers[i] = id, pos, name
691 return
692 self._markers.append((id, pos, name))
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000693
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000694 def getmark(self, id):
695 for marker in self._markers:
696 if id == marker[0]:
697 return marker
698 raise Error, 'marker ' + `id` + ' does not exist'
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000699
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000700 def getmarkers(self):
701 if len(self._markers) == 0:
702 return None
703 return self._markers
704
705 def tell(self):
706 return self._nframeswritten
Sjoerd Mullender43bf0bc1993-12-13 11:42:39 +0000707
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000708 def writeframesraw(self, data):
709 self._ensure_header_written(len(data))
710 nframes = len(data) / (self._sampwidth * self._nchannels)
711 if self._convert:
712 data = self._convert(data)
713 self._file.write(data)
714 self._nframeswritten = self._nframeswritten + nframes
715 self._datawritten = self._datawritten + len(data)
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000716
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000717 def writeframes(self, data):
718 self.writeframesraw(data)
719 if self._nframeswritten != self._nframes or \
720 self._datalength != self._datawritten:
721 self._patchheader()
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000722
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000723 def close(self):
724 self._ensure_header_written(0)
725 if self._datawritten & 1:
726 # quick pad to even size
727 self._file.write(chr(0))
728 self._datawritten = self._datawritten + 1
729 self._writemarkers()
730 if self._nframeswritten != self._nframes or \
731 self._datalength != self._datawritten or \
732 self._marklength:
733 self._patchheader()
734 if self._comp:
735 self._comp.CloseCompressor()
736 self._comp = None
737 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
746 dum = self._comp.SetParam(cl.FRAME_BUFFER_SIZE, len(data))
747 dum = self._comp.SetParam(cl.COMPRESSED_BUFFER_SIZE, len(data))
748 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':
784 import audioop
785 self._convert = self._lin2adpcm
786 return
787 try:
788 import cl
789 except ImportError:
790 if self._comptype == 'ULAW':
791 try:
792 import audioop
793 self._convert = self._lin2ulaw
794 return
795 except ImportError:
796 pass
797 raise Error, 'cannot write compressed AIFF-C files'
798 if self._comptype == 'ULAW':
799 scheme = cl.G711_ULAW
800 elif self._comptype == 'ALAW':
801 scheme = cl.G711_ALAW
802 else:
803 raise Error, 'unsupported compression type'
804 self._comp = cl.OpenCompressor(scheme)
805 params = [cl.ORIGINAL_FORMAT, 0,
806 cl.BITS_PER_COMPONENT, self._sampwidth * 8,
807 cl.FRAME_RATE, self._framerate,
808 cl.FRAME_BUFFER_SIZE, 100,
809 cl.COMPRESSED_BUFFER_SIZE, 100]
810 if self._nchannels == 1:
811 params[1] = cl.MONO
812 elif self._nchannels == 2:
813 params[1] = cl.STEREO_INTERLEAVED
814 else:
815 raise Error, 'cannot compress more than 2 channels'
816 self._comp.SetParams(params)
817 # the compressor produces a header which we ignore
818 dummy = self._comp.Compress(0, '')
819 self._convert = self._comp_data
Sjoerd Mullender43bf0bc1993-12-13 11:42:39 +0000820
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000821 def _write_header(self, initlength):
822 if self._aifc and self._comptype != 'NONE':
823 self._init_compression()
824 self._file.write('FORM')
825 if not self._nframes:
826 self._nframes = initlength / (self._nchannels * self._sampwidth)
827 self._datalength = self._nframes * self._nchannels * self._sampwidth
828 if self._datalength & 1:
829 self._datalength = self._datalength + 1
830 if self._aifc:
831 if self._comptype in ('ULAW', 'ALAW'):
832 self._datalength = self._datalength / 2
833 if self._datalength & 1:
834 self._datalength = self._datalength + 1
835 elif self._comptype == 'G722':
836 self._datalength = (self._datalength + 3) / 4
837 if self._datalength & 1:
838 self._datalength = self._datalength + 1
839 self._form_length_pos = self._file.tell()
840 commlength = self._write_form_length(self._datalength)
841 if self._aifc:
842 self._file.write('AIFC')
843 self._file.write('FVER')
844 _write_long(self._file, 4)
845 _write_long(self._file, self._version)
846 else:
847 self._file.write('AIFF')
848 self._file.write('COMM')
849 _write_long(self._file, commlength)
850 _write_short(self._file, self._nchannels)
851 self._nframes_pos = self._file.tell()
852 _write_long(self._file, self._nframes)
853 _write_short(self._file, self._sampwidth * 8)
854 _write_float(self._file, self._framerate)
855 if self._aifc:
856 self._file.write(self._comptype)
857 _write_string(self._file, self._compname)
858 self._file.write('SSND')
859 self._ssnd_length_pos = self._file.tell()
860 _write_long(self._file, self._datalength + 8)
861 _write_long(self._file, 0)
862 _write_long(self._file, 0)
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000863
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000864 def _write_form_length(self, datalength):
865 if self._aifc:
866 commlength = 18 + 5 + len(self._compname)
867 if commlength & 1:
868 commlength = commlength + 1
869 verslength = 12
870 else:
871 commlength = 18
872 verslength = 0
873 _write_long(self._file, 4 + verslength + self._marklength + \
874 8 + commlength + 16 + datalength)
875 return commlength
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000876
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000877 def _patchheader(self):
878 curpos = self._file.tell()
879 if self._datawritten & 1:
880 datalength = self._datawritten + 1
881 self._file.write(chr(0))
882 else:
883 datalength = self._datawritten
884 if datalength == self._datalength and \
885 self._nframes == self._nframeswritten and \
886 self._marklength == 0:
887 self._file.seek(curpos, 0)
888 return
889 self._file.seek(self._form_length_pos, 0)
890 dummy = self._write_form_length(datalength)
891 self._file.seek(self._nframes_pos, 0)
892 _write_long(self._file, self._nframeswritten)
893 self._file.seek(self._ssnd_length_pos, 0)
894 _write_long(self._file, datalength + 8)
895 self._file.seek(curpos, 0)
896 self._nframes = self._nframeswritten
897 self._datalength = datalength
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000898
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000899 def _writemarkers(self):
900 if len(self._markers) == 0:
901 return
902 self._file.write('MARK')
903 length = 2
904 for marker in self._markers:
905 id, pos, name = marker
906 length = length + len(name) + 1 + 6
907 if len(name) & 1 == 0:
908 length = length + 1
909 _write_long(self._file, length)
910 self._marklength = length + 8
911 _write_short(self._file, len(self._markers))
912 for marker in self._markers:
913 id, pos, name = marker
914 _write_short(self._file, id)
915 _write_long(self._file, pos)
916 _write_string(self._file, name)
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000917
Fred Drake43161351999-06-22 21:23:23 +0000918def open(f, mode=None):
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000919 if mode is None:
920 if hasattr(f, 'mode'):
921 mode = f.mode
922 else:
923 mode = 'rb'
924 if mode in ('r', 'rb'):
925 return Aifc_read(f)
926 elif mode in ('w', 'wb'):
927 return Aifc_write(f)
928 else:
929 raise Error, "mode must be 'r', 'rb', 'w', or 'wb'"
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000930
Guido van Rossum7bc817d1993-12-17 15:25:27 +0000931openfp = open # B/W compatibility
Guido van Rossum36bb1811996-12-31 05:57:34 +0000932
933if __name__ == '__main__':
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000934 import sys
935 if not sys.argv[1:]:
936 sys.argv.append('/usr/demos/data/audio/bach.aiff')
937 fn = sys.argv[1]
938 f = open(fn, 'r')
939 print "Reading", fn
940 print "nchannels =", f.getnchannels()
941 print "nframes =", f.getnframes()
942 print "sampwidth =", f.getsampwidth()
943 print "framerate =", f.getframerate()
944 print "comptype =", f.getcomptype()
945 print "compname =", f.getcompname()
946 if sys.argv[2:]:
947 gn = sys.argv[2]
948 print "Writing", gn
949 g = open(gn, 'w')
950 g.setparams(f.getparams())
951 while 1:
952 data = f.readframes(1024)
953 if not data:
954 break
955 g.writeframes(data)
956 g.close()
957 f.close()
958 print "Done."