Initial commit of OpenSPARC T2 design and verification files.
[OpenSPARC-T2-DV] / tools / src / nas,5.n2.os.2 / lib / python / lib / python2.4 / aifc.py
CommitLineData
86530b38
AT
1"""Stuff to parse AIFF-C and AIFF files.
2
3Unless explicitly stated otherwise, the description below is true
4both for AIFF-C files and AIFF files.
5
6An AIFF-C file has the following structure.
7
8 +-----------------+
9 | FORM |
10 +-----------------+
11 | <size> |
12 +----+------------+
13 | | AIFC |
14 | +------------+
15 | | <chunks> |
16 | | . |
17 | | . |
18 | | . |
19 +----+------------+
20
21An AIFF file has the string "AIFF" instead of "AIFC".
22
23A chunk consists of an identifier (4 bytes) followed by a size (4 bytes,
24big endian order), followed by the data. The size field does not include
25the size of the 8 byte header.
26
27The following chunk types are recognized.
28
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>
50
51A pstring consists of 1 byte length, a string of characters, and 0 or 1
52byte pad to make the total length even.
53
54Usage.
55
56Reading AIFF files:
57 f = aifc.open(file, 'r')
58where file is either the name of a file or an open file pointer.
59The open file pointer must have methods read(), seek(), and close().
60In some types of audio files, if the setpos() method is not used,
61the seek() method is not necessary.
62
63This 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)
83The position returned by tell(), the position given to setpos() and
84the position of marks are all compatible and have nothing to do with
85the actual position in the file.
86The close() method is called automatically when the class instance
87is destroyed.
88
89Writing AIFF files:
90 f = aifc.open(file, 'w')
91where file is either the name of a file or an open file pointer.
92The open file pointer must have methods write(), tell(), seek(), and
93close().
94
95This 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
118You should set the parameters before the first writeframesraw or
119writeframes. The total number of frames does not need to be set,
120but when it is set to the correct value, the header does not have to
121be patched up.
122It is best to first set all parameters, perhaps possibly the
123compression type, and then write audio frames using writeframesraw.
124When all frames have been written, either call writeframes('') or
125close() to patch up the sizes in the header.
126Marks can be added anytime. If there are any marks, ypu must call
127close() after all frames have been written.
128The close() method is called automatically when the class instance
129is destroyed.
130
131When a file is opened with the extension '.aiff', an AIFF file is
132written, otherwise an AIFF-C file is written. This default can be
133changed by calling aiff() or aifc() before the first writeframes or
134writeframesraw.
135"""
136
137import struct
138import __builtin__
139
140__all__ = ["Error","open","openfp"]
141
142class Error(Exception):
143 pass
144
145_AIFC_version = 0xA2805140L # Version 1 of AIFF-C
146
147_skiplist = 'COMT', 'INST', 'MIDI', 'AESD', \
148 'APPL', 'NAME', 'AUTH', '(c) ', 'ANNO'
149
150def _read_long(file):
151 try:
152 return struct.unpack('>l', file.read(4))[0]
153 except struct.error:
154 raise EOFError
155
156def _read_ulong(file):
157 try:
158 return struct.unpack('>L', file.read(4))[0]
159 except struct.error:
160 raise EOFError
161
162def _read_short(file):
163 try:
164 return struct.unpack('>h', file.read(2))[0]
165 except struct.error:
166 raise EOFError
167
168def _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
177
178_HUGE_VAL = 1.79769313486231e+308 # See <limits.h>
179
180def _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
196
197def _write_short(f, x):
198 f.write(struct.pack('>h', x))
199
200def _write_long(f, x):
201 f.write(struct.pack('>L', x))
202
203def _write_string(f, s):
204 f.write(chr(len(s)))
205 f.write(s)
206 if len(s) & 1 == 0:
207 f.write(chr(0))
208
209def _write_float(f, x):
210 import math
211 if x < 0:
212 sign = 0x8000
213 x = x * -1
214 else:
215 sign = 0
216 if x == 0:
217 expon = 0
218 himant = 0
219 lomant = 0
220 else:
221 fmant, expon = math.frexp(x)
222 if expon > 16384 or fmant >= 1: # Infinity or NaN
223 expon = sign|0x7FFF
224 himant = 0
225 lomant = 0
226 else: # Finite
227 expon = expon + 16382
228 if expon < 0: # denormalized
229 fmant = math.ldexp(fmant, expon)
230 expon = 0
231 expon = expon | sign
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)
241
242from chunk import Chunk
243
244class Aifc_read:
245 # Variables used in this class:
246 #
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()
265 # methods
266 # _soundpos -- the position in the audio stream
267 # available through the tell() method, set through the
268 # setpos() method
269 #
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
279
280 def initfp(self, file):
281 self._version = 0
282 self._decomp = None
283 self._convert = None
284 self._markers = []
285 self._soundpos = 0
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':
291 self._aifc = 0
292 elif formdata == 'AIFC':
293 self._aifc = 1
294 else:
295 raise Error, 'not an AIFF or AIFF-C file'
296 self._comm_chunk_read = 0
297 while 1:
298 self._ssnd_seek_needed = 1
299 try:
300 chunk = Chunk(self._file)
301 except EOFError:
302 break
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:
316 pass
317 else:
318 raise Error, 'unrecognized chunk type '+chunk.chunkname
319 chunk.skip()
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:
323 import cl
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:
328 params[1] = cl.MONO
329 elif self._nchannels == 2:
330 params[1] = cl.STEREO_INTERLEAVED
331 else:
332 raise Error, 'cannot compress more than 2 channels'
333 self._decomp.SetParams(params)
334
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
339 self.initfp(f)
340
341 #
342 # User visible methods.
343 #
344 def getfp(self):
345 return self._file
346
347 def rewind(self):
348 self._ssnd_seek_needed = 1
349 self._soundpos = 0
350
351 def close(self):
352 if self._decomp:
353 self._decomp.CloseDecompressor()
354 self._decomp = None
355 self._file = None
356
357 def tell(self):
358 return self._soundpos
359
360 def getnchannels(self):
361 return self._nchannels
362
363 def getnframes(self):
364 return self._nframes
365
366 def getsampwidth(self):
367 return self._sampwidth
368
369 def getframerate(self):
370 return self._framerate
371
372 def getcomptype(self):
373 return self._comptype
374
375 def getcompname(self):
376 return self._compname
377
378## def getversion(self):
379## return self._version
380
381 def getparams(self):
382 return self.getnchannels(), self.getsampwidth(), \
383 self.getframerate(), self.getnframes(), \
384 self.getcomptype(), self.getcompname()
385
386 def getmarkers(self):
387 if len(self._markers) == 0:
388 return None
389 return self._markers
390
391 def getmark(self, id):
392 for marker in self._markers:
393 if id == marker[0]:
394 return marker
395 raise Error, 'marker %r does not exist' % (id,)
396
397 def setpos(self, pos):
398 if pos < 0 or pos > self._nframes:
399 raise Error, 'position not in range'
400 self._soundpos = pos
401 self._ssnd_seek_needed = 1
402
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
408 if pos:
409 self._ssnd_chunk.seek(pos + 8)
410 self._ssnd_seek_needed = 0
411 if nframes == 0:
412 return ''
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)
417 return data
418
419 #
420 # Internal methods.
421 #
422
423 def _decomp_data(self, data):
424 import cl
425 dummy = self._decomp.SetParam(cl.FRAME_BUFFER_SIZE,
426 len(data) * 2)
427 return self._decomp.Decompress(len(data) / self._nchannels,
428 data)
429
430 def _ulaw2lin(self, data):
431 import audioop
432 return audioop.ulaw2lin(data, 2)
433
434 def _adpcm2lin(self, data):
435 import audioop
436 if not hasattr(self, '_adpcmstate'):
437 # first time
438 self._adpcmstate = None
439 data, self._adpcmstate = audioop.adpcm2lin(data, 2,
440 self._adpcmstate)
441 return data
442
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
449 if self._aifc:
450 #DEBUG: SGI's soundeditor produces a bad size :-(
451 kludge = 0
452 if chunk.chunksize == 18:
453 kludge = 1
454 print 'Warning: bad COMM chunk size'
455 chunk.chunksize = 23
456 #DEBUG end
457 self._comptype = chunk.read(4)
458 #DEBUG start
459 if kludge:
460 length = ord(chunk.file.read(1))
461 if length & 1 == 0:
462 length = length + 1
463 chunk.chunksize = chunk.chunksize + length
464 chunk.file.seek(-1, 1)
465 #DEBUG end
466 self._compname = _read_string(chunk)
467 if self._comptype != 'NONE':
468 if self._comptype == 'G722':
469 try:
470 import audioop
471 except ImportError:
472 pass
473 else:
474 self._convert = self._adpcm2lin
475 self._framesize = self._framesize / 4
476 return
477 # for ULAW and ALAW try Compression Library
478 try:
479 import cl
480 except ImportError:
481 if self._comptype == 'ULAW':
482 try:
483 import audioop
484 self._convert = self._ulaw2lin
485 self._framesize = self._framesize / 2
486 return
487 except ImportError:
488 pass
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
496 else:
497 raise Error, 'unsupported compression type'
498 self._decomp = cl.OpenDecompressor(scheme)
499 self._convert = self._decomp_data
500 else:
501 self._comptype = 'NONE'
502 self._compname = 'not compressed'
503
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.
508 try:
509 for i in range(nmarkers):
510 id = _read_short(chunk)
511 pos = _read_long(chunk)
512 name = _read_string(chunk)
513 if pos or name:
514 # some files appear to have
515 # dummy markers consisting of
516 # a position 0 and name ''
517 self._markers.append((id, pos, name))
518 except EOFError:
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
524
525class Aifc_write:
526 # Variables used in this class:
527 #
528 # These variables are user settable through appropriate methods
529 # of this class:
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
546 # aiff() method
547 #
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
554
555 def __init__(self, f):
556 if type(f) == type(''):
557 filename = f
558 f = __builtin__.open(f, 'wb')
559 else:
560 # else, assume it is an open file object already
561 filename = '???'
562 self.initfp(f)
563 if filename[-5:] == '.aiff':
564 self._aifc = 0
565 else:
566 self._aifc = 1
567
568 def initfp(self, file):
569 self._file = file
570 self._version = _AIFC_version
571 self._comptype = 'NONE'
572 self._compname = 'not compressed'
573 self._comp = None
574 self._convert = None
575 self._nchannels = 0
576 self._sampwidth = 0
577 self._framerate = 0
578 self._nframes = 0
579 self._nframeswritten = 0
580 self._datawritten = 0
581 self._datalength = 0
582 self._markers = []
583 self._marklength = 0
584 self._aifc = 1 # AIFF-C is default
585
586 def __del__(self):
587 if self._file:
588 self.close()
589
590 #
591 # User visible methods.
592 #
593 def aiff(self):
594 if self._nframeswritten:
595 raise Error, 'cannot change parameters after starting to write'
596 self._aifc = 0
597
598 def aifc(self):
599 if self._nframeswritten:
600 raise Error, 'cannot change parameters after starting to write'
601 self._aifc = 1
602
603 def setnchannels(self, nchannels):
604 if self._nframeswritten:
605 raise Error, 'cannot change parameters after starting to write'
606 if nchannels < 1:
607 raise Error, 'bad # of channels'
608 self._nchannels = nchannels
609
610 def getnchannels(self):
611 if not self._nchannels:
612 raise Error, 'number of channels not set'
613 return self._nchannels
614
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
621
622 def getsampwidth(self):
623 if not self._sampwidth:
624 raise Error, 'sample width not set'
625 return self._sampwidth
626
627 def setframerate(self, framerate):
628 if self._nframeswritten:
629 raise Error, 'cannot change parameters after starting to write'
630 if framerate <= 0:
631 raise Error, 'bad frame rate'
632 self._framerate = framerate
633
634 def getframerate(self):
635 if not self._framerate:
636 raise Error, 'frame rate not set'
637 return self._framerate
638
639 def setnframes(self, nframes):
640 if self._nframeswritten:
641 raise Error, 'cannot change parameters after starting to write'
642 self._nframes = nframes
643
644 def getnframes(self):
645 return self._nframeswritten
646
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
654
655 def getcomptype(self):
656 return self._comptype
657
658 def getcompname(self):
659 return self._compname
660
661## def setversion(self, version):
662## if self._nframeswritten:
663## raise Error, 'cannot change parameters after starting to write'
664## self._version = version
665
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)
676
677 def getparams(self):
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
682
683 def setmark(self, id, pos, name):
684 if id <= 0:
685 raise Error, 'marker ID must be > 0'
686 if pos < 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
693 return
694 self._markers.append((id, pos, name))
695
696 def getmark(self, id):
697 for marker in self._markers:
698 if id == marker[0]:
699 return marker
700 raise Error, 'marker %r does not exist' % (id,)
701
702 def getmarkers(self):
703 if len(self._markers) == 0:
704 return None
705 return self._markers
706
707 def tell(self):
708 return self._nframeswritten
709
710 def writeframesraw(self, data):
711 self._ensure_header_written(len(data))
712 nframes = len(data) / (self._sampwidth * self._nchannels)
713 if self._convert:
714 data = self._convert(data)
715 self._file.write(data)
716 self._nframeswritten = self._nframeswritten + nframes
717 self._datawritten = self._datawritten + len(data)
718
719 def writeframes(self, data):
720 self.writeframesraw(data)
721 if self._nframeswritten != self._nframes or \
722 self._datalength != self._datawritten:
723 self._patchheader()
724
725 def close(self):
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
731 self._writemarkers()
732 if self._nframeswritten != self._nframes or \
733 self._datalength != self._datawritten or \
734 self._marklength:
735 self._patchheader()
736 if self._comp:
737 self._comp.CloseCompressor()
738 self._comp = None
739 self._file.flush()
740 self._file = None
741
742 #
743 # Internal methods.
744 #
745
746 def _comp_data(self, data):
747 import cl
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)
751
752 def _lin2ulaw(self, data):
753 import audioop
754 return audioop.lin2ulaw(data, 2)
755
756 def _lin2adpcm(self, data):
757 import audioop
758 if not hasattr(self, '_adpcmstate'):
759 self._adpcmstate = None
760 data, self._adpcmstate = audioop.lin2adpcm(data, 2,
761 self._adpcmstate)
762 return data
763
764 def _ensure_header_written(self, datasize):
765 if not self._nframeswritten:
766 if self._comptype in ('ULAW', 'ALAW'):
767 if not self._sampwidth:
768 self._sampwidth = 2
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:
773 self._sampwidth = 2
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)
783
784 def _init_compression(self):
785 if self._comptype == 'G722':
786 self._convert = self._lin2adpcm
787 return
788 try:
789 import cl
790 except ImportError:
791 if self._comptype == 'ULAW':
792 try:
793 import audioop
794 self._convert = self._lin2ulaw
795 return
796 except ImportError:
797 pass
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
803 else:
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:
812 params[1] = cl.MONO
813 elif self._nchannels == 2:
814 params[1] = cl.STEREO_INTERLEAVED
815 else:
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
821
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
831 if self._aifc:
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)
842 if self._aifc:
843 self._file.write('AIFC')
844 self._file.write('FVER')
845 _write_long(self._file, 4)
846 _write_long(self._file, self._version)
847 else:
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)
856 if self._aifc:
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)
864
865 def _write_form_length(self, datalength):
866 if self._aifc:
867 commlength = 18 + 5 + len(self._compname)
868 if commlength & 1:
869 commlength = commlength + 1
870 verslength = 12
871 else:
872 commlength = 18
873 verslength = 0
874 _write_long(self._file, 4 + verslength + self._marklength + \
875 8 + commlength + 16 + datalength)
876 return commlength
877
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))
883 else:
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)
889 return
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
899
900 def _writemarkers(self):
901 if len(self._markers) == 0:
902 return
903 self._file.write('MARK')
904 length = 2
905 for marker in self._markers:
906 id, pos, name = marker
907 length = length + len(name) + 1 + 6
908 if len(name) & 1 == 0:
909 length = length + 1
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)
918
919def open(f, mode=None):
920 if mode is None:
921 if hasattr(f, 'mode'):
922 mode = f.mode
923 else:
924 mode = 'rb'
925 if mode in ('r', 'rb'):
926 return Aifc_read(f)
927 elif mode in ('w', 'wb'):
928 return Aifc_write(f)
929 else:
930 raise Error, "mode must be 'r', 'rb', 'w', or 'wb'"
931
932openfp = open # B/W compatibility
933
934if __name__ == '__main__':
935 import sys
936 if not sys.argv[1:]:
937 sys.argv.append('/usr/demos/data/audio/bach.aiff')
938 fn = sys.argv[1]
939 f = open(fn, 'r')
940 print "Reading", fn
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()
947 if sys.argv[2:]:
948 gn = sys.argv[2]
949 print "Writing", gn
950 g = open(gn, 'w')
951 g.setparams(f.getparams())
952 while 1:
953 data = f.readframes(1024)
954 if not data:
955 break
956 g.writeframes(data)
957 g.close()
958 f.close()
959 print "Done."