|
from enum import Enum |
|
from re import findall, fullmatch |
|
from typing import Dict, List, Optional |
|
|
|
from pydantic import BaseModel, Field, conint, validator |
|
|
|
from .metas.Metas import Speaker, SpeakerInfo |
|
|
|
|
|
class Mora(BaseModel): |
|
""" |
|
モーラ(子音+母音)ごとの情報 |
|
""" |
|
|
|
text: str = Field(title="文字") |
|
consonant: Optional[str] = Field(title="子音の音素") |
|
consonant_length: Optional[float] = Field(title="子音の音長") |
|
vowel: str = Field(title="母音の音素") |
|
vowel_length: float = Field(title="母音の音長") |
|
pitch: float = Field(title="音高") |
|
|
|
def __hash__(self): |
|
items = [ |
|
(k, tuple(v)) if isinstance(v, List) else (k, v) |
|
for k, v in self.__dict__.items() |
|
] |
|
return hash(tuple(sorted(items))) |
|
|
|
|
|
class AccentPhrase(BaseModel): |
|
""" |
|
アクセント句ごとの情報 |
|
""" |
|
|
|
moras: List[Mora] = Field(title="モーラのリスト") |
|
accent: int = Field(title="アクセント箇所") |
|
pause_mora: Optional[Mora] = Field(title="後ろに無音を付けるかどうか") |
|
is_interrogative: bool = Field(default=False, title="疑問系かどうか") |
|
|
|
def __hash__(self): |
|
items = [ |
|
(k, tuple(v)) if isinstance(v, List) else (k, v) |
|
for k, v in self.__dict__.items() |
|
] |
|
return hash(tuple(sorted(items))) |
|
|
|
|
|
class AudioQuery(BaseModel): |
|
""" |
|
音声合成用のクエリ |
|
""" |
|
|
|
accent_phrases: List[AccentPhrase] = Field(title="アクセント句のリスト") |
|
speedScale: float = Field(title="全体の話速") |
|
pitchScale: float = Field(title="全体の音高") |
|
intonationScale: float = Field(title="全体の抑揚") |
|
volumeScale: float = Field(title="全体の音量") |
|
prePhonemeLength: float = Field(title="音声の前の無音時間") |
|
postPhonemeLength: float = Field(title="音声の後の無音時間") |
|
outputSamplingRate: int = Field(title="音声データの出力サンプリングレート") |
|
outputStereo: bool = Field(title="音声データをステレオ出力するか否か") |
|
kana: Optional[str] = Field(title="[読み取り専用]AquesTalkライクな読み仮名。音声合成クエリとしては無視される") |
|
|
|
def __hash__(self): |
|
items = [ |
|
(k, tuple(v)) if isinstance(v, List) else (k, v) |
|
for k, v in self.__dict__.items() |
|
] |
|
return hash(tuple(sorted(items))) |
|
|
|
|
|
class ParseKanaErrorCode(Enum): |
|
UNKNOWN_TEXT = "判別できない読み仮名があります: {text}" |
|
ACCENT_TOP = "句頭にアクセントは置けません: {text}" |
|
ACCENT_TWICE = "1つのアクセント句に二つ以上のアクセントは置けません: {text}" |
|
ACCENT_NOTFOUND = "アクセントを指定していないアクセント句があります: {text}" |
|
EMPTY_PHRASE = "{position}番目のアクセント句が空白です" |
|
INTERROGATION_MARK_NOT_AT_END = "アクセント句末以外に「?」は置けません: {text}" |
|
INFINITE_LOOP = "処理時に無限ループになってしまいました...バグ報告をお願いします。" |
|
|
|
|
|
class ParseKanaError(Exception): |
|
def __init__(self, errcode: ParseKanaErrorCode, **kwargs): |
|
self.errcode = errcode |
|
self.errname = errcode.name |
|
self.kwargs: Dict[str, str] = kwargs |
|
err_fmt: str = errcode.value |
|
self.text = err_fmt.format(**kwargs) |
|
|
|
|
|
class ParseKanaBadRequest(BaseModel): |
|
text: str = Field(title="エラーメッセージ") |
|
error_name: str = Field( |
|
title="エラー名", |
|
description="|name|description|\n|---|---|\n" |
|
+ "\n".join( |
|
[ |
|
"| {} | {} |".format(err.name, err.value) |
|
for err in list(ParseKanaErrorCode) |
|
] |
|
), |
|
) |
|
error_args: Dict[str, str] = Field(title="エラーを起こした箇所") |
|
|
|
def __init__(self, err: ParseKanaError): |
|
super().__init__(text=err.text, error_name=err.errname, error_args=err.kwargs) |
|
|
|
|
|
class MorphableTargetInfo(BaseModel): |
|
|
|
is_morphable: bool = Field(title="指定した話者に対してモーフィングの可否") |
|
|
|
|
|
|
|
|
|
class SpeakerNotFoundError(LookupError): |
|
def __init__(self, speaker: int, *args: object, **kywrds: object) -> None: |
|
self.speaker = speaker |
|
super().__init__(f"speaker {speaker} is not found.", *args, **kywrds) |
|
|
|
|
|
class LibrarySpeaker(BaseModel): |
|
""" |
|
音声ライブラリに含まれる話者の情報 |
|
""" |
|
|
|
speaker: Speaker = Field(title="話者情報") |
|
speaker_info: SpeakerInfo = Field(title="話者の追加情報") |
|
|
|
|
|
class DownloadableLibrary(BaseModel): |
|
""" |
|
ダウンロード可能な音声ライブラリの情報 |
|
""" |
|
|
|
name: str = Field(title="音声ライブラリの名前") |
|
uuid: str = Field(title="音声ライブラリのUUID") |
|
version: str = Field(title="音声ライブラリのバージョン") |
|
download_url: str = Field(title="音声ライブラリのダウンロードURL") |
|
bytes: int = Field(title="音声ライブラリのバイト数") |
|
speakers: List[LibrarySpeaker] = Field(title="音声ライブラリに含まれる話者のリスト") |
|
|
|
|
|
USER_DICT_MIN_PRIORITY = 0 |
|
USER_DICT_MAX_PRIORITY = 10 |
|
|
|
|
|
class UserDictWord(BaseModel): |
|
""" |
|
辞書のコンパイルに使われる情報 |
|
""" |
|
|
|
surface: str = Field(title="表層形") |
|
priority: conint(ge=USER_DICT_MIN_PRIORITY, le=USER_DICT_MAX_PRIORITY) = Field( |
|
title="優先度" |
|
) |
|
context_id: int = Field(title="文脈ID", default=1348) |
|
part_of_speech: str = Field(title="品詞") |
|
part_of_speech_detail_1: str = Field(title="品詞細分類1") |
|
part_of_speech_detail_2: str = Field(title="品詞細分類2") |
|
part_of_speech_detail_3: str = Field(title="品詞細分類3") |
|
inflectional_type: str = Field(title="活用型") |
|
inflectional_form: str = Field(title="活用形") |
|
stem: str = Field(title="原形") |
|
yomi: str = Field(title="読み") |
|
pronunciation: str = Field(title="発音") |
|
accent_type: int = Field(title="アクセント型") |
|
mora_count: Optional[int] = Field(title="モーラ数") |
|
accent_associative_rule: str = Field(title="アクセント結合規則") |
|
|
|
class Config: |
|
validate_assignment = True |
|
|
|
@validator("surface") |
|
def convert_to_zenkaku(cls, surface): |
|
return surface.translate( |
|
str.maketrans( |
|
"".join(chr(0x21 + i) for i in range(94)), |
|
"".join(chr(0xFF01 + i) for i in range(94)), |
|
) |
|
) |
|
|
|
@validator("pronunciation", pre=True) |
|
def check_is_katakana(cls, pronunciation): |
|
if not fullmatch(r"[ァ-ヴー]+", pronunciation): |
|
raise ValueError("発音は有効なカタカナでなくてはいけません。") |
|
sutegana = ["ァ", "ィ", "ゥ", "ェ", "ォ", "ャ", "ュ", "ョ", "ヮ", "ッ"] |
|
for i in range(len(pronunciation)): |
|
if pronunciation[i] in sutegana: |
|
|
|
|
|
if i < len(pronunciation) - 1 and ( |
|
pronunciation[i + 1] in sutegana[:-1] |
|
or ( |
|
pronunciation[i] == sutegana[-1] |
|
and pronunciation[i + 1] == sutegana[-1] |
|
) |
|
): |
|
raise ValueError("無効な発音です。(捨て仮名の連続)") |
|
if pronunciation[i] == "ヮ": |
|
if i != 0 and pronunciation[i - 1] not in ["ク", "グ"]: |
|
raise ValueError("無効な発音です。(「くゎ」「ぐゎ」以外の「ゎ」の使用)") |
|
return pronunciation |
|
|
|
@validator("mora_count", pre=True, always=True) |
|
def check_mora_count_and_accent_type(cls, mora_count, values): |
|
if "pronunciation" not in values or "accent_type" not in values: |
|
|
|
return mora_count |
|
|
|
if mora_count is None: |
|
rule_others = "[イ][ェ]|[ヴ][ャュョ]|[トド][ゥ]|[テデ][ィャュョ]|[デ][ェ]|[クグ][ヮ]" |
|
rule_line_i = "[キシチニヒミリギジビピ][ェャュョ]" |
|
rule_line_u = "[ツフヴ][ァ]|[ウスツフヴズ][ィ]|[ウツフヴ][ェォ]" |
|
rule_one_mora = "[ァ-ヴー]" |
|
mora_count = len( |
|
findall( |
|
f"(?:{rule_others}|{rule_line_i}|{rule_line_u}|{rule_one_mora})", |
|
values["pronunciation"], |
|
) |
|
) |
|
|
|
if not 0 <= values["accent_type"] <= mora_count: |
|
raise ValueError( |
|
"誤ったアクセント型です({})。 expect: 0 <= accent_type <= {}".format( |
|
values["accent_type"], mora_count |
|
) |
|
) |
|
return mora_count |
|
|
|
|
|
class PartOfSpeechDetail(BaseModel): |
|
""" |
|
品詞ごとの情報 |
|
""" |
|
|
|
part_of_speech: str = Field(title="品詞") |
|
part_of_speech_detail_1: str = Field(title="品詞細分類1") |
|
part_of_speech_detail_2: str = Field(title="品詞細分類2") |
|
part_of_speech_detail_3: str = Field(title="品詞細分類3") |
|
|
|
|
|
context_id: int = Field(title="文脈ID") |
|
cost_candidates: List[int] = Field(title="コストのパーセンタイル") |
|
accent_associative_rules: List[str] = Field(title="アクセント結合規則の一覧") |
|
|
|
|
|
class WordTypes(str, Enum): |
|
""" |
|
fastapiでword_type引数を検証する時に使用するクラス |
|
""" |
|
|
|
PROPER_NOUN = "PROPER_NOUN" |
|
COMMON_NOUN = "COMMON_NOUN" |
|
VERB = "VERB" |
|
ADJECTIVE = "ADJECTIVE" |
|
SUFFIX = "SUFFIX" |
|
|
|
|
|
class SupportedDevicesInfo(BaseModel): |
|
""" |
|
対応しているデバイスの情報 |
|
""" |
|
|
|
cpu: bool = Field(title="CPUに対応しているか") |
|
cuda: bool = Field(title="CUDA(Nvidia GPU)に対応しているか") |
|
dml: bool = Field(title="DirectML(Nvidia GPU/Radeon GPU等)に対応しているか") |
|
|
|
|
|
class SupportedFeaturesInfo(BaseModel): |
|
""" |
|
エンジンの機能の情報 |
|
""" |
|
|
|
support_adjusting_mora: bool = Field(title="モーラが調整可能かどうか") |
|
support_adjusting_speed_scale: bool = Field(title="話速が調整可能かどうか") |
|
support_adjusting_pitch_scale: bool = Field(title="音高が調整可能かどうか") |
|
support_adjusting_intonation_scale: bool = Field(title="抑揚が調整可能かどうか") |
|
support_adjusting_volume_scale: bool = Field(title="音量が調整可能かどうか") |
|
support_adjusting_silence_scale: bool = Field(title="前後の無音時間が調節可能かどうか") |
|
support_interrogative_upspeak: bool = Field(title="疑似疑問文に対応しているかどうか") |
|
support_switching_device: bool = Field(title="CPU/GPUの切り替えが可能かどうか") |
|
|