xref: /aosp_15_r20/prebuilts/build-tools/common/py3-stdlib/sunau.py (revision cda5da8d549138a6648c5ee6d7a49cf8f4a657be)
1*cda5da8dSAndroid Build Coastguard Worker"""Stuff to parse Sun and NeXT audio files.
2*cda5da8dSAndroid Build Coastguard Worker
3*cda5da8dSAndroid Build Coastguard WorkerAn audio file consists of a header followed by the data.  The structure
4*cda5da8dSAndroid Build Coastguard Workerof the header is as follows.
5*cda5da8dSAndroid Build Coastguard Worker
6*cda5da8dSAndroid Build Coastguard Worker        +---------------+
7*cda5da8dSAndroid Build Coastguard Worker        | magic word    |
8*cda5da8dSAndroid Build Coastguard Worker        +---------------+
9*cda5da8dSAndroid Build Coastguard Worker        | header size   |
10*cda5da8dSAndroid Build Coastguard Worker        +---------------+
11*cda5da8dSAndroid Build Coastguard Worker        | data size     |
12*cda5da8dSAndroid Build Coastguard Worker        +---------------+
13*cda5da8dSAndroid Build Coastguard Worker        | encoding      |
14*cda5da8dSAndroid Build Coastguard Worker        +---------------+
15*cda5da8dSAndroid Build Coastguard Worker        | sample rate   |
16*cda5da8dSAndroid Build Coastguard Worker        +---------------+
17*cda5da8dSAndroid Build Coastguard Worker        | # of channels |
18*cda5da8dSAndroid Build Coastguard Worker        +---------------+
19*cda5da8dSAndroid Build Coastguard Worker        | info          |
20*cda5da8dSAndroid Build Coastguard Worker        |               |
21*cda5da8dSAndroid Build Coastguard Worker        +---------------+
22*cda5da8dSAndroid Build Coastguard Worker
23*cda5da8dSAndroid Build Coastguard WorkerThe magic word consists of the 4 characters '.snd'.  Apart from the
24*cda5da8dSAndroid Build Coastguard Workerinfo field, all header fields are 4 bytes in size.  They are all
25*cda5da8dSAndroid Build Coastguard Worker32-bit unsigned integers encoded in big-endian byte order.
26*cda5da8dSAndroid Build Coastguard Worker
27*cda5da8dSAndroid Build Coastguard WorkerThe header size really gives the start of the data.
28*cda5da8dSAndroid Build Coastguard WorkerThe data size is the physical size of the data.  From the other
29*cda5da8dSAndroid Build Coastguard Workerparameters the number of frames can be calculated.
30*cda5da8dSAndroid Build Coastguard WorkerThe encoding gives the way in which audio samples are encoded.
31*cda5da8dSAndroid Build Coastguard WorkerPossible values are listed below.
32*cda5da8dSAndroid Build Coastguard WorkerThe info field currently consists of an ASCII string giving a
33*cda5da8dSAndroid Build Coastguard Workerhuman-readable description of the audio file.  The info field is
34*cda5da8dSAndroid Build Coastguard Workerpadded with NUL bytes to the header size.
35*cda5da8dSAndroid Build Coastguard Worker
36*cda5da8dSAndroid Build Coastguard WorkerUsage.
37*cda5da8dSAndroid Build Coastguard Worker
38*cda5da8dSAndroid Build Coastguard WorkerReading audio files:
39*cda5da8dSAndroid Build Coastguard Worker        f = sunau.open(file, 'r')
40*cda5da8dSAndroid Build Coastguard Workerwhere file is either the name of a file or an open file pointer.
41*cda5da8dSAndroid Build Coastguard WorkerThe open file pointer must have methods read(), seek(), and close().
42*cda5da8dSAndroid Build Coastguard WorkerWhen the setpos() and rewind() methods are not used, the seek()
43*cda5da8dSAndroid Build Coastguard Workermethod is not  necessary.
44*cda5da8dSAndroid Build Coastguard Worker
45*cda5da8dSAndroid Build Coastguard WorkerThis returns an instance of a class with the following public methods:
46*cda5da8dSAndroid Build Coastguard Worker        getnchannels()  -- returns number of audio channels (1 for
47*cda5da8dSAndroid Build Coastguard Worker                           mono, 2 for stereo)
48*cda5da8dSAndroid Build Coastguard Worker        getsampwidth()  -- returns sample width in bytes
49*cda5da8dSAndroid Build Coastguard Worker        getframerate()  -- returns sampling frequency
50*cda5da8dSAndroid Build Coastguard Worker        getnframes()    -- returns number of audio frames
51*cda5da8dSAndroid Build Coastguard Worker        getcomptype()   -- returns compression type ('NONE' or 'ULAW')
52*cda5da8dSAndroid Build Coastguard Worker        getcompname()   -- returns human-readable version of
53*cda5da8dSAndroid Build Coastguard Worker                           compression type ('not compressed' matches 'NONE')
54*cda5da8dSAndroid Build Coastguard Worker        getparams()     -- returns a namedtuple consisting of all of the
55*cda5da8dSAndroid Build Coastguard Worker                           above in the above order
56*cda5da8dSAndroid Build Coastguard Worker        getmarkers()    -- returns None (for compatibility with the
57*cda5da8dSAndroid Build Coastguard Worker                           aifc module)
58*cda5da8dSAndroid Build Coastguard Worker        getmark(id)     -- raises an error since the mark does not
59*cda5da8dSAndroid Build Coastguard Worker                           exist (for compatibility with the aifc module)
60*cda5da8dSAndroid Build Coastguard Worker        readframes(n)   -- returns at most n frames of audio
61*cda5da8dSAndroid Build Coastguard Worker        rewind()        -- rewind to the beginning of the audio stream
62*cda5da8dSAndroid Build Coastguard Worker        setpos(pos)     -- seek to the specified position
63*cda5da8dSAndroid Build Coastguard Worker        tell()          -- return the current position
64*cda5da8dSAndroid Build Coastguard Worker        close()         -- close the instance (make it unusable)
65*cda5da8dSAndroid Build Coastguard WorkerThe position returned by tell() and the position given to setpos()
66*cda5da8dSAndroid Build Coastguard Workerare compatible and have nothing to do with the actual position in the
67*cda5da8dSAndroid Build Coastguard Workerfile.
68*cda5da8dSAndroid Build Coastguard WorkerThe close() method is called automatically when the class instance
69*cda5da8dSAndroid Build Coastguard Workeris destroyed.
70*cda5da8dSAndroid Build Coastguard Worker
71*cda5da8dSAndroid Build Coastguard WorkerWriting audio files:
72*cda5da8dSAndroid Build Coastguard Worker        f = sunau.open(file, 'w')
73*cda5da8dSAndroid Build Coastguard Workerwhere file is either the name of a file or an open file pointer.
74*cda5da8dSAndroid Build Coastguard WorkerThe open file pointer must have methods write(), tell(), seek(), and
75*cda5da8dSAndroid Build Coastguard Workerclose().
76*cda5da8dSAndroid Build Coastguard Worker
77*cda5da8dSAndroid Build Coastguard WorkerThis returns an instance of a class with the following public methods:
78*cda5da8dSAndroid Build Coastguard Worker        setnchannels(n) -- set the number of channels
79*cda5da8dSAndroid Build Coastguard Worker        setsampwidth(n) -- set the sample width
80*cda5da8dSAndroid Build Coastguard Worker        setframerate(n) -- set the frame rate
81*cda5da8dSAndroid Build Coastguard Worker        setnframes(n)   -- set the number of frames
82*cda5da8dSAndroid Build Coastguard Worker        setcomptype(type, name)
83*cda5da8dSAndroid Build Coastguard Worker                        -- set the compression type and the
84*cda5da8dSAndroid Build Coastguard Worker                           human-readable compression type
85*cda5da8dSAndroid Build Coastguard Worker        setparams(tuple)-- set all parameters at once
86*cda5da8dSAndroid Build Coastguard Worker        tell()          -- return current position in output file
87*cda5da8dSAndroid Build Coastguard Worker        writeframesraw(data)
88*cda5da8dSAndroid Build Coastguard Worker                        -- write audio frames without pathing up the
89*cda5da8dSAndroid Build Coastguard Worker                           file header
90*cda5da8dSAndroid Build Coastguard Worker        writeframes(data)
91*cda5da8dSAndroid Build Coastguard Worker                        -- write audio frames and patch up the file header
92*cda5da8dSAndroid Build Coastguard Worker        close()         -- patch up the file header and close the
93*cda5da8dSAndroid Build Coastguard Worker                           output file
94*cda5da8dSAndroid Build Coastguard WorkerYou should set the parameters before the first writeframesraw or
95*cda5da8dSAndroid Build Coastguard Workerwriteframes.  The total number of frames does not need to be set,
96*cda5da8dSAndroid Build Coastguard Workerbut when it is set to the correct value, the header does not have to
97*cda5da8dSAndroid Build Coastguard Workerbe patched up.
98*cda5da8dSAndroid Build Coastguard WorkerIt is best to first set all parameters, perhaps possibly the
99*cda5da8dSAndroid Build Coastguard Workercompression type, and then write audio frames using writeframesraw.
100*cda5da8dSAndroid Build Coastguard WorkerWhen all frames have been written, either call writeframes(b'') or
101*cda5da8dSAndroid Build Coastguard Workerclose() to patch up the sizes in the header.
102*cda5da8dSAndroid Build Coastguard WorkerThe close() method is called automatically when the class instance
103*cda5da8dSAndroid Build Coastguard Workeris destroyed.
104*cda5da8dSAndroid Build Coastguard Worker"""
105*cda5da8dSAndroid Build Coastguard Worker
106*cda5da8dSAndroid Build Coastguard Workerfrom collections import namedtuple
107*cda5da8dSAndroid Build Coastguard Workerimport warnings
108*cda5da8dSAndroid Build Coastguard Worker
109*cda5da8dSAndroid Build Coastguard Workerwarnings._deprecated(__name__, remove=(3, 13))
110*cda5da8dSAndroid Build Coastguard Worker
111*cda5da8dSAndroid Build Coastguard Worker
112*cda5da8dSAndroid Build Coastguard Worker_sunau_params = namedtuple('_sunau_params',
113*cda5da8dSAndroid Build Coastguard Worker                           'nchannels sampwidth framerate nframes comptype compname')
114*cda5da8dSAndroid Build Coastguard Worker
115*cda5da8dSAndroid Build Coastguard Worker# from <multimedia/audio_filehdr.h>
116*cda5da8dSAndroid Build Coastguard WorkerAUDIO_FILE_MAGIC = 0x2e736e64
117*cda5da8dSAndroid Build Coastguard WorkerAUDIO_FILE_ENCODING_MULAW_8 = 1
118*cda5da8dSAndroid Build Coastguard WorkerAUDIO_FILE_ENCODING_LINEAR_8 = 2
119*cda5da8dSAndroid Build Coastguard WorkerAUDIO_FILE_ENCODING_LINEAR_16 = 3
120*cda5da8dSAndroid Build Coastguard WorkerAUDIO_FILE_ENCODING_LINEAR_24 = 4
121*cda5da8dSAndroid Build Coastguard WorkerAUDIO_FILE_ENCODING_LINEAR_32 = 5
122*cda5da8dSAndroid Build Coastguard WorkerAUDIO_FILE_ENCODING_FLOAT = 6
123*cda5da8dSAndroid Build Coastguard WorkerAUDIO_FILE_ENCODING_DOUBLE = 7
124*cda5da8dSAndroid Build Coastguard WorkerAUDIO_FILE_ENCODING_ADPCM_G721 = 23
125*cda5da8dSAndroid Build Coastguard WorkerAUDIO_FILE_ENCODING_ADPCM_G722 = 24
126*cda5da8dSAndroid Build Coastguard WorkerAUDIO_FILE_ENCODING_ADPCM_G723_3 = 25
127*cda5da8dSAndroid Build Coastguard WorkerAUDIO_FILE_ENCODING_ADPCM_G723_5 = 26
128*cda5da8dSAndroid Build Coastguard WorkerAUDIO_FILE_ENCODING_ALAW_8 = 27
129*cda5da8dSAndroid Build Coastguard Worker
130*cda5da8dSAndroid Build Coastguard Worker# from <multimedia/audio_hdr.h>
131*cda5da8dSAndroid Build Coastguard WorkerAUDIO_UNKNOWN_SIZE = 0xFFFFFFFF        # ((unsigned)(~0))
132*cda5da8dSAndroid Build Coastguard Worker
133*cda5da8dSAndroid Build Coastguard Worker_simple_encodings = [AUDIO_FILE_ENCODING_MULAW_8,
134*cda5da8dSAndroid Build Coastguard Worker                     AUDIO_FILE_ENCODING_LINEAR_8,
135*cda5da8dSAndroid Build Coastguard Worker                     AUDIO_FILE_ENCODING_LINEAR_16,
136*cda5da8dSAndroid Build Coastguard Worker                     AUDIO_FILE_ENCODING_LINEAR_24,
137*cda5da8dSAndroid Build Coastguard Worker                     AUDIO_FILE_ENCODING_LINEAR_32,
138*cda5da8dSAndroid Build Coastguard Worker                     AUDIO_FILE_ENCODING_ALAW_8]
139*cda5da8dSAndroid Build Coastguard Worker
140*cda5da8dSAndroid Build Coastguard Workerclass Error(Exception):
141*cda5da8dSAndroid Build Coastguard Worker    pass
142*cda5da8dSAndroid Build Coastguard Worker
143*cda5da8dSAndroid Build Coastguard Workerdef _read_u32(file):
144*cda5da8dSAndroid Build Coastguard Worker    x = 0
145*cda5da8dSAndroid Build Coastguard Worker    for i in range(4):
146*cda5da8dSAndroid Build Coastguard Worker        byte = file.read(1)
147*cda5da8dSAndroid Build Coastguard Worker        if not byte:
148*cda5da8dSAndroid Build Coastguard Worker            raise EOFError
149*cda5da8dSAndroid Build Coastguard Worker        x = x*256 + ord(byte)
150*cda5da8dSAndroid Build Coastguard Worker    return x
151*cda5da8dSAndroid Build Coastguard Worker
152*cda5da8dSAndroid Build Coastguard Workerdef _write_u32(file, x):
153*cda5da8dSAndroid Build Coastguard Worker    data = []
154*cda5da8dSAndroid Build Coastguard Worker    for i in range(4):
155*cda5da8dSAndroid Build Coastguard Worker        d, m = divmod(x, 256)
156*cda5da8dSAndroid Build Coastguard Worker        data.insert(0, int(m))
157*cda5da8dSAndroid Build Coastguard Worker        x = d
158*cda5da8dSAndroid Build Coastguard Worker    file.write(bytes(data))
159*cda5da8dSAndroid Build Coastguard Worker
160*cda5da8dSAndroid Build Coastguard Workerclass Au_read:
161*cda5da8dSAndroid Build Coastguard Worker
162*cda5da8dSAndroid Build Coastguard Worker    def __init__(self, f):
163*cda5da8dSAndroid Build Coastguard Worker        if type(f) == type(''):
164*cda5da8dSAndroid Build Coastguard Worker            import builtins
165*cda5da8dSAndroid Build Coastguard Worker            f = builtins.open(f, 'rb')
166*cda5da8dSAndroid Build Coastguard Worker            self._opened = True
167*cda5da8dSAndroid Build Coastguard Worker        else:
168*cda5da8dSAndroid Build Coastguard Worker            self._opened = False
169*cda5da8dSAndroid Build Coastguard Worker        self.initfp(f)
170*cda5da8dSAndroid Build Coastguard Worker
171*cda5da8dSAndroid Build Coastguard Worker    def __del__(self):
172*cda5da8dSAndroid Build Coastguard Worker        if self._file:
173*cda5da8dSAndroid Build Coastguard Worker            self.close()
174*cda5da8dSAndroid Build Coastguard Worker
175*cda5da8dSAndroid Build Coastguard Worker    def __enter__(self):
176*cda5da8dSAndroid Build Coastguard Worker        return self
177*cda5da8dSAndroid Build Coastguard Worker
178*cda5da8dSAndroid Build Coastguard Worker    def __exit__(self, *args):
179*cda5da8dSAndroid Build Coastguard Worker        self.close()
180*cda5da8dSAndroid Build Coastguard Worker
181*cda5da8dSAndroid Build Coastguard Worker    def initfp(self, file):
182*cda5da8dSAndroid Build Coastguard Worker        self._file = file
183*cda5da8dSAndroid Build Coastguard Worker        self._soundpos = 0
184*cda5da8dSAndroid Build Coastguard Worker        magic = int(_read_u32(file))
185*cda5da8dSAndroid Build Coastguard Worker        if magic != AUDIO_FILE_MAGIC:
186*cda5da8dSAndroid Build Coastguard Worker            raise Error('bad magic number')
187*cda5da8dSAndroid Build Coastguard Worker        self._hdr_size = int(_read_u32(file))
188*cda5da8dSAndroid Build Coastguard Worker        if self._hdr_size < 24:
189*cda5da8dSAndroid Build Coastguard Worker            raise Error('header size too small')
190*cda5da8dSAndroid Build Coastguard Worker        if self._hdr_size > 100:
191*cda5da8dSAndroid Build Coastguard Worker            raise Error('header size ridiculously large')
192*cda5da8dSAndroid Build Coastguard Worker        self._data_size = _read_u32(file)
193*cda5da8dSAndroid Build Coastguard Worker        if self._data_size != AUDIO_UNKNOWN_SIZE:
194*cda5da8dSAndroid Build Coastguard Worker            self._data_size = int(self._data_size)
195*cda5da8dSAndroid Build Coastguard Worker        self._encoding = int(_read_u32(file))
196*cda5da8dSAndroid Build Coastguard Worker        if self._encoding not in _simple_encodings:
197*cda5da8dSAndroid Build Coastguard Worker            raise Error('encoding not (yet) supported')
198*cda5da8dSAndroid Build Coastguard Worker        if self._encoding in (AUDIO_FILE_ENCODING_MULAW_8,
199*cda5da8dSAndroid Build Coastguard Worker                  AUDIO_FILE_ENCODING_ALAW_8):
200*cda5da8dSAndroid Build Coastguard Worker            self._sampwidth = 2
201*cda5da8dSAndroid Build Coastguard Worker            self._framesize = 1
202*cda5da8dSAndroid Build Coastguard Worker        elif self._encoding == AUDIO_FILE_ENCODING_LINEAR_8:
203*cda5da8dSAndroid Build Coastguard Worker            self._framesize = self._sampwidth = 1
204*cda5da8dSAndroid Build Coastguard Worker        elif self._encoding == AUDIO_FILE_ENCODING_LINEAR_16:
205*cda5da8dSAndroid Build Coastguard Worker            self._framesize = self._sampwidth = 2
206*cda5da8dSAndroid Build Coastguard Worker        elif self._encoding == AUDIO_FILE_ENCODING_LINEAR_24:
207*cda5da8dSAndroid Build Coastguard Worker            self._framesize = self._sampwidth = 3
208*cda5da8dSAndroid Build Coastguard Worker        elif self._encoding == AUDIO_FILE_ENCODING_LINEAR_32:
209*cda5da8dSAndroid Build Coastguard Worker            self._framesize = self._sampwidth = 4
210*cda5da8dSAndroid Build Coastguard Worker        else:
211*cda5da8dSAndroid Build Coastguard Worker            raise Error('unknown encoding')
212*cda5da8dSAndroid Build Coastguard Worker        self._framerate = int(_read_u32(file))
213*cda5da8dSAndroid Build Coastguard Worker        self._nchannels = int(_read_u32(file))
214*cda5da8dSAndroid Build Coastguard Worker        if not self._nchannels:
215*cda5da8dSAndroid Build Coastguard Worker            raise Error('bad # of channels')
216*cda5da8dSAndroid Build Coastguard Worker        self._framesize = self._framesize * self._nchannels
217*cda5da8dSAndroid Build Coastguard Worker        if self._hdr_size > 24:
218*cda5da8dSAndroid Build Coastguard Worker            self._info = file.read(self._hdr_size - 24)
219*cda5da8dSAndroid Build Coastguard Worker            self._info, _, _ = self._info.partition(b'\0')
220*cda5da8dSAndroid Build Coastguard Worker        else:
221*cda5da8dSAndroid Build Coastguard Worker            self._info = b''
222*cda5da8dSAndroid Build Coastguard Worker        try:
223*cda5da8dSAndroid Build Coastguard Worker            self._data_pos = file.tell()
224*cda5da8dSAndroid Build Coastguard Worker        except (AttributeError, OSError):
225*cda5da8dSAndroid Build Coastguard Worker            self._data_pos = None
226*cda5da8dSAndroid Build Coastguard Worker
227*cda5da8dSAndroid Build Coastguard Worker    def getfp(self):
228*cda5da8dSAndroid Build Coastguard Worker        return self._file
229*cda5da8dSAndroid Build Coastguard Worker
230*cda5da8dSAndroid Build Coastguard Worker    def getnchannels(self):
231*cda5da8dSAndroid Build Coastguard Worker        return self._nchannels
232*cda5da8dSAndroid Build Coastguard Worker
233*cda5da8dSAndroid Build Coastguard Worker    def getsampwidth(self):
234*cda5da8dSAndroid Build Coastguard Worker        return self._sampwidth
235*cda5da8dSAndroid Build Coastguard Worker
236*cda5da8dSAndroid Build Coastguard Worker    def getframerate(self):
237*cda5da8dSAndroid Build Coastguard Worker        return self._framerate
238*cda5da8dSAndroid Build Coastguard Worker
239*cda5da8dSAndroid Build Coastguard Worker    def getnframes(self):
240*cda5da8dSAndroid Build Coastguard Worker        if self._data_size == AUDIO_UNKNOWN_SIZE:
241*cda5da8dSAndroid Build Coastguard Worker            return AUDIO_UNKNOWN_SIZE
242*cda5da8dSAndroid Build Coastguard Worker        if self._encoding in _simple_encodings:
243*cda5da8dSAndroid Build Coastguard Worker            return self._data_size // self._framesize
244*cda5da8dSAndroid Build Coastguard Worker        return 0                # XXX--must do some arithmetic here
245*cda5da8dSAndroid Build Coastguard Worker
246*cda5da8dSAndroid Build Coastguard Worker    def getcomptype(self):
247*cda5da8dSAndroid Build Coastguard Worker        if self._encoding == AUDIO_FILE_ENCODING_MULAW_8:
248*cda5da8dSAndroid Build Coastguard Worker            return 'ULAW'
249*cda5da8dSAndroid Build Coastguard Worker        elif self._encoding == AUDIO_FILE_ENCODING_ALAW_8:
250*cda5da8dSAndroid Build Coastguard Worker            return 'ALAW'
251*cda5da8dSAndroid Build Coastguard Worker        else:
252*cda5da8dSAndroid Build Coastguard Worker            return 'NONE'
253*cda5da8dSAndroid Build Coastguard Worker
254*cda5da8dSAndroid Build Coastguard Worker    def getcompname(self):
255*cda5da8dSAndroid Build Coastguard Worker        if self._encoding == AUDIO_FILE_ENCODING_MULAW_8:
256*cda5da8dSAndroid Build Coastguard Worker            return 'CCITT G.711 u-law'
257*cda5da8dSAndroid Build Coastguard Worker        elif self._encoding == AUDIO_FILE_ENCODING_ALAW_8:
258*cda5da8dSAndroid Build Coastguard Worker            return 'CCITT G.711 A-law'
259*cda5da8dSAndroid Build Coastguard Worker        else:
260*cda5da8dSAndroid Build Coastguard Worker            return 'not compressed'
261*cda5da8dSAndroid Build Coastguard Worker
262*cda5da8dSAndroid Build Coastguard Worker    def getparams(self):
263*cda5da8dSAndroid Build Coastguard Worker        return _sunau_params(self.getnchannels(), self.getsampwidth(),
264*cda5da8dSAndroid Build Coastguard Worker                  self.getframerate(), self.getnframes(),
265*cda5da8dSAndroid Build Coastguard Worker                  self.getcomptype(), self.getcompname())
266*cda5da8dSAndroid Build Coastguard Worker
267*cda5da8dSAndroid Build Coastguard Worker    def getmarkers(self):
268*cda5da8dSAndroid Build Coastguard Worker        return None
269*cda5da8dSAndroid Build Coastguard Worker
270*cda5da8dSAndroid Build Coastguard Worker    def getmark(self, id):
271*cda5da8dSAndroid Build Coastguard Worker        raise Error('no marks')
272*cda5da8dSAndroid Build Coastguard Worker
273*cda5da8dSAndroid Build Coastguard Worker    def readframes(self, nframes):
274*cda5da8dSAndroid Build Coastguard Worker        if self._encoding in _simple_encodings:
275*cda5da8dSAndroid Build Coastguard Worker            if nframes == AUDIO_UNKNOWN_SIZE:
276*cda5da8dSAndroid Build Coastguard Worker                data = self._file.read()
277*cda5da8dSAndroid Build Coastguard Worker            else:
278*cda5da8dSAndroid Build Coastguard Worker                data = self._file.read(nframes * self._framesize)
279*cda5da8dSAndroid Build Coastguard Worker            self._soundpos += len(data) // self._framesize
280*cda5da8dSAndroid Build Coastguard Worker            if self._encoding == AUDIO_FILE_ENCODING_MULAW_8:
281*cda5da8dSAndroid Build Coastguard Worker                with warnings.catch_warnings():
282*cda5da8dSAndroid Build Coastguard Worker                    warnings.simplefilter('ignore', category=DeprecationWarning)
283*cda5da8dSAndroid Build Coastguard Worker                    import audioop
284*cda5da8dSAndroid Build Coastguard Worker                data = audioop.ulaw2lin(data, self._sampwidth)
285*cda5da8dSAndroid Build Coastguard Worker            return data
286*cda5da8dSAndroid Build Coastguard Worker        return None             # XXX--not implemented yet
287*cda5da8dSAndroid Build Coastguard Worker
288*cda5da8dSAndroid Build Coastguard Worker    def rewind(self):
289*cda5da8dSAndroid Build Coastguard Worker        if self._data_pos is None:
290*cda5da8dSAndroid Build Coastguard Worker            raise OSError('cannot seek')
291*cda5da8dSAndroid Build Coastguard Worker        self._file.seek(self._data_pos)
292*cda5da8dSAndroid Build Coastguard Worker        self._soundpos = 0
293*cda5da8dSAndroid Build Coastguard Worker
294*cda5da8dSAndroid Build Coastguard Worker    def tell(self):
295*cda5da8dSAndroid Build Coastguard Worker        return self._soundpos
296*cda5da8dSAndroid Build Coastguard Worker
297*cda5da8dSAndroid Build Coastguard Worker    def setpos(self, pos):
298*cda5da8dSAndroid Build Coastguard Worker        if pos < 0 or pos > self.getnframes():
299*cda5da8dSAndroid Build Coastguard Worker            raise Error('position not in range')
300*cda5da8dSAndroid Build Coastguard Worker        if self._data_pos is None:
301*cda5da8dSAndroid Build Coastguard Worker            raise OSError('cannot seek')
302*cda5da8dSAndroid Build Coastguard Worker        self._file.seek(self._data_pos + pos * self._framesize)
303*cda5da8dSAndroid Build Coastguard Worker        self._soundpos = pos
304*cda5da8dSAndroid Build Coastguard Worker
305*cda5da8dSAndroid Build Coastguard Worker    def close(self):
306*cda5da8dSAndroid Build Coastguard Worker        file = self._file
307*cda5da8dSAndroid Build Coastguard Worker        if file:
308*cda5da8dSAndroid Build Coastguard Worker            self._file = None
309*cda5da8dSAndroid Build Coastguard Worker            if self._opened:
310*cda5da8dSAndroid Build Coastguard Worker                file.close()
311*cda5da8dSAndroid Build Coastguard Worker
312*cda5da8dSAndroid Build Coastguard Workerclass Au_write:
313*cda5da8dSAndroid Build Coastguard Worker
314*cda5da8dSAndroid Build Coastguard Worker    def __init__(self, f):
315*cda5da8dSAndroid Build Coastguard Worker        if type(f) == type(''):
316*cda5da8dSAndroid Build Coastguard Worker            import builtins
317*cda5da8dSAndroid Build Coastguard Worker            f = builtins.open(f, 'wb')
318*cda5da8dSAndroid Build Coastguard Worker            self._opened = True
319*cda5da8dSAndroid Build Coastguard Worker        else:
320*cda5da8dSAndroid Build Coastguard Worker            self._opened = False
321*cda5da8dSAndroid Build Coastguard Worker        self.initfp(f)
322*cda5da8dSAndroid Build Coastguard Worker
323*cda5da8dSAndroid Build Coastguard Worker    def __del__(self):
324*cda5da8dSAndroid Build Coastguard Worker        if self._file:
325*cda5da8dSAndroid Build Coastguard Worker            self.close()
326*cda5da8dSAndroid Build Coastguard Worker        self._file = None
327*cda5da8dSAndroid Build Coastguard Worker
328*cda5da8dSAndroid Build Coastguard Worker    def __enter__(self):
329*cda5da8dSAndroid Build Coastguard Worker        return self
330*cda5da8dSAndroid Build Coastguard Worker
331*cda5da8dSAndroid Build Coastguard Worker    def __exit__(self, *args):
332*cda5da8dSAndroid Build Coastguard Worker        self.close()
333*cda5da8dSAndroid Build Coastguard Worker
334*cda5da8dSAndroid Build Coastguard Worker    def initfp(self, file):
335*cda5da8dSAndroid Build Coastguard Worker        self._file = file
336*cda5da8dSAndroid Build Coastguard Worker        self._framerate = 0
337*cda5da8dSAndroid Build Coastguard Worker        self._nchannels = 0
338*cda5da8dSAndroid Build Coastguard Worker        self._sampwidth = 0
339*cda5da8dSAndroid Build Coastguard Worker        self._framesize = 0
340*cda5da8dSAndroid Build Coastguard Worker        self._nframes = AUDIO_UNKNOWN_SIZE
341*cda5da8dSAndroid Build Coastguard Worker        self._nframeswritten = 0
342*cda5da8dSAndroid Build Coastguard Worker        self._datawritten = 0
343*cda5da8dSAndroid Build Coastguard Worker        self._datalength = 0
344*cda5da8dSAndroid Build Coastguard Worker        self._info = b''
345*cda5da8dSAndroid Build Coastguard Worker        self._comptype = 'ULAW' # default is U-law
346*cda5da8dSAndroid Build Coastguard Worker
347*cda5da8dSAndroid Build Coastguard Worker    def setnchannels(self, nchannels):
348*cda5da8dSAndroid Build Coastguard Worker        if self._nframeswritten:
349*cda5da8dSAndroid Build Coastguard Worker            raise Error('cannot change parameters after starting to write')
350*cda5da8dSAndroid Build Coastguard Worker        if nchannels not in (1, 2, 4):
351*cda5da8dSAndroid Build Coastguard Worker            raise Error('only 1, 2, or 4 channels supported')
352*cda5da8dSAndroid Build Coastguard Worker        self._nchannels = nchannels
353*cda5da8dSAndroid Build Coastguard Worker
354*cda5da8dSAndroid Build Coastguard Worker    def getnchannels(self):
355*cda5da8dSAndroid Build Coastguard Worker        if not self._nchannels:
356*cda5da8dSAndroid Build Coastguard Worker            raise Error('number of channels not set')
357*cda5da8dSAndroid Build Coastguard Worker        return self._nchannels
358*cda5da8dSAndroid Build Coastguard Worker
359*cda5da8dSAndroid Build Coastguard Worker    def setsampwidth(self, sampwidth):
360*cda5da8dSAndroid Build Coastguard Worker        if self._nframeswritten:
361*cda5da8dSAndroid Build Coastguard Worker            raise Error('cannot change parameters after starting to write')
362*cda5da8dSAndroid Build Coastguard Worker        if sampwidth not in (1, 2, 3, 4):
363*cda5da8dSAndroid Build Coastguard Worker            raise Error('bad sample width')
364*cda5da8dSAndroid Build Coastguard Worker        self._sampwidth = sampwidth
365*cda5da8dSAndroid Build Coastguard Worker
366*cda5da8dSAndroid Build Coastguard Worker    def getsampwidth(self):
367*cda5da8dSAndroid Build Coastguard Worker        if not self._framerate:
368*cda5da8dSAndroid Build Coastguard Worker            raise Error('sample width not specified')
369*cda5da8dSAndroid Build Coastguard Worker        return self._sampwidth
370*cda5da8dSAndroid Build Coastguard Worker
371*cda5da8dSAndroid Build Coastguard Worker    def setframerate(self, framerate):
372*cda5da8dSAndroid Build Coastguard Worker        if self._nframeswritten:
373*cda5da8dSAndroid Build Coastguard Worker            raise Error('cannot change parameters after starting to write')
374*cda5da8dSAndroid Build Coastguard Worker        self._framerate = framerate
375*cda5da8dSAndroid Build Coastguard Worker
376*cda5da8dSAndroid Build Coastguard Worker    def getframerate(self):
377*cda5da8dSAndroid Build Coastguard Worker        if not self._framerate:
378*cda5da8dSAndroid Build Coastguard Worker            raise Error('frame rate not set')
379*cda5da8dSAndroid Build Coastguard Worker        return self._framerate
380*cda5da8dSAndroid Build Coastguard Worker
381*cda5da8dSAndroid Build Coastguard Worker    def setnframes(self, nframes):
382*cda5da8dSAndroid Build Coastguard Worker        if self._nframeswritten:
383*cda5da8dSAndroid Build Coastguard Worker            raise Error('cannot change parameters after starting to write')
384*cda5da8dSAndroid Build Coastguard Worker        if nframes < 0:
385*cda5da8dSAndroid Build Coastguard Worker            raise Error('# of frames cannot be negative')
386*cda5da8dSAndroid Build Coastguard Worker        self._nframes = nframes
387*cda5da8dSAndroid Build Coastguard Worker
388*cda5da8dSAndroid Build Coastguard Worker    def getnframes(self):
389*cda5da8dSAndroid Build Coastguard Worker        return self._nframeswritten
390*cda5da8dSAndroid Build Coastguard Worker
391*cda5da8dSAndroid Build Coastguard Worker    def setcomptype(self, type, name):
392*cda5da8dSAndroid Build Coastguard Worker        if type in ('NONE', 'ULAW'):
393*cda5da8dSAndroid Build Coastguard Worker            self._comptype = type
394*cda5da8dSAndroid Build Coastguard Worker        else:
395*cda5da8dSAndroid Build Coastguard Worker            raise Error('unknown compression type')
396*cda5da8dSAndroid Build Coastguard Worker
397*cda5da8dSAndroid Build Coastguard Worker    def getcomptype(self):
398*cda5da8dSAndroid Build Coastguard Worker        return self._comptype
399*cda5da8dSAndroid Build Coastguard Worker
400*cda5da8dSAndroid Build Coastguard Worker    def getcompname(self):
401*cda5da8dSAndroid Build Coastguard Worker        if self._comptype == 'ULAW':
402*cda5da8dSAndroid Build Coastguard Worker            return 'CCITT G.711 u-law'
403*cda5da8dSAndroid Build Coastguard Worker        elif self._comptype == 'ALAW':
404*cda5da8dSAndroid Build Coastguard Worker            return 'CCITT G.711 A-law'
405*cda5da8dSAndroid Build Coastguard Worker        else:
406*cda5da8dSAndroid Build Coastguard Worker            return 'not compressed'
407*cda5da8dSAndroid Build Coastguard Worker
408*cda5da8dSAndroid Build Coastguard Worker    def setparams(self, params):
409*cda5da8dSAndroid Build Coastguard Worker        nchannels, sampwidth, framerate, nframes, comptype, compname = params
410*cda5da8dSAndroid Build Coastguard Worker        self.setnchannels(nchannels)
411*cda5da8dSAndroid Build Coastguard Worker        self.setsampwidth(sampwidth)
412*cda5da8dSAndroid Build Coastguard Worker        self.setframerate(framerate)
413*cda5da8dSAndroid Build Coastguard Worker        self.setnframes(nframes)
414*cda5da8dSAndroid Build Coastguard Worker        self.setcomptype(comptype, compname)
415*cda5da8dSAndroid Build Coastguard Worker
416*cda5da8dSAndroid Build Coastguard Worker    def getparams(self):
417*cda5da8dSAndroid Build Coastguard Worker        return _sunau_params(self.getnchannels(), self.getsampwidth(),
418*cda5da8dSAndroid Build Coastguard Worker                  self.getframerate(), self.getnframes(),
419*cda5da8dSAndroid Build Coastguard Worker                  self.getcomptype(), self.getcompname())
420*cda5da8dSAndroid Build Coastguard Worker
421*cda5da8dSAndroid Build Coastguard Worker    def tell(self):
422*cda5da8dSAndroid Build Coastguard Worker        return self._nframeswritten
423*cda5da8dSAndroid Build Coastguard Worker
424*cda5da8dSAndroid Build Coastguard Worker    def writeframesraw(self, data):
425*cda5da8dSAndroid Build Coastguard Worker        if not isinstance(data, (bytes, bytearray)):
426*cda5da8dSAndroid Build Coastguard Worker            data = memoryview(data).cast('B')
427*cda5da8dSAndroid Build Coastguard Worker        self._ensure_header_written()
428*cda5da8dSAndroid Build Coastguard Worker        if self._comptype == 'ULAW':
429*cda5da8dSAndroid Build Coastguard Worker            with warnings.catch_warnings():
430*cda5da8dSAndroid Build Coastguard Worker                warnings.simplefilter('ignore', category=DeprecationWarning)
431*cda5da8dSAndroid Build Coastguard Worker                import audioop
432*cda5da8dSAndroid Build Coastguard Worker            data = audioop.lin2ulaw(data, self._sampwidth)
433*cda5da8dSAndroid Build Coastguard Worker        nframes = len(data) // self._framesize
434*cda5da8dSAndroid Build Coastguard Worker        self._file.write(data)
435*cda5da8dSAndroid Build Coastguard Worker        self._nframeswritten = self._nframeswritten + nframes
436*cda5da8dSAndroid Build Coastguard Worker        self._datawritten = self._datawritten + len(data)
437*cda5da8dSAndroid Build Coastguard Worker
438*cda5da8dSAndroid Build Coastguard Worker    def writeframes(self, data):
439*cda5da8dSAndroid Build Coastguard Worker        self.writeframesraw(data)
440*cda5da8dSAndroid Build Coastguard Worker        if self._nframeswritten != self._nframes or \
441*cda5da8dSAndroid Build Coastguard Worker                  self._datalength != self._datawritten:
442*cda5da8dSAndroid Build Coastguard Worker            self._patchheader()
443*cda5da8dSAndroid Build Coastguard Worker
444*cda5da8dSAndroid Build Coastguard Worker    def close(self):
445*cda5da8dSAndroid Build Coastguard Worker        if self._file:
446*cda5da8dSAndroid Build Coastguard Worker            try:
447*cda5da8dSAndroid Build Coastguard Worker                self._ensure_header_written()
448*cda5da8dSAndroid Build Coastguard Worker                if self._nframeswritten != self._nframes or \
449*cda5da8dSAndroid Build Coastguard Worker                        self._datalength != self._datawritten:
450*cda5da8dSAndroid Build Coastguard Worker                    self._patchheader()
451*cda5da8dSAndroid Build Coastguard Worker                self._file.flush()
452*cda5da8dSAndroid Build Coastguard Worker            finally:
453*cda5da8dSAndroid Build Coastguard Worker                file = self._file
454*cda5da8dSAndroid Build Coastguard Worker                self._file = None
455*cda5da8dSAndroid Build Coastguard Worker                if self._opened:
456*cda5da8dSAndroid Build Coastguard Worker                    file.close()
457*cda5da8dSAndroid Build Coastguard Worker
458*cda5da8dSAndroid Build Coastguard Worker    #
459*cda5da8dSAndroid Build Coastguard Worker    # private methods
460*cda5da8dSAndroid Build Coastguard Worker    #
461*cda5da8dSAndroid Build Coastguard Worker
462*cda5da8dSAndroid Build Coastguard Worker    def _ensure_header_written(self):
463*cda5da8dSAndroid Build Coastguard Worker        if not self._nframeswritten:
464*cda5da8dSAndroid Build Coastguard Worker            if not self._nchannels:
465*cda5da8dSAndroid Build Coastguard Worker                raise Error('# of channels not specified')
466*cda5da8dSAndroid Build Coastguard Worker            if not self._sampwidth:
467*cda5da8dSAndroid Build Coastguard Worker                raise Error('sample width not specified')
468*cda5da8dSAndroid Build Coastguard Worker            if not self._framerate:
469*cda5da8dSAndroid Build Coastguard Worker                raise Error('frame rate not specified')
470*cda5da8dSAndroid Build Coastguard Worker            self._write_header()
471*cda5da8dSAndroid Build Coastguard Worker
472*cda5da8dSAndroid Build Coastguard Worker    def _write_header(self):
473*cda5da8dSAndroid Build Coastguard Worker        if self._comptype == 'NONE':
474*cda5da8dSAndroid Build Coastguard Worker            if self._sampwidth == 1:
475*cda5da8dSAndroid Build Coastguard Worker                encoding = AUDIO_FILE_ENCODING_LINEAR_8
476*cda5da8dSAndroid Build Coastguard Worker                self._framesize = 1
477*cda5da8dSAndroid Build Coastguard Worker            elif self._sampwidth == 2:
478*cda5da8dSAndroid Build Coastguard Worker                encoding = AUDIO_FILE_ENCODING_LINEAR_16
479*cda5da8dSAndroid Build Coastguard Worker                self._framesize = 2
480*cda5da8dSAndroid Build Coastguard Worker            elif self._sampwidth == 3:
481*cda5da8dSAndroid Build Coastguard Worker                encoding = AUDIO_FILE_ENCODING_LINEAR_24
482*cda5da8dSAndroid Build Coastguard Worker                self._framesize = 3
483*cda5da8dSAndroid Build Coastguard Worker            elif self._sampwidth == 4:
484*cda5da8dSAndroid Build Coastguard Worker                encoding = AUDIO_FILE_ENCODING_LINEAR_32
485*cda5da8dSAndroid Build Coastguard Worker                self._framesize = 4
486*cda5da8dSAndroid Build Coastguard Worker            else:
487*cda5da8dSAndroid Build Coastguard Worker                raise Error('internal error')
488*cda5da8dSAndroid Build Coastguard Worker        elif self._comptype == 'ULAW':
489*cda5da8dSAndroid Build Coastguard Worker            encoding = AUDIO_FILE_ENCODING_MULAW_8
490*cda5da8dSAndroid Build Coastguard Worker            self._framesize = 1
491*cda5da8dSAndroid Build Coastguard Worker        else:
492*cda5da8dSAndroid Build Coastguard Worker            raise Error('internal error')
493*cda5da8dSAndroid Build Coastguard Worker        self._framesize = self._framesize * self._nchannels
494*cda5da8dSAndroid Build Coastguard Worker        _write_u32(self._file, AUDIO_FILE_MAGIC)
495*cda5da8dSAndroid Build Coastguard Worker        header_size = 25 + len(self._info)
496*cda5da8dSAndroid Build Coastguard Worker        header_size = (header_size + 7) & ~7
497*cda5da8dSAndroid Build Coastguard Worker        _write_u32(self._file, header_size)
498*cda5da8dSAndroid Build Coastguard Worker        if self._nframes == AUDIO_UNKNOWN_SIZE:
499*cda5da8dSAndroid Build Coastguard Worker            length = AUDIO_UNKNOWN_SIZE
500*cda5da8dSAndroid Build Coastguard Worker        else:
501*cda5da8dSAndroid Build Coastguard Worker            length = self._nframes * self._framesize
502*cda5da8dSAndroid Build Coastguard Worker        try:
503*cda5da8dSAndroid Build Coastguard Worker            self._form_length_pos = self._file.tell()
504*cda5da8dSAndroid Build Coastguard Worker        except (AttributeError, OSError):
505*cda5da8dSAndroid Build Coastguard Worker            self._form_length_pos = None
506*cda5da8dSAndroid Build Coastguard Worker        _write_u32(self._file, length)
507*cda5da8dSAndroid Build Coastguard Worker        self._datalength = length
508*cda5da8dSAndroid Build Coastguard Worker        _write_u32(self._file, encoding)
509*cda5da8dSAndroid Build Coastguard Worker        _write_u32(self._file, self._framerate)
510*cda5da8dSAndroid Build Coastguard Worker        _write_u32(self._file, self._nchannels)
511*cda5da8dSAndroid Build Coastguard Worker        self._file.write(self._info)
512*cda5da8dSAndroid Build Coastguard Worker        self._file.write(b'\0'*(header_size - len(self._info) - 24))
513*cda5da8dSAndroid Build Coastguard Worker
514*cda5da8dSAndroid Build Coastguard Worker    def _patchheader(self):
515*cda5da8dSAndroid Build Coastguard Worker        if self._form_length_pos is None:
516*cda5da8dSAndroid Build Coastguard Worker            raise OSError('cannot seek')
517*cda5da8dSAndroid Build Coastguard Worker        self._file.seek(self._form_length_pos)
518*cda5da8dSAndroid Build Coastguard Worker        _write_u32(self._file, self._datawritten)
519*cda5da8dSAndroid Build Coastguard Worker        self._datalength = self._datawritten
520*cda5da8dSAndroid Build Coastguard Worker        self._file.seek(0, 2)
521*cda5da8dSAndroid Build Coastguard Worker
522*cda5da8dSAndroid Build Coastguard Workerdef open(f, mode=None):
523*cda5da8dSAndroid Build Coastguard Worker    if mode is None:
524*cda5da8dSAndroid Build Coastguard Worker        if hasattr(f, 'mode'):
525*cda5da8dSAndroid Build Coastguard Worker            mode = f.mode
526*cda5da8dSAndroid Build Coastguard Worker        else:
527*cda5da8dSAndroid Build Coastguard Worker            mode = 'rb'
528*cda5da8dSAndroid Build Coastguard Worker    if mode in ('r', 'rb'):
529*cda5da8dSAndroid Build Coastguard Worker        return Au_read(f)
530*cda5da8dSAndroid Build Coastguard Worker    elif mode in ('w', 'wb'):
531*cda5da8dSAndroid Build Coastguard Worker        return Au_write(f)
532*cda5da8dSAndroid Build Coastguard Worker    else:
533*cda5da8dSAndroid Build Coastguard Worker        raise Error("mode must be 'r', 'rb', 'w', or 'wb'")
534