Vega strike Python Modules doc  0.5.1
Documentation of the " Modules " folder of Vega strike
 All Data Structures Namespaces Files Functions Variables
aifc.py
Go to the documentation of this file.
1 """Stuff to parse AIFF-C and AIFF files.
2 
3 Unless explicitly stated otherwise, the description below is true
4 both for AIFF-C files and AIFF files.
5 
6 An 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 
21 An AIFF file has the string "AIFF" instead of "AIFC".
22 
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.
26 
27 The 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 
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.
53 
54 Usage.
55 
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.
62 
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.
88 
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().
94 
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.
130 
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.
135 """
136 
137 import struct
138 import __builtin__
139 
140 __all__ = ["Error","open","openfp"]
141 
142 class Error(Exception):
143  pass
144 
145 _AIFC_version = 0xA2805140 # Version 1 of AIFF-C
146 
147 _skiplist = 'COMT', 'INST', 'MIDI', 'AESD', \
148  'APPL', 'NAME', 'AUTH', '(c) ', 'ANNO'
149 
150 def _read_long(file):
151  try:
152  return struct.unpack('>l', file.read(4))[0]
153  except struct.error:
154  raise EOFError
155 
156 def _read_ulong(file):
157  try:
158  return struct.unpack('>L', file.read(4))[0]
159  except struct.error:
160  raise EOFError
161 
162 def _read_short(file):
163  try:
164  return struct.unpack('>h', file.read(2))[0]
165  except struct.error:
166  raise EOFError
167 
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
177 
178 _HUGE_VAL = 1.79769313486231e+308 # See <limits.h>
179 
180 def _read_float(f): # 10 bytes
181  import math
182  expon = _read_short(f) # 2 bytes
183  sign = 1
184  if expon < 0:
185  sign = -1
186  expon = expon + 0x8000
187  himant = _read_ulong(f) # 4 bytes
188  lomant = _read_ulong(f) # 4 bytes
189  if expon == himant == lomant == 0:
190  f = 0.0
191  elif expon == 0x7FFF:
192  f = _HUGE_VAL
193  else:
194  expon = expon - 16383
195  f = (himant * 0x100000000L + lomant) * pow(2.0, expon - 63)
196  return sign * f
197 
198 def _write_short(f, x):
199  f.write(struct.pack('>h', x))
200 
201 def _write_long(f, x):
202  f.write(struct.pack('>L', x))
203 
204 def _write_string(f, s):
205  f.write(chr(len(s)))
206  f.write(s)
207  if len(s) & 1 == 0:
208  f.write(chr(0))
209 
210 def _write_float(f, x):
211  import math
212  if x < 0:
213  sign = 0x8000
214  x = x * -1
215  else:
216  sign = 0
217  if x == 0:
218  expon = 0
219  himant = 0
220  lomant = 0
221  else:
222  fmant, expon = math.frexp(x)
223  if expon > 16384 or fmant >= 1: # Infinity or NaN
224  expon = sign|0x7FFF
225  himant = 0
226  lomant = 0
227  else: # Finite
228  expon = expon + 16382
229  if expon < 0: # denormalized
230  fmant = math.ldexp(fmant, expon)
231  expon = 0
232  expon = expon | sign
233  fmant = math.ldexp(fmant, 32)
234  fsmant = math.floor(fmant)
235  himant = long(fsmant)
236  fmant = math.ldexp(fmant - fsmant, 32)
237  fsmant = math.floor(fmant)
238  lomant = long(fsmant)
239  _write_short(f, expon)
240  _write_long(f, himant)
241  _write_long(f, lomant)
242 
243 from chunk import Chunk
244 
245 class Aifc_read:
246  # Variables used in this class:
247  #
248  # These variables are available to the user though appropriate
249  # methods of this class:
250  # _file -- the open file with methods read(), close(), and seek()
251  # set through the __init__() method
252  # _nchannels -- the number of audio channels
253  # available through the getnchannels() method
254  # _nframes -- the number of audio frames
255  # available through the getnframes() method
256  # _sampwidth -- the number of bytes per audio sample
257  # available through the getsampwidth() method
258  # _framerate -- the sampling frequency
259  # available through the getframerate() method
260  # _comptype -- the AIFF-C compression type ('NONE' if AIFF)
261  # available through the getcomptype() method
262  # _compname -- the human-readable AIFF-C compression type
263  # available through the getcomptype() method
264  # _markers -- the marks in the audio file
265  # available through the getmarkers() and getmark()
266  # methods
267  # _soundpos -- the position in the audio stream
268  # available through the tell() method, set through the
269  # setpos() method
270  #
271  # These variables are used internally only:
272  # _version -- the AIFF-C version number
273  # _decomp -- the decompressor from builtin module cl
274  # _comm_chunk_read -- 1 iff the COMM chunk has been read
275  # _aifc -- 1 iff reading an AIFF-C file
276  # _ssnd_seek_needed -- 1 iff positioned correctly in audio
277  # file for readframes()
278  # _ssnd_chunk -- instantiation of a chunk class for the SSND chunk
279  # _framesize -- size of one frame in the file
280 
281  def initfp(self, file):
282  self._version = 0
283  self._decomp = None
284  self._convert = None
285  self._markers = []
286  self._soundpos = 0
287  self._file = Chunk(file)
288  if self._file.getname() != 'FORM':
289  raise Error, 'file does not start with FORM id'
290  formdata = self._file.read(4)
291  if formdata == 'AIFF':
292  self._aifc = 0
293  elif formdata == 'AIFC':
294  self._aifc = 1
295  else:
296  raise Error, 'not an AIFF or AIFF-C file'
297  self._comm_chunk_read = 0
298  while 1:
299  self._ssnd_seek_needed = 1
300  try:
301  chunk = Chunk(self._file)
302  except EOFError:
303  break
304  chunkname = chunk.getname()
305  if chunkname == 'COMM':
306  self._read_comm_chunk(chunk)
307  self._comm_chunk_read = 1
308  elif chunkname == 'SSND':
309  self._ssnd_chunk = chunk
310  dummy = chunk.read(8)
311  self._ssnd_seek_needed = 0
312  elif chunkname == 'FVER':
313  self._version = _read_long(chunk)
314  elif chunkname == 'MARK':
315  self._readmark(chunk)
316  elif chunkname in _skiplist:
317  pass
318  else:
319  raise Error, 'unrecognized chunk type '+chunk.chunkname
320  chunk.skip()
321  if not self._comm_chunk_read or not self._ssnd_chunk:
322  raise Error, 'COMM chunk and/or SSND chunk missing'
323  if self._aifc and self._decomp:
324  import cl
325  params = [cl.ORIGINAL_FORMAT, 0,
326  cl.BITS_PER_COMPONENT, self._sampwidth * 8,
327  cl.FRAME_RATE, self._framerate]
328  if self._nchannels == 1:
329  params[1] = cl.MONO
330  elif self._nchannels == 2:
331  params[1] = cl.STEREO_INTERLEAVED
332  else:
333  raise Error, 'cannot compress more than 2 channels'
334  self._decomp.SetParams(params)
335 
336  def __init__(self, f):
337  if type(f) == type(''):
338  f = __builtin__.open(f, 'rb')
339  # else, assume it is an open file object already
340  self.initfp(f)
341 
342  #
343  # User visible methods.
344  #
345  def getfp(self):
346  return self._file
347 
348  def rewind(self):
349  self._ssnd_seek_needed = 1
350  self._soundpos = 0
351 
352  def close(self):
353  if self._decomp:
354  self._decomp.CloseDecompressor()
355  self._decomp = None
356  self._file = None
357 
358  def tell(self):
359  return self._soundpos
360 
361  def getnchannels(self):
362  return self._nchannels
363 
364  def getnframes(self):
365  return self._nframes
366 
367  def getsampwidth(self):
368  return self._sampwidth
369 
370  def getframerate(self):
371  return self._framerate
372 
373  def getcomptype(self):
374  return self._comptype
375 
376  def getcompname(self):
377  return self._compname
378 
379 ## def getversion(self):
380 ## return self._version
381 
382  def getparams(self):
383  return self.getnchannels(), self.getsampwidth(), \
384  self.getframerate(), self.getnframes(), \
385  self.getcomptype(), self.getcompname()
386 
387  def getmarkers(self):
388  if len(self._markers) == 0:
389  return None
390  return self._markers
391 
392  def getmark(self, id):
393  for marker in self._markers:
394  if id == marker[0]:
395  return marker
396  raise Error, 'marker ' + `id` + ' does not exist'
397 
398  def setpos(self, pos):
399  if pos < 0 or pos > self._nframes:
400  raise Error, 'position not in range'
401  self._soundpos = pos
402  self._ssnd_seek_needed = 1
403 
404  def readframes(self, nframes):
405  if self._ssnd_seek_needed:
406  self._ssnd_chunk.seek(0)
407  dummy = self._ssnd_chunk.read(8)
408  pos = self._soundpos * self._framesize
409  if pos:
410  self._ssnd_chunk.seek(pos + 8)
411  self._ssnd_seek_needed = 0
412  if nframes == 0:
413  return ''
414  data = self._ssnd_chunk.read(nframes * self._framesize)
415  if self._convert and data:
416  data = self._convert(data)
417  self._soundpos = self._soundpos + len(data) / (self._nchannels * self._sampwidth)
418  return data
419 
420  #
421  # Internal methods.
422  #
423 
424  def _decomp_data(self, data):
425  import cl
426  dummy = self._decomp.SetParam(cl.FRAME_BUFFER_SIZE,
427  len(data) * 2)
428  return self._decomp.Decompress(len(data) / self._nchannels,
429  data)
430 
431  def _ulaw2lin(self, data):
432  import audioop
433  return audioop.ulaw2lin(data, 2)
434 
435  def _adpcm2lin(self, data):
436  import audioop
437  if not hasattr(self, '_adpcmstate'):
438  # first time
439  self._adpcmstate = None
440  data, self._adpcmstate = audioop.adpcm2lin(data, 2,
441  self._adpcmstate)
442  return data
443 
444  def _read_comm_chunk(self, chunk):
445  self._nchannels = _read_short(chunk)
446  self._nframes = _read_long(chunk)
447  self._sampwidth = (_read_short(chunk) + 7) / 8
448  self._framerate = int(_read_float(chunk))
449  self._framesize = self._nchannels * self._sampwidth
450  if self._aifc:
451  #DEBUG: SGI's soundeditor produces a bad size :-(
452  kludge = 0
453  if chunk.chunksize == 18:
454  kludge = 1
455  print 'Warning: bad COMM chunk size'
456  chunk.chunksize = 23
457  #DEBUG end
458  self._comptype = chunk.read(4)
459  #DEBUG start
460  if kludge:
461  length = ord(chunk.file.read(1))
462  if length & 1 == 0:
463  length = length + 1
464  chunk.chunksize = chunk.chunksize + length
465  chunk.file.seek(-1, 1)
466  #DEBUG end
467  self._compname = _read_string(chunk)
468  if self._comptype != 'NONE':
469  if self._comptype == 'G722':
470  try:
471  import audioop
472  except ImportError:
473  pass
474  else:
475  self._convert = self._adpcm2lin
476  self._framesize = self._framesize / 4
477  return
478  # for ULAW and ALAW try Compression Library
479  try:
480  import cl
481  except ImportError:
482  if self._comptype == 'ULAW':
483  try:
484  import audioop
485  self._convert = self._ulaw2lin
486  self._framesize = self._framesize / 2
487  return
488  except ImportError:
489  pass
490  raise Error, 'cannot read compressed AIFF-C files'
491  if self._comptype == 'ULAW':
492  scheme = cl.G711_ULAW
493  self._framesize = self._framesize / 2
494  elif self._comptype == 'ALAW':
495  scheme = cl.G711_ALAW
496  self._framesize = self._framesize / 2
497  else:
498  raise Error, 'unsupported compression type'
499  self._decomp = cl.OpenDecompressor(scheme)
500  self._convert = self._decomp_data
501  else:
502  self._comptype = 'NONE'
503  self._compname = 'not compressed'
504 
505  def _readmark(self, chunk):
506  nmarkers = _read_short(chunk)
507  # Some files appear to contain invalid counts.
508  # Cope with this by testing for EOF.
509  try:
510  for i in range(nmarkers):
511  id = _read_short(chunk)
512  pos = _read_long(chunk)
513  name = _read_string(chunk)
514  if pos or name:
515  # some files appear to have
516  # dummy markers consisting of
517  # a position 0 and name ''
518  self._markers.append((id, pos, name))
519  except EOFError:
520  print 'Warning: MARK chunk contains only',
521  print len(self._markers),
522  if len(self._markers) == 1: print 'marker',
523  else: print 'markers',
524  print 'instead of', nmarkers
525 
526 class Aifc_write:
527  # Variables used in this class:
528  #
529  # These variables are user settable through appropriate methods
530  # of this class:
531  # _file -- the open file with methods write(), close(), tell(), seek()
532  # set through the __init__() method
533  # _comptype -- the AIFF-C compression type ('NONE' in AIFF)
534  # set through the setcomptype() or setparams() method
535  # _compname -- the human-readable AIFF-C compression type
536  # set through the setcomptype() or setparams() method
537  # _nchannels -- the number of audio channels
538  # set through the setnchannels() or setparams() method
539  # _sampwidth -- the number of bytes per audio sample
540  # set through the setsampwidth() or setparams() method
541  # _framerate -- the sampling frequency
542  # set through the setframerate() or setparams() method
543  # _nframes -- the number of audio frames written to the header
544  # set through the setnframes() or setparams() method
545  # _aifc -- whether we're writing an AIFF-C file or an AIFF file
546  # set through the aifc() method, reset through the
547  # aiff() method
548  #
549  # These variables are used internally only:
550  # _version -- the AIFF-C version number
551  # _comp -- the compressor from builtin module cl
552  # _nframeswritten -- the number of audio frames actually written
553  # _datalength -- the size of the audio samples written to the header
554  # _datawritten -- the size of the audio samples actually written
555 
556  def __init__(self, f):
557  if type(f) == type(''):
558  filename = f
559  f = __builtin__.open(f, 'wb')
560  else:
561  # else, assume it is an open file object already
562  filename = '???'
563  self.initfp(f)
564  if filename[-5:] == '.aiff':
565  self._aifc = 0
566  else:
567  self._aifc = 1
568 
569  def initfp(self, file):
570  self._file = file
571  self._version = _AIFC_version
572  self._comptype = 'NONE'
573  self._compname = 'not compressed'
574  self._comp = None
575  self._convert = None
576  self._nchannels = 0
577  self._sampwidth = 0
578  self._framerate = 0
579  self._nframes = 0
580  self._nframeswritten = 0
581  self._datawritten = 0
582  self._datalength = 0
583  self._markers = []
584  self._marklength = 0
585  self._aifc = 1 # AIFF-C is default
586 
587  def __del__(self):
588  if self._file:
589  self.close()
590 
591  #
592  # User visible methods.
593  #
594  def aiff(self):
595  if self._nframeswritten:
596  raise Error, 'cannot change parameters after starting to write'
597  self._aifc = 0
598 
599  def aifc(self):
600  if self._nframeswritten:
601  raise Error, 'cannot change parameters after starting to write'
602  self._aifc = 1
603 
604  def setnchannels(self, nchannels):
605  if self._nframeswritten:
606  raise Error, 'cannot change parameters after starting to write'
607  if nchannels < 1:
608  raise Error, 'bad # of channels'
609  self._nchannels = nchannels
610 
611  def getnchannels(self):
612  if not self._nchannels:
613  raise Error, 'number of channels not set'
614  return self._nchannels
615 
616  def setsampwidth(self, sampwidth):
617  if self._nframeswritten:
618  raise Error, 'cannot change parameters after starting to write'
619  if sampwidth < 1 or sampwidth > 4:
620  raise Error, 'bad sample width'
621  self._sampwidth = sampwidth
622 
623  def getsampwidth(self):
624  if not self._sampwidth:
625  raise Error, 'sample width not set'
626  return self._sampwidth
627 
628  def setframerate(self, framerate):
629  if self._nframeswritten:
630  raise Error, 'cannot change parameters after starting to write'
631  if framerate <= 0:
632  raise Error, 'bad frame rate'
633  self._framerate = framerate
634 
635  def getframerate(self):
636  if not self._framerate:
637  raise Error, 'frame rate not set'
638  return self._framerate
639 
640  def setnframes(self, nframes):
641  if self._nframeswritten:
642  raise Error, 'cannot change parameters after starting to write'
643  self._nframes = nframes
644 
645  def getnframes(self):
646  return self._nframeswritten
647 
648  def setcomptype(self, comptype, compname):
649  if self._nframeswritten:
650  raise Error, 'cannot change parameters after starting to write'
651  if comptype not in ('NONE', 'ULAW', 'ALAW', 'G722'):
652  raise Error, 'unsupported compression type'
653  self._comptype = comptype
654  self._compname = compname
655 
656  def getcomptype(self):
657  return self._comptype
658 
659  def getcompname(self):
660  return self._compname
661 
662 ## def setversion(self, version):
663 ## if self._nframeswritten:
664 ## raise Error, 'cannot change parameters after starting to write'
665 ## self._version = version
666 
667  def setparams(self, (nchannels, sampwidth, framerate, nframes, comptype, compname)):
668  if self._nframeswritten:
669  raise Error, 'cannot change parameters after starting to write'
670  if comptype not in ('NONE', 'ULAW', 'ALAW', 'G722'):
671  raise Error, 'unsupported compression type'
672  self.setnchannels(nchannels)
673  self.setsampwidth(sampwidth)
674  self.setframerate(framerate)
675  self.setnframes(nframes)
676  self.setcomptype(comptype, compname)
677 
678  def getparams(self):
679  if not self._nchannels or not self._sampwidth or not self._framerate:
680  raise Error, 'not all parameters set'
681  return self._nchannels, self._sampwidth, self._framerate, \
682  self._nframes, self._comptype, self._compname
683 
684  def setmark(self, id, pos, name):
685  if id <= 0:
686  raise Error, 'marker ID must be > 0'
687  if pos < 0:
688  raise Error, 'marker position must be >= 0'
689  if type(name) != type(''):
690  raise Error, 'marker name must be a string'
691  for i in range(len(self._markers)):
692  if id == self._markers[i][0]:
693  self._markers[i] = id, pos, name
694  return
695  self._markers.append((id, pos, name))
696 
697  def getmark(self, id):
698  for marker in self._markers:
699  if id == marker[0]:
700  return marker
701  raise Error, 'marker ' + `id` + ' does not exist'
702 
703  def getmarkers(self):
704  if len(self._markers) == 0:
705  return None
706  return self._markers
707 
708  def tell(self):
709  return self._nframeswritten
710 
711  def writeframesraw(self, data):
712  self._ensure_header_written(len(data))
713  nframes = len(data) / (self._sampwidth * self._nchannels)
714  if self._convert:
715  data = self._convert(data)
716  self._file.write(data)
717  self._nframeswritten = self._nframeswritten + nframes
718  self._datawritten = self._datawritten + len(data)
719 
720  def writeframes(self, data):
721  self.writeframesraw(data)
722  if self._nframeswritten != self._nframes or \
723  self._datalength != self._datawritten:
724  self._patchheader()
725 
726  def close(self):
727  self._ensure_header_written(0)
728  if self._datawritten & 1:
729  # quick pad to even size
730  self._file.write(chr(0))
731  self._datawritten = self._datawritten + 1
732  self._writemarkers()
733  if self._nframeswritten != self._nframes or \
734  self._datalength != self._datawritten or \
735  self._marklength:
736  self._patchheader()
737  if self._comp:
738  self._comp.CloseCompressor()
739  self._comp = None
740  self._file.flush()
741  self._file = None
742 
743  #
744  # Internal methods.
745  #
746 
747  def _comp_data(self, data):
748  import cl
749  dum = self._comp.SetParam(cl.FRAME_BUFFER_SIZE, len(data))
750  dum = self._comp.SetParam(cl.COMPRESSED_BUFFER_SIZE, len(data))
751  return self._comp.Compress(self._nframes, data)
752 
753  def _lin2ulaw(self, data):
754  import audioop
755  return audioop.lin2ulaw(data, 2)
756 
757  def _lin2adpcm(self, data):
758  import audioop
759  if not hasattr(self, '_adpcmstate'):
760  self._adpcmstate = None
761  data, self._adpcmstate = audioop.lin2adpcm(data, 2,
762  self._adpcmstate)
763  return data
764 
765  def _ensure_header_written(self, datasize):
766  if not self._nframeswritten:
767  if self._comptype in ('ULAW', 'ALAW'):
768  if not self._sampwidth:
769  self._sampwidth = 2
770  if self._sampwidth != 2:
771  raise Error, 'sample width must be 2 when compressing with ULAW or ALAW'
772  if self._comptype == 'G722':
773  if not self._sampwidth:
774  self._sampwidth = 2
775  if self._sampwidth != 2:
776  raise Error, 'sample width must be 2 when compressing with G7.22 (ADPCM)'
777  if not self._nchannels:
778  raise Error, '# channels not specified'
779  if not self._sampwidth:
780  raise Error, 'sample width not specified'
781  if not self._framerate:
782  raise Error, 'sampling rate not specified'
783  self._write_header(datasize)
784 
785  def _init_compression(self):
786  if self._comptype == 'G722':
787  import audioop
788  self._convert = self._lin2adpcm
789  return
790  try:
791  import cl
792  except ImportError:
793  if self._comptype == 'ULAW':
794  try:
795  import audioop
796  self._convert = self._lin2ulaw
797  return
798  except ImportError:
799  pass
800  raise Error, 'cannot write compressed AIFF-C files'
801  if self._comptype == 'ULAW':
802  scheme = cl.G711_ULAW
803  elif self._comptype == 'ALAW':
804  scheme = cl.G711_ALAW
805  else:
806  raise Error, 'unsupported compression type'
807  self._comp = cl.OpenCompressor(scheme)
808  params = [cl.ORIGINAL_FORMAT, 0,
809  cl.BITS_PER_COMPONENT, self._sampwidth * 8,
810  cl.FRAME_RATE, self._framerate,
811  cl.FRAME_BUFFER_SIZE, 100,
812  cl.COMPRESSED_BUFFER_SIZE, 100]
813  if self._nchannels == 1:
814  params[1] = cl.MONO
815  elif self._nchannels == 2:
816  params[1] = cl.STEREO_INTERLEAVED
817  else:
818  raise Error, 'cannot compress more than 2 channels'
819  self._comp.SetParams(params)
820  # the compressor produces a header which we ignore
821  dummy = self._comp.Compress(0, '')
822  self._convert = self._comp_data
823 
824  def _write_header(self, initlength):
825  if self._aifc and self._comptype != 'NONE':
826  self._init_compression()
827  self._file.write('FORM')
828  if not self._nframes:
829  self._nframes = initlength / (self._nchannels * self._sampwidth)
830  self._datalength = self._nframes * self._nchannels * self._sampwidth
831  if self._datalength & 1:
832  self._datalength = self._datalength + 1
833  if self._aifc:
834  if self._comptype in ('ULAW', 'ALAW'):
835  self._datalength = self._datalength / 2
836  if self._datalength & 1:
837  self._datalength = self._datalength + 1
838  elif self._comptype == 'G722':
839  self._datalength = (self._datalength + 3) / 4
840  if self._datalength & 1:
841  self._datalength = self._datalength + 1
842  self._form_length_pos = self._file.tell()
843  commlength = self._write_form_length(self._datalength)
844  if self._aifc:
845  self._file.write('AIFC')
846  self._file.write('FVER')
847  _write_long(self._file, 4)
848  _write_long(self._file, self._version)
849  else:
850  self._file.write('AIFF')
851  self._file.write('COMM')
852  _write_long(self._file, commlength)
853  _write_short(self._file, self._nchannels)
854  self._nframes_pos = self._file.tell()
855  _write_long(self._file, self._nframes)
856  _write_short(self._file, self._sampwidth * 8)
857  _write_float(self._file, self._framerate)
858  if self._aifc:
859  self._file.write(self._comptype)
860  _write_string(self._file, self._compname)
861  self._file.write('SSND')
862  self._ssnd_length_pos = self._file.tell()
863  _write_long(self._file, self._datalength + 8)
864  _write_long(self._file, 0)
865  _write_long(self._file, 0)
866 
867  def _write_form_length(self, datalength):
868  if self._aifc:
869  commlength = 18 + 5 + len(self._compname)
870  if commlength & 1:
871  commlength = commlength + 1
872  verslength = 12
873  else:
874  commlength = 18
875  verslength = 0
876  _write_long(self._file, 4 + verslength + self._marklength + \
877  8 + commlength + 16 + datalength)
878  return commlength
879 
880  def _patchheader(self):
881  curpos = self._file.tell()
882  if self._datawritten & 1:
883  datalength = self._datawritten + 1
884  self._file.write(chr(0))
885  else:
886  datalength = self._datawritten
887  if datalength == self._datalength and \
888  self._nframes == self._nframeswritten and \
889  self._marklength == 0:
890  self._file.seek(curpos, 0)
891  return
892  self._file.seek(self._form_length_pos, 0)
893  dummy = self._write_form_length(datalength)
894  self._file.seek(self._nframes_pos, 0)
895  _write_long(self._file, self._nframeswritten)
896  self._file.seek(self._ssnd_length_pos, 0)
897  _write_long(self._file, datalength + 8)
898  self._file.seek(curpos, 0)
899  self._nframes = self._nframeswritten
900  self._datalength = datalength
901 
902  def _writemarkers(self):
903  if len(self._markers) == 0:
904  return
905  self._file.write('MARK')
906  length = 2
907  for marker in self._markers:
908  id, pos, name = marker
909  length = length + len(name) + 1 + 6
910  if len(name) & 1 == 0:
911  length = length + 1
912  _write_long(self._file, length)
913  self._marklength = length + 8
914  _write_short(self._file, len(self._markers))
915  for marker in self._markers:
916  id, pos, name = marker
917  _write_short(self._file, id)
918  _write_long(self._file, pos)
919  _write_string(self._file, name)
920 
921 def open(f, mode=None):
922  if mode is None:
923  if hasattr(f, 'mode'):
924  mode = f.mode
925  else:
926  mode = 'rb'
927  if mode in ('r', 'rb'):
928  return Aifc_read(f)
929  elif mode in ('w', 'wb'):
930  return Aifc_write(f)
931  else:
932  raise Error, "mode must be 'r', 'rb', 'w', or 'wb'"
933 
934 openfp = open # B/W compatibility
935 
936 if __name__ == '__main__':
937  import sys
938  if not sys.argv[1:]:
939  sys.argv.append('/usr/demos/data/audio/bach.aiff')
940  fn = sys.argv[1]
941  f = open(fn, 'r')
942  print "Reading", fn
943  print "nchannels =", f.getnchannels()
944  print "nframes =", f.getnframes()
945  print "sampwidth =", f.getsampwidth()
946  print "framerate =", f.getframerate()
947  print "comptype =", f.getcomptype()
948  print "compname =", f.getcompname()
949  if sys.argv[2:]:
950  gn = sys.argv[2]
951  print "Writing", gn
952  g = open(gn, 'w')
953  g.setparams(f.getparams())
954  while 1:
955  data = f.readframes(1024)
956  if not data:
957  break
958  g.writeframes(data)
959  g.close()
960  f.close()
961  print "Done."