Merged revisions 82952,82954 via svnmerge from
[python/dscho.git] / Lib / aifc.py
blob015d3985d6bfbca2afb45cf590ad06a4c23df430
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.
8 +-----------------+
9 | FORM |
10 +-----------------+
11 | <size> |
12 +----+------------+
13 | | AIFC |
14 | +------------+
15 | | <chunks> |
16 | | . |
17 | | . |
18 | | . |
19 +----+------------+
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.
29 FVER
30 <version number of AIFF-C defining document> (AIFF-C only).
31 MARK
32 <# of markers> (2 bytes)
33 list of markers:
34 <marker ID> (2 bytes, must be > 0)
35 <position> (4 bytes)
36 <marker name> ("pstring")
37 COMM
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
42 floating point)
43 in AIFF-C files only:
44 <compression type> (4 bytes)
45 <human-readable version of compression type> ("pstring")
46 SSND
47 <offset> (4 bytes, not used by this program)
48 <blocksize> (4 bytes, not used by this program)
49 <sound data>
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.
54 Usage.
56 Reading AIFF files:
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
65 mono, 2 for stereo)
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 position in the file.
86 The close() method is called automatically when the class instance
87 is destroyed.
89 Writing AIFF files:
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
93 close().
95 This returns an instance of a class with the following public methods:
96 aiff() -- create an AIFF file (AIFF-C default)
97 aifc() -- create an AIFF-C file
98 setnchannels(n) -- set the number of channels
99 setsampwidth(n) -- set the sample width
100 setframerate(n) -- set the frame rate
101 setnframes(n) -- set the number of frames
102 setcomptype(type, name)
103 -- set the compression type and the
104 human-readable compression type
105 setparams(tuple)
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
113 file header
114 writeframes(data)
115 -- write audio frames and patch up the file header
116 close() -- patch up the file header and close the
117 output file
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
121 be patched up.
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
129 is destroyed.
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
134 writeframesraw.
137 import struct
138 import builtins
140 __all__ = ["Error", "open", "openfp"]
142 class Error(Exception):
143 pass
145 _AIFC_version = 0xA2805140 # Version 1 of AIFF-C
147 def _read_long(file):
148 try:
149 return struct.unpack('>l', file.read(4))[0]
150 except struct.error:
151 raise EOFError
153 def _read_ulong(file):
154 try:
155 return struct.unpack('>L', file.read(4))[0]
156 except struct.error:
157 raise EOFError
159 def _read_short(file):
160 try:
161 return struct.unpack('>h', file.read(2))[0]
162 except struct.error:
163 raise EOFError
165 def _read_string(file):
166 length = ord(file.read(1))
167 if length == 0:
168 data = b''
169 else:
170 data = file.read(length)
171 if length & 1 == 0:
172 dummy = file.read(1)
173 return data
175 _HUGE_VAL = 1.79769313486231e+308 # See <limits.h>
177 def _read_float(f): # 10 bytes
178 expon = _read_short(f) # 2 bytes
179 sign = 1
180 if expon < 0:
181 sign = -1
182 expon = expon + 0x8000
183 himant = _read_ulong(f) # 4 bytes
184 lomant = _read_ulong(f) # 4 bytes
185 if expon == himant == lomant == 0:
186 f = 0.0
187 elif expon == 0x7FFF:
188 f = _HUGE_VAL
189 else:
190 expon = expon - 16383
191 f = (himant * 0x100000000 + lomant) * pow(2.0, expon - 63)
192 return sign * f
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):
201 if len(s) > 255:
202 raise ValueError("string exceeds maximum pstring length")
203 f.write(struct.pack('b', len(s)))
204 f.write(s)
205 if len(s) & 1 == 0:
206 f.write(b'\x00')
208 def _write_float(f, x):
209 import math
210 if x < 0:
211 sign = 0x8000
212 x = x * -1
213 else:
214 sign = 0
215 if x == 0:
216 expon = 0
217 himant = 0
218 lomant = 0
219 else:
220 fmant, expon = math.frexp(x)
221 if expon > 16384 or fmant >= 1: # Infinity or NaN
222 expon = sign|0x7FFF
223 himant = 0
224 lomant = 0
225 else: # Finite
226 expon = expon + 16382
227 if expon < 0: # denormalized
228 fmant = math.ldexp(fmant, expon)
229 expon = 0
230 expon = expon | sign
231 fmant = math.ldexp(fmant, 32)
232 fsmant = math.floor(fmant)
233 himant = int(fsmant)
234 fmant = math.ldexp(fmant - fsmant, 32)
235 fsmant = math.floor(fmant)
236 lomant = int(fsmant)
237 _write_short(f, expon)
238 _write_long(f, himant)
239 _write_long(f, lomant)
241 from chunk import Chunk
243 class Aifc_read:
244 # Variables used in this class:
246 # These variables are available to the user though appropriate
247 # methods of this class:
248 # _file -- the open file with methods read(), close(), and seek()
249 # set through the __init__() method
250 # _nchannels -- the number of audio channels
251 # available through the getnchannels() method
252 # _nframes -- the number of audio frames
253 # available through the getnframes() method
254 # _sampwidth -- the number of bytes per audio sample
255 # available through the getsampwidth() method
256 # _framerate -- the sampling frequency
257 # available through the getframerate() method
258 # _comptype -- the AIFF-C compression type ('NONE' if AIFF)
259 # available through the getcomptype() method
260 # _compname -- the human-readable AIFF-C compression type
261 # available through the getcomptype() method
262 # _markers -- the marks in the audio file
263 # available through the getmarkers() and getmark()
264 # methods
265 # _soundpos -- the position in the audio stream
266 # available through the tell() method, set through the
267 # setpos() method
269 # These variables are used internally only:
270 # _version -- the AIFF-C version number
271 # _decomp -- the decompressor from builtin module cl
272 # _comm_chunk_read -- 1 iff the COMM chunk has been read
273 # _aifc -- 1 iff reading an AIFF-C file
274 # _ssnd_seek_needed -- 1 iff positioned correctly in audio
275 # file for readframes()
276 # _ssnd_chunk -- instantiation of a chunk class for the SSND chunk
277 # _framesize -- size of one frame in the file
279 def initfp(self, file):
280 self._version = 0
281 self._convert = None
282 self._markers = []
283 self._soundpos = 0
284 self._file = file
285 chunk = Chunk(file)
286 if chunk.getname() != b'FORM':
287 raise Error('file does not start with FORM id')
288 formdata = chunk.read(4)
289 if formdata == b'AIFF':
290 self._aifc = 0
291 elif formdata == b'AIFC':
292 self._aifc = 1
293 else:
294 raise Error('not an AIFF or AIFF-C file')
295 self._comm_chunk_read = 0
296 while 1:
297 self._ssnd_seek_needed = 1
298 try:
299 chunk = Chunk(self._file)
300 except EOFError:
301 break
302 chunkname = chunk.getname()
303 if chunkname == b'COMM':
304 self._read_comm_chunk(chunk)
305 self._comm_chunk_read = 1
306 elif chunkname == b'SSND':
307 self._ssnd_chunk = chunk
308 dummy = chunk.read(8)
309 self._ssnd_seek_needed = 0
310 elif chunkname == b'FVER':
311 self._version = _read_ulong(chunk)
312 elif chunkname == b'MARK':
313 self._readmark(chunk)
314 chunk.skip()
315 if not self._comm_chunk_read or not self._ssnd_chunk:
316 raise Error('COMM chunk and/or SSND chunk missing')
318 def __init__(self, f):
319 if isinstance(f, str):
320 f = builtins.open(f, 'rb')
321 # else, assume it is an open file object already
322 self.initfp(f)
325 # User visible methods.
327 def getfp(self):
328 return self._file
330 def rewind(self):
331 self._ssnd_seek_needed = 1
332 self._soundpos = 0
334 def close(self):
335 self._file.close()
337 def tell(self):
338 return self._soundpos
340 def getnchannels(self):
341 return self._nchannels
343 def getnframes(self):
344 return self._nframes
346 def getsampwidth(self):
347 return self._sampwidth
349 def getframerate(self):
350 return self._framerate
352 def getcomptype(self):
353 return self._comptype
355 def getcompname(self):
356 return self._compname
358 ## def getversion(self):
359 ## return self._version
361 def getparams(self):
362 return self.getnchannels(), self.getsampwidth(), \
363 self.getframerate(), self.getnframes(), \
364 self.getcomptype(), self.getcompname()
366 def getmarkers(self):
367 if len(self._markers) == 0:
368 return None
369 return self._markers
371 def getmark(self, id):
372 for marker in self._markers:
373 if id == marker[0]:
374 return marker
375 raise Error('marker {0!r} does not exist'.format(id))
377 def setpos(self, pos):
378 if pos < 0 or pos > self._nframes:
379 raise Error('position not in range')
380 self._soundpos = pos
381 self._ssnd_seek_needed = 1
383 def readframes(self, nframes):
384 if self._ssnd_seek_needed:
385 self._ssnd_chunk.seek(0)
386 dummy = self._ssnd_chunk.read(8)
387 pos = self._soundpos * self._framesize
388 if pos:
389 self._ssnd_chunk.seek(pos + 8)
390 self._ssnd_seek_needed = 0
391 if nframes == 0:
392 return b''
393 data = self._ssnd_chunk.read(nframes * self._framesize)
394 if self._convert and data:
395 data = self._convert(data)
396 self._soundpos = self._soundpos + len(data) // (self._nchannels
397 * self._sampwidth)
398 return data
401 # Internal methods.
404 def _alaw2lin(self, data):
405 import audioop
406 return audioop.alaw2lin(data, 2)
408 def _ulaw2lin(self, data):
409 import audioop
410 return audioop.ulaw2lin(data, 2)
412 def _adpcm2lin(self, data):
413 import audioop
414 if not hasattr(self, '_adpcmstate'):
415 # first time
416 self._adpcmstate = None
417 data, self._adpcmstate = audioop.adpcm2lin(data, 2, self._adpcmstate)
418 return data
420 def _read_comm_chunk(self, chunk):
421 self._nchannels = _read_short(chunk)
422 self._nframes = _read_long(chunk)
423 self._sampwidth = (_read_short(chunk) + 7) // 8
424 self._framerate = int(_read_float(chunk))
425 self._framesize = self._nchannels * self._sampwidth
426 if self._aifc:
427 #DEBUG: SGI's soundeditor produces a bad size :-(
428 kludge = 0
429 if chunk.chunksize == 18:
430 kludge = 1
431 print('Warning: bad COMM chunk size')
432 chunk.chunksize = 23
433 #DEBUG end
434 self._comptype = chunk.read(4)
435 #DEBUG start
436 if kludge:
437 length = ord(chunk.file.read(1))
438 if length & 1 == 0:
439 length = length + 1
440 chunk.chunksize = chunk.chunksize + length
441 chunk.file.seek(-1, 1)
442 #DEBUG end
443 self._compname = _read_string(chunk)
444 if self._comptype != b'NONE':
445 if self._comptype == b'G722':
446 self._convert = self._adpcm2lin
447 self._framesize = self._framesize // 4
448 elif self._comptype in (b'ulaw', b'ULAW'):
449 self._convert = self._ulaw2lin
450 self._framesize = self._framesize // 2
451 elif self._comptype in (b'alaw', b'ALAW'):
452 self._convert = self._alaw2lin
453 self._framesize = self._framesize // 2
454 else:
455 raise Error('unsupported compression type')
456 else:
457 self._comptype = b'NONE'
458 self._compname = b'not compressed'
460 def _readmark(self, chunk):
461 nmarkers = _read_short(chunk)
462 # Some files appear to contain invalid counts.
463 # Cope with this by testing for EOF.
464 try:
465 for i in range(nmarkers):
466 id = _read_short(chunk)
467 pos = _read_long(chunk)
468 name = _read_string(chunk)
469 if pos or name:
470 # some files appear to have
471 # dummy markers consisting of
472 # a position 0 and name ''
473 self._markers.append((id, pos, name))
474 except EOFError:
475 print('Warning: MARK chunk contains only', end=' ')
476 print(len(self._markers), end=' ')
477 if len(self._markers) == 1: print('marker', end=' ')
478 else: print('markers', end=' ')
479 print('instead of', nmarkers)
481 class Aifc_write:
482 # Variables used in this class:
484 # These variables are user settable through appropriate methods
485 # of this class:
486 # _file -- the open file with methods write(), close(), tell(), seek()
487 # set through the __init__() method
488 # _comptype -- the AIFF-C compression type ('NONE' in AIFF)
489 # set through the setcomptype() or setparams() method
490 # _compname -- the human-readable AIFF-C compression type
491 # set through the setcomptype() or setparams() method
492 # _nchannels -- the number of audio channels
493 # set through the setnchannels() or setparams() method
494 # _sampwidth -- the number of bytes per audio sample
495 # set through the setsampwidth() or setparams() method
496 # _framerate -- the sampling frequency
497 # set through the setframerate() or setparams() method
498 # _nframes -- the number of audio frames written to the header
499 # set through the setnframes() or setparams() method
500 # _aifc -- whether we're writing an AIFF-C file or an AIFF file
501 # set through the aifc() method, reset through the
502 # aiff() method
504 # These variables are used internally only:
505 # _version -- the AIFF-C version number
506 # _comp -- the compressor from builtin module cl
507 # _nframeswritten -- the number of audio frames actually written
508 # _datalength -- the size of the audio samples written to the header
509 # _datawritten -- the size of the audio samples actually written
511 def __init__(self, f):
512 if isinstance(f, str):
513 filename = f
514 f = builtins.open(f, 'wb')
515 else:
516 # else, assume it is an open file object already
517 filename = '???'
518 self.initfp(f)
519 if filename[-5:] == '.aiff':
520 self._aifc = 0
521 else:
522 self._aifc = 1
524 def initfp(self, file):
525 self._file = file
526 self._version = _AIFC_version
527 self._comptype = b'NONE'
528 self._compname = b'not compressed'
529 self._convert = None
530 self._nchannels = 0
531 self._sampwidth = 0
532 self._framerate = 0
533 self._nframes = 0
534 self._nframeswritten = 0
535 self._datawritten = 0
536 self._datalength = 0
537 self._markers = []
538 self._marklength = 0
539 self._aifc = 1 # AIFF-C is default
541 def __del__(self):
542 if self._file:
543 self.close()
546 # User visible methods.
548 def aiff(self):
549 if self._nframeswritten:
550 raise Error('cannot change parameters after starting to write')
551 self._aifc = 0
553 def aifc(self):
554 if self._nframeswritten:
555 raise Error('cannot change parameters after starting to write')
556 self._aifc = 1
558 def setnchannels(self, nchannels):
559 if self._nframeswritten:
560 raise Error('cannot change parameters after starting to write')
561 if nchannels < 1:
562 raise Error('bad # of channels')
563 self._nchannels = nchannels
565 def getnchannels(self):
566 if not self._nchannels:
567 raise Error('number of channels not set')
568 return self._nchannels
570 def setsampwidth(self, sampwidth):
571 if self._nframeswritten:
572 raise Error('cannot change parameters after starting to write')
573 if sampwidth < 1 or sampwidth > 4:
574 raise Error('bad sample width')
575 self._sampwidth = sampwidth
577 def getsampwidth(self):
578 if not self._sampwidth:
579 raise Error('sample width not set')
580 return self._sampwidth
582 def setframerate(self, framerate):
583 if self._nframeswritten:
584 raise Error('cannot change parameters after starting to write')
585 if framerate <= 0:
586 raise Error('bad frame rate')
587 self._framerate = framerate
589 def getframerate(self):
590 if not self._framerate:
591 raise Error('frame rate not set')
592 return self._framerate
594 def setnframes(self, nframes):
595 if self._nframeswritten:
596 raise Error('cannot change parameters after starting to write')
597 self._nframes = nframes
599 def getnframes(self):
600 return self._nframeswritten
602 def setcomptype(self, comptype, compname):
603 if self._nframeswritten:
604 raise Error('cannot change parameters after starting to write')
605 if comptype not in (b'NONE', b'ulaw', b'ULAW',
606 b'alaw', b'ALAW', b'G722'):
607 raise Error('unsupported compression type')
608 self._comptype = comptype
609 self._compname = compname
611 def getcomptype(self):
612 return self._comptype
614 def getcompname(self):
615 return self._compname
617 ## def setversion(self, version):
618 ## if self._nframeswritten:
619 ## raise Error, 'cannot change parameters after starting to write'
620 ## self._version = version
622 def setparams(self, params):
623 nchannels, sampwidth, framerate, nframes, comptype, compname = params
624 if self._nframeswritten:
625 raise Error('cannot change parameters after starting to write')
626 if comptype not in (b'NONE', b'ulaw', b'ULAW',
627 b'alaw', b'ALAW', b'G722'):
628 raise Error('unsupported compression type')
629 self.setnchannels(nchannels)
630 self.setsampwidth(sampwidth)
631 self.setframerate(framerate)
632 self.setnframes(nframes)
633 self.setcomptype(comptype, compname)
635 def getparams(self):
636 if not self._nchannels or not self._sampwidth or not self._framerate:
637 raise Error('not all parameters set')
638 return self._nchannels, self._sampwidth, self._framerate, \
639 self._nframes, self._comptype, self._compname
641 def setmark(self, id, pos, name):
642 if id <= 0:
643 raise Error('marker ID must be > 0')
644 if pos < 0:
645 raise Error('marker position must be >= 0')
646 if not isinstance(name, str):
647 raise Error('marker name must be a string')
648 for i in range(len(self._markers)):
649 if id == self._markers[i][0]:
650 self._markers[i] = id, pos, name
651 return
652 self._markers.append((id, pos, name))
654 def getmark(self, id):
655 for marker in self._markers:
656 if id == marker[0]:
657 return marker
658 raise Error('marker {0!r} does not exist'.format(id))
660 def getmarkers(self):
661 if len(self._markers) == 0:
662 return None
663 return self._markers
665 def tell(self):
666 return self._nframeswritten
668 def writeframesraw(self, data):
669 self._ensure_header_written(len(data))
670 nframes = len(data) // (self._sampwidth * self._nchannels)
671 if self._convert:
672 data = self._convert(data)
673 self._file.write(data)
674 self._nframeswritten = self._nframeswritten + nframes
675 self._datawritten = self._datawritten + len(data)
677 def writeframes(self, data):
678 self.writeframesraw(data)
679 if self._nframeswritten != self._nframes or \
680 self._datalength != self._datawritten:
681 self._patchheader()
683 def close(self):
684 self._ensure_header_written(0)
685 if self._datawritten & 1:
686 # quick pad to even size
687 self._file.write(b'\x00')
688 self._datawritten = self._datawritten + 1
689 self._writemarkers()
690 if self._nframeswritten != self._nframes or \
691 self._datalength != self._datawritten or \
692 self._marklength:
693 self._patchheader()
694 # Prevent ref cycles
695 self._convert = None
696 self._file.close()
699 # Internal methods.
702 def _lin2alaw(self, data):
703 import audioop
704 return audioop.lin2alaw(data, 2)
706 def _lin2ulaw(self, data):
707 import audioop
708 return audioop.lin2ulaw(data, 2)
710 def _lin2adpcm(self, data):
711 import audioop
712 if not hasattr(self, '_adpcmstate'):
713 self._adpcmstate = None
714 data, self._adpcmstate = audioop.lin2adpcm(data, 2, self._adpcmstate)
715 return data
717 def _ensure_header_written(self, datasize):
718 if not self._nframeswritten:
719 if self._comptype in (b'ULAW', b'ALAW'):
720 if not self._sampwidth:
721 self._sampwidth = 2
722 if self._sampwidth != 2:
723 raise Error('sample width must be 2 when compressing '
724 'with ulaw/ULAW or alaw/ALAW')
725 if self._comptype == b'G722':
726 if not self._sampwidth:
727 self._sampwidth = 2
728 if self._sampwidth != 2:
729 raise Error('sample width must be 2 when compressing '
730 'with G7.22 (ADPCM)')
731 if not self._nchannels:
732 raise Error('# channels not specified')
733 if not self._sampwidth:
734 raise Error('sample width not specified')
735 if not self._framerate:
736 raise Error('sampling rate not specified')
737 self._write_header(datasize)
739 def _init_compression(self):
740 if self._comptype == b'G722':
741 self._convert = self._lin2adpcm
742 elif self._comptype in (b'ulaw', b'ULAW'):
743 self._convert = self._lin2ulaw
744 elif self._comptype in (b'alaw', b'ALAW'):
745 self._convert = self._lin2alaw
746 else:
747 raise Error('unsupported compression type')
749 def _write_header(self, initlength):
750 if self._aifc and self._comptype != b'NONE':
751 self._init_compression()
752 self._file.write(b'FORM')
753 if not self._nframes:
754 self._nframes = initlength // (self._nchannels * self._sampwidth)
755 self._datalength = self._nframes * self._nchannels * self._sampwidth
756 if self._datalength & 1:
757 self._datalength = self._datalength + 1
758 if self._aifc:
759 if self._comptype in (b'ulaw', b'ULAW', b'alaw', b'ALAW'):
760 self._datalength = self._datalength // 2
761 if self._datalength & 1:
762 self._datalength = self._datalength + 1
763 elif self._comptype == b'G722':
764 self._datalength = (self._datalength + 3) // 4
765 if self._datalength & 1:
766 self._datalength = self._datalength + 1
767 self._form_length_pos = self._file.tell()
768 commlength = self._write_form_length(self._datalength)
769 if self._aifc:
770 self._file.write(b'AIFC')
771 self._file.write(b'FVER')
772 _write_long(self._file, 4)
773 _write_long(self._file, self._version)
774 else:
775 self._file.write(b'AIFF')
776 self._file.write(b'COMM')
777 _write_long(self._file, commlength)
778 _write_short(self._file, self._nchannels)
779 self._nframes_pos = self._file.tell()
780 _write_long(self._file, self._nframes)
781 _write_short(self._file, self._sampwidth * 8)
782 _write_float(self._file, self._framerate)
783 if self._aifc:
784 self._file.write(self._comptype)
785 _write_string(self._file, self._compname)
786 self._file.write(b'SSND')
787 self._ssnd_length_pos = self._file.tell()
788 _write_long(self._file, self._datalength + 8)
789 _write_long(self._file, 0)
790 _write_long(self._file, 0)
792 def _write_form_length(self, datalength):
793 if self._aifc:
794 commlength = 18 + 5 + len(self._compname)
795 if commlength & 1:
796 commlength = commlength + 1
797 verslength = 12
798 else:
799 commlength = 18
800 verslength = 0
801 _write_long(self._file, 4 + verslength + self._marklength + \
802 8 + commlength + 16 + datalength)
803 return commlength
805 def _patchheader(self):
806 curpos = self._file.tell()
807 if self._datawritten & 1:
808 datalength = self._datawritten + 1
809 self._file.write(b'\x00')
810 else:
811 datalength = self._datawritten
812 if datalength == self._datalength and \
813 self._nframes == self._nframeswritten and \
814 self._marklength == 0:
815 self._file.seek(curpos, 0)
816 return
817 self._file.seek(self._form_length_pos, 0)
818 dummy = self._write_form_length(datalength)
819 self._file.seek(self._nframes_pos, 0)
820 _write_long(self._file, self._nframeswritten)
821 self._file.seek(self._ssnd_length_pos, 0)
822 _write_long(self._file, datalength + 8)
823 self._file.seek(curpos, 0)
824 self._nframes = self._nframeswritten
825 self._datalength = datalength
827 def _writemarkers(self):
828 if len(self._markers) == 0:
829 return
830 self._file.write(b'MARK')
831 length = 2
832 for marker in self._markers:
833 id, pos, name = marker
834 length = length + len(name) + 1 + 6
835 if len(name) & 1 == 0:
836 length = length + 1
837 _write_long(self._file, length)
838 self._marklength = length + 8
839 _write_short(self._file, len(self._markers))
840 for marker in self._markers:
841 id, pos, name = marker
842 _write_short(self._file, id)
843 _write_long(self._file, pos)
844 _write_string(self._file, name)
846 def open(f, mode=None):
847 if mode is None:
848 if hasattr(f, 'mode'):
849 mode = f.mode
850 else:
851 mode = 'rb'
852 if mode in ('r', 'rb'):
853 return Aifc_read(f)
854 elif mode in ('w', 'wb'):
855 return Aifc_write(f)
856 else:
857 raise Error("mode must be 'r', 'rb', 'w', or 'wb'")
859 openfp = open # B/W compatibility
861 if __name__ == '__main__':
862 import sys
863 if not sys.argv[1:]:
864 sys.argv.append('/usr/demos/data/audio/bach.aiff')
865 fn = sys.argv[1]
866 f = open(fn, 'r')
867 print("Reading", fn)
868 print("nchannels =", f.getnchannels())
869 print("nframes =", f.getnframes())
870 print("sampwidth =", f.getsampwidth())
871 print("framerate =", f.getframerate())
872 print("comptype =", f.getcomptype())
873 print("compname =", f.getcompname())
874 if sys.argv[2:]:
875 gn = sys.argv[2]
876 print("Writing", gn)
877 g = open(gn, 'w')
878 g.setparams(f.getparams())
879 while 1:
880 data = f.readframes(1024)
881 if not data:
882 break
883 g.writeframes(data)
884 g.close()
885 f.close()
886 print("Done.")