Ditched '_find_SET()', since it was a no-value-added wrapper around
[python/dscho.git] / Lib / aifc.py
blobf7288d5aa6082f86f4aa5861a4340af704214564
1 """Stuff to parse AIFF-C and AIFF files.
3 Unless explicitly stated otherwise, the description below is true
4 both for AIFF-C files and AIFF files.
6 An AIFF-C file has the following structure.
8 +-----------------+
9 | FORM |
10 +-----------------+
11 | <size> |
12 +----+------------+
13 | | AIFC |
14 | +------------+
15 | | <chunks> |
16 | | . |
17 | | . |
18 | | . |
19 +----+------------+
21 An AIFF file has the string "AIFF" instead of "AIFC".
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.
27 The following chunk types are recognized.
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>
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.
54 Usage.
56 Reading AIFF files:
57 f = aifc.open(file, 'r')
58 where file is either the name of a file or an open file pointer.
59 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.
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
82 close() -- close the instance (make it unusable)
83 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.
86 The close() method is called automatically when the class instance
87 is destroyed.
89 Writing AIFF files:
90 f = aifc.open(file, 'w')
91 where file is either the name of a file or an open file pointer.
92 The open file pointer must have methods write(), tell(), seek(), and
93 close().
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(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
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
123 compression type, and then write audio frames using writeframesraw.
124 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.
128 The close() method is called automatically when the class instance
129 is destroyed.
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.
137 import struct
138 import __builtin__
140 Error = 'aifc.Error'
142 _AIFC_version = 0xA2805140 # Version 1 of AIFF-C
144 _skiplist = 'COMT', 'INST', 'MIDI', 'AESD', \
145 'APPL', 'NAME', 'AUTH', '(c) ', 'ANNO'
147 def _read_long(file):
148 try:
149 return struct.unpack('>l', file.read(4))[0]
150 except struct.error:
151 raise EOFError
153 def _read_ulong(file):
154 try:
155 return struct.unpack('>L', file.read(4))[0]
156 except struct.error:
157 raise EOFError
159 def _read_short(file):
160 try:
161 return struct.unpack('>h', file.read(2))[0]
162 except struct.error:
163 raise EOFError
165 def _read_string(file):
166 length = ord(file.read(1))
167 if length == 0:
168 data = ''
169 else:
170 data = file.read(length)
171 if length & 1 == 0:
172 dummy = file.read(1)
173 return data
175 _HUGE_VAL = 1.79769313486231e+308 # See <limits.h>
177 def _read_float(f): # 10 bytes
178 import math
179 expon = _read_short(f) # 2 bytes
180 sign = 1
181 if expon < 0:
182 sign = -1
183 expon = expon + 0x8000
184 himant = _read_ulong(f) # 4 bytes
185 lomant = _read_ulong(f) # 4 bytes
186 if expon == himant == lomant == 0:
187 f = 0.0
188 elif expon == 0x7FFF:
189 f = _HUGE_VAL
190 else:
191 expon = expon - 16383
192 f = (himant * 0x100000000L + lomant) * pow(2.0, expon - 63)
193 return sign * f
195 def _write_short(f, x):
196 f.write(struct.pack('>h', x))
198 def _write_long(f, x):
199 f.write(struct.pack('>L', x))
201 def _write_string(f, s):
202 f.write(chr(len(s)))
203 f.write(s)
204 if len(s) & 1 == 0:
205 f.write(chr(0))
207 def _write_float(f, x):
208 import math
209 if x < 0:
210 sign = 0x8000
211 x = x * -1
212 else:
213 sign = 0
214 if x == 0:
215 expon = 0
216 himant = 0
217 lomant = 0
218 else:
219 fmant, expon = math.frexp(x)
220 if expon > 16384 or fmant >= 1: # Infinity or NaN
221 expon = sign|0x7FFF
222 himant = 0
223 lomant = 0
224 else: # Finite
225 expon = expon + 16382
226 if expon < 0: # denormalized
227 fmant = math.ldexp(fmant, expon)
228 expon = 0
229 expon = expon | sign
230 fmant = math.ldexp(fmant, 32)
231 fsmant = math.floor(fmant)
232 himant = long(fsmant)
233 fmant = math.ldexp(fmant - fsmant, 32)
234 fsmant = math.floor(fmant)
235 lomant = long(fsmant)
236 _write_short(f, expon)
237 _write_long(f, himant)
238 _write_long(f, lomant)
240 from chunk import Chunk
242 class Aifc_read:
243 # Variables used in this class:
245 # These variables are available to the user though appropriate
246 # methods of this class:
247 # _file -- the open file with methods read(), close(), and seek()
248 # set through the __init__() method
249 # _nchannels -- the number of audio channels
250 # available through the getnchannels() method
251 # _nframes -- the number of audio frames
252 # available through the getnframes() method
253 # _sampwidth -- the number of bytes per audio sample
254 # available through the getsampwidth() method
255 # _framerate -- the sampling frequency
256 # available through the getframerate() method
257 # _comptype -- the AIFF-C compression type ('NONE' if AIFF)
258 # available through the getcomptype() method
259 # _compname -- the human-readable AIFF-C compression type
260 # available through the getcomptype() method
261 # _markers -- the marks in the audio file
262 # available through the getmarkers() and getmark()
263 # methods
264 # _soundpos -- the position in the audio stream
265 # available through the tell() method, set through the
266 # setpos() method
268 # These variables are used internally only:
269 # _version -- the AIFF-C version number
270 # _decomp -- the decompressor from builtin module cl
271 # _comm_chunk_read -- 1 iff the COMM chunk has been read
272 # _aifc -- 1 iff reading an AIFF-C file
273 # _ssnd_seek_needed -- 1 iff positioned correctly in audio
274 # file for readframes()
275 # _ssnd_chunk -- instantiation of a chunk class for the SSND chunk
276 # _framesize -- size of one frame in the file
278 def initfp(self, file):
279 self._version = 0
280 self._decomp = None
281 self._convert = None
282 self._markers = []
283 self._soundpos = 0
284 self._file = Chunk(file)
285 if self._file.getname() != 'FORM':
286 raise Error, 'file does not start with FORM id'
287 formdata = self._file.read(4)
288 if formdata == 'AIFF':
289 self._aifc = 0
290 elif formdata == 'AIFC':
291 self._aifc = 1
292 else:
293 raise Error, 'not an AIFF or AIFF-C file'
294 self._comm_chunk_read = 0
295 while 1:
296 self._ssnd_seek_needed = 1
297 try:
298 chunk = Chunk(self._file)
299 except EOFError:
300 break
301 chunkname = chunk.getname()
302 if chunkname == 'COMM':
303 self._read_comm_chunk(chunk)
304 self._comm_chunk_read = 1
305 elif chunkname == 'SSND':
306 self._ssnd_chunk = chunk
307 dummy = chunk.read(8)
308 self._ssnd_seek_needed = 0
309 elif chunkname == 'FVER':
310 self._version = _read_long(chunk)
311 elif chunkname == 'MARK':
312 self._readmark(chunk)
313 elif chunkname in _skiplist:
314 pass
315 else:
316 raise Error, 'unrecognized chunk type '+chunk.chunkname
317 chunk.skip()
318 if not self._comm_chunk_read or not self._ssnd_chunk:
319 raise Error, 'COMM chunk and/or SSND chunk missing'
320 if self._aifc and self._decomp:
321 import cl
322 params = [cl.ORIGINAL_FORMAT, 0,
323 cl.BITS_PER_COMPONENT, self._sampwidth * 8,
324 cl.FRAME_RATE, self._framerate]
325 if self._nchannels == 1:
326 params[1] = cl.MONO
327 elif self._nchannels == 2:
328 params[1] = cl.STEREO_INTERLEAVED
329 else:
330 raise Error, 'cannot compress more than 2 channels'
331 self._decomp.SetParams(params)
333 def __init__(self, f):
334 if type(f) == type(''):
335 f = __builtin__.open(f, 'rb')
336 # else, assume it is an open file object already
337 self.initfp(f)
340 # User visible methods.
342 def getfp(self):
343 return self._file
345 def rewind(self):
346 self._ssnd_seek_needed = 1
347 self._soundpos = 0
349 def close(self):
350 if self._decomp:
351 self._decomp.CloseDecompressor()
352 self._decomp = None
353 self._file = None
355 def tell(self):
356 return self._soundpos
358 def getnchannels(self):
359 return self._nchannels
361 def getnframes(self):
362 return self._nframes
364 def getsampwidth(self):
365 return self._sampwidth
367 def getframerate(self):
368 return self._framerate
370 def getcomptype(self):
371 return self._comptype
373 def getcompname(self):
374 return self._compname
376 ## def getversion(self):
377 ## return self._version
379 def getparams(self):
380 return self.getnchannels(), self.getsampwidth(), \
381 self.getframerate(), self.getnframes(), \
382 self.getcomptype(), self.getcompname()
384 def getmarkers(self):
385 if len(self._markers) == 0:
386 return None
387 return self._markers
389 def getmark(self, id):
390 for marker in self._markers:
391 if id == marker[0]:
392 return marker
393 raise Error, 'marker ' + `id` + ' does not exist'
395 def setpos(self, pos):
396 if pos < 0 or pos > self._nframes:
397 raise Error, 'position not in range'
398 self._soundpos = pos
399 self._ssnd_seek_needed = 1
401 def readframes(self, nframes):
402 if self._ssnd_seek_needed:
403 self._ssnd_chunk.seek(0)
404 dummy = self._ssnd_chunk.read(8)
405 pos = self._soundpos * self._framesize
406 if pos:
407 self._ssnd_chunk.setpos(pos + 8)
408 self._ssnd_seek_needed = 0
409 if nframes == 0:
410 return ''
411 data = self._ssnd_chunk.read(nframes * self._framesize)
412 if self._convert and data:
413 data = self._convert(data)
414 self._soundpos = self._soundpos + len(data) / (self._nchannels * self._sampwidth)
415 return data
418 # Internal methods.
421 def _decomp_data(self, data):
422 import cl
423 dummy = self._decomp.SetParam(cl.FRAME_BUFFER_SIZE,
424 len(data) * 2)
425 return self._decomp.Decompress(len(data) / self._nchannels,
426 data)
428 def _ulaw2lin(self, data):
429 import audioop
430 return audioop.ulaw2lin(data, 2)
432 def _adpcm2lin(self, data):
433 import audioop
434 if not hasattr(self, '_adpcmstate'):
435 # first time
436 self._adpcmstate = None
437 data, self._adpcmstate = audioop.adpcm2lin(data, 2,
438 self._adpcmstate)
439 return data
441 def _read_comm_chunk(self, chunk):
442 self._nchannels = _read_short(chunk)
443 self._nframes = _read_long(chunk)
444 self._sampwidth = (_read_short(chunk) + 7) / 8
445 self._framerate = int(_read_float(chunk))
446 self._framesize = self._nchannels * self._sampwidth
447 if self._aifc:
448 #DEBUG: SGI's soundeditor produces a bad size :-(
449 kludge = 0
450 if chunk.chunksize == 18:
451 kludge = 1
452 print 'Warning: bad COMM chunk size'
453 chunk.chunksize = 23
454 #DEBUG end
455 self._comptype = chunk.read(4)
456 #DEBUG start
457 if kludge:
458 length = ord(chunk.file.read(1))
459 if length & 1 == 0:
460 length = length + 1
461 chunk.chunksize = chunk.chunksize + length
462 chunk.file.seek(-1, 1)
463 #DEBUG end
464 self._compname = _read_string(chunk)
465 if self._comptype != 'NONE':
466 if self._comptype == 'G722':
467 try:
468 import audioop
469 except ImportError:
470 pass
471 else:
472 self._convert = self._adpcm2lin
473 self._framesize = self._framesize / 4
474 return
475 # for ULAW and ALAW try Compression Library
476 try:
477 import cl
478 except ImportError:
479 if self._comptype == 'ULAW':
480 try:
481 import audioop
482 self._convert = self._ulaw2lin
483 self._framesize = self._framesize / 2
484 return
485 except ImportError:
486 pass
487 raise Error, 'cannot read compressed AIFF-C files'
488 if self._comptype == 'ULAW':
489 scheme = cl.G711_ULAW
490 self._framesize = self._framesize / 2
491 elif self._comptype == 'ALAW':
492 scheme = cl.G711_ALAW
493 self._framesize = self._framesize / 2
494 else:
495 raise Error, 'unsupported compression type'
496 self._decomp = cl.OpenDecompressor(scheme)
497 self._convert = self._decomp_data
498 else:
499 self._comptype = 'NONE'
500 self._compname = 'not compressed'
502 def _readmark(self, chunk):
503 nmarkers = _read_short(chunk)
504 # Some files appear to contain invalid counts.
505 # Cope with this by testing for EOF.
506 try:
507 for i in range(nmarkers):
508 id = _read_short(chunk)
509 pos = _read_long(chunk)
510 name = _read_string(chunk)
511 if pos or name:
512 # some files appear to have
513 # dummy markers consisting of
514 # a position 0 and name ''
515 self._markers.append((id, pos, name))
516 except EOFError:
517 print 'Warning: MARK chunk contains only',
518 print len(self._markers),
519 if len(self._markers) == 1: print 'marker',
520 else: print 'markers',
521 print 'instead of', nmarkers
523 class Aifc_write:
524 # Variables used in this class:
526 # These variables are user settable through appropriate methods
527 # of this class:
528 # _file -- the open file with methods write(), close(), tell(), seek()
529 # set through the __init__() method
530 # _comptype -- the AIFF-C compression type ('NONE' in AIFF)
531 # set through the setcomptype() or setparams() method
532 # _compname -- the human-readable AIFF-C compression type
533 # set through the setcomptype() or setparams() method
534 # _nchannels -- the number of audio channels
535 # set through the setnchannels() or setparams() method
536 # _sampwidth -- the number of bytes per audio sample
537 # set through the setsampwidth() or setparams() method
538 # _framerate -- the sampling frequency
539 # set through the setframerate() or setparams() method
540 # _nframes -- the number of audio frames written to the header
541 # set through the setnframes() or setparams() method
542 # _aifc -- whether we're writing an AIFF-C file or an AIFF file
543 # set through the aifc() method, reset through the
544 # aiff() method
546 # These variables are used internally only:
547 # _version -- the AIFF-C version number
548 # _comp -- the compressor from builtin module cl
549 # _nframeswritten -- the number of audio frames actually written
550 # _datalength -- the size of the audio samples written to the header
551 # _datawritten -- the size of the audio samples actually written
553 def __init__(self, f):
554 if type(f) == type(''):
555 filename = f
556 f = __builtin__.open(f, 'wb')
557 else:
558 # else, assume it is an open file object already
559 filename = '???'
560 self.initfp(f)
561 if filename[-5:] == '.aiff':
562 self._aifc = 0
563 else:
564 self._aifc = 1
566 def initfp(self, file):
567 self._file = file
568 self._version = _AIFC_version
569 self._comptype = 'NONE'
570 self._compname = 'not compressed'
571 self._comp = None
572 self._convert = None
573 self._nchannels = 0
574 self._sampwidth = 0
575 self._framerate = 0
576 self._nframes = 0
577 self._nframeswritten = 0
578 self._datawritten = 0
579 self._datalength = 0
580 self._markers = []
581 self._marklength = 0
582 self._aifc = 1 # AIFF-C is default
584 def __del__(self):
585 if self._file:
586 self.close()
589 # User visible methods.
591 def aiff(self):
592 if self._nframeswritten:
593 raise Error, 'cannot change parameters after starting to write'
594 self._aifc = 0
596 def aifc(self):
597 if self._nframeswritten:
598 raise Error, 'cannot change parameters after starting to write'
599 self._aifc = 1
601 def setnchannels(self, nchannels):
602 if self._nframeswritten:
603 raise Error, 'cannot change parameters after starting to write'
604 if nchannels < 1:
605 raise Error, 'bad # of channels'
606 self._nchannels = nchannels
608 def getnchannels(self):
609 if not self._nchannels:
610 raise Error, 'number of channels not set'
611 return self._nchannels
613 def setsampwidth(self, sampwidth):
614 if self._nframeswritten:
615 raise Error, 'cannot change parameters after starting to write'
616 if sampwidth < 1 or sampwidth > 4:
617 raise Error, 'bad sample width'
618 self._sampwidth = sampwidth
620 def getsampwidth(self):
621 if not self._sampwidth:
622 raise Error, 'sample width not set'
623 return self._sampwidth
625 def setframerate(self, framerate):
626 if self._nframeswritten:
627 raise Error, 'cannot change parameters after starting to write'
628 if framerate <= 0:
629 raise Error, 'bad frame rate'
630 self._framerate = framerate
632 def getframerate(self):
633 if not self._framerate:
634 raise Error, 'frame rate not set'
635 return self._framerate
637 def setnframes(self, nframes):
638 if self._nframeswritten:
639 raise Error, 'cannot change parameters after starting to write'
640 self._nframes = nframes
642 def getnframes(self):
643 return self._nframeswritten
645 def setcomptype(self, comptype, compname):
646 if self._nframeswritten:
647 raise Error, 'cannot change parameters after starting to write'
648 if comptype not in ('NONE', 'ULAW', 'ALAW', 'G722'):
649 raise Error, 'unsupported compression type'
650 self._comptype = comptype
651 self._compname = compname
653 def getcomptype(self):
654 return self._comptype
656 def getcompname(self):
657 return self._compname
659 ## def setversion(self, version):
660 ## if self._nframeswritten:
661 ## raise Error, 'cannot change parameters after starting to write'
662 ## self._version = version
664 def setparams(self, (nchannels, sampwidth, framerate, nframes, comptype, compname)):
665 if self._nframeswritten:
666 raise Error, 'cannot change parameters after starting to write'
667 if comptype not in ('NONE', 'ULAW', 'ALAW', 'G722'):
668 raise Error, 'unsupported compression type'
669 self.setnchannels(nchannels)
670 self.setsampwidth(sampwidth)
671 self.setframerate(framerate)
672 self.setnframes(nframes)
673 self.setcomptype(comptype, compname)
675 def getparams(self):
676 if not self._nchannels or not self._sampwidth or not self._framerate:
677 raise Error, 'not all parameters set'
678 return self._nchannels, self._sampwidth, self._framerate, \
679 self._nframes, self._comptype, self._compname
681 def setmark(self, id, pos, name):
682 if id <= 0:
683 raise Error, 'marker ID must be > 0'
684 if pos < 0:
685 raise Error, 'marker position must be >= 0'
686 if type(name) != type(''):
687 raise Error, 'marker name must be a string'
688 for i in range(len(self._markers)):
689 if id == self._markers[i][0]:
690 self._markers[i] = id, pos, name
691 return
692 self._markers.append((id, pos, name))
694 def getmark(self, id):
695 for marker in self._markers:
696 if id == marker[0]:
697 return marker
698 raise Error, 'marker ' + `id` + ' does not exist'
700 def getmarkers(self):
701 if len(self._markers) == 0:
702 return None
703 return self._markers
705 def tell(self):
706 return self._nframeswritten
708 def writeframesraw(self, data):
709 self._ensure_header_written(len(data))
710 nframes = len(data) / (self._sampwidth * self._nchannels)
711 if self._convert:
712 data = self._convert(data)
713 self._file.write(data)
714 self._nframeswritten = self._nframeswritten + nframes
715 self._datawritten = self._datawritten + len(data)
717 def writeframes(self, data):
718 self.writeframesraw(data)
719 if self._nframeswritten != self._nframes or \
720 self._datalength != self._datawritten:
721 self._patchheader()
723 def close(self):
724 self._ensure_header_written(0)
725 if self._datawritten & 1:
726 # quick pad to even size
727 self._file.write(chr(0))
728 self._datawritten = self._datawritten + 1
729 self._writemarkers()
730 if self._nframeswritten != self._nframes or \
731 self._datalength != self._datawritten or \
732 self._marklength:
733 self._patchheader()
734 if self._comp:
735 self._comp.CloseCompressor()
736 self._comp = None
737 self._file.flush()
738 self._file = None
741 # Internal methods.
744 def _comp_data(self, data):
745 import cl
746 dum = self._comp.SetParam(cl.FRAME_BUFFER_SIZE, len(data))
747 dum = self._comp.SetParam(cl.COMPRESSED_BUFFER_SIZE, len(data))
748 return self._comp.Compress(self._nframes, data)
750 def _lin2ulaw(self, data):
751 import audioop
752 return audioop.lin2ulaw(data, 2)
754 def _lin2adpcm(self, data):
755 import audioop
756 if not hasattr(self, '_adpcmstate'):
757 self._adpcmstate = None
758 data, self._adpcmstate = audioop.lin2adpcm(data, 2,
759 self._adpcmstate)
760 return data
762 def _ensure_header_written(self, datasize):
763 if not self._nframeswritten:
764 if self._comptype in ('ULAW', 'ALAW'):
765 if not self._sampwidth:
766 self._sampwidth = 2
767 if self._sampwidth != 2:
768 raise Error, 'sample width must be 2 when compressing with ULAW or ALAW'
769 if self._comptype == 'G722':
770 if not self._sampwidth:
771 self._sampwidth = 2
772 if self._sampwidth != 2:
773 raise Error, 'sample width must be 2 when compressing with G7.22 (ADPCM)'
774 if not self._nchannels:
775 raise Error, '# channels not specified'
776 if not self._sampwidth:
777 raise Error, 'sample width not specified'
778 if not self._framerate:
779 raise Error, 'sampling rate not specified'
780 self._write_header(datasize)
782 def _init_compression(self):
783 if self._comptype == 'G722':
784 import audioop
785 self._convert = self._lin2adpcm
786 return
787 try:
788 import cl
789 except ImportError:
790 if self._comptype == 'ULAW':
791 try:
792 import audioop
793 self._convert = self._lin2ulaw
794 return
795 except ImportError:
796 pass
797 raise Error, 'cannot write compressed AIFF-C files'
798 if self._comptype == 'ULAW':
799 scheme = cl.G711_ULAW
800 elif self._comptype == 'ALAW':
801 scheme = cl.G711_ALAW
802 else:
803 raise Error, 'unsupported compression type'
804 self._comp = cl.OpenCompressor(scheme)
805 params = [cl.ORIGINAL_FORMAT, 0,
806 cl.BITS_PER_COMPONENT, self._sampwidth * 8,
807 cl.FRAME_RATE, self._framerate,
808 cl.FRAME_BUFFER_SIZE, 100,
809 cl.COMPRESSED_BUFFER_SIZE, 100]
810 if self._nchannels == 1:
811 params[1] = cl.MONO
812 elif self._nchannels == 2:
813 params[1] = cl.STEREO_INTERLEAVED
814 else:
815 raise Error, 'cannot compress more than 2 channels'
816 self._comp.SetParams(params)
817 # the compressor produces a header which we ignore
818 dummy = self._comp.Compress(0, '')
819 self._convert = self._comp_data
821 def _write_header(self, initlength):
822 if self._aifc and self._comptype != 'NONE':
823 self._init_compression()
824 self._file.write('FORM')
825 if not self._nframes:
826 self._nframes = initlength / (self._nchannels * self._sampwidth)
827 self._datalength = self._nframes * self._nchannels * self._sampwidth
828 if self._datalength & 1:
829 self._datalength = self._datalength + 1
830 if self._aifc:
831 if self._comptype in ('ULAW', 'ALAW'):
832 self._datalength = self._datalength / 2
833 if self._datalength & 1:
834 self._datalength = self._datalength + 1
835 elif self._comptype == 'G722':
836 self._datalength = (self._datalength + 3) / 4
837 if self._datalength & 1:
838 self._datalength = self._datalength + 1
839 self._form_length_pos = self._file.tell()
840 commlength = self._write_form_length(self._datalength)
841 if self._aifc:
842 self._file.write('AIFC')
843 self._file.write('FVER')
844 _write_long(self._file, 4)
845 _write_long(self._file, self._version)
846 else:
847 self._file.write('AIFF')
848 self._file.write('COMM')
849 _write_long(self._file, commlength)
850 _write_short(self._file, self._nchannels)
851 self._nframes_pos = self._file.tell()
852 _write_long(self._file, self._nframes)
853 _write_short(self._file, self._sampwidth * 8)
854 _write_float(self._file, self._framerate)
855 if self._aifc:
856 self._file.write(self._comptype)
857 _write_string(self._file, self._compname)
858 self._file.write('SSND')
859 self._ssnd_length_pos = self._file.tell()
860 _write_long(self._file, self._datalength + 8)
861 _write_long(self._file, 0)
862 _write_long(self._file, 0)
864 def _write_form_length(self, datalength):
865 if self._aifc:
866 commlength = 18 + 5 + len(self._compname)
867 if commlength & 1:
868 commlength = commlength + 1
869 verslength = 12
870 else:
871 commlength = 18
872 verslength = 0
873 _write_long(self._file, 4 + verslength + self._marklength + \
874 8 + commlength + 16 + datalength)
875 return commlength
877 def _patchheader(self):
878 curpos = self._file.tell()
879 if self._datawritten & 1:
880 datalength = self._datawritten + 1
881 self._file.write(chr(0))
882 else:
883 datalength = self._datawritten
884 if datalength == self._datalength and \
885 self._nframes == self._nframeswritten and \
886 self._marklength == 0:
887 self._file.seek(curpos, 0)
888 return
889 self._file.seek(self._form_length_pos, 0)
890 dummy = self._write_form_length(datalength)
891 self._file.seek(self._nframes_pos, 0)
892 _write_long(self._file, self._nframeswritten)
893 self._file.seek(self._ssnd_length_pos, 0)
894 _write_long(self._file, datalength + 8)
895 self._file.seek(curpos, 0)
896 self._nframes = self._nframeswritten
897 self._datalength = datalength
899 def _writemarkers(self):
900 if len(self._markers) == 0:
901 return
902 self._file.write('MARK')
903 length = 2
904 for marker in self._markers:
905 id, pos, name = marker
906 length = length + len(name) + 1 + 6
907 if len(name) & 1 == 0:
908 length = length + 1
909 _write_long(self._file, length)
910 self._marklength = length + 8
911 _write_short(self._file, len(self._markers))
912 for marker in self._markers:
913 id, pos, name = marker
914 _write_short(self._file, id)
915 _write_long(self._file, pos)
916 _write_string(self._file, name)
918 def open(f, mode=None):
919 if mode is None:
920 if hasattr(f, 'mode'):
921 mode = f.mode
922 else:
923 mode = 'rb'
924 if mode in ('r', 'rb'):
925 return Aifc_read(f)
926 elif mode in ('w', 'wb'):
927 return Aifc_write(f)
928 else:
929 raise Error, "mode must be 'r', 'rb', 'w', or 'wb'"
931 openfp = open # B/W compatibility
933 if __name__ == '__main__':
934 import sys
935 if not sys.argv[1:]:
936 sys.argv.append('/usr/demos/data/audio/bach.aiff')
937 fn = sys.argv[1]
938 f = open(fn, 'r')
939 print "Reading", fn
940 print "nchannels =", f.getnchannels()
941 print "nframes =", f.getnframes()
942 print "sampwidth =", f.getsampwidth()
943 print "framerate =", f.getframerate()
944 print "comptype =", f.getcomptype()
945 print "compname =", f.getcompname()
946 if sys.argv[2:]:
947 gn = sys.argv[2]
948 print "Writing", gn
949 g = open(gn, 'w')
950 g.setparams(f.getparams())
951 while 1:
952 data = f.readframes(1024)
953 if not data:
954 break
955 g.writeframes(data)
956 g.close()
957 f.close()
958 print "Done."