mirror of
https://github.com/storytold/storyteller-ml.git
synced 2026-10-27 08:58:13 +00:00
Entirely reworked text handling pipeline
This breaks the text pipeline into the following pieces: - input parsing (split known bracketed phoneme sections out first) - normalization (fix spacing, numbers, abbreviations, etc.) - phonetization (do CMUdict lookup or g2p prediction) - sequencing (convert the symbolic representation to integer representation) Each "pipeline" can have a unique combination of operations for each of these pipeline steps. This will grow over time.
This commit is contained in:
@@ -13,6 +13,13 @@ The following files are entrypoints and an essential part of FakeYou.com functio
|
|||||||
files).
|
files).
|
||||||
|
|
||||||
|
|
||||||
|
To Run Tests
|
||||||
|
------------
|
||||||
|
|
||||||
|
```
|
||||||
|
pytest text/
|
||||||
|
```
|
||||||
|
|
||||||
Vocodes Server Parameters
|
Vocodes Server Parameters
|
||||||
-------------------------
|
-------------------------
|
||||||
|
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,10 @@
|
|||||||
|
# For pytest
|
||||||
|
|
||||||
|
[build-system]
|
||||||
|
requires = ["setuptools>=42", "wheel"]
|
||||||
|
build-backend = "setuptools.build_meta"
|
||||||
|
|
||||||
|
[tool.pytest.ini_options]
|
||||||
|
pythonpath = [
|
||||||
|
"."
|
||||||
|
]
|
||||||
@@ -0,0 +1,39 @@
|
|||||||
|
{
|
||||||
|
"include": [
|
||||||
|
"text/"
|
||||||
|
],
|
||||||
|
|
||||||
|
"exclude": [
|
||||||
|
".git/",
|
||||||
|
"**/*test_*.py",
|
||||||
|
"**/node_modules",
|
||||||
|
"**/__pycache__",
|
||||||
|
"NeMo/",
|
||||||
|
"hifi-gan/",
|
||||||
|
"hifigan/",
|
||||||
|
"nemo/",
|
||||||
|
"phonetics",
|
||||||
|
"python/",
|
||||||
|
"tacotron2/",
|
||||||
|
"temp/",
|
||||||
|
"waveglow/"
|
||||||
|
],
|
||||||
|
|
||||||
|
"ignore": [
|
||||||
|
"tacotron2/",
|
||||||
|
"**/*test_*.py"
|
||||||
|
],
|
||||||
|
|
||||||
|
"defineConstant": {
|
||||||
|
},
|
||||||
|
|
||||||
|
"venvPath": "python/",
|
||||||
|
|
||||||
|
"reportMissingImports": true,
|
||||||
|
"reportMissingTypeStubs": true,
|
||||||
|
|
||||||
|
"pythonVersion": "3.6",
|
||||||
|
"pythonPlatform": "Linux",
|
||||||
|
|
||||||
|
"typeCheckingMode": "strict"
|
||||||
|
}
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
# For pytest
|
||||||
|
|
||||||
|
[metadata]
|
||||||
|
name = tts
|
||||||
|
|
||||||
|
[options]
|
||||||
|
packages = find:
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
from typing import List
|
||||||
|
|
||||||
|
class BasePipeline:
|
||||||
|
|
||||||
|
def user_input_to_sequence(self, input_text: str) -> List[int]:
|
||||||
|
"""Convert raw user input into a numerical sequence for ML inference."""
|
||||||
|
return []
|
||||||
@@ -0,0 +1,74 @@
|
|||||||
|
from typing import List
|
||||||
|
from text.pipeline.base_pipeline import BasePipeline
|
||||||
|
from text.pipeline_components.input_parsing.parser import ArpabetSequence, GraphemeSequence, ParsedSequence, parse_grapheme_and_arpabet_sequence
|
||||||
|
from text.pipeline_components.normalization.english_normalization import normalize_english
|
||||||
|
from text.pipeline_components.phonetization.base_phonetization import BasePhonetization
|
||||||
|
from text.pipeline_components.phonetization.cmudict import DEFAULT_CMUDICT_PATH, CMUDict
|
||||||
|
from text.pipeline_components.phonetization.english_phonetization import EnglishPhonetization
|
||||||
|
from text.pipeline_components.sequencing.english_symbols_v1 import SPACE_INTEGER_ENCODING, arpabet_to_sequence, symbols_to_sequence
|
||||||
|
|
||||||
|
class EnglishPipelineV1(BasePipeline):
|
||||||
|
"""
|
||||||
|
This is a new text pipeline for FakeYou that will be introduced in July 2022.
|
||||||
|
It introduces Arpabet and individually upgradable components.
|
||||||
|
The code is not 1:1 with our notebooks, but we'll go to efforts to make sure that
|
||||||
|
the model input expectations on both sides match one another.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, phonetization: BasePhonetization):
|
||||||
|
self.phonetization = phonetization
|
||||||
|
|
||||||
|
def user_input_to_sequence(self, input_text: str) -> List[int]:
|
||||||
|
"""Convert raw user input into a numerical sequence for ML inference."""
|
||||||
|
|
||||||
|
parsed_sequence = parse_grapheme_and_arpabet_sequence(input_text)
|
||||||
|
self._normalize_graphemes(parsed_sequence)
|
||||||
|
|
||||||
|
parsed_sequence = self._convert_phonemes(parsed_sequence)
|
||||||
|
|
||||||
|
return self._convert_to_integer_sequence(parsed_sequence)
|
||||||
|
|
||||||
|
def _normalize_graphemes(self, parsed_sequence: ParsedSequence):
|
||||||
|
for i in range(len(parsed_sequence)):
|
||||||
|
subsequence = parsed_sequence[i]
|
||||||
|
if isinstance(subsequence, GraphemeSequence):
|
||||||
|
normalized = normalize_english(subsequence.sequence)
|
||||||
|
parsed_sequence[i] = GraphemeSequence(normalized)
|
||||||
|
|
||||||
|
|
||||||
|
def _convert_phonemes(self, parsed_sequence: ParsedSequence) -> ParsedSequence:
|
||||||
|
# NB: We will likely break grapheme runs into multiple arpabet sequences, extending the
|
||||||
|
# output list length.
|
||||||
|
updated_sequence = []
|
||||||
|
|
||||||
|
for subsequence in parsed_sequence:
|
||||||
|
if isinstance(subsequence, ArpabetSequence):
|
||||||
|
updated_sequence.append(subsequence)
|
||||||
|
|
||||||
|
elif isinstance(subsequence, GraphemeSequence):
|
||||||
|
converted_sequence = self.phonetization.try_convert_to_phonemes(subsequence.sequence)
|
||||||
|
updated_sequence.extend(converted_sequence)
|
||||||
|
|
||||||
|
return updated_sequence
|
||||||
|
|
||||||
|
def _convert_to_integer_sequence(self, parsed_sequence: ParsedSequence) -> List[int]:
|
||||||
|
integer_sequence = []
|
||||||
|
last_index = len(parsed_sequence) - 1
|
||||||
|
|
||||||
|
for i, subsequence in enumerate(parsed_sequence):
|
||||||
|
integer_subsequence = []
|
||||||
|
|
||||||
|
if isinstance(subsequence, ArpabetSequence):
|
||||||
|
integer_subsequence = arpabet_to_sequence(subsequence.sequence)
|
||||||
|
elif isinstance(subsequence, GraphemeSequence):
|
||||||
|
integer_subsequence = symbols_to_sequence(subsequence.sequence)
|
||||||
|
|
||||||
|
if integer_subsequence:
|
||||||
|
integer_sequence.extend(integer_subsequence)
|
||||||
|
|
||||||
|
if i < last_index and integer_sequence[-1] != SPACE_INTEGER_ENCODING:
|
||||||
|
# Separate words with single spaces if not already present.
|
||||||
|
integer_sequence.append(SPACE_INTEGER_ENCODING)
|
||||||
|
|
||||||
|
return integer_sequence
|
||||||
|
|
||||||
@@ -0,0 +1,74 @@
|
|||||||
|
import re
|
||||||
|
from typing import List
|
||||||
|
from text.pipeline.base_pipeline import BasePipeline
|
||||||
|
from text.pipeline_components.normalization.common_cleaners import collapse_whitespace, convert_to_ascii, lowercase
|
||||||
|
from text.pipeline_components.normalization.english_cleaners import expand_abbreviations, expand_numbers
|
||||||
|
from text.pipeline_components.sequencing.english_symbols_v1 import arpabet_to_sequence, symbols_to_sequence
|
||||||
|
|
||||||
|
# Regular expression matching text enclosed in curly braces:
|
||||||
|
_curly_re = re.compile(r'(.*?)\{(.+?)\}(.*)')
|
||||||
|
|
||||||
|
class LegacyFakeYouPipeline(BasePipeline):
|
||||||
|
"""
|
||||||
|
This pipeline was in use from June 2021 to July 2022.
|
||||||
|
In contrast to legacy "vo.codes" (2020 - 2021), it does not
|
||||||
|
leverage CMUDict to look up arpabet even when models were known to
|
||||||
|
be trained with arpabet support. (It did support curly braces for
|
||||||
|
manual arpabet inclusion.)
|
||||||
|
"""
|
||||||
|
|
||||||
|
def user_input_to_sequence(self, input_text: str) -> List[int]:
|
||||||
|
"""Convert raw user input into a numerical sequence for ML inference."""
|
||||||
|
pre_sequence = self._preprocess_text(input_text)
|
||||||
|
return self._text_to_sequence(pre_sequence, ['english_cleaners'])
|
||||||
|
|
||||||
|
def _preprocess_text(self, input_text: str, is_raw_input: bool = True) -> str:
|
||||||
|
pre_sequence = ""
|
||||||
|
for line in input_text.split("\n"):
|
||||||
|
if len(line) < 1:
|
||||||
|
continue
|
||||||
|
if is_raw_input:
|
||||||
|
if line[-1] != ";":
|
||||||
|
line = line + ";"
|
||||||
|
else:
|
||||||
|
pass
|
||||||
|
pre_sequence += line
|
||||||
|
return pre_sequence
|
||||||
|
|
||||||
|
|
||||||
|
def _text_to_sequence(self, text, cleaner_names):
|
||||||
|
'''Converts a string of text to a sequence of IDs corresponding to the symbols in the text.
|
||||||
|
|
||||||
|
The text can optionally have ARPAbet sequences enclosed in curly braces embedded
|
||||||
|
in it. For example, "Turn left on {HH AW1 S S T AH0 N} Street."
|
||||||
|
|
||||||
|
Args:
|
||||||
|
text: string to convert to a sequence
|
||||||
|
cleaner_names: names of the cleaner functions to run the text through
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
List of integers corresponding to the symbols in the text
|
||||||
|
'''
|
||||||
|
sequence = []
|
||||||
|
|
||||||
|
# Check for curly braces and treat their contents as ARPAbet:
|
||||||
|
while len(text):
|
||||||
|
m = _curly_re.match(text)
|
||||||
|
if not m:
|
||||||
|
sequence += symbols_to_sequence(english_cleaners(text))
|
||||||
|
break
|
||||||
|
sequence += symbols_to_sequence(english_cleaners(m.group(1)))
|
||||||
|
sequence += arpabet_to_sequence(m.group(2))
|
||||||
|
text = m.group(3)
|
||||||
|
|
||||||
|
return sequence
|
||||||
|
|
||||||
|
|
||||||
|
def english_cleaners(text):
|
||||||
|
'''Pipeline for English text, including number and abbreviation expansion.'''
|
||||||
|
text = convert_to_ascii(text)
|
||||||
|
text = lowercase(text)
|
||||||
|
text = expand_numbers(text)
|
||||||
|
text = expand_abbreviations(text)
|
||||||
|
text = collapse_whitespace(text)
|
||||||
|
return text
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
# TODO: In the future we may allow uploads of legacy vocodes models.
|
||||||
|
# These models utilize a very unique encoding scheme.
|
||||||
@@ -0,0 +1,65 @@
|
|||||||
|
from text.pipeline.english_pipeline_v1 import EnglishPipelineV1
|
||||||
|
from text.pipeline_components.phonetization.cmudict import DEFAULT_CMUDICT_PATH, CMUDict
|
||||||
|
from text.pipeline_components.phonetization.english_phonetization import EnglishPhonetization
|
||||||
|
|
||||||
|
|
||||||
|
class TestEnglishPipelineV1:
|
||||||
|
|
||||||
|
def build_pipeline_instance(self):
|
||||||
|
# NB: CMUDict used in production may differ
|
||||||
|
cmudict = CMUDict(DEFAULT_CMUDICT_PATH)
|
||||||
|
phonetization = EnglishPhonetization(cmudict)
|
||||||
|
return EnglishPipelineV1(phonetization)
|
||||||
|
|
||||||
|
def test_no_input(self):
|
||||||
|
pipeline = self.build_pipeline_instance()
|
||||||
|
output = pipeline.user_input_to_sequence("")
|
||||||
|
assert output == []
|
||||||
|
|
||||||
|
def test_whitespace(self):
|
||||||
|
pipeline = self.build_pipeline_instance()
|
||||||
|
output = pipeline.user_input_to_sequence(" \n \t ")
|
||||||
|
assert output == []
|
||||||
|
|
||||||
|
def test_single_word(self):
|
||||||
|
pipeline = self.build_pipeline_instance()
|
||||||
|
output = pipeline.user_input_to_sequence("hello")
|
||||||
|
assert output == [106, 73, 117, 123] # NB: Arpabet encoded
|
||||||
|
|
||||||
|
def test_two_words(self):
|
||||||
|
pipeline = self.build_pipeline_instance()
|
||||||
|
output = pipeline.user_input_to_sequence("hello hello")
|
||||||
|
assert output == [
|
||||||
|
106, 73, 117, 123, # NB: Arpabet encoded
|
||||||
|
11, # Space
|
||||||
|
106, 73, 117, 123, # Arpabet encoded
|
||||||
|
]
|
||||||
|
|
||||||
|
def test_two_words_with_arpabet_segment(self):
|
||||||
|
pipeline = self.build_pipeline_instance()
|
||||||
|
output = pipeline.user_input_to_sequence("hello {HH AH0 L OW1}")
|
||||||
|
assert output == [
|
||||||
|
106, 73, 117, 123, # NB: Arpabet encoded
|
||||||
|
11, # Space
|
||||||
|
106, 73, 117, 123, # Manually arpabet encoded
|
||||||
|
]
|
||||||
|
|
||||||
|
def test_words_and_punctuation(self):
|
||||||
|
pipeline = self.build_pipeline_instance()
|
||||||
|
output = pipeline.user_input_to_sequence("hello friends, welcome!")
|
||||||
|
assert output == [
|
||||||
|
106, 73, 117, 123, # NB: Arpabet encoded
|
||||||
|
11, # Space
|
||||||
|
104, 130, 94, 119, 90, 146,
|
||||||
|
11, # Space (TODO: Get rid of extraneous spaces with richer parsing)
|
||||||
|
6, # Comma
|
||||||
|
11, # Space
|
||||||
|
144, 94, 117, 116, 73, 118,
|
||||||
|
11, # Space
|
||||||
|
2, # Exclamation mark
|
||||||
|
]
|
||||||
|
|
||||||
|
def test_apostrophe_word(self):
|
||||||
|
pipeline = self.build_pipeline_instance()
|
||||||
|
output = pipeline.user_input_to_sequence("you're")
|
||||||
|
assert output == [145, 137, 130] # YOU'RE Y UH1 R ~ 145 137 130
|
||||||
@@ -0,0 +1,42 @@
|
|||||||
|
|
||||||
|
from text.pipeline.legacy_fakeyou_pipeline import LegacyFakeYouPipeline
|
||||||
|
|
||||||
|
|
||||||
|
class TestLegacyFakeYouPipeline():
|
||||||
|
|
||||||
|
def test_no_input(self):
|
||||||
|
pipeline = LegacyFakeYouPipeline()
|
||||||
|
output = pipeline.user_input_to_sequence("")
|
||||||
|
assert output == []
|
||||||
|
|
||||||
|
def test_single_word(self):
|
||||||
|
pipeline = LegacyFakeYouPipeline()
|
||||||
|
output = pipeline.user_input_to_sequence("hello")
|
||||||
|
# NB: Graphemes remains ASCII! No CMUDict lookup or phoneme prediction.
|
||||||
|
# Also note the ending ";".
|
||||||
|
assert output == [
|
||||||
|
45, 42, 49, 49, 52, # "h e l l o"
|
||||||
|
9, # Semicolon line ender
|
||||||
|
]
|
||||||
|
|
||||||
|
def test_two_words(self):
|
||||||
|
pipeline = LegacyFakeYouPipeline()
|
||||||
|
output = pipeline.user_input_to_sequence("hello hello")
|
||||||
|
# NB: Graphemes remains ASCII! No CMUDict lookup or phoneme prediction.
|
||||||
|
assert output == [
|
||||||
|
45, 42, 49, 49, 52, # "h e l l o"
|
||||||
|
11, # Space
|
||||||
|
45, 42, 49, 49, 52, # "h e l l o"
|
||||||
|
9, # Semicolon line ender
|
||||||
|
]
|
||||||
|
|
||||||
|
def test_two_words_with_arpabet_segment(self):
|
||||||
|
pipeline = LegacyFakeYouPipeline()
|
||||||
|
output = pipeline.user_input_to_sequence("hello {HH AH0 L OW1}")
|
||||||
|
# NB: Graphemes remains ASCII! No CMUDict lookup or phoneme prediction.
|
||||||
|
assert output == [
|
||||||
|
45, 42, 49, 49, 52, # "h e l l o"
|
||||||
|
11, # Space
|
||||||
|
106, 73, 117, 123, # Legacy FakeYou supports manual arpabet inclusion
|
||||||
|
9, # Semicolon line ender
|
||||||
|
]
|
||||||
@@ -0,0 +1,72 @@
|
|||||||
|
"""
|
||||||
|
Parse raw text sequences into Arpabet and Grapheme sequences so that they can be
|
||||||
|
treated independently.
|
||||||
|
|
||||||
|
Inspired by and including code from original Tacotron2 by Keith Ito.
|
||||||
|
"""
|
||||||
|
|
||||||
|
# pyright: strict
|
||||||
|
|
||||||
|
import re
|
||||||
|
from typing import List, Union, Any
|
||||||
|
|
||||||
|
|
||||||
|
# Regular expression matching text enclosed in curly braces:
|
||||||
|
_curly_re = re.compile(r'(.*?)\{(.+?)\}(.*)')
|
||||||
|
|
||||||
|
|
||||||
|
class ArpabetSequence:
|
||||||
|
"""
|
||||||
|
Denotes an Arpabet phoneme sequence corresponding to a single word.
|
||||||
|
"""
|
||||||
|
def __init__(self, sequence: str):
|
||||||
|
self.sequence = sequence
|
||||||
|
def __eq__(self, other: Any) -> bool:
|
||||||
|
return isinstance(other, ArpabetSequence) and self.sequence == other.sequence
|
||||||
|
def __repr__(self):
|
||||||
|
return f"<ArpabetSequence: {self.sequence}>"
|
||||||
|
|
||||||
|
|
||||||
|
class GraphemeSequence:
|
||||||
|
"""
|
||||||
|
Denotes any string of non-Arpabet text, which may include multiple words,
|
||||||
|
spacing, and punctuation characters.
|
||||||
|
"""
|
||||||
|
def __init__(self, sequence: str):
|
||||||
|
self.sequence = sequence
|
||||||
|
def __eq__(self, other: Any) -> bool:
|
||||||
|
return isinstance(other, GraphemeSequence) and self.sequence == other.sequence
|
||||||
|
def __repr__(self):
|
||||||
|
return f"<GraphemeSequence: {self.sequence}>"
|
||||||
|
|
||||||
|
|
||||||
|
ParsedSequence = List[Union[ArpabetSequence, GraphemeSequence]]
|
||||||
|
|
||||||
|
|
||||||
|
def parse_grapheme_and_arpabet_sequence(input_text: str) -> ParsedSequence:
|
||||||
|
"""
|
||||||
|
Parse a string into a list of ArpabetSequence and GraphemeSequence so that each
|
||||||
|
subsequence can be treated independently.
|
||||||
|
"""
|
||||||
|
sequence : ParsedSequence = []
|
||||||
|
|
||||||
|
# Check for curly braces and treat their contents as ARPAbet:
|
||||||
|
while len(input_text):
|
||||||
|
input_text = input_text.strip()
|
||||||
|
m = _curly_re.match(input_text)
|
||||||
|
if not m:
|
||||||
|
sequence.append(GraphemeSequence(input_text))
|
||||||
|
break
|
||||||
|
|
||||||
|
before = m.group(1).strip()
|
||||||
|
bracketed = m.group(2).strip()
|
||||||
|
after = m.group(3).strip()
|
||||||
|
|
||||||
|
if before:
|
||||||
|
sequence.append(GraphemeSequence(before))
|
||||||
|
if bracketed:
|
||||||
|
sequence.append(ArpabetSequence(bracketed))
|
||||||
|
|
||||||
|
input_text = after
|
||||||
|
|
||||||
|
return sequence
|
||||||
@@ -0,0 +1,45 @@
|
|||||||
|
from text.pipeline_components.input_parsing.parser import ArpabetSequence, GraphemeSequence, parse_grapheme_and_arpabet_sequence
|
||||||
|
|
||||||
|
class TestParser:
|
||||||
|
|
||||||
|
def test_only_text(self):
|
||||||
|
parsed = parse_grapheme_and_arpabet_sequence("This is a test")
|
||||||
|
assert len(parsed) == 1
|
||||||
|
assert isinstance(parsed[0], GraphemeSequence)
|
||||||
|
|
||||||
|
def test_only_arpabet(self):
|
||||||
|
parsed = parse_grapheme_and_arpabet_sequence("{T EH1 S T IH0 NG}")
|
||||||
|
assert len(parsed) == 1
|
||||||
|
assert isinstance(parsed[0], ArpabetSequence)
|
||||||
|
|
||||||
|
def test_text_and_arpabet(self):
|
||||||
|
parsed = parse_grapheme_and_arpabet_sequence("This is a {T EH1 S T}")
|
||||||
|
assert len(parsed) == 2
|
||||||
|
assert isinstance(parsed[0], GraphemeSequence)
|
||||||
|
assert isinstance(parsed[1], ArpabetSequence)
|
||||||
|
|
||||||
|
def test_text_and_arpabet_interleved_1(self):
|
||||||
|
parsed = parse_grapheme_and_arpabet_sequence("{DH IH1 S} is a {T EH1 S T}")
|
||||||
|
assert len(parsed) == 3
|
||||||
|
assert isinstance(parsed[0], ArpabetSequence)
|
||||||
|
assert isinstance(parsed[1], GraphemeSequence)
|
||||||
|
assert isinstance(parsed[2], ArpabetSequence)
|
||||||
|
|
||||||
|
def test_text_and_arpabet_interleved_2(self):
|
||||||
|
parsed = parse_grapheme_and_arpabet_sequence("Check out {DH IH1 S} {T EH1 S T} of the {S IH1 S T AH0 M}")
|
||||||
|
assert len(parsed) == 5
|
||||||
|
assert isinstance(parsed[0], GraphemeSequence)
|
||||||
|
assert isinstance(parsed[1], ArpabetSequence)
|
||||||
|
assert isinstance(parsed[2], ArpabetSequence)
|
||||||
|
assert isinstance(parsed[3], GraphemeSequence)
|
||||||
|
assert isinstance(parsed[4], ArpabetSequence)
|
||||||
|
|
||||||
|
def test_text_and_arpabet_interleved_2(self):
|
||||||
|
parsed = parse_grapheme_and_arpabet_sequence("Check out {DH IH1 S} {T EH1 S T} of the {S IH1 S T AH0 M}.")
|
||||||
|
assert len(parsed) == 6
|
||||||
|
assert isinstance(parsed[0], GraphemeSequence)
|
||||||
|
assert isinstance(parsed[1], ArpabetSequence)
|
||||||
|
assert isinstance(parsed[2], ArpabetSequence)
|
||||||
|
assert isinstance(parsed[3], GraphemeSequence)
|
||||||
|
assert isinstance(parsed[4], ArpabetSequence)
|
||||||
|
assert isinstance(parsed[5], GraphemeSequence)
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
"""
|
||||||
|
Copied or adapted from Keith Ito's Tacotron2 code.
|
||||||
|
https://github.com/keithito/tacotron
|
||||||
|
"""
|
||||||
|
|
||||||
|
import re
|
||||||
|
from unidecode import unidecode
|
||||||
|
|
||||||
|
# Regular expression matching whitespace:
|
||||||
|
_whitespace_re = re.compile(r'\s+')
|
||||||
|
|
||||||
|
def lowercase(text: str) -> str:
|
||||||
|
return text.lower()
|
||||||
|
|
||||||
|
def collapse_whitespace(text) -> str:
|
||||||
|
return re.sub(_whitespace_re, ' ', text)
|
||||||
|
|
||||||
|
def convert_to_ascii(text: str) -> str:
|
||||||
|
return unidecode(text)
|
||||||
|
|
||||||
|
def basic_cleaners(text: str) -> str:
|
||||||
|
'''Basic pipeline that lowercases and collapses whitespace without transliteration.'''
|
||||||
|
text = lowercase(text)
|
||||||
|
text = collapse_whitespace(text)
|
||||||
|
return text
|
||||||
|
|
||||||
|
def transliteration_cleaners(text: str) -> str:
|
||||||
|
'''Pipeline for non-English text that transliterates to ASCII.'''
|
||||||
|
text = convert_to_ascii(text)
|
||||||
|
text = lowercase(text)
|
||||||
|
text = collapse_whitespace(text)
|
||||||
|
return text
|
||||||
@@ -0,0 +1,102 @@
|
|||||||
|
""" from https://github.com/keithito/tacotron """
|
||||||
|
|
||||||
|
import inflect
|
||||||
|
import re
|
||||||
|
|
||||||
|
# List of (regular expression, replacement) pairs for abbreviations:
|
||||||
|
_abbreviations = [(re.compile('\\b%s\\.' % x[0], re.IGNORECASE), x[1]) for x in [
|
||||||
|
('mrs', 'misess'),
|
||||||
|
('mr', 'mister'),
|
||||||
|
('dr', 'doctor'),
|
||||||
|
('st', 'saint'),
|
||||||
|
('co', 'company'),
|
||||||
|
('jr', 'junior'),
|
||||||
|
('maj', 'major'),
|
||||||
|
('gen', 'general'),
|
||||||
|
('drs', 'doctors'),
|
||||||
|
('rev', 'reverend'),
|
||||||
|
('lt', 'lieutenant'),
|
||||||
|
('hon', 'honorable'),
|
||||||
|
('sgt', 'sergeant'),
|
||||||
|
('capt', 'captain'),
|
||||||
|
('esq', 'esquire'),
|
||||||
|
('ltd', 'limited'),
|
||||||
|
('col', 'colonel'),
|
||||||
|
('ft', 'fort'),
|
||||||
|
]]
|
||||||
|
|
||||||
|
|
||||||
|
def expand_abbreviations(text):
|
||||||
|
for regex, replacement in _abbreviations:
|
||||||
|
text = re.sub(regex, replacement, text)
|
||||||
|
return text
|
||||||
|
|
||||||
|
|
||||||
|
_inflect = inflect.engine()
|
||||||
|
_comma_number_re = re.compile(r'([0-9][0-9\,]+[0-9])')
|
||||||
|
_decimal_number_re = re.compile(r'([0-9]+\.[0-9]+)')
|
||||||
|
_pounds_re = re.compile(r'£([0-9\,]*[0-9]+)')
|
||||||
|
_dollars_re = re.compile(r'\$([0-9\.\,]*[0-9]+)')
|
||||||
|
_ordinal_re = re.compile(r'[0-9]+(st|nd|rd|th)')
|
||||||
|
_number_re = re.compile(r'[0-9]+')
|
||||||
|
|
||||||
|
|
||||||
|
def _remove_commas(m):
|
||||||
|
return m.group(1).replace(',', '')
|
||||||
|
|
||||||
|
|
||||||
|
def _expand_decimal_point(m):
|
||||||
|
return m.group(1).replace('.', ' point ')
|
||||||
|
|
||||||
|
|
||||||
|
def _expand_dollars(m):
|
||||||
|
match = m.group(1)
|
||||||
|
parts = match.split('.')
|
||||||
|
if len(parts) > 2:
|
||||||
|
return match + ' dollars' # Unexpected format
|
||||||
|
dollars = int(parts[0]) if parts[0] else 0
|
||||||
|
cents = int(parts[1]) if len(parts) > 1 and parts[1] else 0
|
||||||
|
if dollars and cents:
|
||||||
|
dollar_unit = 'dollar' if dollars == 1 else 'dollars'
|
||||||
|
cent_unit = 'cent' if cents == 1 else 'cents'
|
||||||
|
return '%s %s, %s %s' % (dollars, dollar_unit, cents, cent_unit)
|
||||||
|
elif dollars:
|
||||||
|
dollar_unit = 'dollar' if dollars == 1 else 'dollars'
|
||||||
|
return '%s %s' % (dollars, dollar_unit)
|
||||||
|
elif cents:
|
||||||
|
cent_unit = 'cent' if cents == 1 else 'cents'
|
||||||
|
return '%s %s' % (cents, cent_unit)
|
||||||
|
else:
|
||||||
|
return 'zero dollars'
|
||||||
|
|
||||||
|
|
||||||
|
def _expand_ordinal(m):
|
||||||
|
return _inflect.number_to_words(m.group(0))
|
||||||
|
|
||||||
|
|
||||||
|
def _expand_number(m):
|
||||||
|
num = int(m.group(0))
|
||||||
|
if num > 1000 and num < 3000:
|
||||||
|
if num == 2000:
|
||||||
|
return 'two thousand'
|
||||||
|
elif num > 2000 and num < 2010:
|
||||||
|
return 'two thousand ' + _inflect.number_to_words(num % 100)
|
||||||
|
elif num % 100 == 0:
|
||||||
|
return _inflect.number_to_words(num // 100) + ' hundred'
|
||||||
|
else:
|
||||||
|
return _inflect.number_to_words(num, andword='', zero='oh', group=2).replace(', ', ' ')
|
||||||
|
else:
|
||||||
|
return _inflect.number_to_words(num, andword='')
|
||||||
|
|
||||||
|
|
||||||
|
def normalize_numbers(text):
|
||||||
|
text = re.sub(_comma_number_re, _remove_commas, text)
|
||||||
|
text = re.sub(_pounds_re, r'\1 pounds', text)
|
||||||
|
text = re.sub(_dollars_re, _expand_dollars, text)
|
||||||
|
text = re.sub(_decimal_number_re, _expand_decimal_point, text)
|
||||||
|
text = re.sub(_ordinal_re, _expand_ordinal, text)
|
||||||
|
text = re.sub(_number_re, _expand_number, text)
|
||||||
|
return text
|
||||||
|
|
||||||
|
def expand_numbers(text):
|
||||||
|
return normalize_numbers(text)
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
from text.pipeline_components.normalization.common_cleaners import collapse_whitespace
|
||||||
|
from text.pipeline_components.normalization.common_cleaners import convert_to_ascii
|
||||||
|
from text.pipeline_components.normalization.common_cleaners import lowercase
|
||||||
|
from text.pipeline_components.normalization.english_cleaners import expand_abbreviations, expand_numbers
|
||||||
|
|
||||||
|
def normalize_english(raw_input_text: str) -> str:
|
||||||
|
text = convert_to_ascii(raw_input_text)
|
||||||
|
text = lowercase(text)
|
||||||
|
text = expand_numbers(text)
|
||||||
|
text = expand_abbreviations(text)
|
||||||
|
text = collapse_whitespace(text)
|
||||||
|
|
||||||
|
return text
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
from text.pipeline_components.normalization.common_cleaners import collapse_whitespace
|
||||||
|
from text.pipeline_components.normalization.common_cleaners import convert_to_ascii
|
||||||
|
from text.pipeline_components.normalization.common_cleaners import lowercase
|
||||||
|
|
||||||
|
def normalize_spanish(raw_input_text: str) -> str:
|
||||||
|
text = convert_to_ascii(raw_input_text)
|
||||||
|
text = lowercase(text)
|
||||||
|
|
||||||
|
# TODO: Expand abbreviations (Spanish)
|
||||||
|
# TODO: Expand numbers (Spanish)
|
||||||
|
|
||||||
|
text = collapse_whitespace(text)
|
||||||
|
|
||||||
|
return text
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
from text.pipeline_components.normalization.english_normalization import normalize_english
|
||||||
|
|
||||||
|
class TestEnglishNormalization:
|
||||||
|
|
||||||
|
def test_whitespace_collapse(self):
|
||||||
|
output = normalize_english(" \t \n normalized \n \t ")
|
||||||
|
assert output == " normalized "
|
||||||
|
|
||||||
|
def test_lowercase(self):
|
||||||
|
output = normalize_english("This is a test of Normalization.")
|
||||||
|
assert output == "this is a test of normalization."
|
||||||
|
|
||||||
|
def test_numbers(self):
|
||||||
|
output = normalize_english("1,000")
|
||||||
|
assert output == "one thousand"
|
||||||
|
|
||||||
|
def test_abbreviations(self):
|
||||||
|
output = normalize_english("mr. ed")
|
||||||
|
assert output == "mister ed"
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
|
||||||
|
import re
|
||||||
|
from typing import List
|
||||||
|
from text.pipeline_components.phonetization.cmudict import CMUDict
|
||||||
|
|
||||||
|
# Regex taken from open source tacotron2 extensions
|
||||||
|
# NB: This was broken and couldn't handle apostrophes
|
||||||
|
#_arpabet_split_re = re.compile(r'[^\s]*{.*?}[^\s]*|[\w]+|[^\w\s]+')
|
||||||
|
|
||||||
|
# Modified regex to not split apostrophe (eg. "you're", "I'm", etc.) or hyphenation (eg "avant-garde")
|
||||||
|
_arpabet_split_re = re.compile(r'[^\s]*{.*?}[^\s]*|[\w]+\'[\w]+|[\w]+\-[\w]+|[\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)]
|
||||||
@@ -0,0 +1,72 @@
|
|||||||
|
"""
|
||||||
|
Contents of this file are mostly from https://github.com/keithito/tacotron
|
||||||
|
"""
|
||||||
|
|
||||||
|
import re
|
||||||
|
|
||||||
|
# TODO: Don't use strings for arpabet phoneme representation, and share the memory.
|
||||||
|
|
||||||
|
# Relative to TTS codebase root
|
||||||
|
# This does not include any CMUDict extensions or alternatives (eg. for other languages).
|
||||||
|
DEFAULT_CMUDICT_PATH = 'dictionaries/cmudict-0.7b'
|
||||||
|
|
||||||
|
valid_symbols = [
|
||||||
|
'AA', 'AA0', 'AA1', 'AA2', 'AE', 'AE0', 'AE1', 'AE2', 'AH', 'AH0', 'AH1', 'AH2',
|
||||||
|
'AO', 'AO0', 'AO1', 'AO2', 'AW', 'AW0', 'AW1', 'AW2', 'AY', 'AY0', 'AY1', 'AY2',
|
||||||
|
'B', 'CH', 'D', 'DH', 'EH', 'EH0', 'EH1', 'EH2', 'ER', 'ER0', 'ER1', 'ER2', 'EY',
|
||||||
|
'EY0', 'EY1', 'EY2', 'F', 'G', 'HH', 'IH', 'IH0', 'IH1', 'IH2', 'IY', 'IY0', 'IY1',
|
||||||
|
'IY2', 'JH', 'K', 'L', 'M', 'N', 'NG', 'OW', 'OW0', 'OW1', 'OW2', 'OY', 'OY0',
|
||||||
|
'OY1', 'OY2', 'P', 'R', 'S', 'SH', 'T', 'TH', 'UH', 'UH0', 'UH1', 'UH2', 'UW',
|
||||||
|
'UW0', 'UW1', 'UW2', 'V', 'W', 'Y', 'Z', 'ZH'
|
||||||
|
]
|
||||||
|
|
||||||
|
_valid_symbol_set = set(valid_symbols)
|
||||||
|
|
||||||
|
|
||||||
|
class CMUDict:
|
||||||
|
'''Thin wrapper around CMUDict data. http://www.speech.cs.cmu.edu/cgi-bin/cmudict'''
|
||||||
|
def __init__(self, file_or_path, keep_ambiguous=True):
|
||||||
|
if isinstance(file_or_path, str):
|
||||||
|
with open(file_or_path, encoding='latin-1') as f:
|
||||||
|
entries = _parse_cmudict(f)
|
||||||
|
else:
|
||||||
|
entries = _parse_cmudict(file_or_path)
|
||||||
|
if not keep_ambiguous:
|
||||||
|
entries = {word: pron for word, pron in entries.items() if len(pron) == 1}
|
||||||
|
self._entries = entries
|
||||||
|
|
||||||
|
|
||||||
|
def __len__(self):
|
||||||
|
return len(self._entries)
|
||||||
|
|
||||||
|
|
||||||
|
def lookup(self, word):
|
||||||
|
'''Returns list of ARPAbet pronunciations of the given word.'''
|
||||||
|
return self._entries.get(word.upper())
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
_alt_re = re.compile(r'\([0-9]+\)')
|
||||||
|
|
||||||
|
|
||||||
|
def _parse_cmudict(file):
|
||||||
|
cmudict = {}
|
||||||
|
for line in file:
|
||||||
|
if len(line) and (line[0] >= 'A' and line[0] <= 'Z' or line[0] == "'"):
|
||||||
|
parts = line.split(' ')
|
||||||
|
word = re.sub(_alt_re, '', parts[0])
|
||||||
|
pronunciation = _get_pronunciation(parts[1])
|
||||||
|
if pronunciation:
|
||||||
|
if word in cmudict:
|
||||||
|
cmudict[word].append(pronunciation)
|
||||||
|
else:
|
||||||
|
cmudict[word] = [pronunciation]
|
||||||
|
return cmudict
|
||||||
|
|
||||||
|
|
||||||
|
def _get_pronunciation(s):
|
||||||
|
parts = s.strip().split(' ')
|
||||||
|
for part in parts:
|
||||||
|
if part not in _valid_symbol_set:
|
||||||
|
return None
|
||||||
|
return ' '.join(parts)
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
from typing import List
|
||||||
|
from text.pipeline_components.input_parsing.parser import ArpabetSequence, GraphemeSequence, ParsedSequence
|
||||||
|
from text.pipeline_components.phonetization.base_phonetization import BasePhonetization
|
||||||
|
from text.pipeline_components.phonetization.cmudict import CMUDict
|
||||||
|
|
||||||
|
class EnglishPhonetization(BasePhonetization):
|
||||||
|
|
||||||
|
def __init__(self, phoneme_dictionary: CMUDict):
|
||||||
|
super().__init__(phoneme_dictionary)
|
||||||
|
|
||||||
|
def try_convert_to_phonemes(self, grapheme_sequence: str) -> ParsedSequence:
|
||||||
|
"""
|
||||||
|
Attempt to enrich a grapheme sequence with phonemes where word lookup succeeds.
|
||||||
|
Grapheme symbols will be left in place.
|
||||||
|
The output list will need to be space-separated before inference.
|
||||||
|
"""
|
||||||
|
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(ArpabetSequence(maybe_phonemes[0]))
|
||||||
|
else:
|
||||||
|
# TODO: Use g2p prediction to predict the phonemes.
|
||||||
|
converted_words.append(GraphemeSequence(word))
|
||||||
|
|
||||||
|
return converted_words
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
from text.pipeline_components.phonetization.cmudict import CMUDict
|
||||||
|
from text.pipeline_components.phonetization.cmudict import DEFAULT_CMUDICT_PATH
|
||||||
|
|
||||||
|
|
||||||
|
class TestCmuDict:
|
||||||
|
|
||||||
|
def test_lookup_success(self):
|
||||||
|
cmudict = CMUDict(DEFAULT_CMUDICT_PATH)
|
||||||
|
arpabet_sequence = cmudict.lookup('word')
|
||||||
|
assert arpabet_sequence == ['W ER1 D'] # TODO: Split the phonemes!
|
||||||
|
|
||||||
|
def test_lookup_failure(self):
|
||||||
|
cmudict = CMUDict(DEFAULT_CMUDICT_PATH)
|
||||||
|
arpabet_sequence = cmudict.lookup('fakeword')
|
||||||
|
assert arpabet_sequence == None
|
||||||
@@ -0,0 +1,94 @@
|
|||||||
|
from text.pipeline_components.phonetization.cmudict import DEFAULT_CMUDICT_PATH, CMUDict
|
||||||
|
from text.pipeline_components.phonetization.english_phonetization import EnglishPhonetization
|
||||||
|
from text.pipeline_components.input_parsing.parser import ArpabetSequence
|
||||||
|
from text.pipeline_components.input_parsing.parser import GraphemeSequence
|
||||||
|
|
||||||
|
class TestEnglishPhonetization:
|
||||||
|
|
||||||
|
def test_no_input(self):
|
||||||
|
phoneme_dictionary = CMUDict(DEFAULT_CMUDICT_PATH)
|
||||||
|
phonetization = EnglishPhonetization(phoneme_dictionary)
|
||||||
|
output = phonetization.try_convert_to_phonemes("")
|
||||||
|
assert output == []
|
||||||
|
|
||||||
|
def test_all_words_in_dictionary(self):
|
||||||
|
phoneme_dictionary = CMUDict(DEFAULT_CMUDICT_PATH)
|
||||||
|
phonetization = EnglishPhonetization(phoneme_dictionary)
|
||||||
|
output = phonetization.try_convert_to_phonemes("this is a test")
|
||||||
|
assert output == [
|
||||||
|
ArpabetSequence("DH IH1 S"),
|
||||||
|
ArpabetSequence("IH1 Z"),
|
||||||
|
ArpabetSequence("AH0"),
|
||||||
|
ArpabetSequence("T EH1 S T"),
|
||||||
|
]
|
||||||
|
|
||||||
|
def test_most_words_in_dictionary(self):
|
||||||
|
phoneme_dictionary = CMUDict(DEFAULT_CMUDICT_PATH)
|
||||||
|
phonetization = EnglishPhonetization(phoneme_dictionary)
|
||||||
|
output = phonetization.try_convert_to_phonemes("nonword is a test")
|
||||||
|
assert output == [
|
||||||
|
GraphemeSequence("nonword"),
|
||||||
|
ArpabetSequence("IH1 Z"),
|
||||||
|
ArpabetSequence("AH0"),
|
||||||
|
ArpabetSequence("T EH1 S T"),
|
||||||
|
]
|
||||||
|
|
||||||
|
def test_words_with_apostrophe(self):
|
||||||
|
phoneme_dictionary = CMUDict(DEFAULT_CMUDICT_PATH)
|
||||||
|
phonetization = EnglishPhonetization(phoneme_dictionary)
|
||||||
|
output = phonetization.try_convert_to_phonemes("I'm can't you're we're ain't")
|
||||||
|
assert output == [
|
||||||
|
ArpabetSequence("AY1 M"),
|
||||||
|
ArpabetSequence("K AE1 N T"),
|
||||||
|
ArpabetSequence("Y UH1 R"),
|
||||||
|
ArpabetSequence("W IY1 R"),
|
||||||
|
ArpabetSequence("EY1 N T"),
|
||||||
|
]
|
||||||
|
|
||||||
|
def test_words_with_hyphenation(self):
|
||||||
|
phoneme_dictionary = CMUDict(DEFAULT_CMUDICT_PATH)
|
||||||
|
phonetization = EnglishPhonetization(phoneme_dictionary)
|
||||||
|
output = phonetization.try_convert_to_phonemes("avant-garde bake-off")
|
||||||
|
assert output == [
|
||||||
|
ArpabetSequence("AH0 V AA1 N T G AA1 R D"),
|
||||||
|
ArpabetSequence("B EY1 K AO1 F"),
|
||||||
|
]
|
||||||
|
|
||||||
|
def test_punctuation(self):
|
||||||
|
phoneme_dictionary = CMUDict(DEFAULT_CMUDICT_PATH)
|
||||||
|
phonetization = EnglishPhonetization(phoneme_dictionary)
|
||||||
|
output = phonetization.try_convert_to_phonemes("this...")
|
||||||
|
assert output == [
|
||||||
|
ArpabetSequence("DH IH1 S"),
|
||||||
|
GraphemeSequence("..."),
|
||||||
|
]
|
||||||
|
output = phonetization.try_convert_to_phonemes("this!")
|
||||||
|
assert output == [
|
||||||
|
ArpabetSequence("DH IH1 S"),
|
||||||
|
GraphemeSequence("!"),
|
||||||
|
]
|
||||||
|
output = phonetization.try_convert_to_phonemes("this?")
|
||||||
|
assert output == [
|
||||||
|
ArpabetSequence("DH IH1 S"),
|
||||||
|
GraphemeSequence("?"),
|
||||||
|
]
|
||||||
|
|
||||||
|
def test_extra_spaces(self):
|
||||||
|
phoneme_dictionary = CMUDict(DEFAULT_CMUDICT_PATH)
|
||||||
|
phonetization = EnglishPhonetization(phoneme_dictionary)
|
||||||
|
output = phonetization.try_convert_to_phonemes(" this is a test ")
|
||||||
|
assert output == [
|
||||||
|
ArpabetSequence("DH IH1 S"),
|
||||||
|
ArpabetSequence("IH1 Z"),
|
||||||
|
ArpabetSequence("AH0"),
|
||||||
|
ArpabetSequence("T EH1 S T"),
|
||||||
|
]
|
||||||
|
|
||||||
|
def test_multi_non_word_extra_spaces(self):
|
||||||
|
phoneme_dictionary = CMUDict(DEFAULT_CMUDICT_PATH)
|
||||||
|
phonetization = EnglishPhonetization(phoneme_dictionary)
|
||||||
|
output = phonetization.try_convert_to_phonemes(" nonword anothernonword ")
|
||||||
|
assert output == [
|
||||||
|
GraphemeSequence("nonword"),
|
||||||
|
GraphemeSequence("anothernonword"),
|
||||||
|
]
|
||||||
@@ -0,0 +1,50 @@
|
|||||||
|
"""
|
||||||
|
Originally adapted from https://github.com/keithito/tacotron
|
||||||
|
|
||||||
|
This defines the set of symbols used in text input to the model.
|
||||||
|
|
||||||
|
This is the set of character encodings that FakeYou and UberDuck models have
|
||||||
|
been trained on. We freeze it here as "V1", because these may change in future
|
||||||
|
notebooks.
|
||||||
|
|
||||||
|
Original documentation:
|
||||||
|
The default is a set of ASCII characters that works well for English or
|
||||||
|
text that has been run through Unidecode. For other data, you can modify
|
||||||
|
_characters. See TRAINING_DATA.md for details.
|
||||||
|
"""
|
||||||
|
|
||||||
|
from text.pipeline_components.phonetization import cmudict
|
||||||
|
from typing import List
|
||||||
|
|
||||||
|
# NB: DO NOT CHANGE THE ORDER OF SYMBOLS!
|
||||||
|
# Models are trained on and depend upon the order.
|
||||||
|
_pad = '_'
|
||||||
|
_punctuation = '!\'(),.:;? ' #NB: Includes space
|
||||||
|
_special = '-'
|
||||||
|
_letters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz'
|
||||||
|
|
||||||
|
# Prepend "@" to ARPAbet symbols to ensure uniqueness (some are the same as uppercase letters):
|
||||||
|
_arpabet = ['@' + s for s in cmudict.valid_symbols]
|
||||||
|
|
||||||
|
# Export all symbols:
|
||||||
|
symbols = [_pad] + list(_special) + list(_punctuation) + list(_letters) + _arpabet
|
||||||
|
|
||||||
|
# Mappings from symbol to numeric ID and vice versa:
|
||||||
|
# The ordering **CANNOT** change.
|
||||||
|
_symbol_to_id = {s: i for i, s in enumerate(symbols)}
|
||||||
|
_id_to_symbol = {i: s for i, s in enumerate(symbols)}
|
||||||
|
|
||||||
|
# Spaces can be injected when joining words.
|
||||||
|
SPACE_INTEGER_ENCODING = _symbol_to_id[' ']
|
||||||
|
|
||||||
|
|
||||||
|
def symbols_to_sequence(symbols: str) -> List[int]:
|
||||||
|
return [_symbol_to_id[s] for s in symbols if _should_keep_symbol(s)]
|
||||||
|
|
||||||
|
|
||||||
|
def arpabet_to_sequence(arpabet_string: str) -> List[int]:
|
||||||
|
return symbols_to_sequence(['@' + s for s in arpabet_string.split()])
|
||||||
|
|
||||||
|
|
||||||
|
def _should_keep_symbol(s):
|
||||||
|
return s in _symbol_to_id and s is not '_' and s is not '~'
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
from text.pipeline_components.sequencing.english_symbols_v1 import arpabet_to_sequence, symbols_to_sequence
|
||||||
|
|
||||||
|
class TestEnglishSymbols:
|
||||||
|
|
||||||
|
def test_no_input_arpabet(self):
|
||||||
|
output = arpabet_to_sequence('')
|
||||||
|
assert output == []
|
||||||
|
|
||||||
|
def test_no_input_symbols(self):
|
||||||
|
output = symbols_to_sequence('')
|
||||||
|
assert output == []
|
||||||
|
|
||||||
|
def test_arpabet_word(self):
|
||||||
|
# NB: Models are trained on these integer encodings.
|
||||||
|
# If these tests break, the order of symbols may have changed.
|
||||||
|
output = arpabet_to_sequence('T EH1 S T')
|
||||||
|
assert output == [133, 94, 131, 133]
|
||||||
|
|
||||||
|
def test_grapheme_word(self):
|
||||||
|
# NB: Models are trained on these integer encodings.
|
||||||
|
# If these tests break, the order of symbols may have changed.
|
||||||
|
output = symbols_to_sequence('test')
|
||||||
|
assert output == [57, 42, 56, 57]
|
||||||
|
|
||||||
|
def test_grapheme_sentence(self):
|
||||||
|
# NB: Models are trained on these integer encodings.
|
||||||
|
# If these tests break, the order of symbols may have changed.
|
||||||
|
output = symbols_to_sequence('this is a test.')
|
||||||
|
assert output == [57, 45, 46, 56, 11, 46, 56, 11, 38, 11, 57, 42, 56, 57, 7]
|
||||||
+28
-1
@@ -1,4 +1,5 @@
|
|||||||
import sys
|
import sys
|
||||||
|
|
||||||
sys.path.append('waveglow/')
|
sys.path.append('waveglow/')
|
||||||
import shutil
|
import shutil
|
||||||
import json
|
import json
|
||||||
@@ -29,6 +30,11 @@ import subprocess
|
|||||||
import magic
|
import magic
|
||||||
import os
|
import os
|
||||||
|
|
||||||
|
from text.pipeline.english_pipeline_v1 import EnglishPipelineV1
|
||||||
|
from text.pipeline.legacy_fakeyou_pipeline import LegacyFakeYouPipeline
|
||||||
|
from text.pipeline_components.phonetization.cmudict import DEFAULT_CMUDICT_PATH, CMUDict
|
||||||
|
from text.pipeline_components.phonetization.english_phonetization import EnglishPhonetization
|
||||||
|
|
||||||
def print_gpu_info():
|
def print_gpu_info():
|
||||||
print('========================================')
|
print('========================================')
|
||||||
print('Python interpreter', sys.executable)
|
print('Python interpreter', sys.executable)
|
||||||
@@ -82,7 +88,27 @@ def load_hifigan_model(checkpoint_path, super_resolution = False):
|
|||||||
denoiser = HifiDenoiser(hifigan, mode="normal")
|
denoiser = HifiDenoiser(hifigan, mode="normal")
|
||||||
return hifigan, h, denoiser
|
return hifigan, h, denoiser
|
||||||
|
|
||||||
def preprocess_text(text, raw_input=True):
|
ENGLISH_PIPELINE_V1 = EnglishPipelineV1(EnglishPhonetization(CMUDict(DEFAULT_CMUDICT_PATH)))
|
||||||
|
LEGACY_FAKEYOU_PIPELINE = LegacyFakeYouPipeline()
|
||||||
|
|
||||||
|
def preprocess_text(raw_user_text: str):
|
||||||
|
integer_sequence = []
|
||||||
|
|
||||||
|
# TODO/NB: This word-based flag is for testing the rollout
|
||||||
|
if "this" in raw_user_text:
|
||||||
|
print("\n\n>>> ENGLISH V1\n\n")
|
||||||
|
integer_sequence = ENGLISH_PIPELINE_V1.user_input_to_sequence(raw_user_text)
|
||||||
|
else:
|
||||||
|
print("\n\n>>> LEGACY\n\n")
|
||||||
|
integer_sequence = LEGACY_FAKEYOU_PIPELINE.user_input_to_sequence(raw_user_text)
|
||||||
|
|
||||||
|
with torch.no_grad():
|
||||||
|
# save VRAM by not including gradients
|
||||||
|
sequence = np.array(integer_sequence)[None, :]
|
||||||
|
sequence = torch.autograd.Variable(torch.from_numpy(sequence)).cuda().long()
|
||||||
|
return sequence
|
||||||
|
|
||||||
|
def preprocess_text_old(text, raw_input=True):
|
||||||
# TODO: Handle ARPAbet
|
# TODO: Handle ARPAbet
|
||||||
pre_sequence = ""
|
pre_sequence = ""
|
||||||
for line in text.split("\n"):
|
for line in text.split("\n"):
|
||||||
@@ -96,6 +122,7 @@ def preprocess_text(text, raw_input=True):
|
|||||||
pass
|
pass
|
||||||
pre_sequence += line
|
pre_sequence += line
|
||||||
|
|
||||||
|
print(pre_sequence)
|
||||||
with torch.no_grad():
|
with torch.no_grad():
|
||||||
# save VRAM by not including gradients
|
# save VRAM by not including gradients
|
||||||
sequence = np.array(text_to_sequence(pre_sequence, ['english_cleaners']))[None, :]
|
sequence = np.array(text_to_sequence(pre_sequence, ['english_cleaners']))[None, :]
|
||||||
|
|||||||
Reference in New Issue
Block a user