blob: 2e4b19caa74d717a431cd7864a86c9851b2a67f5 [file] [log] [blame]
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +00001# Stuff to parse AIFF-C and AIFF files.
2#
3# Unless explicitly stated otherwise, the description below is true
4# both for AIFF-C files and AIFF files.
5#
6# An 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#
21# An AIFF file has the string "AIFF" instead of "AIFC".
22#
23# A chunk consists of an identifier (4 bytes) followed by a size (4 bytes,
24# big endian order), followed by the data. The size field does not include
25# the size of the 8 byte header.
26#
27# The 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)
Sjoerd Mullender43bf0bc1993-12-13 11:42:39 +000043# in AIFF-C files only:
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +000044# <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#
51# A pstring consists of 1 byte length, a string of characters, and 0 or 1
52# byte pad to make the total length even.
53#
54# Usage.
55#
56# Reading AIFF files:
57# f = aifc.open(file, 'r')
Sjoerd Mullender2a451411993-12-20 09:36:01 +000058# where file is either the name of a file or an open file pointer.
Sjoerd Mullender43bf0bc1993-12-13 11:42:39 +000059# The open file pointer must have methods read(), seek(), and close().
60# In some types of audio files, if the setpos() method is not used,
61# the seek() method is not necessary.
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +000062#
63# This 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
Guido van Rossum17ed1ae1993-06-01 13:21:04 +000082# close() -- close the instance (make it unusable)
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +000083# The position returned by tell(), the position given to setpos() and
84# the position of marks are all compatible and have nothing to do with
85# the actual postion in the file.
Sjoerd Mullender43bf0bc1993-12-13 11:42:39 +000086# The close() method is called automatically when the class instance
87# is destroyed.
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +000088#
89# Writing AIFF files:
90# f = aifc.open(file, 'w')
Sjoerd Mullender2a451411993-12-20 09:36:01 +000091# where file is either the name of a file or an open file pointer.
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +000092# The open file pointer must have methods write(), tell(), seek(), and
93# close().
94#
95# This 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(nchannels, sampwidth, framerate, nframes, comptype, compname)
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
118# You should set the parameters before the first writeframesraw or
119# writeframes. The total number of frames does not need to be set,
120# but when it is set to the correct value, the header does not have to
121# be patched up.
122# It is best to first set all parameters, perhaps possibly the
Sjoerd Mullender43bf0bc1993-12-13 11:42:39 +0000123# compression type, and then write audio frames using writeframesraw.
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000124# When all frames have been written, either call writeframes('') or
125# close() to patch up the sizes in the header.
126# Marks can be added anytime. If there are any marks, ypu must call
127# close() after all frames have been written.
Sjoerd Mullender43bf0bc1993-12-13 11:42:39 +0000128# The close() method is called automatically when the class instance
129# is destroyed.
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000130#
131# When a file is opened with the extension '.aiff', an AIFF file is
132# written, otherwise an AIFF-C file is written. This default can be
133# changed by calling aiff() or aifc() before the first writeframes or
134# writeframesraw.
135
Guido van Rossum36bb1811996-12-31 05:57:34 +0000136import struct
Guido van Rossum3db6ebc1994-01-28 09:59:35 +0000137import __builtin__
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000138try:
139 import CL
140except ImportError:
141 pass
142
143Error = 'aifc.Error'
144
145_AIFC_version = 0xA2805140 # Version 1 of AIFF-C
146
147_skiplist = 'COMT', 'INST', 'MIDI', 'AESD', \
148 'APPL', 'NAME', 'AUTH', '(c) ', 'ANNO'
149
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000150def _read_long(file):
Guido van Rossum36bb1811996-12-31 05:57:34 +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 Rossum36bb1811996-12-31 05:57:34 +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 Rossum36bb1811996-12-31 05:57:34 +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):
169 length = ord(file.read(1))
Guido van Rossumb6775db1994-08-01 11:34:53 +0000170 if length == 0:
171 data = ''
172 else:
173 data = file.read(length)
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000174 if length & 1 == 0:
175 dummy = file.read(1)
176 return data
177
178_HUGE_VAL = 1.79769313486231e+308 # See <limits.h>
179
180def _read_float(f): # 10 bytes
181 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
197
198def _write_short(f, x):
Guido van Rossum36bb1811996-12-31 05:57:34 +0000199 f.write(struct.pack('>h', x))
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000200
201def _write_long(f, x):
Guido van Rossum36bb1811996-12-31 05:57:34 +0000202 if x >= 1L<<31:
203 x = x - (1L<<32)
204 f.write(struct.pack('>l', x))
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000205
206def _write_string(f, s):
207 f.write(chr(len(s)))
208 f.write(s)
209 if len(s) & 1 == 0:
210 f.write(chr(0))
211
212def _write_float(f, x):
213 import math
214 if x < 0:
215 sign = 0x8000
216 x = x * -1
217 else:
218 sign = 0
219 if x == 0:
220 expon = 0
221 himant = 0
222 lomant = 0
223 else:
224 fmant, expon = math.frexp(x)
225 if expon > 16384 or fmant >= 1: # Infinity or NaN
226 expon = sign|0x7FFF
227 himant = 0
228 lomant = 0
229 else: # Finite
230 expon = expon + 16382
231 if expon < 0: # denormalized
232 fmant = math.ldexp(fmant, expon)
233 expon = 0
234 expon = expon | sign
235 fmant = math.ldexp(fmant, 32)
236 fsmant = math.floor(fmant)
237 himant = long(fsmant)
238 fmant = math.ldexp(fmant - fsmant, 32)
239 fsmant = math.floor(fmant)
240 lomant = long(fsmant)
241 _write_short(f, expon)
242 _write_long(f, himant)
243 _write_long(f, lomant)
244
Guido van Rossumd3166071993-05-24 14:16:22 +0000245class Chunk:
Guido van Rossum7bc817d1993-12-17 15:25:27 +0000246 def __init__(self, file):
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000247 self.file = file
248 self.chunkname = self.file.read(4)
249 if len(self.chunkname) < 4:
250 raise EOFError
251 self.chunksize = _read_long(self.file)
252 self.size_read = 0
253 self.offset = self.file.tell()
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000254
255 def rewind(self):
256 self.file.seek(self.offset, 0)
257 self.size_read = 0
258
259 def setpos(self, pos):
260 if pos < 0 or pos > self.chunksize:
261 raise RuntimeError
262 self.file.seek(self.offset + pos, 0)
263 self.size_read = pos
264
265 def read(self, length):
266 if self.size_read >= self.chunksize:
267 return ''
268 if length > self.chunksize - self.size_read:
269 length = self.chunksize - self.size_read
270 data = self.file.read(length)
271 self.size_read = self.size_read + len(data)
272 return data
273
274 def skip(self):
275 try:
276 self.file.seek(self.chunksize - self.size_read, 1)
277 except RuntimeError:
278 while self.size_read < self.chunksize:
279 dummy = self.read(8192)
280 if not dummy:
281 raise EOFError
282 if self.chunksize & 1:
283 dummy = self.read(1)
284
Guido van Rossumd3166071993-05-24 14:16:22 +0000285class Aifc_read:
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000286 # Variables used in this class:
287 #
288 # These variables are available to the user though appropriate
289 # methods of this class:
290 # _file -- the open file with methods read(), close(), and seek()
Guido van Rossum7bc817d1993-12-17 15:25:27 +0000291 # set through the __init__() method
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000292 # _nchannels -- the number of audio channels
293 # available through the getnchannels() method
294 # _nframes -- the number of audio frames
295 # available through the getnframes() method
296 # _sampwidth -- the number of bytes per audio sample
297 # available through the getsampwidth() method
298 # _framerate -- the sampling frequency
299 # available through the getframerate() method
300 # _comptype -- the AIFF-C compression type ('NONE' if AIFF)
301 # available through the getcomptype() method
302 # _compname -- the human-readable AIFF-C compression type
303 # available through the getcomptype() method
304 # _markers -- the marks in the audio file
305 # available through the getmarkers() and getmark()
306 # methods
307 # _soundpos -- the position in the audio stream
308 # available through the tell() method, set through the
Sjoerd Mullender43bf0bc1993-12-13 11:42:39 +0000309 # setpos() method
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000310 #
311 # These variables are used internally only:
312 # _version -- the AIFF-C version number
313 # _decomp -- the decompressor from builtin module cl
314 # _comm_chunk_read -- 1 iff the COMM chunk has been read
315 # _aifc -- 1 iff reading an AIFF-C file
316 # _ssnd_seek_needed -- 1 iff positioned correctly in audio
317 # file for readframes()
318 # _ssnd_chunk -- instantiation of a chunk class for the SSND chunk
Sjoerd Mullender3a997271993-02-04 16:43:28 +0000319 # _framesize -- size of one frame in the file
Sjoerd Mullender2a451411993-12-20 09:36:01 +0000320
Guido van Rossumd7abed31996-08-20 20:40:07 +0000321## if 0: access _file, _nchannels, _nframes, _sampwidth, _framerate, \
322## _comptype, _compname, _markers, _soundpos, _version, \
323## _decomp, _comm_chunk_read, __aifc, _ssnd_seek_needed, \
324## _ssnd_chunk, _framesize: private
Sjoerd Mullender2a451411993-12-20 09:36:01 +0000325
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000326 def initfp(self, file):
327 self._file = file
328 self._version = 0
329 self._decomp = None
Sjoerd Mullender43bf0bc1993-12-13 11:42:39 +0000330 self._convert = None
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000331 self._markers = []
332 self._soundpos = 0
333 form = self._file.read(4)
334 if form != 'FORM':
335 raise Error, 'file does not start with FORM id'
336 formlength = _read_long(self._file)
337 if formlength <= 0:
338 raise Error, 'invalid FORM chunk data size'
339 formdata = self._file.read(4)
340 formlength = formlength - 4
341 if formdata == 'AIFF':
342 self._aifc = 0
343 elif formdata == 'AIFC':
344 self._aifc = 1
345 else:
346 raise Error, 'not an AIFF or AIFF-C file'
347 self._comm_chunk_read = 0
Sjoerd Mullender7564a641993-01-22 14:26:28 +0000348 while formlength > 0:
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000349 self._ssnd_seek_needed = 1
Sjoerd Mullender8d733a01993-01-29 12:01:00 +0000350 #DEBUG: SGI's soundfiler has a bug. There should
351 # be no need to check for EOF here.
352 try:
Guido van Rossum7bc817d1993-12-17 15:25:27 +0000353 chunk = Chunk(self._file)
Sjoerd Mullender8d733a01993-01-29 12:01:00 +0000354 except EOFError:
355 if formlength == 8:
356 print 'Warning: FORM chunk size too large'
357 formlength = 0
358 break
359 raise EOFError # different error, raise exception
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000360 if chunk.chunkname == 'COMM':
361 self._read_comm_chunk(chunk)
362 self._comm_chunk_read = 1
363 elif chunk.chunkname == 'SSND':
364 self._ssnd_chunk = chunk
365 dummy = chunk.read(8)
366 self._ssnd_seek_needed = 0
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000367 elif chunk.chunkname == 'FVER':
368 self._version = _read_long(chunk)
369 elif chunk.chunkname == 'MARK':
370 self._readmark(chunk)
371 elif chunk.chunkname in _skiplist:
372 pass
373 else:
374 raise Error, 'unrecognized chunk type '+chunk.chunkname
Sjoerd Mullender4150ede1993-08-26 14:12:07 +0000375 formlength = formlength - 8 - chunk.chunksize
376 if chunk.chunksize & 1:
377 formlength = formlength - 1
Sjoerd Mullender93f07401993-01-26 09:24:37 +0000378 if formlength > 0:
379 chunk.skip()
Sjoerd Mullender7564a641993-01-22 14:26:28 +0000380 if not self._comm_chunk_read or not self._ssnd_chunk:
381 raise Error, 'COMM chunk and/or SSND chunk missing'
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000382 if self._aifc and self._decomp:
Sjoerd Mullender49c2df11994-01-06 16:35:34 +0000383 params = [CL.ORIGINAL_FORMAT, 0,
384 CL.BITS_PER_COMPONENT, self._sampwidth * 8,
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000385 CL.FRAME_RATE, self._framerate]
Sjoerd Mullender49c2df11994-01-06 16:35:34 +0000386 if self._nchannels == 1:
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000387 params[1] = CL.MONO
Sjoerd Mullender49c2df11994-01-06 16:35:34 +0000388 elif self._nchannels == 2:
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000389 params[1] = CL.STEREO_INTERLEAVED
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000390 else:
Sjoerd Mullender49c2df11994-01-06 16:35:34 +0000391 raise Error, 'cannot compress more than 2 channels'
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000392 self._decomp.SetParams(params)
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000393
Guido van Rossum7bc817d1993-12-17 15:25:27 +0000394 def __init__(self, f):
395 if type(f) == type(''):
Guido van Rossume174c151994-09-16 10:55:53 +0000396 f = __builtin__.open(f, 'rb')
Guido van Rossum7bc817d1993-12-17 15:25:27 +0000397 # else, assume it is an open file object already
398 self.initfp(f)
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000399
Sjoerd Mullender43bf0bc1993-12-13 11:42:39 +0000400 def __del__(self):
401 if self._file:
402 self.close()
403
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000404 #
405 # User visible methods.
406 #
407 def getfp(self):
408 return self._file
409
410 def rewind(self):
411 self._ssnd_seek_needed = 1
412 self._soundpos = 0
413
414 def close(self):
415 if self._decomp:
416 self._decomp.CloseDecompressor()
417 self._decomp = None
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000418 self._file = None
419
420 def tell(self):
421 return self._soundpos
422
423 def getnchannels(self):
424 return self._nchannels
425
426 def getnframes(self):
427 return self._nframes
428
429 def getsampwidth(self):
430 return self._sampwidth
431
432 def getframerate(self):
433 return self._framerate
434
435 def getcomptype(self):
436 return self._comptype
437
438 def getcompname(self):
439 return self._compname
440
441## def getversion(self):
442## return self._version
443
444 def getparams(self):
Sjoerd Mullender43bf0bc1993-12-13 11:42:39 +0000445 return self.getnchannels(), self.getsampwidth(), \
446 self.getframerate(), self.getnframes(), \
447 self.getcomptype(), self.getcompname()
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000448
449 def getmarkers(self):
450 if len(self._markers) == 0:
451 return None
452 return self._markers
453
454 def getmark(self, id):
455 for marker in self._markers:
456 if id == marker[0]:
457 return marker
458 raise Error, 'marker ' + `id` + ' does not exist'
459
460 def setpos(self, pos):
461 if pos < 0 or pos > self._nframes:
462 raise Error, 'position not in range'
463 self._soundpos = pos
464 self._ssnd_seek_needed = 1
465
466 def readframes(self, nframes):
467 if self._ssnd_seek_needed:
468 self._ssnd_chunk.rewind()
469 dummy = self._ssnd_chunk.read(8)
Sjoerd Mullender3a997271993-02-04 16:43:28 +0000470 pos = self._soundpos * self._framesize
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000471 if pos:
472 self._ssnd_chunk.setpos(pos + 8)
473 self._ssnd_seek_needed = 0
Sjoerd Mullender8d733a01993-01-29 12:01:00 +0000474 if nframes == 0:
475 return ''
Sjoerd Mullender3a997271993-02-04 16:43:28 +0000476 data = self._ssnd_chunk.read(nframes * self._framesize)
Sjoerd Mullender43bf0bc1993-12-13 11:42:39 +0000477 if self._convert and data:
478 data = self._convert(data)
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000479 self._soundpos = self._soundpos + len(data) / (self._nchannels * self._sampwidth)
480 return data
481
482 #
483 # Internal methods.
484 #
Guido van Rossumd7abed31996-08-20 20:40:07 +0000485## if 0: access *: private
Sjoerd Mullender2a451411993-12-20 09:36:01 +0000486
Sjoerd Mullender43bf0bc1993-12-13 11:42:39 +0000487 def _decomp_data(self, data):
488 dummy = self._decomp.SetParam(CL.FRAME_BUFFER_SIZE,
489 len(data) * 2)
490 return self._decomp.Decompress(len(data) / self._nchannels,
491 data)
492
493 def _ulaw2lin(self, data):
494 import audioop
495 return audioop.ulaw2lin(data, 2)
496
Sjoerd Mullender1f057541994-09-06 16:17:51 +0000497 def _adpcm2lin(self, data):
498 import audioop
499 if not hasattr(self, '_adpcmstate'):
500 # first time
501 self._adpcmstate = None
502 data, self._adpcmstate = audioop.adpcm2lin(data, 2,
503 self._adpcmstate)
504 return data
505
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000506 def _read_comm_chunk(self, chunk):
Sjoerd Mullender49c2df11994-01-06 16:35:34 +0000507 self._nchannels = _read_short(chunk)
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000508 self._nframes = _read_long(chunk)
Sjoerd Mullender49c2df11994-01-06 16:35:34 +0000509 self._sampwidth = (_read_short(chunk) + 7) / 8
Sjoerd Mullenderffe94901994-01-28 09:56:05 +0000510 self._framerate = int(_read_float(chunk))
Sjoerd Mullender3a997271993-02-04 16:43:28 +0000511 self._framesize = self._nchannels * self._sampwidth
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000512 if self._aifc:
513 #DEBUG: SGI's soundeditor produces a bad size :-(
514 kludge = 0
515 if chunk.chunksize == 18:
516 kludge = 1
517 print 'Warning: bad COMM chunk size'
518 chunk.chunksize = 23
519 #DEBUG end
520 self._comptype = chunk.read(4)
521 #DEBUG start
522 if kludge:
523 length = ord(chunk.file.read(1))
524 if length & 1 == 0:
525 length = length + 1
526 chunk.chunksize = chunk.chunksize + length
527 chunk.file.seek(-1, 1)
528 #DEBUG end
529 self._compname = _read_string(chunk)
530 if self._comptype != 'NONE':
Sjoerd Mullender1f057541994-09-06 16:17:51 +0000531 if self._comptype == 'G722':
532 try:
533 import audioop
534 except ImportError:
535 pass
536 else:
537 self._convert = self._adpcm2lin
538 self._framesize = self._framesize / 4
539 return
540 # for ULAW and ALAW try Compression Library
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000541 try:
542 import cl, CL
543 except ImportError:
Sjoerd Mullender43bf0bc1993-12-13 11:42:39 +0000544 if self._comptype == 'ULAW':
545 try:
546 import audioop
547 self._convert = self._ulaw2lin
548 self._framesize = self._framesize / 2
549 return
550 except ImportError:
551 pass
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000552 raise Error, 'cannot read compressed AIFF-C files'
553 if self._comptype == 'ULAW':
554 scheme = CL.G711_ULAW
Sjoerd Mullender3a997271993-02-04 16:43:28 +0000555 self._framesize = self._framesize / 2
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000556 elif self._comptype == 'ALAW':
557 scheme = CL.G711_ALAW
Sjoerd Mullender3a997271993-02-04 16:43:28 +0000558 self._framesize = self._framesize / 2
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000559 else:
560 raise Error, 'unsupported compression type'
561 self._decomp = cl.OpenDecompressor(scheme)
Sjoerd Mullender43bf0bc1993-12-13 11:42:39 +0000562 self._convert = self._decomp_data
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000563 else:
564 self._comptype = 'NONE'
565 self._compname = 'not compressed'
566
567 def _readmark(self, chunk):
568 nmarkers = _read_short(chunk)
Guido van Rossum9b3bc711993-06-20 21:02:22 +0000569 # Some files appear to contain invalid counts.
570 # Cope with this by testing for EOF.
571 try:
572 for i in range(nmarkers):
573 id = _read_short(chunk)
574 pos = _read_long(chunk)
575 name = _read_string(chunk)
Sjoerd Mullenderebea8961994-10-03 10:21:06 +0000576 if pos or name:
577 # some files appear to have
578 # dummy markers consisting of
579 # a position 0 and name ''
580 self._markers.append((id, pos, name))
Guido van Rossum9b3bc711993-06-20 21:02:22 +0000581 except EOFError:
582 print 'Warning: MARK chunk contains only',
583 print len(self._markers),
584 if len(self._markers) == 1: print 'marker',
585 else: print 'markers',
586 print 'instead of', nmarkers
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000587
Guido van Rossumd3166071993-05-24 14:16:22 +0000588class Aifc_write:
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000589 # Variables used in this class:
590 #
591 # These variables are user settable through appropriate methods
592 # of this class:
593 # _file -- the open file with methods write(), close(), tell(), seek()
Guido van Rossum7bc817d1993-12-17 15:25:27 +0000594 # set through the __init__() method
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000595 # _comptype -- the AIFF-C compression type ('NONE' in AIFF)
596 # set through the setcomptype() or setparams() method
597 # _compname -- the human-readable AIFF-C compression type
598 # set through the setcomptype() or setparams() method
599 # _nchannels -- the number of audio channels
600 # set through the setnchannels() or setparams() method
601 # _sampwidth -- the number of bytes per audio sample
602 # set through the setsampwidth() or setparams() method
603 # _framerate -- the sampling frequency
604 # set through the setframerate() or setparams() method
605 # _nframes -- the number of audio frames written to the header
606 # set through the setnframes() or setparams() method
607 # _aifc -- whether we're writing an AIFF-C file or an AIFF file
608 # set through the aifc() method, reset through the
609 # aiff() method
610 #
611 # These variables are used internally only:
612 # _version -- the AIFF-C version number
613 # _comp -- the compressor from builtin module cl
614 # _nframeswritten -- the number of audio frames actually written
615 # _datalength -- the size of the audio samples written to the header
616 # _datawritten -- the size of the audio samples actually written
617
Guido van Rossumd7abed31996-08-20 20:40:07 +0000618## if 0: access _file, _comptype, _compname, _nchannels, _sampwidth, \
619## _framerate, _nframes, _aifc, _version, _comp, \
620## _nframeswritten, _datalength, _datawritten: private
Sjoerd Mullender2a451411993-12-20 09:36:01 +0000621
Guido van Rossum7bc817d1993-12-17 15:25:27 +0000622 def __init__(self, f):
623 if type(f) == type(''):
Guido van Rossum6ed9df21993-12-17 16:43:43 +0000624 filename = f
Guido van Rossume174c151994-09-16 10:55:53 +0000625 f = __builtin__.open(f, 'wb')
Guido van Rossum6ed9df21993-12-17 16:43:43 +0000626 else:
Sjoerd Mullender2a451411993-12-20 09:36:01 +0000627 # else, assume it is an open file object already
Guido van Rossum6ed9df21993-12-17 16:43:43 +0000628 filename = '???'
Guido van Rossum7bc817d1993-12-17 15:25:27 +0000629 self.initfp(f)
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000630 if filename[-5:] == '.aiff':
631 self._aifc = 0
632 else:
633 self._aifc = 1
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000634
635 def initfp(self, file):
636 self._file = file
637 self._version = _AIFC_version
638 self._comptype = 'NONE'
639 self._compname = 'not compressed'
640 self._comp = None
Sjoerd Mullender43bf0bc1993-12-13 11:42:39 +0000641 self._convert = None
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000642 self._nchannels = 0
643 self._sampwidth = 0
644 self._framerate = 0
645 self._nframes = 0
646 self._nframeswritten = 0
647 self._datawritten = 0
Guido van Rossum52fc1f61993-06-17 12:38:10 +0000648 self._datalength = 0
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000649 self._markers = []
650 self._marklength = 0
651 self._aifc = 1 # AIFF-C is default
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000652
Sjoerd Mullender43bf0bc1993-12-13 11:42:39 +0000653 def __del__(self):
654 if self._file:
655 self.close()
656
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000657 #
658 # User visible methods.
659 #
660 def aiff(self):
661 if self._nframeswritten:
662 raise Error, 'cannot change parameters after starting to write'
663 self._aifc = 0
664
665 def aifc(self):
666 if self._nframeswritten:
667 raise Error, 'cannot change parameters after starting to write'
668 self._aifc = 1
669
670 def setnchannels(self, nchannels):
671 if self._nframeswritten:
672 raise Error, 'cannot change parameters after starting to write'
Sjoerd Mullender49c2df11994-01-06 16:35:34 +0000673 if nchannels < 1:
674 raise Error, 'bad # of channels'
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000675 self._nchannels = nchannels
676
677 def getnchannels(self):
678 if not self._nchannels:
679 raise Error, 'number of channels not set'
680 return self._nchannels
681
682 def setsampwidth(self, sampwidth):
683 if self._nframeswritten:
684 raise Error, 'cannot change parameters after starting to write'
Sjoerd Mullender49c2df11994-01-06 16:35:34 +0000685 if sampwidth < 1 or sampwidth > 4:
686 raise Error, 'bad sample width'
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000687 self._sampwidth = sampwidth
688
689 def getsampwidth(self):
690 if not self._sampwidth:
691 raise Error, 'sample width not set'
692 return self._sampwidth
693
694 def setframerate(self, framerate):
695 if self._nframeswritten:
696 raise Error, 'cannot change parameters after starting to write'
Sjoerd Mullender49c2df11994-01-06 16:35:34 +0000697 if framerate <= 0:
698 raise Error, 'bad frame rate'
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000699 self._framerate = framerate
700
701 def getframerate(self):
702 if not self._framerate:
703 raise Error, 'frame rate not set'
704 return self._framerate
705
706 def setnframes(self, nframes):
707 if self._nframeswritten:
708 raise Error, 'cannot change parameters after starting to write'
709 self._nframes = nframes
710
711 def getnframes(self):
712 return self._nframeswritten
713
714 def setcomptype(self, comptype, compname):
715 if self._nframeswritten:
716 raise Error, 'cannot change parameters after starting to write'
Sjoerd Mullender1f057541994-09-06 16:17:51 +0000717 if comptype not in ('NONE', 'ULAW', 'ALAW', 'G722'):
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000718 raise Error, 'unsupported compression type'
719 self._comptype = comptype
720 self._compname = compname
721
722 def getcomptype(self):
723 return self._comptype
724
725 def getcompname(self):
726 return self._compname
727
728## def setversion(self, version):
729## if self._nframeswritten:
730## raise Error, 'cannot change parameters after starting to write'
731## self._version = version
732
733 def setparams(self, (nchannels, sampwidth, framerate, nframes, comptype, compname)):
734 if self._nframeswritten:
735 raise Error, 'cannot change parameters after starting to write'
Sjoerd Mullender1f057541994-09-06 16:17:51 +0000736 if comptype not in ('NONE', 'ULAW', 'ALAW', 'G722'):
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000737 raise Error, 'unsupported compression type'
Sjoerd Mullender49c2df11994-01-06 16:35:34 +0000738 self.setnchannels(nchannels)
739 self.setsampwidth(sampwidth)
740 self.setframerate(framerate)
741 self.setnframes(nframes)
742 self.setcomptype(comptype, compname)
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000743
744 def getparams(self):
745 if not self._nchannels or not self._sampwidth or not self._framerate:
746 raise Error, 'not all parameters set'
747 return self._nchannels, self._sampwidth, self._framerate, \
748 self._nframes, self._comptype, self._compname
749
Sjoerd Mullender7564a641993-01-22 14:26:28 +0000750 def setmark(self, id, pos, name):
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000751 if id <= 0:
752 raise Error, 'marker ID must be > 0'
753 if pos < 0:
754 raise Error, 'marker position must be >= 0'
755 if type(name) != type(''):
756 raise Error, 'marker name must be a string'
757 for i in range(len(self._markers)):
758 if id == self._markers[i][0]:
759 self._markers[i] = id, pos, name
760 return
761 self._markers.append((id, pos, name))
762
763 def getmark(self, id):
764 for marker in self._markers:
765 if id == marker[0]:
766 return marker
767 raise Error, 'marker ' + `id` + ' does not exist'
768
769 def getmarkers(self):
770 if len(self._markers) == 0:
771 return None
772 return self._markers
773
Sjoerd Mullender43bf0bc1993-12-13 11:42:39 +0000774 def tell(self):
775 return self._nframeswritten
776
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000777 def writeframesraw(self, data):
Guido van Rossum52fc1f61993-06-17 12:38:10 +0000778 self._ensure_header_written(len(data))
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000779 nframes = len(data) / (self._sampwidth * self._nchannels)
Sjoerd Mullender43bf0bc1993-12-13 11:42:39 +0000780 if self._convert:
781 data = self._convert(data)
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000782 self._file.write(data)
783 self._nframeswritten = self._nframeswritten + nframes
784 self._datawritten = self._datawritten + len(data)
785
786 def writeframes(self, data):
787 self.writeframesraw(data)
788 if self._nframeswritten != self._nframes or \
789 self._datalength != self._datawritten:
790 self._patchheader()
791
792 def close(self):
Guido van Rossum52fc1f61993-06-17 12:38:10 +0000793 self._ensure_header_written(0)
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000794 if self._datawritten & 1:
795 # quick pad to even size
796 self._file.write(chr(0))
797 self._datawritten = self._datawritten + 1
798 self._writemarkers()
799 if self._nframeswritten != self._nframes or \
800 self._datalength != self._datawritten or \
801 self._marklength:
802 self._patchheader()
803 if self._comp:
804 self._comp.CloseCompressor()
805 self._comp = None
Sjoerd Mullenderfeaa7d21993-12-16 13:56:34 +0000806 self._file.flush()
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000807 self._file = None
808
809 #
810 # Internal methods.
811 #
Guido van Rossumd7abed31996-08-20 20:40:07 +0000812## if 0: access *: private
Sjoerd Mullender2a451411993-12-20 09:36:01 +0000813
Sjoerd Mullender43bf0bc1993-12-13 11:42:39 +0000814 def _comp_data(self, data):
815 dum = self._comp.SetParam(CL.FRAME_BUFFER_SIZE, len(data))
816 dum = self._comp.SetParam(CL.COMPRESSED_BUFFER_SIZE, len(data))
817 return self._comp.Compress(nframes, data)
818
819 def _lin2ulaw(self, data):
820 import audioop
821 return audioop.lin2ulaw(data, 2)
822
Sjoerd Mullender1f057541994-09-06 16:17:51 +0000823 def _lin2adpcm(self, data):
824 import audioop
825 if not hasattr(self, '_adpcmstate'):
826 self._adpcmstate = None
827 data, self._adpcmstate = audioop.lin2adpcm(data, 2,
828 self._adpcmstate)
829 return data
830
Guido van Rossum52fc1f61993-06-17 12:38:10 +0000831 def _ensure_header_written(self, datasize):
832 if not self._nframeswritten:
833 if self._comptype in ('ULAW', 'ALAW'):
834 if not self._sampwidth:
Sjoerd Mullender49c2df11994-01-06 16:35:34 +0000835 self._sampwidth = 2
836 if self._sampwidth != 2:
Guido van Rossum52fc1f61993-06-17 12:38:10 +0000837 raise Error, 'sample width must be 2 when compressing with ULAW or ALAW'
Sjoerd Mullender1f057541994-09-06 16:17:51 +0000838 if self._comptype == 'G722':
839 if not self._sampwidth:
840 self._sampwidth = 2
841 if self._sampwidth != 2:
842 raise Error, 'sample width must be 2 when compressing with G7.22 (ADPCM)'
Guido van Rossum52fc1f61993-06-17 12:38:10 +0000843 if not self._nchannels:
844 raise Error, '# channels not specified'
845 if not self._sampwidth:
846 raise Error, 'sample width not specified'
847 if not self._framerate:
848 raise Error, 'sampling rate not specified'
849 self._write_header(datasize)
850
Sjoerd Mullender43bf0bc1993-12-13 11:42:39 +0000851 def _init_compression(self):
Sjoerd Mullender1f057541994-09-06 16:17:51 +0000852 if self._comptype == 'G722':
853 import audioop
854 self._convert = self._lin2adpcm
855 return
Sjoerd Mullender43bf0bc1993-12-13 11:42:39 +0000856 try:
857 import cl, CL
858 except ImportError:
859 if self._comptype == 'ULAW':
860 try:
861 import audioop
862 self._convert = self._lin2ulaw
863 return
864 except ImportError:
865 pass
866 raise Error, 'cannot write compressed AIFF-C files'
867 if self._comptype == 'ULAW':
868 scheme = CL.G711_ULAW
869 elif self._comptype == 'ALAW':
870 scheme = CL.G711_ALAW
871 else:
872 raise Error, 'unsupported compression type'
873 self._comp = cl.OpenCompressor(scheme)
Sjoerd Mullender49c2df11994-01-06 16:35:34 +0000874 params = [CL.ORIGINAL_FORMAT, 0,
875 CL.BITS_PER_COMPONENT, self._sampwidth * 8,
876 CL.FRAME_RATE, self._framerate,
877 CL.FRAME_BUFFER_SIZE, 100,
Sjoerd Mullender43bf0bc1993-12-13 11:42:39 +0000878 CL.COMPRESSED_BUFFER_SIZE, 100]
Sjoerd Mullender49c2df11994-01-06 16:35:34 +0000879 if self._nchannels == 1:
Sjoerd Mullender43bf0bc1993-12-13 11:42:39 +0000880 params[1] = CL.MONO
Sjoerd Mullender49c2df11994-01-06 16:35:34 +0000881 elif self._nchannels == 2:
Sjoerd Mullender43bf0bc1993-12-13 11:42:39 +0000882 params[1] = CL.STEREO_INTERLEAVED
Sjoerd Mullender43bf0bc1993-12-13 11:42:39 +0000883 else:
Sjoerd Mullender49c2df11994-01-06 16:35:34 +0000884 raise Error, 'cannot compress more than 2 channels'
Sjoerd Mullender43bf0bc1993-12-13 11:42:39 +0000885 self._comp.SetParams(params)
886 # the compressor produces a header which we ignore
887 dummy = self._comp.Compress(0, '')
888 self._convert = self._comp_data
889
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000890 def _write_header(self, initlength):
891 if self._aifc and self._comptype != 'NONE':
Sjoerd Mullender43bf0bc1993-12-13 11:42:39 +0000892 self._init_compression()
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000893 self._file.write('FORM')
894 if not self._nframes:
895 self._nframes = initlength / (self._nchannels * self._sampwidth)
896 self._datalength = self._nframes * self._nchannels * self._sampwidth
897 if self._datalength & 1:
898 self._datalength = self._datalength + 1
Sjoerd Mullender1f057541994-09-06 16:17:51 +0000899 if self._aifc:
900 if self._comptype in ('ULAW', 'ALAW'):
901 self._datalength = self._datalength / 2
902 if self._datalength & 1:
903 self._datalength = self._datalength + 1
904 elif self._comptype == 'G722':
905 self._datalength = (self._datalength + 3) / 4
906 if self._datalength & 1:
907 self._datalength = self._datalength + 1
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000908 self._form_length_pos = self._file.tell()
909 commlength = self._write_form_length(self._datalength)
910 if self._aifc:
911 self._file.write('AIFC')
912 self._file.write('FVER')
913 _write_long(self._file, 4)
914 _write_long(self._file, self._version)
915 else:
916 self._file.write('AIFF')
917 self._file.write('COMM')
918 _write_long(self._file, commlength)
Sjoerd Mullender49c2df11994-01-06 16:35:34 +0000919 _write_short(self._file, self._nchannels)
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000920 self._nframes_pos = self._file.tell()
921 _write_long(self._file, self._nframes)
Sjoerd Mullender49c2df11994-01-06 16:35:34 +0000922 _write_short(self._file, self._sampwidth * 8)
923 _write_float(self._file, self._framerate)
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000924 if self._aifc:
925 self._file.write(self._comptype)
926 _write_string(self._file, self._compname)
927 self._file.write('SSND')
928 self._ssnd_length_pos = self._file.tell()
929 _write_long(self._file, self._datalength + 8)
930 _write_long(self._file, 0)
931 _write_long(self._file, 0)
932
933 def _write_form_length(self, datalength):
934 if self._aifc:
935 commlength = 18 + 5 + len(self._compname)
936 if commlength & 1:
937 commlength = commlength + 1
938 verslength = 12
939 else:
940 commlength = 18
941 verslength = 0
942 _write_long(self._file, 4 + verslength + self._marklength + \
943 8 + commlength + 16 + datalength)
944 return commlength
945
946 def _patchheader(self):
947 curpos = self._file.tell()
948 if self._datawritten & 1:
949 datalength = self._datawritten + 1
950 self._file.write(chr(0))
951 else:
952 datalength = self._datawritten
953 if datalength == self._datalength and \
Sjoerd Mullender7564a641993-01-22 14:26:28 +0000954 self._nframes == self._nframeswritten and \
955 self._marklength == 0:
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000956 self._file.seek(curpos, 0)
957 return
958 self._file.seek(self._form_length_pos, 0)
959 dummy = self._write_form_length(datalength)
960 self._file.seek(self._nframes_pos, 0)
961 _write_long(self._file, self._nframeswritten)
962 self._file.seek(self._ssnd_length_pos, 0)
963 _write_long(self._file, datalength + 8)
964 self._file.seek(curpos, 0)
965 self._nframes = self._nframeswritten
966 self._datalength = datalength
967
968 def _writemarkers(self):
969 if len(self._markers) == 0:
970 return
971 self._file.write('MARK')
972 length = 2
973 for marker in self._markers:
974 id, pos, name = marker
975 length = length + len(name) + 1 + 6
976 if len(name) & 1 == 0:
977 length = length + 1
978 _write_long(self._file, length)
979 self._marklength = length + 8
Sjoerd Mullender7564a641993-01-22 14:26:28 +0000980 _write_short(self._file, len(self._markers))
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000981 for marker in self._markers:
982 id, pos, name = marker
983 _write_short(self._file, id)
984 _write_long(self._file, pos)
985 _write_string(self._file, name)
986
Guido van Rossum7bc817d1993-12-17 15:25:27 +0000987def open(f, mode):
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000988 if mode == 'r':
Guido van Rossum7bc817d1993-12-17 15:25:27 +0000989 return Aifc_read(f)
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000990 elif mode == 'w':
Guido van Rossum7bc817d1993-12-17 15:25:27 +0000991 return Aifc_write(f)
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000992 else:
Sjoerd Mullender2a451411993-12-20 09:36:01 +0000993 raise Error, "mode must be 'r' or 'w'"
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000994
Guido van Rossum7bc817d1993-12-17 15:25:27 +0000995openfp = open # B/W compatibility
Guido van Rossum36bb1811996-12-31 05:57:34 +0000996
997if __name__ == '__main__':
998 import sys
999 if not sys.argv[1:]:
1000 sys.argv.append('/usr/demos/data/audio/bach.aiff')
1001 fn = sys.argv[1]
1002 f = open(fn, 'r')
1003 print "Reading", fn
1004 print "nchannels =", f.getnchannels()
1005 print "nframes =", f.getnframes()
1006 print "sampwidth =", f.getsampwidth()
1007 print "framerate =", f.getframerate()
1008 print "comptype =", f.getcomptype()
1009 print "compname =", f.getcompname()
1010 if sys.argv[2:]:
1011 gn = sys.argv[2]
1012 print "Writing", gn
1013 g = open(gn, 'w')
1014 g.setparams(f.getparams())
1015 while 1:
1016 data = f.readframes(1024)
1017 if not data:
1018 break
1019 g.writeframes(data)
1020 g.close()
1021 f.close()
1022 print "Done."