From 4b748f5a7109c38c6487bd2dac2c124225d98c70 Mon Sep 17 00:00:00 2001 From: Ryo Yamashita Date: Wed, 6 Dec 2023 02:06:45 +0900 Subject: [PATCH] `voicevox_core.asyncio` --- .../test/test_pseudo_raii_for_synthesizer.py | 2 +- .../python/test/test_user_dict_load.py | 8 +- .../python/test/test_user_dict_manipulate.py | 6 +- .../python/voicevox_core/__init__.py | 11 +- .../python/voicevox_core/_rust/__init__.pyi | 415 +----------------- .../python/voicevox_core/_rust/asyncio.pyi | 413 +++++++++++++++++ .../python/voicevox_core/asyncio.py | 3 + crates/voicevox_core_python_api/src/lib.rs | 31 +- example/python/run.py | 10 +- 9 files changed, 450 insertions(+), 449 deletions(-) create mode 100644 crates/voicevox_core_python_api/python/voicevox_core/_rust/asyncio.pyi create mode 100644 crates/voicevox_core_python_api/python/voicevox_core/asyncio.py diff --git a/crates/voicevox_core_python_api/python/test/test_pseudo_raii_for_synthesizer.py b/crates/voicevox_core_python_api/python/test/test_pseudo_raii_for_synthesizer.py index a40c9c160..69ff89d2a 100644 --- a/crates/voicevox_core_python_api/python/test/test_pseudo_raii_for_synthesizer.py +++ b/crates/voicevox_core_python_api/python/test/test_pseudo_raii_for_synthesizer.py @@ -5,7 +5,7 @@ import conftest import pytest import pytest_asyncio -from voicevox_core import OpenJtalk, Synthesizer +from voicevox_core.asyncio import OpenJtalk, Synthesizer def test_enter_returns_workable_self(synthesizer: Synthesizer) -> None: diff --git a/crates/voicevox_core_python_api/python/test/test_user_dict_load.py b/crates/voicevox_core_python_api/python/test/test_user_dict_load.py index 572046496..e91773666 100644 --- a/crates/voicevox_core_python_api/python/test/test_user_dict_load.py +++ b/crates/voicevox_core_python_api/python/test/test_user_dict_load.py @@ -10,9 +10,9 @@ @pytest.mark.asyncio async def test_user_dict_load() -> None: - open_jtalk = await voicevox_core.OpenJtalk.new(conftest.open_jtalk_dic_dir) - model = await voicevox_core.VoiceModel.from_path(conftest.model_dir) - synthesizer = voicevox_core.Synthesizer(open_jtalk) + open_jtalk = await voicevox_core.asyncio.OpenJtalk.new(conftest.open_jtalk_dic_dir) + model = await voicevox_core.asyncio.VoiceModel.from_path(conftest.model_dir) + synthesizer = voicevox_core.asyncio.Synthesizer(open_jtalk) await synthesizer.load_voice_model(model) @@ -20,7 +20,7 @@ async def test_user_dict_load() -> None: "this_word_should_not_exist_in_default_dictionary", style_id=0 ) - temp_dict = voicevox_core.UserDict() + temp_dict = voicevox_core.asyncio.UserDict() uuid = temp_dict.add_word( voicevox_core.UserDictWord( surface="this_word_should_not_exist_in_default_dictionary", diff --git a/crates/voicevox_core_python_api/python/test/test_user_dict_manipulate.py b/crates/voicevox_core_python_api/python/test/test_user_dict_manipulate.py index 1ba37465f..652ddfc7a 100644 --- a/crates/voicevox_core_python_api/python/test/test_user_dict_manipulate.py +++ b/crates/voicevox_core_python_api/python/test/test_user_dict_manipulate.py @@ -12,7 +12,7 @@ @pytest.mark.asyncio async def test_user_dict_load() -> None: - dict_a = voicevox_core.UserDict() + dict_a = voicevox_core.asyncio.UserDict() # 単語の追加 uuid_a = dict_a.add_word( @@ -38,7 +38,7 @@ async def test_user_dict_load() -> None: assert dict_a.words[uuid_a].pronunciation == "フガ" # ユーザー辞書のインポート - dict_b = voicevox_core.UserDict() + dict_b = voicevox_core.asyncio.UserDict() uuid_b = dict_b.add_word( voicevox_core.UserDictWord( surface="foo", @@ -50,7 +50,7 @@ async def test_user_dict_load() -> None: assert uuid_b in dict_a.words # ユーザー辞書のエクスポート - dict_c = voicevox_core.UserDict() + dict_c = voicevox_core.asyncio.UserDict() uuid_c = dict_c.add_word( voicevox_core.UserDictWord( surface="bar", diff --git a/crates/voicevox_core_python_api/python/voicevox_core/__init__.py b/crates/voicevox_core_python_api/python/voicevox_core/__init__.py index 053a7bf1c..4ccbad3fe 100644 --- a/crates/voicevox_core_python_api/python/voicevox_core/__init__.py +++ b/crates/voicevox_core_python_api/python/voicevox_core/__init__.py @@ -25,29 +25,26 @@ ModelAlreadyLoadedError, ModelNotFoundError, NotLoadedOpenjtalkDictError, - OpenJtalk, OpenZipFileError, ParseKanaError, ReadZipEntryError, SaveUserDictError, StyleAlreadyLoadedError, StyleNotFoundError, - Synthesizer, - UserDict, UseUserDictError, - VoiceModel, WordNotFoundError, __version__, supported_devices, ) -from . import blocking # noqa: F401 isort: skip +from . import asyncio, blocking # noqa: F401 isort: skip __all__ = [ "__version__", "AccelerationMode", "AccentPhrase", "AudioQuery", + "asyncio", "blocking", "ExtractFullContextLabelError", "GetSupportedDevicesError", @@ -60,7 +57,6 @@ "ModelNotFoundError", "Mora", "NotLoadedOpenjtalkDictError", - "OpenJtalk", "OpenZipFileError", "ParseKanaError", "ReadZipEntryError", @@ -71,11 +67,8 @@ "StyleNotFoundError", "StyleVersion", "SupportedDevices", - "Synthesizer", - "VoiceModel", "supported_devices", "UseUserDictError", - "UserDict", "UserDictWord", "UserDictWordType", "VoiceModelId", diff --git a/crates/voicevox_core_python_api/python/voicevox_core/_rust/__init__.pyi b/crates/voicevox_core_python_api/python/voicevox_core/_rust/__init__.pyi index b09f8425f..3a47ef02b 100644 --- a/crates/voicevox_core_python_api/python/voicevox_core/_rust/__init__.pyi +++ b/crates/voicevox_core_python_api/python/voicevox_core/_rust/__init__.pyi @@ -1,19 +1,7 @@ -from pathlib import Path -from typing import TYPE_CHECKING, Dict, List, Literal, Union -from uuid import UUID +from typing import TYPE_CHECKING if TYPE_CHECKING: - from voicevox_core import ( - AccelerationMode, - AccentPhrase, - AudioQuery, - SpeakerMeta, - StyleId, - SupportedDevices, - UserDict, - UserDictWord, - VoiceModelId, - ) + from voicevox_core import SupportedDevices __version__: str @@ -29,405 +17,6 @@ def supported_devices() -> SupportedDevices: """ ... -class VoiceModel: - """ - 音声モデル。""" - - @staticmethod - async def from_path(path: Union[Path, str]) -> VoiceModel: - """ - VVMファイルから ``VoiceModel`` を生成する。 - - Parameters - ---------- - path - VVMファイルへのパス。 - """ - ... - @property - def id(self) -> VoiceModelId: - """ID。""" - ... - @property - def metas(self) -> List[SpeakerMeta]: - """メタ情報。""" - ... - -class OpenJtalk: - """ - テキスト解析器としてのOpen JTalk。 - """ - - @staticmethod - async def new(open_jtalk_dict_dir: Union[Path, str]) -> "OpenJtalk": - """ - ``OpenJTalk`` を生成する。 - - Parameters - ---------- - open_jtalk_dict_dir - Open JTalkの辞書ディレクトリ。 - """ - ... - async def use_user_dict(self, user_dict: UserDict) -> None: - """ - ユーザー辞書を設定する。 - - この関数を呼び出した後にユーザー辞書を変更した場合は、再度この関数を呼ぶ必要がある。 - - Parameters - ---------- - user_dict - ユーザー辞書。 - """ - ... - -class Synthesizer: - """ - 音声シンセサイザ。 - - Parameters - ---------- - open_jtalk - Open JTalk。 - acceleration_mode - ハードウェアアクセラレーションモード。 - cpu_num_threads - CPU利用数を指定。0を指定すると環境に合わせたCPUが利用される。 - """ - - def __init__( - self, - open_jtalk: OpenJtalk, - acceleration_mode: Union[ - AccelerationMode, Literal["AUTO", "CPU", "GPU"] - ] = AccelerationMode.AUTO, - cpu_num_threads: int = 0, - ) -> None: ... - def __repr__(self) -> str: ... - def __enter__(self) -> "Synthesizer": ... - def __exit__(self, exc_type, exc_value, traceback) -> None: ... - @property - def is_gpu_mode(self) -> bool: - """ハードウェアアクセラレーションがGPUモードかどうか。""" - ... - @property - def metas(self) -> List[SpeakerMeta]: - """メタ情報。""" - ... - async def load_voice_model(self, model: VoiceModel) -> None: - """ - モデルを読み込む。 - - Parameters - ---------- - style_id - 読み込むモデルのスタイルID。 - """ - ... - def unload_voice_model(self, voice_model_id: Union[VoiceModelId, str]) -> None: - """ - 音声モデルの読み込みを解除する。 - - Parameters - ---------- - voice_model_id - 音声モデルID。 - """ - ... - def is_loaded_voice_model(self, voice_model_id: Union[VoiceModelId, str]) -> bool: - """ - 指定したvoice_model_idのモデルが読み込まれているか判定する。 - - Parameters - ---------- - voice_model_id - 音声モデルID。 - - Returns - ------- - モデルが読み込まれているかどうか。 - """ - ... - async def audio_query_from_kana( - self, - kana: str, - style_id: Union[StyleId, int], - ) -> AudioQuery: - """ - AquesTalk風記法から :class:`AudioQuery` を生成する。 - - Parameters - ---------- - kana - AquesTalk風記法。 - style_id - スタイルID。 - - Returns - ------- - 話者とテキストから生成された :class:`AudioQuery` 。 - """ - ... - async def audio_query( - self, - text: str, - style_id: Union[StyleId, int], - ) -> AudioQuery: - """ - 日本語のテキストから :class:`AudioQuery` を生成する。 - - Parameters - ---------- - text - UTF-8の日本語テキスト。 - style_id - スタイルID。 - - Returns - ------- - 話者とテキストから生成された :class:`AudioQuery` 。 - """ - ... - async def create_accent_phrases_from_kana( - self, - kana: str, - style_id: Union[StyleId, int], - ) -> List[AccentPhrase]: - """ - AquesTalk風記法からAccentPhrase(アクセント句)の配列を生成する。 - - Parameters - ---------- - kana - AquesTalk風記法。 - style_id - スタイルID。 - - Returns - ------- - :class:`AccentPhrase` の配列。 - """ - ... - async def create_accent_phrases( - self, - text: str, - style_id: Union[StyleId, int], - ) -> List[AccentPhrase]: - """ - 日本語のテキストからAccentPhrase(アクセント句)の配列を生成する。 - - Parameters - ---------- - text - UTF-8の日本語テキスト。 - style_id - スタイルID。 - - Returns - ------- - :class:`AccentPhrase` の配列。 - """ - ... - async def replace_mora_data( - self, - accent_phrases: List[AccentPhrase], - style_id: Union[StyleId, int], - ) -> List[AccentPhrase]: - """ - アクセント句の音高・音素長を変更した新しいアクセント句の配列を生成する。 - - 元のアクセント句の音高・音素長は変更されない。 - - Parameters - ---------- - accent_phrases: - 変更元のアクセント句。 - style_id: - スタイルID。 - - Returns - ------- - 新しいアクセント句の配列。 - """ - ... - async def replace_phoneme_length( - self, - accent_phrases: List[AccentPhrase], - style_id: Union[StyleId, int], - ) -> List[AccentPhrase]: - """ - アクセント句の音素長を変更した新しいアクセント句の配列を生成する。 - - 元のアクセント句の音素長は変更されない。 - - Parameters - ---------- - accent_phrases - 変更元のアクセント句。 - style_id - スタイルID。 - """ - ... - async def replace_mora_pitch( - self, - accent_phrases: List[AccentPhrase], - style_id: Union[StyleId, int], - ) -> List[AccentPhrase]: - """ - アクセント句の音高を変更した新しいアクセント句の配列を生成する。 - - 元のアクセント句の音高は変更されない。 - - Parameters - ---------- - accent_phrases - 変更元のアクセント句。 - style_id - スタイルID。 - """ - ... - async def synthesis( - self, - audio_query: AudioQuery, - style_id: Union[StyleId, int], - enable_interrogative_upspeak: bool = True, - ) -> bytes: - """ - :class:`AudioQuery` から音声合成する。 - - Parameters - ---------- - audio_query - :class:`AudioQuery` 。 - style_id - スタイルID。 - enable_interrogative_upspeak - 疑問文の調整を有効にするかどうか。 - - Returns - ------- - WAVデータ。 - """ - ... - async def tts_from_kana( - self, - kana: str, - style_id: Union[StyleId, int], - enable_interrogative_upspeak: bool = True, - ) -> bytes: - """ - AquesTalk風記法から音声合成を行う。 - - Parameters - ---------- - kana - AquesTalk風記法。 - style_id - スタイルID。 - enable_interrogative_upspeak - 疑問文の調整を有効にするかどうか。 - """ - ... - async def tts( - self, - text: str, - style_id: Union[StyleId, int], - enable_interrogative_upspeak: bool = True, - ) -> bytes: - """ - 日本語のテキストから音声合成を行う。 - - Parameters - ---------- - text - UTF-8の日本語テキスト。 - style_id - スタイルID。 - enable_interrogative_upspeak - 疑問文の調整を有効にするかどうか。 - - Returns - ------- - WAVデータ。 - """ - ... - def close(self) -> None: ... - -class UserDict: - """ユーザー辞書。""" - - @property - def words(self) -> Dict[UUID, UserDictWord]: - """このオプジェクトの :class:`dict` としての表現。""" - ... - def __init__(self) -> None: ... - async def load(self, path: str) -> None: - """ファイルに保存されたユーザー辞書を読み込む。 - - Parameters - ---------- - path - ユーザー辞書のパス。 - """ - ... - async def save(self, path: str) -> None: - """ - ユーザー辞書をファイルに保存する。 - - Parameters - ---------- - path - ユーザー辞書のパス。 - """ - ... - def add_word(self, word: UserDictWord) -> UUID: - """ - 単語を追加する。 - - Parameters - ---------- - word - 追加する単語。 - - Returns - ------- - 単語のUUID。 - """ - ... - def update_word(self, word_uuid: UUID, word: UserDictWord) -> None: - """ - 単語を更新する。 - - Parameters - ---------- - word_uuid - 更新する単語のUUID。 - word - 新しい単語のデータ。 - """ - ... - def remove_word(self, word_uuid: UUID) -> None: - """ - 単語を削除する。 - - Parameters - ---------- - word_uuid - 削除する単語のUUID。 - """ - ... - def import_dict(self, other: UserDict) -> None: - """ - ユーザー辞書をインポートする。 - - Parameters - ---------- - other - インポートするユーザー辞書。 - """ - ... - class NotLoadedOpenjtalkDictError(Exception): """open_jtalk辞書ファイルが読み込まれていない。""" diff --git a/crates/voicevox_core_python_api/python/voicevox_core/_rust/asyncio.pyi b/crates/voicevox_core_python_api/python/voicevox_core/_rust/asyncio.pyi new file mode 100644 index 000000000..7a6596008 --- /dev/null +++ b/crates/voicevox_core_python_api/python/voicevox_core/_rust/asyncio.pyi @@ -0,0 +1,413 @@ +from pathlib import Path +from typing import TYPE_CHECKING, Dict, List, Literal, Union +from uuid import UUID + +if TYPE_CHECKING: + from voicevox_core import ( + AccelerationMode, + AccentPhrase, + AudioQuery, + SpeakerMeta, + StyleId, + UserDictWord, + VoiceModelId, + ) + +class VoiceModel: + """ + 音声モデル。""" + + @staticmethod + async def from_path(path: Union[Path, str]) -> VoiceModel: + """ + VVMファイルから ``VoiceModel`` を生成する。 + + Parameters + ---------- + path + VVMファイルへのパス。 + """ + ... + @property + def id(self) -> VoiceModelId: + """ID。""" + ... + @property + def metas(self) -> List[SpeakerMeta]: + """メタ情報。""" + ... + +class OpenJtalk: + """ + テキスト解析器としてのOpen JTalk。 + """ + + @staticmethod + async def new(open_jtalk_dict_dir: Union[Path, str]) -> "OpenJtalk": + """ + ``OpenJTalk`` を生成する。 + + Parameters + ---------- + open_jtalk_dict_dir + Open JTalkの辞書ディレクトリ。 + """ + ... + async def use_user_dict(self, user_dict: UserDict) -> None: + """ + ユーザー辞書を設定する。 + + この関数を呼び出した後にユーザー辞書を変更した場合は、再度この関数を呼ぶ必要がある。 + + Parameters + ---------- + user_dict + ユーザー辞書。 + """ + ... + +class Synthesizer: + """ + 音声シンセサイザ。 + + Parameters + ---------- + open_jtalk + Open JTalk。 + acceleration_mode + ハードウェアアクセラレーションモード。 + cpu_num_threads + CPU利用数を指定。0を指定すると環境に合わせたCPUが利用される。 + """ + + def __init__( + self, + open_jtalk: OpenJtalk, + acceleration_mode: Union[ + AccelerationMode, Literal["AUTO", "CPU", "GPU"] + ] = AccelerationMode.AUTO, + cpu_num_threads: int = 0, + ) -> None: ... + def __repr__(self) -> str: ... + def __enter__(self) -> "Synthesizer": ... + def __exit__(self, exc_type, exc_value, traceback) -> None: ... + @property + def is_gpu_mode(self) -> bool: + """ハードウェアアクセラレーションがGPUモードかどうか。""" + ... + @property + def metas(self) -> List[SpeakerMeta]: + """メタ情報。""" + ... + async def load_voice_model(self, model: VoiceModel) -> None: + """ + モデルを読み込む。 + + Parameters + ---------- + style_id + 読み込むモデルのスタイルID。 + """ + ... + def unload_voice_model(self, voice_model_id: Union[VoiceModelId, str]) -> None: + """ + 音声モデルの読み込みを解除する。 + + Parameters + ---------- + voice_model_id + 音声モデルID。 + """ + ... + def is_loaded_voice_model(self, voice_model_id: Union[VoiceModelId, str]) -> bool: + """ + 指定したvoice_model_idのモデルが読み込まれているか判定する。 + + Parameters + ---------- + voice_model_id + 音声モデルID。 + + Returns + ------- + モデルが読み込まれているかどうか。 + """ + ... + async def audio_query_from_kana( + self, + kana: str, + style_id: Union[StyleId, int], + ) -> AudioQuery: + """ + AquesTalk風記法から :class:`AudioQuery` を生成する。 + + Parameters + ---------- + kana + AquesTalk風記法。 + style_id + スタイルID。 + + Returns + ------- + 話者とテキストから生成された :class:`AudioQuery` 。 + """ + ... + async def audio_query( + self, + text: str, + style_id: Union[StyleId, int], + ) -> AudioQuery: + """ + 日本語のテキストから :class:`AudioQuery` を生成する。 + + Parameters + ---------- + text + UTF-8の日本語テキスト。 + style_id + スタイルID。 + + Returns + ------- + 話者とテキストから生成された :class:`AudioQuery` 。 + """ + ... + async def create_accent_phrases_from_kana( + self, + kana: str, + style_id: Union[StyleId, int], + ) -> List[AccentPhrase]: + """ + AquesTalk風記法からAccentPhrase(アクセント句)の配列を生成する。 + + Parameters + ---------- + kana + AquesTalk風記法。 + style_id + スタイルID。 + + Returns + ------- + :class:`AccentPhrase` の配列。 + """ + ... + async def create_accent_phrases( + self, + text: str, + style_id: Union[StyleId, int], + ) -> List[AccentPhrase]: + """ + 日本語のテキストからAccentPhrase(アクセント句)の配列を生成する。 + + Parameters + ---------- + text + UTF-8の日本語テキスト。 + style_id + スタイルID。 + + Returns + ------- + :class:`AccentPhrase` の配列。 + """ + ... + async def replace_mora_data( + self, + accent_phrases: List[AccentPhrase], + style_id: Union[StyleId, int], + ) -> List[AccentPhrase]: + """ + アクセント句の音高・音素長を変更した新しいアクセント句の配列を生成する。 + + 元のアクセント句の音高・音素長は変更されない。 + + Parameters + ---------- + accent_phrases: + 変更元のアクセント句。 + style_id: + スタイルID。 + + Returns + ------- + 新しいアクセント句の配列。 + """ + ... + async def replace_phoneme_length( + self, + accent_phrases: List[AccentPhrase], + style_id: Union[StyleId, int], + ) -> List[AccentPhrase]: + """ + アクセント句の音素長を変更した新しいアクセント句の配列を生成する。 + + 元のアクセント句の音素長は変更されない。 + + Parameters + ---------- + accent_phrases + 変更元のアクセント句。 + style_id + スタイルID。 + """ + ... + async def replace_mora_pitch( + self, + accent_phrases: List[AccentPhrase], + style_id: Union[StyleId, int], + ) -> List[AccentPhrase]: + """ + アクセント句の音高を変更した新しいアクセント句の配列を生成する。 + + 元のアクセント句の音高は変更されない。 + + Parameters + ---------- + accent_phrases + 変更元のアクセント句。 + style_id + スタイルID。 + """ + ... + async def synthesis( + self, + audio_query: AudioQuery, + style_id: Union[StyleId, int], + enable_interrogative_upspeak: bool = True, + ) -> bytes: + """ + :class:`AudioQuery` から音声合成する。 + + Parameters + ---------- + audio_query + :class:`AudioQuery` 。 + style_id + スタイルID。 + enable_interrogative_upspeak + 疑問文の調整を有効にするかどうか。 + + Returns + ------- + WAVデータ。 + """ + ... + async def tts_from_kana( + self, + kana: str, + style_id: Union[StyleId, int], + enable_interrogative_upspeak: bool = True, + ) -> bytes: + """ + AquesTalk風記法から音声合成を行う。 + + Parameters + ---------- + kana + AquesTalk風記法。 + style_id + スタイルID。 + enable_interrogative_upspeak + 疑問文の調整を有効にするかどうか。 + """ + ... + async def tts( + self, + text: str, + style_id: Union[StyleId, int], + enable_interrogative_upspeak: bool = True, + ) -> bytes: + """ + 日本語のテキストから音声合成を行う。 + + Parameters + ---------- + text + UTF-8の日本語テキスト。 + style_id + スタイルID。 + enable_interrogative_upspeak + 疑問文の調整を有効にするかどうか。 + + Returns + ------- + WAVデータ。 + """ + ... + def close(self) -> None: ... + +class UserDict: + """ユーザー辞書。""" + + @property + def words(self) -> Dict[UUID, UserDictWord]: + """このオプジェクトの :class:`dict` としての表現。""" + ... + def __init__(self) -> None: ... + async def load(self, path: str) -> None: + """ファイルに保存されたユーザー辞書を読み込む。 + + Parameters + ---------- + path + ユーザー辞書のパス。 + """ + ... + async def save(self, path: str) -> None: + """ + ユーザー辞書をファイルに保存する。 + + Parameters + ---------- + path + ユーザー辞書のパス。 + """ + ... + def add_word(self, word: UserDictWord) -> UUID: + """ + 単語を追加する。 + + Parameters + ---------- + word + 追加する単語。 + + Returns + ------- + 単語のUUID。 + """ + ... + def update_word(self, word_uuid: UUID, word: UserDictWord) -> None: + """ + 単語を更新する。 + + Parameters + ---------- + word_uuid + 更新する単語のUUID。 + word + 新しい単語のデータ。 + """ + ... + def remove_word(self, word_uuid: UUID) -> None: + """ + 単語を削除する。 + + Parameters + ---------- + word_uuid + 削除する単語のUUID。 + """ + ... + def import_dict(self, other: UserDict) -> None: + """ + ユーザー辞書をインポートする。 + + Parameters + ---------- + other + インポートするユーザー辞書。 + """ + ... diff --git a/crates/voicevox_core_python_api/python/voicevox_core/asyncio.py b/crates/voicevox_core_python_api/python/voicevox_core/asyncio.py new file mode 100644 index 000000000..fec0c831b --- /dev/null +++ b/crates/voicevox_core_python_api/python/voicevox_core/asyncio.py @@ -0,0 +1,3 @@ +from ._rust.asyncio import OpenJtalk, Synthesizer, UserDict, VoiceModel + +__all__ = ["OpenJtalk", "Synthesizer", "UserDict", "VoiceModel"] diff --git a/crates/voicevox_core_python_api/src/lib.rs b/crates/voicevox_core_python_api/src/lib.rs index b1441881f..54a78a2d8 100644 --- a/crates/voicevox_core_python_api/src/lib.rs +++ b/crates/voicevox_core_python_api/src/lib.rs @@ -2,11 +2,12 @@ use std::{marker::PhantomData, sync::Arc}; mod convert; use convert::*; +use easy_ext::ext; use log::debug; use pyo3::{ create_exception, exceptions::{PyException, PyKeyError, PyValueError}, - py_run, pyclass, pyfunction, pymethods, pymodule, + pyclass, pyfunction, pymethods, pymodule, types::{IntoPyDict as _, PyBytes, PyDict, PyList, PyModule}, wrap_pyfunction, PyAny, PyObject, PyRef, PyResult, PyTypeInfo, Python, ToPyObject, }; @@ -26,23 +27,29 @@ fn rust(py: Python<'_>, module: &PyModule) -> PyResult<()> { module.add_wrapped(wrap_pyfunction!(_validate_pronunciation))?; module.add_wrapped(wrap_pyfunction!(_to_zenkaku))?; - module.add_class::()?; - module.add_class::()?; - module.add_class::()?; - module.add_class::()?; - add_exceptions(module)?; let blocking_module = PyModule::new(py, "voicevox_core._rust.blocking")?; blocking_module.add_class::()?; blocking_module.add_class::()?; + module.add_and_register_submodule(blocking_module)?; + + let asyncio_module = PyModule::new(py, "voicevox_core._rust.asyncio")?; + asyncio_module.add_class::()?; + asyncio_module.add_class::()?; + asyncio_module.add_class::()?; + asyncio_module.add_class::()?; + module.add_and_register_submodule(asyncio_module) +} + +#[ext] +impl PyModule { // https://github.com/PyO3/pyo3/issues/1517#issuecomment-808664021 - py_run!( - py, - blocking_module, - "import sys; sys.modules['voicevox_core._rust.blocking'] = blocking_module" - ); - module.add_submodule(blocking_module) + fn add_and_register_submodule(&self, module: &PyModule) -> PyResult<()> { + let sys = self.py().import("sys")?; + sys.getattr("modules")?.set_item(module.name()?, module)?; + self.add_submodule(module) + } } macro_rules! exceptions { diff --git a/example/python/run.py b/example/python/run.py index fb39715e9..7a40f216b 100644 --- a/example/python/run.py +++ b/example/python/run.py @@ -6,14 +6,10 @@ from pathlib import Path from typing import Tuple +from voicevox_core.asyncio import OpenJtalk, Synthesizer, VoiceModel + import voicevox_core -from voicevox_core import ( - AccelerationMode, - AudioQuery, - OpenJtalk, - Synthesizer, - VoiceModel, -) +from voicevox_core import AccelerationMode, AudioQuery # asyncやawaitは必須です。