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