blob: ea4f600e7b4c2dd8ccbdb6db57aba4c2e3591fde [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 Rossumafe3ebf1997-01-11 19:21:09 +0000202 f.write(struct.pack('>L', x))
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000203
204def _write_string(f, s):
205 f.write(chr(len(s)))
206 f.write(s)
207 if len(s) & 1 == 0:
208 f.write(chr(0))
209
210def _write_float(f, x):
211 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)
242
Guido van Rossumd3166071993-05-24 14:16:22 +0000243class Chunk:
Guido van Rossum7bc817d1993-12-17 15:25:27 +0000244 def __init__(self, file):
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000245 self.file = file
246 self.chunkname = self.file.read(4)
247 if len(self.chunkname) < 4:
248 raise EOFError
249 self.chunksize = _read_long(self.file)
250 self.size_read = 0
251 self.offset = self.file.tell()
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000252
253 def rewind(self):
254 self.file.seek(self.offset, 0)
255 self.size_read = 0
256
257 def setpos(self, pos):
258 if pos < 0 or pos > self.chunksize:
259 raise RuntimeError
260 self.file.seek(self.offset + pos, 0)
261 self.size_read = pos
262
263 def read(self, length):
264 if self.size_read >= self.chunksize:
265 return ''
266 if length > self.chunksize - self.size_read:
267 length = self.chunksize - self.size_read
268 data = self.file.read(length)
269 self.size_read = self.size_read + len(data)
270 return data
271
272 def skip(self):
273 try:
274 self.file.seek(self.chunksize - self.size_read, 1)
275 except RuntimeError:
276 while self.size_read < self.chunksize:
277 dummy = self.read(8192)
278 if not dummy:
279 raise EOFError
280 if self.chunksize & 1:
281 dummy = self.read(1)
282
Guido van Rossumd3166071993-05-24 14:16:22 +0000283class Aifc_read:
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000284 # Variables used in this class:
285 #
286 # These variables are available to the user though appropriate
287 # methods of this class:
288 # _file -- the open file with methods read(), close(), and seek()
Guido van Rossum7bc817d1993-12-17 15:25:27 +0000289 # set through the __init__() method
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000290 # _nchannels -- the number of audio channels
291 # available through the getnchannels() method
292 # _nframes -- the number of audio frames
293 # available through the getnframes() method
294 # _sampwidth -- the number of bytes per audio sample
295 # available through the getsampwidth() method
296 # _framerate -- the sampling frequency
297 # available through the getframerate() method
298 # _comptype -- the AIFF-C compression type ('NONE' if AIFF)
299 # available through the getcomptype() method
300 # _compname -- the human-readable AIFF-C compression type
301 # available through the getcomptype() method
302 # _markers -- the marks in the audio file
303 # available through the getmarkers() and getmark()
304 # methods
305 # _soundpos -- the position in the audio stream
306 # available through the tell() method, set through the
Sjoerd Mullender43bf0bc1993-12-13 11:42:39 +0000307 # setpos() method
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000308 #
309 # These variables are used internally only:
310 # _version -- the AIFF-C version number
311 # _decomp -- the decompressor from builtin module cl
312 # _comm_chunk_read -- 1 iff the COMM chunk has been read
313 # _aifc -- 1 iff reading an AIFF-C file
314 # _ssnd_seek_needed -- 1 iff positioned correctly in audio
315 # file for readframes()
316 # _ssnd_chunk -- instantiation of a chunk class for the SSND chunk
Sjoerd Mullender3a997271993-02-04 16:43:28 +0000317 # _framesize -- size of one frame in the file
Sjoerd Mullender2a451411993-12-20 09:36:01 +0000318
Guido van Rossumd7abed31996-08-20 20:40:07 +0000319## if 0: access _file, _nchannels, _nframes, _sampwidth, _framerate, \
320## _comptype, _compname, _markers, _soundpos, _version, \
321## _decomp, _comm_chunk_read, __aifc, _ssnd_seek_needed, \
322## _ssnd_chunk, _framesize: private
Sjoerd Mullender2a451411993-12-20 09:36:01 +0000323
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000324 def initfp(self, file):
325 self._file = file
326 self._version = 0
327 self._decomp = None
Sjoerd Mullender43bf0bc1993-12-13 11:42:39 +0000328 self._convert = None
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000329 self._markers = []
330 self._soundpos = 0
331 form = self._file.read(4)
332 if form != 'FORM':
333 raise Error, 'file does not start with FORM id'
334 formlength = _read_long(self._file)
335 if formlength <= 0:
336 raise Error, 'invalid FORM chunk data size'
337 formdata = self._file.read(4)
338 formlength = formlength - 4
339 if formdata == 'AIFF':
340 self._aifc = 0
341 elif formdata == 'AIFC':
342 self._aifc = 1
343 else:
344 raise Error, 'not an AIFF or AIFF-C file'
345 self._comm_chunk_read = 0
Sjoerd Mullender7564a641993-01-22 14:26:28 +0000346 while formlength > 0:
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000347 self._ssnd_seek_needed = 1
Sjoerd Mullender8d733a01993-01-29 12:01:00 +0000348 #DEBUG: SGI's soundfiler has a bug. There should
349 # be no need to check for EOF here.
350 try:
Guido van Rossum7bc817d1993-12-17 15:25:27 +0000351 chunk = Chunk(self._file)
Sjoerd Mullender8d733a01993-01-29 12:01:00 +0000352 except EOFError:
353 if formlength == 8:
354 print 'Warning: FORM chunk size too large'
355 formlength = 0
356 break
357 raise EOFError # different error, raise exception
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000358 if chunk.chunkname == 'COMM':
359 self._read_comm_chunk(chunk)
360 self._comm_chunk_read = 1
361 elif chunk.chunkname == 'SSND':
362 self._ssnd_chunk = chunk
363 dummy = chunk.read(8)
364 self._ssnd_seek_needed = 0
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000365 elif chunk.chunkname == 'FVER':
366 self._version = _read_long(chunk)
367 elif chunk.chunkname == 'MARK':
368 self._readmark(chunk)
369 elif chunk.chunkname in _skiplist:
370 pass
371 else:
372 raise Error, 'unrecognized chunk type '+chunk.chunkname
Sjoerd Mullender4150ede1993-08-26 14:12:07 +0000373 formlength = formlength - 8 - chunk.chunksize
374 if chunk.chunksize & 1:
375 formlength = formlength - 1
Sjoerd Mullender93f07401993-01-26 09:24:37 +0000376 if formlength > 0:
377 chunk.skip()
Sjoerd Mullender7564a641993-01-22 14:26:28 +0000378 if not self._comm_chunk_read or not self._ssnd_chunk:
379 raise Error, 'COMM chunk and/or SSND chunk missing'
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000380 if self._aifc and self._decomp:
Sjoerd Mullender49c2df11994-01-06 16:35:34 +0000381 params = [CL.ORIGINAL_FORMAT, 0,
382 CL.BITS_PER_COMPONENT, self._sampwidth * 8,
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000383 CL.FRAME_RATE, self._framerate]
Sjoerd Mullender49c2df11994-01-06 16:35:34 +0000384 if self._nchannels == 1:
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000385 params[1] = CL.MONO
Sjoerd Mullender49c2df11994-01-06 16:35:34 +0000386 elif self._nchannels == 2:
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000387 params[1] = CL.STEREO_INTERLEAVED
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000388 else:
Sjoerd Mullender49c2df11994-01-06 16:35:34 +0000389 raise Error, 'cannot compress more than 2 channels'
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000390 self._decomp.SetParams(params)
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000391
Guido van Rossum7bc817d1993-12-17 15:25:27 +0000392 def __init__(self, f):
393 if type(f) == type(''):
Guido van Rossume174c151994-09-16 10:55:53 +0000394 f = __builtin__.open(f, 'rb')
Guido van Rossum7bc817d1993-12-17 15:25:27 +0000395 # else, assume it is an open file object already
396 self.initfp(f)
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000397
Sjoerd Mullender43bf0bc1993-12-13 11:42:39 +0000398 def __del__(self):
399 if self._file:
400 self.close()
401
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000402 #
403 # User visible methods.
404 #
405 def getfp(self):
406 return self._file
407
408 def rewind(self):
409 self._ssnd_seek_needed = 1
410 self._soundpos = 0
411
412 def close(self):
413 if self._decomp:
414 self._decomp.CloseDecompressor()
415 self._decomp = None
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000416 self._file = None
417
418 def tell(self):
419 return self._soundpos
420
421 def getnchannels(self):
422 return self._nchannels
423
424 def getnframes(self):
425 return self._nframes
426
427 def getsampwidth(self):
428 return self._sampwidth
429
430 def getframerate(self):
431 return self._framerate
432
433 def getcomptype(self):
434 return self._comptype
435
436 def getcompname(self):
437 return self._compname
438
439## def getversion(self):
440## return self._version
441
442 def getparams(self):
Sjoerd Mullender43bf0bc1993-12-13 11:42:39 +0000443 return self.getnchannels(), self.getsampwidth(), \
444 self.getframerate(), self.getnframes(), \
445 self.getcomptype(), self.getcompname()
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000446
447 def getmarkers(self):
448 if len(self._markers) == 0:
449 return None
450 return self._markers
451
452 def getmark(self, id):
453 for marker in self._markers:
454 if id == marker[0]:
455 return marker
456 raise Error, 'marker ' + `id` + ' does not exist'
457
458 def setpos(self, pos):
459 if pos < 0 or pos > self._nframes:
460 raise Error, 'position not in range'
461 self._soundpos = pos
462 self._ssnd_seek_needed = 1
463
464 def readframes(self, nframes):
465 if self._ssnd_seek_needed:
466 self._ssnd_chunk.rewind()
467 dummy = self._ssnd_chunk.read(8)
Sjoerd Mullender3a997271993-02-04 16:43:28 +0000468 pos = self._soundpos * self._framesize
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000469 if pos:
470 self._ssnd_chunk.setpos(pos + 8)
471 self._ssnd_seek_needed = 0
Sjoerd Mullender8d733a01993-01-29 12:01:00 +0000472 if nframes == 0:
473 return ''
Sjoerd Mullender3a997271993-02-04 16:43:28 +0000474 data = self._ssnd_chunk.read(nframes * self._framesize)
Sjoerd Mullender43bf0bc1993-12-13 11:42:39 +0000475 if self._convert and data:
476 data = self._convert(data)
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000477 self._soundpos = self._soundpos + len(data) / (self._nchannels * self._sampwidth)
478 return data
479
480 #
481 # Internal methods.
482 #
Guido van Rossumd7abed31996-08-20 20:40:07 +0000483## if 0: access *: private
Sjoerd Mullender2a451411993-12-20 09:36:01 +0000484
Sjoerd Mullender43bf0bc1993-12-13 11:42:39 +0000485 def _decomp_data(self, data):
486 dummy = self._decomp.SetParam(CL.FRAME_BUFFER_SIZE,
487 len(data) * 2)
488 return self._decomp.Decompress(len(data) / self._nchannels,
489 data)
490
491 def _ulaw2lin(self, data):
492 import audioop
493 return audioop.ulaw2lin(data, 2)
494
Sjoerd Mullender1f057541994-09-06 16:17:51 +0000495 def _adpcm2lin(self, data):
496 import audioop
497 if not hasattr(self, '_adpcmstate'):
498 # first time
499 self._adpcmstate = None
500 data, self._adpcmstate = audioop.adpcm2lin(data, 2,
501 self._adpcmstate)
502 return data
503
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000504 def _read_comm_chunk(self, chunk):
Sjoerd Mullender49c2df11994-01-06 16:35:34 +0000505 self._nchannels = _read_short(chunk)
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000506 self._nframes = _read_long(chunk)
Sjoerd Mullender49c2df11994-01-06 16:35:34 +0000507 self._sampwidth = (_read_short(chunk) + 7) / 8
Sjoerd Mullenderffe94901994-01-28 09:56:05 +0000508 self._framerate = int(_read_float(chunk))
Sjoerd Mullender3a997271993-02-04 16:43:28 +0000509 self._framesize = self._nchannels * self._sampwidth
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000510 if self._aifc:
511 #DEBUG: SGI's soundeditor produces a bad size :-(
512 kludge = 0
513 if chunk.chunksize == 18:
514 kludge = 1
515 print 'Warning: bad COMM chunk size'
516 chunk.chunksize = 23
517 #DEBUG end
518 self._comptype = chunk.read(4)
519 #DEBUG start
520 if kludge:
521 length = ord(chunk.file.read(1))
522 if length & 1 == 0:
523 length = length + 1
524 chunk.chunksize = chunk.chunksize + length
525 chunk.file.seek(-1, 1)
526 #DEBUG end
527 self._compname = _read_string(chunk)
528 if self._comptype != 'NONE':
Sjoerd Mullender1f057541994-09-06 16:17:51 +0000529 if self._comptype == 'G722':
530 try:
531 import audioop
532 except ImportError:
533 pass
534 else:
535 self._convert = self._adpcm2lin
536 self._framesize = self._framesize / 4
537 return
538 # for ULAW and ALAW try Compression Library
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000539 try:
540 import cl, CL
541 except ImportError:
Sjoerd Mullender43bf0bc1993-12-13 11:42:39 +0000542 if self._comptype == 'ULAW':
543 try:
544 import audioop
545 self._convert = self._ulaw2lin
546 self._framesize = self._framesize / 2
547 return
548 except ImportError:
549 pass
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000550 raise Error, 'cannot read compressed AIFF-C files'
551 if self._comptype == 'ULAW':
552 scheme = CL.G711_ULAW
Sjoerd Mullender3a997271993-02-04 16:43:28 +0000553 self._framesize = self._framesize / 2
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000554 elif self._comptype == 'ALAW':
555 scheme = CL.G711_ALAW
Sjoerd Mullender3a997271993-02-04 16:43:28 +0000556 self._framesize = self._framesize / 2
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000557 else:
558 raise Error, 'unsupported compression type'
559 self._decomp = cl.OpenDecompressor(scheme)
Sjoerd Mullender43bf0bc1993-12-13 11:42:39 +0000560 self._convert = self._decomp_data
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000561 else:
562 self._comptype = 'NONE'
563 self._compname = 'not compressed'
564
565 def _readmark(self, chunk):
566 nmarkers = _read_short(chunk)
Guido van Rossum9b3bc711993-06-20 21:02:22 +0000567 # Some files appear to contain invalid counts.
568 # Cope with this by testing for EOF.
569 try:
570 for i in range(nmarkers):
571 id = _read_short(chunk)
572 pos = _read_long(chunk)
573 name = _read_string(chunk)
Sjoerd Mullenderebea8961994-10-03 10:21:06 +0000574 if pos or name:
575 # some files appear to have
576 # dummy markers consisting of
577 # a position 0 and name ''
578 self._markers.append((id, pos, name))
Guido van Rossum9b3bc711993-06-20 21:02:22 +0000579 except EOFError:
580 print 'Warning: MARK chunk contains only',
581 print len(self._markers),
582 if len(self._markers) == 1: print 'marker',
583 else: print 'markers',
584 print 'instead of', nmarkers
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000585
Guido van Rossumd3166071993-05-24 14:16:22 +0000586class Aifc_write:
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000587 # Variables used in this class:
588 #
589 # These variables are user settable through appropriate methods
590 # of this class:
591 # _file -- the open file with methods write(), close(), tell(), seek()
Guido van Rossum7bc817d1993-12-17 15:25:27 +0000592 # set through the __init__() method
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000593 # _comptype -- the AIFF-C compression type ('NONE' in AIFF)
594 # set through the setcomptype() or setparams() method
595 # _compname -- the human-readable AIFF-C compression type
596 # set through the setcomptype() or setparams() method
597 # _nchannels -- the number of audio channels
598 # set through the setnchannels() or setparams() method
599 # _sampwidth -- the number of bytes per audio sample
600 # set through the setsampwidth() or setparams() method
601 # _framerate -- the sampling frequency
602 # set through the setframerate() or setparams() method
603 # _nframes -- the number of audio frames written to the header
604 # set through the setnframes() or setparams() method
605 # _aifc -- whether we're writing an AIFF-C file or an AIFF file
606 # set through the aifc() method, reset through the
607 # aiff() method
608 #
609 # These variables are used internally only:
610 # _version -- the AIFF-C version number
611 # _comp -- the compressor from builtin module cl
612 # _nframeswritten -- the number of audio frames actually written
613 # _datalength -- the size of the audio samples written to the header
614 # _datawritten -- the size of the audio samples actually written
615
Guido van Rossumd7abed31996-08-20 20:40:07 +0000616## if 0: access _file, _comptype, _compname, _nchannels, _sampwidth, \
617## _framerate, _nframes, _aifc, _version, _comp, \
618## _nframeswritten, _datalength, _datawritten: private
Sjoerd Mullender2a451411993-12-20 09:36:01 +0000619
Guido van Rossum7bc817d1993-12-17 15:25:27 +0000620 def __init__(self, f):
621 if type(f) == type(''):
Guido van Rossum6ed9df21993-12-17 16:43:43 +0000622 filename = f
Guido van Rossume174c151994-09-16 10:55:53 +0000623 f = __builtin__.open(f, 'wb')
Guido van Rossum6ed9df21993-12-17 16:43:43 +0000624 else:
Sjoerd Mullender2a451411993-12-20 09:36:01 +0000625 # else, assume it is an open file object already
Guido van Rossum6ed9df21993-12-17 16:43:43 +0000626 filename = '???'
Guido van Rossum7bc817d1993-12-17 15:25:27 +0000627 self.initfp(f)
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000628 if filename[-5:] == '.aiff':
629 self._aifc = 0
630 else:
631 self._aifc = 1
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000632
633 def initfp(self, file):
634 self._file = file
635 self._version = _AIFC_version
636 self._comptype = 'NONE'
637 self._compname = 'not compressed'
638 self._comp = None
Sjoerd Mullender43bf0bc1993-12-13 11:42:39 +0000639 self._convert = None
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000640 self._nchannels = 0
641 self._sampwidth = 0
642 self._framerate = 0
643 self._nframes = 0
644 self._nframeswritten = 0
645 self._datawritten = 0
Guido van Rossum52fc1f61993-06-17 12:38:10 +0000646 self._datalength = 0
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000647 self._markers = []
648 self._marklength = 0
649 self._aifc = 1 # AIFF-C is default
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000650
Sjoerd Mullender43bf0bc1993-12-13 11:42:39 +0000651 def __del__(self):
652 if self._file:
653 self.close()
654
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000655 #
656 # User visible methods.
657 #
658 def aiff(self):
659 if self._nframeswritten:
660 raise Error, 'cannot change parameters after starting to write'
661 self._aifc = 0
662
663 def aifc(self):
664 if self._nframeswritten:
665 raise Error, 'cannot change parameters after starting to write'
666 self._aifc = 1
667
668 def setnchannels(self, nchannels):
669 if self._nframeswritten:
670 raise Error, 'cannot change parameters after starting to write'
Sjoerd Mullender49c2df11994-01-06 16:35:34 +0000671 if nchannels < 1:
672 raise Error, 'bad # of channels'
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000673 self._nchannels = nchannels
674
675 def getnchannels(self):
676 if not self._nchannels:
677 raise Error, 'number of channels not set'
678 return self._nchannels
679
680 def setsampwidth(self, sampwidth):
681 if self._nframeswritten:
682 raise Error, 'cannot change parameters after starting to write'
Sjoerd Mullender49c2df11994-01-06 16:35:34 +0000683 if sampwidth < 1 or sampwidth > 4:
684 raise Error, 'bad sample width'
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000685 self._sampwidth = sampwidth
686
687 def getsampwidth(self):
688 if not self._sampwidth:
689 raise Error, 'sample width not set'
690 return self._sampwidth
691
692 def setframerate(self, framerate):
693 if self._nframeswritten:
694 raise Error, 'cannot change parameters after starting to write'
Sjoerd Mullender49c2df11994-01-06 16:35:34 +0000695 if framerate <= 0:
696 raise Error, 'bad frame rate'
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000697 self._framerate = framerate
698
699 def getframerate(self):
700 if not self._framerate:
701 raise Error, 'frame rate not set'
702 return self._framerate
703
704 def setnframes(self, nframes):
705 if self._nframeswritten:
706 raise Error, 'cannot change parameters after starting to write'
707 self._nframes = nframes
708
709 def getnframes(self):
710 return self._nframeswritten
711
712 def setcomptype(self, comptype, compname):
713 if self._nframeswritten:
714 raise Error, 'cannot change parameters after starting to write'
Sjoerd Mullender1f057541994-09-06 16:17:51 +0000715 if comptype not in ('NONE', 'ULAW', 'ALAW', 'G722'):
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000716 raise Error, 'unsupported compression type'
717 self._comptype = comptype
718 self._compname = compname
719
720 def getcomptype(self):
721 return self._comptype
722
723 def getcompname(self):
724 return self._compname
725
726## def setversion(self, version):
727## if self._nframeswritten:
728## raise Error, 'cannot change parameters after starting to write'
729## self._version = version
730
731 def setparams(self, (nchannels, sampwidth, framerate, nframes, comptype, compname)):
732 if self._nframeswritten:
733 raise Error, 'cannot change parameters after starting to write'
Sjoerd Mullender1f057541994-09-06 16:17:51 +0000734 if comptype not in ('NONE', 'ULAW', 'ALAW', 'G722'):
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000735 raise Error, 'unsupported compression type'
Sjoerd Mullender49c2df11994-01-06 16:35:34 +0000736 self.setnchannels(nchannels)
737 self.setsampwidth(sampwidth)
738 self.setframerate(framerate)
739 self.setnframes(nframes)
740 self.setcomptype(comptype, compname)
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000741
742 def getparams(self):
743 if not self._nchannels or not self._sampwidth or not self._framerate:
744 raise Error, 'not all parameters set'
745 return self._nchannels, self._sampwidth, self._framerate, \
746 self._nframes, self._comptype, self._compname
747
Sjoerd Mullender7564a641993-01-22 14:26:28 +0000748 def setmark(self, id, pos, name):
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000749 if id <= 0:
750 raise Error, 'marker ID must be > 0'
751 if pos < 0:
752 raise Error, 'marker position must be >= 0'
753 if type(name) != type(''):
754 raise Error, 'marker name must be a string'
755 for i in range(len(self._markers)):
756 if id == self._markers[i][0]:
757 self._markers[i] = id, pos, name
758 return
759 self._markers.append((id, pos, name))
760
761 def getmark(self, id):
762 for marker in self._markers:
763 if id == marker[0]:
764 return marker
765 raise Error, 'marker ' + `id` + ' does not exist'
766
767 def getmarkers(self):
768 if len(self._markers) == 0:
769 return None
770 return self._markers
771
Sjoerd Mullender43bf0bc1993-12-13 11:42:39 +0000772 def tell(self):
773 return self._nframeswritten
774
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000775 def writeframesraw(self, data):
Guido van Rossum52fc1f61993-06-17 12:38:10 +0000776 self._ensure_header_written(len(data))
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000777 nframes = len(data) / (self._sampwidth * self._nchannels)
Sjoerd Mullender43bf0bc1993-12-13 11:42:39 +0000778 if self._convert:
779 data = self._convert(data)
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000780 self._file.write(data)
781 self._nframeswritten = self._nframeswritten + nframes
782 self._datawritten = self._datawritten + len(data)
783
784 def writeframes(self, data):
785 self.writeframesraw(data)
786 if self._nframeswritten != self._nframes or \
787 self._datalength != self._datawritten:
788 self._patchheader()
789
790 def close(self):
Guido van Rossum52fc1f61993-06-17 12:38:10 +0000791 self._ensure_header_written(0)
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000792 if self._datawritten & 1:
793 # quick pad to even size
794 self._file.write(chr(0))
795 self._datawritten = self._datawritten + 1
796 self._writemarkers()
797 if self._nframeswritten != self._nframes or \
798 self._datalength != self._datawritten or \
799 self._marklength:
800 self._patchheader()
801 if self._comp:
802 self._comp.CloseCompressor()
803 self._comp = None
Sjoerd Mullenderfeaa7d21993-12-16 13:56:34 +0000804 self._file.flush()
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000805 self._file = None
806
807 #
808 # Internal methods.
809 #
Guido van Rossumd7abed31996-08-20 20:40:07 +0000810## if 0: access *: private
Sjoerd Mullender2a451411993-12-20 09:36:01 +0000811
Sjoerd Mullender43bf0bc1993-12-13 11:42:39 +0000812 def _comp_data(self, data):
813 dum = self._comp.SetParam(CL.FRAME_BUFFER_SIZE, len(data))
814 dum = self._comp.SetParam(CL.COMPRESSED_BUFFER_SIZE, len(data))
815 return self._comp.Compress(nframes, data)
816
817 def _lin2ulaw(self, data):
818 import audioop
819 return audioop.lin2ulaw(data, 2)
820
Sjoerd Mullender1f057541994-09-06 16:17:51 +0000821 def _lin2adpcm(self, data):
822 import audioop
823 if not hasattr(self, '_adpcmstate'):
824 self._adpcmstate = None
825 data, self._adpcmstate = audioop.lin2adpcm(data, 2,
826 self._adpcmstate)
827 return data
828
Guido van Rossum52fc1f61993-06-17 12:38:10 +0000829 def _ensure_header_written(self, datasize):
830 if not self._nframeswritten:
831 if self._comptype in ('ULAW', 'ALAW'):
832 if not self._sampwidth:
Sjoerd Mullender49c2df11994-01-06 16:35:34 +0000833 self._sampwidth = 2
834 if self._sampwidth != 2:
Guido van Rossum52fc1f61993-06-17 12:38:10 +0000835 raise Error, 'sample width must be 2 when compressing with ULAW or ALAW'
Sjoerd Mullender1f057541994-09-06 16:17:51 +0000836 if self._comptype == 'G722':
837 if not self._sampwidth:
838 self._sampwidth = 2
839 if self._sampwidth != 2:
840 raise Error, 'sample width must be 2 when compressing with G7.22 (ADPCM)'
Guido van Rossum52fc1f61993-06-17 12:38:10 +0000841 if not self._nchannels:
842 raise Error, '# channels not specified'
843 if not self._sampwidth:
844 raise Error, 'sample width not specified'
845 if not self._framerate:
846 raise Error, 'sampling rate not specified'
847 self._write_header(datasize)
848
Sjoerd Mullender43bf0bc1993-12-13 11:42:39 +0000849 def _init_compression(self):
Sjoerd Mullender1f057541994-09-06 16:17:51 +0000850 if self._comptype == 'G722':
851 import audioop
852 self._convert = self._lin2adpcm
853 return
Sjoerd Mullender43bf0bc1993-12-13 11:42:39 +0000854 try:
855 import cl, CL
856 except ImportError:
857 if self._comptype == 'ULAW':
858 try:
859 import audioop
860 self._convert = self._lin2ulaw
861 return
862 except ImportError:
863 pass
864 raise Error, 'cannot write compressed AIFF-C files'
865 if self._comptype == 'ULAW':
866 scheme = CL.G711_ULAW
867 elif self._comptype == 'ALAW':
868 scheme = CL.G711_ALAW
869 else:
870 raise Error, 'unsupported compression type'
871 self._comp = cl.OpenCompressor(scheme)
Sjoerd Mullender49c2df11994-01-06 16:35:34 +0000872 params = [CL.ORIGINAL_FORMAT, 0,
873 CL.BITS_PER_COMPONENT, self._sampwidth * 8,
874 CL.FRAME_RATE, self._framerate,
875 CL.FRAME_BUFFER_SIZE, 100,
Sjoerd Mullender43bf0bc1993-12-13 11:42:39 +0000876 CL.COMPRESSED_BUFFER_SIZE, 100]
Sjoerd Mullender49c2df11994-01-06 16:35:34 +0000877 if self._nchannels == 1:
Sjoerd Mullender43bf0bc1993-12-13 11:42:39 +0000878 params[1] = CL.MONO
Sjoerd Mullender49c2df11994-01-06 16:35:34 +0000879 elif self._nchannels == 2:
Sjoerd Mullender43bf0bc1993-12-13 11:42:39 +0000880 params[1] = CL.STEREO_INTERLEAVED
Sjoerd Mullender43bf0bc1993-12-13 11:42:39 +0000881 else:
Sjoerd Mullender49c2df11994-01-06 16:35:34 +0000882 raise Error, 'cannot compress more than 2 channels'
Sjoerd Mullender43bf0bc1993-12-13 11:42:39 +0000883 self._comp.SetParams(params)
884 # the compressor produces a header which we ignore
885 dummy = self._comp.Compress(0, '')
886 self._convert = self._comp_data
887
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000888 def _write_header(self, initlength):
889 if self._aifc and self._comptype != 'NONE':
Sjoerd Mullender43bf0bc1993-12-13 11:42:39 +0000890 self._init_compression()
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000891 self._file.write('FORM')
892 if not self._nframes:
893 self._nframes = initlength / (self._nchannels * self._sampwidth)
894 self._datalength = self._nframes * self._nchannels * self._sampwidth
895 if self._datalength & 1:
896 self._datalength = self._datalength + 1
Sjoerd Mullender1f057541994-09-06 16:17:51 +0000897 if self._aifc:
898 if self._comptype in ('ULAW', 'ALAW'):
899 self._datalength = self._datalength / 2
900 if self._datalength & 1:
901 self._datalength = self._datalength + 1
902 elif self._comptype == 'G722':
903 self._datalength = (self._datalength + 3) / 4
904 if self._datalength & 1:
905 self._datalength = self._datalength + 1
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000906 self._form_length_pos = self._file.tell()
907 commlength = self._write_form_length(self._datalength)
908 if self._aifc:
909 self._file.write('AIFC')
910 self._file.write('FVER')
911 _write_long(self._file, 4)
912 _write_long(self._file, self._version)
913 else:
914 self._file.write('AIFF')
915 self._file.write('COMM')
916 _write_long(self._file, commlength)
Sjoerd Mullender49c2df11994-01-06 16:35:34 +0000917 _write_short(self._file, self._nchannels)
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000918 self._nframes_pos = self._file.tell()
919 _write_long(self._file, self._nframes)
Sjoerd Mullender49c2df11994-01-06 16:35:34 +0000920 _write_short(self._file, self._sampwidth * 8)
921 _write_float(self._file, self._framerate)
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000922 if self._aifc:
923 self._file.write(self._comptype)
924 _write_string(self._file, self._compname)
925 self._file.write('SSND')
926 self._ssnd_length_pos = self._file.tell()
927 _write_long(self._file, self._datalength + 8)
928 _write_long(self._file, 0)
929 _write_long(self._file, 0)
930
931 def _write_form_length(self, datalength):
932 if self._aifc:
933 commlength = 18 + 5 + len(self._compname)
934 if commlength & 1:
935 commlength = commlength + 1
936 verslength = 12
937 else:
938 commlength = 18
939 verslength = 0
940 _write_long(self._file, 4 + verslength + self._marklength + \
941 8 + commlength + 16 + datalength)
942 return commlength
943
944 def _patchheader(self):
945 curpos = self._file.tell()
946 if self._datawritten & 1:
947 datalength = self._datawritten + 1
948 self._file.write(chr(0))
949 else:
950 datalength = self._datawritten
951 if datalength == self._datalength and \
Sjoerd Mullender7564a641993-01-22 14:26:28 +0000952 self._nframes == self._nframeswritten and \
953 self._marklength == 0:
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000954 self._file.seek(curpos, 0)
955 return
956 self._file.seek(self._form_length_pos, 0)
957 dummy = self._write_form_length(datalength)
958 self._file.seek(self._nframes_pos, 0)
959 _write_long(self._file, self._nframeswritten)
960 self._file.seek(self._ssnd_length_pos, 0)
961 _write_long(self._file, datalength + 8)
962 self._file.seek(curpos, 0)
963 self._nframes = self._nframeswritten
964 self._datalength = datalength
965
966 def _writemarkers(self):
967 if len(self._markers) == 0:
968 return
969 self._file.write('MARK')
970 length = 2
971 for marker in self._markers:
972 id, pos, name = marker
973 length = length + len(name) + 1 + 6
974 if len(name) & 1 == 0:
975 length = length + 1
976 _write_long(self._file, length)
977 self._marklength = length + 8
Sjoerd Mullender7564a641993-01-22 14:26:28 +0000978 _write_short(self._file, len(self._markers))
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000979 for marker in self._markers:
980 id, pos, name = marker
981 _write_short(self._file, id)
982 _write_long(self._file, pos)
983 _write_string(self._file, name)
984
Guido van Rossum7bc817d1993-12-17 15:25:27 +0000985def open(f, mode):
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000986 if mode == 'r':
Guido van Rossum7bc817d1993-12-17 15:25:27 +0000987 return Aifc_read(f)
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000988 elif mode == 'w':
Guido van Rossum7bc817d1993-12-17 15:25:27 +0000989 return Aifc_write(f)
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000990 else:
Sjoerd Mullender2a451411993-12-20 09:36:01 +0000991 raise Error, "mode must be 'r' or 'w'"
Sjoerd Mullendereeabe7e1993-01-22 12:53:11 +0000992
Guido van Rossum7bc817d1993-12-17 15:25:27 +0000993openfp = open # B/W compatibility
Guido van Rossum36bb1811996-12-31 05:57:34 +0000994
995if __name__ == '__main__':
996 import sys
997 if not sys.argv[1:]:
998 sys.argv.append('/usr/demos/data/audio/bach.aiff')
999 fn = sys.argv[1]
1000 f = open(fn, 'r')
1001 print "Reading", fn
1002 print "nchannels =", f.getnchannels()
1003 print "nframes =", f.getnframes()
1004 print "sampwidth =", f.getsampwidth()
1005 print "framerate =", f.getframerate()
1006 print "comptype =", f.getcomptype()
1007 print "compname =", f.getcompname()
1008 if sys.argv[2:]:
1009 gn = sys.argv[2]
1010 print "Writing", gn
1011 g = open(gn, 'w')
1012 g.setparams(f.getparams())
1013 while 1:
1014 data = f.readframes(1024)
1015 if not data:
1016 break
1017 g.writeframes(data)
1018 g.close()
1019 f.close()
1020 print "Done."