1#!/usr/bin/env python 2# -*- coding: UTF-8 -*- 3# 4osLibFound = False 5sysLibFound = False 6shutilLibFound = False 7waveLibFound = False 8ctypesLibFound = False 9structLibFound = False 10 11try: 12 import os 13except ImportError: 14 print "[Error] os python library is required to be installed!" 15else: 16 osLibFound = True 17 18try: 19 import sys 20except ImportError: 21 print "[Error] sys python library is required to be installed!" 22else: 23 sysLibFound = True 24 25try: 26 import wave 27except ImportError: 28 print "[Error] Wave python library is required to be installed!" 29else: 30 waveLibFound = True 31 32try: 33 import struct 34except ImportError: 35 print "[Error] struct python library is required to be installed!" 36else: 37 structLibFound = True 38 39if (not osLibFound) \ 40 or (not sysLibFound) \ 41 or (not waveLibFound) \ 42 or (not structLibFound): 43 sys.stdout.write("[Error] Errors were found when trying to import required python libraries\n") 44 sys.exit(1) 45 46from struct import * 47from audFileDecode import * 48 49MY_MODULE_VERSION = "0.80" 50MY_MODULE_NAME = "audFileLib" 51 52#constants 53aud_chunk_id = 0x0000deaf 54SIZE_OF_AUD_HEADER_IN_BYTES = 12 55SIZE_OF_AUD_CHUNK_HEADER_IN_BYTES = 8 56 57class AudHeader(object): 58 m_samplerate = 0 # Frequency // int16_t // TODO should be unsigned (?) 59 m_size_in = 0 # Size of file (without header) // int32_t // TODO should be unsigned (?) 60 m_size_out = 0 # Size of output data // int32_t // TODO should be unsigned (?) 61 m_flags = 0 # bit 0=stereo, bit 1=16bit // int8_t 62 m_compression = 0 # 1=WW compressed, 99=IMA ADPCM (0x63) // int8_t 63 m_populated = False 64 65 def __init__(self): 66 return 67 68 69# The rest of the AUD files is divided in chunks. 70# These are usually 512 bytes long, except for the last one. 71class AudChunkHeader(object): 72 m_ch_size_in = 0 # Size of compressed data // int16_t // TODO should be unsigned (?) 73 m_ch_size_out = 0 # Size of output data // int16_t // TODO should be unsigned (?) 74 m_ch_id = 0x0000FFFF # Always 0x0000DEAF // int32_t 75 76 def __init__(self): 77 return 78 79# 80# 81# 82class audFile(object): 83 m_header = AudHeader() 84 m_traceModeEnabled = False 85 m_simpleAudioFileName = 'GENERIC.AUD' 86 87 # traceModeEnabled is bool to enable more printed debug messages 88 def __init__(self, traceModeEnabled = True): 89 self.m_simpleAudioFileName = 'GENERIC.AUD' 90 self.m_traceModeEnabled = traceModeEnabled 91 return 92 93 # std::fstream& fs, AudFileNS::pos_type startAudFilepos, AudFileNS::pos_type endAudFilepos, const std::string& filename 94 def export_as_wav(self, audBytesBuff, filename): 95 if (not self.header().m_populated): 96 print "[Error] file was not loaded properly (header info missing): " + filename 97 return 1 98 99 print "[Info] Exporting to wav: " + filename 100 101 cvirtualBinaryD = None 102 if self.header().m_compression > 0: 103 cvirtualBinaryD = self.decode(self.header().m_compression, audBytesBuff) 104 elif self.header().m_flags == 2: # compression 0, 16bit stereo 105 cbinaryDataOutLst = [] 106 offsInAudFile = SIZE_OF_AUD_HEADER_IN_BYTES 107 for i in range(0, (len(audBytesBuff) - SIZE_OF_AUD_HEADER_IN_BYTES) / 2): 108 if (self.m_traceModeEnabled): 109 print "[Trace] Reading bytes %d, %d" % (2*i, 2*i + 1) 110 tmpTupleL = struct.unpack_from('B', audBytesBuff, offsInAudFile) 111 offsInAudFile += 1 112 tmpTupleH = struct.unpack_from('B', audBytesBuff, offsInAudFile) 113 offsInAudFile += 1 114 cbinaryDataOutLst.append(tmpTupleL[0]) 115 cbinaryDataOutLst.append(tmpTupleH[0]) 116 cvirtualBinaryD = struct.pack('B'*len(cbinaryDataOutLst), *cbinaryDataOutLst) 117 118 if (not cvirtualBinaryD and (len(audBytesBuff) - SIZE_OF_AUD_HEADER_IN_BYTES) > 0): 119 print "[Error] audio file could not be exported properly (0 data read): %s" % (filename) 120 return 1 121 elif (len(audBytesBuff) - SIZE_OF_AUD_HEADER_IN_BYTES) == 0: 122 print "[Warning] Creating empty wav file: %s" % (filename) 123 124 cb_sample = self.get_cb_sample() 125 cs_remaining = self.get_c_samples() 126 127 waveWritFile = wave.open(filename, 'wb') 128 waveWritFile.setnchannels(self.get_c_channels()) 129 waveWritFile.setsampwidth(cb_sample) 130 waveWritFile.setframerate(self.get_samplerate()) 131 waveWritFile.setnframes(cs_remaining) 132 #waveWritFile.setcomptype(None, '') 133 waveWritFile.writeframesraw(cvirtualBinaryD) 134 waveWritFile.close() 135 136# t_wav_header header; 137# memset(&header, 0, sizeof(t_wav_header)); 138# header.file_header.id = wav_file_id; // # "RIFF" 139# header.file_header.size = sizeof(header) - sizeof(header.file_header) + (cs_remaining << 1); 140# header.form_type = wav_form_id; // # "WAVE" 141# header.format_chunk.header.id = wav_format_id; // #"fmt " 142# header.format_chunk.header.size = sizeof(header.format_chunk) - sizeof(header.format_chunk.header); 143# header.format_chunk.formattag = 1; 144# header.format_chunk.c_channels = 1; 145# header.format_chunk.samplerate = get_samplerate(); 146# header.format_chunk.byterate = cb_sample * get_samplerate(); 147# header.format_chunk.blockalign = cb_sample; 148# header.format_chunk.cbits_sample = cb_sample << 3; 149# header.data_chunk_header.id = wav_data_id; # "data" 150# header.data_chunk_header.size = cb_sample * cs_remaining; 151# error = f.write(&header, sizeof(t_wav_header)); 152# return error ? error : f.write(d); 153 return 0 # TODO fix 154 155 156 def loadAudFile(self, audBytesBuff, maxLength, audFileName): 157 self.m_simpleAudioFileName = audFileName 158 offsInAudFile = 0 159 tmpTuple = struct.unpack_from('H', audBytesBuff, offsInAudFile) 160 self.header().m_samplerate = tmpTuple[0] 161 offsInAudFile += 2 162 tmpTuple = struct.unpack_from('I', audBytesBuff, offsInAudFile) 163 self.header().m_size_in = tmpTuple[0] 164 offsInAudFile += 4 165 tmpTuple = struct.unpack_from('I', audBytesBuff, offsInAudFile) 166 self.header().m_size_out = tmpTuple[0] 167 offsInAudFile += 4 168 tmpTuple = struct.unpack_from('B', audBytesBuff, offsInAudFile) 169 self.header().m_flags = tmpTuple[0] 170 offsInAudFile += 1 171 tmpTuple = struct.unpack_from('B', audBytesBuff, offsInAudFile) 172 self.header().m_compression = tmpTuple[0] 173 offsInAudFile += 1 174 175 if self.m_traceModeEnabled: 176 print "[Debug] Sample rate: %d\tsizeIn: %d\tsizeOut: %d\tflags: %d\tcompression: %d" % (self.get_samplerate(), self.header().m_size_in, self.header().m_size_out, self.header().m_flags, self.header().m_compression) 177 178 if self.get_samplerate() < 8000 or self.get_samplerate() > 48000 or self.header().m_size_in > (maxLength - SIZE_OF_AUD_HEADER_IN_BYTES ): 179 print "[Warning] Bad AUD Header info in file %s, size_in: %d, maxLen: %d" % (self.m_simpleAudioFileName, self.header().m_size_in, (maxLength - SIZE_OF_AUD_HEADER_IN_BYTES)) 180 if self.header().m_size_in == 0: 181 # handle special case where only the header of the AUD file is present and the size_in is 0. 182 # fill the header with "valid" info for an empty wav file 183 self.header().m_size_out = 0 184 self.header().m_samplerate = 22050 185 self.header().m_compression = 0 186 self.header().m_flags = 2 187 self.header().m_populated = True 188 return True 189 else: 190 return False 191 else: 192 if self.header().m_compression == 1: 193 if (self.header().m_flags != 0): 194 return False 195 elif self.header().m_compression == 0x63: 196 if (self.header().m_flags != 2): 197 return False 198 elif self.header().m_compression == 0: # no compression. At least some AUD files in SFX.MIX have this 199 if (self.header().m_flags != 2): 200 return False 201 self.header().m_populated = True 202 return True 203 204 # int AudFile::get_chunk_header(int i, std::fstream& fs, AudFileNS::pos_type startAudFilepos, AudFileNS::pos_type endAudFilepos, AudChunkHeader& outAudChunkHeader) 205 def get_chunk_header(self, chunkIdx, inAudFileBytesBuffer, inAudFileSize): 206 #fs.seekg(int(startAudFilepos) + int(SIZE_OF_AUD_HEADER_IN_BYTES), fs.beg); 207 #AudFileNS::pos_type rAudPos; 208 #rAudPos = fs.tellg(); 209 outAudChunkHeader = AudChunkHeader() 210 rAudPos = SIZE_OF_AUD_HEADER_IN_BYTES 211 212 if (self.m_traceModeEnabled): 213 print "[Trace] Getting chunk header at %d" % (rAudPos) 214 #AudChunkHeader tmpInremediateChunkheader; 215 tmpInremediateChunkheader = AudChunkHeader() 216 #while (i--) # value of i is decreased after checked by while loop 217 while(chunkIdx > 0): 218 chunkIdx -= 1 219 if (rAudPos + SIZE_OF_AUD_CHUNK_HEADER_IN_BYTES > inAudFileSize): 220 return (-1, rAudPos, None) 221 222 tmpAudFileOffset = rAudPos 223 tmpTuple = struct.unpack_from('H', inAudFileBytesBuffer, tmpAudFileOffset) 224 tmpInremediateChunkheader.m_ch_size_in = tmpTuple[0] 225 tmpAudFileOffset += 2 226 tmpTuple = struct.unpack_from('H', inAudFileBytesBuffer, tmpAudFileOffset) 227 tmpInremediateChunkheader.m_ch_size_out = tmpTuple[0] 228 tmpAudFileOffset += 2 229 tmpTuple = struct.unpack_from('I', inAudFileBytesBuffer, tmpAudFileOffset) 230 tmpInremediateChunkheader.m_ch_id = tmpTuple[0] 231 tmpAudFileOffset += 4 232 #fs.read((char*)&tmpInremediateChunkheader, SIZE_OF_AUD_CHUNK_HEADER_IN_BYTES); 233 rAudPos += SIZE_OF_AUD_CHUNK_HEADER_IN_BYTES + tmpInremediateChunkheader.m_ch_size_in 234 #fs.seekg(int(rAudPos), fs.beg); 235 236 if (rAudPos + SIZE_OF_AUD_CHUNK_HEADER_IN_BYTES > inAudFileSize ): 237 return (-1, rAudPos, None) 238 # write to FINAL output chunk header 239 tmpAudFileOffset = rAudPos 240 tmpTuple = struct.unpack_from('H', inAudFileBytesBuffer, tmpAudFileOffset) 241 outAudChunkHeader.m_ch_size_in = tmpTuple[0] 242 tmpAudFileOffset += 2 243 tmpTuple = struct.unpack_from('H', inAudFileBytesBuffer, tmpAudFileOffset) 244 outAudChunkHeader.m_ch_size_out = tmpTuple[0] 245 tmpAudFileOffset += 2 246 tmpTuple = struct.unpack_from('I', inAudFileBytesBuffer, tmpAudFileOffset) 247 outAudChunkHeader.m_ch_id = tmpTuple[0] 248 tmpAudFileOffset += 4 249 #fs.read((char*)&outAudChunkHeader, SIZE_OF_AUD_CHUNK_HEADER_IN_BYTES); 250 if (rAudPos + SIZE_OF_AUD_CHUNK_HEADER_IN_BYTES + outAudChunkHeader.m_ch_size_in > inAudFileSize): 251 return (-1, rAudPos, None) 252 rAudPos += SIZE_OF_AUD_CHUNK_HEADER_IN_BYTES 253 return (0, rAudPos, outAudChunkHeader) # //reinterpret_cast<const AudChunkHeader*>(r); 254 255 # int AudFile::get_chunk_data(int i, std::fstream& fs, int sizeToRead, AudFileNS::byte* byteChunkDataPtr) 256 def get_chunk_data(self, inAudFileBytesBuffer, startOffs, sizeToRead): 257 #fs.read((char*)byteChunkDataPtr, sizeToRead) 258 outChunkDataLst = [] 259 if (self.m_traceModeEnabled): 260 print "[Trace] Getting chunk data" 261 print "[Trace] startOffs: %d, sizeToRead: %d" % (startOffs, sizeToRead) 262 for i in range(startOffs, startOffs + sizeToRead): 263 #outChunkDataLst.append(ctypes.c_char(inAudFileBytesBuffer[i]).value) 264 #outChunkDataLst.append(ctypes.c_byte(inAudFileBytesBuffer[i]).value) 265 tmpTuple = struct.unpack_from('B', inAudFileBytesBuffer, i) 266 outChunkDataLst.append(tmpTuple[0]) 267 #byteChunkDataOut = struct.pack('b'*len(outChunkDataLst), *outChunkDataLst) 268 #return (0, byteChunkDataOut) 269 return (0, outChunkDataLst) 270 271 272 # std::fstream& fs, AudFileNS::pos_type startAudFilepos, AudFileNS::pos_type endAudFilepos 273 # returned Cvirtual_binary 274 def decode(self, speccompression, audBytesBuff): 275 # The * operator unpacks an argument list. It allows you to call a function with the list items as individual arguments. 276 # binDataOut = struct.pack('i'*len(data), *data) 277 if self.m_traceModeEnabled: 278 print "[Debug] Decoding AUD file format..." 279# Cvirtual_binary d; 280 binaryDataOutLst = [] 281 binaryDataOutBuff = None 282 cb_audio = self.get_cb_sample() * self.get_c_samples() # int cb_audio - basically this should be the size_out 283 if speccompression == 1: 284 # write_start allocates space for virtualBinary 285 # AudFileNS::byte* w = d.write_start(cb_audio); 286 errGetChunk = 0 # int errGetChunk 287 #for (int chunk_i = 0; w != d.data_end(); chunk_i++) 288 chunk_i = 0 289 wIndex = 0 290 while (wIndex < cb_audio): 291 #AudChunkHeader out_chunk_header; 292 #out_chunk_header = AudChunkHeader() 293 (errGetChunk, bufferDataPos, out_chunk_header) = self.get_chunk_header(chunk_i, audBytesBuff, len(audBytesBuff)) 294 if errGetChunk != 0: 295 if self.m_traceModeEnabled: 296 print "[Trace] Error OR End file case while getting uncompressed chunk header!" 297 break 298 299 if self.m_traceModeEnabled: 300 print "[Trace] Get uncompressed chunk header returned: %d " % (out_chunk_header.m_ch_id) 301 #Cvirtual_binary out_chunk_data; 302 #AudFileNS::byte* byteChunkDataPtr = out_chunk_data.write_start(out_chunk_header.m_ch_size_in); 303 (errorGCD, byteChunkDataLst) = self.get_chunk_data(audBytesBuff, bufferDataPos, out_chunk_header.m_ch_size_in) 304 # export decoded chunk to w (output) buffer (of CHARS) at the point where we're currently at (so append there) 305 decodedAudioChunkAsLst = aud_decode_ws_chunk(byteChunkDataLst, out_chunk_header.m_ch_size_in, out_chunk_header.m_ch_size_out) 306 binaryDataOutLst.extend(decodedAudioChunkAsLst) 307 wIndex += out_chunk_header.m_ch_size_out 308 chunk_i += 1 309 binaryDataOutBuff = struct.pack('b'*len(binaryDataOutLst), *binaryDataOutLst) 310 elif speccompression == 0x63: 311 decodeInstance = audFileDecode(self.m_traceModeEnabled); 312 #decodeInstance.init(); 313 #AudFileNS::byte* w = d.write_start(cb_audio); 314 errGetChunk = 0 # int errGetChunk 315 # for (int chunk_i = 0; w != d.data_end(); chunk_i++) 316 chunk_i = 0 317 wIndex = 0 318 while (wIndex < cb_audio): 319 if self.m_traceModeEnabled: 320 print("[Trace] chunkI: %d\t Windex: %d\t cb_audio: %d") % (chunk_i,wIndex,cb_audio) 321 #AudChunkHeader out_chunk_header; 322 #out_chunk_header = AudChunkHeader() 323 #errGetChunk = self.get_chunk_header(chunk_i, fs, startAudFilepos, endAudFilepos, out_chunk_header); 324 (errGetChunk, bufferDataPos, out_chunk_header) = self.get_chunk_header(chunk_i, audBytesBuff, len(audBytesBuff)) 325 if errGetChunk != 0: 326 print "[Warning] Error OR End file case while getting COMPRESSED chunk header!" 327 break 328 if self.m_traceModeEnabled: 329 print "[Trace] Get COMPRESSED chunk header returned:: headerInSize: %d headerOutSize: %d id: %d" % (out_chunk_header.m_ch_size_in, out_chunk_header.m_ch_size_out, out_chunk_header.m_ch_id) 330 #Cvirtual_binary out_chunk_data; 331 #AudFileNS::byte* byteChunkDataPtr = out_chunk_data.write_start(out_chunk_header.m_ch_size_in); 332 (errorGCD, byteChunkDataLst) = self.get_chunk_data(audBytesBuff, bufferDataPos, out_chunk_header.m_ch_size_in) 333 # export decoded chunk to w (output) buffer (of SHORTS) at the point where we're currently at (so append there) 334 if self.m_traceModeEnabled: 335 print "[Trace] byteChunkDataLst len: %d, m_ch_size_in was: %d" % (len(byteChunkDataLst), out_chunk_header.m_ch_size_in) 336 decodedAudioChunkAsLst = decodeInstance.decode_chunk(byteChunkDataLst, out_chunk_header.m_ch_size_out / self.get_cb_sample()); 337 binaryDataOutLst.extend(decodedAudioChunkAsLst) 338 wIndex += out_chunk_header.m_ch_size_out 339 if self.m_traceModeEnabled: 340 print("[Trace] New Windex: %d\t cb_audio: %d") % (wIndex,cb_audio) 341 chunk_i += 1 342 binaryDataOutBuff = struct.pack('h'*len(binaryDataOutLst), *binaryDataOutLst) 343 if self.m_traceModeEnabled: 344 if binaryDataOutBuff is not None: 345 if self.m_traceModeEnabled: 346 print "[Trace] Decoding Done." 347 else: #if binaryDataOutBuff is None: 348 print "[Error] Decoding yielded errors (data out buffer is null)." 349 return binaryDataOutBuff 350 351 def header(self): 352 return self.m_header 353 354 def get_c_samples(self): 355 return self.m_header.m_size_out / self.get_cb_sample() 356 357 def get_samplerate(self): 358 return self.m_header.m_samplerate; 359 360 # flag bit 0 is stereo(set) mono(clear) 361 def get_c_channels(self): 362 return 2 if (self.m_header.m_flags & 0x01) else 1; 363 364 # flag bit 1 is 16bit(set) 8bit (clear) 365 def get_cb_sample(self): 366 return 2 if (self.m_header.m_flags & 0x02) else 1 367# 368# 369# 370if __name__ == '__main__': 371 # main() 372 errorFound = False 373 # By default assumes a file of name 000000.AUD in same directory 374 # otherwise tries to use the first command line argument as input file 375 inAUDFile = None 376 inAUDFileName = '00000000.AUD' 377 378 if len(sys.argv[1:]) > 0 \ 379 and os.path.isfile(os.path.join('.', sys.argv[1])) \ 380 and len(sys.argv[1]) >= 5 \ 381 and sys.argv[1][-3:].upper() == 'AUD': 382 inAUDFileName = sys.argv[1] 383 print "[Info] Attempting to use %s as input AUD file..." % (inAUDFileName) 384 elif os.path.isfile(os.path.join('.', inAUDFileName)): 385 print "[Info] Using default %s as input AUD file..." % (inAUDFileName) 386 else: 387 print "[Error] No valid input file argument was specified and default input file %s is missing." % (inAUDFileName) 388 errorFound = True 389 390 if not errorFound: 391 try: 392 print "[Info] Opening %s" % (inAUDFileName) 393 inAUDFile = open(os.path.join('.', inAUDFileName), 'rb') 394 except: 395 errorFound = True 396 print "[Error] Unexpected event:", sys.exc_info()[0] 397 raise 398 if not errorFound: 399 allOfAudFileInBuffer = inAUDFile.read() 400 audFileInstance = audFile(True) 401 if audFileInstance.m_traceModeEnabled: 402 print "[Debug] Running %s (%s) as main module" % (MY_MODULE_NAME, MY_MODULE_VERSION) 403 if audFileInstance.loadAudFile(allOfAudFileInBuffer, len(allOfAudFileInBuffer), inAUDFileName): 404 print "[Info] Audio file (AUD) loaded successfully!" 405 audFileInstance.export_as_wav(allOfAudFileInBuffer, './tmp.wav') 406 else: 407 print "[Error] Error while loading Audio file (AUD)!" 408 inAUDFile.close() 409else: 410 #debug 411 #print "[Debug] Running %s (%s) imported from another module" % (MY_MODULE_NAME, MY_MODULE_VERSION) 412 pass 413