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
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
141 _AIFC_version
= 0xA2805140 # Version 1 of AIFF-C
143 _skiplist
= 'COMT', 'INST', 'MIDI', 'AESD', \
144 'APPL', 'NAME', 'AUTH', '(c) ', 'ANNO'
146 def _read_long(file):
148 return struct
.unpack('>l', file.read(4))[0]
152 def _read_ulong(file):
154 return struct
.unpack('>L', file.read(4))[0]
158 def _read_short(file):
160 return struct
.unpack('>h', file.read(2))[0]
164 def _read_string(file):
165 length
= ord(file.read(1))
169 data
= file.read(length
)
174 _HUGE_VAL
= 1.79769313486231e+308 # See <limits.h>
176 def _read_float(f
): # 10 bytes
178 expon
= _read_short(f
) # 2 bytes
182 expon
= expon
+ 0x8000
183 himant
= _read_ulong(f
) # 4 bytes
184 lomant
= _read_ulong(f
) # 4 bytes
185 if expon
== himant
== lomant
== 0:
187 elif expon
== 0x7FFF:
190 expon
= expon
- 16383
191 f
= (himant
* 0x100000000L
+ lomant
) * pow(2.0, expon
- 63)
194 def _write_short(f
, x
):
195 f
.write(struct
.pack('>h', x
))
197 def _write_long(f
, x
):
198 f
.write(struct
.pack('>L', x
))
200 def _write_string(f
, s
):
206 def _write_float(f
, x
):
218 fmant
, expon
= math
.frexp(x
)
219 if expon
> 16384 or fmant
>= 1: # Infinity or NaN
224 expon
= expon
+ 16382
225 if expon
< 0: # denormalized
226 fmant
= math
.ldexp(fmant
, expon
)
229 fmant
= math
.ldexp(fmant
, 32)
230 fsmant
= math
.floor(fmant
)
231 himant
= long(fsmant
)
232 fmant
= math
.ldexp(fmant
- fsmant
, 32)
233 fsmant
= math
.floor(fmant
)
234 lomant
= long(fsmant
)
235 _write_short(f
, expon
)
236 _write_long(f
, himant
)
237 _write_long(f
, lomant
)
239 from chunk
import Chunk
242 # Variables used in this class:
244 # These variables are available to the user though appropriate
245 # methods of this class:
246 # _file -- the open file with methods read(), close(), and seek()
247 # set through the __init__() method
248 # _nchannels -- the number of audio channels
249 # available through the getnchannels() method
250 # _nframes -- the number of audio frames
251 # available through the getnframes() method
252 # _sampwidth -- the number of bytes per audio sample
253 # available through the getsampwidth() method
254 # _framerate -- the sampling frequency
255 # available through the getframerate() method
256 # _comptype -- the AIFF-C compression type ('NONE' if AIFF)
257 # available through the getcomptype() method
258 # _compname -- the human-readable AIFF-C compression type
259 # available through the getcomptype() method
260 # _markers -- the marks in the audio file
261 # available through the getmarkers() and getmark()
263 # _soundpos -- the position in the audio stream
264 # available through the tell() method, set through the
267 # These variables are used internally only:
268 # _version -- the AIFF-C version number
269 # _decomp -- the decompressor from builtin module cl
270 # _comm_chunk_read -- 1 iff the COMM chunk has been read
271 # _aifc -- 1 iff reading an AIFF-C file
272 # _ssnd_seek_needed -- 1 iff positioned correctly in audio
273 # file for readframes()
274 # _ssnd_chunk -- instantiation of a chunk class for the SSND chunk
275 # _framesize -- size of one frame in the file
277 def initfp(self
, file):
283 self
._file
= Chunk(file)
284 if self
._file
.getname() != 'FORM':
285 raise Error
, 'file does not start with FORM id'
286 formdata
= self
._file
.read(4)
287 if formdata
== 'AIFF':
289 elif formdata
== 'AIFC':
292 raise Error
, 'not an AIFF or AIFF-C file'
293 self
._comm
_chunk
_read
= 0
295 self
._ssnd
_seek
_needed
= 1
297 chunk
= Chunk(self
._file
)
300 chunkname
= chunk
.getname()
301 if chunkname
== 'COMM':
302 self
._read
_comm
_chunk
(chunk
)
303 self
._comm
_chunk
_read
= 1
304 elif chunkname
== 'SSND':
305 self
._ssnd
_chunk
= chunk
306 dummy
= chunk
.read(8)
307 self
._ssnd
_seek
_needed
= 0
308 elif chunkname
== 'FVER':
309 self
._version
= _read_long(chunk
)
310 elif chunkname
== 'MARK':
311 self
._readmark
(chunk
)
312 elif chunkname
in _skiplist
:
315 raise Error
, 'unrecognized chunk type '+chunk
.chunkname
317 if not self
._comm
_chunk
_read
or not self
._ssnd
_chunk
:
318 raise Error
, 'COMM chunk and/or SSND chunk missing'
319 if self
._aifc
and self
._decomp
:
321 params
= [cl
.ORIGINAL_FORMAT
, 0,
322 cl
.BITS_PER_COMPONENT
, self
._sampwidth
* 8,
323 cl
.FRAME_RATE
, self
._framerate
]
324 if self
._nchannels
== 1:
326 elif self
._nchannels
== 2:
327 params
[1] = cl
.STEREO_INTERLEAVED
329 raise Error
, 'cannot compress more than 2 channels'
330 self
._decomp
.SetParams(params
)
332 def __init__(self
, f
):
333 if type(f
) == type(''):
334 f
= __builtin__
.open(f
, 'rb')
335 # else, assume it is an open file object already
339 # User visible methods.
345 self
._ssnd
_seek
_needed
= 1
350 self
._decomp
.CloseDecompressor()
355 return self
._soundpos
357 def getnchannels(self
):
358 return self
._nchannels
360 def getnframes(self
):
363 def getsampwidth(self
):
364 return self
._sampwidth
366 def getframerate(self
):
367 return self
._framerate
369 def getcomptype(self
):
370 return self
._comptype
372 def getcompname(self
):
373 return self
._compname
375 ## def getversion(self):
376 ## return self._version
379 return self
.getnchannels(), self
.getsampwidth(), \
380 self
.getframerate(), self
.getnframes(), \
381 self
.getcomptype(), self
.getcompname()
383 def getmarkers(self
):
384 if len(self
._markers
) == 0:
388 def getmark(self
, id):
389 for marker
in self
._markers
:
392 raise Error
, 'marker ' + `
id`
+ ' does not exist'
394 def setpos(self
, pos
):
395 if pos
< 0 or pos
> self
._nframes
:
396 raise Error
, 'position not in range'
398 self
._ssnd
_seek
_needed
= 1
400 def readframes(self
, nframes
):
401 if self
._ssnd
_seek
_needed
:
402 self
._ssnd
_chunk
.seek(0)
403 dummy
= self
._ssnd
_chunk
.read(8)
404 pos
= self
._soundpos
* self
._framesize
406 self
._ssnd
_chunk
.setpos(pos
+ 8)
407 self
._ssnd
_seek
_needed
= 0
410 data
= self
._ssnd
_chunk
.read(nframes
* self
._framesize
)
411 if self
._convert
and data
:
412 data
= self
._convert
(data
)
413 self
._soundpos
= self
._soundpos
+ len(data
) / (self
._nchannels
* self
._sampwidth
)
420 def _decomp_data(self
, data
):
422 dummy
= self
._decomp
.SetParam(cl
.FRAME_BUFFER_SIZE
,
424 return self
._decomp
.Decompress(len(data
) / self
._nchannels
,
427 def _ulaw2lin(self
, data
):
429 return audioop
.ulaw2lin(data
, 2)
431 def _adpcm2lin(self
, data
):
433 if not hasattr(self
, '_adpcmstate'):
435 self
._adpcmstate
= None
436 data
, self
._adpcmstate
= audioop
.adpcm2lin(data
, 2,
440 def _read_comm_chunk(self
, chunk
):
441 self
._nchannels
= _read_short(chunk
)
442 self
._nframes
= _read_long(chunk
)
443 self
._sampwidth
= (_read_short(chunk
) + 7) / 8
444 self
._framerate
= int(_read_float(chunk
))
445 self
._framesize
= self
._nchannels
* self
._sampwidth
447 #DEBUG: SGI's soundeditor produces a bad size :-(
449 if chunk
.chunksize
== 18:
451 print 'Warning: bad COMM chunk size'
454 self
._comptype
= chunk
.read(4)
457 length
= ord(chunk
.file.read(1))
460 chunk
.chunksize
= chunk
.chunksize
+ length
461 chunk
.file.seek(-1, 1)
463 self
._compname
= _read_string(chunk
)
464 if self
._comptype
!= 'NONE':
465 if self
._comptype
== 'G722':
471 self
._convert
= self
._adpcm
2lin
472 self
._framesize
= self
._framesize
/ 4
474 # for ULAW and ALAW try Compression Library
478 if self
._comptype
== 'ULAW':
481 self
._convert
= self
._ulaw
2lin
482 self
._framesize
= self
._framesize
/ 2
486 raise Error
, 'cannot read compressed AIFF-C files'
487 if self
._comptype
== 'ULAW':
488 scheme
= cl
.G711_ULAW
489 self
._framesize
= self
._framesize
/ 2
490 elif self
._comptype
== 'ALAW':
491 scheme
= cl
.G711_ALAW
492 self
._framesize
= self
._framesize
/ 2
494 raise Error
, 'unsupported compression type'
495 self
._decomp
= cl
.OpenDecompressor(scheme
)
496 self
._convert
= self
._decomp
_data
498 self
._comptype
= 'NONE'
499 self
._compname
= 'not compressed'
501 def _readmark(self
, chunk
):
502 nmarkers
= _read_short(chunk
)
503 # Some files appear to contain invalid counts.
504 # Cope with this by testing for EOF.
506 for i
in range(nmarkers
):
507 id = _read_short(chunk
)
508 pos
= _read_long(chunk
)
509 name
= _read_string(chunk
)
511 # some files appear to have
512 # dummy markers consisting of
513 # a position 0 and name ''
514 self
._markers
.append((id, pos
, name
))
516 print 'Warning: MARK chunk contains only',
517 print len(self
._markers
),
518 if len(self
._markers
) == 1: print 'marker',
519 else: print 'markers',
520 print 'instead of', nmarkers
523 # Variables used in this class:
525 # These variables are user settable through appropriate methods
527 # _file -- the open file with methods write(), close(), tell(), seek()
528 # set through the __init__() method
529 # _comptype -- the AIFF-C compression type ('NONE' in AIFF)
530 # set through the setcomptype() or setparams() method
531 # _compname -- the human-readable AIFF-C compression type
532 # set through the setcomptype() or setparams() method
533 # _nchannels -- the number of audio channels
534 # set through the setnchannels() or setparams() method
535 # _sampwidth -- the number of bytes per audio sample
536 # set through the setsampwidth() or setparams() method
537 # _framerate -- the sampling frequency
538 # set through the setframerate() or setparams() method
539 # _nframes -- the number of audio frames written to the header
540 # set through the setnframes() or setparams() method
541 # _aifc -- whether we're writing an AIFF-C file or an AIFF file
542 # set through the aifc() method, reset through the
545 # These variables are used internally only:
546 # _version -- the AIFF-C version number
547 # _comp -- the compressor from builtin module cl
548 # _nframeswritten -- the number of audio frames actually written
549 # _datalength -- the size of the audio samples written to the header
550 # _datawritten -- the size of the audio samples actually written
552 def __init__(self
, f
):
553 if type(f
) == type(''):
555 f
= __builtin__
.open(f
, 'wb')
557 # else, assume it is an open file object already
560 if filename
[-5:] == '.aiff':
565 def initfp(self
, file):
567 self
._version
= _AIFC_version
568 self
._comptype
= 'NONE'
569 self
._compname
= 'not compressed'
576 self
._nframeswritten
= 0
577 self
._datawritten
= 0
581 self
._aifc
= 1 # AIFF-C is default
588 # User visible methods.
591 if self
._nframeswritten
:
592 raise Error
, 'cannot change parameters after starting to write'
596 if self
._nframeswritten
:
597 raise Error
, 'cannot change parameters after starting to write'
600 def setnchannels(self
, nchannels
):
601 if self
._nframeswritten
:
602 raise Error
, 'cannot change parameters after starting to write'
604 raise Error
, 'bad # of channels'
605 self
._nchannels
= nchannels
607 def getnchannels(self
):
608 if not self
._nchannels
:
609 raise Error
, 'number of channels not set'
610 return self
._nchannels
612 def setsampwidth(self
, sampwidth
):
613 if self
._nframeswritten
:
614 raise Error
, 'cannot change parameters after starting to write'
615 if sampwidth
< 1 or sampwidth
> 4:
616 raise Error
, 'bad sample width'
617 self
._sampwidth
= sampwidth
619 def getsampwidth(self
):
620 if not self
._sampwidth
:
621 raise Error
, 'sample width not set'
622 return self
._sampwidth
624 def setframerate(self
, framerate
):
625 if self
._nframeswritten
:
626 raise Error
, 'cannot change parameters after starting to write'
628 raise Error
, 'bad frame rate'
629 self
._framerate
= framerate
631 def getframerate(self
):
632 if not self
._framerate
:
633 raise Error
, 'frame rate not set'
634 return self
._framerate
636 def setnframes(self
, nframes
):
637 if self
._nframeswritten
:
638 raise Error
, 'cannot change parameters after starting to write'
639 self
._nframes
= nframes
641 def getnframes(self
):
642 return self
._nframeswritten
644 def setcomptype(self
, comptype
, compname
):
645 if self
._nframeswritten
:
646 raise Error
, 'cannot change parameters after starting to write'
647 if comptype
not in ('NONE', 'ULAW', 'ALAW', 'G722'):
648 raise Error
, 'unsupported compression type'
649 self
._comptype
= comptype
650 self
._compname
= compname
652 def getcomptype(self
):
653 return self
._comptype
655 def getcompname(self
):
656 return self
._compname
658 ## def setversion(self, version):
659 ## if self._nframeswritten:
660 ## raise Error, 'cannot change parameters after starting to write'
661 ## self._version = version
663 def setparams(self
, (nchannels
, sampwidth
, framerate
, nframes
, comptype
, compname
)):
664 if self
._nframeswritten
:
665 raise Error
, 'cannot change parameters after starting to write'
666 if comptype
not in ('NONE', 'ULAW', 'ALAW', 'G722'):
667 raise Error
, 'unsupported compression type'
668 self
.setnchannels(nchannels
)
669 self
.setsampwidth(sampwidth
)
670 self
.setframerate(framerate
)
671 self
.setnframes(nframes
)
672 self
.setcomptype(comptype
, compname
)
675 if not self
._nchannels
or not self
._sampwidth
or not self
._framerate
:
676 raise Error
, 'not all parameters set'
677 return self
._nchannels
, self
._sampwidth
, self
._framerate
, \
678 self
._nframes
, self
._comptype
, self
._compname
680 def setmark(self
, id, pos
, name
):
682 raise Error
, 'marker ID must be > 0'
684 raise Error
, 'marker position must be >= 0'
685 if type(name
) != type(''):
686 raise Error
, 'marker name must be a string'
687 for i
in range(len(self
._markers
)):
688 if id == self
._markers
[i
][0]:
689 self
._markers
[i
] = id, pos
, name
691 self
._markers
.append((id, pos
, name
))
693 def getmark(self
, id):
694 for marker
in self
._markers
:
697 raise Error
, 'marker ' + `
id`
+ ' does not exist'
699 def getmarkers(self
):
700 if len(self
._markers
) == 0:
705 return self
._nframeswritten
707 def writeframesraw(self
, data
):
708 self
._ensure
_header
_written
(len(data
))
709 nframes
= len(data
) / (self
._sampwidth
* self
._nchannels
)
711 data
= self
._convert
(data
)
712 self
._file
.write(data
)
713 self
._nframeswritten
= self
._nframeswritten
+ nframes
714 self
._datawritten
= self
._datawritten
+ len(data
)
716 def writeframes(self
, data
):
717 self
.writeframesraw(data
)
718 if self
._nframeswritten
!= self
._nframes
or \
719 self
._datalength
!= self
._datawritten
:
723 self
._ensure
_header
_written
(0)
724 if self
._datawritten
& 1:
725 # quick pad to even size
726 self
._file
.write(chr(0))
727 self
._datawritten
= self
._datawritten
+ 1
729 if self
._nframeswritten
!= self
._nframes
or \
730 self
._datalength
!= self
._datawritten
or \
734 self
._comp
.CloseCompressor()
743 def _comp_data(self
, data
):
745 dum
= self
._comp
.SetParam(cl
.FRAME_BUFFER_SIZE
, len(data
))
746 dum
= self
._comp
.SetParam(cl
.COMPRESSED_BUFFER_SIZE
, len(data
))
747 return self
._comp
.Compress(self
._nframes
, data
)
749 def _lin2ulaw(self
, data
):
751 return audioop
.lin2ulaw(data
, 2)
753 def _lin2adpcm(self
, data
):
755 if not hasattr(self
, '_adpcmstate'):
756 self
._adpcmstate
= None
757 data
, self
._adpcmstate
= audioop
.lin2adpcm(data
, 2,
761 def _ensure_header_written(self
, datasize
):
762 if not self
._nframeswritten
:
763 if self
._comptype
in ('ULAW', 'ALAW'):
764 if not self
._sampwidth
:
766 if self
._sampwidth
!= 2:
767 raise Error
, 'sample width must be 2 when compressing with ULAW or ALAW'
768 if self
._comptype
== 'G722':
769 if not self
._sampwidth
:
771 if self
._sampwidth
!= 2:
772 raise Error
, 'sample width must be 2 when compressing with G7.22 (ADPCM)'
773 if not self
._nchannels
:
774 raise Error
, '# channels not specified'
775 if not self
._sampwidth
:
776 raise Error
, 'sample width not specified'
777 if not self
._framerate
:
778 raise Error
, 'sampling rate not specified'
779 self
._write
_header
(datasize
)
781 def _init_compression(self
):
782 if self
._comptype
== 'G722':
784 self
._convert
= self
._lin
2adpcm
789 if self
._comptype
== 'ULAW':
792 self
._convert
= self
._lin
2ulaw
796 raise Error
, 'cannot write compressed AIFF-C files'
797 if self
._comptype
== 'ULAW':
798 scheme
= cl
.G711_ULAW
799 elif self
._comptype
== 'ALAW':
800 scheme
= cl
.G711_ALAW
802 raise Error
, 'unsupported compression type'
803 self
._comp
= cl
.OpenCompressor(scheme
)
804 params
= [cl
.ORIGINAL_FORMAT
, 0,
805 cl
.BITS_PER_COMPONENT
, self
._sampwidth
* 8,
806 cl
.FRAME_RATE
, self
._framerate
,
807 cl
.FRAME_BUFFER_SIZE
, 100,
808 cl
.COMPRESSED_BUFFER_SIZE
, 100]
809 if self
._nchannels
== 1:
811 elif self
._nchannels
== 2:
812 params
[1] = cl
.STEREO_INTERLEAVED
814 raise Error
, 'cannot compress more than 2 channels'
815 self
._comp
.SetParams(params
)
816 # the compressor produces a header which we ignore
817 dummy
= self
._comp
.Compress(0, '')
818 self
._convert
= self
._comp
_data
820 def _write_header(self
, initlength
):
821 if self
._aifc
and self
._comptype
!= 'NONE':
822 self
._init
_compression
()
823 self
._file
.write('FORM')
824 if not self
._nframes
:
825 self
._nframes
= initlength
/ (self
._nchannels
* self
._sampwidth
)
826 self
._datalength
= self
._nframes
* self
._nchannels
* self
._sampwidth
827 if self
._datalength
& 1:
828 self
._datalength
= self
._datalength
+ 1
830 if self
._comptype
in ('ULAW', 'ALAW'):
831 self
._datalength
= self
._datalength
/ 2
832 if self
._datalength
& 1:
833 self
._datalength
= self
._datalength
+ 1
834 elif self
._comptype
== 'G722':
835 self
._datalength
= (self
._datalength
+ 3) / 4
836 if self
._datalength
& 1:
837 self
._datalength
= self
._datalength
+ 1
838 self
._form
_length
_pos
= self
._file
.tell()
839 commlength
= self
._write
_form
_length
(self
._datalength
)
841 self
._file
.write('AIFC')
842 self
._file
.write('FVER')
843 _write_long(self
._file
, 4)
844 _write_long(self
._file
, self
._version
)
846 self
._file
.write('AIFF')
847 self
._file
.write('COMM')
848 _write_long(self
._file
, commlength
)
849 _write_short(self
._file
, self
._nchannels
)
850 self
._nframes
_pos
= self
._file
.tell()
851 _write_long(self
._file
, self
._nframes
)
852 _write_short(self
._file
, self
._sampwidth
* 8)
853 _write_float(self
._file
, self
._framerate
)
855 self
._file
.write(self
._comptype
)
856 _write_string(self
._file
, self
._compname
)
857 self
._file
.write('SSND')
858 self
._ssnd
_length
_pos
= self
._file
.tell()
859 _write_long(self
._file
, self
._datalength
+ 8)
860 _write_long(self
._file
, 0)
861 _write_long(self
._file
, 0)
863 def _write_form_length(self
, datalength
):
865 commlength
= 18 + 5 + len(self
._compname
)
867 commlength
= commlength
+ 1
872 _write_long(self
._file
, 4 + verslength
+ self
._marklength
+ \
873 8 + commlength
+ 16 + datalength
)
876 def _patchheader(self
):
877 curpos
= self
._file
.tell()
878 if self
._datawritten
& 1:
879 datalength
= self
._datawritten
+ 1
880 self
._file
.write(chr(0))
882 datalength
= self
._datawritten
883 if datalength
== self
._datalength
and \
884 self
._nframes
== self
._nframeswritten
and \
885 self
._marklength
== 0:
886 self
._file
.seek(curpos
, 0)
888 self
._file
.seek(self
._form
_length
_pos
, 0)
889 dummy
= self
._write
_form
_length
(datalength
)
890 self
._file
.seek(self
._nframes
_pos
, 0)
891 _write_long(self
._file
, self
._nframeswritten
)
892 self
._file
.seek(self
._ssnd
_length
_pos
, 0)
893 _write_long(self
._file
, datalength
+ 8)
894 self
._file
.seek(curpos
, 0)
895 self
._nframes
= self
._nframeswritten
896 self
._datalength
= datalength
898 def _writemarkers(self
):
899 if len(self
._markers
) == 0:
901 self
._file
.write('MARK')
903 for marker
in self
._markers
:
904 id, pos
, name
= marker
905 length
= length
+ len(name
) + 1 + 6
906 if len(name
) & 1 == 0:
908 _write_long(self
._file
, length
)
909 self
._marklength
= length
+ 8
910 _write_short(self
._file
, len(self
._markers
))
911 for marker
in self
._markers
:
912 id, pos
, name
= marker
913 _write_short(self
._file
, id)
914 _write_long(self
._file
, pos
)
915 _write_string(self
._file
, name
)
917 def open(f
, mode
=None):
919 if hasattr(f
, 'mode'):
923 if mode
in ('r', 'rb'):
925 elif mode
in ('w', 'wb'):
928 raise Error
, "mode must be 'r', 'rb', 'w', or 'wb'"
930 openfp
= open # B/W compatibility
932 if __name__
== '__main__':
935 sys
.argv
.append('/usr/demos/data/audio/bach.aiff')
939 print "nchannels =", f
.getnchannels()
940 print "nframes =", f
.getnframes()
941 print "sampwidth =", f
.getsampwidth()
942 print "framerate =", f
.getframerate()
943 print "comptype =", f
.getcomptype()
944 print "compname =", f
.getcompname()
949 g
.setparams(f
.getparams())
951 data
= f
.readframes(1024)