blob: a5f86be455b5ffa302384bd1efceae3ef7de67dc [file] [log] [blame]
Guido van Rossum4acc25b2000-02-02 15:10:15 +00001"""Stuff to parse AIFF-C and AIFF files.
2
3Unless explicitly stated otherwise, the description below is true
4both for AIFF-C files and AIFF files.
5
6An AIFF-C file has the following structure.
7
8 +-----------------+
9 | FORM |
10 +-----------------+
11 | <size> |
12 +----+------------+
13 | | AIFC |
14 | +------------+
15 | | <chunks> |
16 | | . |
17 | | . |
18 | | . |
19 +----+------------+
20
21An AIFF file has the string "AIFF" instead of "AIFC".
22
23A chunk consists of an identifier (4 bytes) followed by a size (4 bytes,
24big endian order), followed by the data. The size field does not include
25the size of the 8 byte header.
26
27The following chunk types are recognized.
28
29 FVER
30 <version number of AIFF-C defining document> (AIFF-C only).
31 MARK
32 <# of markers> (2 bytes)
33 list of markers:
34 <marker ID> (2 bytes, must be > 0)
35 <position> (4 bytes)
36 <marker name> ("pstring")
37 COMM
38 <# of channels> (2 bytes)
39 <# of sound frames> (4 bytes)
40 <size of the samples> (2 bytes)
41 <sampling frequency> (10 bytes, IEEE 80-bit extended
42 floating point)
43 in AIFF-C files only:
44 <compression type> (4 bytes)
45 <human-readable version of compression type> ("pstring")
46 SSND
47 <offset> (4 bytes, not used by this program)
48 <blocksize> (4 bytes, not used by this program)
49 <sound data>
50
51A pstring consists of 1 byte length, a string of characters, and 0 or 1
52byte pad to make the total length even.
53
54Usage.
55
56Reading AIFF files:
57 f = aifc.open(file, 'r')
58where file is either the name of a file or an open file pointer.
59The open file pointer must have methods read(), seek(), and close().
60In some types of audio files, if the setpos() method is not used,
61the seek() method is not necessary.
62
63This returns an instance of a class with the following public methods:
64 getnchannels() -- returns number of audio channels (1 for
65 mono, 2 for stereo)
66 getsampwidth() -- returns sample width in bytes
67 getframerate() -- returns sampling frequency
68 getnframes() -- returns number of audio frames
69 getcomptype() -- returns compression type ('NONE' for AIFF files)
70 getcompname() -- returns human-readable version of
71 compression type ('not compressed' for AIFF files)
72 getparams() -- returns a tuple consisting of all of the
73 above in the above order
74 getmarkers() -- get the list of marks in the audio file or None
75 if there are no marks
76 getmark(id) -- get mark with the specified id (raises an error
77 if the mark does not exist)
78 readframes(n) -- returns at most n frames of audio
79 rewind() -- rewind to the beginning of the audio stream
80 setpos(pos) -- seek to the specified position
81 tell() -- return the current position
82 close() -- close the instance (make it unusable)
83The position returned by tell(), the position given to setpos() and
84the position of marks are all compatible and have nothing to do with
Thomas Wouters7e474022000-07-16 12:04:32 +000085the actual position in the file.
Guido van Rossum4acc25b2000-02-02 15:10:15 +000086The close() method is called automatically when the class instance
87is destroyed.
88
89Writing AIFF files:
90 f = aifc.open(file, 'w')
91where file is either the name of a file or an open file pointer.
92The open file pointer must have methods write(), tell(), seek(), and
93close().
94
95This returns an instance of a class with the following public methods:
96 aiff() -- create an AIFF file (AIFF-C default)
97 aifc() -- create an AIFF-C file
98 setnchannels(n) -- set the number of channels
99 setsampwidth(n) -- set the sample width
100 setframerate(n) -- set the frame rate
101 setnframes(n) -- set the number of frames
102 setcomptype(type, name)
103 -- set the compression type and the
104 human-readable compression type
105 setparams(tuple)
106 -- set all parameters at once
107 setmark(id, pos, name)
108 -- add specified mark to the list of marks
109 tell() -- return current position in output file (useful
110 in combination with setmark())
111 writeframesraw(data)
112 -- write audio frames without pathing up the
113 file header
114 writeframes(data)
115 -- write audio frames and patch up the file header
116 close() -- patch up the file header and close the
117 output file
118You should set the parameters before the first writeframesraw or
119writeframes. The total number of frames does not need to be set,
120but when it is set to the correct value, the header does not have to
121be patched up.
122It is best to first set all parameters, perhaps possibly the
123compression type, and then write audio frames using writeframesraw.
124When all frames have been written, either call writeframes('') or
125close() to patch up the sizes in the header.
126Marks can be added anytime. If there are any marks, ypu must call
127close() after all frames have been written.
128The close() method is called automatically when the class instance
129is destroyed.
130
131When a file is opened with the extension '.aiff', an AIFF file is
132written, otherwise an AIFF-C file is written. This default can be
133changed by calling aiff() or aifc() before the first writeframes or
134writeframesraw.
135"""
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000136
Guido van Rossum36bb1811996-12-31 05:57:34 +0000137import struct
Guido van Rossum3db6ebc1994-01-28 09:59:35 +0000138import __builtin__
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000139
Skip Montanaroe99d5ea2001-01-20 19:54:20 +0000140__all__ = ["Error","open","openfp"]
141
Fred Drake227b1202000-08-17 05:06:49 +0000142class Error(Exception):
143 pass
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000144
Guido van Rossum820819c2002-08-12 22:11:28 +0000145_AIFC_version = 0xA2805140L # Version 1 of AIFF-C
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000146
147_skiplist = 'COMT', 'INST', 'MIDI', 'AESD', \
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000148 'APPL', 'NAME', 'AUTH', '(c) ', 'ANNO'
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000149
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000150def _read_long(file):
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000151 try:
152 return struct.unpack('>l', file.read(4))[0]
153 except struct.error:
154 raise EOFError
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000155
156def _read_ulong(file):
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000157 try:
158 return struct.unpack('>L', file.read(4))[0]
159 except struct.error:
160 raise EOFError
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000161
162def _read_short(file):
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000163 try:
164 return struct.unpack('>h', file.read(2))[0]
165 except struct.error:
166 raise EOFError
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000167
168def _read_string(file):
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000169 length = ord(file.read(1))
170 if length == 0:
171 data = ''
172 else:
173 data = file.read(length)
174 if length & 1 == 0:
175 dummy = file.read(1)
176 return data
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000177
178_HUGE_VAL = 1.79769313486231e+308 # See <limits.h>
179
180def _read_float(f): # 10 bytes
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000181 expon = _read_short(f) # 2 bytes
182 sign = 1
183 if expon < 0:
184 sign = -1
185 expon = expon + 0x8000
186 himant = _read_ulong(f) # 4 bytes
187 lomant = _read_ulong(f) # 4 bytes
188 if expon == himant == lomant == 0:
189 f = 0.0
190 elif expon == 0x7FFF:
191 f = _HUGE_VAL
192 else:
193 expon = expon - 16383
194 f = (himant * 0x100000000L + lomant) * pow(2.0, expon - 63)
195 return sign * f
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000196
197def _write_short(f, x):
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000198 f.write(struct.pack('>h', x))
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000199
200def _write_long(f, x):
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000201 f.write(struct.pack('>L', x))
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000202
203def _write_string(f, s):
Bob Ippolito6067f202006-05-30 00:26:01 +0000204 if len(s) > 255:
205 raise ValueError("string exceeds maximum pstring length")
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000206 f.write(chr(len(s)))
207 f.write(s)
208 if len(s) & 1 == 0:
209 f.write(chr(0))
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000210
211def _write_float(f, x):
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000212 import math
213 if x < 0:
214 sign = 0x8000
215 x = x * -1
216 else:
217 sign = 0
218 if x == 0:
219 expon = 0
220 himant = 0
221 lomant = 0
222 else:
223 fmant, expon = math.frexp(x)
224 if expon > 16384 or fmant >= 1: # Infinity or NaN
225 expon = sign|0x7FFF
226 himant = 0
227 lomant = 0
228 else: # Finite
229 expon = expon + 16382
230 if expon < 0: # denormalized
231 fmant = math.ldexp(fmant, expon)
232 expon = 0
233 expon = expon | sign
234 fmant = math.ldexp(fmant, 32)
235 fsmant = math.floor(fmant)
236 himant = long(fsmant)
237 fmant = math.ldexp(fmant - fsmant, 32)
238 fsmant = math.floor(fmant)
239 lomant = long(fsmant)
240 _write_short(f, expon)
241 _write_long(f, himant)
242 _write_long(f, lomant)
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000243
Guido van Rossum8ea7bb81999-06-09 13:32:28 +0000244from chunk import Chunk
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000245
Guido van Rossumd3166071993-05-24 14:16:22 +0000246class Aifc_read:
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000247 # Variables used in this class:
248 #
249 # These variables are available to the user though appropriate
250 # methods of this class:
251 # _file -- the open file with methods read(), close(), and seek()
252 # set through the __init__() method
253 # _nchannels -- the number of audio channels
254 # available through the getnchannels() method
255 # _nframes -- the number of audio frames
256 # available through the getnframes() method
257 # _sampwidth -- the number of bytes per audio sample
258 # available through the getsampwidth() method
259 # _framerate -- the sampling frequency
260 # available through the getframerate() method
261 # _comptype -- the AIFF-C compression type ('NONE' if AIFF)
262 # available through the getcomptype() method
263 # _compname -- the human-readable AIFF-C compression type
264 # available through the getcomptype() method
265 # _markers -- the marks in the audio file
266 # available through the getmarkers() and getmark()
267 # methods
268 # _soundpos -- the position in the audio stream
269 # available through the tell() method, set through the
270 # setpos() method
271 #
272 # These variables are used internally only:
273 # _version -- the AIFF-C version number
274 # _decomp -- the decompressor from builtin module cl
275 # _comm_chunk_read -- 1 iff the COMM chunk has been read
276 # _aifc -- 1 iff reading an AIFF-C file
277 # _ssnd_seek_needed -- 1 iff positioned correctly in audio
278 # file for readframes()
279 # _ssnd_chunk -- instantiation of a chunk class for the SSND chunk
280 # _framesize -- size of one frame in the file
Sjoerd Mullender2a451411993-12-20 09:36:01 +0000281
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000282 def initfp(self, file):
283 self._version = 0
284 self._decomp = None
285 self._convert = None
286 self._markers = []
287 self._soundpos = 0
288 self._file = Chunk(file)
289 if self._file.getname() != 'FORM':
290 raise Error, 'file does not start with FORM id'
291 formdata = self._file.read(4)
292 if formdata == 'AIFF':
293 self._aifc = 0
294 elif formdata == 'AIFC':
295 self._aifc = 1
296 else:
297 raise Error, 'not an AIFF or AIFF-C file'
298 self._comm_chunk_read = 0
299 while 1:
300 self._ssnd_seek_needed = 1
301 try:
302 chunk = Chunk(self._file)
303 except EOFError:
304 break
305 chunkname = chunk.getname()
306 if chunkname == 'COMM':
307 self._read_comm_chunk(chunk)
308 self._comm_chunk_read = 1
309 elif chunkname == 'SSND':
310 self._ssnd_chunk = chunk
311 dummy = chunk.read(8)
312 self._ssnd_seek_needed = 0
313 elif chunkname == 'FVER':
Guido van Rossum820819c2002-08-12 22:11:28 +0000314 self._version = _read_ulong(chunk)
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000315 elif chunkname == 'MARK':
316 self._readmark(chunk)
317 elif chunkname in _skiplist:
318 pass
319 else:
320 raise Error, 'unrecognized chunk type '+chunk.chunkname
321 chunk.skip()
322 if not self._comm_chunk_read or not self._ssnd_chunk:
323 raise Error, 'COMM chunk and/or SSND chunk missing'
324 if self._aifc and self._decomp:
325 import cl
326 params = [cl.ORIGINAL_FORMAT, 0,
327 cl.BITS_PER_COMPONENT, self._sampwidth * 8,
328 cl.FRAME_RATE, self._framerate]
329 if self._nchannels == 1:
330 params[1] = cl.MONO
331 elif self._nchannels == 2:
332 params[1] = cl.STEREO_INTERLEAVED
333 else:
334 raise Error, 'cannot compress more than 2 channels'
335 self._decomp.SetParams(params)
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000336
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000337 def __init__(self, f):
338 if type(f) == type(''):
339 f = __builtin__.open(f, 'rb')
340 # else, assume it is an open file object already
341 self.initfp(f)
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000342
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000343 #
344 # User visible methods.
345 #
346 def getfp(self):
347 return self._file
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000348
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000349 def rewind(self):
350 self._ssnd_seek_needed = 1
351 self._soundpos = 0
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000352
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000353 def close(self):
354 if self._decomp:
355 self._decomp.CloseDecompressor()
356 self._decomp = None
357 self._file = None
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000358
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000359 def tell(self):
360 return self._soundpos
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000361
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000362 def getnchannels(self):
363 return self._nchannels
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000364
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000365 def getnframes(self):
366 return self._nframes
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000367
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000368 def getsampwidth(self):
369 return self._sampwidth
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000370
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000371 def getframerate(self):
372 return self._framerate
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000373
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000374 def getcomptype(self):
375 return self._comptype
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000376
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000377 def getcompname(self):
378 return self._compname
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000379
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000380## def getversion(self):
381## return self._version
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000382
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000383 def getparams(self):
384 return self.getnchannels(), self.getsampwidth(), \
385 self.getframerate(), self.getnframes(), \
386 self.getcomptype(), self.getcompname()
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000387
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000388 def getmarkers(self):
389 if len(self._markers) == 0:
390 return None
391 return self._markers
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000392
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000393 def getmark(self, id):
394 for marker in self._markers:
395 if id == marker[0]:
396 return marker
Walter Dörwald70a6b492004-02-12 17:35:32 +0000397 raise Error, 'marker %r does not exist' % (id,)
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000398
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000399 def setpos(self, pos):
400 if pos < 0 or pos > self._nframes:
401 raise Error, 'position not in range'
402 self._soundpos = pos
403 self._ssnd_seek_needed = 1
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000404
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000405 def readframes(self, nframes):
406 if self._ssnd_seek_needed:
407 self._ssnd_chunk.seek(0)
408 dummy = self._ssnd_chunk.read(8)
409 pos = self._soundpos * self._framesize
410 if pos:
Guido van Rossum2663c132000-03-07 15:19:31 +0000411 self._ssnd_chunk.seek(pos + 8)
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000412 self._ssnd_seek_needed = 0
413 if nframes == 0:
414 return ''
415 data = self._ssnd_chunk.read(nframes * self._framesize)
416 if self._convert and data:
417 data = self._convert(data)
418 self._soundpos = self._soundpos + len(data) / (self._nchannels * self._sampwidth)
419 return data
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000420
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000421 #
422 # Internal methods.
423 #
Sjoerd Mullender2a451411993-12-20 09:36:01 +0000424
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000425 def _decomp_data(self, data):
426 import cl
427 dummy = self._decomp.SetParam(cl.FRAME_BUFFER_SIZE,
428 len(data) * 2)
429 return self._decomp.Decompress(len(data) / self._nchannels,
430 data)
Sjoerd Mullender43bf0bc1993-12-13 11:42:39 +0000431
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000432 def _ulaw2lin(self, data):
433 import audioop
434 return audioop.ulaw2lin(data, 2)
Sjoerd Mullender43bf0bc1993-12-13 11:42:39 +0000435
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000436 def _adpcm2lin(self, data):
437 import audioop
438 if not hasattr(self, '_adpcmstate'):
439 # first time
440 self._adpcmstate = None
441 data, self._adpcmstate = audioop.adpcm2lin(data, 2,
442 self._adpcmstate)
443 return data
Sjoerd Mullender1f057541994-09-06 16:17:51 +0000444
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000445 def _read_comm_chunk(self, chunk):
446 self._nchannels = _read_short(chunk)
447 self._nframes = _read_long(chunk)
448 self._sampwidth = (_read_short(chunk) + 7) / 8
449 self._framerate = int(_read_float(chunk))
450 self._framesize = self._nchannels * self._sampwidth
451 if self._aifc:
452 #DEBUG: SGI's soundeditor produces a bad size :-(
453 kludge = 0
454 if chunk.chunksize == 18:
455 kludge = 1
456 print 'Warning: bad COMM chunk size'
457 chunk.chunksize = 23
458 #DEBUG end
459 self._comptype = chunk.read(4)
460 #DEBUG start
461 if kludge:
462 length = ord(chunk.file.read(1))
463 if length & 1 == 0:
464 length = length + 1
465 chunk.chunksize = chunk.chunksize + length
466 chunk.file.seek(-1, 1)
467 #DEBUG end
468 self._compname = _read_string(chunk)
469 if self._comptype != 'NONE':
470 if self._comptype == 'G722':
471 try:
472 import audioop
473 except ImportError:
474 pass
475 else:
476 self._convert = self._adpcm2lin
477 self._framesize = self._framesize / 4
478 return
479 # for ULAW and ALAW try Compression Library
480 try:
481 import cl
482 except ImportError:
483 if self._comptype == 'ULAW':
484 try:
485 import audioop
486 self._convert = self._ulaw2lin
487 self._framesize = self._framesize / 2
488 return
489 except ImportError:
490 pass
491 raise Error, 'cannot read compressed AIFF-C files'
492 if self._comptype == 'ULAW':
493 scheme = cl.G711_ULAW
494 self._framesize = self._framesize / 2
495 elif self._comptype == 'ALAW':
496 scheme = cl.G711_ALAW
497 self._framesize = self._framesize / 2
498 else:
499 raise Error, 'unsupported compression type'
500 self._decomp = cl.OpenDecompressor(scheme)
501 self._convert = self._decomp_data
502 else:
503 self._comptype = 'NONE'
504 self._compname = 'not compressed'
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000505
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000506 def _readmark(self, chunk):
507 nmarkers = _read_short(chunk)
508 # Some files appear to contain invalid counts.
509 # Cope with this by testing for EOF.
510 try:
511 for i in range(nmarkers):
512 id = _read_short(chunk)
513 pos = _read_long(chunk)
514 name = _read_string(chunk)
515 if pos or name:
516 # some files appear to have
517 # dummy markers consisting of
518 # a position 0 and name ''
519 self._markers.append((id, pos, name))
520 except EOFError:
521 print 'Warning: MARK chunk contains only',
522 print len(self._markers),
523 if len(self._markers) == 1: print 'marker',
524 else: print 'markers',
525 print 'instead of', nmarkers
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000526
Guido van Rossumd3166071993-05-24 14:16:22 +0000527class Aifc_write:
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000528 # Variables used in this class:
529 #
530 # These variables are user settable through appropriate methods
531 # of this class:
532 # _file -- the open file with methods write(), close(), tell(), seek()
533 # set through the __init__() method
534 # _comptype -- the AIFF-C compression type ('NONE' in AIFF)
535 # set through the setcomptype() or setparams() method
536 # _compname -- the human-readable AIFF-C compression type
537 # set through the setcomptype() or setparams() method
538 # _nchannels -- the number of audio channels
539 # set through the setnchannels() or setparams() method
540 # _sampwidth -- the number of bytes per audio sample
541 # set through the setsampwidth() or setparams() method
542 # _framerate -- the sampling frequency
543 # set through the setframerate() or setparams() method
544 # _nframes -- the number of audio frames written to the header
545 # set through the setnframes() or setparams() method
546 # _aifc -- whether we're writing an AIFF-C file or an AIFF file
547 # set through the aifc() method, reset through the
548 # aiff() method
549 #
550 # These variables are used internally only:
551 # _version -- the AIFF-C version number
552 # _comp -- the compressor from builtin module cl
553 # _nframeswritten -- the number of audio frames actually written
554 # _datalength -- the size of the audio samples written to the header
555 # _datawritten -- the size of the audio samples actually written
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000556
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000557 def __init__(self, f):
558 if type(f) == type(''):
559 filename = f
560 f = __builtin__.open(f, 'wb')
561 else:
562 # else, assume it is an open file object already
563 filename = '???'
564 self.initfp(f)
565 if filename[-5:] == '.aiff':
566 self._aifc = 0
567 else:
568 self._aifc = 1
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000569
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000570 def initfp(self, file):
571 self._file = file
572 self._version = _AIFC_version
573 self._comptype = 'NONE'
574 self._compname = 'not compressed'
575 self._comp = None
576 self._convert = None
577 self._nchannels = 0
578 self._sampwidth = 0
579 self._framerate = 0
580 self._nframes = 0
581 self._nframeswritten = 0
582 self._datawritten = 0
583 self._datalength = 0
584 self._markers = []
585 self._marklength = 0
586 self._aifc = 1 # AIFF-C is default
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000587
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000588 def __del__(self):
589 if self._file:
590 self.close()
Sjoerd Mullender43bf0bc1993-12-13 11:42:39 +0000591
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000592 #
593 # User visible methods.
594 #
595 def aiff(self):
596 if self._nframeswritten:
597 raise Error, 'cannot change parameters after starting to write'
598 self._aifc = 0
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000599
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000600 def aifc(self):
601 if self._nframeswritten:
602 raise Error, 'cannot change parameters after starting to write'
603 self._aifc = 1
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000604
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000605 def setnchannels(self, nchannels):
606 if self._nframeswritten:
607 raise Error, 'cannot change parameters after starting to write'
608 if nchannels < 1:
609 raise Error, 'bad # of channels'
610 self._nchannels = nchannels
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000611
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000612 def getnchannels(self):
613 if not self._nchannels:
614 raise Error, 'number of channels not set'
615 return self._nchannels
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000616
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000617 def setsampwidth(self, sampwidth):
618 if self._nframeswritten:
619 raise Error, 'cannot change parameters after starting to write'
620 if sampwidth < 1 or sampwidth > 4:
621 raise Error, 'bad sample width'
622 self._sampwidth = sampwidth
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000623
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000624 def getsampwidth(self):
625 if not self._sampwidth:
626 raise Error, 'sample width not set'
627 return self._sampwidth
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000628
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000629 def setframerate(self, framerate):
630 if self._nframeswritten:
631 raise Error, 'cannot change parameters after starting to write'
632 if framerate <= 0:
633 raise Error, 'bad frame rate'
634 self._framerate = framerate
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000635
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000636 def getframerate(self):
637 if not self._framerate:
638 raise Error, 'frame rate not set'
639 return self._framerate
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000640
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000641 def setnframes(self, nframes):
642 if self._nframeswritten:
643 raise Error, 'cannot change parameters after starting to write'
644 self._nframes = nframes
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000645
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000646 def getnframes(self):
647 return self._nframeswritten
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000648
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000649 def setcomptype(self, comptype, compname):
650 if self._nframeswritten:
651 raise Error, 'cannot change parameters after starting to write'
652 if comptype not in ('NONE', 'ULAW', 'ALAW', 'G722'):
653 raise Error, 'unsupported compression type'
654 self._comptype = comptype
655 self._compname = compname
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000656
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000657 def getcomptype(self):
658 return self._comptype
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000659
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000660 def getcompname(self):
661 return self._compname
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000662
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000663## def setversion(self, version):
664## if self._nframeswritten:
665## raise Error, 'cannot change parameters after starting to write'
666## self._version = version
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000667
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000668 def setparams(self, (nchannels, sampwidth, framerate, nframes, comptype, compname)):
669 if self._nframeswritten:
670 raise Error, 'cannot change parameters after starting to write'
671 if comptype not in ('NONE', 'ULAW', 'ALAW', 'G722'):
672 raise Error, 'unsupported compression type'
673 self.setnchannels(nchannels)
674 self.setsampwidth(sampwidth)
675 self.setframerate(framerate)
676 self.setnframes(nframes)
677 self.setcomptype(comptype, compname)
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000678
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000679 def getparams(self):
680 if not self._nchannels or not self._sampwidth or not self._framerate:
681 raise Error, 'not all parameters set'
682 return self._nchannels, self._sampwidth, self._framerate, \
683 self._nframes, self._comptype, self._compname
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000684
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000685 def setmark(self, id, pos, name):
686 if id <= 0:
687 raise Error, 'marker ID must be > 0'
688 if pos < 0:
689 raise Error, 'marker position must be >= 0'
690 if type(name) != type(''):
691 raise Error, 'marker name must be a string'
692 for i in range(len(self._markers)):
693 if id == self._markers[i][0]:
694 self._markers[i] = id, pos, name
695 return
696 self._markers.append((id, pos, name))
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000697
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000698 def getmark(self, id):
699 for marker in self._markers:
700 if id == marker[0]:
701 return marker
Walter Dörwald70a6b492004-02-12 17:35:32 +0000702 raise Error, 'marker %r does not exist' % (id,)
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000703
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000704 def getmarkers(self):
705 if len(self._markers) == 0:
706 return None
707 return self._markers
Tim Peters146965a2001-01-14 18:09:23 +0000708
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000709 def tell(self):
710 return self._nframeswritten
Sjoerd Mullender43bf0bc1993-12-13 11:42:39 +0000711
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000712 def writeframesraw(self, data):
713 self._ensure_header_written(len(data))
714 nframes = len(data) / (self._sampwidth * self._nchannels)
715 if self._convert:
716 data = self._convert(data)
717 self._file.write(data)
718 self._nframeswritten = self._nframeswritten + nframes
719 self._datawritten = self._datawritten + len(data)
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000720
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000721 def writeframes(self, data):
722 self.writeframesraw(data)
723 if self._nframeswritten != self._nframes or \
724 self._datalength != self._datawritten:
725 self._patchheader()
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000726
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000727 def close(self):
728 self._ensure_header_written(0)
729 if self._datawritten & 1:
730 # quick pad to even size
731 self._file.write(chr(0))
732 self._datawritten = self._datawritten + 1
733 self._writemarkers()
734 if self._nframeswritten != self._nframes or \
735 self._datalength != self._datawritten or \
736 self._marklength:
737 self._patchheader()
738 if self._comp:
739 self._comp.CloseCompressor()
740 self._comp = None
741 self._file.flush()
742 self._file = None
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000743
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000744 #
745 # Internal methods.
746 #
Sjoerd Mullender2a451411993-12-20 09:36:01 +0000747
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000748 def _comp_data(self, data):
749 import cl
Neal Norwitz086ac002002-02-11 17:56:27 +0000750 dummy = self._comp.SetParam(cl.FRAME_BUFFER_SIZE, len(data))
751 dummy = self._comp.SetParam(cl.COMPRESSED_BUFFER_SIZE, len(data))
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000752 return self._comp.Compress(self._nframes, data)
Sjoerd Mullender43bf0bc1993-12-13 11:42:39 +0000753
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000754 def _lin2ulaw(self, data):
755 import audioop
756 return audioop.lin2ulaw(data, 2)
Sjoerd Mullender43bf0bc1993-12-13 11:42:39 +0000757
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000758 def _lin2adpcm(self, data):
759 import audioop
760 if not hasattr(self, '_adpcmstate'):
761 self._adpcmstate = None
762 data, self._adpcmstate = audioop.lin2adpcm(data, 2,
763 self._adpcmstate)
764 return data
Sjoerd Mullender1f057541994-09-06 16:17:51 +0000765
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000766 def _ensure_header_written(self, datasize):
767 if not self._nframeswritten:
768 if self._comptype in ('ULAW', 'ALAW'):
769 if not self._sampwidth:
770 self._sampwidth = 2
771 if self._sampwidth != 2:
772 raise Error, 'sample width must be 2 when compressing with ULAW or ALAW'
773 if self._comptype == 'G722':
774 if not self._sampwidth:
775 self._sampwidth = 2
776 if self._sampwidth != 2:
777 raise Error, 'sample width must be 2 when compressing with G7.22 (ADPCM)'
778 if not self._nchannels:
779 raise Error, '# channels not specified'
780 if not self._sampwidth:
781 raise Error, 'sample width not specified'
782 if not self._framerate:
783 raise Error, 'sampling rate not specified'
784 self._write_header(datasize)
Guido van Rossum52fc1f61993-06-17 12:38:10 +0000785
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000786 def _init_compression(self):
787 if self._comptype == 'G722':
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000788 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."