|
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 = 0xA2805140L # 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 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 |
|
197 def _write_short(f, x): |
|
198 f.write(struct.pack('>h', x)) |
|
199 |
|
200 def _write_long(f, x): |
|
201 f.write(struct.pack('>L', x)) |
|
202 |
|
203 def _write_string(f, s): |
|
204 if len(s) > 255: |
|
205 raise ValueError("string exceeds maximum pstring length") |
|
206 f.write(chr(len(s))) |
|
207 f.write(s) |
|
208 if len(s) & 1 == 0: |
|
209 f.write(chr(0)) |
|
210 |
|
211 def _write_float(f, x): |
|
212 import math |
|
213 if x < 0: |
|
214 sign = 0x8000 |
|
215 x = x * -1 |
|
216 else: |
|
217 sign = 0 |
|
218 if x == 0: |
|
219 expon = 0 |
|
220 himant = 0 |
|
221 lomant = 0 |
|
222 else: |
|
223 fmant, expon = math.frexp(x) |
|
224 if expon > 16384 or fmant >= 1: # Infinity or NaN |
|
225 expon = sign|0x7FFF |
|
226 himant = 0 |
|
227 lomant = 0 |
|
228 else: # Finite |
|
229 expon = expon + 16382 |
|
230 if expon < 0: # denormalized |
|
231 fmant = math.ldexp(fmant, expon) |
|
232 expon = 0 |
|
233 expon = expon | sign |
|
234 fmant = math.ldexp(fmant, 32) |
|
235 fsmant = math.floor(fmant) |
|
236 himant = long(fsmant) |
|
237 fmant = math.ldexp(fmant - fsmant, 32) |
|
238 fsmant = math.floor(fmant) |
|
239 lomant = long(fsmant) |
|
240 _write_short(f, expon) |
|
241 _write_long(f, himant) |
|
242 _write_long(f, lomant) |
|
243 |
|
244 from chunk import Chunk |
|
245 |
|
246 class Aifc_read: |
|
247 # Variables used in this class: |
|
248 # |
|
249 # These variables are available to the user though appropriate |
|
250 # methods of this class: |
|
251 # _file -- the open file with methods read(), close(), and seek() |
|
252 # set through the __init__() method |
|
253 # _nchannels -- the number of audio channels |
|
254 # available through the getnchannels() method |
|
255 # _nframes -- the number of audio frames |
|
256 # available through the getnframes() method |
|
257 # _sampwidth -- the number of bytes per audio sample |
|
258 # available through the getsampwidth() method |
|
259 # _framerate -- the sampling frequency |
|
260 # available through the getframerate() method |
|
261 # _comptype -- the AIFF-C compression type ('NONE' if AIFF) |
|
262 # available through the getcomptype() method |
|
263 # _compname -- the human-readable AIFF-C compression type |
|
264 # available through the getcomptype() method |
|
265 # _markers -- the marks in the audio file |
|
266 # available through the getmarkers() and getmark() |
|
267 # methods |
|
268 # _soundpos -- the position in the audio stream |
|
269 # available through the tell() method, set through the |
|
270 # setpos() method |
|
271 # |
|
272 # These variables are used internally only: |
|
273 # _version -- the AIFF-C version number |
|
274 # _decomp -- the decompressor from builtin module cl |
|
275 # _comm_chunk_read -- 1 iff the COMM chunk has been read |
|
276 # _aifc -- 1 iff reading an AIFF-C file |
|
277 # _ssnd_seek_needed -- 1 iff positioned correctly in audio |
|
278 # file for readframes() |
|
279 # _ssnd_chunk -- instantiation of a chunk class for the SSND chunk |
|
280 # _framesize -- size of one frame in the file |
|
281 |
|
282 def initfp(self, file): |
|
283 self._version = 0 |
|
284 self._decomp = None |
|
285 self._convert = None |
|
286 self._markers = [] |
|
287 self._soundpos = 0 |
|
288 self._file = Chunk(file) |
|
289 if self._file.getname() != 'FORM': |
|
290 raise Error, 'file does not start with FORM id' |
|
291 formdata = self._file.read(4) |
|
292 if formdata == 'AIFF': |
|
293 self._aifc = 0 |
|
294 elif formdata == 'AIFC': |
|
295 self._aifc = 1 |
|
296 else: |
|
297 raise Error, 'not an AIFF or AIFF-C file' |
|
298 self._comm_chunk_read = 0 |
|
299 while 1: |
|
300 self._ssnd_seek_needed = 1 |
|
301 try: |
|
302 chunk = Chunk(self._file) |
|
303 except EOFError: |
|
304 break |
|
305 chunkname = chunk.getname() |
|
306 if chunkname == 'COMM': |
|
307 self._read_comm_chunk(chunk) |
|
308 self._comm_chunk_read = 1 |
|
309 elif chunkname == 'SSND': |
|
310 self._ssnd_chunk = chunk |
|
311 dummy = chunk.read(8) |
|
312 self._ssnd_seek_needed = 0 |
|
313 elif chunkname == 'FVER': |
|
314 self._version = _read_ulong(chunk) |
|
315 elif chunkname == 'MARK': |
|
316 self._readmark(chunk) |
|
317 elif chunkname in _skiplist: |
|
318 pass |
|
319 else: |
|
320 raise Error, 'unrecognized chunk type '+chunk.chunkname |
|
321 chunk.skip() |
|
322 if not self._comm_chunk_read or not self._ssnd_chunk: |
|
323 raise Error, 'COMM chunk and/or SSND chunk missing' |
|
324 if self._aifc and self._decomp: |
|
325 import cl |
|
326 params = [cl.ORIGINAL_FORMAT, 0, |
|
327 cl.BITS_PER_COMPONENT, self._sampwidth * 8, |
|
328 cl.FRAME_RATE, self._framerate] |
|
329 if self._nchannels == 1: |
|
330 params[1] = cl.MONO |
|
331 elif self._nchannels == 2: |
|
332 params[1] = cl.STEREO_INTERLEAVED |
|
333 else: |
|
334 raise Error, 'cannot compress more than 2 channels' |
|
335 self._decomp.SetParams(params) |
|
336 |
|
337 def __init__(self, f): |
|
338 if type(f) == type(''): |
|
339 f = __builtin__.open(f, 'rb') |
|
340 # else, assume it is an open file object already |
|
341 self.initfp(f) |
|
342 |
|
343 # |
|
344 # User visible methods. |
|
345 # |
|
346 def getfp(self): |
|
347 return self._file |
|
348 |
|
349 def rewind(self): |
|
350 self._ssnd_seek_needed = 1 |
|
351 self._soundpos = 0 |
|
352 |
|
353 def close(self): |
|
354 if self._decomp: |
|
355 self._decomp.CloseDecompressor() |
|
356 self._decomp = None |
|
357 self._file = None |
|
358 |
|
359 def tell(self): |
|
360 return self._soundpos |
|
361 |
|
362 def getnchannels(self): |
|
363 return self._nchannels |
|
364 |
|
365 def getnframes(self): |
|
366 return self._nframes |
|
367 |
|
368 def getsampwidth(self): |
|
369 return self._sampwidth |
|
370 |
|
371 def getframerate(self): |
|
372 return self._framerate |
|
373 |
|
374 def getcomptype(self): |
|
375 return self._comptype |
|
376 |
|
377 def getcompname(self): |
|
378 return self._compname |
|
379 |
|
380 ## def getversion(self): |
|
381 ## return self._version |
|
382 |
|
383 def getparams(self): |
|
384 return self.getnchannels(), self.getsampwidth(), \ |
|
385 self.getframerate(), self.getnframes(), \ |
|
386 self.getcomptype(), self.getcompname() |
|
387 |
|
388 def getmarkers(self): |
|
389 if len(self._markers) == 0: |
|
390 return None |
|
391 return self._markers |
|
392 |
|
393 def getmark(self, id): |
|
394 for marker in self._markers: |
|
395 if id == marker[0]: |
|
396 return marker |
|
397 raise Error, 'marker %r does not exist' % (id,) |
|
398 |
|
399 def setpos(self, pos): |
|
400 if pos < 0 or pos > self._nframes: |
|
401 raise Error, 'position not in range' |
|
402 self._soundpos = pos |
|
403 self._ssnd_seek_needed = 1 |
|
404 |
|
405 def readframes(self, nframes): |
|
406 if self._ssnd_seek_needed: |
|
407 self._ssnd_chunk.seek(0) |
|
408 dummy = self._ssnd_chunk.read(8) |
|
409 pos = self._soundpos * self._framesize |
|
410 if pos: |
|
411 self._ssnd_chunk.seek(pos + 8) |
|
412 self._ssnd_seek_needed = 0 |
|
413 if nframes == 0: |
|
414 return '' |
|
415 data = self._ssnd_chunk.read(nframes * self._framesize) |
|
416 if self._convert and data: |
|
417 data = self._convert(data) |
|
418 self._soundpos = self._soundpos + len(data) / (self._nchannels * self._sampwidth) |
|
419 return data |
|
420 |
|
421 # |
|
422 # Internal methods. |
|
423 # |
|
424 |
|
425 def _decomp_data(self, data): |
|
426 import cl |
|
427 dummy = self._decomp.SetParam(cl.FRAME_BUFFER_SIZE, |
|
428 len(data) * 2) |
|
429 return self._decomp.Decompress(len(data) / self._nchannels, |
|
430 data) |
|
431 |
|
432 def _ulaw2lin(self, data): |
|
433 import audioop |
|
434 return audioop.ulaw2lin(data, 2) |
|
435 |
|
436 def _adpcm2lin(self, data): |
|
437 import audioop |
|
438 if not hasattr(self, '_adpcmstate'): |
|
439 # first time |
|
440 self._adpcmstate = None |
|
441 data, self._adpcmstate = audioop.adpcm2lin(data, 2, |
|
442 self._adpcmstate) |
|
443 return data |
|
444 |
|
445 def _read_comm_chunk(self, chunk): |
|
446 self._nchannels = _read_short(chunk) |
|
447 self._nframes = _read_long(chunk) |
|
448 self._sampwidth = (_read_short(chunk) + 7) / 8 |
|
449 self._framerate = int(_read_float(chunk)) |
|
450 self._framesize = self._nchannels * self._sampwidth |
|
451 if self._aifc: |
|
452 #DEBUG: SGI's soundeditor produces a bad size :-( |
|
453 kludge = 0 |
|
454 if chunk.chunksize == 18: |
|
455 kludge = 1 |
|
456 print 'Warning: bad COMM chunk size' |
|
457 chunk.chunksize = 23 |
|
458 #DEBUG end |
|
459 self._comptype = chunk.read(4) |
|
460 #DEBUG start |
|
461 if kludge: |
|
462 length = ord(chunk.file.read(1)) |
|
463 if length & 1 == 0: |
|
464 length = length + 1 |
|
465 chunk.chunksize = chunk.chunksize + length |
|
466 chunk.file.seek(-1, 1) |
|
467 #DEBUG end |
|
468 self._compname = _read_string(chunk) |
|
469 if self._comptype != 'NONE': |
|
470 if self._comptype == 'G722': |
|
471 try: |
|
472 import audioop |
|
473 except ImportError: |
|
474 pass |
|
475 else: |
|
476 self._convert = self._adpcm2lin |
|
477 self._framesize = self._framesize / 4 |
|
478 return |
|
479 # for ULAW and ALAW try Compression Library |
|
480 try: |
|
481 import cl |
|
482 except ImportError: |
|
483 if self._comptype == 'ULAW': |
|
484 try: |
|
485 import audioop |
|
486 self._convert = self._ulaw2lin |
|
487 self._framesize = self._framesize / 2 |
|
488 return |
|
489 except ImportError: |
|
490 pass |
|
491 raise Error, 'cannot read compressed AIFF-C files' |
|
492 if self._comptype == 'ULAW': |
|
493 scheme = cl.G711_ULAW |
|
494 self._framesize = self._framesize / 2 |
|
495 elif self._comptype == 'ALAW': |
|
496 scheme = cl.G711_ALAW |
|
497 self._framesize = self._framesize / 2 |
|
498 else: |
|
499 raise Error, 'unsupported compression type' |
|
500 self._decomp = cl.OpenDecompressor(scheme) |
|
501 self._convert = self._decomp_data |
|
502 else: |
|
503 self._comptype = 'NONE' |
|
504 self._compname = 'not compressed' |
|
505 |
|
506 def _readmark(self, chunk): |
|
507 nmarkers = _read_short(chunk) |
|
508 # Some files appear to contain invalid counts. |
|
509 # Cope with this by testing for EOF. |
|
510 try: |
|
511 for i in range(nmarkers): |
|
512 id = _read_short(chunk) |
|
513 pos = _read_long(chunk) |
|
514 name = _read_string(chunk) |
|
515 if pos or name: |
|
516 # some files appear to have |
|
517 # dummy markers consisting of |
|
518 # a position 0 and name '' |
|
519 self._markers.append((id, pos, name)) |
|
520 except EOFError: |
|
521 print 'Warning: MARK chunk contains only', |
|
522 print len(self._markers), |
|
523 if len(self._markers) == 1: print 'marker', |
|
524 else: print 'markers', |
|
525 print 'instead of', nmarkers |
|
526 |
|
527 class Aifc_write: |
|
528 # Variables used in this class: |
|
529 # |
|
530 # These variables are user settable through appropriate methods |
|
531 # of this class: |
|
532 # _file -- the open file with methods write(), close(), tell(), seek() |
|
533 # set through the __init__() method |
|
534 # _comptype -- the AIFF-C compression type ('NONE' in AIFF) |
|
535 # set through the setcomptype() or setparams() method |
|
536 # _compname -- the human-readable AIFF-C compression type |
|
537 # set through the setcomptype() or setparams() method |
|
538 # _nchannels -- the number of audio channels |
|
539 # set through the setnchannels() or setparams() method |
|
540 # _sampwidth -- the number of bytes per audio sample |
|
541 # set through the setsampwidth() or setparams() method |
|
542 # _framerate -- the sampling frequency |
|
543 # set through the setframerate() or setparams() method |
|
544 # _nframes -- the number of audio frames written to the header |
|
545 # set through the setnframes() or setparams() method |
|
546 # _aifc -- whether we're writing an AIFF-C file or an AIFF file |
|
547 # set through the aifc() method, reset through the |
|
548 # aiff() method |
|
549 # |
|
550 # These variables are used internally only: |
|
551 # _version -- the AIFF-C version number |
|
552 # _comp -- the compressor from builtin module cl |
|
553 # _nframeswritten -- the number of audio frames actually written |
|
554 # _datalength -- the size of the audio samples written to the header |
|
555 # _datawritten -- the size of the audio samples actually written |
|
556 |
|
557 def __init__(self, f): |
|
558 if type(f) == type(''): |
|
559 filename = f |
|
560 f = __builtin__.open(f, 'wb') |
|
561 else: |
|
562 # else, assume it is an open file object already |
|
563 filename = '???' |
|
564 self.initfp(f) |
|
565 if filename[-5:] == '.aiff': |
|
566 self._aifc = 0 |
|
567 else: |
|
568 self._aifc = 1 |
|
569 |
|
570 def initfp(self, file): |
|
571 self._file = file |
|
572 self._version = _AIFC_version |
|
573 self._comptype = 'NONE' |
|
574 self._compname = 'not compressed' |
|
575 self._comp = None |
|
576 self._convert = None |
|
577 self._nchannels = 0 |
|
578 self._sampwidth = 0 |
|
579 self._framerate = 0 |
|
580 self._nframes = 0 |
|
581 self._nframeswritten = 0 |
|
582 self._datawritten = 0 |
|
583 self._datalength = 0 |
|
584 self._markers = [] |
|
585 self._marklength = 0 |
|
586 self._aifc = 1 # AIFF-C is default |
|
587 |
|
588 def __del__(self): |
|
589 if self._file: |
|
590 self.close() |
|
591 |
|
592 # |
|
593 # User visible methods. |
|
594 # |
|
595 def aiff(self): |
|
596 if self._nframeswritten: |
|
597 raise Error, 'cannot change parameters after starting to write' |
|
598 self._aifc = 0 |
|
599 |
|
600 def aifc(self): |
|
601 if self._nframeswritten: |
|
602 raise Error, 'cannot change parameters after starting to write' |
|
603 self._aifc = 1 |
|
604 |
|
605 def setnchannels(self, nchannels): |
|
606 if self._nframeswritten: |
|
607 raise Error, 'cannot change parameters after starting to write' |
|
608 if nchannels < 1: |
|
609 raise Error, 'bad # of channels' |
|
610 self._nchannels = nchannels |
|
611 |
|
612 def getnchannels(self): |
|
613 if not self._nchannels: |
|
614 raise Error, 'number of channels not set' |
|
615 return self._nchannels |
|
616 |
|
617 def setsampwidth(self, sampwidth): |
|
618 if self._nframeswritten: |
|
619 raise Error, 'cannot change parameters after starting to write' |
|
620 if sampwidth < 1 or sampwidth > 4: |
|
621 raise Error, 'bad sample width' |
|
622 self._sampwidth = sampwidth |
|
623 |
|
624 def getsampwidth(self): |
|
625 if not self._sampwidth: |
|
626 raise Error, 'sample width not set' |
|
627 return self._sampwidth |
|
628 |
|
629 def setframerate(self, framerate): |
|
630 if self._nframeswritten: |
|
631 raise Error, 'cannot change parameters after starting to write' |
|
632 if framerate <= 0: |
|
633 raise Error, 'bad frame rate' |
|
634 self._framerate = framerate |
|
635 |
|
636 def getframerate(self): |
|
637 if not self._framerate: |
|
638 raise Error, 'frame rate not set' |
|
639 return self._framerate |
|
640 |
|
641 def setnframes(self, nframes): |
|
642 if self._nframeswritten: |
|
643 raise Error, 'cannot change parameters after starting to write' |
|
644 self._nframes = nframes |
|
645 |
|
646 def getnframes(self): |
|
647 return self._nframeswritten |
|
648 |
|
649 def setcomptype(self, comptype, compname): |
|
650 if self._nframeswritten: |
|
651 raise Error, 'cannot change parameters after starting to write' |
|
652 if comptype not in ('NONE', 'ULAW', 'ALAW', 'G722'): |
|
653 raise Error, 'unsupported compression type' |
|
654 self._comptype = comptype |
|
655 self._compname = compname |
|
656 |
|
657 def getcomptype(self): |
|
658 return self._comptype |
|
659 |
|
660 def getcompname(self): |
|
661 return self._compname |
|
662 |
|
663 ## def setversion(self, version): |
|
664 ## if self._nframeswritten: |
|
665 ## raise Error, 'cannot change parameters after starting to write' |
|
666 ## self._version = version |
|
667 |
|
668 def setparams(self, (nchannels, sampwidth, framerate, nframes, comptype, compname)): |
|
669 if self._nframeswritten: |
|
670 raise Error, 'cannot change parameters after starting to write' |
|
671 if comptype not in ('NONE', 'ULAW', 'ALAW', 'G722'): |
|
672 raise Error, 'unsupported compression type' |
|
673 self.setnchannels(nchannels) |
|
674 self.setsampwidth(sampwidth) |
|
675 self.setframerate(framerate) |
|
676 self.setnframes(nframes) |
|
677 self.setcomptype(comptype, compname) |
|
678 |
|
679 def getparams(self): |
|
680 if not self._nchannels or not self._sampwidth or not self._framerate: |
|
681 raise Error, 'not all parameters set' |
|
682 return self._nchannels, self._sampwidth, self._framerate, \ |
|
683 self._nframes, self._comptype, self._compname |
|
684 |
|
685 def setmark(self, id, pos, name): |
|
686 if id <= 0: |
|
687 raise Error, 'marker ID must be > 0' |
|
688 if pos < 0: |
|
689 raise Error, 'marker position must be >= 0' |
|
690 if type(name) != type(''): |
|
691 raise Error, 'marker name must be a string' |
|
692 for i in range(len(self._markers)): |
|
693 if id == self._markers[i][0]: |
|
694 self._markers[i] = id, pos, name |
|
695 return |
|
696 self._markers.append((id, pos, name)) |
|
697 |
|
698 def getmark(self, id): |
|
699 for marker in self._markers: |
|
700 if id == marker[0]: |
|
701 return marker |
|
702 raise Error, 'marker %r does not exist' % (id,) |
|
703 |
|
704 def getmarkers(self): |
|
705 if len(self._markers) == 0: |
|
706 return None |
|
707 return self._markers |
|
708 |
|
709 def tell(self): |
|
710 return self._nframeswritten |
|
711 |
|
712 def writeframesraw(self, data): |
|
713 self._ensure_header_written(len(data)) |
|
714 nframes = len(data) / (self._sampwidth * self._nchannels) |
|
715 if self._convert: |
|
716 data = self._convert(data) |
|
717 self._file.write(data) |
|
718 self._nframeswritten = self._nframeswritten + nframes |
|
719 self._datawritten = self._datawritten + len(data) |
|
720 |
|
721 def writeframes(self, data): |
|
722 self.writeframesraw(data) |
|
723 if self._nframeswritten != self._nframes or \ |
|
724 self._datalength != self._datawritten: |
|
725 self._patchheader() |
|
726 |
|
727 def close(self): |
|
728 self._ensure_header_written(0) |
|
729 if self._datawritten & 1: |
|
730 # quick pad to even size |
|
731 self._file.write(chr(0)) |
|
732 self._datawritten = self._datawritten + 1 |
|
733 self._writemarkers() |
|
734 if self._nframeswritten != self._nframes or \ |
|
735 self._datalength != self._datawritten or \ |
|
736 self._marklength: |
|
737 self._patchheader() |
|
738 if self._comp: |
|
739 self._comp.CloseCompressor() |
|
740 self._comp = None |
|
741 self._file.flush() |
|
742 self._file = None |
|
743 |
|
744 # |
|
745 # Internal methods. |
|
746 # |
|
747 |
|
748 def _comp_data(self, data): |
|
749 import cl |
|
750 dummy = self._comp.SetParam(cl.FRAME_BUFFER_SIZE, len(data)) |
|
751 dummy = self._comp.SetParam(cl.COMPRESSED_BUFFER_SIZE, len(data)) |
|
752 return self._comp.Compress(self._nframes, data) |
|
753 |
|
754 def _lin2ulaw(self, data): |
|
755 import audioop |
|
756 return audioop.lin2ulaw(data, 2) |
|
757 |
|
758 def _lin2adpcm(self, data): |
|
759 import audioop |
|
760 if not hasattr(self, '_adpcmstate'): |
|
761 self._adpcmstate = None |
|
762 data, self._adpcmstate = audioop.lin2adpcm(data, 2, |
|
763 self._adpcmstate) |
|
764 return data |
|
765 |
|
766 def _ensure_header_written(self, datasize): |
|
767 if not self._nframeswritten: |
|
768 if self._comptype in ('ULAW', 'ALAW'): |
|
769 if not self._sampwidth: |
|
770 self._sampwidth = 2 |
|
771 if self._sampwidth != 2: |
|
772 raise Error, 'sample width must be 2 when compressing with ULAW or ALAW' |
|
773 if self._comptype == 'G722': |
|
774 if not self._sampwidth: |
|
775 self._sampwidth = 2 |
|
776 if self._sampwidth != 2: |
|
777 raise Error, 'sample width must be 2 when compressing with G7.22 (ADPCM)' |
|
778 if not self._nchannels: |
|
779 raise Error, '# channels not specified' |
|
780 if not self._sampwidth: |
|
781 raise Error, 'sample width not specified' |
|
782 if not self._framerate: |
|
783 raise Error, 'sampling rate not specified' |
|
784 self._write_header(datasize) |
|
785 |
|
786 def _init_compression(self): |
|
787 if self._comptype == 'G722': |
|
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." |