Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

PythonのブロッキングAPIを実装 #706

Merged
merged 13 commits into from
Dec 9, 2023
3 changes: 3 additions & 0 deletions .github/workflows/generate_document.yml
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,9 @@ jobs:
run: |
cargo build -p voicevox_core_c_api -vv
maturin develop --manifest-path ./crates/voicevox_core_python_api/Cargo.toml --locked
# https://github.com/readthedocs/sphinx-autoapi/issues/405
Copy link
Member Author

@qryxip qryxip Dec 5, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

PRが出ているが、以下のコメントから約2ヶ月放置されている状態になっている。
readthedocs/sphinx-autoapi#406 (comment)

Copy link
Member Author

@qryxip qryxip Dec 8, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

あと、このworkaroundを実行するとコードとしては壊れて実行できなくなる(_rust.abi3.*よりも_rust/__init__.pyの方が優先されるらしい)。

Sphinxを騙すためだけにGHA上で実行。

Copy link
Member Author

@qryxip qryxip Dec 9, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

手元で生成するのに面倒が生じてきてるので、いっそのこと他言語ごと

cargo xtask doc [--only rust c python ...] [--open]

みたいな感じで生成できるようにした方がよいかもしれない。

- name: Workaround to make Sphinx recognize `_rust` as a module
run: touch ./crates/voicevox_core_python_api/python/voicevox_core/_rust/__init__.py
- name: Generate Sphinx document
run: sphinx-build docs/apis/python_api public/apis/python_api
- name: Generate Javadoc
Expand Down
4 changes: 2 additions & 2 deletions .github/workflows/python_lint.yml
Original file line number Diff line number Diff line change
Expand Up @@ -42,5 +42,5 @@ jobs:
- name: Check code style for example/python
working-directory: ./example/python
run: |
black --check .
isort --check --profile black .
black --check --diff .
isort --check --diff --profile black .
6 changes: 5 additions & 1 deletion .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -262,6 +262,7 @@ jobs:
runs-on: ${{ matrix.os }}
defaults:
run:
shell: bash
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

どうやら今まで、このjob全体がPowerShellで実行されていた模様...
https://github.com/VOICEVOX/voicevox_core/actions/runs/7143365778/job/19454675377?pr=706

image

working-directory: ./crates/voicevox_core_python_api
steps:
- uses: actions/checkout@v3
Expand Down Expand Up @@ -292,7 +293,10 @@ jobs:

poetry run pytest
- name: Exampleを実行
run: poetry run python ../../example/python/run.py ../../model/sample.vvm --dict-dir ../test_util/data/open_jtalk_dic_utf_8-1.11
run: |
for file in ../../example/python/run{,-asyncio}.py; do
poetry run python "$file" ../../model/sample.vvm --dict-dir ../test_util/data/open_jtalk_dic_utf_8-1.11
done
build-and-test-java-api:
strategy:
fail-fast: false
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,9 @@
# ユーザー辞書の単語が反映されるかをテストする。
"""
ユーザー辞書の単語が反映されるかをテストする。

``test_pseudo_raii_for_blocking_synthesizer`` と対になる。
"""

# AudioQueryのkanaを比較して変化するかどうかで判断する。

from uuid import UUID
Expand All @@ -10,17 +15,17 @@

@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)

