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)
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
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
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 position 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
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())
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
140 __all__
= ["Error","open","openfp"]
142 class Error(Exception):
145 _AIFC_version
= 0xA2805140L
# Version 1 of AIFF-C
147 _skiplist
= 'COMT', 'INST', 'MIDI', 'AESD', \
148 'APPL', 'NAME', 'AUTH', '(c) ', 'ANNO'
150 def _read_long(file):
152 return struct
.unpack('>l', file.read(4))[0]
156 def _read_ulong(file):
158 return struct
.unpack('>L', file.read(4))[0]
162 def _read_short(file):
164 return struct
.unpack('>h', file.read(2))[0]
168 def _read_string(file):
169 length
= ord(file.read(1))
173 data
= file.read(length
)
178 _HUGE_VAL
= 1.79769313486231e+308 # See <limits.h>
180 def _read_float(f
): # 10 bytes
181 expon
= _read_short(f
) # 2 bytes
185 expon
= expon
+ 0x8000
186 himant
= _read_ulong(f
) # 4 bytes
187 lomant
= _read_ulong(f
) # 4 bytes
188 if expon
== himant
== lomant
== 0:
190 elif expon
== 0x7FFF:
193 expon
= expon
- 16383
194 f
= (himant
* 0x100000000L
+ lomant
) * pow(2.0, expon
- 63)
197 def _write_short(f
, x
):
198 f
.write(struct
.pack('>h', x
))
200 def _write_long(f
, x
):
201 f
.write(struct
.pack('>L', x
))
203 def _write_string(f
, s
):
205 raise ValueError("string exceeds maximum pstring length")
211 def _write_float(f
, x
):
223 fmant
, expon
= math
.frexp(x
)
224 if expon
> 16384 or fmant
>= 1: # Infinity or NaN
229 expon
= expon
+ 16382
230 if expon
< 0: # denormalized
231 fmant
= math
.ldexp(fmant
, expon
)
234 fmant
= math
.ldexp(fmant
, 32)
235 fsmant
= math
.floor(fmant
)
236 himant
= long(fsmant
)
237 fmant
= math
.ldexp(fmant
- fsmant
, 32)
238 fsmant
= math
.floor(fmant
)
239 lomant
= long(fsmant
)
240 _write_short(f
, expon
)
241 _write_long(f
, himant
)
242 _write_long(f
, lomant
)
244 from chunk
import Chunk
247 # Variables used in this class:
249 # These variables are available to the user though appropriate
250 # methods of this class:
251 # _file -- the open file with methods read(), close(), and seek()
252 # set through the __init__() method
253 # _nchannels -- the number of audio channels
254 # available through the getnchannels() method
255 # _nframes -- the number of audio frames
256 # available through the getnframes() method
257 # _sampwidth -- the number of bytes per audio sample
258 # available through the getsampwidth() method
259 # _framerate -- the sampling frequency
260 # available through the getframerate() method
261 # _comptype -- the AIFF-C compression type ('NONE' if AIFF)
262 # available through the getcomptype() method
263 # _compname -- the human-readable AIFF-C compression type
264 # available through the getcomptype() method
265 # _markers -- the marks in the audio file
266 # available through the getmarkers() and getmark()
268 # _soundpos -- the position in the audio stream
269 # available through the tell() method, set through the
272 # These variables are used internally only:
273 # _version -- the AIFF-C version number
274 # _decomp -- the decompressor from builtin module cl
275 # _comm_chunk_read -- 1 iff the COMM chunk has been read
276 # _aifc -- 1 iff reading an AIFF-C file
277 # _ssnd_seek_needed -- 1 iff positioned correctly in audio
278 # file for readframes()
279 # _ssnd_chunk -- instantiation of a chunk class for the SSND chunk
280 # _framesize -- size of one frame in the file
282 def initfp(self
, file):
288 self
._file
= Chunk(file)
289 if self
._file
.getname() != 'FORM':
290 raise Error
, 'file does not start with FORM id'
291 formdata
= self
._file
.read(4)
292 if formdata
== 'AIFF':
294 elif formdata
== 'AIFC':
297 raise Error
, 'not an AIFF or AIFF-C file'
298 self
._comm
_chunk
_read
= 0
300 self
._ssnd
_seek
_needed
= 1
302 chunk
= Chunk(self
._file
)
305 chunkname
= chunk
.getname()
306 if chunkname
== 'COMM':
307 self
._read
_comm
_chunk
(chunk
)
308 self
._comm
_chunk
_read
= 1
309 elif chunkname
== 'SSND':
310 self
._ssnd
_chunk
= chunk
311 dummy
= chunk
.read(8)
312 self
._ssnd
_seek
_needed
= 0
313 elif chunkname
== 'FVER':
314 self
._version
= _read_ulong(chunk
)
315 elif chunkname
== 'MARK':
316 self
._readmark
(chunk
)
317 elif chunkname
in _skiplist
:
320 raise Error
, 'unrecognized chunk type '+chunk
.chunkname
322 if not self
._comm
_chunk
_read
or not self
._ssnd
_chunk
:
323 raise Error
, 'COMM chunk and/or SSND chunk missing'
324 if self
._aifc
and self
._decomp
:
326 params
= [cl
.ORIGINAL_FORMAT
, 0,
327 cl
.BITS_PER_COMPONENT
, self
._sampwidth
* 8,
328 cl
.FRAME_RATE
, self
._framerate
]
329 if self
._nchannels
== 1:
331 elif self
._nchannels
== 2:
332 params
[1] = cl
.STEREO_INTERLEAVED
334 raise Error
, 'cannot compress more than 2 channels'
335 self
._decomp
.SetParams(params
)
337 def __init__(self
, f
):
338 if type(f
) == type(''):
339 f
= __builtin__
.open(f
, 'rb')
340 # else, assume it is an open file object already
344 # User visible methods.
350 self
._ssnd
_seek
_needed
= 1
355 self
._decomp
.CloseDecompressor()
360 return self
._soundpos
362 def getnchannels(self
):
363 return self
._nchannels
365 def getnframes(self
):
368 def getsampwidth(self
):
369 return self
._sampwidth
371 def getframerate(self
):
372 return self
._framerate
374 def getcomptype(self
):
375 return self
._comptype
377 def getcompname(self
):
378 return self
._compname
380 ## def getversion(self):
381 ## return self._version
384 return self
.getnchannels(), self
.getsampwidth(), \
385 self
.getframerate(), self
.getnframes(), \
386 self
.getcomptype(), self
.getcompname()
388 def getmarkers(self
):
389 if len(self
._markers
) == 0:
393 def getmark(self
, id):
394 for marker
in self
._markers
:
397 raise Error
, 'marker %r does not exist' % (id,)
399 def setpos(self
, pos
):
400 if pos
< 0 or pos
> self
._nframes
:
401 raise Error
, 'position not in range'
403 self
._ssnd
_seek
_needed
= 1
405 def readframes(self
, nframes
):
406 if self
._ssnd
_seek
_needed
:
407 self
._ssnd
_chunk
.seek(0)
408 dummy
= self
._ssnd
_chunk
.read(8)
409 pos
= self
._soundpos
* self
._framesize
411 self
._ssnd
_chunk
.seek(pos
+ 8)
412 self
._ssnd
_seek
_needed
= 0
415 data
= self
._ssnd
_chunk
.read(nframes
* self
._framesize
)
416 if self
._convert
and data
:
417 data
= self
._convert
(data
)
418 self
._soundpos
= self
._soundpos
+ len(data
) / (self
._nchannels
* self
._sampwidth
)
425 def _decomp_data(self
, data
):
427 dummy
= self
._decomp
.SetParam(cl
.FRAME_BUFFER_SIZE
,
429 return self
._decomp
.Decompress(len(data
) / self
._nchannels
,
432 def _ulaw2lin(self
, data
):
434 return audioop
.ulaw2lin(data
, 2)
436 def _adpcm2lin(self
, data
):
438 if not hasattr(self
, '_adpcmstate'):
440 self
._adpcmstate
= None
441 data
, self
._adpcmstate
= audioop
.adpcm2lin(data
, 2,
445 def _read_comm_chunk(self
, chunk
):
446 self
._nchannels
= _read_short(chunk
)
447 self
._nframes
= _read_long(chunk
)
448 self
._sampwidth
= (_read_short(chunk
) + 7) / 8
449 self
._framerate
= int(_read_float(chunk
))
450 self
._framesize
= self
._nchannels
* self
._sampwidth
452 #DEBUG: SGI's soundeditor produces a bad size :-(
454 if chunk
.chunksize
== 18:
456 print 'Warning: bad COMM chunk size'
459 self
._comptype
= chunk
.read(4)
462 length
= ord(chunk
.file.read(1))
465 chunk
.chunksize
= chunk
.chunksize
+ length
466 chunk
.file.seek(-1, 1)
468 self
._compname
= _read_string(chunk
)
469 if self
._comptype
!= 'NONE':
470 if self
._comptype
== 'G722':
476 self
._convert
= self
._adpcm
2lin
477 self
._framesize
= self
._framesize
/ 4
479 # for ULAW and ALAW try Compression Library
483 if self
._comptype
== 'ULAW':
486 self
._convert
= self
._ulaw
2lin
487 self
._framesize
= self
._framesize
/ 2
491 raise Error
, 'cannot read compressed AIFF-C files'
492 if self
._comptype
== 'ULAW':
493 scheme
= cl
.G711_ULAW
494 self
._framesize
= self
._framesize
/ 2
495 elif self
._comptype
== 'ALAW':
496 scheme
= cl
.G711_ALAW
497 self
._framesize
= self
._framesize
/ 2
499 raise Error
, 'unsupported compression type'
500 self
._decomp
= cl
.OpenDecompressor(scheme
)
501 self
._convert
= self
._decomp
_data
503 self
._comptype
= 'NONE'
504 self
._compname
= 'not compressed'
506 def _readmark(self
, chunk
):
507 nmarkers
= _read_short(chunk
)
508 # Some files appear to contain invalid counts.
509 # Cope with this by testing for EOF.
511 for i
in range(nmarkers
):
512 id = _read_short(chunk
)
513 pos
= _read_long(chunk
)
514 name
= _read_string(chunk
)
516 # some files appear to have
517 # dummy markers consisting of
518 # a position 0 and name ''
519 self
._markers
.append((id, pos
, name
))
521 print 'Warning: MARK chunk contains only',
522 print len(self
._markers
),
523 if len(self
._markers
) == 1: print 'marker',
524 else: print 'markers',
525 print 'instead of', nmarkers
528 # Variables used in this class:
530 # These variables are user settable through appropriate methods
532 # _file -- the open file with methods write(), close(), tell(), seek()
533 # set through the __init__() method
534 # _comptype -- the AIFF-C compression type ('NONE' in AIFF)
535 # set through the setcomptype() or setparams() method
536 # _compname -- the human-readable AIFF-C compression type
537 # set through the setcomptype() or setparams() method
538 # _nchannels -- the number of audio channels
539 # set through the setnchannels() or setparams() method
540 # _sampwidth -- the number of bytes per audio sample
541 # set through the setsampwidth() or setparams() method
542 # _framerate -- the sampling frequency
543 # set through the setframerate() or setparams() method
544 # _nframes -- the number of audio frames written to the header
545 # set through the setnframes() or setparams() method
546 # _aifc -- whether we're writing an AIFF-C file or an AIFF file
547 # set through the aifc() method, reset through the
550 # These variables are used internally only:
551 # _version -- the AIFF-C version number
552 # _comp -- the compressor from builtin module cl
553 # _nframeswritten -- the number of audio frames actually written
554 # _datalength -- the size of the audio samples written to the header
555 # _datawritten -- the size of the audio samples actually written
557 def __init__(self
, f
):
558 if type(f
) == type(''):
560 f
= __builtin__
.open(f
, 'wb')
562 # else, assume it is an open file object already
565 if filename
[-5:] == '.aiff':
570 def initfp(self
, file):
572 self
._version
= _AIFC_version
573 self
._comptype
= 'NONE'
574 self
._compname
= 'not compressed'
581 self
._nframeswritten
= 0
582 self
._datawritten
= 0
586 self
._aifc
= 1 # AIFF-C is default
593 # User visible methods.
596 if self
._nframeswritten
:
597 raise Error
, 'cannot change parameters after starting to write'
601 if self
._nframeswritten
:
602 raise Error
, 'cannot change parameters after starting to write'
605 def setnchannels(self
, nchannels
):
606 if self
._nframeswritten
:
607 raise Error
, 'cannot change parameters after starting to write'
609 raise Error
, 'bad # of channels'
610 self
._nchannels
= nchannels
612 def getnchannels(self
):
613 if not self
._nchannels
:
614 raise Error
, 'number of channels not set'
615 return self
._nchannels
617 def setsampwidth(self
, sampwidth
):
618 if self
._nframeswritten
:
619 raise Error
, 'cannot change parameters after starting to write'
620 if sampwidth
< 1 or sampwidth
> 4:
621 raise Error
, 'bad sample width'
622 self
._sampwidth
= sampwidth
624 def getsampwidth(self
):
625 if not self
._sampwidth
:
626 raise Error
, 'sample width not set'
627 return self
._sampwidth
629 def setframerate(self
, framerate
):
630 if self
._nframeswritten
:
631 raise Error
, 'cannot change parameters after starting to write'
633 raise Error
, 'bad frame rate'
634 self
._framerate
= framerate
636 def getframerate(self
):
637 if not self
._framerate
:
638 raise Error
, 'frame rate not set'
639 return self
._framerate
641 def setnframes(self
, nframes
):
642 if self
._nframeswritten
:
643 raise Error
, 'cannot change parameters after starting to write'
644 self
._nframes
= nframes
646 def getnframes(self
):
647 return self
._nframeswritten
649 def setcomptype(self
, comptype
, compname
):
650 if self
._nframeswritten
:
651 raise Error
, 'cannot change parameters after starting to write'
652 if comptype
not in ('NONE', 'ULAW', 'ALAW', 'G722'):
653 raise Error
, 'unsupported compression type'
654 self
._comptype
= comptype
655 self
._compname
= compname
657 def getcomptype(self
):
658 return self
._comptype
660 def getcompname(self
):
661 return self
._compname
663 ## def setversion(self, version):
664 ## if self._nframeswritten:
665 ## raise Error, 'cannot change parameters after starting to write'
666 ## self._version = version
668 def setparams(self
, (nchannels
, sampwidth
, framerate
, nframes
, comptype
, compname
)):
669 if self
._nframeswritten
:
670 raise Error
, 'cannot change parameters after starting to write'
671 if comptype
not in ('NONE', 'ULAW', 'ALAW', 'G722'):
672 raise Error
, 'unsupported compression type'
673 self
.setnchannels(nchannels
)
674 self
.setsampwidth(sampwidth
)
675 self
.setframerate(framerate
)
676 self
.setnframes(nframes
)
677 self
.setcomptype(comptype
, compname
)
680 if not self
._nchannels
or not self
._sampwidth
or not self
._framerate
:
681 raise Error
, 'not all parameters set'
682 return self
._nchannels
, self
._sampwidth
, self
._framerate
, \
683 self
._nframes
, self
._comptype
, self
._compname
685 def setmark(self
, id, pos
, name
):
687 raise Error
, 'marker ID must be > 0'
689 raise Error
, 'marker position must be >= 0'
690 if type(name
) != type(''):
691 raise Error
, 'marker name must be a string'
692 for i
in range(len(self
._markers
)):
693 if id == self
._markers
[i
][0]:
694 self
._markers
[i
] = id, pos
, name
696 self
._markers
.append((id, pos
, name
))
698 def getmark(self
, id):
699 for marker
in self
._markers
:
702 raise Error
, 'marker %r does not exist' % (id,)
704 def getmarkers(self
):
705 if len(self
._markers
) == 0:
710 return self
._nframeswritten
712 def writeframesraw(self
, data
):
713 self
._ensure
_header
_written
(len(data
))
714 nframes
= len(data
) / (self
._sampwidth
* self
._nchannels
)
716 data
= self
._convert
(data
)
717 self
._file
.write(data
)
718 self
._nframeswritten
= self
._nframeswritten
+ nframes
719 self
._datawritten
= self
._datawritten
+ len(data
)
721 def writeframes(self
, data
):
722 self
.writeframesraw(data
)
723 if self
._nframeswritten
!= self
._nframes
or \
724 self
._datalength
!= self
._datawritten
:
728 self
._ensure
_header
_written
(0)
729 if self
._datawritten
& 1:
730 # quick pad to even size
731 self
._file
.write(chr(0))
732 self
._datawritten
= self
._datawritten
+ 1
734 if self
._nframeswritten
!= self
._nframes
or \
735 self
._datalength
!= self
._datawritten
or \
739 self
._comp
.CloseCompressor()
748 def _comp_data(self
, data
):
750 dummy
= self
._comp
.SetParam(cl
.FRAME_BUFFER_SIZE
, len(data
))
751 dummy
= self
._comp
.SetParam(cl
.COMPRESSED_BUFFER_SIZE
, len(data
))
752 return self
._comp
.Compress(self
._nframes
, data
)
754 def _lin2ulaw(self
, data
):
756 return audioop
.lin2ulaw(data
, 2)
758 def _lin2adpcm(self
, data
):
760 if not hasattr(self
, '_adpcmstate'):
761 self
._adpcmstate
= None
762 data
, self
._adpcmstate
= audioop
.lin2adpcm(data
, 2,
766 def _ensure_header_written(self
, datasize
):
767 if not self
._nframeswritten
:
768 if self
._comptype
in ('ULAW', 'ALAW'):
769 if not self
._sampwidth
:
771 if self
._sampwidth
!= 2:
772 raise Error
, 'sample width must be 2 when compressing with ULAW or ALAW'
773 if self
._comptype
== 'G722':
774 if not self
._sampwidth
:
776 if self
._sampwidth
!= 2:
777 raise Error
, 'sample width must be 2 when compressing with G7.22 (ADPCM)'
778 if not self
._nchannels
:
779 raise Error
, '# channels not specified'
780 if not self
._sampwidth
:
781 raise Error
, 'sample width not specified'
782 if not self
._framerate
:
783 raise Error
, 'sampling rate not specified'
784 self
._write
_header
(datasize
)
786 def _init_compression(self
):
787 if self
._comptype
== 'G722':
788 self
._convert
= self
._lin
2adpcm
793 if self
._comptype
== 'ULAW':
796 self
._convert
= self
._lin
2ulaw
800 raise Error
, 'cannot write compressed AIFF-C files'
801 if self
._comptype
== 'ULAW':
802 scheme
= cl
.G711_ULAW
803 elif self
._comptype
== 'ALAW':
804 scheme
= cl
.G711_ALAW
806 raise Error
, 'unsupported compression type'
807 self
._comp
= cl
.OpenCompressor(scheme
)
808 params
= [cl
.ORIGINAL_FORMAT
, 0,
809 cl
.BITS_PER_COMPONENT
, self
._sampwidth
* 8,
810 cl
.FRAME_RATE
, self
._framerate
,
811 cl
.FRAME_BUFFER_SIZE
, 100,
812 cl
.COMPRESSED_BUFFER_SIZE
, 100]
813 if self
._nchannels
== 1:
815 elif self
._nchannels
== 2:
816 params
[1] = cl
.STEREO_INTERLEAVED
818 raise Error
, 'cannot compress more than 2 channels'
819 self
._comp
.SetParams(params
)
820 # the compressor produces a header which we ignore
821 dummy
= self
._comp
.Compress(0, '')
822 self
._convert
= self
._comp
_data
824 def _write_header(self
, initlength
):
825 if self
._aifc
and self
._comptype
!= 'NONE':
826 self
._init
_compression
()
827 self
._file
.write('FORM')
828 if not self
._nframes
:
829 self
._nframes
= initlength
/ (self
._nchannels
* self
._sampwidth
)
830 self
._datalength
= self
._nframes
* self
._nchannels
* self
._sampwidth
831 if self
._datalength
& 1:
832 self
._datalength
= self
._datalength
+ 1
834 if self
._comptype
in ('ULAW', 'ALAW'):
835 self
._datalength
= self
._datalength
/ 2
836 if self
._datalength
& 1:
837 self
._datalength
= self
._datalength
+ 1
838 elif self
._comptype
== 'G722':
839 self
._datalength
= (self
._datalength
+ 3) / 4
840 if self
._datalength
& 1:
841 self
._datalength
= self
._datalength
+ 1
842 self
._form
_length
_pos
= self
._file
.tell()
843 commlength
= self
._write
_form
_length
(self
._datalength
)
845 self
._file
.write('AIFC')
846 self
._file
.write('FVER')
847 _write_long(self
._file
, 4)
848 _write_long(self
._file
, self
._version
)
850 self
._file
.write('AIFF')
851 self
._file
.write('COMM')
852 _write_long(self
._file
, commlength
)
853 _write_short(self
._file
, self
._nchannels
)
854 self
._nframes
_pos
= self
._file
.tell()
855 _write_long(self
._file
, self
._nframes
)
856 _write_short(self
._file
, self
._sampwidth
* 8)
857 _write_float(self
._file
, self
._framerate
)
859 self
._file
.write(self
._comptype
)
860 _write_string(self
._file
, self
._compname
)
861 self
._file
.write('SSND')
862 self
._ssnd
_length
_pos
= self
._file
.tell()
863 _write_long(self
._file
, self
._datalength
+ 8)
864 _write_long(self
._file
, 0)
865 _write_long(self
._file
, 0)
867 def _write_form_length(self
, datalength
):
869 commlength
= 18 + 5 + len(self
._compname
)
871 commlength
= commlength
+ 1
876 _write_long(self
._file
, 4 + verslength
+ self
._marklength
+ \
877 8 + commlength
+ 16 + datalength
)
880 def _patchheader(self
):
881 curpos
= self
._file
.tell()
882 if self
._datawritten
& 1:
883 datalength
= self
._datawritten
+ 1
884 self
._file
.write(chr(0))
886 datalength
= self
._datawritten
887 if datalength
== self
._datalength
and \
888 self
._nframes
== self
._nframeswritten
and \
889 self
._marklength
== 0:
890 self
._file
.seek(curpos
, 0)
892 self
._file
.seek(self
._form
_length
_pos
, 0)
893 dummy
= self
._write
_form
_length
(datalength
)
894 self
._file
.seek(self
._nframes
_pos
, 0)
895 _write_long(self
._file
, self
._nframeswritten
)
896 self
._file
.seek(self
._ssnd
_length
_pos
, 0)
897 _write_long(self
._file
, datalength
+ 8)
898 self
._file
.seek(curpos
, 0)
899 self
._nframes
= self
._nframeswritten
900 self
._datalength
= datalength
902 def _writemarkers(self
):
903 if len(self
._markers
) == 0:
905 self
._file
.write('MARK')
907 for marker
in self
._markers
:
908 id, pos
, name
= marker
909 length
= length
+ len(name
) + 1 + 6
910 if len(name
) & 1 == 0:
912 _write_long(self
._file
, length
)
913 self
._marklength
= length
+ 8
914 _write_short(self
._file
, len(self
._markers
))
915 for marker
in self
._markers
:
916 id, pos
, name
= marker
917 _write_short(self
._file
, id)
918 _write_long(self
._file
, pos
)
919 _write_string(self
._file
, name
)
921 def open(f
, mode
=None):
923 if hasattr(f
, 'mode'):
927 if mode
in ('r', 'rb'):
929 elif mode
in ('w', 'wb'):
932 raise Error
, "mode must be 'r', 'rb', 'w', or 'wb'"
934 openfp
= open # B/W compatibility
936 if __name__
== '__main__':
939 sys
.argv
.append('/usr/demos/data/audio/bach.aiff')
943 print "nchannels =", f
.getnchannels()
944 print "nframes =", f
.getnframes()
945 print "sampwidth =", f
.getsampwidth()
946 print "framerate =", f
.getframerate()
947 print "comptype =", f
.getcomptype()
948 print "compname =", f
.getcompname()
953 g
.setparams(f
.getparams())
955 data
= f
.readframes(1024)