#!/usr/bin/env python # -*- coding: utf-8 -*- # 2004-2008 merlin@ghostwheel.de # Updated by Göran Wik for 920T # You can use this tool as the original but it is updated to use the # file names from the TomTom 920T custom voice recordings. # Also these files need to be converted to .ogg files before joined by # this script. Also some of the files are NOT produced by the 920T, but the # script will show what's missing, these have to be added manually. # Convert the wav files to ogg files with 22050 Hz mono and variable bitrate of # 5% (oggenc -b 40 voice.wav), the dataXX.chk file package size should be # around 1Mbyte, higher might not be accepted by the TomTom software. # start windows cmd and put the viftool.py into the directory where the *.ogg # files are and enter: # To join the files: # python.exe viftool.py join 75 name data75.chk # To split a package: # python.exe viftool.py split data75.chk import sys, os, struct filedescs = { 0: (['After'], 'After'), 1: (['50'], '50'), 2: (['80'], '80'), 3: (['100'], '100'), 4: (['200'], '200'), 5: (['300'], '300'), 6: (['400'], '400'), 7: (['500'], '500'), 8: (['600'], '600'), 9: (['700'], '700'), 10: (['800'], '800'), 11: (['Meters'], 'Meters'), 12: (['Arrive'], 'Arrived at destination'), 13: (['TurnRight'], 'Turn right'), 14: (['2ndLeft'], 'Take second to the left'), 15: (['2ndRight'], 'Take second to the right'), 16: (['3rdLeft'], 'Take third to the left'), 17: (['3rdRight'], 'Take third to the right'), 18: (['AhExit', 'exitahead'], 'Exit ahead'), 19: (['AhExitLeft', 'exitleftahead'], 'Left exit ahead'), 20: (['AhExitRight', 'exitrightahead'], 'Right exit ahead'), 21: (['AhFerry', 'ferryahead'], 'Ferry ahead'), 22: (['AhKeepLeft', 'keepleftahead'], 'Keep left ahead'), 23: (['AhKeepRight', 'keeprightahead'], 'Keep right ahead'), 24: (['AhLeftTurn', 'leftturnahead'], 'Left turn ahead'), 25: (['AhRightTurn', 'rightturnahead'], 'Right turn ahead'), 26: (['AhUTurn', 'uturnahead'], 'U-turn ahead'), 27: (['BearLeft'], 'Bear left'), 28: (['BearRight'], 'Bear right'), 29: (['Charge', 'congestioncharge'], 'Congestion charge'), 30: (['Depart'], 'Departure'), 31: (['KeepLeft'], 'Keep left'), 32: (['KeepRight'], 'Keep right'), 33: (['LnLeft', 'Leftlane'], 'Left lane'), 34: (['LnRight', 'Rightlane'], 'Right lane'), 35: (['MwEnter', 'entermotorway'], 'Enter the motorway'), 36: (['MwExit', 'Exit'], 'Take the exit'), 37: (['MwExitLeft', 'ExitLeft'], 'Take the exit to the left'), 38: (['MwExitRight', 'ExitRight'], 'Take the exit to the right'), 39: (['RbBack', 'roundaboutBack'], 'Go around the roundabout'), 40: (['RbCross', 'roundaboutCross'], 'Go across the roundabout'), 41: (['RbExit1', 'roundaboutExit1'], 'First exit'), 42: (['RbExit2', 'roundaboutExit2'], 'Second exit'), 43: (['RbExit3', 'roundaboutExit3'], 'Third exit'), 44: (['RbExit4', 'roundaboutExit4'], 'Fourth exit'), 45: (['RbExit5', 'roundaboutExit5'], 'Fifth exit'), 46: (['RbExit6', 'roundaboutExit6'], 'Sixth exit'), 47: (['RbLeft', 'roundaboutLeft'], 'Go left in the roundabout'), 48: (['RbRight', 'roundaboutRight'], 'Go right in the roundabout'), 49: (['RoadEnd', 'attheendoftheroad'], 'At the end of the road'), 50: (['SharpLeft'], 'Sharp left'), 51: (['SharpRight'], 'Sharp right'), 52: (['Straight'], 'Go strait'), 53: (['TakeFerry', 'TaketheFerry'], 'Take the ferry'), 54: (['Then'], 'Then'), 55: (['TryUTurn', 'trymakeuturn'], 'Turn around when possible'), 56: (['TurnLeft'], 'Turn left'), 57: (['UTurn', 'UTurnright'], 'Make a U-turn'), 58: (['Yards'], 'Yards'), } silent = False def write(*texts): if not silent: sys.stdout.write(' '.join(map(str,texts))) sys.stdout.write('\n') sys.stdout.flush() def vifchk(file): fext = os.path.splitext(file)[1].lower() path = os.path.split(file)[0] if fext == '.vif': try: fdesc, datafile = filter(None, map(lambda l:l.strip(), open(file,'r').readlines()))[:2] except Exception as reason: write('Invalid vif file: %s!'%(file)) write('[%s]'%(str(reason))) return path, None else: fdesc = fdesc.strip() datafile = os.path.join(path, datafile.strip()) write('DESC: %s'%(fdesc)) return path, datafile elif fext == '.chk': return path, file else: write('Invalid file: %s!'%(file)) return path, None def split(file, intern=None): res = [] rem = {} path, datafile = vifchk(file) if datafile is None: return res write('DATA: %s'%(datafile)) flen = os.stat(datafile)[6] inp = open(datafile,'rb') # read header num = struct.unpack('>L',inp.read(4))[0] entries=[] write('Found %d entries...'%(num)) if (num+1)*4 > flen: write('Invalid chk file!') return while num > 0: entries.append(struct.unpack('>L',inp.read(4))[0]) num -= 1 # check length fend = struct.unpack('>L',inp.read(4))[0] if fend != flen: write('File length mismatch (is %d, should be %d).'%(flen, fend)) return write('Extracting entries...') # check entries cnt=0 for pos in entries: if cnt+1 < len(entries): next = entries[cnt+1] else: next = flen inp.seek(pos,0) tag, typ, blocks = struct.unpack('>BBH', inp.read(4)) err=[] # v debug if typ == 4: head1, head2, head3, length = struct.unpack('>LLBL', inp.read(13)) write('%4d'%cnt, '@ %08x :'%pos, '%02x'%tag, '%02x'%typ, '%04x'%blocks, '%08x'%head1, '%08x'%head2, '%02x'%head3, ':', '%6d'%length, '=', '0x%x'%length, ': blocks:', '%6d'%(blocks*4), 'space:', next-pos) else: head1, head2, length = struct.unpack('>LLL', inp.read(12)) write('%4d'%cnt, '@ %08x :'%pos, '%02x'%tag, '%02x'%typ, '%04x'%blocks, '%08x'%head1, '%08x'%head2, '-- :', '%6d'%length, '=', '0x%x'%length, ': blocks:', '%6d'%(blocks*4), 'space:', next-pos) # ^ debug if (next-pos)%4 != 0: err.append('space is not a multiple of 4') if length > next-pos: err.append('length is bigger than space') if blocks*4 > next-pos: err.append('blocks is bigger than space') if blocks*4 != next-pos-4: err.append('blocks should be space-4') if err: write('!!!', '\n!!! '.join(err)) elif filedescs.has_key(cnt): data = inp.read(length) if intern is None: open(os.path.join(path, '%s.ogg'%filedescs[cnt][0][0]), 'wb').write(data) else: if cnt in intern: rem[cnt] = data else: write('unknown voice file #%d'%(cnt)) cnt+=1 inp.close() if intern is not None: for number in intern: res.append((number, rem[number])) return res def join(chknum, fdesc, file): path = os.path.split(file)[0] if chknum is None: chkfile = file else: chkfile = os.path.join(path, 'data%02d.chk'%(int(chknum))) write('Gathering sound files...') entries = [] for number, (names, desc) in filedescs.items(): found = 0 for name in names: sndfile = os.path.join(path,name) if os.path.isfile(sndfile+'.ogg'): entries.append(open(sndfile+'.ogg','rb').read()) found = 1 break elif os.path.isfile(sndfile+'.wav'): entries.append(os.popen('oggenc -b 40 -o - %s.wav 2>/dev/null'%(sndfile),'rb').read()) found = 1 break if not found: write('entry #%d "%s.ogg" (%s) is missing'%(number,names[0],desc)) return if chknum is not None: write('Creating vif file...') open(file,'wb').write('%s\r\n%s\r\n'%(fdesc,os.path.split(chkfile)[1])) write('Creating chk file...') out = open(chkfile,'wb') # write entry-count out.write(struct.pack('>L', len(filedescs))) # set and write entry start positions pos = 8+len(entries)*4 for entry in entries: out.write(struct.pack('>L', pos)) elen = len(entry) pos += 16 + elen+3-((elen-1)%4) # write file end marker out.write(struct.pack('>L', pos)) # write entry data for entry in entries: elen = len(entry) pad = 3-(elen-1)%4 out.write(struct.pack('>BBHLLL', 1, 0, (elen+12+pad)>>2, 1, 8, elen)) out.write(entry+'\000'*pad) out.close() write('Done.') ## MAIN ## if len(sys.argv)<3 or sys.argv[1] not in ['split', 'join'] or (sys.argv[1]=='join' and len(sys.argv) not in [3,5]): myself = os.path.split(sys.argv[0])[1] write('Usage: %s split [VIFFILE|CHKFILE]+'%(myself)) write(' %s join [NUM DESC VIFFILE|CHKFILE]'%(myself)) sys.exit() else: action = sys.argv[1] if action == 'split': for file in sys.argv[2:]: split(file) elif action == 'join': if len(sys.argv) == 5: join(*sys.argv[2:5]) else: join(None, None, sys.argv[2])