import os import shutil import logging import abc import sys import re import subprocess import platform from typing import Dict, List, Tuple # import phonecode_tables # try: # sys.path.append(".") # import resources.app.python.xvapitch.text.phonecode_tables as phonecode_tables # except: # try: # import python.xvapitch.text.phonecode_tables as phonecode_tables # except: # try: # import xvapitch.text.phonecode_tables as phonecode_tables # except: # try: # import text.phonecode_tables as phonecode_tables # except: # import phonecode_tables as phonecode_tables try: from . import phonecode_tables except: try: sys.path.append(".") import resources.app.python.xvapitch.text.phonecode_tables as phonecode_tables except: try: import python.xvapitch.text.phonecode_tables as phonecode_tables except: try: import text.phonecode_tables as phonecode_tables except: import phonecode_tables as phonecode_tables ARPABET_SYMBOLS = [ 'AA0', 'AA1', 'AA2', 'AA', 'AE0', 'AE1', 'AE2', 'AE', 'AH0', 'AH1', 'AH2', 'AH', 'AO0', 'AO1', 'AO2', 'AO', 'AW0', 'AW1', 'AW2', 'AW', 'AY0', 'AY1', 'AY2', 'AY', 'B', 'CH', 'D', 'DH', 'EH0', 'EH1', 'EH2', 'EH', 'ER0', 'ER1', 'ER2', 'ER', 'EY0', 'EY1', 'EY2', 'EY', 'F', 'G', 'HH', 'IH0', 'IH1', 'IH2', 'IH', 'IY0', 'IY1', 'IY2', 'IY', 'JH', 'K', 'L', 'M', 'N', 'NG', 'OW0', 'OW1', 'OW2', 'OW', 'OY0', 'OY1', 'OY2', 'OY', 'P', 'R', 'S', 'SH', 'T', 'TH', 'UH0', 'UH1', 'UH2', 'UH', 'UW0', 'UW1', 'UW2', 'UW', 'V', 'W', 'Y', 'Z', 'ZH' ] # These are actually in the spec, but they seem to not get used by CMUdict or FastPitch extra_arpabet_symbols = [ "AX", # commA "AXR", # lettER "IX", # rosEs, rabbIt "UX", # dUde "DX", # buTTer "EL", # bottLE "EM", # rhythM "EN0", # buttON "EN1", # buttON "EN2", # buttON "EN", # buttON "NX", # wiNNer "Q", # , eg uh-oh, english (cockney) bottle "WH", # why - but like, when people add H before the W ] new_arpabet_symbols = [ "RRR", # "rrr" - hard r "HR", # the hhhrrr sound common on arabic languages "OE", # ø, german möve, french eu (bleu) "RH", # "run" - soft r, but mostly h "TS", # T and S together - romanian ț "RR", # "run" - soft r "UU", # ʏ, german über "OO", # ŏ, hard o "KH", # K H, but together "SJ", # swedish sj "HJ", # trying to say the "J" sound, but with a wide open mouth "BR", # b mixed in with rolling r, almost like "blowing a raspberry" sound ] ARPABET_SYMBOLS = ARPABET_SYMBOLS + extra_arpabet_symbols + new_arpabet_symbols PUNCTUATION = [".", ",", "!", "?", "-", ";", ":", "—"] PIN_YIN_ENDS = [ "A1", "A2", "A3", "A4", "A5", "AI1", "AI2", "AI3", "AI4", "AI5", "AIR2", "AIR3", "AIR4", "AN1", "AN2", "AN3", "AN4", "AN5", "ANG1", "ANG2", "ANG3", "ANG4", "ANG5", "ANGR2", "ANGR3", "ANGR4", "ANR1", "ANR3", "ANR4", "AO1", "AO2", "AO3", "AO4", "AO5", "AOR1", "AOR2", "AOR3", "AOR4", "AOR5", "AR2", "AR3", "AR4", "AR5", "E1", "E2", "E3", "E4", "E5", "EI1", "EI2", "EI3", "EI4", "EI5", "EIR4", "EN1", "EN2", "EN3", "EN4", "EN5", "ENG1", "ENG2", "ENG3", "ENG4", "ENG5", "ENGR1", "ENGR4", "ENR1", "ENR2", "ENR3", "ENR4", "ENR5", "ER1", "ER2", "ER3", "ER4", "ER5", "I1", "I2", "I3", "I4", "I5", "IA1", "IA2", "IA3", "IA4", "IA5", "IAN1", "IAN2", "IAN3", "IAN4", "IAN5", "IANG1", "IANG2", "IANG3", "IANG4", "IANG5", "IANGR2", "IANR1", "IANR2", "IANR3", "IANR4", "IANR5", "IAO1", "IAO2", "IAO3", "IAO4", "IAO5", "IAOR1", "IAOR2", "IAOR3", "IAOR4", "IAR1", "IAR4", "IE1", "IE2", "IE3", "IE4", "IE5", "IN1", "IN2", "IN3", "IN4", "IN5", "ING1", "ING2", "ING3", "ING4", "ING5", "INGR2", "INGR4", "INR1", "INR4", "IONG1", "IONG2", "IONG3", "IONG4", "IONG5", "IR1", "IR3", "IR4", "IU1", "IU2", "IU3", "IU4", "IU5", "IUR1", "IUR2", "O1", "O2", "O3", "O4", "O5", "ONG1", "ONG2", "ONG3", "ONG4", "ONG5", "OR1", "OR2", "OU1", "OU2", "OU3", "OU4", "OU5", "OUR2", "OUR3", "OUR4", "OUR5", "U1", "U2", "U3", "U4", "U5", "UA1", "UA2", "UA3", "UA4", "UA5", "UAI1", "UAI2", "UAI3", "UAI4", "UAIR4", "UAIR5", "UAN1", "UAN2", "UAN3", "UAN4", "UAN5", "UANG1", "UANG2", "UANG3", "UANG4", "UANG5", "UANR1", "UANR2", "UANR3", "UANR4", "UAR1", "UAR2", "UAR4", "UE1", "UE2", "UE3", "UE4", "UE5", "UER2", "UER3", "UI1", "UI2", "UI3", "UI4", "UI5", "UIR1", "UIR2", "UIR3", "UIR4", "UN1", "UN2", "UN3", "UN4", "UN5", "UNR1", "UNR2", "UNR3", "UNR4", "UO1", "UO2", "UO3", "UO4", "UO5", "UOR1", "UOR2", "UOR3", "UOR5", "UR1", "UR2", "UR4", "UR5", "V2", "V3", "V4", "V5", "VE4", "VR3", "WA1", "WA2", "WA3", "WA4", "WA5", "WAI1", "WAI2", "WAI3", "WAI4", "WAN1", "WAN2", "WAN3", "WAN4", "WAN5", "WANG1", "WANG2", "WANG3", "WANG4", "WANG5", "WANGR2", "WANGR4", "WANR2", "WANR4", "WANR5", "WEI1", "WEI2", "WEI3", "WEI4", "WEI5", "WEIR1", "WEIR2", "WEIR3", "WEIR4", "WEIR5", "WEN1", "WEN2", "WEN3", "WEN4", "WEN5", "WENG1", "WENG2", "WENG3", "WENG4", "WENR2", "WO1", "WO2", "WO3", "WO4", "WO5", "WU1", "WU2", "WU3", "WU4", "WU5", "WUR3", "YA1", "YA2", "YA3", "YA4", "YA5", "YAN1", "YAN2", "YAN3", "YAN4", "YANG1", "YANG2", "YANG3", "YANG4", "YANG5", "YANGR4", "YANR3", "YAO1", "YAO2", "YAO3", "YAO4", "YAO5", "YE1", "YE2", "YE3", "YE4", "YE5", "YER4", "YI1", "YI2", "YI3", "YI4", "YI5", "YIN1", "YIN2", "YIN3", "YIN4", "YIN5", "YING1", "YING2", "YING3", "YING4", "YING5", "YINGR1", "YINGR2", "YINGR3", "YIR4", "YO1", "YO3", "YONG1", "YONG2", "YONG3", "YONG4", "YONG5", "YONGR3", "YOU1", "YOU2", "YOU3", "YOU4", "YOU5", "YOUR2", "YOUR3", "YOUR4", "YU1", "YU2", "YU3", "YU4", "YU5", "YUAN1", "YUAN2", "YUAN3", "YUAN4", "YUAN5", "YUANR2", "YUANR4", "YUE1", "YUE2", "YUE4", "YUE5", "YUER4", "YUN1", "YUN2", "YUN3", "YUN4", ] PIN_YIN_STARTS = [ # NOT USED - already in ARPAbet, or manually mapped to equivalent (see below) "B", "C", "CH", "D", "F", "G", "H", "J", "K", "L", "M", "N", "P", "Q", "R", "S", "SH", "T", "X", "Z", "ZH" # "H", "J", "Q", "X", ] EXTRA = [ # Not currently used, but registered for compatibility, if I do start using them. Plus some extra slots "@BREATHE_IN", "@BREATHE_OUT", "@LAUGH", "@GIGGLE", "@SIGH", "@COUGH", "@AHEM", "@SNEEZE", "@WHISTLE", "@UGH", "@HMM", "@GASP", "@AAH", "@GRUNT", "@YAWN", "@SNIFF", "@_UNUSED_1", "@_UNUSED_2", "@_UNUSED_3", "@_UNUSED_4", "@_UNUSED_5", ] # ALL_SYMBOLS = ARPABET_SYMBOLS + PUNCTUATION + PIN_YIN_STARTS + PIN_YIN_ENDS + ["_"] ALL_SYMBOLS = ARPABET_SYMBOLS + PUNCTUATION + PIN_YIN_ENDS + EXTRA + ["", "_"] pinyin_to_arpabet_mappings = { "C": "TS", "E": "EH0", "H": "HH", "J": "ZH", "Q": "K", "X": "S", } def text_pinyin_to_pinyin_symbs (text): text = re.sub("āng", "ang1", text) text = re.sub("áng", "ang2", text) text = re.sub("ǎng", "ang3", text) text = re.sub("àng", "ang4", text) text = re.sub("ēng", "eng1", text) text = re.sub("éng", "eng2", text) text = re.sub("ěng", "eng3", text) text = re.sub("èng", "eng4", text) text = re.sub("īng", "ing1", text) text = re.sub("íng", "ing2", text) text = re.sub("ǐng", "ing3", text) text = re.sub("ìng", "ing4", text) text = re.sub("ōng", "ong1", text) text = re.sub("óng", "ong2", text) text = re.sub("ǒng", "ong3", text) text = re.sub("òng", "ong4", text) text = re.sub("ān", "an1", text) text = re.sub("án", "an2", text) text = re.sub("ǎn", "an3", text) text = re.sub("àn", "an4", text) text = re.sub("ēn", "en1", text) text = re.sub("én", "en2", text) text = re.sub("ěn", "en3", text) text = re.sub("èn", "en4", text) text = re.sub("īn", "in1", text) text = re.sub("ín", "in2", text) text = re.sub("ǐn", "in3", text) text = re.sub("ìn", "in4", text) text = re.sub("ūn", "un1", text) text = re.sub("ún", "un2", text) text = re.sub("ǔn", "un3", text) text = re.sub("ùn", "un4", text) text = re.sub("ér", "er2", text) text = re.sub("ěr", "er3", text) text = re.sub("èr", "er4", text) text = re.sub("āo", "aō", text) text = re.sub("áo", "aó", text) text = re.sub("ǎo", "aǒ", text) text = re.sub("ào", "aò", text) text = re.sub("ōu", "oū", text) text = re.sub("óu", "oú", text) text = re.sub("ǒu", "oǔ", text) text = re.sub("òu", "où", text) text = re.sub("āi", "aī", text) text = re.sub("ái", "aí", text) text = re.sub("ǎi", "aǐ", text) text = re.sub("ài", "aì", text) text = re.sub("ēi", "eī", text) text = re.sub("éi", "eí", text) text = re.sub("ěi", "eǐ", text) text = re.sub("èi", "eì", text) text = re.sub("ā", "a1", text) text = re.sub("á", "a2", text) text = re.sub("ǎ", "a3", text) text = re.sub("à", "a4", text) text = re.sub("ē", "e1", text) text = re.sub("é", "e2", text) text = re.sub("ě", "e3", text) text = re.sub("è", "e4", text) text = re.sub("ī", "i1", text) text = re.sub("í", "i2", text) text = re.sub("ǐ", "i3", text) text = re.sub("ì", "i4", text) text = re.sub("ō", "o1", text) text = re.sub("ó", "o2", text) text = re.sub("ǒ", "o3", text) text = re.sub("ò", "o4", text) text = re.sub("ū", "u1", text) text = re.sub("ú", "u2", text) text = re.sub("ǔ", "u3", text) text = re.sub("ù", "u4", text) text = re.sub("ǖ", "ü1", text) text = re.sub("ǘ", "ü2", text) text = re.sub("ǚ", "ü3", text) text = re.sub("ǜ", "ü4", text) text = re.sub("ng1a", "n1ga", text) text = re.sub("ng2a", "n2ga", text) text = re.sub("ng3a", "n3ga", text) text = re.sub("ng4a", "n4ga", text) text = re.sub("ng1e", "n1ge", text) text = re.sub("ng2e", "n2ge", text) text = re.sub("ng3e", "n3ge", text) text = re.sub("ng4e", "n4ge", text) text = re.sub("ng1o", "n1go", text) text = re.sub("ng2o", "n2go", text) text = re.sub("ng3o", "n3go", text) text = re.sub("ng4o", "n4go", text) text = re.sub("ng1u", "n1gu", text) text = re.sub("ng2u", "n2gu", text) text = re.sub("ng3u", "n3gu", text) text = re.sub("ng4u", "n4gu", text) text = re.sub("n1ang", "1nang", text) text = re.sub("n2ang", "2nang", text) text = re.sub("n3ang", "3nang", text) text = re.sub("n4ang", "4nang", text) text = re.sub("n1eng", "1neng", text) text = re.sub("n2eng", "2neng", text) text = re.sub("n3eng", "3neng", text) text = re.sub("n4eng", "4neng", text) text = re.sub("n1ing", "1ning", text) text = re.sub("n2ing", "2ning", text) text = re.sub("n3ing", "3ning", text) text = re.sub("n4ing", "4ning", text) text = re.sub("n1ong", "1nong", text) text = re.sub("n2ong", "2nong", text) text = re.sub("n3ong", "3nong", text) text = re.sub("n4ong", "4nong", text) text = re.sub("hen2an2", "he2nan2", text) text = re.sub("hun2an2", "hu2nan2", text) text = re.sub("zhun2an2", "zhu2nan2", text) text = re.sub("hun3an2", "hu3nan2", text) text = re.sub("lin3an2", "li3nan2", text) text = re.sub("zhin3an2", "zhi3nan2", text) text = re.sub("bun4an2", "bu4nan2", text) text = re.sub("chin4an2", "chi4nan2", text) text = re.sub("shin4an2", "shi4nan2", text) text = re.sub("man3an3", "ma3nan3", text) text = re.sub("ban1en4", "ba1nen4", text) text = re.sub("jin2an4", "ji2nan4", text) text = re.sub("yin2an4", "yi2nan4", text) text = re.sub("hen2an4", "he2nan4", text) text = re.sub("lin2an4", "li2nan4", text) text = re.sub("zen2an4", "ze2nan4", text) text = re.sub("kun3an4", "ku3nan4", text) text = re.sub("sin3an4", "si3nan4", text) text = re.sub("yun4an4", "yu4nan4", text) text = re.sub("qun4ian2", "qu4nian2", text) text = re.sub("ner4en3", "ne4ren3", text) text = re.sub("er4an2", "e4ran2", text) text = re.sub("ger4en2", "ge4ren2", text) text = re.sub("her2en2", "he2ren2", text) text = re.sub("zher2en2", "zhe2ren2", text) text = re.sub("zer2en2", "ze2ren2", text) text = re.sub("zer2en4", "ze2ren4", text) text = re.sub("der2en2", "de2ren2", text) text = re.sub("ker4en2", "ke4ren2", text) text = re.sub("ser4en2", "se4ren2", text) text = re.sub("ker4an2", "ke4ran2", text) return text def _espeak_exe(base_dir, args: List, sync=False) -> List[str]: """Run espeak with the given arguments.""" # espeak_lib = f'F:/Speech/espeak/eSpeak/command_line/espeak.exe' # espeak_lib = f'C:/Program Files (x86)/eSpeak/command_line/espeak.exe' # espeak_lib = f'F:/Speech/espeak/eSpeak_NG/espeak-ng.exe' base_dir = base_dir.replace("\\", "/") if platform.system() == 'Linux': espeak_lib = 'espeak-ng' else: espeak_lib = f'{base_dir}/eSpeak_NG/espeak-ng.exe' cmd = [ espeak_lib, # f'--path="F:/Speech/espeak/eSpeak_NG"', f'--path="{base_dir}/eSpeak_NG"', "-q", "-b", "1", # UTF8 text encoding ] cmd.extend(args) # logging.debug("espeakng: executing %s", repr(cmd)) print("espeakng: executing %s", repr(" ".join(cmd))) os.makedirs("/usr/share/espeak-ng-data", exist_ok=True) if not os.path.exists("/usr/share/espeak-ng-data/phontab"): shutil.copytree(f'{base_dir}/eSpeak_NG/espeak-ng-data', "/usr/share/espeak-ng-data", dirs_exist_ok=True) # print(" ".join(cmd)) # print(f'F:/Speech/espeak/eSpeak_NG/espeak-ng.exe --path="F:/Speech/espeak/eSpeak_NG" -q -b 1 -v ro --ipa=1 "bună ziua. Ce mai faceți?"') # print("---") try: with subprocess.Popen( cmd, # cmd, # f'F:/Speech/espeak/eSpeak_NG/espeak-ng.exe --path="F:/Speech/espeak/eSpeak_NG" -q -b 1 -v ro --ipa=1 "bună ziua. Ce mai faceți?"', # F:/Speech/espeak/eSpeak_NG/espeak-ng.exe --path="F:/Speech/espeak/eSpeak_NG" -q -b 1 -v ro --ipa=1 "bună ziua. Ce mai faceți?" # F:/Speech/espeak/eSpeak_NG/espeak-ng.exe --path="F:/Speech/espeak/eSpeak_NG" -q -b 1 -v ro --ipa=1 "bună ziua. Ce mai faceți?" stdout=subprocess.PIPE, stderr=subprocess.STDOUT, ) as p: res = iter(p.stdout.readline, b"") if not sync: p.stdout.close() if p.stderr: p.stderr.close() if p.stdin: p.stdin.close() return res res2 = [] for line in res: res2.append(line) p.stdout.close() if p.stderr: p.stderr.close() if p.stdin: p.stdin.close() p.wait() return res2 except: print(f'espeak subprocess error. base_dir={base_dir}') raise class BasePhonemizer(abc.ABC): """Base phonemizer class Phonemization follows the following steps: 1. Preprocessing: - remove empty lines - remove punctuation - keep track of punctuation marks 2. Phonemization: - convert text to phonemes 3. Postprocessing: - join phonemes - restore punctuation marks Args: language (str): Language used by the phonemizer. punctuations (List[str]): List of punctuation marks to be preserved. keep_puncs (bool): Whether to preserve punctuation marks or not. """ # def __init__(self, language, punctuations=Punctuation.default_puncs(), keep_puncs=False): def __init__(self, language, punctuations=None, keep_puncs=False): # ensure the backend is installed on the system # if not self.is_available(): # raise RuntimeError("{} not installed on your system".format(self.name())) # pragma: nocover # ensure the backend support the requested language self._language = self._init_language(language) # setup punctuation processing self._keep_puncs = keep_puncs # self._punctuator = Punctuation(punctuations) def _init_language(self, language): """Language initialization This method may be overloaded in child classes (see Segments backend) """ # if not self.is_supported_language(language): # raise RuntimeError(f'language "{language}" is not supported by the ' f"{self.name()} backend") return language @property def language(self): """The language code configured to be used for phonemization""" return self._language # @staticmethod # @abc.abstractmethod # def name(): # """The name of the backend""" # ... # @classmethod # @abc.abstractmethod # def is_available(cls): # """Returns True if the backend is installed, False otherwise""" # ... @classmethod @abc.abstractmethod def version(cls): """Return the backend version as a tuple (major, minor, patch)""" ... @staticmethod @abc.abstractmethod def supported_languages(): """Return a dict of language codes -> name supported by the backend""" ... def is_supported_language(self, language): """Returns True if `language` is supported by the backend""" return language in self.supported_languages() @abc.abstractmethod def _phonemize(self, text, separator): """The main phonemization method""" def _phonemize_preprocess(self, text) -> Tuple[List[str], List]: """Preprocess the text before phonemization 1. remove spaces 2. remove punctuation Override this if you need a different behaviour """ text = text.strip() # if self._keep_puncs: # a tuple (text, punctuation marks) # return self._punctuator.strip_to_restore(text) # return [self._punctuator.strip(text)], [] return [text], [] def _phonemize_postprocess(self, phonemized, punctuations) -> str: """Postprocess the raw phonemized output Override this if you need a different behaviour """ # if self._keep_puncs: # return self._punctuator.restore(phonemized, punctuations)[0] return phonemized[0] def phonemize(self, text: str, separator="|") -> str: """Returns the `text` phonemized for the given language Args: text (str): Text to be phonemized. separator (str): string separator used between phonemes. Default to '_'. Returns: (str): Phonemized text """ text, punctuations = self._phonemize_preprocess(text) phonemized = [] for t in text: p = self._phonemize(t, separator) phonemized.append(p) phonemized = self._phonemize_postprocess(phonemized, punctuations) # print("text", text) return phonemized def print_logs(self, level: int = 0): indent = "\t" * level print(f"{indent}| > phoneme language: {self.language}") print(f"{indent}| > phoneme backend: {self.name()}") class ESpeak(BasePhonemizer): """ESpeak wrapper calling `espeak` or `espeak-ng` from the command-line the perform G2P Args: language (str): Valid language code for the used backend. backend (str): Name of the backend library to use. `espeak` or `espeak-ng`. If None, set automatically prefering `espeak-ng` over `espeak`. Defaults to None. punctuations (str): Characters to be treated as punctuation. Defaults to Punctuation.default_puncs(). keep_puncs (bool): If True, keep the punctuations after phonemization. Defaults to True. Example: >>> from TTS.tts.utils.text.phonemizers import ESpeak >>> phonemizer = ESpeak("tr") >>> phonemizer.phonemize("Bu Türkçe, bir örnektir.", separator="|") 'b|ʊ t|ˈø|r|k|tʃ|ɛ, b|ɪ|r œ|r|n|ˈɛ|c|t|ɪ|r.' """ # _ESPEAK_LIB = _DEF_ESPEAK_LIB # def __init__(self, language: str, backend=None, punctuations=Punctuation.default_puncs(), keep_puncs=True): def __init__(self, base_dir, language: str, backend=None, punctuations=None, keep_puncs=True): self.base_dir = base_dir super().__init__(language, punctuations=punctuations, keep_puncs=keep_puncs) if backend is not None: self.backend = backend def phonemize_espeak(self, text: str, separator: str = "|", tie=False) -> str: """Convert input text to phonemes. Args: text (str): Text to be converted to phonemes. tie (bool, optional) : When True use a '͡' character between consecutive characters of a single phoneme. Else separate phoneme with '_'. This option requires espeak>=1.49. Default to False. """ # set arguments args = ["-v", f"{self._language}"] # espeak and espeak-ng parses `ipa` differently if tie: args.append("--ipa=1") else: args.append("--ipa=1") if tie: args.append("--tie=%s" % tie) args.append('"' + text + '"') # compute phonemes phonemes = "" for line in _espeak_exe(self.base_dir, args, sync=True): print("[phonemize_espeak] line", line) logging.debug("line: %s", repr(line)) # phonemes += line.decode("utf8").strip()[self.num_skip_chars :] # skip initial redundant characters phonemes += line.decode("utf8").strip() phonemes = re.sub(r"(\([a-z][a-z]\))", "", phonemes) return phonemes.replace("_", separator) def _phonemize(self, text, separator=None): return self.phonemize_espeak(text, separator, tie=False) @staticmethod def supported_languages(base_dir) -> Dict: """Get a dictionary of supported languages. Returns: Dict: Dictionary of language codes. """ # if _DEF_ESPEAK_LIB is None: # return {} args = ["--voices"] langs = {} count = 0 for line in _espeak_exe(base_dir, args, sync=True): print("[supported_languages] line", line) line = line.decode("utf8").strip() if count > 0: cols = line.split() lang_code = cols[1] lang_name = cols[3] langs[lang_code] = lang_name logging.debug("line: %s", repr(line)) count += 1 return langs def version(self) -> str: """Get the version of the used backend. Returns: str: Version of the used backend. """ args = ["--version"] for line in _espeak_exe(self.base_dir, args, sync=True): version = line.decode("utf8").strip().split()[2] logging.debug("line: %s", repr(line)) return version # ========================= # https://github.com/jhasegaw/phonecodes/blob/master/src/phonecode_tables.py def translate_string(s, d): '''(tl,ttf)=translate_string(s,d): Translate the string, s, using symbols from dict, d, as: 1. Min # untranslatable symbols, then 2. Min # symbols. tl = list of translated or untranslated symbols. ttf[n] = True if tl[n] was translated, else ttf[n]=False. ''' N = len(s) symcost = 1 # path cost per translated symbol oovcost = 10 # path cost per untranslatable symbol maxsym = max(len(k) for k in d.keys()) # max input symbol length # (pathcost to s[(n-m):n], n-m, translation[s[(n-m):m]], True/False) lattice = [ (0,0,'',True) ] for n in range(1,N+1): # Initialize on the assumption that s[n-1] is untranslatable lattice.append((oovcost+lattice[n-1][0],n-1,s[(n-1):n],False)) # Search for translatable sequences s[(n-m):n], and keep the best for m in range(1,min(n+1,maxsym+1)): if s[(n-m):n] in d and symcost+lattice[n-m][0] < lattice[n][0]: lattice[n] = (symcost+lattice[n-m][0],n-m,d[s[(n-m):n]],True) # Back-trace tl = [] translated = [] n = N while n > 0: tl.append(lattice[n][2]) translated.append(lattice[n][3]) n = lattice[n][1] return((tl[::-1], translated[::-1])) def attach_tones_to_vowels(il, tones, vowels, searchstep, catdir): '''Return a copy of il, with each tone attached to nearest vowel if any. searchstep=1 means search for next vowel, searchstep=-1 means prev vowel. catdir>=0 means concatenate after vowel, catdir<0 means cat before vowel. Tones are not combined, except those also included in the vowels set. ''' ol = il.copy() v = 0 if searchstep>0 else len(ol)-1 t = -1 while 0<=v and v1 and ol[v][0] in vowels)) and t>=0: ol[v]= ol[v]+ol[t] if catdir>=0 else ol[t]+ol[v] ol = ol[0:t] + ol[(t+1):] # Remove the tone t = -1 # Done with that tone if v {phone[len(arpabet_phone):]}') phones_final.append(arpabet_phone) phone = phone[len(arpabet_phone):] if phone in ARPABET_SYMBOLS: phones_final.append(phone) phone = "" # If there's anything left over, check to see if I've handled this through the manual key/value replacements # decided through manual inspection of unhandled phones if len(phone): # Replace numbered stress markers if outer_i>2: phone = phone.replace("0","").replace("1","").replace("2","").replace("3","") # print(f'phone 2, {phone}') if phone in manual_replace_dict.keys(): # print(f'manual_replace_dict[phone], {phone} -> {manual_replace_dict[phone]}') phones_final.append(manual_replace_dict[phone]) phone = "" else: # Check up to 3 times, in case there are multiple phones and/or the order is non-alphabetic for i in range(3): for manual_phone_key in manual_replace_dict.keys(): if len(phone) and phone.startswith(manual_phone_key): # print(f'manual_replace_dict[manual_phone_key], {phone} => {manual_phone_key} => {manual_replace_dict[manual_phone_key]} -> {phone[len(manual_phone_key):]}') phones_final.append(manual_replace_dict[manual_phone_key]) # phone = phone[len(manual_replace_dict[manual_phone_key]):] phone = phone[len(manual_phone_key):] if phone in manual_replace_dict.keys(): phones_final.append(manual_replace_dict[phone]) phone = "" # print(f'phones_final, {phones_final}') phones_final_post = [] for phone in phones_final: if phone in manual_phone_replacements.keys(): phones_final_post.append(manual_phone_replacements[phone]) else: phones_final_post.append(phone) # print(f'phones_final, {phones_final}') # print(f'phones_final_post, {phones_final_post}') return " ".join(phones_final_post)