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.
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.
30 # <version number of AIFF-C defining document> (AIFF-C only).
32 # <# of markers> (2 bytes)
34 # <marker ID> (2 bytes, must be > 0)
35 # <position> (4 bytes)
36 # <marker name> ("pstring")
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
43 # in AIFF-C files only:
44 # <compression type> (4 bytes)
45 # <human-readable version of compression type> ("pstring")
47 # <offset> (4 bytes, not used by this program)
48 # <blocksize> (4 bytes, not used by this program)
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.
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
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
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
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
115 # -- write audio frames and patch up the file header
116 # close() -- patch up the file header and close the
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
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
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
144 _AIFC_version
= 0xA2805140 # Version 1 of AIFF-C
146 _skiplist
= 'COMT', 'INST', 'MIDI', 'AESD', \
147 'APPL', 'NAME', 'AUTH', '(c) ', 'ANNO'
149 def _read_long(file):
155 x
= x
*256 + ord(byte
)
160 def _read_ulong(file):
166 x
= x
*256 + ord(byte
)
169 def _read_short(file):
175 x
= x
*256 + ord(byte
)
180 def _read_string(file):
181 length
= ord(file.read(1))
185 data
= file.read(length
)
190 _HUGE_VAL
= 1.79769313486231e+308 # See <limits.h>
192 def _read_float(f
): # 10 bytes
194 expon
= _read_short(f
) # 2 bytes
198 expon
= expon
+ 0x8000
199 himant
= _read_ulong(f
) # 4 bytes
200 lomant
= _read_ulong(f
) # 4 bytes
201 if expon
== himant
== lomant
== 0:
203 elif expon
== 0x7FFF:
206 expon
= expon
- 16383
207 f
= (himant
* 0x100000000L
+ lomant
) * pow(2.0, expon
- 63)
210 def _write_short(f
, x
):
211 d
, m
= divmod(x
, 256)
215 def _write_long(f
, x
):
220 d
, m
= divmod(x
, 256)
224 f
.write(chr(int(data
[i
])))
226 def _write_string(f
, s
):
232 def _write_float(f
, x
):
244 fmant
, expon
= math
.frexp(x
)
245 if expon
> 16384 or fmant
>= 1: # Infinity or NaN
250 expon
= expon
+ 16382
251 if expon
< 0: # denormalized
252 fmant
= math
.ldexp(fmant
, expon
)
255 fmant
= math
.ldexp(fmant
, 32)
256 fsmant
= math
.floor(fmant
)
257 himant
= long(fsmant
)
258 fmant
= math
.ldexp(fmant
- fsmant
, 32)
259 fsmant
= math
.floor(fmant
)
260 lomant
= long(fsmant
)
261 _write_short(f
, expon
)
262 _write_long(f
, himant
)
263 _write_long(f
, lomant
)
266 def __init__(self
, file):
268 self
.chunkname
= self
.file.read(4)
269 if len(self
.chunkname
) < 4:
271 self
.chunksize
= _read_long(self
.file)
273 self
.offset
= self
.file.tell()
276 self
.file.seek(self
.offset
, 0)
279 def setpos(self
, pos
):
280 if pos
< 0 or pos
> self
.chunksize
:
282 self
.file.seek(self
.offset
+ pos
, 0)
285 def read(self
, length
):
286 if self
.size_read
>= self
.chunksize
:
288 if length
> self
.chunksize
- self
.size_read
:
289 length
= self
.chunksize
- self
.size_read
290 data
= self
.file.read(length
)
291 self
.size_read
= self
.size_read
+ len(data
)
296 self
.file.seek(self
.chunksize
- self
.size_read
, 1)
298 while self
.size_read
< self
.chunksize
:
299 dummy
= self
.read(8192)
302 if self
.chunksize
& 1:
306 # Variables used in this class:
308 # These variables are available to the user though appropriate
309 # methods of this class:
310 # _file -- the open file with methods read(), close(), and seek()
311 # set through the __init__() method
312 # _nchannels -- the number of audio channels
313 # available through the getnchannels() method
314 # _nframes -- the number of audio frames
315 # available through the getnframes() method
316 # _sampwidth -- the number of bytes per audio sample
317 # available through the getsampwidth() method
318 # _framerate -- the sampling frequency
319 # available through the getframerate() method
320 # _comptype -- the AIFF-C compression type ('NONE' if AIFF)
321 # available through the getcomptype() method
322 # _compname -- the human-readable AIFF-C compression type
323 # available through the getcomptype() method
324 # _markers -- the marks in the audio file
325 # available through the getmarkers() and getmark()
327 # _soundpos -- the position in the audio stream
328 # available through the tell() method, set through the
331 # These variables are used internally only:
332 # _version -- the AIFF-C version number
333 # _decomp -- the decompressor from builtin module cl
334 # _comm_chunk_read -- 1 iff the COMM chunk has been read
335 # _aifc -- 1 iff reading an AIFF-C file
336 # _ssnd_seek_needed -- 1 iff positioned correctly in audio
337 # file for readframes()
338 # _ssnd_chunk -- instantiation of a chunk class for the SSND chunk
339 # _framesize -- size of one frame in the file
341 access _file
, _nchannels
, _nframes
, _sampwidth
, _framerate
, \
342 _comptype
, _compname
, _markers
, _soundpos
, _version
, \
343 _decomp
, _comm_chunk_read
, __aifc
, _ssnd_seek_needed
, \
344 _ssnd_chunk
, _framesize
: private
346 def initfp(self
, file):
353 form
= self
._file
.read(4)
355 raise Error
, 'file does not start with FORM id'
356 formlength
= _read_long(self
._file
)
358 raise Error
, 'invalid FORM chunk data size'
359 formdata
= self
._file
.read(4)
360 formlength
= formlength
- 4
361 if formdata
== 'AIFF':
363 elif formdata
== 'AIFC':
366 raise Error
, 'not an AIFF or AIFF-C file'
367 self
._comm
_chunk
_read
= 0
368 while formlength
> 0:
369 self
._ssnd
_seek
_needed
= 1
370 #DEBUG: SGI's soundfiler has a bug. There should
371 # be no need to check for EOF here.
373 chunk
= Chunk(self
._file
)
376 print 'Warning: FORM chunk size too large'
379 raise EOFError # different error, raise exception
380 if chunk
.chunkname
== 'COMM':
381 self
._read
_comm
_chunk
(chunk
)
382 self
._comm
_chunk
_read
= 1
383 elif chunk
.chunkname
== 'SSND':
384 self
._ssnd
_chunk
= chunk
385 dummy
= chunk
.read(8)
386 self
._ssnd
_seek
_needed
= 0
387 elif chunk
.chunkname
== 'FVER':
388 self
._version
= _read_long(chunk
)
389 elif chunk
.chunkname
== 'MARK':
390 self
._readmark
(chunk
)
391 elif chunk
.chunkname
in _skiplist
:
394 raise Error
, 'unrecognized chunk type '+chunk
.chunkname
395 formlength
= formlength
- 8 - chunk
.chunksize
396 if chunk
.chunksize
& 1:
397 formlength
= formlength
- 1
400 if not self
._comm
_chunk
_read
or not self
._ssnd
_chunk
:
401 raise Error
, 'COMM chunk and/or SSND chunk missing'
402 if self
._aifc
and self
._decomp
:
403 params
= [CL
.ORIGINAL_FORMAT
, 0,
404 CL
.BITS_PER_COMPONENT
, self
._sampwidth
* 8,
405 CL
.FRAME_RATE
, self
._framerate
]
406 if self
._nchannels
== 1:
408 elif self
._nchannels
== 2:
409 params
[1] = CL
.STEREO_INTERLEAVED
411 raise Error
, 'cannot compress more than 2 channels'
412 self
._decomp
.SetParams(params
)
414 def __init__(self
, f
):
415 if type(f
) == type(''):
416 f
= __builtin__
.open(f
, 'rb')
417 # else, assume it is an open file object already
425 # User visible methods.
431 self
._ssnd
_seek
_needed
= 1
436 self
._decomp
.CloseDecompressor()
441 return self
._soundpos
443 def getnchannels(self
):
444 return self
._nchannels
446 def getnframes(self
):
449 def getsampwidth(self
):
450 return self
._sampwidth
452 def getframerate(self
):
453 return self
._framerate
455 def getcomptype(self
):
456 return self
._comptype
458 def getcompname(self
):
459 return self
._compname
461 ## def getversion(self):
462 ## return self._version
465 return self
.getnchannels(), self
.getsampwidth(), \
466 self
.getframerate(), self
.getnframes(), \
467 self
.getcomptype(), self
.getcompname()
469 def getmarkers(self
):
470 if len(self
._markers
) == 0:
474 def getmark(self
, id):
475 for marker
in self
._markers
:
478 raise Error
, 'marker ' + `
id`
+ ' does not exist'
480 def setpos(self
, pos
):
481 if pos
< 0 or pos
> self
._nframes
:
482 raise Error
, 'position not in range'
484 self
._ssnd
_seek
_needed
= 1
486 def readframes(self
, nframes
):
487 if self
._ssnd
_seek
_needed
:
488 self
._ssnd
_chunk
.rewind()
489 dummy
= self
._ssnd
_chunk
.read(8)
490 pos
= self
._soundpos
* self
._framesize
492 self
._ssnd
_chunk
.setpos(pos
+ 8)
493 self
._ssnd
_seek
_needed
= 0
496 data
= self
._ssnd
_chunk
.read(nframes
* self
._framesize
)
497 if self
._convert
and data
:
498 data
= self
._convert
(data
)
499 self
._soundpos
= self
._soundpos
+ len(data
) / (self
._nchannels
* self
._sampwidth
)
507 def _decomp_data(self
, data
):
508 dummy
= self
._decomp
.SetParam(CL
.FRAME_BUFFER_SIZE
,
510 return self
._decomp
.Decompress(len(data
) / self
._nchannels
,
513 def _ulaw2lin(self
, data
):
515 return audioop
.ulaw2lin(data
, 2)
517 def _adpcm2lin(self
, data
):
519 if not hasattr(self
, '_adpcmstate'):
521 self
._adpcmstate
= None
522 data
, self
._adpcmstate
= audioop
.adpcm2lin(data
, 2,
526 def _read_comm_chunk(self
, chunk
):
527 self
._nchannels
= _read_short(chunk
)
528 self
._nframes
= _read_long(chunk
)
529 self
._sampwidth
= (_read_short(chunk
) + 7) / 8
530 self
._framerate
= int(_read_float(chunk
))
531 self
._framesize
= self
._nchannels
* self
._sampwidth
533 #DEBUG: SGI's soundeditor produces a bad size :-(
535 if chunk
.chunksize
== 18:
537 print 'Warning: bad COMM chunk size'
540 self
._comptype
= chunk
.read(4)
543 length
= ord(chunk
.file.read(1))
546 chunk
.chunksize
= chunk
.chunksize
+ length
547 chunk
.file.seek(-1, 1)
549 self
._compname
= _read_string(chunk
)
550 if self
._comptype
!= 'NONE':
551 if self
._comptype
== 'G722':
557 self
._convert
= self
._adpcm
2lin
558 self
._framesize
= self
._framesize
/ 4
560 # for ULAW and ALAW try Compression Library
564 if self
._comptype
== 'ULAW':
567 self
._convert
= self
._ulaw
2lin
568 self
._framesize
= self
._framesize
/ 2
572 raise Error
, 'cannot read compressed AIFF-C files'
573 if self
._comptype
== 'ULAW':
574 scheme
= CL
.G711_ULAW
575 self
._framesize
= self
._framesize
/ 2
576 elif self
._comptype
== 'ALAW':
577 scheme
= CL
.G711_ALAW
578 self
._framesize
= self
._framesize
/ 2
580 raise Error
, 'unsupported compression type'
581 self
._decomp
= cl
.OpenDecompressor(scheme
)
582 self
._convert
= self
._decomp
_data
584 self
._comptype
= 'NONE'
585 self
._compname
= 'not compressed'
587 def _readmark(self
, chunk
):
588 nmarkers
= _read_short(chunk
)
589 # Some files appear to contain invalid counts.
590 # Cope with this by testing for EOF.
592 for i
in range(nmarkers
):
593 id = _read_short(chunk
)
594 pos
= _read_long(chunk
)
595 name
= _read_string(chunk
)
597 # some files appear to have
598 # dummy markers consisting of
599 # a position 0 and name ''
600 self
._markers
.append((id, pos
, name
))
602 print 'Warning: MARK chunk contains only',
603 print len(self
._markers
),
604 if len(self
._markers
) == 1: print 'marker',
605 else: print 'markers',
606 print 'instead of', nmarkers
609 # Variables used in this class:
611 # These variables are user settable through appropriate methods
613 # _file -- the open file with methods write(), close(), tell(), seek()
614 # set through the __init__() method
615 # _comptype -- the AIFF-C compression type ('NONE' in AIFF)
616 # set through the setcomptype() or setparams() method
617 # _compname -- the human-readable AIFF-C compression type
618 # set through the setcomptype() or setparams() method
619 # _nchannels -- the number of audio channels
620 # set through the setnchannels() or setparams() method
621 # _sampwidth -- the number of bytes per audio sample
622 # set through the setsampwidth() or setparams() method
623 # _framerate -- the sampling frequency
624 # set through the setframerate() or setparams() method
625 # _nframes -- the number of audio frames written to the header
626 # set through the setnframes() or setparams() method
627 # _aifc -- whether we're writing an AIFF-C file or an AIFF file
628 # set through the aifc() method, reset through the
631 # These variables are used internally only:
632 # _version -- the AIFF-C version number
633 # _comp -- the compressor from builtin module cl
634 # _nframeswritten -- the number of audio frames actually written
635 # _datalength -- the size of the audio samples written to the header
636 # _datawritten -- the size of the audio samples actually written
638 access _file
, _comptype
, _compname
, _nchannels
, _sampwidth
, \
639 _framerate
, _nframes
, _aifc
, _version
, _comp
, \
640 _nframeswritten
, _datalength
, _datawritten
: private
642 def __init__(self
, f
):
643 if type(f
) == type(''):
645 f
= __builtin__
.open(f
, 'wb')
647 # else, assume it is an open file object already
650 if filename
[-5:] == '.aiff':
655 def initfp(self
, file):
657 self
._version
= _AIFC_version
658 self
._comptype
= 'NONE'
659 self
._compname
= 'not compressed'
666 self
._nframeswritten
= 0
667 self
._datawritten
= 0
671 self
._aifc
= 1 # AIFF-C is default
678 # User visible methods.
681 if self
._nframeswritten
:
682 raise Error
, 'cannot change parameters after starting to write'
686 if self
._nframeswritten
:
687 raise Error
, 'cannot change parameters after starting to write'
690 def setnchannels(self
, nchannels
):
691 if self
._nframeswritten
:
692 raise Error
, 'cannot change parameters after starting to write'
694 raise Error
, 'bad # of channels'
695 self
._nchannels
= nchannels
697 def getnchannels(self
):
698 if not self
._nchannels
:
699 raise Error
, 'number of channels not set'
700 return self
._nchannels
702 def setsampwidth(self
, sampwidth
):
703 if self
._nframeswritten
:
704 raise Error
, 'cannot change parameters after starting to write'
705 if sampwidth
< 1 or sampwidth
> 4:
706 raise Error
, 'bad sample width'
707 self
._sampwidth
= sampwidth
709 def getsampwidth(self
):
710 if not self
._sampwidth
:
711 raise Error
, 'sample width not set'
712 return self
._sampwidth
714 def setframerate(self
, framerate
):
715 if self
._nframeswritten
:
716 raise Error
, 'cannot change parameters after starting to write'
718 raise Error
, 'bad frame rate'
719 self
._framerate
= framerate
721 def getframerate(self
):
722 if not self
._framerate
:
723 raise Error
, 'frame rate not set'
724 return self
._framerate
726 def setnframes(self
, nframes
):
727 if self
._nframeswritten
:
728 raise Error
, 'cannot change parameters after starting to write'
729 self
._nframes
= nframes
731 def getnframes(self
):
732 return self
._nframeswritten
734 def setcomptype(self
, comptype
, compname
):
735 if self
._nframeswritten
:
736 raise Error
, 'cannot change parameters after starting to write'
737 if comptype
not in ('NONE', 'ULAW', 'ALAW', 'G722'):
738 raise Error
, 'unsupported compression type'
739 self
._comptype
= comptype
740 self
._compname
= compname
742 def getcomptype(self
):
743 return self
._comptype
745 def getcompname(self
):
746 return self
._compname
748 ## def setversion(self, version):
749 ## if self._nframeswritten:
750 ## raise Error, 'cannot change parameters after starting to write'
751 ## self._version = version
753 def setparams(self
, (nchannels
, sampwidth
, framerate
, nframes
, comptype
, compname
)):
754 if self
._nframeswritten
:
755 raise Error
, 'cannot change parameters after starting to write'
756 if comptype
not in ('NONE', 'ULAW', 'ALAW', 'G722'):
757 raise Error
, 'unsupported compression type'
758 self
.setnchannels(nchannels
)
759 self
.setsampwidth(sampwidth
)
760 self
.setframerate(framerate
)
761 self
.setnframes(nframes
)
762 self
.setcomptype(comptype
, compname
)
765 if not self
._nchannels
or not self
._sampwidth
or not self
._framerate
:
766 raise Error
, 'not all parameters set'
767 return self
._nchannels
, self
._sampwidth
, self
._framerate
, \
768 self
._nframes
, self
._comptype
, self
._compname
770 def setmark(self
, id, pos
, name
):
772 raise Error
, 'marker ID must be > 0'
774 raise Error
, 'marker position must be >= 0'
775 if type(name
) != type(''):
776 raise Error
, 'marker name must be a string'
777 for i
in range(len(self
._markers
)):
778 if id == self
._markers
[i
][0]:
779 self
._markers
[i
] = id, pos
, name
781 self
._markers
.append((id, pos
, name
))
783 def getmark(self
, id):
784 for marker
in self
._markers
:
787 raise Error
, 'marker ' + `
id`
+ ' does not exist'
789 def getmarkers(self
):
790 if len(self
._markers
) == 0:
795 return self
._nframeswritten
797 def writeframesraw(self
, data
):
798 self
._ensure
_header
_written
(len(data
))
799 nframes
= len(data
) / (self
._sampwidth
* self
._nchannels
)
801 data
= self
._convert
(data
)
802 self
._file
.write(data
)
803 self
._nframeswritten
= self
._nframeswritten
+ nframes
804 self
._datawritten
= self
._datawritten
+ len(data
)
806 def writeframes(self
, data
):
807 self
.writeframesraw(data
)
808 if self
._nframeswritten
!= self
._nframes
or \
809 self
._datalength
!= self
._datawritten
:
813 self
._ensure
_header
_written
(0)
814 if self
._datawritten
& 1:
815 # quick pad to even size
816 self
._file
.write(chr(0))
817 self
._datawritten
= self
._datawritten
+ 1
819 if self
._nframeswritten
!= self
._nframes
or \
820 self
._datalength
!= self
._datawritten
or \
824 self
._comp
.CloseCompressor()
834 def _comp_data(self
, data
):
835 dum
= self
._comp
.SetParam(CL
.FRAME_BUFFER_SIZE
, len(data
))
836 dum
= self
._comp
.SetParam(CL
.COMPRESSED_BUFFER_SIZE
, len(data
))
837 return self
._comp
.Compress(nframes
, data
)
839 def _lin2ulaw(self
, data
):
841 return audioop
.lin2ulaw(data
, 2)
843 def _lin2adpcm(self
, data
):
845 if not hasattr(self
, '_adpcmstate'):
846 self
._adpcmstate
= None
847 data
, self
._adpcmstate
= audioop
.lin2adpcm(data
, 2,
851 def _ensure_header_written(self
, datasize
):
852 if not self
._nframeswritten
:
853 if self
._comptype
in ('ULAW', 'ALAW'):
854 if not self
._sampwidth
:
856 if self
._sampwidth
!= 2:
857 raise Error
, 'sample width must be 2 when compressing with ULAW or ALAW'
858 if self
._comptype
== 'G722':
859 if not self
._sampwidth
:
861 if self
._sampwidth
!= 2:
862 raise Error
, 'sample width must be 2 when compressing with G7.22 (ADPCM)'
863 if not self
._nchannels
:
864 raise Error
, '# channels not specified'
865 if not self
._sampwidth
:
866 raise Error
, 'sample width not specified'
867 if not self
._framerate
:
868 raise Error
, 'sampling rate not specified'
869 self
._write
_header
(datasize
)
871 def _init_compression(self
):
872 if self
._comptype
== 'G722':
874 self
._convert
= self
._lin
2adpcm
879 if self
._comptype
== 'ULAW':
882 self
._convert
= self
._lin
2ulaw
886 raise Error
, 'cannot write compressed AIFF-C files'
887 if self
._comptype
== 'ULAW':
888 scheme
= CL
.G711_ULAW
889 elif self
._comptype
== 'ALAW':
890 scheme
= CL
.G711_ALAW
892 raise Error
, 'unsupported compression type'
893 self
._comp
= cl
.OpenCompressor(scheme
)
894 params
= [CL
.ORIGINAL_FORMAT
, 0,
895 CL
.BITS_PER_COMPONENT
, self
._sampwidth
* 8,
896 CL
.FRAME_RATE
, self
._framerate
,
897 CL
.FRAME_BUFFER_SIZE
, 100,
898 CL
.COMPRESSED_BUFFER_SIZE
, 100]
899 if self
._nchannels
== 1:
901 elif self
._nchannels
== 2:
902 params
[1] = CL
.STEREO_INTERLEAVED
904 raise Error
, 'cannot compress more than 2 channels'
905 self
._comp
.SetParams(params
)
906 # the compressor produces a header which we ignore
907 dummy
= self
._comp
.Compress(0, '')
908 self
._convert
= self
._comp
_data
910 def _write_header(self
, initlength
):
911 if self
._aifc
and self
._comptype
!= 'NONE':
912 self
._init
_compression
()
913 self
._file
.write('FORM')
914 if not self
._nframes
:
915 self
._nframes
= initlength
/ (self
._nchannels
* self
._sampwidth
)
916 self
._datalength
= self
._nframes
* self
._nchannels
* self
._sampwidth
917 if self
._datalength
& 1:
918 self
._datalength
= self
._datalength
+ 1
920 if self
._comptype
in ('ULAW', 'ALAW'):
921 self
._datalength
= self
._datalength
/ 2
922 if self
._datalength
& 1:
923 self
._datalength
= self
._datalength
+ 1
924 elif self
._comptype
== 'G722':
925 self
._datalength
= (self
._datalength
+ 3) / 4
926 if self
._datalength
& 1:
927 self
._datalength
= self
._datalength
+ 1
928 self
._form
_length
_pos
= self
._file
.tell()
929 commlength
= self
._write
_form
_length
(self
._datalength
)
931 self
._file
.write('AIFC')
932 self
._file
.write('FVER')
933 _write_long(self
._file
, 4)
934 _write_long(self
._file
, self
._version
)
936 self
._file
.write('AIFF')
937 self
._file
.write('COMM')
938 _write_long(self
._file
, commlength
)
939 _write_short(self
._file
, self
._nchannels
)
940 self
._nframes
_pos
= self
._file
.tell()
941 _write_long(self
._file
, self
._nframes
)
942 _write_short(self
._file
, self
._sampwidth
* 8)
943 _write_float(self
._file
, self
._framerate
)
945 self
._file
.write(self
._comptype
)
946 _write_string(self
._file
, self
._compname
)
947 self
._file
.write('SSND')
948 self
._ssnd
_length
_pos
= self
._file
.tell()
949 _write_long(self
._file
, self
._datalength
+ 8)
950 _write_long(self
._file
, 0)
951 _write_long(self
._file
, 0)
953 def _write_form_length(self
, datalength
):
955 commlength
= 18 + 5 + len(self
._compname
)
957 commlength
= commlength
+ 1
962 _write_long(self
._file
, 4 + verslength
+ self
._marklength
+ \
963 8 + commlength
+ 16 + datalength
)
966 def _patchheader(self
):
967 curpos
= self
._file
.tell()
968 if self
._datawritten
& 1:
969 datalength
= self
._datawritten
+ 1
970 self
._file
.write(chr(0))
972 datalength
= self
._datawritten
973 if datalength
== self
._datalength
and \
974 self
._nframes
== self
._nframeswritten
and \
975 self
._marklength
== 0:
976 self
._file
.seek(curpos
, 0)
978 self
._file
.seek(self
._form
_length
_pos
, 0)
979 dummy
= self
._write
_form
_length
(datalength
)
980 self
._file
.seek(self
._nframes
_pos
, 0)
981 _write_long(self
._file
, self
._nframeswritten
)
982 self
._file
.seek(self
._ssnd
_length
_pos
, 0)
983 _write_long(self
._file
, datalength
+ 8)
984 self
._file
.seek(curpos
, 0)
985 self
._nframes
= self
._nframeswritten
986 self
._datalength
= datalength
988 def _writemarkers(self
):
989 if len(self
._markers
) == 0:
991 self
._file
.write('MARK')
993 for marker
in self
._markers
:
994 id, pos
, name
= marker
995 length
= length
+ len(name
) + 1 + 6
996 if len(name
) & 1 == 0:
998 _write_long(self
._file
, length
)
999 self
._marklength
= length
+ 8
1000 _write_short(self
._file
, len(self
._markers
))
1001 for marker
in self
._markers
:
1002 id, pos
, name
= marker
1003 _write_short(self
._file
, id)
1004 _write_long(self
._file
, pos
)
1005 _write_string(self
._file
, name
)
1011 return Aifc_write(f
)
1013 raise Error
, "mode must be 'r' or 'w'"
1015 openfp
= open # B/W compatibility