Permalink
Newer
100644
984 lines (872 sloc)
33.4 KB
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
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
"""
139
import warnings
141
__all__ = ["Error", "open"]
143
144
warnings._deprecated(__name__, remove=(3, 13))
145
146
147
class Error(Exception):
148
pass
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:
157
158
def _read_ulong(file):
159
try:
160
return struct.unpack('>L', file.read(4))[0]
161
except struct.error:
163
164
def _read_short(file):
165
try:
166
return struct.unpack('>h', file.read(2))[0]
167
except struct.error:
170
def _read_ushort(file):
171
try:
172
return struct.unpack('>H', file.read(2))[0]
173
except struct.error:
176
def _read_string(file):
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)
204
205
def _write_short(f, x):
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):
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)))
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)
251
fmant = math.ldexp(fmant - fsmant, 32)
252
fsmant = math.floor(fmant)
254
_write_ushort(f, expon)
255
_write_ulong(f, himant)
256
_write_ulong(f, lomant)
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)""")
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')
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()
340
self._read_comm_chunk(chunk)
341
self._comm_chunk_read = 1
343
self._ssnd_chunk = chunk
344
dummy = chunk.read(8)
345
self._ssnd_seek_needed = 0
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')
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)
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:
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:
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)
456
with warnings.catch_warnings():
457
warnings.simplefilter('ignore', category=DeprecationWarning)
458
import audioop
459
return audioop.alaw2lin(data, 2)
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)
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
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
522
self._sampwidth = 2
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)
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
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
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):
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')
639
self._nchannels = nchannels
640
641
def getnchannels(self):
642
if not self._nchannels:
644
return self._nchannels
645
646
def setsampwidth(self, sampwidth):
647
if self._nframeswritten:
648
raise Error('cannot change parameters after starting to write')
651
self._sampwidth = sampwidth
652
653
def getsampwidth(self):
654
if not self._sampwidth:
656
return self._sampwidth
657
658
def setframerate(self, framerate):
659
if self._nframeswritten:
660
raise Error('cannot change parameters after starting to write')
663
self._framerate = framerate
664
665
def getframerate(self):
666
if not self._framerate:
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'):
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
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'):
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:
714
return _aifc_params(self._nchannels, self._sampwidth, self._framerate,
715
self._nframes, self._comptype, self._compname)
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
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')
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()
776
# Prevent ref cycles
777
self._convert = None
778
f = self._file
779
self._file = None
787
with warnings.catch_warnings():
788
warnings.simplefilter('ignore', category=DeprecationWarning)
789
import audioop
790
return audioop.lin2alaw(data, 2)
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)
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)')
830
self._write_header(datasize)
831
832
def _init_compression(self):
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
843
if self._aifc and self._comptype != b'NONE':
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)
871
self._file.write(b'AIFF')
872
self._file.write(b'COMM')
873
_write_ulong(self._file, commlength)
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)
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
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)
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
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)
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'")
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.")