audio_query_without_dict = await synthesizer.audio_query(
"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",
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,9 @@
# ユーザー辞書の操作をテストする。
"""
ユーザー辞書の操作をテストする。

``test_blocking_user_dict_manipulate`` と対になる。
"""

# どのコードがどの操作を行っているかはコメントを参照。

import os
Expand All @@ -12,7 +17,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(
Expand All @@ -38,7 +43,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",
Expand All @@ -50,7 +55,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",
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
"""
ユーザー辞書の単語が反映されるかをテストする。

``test_pseudo_raii_for_asyncio_synthesizer`` と対になる。
"""

# AudioQueryのkanaを比較して変化するかどうかで判断する。

from uuid import UUID

import conftest
import voicevox_core


def test_user_dict_load() -> None:
open_jtalk = voicevox_core.blocking.OpenJtalk(conftest.open_jtalk_dic_dir)
model = voicevox_core.blocking.VoiceModel.from_path(conftest.model_dir)
synthesizer = voicevox_core.blocking.Synthesizer(open_jtalk)

synthesizer.load_voice_model(model)

audio_query_without_dict = synthesizer.audio_query(
"this_word_should_not_exist_in_default_dictionary", style_id=0
)

temp_dict = voicevox_core.blocking.UserDict()
uuid = temp_dict.add_word(
voicevox_core.UserDictWord(
surface="this_word_should_not_exist_in_default_dictionary",
pronunciation="アイウエオ",
)
)
assert isinstance(uuid, UUID)

open_jtalk.use_user_dict(temp_dict)

audio_query_with_dict = synthesizer.audio_query(
"this_word_should_not_exist_in_default_dictionary", style_id=0
)
assert audio_query_without_dict != audio_query_with_dict
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
"""
ユーザー辞書の操作をテストする。

``test_asyncio_user_dict_manipulate`` と対になる。
"""

# どのコードがどの操作を行っているかはコメントを参照。

import os
import tempfile
from uuid import UUID

import pydantic
import pytest
import voicevox_core


def test_user_dict_load() -> None:
dict_a = voicevox_core.blocking.UserDict()

# 単語の追加
uuid_a = dict_a.add_word(
voicevox_core.UserDictWord(
surface="hoge",
pronunciation="ホゲ",
)
)
assert isinstance(uuid_a, UUID)
assert dict_a.words[uuid_a].surface == "hoge"
assert dict_a.words[uuid_a].pronunciation == "ホゲ"

# 単語の更新
dict_a.update_word(
uuid_a,
voicevox_core.UserDictWord(
surface="fuga",
pronunciation="フガ",
),
)

assert dict_a.words[uuid_a].surface == "fuga"
assert dict_a.words[uuid_a].pronunciation == "フガ"

# ユーザー辞書のインポート
dict_b = voicevox_core.blocking.UserDict()
uuid_b = dict_b.add_word(
voicevox_core.UserDictWord(
surface="foo",
pronunciation="フー",
)
)

dict_a.import_dict(dict_b)
assert uuid_b in dict_a.words

# ユーザー辞書のエクスポート
dict_c = voicevox_core.blocking.UserDict()
uuid_c = dict_c.add_word(
voicevox_core.UserDictWord(
surface="bar",
pronunciation="バー",
)
)
temp_path_fd, temp_path = tempfile.mkstemp()
os.close(temp_path_fd)
dict_c.save(temp_path)
dict_a.load(temp_path)
assert uuid_a in dict_a.words
assert uuid_c in dict_a.words

# 単語の削除
dict_a.remove_word(uuid_a)
assert uuid_a not in dict_a.words
assert uuid_c in dict_a.words

# 単語のバリデーション
with pytest.raises(pydantic.ValidationError):
dict_a.add_word(
voicevox_core.UserDictWord(
surface="",
pronunciation="カタカナ以外の文字",
)
)
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
"""
``Synthesizer`` について、(広義の)RAIIができることをテストする。

``test_pseudo_raii_for_blocking_synthesizer`` と対になる。
"""

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:
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
"""
``Synthesizer`` について、(広義の)RAIIができることをテストする。

``test_pseudo_raii_for_asyncio_synthesizer`` と対になる。
"""

import conftest
import pytest
from voicevox_core.blocking import OpenJtalk, Synthesizer


def test_enter_returns_workable_self(synthesizer: Synthesizer) -> None:
with synthesizer as ctx:
assert ctx is synthesizer
_ = synthesizer.metas


def test_closing_multiple_times_is_allowed(synthesizer: Synthesizer) -> None:
with synthesizer:
with synthesizer:
pass
synthesizer.close()
synthesizer.close()


def test_access_after_close_denied(synthesizer: Synthesizer) -> None:
synthesizer.close()
with pytest.raises(ValueError, match="^The `Synthesizer` is closed$"):
_ = synthesizer.metas


def test_access_after_exit_denied(synthesizer: Synthesizer) -> None:
with synthesizer:
pass
with pytest.raises(ValueError, match="^The `Synthesizer` is closed$"):
_ = synthesizer.metas


@pytest.fixture
def synthesizer(open_jtalk: OpenJtalk) -> Synthesizer:
return Synthesizer(open_jtalk)


@pytest.fixture(scope="session")
def open_jtalk() -> OpenJtalk:
return OpenJtalk(conftest.open_jtalk_dic_dir)
12 changes: 4 additions & 8 deletions crates/voicevox_core_python_api/python/voicevox_core/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,27 +25,27 @@
ModelAlreadyLoadedError,
ModelNotFoundError,
NotLoadedOpenjtalkDictError,
OpenJtalk,
OpenZipFileError,
ParseKanaError,
ReadZipEntryError,
SaveUserDictError,
StyleAlreadyLoadedError,
StyleNotFoundError,
Synthesizer,
UserDict,
UseUserDictError,
VoiceModel,
WordNotFoundError,
__version__,
supported_devices,
)

from . import asyncio, blocking # noqa: F401 isort: skip

__all__ = [
"__version__",
"AccelerationMode",
"AccentPhrase",
"AudioQuery",
"asyncio",
"blocking",
"ExtractFullContextLabelError",
"GetSupportedDevicesError",
"GpuSupportError",
Expand All @@ -57,7 +57,6 @@
"ModelNotFoundError",
"Mora",
"NotLoadedOpenjtalkDictError",
"OpenJtalk",
"OpenZipFileError",
"ParseKanaError",
"ReadZipEntryError",
Expand All @@ -68,11 +67,8 @@
"StyleNotFoundError",
"StyleVersion",
"SupportedDevices",
"Synthesizer",
"VoiceModel",
"supported_devices",
"UseUserDictError",
"UserDict",
"UserDictWord",
"UserDictWordType",
"VoiceModelId",
Expand Down
Loading
Loading