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
, info
):
669 nchannels
, sampwidth
, framerate
, nframes
, comptype
, compname
= info
670 if self
._nframeswritten
:
671 raise Error
, 'cannot change parameters after starting to write'
672 if comptype
not in ('NONE', 'ULAW', 'ALAW', 'G722'):
673 raise Error
, 'unsupported compression type'
674 self
.setnchannels(nchannels
)
675 self
.setsampwidth(sampwidth
)
676 self
.setframerate(framerate
)
677 self
.setnframes(nframes
)
678 self
.setcomptype(comptype
, compname
)
681 if not self
._nchannels
or not self
._sampwidth
or not self
._framerate
:
682 raise Error
, 'not all parameters set'
683 return self
._nchannels
, self
._sampwidth
, self
._framerate
, \
684 self
._nframes
, self
._comptype
, self
._compname
686 def setmark(self
, id, pos
, name
):
688 raise Error
, 'marker ID must be > 0'
690 raise Error
, 'marker position must be >= 0'
691 if type(name
) != type(''):
692 raise Error
, 'marker name must be a string'
693 for i
in range(len(self
._markers
)):
694 if id == self
._markers
[i
][0]:
695 self
._markers
[i
] = id, pos
, name
697 self
._markers
.append((id, pos
, name
))
699 def getmark(self
, id):
700 for marker
in self
._markers
:
703 raise Error
, 'marker %r does not exist' % (id,)
705 def getmarkers(self
):
706 if len(self
._markers
) == 0:
711 return self
._nframeswritten
713 def writeframesraw(self
, data
):
714 self
._ensure
_header
_written
(len(data
))
715 nframes
= len(data
) / (self
._sampwidth
* self
._nchannels
)
717 data
= self
._convert
(data
)
718 self
._file
.write(data
)
719 self
._nframeswritten
= self
._nframeswritten
+ nframes
720 self
._datawritten
= self
._datawritten
+ len(data
)
722 def writeframes(self
, data
):
723 self
.writeframesraw(data
)
724 if self
._nframeswritten
!= self
._nframes
or \
725 self
._datalength
!= self
._datawritten
:
729 self
._ensure
_header
_written
(0)
730 if self
._datawritten
& 1:
731 # quick pad to even size
732 self
._file
.write(chr(0))
733 self
._datawritten
= self
._datawritten
+ 1
735 if self
._nframeswritten
!= self
._nframes
or \
736 self
._datalength
!= self
._datawritten
or \
740 self
._comp
.CloseCompressor()
749 def _comp_data(self
, data
):
751 dummy
= self
._comp
.SetParam(cl
.FRAME_BUFFER_SIZE
, len(data
))
752 dummy
= self
._comp
.SetParam(cl
.COMPRESSED_BUFFER_SIZE
, len(data
))
753 return self
._comp
.Compress(self
._nframes
, data
)
755 def _lin2ulaw(self
, data
):
757 return audioop
.lin2ulaw(data
, 2)
759 def _lin2adpcm(self
, data
):
761 if not hasattr(self
, '_adpcmstate'):
762 self
._adpcmstate
= None
763 data
, self
._adpcmstate
= audioop
.lin2adpcm(data
, 2,
767 def _ensure_header_written(self
, datasize
):
768 if not self
._nframeswritten
:
769 if self
._comptype
in ('ULAW', 'ALAW'):
770 if not self
._sampwidth
:
772 if self
._sampwidth
!= 2:
773 raise Error
, 'sample width must be 2 when compressing with ULAW or ALAW'
774 if self
._comptype
== 'G722':
775 if not self
._sampwidth
:
777 if self
._sampwidth
!= 2:
778 raise Error
, 'sample width must be 2 when compressing with G7.22 (ADPCM)'
779 if not self
._nchannels
:
780 raise Error
, '# channels not specified'
781 if not self
._sampwidth
:
782 raise Error
, 'sample width not specified'
783 if not self
._framerate
:
784 raise Error
, 'sampling rate not specified'
785 self
._write
_header
(datasize
)
787 def _init_compression(self
):
788 if self
._comptype
== 'G722':
789 self
._convert
= self
._lin
2adpcm
794 if self
._comptype
== 'ULAW':
797 self
._convert
= self
._lin
2ulaw
801 raise Error
, 'cannot write compressed AIFF-C files'
802 if self
._comptype
== 'ULAW':
803 scheme
= cl
.G711_ULAW
804 elif self
._comptype
== 'ALAW':
805 scheme
= cl
.G711_ALAW
807 raise Error
, 'unsupported compression type'
808 self
._comp
= cl
.OpenCompressor(scheme
)
809 params
= [cl
.ORIGINAL_FORMAT
, 0,
810 cl
.BITS_PER_COMPONENT
, self
._sampwidth
* 8,
811 cl
.FRAME_RATE
, self
._framerate
,
812 cl
.FRAME_BUFFER_SIZE
, 100,
813 cl
.COMPRESSED_BUFFER_SIZE
, 100]
814 if self
._nchannels
== 1:
816 elif self
._nchannels
== 2:
817 params
[1] = cl
.STEREO_INTERLEAVED
819 raise Error
, 'cannot compress more than 2 channels'
820 self
._comp
.SetParams(params
)
821 # the compressor produces a header which we ignore
822 dummy
= self
._comp
.Compress(0, '')
823 self
._convert
= self
._comp
_data
825 def _write_header(self
, initlength
):
826 if self
._aifc
and self
._comptype
!= 'NONE':
827 self
._init
_compression
()
828 self
._file
.write('FORM')
829 if not self
._nframes
:
830 self
._nframes
= initlength
/ (self
._nchannels
* self
._sampwidth
)
831 self
._datalength
= self
._nframes
* self
._nchannels
* self
._sampwidth
832 if self
._datalength
& 1:
833 self
._datalength
= self
._datalength
+ 1
835 if self
._comptype
in ('ULAW', 'ALAW'):
836 self
._datalength
= self
._datalength
/ 2
837 if self
._datalength
& 1:
838 self
._datalength
= self
._datalength
+ 1
839 elif self
._comptype
== 'G722':
840 self
._datalength
= (self
._datalength
+ 3) / 4
841 if self
._datalength
& 1:
842 self
._datalength
= self
._datalength
+ 1
843 self
._form
_length
_pos
= self
._file
.tell()
844 commlength
= self
._write
_form
_length
(self
._datalength
)
846 self
._file
.write('AIFC')
847 self
._file
.write('FVER')
848 _write_long(self
._file
, 4)
849 _write_long(self
._file
, self
._version
)
851 self
._file
.write('AIFF')
852 self
._file
.write('COMM')
853 _write_long(self
._file
, commlength
)
854 _write_short(self
._file
, self
._nchannels
)
855 self
._nframes
_pos
= self
._file
.tell()
856 _write_long(self
._file
, self
._nframes
)
857 _write_short(self
._file
, self
._sampwidth
* 8)
858 _write_float(self
._file
, self
._framerate
)
860 self
._file
.write(self
._comptype
)
861 _write_string(self
._file
, self
._compname
)
862 self
._file
.write('SSND')
863 self
._ssnd
_length
_pos
= self
._file
.tell()
864 _write_long(self
._file
, self
._datalength
+ 8)
865 _write_long(self
._file
, 0)
866 _write_long(self
._file
, 0)
868 def _write_form_length(self
, datalength
):
870 commlength
= 18 + 5 + len(self
._compname
)
872 commlength
= commlength
+ 1
877 _write_long(self
._file
, 4 + verslength
+ self
._marklength
+ \
878 8 + commlength
+ 16 + datalength
)
881 def _patchheader(self
):
882 curpos
= self
._file
.tell()
883 if self
._datawritten
& 1:
884 datalength
= self
._datawritten
+ 1
885 self
._file
.write(chr(0))
887 datalength
= self
._datawritten
888 if datalength
== self
._datalength
and \
889 self
._nframes
== self
._nframeswritten
and \
890 self
._marklength
== 0:
891 self
._file
.seek(curpos
, 0)
893 self
._file
.seek(self
._form
_length
_pos
, 0)
894 dummy
= self
._write
_form
_length
(datalength
)
895 self
._file
.seek(self
._nframes
_pos
, 0)
896 _write_long(self
._file
, self
._nframeswritten
)
897 self
._file
.seek(self
._ssnd
_length
_pos
, 0)
898 _write_long(self
._file
, datalength
+ 8)
899 self
._file
.seek(curpos
, 0)
900 self
._nframes
= self
._nframeswritten
901 self
._datalength
= datalength
903 def _writemarkers(self
):
904 if len(self
._markers
) == 0:
906 self
._file
.write('MARK')
908 for marker
in self
._markers
:
909 id, pos
, name
= marker
910 length
= length
+ len(name
) + 1 + 6
911 if len(name
) & 1 == 0:
913 _write_long(self
._file
, length
)
914 self
._marklength
= length
+ 8
915 _write_short(self
._file
, len(self
._markers
))
916 for marker
in self
._markers
:
917 id, pos
, name
= marker
918 _write_short(self
._file
, id)
919 _write_long(self
._file
, pos
)
920 _write_string(self
._file
, name
)
922 def open(f
, mode
=None):
924 if hasattr(f
, 'mode'):
928 if mode
in ('r', 'rb'):
930 elif mode
in ('w', 'wb'):
933 raise Error
, "mode must be 'r', 'rb', 'w', or 'wb'"
935 openfp
= open # B/W compatibility
937 if __name__
== '__main__':
940 sys
.argv
.append('/usr/demos/data/audio/bach.aiff')
944 print "nchannels =", f
.getnchannels()
945 print "nframes =", f
.getnframes()
946 print "sampwidth =", f
.getsampwidth()
947 print "framerate =", f
.getframerate()
948 print "comptype =", f
.getcomptype()
949 print "compname =", f
.getcompname()
954 g
.setparams(f
.getparams())
956 data
= f
.readframes(1024)