Update user accent control

This commit is contained in:
litagin02
2024-01-08 16:20:28 +09:00
parent b4aa81c078
commit 2bf89403b2
6 changed files with 431 additions and 403 deletions

68
app.py
View File

@@ -1,7 +1,9 @@
import argparse
import datetime
import json
import os
import sys
from typing import Optional
import gradio as gr
import torch
@@ -21,6 +23,8 @@ from common.constants import (
)
from common.log import logger
from common.tts_model import ModelHolder
from infer import InvalidToneError
from text.japanese import g2kata_tone, kata_tone2phone_tone, text_normalize
# Get path settings
with open(os.path.join("configs", "paths.yml"), "r", encoding="utf-8") as f:
@@ -46,15 +50,41 @@ def tts_fn(
use_assist_text,
style,
style_weight,
given_tone,
kata_tone_json_str,
use_tone,
):
assert model_holder.current_model is not None
if given_tone == "":
given_tone = None
else:
given_tone = [int(i) for i in given_tone]
wrong_tone_message = ""
kata_tone: Optional[list[tuple[str, int]]] = None
if use_tone and kata_tone_json_str != "":
if language != "JP":
logger.warning("Only Japanese is supported for tone generation.")
wrong_tone_message = "アクセント指定は現在日本語のみ対応しています。"
if line_split:
logger.warning("Tone generation is not supported for line split.")
wrong_tone_message = "アクセント指定は改行で分けて生成を使わない場合のみ対応しています。"
try:
kata_tone = []
json_data = json.loads(kata_tone_json_str)
# tupleを使うように変換
for kana, tone in json_data:
assert isinstance(kana, str) and isinstance(tone, int)
kata_tone.append((kana, tone))
except Exception as e:
logger.warning(f"Error occurred when parsing kana_tone_json: {e}")
wrong_tone_message = f"アクセント指定が不正です: {e}"
kata_tone = None
# toneは実際に音声合成に代入される際のみnot Noneになる
tone: Optional[list[int]] = None
if kata_tone is not None:
phone_tone = kata_tone2phone_tone(kata_tone)
tone = [t for _, t in phone_tone]
start_time = datetime.datetime.now()
try:
sr, audio = model_holder.current_model.infer(
text=text,
language=language,
@@ -70,12 +100,26 @@ def tts_fn(
use_assist_text=use_assist_text,
style=style,
style_weight=style_weight,
given_tone=given_tone,
given_tone=tone,
)
except InvalidToneError as e:
logger.error(f"Tone error: {e}")
return f"Error: アクセント指定が不正です:\n{e}", None, kata_tone_json_str
end_time = datetime.datetime.now()
duration = (end_time - start_time).total_seconds()
return f"Success, time: {duration} seconds.", (sr, audio)
if tone is None and language == "JP" and not line_split:
# アクセント指定に使えるようにアクセント情報を返す
norm_text = text_normalize(text)
kata_tone = g2kata_tone(norm_text)
kata_tone_json_str = json.dumps(kata_tone, ensure_ascii=False, indent=2)
elif tone is None:
kata_tone_json_str = ""
message = f"Success, time: {duration} seconds."
if wrong_tone_message != "":
message = wrong_tone_message + "\n" + message
return message, (sr, audio), kata_tone_json_str
initial_text = "こんにちは、初めまして。あなたの名前はなんていうの?"
@@ -243,7 +287,10 @@ if __name__ == "__main__":
step=0.1,
label="分けた場合に挟む無音の長さ(秒)",
)
given_tone = gr.Textbox("トーン、0と1の数値列")
tone = gr.Textbox(label="アクセント指定")
use_tone = gr.Checkbox(
label="アクセント指定を使う", info="改行で分けない場合のみ使えます", value=False
)
language = gr.Dropdown(choices=languages, value="JP", label="Language")
with gr.Accordion(label="詳細設定", open=False):
sdp_ratio = gr.Slider(
@@ -340,9 +387,10 @@ if __name__ == "__main__":
use_assist_text,
style,
style_weight,
given_tone,
tone,
use_tone,
],
outputs=[text_output, audio_output],
outputs=[text_output, audio_output, tone],
)
model_name.change(

View File

@@ -71,9 +71,9 @@ class Model:
def get_style_vector_from_audio(
self, audio_path: str, weight: float = 1.0
) -> np.ndarray:
from style_gen import extract_style_vector
from style_gen import get_style_vector
xvec = extract_style_vector(audio_path)
xvec = get_style_vector(audio_path)
mean = self.style_vectors[0]
xvec = mean + (xvec - mean) * weight
return xvec
@@ -190,7 +190,7 @@ class ModelHolder:
if f.endswith(".pth") or f.endswith(".pt") or f.endswith(".safetensors")
]
if len(model_files) == 0:
logger.info(
logger.warning(
f"No model files found in {self.root_dir}/{model_name}, so skip it"
)
continue

View File

@@ -11,6 +11,10 @@ from common.log import logger
# latest_version = "1.0"
class InvalidToneError(ValueError):
pass
def get_net_g(model_path: str, version: str, device: str, hps):
net_g = SynthesizerTrn(
len(symbols),
@@ -41,9 +45,11 @@ def get_text(
):
# 在此处实现当前版本的get_text
norm_text, phone, tone, word2ph = clean_text(text, language_str)
logger.info(f"Original tone: {''.join(str(num) for num in tone)}")
if given_tone is not None:
logger.debug(f"Tone given: {given_tone}")
if len(given_tone) != len(phone):
raise InvalidToneError(
f"Length of given_tone ({len(given_tone)}) != length of phone ({len(phone)})"
)
tone = given_tone
phone, tone, language = cleaned_text_to_sequence(phone, tone, language_str)

View File

@@ -20,9 +20,14 @@ device = torch.device(config.style_gen_config.device)
inference.to(device)
# 推論時にインポートするために短いが関数を書く
def get_style_vector(wav_path):
return inference(wav_path)
def save_style_vector(wav_path):
try:
style_vec = inference(wav_path)
style_vec = get_style_vector(wav_path)
except Exception as e:
logger.error(f"\nError occurred with file: {wav_path}, Details:\n{e}\n")
raise

View File

@@ -3,13 +3,29 @@
import re
import unicodedata
import jaconv
import pyopenjtalk
from num2words import num2words
from transformers import AutoTokenizer
from common.log import logger
from text import punctuation
from text.japanese_mora_list import (
mora_kata_to_mora_phonemes,
mora_phonemes_to_mora_kata,
)
# 子音の集合
COSONANTS = set(
[
cosonant
for cosonant, _ in mora_kata_to_mora_phonemes.values()
if cosonant is not None
]
)
# 母音の集合
VOWELS = {"a", "i", "u", "e", "o"}
# 正規化で記号を変換するための辞書
rep_map = {
@@ -145,14 +161,14 @@ def g2p(norm_text: str) -> tuple[list[str], list[int], list[int]]:
# アクセント割当をしなおすことによってpunctuationを含めた音素とアクセントのリストを作る。
# punctuationがすべて消えた、音素とアクセントのタプルのリスト
phone_tone_list_wo_punct = g2phone_tone_list(norm_text)
phone_tone_list_wo_punct = g2phone_tone_wo_punct(norm_text)
# sep_text: 単語単位の単語のリスト
# sep_kata: 単語単位の単語のカタカナ読みのリスト
sep_text, sep_kata = text2sep_kata(norm_text)
# sep_phonemes: 各単語ごとの音素のリストのリスト
sep_phonemes = handle_long([kata2phoneme(i) for i in sep_kata])
sep_phonemes = handle_long([kata2phoneme_list(i) for i in sep_kata])
# phone_w_punct: sep_phonemesを結合した、punctuationを元のまま保持した音素列
phone_w_punct: list[str] = []
@@ -192,10 +208,71 @@ def g2p(norm_text: str) -> tuple[list[str], list[int], list[int]]:
return phones, tones, word2ph
def g2phone_tone_list(text: str) -> list[tuple[str, int]]:
def g2kata_tone(norm_text: str) -> list[tuple[str, int]]:
phones, tones, _ = g2p(norm_text)
return phone_tone2kata_tone(list(zip(phones, tones)))
def phone_tone2kata_tone(phone_tone: list[tuple[str, int]]) -> list[tuple[str, int]]:
"""phone_toneをのphone部分をカタカナに変換する。ただし最初と最後の("_", 0)は無視"""
phone_tone = phone_tone[1:] # 最初の("_", 0)を無視
phones = [phone for phone, _ in phone_tone]
tones = [tone for _, tone in phone_tone]
logger.debug(f"phones: {phones}")
logger.debug(f"tones: {tones}")
result: list[tuple[str, int]] = []
current_mora = ""
for phone, next_phone, tone, next_tone in zip(phones, phones[1:], tones, tones[1:]):
# zipの関係で最後の("_", 0)は無視されている
if phone in punctuation:
result.append((phone, tone))
continue
if phone in COSONANTS and phone != "n": # n以外の子音の場合
assert current_mora == "", f"Unexpected {phone} after {current_mora}"
assert tone == next_tone, f"Unexpected {phone} tone {tone} != {next_tone}"
current_mora = phone
elif phone == "n":
logger.debug(f"current_mora: {current_mora}")
logger.debug(f"next_phone: {next_phone}")
assert current_mora == "", f"Unexpected {phone} after {current_mora}"
if next_phone not in VOWELS: # 次の音素が母音でない場合
result.append(("", tone))
else:
# 子音のnの場合
assert (
tone == next_tone
), f"Unexpected {phone} tone {tone} != {next_tone}"
current_mora = "n"
else:
# phoneが母音
current_mora += phone
result.append((mora_phonemes_to_mora_kata[current_mora], tone))
current_mora = ""
logger.debug(f"result: {result}")
return result
def kata_tone2phone_tone(kata_tone: list[tuple[str, int]]) -> list[tuple[str, int]]:
"""`phone_tone2kata_tone()`の逆。"""
result: list[tuple[str, int]] = [("_", 0)]
for mora, tone in kata_tone:
if mora in punctuation:
result.append((mora, tone))
else:
cosonant, vowel = mora_kata_to_mora_phonemes[mora]
if cosonant is None:
result.append((vowel, tone))
else:
result.append((cosonant, tone))
result.append((vowel, tone))
result.append(("_", 0))
return result
def g2phone_tone_wo_punct(text: str) -> list[tuple[str, int]]:
"""
テキストに対して、音素とアクセント0か1のペアのリストを返す。
ただし「!」「.」「?」等の非音素記号は全て消える(ポーズ記号も残さない)。
ただし「!」「.」「?」等の非音素記号(punctuation)は全て消える(ポーズ記号も残さない)。
非音素記号を含める処理は`align_tones()`で行われる。
また「っ」は「q」に、「ん」は「n」に変換される。
例: "こんにちは、世界ー。。元気?!"
@@ -272,8 +349,7 @@ def text2sep_kata(norm_text: str) -> tuple[list[str], list[str]]:
"""
assert yomi != "", f"Empty yomi: {word}"
if yomi == "":
assert len(word) == 1, f"yomi `、` comes from: {word}"
# wordは1文字の記号。正規化されているので、`.`, `,`, `!`, `'`, `-`のいずれか
# wordは正規化されているので、`.`, `,`, `!`, `'`, `-`のいずれか
if word not in (
".",
",",
@@ -281,6 +357,7 @@ def text2sep_kata(norm_text: str) -> tuple[list[str], list[str]]:
"'",
"-",
):
# ここはpyopenjtalkが読めない文字等のときに起こる
raise ValueError(f"Cannot read: {word} in:\n{norm_text}")
# yomiは元の記号のままに変更
yomi = word
@@ -292,35 +369,6 @@ def text2sep_kata(norm_text: str) -> tuple[list[str], list[str]]:
return sep_text, sep_kata
def kata2phoneme(text: str) -> list[str]:
"""
原則カタカナの`text`を受け取り、それをそのままいじらずに音素記号のリストに変換。
注意点:
- punctuationが来た場合punctuationが1文字の場合がありうる、処理せず1文字のリストを返す
- 冒頭に続く「ー」はそのまま「ー」のままにする(`handle_long()`で処理される)
- 文中の「ー」は前の音素記号の最後の音素記号に変換される。
例:
`ーーソーナノカーー` → ["", "", "s", "o", "o", "n", "a", "n", "o", "k", "a", "a", "a"]
`?` → ["?"]
"""
if text in punctuation:
return [text]
# `text`がカタカナ(`ー`含む)のみからなるかどうかをチェック
if re.fullmatch(r"[\u30A0-\u30FF]+", text) is None:
raise ValueError(f"Non-punctuation input must be katakana only: {text}")
# 以降`text`はカタカナのみからなる
if text == "":
return [""]
elif text.startswith(""):
return [""] + kata2phoneme(text[1:])
res: list[str] = []
while text:
# カタカナをひらがなに変換してから`hiragana2p`をかける
res += hiragana2p(jaconv.kata2hira(text)).split(" ")
break
return res
# ESPnetの実装から引用、変更点無し
# https://github.com/espnet/espnet/blob/master/espnet2/text/phoneme_tokenizer.py
def pyopenjtalk_g2p_prosody(text: str, drop_unvoiced_vowels: bool = True) -> list[str]:
@@ -492,346 +540,38 @@ def align_tones(
return result
# jaconvから借りて修正
# https://github.com/ikegami-yukino/jaconv/blob/master/jaconv/jaconv.py
def hiragana2p(text: str) -> str:
def kata2phoneme_list(text: str) -> list[str]:
"""
Modification of `jaconv.hiragana2julius`.
- avoid using `:`, instead, `あーーー` -> `a a a a`.
- avoid converting `o u` to `o o` (because the input is already actual `yomi`).
- avoid using `N` for `ん` (for compatibility)
- use `v` for `ゔ` related text.
- add bare `ゃ` `ゅ` `ょ` to `y a` `y u` `y o` (for compatibility).
原則カタカナの`text`を受け取り、それをそのままいじらずに音素記号のリストに変換。
注意点:
- punctuationが来た場合punctuationが1文字の場合がありうる、処理せず1文字のリストを返す
- 冒頭に続く「ー」はそのまま「ー」のままにする(`handle_long()`で処理される)
- 文中の「ー」は前の音素記号の最後の音素記号に変換される。
例:
`ーーソーナノカーー` → ["", "", "s", "o", "o", "n", "a", "n", "o", "k", "a", "a", "a"]
`?` → ["?"]
"""
# 3文字以上からなる変換規則
text = text.replace("う゛ぁ", " v a")
text = text.replace("う゛ぃ", " v i")
text = text.replace("う゛ぇ", " v e")
text = text.replace("う゛ぉ", " v o")
text = text.replace("う゛ゅ", " by u")
if text in punctuation:
return [text]
# `text`がカタカナ(`ー`含む)のみからなるかどうかをチェック
if re.fullmatch(r"[\u30A0-\u30FF]+", text) is None:
raise ValueError(f"Input must be katakana only: {text}")
sorted_keys = sorted(mora_kata_to_mora_phonemes.keys(), key=len, reverse=True)
pattern = "|".join(map(re.escape, sorted_keys))
# ゔ等の処理を追加
text = text.replace("ゔぁ", " v a")
text = text.replace("ゔぃ", " v i")
text = text.replace("ゔぇ", " v e")
text = text.replace("ゔぉ", " v o")
text = text.replace("ゔゅ", " by u")
def mora2phonemes(mora: str) -> str:
cosonant, vowel = mora_kata_to_mora_phonemes[mora]
if cosonant is None:
return f" {vowel}"
return f" {cosonant} {vowel}"
# 2文字からなる変換規則
text = text.replace("ぅ゛", " v u")
spaced_phonemes = re.sub(pattern, lambda m: mora2phonemes(m.group()), text)
text = text.replace("あぁ", " a a")
text = text.replace("いぃ", " i i")
text = text.replace("いぇ", " i e")
text = text.replace("いゃ", " y a")
text = text.replace("うぅ", " u:")
text = text.replace("えぇ", " e e")
text = text.replace("おぉ", " o:")
text = text.replace("かぁ", " k a:")
text = text.replace("きぃ", " k i:")
text = text.replace("くぅ", " k u:")
text = text.replace("くゃ", " ky a")
text = text.replace("くゅ", " ky u")
text = text.replace("くょ", " ky o")
text = text.replace("けぇ", " k e:")
text = text.replace("こぉ", " k o:")
text = text.replace("がぁ", " g a:")
text = text.replace("ぎぃ", " g i:")
text = text.replace("ぐぅ", " g u:")
text = text.replace("ぐゃ", " gy a")
text = text.replace("ぐゅ", " gy u")
text = text.replace("ぐょ", " gy o")
text = text.replace("げぇ", " g e:")
text = text.replace("ごぉ", " g o:")
text = text.replace("さぁ", " s a:")
text = text.replace("しぃ", " sh i:")
text = text.replace("すぅ", " s u:")
text = text.replace("すゃ", " sh a")
text = text.replace("すゅ", " sh u")
text = text.replace("すょ", " sh o")
text = text.replace("せぇ", " s e:")
text = text.replace("そぉ", " s o:")
text = text.replace("ざぁ", " z a:")
text = text.replace("じぃ", " j i:")
text = text.replace("ずぅ", " z u:")
text = text.replace("ずゃ", " zy a")
text = text.replace("ずゅ", " zy u")
text = text.replace("ずょ", " zy o")
text = text.replace("ぜぇ", " z e:")
text = text.replace("ぞぉ", " z o:")
text = text.replace("たぁ", " t a:")
text = text.replace("ちぃ", " ch i:")
text = text.replace("つぁ", " ts a")
text = text.replace("つぃ", " ts i")
text = text.replace("つぅ", " ts u:")
text = text.replace("つゃ", " ch a")
text = text.replace("つゅ", " ch u")
text = text.replace("つょ", " ch o")
text = text.replace("つぇ", " ts e")
text = text.replace("つぉ", " ts o")
text = text.replace("てぇ", " t e:")
text = text.replace("とぉ", " t o:")
text = text.replace("だぁ", " d a:")
text = text.replace("ぢぃ", " j i:")
text = text.replace("づぅ", " d u:")
text = text.replace("づゃ", " zy a")
text = text.replace("づゅ", " zy u")
text = text.replace("づょ", " zy o")
text = text.replace("でぇ", " d e:")
text = text.replace("どぉ", " d o:")
text = text.replace("なぁ", " n a:")
text = text.replace("にぃ", " n i:")
text = text.replace("ぬぅ", " n u:")
text = text.replace("ぬゃ", " ny a")
text = text.replace("ぬゅ", " ny u")
text = text.replace("ぬょ", " ny o")
text = text.replace("ねぇ", " n e:")
text = text.replace("のぉ", " n o:")
text = text.replace("はぁ", " h a:")
text = text.replace("ひぃ", " h i:")
text = text.replace("ふぅ", " f u:")
text = text.replace("ふゃ", " hy a")
text = text.replace("ふゅ", " hy u")
text = text.replace("ふょ", " hy o")
text = text.replace("へぇ", " h e:")
text = text.replace("ほぉ", " h o:")
text = text.replace("ばぁ", " b a:")
text = text.replace("びぃ", " b i:")
text = text.replace("ぶぅ", " b u:")
text = text.replace("ふゃ", " hy a")
text = text.replace("ぶゅ", " by u")
text = text.replace("ふょ", " hy o")
text = text.replace("べぇ", " b e:")
text = text.replace("ぼぉ", " b o:")
text = text.replace("ぱぁ", " p a:")
text = text.replace("ぴぃ", " p i:")
text = text.replace("ぷぅ", " p u:")
text = text.replace("ぷゃ", " py a")
text = text.replace("ぷゅ", " py u")
text = text.replace("ぷょ", " py o")
text = text.replace("ぺぇ", " p e:")
text = text.replace("ぽぉ", " p o:")
text = text.replace("まぁ", " m a:")
text = text.replace("みぃ", " m i:")
text = text.replace("むぅ", " m u:")
text = text.replace("むゃ", " my a")
text = text.replace("むゅ", " my u")
text = text.replace("むょ", " my o")
text = text.replace("めぇ", " m e:")
text = text.replace("もぉ", " m o:")
text = text.replace("やぁ", " y a:")
text = text.replace("ゆぅ", " y u:")
text = text.replace("ゆゃ", " y a:")
text = text.replace("ゆゅ", " y u:")
text = text.replace("ゆょ", " y o:")
text = text.replace("よぉ", " y o:")
text = text.replace("らぁ", " r a:")
text = text.replace("りぃ", " r i:")
text = text.replace("るぅ", " r u:")
text = text.replace("るゃ", " ry a")
text = text.replace("るゅ", " ry u")
text = text.replace("るょ", " ry o")
text = text.replace("れぇ", " r e:")
text = text.replace("ろぉ", " r o:")
text = text.replace("わぁ", " w a:")
text = text.replace("をぉ", " o:")
text = text.replace("う゛", " b u")
text = text.replace("でぃ", " d i")
text = text.replace("でぇ", " d e:")
text = text.replace("でゃ", " dy a")
text = text.replace("でゅ", " dy u")
text = text.replace("でょ", " dy o")
text = text.replace("てぃ", " t i")
text = text.replace("てぇ", " t e:")
text = text.replace("てゃ", " ty a")
text = text.replace("てゅ", " ty u")
text = text.replace("てょ", " ty o")
text = text.replace("すぃ", " s i")
text = text.replace("ずぁ", " z u a")
text = text.replace("ずぃ", " z i")
text = text.replace("ずぅ", " z u")
text = text.replace("ずゃ", " zy a")
text = text.replace("ずゅ", " zy u")
text = text.replace("ずょ", " zy o")
text = text.replace("ずぇ", " z e")
text = text.replace("ずぉ", " z o")
text = text.replace("きゃ", " ky a")
text = text.replace("きゅ", " ky u")
text = text.replace("きょ", " ky o")
text = text.replace("しゃ", " sh a")
text = text.replace("しゅ", " sh u")
text = text.replace("しぇ", " sh e")
text = text.replace("しょ", " sh o")
text = text.replace("ちゃ", " ch a")
text = text.replace("ちゅ", " ch u")
text = text.replace("ちぇ", " ch e")
text = text.replace("ちょ", " ch o")
text = text.replace("とぅ", " t u")
text = text.replace("とゃ", " ty a")
text = text.replace("とゅ", " ty u")
text = text.replace("とょ", " ty o")
text = text.replace("どぁ", " d o a")
text = text.replace("どぅ", " d u")
text = text.replace("どゃ", " dy a")
text = text.replace("どゅ", " dy u")
text = text.replace("どょ", " dy o")
text = text.replace("どぉ", " d o:")
text = text.replace("にゃ", " ny a")
text = text.replace("にゅ", " ny u")
text = text.replace("にょ", " ny o")
text = text.replace("ひゃ", " hy a")
text = text.replace("ひゅ", " hy u")
text = text.replace("ひょ", " hy o")
text = text.replace("みゃ", " my a")
text = text.replace("みゅ", " my u")
text = text.replace("みょ", " my o")
text = text.replace("りゃ", " ry a")
text = text.replace("りゅ", " ry u")
text = text.replace("りょ", " ry o")
text = text.replace("ぎゃ", " gy a")
text = text.replace("ぎゅ", " gy u")
text = text.replace("ぎょ", " gy o")
text = text.replace("ぢぇ", " j e")
text = text.replace("ぢゃ", " j a")
text = text.replace("ぢゅ", " j u")
text = text.replace("ぢょ", " j o")
text = text.replace("じぇ", " j e")
text = text.replace("じゃ", " j a")
text = text.replace("じゅ", " j u")
text = text.replace("じょ", " j o")
text = text.replace("びゃ", " by a")
text = text.replace("びゅ", " by u")
text = text.replace("びょ", " by o")
text = text.replace("ぴゃ", " py a")
text = text.replace("ぴゅ", " py u")
text = text.replace("ぴょ", " py o")
text = text.replace("うぁ", " u a")
text = text.replace("うぃ", " w i")
text = text.replace("うぇ", " w e")
text = text.replace("うぉ", " w o")
text = text.replace("ふぁ", " f a")
text = text.replace("ふぃ", " f i")
text = text.replace("ふぅ", " f u")
text = text.replace("ふゃ", " hy a")
text = text.replace("ふゅ", " hy u")
text = text.replace("ふょ", " hy o")
text = text.replace("ふぇ", " f e")
text = text.replace("ふぉ", " f o")
# 1音からなる変換規則
text = text.replace("", " a")
text = text.replace("", " i")
text = text.replace("", " u")
text = text.replace("", " v u") # ゔの処理を追加
text = text.replace("", " e")
text = text.replace("", " o")
text = text.replace("", " k a")
text = text.replace("", " k i")
text = text.replace("", " k u")
text = text.replace("", " k e")
text = text.replace("", " k o")
text = text.replace("", " s a")
text = text.replace("", " sh i")
text = text.replace("", " s u")
text = text.replace("", " s e")
text = text.replace("", " s o")
text = text.replace("", " t a")
text = text.replace("", " ch i")
text = text.replace("", " ts u")
text = text.replace("", " t e")
text = text.replace("", " t o")
text = text.replace("", " n a")
text = text.replace("", " n i")
text = text.replace("", " n u")
text = text.replace("", " n e")
text = text.replace("", " n o")
text = text.replace("", " h a")
text = text.replace("", " h i")
text = text.replace("", " f u")
text = text.replace("", " h e")
text = text.replace("", " h o")
text = text.replace("", " m a")
text = text.replace("", " m i")
text = text.replace("", " m u")
text = text.replace("", " m e")
text = text.replace("", " m o")
text = text.replace("", " r a")
text = text.replace("", " r i")
text = text.replace("", " r u")
text = text.replace("", " r e")
text = text.replace("", " r o")
text = text.replace("", " g a")
text = text.replace("", " g i")
text = text.replace("", " g u")
text = text.replace("", " g e")
text = text.replace("", " g o")
text = text.replace("", " z a")
text = text.replace("", " j i")
text = text.replace("", " z u")
text = text.replace("", " z e")
text = text.replace("", " z o")
text = text.replace("", " d a")
text = text.replace("", " j i")
text = text.replace("", " z u")
text = text.replace("", " d e")
text = text.replace("", " d o")
text = text.replace("", " b a")
text = text.replace("", " b i")
text = text.replace("", " b u")
text = text.replace("", " b e")
text = text.replace("", " b o")
text = text.replace("", " p a")
text = text.replace("", " p i")
text = text.replace("", " p u")
text = text.replace("", " p e")
text = text.replace("", " p o")
text = text.replace("", " y a")
text = text.replace("", " y u")
text = text.replace("", " y o")
text = text.replace("", " w a")
text = text.replace("", " i")
text = text.replace("", " e")
text = text.replace("", " N")
text = text.replace("", " q")
# ここまでに処理されてない ぁぃぅぇぉ はそのまま大文字扱い
text = text.replace("", " a")
text = text.replace("", " i")
text = text.replace("", " u")
text = text.replace("", " e")
text = text.replace("", " o")
text = text.replace("", " w a")
text = text.replace("", " o")
# ここまでに処理されていないゅ等もそのまま大文字扱い(追加)
text = text.replace("", " y a")
text = text.replace("", " y u")
text = text.replace("", " y o")
# 長音の処理
# for (pattern, replace_str) in JULIUS_LONG_VOWEL:
# text = pattern.sub(replace_str, text)
# text = text.replace("o u", "o:") # おう -> おーの音便
text = text.replace("", ":")
text = text.replace("", ":")
text = text.replace("", ":")
text = text.replace("-", ":")
# その他特別な処理
text = text.replace("", " o")
text = text.strip()
text = text.replace(":+", ":")
# ここまで`jaconv.hiragana2julius`と音便処理と長音処理をのぞいて同じ
# ここから`k a:: k i:`→`k a a a k i i`のように`:`の数だけ繰り返す処理
pattern = r"(\w)(:*)"
replacement = lambda m: m.group(1) + (" " + m.group(1)) * len(m.group(2))
text = re.sub(pattern, replacement, text)
text = text.replace("N", "n") # 「ん」のNをnに変換
return text
# 長音記号「ー」の処理
long_pattern = r"(\w)(ー*)"
long_replacement = lambda m: m.group(1) + (" " + m.group(1)) * len(m.group(2))
spaced_phonemes = re.sub(long_pattern, long_replacement, spaced_phonemes)
return spaced_phonemes.strip().split(" ")
if __name__ == "__main__":

229
text/japanese_mora_list.py Normal file
View File

@@ -0,0 +1,229 @@
"""
VOICEVOXのソースコードからお借りして最低限に改造したコード。
https://github.com/VOICEVOX/voicevox_engine/blob/master/voicevox_engine/tts_pipeline/mora_list.py
"""
"""
以下のモーラ対応表はOpenJTalkのソースコードから取得し、
カタカナ表記とモーラが一対一対応するように改造した。
ライセンス表記:
-----------------------------------------------------------------
The Japanese TTS System "Open JTalk"
developed by HTS Working Group
http://open-jtalk.sourceforge.net/
-----------------------------------------------------------------
Copyright (c) 2008-2014 Nagoya Institute of Technology
Department of Computer Science
All rights reserved.
Redistribution and use in source and binary forms, with or
without modification, are permitted provided that the following
conditions are met:
- Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
- Redistributions in binary form must reproduce the above
copyright notice, this list of conditions and the following
disclaimer in the documentation and/or other materials provided
with the distribution.
- Neither the name of the HTS working group nor the names of its
contributors may be used to endorse or promote products derived
from this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS
BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
POSSIBILITY OF SUCH DAMAGE.
"""
# カタカナ, 子音, 母音)の順。子音がない場合はNoneを入れる。
# 但し「ン」と「ッ」は母音のみという扱いで、「ン」は「N」、「ッ」は「q」に変更
# 元々はそれぞれ「N」「cl」
_mora_list_minimum: list[tuple[str, str | None, str]] = [
("ヴォ", "v", "o"),
("ヴェ", "v", "e"),
("ヴィ", "v", "i"),
("ヴァ", "v", "a"),
("", "v", "u"),
("", None, "n"), # 「N」から「n」に変更
("", "w", "a"),
("", "r", "o"),
("", "r", "e"),
("", "r", "u"),
("リョ", "ry", "o"),
("リュ", "ry", "u"),
("リャ", "ry", "a"),
("リェ", "ry", "e"),
("", "r", "i"),
("", "r", "a"),
("", "y", "o"),
("", "y", "u"),
("", "y", "a"),
("", "m", "o"),
("", "m", "e"),
("", "m", "u"),
("ミョ", "my", "o"),
("ミュ", "my", "u"),
("ミャ", "my", "a"),
("ミェ", "my", "e"),
("", "m", "i"),
("", "m", "a"),
("", "p", "o"),
("", "b", "o"),
("", "h", "o"),
("", "p", "e"),
("", "b", "e"),
("", "h", "e"),
("", "p", "u"),
("", "b", "u"),
("フォ", "f", "o"),
("フェ", "f", "e"),
("フィ", "f", "i"),
("ファ", "f", "a"),
("", "f", "u"),
("ピョ", "py", "o"),
("ピュ", "py", "u"),
("ピャ", "py", "a"),
("ピェ", "py", "e"),
("", "p", "i"),
("ビョ", "by", "o"),
("ビュ", "by", "u"),
("ビャ", "by", "a"),
("ビェ", "by", "e"),
("", "b", "i"),
("ヒョ", "hy", "o"),
("ヒュ", "hy", "u"),
("ヒャ", "hy", "a"),
("ヒェ", "hy", "e"),
("", "h", "i"),
("", "p", "a"),
("", "b", "a"),
("", "h", "a"),
("", "n", "o"),
("", "n", "e"),
("", "n", "u"),
("ニョ", "ny", "o"),
("ニュ", "ny", "u"),
("ニャ", "ny", "a"),
("ニェ", "ny", "e"),
("", "n", "i"),
("", "n", "a"),
("ドゥ", "d", "u"),
("", "d", "o"),
("トゥ", "t", "u"),
("", "t", "o"),
("デョ", "dy", "o"),
("デュ", "dy", "u"),
("デャ", "dy", "a"),
("デェ", "dy", "e"),
("ディ", "d", "i"),
("", "d", "e"),
("テョ", "ty", "o"),
("テュ", "ty", "u"),
("テャ", "ty", "a"),
("ティ", "t", "i"),
("", "t", "e"),
("ツォ", "ts", "o"),
("ツェ", "ts", "e"),
("ツィ", "ts", "i"),
("ツァ", "ts", "a"),
("", "ts", "u"),
("", None, "q"), # 「cl」から「q」に変更
("チョ", "ch", "o"),
("チュ", "ch", "u"),
("チャ", "ch", "a"),
("チェ", "ch", "e"),
("", "ch", "i"),
("", "d", "a"),
("", "t", "a"),
("", "z", "o"),
("", "s", "o"),
("", "z", "e"),
("", "s", "e"),
("ズィ", "z", "i"),
("", "z", "u"),
("スィ", "s", "i"),
("", "s", "u"),
("ジョ", "j", "o"),
("ジュ", "j", "u"),
("ジャ", "j", "a"),
("ジェ", "j", "e"),
("", "j", "i"),
("ショ", "sh", "o"),
("シュ", "sh", "u"),
("シャ", "sh", "a"),
("シェ", "sh", "e"),
("", "sh", "i"),
("", "z", "a"),
("", "s", "a"),
("", "g", "o"),
("", "k", "o"),
("", "g", "e"),
("", "k", "e"),
("グヮ", "gw", "a"),
("", "g", "u"),
("クヮ", "kw", "a"),
("", "k", "u"),
("ギョ", "gy", "o"),
("ギュ", "gy", "u"),
("ギャ", "gy", "a"),
("ギェ", "gy", "e"),
("", "g", "i"),
("キョ", "ky", "o"),
("キュ", "ky", "u"),
("キャ", "ky", "a"),
("キェ", "ky", "e"),
("", "k", "i"),
("", "g", "a"),
("", "k", "a"),
("", None, "o"),
("", None, "e"),
("ウォ", "w", "o"),
("ウェ", "w", "e"),
("ウィ", "w", "i"),
("", None, "u"),
("イェ", "y", "e"),
("", None, "i"),
("", None, "a"),
]
_mora_list_additional: list[tuple[str, str | None, str]] = [
("ヴョ", "by", "o"),
("ヴュ", "by", "u"),
("ヴャ", "by", "a"),
("", None, "o"),
("", None, "e"),
("", None, "i"),
("", "w", "a"),
("", "y", "o"),
("", "y", "u"),
("", "z", "u"),
("", "j", "i"),
("", "k", "e"),
("", "y", "a"),
("", None, "o"),
("", None, "e"),
("", None, "u"),
("", None, "i"),
("", None, "a"),
]
# 例: "vo" -> "ヴォ", "a" -> "ア"
mora_phonemes_to_mora_kata: dict[str, str] = {
(consonant or "") + vowel: kana for [kana, consonant, vowel] in _mora_list_minimum
}
# 例: "ヴォ" -> ("v", "o"), "ア" -> (None, "a")
mora_kata_to_mora_phonemes: dict[str, tuple[str | None, str]] = {
kana: (consonant, vowel)
for [kana, consonant, vowel] in _mora_list_minimum + _mora_list_additional
}