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