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
):
209 def _write_float(f
, x
):
221 fmant
, expon
= math
.frexp(x
)
222 if expon
> 16384 or fmant
>= 1: # Infinity or NaN
227 expon
= expon
+ 16382
228 if expon
< 0: # denormalized
229 fmant
= math
.ldexp(fmant
, expon
)
232 fmant
= math
.ldexp(fmant
, 32)
233 fsmant
= math
.floor(fmant
)
234 himant
= long(fsmant
)
235 fmant
= math
.ldexp(fmant
- fsmant
, 32)
236 fsmant
= math
.floor(fmant
)
237 lomant
= long(fsmant
)
238 _write_short(f
, expon
)
239 _write_long(f
, himant
)
240 _write_long(f
, lomant
)
242 from chunk
import Chunk
245 # Variables used in this class:
247 # These variables are available to the user though appropriate
248 # methods of this class:
249 # _file -- the open file with methods read(), close(), and seek()
250 # set through the __init__() method
251 # _nchannels -- the number of audio channels
252 # available through the getnchannels() method
253 # _nframes -- the number of audio frames
254 # available through the getnframes() method
255 # _sampwidth -- the number of bytes per audio sample
256 # available through the getsampwidth() method
257 # _framerate -- the sampling frequency
258 # available through the getframerate() method
259 # _comptype -- the AIFF-C compression type ('NONE' if AIFF)
260 # available through the getcomptype() method
261 # _compname -- the human-readable AIFF-C compression type
262 # available through the getcomptype() method
263 # _markers -- the marks in the audio file
264 # available through the getmarkers() and getmark()
266 # _soundpos -- the position in the audio stream
267 # available through the tell() method, set through the
270 # These variables are used internally only:
271 # _version -- the AIFF-C version number
272 # _decomp -- the decompressor from builtin module cl
273 # _comm_chunk_read -- 1 iff the COMM chunk has been read
274 # _aifc -- 1 iff reading an AIFF-C file
275 # _ssnd_seek_needed -- 1 iff positioned correctly in audio
276 # file for readframes()
277 # _ssnd_chunk -- instantiation of a chunk class for the SSND chunk
278 # _framesize -- size of one frame in the file
280 def initfp(self
, file):
286 self
._file
= Chunk(file)
287 if self
._file
.getname() != 'FORM':
288 raise Error
, 'file does not start with FORM id'
289 formdata
= self
._file
.read(4)
290 if formdata
== 'AIFF':
292 elif formdata
== 'AIFC':
295 raise Error
, 'not an AIFF or AIFF-C file'
296 self
._comm
_chunk
_read
= 0
298 self
._ssnd
_seek
_needed
= 1
300 chunk
= Chunk(self
._file
)
303 chunkname
= chunk
.getname()
304 if chunkname
== 'COMM':
305 self
._read
_comm
_chunk
(chunk
)
306 self
._comm
_chunk
_read
= 1
307 elif chunkname
== 'SSND':
308 self
._ssnd
_chunk
= chunk
309 dummy
= chunk
.read(8)
310 self
._ssnd
_seek
_needed
= 0
311 elif chunkname
== 'FVER':
312 self
._version
= _read_ulong(chunk
)
313 elif chunkname
== 'MARK':
314 self
._readmark
(chunk
)
315 elif chunkname
in _skiplist
:
318 raise Error
, 'unrecognized chunk type '+chunk
.chunkname
320 if not self
._comm
_chunk
_read
or not self
._ssnd
_chunk
:
321 raise Error
, 'COMM chunk and/or SSND chunk missing'
322 if self
._aifc
and self
._decomp
:
324 params
= [cl
.ORIGINAL_FORMAT
, 0,
325 cl
.BITS_PER_COMPONENT
, self
._sampwidth
* 8,
326 cl
.FRAME_RATE
, self
._framerate
]
327 if self
._nchannels
== 1:
329 elif self
._nchannels
== 2:
330 params
[1] = cl
.STEREO_INTERLEAVED
332 raise Error
, 'cannot compress more than 2 channels'
333 self
._decomp
.SetParams(params
)
335 def __init__(self
, f
):
336 if type(f
) == type(''):
337 f
= __builtin__
.open(f
, 'rb')
338 # else, assume it is an open file object already
342 # User visible methods.
348 self
._ssnd
_seek
_needed
= 1
353 self
._decomp
.CloseDecompressor()
358 return self
._soundpos
360 def getnchannels(self
):
361 return self
._nchannels
363 def getnframes(self
):
366 def getsampwidth(self
):
367 return self
._sampwidth
369 def getframerate(self
):
370 return self
._framerate
372 def getcomptype(self
):
373 return self
._comptype
375 def getcompname(self
):
376 return self
._compname
378 ## def getversion(self):
379 ## return self._version
382 return self
.getnchannels(), self
.getsampwidth(), \
383 self
.getframerate(), self
.getnframes(), \
384 self
.getcomptype(), self
.getcompname()
386 def getmarkers(self
):
387 if len(self
._markers
) == 0:
391 def getmark(self
, id):
392 for marker
in self
._markers
:
395 raise Error
, 'marker %r does not exist' % (id,)
397 def setpos(self
, pos
):
398 if pos
< 0 or pos
> self
._nframes
:
399 raise Error
, 'position not in range'
401 self
._ssnd
_seek
_needed
= 1
403 def readframes(self
, nframes
):
404 if self
._ssnd
_seek
_needed
:
405 self
._ssnd
_chunk
.seek(0)
406 dummy
= self
._ssnd
_chunk
.read(8)
407 pos
= self
._soundpos
* self
._framesize
409 self
._ssnd
_chunk
.seek(pos
+ 8)
410 self
._ssnd
_seek
_needed
= 0
413 data
= self
._ssnd
_chunk
.read(nframes
* self
._framesize
)
414 if self
._convert
and data
:
415 data
= self
._convert
(data
)
416 self
._soundpos
= self
._soundpos
+ len(data
) / (self
._nchannels
* self
._sampwidth
)
423 def _decomp_data(self
, data
):
425 dummy
= self
._decomp
.SetParam(cl
.FRAME_BUFFER_SIZE
,
427 return self
._decomp
.Decompress(len(data
) / self
._nchannels
,
430 def _ulaw2lin(self
, data
):
432 return audioop
.ulaw2lin(data
, 2)
434 def _adpcm2lin(self
, data
):
436 if not hasattr(self
, '_adpcmstate'):
438 self
._adpcmstate
= None
439 data
, self
._adpcmstate
= audioop
.adpcm2lin(data
, 2,
443 def _read_comm_chunk(self
, chunk
):
444 self
._nchannels
= _read_short(chunk
)
445 self
._nframes
= _read_long(chunk
)
446 self
._sampwidth
= (_read_short(chunk
) + 7) / 8
447 self
._framerate
= int(_read_float(chunk
))
448 self
._framesize
= self
._nchannels
* self
._sampwidth
450 #DEBUG: SGI's soundeditor produces a bad size :-(
452 if chunk
.chunksize
== 18:
454 print 'Warning: bad COMM chunk size'
457 self
._comptype
= chunk
.read(4)
460 length
= ord(chunk
.file.read(1))
463 chunk
.chunksize
= chunk
.chunksize
+ length
464 chunk
.file.seek(-1, 1)
466 self
._compname
= _read_string(chunk
)
467 if self
._comptype
!= 'NONE':
468 if self
._comptype
== 'G722':
474 self
._convert
= self
._adpcm
2lin
475 self
._framesize
= self
._framesize
/ 4
477 # for ULAW and ALAW try Compression Library
481 if self
._comptype
== 'ULAW':
484 self
._convert
= self
._ulaw
2lin
485 self
._framesize
= self
._framesize
/ 2
489 raise Error
, 'cannot read compressed AIFF-C files'
490 if self
._comptype
== 'ULAW':
491 scheme
= cl
.G711_ULAW
492 self
._framesize
= self
._framesize
/ 2
493 elif self
._comptype
== 'ALAW':
494 scheme
= cl
.G711_ALAW
495 self
._framesize
= self
._framesize
/ 2
497 raise Error
, 'unsupported compression type'
498 self
._decomp
= cl
.OpenDecompressor(scheme
)
499 self
._convert
= self
._decomp
_data
501 self
._comptype
= 'NONE'
502 self
._compname
= 'not compressed'
504 def _readmark(self
, chunk
):
505 nmarkers
= _read_short(chunk
)
506 # Some files appear to contain invalid counts.
507 # Cope with this by testing for EOF.
509 for i
in range(nmarkers
):
510 id = _read_short(chunk
)
511 pos
= _read_long(chunk
)
512 name
= _read_string(chunk
)
514 # some files appear to have
515 # dummy markers consisting of
516 # a position 0 and name ''
517 self
._markers
.append((id, pos
, name
))
519 print 'Warning: MARK chunk contains only',
520 print len(self
._markers
),
521 if len(self
._markers
) == 1: print 'marker',
522 else: print 'markers',
523 print 'instead of', nmarkers
526 # Variables used in this class:
528 # These variables are user settable through appropriate methods
530 # _file -- the open file with methods write(), close(), tell(), seek()
531 # set through the __init__() method
532 # _comptype -- the AIFF-C compression type ('NONE' in AIFF)
533 # set through the setcomptype() or setparams() method
534 # _compname -- the human-readable AIFF-C compression type
535 # set through the setcomptype() or setparams() method
536 # _nchannels -- the number of audio channels
537 # set through the setnchannels() or setparams() method
538 # _sampwidth -- the number of bytes per audio sample
539 # set through the setsampwidth() or setparams() method
540 # _framerate -- the sampling frequency
541 # set through the setframerate() or setparams() method
542 # _nframes -- the number of audio frames written to the header
543 # set through the setnframes() or setparams() method
544 # _aifc -- whether we're writing an AIFF-C file or an AIFF file
545 # set through the aifc() method, reset through the
548 # These variables are used internally only:
549 # _version -- the AIFF-C version number
550 # _comp -- the compressor from builtin module cl
551 # _nframeswritten -- the number of audio frames actually written
552 # _datalength -- the size of the audio samples written to the header
553 # _datawritten -- the size of the audio samples actually written
555 def __init__(self
, f
):
556 if type(f
) == type(''):
558 f
= __builtin__
.open(f
, 'wb')
560 # else, assume it is an open file object already
563 if filename
[-5:] == '.aiff':
568 def initfp(self
, file):
570 self
._version
= _AIFC_version
571 self
._comptype
= 'NONE'
572 self
._compname
= 'not compressed'
579 self
._nframeswritten
= 0
580 self
._datawritten
= 0
584 self
._aifc
= 1 # AIFF-C is default
591 # User visible methods.
594 if self
._nframeswritten
:
595 raise Error
, 'cannot change parameters after starting to write'
599 if self
._nframeswritten
:
600 raise Error
, 'cannot change parameters after starting to write'
603 def setnchannels(self
, nchannels
):
604 if self
._nframeswritten
:
605 raise Error
, 'cannot change parameters after starting to write'
607 raise Error
, 'bad # of channels'
608 self
._nchannels
= nchannels
610 def getnchannels(self
):
611 if not self
._nchannels
:
612 raise Error
, 'number of channels not set'
613 return self
._nchannels
615 def setsampwidth(self
, sampwidth
):
616 if self
._nframeswritten
:
617 raise Error
, 'cannot change parameters after starting to write'
618 if sampwidth
< 1 or sampwidth
> 4:
619 raise Error
, 'bad sample width'
620 self
._sampwidth
= sampwidth
622 def getsampwidth(self
):
623 if not self
._sampwidth
:
624 raise Error
, 'sample width not set'
625 return self
._sampwidth
627 def setframerate(self
, framerate
):
628 if self
._nframeswritten
:
629 raise Error
, 'cannot change parameters after starting to write'
631 raise Error
, 'bad frame rate'
632 self
._framerate
= framerate
634 def getframerate(self
):
635 if not self
._framerate
:
636 raise Error
, 'frame rate not set'
637 return self
._framerate
639 def setnframes(self
, nframes
):
640 if self
._nframeswritten
:
641 raise Error
, 'cannot change parameters after starting to write'
642 self
._nframes
= nframes
644 def getnframes(self
):
645 return self
._nframeswritten
647 def setcomptype(self
, comptype
, compname
):
648 if self
._nframeswritten
:
649 raise Error
, 'cannot change parameters after starting to write'
650 if comptype
not in ('NONE', 'ULAW', 'ALAW', 'G722'):
651 raise Error
, 'unsupported compression type'
652 self
._comptype
= comptype
653 self
._compname
= compname
655 def getcomptype(self
):
656 return self
._comptype
658 def getcompname(self
):
659 return self
._compname
661 ## def setversion(self, version):
662 ## if self._nframeswritten:
663 ## raise Error, 'cannot change parameters after starting to write'
664 ## self._version = version
666 def setparams(self
, (nchannels
, sampwidth
, framerate
, nframes
, comptype
, compname
)):
667 if self
._nframeswritten
:
668 raise Error
, 'cannot change parameters after starting to write'
669 if comptype
not in ('NONE', 'ULAW', 'ALAW', 'G722'):
670 raise Error
, 'unsupported compression type'
671 self
.setnchannels(nchannels
)
672 self
.setsampwidth(sampwidth
)
673 self
.setframerate(framerate
)
674 self
.setnframes(nframes
)
675 self
.setcomptype(comptype
, compname
)
678 if not self
._nchannels
or not self
._sampwidth
or not self
._framerate
:
679 raise Error
, 'not all parameters set'
680 return self
._nchannels
, self
._sampwidth
, self
._framerate
, \
681 self
._nframes
, self
._comptype
, self
._compname
683 def setmark(self
, id, pos
, name
):
685 raise Error
, 'marker ID must be > 0'
687 raise Error
, 'marker position must be >= 0'
688 if type(name
) != type(''):
689 raise Error
, 'marker name must be a string'
690 for i
in range(len(self
._markers
)):
691 if id == self
._markers
[i
][0]:
692 self
._markers
[i
] = id, pos
, name
694 self
._markers
.append((id, pos
, name
))
696 def getmark(self
, id):
697 for marker
in self
._markers
:
700 raise Error
, 'marker %r does not exist' % (id,)
702 def getmarkers(self
):
703 if len(self
._markers
) == 0:
708 return self
._nframeswritten
710 def writeframesraw(self
, data
):
711 self
._ensure
_header
_written
(len(data
))
712 nframes
= len(data
) / (self
._sampwidth
* self
._nchannels
)
714 data
= self
._convert
(data
)
715 self
._file
.write(data
)
716 self
._nframeswritten
= self
._nframeswritten
+ nframes
717 self
._datawritten
= self
._datawritten
+ len(data
)
719 def writeframes(self
, data
):
720 self
.writeframesraw(data
)
721 if self
._nframeswritten
!= self
._nframes
or \
722 self
._datalength
!= self
._datawritten
:
726 self
._ensure
_header
_written
(0)
727 if self
._datawritten
& 1:
728 # quick pad to even size
729 self
._file
.write(chr(0))
730 self
._datawritten
= self
._datawritten
+ 1
732 if self
._nframeswritten
!= self
._nframes
or \
733 self
._datalength
!= self
._datawritten
or \
737 self
._comp
.CloseCompressor()
746 def _comp_data(self
, data
):
748 dummy
= self
._comp
.SetParam(cl
.FRAME_BUFFER_SIZE
, len(data
))
749 dummy
= self
._comp
.SetParam(cl
.COMPRESSED_BUFFER_SIZE
, len(data
))
750 return self
._comp
.Compress(self
._nframes
, data
)
752 def _lin2ulaw(self
, data
):
754 return audioop
.lin2ulaw(data
, 2)
756 def _lin2adpcm(self
, data
):
758 if not hasattr(self
, '_adpcmstate'):
759 self
._adpcmstate
= None
760 data
, self
._adpcmstate
= audioop
.lin2adpcm(data
, 2,
764 def _ensure_header_written(self
, datasize
):
765 if not self
._nframeswritten
:
766 if self
._comptype
in ('ULAW', 'ALAW'):
767 if not self
._sampwidth
:
769 if self
._sampwidth
!= 2:
770 raise Error
, 'sample width must be 2 when compressing with ULAW or ALAW'
771 if self
._comptype
== 'G722':
772 if not self
._sampwidth
:
774 if self
._sampwidth
!= 2:
775 raise Error
, 'sample width must be 2 when compressing with G7.22 (ADPCM)'
776 if not self
._nchannels
:
777 raise Error
, '# channels not specified'
778 if not self
._sampwidth
:
779 raise Error
, 'sample width not specified'
780 if not self
._framerate
:
781 raise Error
, 'sampling rate not specified'
782 self
._write
_header
(datasize
)
784 def _init_compression(self
):
785 if self
._comptype
== 'G722':
786 self
._convert
= self
._lin
2adpcm
791 if self
._comptype
== 'ULAW':
794 self
._convert
= self
._lin
2ulaw
798 raise Error
, 'cannot write compressed AIFF-C files'
799 if self
._comptype
== 'ULAW':
800 scheme
= cl
.G711_ULAW
801 elif self
._comptype
== 'ALAW':
802 scheme
= cl
.G711_ALAW
804 raise Error
, 'unsupported compression type'
805 self
._comp
= cl
.OpenCompressor(scheme
)
806 params
= [cl
.ORIGINAL_FORMAT
, 0,
807 cl
.BITS_PER_COMPONENT
, self
._sampwidth
* 8,
808 cl
.FRAME_RATE
, self
._framerate
,
809 cl
.FRAME_BUFFER_SIZE
, 100,
810 cl
.COMPRESSED_BUFFER_SIZE
, 100]
811 if self
._nchannels
== 1:
813 elif self
._nchannels
== 2:
814 params
[1] = cl
.STEREO_INTERLEAVED
816 raise Error
, 'cannot compress more than 2 channels'
817 self
._comp
.SetParams(params
)
818 # the compressor produces a header which we ignore
819 dummy
= self
._comp
.Compress(0, '')
820 self
._convert
= self
._comp
_data
822 def _write_header(self
, initlength
):
823 if self
._aifc
and self
._comptype
!= 'NONE':
824 self
._init
_compression
()
825 self
._file
.write('FORM')
826 if not self
._nframes
:
827 self
._nframes
= initlength
/ (self
._nchannels
* self
._sampwidth
)
828 self
._datalength
= self
._nframes
* self
._nchannels
* self
._sampwidth
829 if self
._datalength
& 1:
830 self
._datalength
= self
._datalength
+ 1
832 if self
._comptype
in ('ULAW', 'ALAW'):
833 self
._datalength
= self
._datalength
/ 2
834 if self
._datalength
& 1:
835 self
._datalength
= self
._datalength
+ 1
836 elif self
._comptype
== 'G722':
837 self
._datalength
= (self
._datalength
+ 3) / 4
838 if self
._datalength
& 1:
839 self
._datalength
= self
._datalength
+ 1
840 self
._form
_length
_pos
= self
._file
.tell()
841 commlength
= self
._write
_form
_length
(self
._datalength
)
843 self
._file
.write('AIFC')
844 self
._file
.write('FVER')
845 _write_long(self
._file
, 4)
846 _write_long(self
._file
, self
._version
)
848 self
._file
.write('AIFF')
849 self
._file
.write('COMM')
850 _write_long(self
._file
, commlength
)
851 _write_short(self
._file
, self
._nchannels
)
852 self
._nframes
_pos
= self
._file
.tell()
853 _write_long(self
._file
, self
._nframes
)
854 _write_short(self
._file
, self
._sampwidth
* 8)
855 _write_float(self
._file
, self
._framerate
)
857 self
._file
.write(self
._comptype
)
858 _write_string(self
._file
, self
._compname
)
859 self
._file
.write('SSND')
860 self
._ssnd
_length
_pos
= self
._file
.tell()
861 _write_long(self
._file
, self
._datalength
+ 8)
862 _write_long(self
._file
, 0)
863 _write_long(self
._file
, 0)
865 def _write_form_length(self
, datalength
):
867 commlength
= 18 + 5 + len(self
._compname
)
869 commlength
= commlength
+ 1
874 _write_long(self
._file
, 4 + verslength
+ self
._marklength
+ \
875 8 + commlength
+ 16 + datalength
)
878 def _patchheader(self
):
879 curpos
= self
._file
.tell()
880 if self
._datawritten
& 1:
881 datalength
= self
._datawritten
+ 1
882 self
._file
.write(chr(0))
884 datalength
= self
._datawritten
885 if datalength
== self
._datalength
and \
886 self
._nframes
== self
._nframeswritten
and \
887 self
._marklength
== 0:
888 self
._file
.seek(curpos
, 0)
890 self
._file
.seek(self
._form
_length
_pos
, 0)
891 dummy
= self
._write
_form
_length
(datalength
)
892 self
._file
.seek(self
._nframes
_pos
, 0)
893 _write_long(self
._file
, self
._nframeswritten
)
894 self
._file
.seek(self
._ssnd
_length
_pos
, 0)
895 _write_long(self
._file
, datalength
+ 8)
896 self
._file
.seek(curpos
, 0)
897 self
._nframes
= self
._nframeswritten
898 self
._datalength
= datalength
900 def _writemarkers(self
):
901 if len(self
._markers
) == 0:
903 self
._file
.write('MARK')
905 for marker
in self
._markers
:
906 id, pos
, name
= marker
907 length
= length
+ len(name
) + 1 + 6
908 if len(name
) & 1 == 0:
910 _write_long(self
._file
, length
)
911 self
._marklength
= length
+ 8
912 _write_short(self
._file
, len(self
._markers
))
913 for marker
in self
._markers
:
914 id, pos
, name
= marker
915 _write_short(self
._file
, id)
916 _write_long(self
._file
, pos
)
917 _write_string(self
._file
, name
)
919 def open(f
, mode
=None):
921 if hasattr(f
, 'mode'):
925 if mode
in ('r', 'rb'):
927 elif mode
in ('w', 'wb'):
930 raise Error
, "mode must be 'r', 'rb', 'w', or 'wb'"
932 openfp
= open # B/W compatibility
934 if __name__
== '__main__':
937 sys
.argv
.append('/usr/demos/data/audio/bach.aiff')
941 print "nchannels =", f
.getnchannels()
942 print "nframes =", f
.getnframes()
943 print "sampwidth =", f
.getsampwidth()
944 print "framerate =", f
.getframerate()
945 print "comptype =", f
.getcomptype()
946 print "compname =", f
.getcompname()
951 g
.setparams(f
.getparams())
953 data
= f
.readframes(1024)