1 # Stuff to parse Sun and NeXT audio files.
3 # An audio consists of a header followed by the data. The structure
4 # of the header is as follows.
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.
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 # parameter 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.
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.
45 # This returns an instance of a class with the following public methods:
46 # getnchannels() -- returns number of audio channels (1 for
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' for AIFF files)
52 # getcompname() -- returns human-readable version of
53 # compression type ('not compressed' for AIFF files)
54 # getparams() -- returns a tuple consisting of all of the
55 # above in the above order
56 # getmarkers() -- returns None (for compatibility with the
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 postion in the
68 # The close() method is called automatically when the class instance
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
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(nchannels, sampwidth, framerate, nframes, comptype, compname)
86 # -- set all parameters at once
87 # tell() -- return current position in output file
88 # writeframesraw(data)
89 # -- write audio frames without pathing up the
92 # -- write audio frames and patch up the file header
93 # close() -- patch up the file header and close the
95 # You should set the parameters before the first writeframesraw or
96 # writeframes. The total number of frames does not need to be set,
97 # but when it is set to the correct value, the header does not have to
99 # It is best to first set all parameters, perhaps possibly the
100 # compression type, and then write audio frames using writeframesraw.
101 # When all frames have been written, either call writeframes('') or
102 # close() to patch up the sizes in the header.
103 # The close() method is called automatically when the class instance
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
121 # from <multimedia/audio_hdr.h>
122 AUDIO_UNKNOWN_SIZE
= 0xFFFFFFFFL
# ((unsigned)(~0))
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
]
137 x
= x
*256 + ord(byte
)
140 def _write_u32(file, x
):
143 d
, m
= divmod(x
, 256)
147 file.write(chr(int(data
[i
])))
150 access _file
, _soundpos
, _hdr_size
, _data_size
, _encoding
, \
151 _sampwidth
, _framesize
, _framerate
, _nchannels
, \
154 def __init__(self
, f
):
155 if type(f
) == type(''):
157 f
= __builtin__
.open(f
, 'r')
164 def initfp(self
, file):
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 rediculously 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_LINEAR_8
,
183 AUDIO_FILE_ENCODING_ALAW_8
):
186 elif self
._encoding
== AUDIO_FILE_ENCODING_LINEAR_16
:
187 self
._framesize
= self
._sampwidth
= 2
188 elif self
._encoding
== AUDIO_FILE_ENCODING_LINEAR_24
:
189 self
._framesize
= self
._sampwidth
= 3
190 elif self
._encoding
== AUDIO_FILE_ENCODING_LINEAR_32
:
191 self
._framesize
= self
._sampwidth
= 4
193 raise Error
, 'unknown encoding'
194 self
._framerate
= int(_read_u32(file))
195 self
._nchannels
= int(_read_u32(file))
196 self
._framesize
= self
._framesize
* self
._nchannels
197 if self
._hdr
_size
> 24:
198 self
._info
= file.read(self
._hdr
_size
- 24)
199 for i
in range(len(self
._info
)):
200 if self
._info
[i
] == '\0':
201 self
._info
= self
._info
[:i
]
209 def getnchannels(self
):
210 return self
._nchannels
212 def getsampwidth(self
):
213 return self
._sampwidth
215 def getframerate(self
):
216 return self
._framerate
218 def getnframes(self
):
219 if self
._data
_size
== AUDIO_UNKNOWN_SIZE
:
220 return AUDIO_UNKNOWN_SIZE
221 if self
._encoding
in _simple_encodings
:
222 return self
._data
_size
/ self
._framesize
223 return 0 # XXX--must do some arithmetic here
225 def getcomptype(self
):
226 if self
._encoding
== AUDIO_FILE_ENCODING_MULAW_8
:
228 elif self
._encoding
== AUDIO_FILE_ENCODING_ALAW_8
:
233 def getcompname(self
):
234 if self
._encoding
== AUDIO_FILE_ENCODING_MULAW_8
:
235 return 'CCITT G.711 u-law'
236 elif self
._encoding
== AUDIO_FILE_ENCODING_ALAW_8
:
237 return 'CCITT G.711 A-law'
239 return 'not compressed'
242 return self
.getnchannels(), self
.getsampwidth(), \
243 self
.getframerate(), self
.getnframes(), \
244 self
.getcomptype(), self
.getcompname()
246 def getmarkers(self
):
249 def getmark(self
, id):
250 raise Error
, 'no marks'
252 def readframes(self
, nframes
):
253 if self
._encoding
in _simple_encodings
:
254 if nframes
== AUDIO_UNKNOWN_SIZE
:
255 data
= self
._file
.read()
257 data
= self
._file
.read(nframes
* self
._framesize
* self
._nchannels
)
258 if self
._encoding
== AUDIO_FILE_ENCODING_MULAW_8
:
260 data
= audioop
.ulaw2lin(data
, self
._sampwidth
)
262 return None # XXX--not implemented yet
266 self
._file
.seek(self
._hdr
_size
)
269 return self
._soundpos
271 def setpos(self
, pos
):
272 if pos
< 0 or pos
> self
.getnframes():
273 raise Error
, 'position not in range'
274 self
._file
.seek(pos
* self
._framesize
+ self
._hdr
_size
)
281 access _file
, _framerate
, _nchannels
, _sampwidth
, _framesize
, \
282 _nframes
, _nframeswritten
, _datawritten
, _info
, \
285 def __init__(self
, f
):
286 if type(f
) == type(''):
288 f
= __builtin__
.open(f
, 'w')
295 def initfp(self
, file):
301 self
._nframes
= AUDIO_UNKNOWN_SIZE
302 self
._nframeswritten
= 0
303 self
._datawritten
= 0
306 self
._comptype
= 'ULAW' # default is U-law
308 def setnchannels(self
, nchannels
):
309 if self
._nframeswritten
:
310 raise Error
, 'cannot change parameters after starting to write'
311 if nchannels
not in (1, 2, 4):
312 raise Error
, 'only 1, 2, or 4 channels supported'
313 self
._nchannels
= nchannels
315 def getnchannels(self
):
316 if not self
._nchannels
:
317 raise Error
, 'number of channels not set'
318 return self
._nchannels
320 def setsampwidth(self
, sampwidth
):
321 if self
._nframeswritten
:
322 raise Error
, 'cannot change parameters after starting to write'
323 if sampwidth
not in (1, 2, 4):
324 raise Error
, 'bad sample width'
325 self
._sampwidth
= sampwidth
327 def getsampwidth(self
):
328 if not self
._framerate
:
329 raise Error
, 'sample width not specified'
330 return self
._sampwidth
332 def setframerate(self
, framerate
):
333 if self
._nframeswritten
:
334 raise Error
, 'cannot change parameters after starting to write'
335 self
._framerate
= framerate
337 def getframerate(self
):
338 if not self
._framerate
:
339 raise Error
, 'frame rate not set'
340 return self
._framerate
342 def setnframes(self
, nframes
):
343 if self
._nframeswritten
:
344 raise Error
, 'cannot change parameters after starting to write'
346 raise Error
, '# of frames cannot be negative'
347 self
._nframes
= nframes
349 def getnframes(self
):
350 return self
._nframeswritten
352 def setcomptype(self
, type, name
):
353 if type in ('NONE', 'ULAW'):
354 self
._comptype
= type
356 raise Error
, 'unknown compression type'
358 def getcomptype(self
):
359 return self
._comptype
361 def getcompname(self
):
362 if self
._comptype
== 'ULAW':
363 return 'CCITT G.711 u-law'
364 elif self
._comptype
== 'ALAW':
365 return 'CCITT G.711 A-law'
367 return 'not compressed'
369 def setparams(self
, (nchannels
, sampwidth
, framerate
, nframes
, comptype
, compname
)):
370 self
.setnchannels(nchannels
)
371 self
.setsampwidth(sampwidth
)
372 self
.setframerate(framerate
)
373 self
.setnframes(nframes
)
374 self
.setcomptype(comptype
, compname
)
377 return self
.getnchannels(), self
.getsampwidth(), \
378 self
.getframerate(), self
.getnframes(), \
379 self
.getcomptype(), self
.getcompname()
382 return self
._nframeswritten
384 def writeframesraw(self
, data
):
385 self
._ensure
_header
_written
()
386 nframes
= len(data
) / self
._framesize
387 if self
._comptype
== 'ULAW':
389 data
= audioop
.lin2ulaw(data
, self
._sampwidth
)
390 self
._file
.write(data
)
391 self
._nframeswritten
= self
._nframeswritten
+ nframes
392 self
._datawritten
= self
._datawritten
+ len(data
)
394 def writeframes(self
, data
):
395 self
.writeframesraw(data
)
396 if self
._nframeswritten
!= self
._nframes
or \
397 self
._datalength
!= self
._datawritten
:
401 self
._ensure
_header
_written
()
402 if self
._nframeswritten
!= self
._nframes
or \
403 self
._datalength
!= self
._datawritten
:
413 def _ensure_header_written(self
):
414 if not self
._nframeswritten
:
415 if not self
._nchannels
:
416 raise Error
, '# of channels not specified'
417 if not self
._sampwidth
:
418 raise Error
, 'sample width not specified'
419 if not self
._framerate
:
420 raise Error
, 'frame rate not specified'
423 def _write_header(self
):
424 if self
._comptype
== 'NONE':
425 if self
._sampwidth
== 1:
426 encoding
= AUDIO_FILE_ENCODING_LINEAR_8
428 elif self
._sampwidth
== 2:
429 encoding
= AUDIO_FILE_ENCODING_LINEAR_16
431 elif self
._sampwidth
== 4:
432 encoding
= AUDIO_FILE_ENCODING_LINEAR_32
435 raise Error
, 'internal error'
436 elif self
._comptype
== 'ULAW':
437 encoding
= AUDIO_FILE_ENCODING_MULAW_8
440 raise Error
, 'internal error'
441 self
._framesize
= self
._framesize
* self
._nchannels
442 _write_u32(self
._file
, AUDIO_FILE_MAGIC
)
443 header_size
= 25 + len(self
._info
)
444 header_size
= (header_size
+ 7) & ~
7
445 _write_u32(self
._file
, header_size
)
446 if self
._nframes
== AUDIO_UNKNOWN_SIZE
:
447 length
= AUDIO_UNKNOWN_SIZE
449 length
= self
._nframes
* self
._framesize
450 _write_u32(self
._file
, length
)
451 self
._datalength
= length
452 _write_u32(self
._file
, encoding
)
453 _write_u32(self
._file
, self
._framerate
)
454 _write_u32(self
._file
, self
._nchannels
)
455 self
._file
.write(self
._info
)
456 self
._file
.write('\0'*(header_size
- len(self
._info
) - 24))
458 def _patchheader(self
):
460 _write_u32(self
._file
, self
._datawritten
)
461 self
._datalength
= self
._datawritten
462 self
._file
.seek(0, 2)
470 raise Error
, "mode must be 'r' or 'w'"