Add files via upload
This commit is contained in:
@@ -0,0 +1 @@
|
||||
from TTS.tts.utils.text.tokenizer import TTSTokenizer
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,121 @@
|
||||
import re
|
||||
|
||||
import bangla
|
||||
from bnnumerizer import numerize
|
||||
from bnunicodenormalizer import Normalizer
|
||||
|
||||
# initialize
|
||||
bnorm = Normalizer()
|
||||
|
||||
|
||||
attribution_dict = {
|
||||
"সাঃ": "সাল্লাল্লাহু আলাইহি ওয়া সাল্লাম",
|
||||
"আঃ": "আলাইহিস সালাম",
|
||||
"রাঃ": "রাদিআল্লাহু আনহু",
|
||||
"রহঃ": "রহমাতুল্লাহি আলাইহি",
|
||||
"রহিঃ": "রহিমাহুল্লাহ",
|
||||
"হাফিঃ": "হাফিযাহুল্লাহ",
|
||||
"বায়ান": "বাইআন",
|
||||
"দাঃবাঃ": "দামাত বারাকাতুহুম,দামাত বারাকাতুল্লাহ",
|
||||
# "আয়াত" : "আইআত",#আইআত
|
||||
# "ওয়া" : "ওআ",
|
||||
# "ওয়াসাল্লাম" : "ওআসাল্লাম",
|
||||
# "কেন" : "কেনো",
|
||||
# "কোন" : "কোনো",
|
||||
# "বল" : "বলো",
|
||||
# "চল" : "চলো",
|
||||
# "কর" : "করো",
|
||||
# "রাখ" : "রাখো",
|
||||
"’": "",
|
||||
"‘": "",
|
||||
# "য়" : "অ",
|
||||
# "সম্প্রদায়" : "সম্প্রদাই",
|
||||
# "রয়েছে" : "রইছে",
|
||||
# "রয়েছ" : "রইছ",
|
||||
"/": " বাই ",
|
||||
}
|
||||
|
||||
|
||||
def tag_text(text: str):
|
||||
# remove multiple spaces
|
||||
text = re.sub(" +", " ", text)
|
||||
# create start and end
|
||||
text = "start" + text + "end"
|
||||
# tag text
|
||||
parts = re.split("[\u0600-\u06FF]+", text)
|
||||
# remove non chars
|
||||
parts = [p for p in parts if p.strip()]
|
||||
# unique parts
|
||||
parts = set(parts)
|
||||
# tag the text
|
||||
for m in parts:
|
||||
if len(m.strip()) > 1:
|
||||
text = text.replace(m, f"{m}")
|
||||
# clean-tags
|
||||
text = text.replace("start", "")
|
||||
text = text.replace("end", "")
|
||||
return text
|
||||
|
||||
|
||||
def normalize(sen):
|
||||
global bnorm # pylint: disable=global-statement
|
||||
_words = [bnorm(word)["normalized"] for word in sen.split()]
|
||||
return " ".join([word for word in _words if word is not None])
|
||||
|
||||
|
||||
def expand_full_attribution(text):
|
||||
for word, attr in attribution_dict.items():
|
||||
if word in text:
|
||||
text = text.replace(word, normalize(attr))
|
||||
return text
|
||||
|
||||
|
||||
def collapse_whitespace(text):
|
||||
# Regular expression matching whitespace:
|
||||
_whitespace_re = re.compile(r"\s+")
|
||||
return re.sub(_whitespace_re, " ", text)
|
||||
|
||||
|
||||
def bangla_text_to_phonemes(text: str) -> str:
|
||||
# english numbers to bangla conversion
|
||||
res = re.search("[0-9]", text)
|
||||
if res is not None:
|
||||
text = bangla.convert_english_digit_to_bangla_digit(text)
|
||||
|
||||
# replace ':' in between two bangla numbers with ' এর '
|
||||
pattern = r"[০, ১, ২, ৩, ৪, ৫, ৬, ৭, ৮, ৯]:[০, ১, ২, ৩, ৪, ৫, ৬, ৭, ৮, ৯]"
|
||||
matches = re.findall(pattern, text)
|
||||
for m in matches:
|
||||
r = m.replace(":", " এর ")
|
||||
text = text.replace(m, r)
|
||||
|
||||
# numerize text
|
||||
text = numerize(text)
|
||||
|
||||
# tag sections
|
||||
text = tag_text(text)
|
||||
|
||||
# text blocks
|
||||
# blocks = text.split("")
|
||||
# blocks = [b for b in blocks if b.strip()]
|
||||
|
||||
# create tuple of (lang,text)
|
||||
if "" in text:
|
||||
text = text.replace("", "").replace("", "")
|
||||
# Split based on sentence ending Characters
|
||||
bn_text = text.strip()
|
||||
|
||||
sentenceEnders = re.compile("[।!?]")
|
||||
sentences = sentenceEnders.split(str(bn_text))
|
||||
|
||||
data = ""
|
||||
for sent in sentences:
|
||||
res = re.sub("\n", "", sent)
|
||||
res = normalize(res)
|
||||
# expand attributes
|
||||
res = expand_full_attribution(res)
|
||||
|
||||
res = collapse_whitespace(res)
|
||||
res += "।"
|
||||
data += res
|
||||
return data
|
||||
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,37 @@
|
||||
import os
|
||||
|
||||
finder = None
|
||||
|
||||
|
||||
def init():
|
||||
try:
|
||||
import jpype
|
||||
import jpype.imports
|
||||
except ModuleNotFoundError:
|
||||
raise ModuleNotFoundError(
|
||||
"Belarusian phonemizer requires to install module 'jpype1' manually. Try `pip install jpype1`."
|
||||
)
|
||||
|
||||
try:
|
||||
jar_path = os.environ["BEL_FANETYKA_JAR"]
|
||||
except KeyError:
|
||||
raise KeyError("You need to define 'BEL_FANETYKA_JAR' environment variable as path to the fanetyka.jar file")
|
||||
|
||||
jpype.startJVM(classpath=[jar_path])
|
||||
|
||||
# import the Java modules
|
||||
from org.alex73.korpus.base import GrammarDB2, GrammarFinder
|
||||
|
||||
grammar_db = GrammarDB2.initializeFromJar()
|
||||
global finder
|
||||
finder = GrammarFinder(grammar_db)
|
||||
|
||||
|
||||
def belarusian_text_to_phonemes(text: str) -> str:
|
||||
# Initialize only on first run
|
||||
if finder is None:
|
||||
init()
|
||||
|
||||
from org.alex73.fanetyka.impl import FanetykaText
|
||||
|
||||
return str(FanetykaText(finder, text).ipa)
|
||||
@@ -0,0 +1,501 @@
|
||||
from dataclasses import replace
|
||||
from typing import Dict
|
||||
|
||||
from TTS.tts.configs.shared_configs import CharactersConfig
|
||||
|
||||
|
||||
def parse_symbols():
|
||||
return {
|
||||
"pad": _pad,
|
||||
"eos": _eos,
|
||||
"bos": _bos,
|
||||
"characters": _characters,
|
||||
"punctuations": _punctuations,
|
||||
"phonemes": _phonemes,
|
||||
}
|
||||
|
||||
|
||||
# DEFAULT SET OF GRAPHEMES
|
||||
_pad = "<PAD>"
|
||||
_eos = "<EOS>"
|
||||
_bos = "<BOS>"
|
||||
_blank = "<BLNK>" # TODO: check if we need this alongside with PAD
|
||||
_characters = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"
|
||||
_punctuations = "!'(),-.:;? "
|
||||
|
||||
|
||||
# DEFAULT SET OF IPA PHONEMES
|
||||
# Phonemes definition (All IPA characters)
|
||||
_vowels = "iyɨʉɯuɪʏʊeøɘəɵɤoɛœɜɞʌɔæɐaɶɑɒᵻ"
|
||||
_non_pulmonic_consonants = "ʘɓǀɗǃʄǂɠǁʛ"
|
||||
_pulmonic_consonants = "pbtdʈɖcɟkɡqɢʔɴŋɲɳnɱmʙrʀⱱɾɽɸβfvθðszʃʒʂʐçʝxɣχʁħʕhɦɬɮʋɹɻjɰlɭʎʟ"
|
||||
_suprasegmentals = "ˈˌːˑ"
|
||||
_other_symbols = "ʍwɥʜʢʡɕʑɺɧʲ"
|
||||
_diacrilics = "ɚ˞ɫ"
|
||||
_phonemes = _vowels + _non_pulmonic_consonants + _pulmonic_consonants + _suprasegmentals + _other_symbols + _diacrilics
|
||||
|
||||
|
||||
class BaseVocabulary:
|
||||
"""Base Vocabulary class.
|
||||
|
||||
This class only needs a vocabulary dictionary without specifying the characters.
|
||||
|
||||
Args:
|
||||
vocab (Dict): A dictionary of characters and their corresponding indices.
|
||||
"""
|
||||
|
||||
def __init__(self, vocab: Dict, pad: str = None, blank: str = None, bos: str = None, eos: str = None):
|
||||
self.vocab = vocab
|
||||
self.pad = pad
|
||||
self.blank = blank
|
||||
self.bos = bos
|
||||
self.eos = eos
|
||||
|
||||
@property
|
||||
def pad_id(self) -> int:
|
||||
"""Return the index of the padding character. If the padding character is not specified, return the length
|
||||
of the vocabulary."""
|
||||
return self.char_to_id(self.pad) if self.pad else len(self.vocab)
|
||||
|
||||
@property
|
||||
def blank_id(self) -> int:
|
||||
"""Return the index of the blank character. If the blank character is not specified, return the length of
|
||||
the vocabulary."""
|
||||
return self.char_to_id(self.blank) if self.blank else len(self.vocab)
|
||||
|
||||
@property
|
||||
def bos_id(self) -> int:
|
||||
"""Return the index of the bos character. If the bos character is not specified, return the length of the
|
||||
vocabulary."""
|
||||
return self.char_to_id(self.bos) if self.bos else len(self.vocab)
|
||||
|
||||
@property
|
||||
def eos_id(self) -> int:
|
||||
"""Return the index of the eos character. If the eos character is not specified, return the length of the
|
||||
vocabulary."""
|
||||
return self.char_to_id(self.eos) if self.eos else len(self.vocab)
|
||||
|
||||
@property
|
||||
def vocab(self):
|
||||
"""Return the vocabulary dictionary."""
|
||||
return self._vocab
|
||||
|
||||
@vocab.setter
|
||||
def vocab(self, vocab):
|
||||
"""Set the vocabulary dictionary and character mapping dictionaries."""
|
||||
self._vocab, self._char_to_id, self._id_to_char = None, None, None
|
||||
if vocab is not None:
|
||||
self._vocab = vocab
|
||||
self._char_to_id = {char: idx for idx, char in enumerate(self._vocab)}
|
||||
self._id_to_char = {
|
||||
idx: char for idx, char in enumerate(self._vocab) # pylint: disable=unnecessary-comprehension
|
||||
}
|
||||
|
||||
@staticmethod
|
||||
def init_from_config(config, **kwargs):
|
||||
"""Initialize from the given config."""
|
||||
if config.characters is not None and "vocab_dict" in config.characters and config.characters.vocab_dict:
|
||||
return (
|
||||
BaseVocabulary(
|
||||
config.characters.vocab_dict,
|
||||
config.characters.pad,
|
||||
config.characters.blank,
|
||||
config.characters.bos,
|
||||
config.characters.eos,
|
||||
),
|
||||
config,
|
||||
)
|
||||
return BaseVocabulary(**kwargs), config
|
||||
|
||||
def to_config(self) -> "CharactersConfig":
|
||||
return CharactersConfig(
|
||||
vocab_dict=self._vocab,
|
||||
pad=self.pad,
|
||||
eos=self.eos,
|
||||
bos=self.bos,
|
||||
blank=self.blank,
|
||||
is_unique=False,
|
||||
is_sorted=False,
|
||||
)
|
||||
|
||||
@property
|
||||
def num_chars(self):
|
||||
"""Return number of tokens in the vocabulary."""
|
||||
return len(self._vocab)
|
||||
|
||||
def char_to_id(self, char: str) -> int:
|
||||
"""Map a character to an token ID."""
|
||||
try:
|
||||
return self._char_to_id[char]
|
||||
except KeyError as e:
|
||||
raise KeyError(f" [!] {repr(char)} is not in the vocabulary.") from e
|
||||
|
||||
def id_to_char(self, idx: int) -> str:
|
||||
"""Map an token ID to a character."""
|
||||
return self._id_to_char[idx]
|
||||
|
||||
|
||||
class BaseCharacters:
|
||||
"""🐸BaseCharacters class
|
||||
|
||||
Every new character class should inherit from this.
|
||||
|
||||
Characters are oredered as follows ```[PAD, EOS, BOS, BLANK, CHARACTERS, PUNCTUATIONS]```.
|
||||
|
||||
If you need a custom order, you need to define inherit from this class and override the ```_create_vocab``` method.
|
||||
|
||||
Args:
|
||||
characters (str):
|
||||
Main set of characters to be used in the vocabulary.
|
||||
|
||||
punctuations (str):
|
||||
Characters to be treated as punctuation.
|
||||
|
||||
pad (str):
|
||||
Special padding character that would be ignored by the model.
|
||||
|
||||
eos (str):
|
||||
End of the sentence character.
|
||||
|
||||
bos (str):
|
||||
Beginning of the sentence character.
|
||||
|
||||
blank (str):
|
||||
Optional character used between characters by some models for better prosody.
|
||||
|
||||
is_unique (bool):
|
||||
Remove duplicates from the provided characters. Defaults to True.
|
||||
el
|
||||
is_sorted (bool):
|
||||
Sort the characters in alphabetical order. Only applies to `self.characters`. Defaults to True.
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
characters: str = None,
|
||||
punctuations: str = None,
|
||||
pad: str = None,
|
||||
eos: str = None,
|
||||
bos: str = None,
|
||||
blank: str = None,
|
||||
is_unique: bool = False,
|
||||
is_sorted: bool = True,
|
||||
) -> None:
|
||||
self._characters = characters
|
||||
self._punctuations = punctuations
|
||||
self._pad = pad
|
||||
self._eos = eos
|
||||
self._bos = bos
|
||||
self._blank = blank
|
||||
self.is_unique = is_unique
|
||||
self.is_sorted = is_sorted
|
||||
self._create_vocab()
|
||||
|
||||
@property
|
||||
def pad_id(self) -> int:
|
||||
return self.char_to_id(self.pad) if self.pad else len(self.vocab)
|
||||
|
||||
@property
|
||||
def blank_id(self) -> int:
|
||||
return self.char_to_id(self.blank) if self.blank else len(self.vocab)
|
||||
|
||||
@property
|
||||
def eos_id(self) -> int:
|
||||
return self.char_to_id(self.eos) if self.eos else len(self.vocab)
|
||||
|
||||
@property
|
||||
def bos_id(self) -> int:
|
||||
return self.char_to_id(self.bos) if self.bos else len(self.vocab)
|
||||
|
||||
@property
|
||||
def characters(self):
|
||||
return self._characters
|
||||
|
||||
@characters.setter
|
||||
def characters(self, characters):
|
||||
self._characters = characters
|
||||
self._create_vocab()
|
||||
|
||||
@property
|
||||
def punctuations(self):
|
||||
return self._punctuations
|
||||
|
||||
@punctuations.setter
|
||||
def punctuations(self, punctuations):
|
||||
self._punctuations = punctuations
|
||||
self._create_vocab()
|
||||
|
||||
@property
|
||||
def pad(self):
|
||||
return self._pad
|
||||
|
||||
@pad.setter
|
||||
def pad(self, pad):
|
||||
self._pad = pad
|
||||
self._create_vocab()
|
||||
|
||||
@property
|
||||
def eos(self):
|
||||
return self._eos
|
||||
|
||||
@eos.setter
|
||||
def eos(self, eos):
|
||||
self._eos = eos
|
||||
self._create_vocab()
|
||||
|
||||
@property
|
||||
def bos(self):
|
||||
return self._bos
|
||||
|
||||
@bos.setter
|
||||
def bos(self, bos):
|
||||
self._bos = bos
|
||||
self._create_vocab()
|
||||
|
||||
@property
|
||||
def blank(self):
|
||||
return self._blank
|
||||
|
||||
@blank.setter
|
||||
def blank(self, blank):
|
||||
self._blank = blank
|
||||
self._create_vocab()
|
||||
|
||||
@property
|
||||
def vocab(self):
|
||||
return self._vocab
|
||||
|
||||
@vocab.setter
|
||||
def vocab(self, vocab):
|
||||
self._vocab = vocab
|
||||
self._char_to_id = {char: idx for idx, char in enumerate(self.vocab)}
|
||||
self._id_to_char = {
|
||||
idx: char for idx, char in enumerate(self.vocab) # pylint: disable=unnecessary-comprehension
|
||||
}
|
||||
|
||||
@property
|
||||
def num_chars(self):
|
||||
return len(self._vocab)
|
||||
|
||||
def _create_vocab(self):
|
||||
_vocab = self._characters
|
||||
if self.is_unique:
|
||||
_vocab = list(set(_vocab))
|
||||
if self.is_sorted:
|
||||
_vocab = sorted(_vocab)
|
||||
_vocab = list(_vocab)
|
||||
_vocab = [self._blank] + _vocab if self._blank is not None and len(self._blank) > 0 else _vocab
|
||||
_vocab = [self._bos] + _vocab if self._bos is not None and len(self._bos) > 0 else _vocab
|
||||
_vocab = [self._eos] + _vocab if self._eos is not None and len(self._eos) > 0 else _vocab
|
||||
_vocab = [self._pad] + _vocab if self._pad is not None and len(self._pad) > 0 else _vocab
|
||||
self.vocab = _vocab + list(self._punctuations)
|
||||
if self.is_unique:
|
||||
duplicates = {x for x in self.vocab if self.vocab.count(x) > 1}
|
||||
assert (
|
||||
len(self.vocab) == len(self._char_to_id) == len(self._id_to_char)
|
||||
), f" [!] There are duplicate characters in the character set. {duplicates}"
|
||||
|
||||
def char_to_id(self, char: str) -> int:
|
||||
try:
|
||||
return self._char_to_id[char]
|
||||
except KeyError as e:
|
||||
raise KeyError(f" [!] {repr(char)} is not in the vocabulary.") from e
|
||||
|
||||
def id_to_char(self, idx: int) -> str:
|
||||
return self._id_to_char[idx]
|
||||
|
||||
def print_log(self, level: int = 0):
|
||||
"""
|
||||
Prints the vocabulary in a nice format.
|
||||
"""
|
||||
indent = "\t" * level
|
||||
print(f"{indent}| > Characters: {self._characters}")
|
||||
print(f"{indent}| > Punctuations: {self._punctuations}")
|
||||
print(f"{indent}| > Pad: {self._pad}")
|
||||
print(f"{indent}| > EOS: {self._eos}")
|
||||
print(f"{indent}| > BOS: {self._bos}")
|
||||
print(f"{indent}| > Blank: {self._blank}")
|
||||
print(f"{indent}| > Vocab: {self.vocab}")
|
||||
print(f"{indent}| > Num chars: {self.num_chars}")
|
||||
|
||||
@staticmethod
|
||||
def init_from_config(config: "Coqpit"): # pylint: disable=unused-argument
|
||||
"""Init your character class from a config.
|
||||
|
||||
Implement this method for your subclass.
|
||||
"""
|
||||
# use character set from config
|
||||
if config.characters is not None:
|
||||
return BaseCharacters(**config.characters), config
|
||||
# return default character set
|
||||
characters = BaseCharacters()
|
||||
new_config = replace(config, characters=characters.to_config())
|
||||
return characters, new_config
|
||||
|
||||
def to_config(self) -> "CharactersConfig":
|
||||
return CharactersConfig(
|
||||
characters=self._characters,
|
||||
punctuations=self._punctuations,
|
||||
pad=self._pad,
|
||||
eos=self._eos,
|
||||
bos=self._bos,
|
||||
blank=self._blank,
|
||||
is_unique=self.is_unique,
|
||||
is_sorted=self.is_sorted,
|
||||
)
|
||||
|
||||
|
||||
class IPAPhonemes(BaseCharacters):
|
||||
"""🐸IPAPhonemes class to manage `TTS.tts` model vocabulary
|
||||
|
||||
Intended to be used with models using IPAPhonemes as input.
|
||||
It uses system defaults for the undefined class arguments.
|
||||
|
||||
Args:
|
||||
characters (str):
|
||||
Main set of case-sensitive characters to be used in the vocabulary. Defaults to `_phonemes`.
|
||||
|
||||
punctuations (str):
|
||||
Characters to be treated as punctuation. Defaults to `_punctuations`.
|
||||
|
||||
pad (str):
|
||||
Special padding character that would be ignored by the model. Defaults to `_pad`.
|
||||
|
||||
eos (str):
|
||||
End of the sentence character. Defaults to `_eos`.
|
||||
|
||||
bos (str):
|
||||
Beginning of the sentence character. Defaults to `_bos`.
|
||||
|
||||
blank (str):
|
||||
Optional character used between characters by some models for better prosody. Defaults to `_blank`.
|
||||
|
||||
is_unique (bool):
|
||||
Remove duplicates from the provided characters. Defaults to True.
|
||||
|
||||
is_sorted (bool):
|
||||
Sort the characters in alphabetical order. Defaults to True.
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
characters: str = _phonemes,
|
||||
punctuations: str = _punctuations,
|
||||
pad: str = _pad,
|
||||
eos: str = _eos,
|
||||
bos: str = _bos,
|
||||
blank: str = _blank,
|
||||
is_unique: bool = False,
|
||||
is_sorted: bool = True,
|
||||
) -> None:
|
||||
super().__init__(characters, punctuations, pad, eos, bos, blank, is_unique, is_sorted)
|
||||
|
||||
@staticmethod
|
||||
def init_from_config(config: "Coqpit"):
|
||||
"""Init a IPAPhonemes object from a model config
|
||||
|
||||
If characters are not defined in the config, it will be set to the default characters and the config
|
||||
will be updated.
|
||||
"""
|
||||
# band-aid for compatibility with old models
|
||||
if "characters" in config and config.characters is not None:
|
||||
if "phonemes" in config.characters and config.characters.phonemes is not None:
|
||||
config.characters["characters"] = config.characters["phonemes"]
|
||||
return (
|
||||
IPAPhonemes(
|
||||
characters=config.characters["characters"],
|
||||
punctuations=config.characters["punctuations"],
|
||||
pad=config.characters["pad"],
|
||||
eos=config.characters["eos"],
|
||||
bos=config.characters["bos"],
|
||||
blank=config.characters["blank"],
|
||||
is_unique=config.characters["is_unique"],
|
||||
is_sorted=config.characters["is_sorted"],
|
||||
),
|
||||
config,
|
||||
)
|
||||
# use character set from config
|
||||
if config.characters is not None:
|
||||
return IPAPhonemes(**config.characters), config
|
||||
# return default character set
|
||||
characters = IPAPhonemes()
|
||||
new_config = replace(config, characters=characters.to_config())
|
||||
return characters, new_config
|
||||
|
||||
|
||||
class Graphemes(BaseCharacters):
|
||||
"""🐸Graphemes class to manage `TTS.tts` model vocabulary
|
||||
|
||||
Intended to be used with models using graphemes as input.
|
||||
It uses system defaults for the undefined class arguments.
|
||||
|
||||
Args:
|
||||
characters (str):
|
||||
Main set of case-sensitive characters to be used in the vocabulary. Defaults to `_characters`.
|
||||
|
||||
punctuations (str):
|
||||
Characters to be treated as punctuation. Defaults to `_punctuations`.
|
||||
|
||||
pad (str):
|
||||
Special padding character that would be ignored by the model. Defaults to `_pad`.
|
||||
|
||||
eos (str):
|
||||
End of the sentence character. Defaults to `_eos`.
|
||||
|
||||
bos (str):
|
||||
Beginning of the sentence character. Defaults to `_bos`.
|
||||
|
||||
is_unique (bool):
|
||||
Remove duplicates from the provided characters. Defaults to True.
|
||||
|
||||
is_sorted (bool):
|
||||
Sort the characters in alphabetical order. Defaults to True.
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
characters: str = _characters,
|
||||
punctuations: str = _punctuations,
|
||||
pad: str = _pad,
|
||||
eos: str = _eos,
|
||||
bos: str = _bos,
|
||||
blank: str = _blank,
|
||||
is_unique: bool = False,
|
||||
is_sorted: bool = True,
|
||||
) -> None:
|
||||
super().__init__(characters, punctuations, pad, eos, bos, blank, is_unique, is_sorted)
|
||||
|
||||
@staticmethod
|
||||
def init_from_config(config: "Coqpit"):
|
||||
"""Init a Graphemes object from a model config
|
||||
|
||||
If characters are not defined in the config, it will be set to the default characters and the config
|
||||
will be updated.
|
||||
"""
|
||||
if config.characters is not None:
|
||||
# band-aid for compatibility with old models
|
||||
if "phonemes" in config.characters:
|
||||
return (
|
||||
Graphemes(
|
||||
characters=config.characters["characters"],
|
||||
punctuations=config.characters["punctuations"],
|
||||
pad=config.characters["pad"],
|
||||
eos=config.characters["eos"],
|
||||
bos=config.characters["bos"],
|
||||
blank=config.characters["blank"],
|
||||
is_unique=config.characters["is_unique"],
|
||||
is_sorted=config.characters["is_sorted"],
|
||||
),
|
||||
config,
|
||||
)
|
||||
return Graphemes(**config.characters), config
|
||||
characters = Graphemes()
|
||||
new_config = replace(config, characters=characters.to_config())
|
||||
return characters, new_config
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
gr = Graphemes()
|
||||
ph = IPAPhonemes()
|
||||
gr.print_log()
|
||||
ph.print_log()
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,127 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Licensed under WTFPL or the Unlicense or CC0.
|
||||
# This uses Python 3, but it's easy to port to Python 2 by changing
|
||||
# strings to u'xx'.
|
||||
|
||||
import itertools
|
||||
import re
|
||||
|
||||
|
||||
def _num2chinese(num: str, big=False, simp=True, o=False, twoalt=False) -> str:
|
||||
"""Convert numerical arabic numbers (0->9) to chinese hanzi numbers (〇 -> 九)
|
||||
|
||||
Args:
|
||||
num (str): arabic number to convert
|
||||
big (bool, optional): use financial characters. Defaults to False.
|
||||
simp (bool, optional): use simplified characters instead of tradictional characters. Defaults to True.
|
||||
o (bool, optional): use 〇 for 'zero'. Defaults to False.
|
||||
twoalt (bool, optional): use 两/兩 for 'two' when appropriate. Defaults to False.
|
||||
|
||||
Raises:
|
||||
ValueError: if number is more than 1e48
|
||||
ValueError: if 'e' exposent in number
|
||||
|
||||
Returns:
|
||||
str: converted number as hanzi characters
|
||||
"""
|
||||
|
||||
# check num first
|
||||
nd = str(num)
|
||||
if abs(float(nd)) >= 1e48:
|
||||
raise ValueError("number out of range")
|
||||
if "e" in nd:
|
||||
raise ValueError("scientific notation is not supported")
|
||||
c_symbol = "正负点" if simp else "正負點"
|
||||
if o: # formal
|
||||
twoalt = False
|
||||
if big:
|
||||
c_basic = "零壹贰叁肆伍陆柒捌玖" if simp else "零壹貳參肆伍陸柒捌玖"
|
||||
c_unit1 = "拾佰仟"
|
||||
c_twoalt = "贰" if simp else "貳"
|
||||
else:
|
||||
c_basic = "〇一二三四五六七八九" if o else "零一二三四五六七八九"
|
||||
c_unit1 = "十百千"
|
||||
if twoalt:
|
||||
c_twoalt = "两" if simp else "兩"
|
||||
else:
|
||||
c_twoalt = "二"
|
||||
c_unit2 = "万亿兆京垓秭穰沟涧正载" if simp else "萬億兆京垓秭穰溝澗正載"
|
||||
revuniq = lambda l: "".join(k for k, g in itertools.groupby(reversed(l)))
|
||||
nd = str(num)
|
||||
result = []
|
||||
if nd[0] == "+":
|
||||
result.append(c_symbol[0])
|
||||
elif nd[0] == "-":
|
||||
result.append(c_symbol[1])
|
||||
if "." in nd:
|
||||
integer, remainder = nd.lstrip("+-").split(".")
|
||||
else:
|
||||
integer, remainder = nd.lstrip("+-"), None
|
||||
if int(integer):
|
||||
splitted = [integer[max(i - 4, 0) : i] for i in range(len(integer), 0, -4)]
|
||||
intresult = []
|
||||
for nu, unit in enumerate(splitted):
|
||||
# special cases
|
||||
if int(unit) == 0: # 0000
|
||||
intresult.append(c_basic[0])
|
||||
continue
|
||||
if nu > 0 and int(unit) == 2: # 0002
|
||||
intresult.append(c_twoalt + c_unit2[nu - 1])
|
||||
continue
|
||||
ulist = []
|
||||
unit = unit.zfill(4)
|
||||
for nc, ch in enumerate(reversed(unit)):
|
||||
if ch == "0":
|
||||
if ulist: # ???0
|
||||
ulist.append(c_basic[0])
|
||||
elif nc == 0:
|
||||
ulist.append(c_basic[int(ch)])
|
||||
elif nc == 1 and ch == "1" and unit[1] == "0":
|
||||
# special case for tens
|
||||
# edit the 'elif' if you don't like
|
||||
# 十四, 三千零十四, 三千三百一十四
|
||||
ulist.append(c_unit1[0])
|
||||
elif nc > 1 and ch == "2":
|
||||
ulist.append(c_twoalt + c_unit1[nc - 1])
|
||||
else:
|
||||
ulist.append(c_basic[int(ch)] + c_unit1[nc - 1])
|
||||
ustr = revuniq(ulist)
|
||||
if nu == 0:
|
||||
intresult.append(ustr)
|
||||
else:
|
||||
intresult.append(ustr + c_unit2[nu - 1])
|
||||
result.append(revuniq(intresult).strip(c_basic[0]))
|
||||
else:
|
||||
result.append(c_basic[0])
|
||||
if remainder:
|
||||
result.append(c_symbol[2])
|
||||
result.append("".join(c_basic[int(ch)] for ch in remainder))
|
||||
return "".join(result)
|
||||
|
||||
|
||||
def _number_replace(match) -> str:
|
||||
"""function to apply in a match, transform all numbers in a match by chinese characters
|
||||
|
||||
Args:
|
||||
match (re.Match): numbers regex matches
|
||||
|
||||
Returns:
|
||||
str: replaced characters for the numbers
|
||||
"""
|
||||
match_str: str = match.group()
|
||||
return _num2chinese(match_str)
|
||||
|
||||
|
||||
def replace_numbers_to_characters_in_text(text: str) -> str:
|
||||
"""Replace all arabic numbers in a text by their equivalent in chinese characters (simplified)
|
||||
|
||||
Args:
|
||||
text (str): input text to transform
|
||||
|
||||
Returns:
|
||||
str: output text
|
||||
"""
|
||||
text = re.sub(r"[0-9]+", _number_replace, text)
|
||||
return text
|
||||
@@ -0,0 +1,37 @@
|
||||
from typing import List
|
||||
|
||||
import jieba
|
||||
import pypinyin
|
||||
|
||||
from .pinyinToPhonemes import PINYIN_DICT
|
||||
|
||||
|
||||
def _chinese_character_to_pinyin(text: str) -> List[str]:
|
||||
pinyins = pypinyin.pinyin(text, style=pypinyin.Style.TONE3, heteronym=False, neutral_tone_with_five=True)
|
||||
pinyins_flat_list = [item for sublist in pinyins for item in sublist]
|
||||
return pinyins_flat_list
|
||||
|
||||
|
||||
def _chinese_pinyin_to_phoneme(pinyin: str) -> str:
|
||||
segment = pinyin[:-1]
|
||||
tone = pinyin[-1]
|
||||
phoneme = PINYIN_DICT.get(segment, [""])[0]
|
||||
return phoneme + tone
|
||||
|
||||
|
||||
def chinese_text_to_phonemes(text: str, seperator: str = "|") -> str:
|
||||
tokenized_text = jieba.cut(text, HMM=False)
|
||||
tokenized_text = " ".join(tokenized_text)
|
||||
pinyined_text: List[str] = _chinese_character_to_pinyin(tokenized_text)
|
||||
|
||||
results: List[str] = []
|
||||
|
||||
for token in pinyined_text:
|
||||
if token[-1] in "12345": # TODO transform to is_pinyin()
|
||||
pinyin_phonemes = _chinese_pinyin_to_phoneme(token)
|
||||
|
||||
results += list(pinyin_phonemes)
|
||||
else: # is ponctuation or other
|
||||
results += list(token)
|
||||
|
||||
return seperator.join(results)
|
||||
@@ -0,0 +1,419 @@
|
||||
PINYIN_DICT = {
|
||||
"a": ["a"],
|
||||
"ai": ["ai"],
|
||||
"an": ["an"],
|
||||
"ang": ["ɑŋ"],
|
||||
"ao": ["aʌ"],
|
||||
"ba": ["ba"],
|
||||
"bai": ["bai"],
|
||||
"ban": ["ban"],
|
||||
"bang": ["bɑŋ"],
|
||||
"bao": ["baʌ"],
|
||||
# "be": ["be"], doesnt exist
|
||||
"bei": ["bɛi"],
|
||||
"ben": ["bœn"],
|
||||
"beng": ["bɵŋ"],
|
||||
"bi": ["bi"],
|
||||
"bian": ["biɛn"],
|
||||
"biao": ["biaʌ"],
|
||||
"bie": ["bie"],
|
||||
"bin": ["bin"],
|
||||
"bing": ["bɨŋ"],
|
||||
"bo": ["bo"],
|
||||
"bu": ["bu"],
|
||||
"ca": ["tsa"],
|
||||
"cai": ["tsai"],
|
||||
"can": ["tsan"],
|
||||
"cang": ["tsɑŋ"],
|
||||
"cao": ["tsaʌ"],
|
||||
"ce": ["tsø"],
|
||||
"cen": ["tsœn"],
|
||||
"ceng": ["tsɵŋ"],
|
||||
"cha": ["ʈʂa"],
|
||||
"chai": ["ʈʂai"],
|
||||
"chan": ["ʈʂan"],
|
||||
"chang": ["ʈʂɑŋ"],
|
||||
"chao": ["ʈʂaʌ"],
|
||||
"che": ["ʈʂø"],
|
||||
"chen": ["ʈʂœn"],
|
||||
"cheng": ["ʈʂɵŋ"],
|
||||
"chi": ["ʈʂʏ"],
|
||||
"chong": ["ʈʂoŋ"],
|
||||
"chou": ["ʈʂou"],
|
||||
"chu": ["ʈʂu"],
|
||||
"chua": ["ʈʂua"],
|
||||
"chuai": ["ʈʂuai"],
|
||||
"chuan": ["ʈʂuan"],
|
||||
"chuang": ["ʈʂuɑŋ"],
|
||||
"chui": ["ʈʂuei"],
|
||||
"chun": ["ʈʂun"],
|
||||
"chuo": ["ʈʂuo"],
|
||||
"ci": ["tsɪ"],
|
||||
"cong": ["tsoŋ"],
|
||||
"cou": ["tsou"],
|
||||
"cu": ["tsu"],
|
||||
"cuan": ["tsuan"],
|
||||
"cui": ["tsuei"],
|
||||
"cun": ["tsun"],
|
||||
"cuo": ["tsuo"],
|
||||
"da": ["da"],
|
||||
"dai": ["dai"],
|
||||
"dan": ["dan"],
|
||||
"dang": ["dɑŋ"],
|
||||
"dao": ["daʌ"],
|
||||
"de": ["dø"],
|
||||
"dei": ["dei"],
|
||||
# "den": ["dœn"],
|
||||
"deng": ["dɵŋ"],
|
||||
"di": ["di"],
|
||||
"dia": ["dia"],
|
||||
"dian": ["diɛn"],
|
||||
"diao": ["diaʌ"],
|
||||
"die": ["die"],
|
||||
"ding": ["dɨŋ"],
|
||||
"diu": ["dio"],
|
||||
"dong": ["doŋ"],
|
||||
"dou": ["dou"],
|
||||
"du": ["du"],
|
||||
"duan": ["duan"],
|
||||
"dui": ["duei"],
|
||||
"dun": ["dun"],
|
||||
"duo": ["duo"],
|
||||
"e": ["ø"],
|
||||
"ei": ["ei"],
|
||||
"en": ["œn"],
|
||||
# "ng": ["œn"],
|
||||
# "eng": ["ɵŋ"],
|
||||
"er": ["er"],
|
||||
"fa": ["fa"],
|
||||
"fan": ["fan"],
|
||||
"fang": ["fɑŋ"],
|
||||
"fei": ["fei"],
|
||||
"fen": ["fœn"],
|
||||
"feng": ["fɵŋ"],
|
||||
"fo": ["fo"],
|
||||
"fou": ["fou"],
|
||||
"fu": ["fu"],
|
||||
"ga": ["ga"],
|
||||
"gai": ["gai"],
|
||||
"gan": ["gan"],
|
||||
"gang": ["gɑŋ"],
|
||||
"gao": ["gaʌ"],
|
||||
"ge": ["gø"],
|
||||
"gei": ["gei"],
|
||||
"gen": ["gœn"],
|
||||
"geng": ["gɵŋ"],
|
||||
"gong": ["goŋ"],
|
||||
"gou": ["gou"],
|
||||
"gu": ["gu"],
|
||||
"gua": ["gua"],
|
||||
"guai": ["guai"],
|
||||
"guan": ["guan"],
|
||||
"guang": ["guɑŋ"],
|
||||
"gui": ["guei"],
|
||||
"gun": ["gun"],
|
||||
"guo": ["guo"],
|
||||
"ha": ["xa"],
|
||||
"hai": ["xai"],
|
||||
"han": ["xan"],
|
||||
"hang": ["xɑŋ"],
|
||||
"hao": ["xaʌ"],
|
||||
"he": ["xø"],
|
||||
"hei": ["xei"],
|
||||
"hen": ["xœn"],
|
||||
"heng": ["xɵŋ"],
|
||||
"hong": ["xoŋ"],
|
||||
"hou": ["xou"],
|
||||
"hu": ["xu"],
|
||||
"hua": ["xua"],
|
||||
"huai": ["xuai"],
|
||||
"huan": ["xuan"],
|
||||
"huang": ["xuɑŋ"],
|
||||
"hui": ["xuei"],
|
||||
"hun": ["xun"],
|
||||
"huo": ["xuo"],
|
||||
"ji": ["dʑi"],
|
||||
"jia": ["dʑia"],
|
||||
"jian": ["dʑiɛn"],
|
||||
"jiang": ["dʑiɑŋ"],
|
||||
"jiao": ["dʑiaʌ"],
|
||||
"jie": ["dʑie"],
|
||||
"jin": ["dʑin"],
|
||||
"jing": ["dʑɨŋ"],
|
||||
"jiong": ["dʑioŋ"],
|
||||
"jiu": ["dʑio"],
|
||||
"ju": ["dʑy"],
|
||||
"juan": ["dʑyɛn"],
|
||||
"jue": ["dʑye"],
|
||||
"jun": ["dʑyn"],
|
||||
"ka": ["ka"],
|
||||
"kai": ["kai"],
|
||||
"kan": ["kan"],
|
||||
"kang": ["kɑŋ"],
|
||||
"kao": ["kaʌ"],
|
||||
"ke": ["kø"],
|
||||
"kei": ["kei"],
|
||||
"ken": ["kœn"],
|
||||
"keng": ["kɵŋ"],
|
||||
"kong": ["koŋ"],
|
||||
"kou": ["kou"],
|
||||
"ku": ["ku"],
|
||||
"kua": ["kua"],
|
||||
"kuai": ["kuai"],
|
||||
"kuan": ["kuan"],
|
||||
"kuang": ["kuɑŋ"],
|
||||
"kui": ["kuei"],
|
||||
"kun": ["kun"],
|
||||
"kuo": ["kuo"],
|
||||
"la": ["la"],
|
||||
"lai": ["lai"],
|
||||
"lan": ["lan"],
|
||||
"lang": ["lɑŋ"],
|
||||
"lao": ["laʌ"],
|
||||
"le": ["lø"],
|
||||
"lei": ["lei"],
|
||||
"leng": ["lɵŋ"],
|
||||
"li": ["li"],
|
||||
"lia": ["lia"],
|
||||
"lian": ["liɛn"],
|
||||
"liang": ["liɑŋ"],
|
||||
"liao": ["liaʌ"],
|
||||
"lie": ["lie"],
|
||||
"lin": ["lin"],
|
||||
"ling": ["lɨŋ"],
|
||||
"liu": ["lio"],
|
||||
"lo": ["lo"],
|
||||
"long": ["loŋ"],
|
||||
"lou": ["lou"],
|
||||
"lu": ["lu"],
|
||||
"lv": ["ly"],
|
||||
"luan": ["luan"],
|
||||
"lve": ["lye"],
|
||||
"lue": ["lue"],
|
||||
"lun": ["lun"],
|
||||
"luo": ["luo"],
|
||||
"ma": ["ma"],
|
||||
"mai": ["mai"],
|
||||
"man": ["man"],
|
||||
"mang": ["mɑŋ"],
|
||||
"mao": ["maʌ"],
|
||||
"me": ["mø"],
|
||||
"mei": ["mei"],
|
||||
"men": ["mœn"],
|
||||
"meng": ["mɵŋ"],
|
||||
"mi": ["mi"],
|
||||
"mian": ["miɛn"],
|
||||
"miao": ["miaʌ"],
|
||||
"mie": ["mie"],
|
||||
"min": ["min"],
|
||||
"ming": ["mɨŋ"],
|
||||
"miu": ["mio"],
|
||||
"mo": ["mo"],
|
||||
"mou": ["mou"],
|
||||
"mu": ["mu"],
|
||||
"na": ["na"],
|
||||
"nai": ["nai"],
|
||||
"nan": ["nan"],
|
||||
"nang": ["nɑŋ"],
|
||||
"nao": ["naʌ"],
|
||||
"ne": ["nø"],
|
||||
"nei": ["nei"],
|
||||
"nen": ["nœn"],
|
||||
"neng": ["nɵŋ"],
|
||||
"ni": ["ni"],
|
||||
"nia": ["nia"],
|
||||
"nian": ["niɛn"],
|
||||
"niang": ["niɑŋ"],
|
||||
"niao": ["niaʌ"],
|
||||
"nie": ["nie"],
|
||||
"nin": ["nin"],
|
||||
"ning": ["nɨŋ"],
|
||||
"niu": ["nio"],
|
||||
"nong": ["noŋ"],
|
||||
"nou": ["nou"],
|
||||
"nu": ["nu"],
|
||||
"nv": ["ny"],
|
||||
"nuan": ["nuan"],
|
||||
"nve": ["nye"],
|
||||
"nue": ["nye"],
|
||||
"nuo": ["nuo"],
|
||||
"o": ["o"],
|
||||
"ou": ["ou"],
|
||||
"pa": ["pa"],
|
||||
"pai": ["pai"],
|
||||
"pan": ["pan"],
|
||||
"pang": ["pɑŋ"],
|
||||
"pao": ["paʌ"],
|
||||
"pe": ["pø"],
|
||||
"pei": ["pei"],
|
||||
"pen": ["pœn"],
|
||||
"peng": ["pɵŋ"],
|
||||
"pi": ["pi"],
|
||||
"pian": ["piɛn"],
|
||||
"piao": ["piaʌ"],
|
||||
"pie": ["pie"],
|
||||
"pin": ["pin"],
|
||||
"ping": ["pɨŋ"],
|
||||
"po": ["po"],
|
||||
"pou": ["pou"],
|
||||
"pu": ["pu"],
|
||||
"qi": ["tɕi"],
|
||||
"qia": ["tɕia"],
|
||||
"qian": ["tɕiɛn"],
|
||||
"qiang": ["tɕiɑŋ"],
|
||||
"qiao": ["tɕiaʌ"],
|
||||
"qie": ["tɕie"],
|
||||
"qin": ["tɕin"],
|
||||
"qing": ["tɕɨŋ"],
|
||||
"qiong": ["tɕioŋ"],
|
||||
"qiu": ["tɕio"],
|
||||
"qu": ["tɕy"],
|
||||
"quan": ["tɕyɛn"],
|
||||
"que": ["tɕye"],
|
||||
"qun": ["tɕyn"],
|
||||
"ran": ["ʐan"],
|
||||
"rang": ["ʐɑŋ"],
|
||||
"rao": ["ʐaʌ"],
|
||||
"re": ["ʐø"],
|
||||
"ren": ["ʐœn"],
|
||||
"reng": ["ʐɵŋ"],
|
||||
"ri": ["ʐʏ"],
|
||||
"rong": ["ʐoŋ"],
|
||||
"rou": ["ʐou"],
|
||||
"ru": ["ʐu"],
|
||||
"rua": ["ʐua"],
|
||||
"ruan": ["ʐuan"],
|
||||
"rui": ["ʐuei"],
|
||||
"run": ["ʐun"],
|
||||
"ruo": ["ʐuo"],
|
||||
"sa": ["sa"],
|
||||
"sai": ["sai"],
|
||||
"san": ["san"],
|
||||
"sang": ["sɑŋ"],
|
||||
"sao": ["saʌ"],
|
||||
"se": ["sø"],
|
||||
"sen": ["sœn"],
|
||||
"seng": ["sɵŋ"],
|
||||
"sha": ["ʂa"],
|
||||
"shai": ["ʂai"],
|
||||
"shan": ["ʂan"],
|
||||
"shang": ["ʂɑŋ"],
|
||||
"shao": ["ʂaʌ"],
|
||||
"she": ["ʂø"],
|
||||
"shei": ["ʂei"],
|
||||
"shen": ["ʂœn"],
|
||||
"sheng": ["ʂɵŋ"],
|
||||
"shi": ["ʂʏ"],
|
||||
"shou": ["ʂou"],
|
||||
"shu": ["ʂu"],
|
||||
"shua": ["ʂua"],
|
||||
"shuai": ["ʂuai"],
|
||||
"shuan": ["ʂuan"],
|
||||
"shuang": ["ʂuɑŋ"],
|
||||
"shui": ["ʂuei"],
|
||||
"shun": ["ʂun"],
|
||||
"shuo": ["ʂuo"],
|
||||
"si": ["sɪ"],
|
||||
"song": ["soŋ"],
|
||||
"sou": ["sou"],
|
||||
"su": ["su"],
|
||||
"suan": ["suan"],
|
||||
"sui": ["suei"],
|
||||
"sun": ["sun"],
|
||||
"suo": ["suo"],
|
||||
"ta": ["ta"],
|
||||
"tai": ["tai"],
|
||||
"tan": ["tan"],
|
||||
"tang": ["tɑŋ"],
|
||||
"tao": ["taʌ"],
|
||||
"te": ["tø"],
|
||||
"tei": ["tei"],
|
||||
"teng": ["tɵŋ"],
|
||||
"ti": ["ti"],
|
||||
"tian": ["tiɛn"],
|
||||
"tiao": ["tiaʌ"],
|
||||
"tie": ["tie"],
|
||||
"ting": ["tɨŋ"],
|
||||
"tong": ["toŋ"],
|
||||
"tou": ["tou"],
|
||||
"tu": ["tu"],
|
||||
"tuan": ["tuan"],
|
||||
"tui": ["tuei"],
|
||||
"tun": ["tun"],
|
||||
"tuo": ["tuo"],
|
||||
"wa": ["wa"],
|
||||
"wai": ["wai"],
|
||||
"wan": ["wan"],
|
||||
"wang": ["wɑŋ"],
|
||||
"wei": ["wei"],
|
||||
"wen": ["wœn"],
|
||||
"weng": ["wɵŋ"],
|
||||
"wo": ["wo"],
|
||||
"wu": ["wu"],
|
||||
"xi": ["ɕi"],
|
||||
"xia": ["ɕia"],
|
||||
"xian": ["ɕiɛn"],
|
||||
"xiang": ["ɕiɑŋ"],
|
||||
"xiao": ["ɕiaʌ"],
|
||||
"xie": ["ɕie"],
|
||||
"xin": ["ɕin"],
|
||||
"xing": ["ɕɨŋ"],
|
||||
"xiong": ["ɕioŋ"],
|
||||
"xiu": ["ɕio"],
|
||||
"xu": ["ɕy"],
|
||||
"xuan": ["ɕyɛn"],
|
||||
"xue": ["ɕye"],
|
||||
"xun": ["ɕyn"],
|
||||
"ya": ["ia"],
|
||||
"yan": ["iɛn"],
|
||||
"yang": ["iɑŋ"],
|
||||
"yao": ["iaʌ"],
|
||||
"ye": ["ie"],
|
||||
"yi": ["i"],
|
||||
"yin": ["in"],
|
||||
"ying": ["ɨŋ"],
|
||||
"yo": ["io"],
|
||||
"yong": ["ioŋ"],
|
||||
"you": ["io"],
|
||||
"yu": ["y"],
|
||||
"yuan": ["yɛn"],
|
||||
"yue": ["ye"],
|
||||
"yun": ["yn"],
|
||||
"za": ["dza"],
|
||||
"zai": ["dzai"],
|
||||
"zan": ["dzan"],
|
||||
"zang": ["dzɑŋ"],
|
||||
"zao": ["dzaʌ"],
|
||||
"ze": ["dzø"],
|
||||
"zei": ["dzei"],
|
||||
"zen": ["dzœn"],
|
||||
"zeng": ["dzɵŋ"],
|
||||
"zha": ["dʒa"],
|
||||
"zhai": ["dʒai"],
|
||||
"zhan": ["dʒan"],
|
||||
"zhang": ["dʒɑŋ"],
|
||||
"zhao": ["dʒaʌ"],
|
||||
"zhe": ["dʒø"],
|
||||
# "zhei": ["dʒei"], it doesn't exist
|
||||
"zhen": ["dʒœn"],
|
||||
"zheng": ["dʒɵŋ"],
|
||||
"zhi": ["dʒʏ"],
|
||||
"zhong": ["dʒoŋ"],
|
||||
"zhou": ["dʒou"],
|
||||
"zhu": ["dʒu"],
|
||||
"zhua": ["dʒua"],
|
||||
"zhuai": ["dʒuai"],
|
||||
"zhuan": ["dʒuan"],
|
||||
"zhuang": ["dʒuɑŋ"],
|
||||
"zhui": ["dʒuei"],
|
||||
"zhun": ["dʒun"],
|
||||
"zhuo": ["dʒuo"],
|
||||
"zi": ["dzɪ"],
|
||||
"zong": ["dzoŋ"],
|
||||
"zou": ["dzou"],
|
||||
"zu": ["dzu"],
|
||||
"zuan": ["dzuan"],
|
||||
"zui": ["dzuei"],
|
||||
"zun": ["dzun"],
|
||||
"zuo": ["dzuo"],
|
||||
}
|
||||
@@ -0,0 +1,171 @@
|
||||
"""Set of default text cleaners"""
|
||||
# TODO: pick the cleaner for languages dynamically
|
||||
|
||||
import re
|
||||
|
||||
from anyascii import anyascii
|
||||
|
||||
from TTS.tts.utils.text.chinese_mandarin.numbers import replace_numbers_to_characters_in_text
|
||||
|
||||
from .english.abbreviations import abbreviations_en
|
||||
from .english.number_norm import normalize_numbers as en_normalize_numbers
|
||||
from .english.time_norm import expand_time_english
|
||||
from .french.abbreviations import abbreviations_fr
|
||||
|
||||
# Regular expression matching whitespace:
|
||||
_whitespace_re = re.compile(r"\s+")
|
||||
|
||||
|
||||
def expand_abbreviations(text, lang="en"):
|
||||
if lang == "en":
|
||||
_abbreviations = abbreviations_en
|
||||
elif lang == "fr":
|
||||
_abbreviations = abbreviations_fr
|
||||
for regex, replacement in _abbreviations:
|
||||
text = re.sub(regex, replacement, text)
|
||||
return text
|
||||
|
||||
|
||||
def lowercase(text):
|
||||
return text.lower()
|
||||
|
||||
|
||||
def collapse_whitespace(text):
|
||||
return re.sub(_whitespace_re, " ", text).strip()
|
||||
|
||||
|
||||
def convert_to_ascii(text):
|
||||
return anyascii(text)
|
||||
|
||||
|
||||
def remove_aux_symbols(text):
|
||||
text = re.sub(r"[\<\>\(\)\[\]\"]+", "", text)
|
||||
return text
|
||||
|
||||
|
||||
def replace_symbols(text, lang="en"):
|
||||
"""Replace symbols based on the lenguage tag.
|
||||
|
||||
Args:
|
||||
text:
|
||||
Input text.
|
||||
lang:
|
||||
Lenguage identifier. ex: "en", "fr", "pt", "ca".
|
||||
|
||||
Returns:
|
||||
The modified text
|
||||
example:
|
||||
input args:
|
||||
text: "si l'avi cau, diguem-ho"
|
||||
lang: "ca"
|
||||
Output:
|
||||
text: "si lavi cau, diguemho"
|
||||
"""
|
||||
text = text.replace(";", ",")
|
||||
text = text.replace("-", " ") if lang != "ca" else text.replace("-", "")
|
||||
text = text.replace(":", ",")
|
||||
if lang == "en":
|
||||
text = text.replace("&", " and ")
|
||||
elif lang == "fr":
|
||||
text = text.replace("&", " et ")
|
||||
elif lang == "pt":
|
||||
text = text.replace("&", " e ")
|
||||
elif lang == "ca":
|
||||
text = text.replace("&", " i ")
|
||||
text = text.replace("'", "")
|
||||
return text
|
||||
|
||||
|
||||
def basic_cleaners(text):
|
||||
"""Basic pipeline that lowercases and collapses whitespace without transliteration."""
|
||||
text = lowercase(text)
|
||||
text = collapse_whitespace(text)
|
||||
return text
|
||||
|
||||
|
||||
def transliteration_cleaners(text):
|
||||
"""Pipeline for non-English text that transliterates to ASCII."""
|
||||
# text = convert_to_ascii(text)
|
||||
text = lowercase(text)
|
||||
text = collapse_whitespace(text)
|
||||
return text
|
||||
|
||||
|
||||
def basic_german_cleaners(text):
|
||||
"""Pipeline for German text"""
|
||||
text = lowercase(text)
|
||||
text = collapse_whitespace(text)
|
||||
return text
|
||||
|
||||
|
||||
# TODO: elaborate it
|
||||
def basic_turkish_cleaners(text):
|
||||
"""Pipeline for Turkish text"""
|
||||
text = text.replace("I", "ı")
|
||||
text = lowercase(text)
|
||||
text = collapse_whitespace(text)
|
||||
return text
|
||||
|
||||
|
||||
def english_cleaners(text):
|
||||
"""Pipeline for English text, including number and abbreviation expansion."""
|
||||
# text = convert_to_ascii(text)
|
||||
text = lowercase(text)
|
||||
text = expand_time_english(text)
|
||||
text = en_normalize_numbers(text)
|
||||
text = expand_abbreviations(text)
|
||||
text = replace_symbols(text)
|
||||
text = remove_aux_symbols(text)
|
||||
text = collapse_whitespace(text)
|
||||
return text
|
||||
|
||||
|
||||
def phoneme_cleaners(text):
|
||||
"""Pipeline for phonemes mode, including number and abbreviation expansion."""
|
||||
text = en_normalize_numbers(text)
|
||||
text = expand_abbreviations(text)
|
||||
text = replace_symbols(text)
|
||||
text = remove_aux_symbols(text)
|
||||
text = collapse_whitespace(text)
|
||||
return text
|
||||
|
||||
|
||||
def french_cleaners(text):
|
||||
"""Pipeline for French text. There is no need to expand numbers, phonemizer already does that"""
|
||||
text = expand_abbreviations(text, lang="fr")
|
||||
text = lowercase(text)
|
||||
text = replace_symbols(text, lang="fr")
|
||||
text = remove_aux_symbols(text)
|
||||
text = collapse_whitespace(text)
|
||||
return text
|
||||
|
||||
|
||||
def portuguese_cleaners(text):
|
||||
"""Basic pipeline for Portuguese text. There is no need to expand abbreviation and
|
||||
numbers, phonemizer already does that"""
|
||||
text = lowercase(text)
|
||||
text = replace_symbols(text, lang="pt")
|
||||
text = remove_aux_symbols(text)
|
||||
text = collapse_whitespace(text)
|
||||
return text
|
||||
|
||||
|
||||
def chinese_mandarin_cleaners(text: str) -> str:
|
||||
"""Basic pipeline for chinese"""
|
||||
text = replace_numbers_to_characters_in_text(text)
|
||||
return text
|
||||
|
||||
|
||||
def multilingual_cleaners(text):
|
||||
"""Pipeline for multilingual text"""
|
||||
text = lowercase(text)
|
||||
text = replace_symbols(text, lang=None)
|
||||
text = remove_aux_symbols(text)
|
||||
text = collapse_whitespace(text)
|
||||
return text
|
||||
|
||||
|
||||
def no_cleaners(text):
|
||||
# remove newline characters
|
||||
text = text.replace("\n", "")
|
||||
return text
|
||||
@@ -0,0 +1,151 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
import re
|
||||
|
||||
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",
|
||||
]
|
||||
|
||||
|
||||
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())
|
||||
|
||||
@staticmethod
|
||||
def get_arpabet(word, cmudict, punctuation_symbols):
|
||||
first_symbol, last_symbol = "", ""
|
||||
if word and word[0] in punctuation_symbols:
|
||||
first_symbol = word[0]
|
||||
word = word[1:]
|
||||
if word and word[-1] in punctuation_symbols:
|
||||
last_symbol = word[-1]
|
||||
word = word[:-1]
|
||||
arpabet = cmudict.lookup(word)
|
||||
if arpabet is not None:
|
||||
return first_symbol + "{%s}" % arpabet[0] + last_symbol
|
||||
return first_symbol + word + last_symbol
|
||||
|
||||
|
||||
_alt_re = re.compile(r"\([0-9]+\)")
|
||||
|
||||
|
||||
def _parse_cmudict(file):
|
||||
cmudict = {}
|
||||
for line in file:
|
||||
if 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_SYMBOLS:
|
||||
return None
|
||||
return " ".join(parts)
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,26 @@
|
||||
import re
|
||||
|
||||
# List of (regular expression, replacement) pairs for abbreviations in english:
|
||||
abbreviations_en = [
|
||||
(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"),
|
||||
]
|
||||
]
|
||||
@@ -0,0 +1,97 @@
|
||||
""" from https://github.com/keithito/tacotron """
|
||||
|
||||
import re
|
||||
from typing import Dict
|
||||
|
||||
import inflect
|
||||
|
||||
_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]+)")
|
||||
_currency_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_currency(value: str, inflection: Dict[float, str]) -> str:
|
||||
parts = value.replace(",", "").split(".")
|
||||
if len(parts) > 2:
|
||||
return f"{value} {inflection[2]}" # Unexpected format
|
||||
text = []
|
||||
integer = int(parts[0]) if parts[0] else 0
|
||||
if integer > 0:
|
||||
integer_unit = inflection.get(integer, inflection[2])
|
||||
text.append(f"{integer} {integer_unit}")
|
||||
fraction = int(parts[1]) if len(parts) > 1 and parts[1] else 0
|
||||
if fraction > 0:
|
||||
fraction_unit = inflection.get(fraction / 100, inflection[0.02])
|
||||
text.append(f"{fraction} {fraction_unit}")
|
||||
if len(text) == 0:
|
||||
return f"zero {inflection[2]}"
|
||||
return " ".join(text)
|
||||
|
||||
|
||||
def _expand_currency(m: "re.Match") -> str:
|
||||
currencies = {
|
||||
"$": {
|
||||
0.01: "cent",
|
||||
0.02: "cents",
|
||||
1: "dollar",
|
||||
2: "dollars",
|
||||
},
|
||||
"€": {
|
||||
0.01: "cent",
|
||||
0.02: "cents",
|
||||
1: "euro",
|
||||
2: "euros",
|
||||
},
|
||||
"£": {
|
||||
0.01: "penny",
|
||||
0.02: "pence",
|
||||
1: "pound sterling",
|
||||
2: "pounds sterling",
|
||||
},
|
||||
"¥": {
|
||||
# TODO rin
|
||||
0.02: "sen",
|
||||
2: "yen",
|
||||
},
|
||||
}
|
||||
unit = m.group(1)
|
||||
currency = currencies[unit]
|
||||
value = m.group(2)
|
||||
return __expand_currency(value, currency)
|
||||
|
||||
|
||||
def _expand_ordinal(m):
|
||||
return _inflect.number_to_words(m.group(0))
|
||||
|
||||
|
||||
def _expand_number(m):
|
||||
num = int(m.group(0))
|
||||
if 1000 < num < 3000:
|
||||
if num == 2000:
|
||||
return "two thousand"
|
||||
if 2000 < num < 2010:
|
||||
return "two thousand " + _inflect.number_to_words(num % 100)
|
||||
if num % 100 == 0:
|
||||
return _inflect.number_to_words(num // 100) + " hundred"
|
||||
return _inflect.number_to_words(num, andword="", zero="oh", group=2).replace(", ", " ")
|
||||
return _inflect.number_to_words(num, andword="")
|
||||
|
||||
|
||||
def normalize_numbers(text):
|
||||
text = re.sub(_comma_number_re, _remove_commas, text)
|
||||
text = re.sub(_currency_re, _expand_currency, 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
|
||||
@@ -0,0 +1,47 @@
|
||||
import re
|
||||
|
||||
import inflect
|
||||
|
||||
_inflect = inflect.engine()
|
||||
|
||||
_time_re = re.compile(
|
||||
r"""\b
|
||||
((0?[0-9])|(1[0-1])|(1[2-9])|(2[0-3])) # hours
|
||||
:
|
||||
([0-5][0-9]) # minutes
|
||||
\s*(a\\.m\\.|am|pm|p\\.m\\.|a\\.m|p\\.m)? # am/pm
|
||||
\b""",
|
||||
re.IGNORECASE | re.X,
|
||||
)
|
||||
|
||||
|
||||
def _expand_num(n: int) -> str:
|
||||
return _inflect.number_to_words(n)
|
||||
|
||||
|
||||
def _expand_time_english(match: "re.Match") -> str:
|
||||
hour = int(match.group(1))
|
||||
past_noon = hour >= 12
|
||||
time = []
|
||||
if hour > 12:
|
||||
hour -= 12
|
||||
elif hour == 0:
|
||||
hour = 12
|
||||
past_noon = True
|
||||
time.append(_expand_num(hour))
|
||||
|
||||
minute = int(match.group(6))
|
||||
if minute > 0:
|
||||
if minute < 10:
|
||||
time.append("oh")
|
||||
time.append(_expand_num(minute))
|
||||
am_pm = match.group(7)
|
||||
if am_pm is None:
|
||||
time.append("p m" if past_noon else "a m")
|
||||
else:
|
||||
time.extend(list(am_pm.replace(".", "")))
|
||||
return " ".join(time)
|
||||
|
||||
|
||||
def expand_time_english(text: str) -> str:
|
||||
return re.sub(_time_re, _expand_time_english, text)
|
||||
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,48 @@
|
||||
import re
|
||||
|
||||
# List of (regular expression, replacement) pairs for abbreviations in french:
|
||||
abbreviations_fr = [
|
||||
(re.compile("\\b%s\\." % x[0], re.IGNORECASE), x[1])
|
||||
for x in [
|
||||
("M", "monsieur"),
|
||||
("Mlle", "mademoiselle"),
|
||||
("Mlles", "mesdemoiselles"),
|
||||
("Mme", "Madame"),
|
||||
("Mmes", "Mesdames"),
|
||||
("N.B", "nota bene"),
|
||||
("M", "monsieur"),
|
||||
("p.c.q", "parce que"),
|
||||
("Pr", "professeur"),
|
||||
("qqch", "quelque chose"),
|
||||
("rdv", "rendez-vous"),
|
||||
("max", "maximum"),
|
||||
("min", "minimum"),
|
||||
("no", "numéro"),
|
||||
("adr", "adresse"),
|
||||
("dr", "docteur"),
|
||||
("st", "saint"),
|
||||
("co", "companie"),
|
||||
("jr", "junior"),
|
||||
("sgt", "sergent"),
|
||||
("capt", "capitain"),
|
||||
("col", "colonel"),
|
||||
("av", "avenue"),
|
||||
("av. J.-C", "avant Jésus-Christ"),
|
||||
("apr. J.-C", "après Jésus-Christ"),
|
||||
("art", "article"),
|
||||
("boul", "boulevard"),
|
||||
("c.-à-d", "c’est-à-dire"),
|
||||
("etc", "et cetera"),
|
||||
("ex", "exemple"),
|
||||
("excl", "exclusivement"),
|
||||
("boul", "boulevard"),
|
||||
]
|
||||
] + [
|
||||
(re.compile("\\b%s" % x[0]), x[1])
|
||||
for x in [
|
||||
("Mlle", "mademoiselle"),
|
||||
("Mlles", "mesdemoiselles"),
|
||||
("Mme", "Madame"),
|
||||
("Mmes", "Mesdames"),
|
||||
]
|
||||
]
|
||||
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,470 @@
|
||||
# Convert Japanese text to phonemes which is
|
||||
# compatible with Julius https://github.com/julius-speech/segmentation-kit
|
||||
|
||||
import re
|
||||
import unicodedata
|
||||
|
||||
try:
|
||||
import MeCab
|
||||
except ImportError as e:
|
||||
raise ImportError("Japanese requires mecab-python3 and unidic-lite.") from e
|
||||
from num2words import num2words
|
||||
|
||||
_CONVRULES = [
|
||||
# Conversion of 2 letters
|
||||
"アァ/ a a",
|
||||
"イィ/ i i",
|
||||
"イェ/ i e",
|
||||
"イャ/ y a",
|
||||
"ウゥ/ u:",
|
||||
"エェ/ e e",
|
||||
"オォ/ o:",
|
||||
"カァ/ k a:",
|
||||
"キィ/ k i:",
|
||||
"クゥ/ k u:",
|
||||
"クャ/ ky a",
|
||||
"クュ/ ky u",
|
||||
"クョ/ ky o",
|
||||
"ケェ/ k e:",
|
||||
"コォ/ k o:",
|
||||
"ガァ/ g a:",
|
||||
"ギィ/ g i:",
|
||||
"グゥ/ g u:",
|
||||
"グャ/ gy a",
|
||||
"グュ/ gy u",
|
||||
"グョ/ gy o",
|
||||
"ゲェ/ g e:",
|
||||
"ゴォ/ g o:",
|
||||
"サァ/ s a:",
|
||||
"シィ/ sh i:",
|
||||
"スゥ/ s u:",
|
||||
"スャ/ sh a",
|
||||
"スュ/ sh u",
|
||||
"スョ/ sh o",
|
||||
"セェ/ s e:",
|
||||
"ソォ/ s o:",
|
||||
"ザァ/ z a:",
|
||||
"ジィ/ j i:",
|
||||
"ズゥ/ z u:",
|
||||
"ズャ/ zy a",
|
||||
"ズュ/ zy u",
|
||||
"ズョ/ zy o",
|
||||
"ゼェ/ z e:",
|
||||
"ゾォ/ z o:",
|
||||
"タァ/ t a:",
|
||||
"チィ/ ch i:",
|
||||
"ツァ/ ts a",
|
||||
"ツィ/ ts i",
|
||||
"ツゥ/ ts u:",
|
||||
"ツャ/ ch a",
|
||||
"ツュ/ ch u",
|
||||
"ツョ/ ch o",
|
||||
"ツェ/ ts e",
|
||||
"ツォ/ ts o",
|
||||
"テェ/ t e:",
|
||||
"トォ/ t o:",
|
||||
"ダァ/ d a:",
|
||||
"ヂィ/ j i:",
|
||||
"ヅゥ/ d u:",
|
||||
"ヅャ/ zy a",
|
||||
"ヅュ/ zy u",
|
||||
"ヅョ/ zy o",
|
||||
"デェ/ d e:",
|
||||
"ドォ/ d o:",
|
||||
"ナァ/ n a:",
|
||||
"ニィ/ n i:",
|
||||
"ヌゥ/ n u:",
|
||||
"ヌャ/ ny a",
|
||||
"ヌュ/ ny u",
|
||||
"ヌョ/ ny o",
|
||||
"ネェ/ n e:",
|
||||
"ノォ/ n o:",
|
||||
"ハァ/ h a:",
|
||||
"ヒィ/ h i:",
|
||||
"フゥ/ f u:",
|
||||
"フャ/ hy a",
|
||||
"フュ/ hy u",
|
||||
"フョ/ hy o",
|
||||
"ヘェ/ h e:",
|
||||
"ホォ/ h o:",
|
||||
"バァ/ b a:",
|
||||
"ビィ/ b i:",
|
||||
"ブゥ/ b u:",
|
||||
"フャ/ hy a",
|
||||
"ブュ/ by u",
|
||||
"フョ/ hy o",
|
||||
"ベェ/ b e:",
|
||||
"ボォ/ b o:",
|
||||
"パァ/ p a:",
|
||||
"ピィ/ p i:",
|
||||
"プゥ/ p u:",
|
||||
"プャ/ py a",
|
||||
"プュ/ py u",
|
||||
"プョ/ py o",
|
||||
"ペェ/ p e:",
|
||||
"ポォ/ p o:",
|
||||
"マァ/ m a:",
|
||||
"ミィ/ m i:",
|
||||
"ムゥ/ m u:",
|
||||
"ムャ/ my a",
|
||||
"ムュ/ my u",
|
||||
"ムョ/ my o",
|
||||
"メェ/ m e:",
|
||||
"モォ/ m o:",
|
||||
"ヤァ/ y a:",
|
||||
"ユゥ/ y u:",
|
||||
"ユャ/ y a:",
|
||||
"ユュ/ y u:",
|
||||
"ユョ/ y o:",
|
||||
"ヨォ/ y o:",
|
||||
"ラァ/ r a:",
|
||||
"リィ/ r i:",
|
||||
"ルゥ/ r u:",
|
||||
"ルャ/ ry a",
|
||||
"ルュ/ ry u",
|
||||
"ルョ/ ry o",
|
||||
"レェ/ r e:",
|
||||
"ロォ/ r o:",
|
||||
"ワァ/ w a:",
|
||||
"ヲォ/ o:",
|
||||
"ディ/ d i",
|
||||
"デェ/ d e:",
|
||||
"デャ/ dy a",
|
||||
"デュ/ dy u",
|
||||
"デョ/ dy o",
|
||||
"ティ/ t i",
|
||||
"テェ/ t e:",
|
||||
"テャ/ ty a",
|
||||
"テュ/ ty u",
|
||||
"テョ/ ty o",
|
||||
"スィ/ s i",
|
||||
"ズァ/ z u a",
|
||||
"ズィ/ z i",
|
||||
"ズゥ/ z u",
|
||||
"ズャ/ zy a",
|
||||
"ズュ/ zy u",
|
||||
"ズョ/ zy o",
|
||||
"ズェ/ z e",
|
||||
"ズォ/ z o",
|
||||
"キャ/ ky a",
|
||||
"キュ/ ky u",
|
||||
"キョ/ ky o",
|
||||
"シャ/ sh a",
|
||||
"シュ/ sh u",
|
||||
"シェ/ sh e",
|
||||
"ショ/ sh o",
|
||||
"チャ/ ch a",
|
||||
"チュ/ ch u",
|
||||
"チェ/ ch e",
|
||||
"チョ/ ch o",
|
||||
"トゥ/ t u",
|
||||
"トャ/ ty a",
|
||||
"トュ/ ty u",
|
||||
"トョ/ ty o",
|
||||
"ドァ/ d o a",
|
||||
"ドゥ/ d u",
|
||||
"ドャ/ dy a",
|
||||
"ドュ/ dy u",
|
||||
"ドョ/ dy o",
|
||||
"ドォ/ d o:",
|
||||
"ニャ/ ny a",
|
||||
"ニュ/ ny u",
|
||||
"ニョ/ ny o",
|
||||
"ヒャ/ hy a",
|
||||
"ヒュ/ hy u",
|
||||
"ヒョ/ hy o",
|
||||
"ミャ/ my a",
|
||||
"ミュ/ my u",
|
||||
"ミョ/ my o",
|
||||
"リャ/ ry a",
|
||||
"リュ/ ry u",
|
||||
"リョ/ ry o",
|
||||
"ギャ/ gy a",
|
||||
"ギュ/ gy u",
|
||||
"ギョ/ gy o",
|
||||
"ヂェ/ j e",
|
||||
"ヂャ/ j a",
|
||||
"ヂュ/ j u",
|
||||
"ヂョ/ j o",
|
||||
"ジェ/ j e",
|
||||
"ジャ/ j a",
|
||||
"ジュ/ j u",
|
||||
"ジョ/ j o",
|
||||
"ビャ/ by a",
|
||||
"ビュ/ by u",
|
||||
"ビョ/ by o",
|
||||
"ピャ/ py a",
|
||||
"ピュ/ py u",
|
||||
"ピョ/ py o",
|
||||
"ウァ/ u a",
|
||||
"ウィ/ w i",
|
||||
"ウェ/ w e",
|
||||
"ウォ/ w o",
|
||||
"ファ/ f a",
|
||||
"フィ/ f i",
|
||||
"フゥ/ f u",
|
||||
"フャ/ hy a",
|
||||
"フュ/ hy u",
|
||||
"フョ/ hy o",
|
||||
"フェ/ f e",
|
||||
"フォ/ f o",
|
||||
"ヴァ/ b a",
|
||||
"ヴィ/ b i",
|
||||
"ヴェ/ b e",
|
||||
"ヴォ/ b o",
|
||||
"ヴュ/ by u",
|
||||
# Conversion of 1 letter
|
||||
"ア/ a",
|
||||
"イ/ i",
|
||||
"ウ/ u",
|
||||
"エ/ e",
|
||||
"オ/ o",
|
||||
"カ/ k a",
|
||||
"キ/ k i",
|
||||
"ク/ k u",
|
||||
"ケ/ k e",
|
||||
"コ/ k o",
|
||||
"サ/ s a",
|
||||
"シ/ sh i",
|
||||
"ス/ s u",
|
||||
"セ/ s e",
|
||||
"ソ/ s o",
|
||||
"タ/ t a",
|
||||
"チ/ ch i",
|
||||
"ツ/ ts u",
|
||||
"テ/ t e",
|
||||
"ト/ t o",
|
||||
"ナ/ n a",
|
||||
"ニ/ n i",
|
||||
"ヌ/ n u",
|
||||
"ネ/ n e",
|
||||
"ノ/ n o",
|
||||
"ハ/ h a",
|
||||
"ヒ/ h i",
|
||||
"フ/ f u",
|
||||
"ヘ/ h e",
|
||||
"ホ/ h o",
|
||||
"マ/ m a",
|
||||
"ミ/ m i",
|
||||
"ム/ m u",
|
||||
"メ/ m e",
|
||||
"モ/ m o",
|
||||
"ラ/ r a",
|
||||
"リ/ r i",
|
||||
"ル/ r u",
|
||||
"レ/ r e",
|
||||
"ロ/ r o",
|
||||
"ガ/ g a",
|
||||
"ギ/ g i",
|
||||
"グ/ g u",
|
||||
"ゲ/ g e",
|
||||
"ゴ/ g o",
|
||||
"ザ/ z a",
|
||||
"ジ/ j i",
|
||||
"ズ/ z u",
|
||||
"ゼ/ z e",
|
||||
"ゾ/ z o",
|
||||
"ダ/ d a",
|
||||
"ヂ/ j i",
|
||||
"ヅ/ z u",
|
||||
"デ/ d e",
|
||||
"ド/ d o",
|
||||
"バ/ b a",
|
||||
"ビ/ b i",
|
||||
"ブ/ b u",
|
||||
"ベ/ b e",
|
||||
"ボ/ b o",
|
||||
"パ/ p a",
|
||||
"ピ/ p i",
|
||||
"プ/ p u",
|
||||
"ペ/ p e",
|
||||
"ポ/ p o",
|
||||
"ヤ/ y a",
|
||||
"ユ/ y u",
|
||||
"ヨ/ y o",
|
||||
"ワ/ w a",
|
||||
"ヰ/ i",
|
||||
"ヱ/ e",
|
||||
"ヲ/ o",
|
||||
"ン/ N",
|
||||
"ッ/ q",
|
||||
"ヴ/ b u",
|
||||
"ー/:",
|
||||
# Try converting broken text
|
||||
"ァ/ a",
|
||||
"ィ/ i",
|
||||
"ゥ/ u",
|
||||
"ェ/ e",
|
||||
"ォ/ o",
|
||||
"ヮ/ w a",
|
||||
"ォ/ o",
|
||||
# Symbols
|
||||
"、/ ,",
|
||||
"。/ .",
|
||||
"!/ !",
|
||||
"?/ ?",
|
||||
"・/ ,",
|
||||
]
|
||||
|
||||
_COLON_RX = re.compile(":+")
|
||||
_REJECT_RX = re.compile("[^ a-zA-Z:,.?]")
|
||||
|
||||
|
||||
def _makerulemap():
|
||||
l = [tuple(x.split("/")) for x in _CONVRULES]
|
||||
return tuple({k: v for k, v in l if len(k) == i} for i in (1, 2))
|
||||
|
||||
|
||||
_RULEMAP1, _RULEMAP2 = _makerulemap()
|
||||
|
||||
|
||||
def kata2phoneme(text: str) -> str:
|
||||
"""Convert katakana text to phonemes."""
|
||||
text = text.strip()
|
||||
res = ""
|
||||
while text:
|
||||
if len(text) >= 2:
|
||||
x = _RULEMAP2.get(text[:2])
|
||||
if x is not None:
|
||||
text = text[2:]
|
||||
res += x
|
||||
continue
|
||||
x = _RULEMAP1.get(text[0])
|
||||
if x is not None:
|
||||
text = text[1:]
|
||||
res += x
|
||||
continue
|
||||
res += " " + text[0]
|
||||
text = text[1:]
|
||||
res = _COLON_RX.sub(":", res)
|
||||
return res[1:]
|
||||
|
||||
|
||||
_KATAKANA = "".join(chr(ch) for ch in range(ord("ァ"), ord("ン") + 1))
|
||||
_HIRAGANA = "".join(chr(ch) for ch in range(ord("ぁ"), ord("ん") + 1))
|
||||
_HIRA2KATATRANS = str.maketrans(_HIRAGANA, _KATAKANA)
|
||||
|
||||
|
||||
def hira2kata(text: str) -> str:
|
||||
text = text.translate(_HIRA2KATATRANS)
|
||||
return text.replace("う゛", "ヴ")
|
||||
|
||||
|
||||
_SYMBOL_TOKENS = set(list("・、。?!"))
|
||||
_NO_YOMI_TOKENS = set(list("「」『』―()[][] …"))
|
||||
_TAGGER = MeCab.Tagger()
|
||||
|
||||
|
||||
def text2kata(text: str) -> str:
|
||||
parsed = _TAGGER.parse(text)
|
||||
res = []
|
||||
for line in parsed.split("\n"):
|
||||
if line == "EOS":
|
||||
break
|
||||
parts = line.split("\t")
|
||||
|
||||
word, yomi = parts[0], parts[1]
|
||||
if yomi:
|
||||
res.append(yomi)
|
||||
else:
|
||||
if word in _SYMBOL_TOKENS:
|
||||
res.append(word)
|
||||
elif word in ("っ", "ッ"):
|
||||
res.append("ッ")
|
||||
elif word in _NO_YOMI_TOKENS:
|
||||
pass
|
||||
else:
|
||||
res.append(word)
|
||||
return hira2kata("".join(res))
|
||||
|
||||
|
||||
_ALPHASYMBOL_YOMI = {
|
||||
"#": "シャープ",
|
||||
"%": "パーセント",
|
||||
"&": "アンド",
|
||||
"+": "プラス",
|
||||
"-": "マイナス",
|
||||
":": "コロン",
|
||||
";": "セミコロン",
|
||||
"<": "小なり",
|
||||
"=": "イコール",
|
||||
">": "大なり",
|
||||
"@": "アット",
|
||||
"a": "エー",
|
||||
"b": "ビー",
|
||||
"c": "シー",
|
||||
"d": "ディー",
|
||||
"e": "イー",
|
||||
"f": "エフ",
|
||||
"g": "ジー",
|
||||
"h": "エイチ",
|
||||
"i": "アイ",
|
||||
"j": "ジェー",
|
||||
"k": "ケー",
|
||||
"l": "エル",
|
||||
"m": "エム",
|
||||
"n": "エヌ",
|
||||
"o": "オー",
|
||||
"p": "ピー",
|
||||
"q": "キュー",
|
||||
"r": "アール",
|
||||
"s": "エス",
|
||||
"t": "ティー",
|
||||
"u": "ユー",
|
||||
"v": "ブイ",
|
||||
"w": "ダブリュー",
|
||||
"x": "エックス",
|
||||
"y": "ワイ",
|
||||
"z": "ゼット",
|
||||
"α": "アルファ",
|
||||
"β": "ベータ",
|
||||
"γ": "ガンマ",
|
||||
"δ": "デルタ",
|
||||
"ε": "イプシロン",
|
||||
"ζ": "ゼータ",
|
||||
"η": "イータ",
|
||||
"θ": "シータ",
|
||||
"ι": "イオタ",
|
||||
"κ": "カッパ",
|
||||
"λ": "ラムダ",
|
||||
"μ": "ミュー",
|
||||
"ν": "ニュー",
|
||||
"ξ": "クサイ",
|
||||
"ο": "オミクロン",
|
||||
"π": "パイ",
|
||||
"ρ": "ロー",
|
||||
"σ": "シグマ",
|
||||
"τ": "タウ",
|
||||
"υ": "ウプシロン",
|
||||
"φ": "ファイ",
|
||||
"χ": "カイ",
|
||||
"ψ": "プサイ",
|
||||
"ω": "オメガ",
|
||||
}
|
||||
|
||||
|
||||
_NUMBER_WITH_SEPARATOR_RX = re.compile("[0-9]{1,3}(,[0-9]{3})+")
|
||||
_CURRENCY_MAP = {"$": "ドル", "¥": "円", "£": "ポンド", "€": "ユーロ"}
|
||||
_CURRENCY_RX = re.compile(r"([$¥£€])([0-9.]*[0-9])")
|
||||
_NUMBER_RX = re.compile(r"[0-9]+(\.[0-9]+)?")
|
||||
|
||||
|
||||
def japanese_convert_numbers_to_words(text: str) -> str:
|
||||
res = _NUMBER_WITH_SEPARATOR_RX.sub(lambda m: m[0].replace(",", ""), text)
|
||||
res = _CURRENCY_RX.sub(lambda m: m[2] + _CURRENCY_MAP.get(m[1], m[1]), res)
|
||||
res = _NUMBER_RX.sub(lambda m: num2words(m[0], lang="ja"), res)
|
||||
return res
|
||||
|
||||
|
||||
def japanese_convert_alpha_symbols_to_words(text: str) -> str:
|
||||
return "".join([_ALPHASYMBOL_YOMI.get(ch, ch) for ch in text.lower()])
|
||||
|
||||
|
||||
def japanese_text_to_phonemes(text: str) -> str:
|
||||
"""Convert Japanese text to phonemes."""
|
||||
res = unicodedata.normalize("NFKC", text)
|
||||
res = japanese_convert_numbers_to_words(res)
|
||||
res = japanese_convert_alpha_symbols_to_words(res)
|
||||
res = text2kata(res)
|
||||
res = kata2phoneme(res)
|
||||
return res.replace(" ", "")
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,44 @@
|
||||
# coding: utf-8
|
||||
# Add the word you want to the dictionary.
|
||||
etc_dictionary = {"1+1": "원플러스원", "2+1": "투플러스원"}
|
||||
|
||||
|
||||
english_dictionary = {
|
||||
"KOREA": "코리아",
|
||||
"IDOL": "아이돌",
|
||||
"IT": "아이티",
|
||||
"IQ": "아이큐",
|
||||
"UP": "업",
|
||||
"DOWN": "다운",
|
||||
"PC": "피씨",
|
||||
"CCTV": "씨씨티비",
|
||||
"SNS": "에스엔에스",
|
||||
"AI": "에이아이",
|
||||
"CEO": "씨이오",
|
||||
"A": "에이",
|
||||
"B": "비",
|
||||
"C": "씨",
|
||||
"D": "디",
|
||||
"E": "이",
|
||||
"F": "에프",
|
||||
"G": "지",
|
||||
"H": "에이치",
|
||||
"I": "아이",
|
||||
"J": "제이",
|
||||
"K": "케이",
|
||||
"L": "엘",
|
||||
"M": "엠",
|
||||
"N": "엔",
|
||||
"O": "오",
|
||||
"P": "피",
|
||||
"Q": "큐",
|
||||
"R": "알",
|
||||
"S": "에스",
|
||||
"T": "티",
|
||||
"U": "유",
|
||||
"V": "브이",
|
||||
"W": "더블유",
|
||||
"X": "엑스",
|
||||
"Y": "와이",
|
||||
"Z": "제트",
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
# coding: utf-8
|
||||
# Code based on https://github.com/carpedm20/multi-speaker-tacotron-tensorflow/blob/master/text/korean.py
|
||||
import re
|
||||
|
||||
from TTS.tts.utils.text.korean.ko_dictionary import english_dictionary, etc_dictionary
|
||||
|
||||
|
||||
def normalize(text):
|
||||
text = text.strip()
|
||||
text = re.sub("[⺀-⺙⺛-⻳⼀-⿕々〇〡-〩〸-〺〻㐀-䶵一-鿃豈-鶴侮-頻並-龎]", "", text)
|
||||
text = normalize_with_dictionary(text, etc_dictionary)
|
||||
text = normalize_english(text)
|
||||
text = text.lower()
|
||||
return text
|
||||
|
||||
|
||||
def normalize_with_dictionary(text, dic):
|
||||
if any(key in text for key in dic.keys()):
|
||||
pattern = re.compile("|".join(re.escape(key) for key in dic.keys()))
|
||||
return pattern.sub(lambda x: dic[x.group()], text)
|
||||
return text
|
||||
|
||||
|
||||
def normalize_english(text):
|
||||
def fn(m):
|
||||
word = m.group()
|
||||
if word in english_dictionary:
|
||||
return english_dictionary.get(word)
|
||||
return word
|
||||
|
||||
text = re.sub("([A-Za-z]+)", fn, text)
|
||||
return text
|
||||
@@ -0,0 +1,36 @@
|
||||
from jamo import hangul_to_jamo
|
||||
|
||||
from TTS.tts.utils.text.korean.korean import normalize
|
||||
|
||||
g2p = None
|
||||
|
||||
|
||||
def korean_text_to_phonemes(text, character: str = "hangeul") -> str:
|
||||
"""
|
||||
|
||||
The input and output values look the same, but they are different in Unicode.
|
||||
|
||||
example :
|
||||
|
||||
input = '하늘' (Unicode : \ud558\ub298), (하 + 늘)
|
||||
output = '하늘' (Unicode :\u1112\u1161\u1102\u1173\u11af), (ᄒ + ᅡ + ᄂ + ᅳ + ᆯ)
|
||||
|
||||
"""
|
||||
global g2p # pylint: disable=global-statement
|
||||
if g2p is None:
|
||||
from g2pkk import G2p
|
||||
|
||||
g2p = G2p()
|
||||
|
||||
if character == "english":
|
||||
from anyascii import anyascii
|
||||
|
||||
text = normalize(text)
|
||||
text = g2p(text)
|
||||
text = anyascii(text)
|
||||
return text
|
||||
|
||||
text = normalize(text)
|
||||
text = g2p(text)
|
||||
text = list(hangul_to_jamo(text)) # '하늘' --> ['ᄒ', 'ᅡ', 'ᄂ', 'ᅳ', 'ᆯ']
|
||||
return "".join(text)
|
||||
@@ -0,0 +1,79 @@
|
||||
from TTS.tts.utils.text.phonemizers.bangla_phonemizer import BN_Phonemizer
|
||||
from TTS.tts.utils.text.phonemizers.base import BasePhonemizer
|
||||
from TTS.tts.utils.text.phonemizers.belarusian_phonemizer import BEL_Phonemizer
|
||||
from TTS.tts.utils.text.phonemizers.espeak_wrapper import ESpeak
|
||||
from TTS.tts.utils.text.phonemizers.gruut_wrapper import Gruut
|
||||
from TTS.tts.utils.text.phonemizers.ko_kr_phonemizer import KO_KR_Phonemizer
|
||||
from TTS.tts.utils.text.phonemizers.zh_cn_phonemizer import ZH_CN_Phonemizer
|
||||
|
||||
try:
|
||||
from TTS.tts.utils.text.phonemizers.ja_jp_phonemizer import JA_JP_Phonemizer
|
||||
except ImportError:
|
||||
JA_JP_Phonemizer = None
|
||||
pass
|
||||
|
||||
PHONEMIZERS = {b.name(): b for b in (ESpeak, Gruut, KO_KR_Phonemizer, BN_Phonemizer)}
|
||||
|
||||
|
||||
ESPEAK_LANGS = list(ESpeak.supported_languages().keys())
|
||||
GRUUT_LANGS = list(Gruut.supported_languages())
|
||||
|
||||
|
||||
# Dict setting default phonemizers for each language
|
||||
# Add Gruut languages
|
||||
_ = [Gruut.name()] * len(GRUUT_LANGS)
|
||||
DEF_LANG_TO_PHONEMIZER = dict(list(zip(GRUUT_LANGS, _)))
|
||||
|
||||
|
||||
# Add ESpeak languages and override any existing ones
|
||||
_ = [ESpeak.name()] * len(ESPEAK_LANGS)
|
||||
_new_dict = dict(list(zip(list(ESPEAK_LANGS), _)))
|
||||
DEF_LANG_TO_PHONEMIZER.update(_new_dict)
|
||||
|
||||
|
||||
# Force default for some languages
|
||||
DEF_LANG_TO_PHONEMIZER["en"] = DEF_LANG_TO_PHONEMIZER["en-us"]
|
||||
DEF_LANG_TO_PHONEMIZER["zh-cn"] = ZH_CN_Phonemizer.name()
|
||||
DEF_LANG_TO_PHONEMIZER["ko-kr"] = KO_KR_Phonemizer.name()
|
||||
DEF_LANG_TO_PHONEMIZER["bn"] = BN_Phonemizer.name()
|
||||
DEF_LANG_TO_PHONEMIZER["be"] = BEL_Phonemizer.name()
|
||||
|
||||
|
||||
# JA phonemizer has deal breaking dependencies like MeCab for some systems.
|
||||
# So we only have it when we have it.
|
||||
if JA_JP_Phonemizer is not None:
|
||||
PHONEMIZERS[JA_JP_Phonemizer.name()] = JA_JP_Phonemizer
|
||||
DEF_LANG_TO_PHONEMIZER["ja-jp"] = JA_JP_Phonemizer.name()
|
||||
|
||||
|
||||
def get_phonemizer_by_name(name: str, **kwargs) -> BasePhonemizer:
|
||||
"""Initiate a phonemizer by name
|
||||
|
||||
Args:
|
||||
name (str):
|
||||
Name of the phonemizer that should match `phonemizer.name()`.
|
||||
|
||||
kwargs (dict):
|
||||
Extra keyword arguments that should be passed to the phonemizer.
|
||||
"""
|
||||
if name == "espeak":
|
||||
return ESpeak(**kwargs)
|
||||
if name == "gruut":
|
||||
return Gruut(**kwargs)
|
||||
if name == "zh_cn_phonemizer":
|
||||
return ZH_CN_Phonemizer(**kwargs)
|
||||
if name == "ja_jp_phonemizer":
|
||||
if JA_JP_Phonemizer is None:
|
||||
raise ValueError(" ❗ You need to install JA phonemizer dependencies. Try `pip install TTS[ja]`.")
|
||||
return JA_JP_Phonemizer(**kwargs)
|
||||
if name == "ko_kr_phonemizer":
|
||||
return KO_KR_Phonemizer(**kwargs)
|
||||
if name == "bn_phonemizer":
|
||||
return BN_Phonemizer(**kwargs)
|
||||
if name == "be_phonemizer":
|
||||
return BEL_Phonemizer(**kwargs)
|
||||
raise ValueError(f"Phonemizer {name} not found")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
print(DEF_LANG_TO_PHONEMIZER)
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,62 @@
|
||||
from typing import Dict
|
||||
|
||||
from TTS.tts.utils.text.bangla.phonemizer import bangla_text_to_phonemes
|
||||
from TTS.tts.utils.text.phonemizers.base import BasePhonemizer
|
||||
|
||||
_DEF_ZH_PUNCS = "、.,[]()?!〽~『』「」【】"
|
||||
|
||||
|
||||
class BN_Phonemizer(BasePhonemizer):
|
||||
"""🐸TTS bn phonemizer using functions in `TTS.tts.utils.text.bangla.phonemizer`
|
||||
|
||||
Args:
|
||||
punctuations (str):
|
||||
Set of characters to be treated as punctuation. Defaults to `_DEF_ZH_PUNCS`.
|
||||
|
||||
keep_puncs (bool):
|
||||
If True, keep the punctuations after phonemization. Defaults to False.
|
||||
|
||||
Example ::
|
||||
|
||||
"这是,样本中文。" -> `d|ʒ|ø|4| |ʂ|ʏ|4| |,| |i|ɑ|ŋ|4|b|œ|n|3| |d|ʒ|o|ŋ|1|w|œ|n|2| |。`
|
||||
|
||||
TODO: someone with Bangla knowledge should check this implementation
|
||||
"""
|
||||
|
||||
language = "bn"
|
||||
|
||||
def __init__(self, punctuations=_DEF_ZH_PUNCS, keep_puncs=False, **kwargs): # pylint: disable=unused-argument
|
||||
super().__init__(self.language, punctuations=punctuations, keep_puncs=keep_puncs)
|
||||
|
||||
@staticmethod
|
||||
def name():
|
||||
return "bn_phonemizer"
|
||||
|
||||
@staticmethod
|
||||
def phonemize_bn(text: str, separator: str = "|") -> str: # pylint: disable=unused-argument
|
||||
ph = bangla_text_to_phonemes(text)
|
||||
return ph
|
||||
|
||||
def _phonemize(self, text, separator):
|
||||
return self.phonemize_bn(text, separator)
|
||||
|
||||
@staticmethod
|
||||
def supported_languages() -> Dict:
|
||||
return {"bn": "Bangla"}
|
||||
|
||||
def version(self) -> str:
|
||||
return "0.0.1"
|
||||
|
||||
def is_available(self) -> bool:
|
||||
return True
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
txt = "রাসূলুল্লাহ সাল্লাল্লাহু আলাইহি ওয়া সাল্লাম শিক্ষা দিয়েছেন যে, কেউ যদি কোন খারাপ কিছুর সম্মুখীন হয়, তখনও যেন বলে."
|
||||
e = BN_Phonemizer()
|
||||
print(e.supported_languages())
|
||||
print(e.version())
|
||||
print(e.language)
|
||||
print(e.name())
|
||||
print(e.is_available())
|
||||
print("`" + e.phonemize(txt) + "`")
|
||||
@@ -0,0 +1,140 @@
|
||||
import abc
|
||||
from typing import List, Tuple
|
||||
|
||||
from TTS.tts.utils.text.punctuation import Punctuation
|
||||
|
||||
|
||||
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):
|
||||
# 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)], []
|
||||
|
||||
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="|", language: str = None) -> str: # pylint: disable=unused-argument
|
||||
"""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)
|
||||
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()}")
|
||||
@@ -0,0 +1,55 @@
|
||||
from typing import Dict
|
||||
|
||||
from TTS.tts.utils.text.belarusian.phonemizer import belarusian_text_to_phonemes
|
||||
from TTS.tts.utils.text.phonemizers.base import BasePhonemizer
|
||||
|
||||
_DEF_BE_PUNCS = ",!." # TODO
|
||||
|
||||
|
||||
class BEL_Phonemizer(BasePhonemizer):
|
||||
"""🐸TTS be phonemizer using functions in `TTS.tts.utils.text.belarusian.phonemizer`
|
||||
|
||||
Args:
|
||||
punctuations (str):
|
||||
Set of characters to be treated as punctuation. Defaults to `_DEF_BE_PUNCS`.
|
||||
|
||||
keep_puncs (bool):
|
||||
If True, keep the punctuations after phonemization. Defaults to False.
|
||||
"""
|
||||
|
||||
language = "be"
|
||||
|
||||
def __init__(self, punctuations=_DEF_BE_PUNCS, keep_puncs=True, **kwargs): # pylint: disable=unused-argument
|
||||
super().__init__(self.language, punctuations=punctuations, keep_puncs=keep_puncs)
|
||||
|
||||
@staticmethod
|
||||
def name():
|
||||
return "be_phonemizer"
|
||||
|
||||
@staticmethod
|
||||
def phonemize_be(text: str, separator: str = "|") -> str: # pylint: disable=unused-argument
|
||||
return belarusian_text_to_phonemes(text)
|
||||
|
||||
def _phonemize(self, text, separator):
|
||||
return self.phonemize_be(text, separator)
|
||||
|
||||
@staticmethod
|
||||
def supported_languages() -> Dict:
|
||||
return {"be": "Belarusian"}
|
||||
|
||||
def version(self) -> str:
|
||||
return "0.0.1"
|
||||
|
||||
def is_available(self) -> bool:
|
||||
return True
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
txt = "тэст"
|
||||
e = BEL_Phonemizer()
|
||||
print(e.supported_languages())
|
||||
print(e.version())
|
||||
print(e.language)
|
||||
print(e.name())
|
||||
print(e.is_available())
|
||||
print("`" + e.phonemize(txt) + "`")
|
||||
@@ -0,0 +1,264 @@
|
||||
import logging
|
||||
import re
|
||||
import subprocess
|
||||
from typing import Dict, List
|
||||
|
||||
from packaging.version import Version
|
||||
|
||||
from TTS.tts.utils.text.phonemizers.base import BasePhonemizer
|
||||
from TTS.tts.utils.text.punctuation import Punctuation
|
||||
|
||||
|
||||
def is_tool(name):
|
||||
from shutil import which
|
||||
|
||||
return which(name) is not None
|
||||
|
||||
|
||||
# Use a regex pattern to match the espeak version, because it may be
|
||||
# symlinked to espeak-ng, which moves the version bits to another spot.
|
||||
espeak_version_pattern = re.compile(r"text-to-speech:\s(?P<version>\d+\.\d+(\.\d+)?)")
|
||||
|
||||
|
||||
def get_espeak_version():
|
||||
output = subprocess.getoutput("espeak --version")
|
||||
match = espeak_version_pattern.search(output)
|
||||
|
||||
return match.group("version")
|
||||
|
||||
|
||||
def get_espeakng_version():
|
||||
output = subprocess.getoutput("espeak-ng --version")
|
||||
return output.split()[3]
|
||||
|
||||
|
||||
# priority: espeakng > espeak
|
||||
if is_tool("espeak-ng"):
|
||||
_DEF_ESPEAK_LIB = "espeak-ng"
|
||||
_DEF_ESPEAK_VER = get_espeakng_version()
|
||||
elif is_tool("espeak"):
|
||||
_DEF_ESPEAK_LIB = "espeak"
|
||||
_DEF_ESPEAK_VER = get_espeak_version()
|
||||
else:
|
||||
_DEF_ESPEAK_LIB = None
|
||||
_DEF_ESPEAK_VER = None
|
||||
|
||||
|
||||
def _espeak_exe(espeak_lib: str, args: List, sync=False) -> List[str]:
|
||||
"""Run espeak with the given arguments."""
|
||||
cmd = [
|
||||
espeak_lib,
|
||||
"-q",
|
||||
"-b",
|
||||
"1", # UTF8 text encoding
|
||||
]
|
||||
cmd.extend(args)
|
||||
logging.debug("espeakng: executing %s", repr(cmd))
|
||||
|
||||
with subprocess.Popen(
|
||||
cmd,
|
||||
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
|
||||
|
||||
|
||||
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
|
||||
_ESPEAK_VER = _DEF_ESPEAK_VER
|
||||
|
||||
def __init__(self, language: str, backend=None, punctuations=Punctuation.default_puncs(), keep_puncs=True):
|
||||
if self._ESPEAK_LIB is None:
|
||||
raise Exception(" [!] No espeak backend found. Install espeak-ng or espeak to your system.")
|
||||
self.backend = self._ESPEAK_LIB
|
||||
|
||||
# band-aid for backwards compatibility
|
||||
if language == "en":
|
||||
language = "en-us"
|
||||
if language == "zh-cn":
|
||||
language = "cmn"
|
||||
|
||||
super().__init__(language, punctuations=punctuations, keep_puncs=keep_puncs)
|
||||
if backend is not None:
|
||||
self.backend = backend
|
||||
|
||||
@property
|
||||
def backend(self):
|
||||
return self._ESPEAK_LIB
|
||||
|
||||
@property
|
||||
def backend_version(self):
|
||||
return self._ESPEAK_VER
|
||||
|
||||
@backend.setter
|
||||
def backend(self, backend):
|
||||
if backend not in ["espeak", "espeak-ng"]:
|
||||
raise Exception("Unknown backend: %s" % backend)
|
||||
self._ESPEAK_LIB = backend
|
||||
self._ESPEAK_VER = get_espeakng_version() if backend == "espeak-ng" else get_espeak_version()
|
||||
|
||||
def auto_set_espeak_lib(self) -> None:
|
||||
if is_tool("espeak-ng"):
|
||||
self._ESPEAK_LIB = "espeak-ng"
|
||||
self._ESPEAK_VER = get_espeakng_version()
|
||||
elif is_tool("espeak"):
|
||||
self._ESPEAK_LIB = "espeak"
|
||||
self._ESPEAK_VER = get_espeak_version()
|
||||
else:
|
||||
raise Exception("Cannot set backend automatically. espeak-ng or espeak not found")
|
||||
|
||||
@staticmethod
|
||||
def name():
|
||||
return "espeak"
|
||||
|
||||
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:
|
||||
# use '͡' between phonemes
|
||||
if self.backend == "espeak":
|
||||
args.append("--ipa=1")
|
||||
else:
|
||||
args.append("--ipa=3")
|
||||
else:
|
||||
# split with '_'
|
||||
if self.backend == "espeak":
|
||||
if Version(self.backend_version) >= Version("1.48.15"):
|
||||
args.append("--ipa=1")
|
||||
else:
|
||||
args.append("--ipa=3")
|
||||
else:
|
||||
args.append("--ipa=1")
|
||||
if tie:
|
||||
args.append("--tie=%s" % tie)
|
||||
|
||||
args.append(text)
|
||||
# compute phonemes
|
||||
phonemes = ""
|
||||
for line in _espeak_exe(self._ESPEAK_LIB, args, sync=True):
|
||||
logging.debug("line: %s", repr(line))
|
||||
ph_decoded = line.decode("utf8").strip()
|
||||
# espeak:
|
||||
# version 1.48.15: " p_ɹ_ˈaɪ_ɚ t_ə n_oʊ_v_ˈɛ_m_b_ɚ t_w_ˈɛ_n_t_i t_ˈuː\n"
|
||||
# espeak-ng:
|
||||
# "p_ɹ_ˈaɪ_ɚ t_ə n_oʊ_v_ˈɛ_m_b_ɚ t_w_ˈɛ_n_t_i t_ˈuː\n"
|
||||
|
||||
# espeak-ng backend can add language flags that need to be removed:
|
||||
# "sɛʁtˈɛ̃ mˈo kɔm (en)fˈʊtbɔːl(fr) ʒenˈɛʁ de- flˈaɡ də- lˈɑ̃ɡ."
|
||||
# phonemize needs to remove the language flags of the returned text:
|
||||
# "sɛʁtˈɛ̃ mˈo kɔm fˈʊtbɔːl ʒenˈɛʁ de- flˈaɡ də- lˈɑ̃ɡ."
|
||||
ph_decoded = re.sub(r"\(.+?\)", "", ph_decoded)
|
||||
|
||||
phonemes += ph_decoded.strip()
|
||||
return phonemes.replace("_", separator)
|
||||
|
||||
def _phonemize(self, text, separator=None):
|
||||
return self.phonemize_espeak(text, separator, tie=False)
|
||||
|
||||
@staticmethod
|
||||
def supported_languages() -> 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(_DEF_ESPEAK_LIB, args, sync=True):
|
||||
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.backend, args, sync=True):
|
||||
version = line.decode("utf8").strip().split()[2]
|
||||
logging.debug("line: %s", repr(line))
|
||||
return version
|
||||
|
||||
@classmethod
|
||||
def is_available(cls):
|
||||
"""Return true if ESpeak is available else false"""
|
||||
return is_tool("espeak") or is_tool("espeak-ng")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
e = ESpeak(language="en-us")
|
||||
print(e.supported_languages())
|
||||
print(e.version())
|
||||
print(e.language)
|
||||
print(e.name())
|
||||
print(e.is_available())
|
||||
|
||||
e = ESpeak(language="en-us", keep_puncs=False)
|
||||
print("`" + e.phonemize("hello how are you today?") + "`")
|
||||
|
||||
e = ESpeak(language="en-us", keep_puncs=True)
|
||||
print("`" + e.phonemize("hello how are you today?") + "`")
|
||||
@@ -0,0 +1,151 @@
|
||||
import importlib
|
||||
from typing import List
|
||||
|
||||
import gruut
|
||||
from gruut_ipa import IPA
|
||||
|
||||
from TTS.tts.utils.text.phonemizers.base import BasePhonemizer
|
||||
from TTS.tts.utils.text.punctuation import Punctuation
|
||||
|
||||
# Table for str.translate to fix gruut/TTS phoneme mismatch
|
||||
GRUUT_TRANS_TABLE = str.maketrans("g", "ɡ")
|
||||
|
||||
|
||||
class Gruut(BasePhonemizer):
|
||||
"""Gruut wrapper for G2P
|
||||
|
||||
Args:
|
||||
language (str):
|
||||
Valid language code for the used backend.
|
||||
|
||||
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.
|
||||
|
||||
use_espeak_phonemes (bool):
|
||||
If true, use espeak lexicons instead of default Gruut lexicons. Defaults to False.
|
||||
|
||||
keep_stress (bool):
|
||||
If true, keep the stress characters after phonemization. Defaults to False.
|
||||
|
||||
Example:
|
||||
|
||||
>>> from TTS.tts.utils.text.phonemizers.gruut_wrapper import Gruut
|
||||
>>> phonemizer = Gruut('en-us')
|
||||
>>> phonemizer.phonemize("Be a voice, not an! echo?", separator="|")
|
||||
'b|i| ə| v|ɔ|ɪ|s, n|ɑ|t| ə|n! ɛ|k|o|ʊ?'
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
language: str,
|
||||
punctuations=Punctuation.default_puncs(),
|
||||
keep_puncs=True,
|
||||
use_espeak_phonemes=False,
|
||||
keep_stress=False,
|
||||
):
|
||||
super().__init__(language, punctuations=punctuations, keep_puncs=keep_puncs)
|
||||
self.use_espeak_phonemes = use_espeak_phonemes
|
||||
self.keep_stress = keep_stress
|
||||
|
||||
@staticmethod
|
||||
def name():
|
||||
return "gruut"
|
||||
|
||||
def phonemize_gruut(self, text: str, separator: str = "|", tie=False) -> str: # pylint: disable=unused-argument
|
||||
"""Convert input text to phonemes.
|
||||
|
||||
Gruut phonemizes the given `str` by seperating each phoneme character with `separator`, even for characters
|
||||
that constitude a single sound.
|
||||
|
||||
It doesn't affect 🐸TTS since it individually converts each character to token IDs.
|
||||
|
||||
Examples::
|
||||
"hello how are you today?" -> `h|ɛ|l|o|ʊ| h|a|ʊ| ɑ|ɹ| j|u| t|ə|d|e|ɪ`
|
||||
|
||||
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.
|
||||
"""
|
||||
ph_list = []
|
||||
for sentence in gruut.sentences(text, lang=self.language, espeak=self.use_espeak_phonemes):
|
||||
for word in sentence:
|
||||
if word.is_break:
|
||||
# Use actual character for break phoneme (e.g., comma)
|
||||
if ph_list:
|
||||
# Join with previous word
|
||||
ph_list[-1].append(word.text)
|
||||
else:
|
||||
# First word is punctuation
|
||||
ph_list.append([word.text])
|
||||
elif word.phonemes:
|
||||
# Add phonemes for word
|
||||
word_phonemes = []
|
||||
|
||||
for word_phoneme in word.phonemes:
|
||||
if not self.keep_stress:
|
||||
# Remove primary/secondary stress
|
||||
word_phoneme = IPA.without_stress(word_phoneme)
|
||||
|
||||
word_phoneme = word_phoneme.translate(GRUUT_TRANS_TABLE)
|
||||
|
||||
if word_phoneme:
|
||||
# Flatten phonemes
|
||||
word_phonemes.extend(word_phoneme)
|
||||
|
||||
if word_phonemes:
|
||||
ph_list.append(word_phonemes)
|
||||
|
||||
ph_words = [separator.join(word_phonemes) for word_phonemes in ph_list]
|
||||
ph = f"{separator} ".join(ph_words)
|
||||
return ph
|
||||
|
||||
def _phonemize(self, text, separator):
|
||||
return self.phonemize_gruut(text, separator, tie=False)
|
||||
|
||||
def is_supported_language(self, language):
|
||||
"""Returns True if `language` is supported by the backend"""
|
||||
return gruut.is_language_supported(language)
|
||||
|
||||
@staticmethod
|
||||
def supported_languages() -> List:
|
||||
"""Get a dictionary of supported languages.
|
||||
|
||||
Returns:
|
||||
List: List of language codes.
|
||||
"""
|
||||
return list(gruut.get_supported_languages())
|
||||
|
||||
def version(self):
|
||||
"""Get the version of the used backend.
|
||||
|
||||
Returns:
|
||||
str: Version of the used backend.
|
||||
"""
|
||||
return gruut.__version__
|
||||
|
||||
@classmethod
|
||||
def is_available(cls):
|
||||
"""Return true if ESpeak is available else false"""
|
||||
return importlib.util.find_spec("gruut") is not None
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
e = Gruut(language="en-us")
|
||||
print(e.supported_languages())
|
||||
print(e.version())
|
||||
print(e.language)
|
||||
print(e.name())
|
||||
print(e.is_available())
|
||||
|
||||
e = Gruut(language="en-us", keep_puncs=False)
|
||||
print("`" + e.phonemize("hello how are you today?") + "`")
|
||||
|
||||
e = Gruut(language="en-us", keep_puncs=True)
|
||||
print("`" + e.phonemize("hello how, are you today?") + "`")
|
||||
@@ -0,0 +1,72 @@
|
||||
from typing import Dict
|
||||
|
||||
from TTS.tts.utils.text.japanese.phonemizer import japanese_text_to_phonemes
|
||||
from TTS.tts.utils.text.phonemizers.base import BasePhonemizer
|
||||
|
||||
_DEF_JA_PUNCS = "、.,[]()?!〽~『』「」【】"
|
||||
|
||||
_TRANS_TABLE = {"、": ","}
|
||||
|
||||
|
||||
def trans(text):
|
||||
for i, j in _TRANS_TABLE.items():
|
||||
text = text.replace(i, j)
|
||||
return text
|
||||
|
||||
|
||||
class JA_JP_Phonemizer(BasePhonemizer):
|
||||
"""🐸TTS Ja-Jp phonemizer using functions in `TTS.tts.utils.text.japanese.phonemizer`
|
||||
|
||||
TODO: someone with JA knowledge should check this implementation
|
||||
|
||||
Example:
|
||||
|
||||
>>> from TTS.tts.utils.text.phonemizers import JA_JP_Phonemizer
|
||||
>>> phonemizer = JA_JP_Phonemizer()
|
||||
>>> phonemizer.phonemize("どちらに行きますか?", separator="|")
|
||||
'd|o|c|h|i|r|a|n|i|i|k|i|m|a|s|u|k|a|?'
|
||||
|
||||
"""
|
||||
|
||||
language = "ja-jp"
|
||||
|
||||
def __init__(self, punctuations=_DEF_JA_PUNCS, keep_puncs=True, **kwargs): # pylint: disable=unused-argument
|
||||
super().__init__(self.language, punctuations=punctuations, keep_puncs=keep_puncs)
|
||||
|
||||
@staticmethod
|
||||
def name():
|
||||
return "ja_jp_phonemizer"
|
||||
|
||||
def _phonemize(self, text: str, separator: str = "|") -> str:
|
||||
ph = japanese_text_to_phonemes(text)
|
||||
if separator is not None or separator != "":
|
||||
return separator.join(ph)
|
||||
return ph
|
||||
|
||||
def phonemize(self, text: str, separator="|", language=None) -> str:
|
||||
"""Custom phonemize for JP_JA
|
||||
|
||||
Skip pre-post processing steps used by the other phonemizers.
|
||||
"""
|
||||
return self._phonemize(text, separator)
|
||||
|
||||
@staticmethod
|
||||
def supported_languages() -> Dict:
|
||||
return {"ja-jp": "Japanese (Japan)"}
|
||||
|
||||
def version(self) -> str:
|
||||
return "0.0.1"
|
||||
|
||||
def is_available(self) -> bool:
|
||||
return True
|
||||
|
||||
|
||||
# if __name__ == "__main__":
|
||||
# text = "これは、電話をかけるための私の日本語の例のテキストです。"
|
||||
# e = JA_JP_Phonemizer()
|
||||
# print(e.supported_languages())
|
||||
# print(e.version())
|
||||
# print(e.language)
|
||||
# print(e.name())
|
||||
# print(e.is_available())
|
||||
# print("`" + e.phonemize(text) + "`")
|
||||
@@ -0,0 +1,65 @@
|
||||
from typing import Dict
|
||||
|
||||
from TTS.tts.utils.text.korean.phonemizer import korean_text_to_phonemes
|
||||
from TTS.tts.utils.text.phonemizers.base import BasePhonemizer
|
||||
|
||||
_DEF_KO_PUNCS = "、.,[]()?!〽~『』「」【】"
|
||||
|
||||
|
||||
class KO_KR_Phonemizer(BasePhonemizer):
|
||||
"""🐸TTS ko_kr_phonemizer using functions in `TTS.tts.utils.text.korean.phonemizer`
|
||||
|
||||
TODO: Add Korean to character (ᄀᄁᄂᄃᄄᄅᄆᄇᄈᄉᄊᄋᄌᄍᄎᄏᄐᄑ하ᅢᅣᅤᅥᅦᅧᅨᅩᅪᅫᅬᅭᅮᅯᅰᅱᅲᅳᅴᅵᆨᆩᆪᆫᆬᆭᆮᆯᆰᆱᆲᆳᆴᆵᆶᆷᆸᆹᆺᆻᆼᆽᆾᆿᇀᇁᇂ)
|
||||
|
||||
Example:
|
||||
|
||||
>>> from TTS.tts.utils.text.phonemizers import KO_KR_Phonemizer
|
||||
>>> phonemizer = KO_KR_Phonemizer()
|
||||
>>> phonemizer.phonemize("이 문장은 음성합성 테스트를 위한 문장입니다.", separator="|")
|
||||
'ᄋ|ᅵ| |ᄆ|ᅮ|ᆫ|ᄌ|ᅡ|ᆼ|ᄋ|ᅳ| |ᄂ|ᅳ|ᆷ|ᄉ|ᅥ|ᆼ|ᄒ|ᅡ|ᆸ|ᄊ|ᅥ|ᆼ| |ᄐ|ᅦ|ᄉ|ᅳ|ᄐ|ᅳ|ᄅ|ᅳ| |ᄅ|ᅱ|ᄒ|ᅡ|ᆫ| |ᄆ|ᅮ|ᆫ|ᄌ|ᅡ|ᆼ|ᄋ|ᅵ|ᆷ|ᄂ|ᅵ|ᄃ|ᅡ|.'
|
||||
|
||||
>>> from TTS.tts.utils.text.phonemizers import KO_KR_Phonemizer
|
||||
>>> phonemizer = KO_KR_Phonemizer()
|
||||
>>> phonemizer.phonemize("이 문장은 음성합성 테스트를 위한 문장입니다.", separator="|", character='english')
|
||||
'I| |M|u|n|J|a|n|g|E|u| |N|e|u|m|S|e|o|n|g|H|a|b|S|s|e|o|n|g| |T|e|S|e|u|T|e|u|L|e|u| |L|w|i|H|a|n| |M|u|n|J|a|n|g|I|m|N|i|D|a|.'
|
||||
|
||||
"""
|
||||
|
||||
language = "ko-kr"
|
||||
|
||||
def __init__(self, punctuations=_DEF_KO_PUNCS, keep_puncs=True, **kwargs): # pylint: disable=unused-argument
|
||||
super().__init__(self.language, punctuations=punctuations, keep_puncs=keep_puncs)
|
||||
|
||||
@staticmethod
|
||||
def name():
|
||||
return "ko_kr_phonemizer"
|
||||
|
||||
def _phonemize(self, text: str, separator: str = "", character: str = "hangeul") -> str:
|
||||
ph = korean_text_to_phonemes(text, character=character)
|
||||
if separator is not None or separator != "":
|
||||
return separator.join(ph)
|
||||
return ph
|
||||
|
||||
def phonemize(self, text: str, separator: str = "", character: str = "hangeul", language=None) -> str:
|
||||
return self._phonemize(text, separator, character)
|
||||
|
||||
@staticmethod
|
||||
def supported_languages() -> Dict:
|
||||
return {"ko-kr": "hangeul(korean)"}
|
||||
|
||||
def version(self) -> str:
|
||||
return "0.0.2"
|
||||
|
||||
def is_available(self) -> bool:
|
||||
return True
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
texts = "이 문장은 음성합성 테스트를 위한 문장입니다."
|
||||
e = KO_KR_Phonemizer()
|
||||
print(e.supported_languages())
|
||||
print(e.version())
|
||||
print(e.language)
|
||||
print(e.name())
|
||||
print(e.is_available())
|
||||
print(e.phonemize(texts))
|
||||
@@ -0,0 +1,65 @@
|
||||
from typing import Dict, List
|
||||
|
||||
from TTS.tts.utils.text.phonemizers import DEF_LANG_TO_PHONEMIZER, get_phonemizer_by_name
|
||||
|
||||
|
||||
class MultiPhonemizer:
|
||||
"""🐸TTS multi-phonemizer that operates phonemizers for multiple langugages
|
||||
|
||||
Args:
|
||||
custom_lang_to_phonemizer (Dict):
|
||||
Custom phonemizer mapping if you want to change the defaults. In the format of
|
||||
`{"lang_code", "phonemizer_name"}`. When it is None, `DEF_LANG_TO_PHONEMIZER` is used. Defaults to `{}`.
|
||||
|
||||
TODO: find a way to pass custom kwargs to the phonemizers
|
||||
"""
|
||||
|
||||
lang_to_phonemizer = {}
|
||||
|
||||
def __init__(self, lang_to_phonemizer_name: Dict = {}) -> None: # pylint: disable=dangerous-default-value
|
||||
for k, v in lang_to_phonemizer_name.items():
|
||||
if v == "" and k in DEF_LANG_TO_PHONEMIZER.keys():
|
||||
lang_to_phonemizer_name[k] = DEF_LANG_TO_PHONEMIZER[k]
|
||||
elif v == "":
|
||||
raise ValueError(f"Phonemizer wasn't set for language {k} and doesn't have a default.")
|
||||
self.lang_to_phonemizer_name = lang_to_phonemizer_name
|
||||
self.lang_to_phonemizer = self.init_phonemizers(self.lang_to_phonemizer_name)
|
||||
|
||||
@staticmethod
|
||||
def init_phonemizers(lang_to_phonemizer_name: Dict) -> Dict:
|
||||
lang_to_phonemizer = {}
|
||||
for k, v in lang_to_phonemizer_name.items():
|
||||
lang_to_phonemizer[k] = get_phonemizer_by_name(v, language=k)
|
||||
return lang_to_phonemizer
|
||||
|
||||
@staticmethod
|
||||
def name():
|
||||
return "multi-phonemizer"
|
||||
|
||||
def phonemize(self, text, separator="|", language=""):
|
||||
if language == "":
|
||||
raise ValueError("Language must be set for multi-phonemizer to phonemize.")
|
||||
return self.lang_to_phonemizer[language].phonemize(text, separator)
|
||||
|
||||
def supported_languages(self) -> List:
|
||||
return list(self.lang_to_phonemizer.keys())
|
||||
|
||||
def print_logs(self, level: int = 0):
|
||||
indent = "\t" * level
|
||||
print(f"{indent}| > phoneme language: {self.supported_languages()}")
|
||||
print(f"{indent}| > phoneme backend: {self.name()}")
|
||||
|
||||
|
||||
# if __name__ == "__main__":
|
||||
# texts = {
|
||||
# "tr": "Merhaba, bu Türkçe bit örnek!",
|
||||
# "en-us": "Hello, this is English example!",
|
||||
# "de": "Hallo, das ist ein Deutches Beipiel!",
|
||||
# "zh-cn": "这是中国的例子",
|
||||
# }
|
||||
# phonemes = {}
|
||||
# ph = MultiPhonemizer({"tr": "espeak", "en-us": "", "de": "gruut", "zh-cn": ""})
|
||||
# for lang, text in texts.items():
|
||||
# phoneme = ph.phonemize(text, lang)
|
||||
# phonemes[lang] = phoneme
|
||||
# print(phonemes)
|
||||
@@ -0,0 +1,62 @@
|
||||
from typing import Dict
|
||||
|
||||
from TTS.tts.utils.text.chinese_mandarin.phonemizer import chinese_text_to_phonemes
|
||||
from TTS.tts.utils.text.phonemizers.base import BasePhonemizer
|
||||
|
||||
_DEF_ZH_PUNCS = "、.,[]()?!〽~『』「」【】"
|
||||
|
||||
|
||||
class ZH_CN_Phonemizer(BasePhonemizer):
|
||||
"""🐸TTS Zh-Cn phonemizer using functions in `TTS.tts.utils.text.chinese_mandarin.phonemizer`
|
||||
|
||||
Args:
|
||||
punctuations (str):
|
||||
Set of characters to be treated as punctuation. Defaults to `_DEF_ZH_PUNCS`.
|
||||
|
||||
keep_puncs (bool):
|
||||
If True, keep the punctuations after phonemization. Defaults to False.
|
||||
|
||||
Example ::
|
||||
|
||||
"这是,样本中文。" -> `d|ʒ|ø|4| |ʂ|ʏ|4| |,| |i|ɑ|ŋ|4|b|œ|n|3| |d|ʒ|o|ŋ|1|w|œ|n|2| |。`
|
||||
|
||||
TODO: someone with Mandarin knowledge should check this implementation
|
||||
"""
|
||||
|
||||
language = "zh-cn"
|
||||
|
||||
def __init__(self, punctuations=_DEF_ZH_PUNCS, keep_puncs=False, **kwargs): # pylint: disable=unused-argument
|
||||
super().__init__(self.language, punctuations=punctuations, keep_puncs=keep_puncs)
|
||||
|
||||
@staticmethod
|
||||
def name():
|
||||
return "zh_cn_phonemizer"
|
||||
|
||||
@staticmethod
|
||||
def phonemize_zh_cn(text: str, separator: str = "|") -> str:
|
||||
ph = chinese_text_to_phonemes(text, separator)
|
||||
return ph
|
||||
|
||||
def _phonemize(self, text, separator):
|
||||
return self.phonemize_zh_cn(text, separator)
|
||||
|
||||
@staticmethod
|
||||
def supported_languages() -> Dict:
|
||||
return {"zh-cn": "Chinese (China)"}
|
||||
|
||||
def version(self) -> str:
|
||||
return "0.0.1"
|
||||
|
||||
def is_available(self) -> bool:
|
||||
return True
|
||||
|
||||
|
||||
# if __name__ == "__main__":
|
||||
# text = "这是,样本中文。"
|
||||
# e = ZH_CN_Phonemizer()
|
||||
# print(e.supported_languages())
|
||||
# print(e.version())
|
||||
# print(e.language)
|
||||
# print(e.name())
|
||||
# print(e.is_available())
|
||||
# print("`" + e.phonemize(text) + "`")
|
||||
@@ -0,0 +1,171 @@
|
||||
import collections
|
||||
import re
|
||||
from enum import Enum
|
||||
|
||||
import six
|
||||
|
||||
_DEF_PUNCS = ';:,.!?¡¿—…"«»“”'
|
||||
|
||||
_PUNC_IDX = collections.namedtuple("_punc_index", ["punc", "position"])
|
||||
|
||||
|
||||
class PuncPosition(Enum):
|
||||
"""Enum for the punctuations positions"""
|
||||
|
||||
BEGIN = 0
|
||||
END = 1
|
||||
MIDDLE = 2
|
||||
|
||||
|
||||
class Punctuation:
|
||||
"""Handle punctuations in text.
|
||||
|
||||
Just strip punctuations from text or strip and restore them later.
|
||||
|
||||
Args:
|
||||
puncs (str): The punctuations to be processed. Defaults to `_DEF_PUNCS`.
|
||||
|
||||
Example:
|
||||
>>> punc = Punctuation()
|
||||
>>> punc.strip("This is. example !")
|
||||
'This is example'
|
||||
|
||||
>>> text_striped, punc_map = punc.strip_to_restore("This is. example !")
|
||||
>>> ' '.join(text_striped)
|
||||
'This is example'
|
||||
|
||||
>>> text_restored = punc.restore(text_striped, punc_map)
|
||||
>>> text_restored[0]
|
||||
'This is. example !'
|
||||
"""
|
||||
|
||||
def __init__(self, puncs: str = _DEF_PUNCS):
|
||||
self.puncs = puncs
|
||||
|
||||
@staticmethod
|
||||
def default_puncs():
|
||||
"""Return default set of punctuations."""
|
||||
return _DEF_PUNCS
|
||||
|
||||
@property
|
||||
def puncs(self):
|
||||
return self._puncs
|
||||
|
||||
@puncs.setter
|
||||
def puncs(self, value):
|
||||
if not isinstance(value, six.string_types):
|
||||
raise ValueError("[!] Punctuations must be of type str.")
|
||||
self._puncs = "".join(list(dict.fromkeys(list(value)))) # remove duplicates without changing the oreder
|
||||
self.puncs_regular_exp = re.compile(rf"(\s*[{re.escape(self._puncs)}]+\s*)+")
|
||||
|
||||
def strip(self, text):
|
||||
"""Remove all the punctuations by replacing with `space`.
|
||||
|
||||
Args:
|
||||
text (str): The text to be processed.
|
||||
|
||||
Example::
|
||||
|
||||
"This is. example !" -> "This is example "
|
||||
"""
|
||||
return re.sub(self.puncs_regular_exp, " ", text).rstrip().lstrip()
|
||||
|
||||
def strip_to_restore(self, text):
|
||||
"""Remove punctuations from text to restore them later.
|
||||
|
||||
Args:
|
||||
text (str): The text to be processed.
|
||||
|
||||
Examples ::
|
||||
|
||||
"This is. example !" -> [["This is", "example"], [".", "!"]]
|
||||
|
||||
"""
|
||||
text, puncs = self._strip_to_restore(text)
|
||||
return text, puncs
|
||||
|
||||
def _strip_to_restore(self, text):
|
||||
"""Auxiliary method for Punctuation.preserve()"""
|
||||
matches = list(re.finditer(self.puncs_regular_exp, text))
|
||||
if not matches:
|
||||
return [text], []
|
||||
# the text is only punctuations
|
||||
if len(matches) == 1 and matches[0].group() == text:
|
||||
return [], [_PUNC_IDX(text, PuncPosition.BEGIN)]
|
||||
# build a punctuation map to be used later to restore punctuations
|
||||
puncs = []
|
||||
for match in matches:
|
||||
position = PuncPosition.MIDDLE
|
||||
if match == matches[0] and text.startswith(match.group()):
|
||||
position = PuncPosition.BEGIN
|
||||
elif match == matches[-1] and text.endswith(match.group()):
|
||||
position = PuncPosition.END
|
||||
puncs.append(_PUNC_IDX(match.group(), position))
|
||||
# convert str text to a List[str], each item is separated by a punctuation
|
||||
splitted_text = []
|
||||
for idx, punc in enumerate(puncs):
|
||||
split = text.split(punc.punc)
|
||||
prefix, suffix = split[0], punc.punc.join(split[1:])
|
||||
text = suffix
|
||||
if prefix == "":
|
||||
# We don't want to insert an empty string in case of initial punctuation
|
||||
continue
|
||||
splitted_text.append(prefix)
|
||||
# if the text does not end with a punctuation, add it to the last item
|
||||
if idx == len(puncs) - 1 and len(suffix) > 0:
|
||||
splitted_text.append(suffix)
|
||||
return splitted_text, puncs
|
||||
|
||||
@classmethod
|
||||
def restore(cls, text, puncs):
|
||||
"""Restore punctuation in a text.
|
||||
|
||||
Args:
|
||||
text (str): The text to be processed.
|
||||
puncs (List[str]): The list of punctuations map to be used for restoring.
|
||||
|
||||
Examples ::
|
||||
|
||||
['This is', 'example'], ['.', '!'] -> "This is. example!"
|
||||
|
||||
"""
|
||||
return cls._restore(text, puncs)
|
||||
|
||||
@classmethod
|
||||
def _restore(cls, text, puncs): # pylint: disable=too-many-return-statements
|
||||
"""Auxiliary method for Punctuation.restore()"""
|
||||
if not puncs:
|
||||
return text
|
||||
|
||||
# nothing have been phonemized, returns the puncs alone
|
||||
if not text:
|
||||
return ["".join(m.punc for m in puncs)]
|
||||
|
||||
current = puncs[0]
|
||||
|
||||
if current.position == PuncPosition.BEGIN:
|
||||
return cls._restore([current.punc + text[0]] + text[1:], puncs[1:])
|
||||
|
||||
if current.position == PuncPosition.END:
|
||||
return [text[0] + current.punc] + cls._restore(text[1:], puncs[1:])
|
||||
|
||||
# POSITION == MIDDLE
|
||||
if len(text) == 1: # pragma: nocover
|
||||
# a corner case where the final part of an intermediate
|
||||
# mark (I) has not been phonemized
|
||||
return cls._restore([text[0] + current.punc], puncs[1:])
|
||||
|
||||
return cls._restore([text[0] + current.punc + text[1]] + text[2:], puncs[1:])
|
||||
|
||||
|
||||
# if __name__ == "__main__":
|
||||
# punc = Punctuation()
|
||||
# text = "This is. This is, example!"
|
||||
|
||||
# print(punc.strip(text))
|
||||
|
||||
# split_text, puncs = punc.strip_to_restore(text)
|
||||
# print(split_text, " ---- ", puncs)
|
||||
|
||||
# restored_text = punc.restore(split_text, puncs)
|
||||
# print(restored_text)
|
||||
@@ -0,0 +1,216 @@
|
||||
from typing import Callable, Dict, List, Union
|
||||
|
||||
from TTS.tts.utils.text import cleaners
|
||||
from TTS.tts.utils.text.characters import Graphemes, IPAPhonemes
|
||||
from TTS.tts.utils.text.phonemizers import DEF_LANG_TO_PHONEMIZER, get_phonemizer_by_name
|
||||
from TTS.tts.utils.text.phonemizers.multi_phonemizer import MultiPhonemizer
|
||||
from TTS.utils.generic_utils import get_import_path, import_class
|
||||
|
||||
|
||||
class TTSTokenizer:
|
||||
"""🐸TTS tokenizer to convert input characters to token IDs and back.
|
||||
|
||||
Token IDs for OOV chars are discarded but those are stored in `self.not_found_characters` for later.
|
||||
|
||||
Args:
|
||||
use_phonemes (bool):
|
||||
Whether to use phonemes instead of characters. Defaults to False.
|
||||
|
||||
characters (Characters):
|
||||
A Characters object to use for character-to-ID and ID-to-character mappings.
|
||||
|
||||
text_cleaner (callable):
|
||||
A function to pre-process the text before tokenization and phonemization. Defaults to None.
|
||||
|
||||
phonemizer (Phonemizer):
|
||||
A phonemizer object or a dict that maps language codes to phonemizer objects. Defaults to None.
|
||||
|
||||
Example:
|
||||
|
||||
>>> from TTS.tts.utils.text.tokenizer import TTSTokenizer
|
||||
>>> tokenizer = TTSTokenizer(use_phonemes=False, characters=Graphemes())
|
||||
>>> text = "Hello world!"
|
||||
>>> ids = tokenizer.text_to_ids(text)
|
||||
>>> text_hat = tokenizer.ids_to_text(ids)
|
||||
>>> assert text == text_hat
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
use_phonemes=False,
|
||||
text_cleaner: Callable = None,
|
||||
characters: "BaseCharacters" = None,
|
||||
phonemizer: Union["Phonemizer", Dict] = None,
|
||||
add_blank: bool = False,
|
||||
use_eos_bos=False,
|
||||
):
|
||||
self.text_cleaner = text_cleaner
|
||||
self.use_phonemes = use_phonemes
|
||||
self.add_blank = add_blank
|
||||
self.use_eos_bos = use_eos_bos
|
||||
self.characters = characters
|
||||
self.not_found_characters = []
|
||||
self.phonemizer = phonemizer
|
||||
|
||||
@property
|
||||
def characters(self):
|
||||
return self._characters
|
||||
|
||||
@characters.setter
|
||||
def characters(self, new_characters):
|
||||
self._characters = new_characters
|
||||
self.pad_id = self.characters.char_to_id(self.characters.pad) if self.characters.pad else None
|
||||
self.blank_id = self.characters.char_to_id(self.characters.blank) if self.characters.blank else None
|
||||
|
||||
def encode(self, text: str) -> List[int]:
|
||||
"""Encodes a string of text as a sequence of IDs."""
|
||||
token_ids = []
|
||||
for char in text:
|
||||
try:
|
||||
idx = self.characters.char_to_id(char)
|
||||
token_ids.append(idx)
|
||||
except KeyError:
|
||||
# discard but store not found characters
|
||||
if char not in self.not_found_characters:
|
||||
self.not_found_characters.append(char)
|
||||
print(text)
|
||||
print(f" [!] Character {repr(char)} not found in the vocabulary. Discarding it.")
|
||||
return token_ids
|
||||
|
||||
def decode(self, token_ids: List[int]) -> str:
|
||||
"""Decodes a sequence of IDs to a string of text."""
|
||||
text = ""
|
||||
for token_id in token_ids:
|
||||
text += self.characters.id_to_char(token_id)
|
||||
return text
|
||||
|
||||
def text_to_ids(self, text: str, language: str = None) -> List[int]: # pylint: disable=unused-argument
|
||||
"""Converts a string of text to a sequence of token IDs.
|
||||
|
||||
Args:
|
||||
text(str):
|
||||
The text to convert to token IDs.
|
||||
|
||||
language(str):
|
||||
The language code of the text. Defaults to None.
|
||||
|
||||
TODO:
|
||||
- Add support for language-specific processing.
|
||||
|
||||
1. Text normalizatin
|
||||
2. Phonemization (if use_phonemes is True)
|
||||
3. Add blank char between characters
|
||||
4. Add BOS and EOS characters
|
||||
5. Text to token IDs
|
||||
"""
|
||||
# TODO: text cleaner should pick the right routine based on the language
|
||||
if self.text_cleaner is not None:
|
||||
text = self.text_cleaner(text)
|
||||
if self.use_phonemes:
|
||||
text = self.phonemizer.phonemize(text, separator="", language=language)
|
||||
text = self.encode(text)
|
||||
if self.add_blank:
|
||||
text = self.intersperse_blank_char(text, True)
|
||||
if self.use_eos_bos:
|
||||
text = self.pad_with_bos_eos(text)
|
||||
return text
|
||||
|
||||
def ids_to_text(self, id_sequence: List[int]) -> str:
|
||||
"""Converts a sequence of token IDs to a string of text."""
|
||||
return self.decode(id_sequence)
|
||||
|
||||
def pad_with_bos_eos(self, char_sequence: List[str]):
|
||||
"""Pads a sequence with the special BOS and EOS characters."""
|
||||
return [self.characters.bos_id] + list(char_sequence) + [self.characters.eos_id]
|
||||
|
||||
def intersperse_blank_char(self, char_sequence: List[str], use_blank_char: bool = False):
|
||||
"""Intersperses the blank character between characters in a sequence.
|
||||
|
||||
Use the ```blank``` character if defined else use the ```pad``` character.
|
||||
"""
|
||||
char_to_use = self.characters.blank_id if use_blank_char else self.characters.pad
|
||||
result = [char_to_use] * (len(char_sequence) * 2 + 1)
|
||||
result[1::2] = char_sequence
|
||||
return result
|
||||
|
||||
def print_logs(self, level: int = 0):
|
||||
indent = "\t" * level
|
||||
print(f"{indent}| > add_blank: {self.add_blank}")
|
||||
print(f"{indent}| > use_eos_bos: {self.use_eos_bos}")
|
||||
print(f"{indent}| > use_phonemes: {self.use_phonemes}")
|
||||
if self.use_phonemes:
|
||||
print(f"{indent}| > phonemizer:")
|
||||
self.phonemizer.print_logs(level + 1)
|
||||
if len(self.not_found_characters) > 0:
|
||||
print(f"{indent}| > {len(self.not_found_characters)} not found characters:")
|
||||
for char in self.not_found_characters:
|
||||
print(f"{indent}| > {char}")
|
||||
|
||||
@staticmethod
|
||||
def init_from_config(config: "Coqpit", characters: "BaseCharacters" = None):
|
||||
"""Init Tokenizer object from config
|
||||
|
||||
Args:
|
||||
config (Coqpit): Coqpit model config.
|
||||
characters (BaseCharacters): Defines the model character set. If not set, use the default options based on
|
||||
the config values. Defaults to None.
|
||||
"""
|
||||
# init cleaners
|
||||
text_cleaner = None
|
||||
if isinstance(config.text_cleaner, (str, list)):
|
||||
text_cleaner = getattr(cleaners, config.text_cleaner)
|
||||
|
||||
# init characters
|
||||
if characters is None:
|
||||
# set characters based on defined characters class
|
||||
if config.characters and config.characters.characters_class:
|
||||
CharactersClass = import_class(config.characters.characters_class)
|
||||
characters, new_config = CharactersClass.init_from_config(config)
|
||||
# set characters based on config
|
||||
else:
|
||||
if config.use_phonemes:
|
||||
# init phoneme set
|
||||
characters, new_config = IPAPhonemes().init_from_config(config)
|
||||
else:
|
||||
# init character set
|
||||
characters, new_config = Graphemes().init_from_config(config)
|
||||
|
||||
else:
|
||||
characters, new_config = characters.init_from_config(config)
|
||||
|
||||
# set characters class
|
||||
new_config.characters.characters_class = get_import_path(characters)
|
||||
|
||||
# init phonemizer
|
||||
phonemizer = None
|
||||
if config.use_phonemes:
|
||||
if "phonemizer" in config and config.phonemizer == "multi_phonemizer":
|
||||
lang_to_phonemizer_name = {}
|
||||
for dataset in config.datasets:
|
||||
if dataset.language != "":
|
||||
lang_to_phonemizer_name[dataset.language] = dataset.phonemizer
|
||||
else:
|
||||
raise ValueError("Multi phonemizer requires language to be set for each dataset.")
|
||||
phonemizer = MultiPhonemizer(lang_to_phonemizer_name)
|
||||
else:
|
||||
phonemizer_kwargs = {"language": config.phoneme_language}
|
||||
if "phonemizer" in config and config.phonemizer:
|
||||
phonemizer = get_phonemizer_by_name(config.phonemizer, **phonemizer_kwargs)
|
||||
else:
|
||||
try:
|
||||
phonemizer = get_phonemizer_by_name(
|
||||
DEF_LANG_TO_PHONEMIZER[config.phoneme_language], **phonemizer_kwargs
|
||||
)
|
||||
new_config.phonemizer = phonemizer.name()
|
||||
except KeyError as e:
|
||||
raise ValueError(
|
||||
f"""No phonemizer found for language {config.phoneme_language}.
|
||||
You may need to install a third party library for this language."""
|
||||
) from e
|
||||
|
||||
return (
|
||||
TTSTokenizer(
|
||||
config.use_phonemes, text_cleaner, characters, phonemizer, config.add_blank, config.enable_eos_bos_chars
|
||||
),
|
||||
new_config,
|
||||
)
|
||||
Reference in New Issue
Block a user