From 25bd7db348772d299b3fab5a378332c1376ec6c2 Mon Sep 17 00:00:00 2001 From: Simon Date: Fri, 14 May 2021 00:07:11 +0100 Subject: [PATCH] Allow loading if multiple languages and selection both via default in config file and OPTIONS. Remove ANNOUNCEMENT_LANGUAGE from [GLOBAL] Replace with ANNOUNCEMENT_LANGUAGES - Comma Separated list Current list: ANNOUNCEMENT_LANGUAGES: en_GB,en_GB_2,en_US,es_ES,es_ES_2,fr_FR,de_DE,dk_DK,it_IT,no_NO,pl_PL,se_SE Add: ANNOUNCEMENT_LANGUAGE to MASTER definition. If using GENERATOR, this becomes the template default. To change via OPTIONS add LANG= take codes from list above --- bridge_master.py | 134 +++++++++++++++++++---------------- config.py | 5 +- read_ambe.py | 179 +++++++++++++++++++++++++---------------------- 3 files changed, 172 insertions(+), 146 deletions(-) diff --git a/bridge_master.py b/bridge_master.py index 08b4d20..3be3500 100755 --- a/bridge_master.py +++ b/bridge_master.py @@ -496,24 +496,25 @@ def sendSpeech(self,speech): def disconnectedVoice(system): _nine = bytes_3(9) _source_id = bytes_3(5000) + _lang = CONFIG['SYSTEMS'][system]['ANNOUNCEMENT_LANGUAGE'] logger.debug('(%s) Sending disconnected voice',system) - _say = [words['silence']] - _say.append(words['silence']) + _say = [words[_lang]['silence']] + _say.append(words[_lang]['silence']) if CONFIG['SYSTEMS'][system]['DEFAULT_REFLECTOR'] > 0: - _say.append(words['silence']) - _say.append(words['linkedto']) - _say.append(words['silence']) - _say.append(words['to']) - _say.append(words['silence']) - _say.append(words['silence']) + _say.append(words[_lang]['silence']) + _say.append(words[_lang]['linkedto']) + _say.append(words[_lang]['silence']) + _say.append(words[_lang]['to']) + _say.append(words[_lang]['silence']) + _say.append(words[_lang]['silence']) for number in str(CONFIG['SYSTEMS'][system]['DEFAULT_REFLECTOR']): - _say.append(words[number]) - _say.append(words['silence']) + _say.append(words[_lang][number]) + _say.append(words[_lang]['silence']) else: - _say.append(words['notlinked']) + _say.append(words[_lang]['notlinked']) - _say.append(words['silence']) + _say.append(words[_lang]['silence']) speech = pkt_gen(_source_id, _nine, bytes_4(9), 1, _say) @@ -545,6 +546,7 @@ def ident(): if CONFIG['SYSTEMS'][system]['MODE'] != 'MASTER': continue if CONFIG['SYSTEMS'][system]['VOICE_IDENT'] == True: + _lang = CONFIG['SYSTEMS'][system]['ANNOUNCEMENT_LANGUAGE'] if CONFIG['SYSTEMS'][system]['MAX_PEERS'] > 1: logger.debug("(IDENT) %s System has MAX_PEERS > 1, skipping",system) continue @@ -560,31 +562,31 @@ def ident(): if (_slot['RX_TYPE'] == HBPF_SLT_VTERM) and (_slot['TX_TYPE'] == HBPF_SLT_VTERM) and (time() - _slot['TX_TIME'] > CONFIG['SYSTEMS'][system]['GROUP_HANGTIME']): #_stream_id = hex_str_4(1234567) logger.info('(%s) System idle. Sending voice ident',system) - _say = [words['silence']] - _say.append(words['silence']) - _say.append(words['silence']) - _say.append(words['this-is']) - _say.append(words['silence']) - _say.append(words['silence']) - _say.append(words['silence']) - _say.append(words['silence']) - _say.append(words['silence']) - _say.append(words['silence']) - _say.append(words['silence']) + _say = [words[_lang]['silence']] + _say.append(words[_lang]['silence']) + _say.append(words[_lang]['silence']) + _say.append(words[_lang]['this-is']) + _say.append(words[_lang]['silence']) + _say.append(words[_lang]['silence']) + _say.append(words[_lang]['silence']) + _say.append(words[_lang]['silence']) + _say.append(words[_lang]['silence']) + _say.append(words[_lang]['silence']) + _say.append(words[_lang]['silence']) _systemcs = re.sub(r'\W+', '', _callsign) _systemcs.upper() for character in _systemcs: - _say.append(words[character]) - _say.append(words['silence']) - _say.append(words['silence']) - _say.append(words['silence']) - _say.append(words['silence']) - _say.append(words['silence']) - _say.append(words['silence']) - _say.append(words['silence']) + _say.append(words[_lang][character]) + _say.append(words[_lang]['silence']) + _say.append(words[_lang]['silence']) + _say.append(words[_lang]['silence']) + _say.append(words[_lang]['silence']) + _say.append(words[_lang]['silence']) + _say.append(words[_lang]['silence']) + _say.append(words[_lang]['silence']) - _say.append(words['freedmr']) + _say.append(words[_lang]['freedmr']) #test #_say.append(AMBEobj.readSingleFile('alpha.ambe')) @@ -680,6 +682,10 @@ def options_config(): CONFIG['SYSTEMS'][_system]['VOICE_IDENT'] = bool(int(_options['VOICE'])) logger.debug("(OPTIONS) %s - Setting voice ident to %s",_system,CONFIG['SYSTEMS'][_system]['VOICE_IDENT']) + if 'LANG' in _options and _options['LANG'] in words and _options['LANG'] != CONFIG['SYSTEMS'][_system]['ANNOUNCEMENT_LANGUAGE'] : + CONFIG['SYSTEMS'][_system]['ANNOUNCEMENT_LANGUAGE'] = _options['LANG'] + logger.debug("(OPTIONS) %s - Setting voice language to %s",_system,CONFIG['SYSTEMS'][_system]['ANNOUNCEMENT_LANGUAGE']) + if 'SINGLE' in _options and (CONFIG['SYSTEMS'][_system]['SINGLE_MODE'] != bool(int(_options['SINGLE']))): CONFIG['SYSTEMS'][_system]['SINGLE_MODE'] = bool(int(_options['SINGLE'])) logger.debug("(OPTIONS) %s - Setting SINGLE_MODE to %s",_system,CONFIG['SYSTEMS'][_system]['SINGLE_MODE']) @@ -1643,6 +1649,8 @@ class routerHBP(HBSYSTEM): _nine = bytes_3(9) + _lang = CONFIG['SYSTEMS'][self._system]['ANNOUNCEMENT_LANGUAGE'] + _int_dst_id = int_id(_dst_id) #Handle private calls (for reflectors) @@ -1711,7 +1719,7 @@ class routerHBP(HBSYSTEM): if (_frame_type == HBPF_DATA_SYNC) and (_dtype_vseq == HBPF_SLT_VTERM) and (self.STATUS[_slot]['RX_TYPE'] != HBPF_SLT_VTERM): #Speak callsign before message - _say = [words['silence']] + _say = [words[_lang]['silence']] # _systemcs = re.sub(r'\W+', '', self._system) # _systemcs.upper() # for character in _systemcs: @@ -1721,8 +1729,8 @@ class routerHBP(HBSYSTEM): #If disconnection called if _int_dst_id == 4000: logger.info('(%s) Reflector: voice called - 4000 "not linked"', self._system) - _say.append(words['notlinked']) - _say.append(words['silence']) + _say.append(words[_lang]['notlinked']) + _say.append(words[_lang]['silence']) #If status called elif _int_dst_id == 5000: @@ -1735,35 +1743,35 @@ class routerHBP(HBSYSTEM): if _system['SYSTEM'] == self._system and _slot == _system['TS']: if _system['ACTIVE'] == True: logger.info('(%s) Reflector: voice called - 5000 status - "linked to %s"', self._system,_dehash_bridge) - _say.append(words['silence']) - _say.append(words['linkedto']) - _say.append(words['silence']) - _say.append(words['to']) - _say.append(words['silence']) - _say.append(words['silence']) + _say.append(words[_lang]['silence']) + _say.append(words[_lang]['linkedto']) + _say.append(words[_lang]['silence']) + _say.append(words[_lang]['to']) + _say.append(words[_lang]['silence']) + _say.append(words[_lang]['silence']) for num in str(_dehash_bridge): - _say.append(words[num]) + _say.append(words[_lang][num]) _active = True break if _active == False: logger.info('(%s) Reflector: voice called - 5000 status - "not linked"', self._system) - _say.append(words['notlinked']) + _say.append(words[_lang]['notlinked']) #Speak what TG was requested to link else: logger.info('(%s) Reflector: voice called (linking) "linked to %s"', self._system,_int_dst_id) - _say.append(words['silence']) - _say.append(words['linkedto']) - _say.append(words['silence']) - _say.append(words['to']) - _say.append(words['silence']) - _say.append(words['silence']) + _say.append(words[_lang]['silence']) + _say.append(words[_lang]['linkedto']) + _say.append(words[_lang]['silence']) + _say.append(words[_lang]['to']) + _say.append(words[_lang]['silence']) + _say.append(words[_lang]['silence']) for num in str(_int_dst_id): - _say.append(words[num]) + _say.append(words[_lang][num]) speech = pkt_gen(bytes_3(5000), _nine, bytes_4(9), 1, _say) @@ -2104,7 +2112,7 @@ if __name__ == '__main__': _systemname = system+'-'+str(count) generator[_systemname] = copy.deepcopy(CONFIG['SYSTEMS'][system]) generator[_systemname]['PORT'] = generator[_systemname]['PORT'] + count - generator[_systemname]['_default_options'] = "TS1_STATIC={};TS2_STATIC={};SINGLE={};DEFAULT_UA_TIMER={};DEFAULT_REFLECTOR={};VOICE={}".format(generator[_systemname]['TS1_STATIC'],generator[_systemname]['TS2_STATIC'],int(generator[_systemname]['SINGLE_MODE']),generator[_systemname]['DEFAULT_UA_TIMER'],generator[_systemname]['DEFAULT_REFLECTOR'],int(generator[_systemname]['VOICE_IDENT']) ) + generator[_systemname]['_default_options'] = "TS1_STATIC={};TS2_STATIC={};SINGLE={};DEFAULT_UA_TIMER={};DEFAULT_REFLECTOR={};VOICE={};LANG={}".format(generator[_systemname]['TS1_STATIC'],generator[_systemname]['TS2_STATIC'],int(generator[_systemname]['SINGLE_MODE']),generator[_systemname]['DEFAULT_UA_TIMER'],generator[_systemname]['DEFAULT_REFLECTOR'],int(generator[_systemname]['VOICE_IDENT']), generator[_systemname]['ANNOUNCEMENT_LANGUAGE']) logger.debug('(GLOBAL) Generator - generated system %s',_systemname) generator[_systemname]['_default_options'] systemdelete.append(system) @@ -2163,18 +2171,21 @@ if __name__ == '__main__': logger.info('(REPORT) TCP Socket reporting not configured') #Read AMBE - AMBEobj = readAMBE(CONFIG['GLOBAL']['ANNOUNCEMENT_LANGUAGE'],'./Audio/') + AMBEobj = readAMBE(CONFIG['GLOBAL']['ANNOUNCEMENT_LANGUAGES'],'./Audio/') + #global words words = AMBEobj.readfiles() - logger.info('(AMBE) Read %s words into voice dict',len(words) - 1) + + for lang in words.keys(): + logger.info('(AMBE) for language %s, read %s words into voice dict',lang,len(words[lang]) - 1) - #Remap words for internationalisation - if CONFIG['GLOBAL']['ANNOUNCEMENT_LANGUAGE'] in voiceMap: - logger.info('(AMBE) i8n voice map entry for language %s',CONFIG['GLOBAL']['ANNOUNCEMENT_LANGUAGE']) - _map = voiceMap[CONFIG['GLOBAL']['ANNOUNCEMENT_LANGUAGE']] - for _mapword in _map: - logger.info('(AMBE) Mapping \"%s\" to \"%s\"',_mapword,_map[_mapword]) - words[_mapword] = words[_map[_mapword]] + #Remap words for internationalisation + if lang in voiceMap: + logger.info('(AMBE) i8n voice map entry for language %s',lang) + _map = voiceMap[lang] + for _mapword in _map: + logger.info('(AMBE) Mapping \"%s\" to \"%s\"',_mapword,_map[_mapword]) + words[lang][_mapword] = words[lang][_map[_mapword]] # HBlink instance creation logger.info('(GLOBAL) FreeDMR \'bridge_master.py\' -- SYSTEM STARTING...') @@ -2191,6 +2202,9 @@ if __name__ == '__main__': if CONFIG['SYSTEMS'][system]['MODE'] == 'OPENBRIDGE': systems[system] = routerOBP(system, CONFIG, report_server) else: + if CONFIG['SYSTEMS'][system]['ANNOUNCEMENT_LANGUAGE'] not in CONFIG['GLOBAL']['ANNOUNCEMENT_LANGUAGES'].split(','): + logger.warning('(GLOBAL) Invalid language in ANNOUNCEMENT_LANGUAGE, skipping system %s',system) + continue systems[system] = routerHBP(system, CONFIG, report_server) listeningPorts[system] = reactor.listenUDP(CONFIG['SYSTEMS'][system]['PORT'], systems[system], interface=CONFIG['SYSTEMS'][system]['IP']) logger.debug('(GLOBAL) %s instance created: %s, %s', CONFIG['SYSTEMS'][system]['MODE'], system, systems[system]) diff --git a/config.py b/config.py index c4374bc..0660640 100755 --- a/config.py +++ b/config.py @@ -124,7 +124,7 @@ def build_config(_config_file): 'TG2_ACL': config.get(section, 'TGID_TS2_ACL'), 'GEN_STAT_BRIDGES': config.getboolean(section, 'GEN_STAT_BRIDGES'), 'ALLOW_NULL_PASSPHRASE': config.getboolean(section, 'ALLOW_NULL_PASSPHRASE'), - 'ANNOUNCEMENT_LANGUAGE': config.get(section, 'ANNOUNCEMENT_LANGUAGE'), + 'ANNOUNCEMENT_LANGUAGES': config.get(section, 'ANNOUNCEMENT_LANGUAGES'), 'SERVER_ID': config.get(section, 'SERVER_ID') }) @@ -284,7 +284,8 @@ def build_config(_config_file): 'TS1_STATIC': config.get(section,'TS1_STATIC'), 'TS2_STATIC': config.get(section,'TS2_STATIC'), 'DEFAULT_REFLECTOR': config.getint(section, 'DEFAULT_REFLECTOR'), - 'GENERATOR': config.getint(section, 'GENERATOR') + 'GENERATOR': config.getint(section, 'GENERATOR'), + 'ANNOUNCEMENT_LANGUAGE': config.get(section, 'ANNOUNCEMENT_LANGUAGE') }}) CONFIG['SYSTEMS'][section].update({'PEERS': {}}) diff --git a/read_ambe.py b/read_ambe.py index b79e506..9525f59 100644 --- a/read_ambe.py +++ b/read_ambe.py @@ -6,10 +6,10 @@ import glob class readAMBE: def __init__(self, lang,path): - self.lang = lang + self.langcsv = lang + self.langs = lang.split(',') self.path = path - self.prefix = path+lang - + def _make_bursts(self,data): it = iter(data) for i in range(0, len(data), 108): @@ -17,91 +17,102 @@ class readAMBE: #Read indexed files def readfiles(self): - _AMBE_LENGTH = 9 - indexDict = {} - if os.path.isdir(self.prefix): - ambeBytearray = {} - _wordBitarray = bitarray(endian='big') - _wordBADict = {} - _glob = self.prefix + "/*.ambe" - for ambe in glob.glob(_glob): - basename = os.path.basename(ambe) - _voice,ext = basename.split('.') - inambe = open(ambe,'rb') - _wordBitarray.frombytes(inambe.read()) - inambe.close() - _wordBADict[_voice] = [] - pairs = 1 - _lastburst = '' - for _burst in self._make_bursts(_wordBitarray): -#Not sure if we need to pad or not? Seems to make little difference. - if len(_burst) < 108: - pad = (108 - len(_burst)) - for i in range(0,pad,1): - _burst.append(False) - if pairs == 2: - _wordBADict[_voice].append([_lastburst,_burst]) - _lastburst = '' - pairs = 1 - next - else: - pairs = pairs + 1 - _lastburst = _burst - - _wordBitarray.clear() - _wordBADict['silence'] = ([ - [bitarray('101011000000101010100000010000000000001000000000000000000000010001000000010000000000100000000000100000000000'), - bitarray('001010110000001010101000000100000000000010000000000000000000000100010000000100000000001000000000001000000000')] - ]) - return _wordBADict - else: - try: - with open(self.prefix+'.indx') as index: - for line in index: - (voice,start,length) = line.split() - indexDict[voice] = [int(start) * _AMBE_LENGTH ,int(length) * _AMBE_LENGTH] - index.close() - except IOError: - return False + _AMBE_LENGTH = 9 + + _wordBADictofDicts = {} - ambeBytearray = {} - _wordBitarray = bitarray(endian='big') + for _lang in self.langs: + + _prefix = self.path+_lang _wordBADict = {} - try: - with open(self.prefix+'.ambe','rb') as ambe: - for _voice in indexDict: - ambe.seek(indexDict[_voice][0]) - _wordBitarray.frombytes(ambe.read(indexDict[_voice][1])) - #108 - _wordBADict[_voice] = [] - pairs = 1 - _lastburst = '' - for _burst in self._make_bursts(_wordBitarray): + + indexDict = {} + + if os.path.isdir(_prefix): + ambeBytearray = {} + _wordBitarray = bitarray(endian='big') + _wordBADict = {} + _glob = _prefix + "/*.ambe" + for ambe in glob.glob(_glob): + basename = os.path.basename(ambe) + _voice,ext = basename.split('.') + inambe = open(ambe,'rb') + _wordBitarray.frombytes(inambe.read()) + inambe.close() + _wordBADict[_voice] = [] + pairs = 1 + _lastburst = '' + for _burst in self._make_bursts(_wordBitarray): #Not sure if we need to pad or not? Seems to make little difference. - if len(_burst) < 108: - pad = (108 - len(_burst)) - for i in range(0,pad,1): - _burst.append(False) - if pairs == 2: - _wordBADict[_voice].append([_lastburst,_burst]) - _lastburst = '' - pairs = 1 - next - else: - pairs = pairs + 1 - _lastburst = _burst - - _wordBitarray.clear() - ambe.close() - except IOError: - return False - _wordBADict['silence'] = ([ - [bitarray('101011000000101010100000010000000000001000000000000000000000010001000000010000000000100000000000100000000000'), - bitarray('001010110000001010101000000100000000000010000000000000000000000100010000000100000000001000000000001000000000')] - ]) - return _wordBADict - + if len(_burst) < 108: + pad = (108 - len(_burst)) + for i in range(0,pad,1): + _burst.append(False) + if pairs == 2: + _wordBADict[_voice].append([_lastburst,_burst]) + _lastburst = '' + pairs = 1 + next + else: + pairs = pairs + 1 + _lastburst = _burst + + _wordBitarray.clear() + _wordBADict['silence'] = ([ + [bitarray('101011000000101010100000010000000000001000000000000000000000010001000000010000000000100000000000100000000000'), + bitarray('001010110000001010101000000100000000000010000000000000000000000100010000000100000000001000000000001000000000')] + ]) + _wordBADictofDicts[_lang] = _wordBADict + else: + try: + with open(_prefix+'.indx') as index: + for line in index: + (voice,start,length) = line.split() + indexDict[voice] = [int(start) * _AMBE_LENGTH ,int(length) * _AMBE_LENGTH] + index.close() + except IOError: + return False + + ambeBytearray = {} + _wordBitarray = bitarray(endian='big') + _wordBADict = {} + try: + with open(_prefix+'.ambe','rb') as ambe: + for _voice in indexDict: + ambe.seek(indexDict[_voice][0]) + _wordBitarray.frombytes(ambe.read(indexDict[_voice][1])) + #108 + _wordBADict[_voice] = [] + pairs = 1 + _lastburst = '' + for _burst in self._make_bursts(_wordBitarray): + #Not sure if we need to pad or not? Seems to make little difference. + if len(_burst) < 108: + pad = (108 - len(_burst)) + for i in range(0,pad,1): + _burst.append(False) + if pairs == 2: + _wordBADict[_voice].append([_lastburst,_burst]) + _lastburst = '' + pairs = 1 + next + else: + pairs = pairs + 1 + _lastburst = _burst + + _wordBitarray.clear() + ambe.close() + except IOError: + return False + _wordBADict['silence'] = ([ + [bitarray('101011000000101010100000010000000000001000000000000000000000010001000000010000000000100000000000100000000000'), + bitarray('001010110000001010101000000100000000000010000000000000000000000100010000000100000000001000000000001000000000')] + ]) + _wordBADictofDicts[_lang] = _wordBADict + + return _wordBADictofDicts + #Read a single ambe file from the audio directory def readSingleFile(self,filename): ambeBytearray = {}