1 """Stuff to parse Sun and NeXT audio files.
3 An audio file 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 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.
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' 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
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
68 The close() method is called automatically when the class instance
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(tuple)-- set all parameters at once
86 tell() -- return current position in output file
88 -- write audio frames without pathing up the
91 -- write audio frames and patch up the file header
92 close() -- patch up the file header and close the
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
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
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
= 0xFFFFFFFF # ((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
]
131 class Error(Exception):
140 x
= x
*256 + ord(byte
)
143 def _write_u32(file, x
):
146 d
, m
= divmod(x
, 256)
147 data
.insert(0, int(m
))
149 file.write(bytes(data
))
153 def __init__(self
, f
):
154 if type(f
) == type(''):
156 f
= builtins
.open(f
, 'rb')
163 def initfp(self
, file):
166 magic
= int(_read_u32(file))
167 if magic
!= AUDIO_FILE_MAGIC
:
168 raise Error('bad magic number')
169 self
._hdr
_size
= int(_read_u32(file))
170 if self
._hdr
_size
< 24:
171 raise Error('header size too small')
172 if self
._hdr
_size
> 100:
173 raise Error('header size ridiculously large')
174 self
._data
_size
= _read_u32(file)
175 if self
._data
_size
!= AUDIO_UNKNOWN_SIZE
:
176 self
._data
_size
= int(self
._data
_size
)
177 self
._encoding
= int(_read_u32(file))
178 if self
._encoding
not in _simple_encodings
:
179 raise Error('encoding not (yet) supported')
180 if self
._encoding
in (AUDIO_FILE_ENCODING_MULAW_8
,
181 AUDIO_FILE_ENCODING_ALAW_8
):
184 elif self
._encoding
== AUDIO_FILE_ENCODING_LINEAR_8
:
185 self
._framesize
= self
._sampwidth
= 1
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
] == b
'\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
)
282 def __init__(self
, f
):
283 if type(f
) == type(''):
285 f
= builtins
.open(f
, 'wb')
292 def initfp(self
, file):
298 self
._nframes
= AUDIO_UNKNOWN_SIZE
299 self
._nframeswritten
= 0
300 self
._datawritten
= 0
303 self
._comptype
= 'ULAW' # default is U-law
305 def setnchannels(self
, nchannels
):
306 if self
._nframeswritten
:
307 raise Error('cannot change parameters after starting to write')
308 if nchannels
not in (1, 2, 4):
309 raise Error('only 1, 2, or 4 channels supported')
310 self
._nchannels
= nchannels
312 def getnchannels(self
):
313 if not self
._nchannels
:
314 raise Error('number of channels not set')
315 return self
._nchannels
317 def setsampwidth(self
, sampwidth
):
318 if self
._nframeswritten
:
319 raise Error('cannot change parameters after starting to write')
320 if sampwidth
not in (1, 2, 4):
321 raise Error('bad sample width')
322 self
._sampwidth
= sampwidth
324 def getsampwidth(self
):
325 if not self
._framerate
:
326 raise Error('sample width not specified')
327 return self
._sampwidth
329 def setframerate(self
, framerate
):
330 if self
._nframeswritten
:
331 raise Error('cannot change parameters after starting to write')
332 self
._framerate
= framerate
334 def getframerate(self
):
335 if not self
._framerate
:
336 raise Error('frame rate not set')
337 return self
._framerate
339 def setnframes(self
, nframes
):
340 if self
._nframeswritten
:
341 raise Error('cannot change parameters after starting to write')
343 raise Error('# of frames cannot be negative')
344 self
._nframes
= nframes
346 def getnframes(self
):
347 return self
._nframeswritten
349 def setcomptype(self
, type, name
):
350 if type in ('NONE', 'ULAW'):
351 self
._comptype
= type
353 raise Error('unknown compression type')
355 def getcomptype(self
):
356 return self
._comptype
358 def getcompname(self
):
359 if self
._comptype
== 'ULAW':
360 return 'CCITT G.711 u-law'
361 elif self
._comptype
== 'ALAW':
362 return 'CCITT G.711 A-law'
364 return 'not compressed'
366 def setparams(self
, params
):
367 nchannels
, sampwidth
, framerate
, nframes
, comptype
, compname
= params
368 self
.setnchannels(nchannels
)
369 self
.setsampwidth(sampwidth
)
370 self
.setframerate(framerate
)
371 self
.setnframes(nframes
)
372 self
.setcomptype(comptype
, compname
)
375 return self
.getnchannels(), self
.getsampwidth(), \
376 self
.getframerate(), self
.getnframes(), \
377 self
.getcomptype(), self
.getcompname()
380 return self
._nframeswritten
382 def writeframesraw(self
, data
):
383 self
._ensure
_header
_written
()
384 nframes
= len(data
) / self
._framesize
385 if self
._comptype
== 'ULAW':
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
)
392 def writeframes(self
, data
):
393 self
.writeframesraw(data
)
394 if self
._nframeswritten
!= self
._nframes
or \
395 self
._datalength
!= self
._datawritten
:
399 self
._ensure
_header
_written
()
400 if self
._nframeswritten
!= self
._nframes
or \
401 self
._datalength
!= self
._datawritten
:
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')
420 def _write_header(self
):
421 if self
._comptype
== 'NONE':
422 if self
._sampwidth
== 1:
423 encoding
= AUDIO_FILE_ENCODING_LINEAR_8
425 elif self
._sampwidth
== 2:
426 encoding
= AUDIO_FILE_ENCODING_LINEAR_16
428 elif self
._sampwidth
== 4:
429 encoding
= AUDIO_FILE_ENCODING_LINEAR_32
432 raise Error('internal error')
433 elif self
._comptype
== 'ULAW':
434 encoding
= AUDIO_FILE_ENCODING_MULAW_8
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
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(b
'\0'*(header_size
- len(self
._info
) - 24))
455 def _patchheader(self
):
457 _write_u32(self
._file
, self
._datawritten
)
458 self
._datalength
= self
._datawritten
459 self
._file
.seek(0, 2)
461 def open(f
, mode
=None):
463 if hasattr(f
, 'mode'):
467 if mode
in ('r', 'rb'):
469 elif mode
in ('w', 'wb'):
472 raise Error("mode must be 'r', 'rb', 'w', or 'wb'")