From 4926faf42c18386f2b3904b3064f2b25e1113e87 Mon Sep 17 00:00:00 2001 From: Brandon Thomas Date: Sat, 2 Jul 2022 11:24:04 -0400 Subject: [PATCH] Work on phonetization --- tts/text/phonetization/base_phonetization.py | 15 ++++++ .../phonetization/english_phonetization.py | 24 ++++++++++ .../test_english_phonetization.py | 22 +++++++++ tts/text/pipeline.py | 46 +++++++++++++++++++ 4 files changed, 107 insertions(+) create mode 100644 tts/text/phonetization/base_phonetization.py create mode 100644 tts/text/phonetization/english_phonetization.py create mode 100644 tts/text/phonetization/test_english_phonetization.py create mode 100644 tts/text/pipeline.py diff --git a/tts/text/phonetization/base_phonetization.py b/tts/text/phonetization/base_phonetization.py new file mode 100644 index 0000000..73f4115 --- /dev/null +++ b/tts/text/phonetization/base_phonetization.py @@ -0,0 +1,15 @@ + +import re +from typing import List +from tts.text.phonetization.cmudict import CMUDict + +# Regex taken from open source tacotron2 extensions +_arpabet_split_re = re.compile(r'[^\s]*{.*?}[^\s]*|[\w]+|[^\w\s]+') + +class BasePhonetization: + def __init__(self, phoneme_dictionary: CMUDict): + self.phoneme_dictionary = phoneme_dictionary + + def _split_grapheme_sentence_to_words(self, text_sequence: str) -> List[str]: + """Split a grapheme word sequence to individual grapheme words""" + return [word for word in _arpabet_split_re.findall(text_sequence)] diff --git a/tts/text/phonetization/english_phonetization.py b/tts/text/phonetization/english_phonetization.py new file mode 100644 index 0000000..5c48aac --- /dev/null +++ b/tts/text/phonetization/english_phonetization.py @@ -0,0 +1,24 @@ +from typing import List +from tts.text.phonetization.base_phonetization import BasePhonetization +from tts.text.phonetization.cmudict import CMUDict + +class EnglishPhonetization(BasePhonetization): + + def __init__(self, phoneme_dictionary: CMUDict): + super().__init__(phoneme_dictionary) + + def convert_to_phonemes(self, grapheme_sequence: str) -> List[str]: + words = self._split_grapheme_sentence_to_words(grapheme_sequence) + converted_words = [] + + for word in words: + maybe_phonemes = self.phoneme_dictionary.lookup(word) + if maybe_phonemes: + # NB: Dictionary returns a list of possible matches. + # We'll just use the first. + converted_words.append(maybe_phonemes[0]) + else: + # TODO: Use g2p prediction to predict the phonemes. + converted_words.append(word) + + return converted_words diff --git a/tts/text/phonetization/test_english_phonetization.py b/tts/text/phonetization/test_english_phonetization.py new file mode 100644 index 0000000..28662ea --- /dev/null +++ b/tts/text/phonetization/test_english_phonetization.py @@ -0,0 +1,22 @@ +from tts.text.phonetization.cmudict import DEFAULT_CMUDICT_PATH, CMUDict +from tts.text.phonetization.english_phonetization import EnglishPhonetization + +class TestEnglishPhonetization: + + def test_no_input(self): + phoneme_dictionary = CMUDict(DEFAULT_CMUDICT_PATH) + phonetization = EnglishPhonetization(phoneme_dictionary) + output = phonetization.convert_to_phonemes("") + assert output == [] + + def test_all_words_in_dictionary(self): + phoneme_dictionary = CMUDict(DEFAULT_CMUDICT_PATH) + phonetization = EnglishPhonetization(phoneme_dictionary) + output = phonetization.convert_to_phonemes("this is a test") + assert output == ["DH IH1 S", "IH1 Z", "AH0", "T EH1 S T"] + + def test_most_words_in_dictionary(self): + phoneme_dictionary = CMUDict(DEFAULT_CMUDICT_PATH) + phonetization = EnglishPhonetization(phoneme_dictionary) + output = phonetization.convert_to_phonemes("nonword is a test") + assert output == ["nonword", "IH1 Z", "AH0", "T EH1 S T"] diff --git a/tts/text/pipeline.py b/tts/text/pipeline.py new file mode 100644 index 0000000..de486a6 --- /dev/null +++ b/tts/text/pipeline.py @@ -0,0 +1,46 @@ +from enum import Enum, auto + +from tts.text.input_parsing.parser import GraphemeSquence, parse_grapheme_and_arpabet_sequence +from tts.text.normalization.english_normalization import EnglishNormalization +from tts.text.normalization.spanish_normalization import SpanishNormalization + + +class PipelineType(Enum): + LEGACY_FAKEYOU = auto() + ENGLISH_FORCED_ARPABET_V1 = auto() + SPANISH_FORCED_ARPABET_V1 = auto() + +_ENGLISH_NORMALIZER = EnglishNormalization() +_SPANISH_NORMALIZER = SpanishNormalization() + +def _legacy_fakeyou_pipeline(input_text): + """ + Legacy FakeYou.com did not do Grapheme -> Arpabet conversions automatically. + """ + pass + + +def _english_forced_arpabet_v1(input_text): + parsed_sequence = parse_grapheme_and_arpabet_sequence(input_text) + for i in range(len(parsed_sequence)): + subsequence = parsed_sequence[i] + if isinstance(subsequence, GraphemeSquence): + normalized = _ENGLISH_NORMALIZER.normalize(subsequence.sequence) + parsed_sequence[i] = GraphemeSquence(normalized) + + integer_sequence = [] + + + +def _spanish_forced_arpabet_v1(input_text): + parsed_sequence = parse_grapheme_and_arpabet_sequence(input_text) + + +def text_to_sequence(input_text: str, pipeline_type: PipelineType): + if pipeline_type == PipelineType.ENGLISH_FORCED_ARPABET_V1: + return _english_forced_arpabet_v1(input_text) + elif pipeline_type == PipelineType.SPANISH_FORCED_ARPABET_V1: + return _spanish_forced_arpabet_v1(input_text) + else: + return _legacy_fakeyou_pipeline(input_text) +