Spaces:
Build error
Build error
| 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="音高") # デフォルト値をつけるとts側のOpenAPIで生成されたコードの型がOptionalになる | |
| 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="指定した話者に対してモーフィングの可否") | |
| # FIXME: add reason property | |
| # reason: Optional[str] = Field(title="is_morphableがfalseである場合、その理由") | |
| 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 | |
| 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)), | |
| ) | |
| ) | |
| 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 | |
| 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は辞書の左・右文脈IDのこと | |
| # https://github.com/VOICEVOX/open_jtalk/blob/427cfd761b78efb6094bea3c5bb8c968f0d711ab/src/mecab-naist-jdic/_left-id.def # noqa | |
| 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の切り替えが可能かどうか") | |