blob: 0821cffe6e35200fc3b59b0b9d3064b153f62be6 [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 Rossum4acc25b2000-02-02 15:10:15 +0000145_AIFC_version = 0xA2805140 # 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 import math
182 expon = _read_short(f) # 2 bytes
183 sign = 1
184 if expon < 0:
185 sign = -1
186 expon = expon + 0x8000
187 himant = _read_ulong(f) # 4 bytes
188 lomant = _read_ulong(f) # 4 bytes
189 if expon == himant == lomant == 0:
190 f = 0.0
191 elif expon == 0x7FFF:
192 f = _HUGE_VAL
193 else:
194 expon = expon - 16383
195 f = (himant * 0x100000000L + lomant) * pow(2.0, expon - 63)
196 return sign * f
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000197
198def _write_short(f, x):
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000199 f.write(struct.pack('>h', x))
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000200
201def _write_long(f, x):
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000202 f.write(struct.pack('>L', x))
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000203
204def _write_string(f, s):
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000205 f.write(chr(len(s)))
206 f.write(s)
207 if len(s) & 1 == 0:
208 f.write(chr(0))
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000209
210def _write_float(f, x):
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000211 import math
212 if x < 0:
213 sign = 0x8000
214 x = x * -1
215 else:
216 sign = 0
217 if x == 0:
218 expon = 0
219 himant = 0
220 lomant = 0
221 else:
222 fmant, expon = math.frexp(x)
223 if expon > 16384 or fmant >= 1: # Infinity or NaN
224 expon = sign|0x7FFF
225 himant = 0
226 lomant = 0
227 else: # Finite
228 expon = expon + 16382
229 if expon < 0: # denormalized
230 fmant = math.ldexp(fmant, expon)
231 expon = 0
232 expon = expon | sign
233 fmant = math.ldexp(fmant, 32)
234 fsmant = math.floor(fmant)
235 himant = long(fsmant)
236 fmant = math.ldexp(fmant - fsmant, 32)
237 fsmant = math.floor(fmant)
238 lomant = long(fsmant)
239 _write_short(f, expon)
240 _write_long(f, himant)
241 _write_long(f, lomant)
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000242
Guido van Rossum8ea7bb81999-06-09 13:32:28 +0000243from chunk import Chunk
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000244
Guido van Rossumd3166071993-05-24 14:16:22 +0000245class Aifc_read:
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000246 # Variables used in this class:
247 #
248 # These variables are available to the user though appropriate
249 # methods of this class:
250 # _file -- the open file with methods read(), close(), and seek()
251 # set through the __init__() method
252 # _nchannels -- the number of audio channels
253 # available through the getnchannels() method
254 # _nframes -- the number of audio frames
255 # available through the getnframes() method
256 # _sampwidth -- the number of bytes per audio sample
257 # available through the getsampwidth() method
258 # _framerate -- the sampling frequency
259 # available through the getframerate() method
260 # _comptype -- the AIFF-C compression type ('NONE' if AIFF)
261 # available through the getcomptype() method
262 # _compname -- the human-readable AIFF-C compression type
263 # available through the getcomptype() method
264 # _markers -- the marks in the audio file
265 # available through the getmarkers() and getmark()
266 # methods
267 # _soundpos -- the position in the audio stream
268 # available through the tell() method, set through the
269 # setpos() method
270 #
271 # These variables are used internally only:
272 # _version -- the AIFF-C version number
273 # _decomp -- the decompressor from builtin module cl
274 # _comm_chunk_read -- 1 iff the COMM chunk has been read
275 # _aifc -- 1 iff reading an AIFF-C file
276 # _ssnd_seek_needed -- 1 iff positioned correctly in audio
277 # file for readframes()
278 # _ssnd_chunk -- instantiation of a chunk class for the SSND chunk
279 # _framesize -- size of one frame in the file
Sjoerd Mullender2a451411993-12-20 09:36:01 +0000280
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000281 def initfp(self, file):
282 self._version = 0
283 self._decomp = None
284 self._convert = None
285 self._markers = []
286 self._soundpos = 0
287 self._file = Chunk(file)
288 if self._file.getname() != 'FORM':
289 raise Error, 'file does not start with FORM id'
290 formdata = self._file.read(4)
291 if formdata == 'AIFF':
292 self._aifc = 0
293 elif formdata == 'AIFC':
294 self._aifc = 1
295 else:
296 raise Error, 'not an AIFF or AIFF-C file'
297 self._comm_chunk_read = 0
298 while 1:
299 self._ssnd_seek_needed = 1
300 try:
301 chunk = Chunk(self._file)
302 except EOFError:
303 break
304 chunkname = chunk.getname()
305 if chunkname == 'COMM':
306 self._read_comm_chunk(chunk)
307 self._comm_chunk_read = 1
308 elif chunkname == 'SSND':
309 self._ssnd_chunk = chunk
310 dummy = chunk.read(8)
311 self._ssnd_seek_needed = 0
312 elif chunkname == 'FVER':
313 self._version = _read_long(chunk)
314 elif chunkname == 'MARK':
315 self._readmark(chunk)
316 elif chunkname in _skiplist:
317 pass
318 else:
319 raise Error, 'unrecognized chunk type '+chunk.chunkname
320 chunk.skip()
321 if not self._comm_chunk_read or not self._ssnd_chunk:
322 raise Error, 'COMM chunk and/or SSND chunk missing'
323 if self._aifc and self._decomp:
324 import cl
325 params = [cl.ORIGINAL_FORMAT, 0,
326 cl.BITS_PER_COMPONENT, self._sampwidth * 8,
327 cl.FRAME_RATE, self._framerate]
328 if self._nchannels == 1:
329 params[1] = cl.MONO
330 elif self._nchannels == 2:
331 params[1] = cl.STEREO_INTERLEAVED
332 else:
333 raise Error, 'cannot compress more than 2 channels'
334 self._decomp.SetParams(params)
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000335
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000336 def __init__(self, f):
337 if type(f) == type(''):
338 f = __builtin__.open(f, 'rb')
339 # else, assume it is an open file object already
340 self.initfp(f)
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000341
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000342 #
343 # User visible methods.
344 #
345 def getfp(self):
346 return self._file
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000347
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000348 def rewind(self):
349 self._ssnd_seek_needed = 1
350 self._soundpos = 0
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000351
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000352 def close(self):
353 if self._decomp:
354 self._decomp.CloseDecompressor()
355 self._decomp = None
356 self._file = None
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000357
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000358 def tell(self):
359 return self._soundpos
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000360
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000361 def getnchannels(self):
362 return self._nchannels
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000363
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000364 def getnframes(self):
365 return self._nframes
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000366
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000367 def getsampwidth(self):
368 return self._sampwidth
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000369
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000370 def getframerate(self):
371 return self._framerate
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000372
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000373 def getcomptype(self):
374 return self._comptype
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000375
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000376 def getcompname(self):
377 return self._compname
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000378
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000379## def getversion(self):
380## return self._version
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000381
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000382 def getparams(self):
383 return self.getnchannels(), self.getsampwidth(), \
384 self.getframerate(), self.getnframes(), \
385 self.getcomptype(), self.getcompname()
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000386
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000387 def getmarkers(self):
388 if len(self._markers) == 0:
389 return None
390 return self._markers
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000391
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000392 def getmark(self, id):
393 for marker in self._markers:
394 if id == marker[0]:
395 return marker
396 raise Error, 'marker ' + `id` + ' does not exist'
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000397
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000398 def setpos(self, pos):
399 if pos < 0 or pos > self._nframes:
400 raise Error, 'position not in range'
401 self._soundpos = pos
402 self._ssnd_seek_needed = 1
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000403
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000404 def readframes(self, nframes):
405 if self._ssnd_seek_needed:
406 self._ssnd_chunk.seek(0)
407 dummy = self._ssnd_chunk.read(8)
408 pos = self._soundpos * self._framesize
409 if pos:
Guido van Rossum2663c132000-03-07 15:19:31 +0000410 self._ssnd_chunk.seek(pos + 8)
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000411 self._ssnd_seek_needed = 0
412 if nframes == 0:
413 return ''
414 data = self._ssnd_chunk.read(nframes * self._framesize)
415 if self._convert and data:
416 data = self._convert(data)
417 self._soundpos = self._soundpos + len(data) / (self._nchannels * self._sampwidth)
418 return data
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000419
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000420 #
421 # Internal methods.
422 #
Sjoerd Mullender2a451411993-12-20 09:36:01 +0000423
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000424 def _decomp_data(self, data):
425 import cl
426 dummy = self._decomp.SetParam(cl.FRAME_BUFFER_SIZE,
427 len(data) * 2)
428 return self._decomp.Decompress(len(data) / self._nchannels,
429 data)
Sjoerd Mullender43bf0bc1993-12-13 11:42:39 +0000430
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000431 def _ulaw2lin(self, data):
432 import audioop
433 return audioop.ulaw2lin(data, 2)
Sjoerd Mullender43bf0bc1993-12-13 11:42:39 +0000434
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000435 def _adpcm2lin(self, data):
436 import audioop
437 if not hasattr(self, '_adpcmstate'):
438 # first time
439 self._adpcmstate = None
440 data, self._adpcmstate = audioop.adpcm2lin(data, 2,
441 self._adpcmstate)
442 return data
Sjoerd Mullender1f057541994-09-06 16:17:51 +0000443
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000444 def _read_comm_chunk(self, chunk):
445 self._nchannels = _read_short(chunk)
446 self._nframes = _read_long(chunk)
447 self._sampwidth = (_read_short(chunk) + 7) / 8
448 self._framerate = int(_read_float(chunk))
449 self._framesize = self._nchannels * self._sampwidth
450 if self._aifc:
451 #DEBUG: SGI's soundeditor produces a bad size :-(
452 kludge = 0
453 if chunk.chunksize == 18:
454 kludge = 1
455 print 'Warning: bad COMM chunk size'
456 chunk.chunksize = 23
457 #DEBUG end
458 self._comptype = chunk.read(4)
459 #DEBUG start
460 if kludge:
461 length = ord(chunk.file.read(1))
462 if length & 1 == 0:
463 length = length + 1
464 chunk.chunksize = chunk.chunksize + length
465 chunk.file.seek(-1, 1)
466 #DEBUG end
467 self._compname = _read_string(chunk)
468 if self._comptype != 'NONE':
469 if self._comptype == 'G722':
470 try:
471 import audioop
472 except ImportError:
473 pass
474 else:
475 self._convert = self._adpcm2lin
476 self._framesize = self._framesize / 4
477 return
478 # for ULAW and ALAW try Compression Library
479 try:
480 import cl
481 except ImportError:
482 if self._comptype == 'ULAW':
483 try:
484 import audioop
485 self._convert = self._ulaw2lin
486 self._framesize = self._framesize / 2
487 return
488 except ImportError:
489 pass
490 raise Error, 'cannot read compressed AIFF-C files'
491 if self._comptype == 'ULAW':
492 scheme = cl.G711_ULAW
493 self._framesize = self._framesize / 2
494 elif self._comptype == 'ALAW':
495 scheme = cl.G711_ALAW
496 self._framesize = self._framesize / 2
497 else:
498 raise Error, 'unsupported compression type'
499 self._decomp = cl.OpenDecompressor(scheme)
500 self._convert = self._decomp_data
501 else:
502 self._comptype = 'NONE'
503 self._compname = 'not compressed'
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000504
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000505 def _readmark(self, chunk):
506 nmarkers = _read_short(chunk)
507 # Some files appear to contain invalid counts.
508 # Cope with this by testing for EOF.
509 try:
510 for i in range(nmarkers):
511 id = _read_short(chunk)
512 pos = _read_long(chunk)
513 name = _read_string(chunk)
514 if pos or name:
515 # some files appear to have
516 # dummy markers consisting of
517 # a position 0 and name ''
518 self._markers.append((id, pos, name))
519 except EOFError:
520 print 'Warning: MARK chunk contains only',
521 print len(self._markers),
522 if len(self._markers) == 1: print 'marker',
523 else: print 'markers',
524 print 'instead of', nmarkers
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000525
Guido van Rossumd3166071993-05-24 14:16:22 +0000526class Aifc_write:
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000527 # Variables used in this class:
528 #
529 # These variables are user settable through appropriate methods
530 # of this class:
531 # _file -- the open file with methods write(), close(), tell(), seek()
532 # set through the __init__() method
533 # _comptype -- the AIFF-C compression type ('NONE' in AIFF)
534 # set through the setcomptype() or setparams() method
535 # _compname -- the human-readable AIFF-C compression type
536 # set through the setcomptype() or setparams() method
537 # _nchannels -- the number of audio channels
538 # set through the setnchannels() or setparams() method
539 # _sampwidth -- the number of bytes per audio sample
540 # set through the setsampwidth() or setparams() method
541 # _framerate -- the sampling frequency
542 # set through the setframerate() or setparams() method
543 # _nframes -- the number of audio frames written to the header
544 # set through the setnframes() or setparams() method
545 # _aifc -- whether we're writing an AIFF-C file or an AIFF file
546 # set through the aifc() method, reset through the
547 # aiff() method
548 #
549 # These variables are used internally only:
550 # _version -- the AIFF-C version number
551 # _comp -- the compressor from builtin module cl
552 # _nframeswritten -- the number of audio frames actually written
553 # _datalength -- the size of the audio samples written to the header
554 # _datawritten -- the size of the audio samples actually written
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000555
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000556 def __init__(self, f):
557 if type(f) == type(''):
558 filename = f
559 f = __builtin__.open(f, 'wb')
560 else:
561 # else, assume it is an open file object already
562 filename = '???'
563 self.initfp(f)
564 if filename[-5:] == '.aiff':
565 self._aifc = 0
566 else:
567 self._aifc = 1
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000568
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000569 def initfp(self, file):
570 self._file = file
571 self._version = _AIFC_version
572 self._comptype = 'NONE'
573 self._compname = 'not compressed'
574 self._comp = None
575 self._convert = None
576 self._nchannels = 0
577 self._sampwidth = 0
578 self._framerate = 0
579 self._nframes = 0
580 self._nframeswritten = 0
581 self._datawritten = 0
582 self._datalength = 0
583 self._markers = []
584 self._marklength = 0
585 self._aifc = 1 # AIFF-C is default
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000586
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000587 def __del__(self):
588 if self._file:
589 self.close()
Sjoerd Mullender43bf0bc1993-12-13 11:42:39 +0000590
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000591 #
592 # User visible methods.
593 #
594 def aiff(self):
595 if self._nframeswritten:
596 raise Error, 'cannot change parameters after starting to write'
597 self._aifc = 0
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000598
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000599 def aifc(self):
600 if self._nframeswritten:
601 raise Error, 'cannot change parameters after starting to write'
602 self._aifc = 1
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000603
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000604 def setnchannels(self, nchannels):
605 if self._nframeswritten:
606 raise Error, 'cannot change parameters after starting to write'
607 if nchannels < 1:
608 raise Error, 'bad # of channels'
609 self._nchannels = nchannels
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000610
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000611 def getnchannels(self):
612 if not self._nchannels:
613 raise Error, 'number of channels not set'
614 return self._nchannels
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000615
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000616 def setsampwidth(self, sampwidth):
617 if self._nframeswritten:
618 raise Error, 'cannot change parameters after starting to write'
619 if sampwidth < 1 or sampwidth > 4:
620 raise Error, 'bad sample width'
621 self._sampwidth = sampwidth
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000622
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000623 def getsampwidth(self):
624 if not self._sampwidth:
625 raise Error, 'sample width not set'
626 return self._sampwidth
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000627
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000628 def setframerate(self, framerate):
629 if self._nframeswritten:
630 raise Error, 'cannot change parameters after starting to write'
631 if framerate <= 0:
632 raise Error, 'bad frame rate'
633 self._framerate = framerate
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000634
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000635 def getframerate(self):
636 if not self._framerate:
637 raise Error, 'frame rate not set'
638 return self._framerate
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000639
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000640 def setnframes(self, nframes):
641 if self._nframeswritten:
642 raise Error, 'cannot change parameters after starting to write'
643 self._nframes = nframes
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000644
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000645 def getnframes(self):
646 return self._nframeswritten
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000647
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000648 def setcomptype(self, comptype, compname):
649 if self._nframeswritten:
650 raise Error, 'cannot change parameters after starting to write'
651 if comptype not in ('NONE', 'ULAW', 'ALAW', 'G722'):
652 raise Error, 'unsupported compression type'
653 self._comptype = comptype
654 self._compname = compname
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000655
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000656 def getcomptype(self):
657 return self._comptype
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000658
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000659 def getcompname(self):
660 return self._compname
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000661
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000662## def setversion(self, version):
663## if self._nframeswritten:
664## raise Error, 'cannot change parameters after starting to write'
665## self._version = version
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000666
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000667 def setparams(self, (nchannels, sampwidth, framerate, nframes, comptype, compname)):
668 if self._nframeswritten:
669 raise Error, 'cannot change parameters after starting to write'
670 if comptype not in ('NONE', 'ULAW', 'ALAW', 'G722'):
671 raise Error, 'unsupported compression type'
672 self.setnchannels(nchannels)
673 self.setsampwidth(sampwidth)
674 self.setframerate(framerate)
675 self.setnframes(nframes)
676 self.setcomptype(comptype, compname)
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000677
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000678 def getparams(self):
679 if not self._nchannels or not self._sampwidth or not self._framerate:
680 raise Error, 'not all parameters set'
681 return self._nchannels, self._sampwidth, self._framerate, \
682 self._nframes, self._comptype, self._compname
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000683
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000684 def setmark(self, id, pos, name):
685 if id <= 0:
686 raise Error, 'marker ID must be > 0'
687 if pos < 0:
688 raise Error, 'marker position must be >= 0'
689 if type(name) != type(''):
690 raise Error, 'marker name must be a string'
691 for i in range(len(self._markers)):
692 if id == self._markers[i][0]:
693 self._markers[i] = id, pos, name
694 return
695 self._markers.append((id, pos, name))
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000696
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000697 def getmark(self, id):
698 for marker in self._markers:
699 if id == marker[0]:
700 return marker
701 raise Error, 'marker ' + `id` + ' does not exist'
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000702
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000703 def getmarkers(self):
704 if len(self._markers) == 0:
705 return None
706 return self._markers
Tim Peters146965a2001-01-14 18:09:23 +0000707
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000708 def tell(self):
709 return self._nframeswritten
Sjoerd Mullender43bf0bc1993-12-13 11:42:39 +0000710
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000711 def writeframesraw(self, data):
712 self._ensure_header_written(len(data))
713 nframes = len(data) / (self._sampwidth * self._nchannels)
714 if self._convert:
715 data = self._convert(data)
716 self._file.write(data)
717 self._nframeswritten = self._nframeswritten + nframes
718 self._datawritten = self._datawritten + len(data)
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000719
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000720 def writeframes(self, data):
721 self.writeframesraw(data)
722 if self._nframeswritten != self._nframes or \
723 self._datalength != self._datawritten:
724 self._patchheader()
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000725
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000726 def close(self):
727 self._ensure_header_written(0)
728 if self._datawritten & 1:
729 # quick pad to even size
730 self._file.write(chr(0))
731 self._datawritten = self._datawritten + 1
732 self._writemarkers()
733 if self._nframeswritten != self._nframes or \
734 self._datalength != self._datawritten or \
735 self._marklength:
736 self._patchheader()
737 if self._comp:
738 self._comp.CloseCompressor()
739 self._comp = None
740 self._file.flush()
741 self._file = None
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000742
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000743 #
744 # Internal methods.
745 #
Sjoerd Mullender2a451411993-12-20 09:36:01 +0000746
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000747 def _comp_data(self, data):
748 import cl
749 dum = self._comp.SetParam(cl.FRAME_BUFFER_SIZE, len(data))
750 dum = self._comp.SetParam(cl.COMPRESSED_BUFFER_SIZE, len(data))
751 return self._comp.Compress(self._nframes, data)
Sjoerd Mullender43bf0bc1993-12-13 11:42:39 +0000752
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000753 def _lin2ulaw(self, data):
754 import audioop
755 return audioop.lin2ulaw(data, 2)
Sjoerd Mullender43bf0bc1993-12-13 11:42:39 +0000756
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000757 def _lin2adpcm(self, data):
758 import audioop
759 if not hasattr(self, '_adpcmstate'):
760 self._adpcmstate = None
761 data, self._adpcmstate = audioop.lin2adpcm(data, 2,
762 self._adpcmstate)
763 return data
Sjoerd Mullender1f057541994-09-06 16:17:51 +0000764
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000765 def _ensure_header_written(self, datasize):
766 if not self._nframeswritten:
767 if self._comptype in ('ULAW', 'ALAW'):
768 if not self._sampwidth:
769 self._sampwidth = 2
770 if self._sampwidth != 2:
771 raise Error, 'sample width must be 2 when compressing with ULAW or ALAW'
772 if self._comptype == 'G722':
773 if not self._sampwidth:
774 self._sampwidth = 2
775 if self._sampwidth != 2:
776 raise Error, 'sample width must be 2 when compressing with G7.22 (ADPCM)'
777 if not self._nchannels:
778 raise Error, '# channels not specified'
779 if not self._sampwidth:
780 raise Error, 'sample width not specified'
781 if not self._framerate:
782 raise Error, 'sampling rate not specified'
783 self._write_header(datasize)
Guido van Rossum52fc1f61993-06-17 12:38:10 +0000784
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000785 def _init_compression(self):
786 if self._comptype == 'G722':
787 import audioop
788 self._convert = self._lin2adpcm
789 return
790 try:
791 import cl
792 except ImportError:
793 if self._comptype == 'ULAW':
794 try:
795 import audioop
796 self._convert = self._lin2ulaw
797 return
798 except ImportError:
799 pass
800 raise Error, 'cannot write compressed AIFF-C files'
801 if self._comptype == 'ULAW':
802 scheme = cl.G711_ULAW
803 elif self._comptype == 'ALAW':
804 scheme = cl.G711_ALAW
805 else:
806 raise Error, 'unsupported compression type'
807 self._comp = cl.OpenCompressor(scheme)
808 params = [cl.ORIGINAL_FORMAT, 0,
809 cl.BITS_PER_COMPONENT, self._sampwidth * 8,
810 cl.FRAME_RATE, self._framerate,
811 cl.FRAME_BUFFER_SIZE, 100,
812 cl.COMPRESSED_BUFFER_SIZE, 100]
813 if self._nchannels == 1:
814 params[1] = cl.MONO
815 elif self._nchannels == 2:
816 params[1] = cl.STEREO_INTERLEAVED
817 else:
818 raise Error, 'cannot compress more than 2 channels'
819 self._comp.SetParams(params)
820 # the compressor produces a header which we ignore
821 dummy = self._comp.Compress(0, '')
822 self._convert = self._comp_data
Sjoerd Mullender43bf0bc1993-12-13 11:42:39 +0000823
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000824 def _write_header(self, initlength):
825 if self._aifc and self._comptype != 'NONE':
826 self._init_compression()
827 self._file.write('FORM')
828 if not self._nframes:
829 self._nframes = initlength / (self._nchannels * self._sampwidth)
830 self._datalength = self._nframes * self._nchannels * self._sampwidth
831 if self._datalength & 1:
832 self._datalength = self._datalength + 1
833 if self._aifc:
834 if self._comptype in ('ULAW', 'ALAW'):
835 self._datalength = self._datalength / 2
836 if self._datalength & 1:
837 self._datalength = self._datalength + 1
838 elif self._comptype == 'G722':
839 self._datalength = (self._datalength + 3) / 4
840 if self._datalength & 1:
841 self._datalength = self._datalength + 1
842 self._form_length_pos = self._file.tell()
843 commlength = self._write_form_length(self._datalength)
844 if self._aifc:
845 self._file.write('AIFC')
846 self._file.write('FVER')
847 _write_long(self._file, 4)
848 _write_long(self._file, self._version)
849 else:
850 self._file.write('AIFF')
851 self._file.write('COMM')
852 _write_long(self._file, commlength)
853 _write_short(self._file, self._nchannels)
854 self._nframes_pos = self._file.tell()
855 _write_long(self._file, self._nframes)
856 _write_short(self._file, self._sampwidth * 8)
857 _write_float(self._file, self._framerate)
858 if self._aifc:
859 self._file.write(self._comptype)
860 _write_string(self._file, self._compname)
861 self._file.write('SSND')
862 self._ssnd_length_pos = self._file.tell()
863 _write_long(self._file, self._datalength + 8)
864 _write_long(self._file, 0)
865 _write_long(self._file, 0)
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000866
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000867 def _write_form_length(self, datalength):
868 if self._aifc:
869 commlength = 18 + 5 + len(self._compname)
870 if commlength & 1:
871 commlength = commlength + 1
872 verslength = 12
873 else:
874 commlength = 18
875 verslength = 0
876 _write_long(self._file, 4 + verslength + self._marklength + \
877 8 + commlength + 16 + datalength)
878 return commlength
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000879
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000880 def _patchheader(self):
881 curpos = self._file.tell()
882 if self._datawritten & 1:
883 datalength = self._datawritten + 1
884 self._file.write(chr(0))
885 else:
886 datalength = self._datawritten
887 if datalength == self._datalength and \
888 self._nframes == self._nframeswritten and \
889 self._marklength == 0:
890 self._file.seek(curpos, 0)
891 return
892 self._file.seek(self._form_length_pos, 0)
893 dummy = self._write_form_length(datalength)
894 self._file.seek(self._nframes_pos, 0)
895 _write_long(self._file, self._nframeswritten)
896 self._file.seek(self._ssnd_length_pos, 0)
897 _write_long(self._file, datalength + 8)
898 self._file.seek(curpos, 0)
899 self._nframes = self._nframeswritten
900 self._datalength = datalength
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000901
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000902 def _writemarkers(self):
903 if len(self._markers) == 0:
904 return
905 self._file.write('MARK')
906 length = 2
907 for marker in self._markers:
908 id, pos, name = marker
909 length = length + len(name) + 1 + 6
910 if len(name) & 1 == 0:
911 length = length + 1
912 _write_long(self._file, length)
913 self._marklength = length + 8
914 _write_short(self._file, len(self._markers))
915 for marker in self._markers:
916 id, pos, name = marker
917 _write_short(self._file, id)
918 _write_long(self._file, pos)
919 _write_string(self._file, name)
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000920
Fred Drake43161351999-06-22 21:23:23 +0000921def open(f, mode=None):
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000922 if mode is None:
923 if hasattr(f, 'mode'):
924 mode = f.mode
925 else:
926 mode = 'rb'
927 if mode in ('r', 'rb'):
928 return Aifc_read(f)
929 elif mode in ('w', 'wb'):
930 return Aifc_write(f)
931 else:
932 raise Error, "mode must be 'r', 'rb', 'w', or 'wb'"
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000933
Guido van Rossum7bc817d1993-12-17 15:25:27 +0000934openfp = open # B/W compatibility
Guido van Rossum36bb1811996-12-31 05:57:34 +0000935
936if __name__ == '__main__':
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000937 import sys
938 if not sys.argv[1:]:
939 sys.argv.append('/usr/demos/data/audio/bach.aiff')
940 fn = sys.argv[1]
941 f = open(fn, 'r')
942 print "Reading", fn
943 print "nchannels =", f.getnchannels()
944 print "nframes =", f.getnframes()
945 print "sampwidth =", f.getsampwidth()
946 print "framerate =", f.getframerate()
947 print "comptype =", f.getcomptype()
948 print "compname =", f.getcompname()
949 if sys.argv[2:]:
950 gn = sys.argv[2]
951 print "Writing", gn
952 g = open(gn, 'w')
953 g.setparams(f.getparams())
954 while 1:
955 data = f.readframes(1024)
956 if not data:
957 break
958 g.writeframes(data)
959 g.close()
960 f.close()
961 print "Done."