# Convert Japanese text to phonemes which is # compatible with Julius https://github.com/julius-speech/segmentation-kit import re import unicodedata from transformers import AutoTokenizer from text import punctuation, symbols 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", "アー/ 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:", "ヴー/ b 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.split(" ")[1:] continue x = _RULEMAP1.get(text[0]) if x is not None: text = text[1:] res += x.split(" ")[1:] continue res.append(text[0]) text = text[1:] # res = _COLON_RX.sub(":", res) return res _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)) def text2sep_kata(text: str) -> (list, list): parsed = _TAGGER.parse(text) res = [] sep = [] 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) sep.append(word) return sep, [hira2kata(i) for i in 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 def is_japanese_character(char): # 定义日语文字系统的 Unicode 范围 japanese_ranges = [ (0x3040, 0x309F), # 平假名 (0x30A0, 0x30FF), # 片假名 (0x4E00, 0x9FFF), # 汉字 (CJK Unified Ideographs) (0x3400, 0x4DBF), # 汉字扩展 A (0x20000, 0x2A6DF), # 汉字扩展 B # 可以根据需要添加其他汉字扩展范围 ] # 将字符的 Unicode 编码转换为整数 char_code = ord(char) # 检查字符是否在任何一个日语范围内 for start, end in japanese_ranges: if start <= char_code <= end: return True return False rep_map = { ":": ",", ";": ",", ",": ",", "。": ".", "!": "!", "?": "?", "\n": ".", "·": ",", "、": ",", "…": "...", } def replace_punctuation(text): pattern = re.compile("|".join(re.escape(p) for p in rep_map.keys())) replaced_text = pattern.sub(lambda x: rep_map[x.group()], text) replaced_text = re.sub( r"[^\u3040-\u309F\u30A0-\u30FF\u4E00-\u9FFF\u3400-\u4DBF" + "".join(punctuation) + r"]+", "", replaced_text, ) return replaced_text def text_normalize(text): res = unicodedata.normalize("NFKC", text) res = japanese_convert_numbers_to_words(res) # res = "".join([i for i in res if is_japanese_character(i)]) res = replace_punctuation(res) return res def distribute_phone(n_phone, n_word): phones_per_word = [0] * n_word for task in range(n_phone): min_tasks = min(phones_per_word) min_index = phones_per_word.index(min_tasks) phones_per_word[min_index] += 1 return phones_per_word tokenizer = AutoTokenizer.from_pretrained("./bert/bert-base-japanese-v3") def g2p(norm_text): sep_text, sep_kata = text2sep_kata(norm_text) sep_tokenized = [tokenizer.tokenize(i) for i in sep_text] sep_phonemes = [kata2phoneme(i) for i in sep_kata] # 异常处理,MeCab不认识的词的话会一路传到这里来,然后炸掉。目前来看只有那些超级稀有的生僻词会出现这种情况 for i in sep_phonemes: for j in i: assert j in symbols, (sep_text, sep_kata, sep_phonemes) word2ph = [] for token, phoneme in zip(sep_tokenized, sep_phonemes): phone_len = len(phoneme) word_len = len(token) aaa = distribute_phone(phone_len, word_len) word2ph += aaa phones = ["_"] + [j for i in sep_phonemes for j in i] + ["_"] tones = [0 for i in phones] word2ph = [1] + word2ph + [1] return phones, tones, word2ph if __name__ == "__main__": tokenizer = AutoTokenizer.from_pretrained("./bert/bert-base-japanese-v3") text = "だったら私、スズカさんと同じチームに入りたいです! スズカさんの走りを毎日近くで、なんなら真横から見ていたいので!" #print(_TAGGER.parse(text)) # nodes = [{"surface": "こんにちは", "pos": "感動詞:*:*:*", "pron": "コンニチワ", "c_type": "*", "c_form": "*", "accent_type": 0, "accent_con_type": "-1", "chain_flag": -1}] nodes = [{"surface":"こんにちは","pron": "コンニチワ","pos": "感動詞:*:*:*",}] from text.japanese_bert import get_bert_feature import pyopenjtalk from marine.predict import Predictor from marine.utils.openjtalk_util import convert_njd_feature_to_marine_feature text = text_normalize(text) NJD_NODES = pyopenjtalk.run_frontend(text) predictor = Predictor() # important_info = [{"string":i["string"],"pron":i["pron"],"acc":i["acc"]}for i in pyopenjtalk.estimate_accent(NJD_NODES)] print(text) marine_feature = convert_njd_feature_to_marine_feature(NJD_NODES) results = predictor.predict([marine_feature]) for mora,acc in zip(results["mora"][0],results["accent_status"][0]): print(f"{mora}:{acc}") # for i in pyopenjtalk.estimate_accent(NJD_NODES): # print(f"{i['string']}:{i['pron']}:{i['acc']}") # info = pyopenjtalk.extract_fullcontext(text,run_marine=True) # info_nomarine = pyopenjtalk.extract_fullcontext(text,run_marine=False) # # nodes = pyopenjtalk # # print(info) # for i,j in zip(info,info_nomarine): # print(i) # print(j) # print("\n") # predictor = Predictor() #print(pyopenjtalk.estimate_accent(text)) # output = predictor.predict([nodes],accent_represent_mode="high_low") #print(output) # phones, tones, word2ph = g2p(text) # bert = get_bert_feature(text, word2ph) # print(phones, tones, word2ph, bert.shape)