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