xref: /aosp_15_r20/prebuilts/build-tools/common/py3-stdlib/sndhdr.py (revision cda5da8d549138a6648c5ee6d7a49cf8f4a657be)
1*cda5da8dSAndroid Build Coastguard Worker"""Routines to help recognizing sound files.
2*cda5da8dSAndroid Build Coastguard Worker
3*cda5da8dSAndroid Build Coastguard WorkerFunction whathdr() recognizes various types of sound file headers.
4*cda5da8dSAndroid Build Coastguard WorkerIt understands almost all headers that SOX can decode.
5*cda5da8dSAndroid Build Coastguard Worker
6*cda5da8dSAndroid Build Coastguard WorkerThe return tuple contains the following items, in this order:
7*cda5da8dSAndroid Build Coastguard Worker- file type (as SOX understands it)
8*cda5da8dSAndroid Build Coastguard Worker- sampling rate (0 if unknown or hard to decode)
9*cda5da8dSAndroid Build Coastguard Worker- number of channels (0 if unknown or hard to decode)
10*cda5da8dSAndroid Build Coastguard Worker- number of frames in the file (-1 if unknown or hard to decode)
11*cda5da8dSAndroid Build Coastguard Worker- number of bits/sample, or 'U' for U-LAW, or 'A' for A-LAW
12*cda5da8dSAndroid Build Coastguard Worker
13*cda5da8dSAndroid Build Coastguard WorkerIf the file doesn't have a recognizable type, it returns None.
14*cda5da8dSAndroid Build Coastguard WorkerIf the file can't be opened, OSError is raised.
15*cda5da8dSAndroid Build Coastguard Worker
16*cda5da8dSAndroid Build Coastguard WorkerTo compute the total time, divide the number of frames by the
17*cda5da8dSAndroid Build Coastguard Workersampling rate (a frame contains a sample for each channel).
18*cda5da8dSAndroid Build Coastguard Worker
19*cda5da8dSAndroid Build Coastguard WorkerFunction what() calls whathdr().  (It used to also use some
20*cda5da8dSAndroid Build Coastguard Workerheuristics for raw data, but this doesn't work very well.)
21*cda5da8dSAndroid Build Coastguard Worker
22*cda5da8dSAndroid Build Coastguard WorkerFinally, the function test() is a simple main program that calls
23*cda5da8dSAndroid Build Coastguard Workerwhat() for all files mentioned on the argument list.  For directory
24*cda5da8dSAndroid Build Coastguard Workerarguments it calls what() for all files in that directory.  Default
25*cda5da8dSAndroid Build Coastguard Workerargument is "." (testing all files in the current directory).  The
26*cda5da8dSAndroid Build Coastguard Workeroption -r tells it to recurse down directories found inside
27*cda5da8dSAndroid Build Coastguard Workerexplicitly given directories.
28*cda5da8dSAndroid Build Coastguard Worker"""
29*cda5da8dSAndroid Build Coastguard Worker
30*cda5da8dSAndroid Build Coastguard Workerimport warnings
31*cda5da8dSAndroid Build Coastguard Worker
32*cda5da8dSAndroid Build Coastguard Workerwarnings._deprecated(__name__, remove=(3, 13))
33*cda5da8dSAndroid Build Coastguard Worker
34*cda5da8dSAndroid Build Coastguard Worker# The file structure is top-down except that the test program and its
35*cda5da8dSAndroid Build Coastguard Worker# subroutine come last.
36*cda5da8dSAndroid Build Coastguard Worker
37*cda5da8dSAndroid Build Coastguard Worker__all__ = ['what', 'whathdr']
38*cda5da8dSAndroid Build Coastguard Worker
39*cda5da8dSAndroid Build Coastguard Workerfrom collections import namedtuple
40*cda5da8dSAndroid Build Coastguard Worker
41*cda5da8dSAndroid Build Coastguard WorkerSndHeaders = namedtuple('SndHeaders',
42*cda5da8dSAndroid Build Coastguard Worker                        'filetype framerate nchannels nframes sampwidth')
43*cda5da8dSAndroid Build Coastguard Worker
44*cda5da8dSAndroid Build Coastguard WorkerSndHeaders.filetype.__doc__ = ("""The value for type indicates the data type
45*cda5da8dSAndroid Build Coastguard Workerand will be one of the strings 'aifc', 'aiff', 'au','hcom',
46*cda5da8dSAndroid Build Coastguard Worker'sndr', 'sndt', 'voc', 'wav', '8svx', 'sb', 'ub', or 'ul'.""")
47*cda5da8dSAndroid Build Coastguard WorkerSndHeaders.framerate.__doc__ = ("""The sampling_rate will be either the actual
48*cda5da8dSAndroid Build Coastguard Workervalue or 0 if unknown or difficult to decode.""")
49*cda5da8dSAndroid Build Coastguard WorkerSndHeaders.nchannels.__doc__ = ("""The number of channels or 0 if it cannot be
50*cda5da8dSAndroid Build Coastguard Workerdetermined or if the value is difficult to decode.""")
51*cda5da8dSAndroid Build Coastguard WorkerSndHeaders.nframes.__doc__ = ("""The value for frames will be either the number
52*cda5da8dSAndroid Build Coastguard Workerof frames or -1.""")
53*cda5da8dSAndroid Build Coastguard WorkerSndHeaders.sampwidth.__doc__ = ("""Either the sample size in bits or
54*cda5da8dSAndroid Build Coastguard Worker'A' for A-LAW or 'U' for u-LAW.""")
55*cda5da8dSAndroid Build Coastguard Worker
56*cda5da8dSAndroid Build Coastguard Workerdef what(filename):
57*cda5da8dSAndroid Build Coastguard Worker    """Guess the type of a sound file."""
58*cda5da8dSAndroid Build Coastguard Worker    res = whathdr(filename)
59*cda5da8dSAndroid Build Coastguard Worker    return res
60*cda5da8dSAndroid Build Coastguard Worker
61*cda5da8dSAndroid Build Coastguard Worker
62*cda5da8dSAndroid Build Coastguard Workerdef whathdr(filename):
63*cda5da8dSAndroid Build Coastguard Worker    """Recognize sound headers."""
64*cda5da8dSAndroid Build Coastguard Worker    with open(filename, 'rb') as f:
65*cda5da8dSAndroid Build Coastguard Worker        h = f.read(512)
66*cda5da8dSAndroid Build Coastguard Worker        for tf in tests:
67*cda5da8dSAndroid Build Coastguard Worker            res = tf(h, f)
68*cda5da8dSAndroid Build Coastguard Worker            if res:
69*cda5da8dSAndroid Build Coastguard Worker                return SndHeaders(*res)
70*cda5da8dSAndroid Build Coastguard Worker        return None
71*cda5da8dSAndroid Build Coastguard Worker
72*cda5da8dSAndroid Build Coastguard Worker
73*cda5da8dSAndroid Build Coastguard Worker#-----------------------------------#
74*cda5da8dSAndroid Build Coastguard Worker# Subroutines per sound header type #
75*cda5da8dSAndroid Build Coastguard Worker#-----------------------------------#
76*cda5da8dSAndroid Build Coastguard Worker
77*cda5da8dSAndroid Build Coastguard Workertests = []
78*cda5da8dSAndroid Build Coastguard Worker
79*cda5da8dSAndroid Build Coastguard Workerdef test_aifc(h, f):
80*cda5da8dSAndroid Build Coastguard Worker    """AIFC and AIFF files"""
81*cda5da8dSAndroid Build Coastguard Worker    with warnings.catch_warnings():
82*cda5da8dSAndroid Build Coastguard Worker        warnings.simplefilter('ignore', category=DeprecationWarning)
83*cda5da8dSAndroid Build Coastguard Worker        import aifc
84*cda5da8dSAndroid Build Coastguard Worker    if not h.startswith(b'FORM'):
85*cda5da8dSAndroid Build Coastguard Worker        return None
86*cda5da8dSAndroid Build Coastguard Worker    if h[8:12] == b'AIFC':
87*cda5da8dSAndroid Build Coastguard Worker        fmt = 'aifc'
88*cda5da8dSAndroid Build Coastguard Worker    elif h[8:12] == b'AIFF':
89*cda5da8dSAndroid Build Coastguard Worker        fmt = 'aiff'
90*cda5da8dSAndroid Build Coastguard Worker    else:
91*cda5da8dSAndroid Build Coastguard Worker        return None
92*cda5da8dSAndroid Build Coastguard Worker    f.seek(0)
93*cda5da8dSAndroid Build Coastguard Worker    try:
94*cda5da8dSAndroid Build Coastguard Worker        a = aifc.open(f, 'r')
95*cda5da8dSAndroid Build Coastguard Worker    except (EOFError, aifc.Error):
96*cda5da8dSAndroid Build Coastguard Worker        return None
97*cda5da8dSAndroid Build Coastguard Worker    return (fmt, a.getframerate(), a.getnchannels(),
98*cda5da8dSAndroid Build Coastguard Worker            a.getnframes(), 8 * a.getsampwidth())
99*cda5da8dSAndroid Build Coastguard Worker
100*cda5da8dSAndroid Build Coastguard Workertests.append(test_aifc)
101*cda5da8dSAndroid Build Coastguard Worker
102*cda5da8dSAndroid Build Coastguard Worker
103*cda5da8dSAndroid Build Coastguard Workerdef test_au(h, f):
104*cda5da8dSAndroid Build Coastguard Worker    """AU and SND files"""
105*cda5da8dSAndroid Build Coastguard Worker    if h.startswith(b'.snd'):
106*cda5da8dSAndroid Build Coastguard Worker        func = get_long_be
107*cda5da8dSAndroid Build Coastguard Worker    elif h[:4] in (b'\0ds.', b'dns.'):
108*cda5da8dSAndroid Build Coastguard Worker        func = get_long_le
109*cda5da8dSAndroid Build Coastguard Worker    else:
110*cda5da8dSAndroid Build Coastguard Worker        return None
111*cda5da8dSAndroid Build Coastguard Worker    filetype = 'au'
112*cda5da8dSAndroid Build Coastguard Worker    hdr_size = func(h[4:8])
113*cda5da8dSAndroid Build Coastguard Worker    data_size = func(h[8:12])
114*cda5da8dSAndroid Build Coastguard Worker    encoding = func(h[12:16])
115*cda5da8dSAndroid Build Coastguard Worker    rate = func(h[16:20])
116*cda5da8dSAndroid Build Coastguard Worker    nchannels = func(h[20:24])
117*cda5da8dSAndroid Build Coastguard Worker    sample_size = 1 # default
118*cda5da8dSAndroid Build Coastguard Worker    if encoding == 1:
119*cda5da8dSAndroid Build Coastguard Worker        sample_bits = 'U'
120*cda5da8dSAndroid Build Coastguard Worker    elif encoding == 2:
121*cda5da8dSAndroid Build Coastguard Worker        sample_bits = 8
122*cda5da8dSAndroid Build Coastguard Worker    elif encoding == 3:
123*cda5da8dSAndroid Build Coastguard Worker        sample_bits = 16
124*cda5da8dSAndroid Build Coastguard Worker        sample_size = 2
125*cda5da8dSAndroid Build Coastguard Worker    else:
126*cda5da8dSAndroid Build Coastguard Worker        sample_bits = '?'
127*cda5da8dSAndroid Build Coastguard Worker    frame_size = sample_size * nchannels
128*cda5da8dSAndroid Build Coastguard Worker    if frame_size:
129*cda5da8dSAndroid Build Coastguard Worker        nframe = data_size / frame_size
130*cda5da8dSAndroid Build Coastguard Worker    else:
131*cda5da8dSAndroid Build Coastguard Worker        nframe = -1
132*cda5da8dSAndroid Build Coastguard Worker    return filetype, rate, nchannels, nframe, sample_bits
133*cda5da8dSAndroid Build Coastguard Worker
134*cda5da8dSAndroid Build Coastguard Workertests.append(test_au)
135*cda5da8dSAndroid Build Coastguard Worker
136*cda5da8dSAndroid Build Coastguard Worker
137*cda5da8dSAndroid Build Coastguard Workerdef test_hcom(h, f):
138*cda5da8dSAndroid Build Coastguard Worker    """HCOM file"""
139*cda5da8dSAndroid Build Coastguard Worker    if h[65:69] != b'FSSD' or h[128:132] != b'HCOM':
140*cda5da8dSAndroid Build Coastguard Worker        return None
141*cda5da8dSAndroid Build Coastguard Worker    divisor = get_long_be(h[144:148])
142*cda5da8dSAndroid Build Coastguard Worker    if divisor:
143*cda5da8dSAndroid Build Coastguard Worker        rate = 22050 / divisor
144*cda5da8dSAndroid Build Coastguard Worker    else:
145*cda5da8dSAndroid Build Coastguard Worker        rate = 0
146*cda5da8dSAndroid Build Coastguard Worker    return 'hcom', rate, 1, -1, 8
147*cda5da8dSAndroid Build Coastguard Worker
148*cda5da8dSAndroid Build Coastguard Workertests.append(test_hcom)
149*cda5da8dSAndroid Build Coastguard Worker
150*cda5da8dSAndroid Build Coastguard Worker
151*cda5da8dSAndroid Build Coastguard Workerdef test_voc(h, f):
152*cda5da8dSAndroid Build Coastguard Worker    """VOC file"""
153*cda5da8dSAndroid Build Coastguard Worker    if not h.startswith(b'Creative Voice File\032'):
154*cda5da8dSAndroid Build Coastguard Worker        return None
155*cda5da8dSAndroid Build Coastguard Worker    sbseek = get_short_le(h[20:22])
156*cda5da8dSAndroid Build Coastguard Worker    rate = 0
157*cda5da8dSAndroid Build Coastguard Worker    if 0 <= sbseek < 500 and h[sbseek] == 1:
158*cda5da8dSAndroid Build Coastguard Worker        ratecode = 256 - h[sbseek+4]
159*cda5da8dSAndroid Build Coastguard Worker        if ratecode:
160*cda5da8dSAndroid Build Coastguard Worker            rate = int(1000000.0 / ratecode)
161*cda5da8dSAndroid Build Coastguard Worker    return 'voc', rate, 1, -1, 8
162*cda5da8dSAndroid Build Coastguard Worker
163*cda5da8dSAndroid Build Coastguard Workertests.append(test_voc)
164*cda5da8dSAndroid Build Coastguard Worker
165*cda5da8dSAndroid Build Coastguard Worker
166*cda5da8dSAndroid Build Coastguard Workerdef test_wav(h, f):
167*cda5da8dSAndroid Build Coastguard Worker    """WAV file"""
168*cda5da8dSAndroid Build Coastguard Worker    import wave
169*cda5da8dSAndroid Build Coastguard Worker    # 'RIFF' <len> 'WAVE' 'fmt ' <len>
170*cda5da8dSAndroid Build Coastguard Worker    if not h.startswith(b'RIFF') or h[8:12] != b'WAVE' or h[12:16] != b'fmt ':
171*cda5da8dSAndroid Build Coastguard Worker        return None
172*cda5da8dSAndroid Build Coastguard Worker    f.seek(0)
173*cda5da8dSAndroid Build Coastguard Worker    try:
174*cda5da8dSAndroid Build Coastguard Worker        w = wave.open(f, 'r')
175*cda5da8dSAndroid Build Coastguard Worker    except (EOFError, wave.Error):
176*cda5da8dSAndroid Build Coastguard Worker        return None
177*cda5da8dSAndroid Build Coastguard Worker    return ('wav', w.getframerate(), w.getnchannels(),
178*cda5da8dSAndroid Build Coastguard Worker                   w.getnframes(), 8*w.getsampwidth())
179*cda5da8dSAndroid Build Coastguard Worker
180*cda5da8dSAndroid Build Coastguard Workertests.append(test_wav)
181*cda5da8dSAndroid Build Coastguard Worker
182*cda5da8dSAndroid Build Coastguard Worker
183*cda5da8dSAndroid Build Coastguard Workerdef test_8svx(h, f):
184*cda5da8dSAndroid Build Coastguard Worker    """8SVX file"""
185*cda5da8dSAndroid Build Coastguard Worker    if not h.startswith(b'FORM') or h[8:12] != b'8SVX':
186*cda5da8dSAndroid Build Coastguard Worker        return None
187*cda5da8dSAndroid Build Coastguard Worker    # Should decode it to get #channels -- assume always 1
188*cda5da8dSAndroid Build Coastguard Worker    return '8svx', 0, 1, 0, 8
189*cda5da8dSAndroid Build Coastguard Worker
190*cda5da8dSAndroid Build Coastguard Workertests.append(test_8svx)
191*cda5da8dSAndroid Build Coastguard Worker
192*cda5da8dSAndroid Build Coastguard Worker
193*cda5da8dSAndroid Build Coastguard Workerdef test_sndt(h, f):
194*cda5da8dSAndroid Build Coastguard Worker    """SNDT file"""
195*cda5da8dSAndroid Build Coastguard Worker    if h.startswith(b'SOUND'):
196*cda5da8dSAndroid Build Coastguard Worker        nsamples = get_long_le(h[8:12])
197*cda5da8dSAndroid Build Coastguard Worker        rate = get_short_le(h[20:22])
198*cda5da8dSAndroid Build Coastguard Worker        return 'sndt', rate, 1, nsamples, 8
199*cda5da8dSAndroid Build Coastguard Worker
200*cda5da8dSAndroid Build Coastguard Workertests.append(test_sndt)
201*cda5da8dSAndroid Build Coastguard Worker
202*cda5da8dSAndroid Build Coastguard Worker
203*cda5da8dSAndroid Build Coastguard Workerdef test_sndr(h, f):
204*cda5da8dSAndroid Build Coastguard Worker    """SNDR file"""
205*cda5da8dSAndroid Build Coastguard Worker    if h.startswith(b'\0\0'):
206*cda5da8dSAndroid Build Coastguard Worker        rate = get_short_le(h[2:4])
207*cda5da8dSAndroid Build Coastguard Worker        if 4000 <= rate <= 25000:
208*cda5da8dSAndroid Build Coastguard Worker            return 'sndr', rate, 1, -1, 8
209*cda5da8dSAndroid Build Coastguard Worker
210*cda5da8dSAndroid Build Coastguard Workertests.append(test_sndr)
211*cda5da8dSAndroid Build Coastguard Worker
212*cda5da8dSAndroid Build Coastguard Worker
213*cda5da8dSAndroid Build Coastguard Worker#-------------------------------------------#
214*cda5da8dSAndroid Build Coastguard Worker# Subroutines to extract numbers from bytes #
215*cda5da8dSAndroid Build Coastguard Worker#-------------------------------------------#
216*cda5da8dSAndroid Build Coastguard Worker
217*cda5da8dSAndroid Build Coastguard Workerdef get_long_be(b):
218*cda5da8dSAndroid Build Coastguard Worker    return (b[0] << 24) | (b[1] << 16) | (b[2] << 8) | b[3]
219*cda5da8dSAndroid Build Coastguard Worker
220*cda5da8dSAndroid Build Coastguard Workerdef get_long_le(b):
221*cda5da8dSAndroid Build Coastguard Worker    return (b[3] << 24) | (b[2] << 16) | (b[1] << 8) | b[0]
222*cda5da8dSAndroid Build Coastguard Worker
223*cda5da8dSAndroid Build Coastguard Workerdef get_short_be(b):
224*cda5da8dSAndroid Build Coastguard Worker    return (b[0] << 8) | b[1]
225*cda5da8dSAndroid Build Coastguard Worker
226*cda5da8dSAndroid Build Coastguard Workerdef get_short_le(b):
227*cda5da8dSAndroid Build Coastguard Worker    return (b[1] << 8) | b[0]
228*cda5da8dSAndroid Build Coastguard Worker
229*cda5da8dSAndroid Build Coastguard Worker
230*cda5da8dSAndroid Build Coastguard Worker#--------------------#
231*cda5da8dSAndroid Build Coastguard Worker# Small test program #
232*cda5da8dSAndroid Build Coastguard Worker#--------------------#
233*cda5da8dSAndroid Build Coastguard Worker
234*cda5da8dSAndroid Build Coastguard Workerdef test():
235*cda5da8dSAndroid Build Coastguard Worker    import sys
236*cda5da8dSAndroid Build Coastguard Worker    recursive = 0
237*cda5da8dSAndroid Build Coastguard Worker    if sys.argv[1:] and sys.argv[1] == '-r':
238*cda5da8dSAndroid Build Coastguard Worker        del sys.argv[1:2]
239*cda5da8dSAndroid Build Coastguard Worker        recursive = 1
240*cda5da8dSAndroid Build Coastguard Worker    try:
241*cda5da8dSAndroid Build Coastguard Worker        if sys.argv[1:]:
242*cda5da8dSAndroid Build Coastguard Worker            testall(sys.argv[1:], recursive, 1)
243*cda5da8dSAndroid Build Coastguard Worker        else:
244*cda5da8dSAndroid Build Coastguard Worker            testall(['.'], recursive, 1)
245*cda5da8dSAndroid Build Coastguard Worker    except KeyboardInterrupt:
246*cda5da8dSAndroid Build Coastguard Worker        sys.stderr.write('\n[Interrupted]\n')
247*cda5da8dSAndroid Build Coastguard Worker        sys.exit(1)
248*cda5da8dSAndroid Build Coastguard Worker
249*cda5da8dSAndroid Build Coastguard Workerdef testall(list, recursive, toplevel):
250*cda5da8dSAndroid Build Coastguard Worker    import sys
251*cda5da8dSAndroid Build Coastguard Worker    import os
252*cda5da8dSAndroid Build Coastguard Worker    for filename in list:
253*cda5da8dSAndroid Build Coastguard Worker        if os.path.isdir(filename):
254*cda5da8dSAndroid Build Coastguard Worker            print(filename + '/:', end=' ')
255*cda5da8dSAndroid Build Coastguard Worker            if recursive or toplevel:
256*cda5da8dSAndroid Build Coastguard Worker                print('recursing down:')
257*cda5da8dSAndroid Build Coastguard Worker                import glob
258*cda5da8dSAndroid Build Coastguard Worker                names = glob.glob(os.path.join(glob.escape(filename), '*'))
259*cda5da8dSAndroid Build Coastguard Worker                testall(names, recursive, 0)
260*cda5da8dSAndroid Build Coastguard Worker            else:
261*cda5da8dSAndroid Build Coastguard Worker                print('*** directory (use -r) ***')
262*cda5da8dSAndroid Build Coastguard Worker        else:
263*cda5da8dSAndroid Build Coastguard Worker            print(filename + ':', end=' ')
264*cda5da8dSAndroid Build Coastguard Worker            sys.stdout.flush()
265*cda5da8dSAndroid Build Coastguard Worker            try:
266*cda5da8dSAndroid Build Coastguard Worker                print(what(filename))
267*cda5da8dSAndroid Build Coastguard Worker            except OSError:
268*cda5da8dSAndroid Build Coastguard Worker                print('*** not found ***')
269*cda5da8dSAndroid Build Coastguard Worker
270*cda5da8dSAndroid Build Coastguard Workerif __name__ == '__main__':
271*cda5da8dSAndroid Build Coastguard Worker    test()
272