Skip to content

Commit

Permalink
implemented interrogative feature for parse_kana(accent_phrases API)
Browse files Browse the repository at this point in the history
refs #235
  • Loading branch information
qwerty2501 committed Dec 22, 2021
1 parent eac0e18 commit d13d678
Show file tree
Hide file tree
Showing 4 changed files with 211 additions and 44 deletions.
13 changes: 11 additions & 2 deletions run.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,9 @@
)
from voicevox_engine.preset import Preset, PresetLoader
from voicevox_engine.synthesis_engine import SynthesisEngineBase, make_synthesis_engine
from voicevox_engine.synthesis_engine.synthesis_engine_base import (
adjust_interrogative_accent_phrases,
)
from voicevox_engine.utility import ConnectBase64WavesException, connect_base64_waves


Expand Down Expand Up @@ -181,15 +184,21 @@ def accent_phrases(
"""
if is_kana:
try:
accent_phrases = parse_kana(text)
accent_phrases, interrogative_accent_phrase_marks = parse_kana(
text, enable_interrogative
)
except ParseKanaError as err:
raise HTTPException(
status_code=400,
detail=ParseKanaBadRequest(err).dict(),
)
return engine.replace_mora_data(
accent_phrases = engine.replace_mora_data(
accent_phrases=accent_phrases, speaker_id=speaker
)

return adjust_interrogative_accent_phrases(
accent_phrases, interrogative_accent_phrase_marks, enable_interrogative
)
else:
return engine.create_accent_phrases(
text,
Expand Down
195 changes: 163 additions & 32 deletions test/test_kana_parser.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
from typing import List
from unittest import TestCase

from voicevox_engine.kana_parser import create_kana, parse_kana
Expand All @@ -6,54 +7,183 @@

class TestParseKana(TestCase):
def test_phrase_length(self):
self.assertEqual(len(parse_kana("ア'/ア'")), 2)
self.assertEqual(len(parse_kana("ア'、ア'")), 2)
self.assertEqual(len(parse_kana("ア'/ア'/ア'/ア'/ア'")), 5)
self.assertEqual(len(parse_kana("ス'")), 1)
self.assertEqual(len(parse_kana("_ス'")), 1)
self.assertEqual(len(parse_kana("ギェ'")), 1)
self.assertEqual(len(parse_kana("ギェ'、ギェ'/ギェ'")), 3)
self.assertEqual(len(parse_kana("ア'/ア'", False)[0]), 2)
self.assertEqual(len(parse_kana("ア'、ア'", False)[0]), 2)
self.assertEqual(len(parse_kana("ア'/ア'/ア'/ア'/ア'", False)[0]), 5)
self.assertEqual(len(parse_kana("ス'", False)[0]), 1)
self.assertEqual(len(parse_kana("_ス'", False)[0]), 1)
self.assertEqual(len(parse_kana("ギェ'", False)[0]), 1)
self.assertEqual(len(parse_kana("ギェ'、ギェ'/ギェ'", False)[0]), 3)

def test_accent(self):
self.assertEqual(parse_kana("シャ'シシュシェショ")[0].accent, 1)
self.assertEqual(parse_kana("シャ'_シシュシェショ")[0].accent, 1)
self.assertEqual(parse_kana("シャシ'シュシェショ")[0].accent, 2)
self.assertEqual(parse_kana("シャ_シ'シュシェショ")[0].accent, 2)
self.assertEqual(parse_kana("シャシシュ'シェショ")[0].accent, 3)
self.assertEqual(parse_kana("シャ_シシュ'シェショ")[0].accent, 3)
self.assertEqual(parse_kana("シャシシュシェショ'")[0].accent, 5)
self.assertEqual(parse_kana("シャ_シシュシェショ'")[0].accent, 5)
self.assertEqual(parse_kana("シャ'シシュシェショ", False)[0][0].accent, 1)
self.assertEqual(parse_kana("シャ'_シシュシェショ", False)[0][0].accent, 1)
self.assertEqual(parse_kana("シャシ'シュシェショ", False)[0][0].accent, 2)
self.assertEqual(parse_kana("シャ_シ'シュシェショ", False)[0][0].accent, 2)
self.assertEqual(parse_kana("シャシシュ'シェショ", False)[0][0].accent, 3)
self.assertEqual(parse_kana("シャ_シシュ'シェショ", False)[0][0].accent, 3)
self.assertEqual(parse_kana("シャシシュシェショ'", False)[0][0].accent, 5)
self.assertEqual(parse_kana("シャ_シシュシェショ'", False)[0][0].accent, 5)

def test_mora_length(self):
self.assertEqual(len(parse_kana("シャ'シシュシェショ")[0].moras), 5)
self.assertEqual(len(parse_kana("シャ'_シシュシェショ")[0].moras), 5)
self.assertEqual(len(parse_kana("シャシ'シュシェショ")[0].moras), 5)
self.assertEqual(len(parse_kana("シャ_シ'シュシェショ")[0].moras), 5)
self.assertEqual(len(parse_kana("シャシシュシェショ'")[0].moras), 5)
self.assertEqual(len(parse_kana("シャ_シシュシェショ'")[0].moras), 5)
self.assertEqual(len(parse_kana("シャ'シシュシェショ", False)[0][0].moras), 5)
self.assertEqual(len(parse_kana("シャ'_シシュシェショ", False)[0][0].moras), 5)
self.assertEqual(len(parse_kana("シャシ'シュシェショ", False)[0][0].moras), 5)
self.assertEqual(len(parse_kana("シャ_シ'シュシェショ", False)[0][0].moras), 5)
self.assertEqual(len(parse_kana("シャシシュシェショ'", False)[0][0].moras), 5)
self.assertEqual(len(parse_kana("シャ_シシュシェショ'", False)[0][0].moras), 5)

def test_pause(self):
self.assertIsNone(parse_kana("ア'/ア'")[0].pause_mora)
self.assertIsNone(parse_kana("ア'/ア'")[1].pause_mora)
self.assertIsNotNone(parse_kana("ア'、ア'")[0].pause_mora)
self.assertIsNone(parse_kana("ア'、ア'")[1].pause_mora)
self.assertIsNone(parse_kana("ア'/ア'", False)[0][0].pause_mora)
self.assertIsNone(parse_kana("ア'/ア'", False)[0][1].pause_mora)
self.assertIsNotNone(parse_kana("ア'、ア'", False)[0][0].pause_mora)
self.assertIsNone(parse_kana("ア'、ア'", False)[0][1].pause_mora)

def test_unvoice(self):
self.assertEqual(parse_kana("ス'")[0].moras[0].vowel, "u")
self.assertEqual(parse_kana("_ス'")[0].moras[0].vowel, "U")
self.assertEqual(parse_kana("ス'", False)[0][0].moras[0].vowel, "u", False)
self.assertEqual(parse_kana("_ス'", False)[0][0].moras[0].vowel, "U", False)

def test_roundtrip(self):
for text in ["コンニチワ'", "ワタシワ'/シャチョオデ'_ス", "トテモ'、エラ'インデス"]:
self.assertEqual(create_kana(parse_kana(text)), text)
self.assertEqual(create_kana(parse_kana(text, False)[0]), text)

for text in ["ヲ'", "ェ'"]:
self.assertEqual(create_kana(parse_kana(text)), text)
self.assertEqual(create_kana(parse_kana(text, False)[0]), text)

def _interrogative_accent_phrase_marks_base(
self,
text: str,
enable_interrogative: bool,
expected_interrogative_accent_phrase_marks: List[bool],
):
accent_phrases, interrogative_accent_phrase_marks = parse_kana(
text, enable_interrogative
)
self.assertEqual(len(accent_phrases), len(interrogative_accent_phrase_marks))
self.assertEqual(
interrogative_accent_phrase_marks,
expected_interrogative_accent_phrase_marks,
)

def test_interrogative_accent_phrase_marks(self):
self._interrogative_accent_phrase_marks_base(
text="ア'/ア'",
enable_interrogative=False,
expected_interrogative_accent_phrase_marks=[False, False],
)
self._interrogative_accent_phrase_marks_base(
text="ア'/ア'",
enable_interrogative=True,
expected_interrogative_accent_phrase_marks=[False, False],
)

self._interrogative_accent_phrase_marks_base(
text="ア'、ア'",
enable_interrogative=False,
expected_interrogative_accent_phrase_marks=[False, False],
)
self._interrogative_accent_phrase_marks_base(
text="ア'、ア'",
enable_interrogative=True,
expected_interrogative_accent_phrase_marks=[False, False],
)

self._interrogative_accent_phrase_marks_base(
text="ア'/ア'/ア'/ア'/ア'",
enable_interrogative=False,
expected_interrogative_accent_phrase_marks=[
False,
False,
False,
False,
False,
],
)
self._interrogative_accent_phrase_marks_base(
text="ア'/ア'/ア'/ア'/ア'",
enable_interrogative=True,
expected_interrogative_accent_phrase_marks=[
False,
False,
False,
False,
False,
],
)

self._interrogative_accent_phrase_marks_base(
text="ス'",
enable_interrogative=False,
expected_interrogative_accent_phrase_marks=[False],
)
self._interrogative_accent_phrase_marks_base(
text="ス'",
enable_interrogative=True,
expected_interrogative_accent_phrase_marks=[False],
)

self._interrogative_accent_phrase_marks_base(
text="_ス'",
enable_interrogative=False,
expected_interrogative_accent_phrase_marks=[False],
)
self._interrogative_accent_phrase_marks_base(
text="_ス'",
enable_interrogative=True,
expected_interrogative_accent_phrase_marks=[False],
)

self._interrogative_accent_phrase_marks_base(
text="ギェ'",
enable_interrogative=False,
expected_interrogative_accent_phrase_marks=[False],
)
self._interrogative_accent_phrase_marks_base(
text="ギェ'",
enable_interrogative=True,
expected_interrogative_accent_phrase_marks=[False],
)

self._interrogative_accent_phrase_marks_base(
text="ギェ'、ギェ'/ギェ'",
enable_interrogative=False,
expected_interrogative_accent_phrase_marks=[False, False, False],
)
self._interrogative_accent_phrase_marks_base(
text="ギェ'、ギェ'/ギェ'",
enable_interrogative=True,
expected_interrogative_accent_phrase_marks=[False, False, False],
)

self._interrogative_accent_phrase_marks_base(
text="ア'?",
enable_interrogative=False,
expected_interrogative_accent_phrase_marks=[False],
)

self._interrogative_accent_phrase_marks_base(
text="ア'?",
enable_interrogative=True,
expected_interrogative_accent_phrase_marks=[True],
)

self._interrogative_accent_phrase_marks_base(
text="ギェ'、ギェ'/ギェ'?",
enable_interrogative=False,
expected_interrogative_accent_phrase_marks=[False, False, False],
)

self._interrogative_accent_phrase_marks_base(
text="ギェ'、ギェ'/ギェ'?",
enable_interrogative=True,
expected_interrogative_accent_phrase_marks=[False, False, True],
)


class TestParseKanaException(TestCase):
def _assert_error_code(self, kana: str, code: ParseKanaErrorCode):
with self.assertRaises(ParseKanaError) as err:
parse_kana(kana)
parse_kana(kana, False)
self.assertEqual(err.exception.errcode, code)

def test_exceptions(self):
Expand All @@ -64,13 +194,14 @@ def test_exceptions(self):
self._assert_error_code("__ス'", ParseKanaErrorCode.UNKNOWN_TEXT)
self._assert_error_code("ア'/", ParseKanaErrorCode.EMPTY_PHRASE)
self._assert_error_code("/ア'", ParseKanaErrorCode.EMPTY_PHRASE)
self._assert_error_code("ア?ア'", ParseKanaErrorCode.UNKNOWN_TEXT)

with self.assertRaises(ParseKanaError) as err:
parse_kana("ヒト'ツメ/フタツメ")
parse_kana("ヒト'ツメ/フタツメ", False)
self.assertEqual(err.exception.errcode, ParseKanaErrorCode.ACCENT_NOTFOUND)
self.assertEqual(err.exception.kwargs, {"text": "フタツメ"})

with self.assertRaises(ParseKanaError) as err:
parse_kana("ア'/")
parse_kana("ア'/", False)
self.assertEqual(err.exception.errcode, ParseKanaErrorCode.EMPTY_PHRASE)
self.assertEqual(err.exception.kwargs, {"position": "2"})
32 changes: 28 additions & 4 deletions voicevox_engine/kana_parser.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
from typing import List, Optional
from typing import List, Optional, Tuple

from .model import AccentPhrase, Mora, ParseKanaError, ParseKanaErrorCode
from .mora_list import openjtalk_text2mora
Expand All @@ -8,6 +8,7 @@
ACCENT_SYMBOL = "'"
NOPAUSE_DELIMITER = "/"
PAUSE_DELIMITER = "、"
WIDE_INTERROGATION_MARK = "?"

text2mora_with_unvoice = {}
for text, (consonant, vowel) in openjtalk_text2mora.items():
Expand Down Expand Up @@ -78,12 +79,18 @@ def _text_to_accent_phrase(phrase: str) -> List[AccentPhrase]:
return AccentPhrase(moras=moras, accent=accent_index, pause_mora=None)


def parse_kana(text: str) -> List[AccentPhrase]:
def parse_kana(
text: str, enable_interrogative: bool
) -> Tuple[List[AccentPhrase], List[bool]]:
"""
AquesTalkライクな読み仮名をパースして音長・音高未指定のaccent phraseに変換
"""
parsed_results: List[AccentPhrase] = []
phrase_base = 0
is_interrogative_text = text[-1] == WIDE_INTERROGATION_MARK
if is_interrogative_text:
text = text[:-1]

for i in range(len(text) + 1):
if i == len(text) or text[i] in [PAUSE_DELIMITER, NOPAUSE_DELIMITER]:
phrase = text[phrase_base:i]
Expand All @@ -102,10 +109,27 @@ def parse_kana(text: str) -> List[AccentPhrase]:
vowel="pau",
vowel_length=0,
pitch=0,
is_interrogative=False,
)
parsed_results.append(accent_phrase)
return parsed_results

interrogative_accent_phrase_marks = [False] * len(parsed_results)

if enable_interrogative and is_interrogative_text:
last_parsed_result = parsed_results[-1]
last_mora = last_parsed_result.moras[-1]
last_parsed_result.moras.append(
Mora(
text=last_mora.vowel,
consonant=None,
consonant_length=None,
vowel=last_mora.vowel,
vowel_length=last_mora.vowel_length,
pitch=0,
)
)
interrogative_accent_phrase_marks[-1] = True

return parsed_results, interrogative_accent_phrase_marks


def create_kana(accent_phrases: List[AccentPhrase]) -> str:
Expand Down
15 changes: 9 additions & 6 deletions voicevox_engine/synthesis_engine/synthesis_engine_base.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,22 +37,25 @@ def add_interrogative_mora_if_last_phoneme_is_interrogative(

def adjust_interrogative_accent_phrases(
accent_phrases: List[AccentPhrase],
fcl_accent_phrases: List[full_context_label.AccentPhrase],
interrogative_accent_phrase_marks: List[bool],
enable_interrogative: bool,
) -> List[AccentPhrase]:
"""
enable_interrogativeが有効になっていて与えられたaccent_phrasesに疑問系のものがあった場合、
SynthesisEngineの実装によって調整されたあとの各accent_phraseの末尾にある疑問系発音用のMoraに対して直前のMoraより少し音を高くすることで疑問文ぽくする
NOTE: リファクタリング時に適切な場所へ移動させること
"""
return [
AccentPhrase(
moras=adjust_interrogative_moras(accent_phrase.moras)
if enable_interrogative and fcl_accent_phrase.is_interrogative
if enable_interrogative and interrogative_accent_phrase_mark
else accent_phrase.moras,
accent=accent_phrase.accent,
pause_mora=accent_phrase.pause_mora,
)
for accent_phrase, fcl_accent_phrase in zip(accent_phrases, fcl_accent_phrases)
for accent_phrase, interrogative_accent_phrase_mark in zip(
accent_phrases, interrogative_accent_phrase_marks
)
]


Expand Down Expand Up @@ -153,8 +156,8 @@ def create_accent_phrases(
if len(utterance.breath_groups) == 0:
return []

fcl_accent_phrases = [
accent_phrase
interrogative_accent_phrase_marks = [
accent_phrase.is_interrogative
for breath_group in utterance.breath_groups
for accent_phrase in breath_group.accent_phrases
]
Expand Down Expand Up @@ -193,7 +196,7 @@ def create_accent_phrases(
speaker_id=speaker_id,
)
return adjust_interrogative_accent_phrases(
accent_phrases, fcl_accent_phrases, enable_interrogative
accent_phrases, interrogative_accent_phrase_marks, enable_interrogative
)

@abstractmethod
Expand Down

0 comments on commit d13d678

Please sign in to comment.