Vega strike Python Modules doc  0.5.1
Documentation of the " Modules " folder of Vega strike
 All Data Structures Namespaces Files Functions Variables
sunau.py
Go to the documentation of this file.
1 """Stuff to parse Sun and NeXT audio files.
2 
3 An audio file consists of a header followed by the data. The structure
4 of the header is as follows.
5 
6  +---------------+
7  | magic word |
8  +---------------+
9  | header size |
10  +---------------+
11  | data size |
12  +---------------+
13  | encoding |
14  +---------------+
15  | sample rate |
16  +---------------+
17  | # of channels |
18  +---------------+
19  | info |
20  | |
21  +---------------+
22 
23 The magic word consists of the 4 characters '.snd'. Apart from the
24 info field, all header fields are 4 bytes in size. They are all
25 32-bit unsigned integers encoded in big-endian byte order.
26 
27 The header size really gives the start of the data.
28 The data size is the physical size of the data. From the other
29 parameters the number of frames can be calculated.
30 The encoding gives the way in which audio samples are encoded.
31 Possible values are listed below.
32 The info field currently consists of an ASCII string giving a
33 human-readable description of the audio file. The info field is
34 padded with NUL bytes to the header size.
35 
36 Usage.
37 
38 Reading audio files:
39  f = sunau.open(file, 'r')
40 where file is either the name of a file or an open file pointer.
41 The open file pointer must have methods read(), seek(), and close().
42 When the setpos() and rewind() methods are not used, the seek()
43 method is not necessary.
44 
45 This returns an instance of a class with the following public methods:
46  getnchannels() -- returns number of audio channels (1 for
47  mono, 2 for stereo)
48  getsampwidth() -- returns sample width in bytes
49  getframerate() -- returns sampling frequency
50  getnframes() -- returns number of audio frames
51  getcomptype() -- returns compression type ('NONE' or 'ULAW')
52  getcompname() -- returns human-readable version of
53  compression type ('not compressed' matches 'NONE')
54  getparams() -- returns a tuple consisting of all of the
55  above in the above order
56  getmarkers() -- returns None (for compatibility with the
57  aifc module)
58  getmark(id) -- raises an error since the mark does not
59  exist (for compatibility with the aifc module)
60  readframes(n) -- returns at most n frames of audio
61  rewind() -- rewind to the beginning of the audio stream
62  setpos(pos) -- seek to the specified position
63  tell() -- return the current position
64  close() -- close the instance (make it unusable)
65 The position returned by tell() and the position given to setpos()
66 are compatible and have nothing to do with the actual position in the
67 file.
68 The close() method is called automatically when the class instance
69 is destroyed.
70 
71 Writing audio files:
72  f = sunau.open(file, 'w')
73 where file is either the name of a file or an open file pointer.
74 The open file pointer must have methods write(), tell(), seek(), and
75 close().
76 
77 This returns an instance of a class with the following public methods:
78  setnchannels(n) -- set the number of channels
79  setsampwidth(n) -- set the sample width
80  setframerate(n) -- set the frame rate
81  setnframes(n) -- set the number of frames
82  setcomptype(type, name)
83  -- set the compression type and the
84  human-readable compression type
85  setparams(tuple)-- set all parameters at once
86  tell() -- return current position in output file
87  writeframesraw(data)
88  -- write audio frames without pathing up the
89  file header
90  writeframes(data)
91  -- write audio frames and patch up the file header
92  close() -- patch up the file header and close the
93  output file
94 You should set the parameters before the first writeframesraw or
95 writeframes. The total number of frames does not need to be set,
96 but when it is set to the correct value, the header does not have to
97 be patched up.
98 It is best to first set all parameters, perhaps possibly the
99 compression type, and then write audio frames using writeframesraw.
100 When all frames have been written, either call writeframes('') or
101 close() to patch up the sizes in the header.
102 The close() method is called automatically when the class instance
103 is destroyed.
104 """
105 
106 # from <multimedia/audio_filehdr.h>
107 AUDIO_FILE_MAGIC = 0x2e736e64
108 AUDIO_FILE_ENCODING_MULAW_8 = 1
109 AUDIO_FILE_ENCODING_LINEAR_8 = 2
110 AUDIO_FILE_ENCODING_LINEAR_16 = 3
111 AUDIO_FILE_ENCODING_LINEAR_24 = 4
112 AUDIO_FILE_ENCODING_LINEAR_32 = 5
113 AUDIO_FILE_ENCODING_FLOAT = 6
114 AUDIO_FILE_ENCODING_DOUBLE = 7
115 AUDIO_FILE_ENCODING_ADPCM_G721 = 23
116 AUDIO_FILE_ENCODING_ADPCM_G722 = 24
117 AUDIO_FILE_ENCODING_ADPCM_G723_3 = 25
118 AUDIO_FILE_ENCODING_ADPCM_G723_5 = 26
119 AUDIO_FILE_ENCODING_ALAW_8 = 27
120 
121 # from <multimedia/audio_hdr.h>
122 AUDIO_UNKNOWN_SIZE = 0xFFFFFFFFL # ((unsigned)(~0))
123 
124 _simple_encodings = [AUDIO_FILE_ENCODING_MULAW_8,
125  AUDIO_FILE_ENCODING_LINEAR_8,
126  AUDIO_FILE_ENCODING_LINEAR_16,
127  AUDIO_FILE_ENCODING_LINEAR_24,
128  AUDIO_FILE_ENCODING_LINEAR_32,
129  AUDIO_FILE_ENCODING_ALAW_8]
130 
131 class Error(Exception):
132  pass
133 
134 def _read_u32(file):
135  x = 0L
136  for i in range(4):
137  byte = file.read(1)
138  if byte == '':
139  raise EOFError
140  x = x*256 + ord(byte)
141  return x
142 
143 def _write_u32(file, x):
144  data = []
145  for i in range(4):
146  d, m = divmod(x, 256)
147  data.insert(0, m)
148  x = d
149  for i in range(4):
150  file.write(chr(int(data[i])))
151 
152 class Au_read:
153 
154  def __init__(self, f):
155  if type(f) == type(''):
156  import __builtin__
157  f = __builtin__.open(f, 'rb')
158  self.initfp(f)
159 
160  def __del__(self):
161  if self._file:
162  self.close()
163 
164  def initfp(self, file):
165  self._file = file
166  self._soundpos = 0
167  magic = int(_read_u32(file))
168  if magic != AUDIO_FILE_MAGIC:
169  raise Error, 'bad magic number'
170  self._hdr_size = int(_read_u32(file))
171  if self._hdr_size < 24:
172  raise Error, 'header size too small'
173  if self._hdr_size > 100:
174  raise Error, 'header size ridiculously large'
175  self._data_size = _read_u32(file)
176  if self._data_size != AUDIO_UNKNOWN_SIZE:
177  self._data_size = int(self._data_size)
178  self._encoding = int(_read_u32(file))
179  if self._encoding not in _simple_encodings:
180  raise Error, 'encoding not (yet) supported'
181  if self._encoding in (AUDIO_FILE_ENCODING_MULAW_8,
182  AUDIO_FILE_ENCODING_ALAW_8):
183  self._sampwidth = 2
184  self._framesize = 1
185  elif self._encoding == AUDIO_FILE_ENCODING_LINEAR_8:
186  self._framesize = self._sampwidth = 1
187  elif self._encoding == AUDIO_FILE_ENCODING_LINEAR_16:
188  self._framesize = self._sampwidth = 2
189  elif self._encoding == AUDIO_FILE_ENCODING_LINEAR_24:
190  self._framesize = self._sampwidth = 3
191  elif self._encoding == AUDIO_FILE_ENCODING_LINEAR_32:
192  self._framesize = self._sampwidth = 4
193  else:
194  raise Error, 'unknown encoding'
195  self._framerate = int(_read_u32(file))
196  self._nchannels = int(_read_u32(file))
197  self._framesize = self._framesize * self._nchannels
198  if self._hdr_size > 24:
199  self._info = file.read(self._hdr_size - 24)
200  for i in range(len(self._info)):
201  if self._info[i] == '\0':
202  self._info = self._info[:i]
203  break
204  else:
205  self._info = ''
206 
207  def getfp(self):
208  return self._file
209 
210  def getnchannels(self):
211  return self._nchannels
212 
213  def getsampwidth(self):
214  return self._sampwidth
215 
216  def getframerate(self):
217  return self._framerate
218 
219  def getnframes(self):
220  if self._data_size == AUDIO_UNKNOWN_SIZE:
221  return AUDIO_UNKNOWN_SIZE
222  if self._encoding in _simple_encodings:
223  return self._data_size / self._framesize
224  return 0 # XXX--must do some arithmetic here
225 
226  def getcomptype(self):
227  if self._encoding == AUDIO_FILE_ENCODING_MULAW_8:
228  return 'ULAW'
229  elif self._encoding == AUDIO_FILE_ENCODING_ALAW_8:
230  return 'ALAW'
231  else:
232  return 'NONE'
233 
234  def getcompname(self):
235  if self._encoding == AUDIO_FILE_ENCODING_MULAW_8:
236  return 'CCITT G.711 u-law'
237  elif self._encoding == AUDIO_FILE_ENCODING_ALAW_8:
238  return 'CCITT G.711 A-law'
239  else:
240  return 'not compressed'
241 
242  def getparams(self):
243  return self.getnchannels(), self.getsampwidth(), \
244  self.getframerate(), self.getnframes(), \
245  self.getcomptype(), self.getcompname()
246 
247  def getmarkers(self):
248  return None
249 
250  def getmark(self, id):
251  raise Error, 'no marks'
252 
253  def readframes(self, nframes):
254  if self._encoding in _simple_encodings:
255  if nframes == AUDIO_UNKNOWN_SIZE:
256  data = self._file.read()
257  else:
258  data = self._file.read(nframes * self._framesize * self._nchannels)
259  if self._encoding == AUDIO_FILE_ENCODING_MULAW_8:
260  import audioop
261  data = audioop.ulaw2lin(data, self._sampwidth)
262  return data
263  return None # XXX--not implemented yet
264 
265  def rewind(self):
266  self._soundpos = 0
267  self._file.seek(self._hdr_size)
268 
269  def tell(self):
270  return self._soundpos
271 
272  def setpos(self, pos):
273  if pos < 0 or pos > self.getnframes():
274  raise Error, 'position not in range'
275  self._file.seek(pos * self._framesize + self._hdr_size)
276  self._soundpos = pos
277 
278  def close(self):
279  self._file = None
280 
281 class Au_write:
282 
283  def __init__(self, f):
284  if type(f) == type(''):
285  import __builtin__
286  f = __builtin__.open(f, 'wb')
287  self.initfp(f)
288 
289  def __del__(self):
290  if self._file:
291  self.close()
292 
293  def initfp(self, file):
294  self._file = file
295  self._framerate = 0
296  self._nchannels = 0
297  self._sampwidth = 0
298  self._framesize = 0
299  self._nframes = AUDIO_UNKNOWN_SIZE
300  self._nframeswritten = 0
301  self._datawritten = 0
302  self._datalength = 0
303  self._info = ''
304  self._comptype = 'ULAW' # default is U-law
305 
306  def setnchannels(self, nchannels):
307  if self._nframeswritten:
308  raise Error, 'cannot change parameters after starting to write'
309  if nchannels not in (1, 2, 4):
310  raise Error, 'only 1, 2, or 4 channels supported'
311  self._nchannels = nchannels
312 
313  def getnchannels(self):
314  if not self._nchannels:
315  raise Error, 'number of channels not set'
316  return self._nchannels
317 
318  def setsampwidth(self, sampwidth):
319  if self._nframeswritten:
320  raise Error, 'cannot change parameters after starting to write'
321  if sampwidth not in (1, 2, 4):
322  raise Error, 'bad sample width'
323  self._sampwidth = sampwidth
324 
325  def getsampwidth(self):
326  if not self._framerate:
327  raise Error, 'sample width not specified'
328  return self._sampwidth
329 
330  def setframerate(self, framerate):
331  if self._nframeswritten:
332  raise Error, 'cannot change parameters after starting to write'
333  self._framerate = framerate
334 
335  def getframerate(self):
336  if not self._framerate:
337  raise Error, 'frame rate not set'
338  return self._framerate
339 
340  def setnframes(self, nframes):
341  if self._nframeswritten:
342  raise Error, 'cannot change parameters after starting to write'
343  if nframes < 0:
344  raise Error, '# of frames cannot be negative'
345  self._nframes = nframes
346 
347  def getnframes(self):
348  return self._nframeswritten
349 
350  def setcomptype(self, type, name):
351  if type in ('NONE', 'ULAW'):
352  self._comptype = type
353  else:
354  raise Error, 'unknown compression type'
355 
356  def getcomptype(self):
357  return self._comptype
358 
359  def getcompname(self):
360  if self._comptype == 'ULAW':
361  return 'CCITT G.711 u-law'
362  elif self._comptype == 'ALAW':
363  return 'CCITT G.711 A-law'
364  else:
365  return 'not compressed'
366 
367  def setparams(self, (nchannels, sampwidth, framerate, nframes, comptype, compname)):
368  self.setnchannels(nchannels)
369  self.setsampwidth(sampwidth)
370  self.setframerate(framerate)
371  self.setnframes(nframes)
372  self.setcomptype(comptype, compname)
373 
374  def getparams(self):
375  return self.getnchannels(), self.getsampwidth(), \
376  self.getframerate(), self.getnframes(), \
377  self.getcomptype(), self.getcompname()
378 
379  def tell(self):
380  return self._nframeswritten
381 
382  def writeframesraw(self, data):
383  self._ensure_header_written()
384  nframes = len(data) / self._framesize
385  if self._comptype == 'ULAW':
386  import audioop
387  data = audioop.lin2ulaw(data, self._sampwidth)
388  self._file.write(data)
389  self._nframeswritten = self._nframeswritten + nframes
390  self._datawritten = self._datawritten + len(data)
391 
392  def writeframes(self, data):
393  self.writeframesraw(data)
394  if self._nframeswritten != self._nframes or \
395  self._datalength != self._datawritten:
396  self._patchheader()
397 
398  def close(self):
399  self._ensure_header_written()
400  if self._nframeswritten != self._nframes or \
401  self._datalength != self._datawritten:
402  self._patchheader()
403  self._file.flush()
404  self._file = None
405 
406  #
407  # private methods
408  #
409 
410  def _ensure_header_written(self):
411  if not self._nframeswritten:
412  if not self._nchannels:
413  raise Error, '# of channels not specified'
414  if not self._sampwidth:
415  raise Error, 'sample width not specified'
416  if not self._framerate:
417  raise Error, 'frame rate not specified'
418  self._write_header()
419 
420  def _write_header(self):
421  if self._comptype == 'NONE':
422  if self._sampwidth == 1:
423  encoding = AUDIO_FILE_ENCODING_LINEAR_8
424  self._framesize = 1
425  elif self._sampwidth == 2:
426  encoding = AUDIO_FILE_ENCODING_LINEAR_16
427  self._framesize = 2
428  elif self._sampwidth == 4:
429  encoding = AUDIO_FILE_ENCODING_LINEAR_32
430  self._framesize = 4
431  else:
432  raise Error, 'internal error'
433  elif self._comptype == 'ULAW':
434  encoding = AUDIO_FILE_ENCODING_MULAW_8
435  self._framesize = 1
436  else:
437  raise Error, 'internal error'
438  self._framesize = self._framesize * self._nchannels
439  _write_u32(self._file, AUDIO_FILE_MAGIC)
440  header_size = 25 + len(self._info)
441  header_size = (header_size + 7) & ~7
442  _write_u32(self._file, header_size)
443  if self._nframes == AUDIO_UNKNOWN_SIZE:
444  length = AUDIO_UNKNOWN_SIZE
445  else:
446  length = self._nframes * self._framesize
447  _write_u32(self._file, length)
448  self._datalength = length
449  _write_u32(self._file, encoding)
450  _write_u32(self._file, self._framerate)
451  _write_u32(self._file, self._nchannels)
452  self._file.write(self._info)
453  self._file.write('\0'*(header_size - len(self._info) - 24))
454 
455  def _patchheader(self):
456  self._file.seek(8)
457  _write_u32(self._file, self._datawritten)
458  self._datalength = self._datawritten
459  self._file.seek(0, 2)
460 
461 def open(f, mode=None):
462  if mode is None:
463  if hasattr(f, 'mode'):
464  mode = f.mode
465  else:
466  mode = 'rb'
467  if mode in ('r', 'rb'):
468  return Au_read(f)
469  elif mode in ('w', 'wb'):
470  return Au_write(f)
471  else:
472  raise Error, "mode must be 'r', 'rb', 'w', or 'wb'"
473 
474 openfp = open