Copy reduce() to _functools so to have functools.reduce() not raise a warning
[python.git] / Lib / aifc.py
blobf663dd67d2e7a1f149ab99753097757aff50dc93
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 __builtin__
140 __all__ = ["Error","open","openfp"]
142 class Error(Exception):
143 pass
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):
151 try:
152 return struct.unpack('>l', file.read(4))[0]
153 except struct.error:
154 raise EOFError
156 def _read_ulong(file):
157 try:
158 return struct.unpack('>L', file.read(4))[0]
159 except struct.error:
160 raise EOFError
162 def _read_short(file):
163 try:
164 return struct.unpack('>h', file.read(2))[0]
165 except struct.error:
166 raise EOFError
168 def _read_string(file):
169 length = ord(file.read(1))
170 if length == 0:
171 data = ''
172 else:
173 data = file.read(length)
174 if length & 1 == 0:
175 dummy = file.read(1)
176 return data
178 _HUGE_VAL = 1.79769313486231e+308 # See <limits.h>
180 def _read_float(f): # 10 bytes
181 expon = _read_short(f) # 2 bytes
182 sign = 1
183 if expon < 0:
184 sign = -1
185 expon = expon + 0x8000
186 himant = _read_ulong(f) # 4 bytes
187 lomant = _read_ulong(f) # 4 bytes
188 if expon == himant == lomant == 0:
189 f = 0.0
190 elif expon == 0x7FFF:
191 f = _HUGE_VAL
192 else:
193 expon = expon - 16383
194 f = (himant * 0x100000000L + lomant) * pow(2.0, expon - 63)
195 return sign * f
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):
204 if len(s) > 255:
205 raise ValueError("string exceeds maximum pstring length")
206 f.write(chr(len(s)))
207 f.write(s)
208 if len(s) & 1 == 0:
209 f.write(chr(0))
211 def _write_float(f, x):
212 import math
213 if x < 0:
214 sign = 0x8000
215 x = x * -1
216 else:
217 sign = 0
218 if x == 0:
219 expon = 0
220 himant = 0
221 lomant = 0
222 else:
223 fmant, expon = math.frexp(x)
224 if expon > 16384 or fmant >= 1: # Infinity or NaN
225 expon = sign|0x7FFF
226 himant = 0
227 lomant = 0
228 else: # Finite
229 expon = expon + 16382
230 if expon < 0: # denormalized
231 fmant = math.ldexp(fmant, expon)
232 expon = 0
233 expon = expon | sign
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
246 class Aifc_read:
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()
267 # methods
268 # _soundpos -- the position in the audio stream
269 # available through the tell() method, set through the
270 # setpos() method
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):
283 self._version = 0
284 self._decomp = None
285 self._convert = None
286 self._markers = []
287 self._soundpos = 0
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':
293 self._aifc = 0
294 elif formdata == 'AIFC':
295 self._aifc = 1
296 else:
297 raise Error, 'not an AIFF or AIFF-C file'
298 self._comm_chunk_read = 0
299 while 1:
300 self._ssnd_seek_needed = 1
301 try:
302 chunk = Chunk(self._file)
303 except EOFError:
304 break
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:
318 pass
319 else:
320 raise Error, 'unrecognized chunk type '+chunk.chunkname
321 chunk.skip()
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:
325 import cl
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:
330 params[1] = cl.MONO
331 elif self._nchannels == 2:
332 params[1] = cl.STEREO_INTERLEAVED
333 else:
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
341 self.initfp(f)
344 # User visible methods.
346 def getfp(self):
347 return self._file
349 def rewind(self):
350 self._ssnd_seek_needed = 1
351 self._soundpos = 0
353 def close(self):
354 if self._decomp:
355 self._decomp.CloseDecompressor()
356 self._decomp = None
357 self._file = None
359 def tell(self):
360 return self._soundpos
362 def getnchannels(self):
363 return self._nchannels
365 def getnframes(self):
366 return self._nframes
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
383 def getparams(self):
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:
390 return None
391 return self._markers
393 def getmark(self, id):
394 for marker in self._markers:
395 if id == marker[0]:
396 return marker
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'
402 self._soundpos = pos
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
410 if pos:
411 self._ssnd_chunk.seek(pos + 8)
412 self._ssnd_seek_needed = 0
413 if nframes == 0:
414 return ''
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)
419 return data
422 # Internal methods.
425 def _decomp_data(self, data):
426 import cl
427 dummy = self._decomp.SetParam(cl.FRAME_BUFFER_SIZE,
428 len(data) * 2)
429 return self._decomp.Decompress(len(data) / self._nchannels,
430 data)
432 def _ulaw2lin(self, data):
433 import audioop
434 return audioop.ulaw2lin(data, 2)
436 def _adpcm2lin(self, data):
437 import audioop
438 if not hasattr(self, '_adpcmstate'):
439 # first time
440 self._adpcmstate = None
441 data, self._adpcmstate = audioop.adpcm2lin(data, 2,
442 self._adpcmstate)
443 return data
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
451 if self._aifc:
452 #DEBUG: SGI's soundeditor produces a bad size :-(
453 kludge = 0
454 if chunk.chunksize == 18:
455 kludge = 1
456 print 'Warning: bad COMM chunk size'
457 chunk.chunksize = 23
458 #DEBUG end
459 self._comptype = chunk.read(4)
460 #DEBUG start
461 if kludge:
462 length = ord(chunk.file.read(1))
463 if length & 1 == 0:
464 length = length + 1
465 chunk.chunksize = chunk.chunksize + length
466 chunk.file.seek(-1, 1)
467 #DEBUG end
468 self._compname = _read_string(chunk)
469 if self._comptype != 'NONE':
470 if self._comptype == 'G722':
471 try:
472 import audioop
473 except ImportError:
474 pass
475 else:
476 self._convert = self._adpcm2lin
477 self._framesize = self._framesize / 4
478 return
479 # for ULAW and ALAW try Compression Library
480 try:
481 import cl
482 except ImportError:
483 if self._comptype == 'ULAW':
484 try:
485 import audioop
486 self._convert = self._ulaw2lin
487 self._framesize = self._framesize / 2
488 return
489 except ImportError:
490 pass
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
498 else:
499 raise Error, 'unsupported compression type'
500 self._decomp = cl.OpenDecompressor(scheme)
501 self._convert = self._decomp_data
502 else:
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.
510 try:
511 for i in range(nmarkers):
512 id = _read_short(chunk)
513 pos = _read_long(chunk)
514 name = _read_string(chunk)
515 if pos or name:
516 # some files appear to have
517 # dummy markers consisting of
518 # a position 0 and name ''
519 self._markers.append((id, pos, name))
520 except EOFError:
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
527 class Aifc_write:
528 # Variables used in this class:
530 # These variables are user settable through appropriate methods
531 # of this class:
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
548 # aiff() method
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(''):
559 filename = f
560 f = __builtin__.open(f, 'wb')
561 else:
562 # else, assume it is an open file object already
563 filename = '???'
564 self.initfp(f)
565 if filename[-5:] == '.aiff':
566 self._aifc = 0
567 else:
568 self._aifc = 1
570 def initfp(self, file):
571 self._file = file
572 self._version = _AIFC_version
573 self._comptype = 'NONE'
574 self._compname = 'not compressed'
575 self._comp = None
576 self._convert = None
577 self._nchannels = 0
578 self._sampwidth = 0
579 self._framerate = 0
580 self._nframes = 0
581 self._nframeswritten = 0
582 self._datawritten = 0
583 self._datalength = 0
584 self._markers = []
585 self._marklength = 0
586 self._aifc = 1 # AIFF-C is default
588 def __del__(self):
589 if self._file:
590 self.close()
593 # User visible methods.
595 def aiff(self):
596 if self._nframeswritten:
597 raise Error, 'cannot change parameters after starting to write'
598 self._aifc = 0
600 def aifc(self):
601 if self._nframeswritten:
602 raise Error, 'cannot change parameters after starting to write'
603 self._aifc = 1
605 def setnchannels(self, nchannels):
606 if self._nframeswritten:
607 raise Error, 'cannot change parameters after starting to write'
608 if nchannels < 1:
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'
632 if framerate <= 0:
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)
680 def getparams(self):
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):
687 if id <= 0:
688 raise Error, 'marker ID must be > 0'
689 if pos < 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
696 return
697 self._markers.append((id, pos, name))
699 def getmark(self, id):
700 for marker in self._markers:
701 if id == marker[0]:
702 return marker
703 raise Error, 'marker %r does not exist' % (id,)
705 def getmarkers(self):
706 if len(self._markers) == 0:
707 return None
708 return self._markers
710 def tell(self):
711 return self._nframeswritten
713 def writeframesraw(self, data):
714 self._ensure_header_written(len(data))
715 nframes = len(data) / (self._sampwidth * self._nchannels)
716 if self._convert:
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:
726 self._patchheader()
728 def close(self):
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
734 self._writemarkers()
735 if self._nframeswritten != self._nframes or \
736 self._datalength != self._datawritten or \
737 self._marklength:
738 self._patchheader()
739 if self._comp:
740 self._comp.CloseCompressor()
741 self._comp = None
742 self._file.flush()
743 self._file = None
746 # Internal methods.
749 def _comp_data(self, data):
750 import cl
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):
756 import audioop
757 return audioop.lin2ulaw(data, 2)
759 def _lin2adpcm(self, data):
760 import audioop
761 if not hasattr(self, '_adpcmstate'):
762 self._adpcmstate = None
763 data, self._adpcmstate = audioop.lin2adpcm(data, 2,
764 self._adpcmstate)
765 return data
767 def _ensure_header_written(self, datasize):
768 if not self._nframeswritten:
769 if self._comptype in ('ULAW', 'ALAW'):
770 if not self._sampwidth:
771 self._sampwidth = 2
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:
776 self._sampwidth = 2
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._lin2adpcm
790 return
791 try:
792 import cl
793 except ImportError:
794 if self._comptype == 'ULAW':
795 try:
796 import audioop
797 self._convert = self._lin2ulaw
798 return
799 except ImportError:
800 pass
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
806 else:
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:
815 params[1] = cl.MONO
816 elif self._nchannels == 2:
817 params[1] = cl.STEREO_INTERLEAVED
818 else:
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
834 if self._aifc:
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)
845 if self._aifc:
846 self._file.write('AIFC')
847 self._file.write('FVER')
848 _write_long(self._file, 4)
849 _write_long(self._file, self._version)
850 else:
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)
859 if self._aifc:
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):
869 if self._aifc:
870 commlength = 18 + 5 + len(self._compname)
871 if commlength & 1:
872 commlength = commlength + 1
873 verslength = 12
874 else:
875 commlength = 18
876 verslength = 0
877 _write_long(self._file, 4 + verslength + self._marklength + \
878 8 + commlength + 16 + datalength)
879 return commlength
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))
886 else:
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)
892 return
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:
905 return
906 self._file.write('MARK')
907 length = 2
908 for marker in self._markers:
909 id, pos, name = marker
910 length = length + len(name) + 1 + 6
911 if len(name) & 1 == 0:
912 length = length + 1
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):
923 if mode is None:
924 if hasattr(f, 'mode'):
925 mode = f.mode
926 else:
927 mode = 'rb'
928 if mode in ('r', 'rb'):
929 return Aifc_read(f)
930 elif mode in ('w', 'wb'):
931 return Aifc_write(f)
932 else:
933 raise Error, "mode must be 'r', 'rb', 'w', or 'wb'"
935 openfp = open # B/W compatibility
937 if __name__ == '__main__':
938 import sys
939 if not sys.argv[1:]:
940 sys.argv.append('/usr/demos/data/audio/bach.aiff')
941 fn = sys.argv[1]
942 f = open(fn, 'r')
943 print "Reading", fn
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()
950 if sys.argv[2:]:
951 gn = sys.argv[2]
952 print "Writing", gn
953 g = open(gn, 'w')
954 g.setparams(f.getparams())
955 while 1:
956 data = f.readframes(1024)
957 if not data:
958 break
959 g.writeframes(data)
960 g.close()
961 f.close()
962 print "Done."