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