From 1b8fe844c75ec71bdb7314149c04753630d7db36 Mon Sep 17 00:00:00 2001 From: Nanashi Date: Thu, 14 Sep 2023 23:57:20 +0900 Subject: [PATCH 01/20] =?UTF-8?q?Java=20API=EF=BC=9AVisibility=E3=82=92?= =?UTF-8?q?=E4=BF=AE=E6=AD=A3=20(#611)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/jp/hiroshiba/voicevoxcore/VoiceModel.java | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/crates/voicevox_core_java_api/lib/src/main/java/jp/hiroshiba/voicevoxcore/VoiceModel.java b/crates/voicevox_core_java_api/lib/src/main/java/jp/hiroshiba/voicevoxcore/VoiceModel.java index 6b157fdba..518f1de31 100644 --- a/crates/voicevox_core_java_api/lib/src/main/java/jp/hiroshiba/voicevoxcore/VoiceModel.java +++ b/crates/voicevox_core_java_api/lib/src/main/java/jp/hiroshiba/voicevoxcore/VoiceModel.java @@ -47,25 +47,25 @@ public static class SpeakerMeta { @SerializedName("name") @Expose @Nonnull - final String name; + public final String name; /** 話者に属するスタイル。 */ @SerializedName("styles") @Expose @Nonnull - final StyleMeta[] styles; + public final StyleMeta[] styles; /** 話者のUUID。 */ @SerializedName("speaker_uuid") @Expose @Nonnull - final String speakerUuid; + public final String speakerUuid; /** 話者のバージョン。 */ @SerializedName("version") @Expose @Nonnull - final String version; + public final String version; private SpeakerMeta() { // GSONからコンストラクトするため、このメソッドは呼ばれることは無い。 @@ -83,12 +83,12 @@ public static class StyleMeta { @SerializedName("name") @Expose @Nonnull - final String name; + public final String name; /** スタイルID。 */ @SerializedName("id") @Expose - final int id; + public final int id; private StyleMeta() { this.name = ""; From 15ea1556ea3a01a383aa93be01321dff5a490546 Mon Sep 17 00:00:00 2001 From: Nanashi Date: Fri, 15 Sep 2023 03:12:51 +0900 Subject: [PATCH 02/20] =?UTF-8?q?Add:=20=E4=BB=96=E8=A8=80=E8=AA=9E?= =?UTF-8?q?=E3=81=AElint=E3=81=AEWorkflow=E3=82=92=E8=BF=BD=E5=8A=A0=20(#5?= =?UTF-8?q?98)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: qryxip --- .github/workflows/java_lint.yml | 31 ++++++++++++++ .github/workflows/python_lint.yml | 40 +++++++++++++++++++ .../hiroshiba/voicevoxcore/Synthesizer.java | 4 -- .../jp/hiroshiba/voicevoxcore/UserDict.java | 3 +- .../python/test/test_user_dict_load.py | 3 +- .../python/test/test_user_dict_manipulate.py | 3 +- .../python/voicevox_core/__init__.py | 8 ++-- .../python/voicevox_core/_models.py | 2 +- .../python/voicevox_core/_rust.pyi | 2 +- example/python/run.py | 9 +---- 10 files changed, 84 insertions(+), 21 deletions(-) create mode 100644 .github/workflows/java_lint.yml create mode 100644 .github/workflows/python_lint.yml diff --git a/.github/workflows/java_lint.yml b/.github/workflows/java_lint.yml new file mode 100644 index 000000000..b631819b8 --- /dev/null +++ b/.github/workflows/java_lint.yml @@ -0,0 +1,31 @@ +name: "Lint Java code" + +on: + push: + branches: + - main + pull_request: + paths: + - ./crates/voicevox_core_java_api/**/*.java + - ./crates/voicevox_core_java_api/gradle/** + - ./crates/voicevox_core_java_api/gradlew + - ./crates/voicevox_core_java_api/settings.gradle + - ./.github/workflows/java_lint.yml + +defaults: + run: + shell: bash + working-directory: ./crates/voicevox_core_java_api + +jobs: + java-lint: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - uses: actions/setup-java@v2 + with: + java-version: "11" + distribution: "adopt" + - name: Check code style + run: | + ./gradlew spotlessCheck --info diff --git a/.github/workflows/python_lint.yml b/.github/workflows/python_lint.yml new file mode 100644 index 000000000..c12b256cf --- /dev/null +++ b/.github/workflows/python_lint.yml @@ -0,0 +1,40 @@ +name: "Lint Python code" + +on: + push: + branches: + - main + pull_request: + paths: + - ./example/python/**/*.py + - ./crates/voicevox_core_python_api/**/*.py + - ./crates/voicevox_core_python_api/requirements*.txt + - ./crates/voicevox_core_python_api/pyproject.toml + - ./.github/workflows/python_lint.yml + +defaults: + run: + shell: bash + working-directory: ./crates/voicevox_core_python_api + +jobs: + python: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - uses: actions/setup-python@v4 + with: + python-version: "3.8" + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install -r requirements-dev.txt + - name: Check code style for voicevox_core_python_api + run: | + black --check . + isort --check . + - name: Check code style for example/python + working-directory: ./example/python + run: | + black --check . + isort --check . diff --git a/crates/voicevox_core_java_api/lib/src/main/java/jp/hiroshiba/voicevoxcore/Synthesizer.java b/crates/voicevox_core_java_api/lib/src/main/java/jp/hiroshiba/voicevoxcore/Synthesizer.java index 5047048b8..98bf9f403 100644 --- a/crates/voicevox_core_java_api/lib/src/main/java/jp/hiroshiba/voicevoxcore/Synthesizer.java +++ b/crates/voicevox_core_java_api/lib/src/main/java/jp/hiroshiba/voicevoxcore/Synthesizer.java @@ -55,7 +55,6 @@ public boolean isLoadedVoiceModel(String voiceModelId) { * @param text テキスト。 * @param styleId スタイルID。 * @return {@link CreateAudioQueryConfigurator}。 - * * @see CreateAudioQueryConfigurator#execute */ @Nonnull @@ -69,7 +68,6 @@ public CreateAudioQueryConfigurator createAudioQuery(String text, int styleId) { * @param text テキスト。 * @param styleId スタイルID。 * @return {@link CreateAccentPhrasesConfigurator}。 - * * @see CreateAccentPhrasesConfigurator#execute */ @Nonnull @@ -137,7 +135,6 @@ public List replaceMoraPitch(List accentPhrases, int * @param audioQuery {@link AudioQuery}。 * @param styleId スタイルID。 * @return {@link SynthesisConfigurator}。 - * * @see SynthesisConfigurator#execute */ @Nonnull @@ -151,7 +148,6 @@ public SynthesisConfigurator synthesis(AudioQuery audioQuery, int styleId) { * @param text テキスト。 * @param styleId スタイルID。 * @return {@link TtsConfigurator}。 - * * @see TtsConfigurator#execute */ @Nonnull diff --git a/crates/voicevox_core_java_api/lib/src/main/java/jp/hiroshiba/voicevoxcore/UserDict.java b/crates/voicevox_core_java_api/lib/src/main/java/jp/hiroshiba/voicevoxcore/UserDict.java index e31d56338..d75924feb 100644 --- a/crates/voicevox_core_java_api/lib/src/main/java/jp/hiroshiba/voicevoxcore/UserDict.java +++ b/crates/voicevox_core_java_api/lib/src/main/java/jp/hiroshiba/voicevoxcore/UserDict.java @@ -6,7 +6,6 @@ import com.google.gson.internal.LinkedTreeMap; import jakarta.validation.constraints.Max; import jakarta.validation.constraints.Min; - import java.lang.ref.Cleaner; import java.util.HashMap; import javax.annotation.Nonnull; @@ -177,7 +176,7 @@ public static class Word { /** * UserDict.Wordを作成する。 * - * @param surface 言葉の表層形。 + * @param surface 言葉の表層形。 * @param pronunciation 言葉の発音。 * @throws IllegalArgumentException pronunciationが不正な場合。 */ 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 8c506a50f..d9829e6da 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 @@ -2,8 +2,9 @@ # AudioQueryのkanaを比較して変化するかどうかで判断する。 from uuid import UUID -import pytest + import conftest # noqa: F401 +import pytest import voicevox_core # noqa: F401 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 c9bc2c454..f7902ea94 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 @@ -2,8 +2,9 @@ # どのコードがどの操作を行っているかはコメントを参照。 import os -from uuid import UUID import tempfile +from uuid import UUID + import pytest import voicevox_core # noqa: F401 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 f59f705dd..ce6245bfc 100644 --- a/crates/voicevox_core_python_api/python/voicevox_core/__init__.py +++ b/crates/voicevox_core_python_api/python/voicevox_core/__init__.py @@ -11,15 +11,15 @@ UserDictWord, UserDictWordType, ) -from ._rust import ( - __version__, +from ._rust import ( # noqa: F401 OpenJtalk, Synthesizer, + UserDict, VoiceModel, VoicevoxError, - UserDict, + __version__, supported_devices, -) # noqa: F401 +) __all__ = [ "__version__", diff --git a/crates/voicevox_core_python_api/python/voicevox_core/_models.py b/crates/voicevox_core_python_api/python/voicevox_core/_models.py index b559100be..c1cd9ccca 100644 --- a/crates/voicevox_core_python_api/python/voicevox_core/_models.py +++ b/crates/voicevox_core_python_api/python/voicevox_core/_models.py @@ -4,7 +4,7 @@ import pydantic -from ._rust import _validate_pronunciation, _to_zenkaku +from ._rust import _to_zenkaku, _validate_pronunciation @pydantic.dataclasses.dataclass diff --git a/crates/voicevox_core_python_api/python/voicevox_core/_rust.pyi b/crates/voicevox_core_python_api/python/voicevox_core/_rust.pyi index e9c700a91..324ea0117 100644 --- a/crates/voicevox_core_python_api/python/voicevox_core/_rust.pyi +++ b/crates/voicevox_core_python_api/python/voicevox_core/_rust.pyi @@ -1,5 +1,5 @@ from pathlib import Path -from typing import Dict, Final, List, Literal, Union, TYPE_CHECKING +from typing import TYPE_CHECKING, Dict, Final, List, Literal, Union from uuid import UUID import numpy as np diff --git a/example/python/run.py b/example/python/run.py index 04feef83e..026b67a80 100644 --- a/example/python/run.py +++ b/example/python/run.py @@ -7,13 +7,8 @@ from typing import Tuple import voicevox_core -from voicevox_core import ( - AccelerationMode, - AudioQuery, - OpenJtalk, - Synthesizer, - VoiceModel, -) +from voicevox_core import (AccelerationMode, AudioQuery, OpenJtalk, + Synthesizer, VoiceModel) async def main() -> None: From 09c865684252a969ca9f62ce0e6163343a9cd0b0 Mon Sep 17 00:00:00 2001 From: Ryo Yamashita Date: Fri, 15 Sep 2023 03:26:25 +0900 Subject: [PATCH 03/20] =?UTF-8?q?=E3=83=80=E3=82=A6=E3=83=B3=E3=83=AD?= =?UTF-8?q?=E3=83=BC=E3=83=80=E3=81=AECLI=E3=82=AA=E3=83=97=E3=82=B7?= =?UTF-8?q?=E3=83=A7=E3=83=B3=E3=81=A7`clap::Arg::value=5Fname`=E3=82=92?= =?UTF-8?q?=E8=A8=AD=E5=AE=9A=20(#604)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- crates/download/src/main.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/crates/download/src/main.rs b/crates/download/src/main.rs index fba1bc342..e52ca45f2 100644 --- a/crates/download/src/main.rs +++ b/crates/download/src/main.rs @@ -53,15 +53,15 @@ struct Args { min: bool, /// 出力先の指定 - #[arg(short, long, default_value(DEFAULT_OUTPUT))] + #[arg(short, long, value_name("DIRECTORY"), default_value(DEFAULT_OUTPUT))] output: PathBuf, /// ダウンロードするvoicevox_coreのバージョンの指定 - #[arg(short, long, default_value("latest"))] + #[arg(short, long, value_name("GIT_TAG_OR_LATEST"), default_value("latest"))] version: String, /// 追加でダウンロードするライブラリのバージョン - #[arg(long, default_value("latest"))] + #[arg(long, value_name("GIT_TAG_OR_LATEST"), default_value("latest"))] additional_libraries_version: String, /// ダウンロードするデバイスを指定する(cudaはlinuxのみ) From f3427120c98b259c98c5b225860b124e3dd23ede Mon Sep 17 00:00:00 2001 From: Hiroshiba Date: Fri, 15 Sep 2023 03:27:26 +0900 Subject: [PATCH 04/20] =?UTF-8?q?download=5Ftest=E3=82=92=E3=83=87?= =?UTF-8?q?=E3=83=95=E3=82=A9=E3=83=AB=E3=83=88=E3=81=A7prerelease?= =?UTF-8?q?=E8=BE=BC=E3=81=BF=E3=81=AElatest=E3=81=8C=E4=BD=BF=E3=82=8F?= =?UTF-8?q?=E3=82=8C=E3=82=8B=E3=82=88=E3=81=86=E3=81=AB=E5=A4=89=E6=9B=B4?= =?UTF-8?q?=20(#608)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Ryo Yamashita --- .github/workflows/download_test.yml | 32 ++++++++++++++++------------- 1 file changed, 18 insertions(+), 14 deletions(-) diff --git a/.github/workflows/download_test.yml b/.github/workflows/download_test.yml index 86f728d20..b7885ba09 100644 --- a/.github/workflows/download_test.yml +++ b/.github/workflows/download_test.yml @@ -8,9 +8,14 @@ on: - "Cargo.*" - "crates/download/**" - ".github/workflows/download_test.yml" + +env: + VERSION: "prerelease-latest" + defaults: run: shell: bash + jobs: download-releases: strategy: @@ -19,7 +24,7 @@ jobs: include: - name: 通常ダウンロード os: windows-latest - download_command: cargo run -vv -p download + download_command: cargo run -vv -p download -- # バージョン指定のために -- が必要 download_dir: voicevox_core check_items: | voicevox_core.dll @@ -169,29 +174,28 @@ jobs: open_jtalk_dic_utf_8-1.11 runs-on: ${{ matrix.os }} name: ${{ matrix.name }}-${{ matrix.os }} - env: - EXPECTED_VOICEVOX_CORE_VERSION: latest - # See https://github.com/VOICEVOX/voicevox_core/issues/310 - #EXPECTED_VOICEVOX_CORE_VERSION: ${{ matrix.expected_version || 'latest' }} steps: - uses: actions/checkout@v3 - name: Set up Rust - if: ${{ startsWith(matrix.download_command, 'cargo ') }} uses: ./.github/actions/rust-toolchain-from-file + - name: Get prerelease latest version + if: ${{ env.VERSION == 'prerelease-latest' }} + run: | + VERSION=$( + curl -sSf https://api.github.com/repos/VOICEVOX/voicevox_core/releases \ + -H 'authorization: Bearer ${{ github.token }}' \ + -H 'content-type: application/json' | + jq -er '.[0].tag_name' + ) + echo "VERSION=$VERSION" >> "$GITHUB_ENV" - name: Execute download command - run: ${{ matrix.download_command }} + run: ${{ matrix.download_command }} --version ${{ env.VERSION }} env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - - name: Get latest version - if: ${{ env.EXPECTED_VOICEVOX_CORE_VERSION == 'latest' }} - run: | - echo "EXPECTED_VOICEVOX_CORE_VERSION=$(gh release view --repo VOICEVOX/voicevox_core --json 'tagName' --jq '.tagName')" >> "$GITHUB_ENV" - env: - GITHUB_TOKEN: ${{ github.token }} - name: Check downloaded version run: | [ -e "${{ matrix.download_dir }}/VERSION" ] - [ "$(cat "${{ matrix.download_dir }}/VERSION")" = "${{ env.EXPECTED_VOICEVOX_CORE_VERSION }}" ] + [ "$(cat "${{ matrix.download_dir }}/VERSION")" = "${{ env.VERSION }}" ] - name: Check downloaded files run: | mapfile -t items < <(echo -n '${{ matrix.check_items }}') From 239a1d7e3874687a4443dacc1d9cdaca29844326 Mon Sep 17 00:00:00 2001 From: Nanashi Date: Fri, 15 Sep 2023 19:54:01 +0900 Subject: [PATCH 05/20] =?UTF-8?q?Code:=20black=E3=81=A7=E3=83=95=E3=82=A9?= =?UTF-8?q?=E3=83=BC=E3=83=9E=E3=83=83=E3=83=88=20(#613)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- example/python/run.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/example/python/run.py b/example/python/run.py index 026b67a80..04feef83e 100644 --- a/example/python/run.py +++ b/example/python/run.py @@ -7,8 +7,13 @@ from typing import Tuple import voicevox_core -from voicevox_core import (AccelerationMode, AudioQuery, OpenJtalk, - Synthesizer, VoiceModel) +from voicevox_core import ( + AccelerationMode, + AudioQuery, + OpenJtalk, + Synthesizer, + VoiceModel, +) async def main() -> None: From bf708391ae32c417f15486e0f4aa20c83ac0cc38 Mon Sep 17 00:00:00 2001 From: Nanashi Date: Sat, 16 Sep 2023 18:09:39 +0900 Subject: [PATCH 06/20] =?UTF-8?q?Fix:=20Python=20Lint=20Workflow=E3=81=AE?= =?UTF-8?q?=E5=AE=9F=E8=A1=8C=E6=9D=A1=E4=BB=B6=E3=82=92=E5=A4=89=E6=9B=B4?= =?UTF-8?q?=20(#614)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/java_lint.yml | 10 +++++----- .github/workflows/python_lint.yml | 12 ++++++------ 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/.github/workflows/java_lint.yml b/.github/workflows/java_lint.yml index b631819b8..c75222912 100644 --- a/.github/workflows/java_lint.yml +++ b/.github/workflows/java_lint.yml @@ -6,11 +6,11 @@ on: - main pull_request: paths: - - ./crates/voicevox_core_java_api/**/*.java - - ./crates/voicevox_core_java_api/gradle/** - - ./crates/voicevox_core_java_api/gradlew - - ./crates/voicevox_core_java_api/settings.gradle - - ./.github/workflows/java_lint.yml + - 'crates/voicevox_core_java_api/**.java' + - 'crates/voicevox_core_java_api/gradle/**' + - 'crates/voicevox_core_java_api/gradlew' + - 'crates/voicevox_core_java_api/settings.gradle' + - '.github/workflows/java_lint.yml' defaults: run: diff --git a/.github/workflows/python_lint.yml b/.github/workflows/python_lint.yml index c12b256cf..6f6dfd1b3 100644 --- a/.github/workflows/python_lint.yml +++ b/.github/workflows/python_lint.yml @@ -6,11 +6,11 @@ on: - main pull_request: paths: - - ./example/python/**/*.py - - ./crates/voicevox_core_python_api/**/*.py - - ./crates/voicevox_core_python_api/requirements*.txt - - ./crates/voicevox_core_python_api/pyproject.toml - - ./.github/workflows/python_lint.yml + - 'example/python/**.py' + - 'crates/voicevox_core_python_api/**.py' + - 'crates/voicevox_core_python_api/requirements*.txt' + - 'crates/voicevox_core_python_api/pyproject.toml' + - '.github/workflows/python_lint.yml' defaults: run: @@ -37,4 +37,4 @@ jobs: working-directory: ./example/python run: | black --check . - isort --check . + isort --check --profile black . From 814d270628b6a4bf68fa009ddfa4e919d12686e4 Mon Sep 17 00:00:00 2001 From: Ryo Yamashita Date: Sun, 17 Sep 2023 19:20:14 +0900 Subject: [PATCH 07/20] =?UTF-8?q?ANSI=20escape=20sequence=E3=81=AE?= =?UTF-8?q?=E5=87=BA=E5=8A=9B=E3=81=AE=E5=88=A4=E6=96=AD=E3=82=92=E5=AE=8C?= =?UTF-8?q?=E5=85=A8=E3=81=AB=E3=81=99=E3=82=8B=20(#616)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Cargo.lock | 52 +++++++++++++++++++++++++++ crates/voicevox_core_c_api/Cargo.toml | 3 ++ crates/voicevox_core_c_api/src/lib.rs | 35 +++++++++++------- 3 files changed, 77 insertions(+), 13 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 871df23ea..4503ec451 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -125,6 +125,43 @@ dependencies = [ "libc", ] +[[package]] +name = "anstream" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1f58811cfac344940f1a400b6e6231ce35171f614f26439e80f8c1465c5cc0c" +dependencies = [ + "anstyle", + "anstyle-parse", + "anstyle-query", + "colorchoice", + "utf8parse", +] + +[[package]] +name = "anstyle" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b84bf0a05bbb2a83e5eb6fa36bb6e87baa08193c35ff52bbf6b38d8af2890e46" + +[[package]] +name = "anstyle-parse" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "938874ff5980b03a87c5524b3ae5b59cf99b1d6bc836848df7bc5ada9643c333" +dependencies = [ + "utf8parse", +] + +[[package]] +name = "anstyle-query" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ca11d4be1bab0c8bc8734a9aa7bf4ee8316d462a08c6ac5052f888fef5b494b" +dependencies = [ + "windows-sys 0.48.0", +] + [[package]] name = "anyhow" version = "1.0.65" @@ -712,6 +749,12 @@ dependencies = [ "tracing-error", ] +[[package]] +name = "colorchoice" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7" + [[package]] name = "combine" version = "4.6.6" @@ -4107,6 +4150,12 @@ dependencies = [ "serde", ] +[[package]] +name = "utf8parse" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a" + [[package]] name = "uuid" version = "1.4.0" @@ -4189,10 +4238,13 @@ dependencies = [ name = "voicevox_core_c_api" version = "0.0.0" dependencies = [ + "anstream", + "anstyle-query", "anyhow", "assert_cmd", "chrono", "clap 4.0.10", + "colorchoice", "cstr", "derive-getters", "duct", diff --git a/crates/voicevox_core_c_api/Cargo.toml b/crates/voicevox_core_c_api/Cargo.toml index 58db5c0ec..ae691c99b 100644 --- a/crates/voicevox_core_c_api/Cargo.toml +++ b/crates/voicevox_core_c_api/Cargo.toml @@ -16,6 +16,9 @@ name = "e2e" directml = ["voicevox_core/directml"] [dependencies] +anstream = { version = "0.5.0", default-features = false, features = ["auto"] } +anstyle-query = "1.0.0" +colorchoice = "1.0.0" cstr = "0.2.11" derive-getters.workspace = true itertools.workspace = true diff --git a/crates/voicevox_core_c_api/src/lib.rs b/crates/voicevox_core_c_api/src/lib.rs index 8ca5becc8..4c692b85d 100644 --- a/crates/voicevox_core_c_api/src/lib.rs +++ b/crates/voicevox_core_c_api/src/lib.rs @@ -13,13 +13,15 @@ use self::drop_check::C_STRING_DROP_CHECKER; use self::helpers::*; use self::result_code::VoicevoxResultCode; use self::slice_owner::U8_SLICE_OWNER; +use anstream::{AutoStream, RawStream}; use chrono::SecondsFormat; +use colorchoice::ColorChoice; use derive_getters::Getters; use once_cell::sync::Lazy; use std::env; use std::ffi::{CStr, CString}; use std::fmt; -use std::io::{self, IsTerminal, Write}; +use std::io; use std::os::raw::c_char; use std::ptr::NonNull; use std::sync::{Arc, Mutex, MutexGuard}; @@ -37,6 +39,23 @@ static RUNTIME: Lazy = Lazy::new(|| { let _ = init_logger(); fn init_logger() -> std::result::Result<(), impl Sized> { + let ansi = { + // anstyle系のクレートを利用して次の2つを行う。 + // + // * ANSI escape codeを出してよいかの判定(環境変数のチェックとisatty) + // * 必要であれば`ENABLE_VIRTUAL_TERMINAL_PROCESSING`の有効化 + + assert_eq!( + ColorChoice::Auto, + ColorChoice::global(), + "`ColorChoice::write_global` should not have been called", + ); + + AutoStream::choice(&out()) != ColorChoice::Never + && anstyle_query::term_supports_ansi_color() + && anstyle_query::windows::enable_ansi_colors().unwrap_or(true) + }; + tracing_subscriber::fmt() .with_env_filter(if env::var_os(EnvFilter::DEFAULT_ENV).is_some() { EnvFilter::from_default_env() @@ -44,7 +63,7 @@ static RUNTIME: Lazy = Lazy::new(|| { "error,voicevox_core=info,voicevox_core_c_api=info,onnxruntime=info".into() }) .with_timer(local_time as fn(&mut Writer<'_>) -> _) - .with_ansi(out().is_terminal() && env_allows_ansi()) + .with_ansi(ansi) .with_writer(out) .try_init() } @@ -55,20 +74,10 @@ static RUNTIME: Lazy = Lazy::new(|| { wtr.write_str(&chrono::Local::now().to_rfc3339_opts(SecondsFormat::Micros, false)) } - fn out() -> impl IsTerminal + Write { + fn out() -> impl RawStream { io::stderr() } - fn env_allows_ansi() -> bool { - // https://docs.rs/termcolor/1.2.0/src/termcolor/lib.rs.html#245-291 - // ただしWindowsではPowerShellっぽかったらそのまま許可する。 - // ちゃんとやるなら`ENABLE_VIRTUAL_TERMINAL_PROCESSING`をチェックするなり、そもそも - // fwdansiとかでWin32の色に変換するべきだが、面倒。 - env::var_os("TERM").map_or( - cfg!(windows) && env::var_os("PSModulePath").is_some(), - |term| term != "dumb", - ) && env::var_os("NO_COLOR").is_none() - } Runtime::new().unwrap() }); From 6a662757b8d42fc5d0902364b1d549684b50b5bc Mon Sep 17 00:00:00 2001 From: Hiroshiba Date: Mon, 18 Sep 2023 09:33:48 +0900 Subject: [PATCH 08/20] =?UTF-8?q?build=5Fand=5Fdeploy=E3=81=AE=E6=9C=80?= =?UTF-8?q?=E5=BE=8C=E3=81=ABdownload=5Ftest=E3=82=92=E5=AE=9F=E8=A1=8C=20?= =?UTF-8?q?(#609)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/build_and_deploy.yml | 22 +++++++++++++++------- .github/workflows/download_test.yml | 9 ++++++++- 2 files changed, 23 insertions(+), 8 deletions(-) diff --git a/.github/workflows/build_and_deploy.yml b/.github/workflows/build_and_deploy.yml index cdc2d1c7a..1994a34e0 100644 --- a/.github/workflows/build_and_deploy.yml +++ b/.github/workflows/build_and_deploy.yml @@ -41,10 +41,11 @@ defaults: shell: bash jobs: - build_and_deploy_strategy_matrix: # 実行対象の条件をフィルタリングする + config: # 全 jobs で利用する定数の定義。実行対象の条件をフィルタリングする。 runs-on: ubuntu-latest outputs: includes: ${{ steps.strategy_matrix.outputs.includes }} + deploy: ${{ env.VERSION != '0.0.0' }} steps: - name: declare strategy matrix id: strategy_matrix @@ -181,11 +182,11 @@ jobs: echo "includes=${includes}" >> "$GITHUB_OUTPUT" build_and_deploy: - needs: build_and_deploy_strategy_matrix + needs: config environment: ${{ github.event.inputs.is_production == 'true' && 'production' || '' }} # 製品版のenvironment strategy: matrix: - include: ${{ fromJson(needs.build_and_deploy_strategy_matrix.outputs.includes) }} + include: ${{ fromJson(needs.config.outputs.includes) }} runs-on: ${{ matrix.os }} steps: - uses: actions/checkout@v3 # 製品版ではない場合 @@ -318,7 +319,7 @@ jobs: cd artifact 7z a "../${{ env.ASSET_NAME }}.zip" "${{ env.ASSET_NAME }}" - name: Upload to Release - if: env.VERSION != '0.0.0' && env.SKIP_UPLOADING_RELEASE_ASSET == '0' && !contains(matrix.target, 'ios') + if: needs.config.outputs.deploy == 'true' && env.SKIP_UPLOADING_RELEASE_ASSET == '0' && !contains(matrix.target, 'ios') uses: softprops/action-gh-release@v1 with: prerelease: true @@ -327,7 +328,7 @@ jobs: ${{ env.ASSET_NAME }}.zip target_commitish: ${{ github.sha }} - name: Upload Python whl to Release - if: env.VERSION != '0.0.0' && matrix.whl_local_version + if: needs.config.outputs.deploy == 'true' && matrix.whl_local_version uses: softprops/action-gh-release@v1 with: prerelease: true @@ -338,7 +339,7 @@ jobs: build_xcframework: if: ${{ !(github.event_name != 'release' && github.event_name != 'workflow_dispatch') }} # !env.IS_SIMPLE_TEST と同じ - needs: build_and_deploy + needs: [config, build_and_deploy] runs-on: macos-12 steps: - uses: actions/checkout@v3 @@ -381,7 +382,7 @@ jobs: cd artifact/${{ env.ASSET_NAME }} 7z a "../../${{ env.ASSET_NAME }}.zip" "voicevox_core.xcframework" - name: Upload to Release - if: env.VERSION != '0.0.0' && env.SKIP_UPLOADING_RELEASE_ASSET == '0' + if: needs.config.outputs.deploy == 'true' && env.SKIP_UPLOADING_RELEASE_ASSET == '0' uses: softprops/action-gh-release@v1 with: prerelease: true @@ -389,3 +390,10 @@ jobs: files: |- ${{ env.ASSET_NAME }}.zip target_commitish: ${{ github.sha }} + + download_test: + needs: [config, build_and_deploy] + if: needs.config.outputs.deploy == 'true' + uses: ./.github/workflows/download_test.yml + with: + version: ${{ inputs.version }} diff --git a/.github/workflows/download_test.yml b/.github/workflows/download_test.yml index b7885ba09..af42532b8 100644 --- a/.github/workflows/download_test.yml +++ b/.github/workflows/download_test.yml @@ -1,5 +1,12 @@ name: Download test workflow + on: + workflow_call: + inputs: + version: + description: "テスト対象のコアのバージョン。無指定時はprerelease込みの最新release。" + type: string + required: false push: branches: - main @@ -10,7 +17,7 @@ on: - ".github/workflows/download_test.yml" env: - VERSION: "prerelease-latest" + VERSION: ${{ inputs.version || 'prerelease-latest' }} defaults: run: From 15db87525f07eb99cc7763152cf28644704519be Mon Sep 17 00:00:00 2001 From: Nanashi Date: Tue, 19 Sep 2023 01:08:02 +0900 Subject: [PATCH 09/20] =?UTF-8?q?Java=20API=EF=BC=9AAndroid=E7=89=88?= =?UTF-8?q?=E3=81=AE=E8=A8=AD=E8=A8=88=E3=82=92=E8=89=B2=E3=80=85=E4=BF=AE?= =?UTF-8?q?=E6=AD=A3=20(#612)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- crates/voicevox_core_java_api/README.md | 10 +++- .../lib/build-android.gradle | 56 +++++++++++++++++++ .../voicevox_core_java_api/lib/build.gradle | 26 +++++---- .../hiroshiba/voicevoxcore/AccentPhrase.java | 4 +- .../jp/hiroshiba/voicevoxcore/AudioQuery.java | 4 +- .../java/jp/hiroshiba/voicevoxcore/Mora.java | 4 +- .../jp/hiroshiba/voicevoxcore/OpenJtalk.java | 9 ++- .../hiroshiba/voicevoxcore/Synthesizer.java | 10 ++-- .../jp/hiroshiba/voicevoxcore/UserDict.java | 9 +-- .../jp/hiroshiba/voicevoxcore/VoiceModel.java | 9 +-- .../lib/src/main/resources/jniLibs/README.md | 5 ++ .../main/resources/jniLibs/arm64-x8a/.gitkeep | 0 .../main/resources/jniLibs/x86_64/.gitkeep | 0 crates/voicevox_core_java_api/settings.gradle | 37 ++++++++++++ 14 files changed, 146 insertions(+), 37 deletions(-) create mode 100644 crates/voicevox_core_java_api/lib/build-android.gradle create mode 100644 crates/voicevox_core_java_api/lib/src/main/resources/jniLibs/README.md delete mode 100644 crates/voicevox_core_java_api/lib/src/main/resources/jniLibs/arm64-x8a/.gitkeep delete mode 100644 crates/voicevox_core_java_api/lib/src/main/resources/jniLibs/x86_64/.gitkeep diff --git a/crates/voicevox_core_java_api/README.md b/crates/voicevox_core_java_api/README.md index d1bc31938..ab5ef5064 100644 --- a/crates/voicevox_core_java_api/README.md +++ b/crates/voicevox_core_java_api/README.md @@ -44,25 +44,31 @@ Java プロジェクトを動かすには、 - `lib/src/main/resources/dll/[target]/libvoicevox_core_java_api.so` を作成する(`libvoicevox_core_java_api.so`はプラットフォームによって異なります、詳細は後述)。 必要があります。 +また、ハードウェアアクセラレーションを有効にする時は`DEVICE`環境変数を`cuda`または`directml`にし、Android 版をビルドする時は`OS`環境変数を`android`にしてください。 ```console ❯ cargo build -❯ LD_LIBRARY_PATH=$(realpath ../../target/debug) ./gradlew build +❯ LD_LIBRARY_PATH=$(realpath ../../target/debug) ./gradlew test # または ❯ cp ../../target/debug/libvoicevox_core_java_api.so lib/src/main/resources/dll/[target]/libvoicevox_core_java_api.so -❯ ./gradlew build +❯ ./gradlew test +❯ DEVICE=cuda ./gradlew test +❯ OS=android ./gradlew test ``` ## ビルド(リリース) `cargo build --release` で Rust 側を、`./gradlew build` で Java 側をビルドできます。 パッケージ化する時は lib/src/main/resources/dll 内に dll をコピーしてください。 +`DEVICE`、`OS`環境変数は開発時と同様です。 ```console ❯ cargo build --release ❯ cp ../../target/release/libvoicevox_core_java_api.so lib/src/main/resources/dll/[target]/libvoicevox_core_java_api.so ❯ ./gradlew build +❯ DEVICE=cuda ./gradlew build +❯ OS=android ./gradlew build ``` ## テスト diff --git a/crates/voicevox_core_java_api/lib/build-android.gradle b/crates/voicevox_core_java_api/lib/build-android.gradle new file mode 100644 index 000000000..f369c663c --- /dev/null +++ b/crates/voicevox_core_java_api/lib/build-android.gradle @@ -0,0 +1,56 @@ +plugins { + id 'com.android.library' version '8.1.1' + id 'org.jetbrains.kotlin.android' version '1.9.10' +} + +version = gradle.ext.version + +repositories { + google() + mavenCentral() +} + +dependencies { + // Use JUnit Jupiter for testing. + testImplementation 'org.junit.jupiter:junit-jupiter:5.9.2' + + testRuntimeOnly 'org.junit.platform:junit-platform-launcher' + + // https://mvnrepository.com/artifact/com.google.code.gson/gson + implementation group: 'com.google.code.gson', name: 'gson', version: gradle.ext.gsonVersion + + // https://mvnrepository.com/artifact/jakarta.validation/jakarta.validation-api + implementation group: 'jakarta.validation', name: 'jakarta.validation-api', version: gradle.ext.jakartaValidationVersion + + // https://mvnrepository.com/artifact/jakarta.annotation/jakarta.annotation-api + implementation group: 'jakarta.annotation', name: 'jakarta.annotation-api', version: gradle.ext.jakartaAnnotationVersion + + implementation group: 'com.microsoft.onnxruntime', name: 'onnxruntime-android', version: gradle.ext.onnxruntimeVersion +} + +// Apply a specific Java toolchain to ease working on different environments. +java { + toolchain { + languageVersion = JavaLanguageVersion.of(8) + } +} + +android { + compileSdkVersion 26 + + defaultConfig { + minSdkVersion 26 + targetSdkVersion 26 + } + namespace "jp.hiroshiba.voicevoxcore" + + compileOptions { + sourceCompatibility JavaVersion.VERSION_1_8 + targetCompatibility JavaVersion.VERSION_1_8 + } + sourceSets { + main { + jniLibs.srcDirs = ["./src/main/resources/jniLibs"] + } + } +} diff --git a/crates/voicevox_core_java_api/lib/build.gradle b/crates/voicevox_core_java_api/lib/build.gradle index 3e87cceff..f1eb5b106 100644 --- a/crates/voicevox_core_java_api/lib/build.gradle +++ b/crates/voicevox_core_java_api/lib/build.gradle @@ -11,7 +11,9 @@ plugins { id "com.diffplug.spotless" version "6.20.0" } -version = '0.0.0' +def boolean isGpu = ['cuda', 'directml'].contains(gradle.ext.targetDevice) + +version = gradle.ext.version repositories { // Use Maven Central for resolving dependencies. @@ -24,28 +26,28 @@ dependencies { testRuntimeOnly 'org.junit.platform:junit-platform-launcher' - // This dependency is exported to consumers, that is to say found on their compile classpath. - api 'org.apache.commons:commons-math3:3.6.1' - - // This dependency is used internally, and not exposed to consumers on their own compile classpath. - implementation 'com.google.guava:guava:31.1-jre' - // https://mvnrepository.com/artifact/com.google.code.gson/gson - implementation group: 'com.google.code.gson', name: 'gson', version: '2.10.1' + implementation group: 'com.google.code.gson', name: 'gson', version: gradle.ext.gsonVersion // https://mvnrepository.com/artifact/jakarta.validation/jakarta.validation-api - implementation group: 'jakarta.validation', name: 'jakarta.validation-api', version: '3.0.2' + implementation group: 'jakarta.validation', name: 'jakarta.validation-api', version: gradle.ext.jakartaValidationVersion - implementation group: 'com.microsoft.onnxruntime', name: 'onnxruntime', version: '1.14.0' + // https://mvnrepository.com/artifact/jakarta.annotation/jakarta.annotation-api + implementation group: 'jakarta.annotation', name: 'jakarta.annotation-api', version: gradle.ext.jakartaAnnotationVersion + + if (isGpu) { + implementation group: 'com.microsoft.onnxruntime', name: 'onnxruntime_gpu', version: gradle.ext.onnxruntimeVersion + } else { + implementation group: 'com.microsoft.onnxruntime', name: 'onnxruntime', version: gradle.ext.onnxruntimeVersion + } } // Apply a specific Java toolchain to ease working on different environments. java { toolchain { - languageVersion = JavaLanguageVersion.of(11) + languageVersion = JavaLanguageVersion.of(8) } } - tasks.named('test') { // Use JUnit Platform for unit tests. useJUnitPlatform() diff --git a/crates/voicevox_core_java_api/lib/src/main/java/jp/hiroshiba/voicevoxcore/AccentPhrase.java b/crates/voicevox_core_java_api/lib/src/main/java/jp/hiroshiba/voicevoxcore/AccentPhrase.java index 8c64c9d11..61e8b5d28 100644 --- a/crates/voicevox_core_java_api/lib/src/main/java/jp/hiroshiba/voicevoxcore/AccentPhrase.java +++ b/crates/voicevox_core_java_api/lib/src/main/java/jp/hiroshiba/voicevoxcore/AccentPhrase.java @@ -2,10 +2,10 @@ import com.google.gson.annotations.Expose; import com.google.gson.annotations.SerializedName; +import jakarta.annotation.Nonnull; +import jakarta.annotation.Nullable; import java.util.ArrayList; import java.util.List; -import javax.annotation.Nonnull; -import javax.annotation.Nullable; /** AccentPhrase (アクセント句ごとの情報)。 */ public class AccentPhrase { diff --git a/crates/voicevox_core_java_api/lib/src/main/java/jp/hiroshiba/voicevoxcore/AudioQuery.java b/crates/voicevox_core_java_api/lib/src/main/java/jp/hiroshiba/voicevoxcore/AudioQuery.java index 4a23be3f7..c03accf2f 100644 --- a/crates/voicevox_core_java_api/lib/src/main/java/jp/hiroshiba/voicevoxcore/AudioQuery.java +++ b/crates/voicevox_core_java_api/lib/src/main/java/jp/hiroshiba/voicevoxcore/AudioQuery.java @@ -2,10 +2,10 @@ import com.google.gson.annotations.Expose; import com.google.gson.annotations.SerializedName; +import jakarta.annotation.Nonnull; +import jakarta.annotation.Nullable; import java.util.ArrayList; import java.util.List; -import javax.annotation.Nonnull; -import javax.annotation.Nullable; /** AudioQuery(音声合成用のクエリ)。 */ public class AudioQuery { diff --git a/crates/voicevox_core_java_api/lib/src/main/java/jp/hiroshiba/voicevoxcore/Mora.java b/crates/voicevox_core_java_api/lib/src/main/java/jp/hiroshiba/voicevoxcore/Mora.java index 61dbaf233..f03874323 100644 --- a/crates/voicevox_core_java_api/lib/src/main/java/jp/hiroshiba/voicevoxcore/Mora.java +++ b/crates/voicevox_core_java_api/lib/src/main/java/jp/hiroshiba/voicevoxcore/Mora.java @@ -2,8 +2,8 @@ import com.google.gson.annotations.Expose; import com.google.gson.annotations.SerializedName; -import javax.annotation.Nonnull; -import javax.annotation.Nullable; +import jakarta.annotation.Nonnull; +import jakarta.annotation.Nullable; /** モーラ(子音+母音)ごとの情報。 */ public class Mora { diff --git a/crates/voicevox_core_java_api/lib/src/main/java/jp/hiroshiba/voicevoxcore/OpenJtalk.java b/crates/voicevox_core_java_api/lib/src/main/java/jp/hiroshiba/voicevoxcore/OpenJtalk.java index 3228b8d51..c9ad2f963 100644 --- a/crates/voicevox_core_java_api/lib/src/main/java/jp/hiroshiba/voicevoxcore/OpenJtalk.java +++ b/crates/voicevox_core_java_api/lib/src/main/java/jp/hiroshiba/voicevoxcore/OpenJtalk.java @@ -1,11 +1,7 @@ package jp.hiroshiba.voicevoxcore; -import java.lang.ref.Cleaner; - -/** テキスト解析器としてのOpen JTalk。 */ public class OpenJtalk extends Dll { private long handle; - private static final Cleaner cleaner = Cleaner.create(); /** * Open JTalkの辞書ディレクトリ。 @@ -14,8 +10,11 @@ public class OpenJtalk extends Dll { */ public OpenJtalk(String openJtalkDictDir) { rsNewWithInitialize(openJtalkDictDir); + } - cleaner.register(this, () -> rsDrop()); + protected void finalize() throws Throwable { + rsDrop(); + super.finalize(); } /** diff --git a/crates/voicevox_core_java_api/lib/src/main/java/jp/hiroshiba/voicevoxcore/Synthesizer.java b/crates/voicevox_core_java_api/lib/src/main/java/jp/hiroshiba/voicevoxcore/Synthesizer.java index 98bf9f403..5739b14f7 100644 --- a/crates/voicevox_core_java_api/lib/src/main/java/jp/hiroshiba/voicevoxcore/Synthesizer.java +++ b/crates/voicevox_core_java_api/lib/src/main/java/jp/hiroshiba/voicevoxcore/Synthesizer.java @@ -1,11 +1,10 @@ package jp.hiroshiba.voicevoxcore; import com.google.gson.Gson; -import java.lang.ref.Cleaner; +import jakarta.annotation.Nonnull; import java.util.ArrayList; import java.util.Arrays; import java.util.List; -import javax.annotation.Nonnull; /** * 音声シンセサイザ。 @@ -14,11 +13,14 @@ */ public class Synthesizer extends Dll { private long handle; - private static final Cleaner cleaner = Cleaner.create(); private Synthesizer(OpenJtalk openJtalk, Builder builder) { rsNewWithInitialize(openJtalk, builder); - cleaner.register(this, () -> rsDrop()); + } + + protected void finalize() throws Throwable { + rsDrop(); + super.finalize(); } /** diff --git a/crates/voicevox_core_java_api/lib/src/main/java/jp/hiroshiba/voicevoxcore/UserDict.java b/crates/voicevox_core_java_api/lib/src/main/java/jp/hiroshiba/voicevoxcore/UserDict.java index d75924feb..b0b8921a2 100644 --- a/crates/voicevox_core_java_api/lib/src/main/java/jp/hiroshiba/voicevoxcore/UserDict.java +++ b/crates/voicevox_core_java_api/lib/src/main/java/jp/hiroshiba/voicevoxcore/UserDict.java @@ -4,22 +4,23 @@ import com.google.gson.annotations.Expose; import com.google.gson.annotations.SerializedName; import com.google.gson.internal.LinkedTreeMap; +import jakarta.annotation.Nonnull; import jakarta.validation.constraints.Max; import jakarta.validation.constraints.Min; -import java.lang.ref.Cleaner; import java.util.HashMap; -import javax.annotation.Nonnull; /** ユーザー辞書。 */ public class UserDict extends Dll { private long handle; - private static final Cleaner cleaner = Cleaner.create(); /** ユーザー辞書を作成する。 */ public UserDict() { rsNew(); + } - cleaner.register(this, () -> rsDrop()); + protected void finalize() throws Throwable { + rsDrop(); + super.finalize(); } /** diff --git a/crates/voicevox_core_java_api/lib/src/main/java/jp/hiroshiba/voicevoxcore/VoiceModel.java b/crates/voicevox_core_java_api/lib/src/main/java/jp/hiroshiba/voicevoxcore/VoiceModel.java index 518f1de31..05c1a11b2 100644 --- a/crates/voicevox_core_java_api/lib/src/main/java/jp/hiroshiba/voicevoxcore/VoiceModel.java +++ b/crates/voicevox_core_java_api/lib/src/main/java/jp/hiroshiba/voicevoxcore/VoiceModel.java @@ -3,13 +3,11 @@ import com.google.gson.Gson; import com.google.gson.annotations.Expose; import com.google.gson.annotations.SerializedName; -import java.lang.ref.Cleaner; -import javax.annotation.Nonnull; +import jakarta.annotation.Nonnull; /** 音声モデル。 */ public class VoiceModel extends Dll { private long handle; - private static final Cleaner cleaner = Cleaner.create(); /** ID。 */ @Nonnull public final String id; @@ -27,8 +25,11 @@ public VoiceModel(String modelPath) { throw new RuntimeException("Failed to parse metasJson"); } metas = rawMetas; + } - cleaner.register(this, () -> rsDrop()); + protected void finalize() throws Throwable { + rsDrop(); + super.finalize(); } private native void rsFromPath(String modelPath); diff --git a/crates/voicevox_core_java_api/lib/src/main/resources/jniLibs/README.md b/crates/voicevox_core_java_api/lib/src/main/resources/jniLibs/README.md new file mode 100644 index 000000000..9073988ec --- /dev/null +++ b/crates/voicevox_core_java_api/lib/src/main/resources/jniLibs/README.md @@ -0,0 +1,5 @@ +このディレクトリに Android での JNI 用に読み込まれる DLL を配置します。 +ディレクトリ名は以下のうちのいずれかになります。 + +- `arm64-v8a` +- `x86_64` diff --git a/crates/voicevox_core_java_api/lib/src/main/resources/jniLibs/arm64-x8a/.gitkeep b/crates/voicevox_core_java_api/lib/src/main/resources/jniLibs/arm64-x8a/.gitkeep deleted file mode 100644 index e69de29bb..000000000 diff --git a/crates/voicevox_core_java_api/lib/src/main/resources/jniLibs/x86_64/.gitkeep b/crates/voicevox_core_java_api/lib/src/main/resources/jniLibs/x86_64/.gitkeep deleted file mode 100644 index e69de29bb..000000000 diff --git a/crates/voicevox_core_java_api/settings.gradle b/crates/voicevox_core_java_api/settings.gradle index 7d07347eb..20a5e2c6a 100644 --- a/crates/voicevox_core_java_api/settings.gradle +++ b/crates/voicevox_core_java_api/settings.gradle @@ -1,7 +1,44 @@ +pluginManagement { + repositories { + google() + mavenCentral() + gradlePluginPortal() + } + + resolutionStrategy { + eachPlugin { + if(requested.id.namespace == "com.android") { + useModule("com.android.tools.build:gradle:${requested.version}") + } + } + } +} plugins { // Apply the foojay-resolver plugin to allow automatic download of JDKs id 'org.gradle.toolchains.foojay-resolver-convention' version '0.4.0' } rootProject.name = 'jp.hiroshiba.voicevoxcore' +def String targetOs = System.getenv('OS')?.toLowerCase() ?: "desktop" +def boolean isAndroid = targetOs == 'android' include('lib') +if (isAndroid) { + project(':lib').buildFileName = 'build-android.gradle' +} else { + project(':lib').buildFileName = 'build.gradle' +} + +def String cargoToml = file('../../Cargo.toml').text +def String cargoTomlVersion = (cargoToml =~ /(?m)^version = "(\S+)"$/)[0][1] + +gradle.ext { + version = cargoTomlVersion + + targetOs = targetOs + targetDevice = System.getenv('DEVICE') ?: 'cpu' + + gsonVersion = '2.10.1' + jakartaValidationVersion = '3.0.2' + jakartaAnnotationVersion = '2.1.1' + onnxruntimeVersion = '1.14.0' +} From bbb35b4c89f36016a95cd313d103daa9dc97b90e Mon Sep 17 00:00:00 2001 From: Hiroshiba Date: Tue, 19 Sep 2023 10:51:27 +0900 Subject: [PATCH 10/20] =?UTF-8?q?download=5Ftest=E3=82=92=E6=9C=80?= =?UTF-8?q?=E6=96=B0=E3=81=AE=E3=82=82=E3=81=AE=E3=81=A7=E9=80=9A=E3=82=8B?= =?UTF-8?q?=E3=82=88=E3=81=86=E3=81=AB=E5=A4=89=E6=9B=B4=20(#618)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/download_test.yml | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/.github/workflows/download_test.yml b/.github/workflows/download_test.yml index af42532b8..6e8ff1b6f 100644 --- a/.github/workflows/download_test.yml +++ b/.github/workflows/download_test.yml @@ -35,7 +35,7 @@ jobs: download_dir: voicevox_core check_items: | voicevox_core.dll - model/metas.json + model/README.* open_jtalk_dic_utf_8-1.11 README.txt # https://github.com/VOICEVOX/voicevox_core/pull/411#issuecomment-1412457592 @@ -53,7 +53,7 @@ jobs: download_dir: voicevox_core check_items: | voicevox_core.dll - model/metas.json + model/README.* open_jtalk_dic_utf_8-1.11 README.txt check_not_exists_items: | @@ -70,7 +70,7 @@ jobs: download_dir: other_output check_items: | voicevox_core.dll - model/metas.json + model/README.* open_jtalk_dic_utf_8-1.11 README.txt check_not_exists_items: | @@ -87,7 +87,7 @@ jobs: download_dir: voicevox_core check_items: | voicevox_core.dll - model/metas.json + model/README.* README.txt check_not_exists_items: | *directml* @@ -104,7 +104,7 @@ jobs: download_dir: voicevox_core check_items: | voicevox_core.dll - model/metas.json + model/README.* open_jtalk_dic_utf_8-1.11 README.txt DirectML.dll @@ -123,7 +123,7 @@ jobs: download_dir: voicevox_core check_items: | voicevox_core.dll - model/metas.json + model/README.* README.txt check_not_exists_items: | *cuda* @@ -141,7 +141,7 @@ jobs: download_dir: voicevox_core check_items: | voicevox_core.dll - model/metas.json + model/README.* open_jtalk_dic_utf_8-1.11 README.txt EULA.txt @@ -163,7 +163,7 @@ jobs: download_dir: voicevox_core check_items: | voicevox_core.dll - model/metas.json + model/README.* README.txt check_not_exists_items: | *directml* From 302d6b9939e0b9614016e757cbca4609f2016372 Mon Sep 17 00:00:00 2001 From: Ryo Yamashita Date: Sun, 24 Sep 2023 03:09:11 +0900 Subject: [PATCH 11/20] =?UTF-8?q?`kana:=20bool`=E3=82=92=E3=82=84=E3=82=81?= =?UTF-8?q?=E3=80=81"=5Ffrom=5Fkana"=E3=82=92=E5=BE=A9=E6=B4=BB=E3=81=95?= =?UTF-8?q?=E3=81=9B=E3=82=8B=20(#577)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- crates/voicevox_core/src/engine/model.rs | 6 + crates/voicevox_core/src/voice_synthesizer.rs | 255 ++++++++---------- .../include/voicevox_core.h | 173 +++++++----- crates/voicevox_core_c_api/src/helpers.rs | 24 -- crates/voicevox_core_c_api/src/lib.rs | 231 ++++++++++------ .../voicevox_core_c_api/tests/e2e/symbols.rs | 32 ++- .../e2e/testcases/tts_via_audio_query.rs | 2 - .../tests/e2e/testcases/user_dict_load.rs | 3 - .../hiroshiba/voicevoxcore/Synthesizer.java | 229 ++++++++-------- .../voicevoxcore/SynthesizerTest.java | 6 +- .../hiroshiba/voicevoxcore/UserDictTest.java | 12 +- .../voicevox_core_java_api/src/synthesizer.rs | 126 +++++++-- .../python/test/test_user_dict_load.py | 4 +- .../python/voicevox_core/_rust.pyi | 80 ++++-- crates/voicevox_core_python_api/src/lib.rs | 110 ++++++-- 15 files changed, 785 insertions(+), 508 deletions(-) diff --git a/crates/voicevox_core/src/engine/model.rs b/crates/voicevox_core/src/engine/model.rs index d403d60ec..657c3c232 100644 --- a/crates/voicevox_core/src/engine/model.rs +++ b/crates/voicevox_core/src/engine/model.rs @@ -76,6 +76,12 @@ pub struct AudioQueryModel { kana: Option, } +impl AudioQueryModel { + pub(crate) fn with_kana(self, kana: Option) -> Self { + Self { kana, ..self } + } +} + #[cfg(test)] mod tests { use super::*; diff --git a/crates/voicevox_core/src/voice_synthesizer.rs b/crates/voicevox_core/src/voice_synthesizer.rs index 59e219f48..604ee5ea7 100644 --- a/crates/voicevox_core/src/voice_synthesizer.rs +++ b/crates/voicevox_core/src/voice_synthesizer.rs @@ -25,36 +25,10 @@ impl From<&TtsOptions> for SynthesisOptions { } } -/// [`Synthesizer::create_accent_phrases`]のオプション。 -/// -/// [`Synthesizer::create_accent_phrases`]: Synthesizer::create_accent_phrases -#[derive(Default)] -pub struct AccentPhrasesOptions { - /// AquesTalk風記法としてテキストを解釈する。 - pub kana: bool, -} - -/// [`Synthesizer::audio_query`]のオプション。 -/// -/// [`Synthesizer::audio_query`]: Synthesizer::audio_query -#[derive(Default)] -pub struct AudioQueryOptions { - /// AquesTalk風記法としてテキストを解釈する。 - pub kana: bool, -} - -impl From<&TtsOptions> for AudioQueryOptions { - fn from(options: &TtsOptions) -> Self { - Self { kana: options.kana } - } -} - /// [`Synthesizer::tts`]のオプション。 /// /// [`Synthesizer::tts`]: Synthesizer::tts pub struct TtsOptions { - /// AquesTalk風記法としてテキストを解釈する。 - pub kana: bool, pub enable_interrogative_upspeak: bool, } @@ -68,7 +42,6 @@ impl Default for TtsOptions { fn default() -> Self { Self { enable_interrogative_upspeak: true, - kana: Default::default(), } } } @@ -267,12 +240,9 @@ impl Synthesizer { .await } - /// AccentPhrase (アクセント句)の配列を生成する。 - /// - /// `text`は[`options.kana`]が有効化されているときにはAquesTalk風記法として、そうでないときには - /// 日本語のテキストとして解釈される。 + /// AquesTalk風記法からAccentPhrase (アクセント句)の配列を生成する。 /// - /// # Examples + /// # Example /// #[cfg_attr(windows, doc = "```no_run")] // https://github.com/VOICEVOX/voicevox_core/issues/537 #[cfg_attr(not(windows), doc = "```")] @@ -308,16 +278,25 @@ impl Synthesizer { /// use voicevox_core::StyleId; /// /// let accent_phrases = syntesizer - /// .create_accent_phrases( - /// "こんにちは", // 日本語のテキスト - /// StyleId::new(302), - /// &Default::default(), - /// ) + /// .create_accent_phrases_from_kana("コンニチワ'", StyleId::new(302)) /// .await?; /// # /// # Ok(()) /// # } /// ``` + pub async fn create_accent_phrases_from_kana( + &self, + kana: &str, + style_id: StyleId, + ) -> Result> { + self.synthesis_engine + .replace_mora_data(&parse_kana(kana)?, style_id) + .await + } + + /// 日本語のテキストからAccentPhrase (アクセント句)の配列を生成する。 + /// + /// # Example /// #[cfg_attr(windows, doc = "```no_run")] // https://github.com/VOICEVOX/voicevox_core/issues/537 #[cfg_attr(not(windows), doc = "```")] @@ -350,39 +329,26 @@ impl Synthesizer { /// # syntesizer /// # }; /// # - /// use voicevox_core::{AccentPhrasesOptions, StyleId}; + /// use voicevox_core::StyleId; /// /// let accent_phrases = syntesizer - /// .create_accent_phrases( - /// "コンニチワ'", // AquesTalk風記法 - /// StyleId::new(302), - /// &AccentPhrasesOptions { kana: true }, - /// ) + /// .create_accent_phrases("こんにちは", StyleId::new(302)) /// .await?; /// # /// # Ok(()) /// # } /// ``` - /// - /// [`options.kana`]: crate::AccentPhrasesOptions::kana pub async fn create_accent_phrases( &self, text: &str, style_id: StyleId, - options: &AccentPhrasesOptions, ) -> Result> { if !self.synthesis_engine.is_openjtalk_dict_loaded() { return Err(ErrorRepr::NotLoadedOpenjtalkDict.into()); } - if options.kana { - self.synthesis_engine - .replace_mora_data(&parse_kana(text)?, style_id) - .await - } else { - self.synthesis_engine - .create_accent_phrases(text, style_id) - .await - } + self.synthesis_engine + .create_accent_phrases(text, style_id) + .await } /// AccentPhraseの配列の音高・音素長を、特定の声で生成しなおす。 @@ -418,12 +384,9 @@ impl Synthesizer { .await } - /// [AudioQuery]を生成する。 + /// AquesTalk風記法から[AudioQuery]を生成する。 /// - /// `text`は[`options.kana`]が有効化されているときにはAquesTalk風記法として、そうでないときには - /// 日本語のテキストとして解釈される。 - /// - /// # Examples + /// # Example /// #[cfg_attr(windows, doc = "```no_run")] // https://github.com/VOICEVOX/voicevox_core/issues/537 #[cfg_attr(not(windows), doc = "```")] @@ -459,17 +422,27 @@ impl Synthesizer { /// use voicevox_core::StyleId; /// /// let audio_query = syntesizer - /// .audio_query( - /// "こんにちは", // 日本語のテキスト - /// StyleId::new(302), - /// &Default::default(), - /// ) + /// .audio_query_from_kana("コンニチワ'", StyleId::new(302)) /// .await?; /// # /// # Ok(()) /// # } /// ``` /// + /// [AudioQuery]: crate::AudioQueryModel + pub async fn audio_query_from_kana( + &self, + kana: &str, + style_id: StyleId, + ) -> Result { + let accent_phrases = self.create_accent_phrases_from_kana(kana, style_id).await?; + Ok(AudioQueryModel::from_accent_phrases(accent_phrases).with_kana(Some(kana.to_owned()))) + } + + /// 日本語のテキストから[AudioQuery]を生成する。 + /// + /// # Examples + /// #[cfg_attr(windows, doc = "```no_run")] // https://github.com/VOICEVOX/voicevox_core/issues/537 #[cfg_attr(not(windows), doc = "```")] /// # #[tokio::main] @@ -501,14 +474,10 @@ impl Synthesizer { /// # syntesizer /// # }; /// # - /// use voicevox_core::{AudioQueryOptions, StyleId}; + /// use voicevox_core::StyleId; /// /// let audio_query = syntesizer - /// .audio_query( - /// "コンニチワ'", // AquesTalk風記法 - /// StyleId::new(302), - /// &AudioQueryOptions { kana: true }, - /// ) + /// .audio_query("こんにちは", StyleId::new(302)) /// .await?; /// # /// # Ok(()) @@ -516,46 +485,31 @@ impl Synthesizer { /// ``` /// /// [AudioQuery]: crate::AudioQueryModel - /// [`options.kana`]: crate::AudioQueryOptions::kana - pub async fn audio_query( + pub async fn audio_query(&self, text: &str, style_id: StyleId) -> Result { + let accent_phrases = self.create_accent_phrases(text, style_id).await?; + Ok(AudioQueryModel::from_accent_phrases(accent_phrases)) + } + + /// AquesTalk風記法から音声合成を行う。 + pub async fn tts_from_kana( &self, - text: &str, + kana: &str, style_id: StyleId, - options: &AudioQueryOptions, - ) -> Result { - let accent_phrases = self - .create_accent_phrases(text, style_id, &AccentPhrasesOptions { kana: options.kana }) - .await?; - let kana = create_kana(&accent_phrases); - Ok(AudioQueryModel::new( - accent_phrases, - 1., - 0., - 1., - 1., - 0.1, - 0.1, - SynthesisEngine::DEFAULT_SAMPLING_RATE, - false, - Some(kana), - )) + options: &TtsOptions, + ) -> Result> { + let audio_query = &self.audio_query_from_kana(kana, style_id).await?; + self.synthesis(audio_query, style_id, &SynthesisOptions::from(options)) + .await } - /// テキスト音声合成を行う。 - /// - /// `text`は[`options.kana`]が有効化されているときにはAquesTalk風記法として、そうでないときには - /// 日本語のテキストとして解釈される。 - /// - /// [`options.kana`]: crate::TtsOptions::kana + /// 日本語のテキストから音声合成を行う。 pub async fn tts( &self, text: &str, style_id: StyleId, options: &TtsOptions, ) -> Result> { - let audio_query = &self - .audio_query(text, style_id, &AudioQueryOptions::from(options)) - .await?; + let audio_query = &self.audio_query(text, style_id).await?; self.synthesis(audio_query, style_id, &SynthesisOptions::from(options)) .await } @@ -599,6 +553,24 @@ fn list_windows_video_cards() { } } +impl AudioQueryModel { + fn from_accent_phrases(accent_phrases: Vec) -> Self { + let kana = create_kana(&accent_phrases); + Self::new( + accent_phrases, + 1., + 0., + 1., + 1., + 0.1, + 0.1, + SynthesisEngine::DEFAULT_SAMPLING_RATE, + false, + Some(kana), + ) + } +} + #[cfg(test)] mod tests { @@ -832,21 +804,18 @@ mod tests { #[rstest] #[case( - "これはテストです", - false, + Input::Japanese("これはテストです"), TEXT_CONSONANT_VOWEL_DATA1, "コレワ'/テ'_ストデ_ス" )] #[case( - "コ'レワ/テ_スト'デ_ス", - true, + Input::Kana("コ'レワ/テ_スト'デ_ス"), TEXT_CONSONANT_VOWEL_DATA2, "コ'レワ/テ_スト'デ_ス" )] #[tokio::test] async fn audio_query_works( - #[case] input_text: &str, - #[case] input_kana_option: bool, + #[case] input: Input, #[case] expected_text_consonant_vowel_data: &TextConsonantVowelData, #[case] expected_kana_text: &str, ) { @@ -863,16 +832,15 @@ mod tests { let model = &VoiceModel::sample().await.unwrap(); syntesizer.load_voice_model(model).await.unwrap(); - let query = syntesizer - .audio_query( - input_text, - StyleId::new(0), - &AudioQueryOptions { - kana: input_kana_option, - }, - ) - .await - .unwrap(); + let query = match input { + Input::Kana(input) => { + syntesizer + .audio_query_from_kana(input, StyleId::new(0)) + .await + } + Input::Japanese(input) => syntesizer.audio_query(input, StyleId::new(0)).await, + } + .unwrap(); assert_eq!( query.accent_phrases().len(), @@ -913,12 +881,11 @@ mod tests { } #[rstest] - #[case("これはテストです", false, TEXT_CONSONANT_VOWEL_DATA1)] - #[case("コ'レワ/テ_スト'デ_ス", true, TEXT_CONSONANT_VOWEL_DATA2)] + #[case(Input::Japanese("これはテストです"), TEXT_CONSONANT_VOWEL_DATA1)] + #[case(Input::Kana("コ'レワ/テ_スト'デ_ス"), TEXT_CONSONANT_VOWEL_DATA2)] #[tokio::test] async fn accent_phrases_works( - #[case] input_text: &str, - #[case] input_kana_option: bool, + #[case] input: Input, #[case] expected_text_consonant_vowel_data: &TextConsonantVowelData, ) { let syntesizer = Synthesizer::new_with_initialize( @@ -934,16 +901,19 @@ mod tests { let model = &VoiceModel::sample().await.unwrap(); syntesizer.load_voice_model(model).await.unwrap(); - let accent_phrases = syntesizer - .create_accent_phrases( - input_text, - StyleId::new(0), - &AccentPhrasesOptions { - kana: input_kana_option, - }, - ) - .await - .unwrap(); + let accent_phrases = match input { + Input::Kana(input) => { + syntesizer + .create_accent_phrases_from_kana(input, StyleId::new(0)) + .await + } + Input::Japanese(input) => { + syntesizer + .create_accent_phrases(input, StyleId::new(0)) + .await + } + } + .unwrap(); assert_eq!( accent_phrases.len(), @@ -998,11 +968,7 @@ mod tests { syntesizer.load_voice_model(model).await.unwrap(); let accent_phrases = syntesizer - .create_accent_phrases( - "これはテストです", - StyleId::new(0), - &AccentPhrasesOptions { kana: false }, - ) + .create_accent_phrases("これはテストです", StyleId::new(0)) .await .unwrap(); @@ -1039,11 +1005,7 @@ mod tests { syntesizer.load_voice_model(model).await.unwrap(); let accent_phrases = syntesizer - .create_accent_phrases( - "これはテストです", - StyleId::new(0), - &AccentPhrasesOptions { kana: false }, - ) + .create_accent_phrases("これはテストです", StyleId::new(0)) .await .unwrap(); @@ -1076,11 +1038,7 @@ mod tests { syntesizer.load_voice_model(model).await.unwrap(); let accent_phrases = syntesizer - .create_accent_phrases( - "これはテストです", - StyleId::new(0), - &AccentPhrasesOptions { kana: false }, - ) + .create_accent_phrases("これはテストです", StyleId::new(0)) .await .unwrap(); @@ -1114,4 +1072,9 @@ mod tests { .flat_map(move |(before, after)| std::iter::zip(before.moras(), after.moras())) .any(|(before, after)| param(before) != param(after)) } + + enum Input { + Japanese(&'static str), + Kana(&'static str), + } } diff --git a/crates/voicevox_core_c_api/include/voicevox_core.h b/crates/voicevox_core_c_api/include/voicevox_core.h index 9e32bf982..d376544eb 100644 --- a/crates/voicevox_core_c_api/include/voicevox_core.h +++ b/crates/voicevox_core_c_api/include/voicevox_core.h @@ -279,16 +279,6 @@ typedef struct VoicevoxInitializeOptions { */ typedef const char *VoicevoxVoiceModelId; -/** - * ::voicevox_synthesizer_create_audio_query のオプション。 - */ -typedef struct VoicevoxAudioQueryOptions { - /** - * AquesTalk風記法としてテキストを解釈する - */ - bool kana; -} VoicevoxAudioQueryOptions; - /** * スタイルID。 * @@ -296,16 +286,6 @@ typedef struct VoicevoxAudioQueryOptions { */ typedef uint32_t VoicevoxStyleId; -/** - * ::voicevox_synthesizer_create_accent_phrases のオプション。 - */ -typedef struct VoicevoxAccentPhrasesOptions { - /** - * AquesTalk風記法としてテキストを解釈する - */ - bool kana; -} VoicevoxAccentPhrasesOptions; - /** * ::voicevox_synthesizer_synthesis のオプション。 */ @@ -320,10 +300,6 @@ typedef struct VoicevoxSynthesisOptions { * ::voicevox_synthesizer_tts のオプション。 */ typedef struct VoicevoxTtsOptions { - /** - * AquesTalk風記法としてテキストを解釈する - */ - bool kana; /** * 疑問文の調整を有効にする */ @@ -668,48 +644,61 @@ __declspec(dllimport) VoicevoxResultCode voicevox_create_supported_devices_json(char **output_supported_devices_json); /** - * デフォルトの AudioQuery のオプションを生成する - * @return デフォルト値が設定された AudioQuery オプション + * AquesTalk風記法から、AudioQueryをJSONとして生成する。 + * + * 生成したJSON文字列を解放するには ::voicevox_json_free を使う。 + * + * @param [in] synthesizer 音声シンセサイザ + * @param [in] kana AquesTalk風記法 + * @param [in] style_id スタイルID + * @param [out] output_audio_query_json 生成先 + * + * @returns 結果コード + * + * \example{ + * ```c + * char *audio_query; + * voicevox_synthesizer_create_audio_query_from_kana(synthesizer, "コンニチワ'", + * 2, // "四国めたん (ノーマル)" + * &audio_query); + * ``` + * } + * + * \safety{ + * - `synthesizer`は ::voicevox_synthesizer_new_with_initialize で得たものでなければならず、また ::voicevox_synthesizer_delete で解放されていてはいけない。 + * - `kana`はヌル終端文字列を指し、かつ読み込みについて有効でなければならない。 + * - `output_audio_query_json`は書き込みについて有効でなければならない。 + * } */ #ifdef _WIN32 __declspec(dllimport) #endif -struct VoicevoxAudioQueryOptions voicevox_make_default_audio_query_options(void); +VoicevoxResultCode voicevox_synthesizer_create_audio_query_from_kana(const struct VoicevoxSynthesizer *synthesizer, + const char *kana, + VoicevoxStyleId style_id, + char **output_audio_query_json); /** - * AudioQueryをJSONとして生成する。 + * 日本語テキストから、AudioQueryをJSONとして生成する。 * * 生成したJSON文字列を解放するには ::voicevox_json_free を使う。 * * @param [in] synthesizer 音声シンセサイザ - * @param [in] text UTF-8の日本語テキストまたはAquesTalk風記法 + * @param [in] text UTF-8の日本語テキスト * @param [in] style_id スタイルID - * @param [in] options オプション * @param [out] output_audio_query_json 生成先 * * @returns 結果コード * - * \examples{ - * ```c - * char *audio_query; - * voicevox_synthesizer_create_audio_query(synthesizer, - * "こんにちは", // 日本語テキスト - * 2, // "四国めたん (ノーマル)" - * (VoicevoxAudioQueryOptions){.kana = false}, - * &audio_query); - * ``` - * + * \example{ * ```c * char *audio_query; - * voicevox_synthesizer_create_audio_query(synthesizer, - * "コンニチワ'", // AquesTalk風記法 - * 2, // "四国めたん (ノーマル)" - * (VoicevoxAudioQueryOptions){.kana = true}, + * voicevox_synthesizer_create_audio_query(synthesizer, "こんにちは", + * 2, // "四国めたん (ノーマル)" * &audio_query); * ``` * } * - * * \safety{ * - `synthesizer`は ::voicevox_synthesizer_new_with_initialize で得たものでなければならず、また ::voicevox_synthesizer_delete で解放されていてはいけない。 * - `text`はヌル終端文字列を指し、かつ読み込みについて有効でなければならない。 @@ -722,48 +711,62 @@ __declspec(dllimport) VoicevoxResultCode voicevox_synthesizer_create_audio_query(const struct VoicevoxSynthesizer *synthesizer, const char *text, VoicevoxStyleId style_id, - struct VoicevoxAudioQueryOptions options, char **output_audio_query_json); /** - * デフォルトの `accent_phrases` のオプションを生成する - * @return デフォルト値が設定された `accent_phrases` のオプション + * AquesTalk風記法から、AccentPhrase (アクセント句)の配列をJSON形式で生成する。 + * + * 生成したJSON文字列を解放するには ::voicevox_json_free を使う。 + * + * @param [in] synthesizer 音声シンセサイザ + * @param [in] kana AquesTalk風記法 + * @param [in] style_id スタイルID + * @param [out] output_accent_phrases_json 生成先 + * + * @returns 結果コード + * + * \example{ + * ```c + * char *accent_phrases; + * voicevox_synthesizer_create_accent_phrases_from_kana( + * synthesizer, "コンニチワ'", + * 2, // "四国めたん (ノーマル)" + * &accent_phrases); + * ``` + * } + * + * \safety{ + * - `synthesizer`は ::voicevox_synthesizer_new_with_initialize で得たものでなければならず、また ::voicevox_synthesizer_delete で解放されていてはいけない。 + * - `kana`はヌル終端文字列を指し、かつ読み込みについて有効でなければならない。 + * - `output_audio_query_json`は書き込みについて有効でなければならない。 + * } */ #ifdef _WIN32 __declspec(dllimport) #endif -struct VoicevoxAccentPhrasesOptions voicevox_make_default_accent_phrases_options(void); +VoicevoxResultCode voicevox_synthesizer_create_accent_phrases_from_kana(const struct VoicevoxSynthesizer *synthesizer, + const char *kana, + VoicevoxStyleId style_id, + char **output_accent_phrases_json); /** - * AccentPhrase (アクセント句)の配列をJSON形式で生成する。 + * 日本語テキストから、AccentPhrase (アクセント句)の配列をJSON形式で生成する。 * * 生成したJSON文字列を解放するには ::voicevox_json_free を使う。 * * @param [in] synthesizer 音声シンセサイザ - * @param [in] text UTF-8の日本語テキストまたはAquesTalk風記法 + * @param [in] text UTF-8の日本語テキスト * @param [in] style_id スタイルID - * @param [in] options オプション * @param [out] output_accent_phrases_json 生成先 * * @returns 結果コード * - * \examples{ - * ```c - * char *accent_phrases; - * voicevox_synthesizer_create_accent_phrases( - * synthesizer, - * "こんにちは", // 日本語テキスト - * 2, // "四国めたん (ノーマル)" - * voicevox_default_accent_phrases_options, &accent_phrases); - * ``` - * + * \example{ * ```c * char *accent_phrases; - * voicevox_synthesizer_create_accent_phrases( - * synthesizer, - * "コンニチワ'", // AquesTalk風記法 - * 2, // "四国めたん (ノーマル)" - * (VoicevoxAccentPhrasesOptions){.kana = true}, &accent_phrases); + * voicevox_synthesizer_create_accent_phrases(synthesizer, "こんにちは", + * 2, // "四国めたん (ノーマル)" + * &accent_phrases); * ``` * } * @@ -779,7 +782,6 @@ __declspec(dllimport) VoicevoxResultCode voicevox_synthesizer_create_accent_phrases(const struct VoicevoxSynthesizer *synthesizer, const char *text, VoicevoxStyleId style_id, - struct VoicevoxAccentPhrasesOptions options, char **output_accent_phrases_json); /** @@ -910,12 +912,43 @@ __declspec(dllimport) struct VoicevoxTtsOptions voicevox_make_default_tts_options(void); /** - * テキスト音声合成を行う。 + * AquesTalk風記法から音声合成を行う。 + * + * 生成したWAVデータを解放するには ::voicevox_wav_free を使う。 + * + * @param [in] synthesizer + * @param [in] kana AquesTalk風記法 + * @param [in] style_id スタイルID + * @param [in] options オプション + * @param [out] output_wav_length 出力のバイト長 + * @param [out] output_wav 出力先 + * + * @returns 結果コード + * + * \safety{ + * - `synthesizer`は ::voicevox_synthesizer_new_with_initialize で得たものでなければならず、また ::voicevox_synthesizer_delete で解放されていてはいけない。 + * - `kana`はヌル終端文字列を指し、かつ読み込みについて有効でなければならない。 + * - `output_wav_length`は書き込みについて有効でなければならない。 + * - `output_wav`は書き込みについて有効でなければならない。 + * } + */ +#ifdef _WIN32 +__declspec(dllimport) +#endif +VoicevoxResultCode voicevox_synthesizer_tts_from_kana(const struct VoicevoxSynthesizer *synthesizer, + const char *kana, + VoicevoxStyleId style_id, + struct VoicevoxTtsOptions options, + uintptr_t *output_wav_length, + uint8_t **output_wav); + +/** + * 日本語テキストから音声合成を行う。 * * 生成したWAVデータを解放するには ::voicevox_wav_free を使う。 * * @param [in] synthesizer - * @param [in] text UTF-8の日本語テキストまたはAquesTalk風記法 + * @param [in] text UTF-8の日本語テキスト * @param [in] style_id スタイルID * @param [in] options オプション * @param [out] output_wav_length 出力のバイト長 diff --git a/crates/voicevox_core_c_api/src/helpers.rs b/crates/voicevox_core_c_api/src/helpers.rs index 8b32607be..bd1c95c8f 100644 --- a/crates/voicevox_core_c_api/src/helpers.rs +++ b/crates/voicevox_core_c_api/src/helpers.rs @@ -85,28 +85,6 @@ pub(crate) fn ensure_utf8(s: &CStr) -> CApiResult<&str> { s.to_str().map_err(|_| CApiError::InvalidUtf8Input) } -impl From for VoicevoxAudioQueryOptions { - fn from(options: voicevox_core::AudioQueryOptions) -> Self { - Self { kana: options.kana } - } -} -impl From for voicevox_core::AudioQueryOptions { - fn from(options: VoicevoxAudioQueryOptions) -> Self { - Self { kana: options.kana } - } -} - -impl From for VoicevoxAccentPhrasesOptions { - fn from(options: voicevox_core::AccentPhrasesOptions) -> Self { - Self { kana: options.kana } - } -} -impl From for voicevox_core::AccentPhrasesOptions { - fn from(options: VoicevoxAccentPhrasesOptions) -> Self { - Self { kana: options.kana } - } -} - impl From for voicevox_core::SynthesisOptions { fn from(options: VoicevoxSynthesisOptions) -> Self { Self { @@ -159,7 +137,6 @@ impl From for voicevox_core::InitializeOptions { impl From for VoicevoxTtsOptions { fn from(options: voicevox_core::TtsOptions) -> Self { Self { - kana: options.kana, enable_interrogative_upspeak: options.enable_interrogative_upspeak, } } @@ -168,7 +145,6 @@ impl From for VoicevoxTtsOptions { impl From for voicevox_core::TtsOptions { fn from(options: VoicevoxTtsOptions) -> Self { Self { - kana: options.kana, enable_interrogative_upspeak: options.enable_interrogative_upspeak, } } diff --git a/crates/voicevox_core_c_api/src/lib.rs b/crates/voicevox_core_c_api/src/lib.rs index 4c692b85d..9bc6698b4 100644 --- a/crates/voicevox_core_c_api/src/lib.rs +++ b/crates/voicevox_core_c_api/src/lib.rs @@ -30,8 +30,8 @@ use tracing_subscriber::fmt::format::Writer; use tracing_subscriber::EnvFilter; use uuid::Uuid; use voicevox_core::{ - AccentPhraseModel, AudioQueryModel, AudioQueryOptions, OpenJtalk, TtsOptions, UserDictWord, - VoiceModel, VoiceModelId, + AccentPhraseModel, AudioQueryModel, OpenJtalk, TtsOptions, UserDictWord, VoiceModel, + VoiceModelId, }; use voicevox_core::{StyleId, SupportedDevices, SynthesisOptions, Synthesizer}; @@ -500,53 +500,75 @@ pub unsafe extern "C" fn voicevox_create_supported_devices_json( })()) } -/// ::voicevox_synthesizer_create_audio_query のオプション。 -#[repr(C)] -pub struct VoicevoxAudioQueryOptions { - /// AquesTalk風記法としてテキストを解釈する - kana: bool, -} - -/// デフォルトの AudioQuery のオプションを生成する -/// @return デフォルト値が設定された AudioQuery オプション -#[no_mangle] -pub extern "C" fn voicevox_make_default_audio_query_options() -> VoicevoxAudioQueryOptions { - voicevox_core::AudioQueryOptions::default().into() -} - -/// AudioQueryをJSONとして生成する。 +/// AquesTalk風記法から、AudioQueryをJSONとして生成する。 /// /// 生成したJSON文字列を解放するには ::voicevox_json_free を使う。 /// /// @param [in] synthesizer 音声シンセサイザ -/// @param [in] text UTF-8の日本語テキストまたはAquesTalk風記法 +/// @param [in] kana AquesTalk風記法 /// @param [in] style_id スタイルID -/// @param [in] options オプション /// @param [out] output_audio_query_json 生成先 /// /// @returns 結果コード /// -/// \examples{ +/// \example{ /// ```c /// char *audio_query; -/// voicevox_synthesizer_create_audio_query(synthesizer, -/// "こんにちは", // 日本語テキスト -/// 2, // "四国めたん (ノーマル)" -/// (VoicevoxAudioQueryOptions){.kana = false}, -/// &audio_query); +/// voicevox_synthesizer_create_audio_query_from_kana(synthesizer, "コンニチワ'", +/// 2, // "四国めたん (ノーマル)" +/// &audio_query); /// ``` +/// } +/// +/// \safety{ +/// - `synthesizer`は ::voicevox_synthesizer_new_with_initialize で得たものでなければならず、また ::voicevox_synthesizer_delete で解放されていてはいけない。 +/// - `kana`はヌル終端文字列を指し、かつ読み込みについて有効でなければならない。 +/// - `output_audio_query_json`は書き込みについて有効でなければならない。 +/// } +#[no_mangle] +pub unsafe extern "C" fn voicevox_synthesizer_create_audio_query_from_kana( + synthesizer: &VoicevoxSynthesizer, + kana: *const c_char, + style_id: VoicevoxStyleId, + output_audio_query_json: NonNull<*mut c_char>, +) -> VoicevoxResultCode { + into_result_code_with_error((|| { + let kana = CStr::from_ptr(kana); + let kana = ensure_utf8(kana)?; + let audio_query = RUNTIME.block_on( + synthesizer + .synthesizer() + .audio_query_from_kana(kana, StyleId::new(style_id)), + )?; + let audio_query = CString::new(audio_query_model_to_json(&audio_query)) + .expect("should not contain '\\0'"); + output_audio_query_json + .as_ptr() + .write_unaligned(C_STRING_DROP_CHECKER.whitelist(audio_query).into_raw()); + Ok(()) + })()) +} + +/// 日本語テキストから、AudioQueryをJSONとして生成する。 +/// +/// 生成したJSON文字列を解放するには ::voicevox_json_free を使う。 +/// +/// @param [in] synthesizer 音声シンセサイザ +/// @param [in] text UTF-8の日本語テキスト +/// @param [in] style_id スタイルID +/// @param [out] output_audio_query_json 生成先 +/// +/// @returns 結果コード /// +/// \example{ /// ```c /// char *audio_query; -/// voicevox_synthesizer_create_audio_query(synthesizer, -/// "コンニチワ'", // AquesTalk風記法 -/// 2, // "四国めたん (ノーマル)" -/// (VoicevoxAudioQueryOptions){.kana = true}, +/// voicevox_synthesizer_create_audio_query(synthesizer, "こんにちは", +/// 2, // "四国めたん (ノーマル)" /// &audio_query); /// ``` /// } /// -/// /// \safety{ /// - `synthesizer`は ::voicevox_synthesizer_new_with_initialize で得たものでなければならず、また ::voicevox_synthesizer_delete で解放されていてはいけない。 /// - `text`はヌル終端文字列を指し、かつ読み込みについて有効でなければならない。 @@ -557,17 +579,16 @@ pub unsafe extern "C" fn voicevox_synthesizer_create_audio_query( synthesizer: &VoicevoxSynthesizer, text: *const c_char, style_id: VoicevoxStyleId, - options: VoicevoxAudioQueryOptions, output_audio_query_json: NonNull<*mut c_char>, ) -> VoicevoxResultCode { into_result_code_with_error((|| { let text = CStr::from_ptr(text); - let japanese_or_kana = ensure_utf8(text)?; - let audio_query = RUNTIME.block_on(synthesizer.synthesizer().audio_query( - japanese_or_kana, - StyleId::new(style_id), - &AudioQueryOptions::from(options), - ))?; + let text = ensure_utf8(text)?; + let audio_query = RUNTIME.block_on( + synthesizer + .synthesizer() + .audio_query(text, StyleId::new(style_id)), + )?; let audio_query = CString::new(audio_query_model_to_json(&audio_query)) .expect("should not contain '\\0'"); output_audio_query_json @@ -577,49 +598,72 @@ pub unsafe extern "C" fn voicevox_synthesizer_create_audio_query( })()) } -/// ::voicevox_synthesizer_create_accent_phrases のオプション。 -#[repr(C)] -pub struct VoicevoxAccentPhrasesOptions { - /// AquesTalk風記法としてテキストを解釈する - kana: bool, -} - -/// デフォルトの `accent_phrases` のオプションを生成する -/// @return デフォルト値が設定された `accent_phrases` のオプション -#[no_mangle] -pub extern "C" fn voicevox_make_default_accent_phrases_options() -> VoicevoxAccentPhrasesOptions { - voicevox_core::AccentPhrasesOptions::default().into() -} - -/// AccentPhrase (アクセント句)の配列をJSON形式で生成する。 +/// AquesTalk風記法から、AccentPhrase (アクセント句)の配列をJSON形式で生成する。 /// /// 生成したJSON文字列を解放するには ::voicevox_json_free を使う。 /// /// @param [in] synthesizer 音声シンセサイザ -/// @param [in] text UTF-8の日本語テキストまたはAquesTalk風記法 +/// @param [in] kana AquesTalk風記法 /// @param [in] style_id スタイルID -/// @param [in] options オプション /// @param [out] output_accent_phrases_json 生成先 /// /// @returns 結果コード /// -/// \examples{ +/// \example{ /// ```c /// char *accent_phrases; -/// voicevox_synthesizer_create_accent_phrases( -/// synthesizer, -/// "こんにちは", // 日本語テキスト -/// 2, // "四国めたん (ノーマル)" -/// voicevox_default_accent_phrases_options, &accent_phrases); +/// voicevox_synthesizer_create_accent_phrases_from_kana( +/// synthesizer, "コンニチワ'", +/// 2, // "四国めたん (ノーマル)" +/// &accent_phrases); /// ``` +/// } /// +/// \safety{ +/// - `synthesizer`は ::voicevox_synthesizer_new_with_initialize で得たものでなければならず、また ::voicevox_synthesizer_delete で解放されていてはいけない。 +/// - `kana`はヌル終端文字列を指し、かつ読み込みについて有効でなければならない。 +/// - `output_audio_query_json`は書き込みについて有効でなければならない。 +/// } +#[no_mangle] +pub unsafe extern "C" fn voicevox_synthesizer_create_accent_phrases_from_kana( + synthesizer: &VoicevoxSynthesizer, + kana: *const c_char, + style_id: VoicevoxStyleId, + output_accent_phrases_json: NonNull<*mut c_char>, +) -> VoicevoxResultCode { + into_result_code_with_error((|| { + let kana = ensure_utf8(CStr::from_ptr(kana))?; + let accent_phrases = RUNTIME.block_on( + synthesizer + .synthesizer() + .create_accent_phrases_from_kana(kana, StyleId::new(style_id)), + )?; + let accent_phrases = CString::new(accent_phrases_to_json(&accent_phrases)) + .expect("should not contain '\\0'"); + output_accent_phrases_json + .as_ptr() + .write_unaligned(C_STRING_DROP_CHECKER.whitelist(accent_phrases).into_raw()); + Ok(()) + })()) +} + +/// 日本語テキストから、AccentPhrase (アクセント句)の配列をJSON形式で生成する。 +/// +/// 生成したJSON文字列を解放するには ::voicevox_json_free を使う。 +/// +/// @param [in] synthesizer 音声シンセサイザ +/// @param [in] text UTF-8の日本語テキスト +/// @param [in] style_id スタイルID +/// @param [out] output_accent_phrases_json 生成先 +/// +/// @returns 結果コード +/// +/// \example{ /// ```c /// char *accent_phrases; -/// voicevox_synthesizer_create_accent_phrases( -/// synthesizer, -/// "コンニチワ'", // AquesTalk風記法 -/// 2, // "四国めたん (ノーマル)" -/// (VoicevoxAccentPhrasesOptions){.kana = true}, &accent_phrases); +/// voicevox_synthesizer_create_accent_phrases(synthesizer, "こんにちは", +/// 2, // "四国めたん (ノーマル)" +/// &accent_phrases); /// ``` /// } /// @@ -633,16 +677,15 @@ pub unsafe extern "C" fn voicevox_synthesizer_create_accent_phrases( synthesizer: &VoicevoxSynthesizer, text: *const c_char, style_id: VoicevoxStyleId, - options: VoicevoxAccentPhrasesOptions, output_accent_phrases_json: NonNull<*mut c_char>, ) -> VoicevoxResultCode { into_result_code_with_error((|| { let text = ensure_utf8(CStr::from_ptr(text))?; - let accent_phrases = RUNTIME.block_on(synthesizer.synthesizer().create_accent_phrases( - text, - StyleId::new(style_id), - &options.into(), - ))?; + let accent_phrases = RUNTIME.block_on( + synthesizer + .synthesizer() + .create_accent_phrases(text, StyleId::new(style_id)), + )?; let accent_phrases = CString::new(accent_phrases_to_json(&accent_phrases)) .expect("should not contain '\\0'"); output_accent_phrases_json @@ -836,8 +879,6 @@ pub unsafe extern "C" fn voicevox_synthesizer_synthesis( /// ::voicevox_synthesizer_tts のオプション。 #[repr(C)] pub struct VoicevoxTtsOptions { - /// AquesTalk風記法としてテキストを解釈する - kana: bool, /// 疑問文の調整を有効にする enable_interrogative_upspeak: bool, } @@ -849,12 +890,52 @@ pub extern "C" fn voicevox_make_default_tts_options() -> VoicevoxTtsOptions { voicevox_core::TtsOptions::default().into() } -/// テキスト音声合成を行う。 +/// AquesTalk風記法から音声合成を行う。 +/// +/// 生成したWAVデータを解放するには ::voicevox_wav_free を使う。 +/// +/// @param [in] synthesizer +/// @param [in] kana AquesTalk風記法 +/// @param [in] style_id スタイルID +/// @param [in] options オプション +/// @param [out] output_wav_length 出力のバイト長 +/// @param [out] output_wav 出力先 +/// +/// @returns 結果コード +/// +/// \safety{ +/// - `synthesizer`は ::voicevox_synthesizer_new_with_initialize で得たものでなければならず、また ::voicevox_synthesizer_delete で解放されていてはいけない。 +/// - `kana`はヌル終端文字列を指し、かつ読み込みについて有効でなければならない。 +/// - `output_wav_length`は書き込みについて有効でなければならない。 +/// - `output_wav`は書き込みについて有効でなければならない。 +/// } +#[no_mangle] +pub unsafe extern "C" fn voicevox_synthesizer_tts_from_kana( + synthesizer: &VoicevoxSynthesizer, + kana: *const c_char, + style_id: VoicevoxStyleId, + options: VoicevoxTtsOptions, + output_wav_length: NonNull, + output_wav: NonNull<*mut u8>, +) -> VoicevoxResultCode { + into_result_code_with_error((|| { + let kana = ensure_utf8(CStr::from_ptr(kana))?; + let output = RUNTIME.block_on(synthesizer.synthesizer().tts_from_kana( + kana, + StyleId::new(style_id), + &TtsOptions::from(options), + ))?; + U8_SLICE_OWNER.own_and_lend(output, output_wav, output_wav_length); + Ok(()) + })()) +} + +/// 日本語テキストから音声合成を行う。 /// /// 生成したWAVデータを解放するには ::voicevox_wav_free を使う。 /// /// @param [in] synthesizer -/// @param [in] text UTF-8の日本語テキストまたはAquesTalk風記法 +/// @param [in] text UTF-8の日本語テキスト /// @param [in] style_id スタイルID /// @param [in] options オプション /// @param [out] output_wav_length 出力のバイト長 diff --git a/crates/voicevox_core_c_api/tests/e2e/symbols.rs b/crates/voicevox_core_c_api/tests/e2e/symbols.rs index afcb105b2..ae929ad0f 100644 --- a/crates/voicevox_core_c_api/tests/e2e/symbols.rs +++ b/crates/voicevox_core_c_api/tests/e2e/symbols.rs @@ -59,15 +59,21 @@ pub(crate) struct Symbols<'lib> { Symbol<'lib, unsafe extern "C" fn(*const VoicevoxSynthesizer) -> *mut c_char>, pub(crate) voicevox_create_supported_devices_json: Symbol<'lib, unsafe extern "C" fn(*mut *mut c_char) -> VoicevoxResultCode>, - pub(crate) voicevox_make_default_audio_query_options: - Symbol<'lib, unsafe extern "C" fn() -> VoicevoxAudioQueryOptions>, + pub(crate) voicevox_synthesizer_create_audio_query_from_kana: Symbol< + 'lib, + unsafe extern "C" fn( + *const VoicevoxSynthesizer, + *const c_char, + VoicevoxStyleId, + *mut *mut c_char, + ) -> VoicevoxResultCode, + >, pub(crate) voicevox_synthesizer_create_audio_query: Symbol< 'lib, unsafe extern "C" fn( *const VoicevoxSynthesizer, *const c_char, VoicevoxStyleId, - VoicevoxAudioQueryOptions, *mut *mut c_char, ) -> VoicevoxResultCode, >, @@ -86,6 +92,17 @@ pub(crate) struct Symbols<'lib> { >, pub(crate) voicevox_make_default_tts_options: Symbol<'lib, unsafe extern "C" fn() -> VoicevoxTtsOptions>, + pub(crate) voicevox_synthesizer_tts_from_kana: Symbol< + 'lib, + unsafe extern "C" fn( + *const VoicevoxSynthesizer, + *const c_char, + VoicevoxStyleId, + VoicevoxTtsOptions, + *mut usize, + *mut *mut u8, + ) -> VoicevoxResultCode, + >, pub(crate) voicevox_synthesizer_tts: Symbol< 'lib, unsafe extern "C" fn( @@ -205,11 +222,12 @@ impl<'lib> Symbols<'lib> { voicevox_synthesizer_is_loaded_voice_model, voicevox_synthesizer_create_metas_json, voicevox_create_supported_devices_json, - voicevox_make_default_audio_query_options, + voicevox_synthesizer_create_audio_query_from_kana, voicevox_synthesizer_create_audio_query, voicevox_make_default_synthesis_options, voicevox_synthesizer_synthesis, voicevox_make_default_tts_options, + voicevox_synthesizer_tts_from_kana, voicevox_synthesizer_tts, voicevox_json_free, voicevox_wav_free, @@ -286,11 +304,6 @@ pub(crate) struct VoicevoxInitializeOptions { pub(crate) _cpu_num_threads: u16, } -#[repr(C)] -pub(crate) struct VoicevoxAudioQueryOptions { - _kana: bool, -} - #[repr(C)] pub(crate) struct VoicevoxSynthesisOptions { _enable_interrogative_upspeak: bool, @@ -298,7 +311,6 @@ pub(crate) struct VoicevoxSynthesisOptions { #[repr(C)] pub(crate) struct VoicevoxTtsOptions { - _kana: bool, _enable_interrogative_upspeak: bool, } diff --git a/crates/voicevox_core_c_api/tests/e2e/testcases/tts_via_audio_query.rs b/crates/voicevox_core_c_api/tests/e2e/testcases/tts_via_audio_query.rs index 0ff0ef6e9..83a9d7bcf 100644 --- a/crates/voicevox_core_c_api/tests/e2e/testcases/tts_via_audio_query.rs +++ b/crates/voicevox_core_c_api/tests/e2e/testcases/tts_via_audio_query.rs @@ -34,7 +34,6 @@ impl assert_cdylib::TestCase for TestCase { voicevox_synthesizer_new_with_initialize, voicevox_synthesizer_delete, voicevox_synthesizer_load_voice_model, - voicevox_make_default_audio_query_options, voicevox_synthesizer_create_audio_query, voicevox_make_default_synthesis_options, voicevox_synthesizer_synthesis, @@ -84,7 +83,6 @@ impl assert_cdylib::TestCase for TestCase { synthesizer, text.as_ptr(), STYLE_ID, - voicevox_make_default_audio_query_options(), audio_query.as_mut_ptr(), )); audio_query.assume_init() diff --git a/crates/voicevox_core_c_api/tests/e2e/testcases/user_dict_load.rs b/crates/voicevox_core_c_api/tests/e2e/testcases/user_dict_load.rs index b6f69e7a1..85cd40dc2 100644 --- a/crates/voicevox_core_c_api/tests/e2e/testcases/user_dict_load.rs +++ b/crates/voicevox_core_c_api/tests/e2e/testcases/user_dict_load.rs @@ -32,7 +32,6 @@ impl assert_cdylib::TestCase for TestCase { voicevox_user_dict_add_word, voicevox_user_dict_delete, voicevox_make_default_initialize_options, - voicevox_make_default_audio_query_options, voicevox_open_jtalk_rc_new, voicevox_open_jtalk_rc_use_user_dict, voicevox_open_jtalk_rc_delete, @@ -101,7 +100,6 @@ impl assert_cdylib::TestCase for TestCase { synthesizer, cstr!("this_word_should_not_exist_in_default_dictionary").as_ptr(), STYLE_ID, - voicevox_make_default_audio_query_options(), &mut audio_query_without_dict, )); let audio_query_without_dict = serde_json::from_str::( @@ -115,7 +113,6 @@ impl assert_cdylib::TestCase for TestCase { synthesizer, cstr!("this_word_should_not_exist_in_default_dictionary").as_ptr(), STYLE_ID, - voicevox_make_default_audio_query_options(), &mut audio_query_with_dict, )); diff --git a/crates/voicevox_core_java_api/lib/src/main/java/jp/hiroshiba/voicevoxcore/Synthesizer.java b/crates/voicevox_core_java_api/lib/src/main/java/jp/hiroshiba/voicevoxcore/Synthesizer.java index 5739b14f7..5f3df9ea8 100644 --- a/crates/voicevox_core_java_api/lib/src/main/java/jp/hiroshiba/voicevoxcore/Synthesizer.java +++ b/crates/voicevox_core_java_api/lib/src/main/java/jp/hiroshiba/voicevoxcore/Synthesizer.java @@ -52,29 +52,83 @@ public boolean isLoadedVoiceModel(String voiceModelId) { } /** - * {@link AudioQuery} を生成するためのオブジェクトを生成する。 + * AquesTalk風記法から {@link AudioQuery} を生成する。 * - * @param text テキスト。 + * @param kana AquesTalk風記法。 * @param styleId スタイルID。 - * @return {@link CreateAudioQueryConfigurator}。 - * @see CreateAudioQueryConfigurator#execute + * @return {@link AudioQuery}。 */ @Nonnull - public CreateAudioQueryConfigurator createAudioQuery(String text, int styleId) { - return new CreateAudioQueryConfigurator(this, text, styleId); + public AudioQuery createAudioQueryFromKana(String kana, int styleId) { + if (!Utils.isU32(styleId)) { + throw new IllegalArgumentException("styleId"); + } + String queryJson = rsAudioQueryFromKana(kana, styleId); + Gson gson = new Gson(); + + AudioQuery audioQuery = gson.fromJson(queryJson, AudioQuery.class); + if (audioQuery == null) { + throw new NullPointerException("audio_query"); + } + return audioQuery; + } + + /** + * 日本語のテキストから {@link AudioQuery} を生成する。 + * + * @param text 日本語のテキスト。 + * @param styleId スタイルID。 + * @return {@link AudioQuery}。 + */ + @Nonnull + public AudioQuery createAudioQuery(String text, int styleId) { + if (!Utils.isU32(styleId)) { + throw new IllegalArgumentException("styleId"); + } + String queryJson = rsAudioQuery(text, styleId); + Gson gson = new Gson(); + + AudioQuery audioQuery = gson.fromJson(queryJson, AudioQuery.class); + if (audioQuery == null) { + throw new NullPointerException("audio_query"); + } + return audioQuery; + } + + /** + * AquesTalk風記法から {@link AccentPhrase} のリストを生成する。 + * + * @param kana AquesTalk風記法。 + * @param styleId スタイルID。 + * @return {@link AccentPhrase} のリスト。 + */ + @Nonnull + public List createAccentPhrasesFromKana(String kana, int styleId) { + String accentPhrasesJson = rsAccentPhrasesFromKana(kana, styleId); + Gson gson = new Gson(); + AccentPhrase[] rawAccentPhrases = gson.fromJson(accentPhrasesJson, AccentPhrase[].class); + if (rawAccentPhrases == null) { + throw new NullPointerException("accent_phrases"); + } + return new ArrayList(Arrays.asList(rawAccentPhrases)); } /** - * {@link AccentPhrase} のリストを生成するためのオブジェクトを生成する。 + * 日本語のテキストから {@link AccentPhrase} のリストを生成する。 * - * @param text テキスト。 + * @param text 日本語のテキスト。 * @param styleId スタイルID。 - * @return {@link CreateAccentPhrasesConfigurator}。 - * @see CreateAccentPhrasesConfigurator#execute + * @return {@link AccentPhrase} のリスト。 */ @Nonnull - public CreateAccentPhrasesConfigurator createAccentPhrases(String text, int styleId) { - return new CreateAccentPhrasesConfigurator(this, text, styleId); + public List createAccentPhrases(String text, int styleId) { + String accentPhrasesJson = rsAccentPhrases(text, styleId); + Gson gson = new Gson(); + AccentPhrase[] rawAccentPhrases = gson.fromJson(accentPhrasesJson, AccentPhrase[].class); + if (rawAccentPhrases == null) { + throw new NullPointerException("accent_phrases"); + } + return new ArrayList(Arrays.asList(rawAccentPhrases)); } /** @@ -145,9 +199,22 @@ public SynthesisConfigurator synthesis(AudioQuery audioQuery, int styleId) { } /** - * テキスト音声合成を実行するためのオブジェクトを生成する。 + * AquesTalk風記法をもとに音声合成を実行するためのオブジェクトを生成する。 * - * @param text テキスト。 + * @param kana AquesTalk風記法。 + * @param styleId スタイルID。 + * @return {@link TtsFromKanaConfigurator}。 + * @see TtsFromKanaConfigurator#execute + */ + @Nonnull + public TtsFromKanaConfigurator ttsFromKana(String kana, int styleId) { + return new TtsFromKanaConfigurator(this, kana, styleId); + } + + /** + * 日本語のテキストをもとに音声合成を実行するためのオブジェクトを生成する。 + * + * @param text 日本語のテキスト。 * @param styleId スタイルID。 * @return {@link TtsConfigurator}。 * @see TtsConfigurator#execute @@ -166,10 +233,16 @@ public TtsConfigurator tts(String text, int styleId) { private native boolean rsIsLoadedVoiceModel(String voiceModelId); @Nonnull - private native String rsAudioQuery(String text, int styleId, boolean kana); + private native String rsAudioQueryFromKana(String kana, int styleId); + + @Nonnull + private native String rsAudioQuery(String text, int styleId); @Nonnull - private native String rsAccentPhrases(String text, int styleId, boolean kana); + private native String rsAccentPhrasesFromKana(String kana, int styleId); + + @Nonnull + private native String rsAccentPhrases(String text, int styleId); @Nonnull private native String rsReplaceMoraData(String accentPhrasesJson, int styleId, boolean kana); @@ -185,8 +258,10 @@ private native byte[] rsSynthesis( String queryJson, int styleId, boolean enableInterrogativeUpspeak); @Nonnull - private native byte[] rsTts( - String text, int styleId, boolean kana, boolean enableInterrogativeUpspeak); + private native byte[] rsTtsFromKana(String kana, int styleId, boolean enableInterrogativeUpspeak); + + @Nonnull + private native byte[] rsTts(String text, int styleId, boolean enableInterrogativeUpspeak); private native void rsDrop(); @@ -258,117 +333,65 @@ public static enum AccelerationMode { GPU, } - /** {@link Synthesizer#createAudioQuery} のオプション。 */ - public class CreateAudioQueryConfigurator { + /** {@link Synthesizer#synthesis} のオプション。 */ + public class SynthesisConfigurator { private Synthesizer synthesizer; - private String text; + private AudioQuery audioQuery; private int styleId; - private boolean kana; + private boolean interrogativeUpspeak; - private CreateAudioQueryConfigurator(Synthesizer synthesizer, String text, int styleId) { + private SynthesisConfigurator(Synthesizer synthesizer, AudioQuery audioQuery, int styleId) { if (!Utils.isU32(styleId)) { throw new IllegalArgumentException("styleId"); } this.synthesizer = synthesizer; - this.text = text; + this.audioQuery = audioQuery; this.styleId = styleId; - this.kana = false; + this.interrogativeUpspeak = false; } /** - * 入力テキストをAquesTalk風記法として解釈するかどうか。 + * 疑問文の調整を有効にするかどうか。 * - * @param kana 入力テキストをAquesTalk風記法として解釈するかどうか。 - * @return {@link CreateAudioQueryConfigurator}。 + * @param interrogativeUpspeak 疑問文の調整を有効にするかどうか。 + * @return {@link SynthesisConfigurator}。 */ @Nonnull - public CreateAudioQueryConfigurator kana(boolean kana) { - this.kana = kana; + public SynthesisConfigurator interrogativeUpspeak(boolean interrogativeUpspeak) { + this.interrogativeUpspeak = interrogativeUpspeak; return this; } /** - * {@link AudioQuery} を生成する。 + * {@link AudioQuery} から音声合成する。 * - * @return {@link AudioQuery}。 + * @return 音声データ。 */ @Nonnull - public AudioQuery execute() { - if (!Utils.isU32(styleId)) { - throw new IllegalArgumentException("styleId"); - } - String queryJson = synthesizer.rsAudioQuery(this.text, this.styleId, this.kana); - Gson gson = new Gson(); - - AudioQuery audioQuery = gson.fromJson(queryJson, AudioQuery.class); - if (audioQuery == null) { - throw new NullPointerException("audio_query"); - } - return audioQuery; - } - } - - /** {@link Synthesizer#createAccentPhrases} のオプション。 */ - public class CreateAccentPhrasesConfigurator { - private Synthesizer synthesizer; - private String text; - private int styleId; - private boolean kana; - - private CreateAccentPhrasesConfigurator(Synthesizer synthesizer, String text, int styleId) { + public byte[] execute() { if (!Utils.isU32(styleId)) { throw new IllegalArgumentException("styleId"); } - this.synthesizer = synthesizer; - this.text = text; - this.styleId = styleId; - this.kana = false; - } - - /** - * 入力テキストをAquesTalk風記法として解釈するかどうか。 - * - * @param kana 入力テキストをAquesTalk風記法として解釈するかどうか。 - * @return {@link CreateAudioQueryConfigurator}。 - */ - @Nonnull - public CreateAccentPhrasesConfigurator kana(boolean kana) { - this.kana = kana; - return this; - } - - /** - * {@link AccentPhrase} のリストを取得する。 - * - * @return {@link AccentPhrase} のリスト。 - */ - @Nonnull - public List execute() { - String accentPhrasesJson = synthesizer.rsAccentPhrases(this.text, this.styleId, this.kana); Gson gson = new Gson(); - AccentPhrase[] rawAccentPhrases = gson.fromJson(accentPhrasesJson, AccentPhrase[].class); - if (rawAccentPhrases == null) { - throw new NullPointerException("accent_phrases"); - } - return new ArrayList(Arrays.asList(rawAccentPhrases)); + String queryJson = gson.toJson(this.audioQuery); + return synthesizer.rsSynthesis(queryJson, this.styleId, this.interrogativeUpspeak); } } - /** {@link Synthesizer#synthesis} のオプション。 */ - public class SynthesisConfigurator { + /** {@link Synthesizer#ttsFromKana} のオプション。 */ + public class TtsFromKanaConfigurator { private Synthesizer synthesizer; - private AudioQuery audioQuery; + private String kana; private int styleId; private boolean interrogativeUpspeak; - private SynthesisConfigurator(Synthesizer synthesizer, AudioQuery audioQuery, int styleId) { + private TtsFromKanaConfigurator(Synthesizer synthesizer, String kana, int styleId) { if (!Utils.isU32(styleId)) { throw new IllegalArgumentException("styleId"); } this.synthesizer = synthesizer; - this.audioQuery = audioQuery; + this.kana = kana; this.styleId = styleId; - this.interrogativeUpspeak = false; } /** @@ -378,7 +401,7 @@ private SynthesisConfigurator(Synthesizer synthesizer, AudioQuery audioQuery, in * @return {@link SynthesisConfigurator}。 */ @Nonnull - public SynthesisConfigurator interrogativeUpspeak(boolean interrogativeUpspeak) { + public TtsFromKanaConfigurator interrogativeUpspeak(boolean interrogativeUpspeak) { this.interrogativeUpspeak = interrogativeUpspeak; return this; } @@ -393,9 +416,7 @@ public byte[] execute() { if (!Utils.isU32(styleId)) { throw new IllegalArgumentException("styleId"); } - Gson gson = new Gson(); - String queryJson = gson.toJson(this.audioQuery); - return synthesizer.rsSynthesis(queryJson, this.styleId, this.interrogativeUpspeak); + return synthesizer.rsTtsFromKana(this.kana, this.styleId, this.interrogativeUpspeak); } } @@ -404,7 +425,6 @@ public class TtsConfigurator { private Synthesizer synthesizer; private String text; private int styleId; - private boolean kana; private boolean interrogativeUpspeak; private TtsConfigurator(Synthesizer synthesizer, String text, int styleId) { @@ -414,19 +434,6 @@ private TtsConfigurator(Synthesizer synthesizer, String text, int styleId) { this.synthesizer = synthesizer; this.text = text; this.styleId = styleId; - this.kana = false; - } - - /** - * 入力テキストをAquesTalk風記法として解釈するかどうか。 - * - * @param kana 入力テキストをAquesTalk風記法として解釈するかどうか。 - * @return {@link CreateAudioQueryConfigurator}。 - */ - @Nonnull - public TtsConfigurator kana(boolean kana) { - this.kana = kana; - return this; } /** @@ -451,7 +458,7 @@ public byte[] execute() { if (!Utils.isU32(styleId)) { throw new IllegalArgumentException("styleId"); } - return synthesizer.rsTts(this.text, this.styleId, this.kana, this.interrogativeUpspeak); + return synthesizer.rsTts(this.text, this.styleId, this.interrogativeUpspeak); } } } diff --git a/crates/voicevox_core_java_api/lib/src/test/java/jp/hiroshiba/voicevoxcore/SynthesizerTest.java b/crates/voicevox_core_java_api/lib/src/test/java/jp/hiroshiba/voicevoxcore/SynthesizerTest.java index fe9b8220b..f5cdaea66 100644 --- a/crates/voicevox_core_java_api/lib/src/test/java/jp/hiroshiba/voicevoxcore/SynthesizerTest.java +++ b/crates/voicevox_core_java_api/lib/src/test/java/jp/hiroshiba/voicevoxcore/SynthesizerTest.java @@ -50,7 +50,7 @@ void checkAudioQuery() { OpenJtalk openJtalk = loadOpenJtalk(); Synthesizer synthesizer = Synthesizer.builder(openJtalk).build(); synthesizer.loadVoiceModel(model); - AudioQuery query = synthesizer.createAudioQuery("こんにちは", model.metas[0].styles[0].id).execute(); + AudioQuery query = synthesizer.createAudioQuery("こんにちは", model.metas[0].styles[0].id); synthesizer.synthesis(query, model.metas[0].styles[0].id).execute(); } @@ -62,7 +62,7 @@ void checkAccentPhrases() { Synthesizer synthesizer = Synthesizer.builder(openJtalk).build(); synthesizer.loadVoiceModel(model); List accentPhrases = - synthesizer.createAccentPhrases("こんにちは", model.metas[0].styles[0].id).execute(); + synthesizer.createAccentPhrases("こんにちは", model.metas[0].styles[0].id); List accentPhrases2 = synthesizer.replaceMoraPitch(accentPhrases, model.metas[1].styles[0].id); assertTrue( @@ -91,6 +91,6 @@ void checkTts() { OpenJtalk openJtalk = loadOpenJtalk(); Synthesizer synthesizer = Synthesizer.builder(openJtalk).build(); synthesizer.loadVoiceModel(model); - synthesizer.tts("こんにちは", model.metas[0].styles[0].id).execute(); + synthesizer.tts("こんにちは", model.metas[0].styles[0].id); } } diff --git a/crates/voicevox_core_java_api/lib/src/test/java/jp/hiroshiba/voicevoxcore/UserDictTest.java b/crates/voicevox_core_java_api/lib/src/test/java/jp/hiroshiba/voicevoxcore/UserDictTest.java index a7847efba..9eb1077f5 100644 --- a/crates/voicevox_core_java_api/lib/src/test/java/jp/hiroshiba/voicevoxcore/UserDictTest.java +++ b/crates/voicevox_core_java_api/lib/src/test/java/jp/hiroshiba/voicevoxcore/UserDictTest.java @@ -18,18 +18,14 @@ void checkLoad() { UserDict userDict = new UserDict(); synthesizer.loadVoiceModel(model); AudioQuery query1 = - synthesizer - .createAudioQuery( - "this_word_should_not_exist_in_default_dictionary", model.metas[0].styles[0].id) - .execute(); + synthesizer.createAudioQuery( + "this_word_should_not_exist_in_default_dictionary", model.metas[0].styles[0].id); userDict.addWord(new UserDict.Word("this_word_should_not_exist_in_default_dictionary", "テスト")); openJtalk.useUserDict(userDict); AudioQuery query2 = - synthesizer - .createAudioQuery( - "this_word_should_not_exist_in_default_dictionary", model.metas[0].styles[0].id) - .execute(); + synthesizer.createAudioQuery( + "this_word_should_not_exist_in_default_dictionary", model.metas[0].styles[0].id); assertTrue(query1.kana != query2.kana); } diff --git a/crates/voicevox_core_java_api/src/synthesizer.rs b/crates/voicevox_core_java_api/src/synthesizer.rs index ed217462c..9f245f8f7 100644 --- a/crates/voicevox_core_java_api/src/synthesizer.rs +++ b/crates/voicevox_core_java_api/src/synthesizer.rs @@ -127,13 +127,44 @@ unsafe extern "system" fn Java_jp_hiroshiba_voicevoxcore_Synthesizer_rsIsLoadedV .into() } +#[no_mangle] +unsafe extern "system" fn Java_jp_hiroshiba_voicevoxcore_Synthesizer_rsAudioQueryFromKana< + 'local, +>( + env: JNIEnv<'local>, + this: JObject<'local>, + kana: JString<'local>, + style_id: jint, +) -> jobject { + throw_if_err(env, std::ptr::null_mut(), |env| { + let kana: String = env.get_string(&kana)?.into(); + let style_id = style_id as u32; + + let internal = env + .get_rust_field::<_, _, Arc>>(&this, "handle")? + .clone(); + + let audio_query = { + let internal = internal.lock().unwrap(); + RUNTIME.block_on( + internal.audio_query_from_kana(&kana, voicevox_core::StyleId::new(style_id)), + )? + }; + + let query_json = serde_json::to_string(&audio_query)?; + + let j_audio_query = env.new_string(query_json)?; + + Ok(j_audio_query.into_raw()) + }) +} + #[no_mangle] unsafe extern "system" fn Java_jp_hiroshiba_voicevoxcore_Synthesizer_rsAudioQuery<'local>( env: JNIEnv<'local>, this: JObject<'local>, text: JString<'local>, style_id: jint, - kana: jboolean, ) -> jobject { throw_if_err(env, std::ptr::null_mut(), |env| { let text: String = env.get_string(&text)?.into(); @@ -145,15 +176,7 @@ unsafe extern "system" fn Java_jp_hiroshiba_voicevoxcore_Synthesizer_rsAudioQuer let audio_query = { let internal = internal.lock().unwrap(); - let options = voicevox_core::AudioQueryOptions { - kana: kana != 0, - // ..Default::default() - }; - RUNTIME.block_on(internal.audio_query( - &text, - voicevox_core::StyleId::new(style_id), - &options, - ))? + RUNTIME.block_on(internal.audio_query(&text, voicevox_core::StyleId::new(style_id)))? }; let query_json = serde_json::to_string(&audio_query)?; @@ -164,13 +187,45 @@ unsafe extern "system" fn Java_jp_hiroshiba_voicevoxcore_Synthesizer_rsAudioQuer }) } +#[no_mangle] +unsafe extern "system" fn Java_jp_hiroshiba_voicevoxcore_Synthesizer_rsAccentPhrasesFromKana< + 'local, +>( + env: JNIEnv<'local>, + this: JObject<'local>, + kana: JString<'local>, + style_id: jint, +) -> jobject { + throw_if_err(env, std::ptr::null_mut(), |env| { + let kana: String = env.get_string(&kana)?.into(); + let style_id = style_id as u32; + + let internal = env + .get_rust_field::<_, _, Arc>>(&this, "handle")? + .clone(); + + let accent_phrases = { + let internal = internal.lock().unwrap(); + RUNTIME.block_on( + internal + .create_accent_phrases_from_kana(&kana, voicevox_core::StyleId::new(style_id)), + )? + }; + + let query_json = serde_json::to_string(&accent_phrases)?; + + let j_accent_phrases = env.new_string(query_json)?; + + Ok(j_accent_phrases.into_raw()) + }) +} + #[no_mangle] unsafe extern "system" fn Java_jp_hiroshiba_voicevoxcore_Synthesizer_rsAccentPhrases<'local>( env: JNIEnv<'local>, this: JObject<'local>, text: JString<'local>, style_id: jint, - kana: jboolean, ) -> jobject { throw_if_err(env, std::ptr::null_mut(), |env| { let text: String = env.get_string(&text)?.into(); @@ -182,15 +237,9 @@ unsafe extern "system" fn Java_jp_hiroshiba_voicevoxcore_Synthesizer_rsAccentPhr let accent_phrases = { let internal = internal.lock().unwrap(); - let options = voicevox_core::AccentPhrasesOptions { - kana: kana != 0, - // ..Default::default() - }; - RUNTIME.block_on(internal.create_accent_phrases( - &text, - voicevox_core::StyleId::new(style_id), - &options, - ))? + RUNTIME.block_on( + internal.create_accent_phrases(&text, voicevox_core::StyleId::new(style_id)), + )? }; let query_json = serde_json::to_string(&accent_phrases)?; @@ -330,13 +379,47 @@ unsafe extern "system" fn Java_jp_hiroshiba_voicevoxcore_Synthesizer_rsSynthesis }) } +#[no_mangle] +unsafe extern "system" fn Java_jp_hiroshiba_voicevoxcore_Synthesizer_rsTtsFromKana<'local>( + env: JNIEnv<'local>, + this: JObject<'local>, + kana: JString<'local>, + style_id: jint, + enable_interrogative_upspeak: jboolean, +) -> jobject { + throw_if_err(env, std::ptr::null_mut(), |env| { + let kana: String = env.get_string(&kana)?.into(); + let style_id = style_id as u32; + + let internal = env + .get_rust_field::<_, _, Arc>>(&this, "handle")? + .clone(); + + let wave = { + let internal = internal.lock().unwrap(); + let options = voicevox_core::TtsOptions { + enable_interrogative_upspeak: enable_interrogative_upspeak != 0, + // ..Default::default() + }; + RUNTIME.block_on(internal.tts_from_kana( + &kana, + voicevox_core::StyleId::new(style_id), + &options, + ))? + }; + + let j_bytes = env.byte_array_from_slice(&wave)?; + + Ok(j_bytes.into_raw()) + }) +} + #[no_mangle] unsafe extern "system" fn Java_jp_hiroshiba_voicevoxcore_Synthesizer_rsTts<'local>( env: JNIEnv<'local>, this: JObject<'local>, query_json: JString<'local>, style_id: jint, - kana: jboolean, enable_interrogative_upspeak: jboolean, ) -> jobject { throw_if_err(env, std::ptr::null_mut(), |env| { @@ -350,7 +433,6 @@ unsafe extern "system" fn Java_jp_hiroshiba_voicevoxcore_Synthesizer_rsTts<'loca let wave = { let internal = internal.lock().unwrap(); let options = voicevox_core::TtsOptions { - kana: kana != 0, enable_interrogative_upspeak: enable_interrogative_upspeak != 0, // ..Default::default() }; 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 d9829e6da..667dfe7f3 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 @@ -19,7 +19,7 @@ async def test_user_dict_load() -> None: 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, kana=False + "this_word_should_not_exist_in_default_dictionary", style_id=0 ) temp_dict = voicevox_core.UserDict() @@ -34,6 +34,6 @@ async def test_user_dict_load() -> None: open_jtalk.use_user_dict(temp_dict) audio_query_with_dict = await synthesizer.audio_query( - "this_word_should_not_exist_in_default_dictionary", style_id=0, kana=False + "this_word_should_not_exist_in_default_dictionary", style_id=0 ) assert audio_query_without_dict != audio_query_with_dict diff --git a/crates/voicevox_core_python_api/python/voicevox_core/_rust.pyi b/crates/voicevox_core_python_api/python/voicevox_core/_rust.pyi index 324ea0117..b4e44c8e2 100644 --- a/crates/voicevox_core_python_api/python/voicevox_core/_rust.pyi +++ b/crates/voicevox_core_python_api/python/voicevox_core/_rust.pyi @@ -147,46 +147,80 @@ class Synthesizer: モデルが読み込まれているかどうか。 """ ... + async def audio_query_from_kana( + self, + kana: str, + style_id: int, + ) -> AudioQuery: + """ + AquesTalk風記法から :class:`AudioQuery` を生成する。 + + Parameters + ---------- + kana + AquesTalk風記法。 + style_id + スタイルID。 + + Returns + ------- + 話者とテキストから生成された :class:`AudioQuery` 。 + """ + ... async def audio_query( self, text: str, style_id: int, - kana: bool = False, ) -> AudioQuery: """ - :class:`AudioQuery` を生成する。 + 日本語のテキストから :class:`AudioQuery` を生成する。 Parameters ---------- text - テキスト。文字コードはUTF-8。 + UTF-8の日本語テキスト。 style_id スタイルID。 - kana - ``text`` をAquesTalk風記法として解釈するかどうか。 Returns ------- 話者とテキストから生成された :class:`AudioQuery` 。 """ ... + async def create_accent_phrases_from_kana( + self, + kana: str, + style_id: int, + ) -> List[AccentPhrase]: + """ + AquesTalk風記法からAccentPhrase(アクセント句)の配列を生成する。 + + Parameters + ---------- + kana + AquesTalk風記法。 + style_id + スタイルID。 + + Returns + ------- + :class:`AccentPhrase` の配列。 + """ + ... async def create_accent_phrases( self, text: str, style_id: int, - kana: bool = False, ) -> List[AccentPhrase]: """ - AccentPhrase(アクセント句)の配列を生成する。 + 日本語のテキストからAccentPhrase(アクセント句)の配列を生成する。 Parameters ---------- text - UTF-8の日本語テキストまたはAquesTalk風記法。 + UTF-8の日本語テキスト。 style_id スタイルID。 - kana - ``text`` をAquesTalk風記法として解釈するかどうか。 Returns ------- @@ -274,24 +308,40 @@ class Synthesizer: WAVデータ。 """ ... + async def tts_from_kana( + self, + kana: str, + style_id: 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: int, - kana: bool = False, enable_interrogative_upspeak: bool = True, ) -> bytes: """ - テキスト音声合成を実行する。 + 日本語のテキストから音声合成を行う。 Parameters ---------- text - UTF-8の日本語テキストまたはAquesTalk風記法。 + UTF-8の日本語テキスト。 style_id スタイルID。 - kana - ``text`` をAquesTalk風記法として解釈するかどうか。 enable_interrogative_upspeak 疑問文の調整を有効にするかどうか。 diff --git a/crates/voicevox_core_python_api/src/lib.rs b/crates/voicevox_core_python_api/src/lib.rs index bd5406436..42d3feaeb 100644 --- a/crates/voicevox_core_python_api/src/lib.rs +++ b/crates/voicevox_core_python_api/src/lib.rs @@ -14,8 +14,8 @@ use pyo3::{ use tokio::{runtime::Runtime, sync::Mutex}; use uuid::Uuid; use voicevox_core::{ - AccelerationMode, AccentPhrasesOptions, AudioQueryModel, AudioQueryOptions, InitializeOptions, - StyleId, SynthesisOptions, TtsOptions, UserDictWord, VoiceModelId, + AccelerationMode, AudioQueryModel, InitializeOptions, StyleId, SynthesisOptions, TtsOptions, + UserDictWord, VoiceModelId, }; static RUNTIME: Lazy = Lazy::new(|| Runtime::new().unwrap()); @@ -208,14 +208,35 @@ impl Synthesizer { .is_loaded_voice_model(&VoiceModelId::new(voice_model_id.to_string()))) } - #[pyo3(signature=(text,style_id,kana = AudioQueryOptions::default().kana))] - fn audio_query<'py>( + fn audio_query_from_kana<'py>( &self, - text: &str, + kana: &str, style_id: u32, - kana: bool, py: Python<'py>, ) -> PyResult<&'py PyAny> { + let synthesizer = self.synthesizer.get()?.clone(); + let kana = kana.to_owned(); + pyo3_asyncio::tokio::future_into_py_with_locals( + py, + pyo3_asyncio::tokio::get_current_locals(py)?, + async move { + let audio_query = synthesizer + .lock() + .await + .audio_query_from_kana(&kana, StyleId::new(style_id)) + .await + .into_py_result()?; + + Python::with_gil(|py| { + let class = py.import("voicevox_core")?.getattr("AudioQuery")?; + let ret = to_pydantic_dataclass(audio_query, class)?; + Ok(ret.to_object(py)) + }) + }, + ) + } + + fn audio_query<'py>(&self, text: &str, style_id: u32, py: Python<'py>) -> PyResult<&'py PyAny> { let synthesizer = self.synthesizer.get()?.clone(); let text = text.to_owned(); pyo3_asyncio::tokio::future_into_py_with_locals( @@ -225,7 +246,7 @@ impl Synthesizer { let audio_query = synthesizer .lock() .await - .audio_query(&text, StyleId::new(style_id), &AudioQueryOptions { kana }) + .audio_query(&text, StyleId::new(style_id)) .await .into_py_result()?; @@ -238,12 +259,41 @@ impl Synthesizer { ) } - #[pyo3(signature=(text, style_id, kana = AccentPhrasesOptions::default().kana))] + fn create_accent_phrases_from_kana<'py>( + &self, + kana: &str, + style_id: u32, + py: Python<'py>, + ) -> PyResult<&'py PyAny> { + let synthesizer = self.synthesizer.get()?.clone(); + let kana = kana.to_owned(); + pyo3_asyncio::tokio::future_into_py_with_locals( + py, + pyo3_asyncio::tokio::get_current_locals(py)?, + async move { + let accent_phrases = synthesizer + .lock() + .await + .create_accent_phrases_from_kana(&kana, StyleId::new(style_id)) + .await + .into_py_result()?; + Python::with_gil(|py| { + let class = py.import("voicevox_core")?.getattr("AccentPhrase")?; + let accent_phrases = accent_phrases + .iter() + .map(|ap| to_pydantic_dataclass(ap, class)) + .collect::>>(); + let list = PyList::new(py, accent_phrases.into_iter()); + Ok(list.to_object(py)) + }) + }, + ) + } + fn create_accent_phrases<'py>( &self, text: &str, style_id: u32, - kana: bool, py: Python<'py>, ) -> PyResult<&'py PyAny> { let synthesizer = self.synthesizer.get()?.clone(); @@ -255,11 +305,7 @@ impl Synthesizer { let accent_phrases = synthesizer .lock() .await - .create_accent_phrases( - &text, - StyleId::new(style_id), - &AccentPhrasesOptions { kana }, - ) + .create_accent_phrases(&text, StyleId::new(style_id)) .await .into_py_result()?; Python::with_gil(|py| { @@ -350,23 +396,53 @@ impl Synthesizer { ) } + #[pyo3(signature=( + kana, + style_id, + enable_interrogative_upspeak = TtsOptions::default().enable_interrogative_upspeak + ))] + fn tts_from_kana<'py>( + &self, + kana: &str, + style_id: u32, + enable_interrogative_upspeak: bool, + py: Python<'py>, + ) -> PyResult<&'py PyAny> { + let style_id = StyleId::new(style_id); + let options = TtsOptions { + enable_interrogative_upspeak, + }; + let synthesizer = self.synthesizer.get()?.clone(); + let kana = kana.to_owned(); + pyo3_asyncio::tokio::future_into_py_with_locals( + py, + pyo3_asyncio::tokio::get_current_locals(py)?, + async move { + let wav = synthesizer + .lock() + .await + .tts_from_kana(&kana, style_id, &options) + .await + .into_py_result()?; + Python::with_gil(|py| Ok(PyBytes::new(py, &wav).to_object(py))) + }, + ) + } + #[pyo3(signature=( text, style_id, - kana = TtsOptions::default().kana, enable_interrogative_upspeak = TtsOptions::default().enable_interrogative_upspeak ))] fn tts<'py>( &self, text: &str, style_id: u32, - kana: bool, enable_interrogative_upspeak: bool, py: Python<'py>, ) -> PyResult<&'py PyAny> { let style_id = StyleId::new(style_id); let options = TtsOptions { - kana, enable_interrogative_upspeak, }; let synthesizer = self.synthesizer.get()?.clone(); From 38effe94b2678f7977f0e52a7747e1f49d8b1f6f Mon Sep 17 00:00:00 2001 From: Ryo Yamashita Date: Mon, 25 Sep 2023 02:32:16 +0900 Subject: [PATCH 12/20] =?UTF-8?q?`InvalidStyleId`,=20`InvalidModelId`,=20`?= =?UTF-8?q?UnknownWord`=E3=82=92`=E2=80=A6NotFound`=E3=81=AB=E3=81=99?= =?UTF-8?q?=E3=82=8B=20(#622)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Nanashi. --- crates/voicevox_core/src/error.rs | 32 +++++++++++-------- crates/voicevox_core/src/inference_core.rs | 6 ++-- crates/voicevox_core/src/status.rs | 2 +- crates/voicevox_core/src/user_dict/dict.rs | 4 +-- .../include/voicevox_core.h | 10 +++--- crates/voicevox_core_c_api/src/helpers.rs | 6 ++-- crates/voicevox_core_c_api/src/result_code.rs | 22 ++++++++----- .../tests/e2e/snapshots.toml | 4 +-- .../voicevox_core_c_api/tests/e2e/symbols.rs | 6 ++-- 9 files changed, 52 insertions(+), 40 deletions(-) diff --git a/crates/voicevox_core/src/error.rs b/crates/voicevox_core/src/error.rs index 1d9eda849..f101dcb15 100644 --- a/crates/voicevox_core/src/error.rs +++ b/crates/voicevox_core/src/error.rs @@ -45,14 +45,14 @@ impl Error { }, ErrorRepr::UnloadedModel { .. } => ErrorKind::UnloadedModel, ErrorRepr::GetSupportedDevices(_) => ErrorKind::GetSupportedDevices, - ErrorRepr::InvalidStyleId { .. } => ErrorKind::InvalidStyleId, - ErrorRepr::InvalidModelId { .. } => ErrorKind::InvalidModelId, + ErrorRepr::StyleNotFound { .. } => ErrorKind::StyleNotFound, + ErrorRepr::ModelNotFound { .. } => ErrorKind::ModelNotFound, ErrorRepr::InferenceFailed => ErrorKind::InferenceFailed, ErrorRepr::ExtractFullContextLabel(_) => ErrorKind::ExtractFullContextLabel, ErrorRepr::ParseKana(_) => ErrorKind::ParseKana, ErrorRepr::LoadUserDict(_) => ErrorKind::LoadUserDict, ErrorRepr::SaveUserDict(_) => ErrorKind::SaveUserDict, - ErrorRepr::UnknownWord(_) => ErrorKind::UnknownWord, + ErrorRepr::WordNotFound(_) => ErrorKind::WordNotFound, ErrorRepr::UseUserDict(_) => ErrorKind::UseUserDict, ErrorRepr::InvalidWord(_) => ErrorKind::InvalidWord, } @@ -76,12 +76,18 @@ pub(crate) enum ErrorRepr { #[error("サポートされているデバイス情報取得中にエラーが発生しました,{0}")] GetSupportedDevices(#[source] anyhow::Error), - #[error("無効なspeaker_idです: {style_id:?}")] - InvalidStyleId { style_id: StyleId }, + #[error( + "`{style_id}`に対するスタイルが見つかりませんでした。音声モデルが読み込まれていないか、読\ + み込みが解除されています" + )] + StyleNotFound { style_id: StyleId }, #[allow(dead_code)] // FIXME - #[error("無効なmodel_idです: {model_id:?}")] - InvalidModelId { model_id: VoiceModelId }, + #[error( + "`{model_id}`に対する音声モデルが見つかりませんでした。読み込まれていないか、読み込みが既\ + に解除されています" + )] + ModelNotFound { model_id: VoiceModelId }, #[error("推論に失敗しました")] InferenceFailed, @@ -99,7 +105,7 @@ pub(crate) enum ErrorRepr { SaveUserDict(String), #[error("ユーザー辞書に単語が見つかりませんでした: {0}")] - UnknownWord(Uuid), + WordNotFound(Uuid), #[error("OpenJTalkのユーザー辞書の設定に失敗しました: {0}")] UseUserDict(String), @@ -129,10 +135,10 @@ pub enum ErrorKind { UnloadedModel, /// サポートされているデバイス情報取得に失敗した。 GetSupportedDevices, - /// 無効なstyle_idが指定された。 - InvalidStyleId, - /// 無効なmodel_idが指定された。 - InvalidModelId, + /// スタイルIDに対するスタイルが見つからなかった。 + StyleNotFound, + /// 音声モデルIDに対する音声モデルが見つからなかった。 + ModelNotFound, /// 推論に失敗した。 InferenceFailed, /// コンテキストラベル出力に失敗した。 @@ -144,7 +150,7 @@ pub enum ErrorKind { /// ユーザー辞書を書き込めなかった。 SaveUserDict, /// ユーザー辞書に単語が見つからなかった。 - UnknownWord, + WordNotFound, /// OpenJTalkのユーザー辞書の設定に失敗した。 UseUserDict, /// ユーザー辞書の単語のバリデーションに失敗した。 diff --git a/crates/voicevox_core/src/inference_core.rs b/crates/voicevox_core/src/inference_core.rs index a02da2639..e33113589 100644 --- a/crates/voicevox_core/src/inference_core.rs +++ b/crates/voicevox_core/src/inference_core.rs @@ -55,7 +55,7 @@ impl InferenceCore { style_id: StyleId, ) -> Result> { if !self.status.validate_speaker_id(style_id) { - return Err(ErrorRepr::InvalidStyleId { style_id }.into()); + return Err(ErrorRepr::StyleNotFound { style_id }.into()); } let (model_id, model_inner_id) = self.status.ids_for(style_id)?; @@ -90,7 +90,7 @@ impl InferenceCore { style_id: StyleId, ) -> Result> { if !self.status.validate_speaker_id(style_id) { - return Err(ErrorRepr::InvalidStyleId { style_id }.into()); + return Err(ErrorRepr::StyleNotFound { style_id }.into()); } let (model_id, model_inner_id) = self.status.ids_for(style_id)?; @@ -129,7 +129,7 @@ impl InferenceCore { style_id: StyleId, ) -> Result> { if !self.status.validate_speaker_id(style_id) { - return Err(ErrorRepr::InvalidStyleId { style_id }.into()); + return Err(ErrorRepr::StyleNotFound { style_id }.into()); } let (model_id, model_inner_id) = self.status.ids_for(style_id)?; diff --git a/crates/voicevox_core/src/status.rs b/crates/voicevox_core/src/status.rs index c173d87db..852054488 100644 --- a/crates/voicevox_core/src/status.rs +++ b/crates/voicevox_core/src/status.rs @@ -305,7 +305,7 @@ impl LoadedModels { .flat_map(SpeakerMeta::styles) .any(|style| *style.id() == style_id) }) - .ok_or(ErrorRepr::InvalidStyleId { style_id })?; + .ok_or(ErrorRepr::StyleNotFound { style_id })?; let model_inner_id = *model_inner_ids .get(&style_id) diff --git a/crates/voicevox_core/src/user_dict/dict.rs b/crates/voicevox_core/src/user_dict/dict.rs index 60dfe3ce4..dd342ddd0 100644 --- a/crates/voicevox_core/src/user_dict/dict.rs +++ b/crates/voicevox_core/src/user_dict/dict.rs @@ -48,7 +48,7 @@ impl UserDict { /// ユーザー辞書の単語を変更する。 pub fn update_word(&mut self, word_uuid: Uuid, new_word: UserDictWord) -> Result<()> { if !self.words.contains_key(&word_uuid) { - return Err(ErrorRepr::UnknownWord(word_uuid).into()); + return Err(ErrorRepr::WordNotFound(word_uuid).into()); } self.words.insert(word_uuid, new_word); Ok(()) @@ -57,7 +57,7 @@ impl UserDict { /// ユーザー辞書から単語を削除する。 pub fn remove_word(&mut self, word_uuid: Uuid) -> Result { let Some(word) = self.words.remove(&word_uuid) else { - return Err(ErrorRepr::UnknownWord(word_uuid).into()); + return Err(ErrorRepr::WordNotFound(word_uuid).into()); }; Ok(word) } diff --git a/crates/voicevox_core_c_api/include/voicevox_core.h b/crates/voicevox_core_c_api/include/voicevox_core.h index d376544eb..001f12f1a 100644 --- a/crates/voicevox_core_c_api/include/voicevox_core.h +++ b/crates/voicevox_core_c_api/include/voicevox_core.h @@ -103,13 +103,13 @@ enum VoicevoxResultCode */ VOICEVOX_RESULT_GPU_SUPPORT_ERROR = 4, /** - * 無効なstyle_idが指定された + * スタイルIDに対するスタイルが見つからなかった */ - VOICEVOX_RESULT_INVALID_STYLE_ID_ERROR = 6, + VOICEVOX_RESULT_STYLE_NOT_FOUND_ERROR = 6, /** - * 無効なmodel_idが指定された + * 音声モデルIDに対する音声モデルが見つからなかった */ - VOICEVOX_RESULT_INVALID_MODEL_ID_ERROR = 7, + VOICEVOX_RESULT_MODEL_NOT_FOUND_ERROR = 7, /** * 推論に失敗した */ @@ -169,7 +169,7 @@ enum VoicevoxResultCode /** * ユーザー辞書に単語が見つからなかった */ - VOICEVOX_RESULT_UNKNOWN_USER_DICT_WORD_ERROR = 22, + VOICEVOX_RESULT_USER_DICT_WORD_NOT_FOUND_ERROR = 22, /** * OpenJTalkのユーザー辞書の設定に失敗した */ diff --git a/crates/voicevox_core_c_api/src/helpers.rs b/crates/voicevox_core_c_api/src/helpers.rs index bd1c95c8f..db70b9af3 100644 --- a/crates/voicevox_core_c_api/src/helpers.rs +++ b/crates/voicevox_core_c_api/src/helpers.rs @@ -38,14 +38,14 @@ pub(crate) fn into_result_code_with_error(result: CApiResult<()>) -> VoicevoxRes InvalidModelData => VOICEVOX_RESULT_INVALID_MODEL_DATA_ERROR, UnloadedModel => VOICEVOX_RESULT_UNLOADED_MODEL_ERROR, GetSupportedDevices => VOICEVOX_RESULT_GET_SUPPORTED_DEVICES_ERROR, - InvalidStyleId => VOICEVOX_RESULT_INVALID_STYLE_ID_ERROR, - InvalidModelId => VOICEVOX_RESULT_INVALID_MODEL_ID_ERROR, + StyleNotFound => VOICEVOX_RESULT_STYLE_NOT_FOUND_ERROR, + ModelNotFound => VOICEVOX_RESULT_MODEL_NOT_FOUND_ERROR, InferenceFailed => VOICEVOX_RESULT_INFERENCE_ERROR, ExtractFullContextLabel => VOICEVOX_RESULT_EXTRACT_FULL_CONTEXT_LABEL_ERROR, ParseKana => VOICEVOX_RESULT_PARSE_KANA_ERROR, LoadUserDict => VOICEVOX_RESULT_LOAD_USER_DICT_ERROR, SaveUserDict => VOICEVOX_RESULT_SAVE_USER_DICT_ERROR, - UnknownWord => VOICEVOX_RESULT_UNKNOWN_USER_DICT_WORD_ERROR, + WordNotFound => VOICEVOX_RESULT_USER_DICT_WORD_NOT_FOUND_ERROR, UseUserDict => VOICEVOX_RESULT_USE_USER_DICT_ERROR, InvalidWord => VOICEVOX_RESULT_INVALID_USER_DICT_WORD_ERROR, }, diff --git a/crates/voicevox_core_c_api/src/result_code.rs b/crates/voicevox_core_c_api/src/result_code.rs index daf461705..70fb0bb12 100644 --- a/crates/voicevox_core_c_api/src/result_code.rs +++ b/crates/voicevox_core_c_api/src/result_code.rs @@ -17,10 +17,10 @@ pub enum VoicevoxResultCode { VOICEVOX_RESULT_GET_SUPPORTED_DEVICES_ERROR = 3, /// GPUモードがサポートされていない VOICEVOX_RESULT_GPU_SUPPORT_ERROR = 4, - /// 無効なstyle_idが指定された - VOICEVOX_RESULT_INVALID_STYLE_ID_ERROR = 6, - /// 無効なmodel_idが指定された - VOICEVOX_RESULT_INVALID_MODEL_ID_ERROR = 7, + /// スタイルIDに対するスタイルが見つからなかった + VOICEVOX_RESULT_STYLE_NOT_FOUND_ERROR = 6, + /// 音声モデルIDに対する音声モデルが見つからなかった + VOICEVOX_RESULT_MODEL_NOT_FOUND_ERROR = 7, /// 推論に失敗した VOICEVOX_RESULT_INFERENCE_ERROR = 8, /// コンテキストラベル出力に失敗した @@ -50,7 +50,7 @@ pub enum VoicevoxResultCode { /// ユーザー辞書を書き込めなかった VOICEVOX_RESULT_SAVE_USER_DICT_ERROR = 21, /// ユーザー辞書に単語が見つからなかった - VOICEVOX_RESULT_UNKNOWN_USER_DICT_WORD_ERROR = 22, + VOICEVOX_RESULT_USER_DICT_WORD_NOT_FOUND_ERROR = 22, /// OpenJTalkのユーザー辞書の設定に失敗した VOICEVOX_RESULT_USE_USER_DICT_ERROR = 23, /// ユーザー辞書の単語のバリデーションに失敗した @@ -70,8 +70,14 @@ pub(crate) const fn error_result_to_message(result_code: VoicevoxResultCode) -> cstr!("サポートされているデバイス情報取得中にエラーが発生しました") } VOICEVOX_RESULT_OK => cstr!("エラーが発生しませんでした"), - VOICEVOX_RESULT_INVALID_STYLE_ID_ERROR => cstr!("無効なspeaker_idです"), - VOICEVOX_RESULT_INVALID_MODEL_ID_ERROR => cstr!("無効なmodel_idです"), + VOICEVOX_RESULT_STYLE_NOT_FOUND_ERROR => cstr!( + "指定されたIDに対するスタイルが見つかりませんでした。音声モデルが読み込まれていないか\ + 、読み込みが解除されています" + ), + VOICEVOX_RESULT_MODEL_NOT_FOUND_ERROR => cstr!( + "指定されたIDに対する音声モデルが見つかりませんでした。読み込まれていないか、読み込み\ + が既に解除されています" + ), VOICEVOX_RESULT_INFERENCE_ERROR => cstr!("推論に失敗しました"), VOICEVOX_RESULT_EXTRACT_FULL_CONTEXT_LABEL_ERROR => { cstr!("入力テキストからのフルコンテキストラベル抽出に失敗しました") @@ -96,7 +102,7 @@ pub(crate) const fn error_result_to_message(result_code: VoicevoxResultCode) -> VOICEVOX_RESULT_UNLOADED_MODEL_ERROR => cstr!("Modelが読み込まれていません"), VOICEVOX_RESULT_LOAD_USER_DICT_ERROR => cstr!("ユーザー辞書を読み込めませんでした"), VOICEVOX_RESULT_SAVE_USER_DICT_ERROR => cstr!("ユーザー辞書を書き込めませんでした"), - VOICEVOX_RESULT_UNKNOWN_USER_DICT_WORD_ERROR => { + VOICEVOX_RESULT_USER_DICT_WORD_NOT_FOUND_ERROR => { cstr!("ユーザー辞書に単語が見つかりませんでした") } VOICEVOX_RESULT_USE_USER_DICT_ERROR => cstr!("OpenJTalkのユーザー辞書の設定に失敗しました"), diff --git a/crates/voicevox_core_c_api/tests/e2e/snapshots.toml b/crates/voicevox_core_c_api/tests/e2e/snapshots.toml index eeeec0c93..b41480551 100644 --- a/crates/voicevox_core_c_api/tests/e2e/snapshots.toml +++ b/crates/voicevox_core_c_api/tests/e2e/snapshots.toml @@ -53,8 +53,8 @@ result_messages.0 = "エラーが発生しませんでした" result_messages.1 = "OpenJTalkの辞書が読み込まれていません" result_messages.3 = "サポートされているデバイス情報取得中にエラーが発生しました" result_messages.4 = "GPU機能をサポートすることができません" -result_messages.6 = "無効なspeaker_idです" -result_messages.7 = "無効なmodel_idです" +result_messages.6 = "指定されたIDに対するスタイルが見つかりませんでした。音声モデルが読み込まれていないか、読み込みが解除されています" +result_messages.7 = "指定されたIDに対する音声モデルが見つかりませんでした。読み込まれていないか、読み込みが既に解除されています" result_messages.8 = "推論に失敗しました" result_messages.11 = "入力テキストからのフルコンテキストラベル抽出に失敗しました" result_messages.12 = "入力テキストが無効なUTF-8データでした" diff --git a/crates/voicevox_core_c_api/tests/e2e/symbols.rs b/crates/voicevox_core_c_api/tests/e2e/symbols.rs index ae929ad0f..840757a1a 100644 --- a/crates/voicevox_core_c_api/tests/e2e/symbols.rs +++ b/crates/voicevox_core_c_api/tests/e2e/symbols.rs @@ -270,8 +270,8 @@ pub(crate) enum VoicevoxResultCode { VOICEVOX_RESULT_NOT_LOADED_OPENJTALK_DICT_ERROR = 1, VOICEVOX_RESULT_GET_SUPPORTED_DEVICES_ERROR = 3, VOICEVOX_RESULT_GPU_SUPPORT_ERROR = 4, - VOICEVOX_RESULT_INVALID_STYLE_ID_ERROR = 6, - VOICEVOX_RESULT_INVALID_MODEL_ID_ERROR = 7, + VOICEVOX_RESULT_STYLE_NOT_FOUND_ERROR = 6, + VOICEVOX_RESULT_MODEL_NOT_FOUND_ERROR = 7, VOICEVOX_RESULT_INFERENCE_ERROR = 8, VOICEVOX_RESULT_EXTRACT_FULL_CONTEXT_LABEL_ERROR = 11, VOICEVOX_RESULT_INVALID_UTF8_INPUT_ERROR = 12, @@ -286,7 +286,7 @@ pub(crate) enum VoicevoxResultCode { VOICEVOX_RESULT_UNLOADED_MODEL_ERROR = 19, VOICEVOX_RESULT_LOAD_USER_DICT_ERROR = 20, VOICEVOX_RESULT_SAVE_USER_DICT_ERROR = 21, - VOICEVOX_RESULT_UNKNOWN_USER_DICT_WORD_ERROR = 22, + VOICEVOX_RESULT_USER_DICT_WORD_NOT_FOUND_ERROR = 22, VOICEVOX_RESULT_USE_USER_DICT_ERROR = 23, VOICEVOX_RESULT_INVALID_USER_DICT_WORD_ERROR = 24, VOICEVOX_RESULT_INVALID_UUID_ERROR = 25, From 4fe11f2afbb5745e3fa1a46178d589c4f55c0dae Mon Sep 17 00:00:00 2001 From: Ryo Yamashita Date: Tue, 26 Sep 2023 02:03:46 +0900 Subject: [PATCH 13/20] =?UTF-8?q?`UnloadedModel`=20=E2=86=92=20`ModelNotFo?= =?UTF-8?q?und`=20(#623)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- crates/voicevox_core/src/error.rs | 7 ------- crates/voicevox_core/src/status.rs | 2 +- crates/voicevox_core_c_api/include/voicevox_core.h | 4 ---- crates/voicevox_core_c_api/src/helpers.rs | 1 - crates/voicevox_core_c_api/src/result_code.rs | 3 --- crates/voicevox_core_c_api/tests/e2e/snapshots.toml | 1 - crates/voicevox_core_c_api/tests/e2e/symbols.rs | 1 - 7 files changed, 1 insertion(+), 18 deletions(-) diff --git a/crates/voicevox_core/src/error.rs b/crates/voicevox_core/src/error.rs index f101dcb15..ce4a90b33 100644 --- a/crates/voicevox_core/src/error.rs +++ b/crates/voicevox_core/src/error.rs @@ -43,7 +43,6 @@ impl Error { LoadModelErrorKind::StyleAlreadyLoaded { .. } => ErrorKind::StyleAlreadyLoaded, LoadModelErrorKind::InvalidModelData => ErrorKind::InvalidModelData, }, - ErrorRepr::UnloadedModel { .. } => ErrorKind::UnloadedModel, ErrorRepr::GetSupportedDevices(_) => ErrorKind::GetSupportedDevices, ErrorRepr::StyleNotFound { .. } => ErrorKind::StyleNotFound, ErrorRepr::ModelNotFound { .. } => ErrorKind::ModelNotFound, @@ -70,9 +69,6 @@ pub(crate) enum ErrorRepr { #[error(transparent)] LoadModel(#[from] LoadModelError), - #[error("Modelが読み込まれていません ({model_id:?})")] - UnloadedModel { model_id: VoiceModelId }, - #[error("サポートされているデバイス情報取得中にエラーが発生しました,{0}")] GetSupportedDevices(#[source] anyhow::Error), @@ -82,7 +78,6 @@ pub(crate) enum ErrorRepr { )] StyleNotFound { style_id: StyleId }, - #[allow(dead_code)] // FIXME #[error( "`{model_id}`に対する音声モデルが見つかりませんでした。読み込まれていないか、読み込みが既\ に解除されています" @@ -131,8 +126,6 @@ pub enum ErrorKind { StyleAlreadyLoaded, /// 無効なモデルデータ。 InvalidModelData, - /// Modelが読み込まれていない。 - UnloadedModel, /// サポートされているデバイス情報取得に失敗した。 GetSupportedDevices, /// スタイルIDに対するスタイルが見つからなかった。 diff --git a/crates/voicevox_core/src/status.rs b/crates/voicevox_core/src/status.rs index 852054488..44c87c5f8 100644 --- a/crates/voicevox_core/src/status.rs +++ b/crates/voicevox_core/src/status.rs @@ -390,7 +390,7 @@ impl LoadedModels { fn remove(&mut self, model_id: &VoiceModelId) -> Result<()> { if self.0.remove(model_id).is_none() { - return Err(ErrorRepr::UnloadedModel { + return Err(ErrorRepr::ModelNotFound { model_id: model_id.clone(), } .into()); diff --git a/crates/voicevox_core_c_api/include/voicevox_core.h b/crates/voicevox_core_c_api/include/voicevox_core.h index 001f12f1a..394137d50 100644 --- a/crates/voicevox_core_c_api/include/voicevox_core.h +++ b/crates/voicevox_core_c_api/include/voicevox_core.h @@ -154,10 +154,6 @@ enum VoicevoxResultCode * 無効なモデルデータ */ VOICEVOX_RESULT_INVALID_MODEL_DATA_ERROR = 27, - /** - * Modelが読み込まれていない - */ - VOICEVOX_RESULT_UNLOADED_MODEL_ERROR = 19, /** * ユーザー辞書を読み込めなかった */ diff --git a/crates/voicevox_core_c_api/src/helpers.rs b/crates/voicevox_core_c_api/src/helpers.rs index db70b9af3..698e89b45 100644 --- a/crates/voicevox_core_c_api/src/helpers.rs +++ b/crates/voicevox_core_c_api/src/helpers.rs @@ -36,7 +36,6 @@ pub(crate) fn into_result_code_with_error(result: CApiResult<()>) -> VoicevoxRes ModelAlreadyLoaded => VOICEVOX_RESULT_MODEL_ALREADY_LOADED_ERROR, StyleAlreadyLoaded => VOICEVOX_RESULT_STYLE_ALREADY_LOADED_ERROR, InvalidModelData => VOICEVOX_RESULT_INVALID_MODEL_DATA_ERROR, - UnloadedModel => VOICEVOX_RESULT_UNLOADED_MODEL_ERROR, GetSupportedDevices => VOICEVOX_RESULT_GET_SUPPORTED_DEVICES_ERROR, StyleNotFound => VOICEVOX_RESULT_STYLE_NOT_FOUND_ERROR, ModelNotFound => VOICEVOX_RESULT_MODEL_NOT_FOUND_ERROR, diff --git a/crates/voicevox_core_c_api/src/result_code.rs b/crates/voicevox_core_c_api/src/result_code.rs index 70fb0bb12..65236ada4 100644 --- a/crates/voicevox_core_c_api/src/result_code.rs +++ b/crates/voicevox_core_c_api/src/result_code.rs @@ -43,8 +43,6 @@ pub enum VoicevoxResultCode { VOICEVOX_RESULT_STYLE_ALREADY_LOADED_ERROR = 26, /// 無効なモデルデータ VOICEVOX_RESULT_INVALID_MODEL_DATA_ERROR = 27, - /// Modelが読み込まれていない - VOICEVOX_RESULT_UNLOADED_MODEL_ERROR = 19, /// ユーザー辞書を読み込めなかった VOICEVOX_RESULT_LOAD_USER_DICT_ERROR = 20, /// ユーザー辞書を書き込めなかった @@ -99,7 +97,6 @@ pub(crate) const fn error_result_to_message(result_code: VoicevoxResultCode) -> VOICEVOX_RESULT_INVALID_MODEL_DATA_ERROR => { cstr!("モデルデータを読むことができませんでした") } - VOICEVOX_RESULT_UNLOADED_MODEL_ERROR => cstr!("Modelが読み込まれていません"), VOICEVOX_RESULT_LOAD_USER_DICT_ERROR => cstr!("ユーザー辞書を読み込めませんでした"), VOICEVOX_RESULT_SAVE_USER_DICT_ERROR => cstr!("ユーザー辞書を書き込めませんでした"), VOICEVOX_RESULT_USER_DICT_WORD_NOT_FOUND_ERROR => { diff --git a/crates/voicevox_core_c_api/tests/e2e/snapshots.toml b/crates/voicevox_core_c_api/tests/e2e/snapshots.toml index b41480551..75041ba6f 100644 --- a/crates/voicevox_core_c_api/tests/e2e/snapshots.toml +++ b/crates/voicevox_core_c_api/tests/e2e/snapshots.toml @@ -66,7 +66,6 @@ result_messages.17 = "ZIP内のファイルを読むことができませんで result_messages.18 = "同じIDのモデルを読むことはできません" result_messages.26 = "同じIDのスタイルを読むことはできません" result_messages.27 = "モデルデータを読むことができませんでした" -result_messages.19 = "Modelが読み込まれていません" result_messages.20 = "ユーザー辞書を読み込めませんでした" result_messages.21 = "ユーザー辞書を書き込めませんでした" result_messages.22 = "ユーザー辞書に単語が見つかりませんでした" diff --git a/crates/voicevox_core_c_api/tests/e2e/symbols.rs b/crates/voicevox_core_c_api/tests/e2e/symbols.rs index 840757a1a..8a39161af 100644 --- a/crates/voicevox_core_c_api/tests/e2e/symbols.rs +++ b/crates/voicevox_core_c_api/tests/e2e/symbols.rs @@ -283,7 +283,6 @@ pub(crate) enum VoicevoxResultCode { VOICEVOX_RESULT_MODEL_ALREADY_LOADED_ERROR = 18, VOICEVOX_RESULT_STYLE_ALREADY_LOADED_ERROR = 26, VOICEVOX_RESULT_INVALID_MODEL_DATA_ERROR = 27, - VOICEVOX_RESULT_UNLOADED_MODEL_ERROR = 19, VOICEVOX_RESULT_LOAD_USER_DICT_ERROR = 20, VOICEVOX_RESULT_SAVE_USER_DICT_ERROR = 21, VOICEVOX_RESULT_USER_DICT_WORD_NOT_FOUND_ERROR = 22, From 48a06294a61eeb3d751ffd3cd1a428a7a07d0ab0 Mon Sep 17 00:00:00 2001 From: Ryo Yamashita Date: Sat, 30 Sep 2023 21:29:46 +0900 Subject: [PATCH 14/20] =?UTF-8?q?Sphinx=E3=82=92v6=E3=81=AB=E4=B8=8A?= =?UTF-8?q?=E3=81=92=E3=82=8B=20(#626)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- crates/voicevox_core_python_api/requirements.txt | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/crates/voicevox_core_python_api/requirements.txt b/crates/voicevox_core_python_api/requirements.txt index cad09a4ef..ff06d7870 100644 --- a/crates/voicevox_core_python_api/requirements.txt +++ b/crates/voicevox_core_python_api/requirements.txt @@ -1,4 +1,4 @@ -Sphinx>=5.3.0,<6 +Sphinx>=6.2.1,<7 maturin>=0.13.2,<0.14 -pydata-sphinx-theme>=0.11.0,<0.12 -sphinx-autoapi>=2.0.0,<3 +pydata-sphinx-theme>=0.14.1,<0.15 +sphinx-autoapi>=3.0.0,<4 From ad2a3e9dfab09021d7c7f1067c512a00f33b9fe4 Mon Sep 17 00:00:00 2001 From: Ryo Yamashita Date: Wed, 4 Oct 2023 06:47:28 +0900 Subject: [PATCH 15/20] =?UTF-8?q?=E3=82=A8=E3=83=A9=E3=83=BC=E3=83=A1?= =?UTF-8?q?=E3=83=83=E3=82=BB=E3=83=BC=E3=82=B8=E3=81=AB=E3=81=8A=E3=81=91?= =?UTF-8?q?=E3=82=8Bcontext=E3=81=A8source=E3=82=92=E6=98=8E=E7=A2=BA?= =?UTF-8?q?=E3=81=AB=E5=8C=BA=E5=88=86=E3=81=99=E3=82=8B=20(#624)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * [skip ci] WIP * [skip ci] 不要な`map_err`を削除 * [skip ci] 定数を関連定数に * `UseUserDict` * `SaveUserDict` * `LoadUserDict` * `ParseKana` * `ExtractFullContextLabel` * `GetSupportedDevices` * `LoadModel` * `name` → `function` --- crates/voicevox_core/src/devices.rs | 2 +- .../src/engine/full_context_label.rs | 66 +++++++++------ .../voicevox_core/src/engine/kana_parser.rs | 11 +-- crates/voicevox_core/src/engine/open_jtalk.rs | 82 +++++++++---------- crates/voicevox_core/src/error.rs | 38 ++++----- crates/voicevox_core/src/user_dict/dict.rs | 12 ++- crates/voicevox_core/src/user_dict/word.rs | 22 +++-- 7 files changed, 121 insertions(+), 112 deletions(-) diff --git a/crates/voicevox_core/src/devices.rs b/crates/voicevox_core/src/devices.rs index 673144881..70847cb81 100644 --- a/crates/voicevox_core/src/devices.rs +++ b/crates/voicevox_core/src/devices.rs @@ -45,7 +45,7 @@ impl SupportedDevices { let mut cuda_support = false; let mut dml_support = false; for provider in onnxruntime::session::get_available_providers() - .map_err(|e| ErrorRepr::GetSupportedDevices(e.into()))? + .map_err(ErrorRepr::GetSupportedDevices)? .iter() { match provider.as_str() { diff --git a/crates/voicevox_core/src/engine/full_context_label.rs b/crates/voicevox_core/src/engine/full_context_label.rs index 31471c004..667dbc8b1 100644 --- a/crates/voicevox_core/src/engine/full_context_label.rs +++ b/crates/voicevox_core/src/engine/full_context_label.rs @@ -1,22 +1,32 @@ use std::collections::HashMap; use super::*; +use crate::engine::open_jtalk::OpenjtalkFunctionError; use once_cell::sync::Lazy; use regex::Regex; +// FIXME: 入力テキストをここで持って、メッセージに含む #[derive(thiserror::Error, Debug)] -pub(crate) enum FullContextLabelError { - #[error("label parse error label:{label}")] +#[error("入力テキストからのフルコンテキストラベル抽出に失敗しました: {context}")] +pub(crate) struct FullContextLabelError { + context: ErrorKind, + #[source] + source: Option, +} + +#[derive(derive_more::Display, Debug)] +enum ErrorKind { + #[display(fmt = "Open JTalkで解釈することができませんでした")] + OpenJtalk, + + #[display(fmt = "label parse error label: {label}")] LabelParse { label: String }, - #[error("too long mora mora_phonemes:{mora_phonemes:?}")] + #[display(fmt = "too long mora mora_phonemes: {mora_phonemes:?}")] TooLongMora { mora_phonemes: Vec }, - #[error("invalid mora:{mora:?}")] + #[display(fmt = "invalid mora: {mora:?}")] InvalidMora { mora: Box }, - - #[error(transparent)] - OpenJtalk(#[from] open_jtalk::OpenJtalkError), } type Result = std::result::Result; @@ -38,18 +48,18 @@ static H1_REGEX: Lazy = Lazy::new(|| Regex::new(r"(/H:(\d+|xx)_)").unwrap static I3_REGEX: Lazy = Lazy::new(|| Regex::new(r"(@(\d+|xx)\+)").unwrap()); static J1_REGEX: Lazy = Lazy::new(|| Regex::new(r"(/J:(\d+|xx)_)").unwrap()); -fn string_feature_by_regex(re: &Regex, label: &str) -> Result { +fn string_feature_by_regex(re: &Regex, label: &str) -> std::result::Result { if let Some(caps) = re.captures(label) { Ok(caps.get(2).unwrap().as_str().to_string()) } else { - Err(FullContextLabelError::LabelParse { + Err(ErrorKind::LabelParse { label: label.into(), }) } } impl Phoneme { - pub(crate) fn from_label(label: impl Into) -> Result { + fn from_label(label: impl Into) -> std::result::Result { let mut contexts = HashMap::::with_capacity(10); let label = label.into(); contexts.insert("p3".into(), string_feature_by_regex(&P3_REGEX, &label)?); @@ -116,7 +126,7 @@ pub struct AccentPhrase { } impl AccentPhrase { - pub(crate) fn from_phonemes(mut phonemes: Vec) -> Result { + fn from_phonemes(mut phonemes: Vec) -> std::result::Result { let mut moras = Vec::with_capacity(phonemes.len()); let mut mora_phonemes = Vec::with_capacity(phonemes.len()); for i in 0..phonemes.len() { @@ -140,7 +150,7 @@ impl AccentPhrase { mora_phonemes.get(1).unwrap().clone(), )); } else { - return Err(FullContextLabelError::TooLongMora { mora_phonemes }); + return Err(ErrorKind::TooLongMora { mora_phonemes }); } mora_phonemes.clear(); } @@ -151,11 +161,11 @@ impl AccentPhrase { .vowel() .contexts() .get("f2") - .ok_or_else(|| FullContextLabelError::InvalidMora { + .ok_or_else(|| ErrorKind::InvalidMora { mora: mora.clone().into(), })? .parse() - .map_err(|_| FullContextLabelError::InvalidMora { + .map_err(|_| ErrorKind::InvalidMora { mora: mora.clone().into(), })?; @@ -208,7 +218,7 @@ pub struct BreathGroup { } impl BreathGroup { - pub(crate) fn from_phonemes(phonemes: Vec) -> Result { + fn from_phonemes(phonemes: Vec) -> std::result::Result { let mut accent_phrases = Vec::with_capacity(phonemes.len()); let mut accent_phonemes = Vec::with_capacity(phonemes.len()); for i in 0..phonemes.len() { @@ -256,7 +266,7 @@ pub struct Utterance { } impl Utterance { - pub(crate) fn from_phonemes(phonemes: Vec) -> Result { + fn from_phonemes(phonemes: Vec) -> std::result::Result { let mut breath_groups = vec![]; let mut group_phonemes = Vec::with_capacity(phonemes.len()); let mut pauses = vec![]; @@ -309,12 +319,22 @@ impl Utterance { open_jtalk: &open_jtalk::OpenJtalk, text: impl AsRef, ) -> Result { - let labels = open_jtalk.extract_fullcontext(text)?; - Self::from_phonemes( - labels - .into_iter() - .map(Phoneme::from_label) - .collect::>>()?, - ) + let labels = + open_jtalk + .extract_fullcontext(text) + .map_err(|source| FullContextLabelError { + context: ErrorKind::OpenJtalk, + source: Some(source), + })?; + + labels + .into_iter() + .map(Phoneme::from_label) + .collect::, _>>() + .and_then(Self::from_phonemes) + .map_err(|context| FullContextLabelError { + context, + source: None, + }) } } diff --git a/crates/voicevox_core/src/engine/kana_parser.rs b/crates/voicevox_core/src/engine/kana_parser.rs index 14a3aa70d..5bd09f3e6 100644 --- a/crates/voicevox_core/src/engine/kana_parser.rs +++ b/crates/voicevox_core/src/engine/kana_parser.rs @@ -10,17 +10,10 @@ const PAUSE_DELIMITER: char = '、'; const WIDE_INTERROGATION_MARK: char = '?'; const LOOP_LIMIT: usize = 300; -#[derive(Clone, Debug, PartialEq, Eq)] +#[derive(Clone, Debug, PartialEq, Eq, thiserror::Error)] +#[error("入力テキストをAquesTalk風記法としてパースすることに失敗しました: {_0}")] pub(crate) struct KanaParseError(String); -impl std::fmt::Display for KanaParseError { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(f, "Parse Error: {}", self.0) - } -} - -impl std::error::Error for KanaParseError {} - type KanaParseResult = std::result::Result; static TEXT2MORA_WITH_UNVOICE: Lazy> = Lazy::new(|| { diff --git a/crates/voicevox_core/src/engine/open_jtalk.rs b/crates/voicevox_core/src/engine/open_jtalk.rs index b67709b08..f34b43751 100644 --- a/crates/voicevox_core/src/engine/open_jtalk.rs +++ b/crates/voicevox_core/src/engine/open_jtalk.rs @@ -3,6 +3,8 @@ use std::{ path::{Path, PathBuf}, sync::Mutex, }; + +use anyhow::anyhow; use tempfile::NamedTempFile; use ::open_jtalk::*; @@ -10,19 +12,13 @@ use ::open_jtalk::*; use crate::{error::ErrorRepr, UserDict}; #[derive(thiserror::Error, Debug)] -pub(crate) enum OpenJtalkError { - #[error("open_jtalk load error")] - Load { mecab_dict_dir: PathBuf }, - #[error("open_jtalk extract_fullcontext error")] - ExtractFullContext { - text: String, - #[source] - source: Option, - }, +#[error("`{function}`の実行が失敗しました")] +pub(crate) struct OpenjtalkFunctionError { + function: &'static str, + #[source] + source: Option, } -type Result = std::result::Result; - /// テキスト解析器としてのOpen JTalk。 pub struct OpenJtalk { resources: Mutex, @@ -53,8 +49,10 @@ impl OpenJtalk { open_jtalk_dict_dir: impl AsRef, ) -> crate::result::Result { let mut s = Self::new_without_dic(); - s.load(open_jtalk_dict_dir) - .map_err(|_| ErrorRepr::NotLoadedOpenjtalkDict)?; + s.load(open_jtalk_dict_dir).map_err(|()| { + // FIXME: 「システム辞書を読もうとしたけど読めなかった」というエラーをちゃんと用意する + ErrorRepr::NotLoadedOpenjtalkDict + })?; Ok(s) } @@ -70,13 +68,12 @@ impl OpenJtalk { .ok_or(ErrorRepr::NotLoadedOpenjtalkDict)?; // ユーザー辞書用のcsvを作成 - let mut temp_csv = - NamedTempFile::new().map_err(|e| ErrorRepr::UseUserDict(e.to_string()))?; + let mut temp_csv = NamedTempFile::new().map_err(|e| ErrorRepr::UseUserDict(e.into()))?; temp_csv .write_all(user_dict.to_mecab_format().as_bytes()) - .map_err(|e| ErrorRepr::UseUserDict(e.to_string()))?; + .map_err(|e| ErrorRepr::UseUserDict(e.into()))?; let temp_csv_path = temp_csv.into_temp_path(); - let temp_dict = NamedTempFile::new().map_err(|e| ErrorRepr::UseUserDict(e.to_string()))?; + let temp_dict = NamedTempFile::new().map_err(|e| ErrorRepr::UseUserDict(e.into()))?; let temp_dict_path = temp_dict.into_temp_path(); // Mecabでユーザー辞書をコンパイル @@ -100,15 +97,16 @@ impl OpenJtalk { let result = mecab.load_with_userdic(Path::new(dict_dir), Some(Path::new(&temp_dict_path))); if !result { - return Err( - ErrorRepr::UseUserDict("辞書のコンパイルに失敗しました".to_string()).into(), - ); + return Err(ErrorRepr::UseUserDict(anyhow!("辞書のコンパイルに失敗しました")).into()); } Ok(()) } - pub(crate) fn extract_fullcontext(&self, text: impl AsRef) -> Result> { + pub(crate) fn extract_fullcontext( + &self, + text: impl AsRef, + ) -> std::result::Result, OpenjtalkFunctionError> { let Resources { mecab, njd, @@ -119,19 +117,16 @@ impl OpenJtalk { njd.refresh(); mecab.refresh(); - let mecab_text = - text2mecab(text.as_ref()).map_err(|e| OpenJtalkError::ExtractFullContext { - text: text.as_ref().into(), - source: Some(e.into()), - })?; + let mecab_text = text2mecab(text.as_ref()).map_err(|e| OpenjtalkFunctionError { + function: "text2mecab", + source: Some(e), + })?; if mecab.analysis(mecab_text) { njd.mecab2njd( - mecab - .get_feature() - .ok_or(OpenJtalkError::ExtractFullContext { - text: text.as_ref().into(), - source: None, - })?, + mecab.get_feature().ok_or(OpenjtalkFunctionError { + function: "Mecab_get_feature", + source: None, + })?, mecab.get_size(), ); njd.set_pronunciation(); @@ -144,20 +139,20 @@ impl OpenJtalk { jpcommon.make_label(); jpcommon .get_label_feature_to_iter() - .ok_or_else(|| OpenJtalkError::ExtractFullContext { - text: text.as_ref().into(), + .ok_or(OpenjtalkFunctionError { + function: "JPCommon_get_label_feature", source: None, }) .map(|iter| iter.map(|s| s.to_string()).collect()) } else { - Err(OpenJtalkError::ExtractFullContext { - text: text.as_ref().into(), + Err(OpenjtalkFunctionError { + function: "Mecab_analysis", source: None, }) } } - fn load(&mut self, open_jtalk_dict_dir: impl AsRef) -> Result<()> { + fn load(&mut self, open_jtalk_dict_dir: impl AsRef) -> std::result::Result<(), ()> { let result = self .resources .lock() @@ -169,9 +164,7 @@ impl OpenJtalk { Ok(()) } else { self.dict_dir = None; - Err(OpenJtalkError::Load { - mecab_dict_dir: open_jtalk_dict_dir.as_ref().into(), - }) + Err(()) } } @@ -275,9 +268,12 @@ mod tests { } #[rstest] - #[case("",Err(OpenJtalkError::ExtractFullContext{text:"".into(),source:None}))] + #[case("", Err(OpenjtalkFunctionError { function: "Mecab_get_feature", source: None }))] #[case("こんにちは、ヒホです。", Ok(testdata_hello_hiho()))] - fn extract_fullcontext_works(#[case] text: &str, #[case] expected: super::Result>) { + fn extract_fullcontext_works( + #[case] text: &str, + #[case] expected: std::result::Result, OpenjtalkFunctionError>, + ) { let open_jtalk = OpenJtalk::new_with_initialize(OPEN_JTALK_DIC_DIR).unwrap(); let result = open_jtalk.extract_fullcontext(text); assert_debug_fmt_eq!(expected, result); @@ -287,7 +283,7 @@ mod tests { #[case("こんにちは、ヒホです。", Ok(testdata_hello_hiho()))] fn extract_fullcontext_loop_works( #[case] text: &str, - #[case] expected: super::Result>, + #[case] expected: std::result::Result, OpenjtalkFunctionError>, ) { let open_jtalk = OpenJtalk::new_with_initialize(OPEN_JTALK_DIC_DIR).unwrap(); for _ in 0..10 { diff --git a/crates/voicevox_core/src/error.rs b/crates/voicevox_core/src/error.rs index ce4a90b33..551bd8c52 100644 --- a/crates/voicevox_core/src/error.rs +++ b/crates/voicevox_core/src/error.rs @@ -2,6 +2,7 @@ use self::engine::{FullContextLabelError, KanaParseError}; use super::*; //use engine:: use duplicate::duplicate_item; +use onnxruntime::OrtError; use std::path::PathBuf; use thiserror::Error; use uuid::Uuid; @@ -16,6 +17,7 @@ pub struct Error(#[from] ErrorRepr); [ LoadModelError ]; [ FullContextLabelError ]; [ KanaParseError ]; + [ InvalidWordError ]; )] impl From for Error { fn from(err: E) -> Self { @@ -23,13 +25,6 @@ impl From for Error { } } -// FIXME: `ErrorRepr::InvalidWord`を`#[error(transparent)]`にする -impl From for Error { - fn from(err: InvalidWordError) -> Self { - ErrorRepr::InvalidWord(err).into() - } -} - impl Error { /// 対応する[`ErrorKind`]を返す。 pub fn kind(&self) -> ErrorKind { @@ -69,8 +64,8 @@ pub(crate) enum ErrorRepr { #[error(transparent)] LoadModel(#[from] LoadModelError), - #[error("サポートされているデバイス情報取得中にエラーが発生しました,{0}")] - GetSupportedDevices(#[source] anyhow::Error), + #[error("サポートされているデバイス情報取得中にエラーが発生しました")] + GetSupportedDevices(#[source] OrtError), #[error( "`{style_id}`に対するスタイルが見つかりませんでした。音声モデルが読み込まれていないか、読\ @@ -87,26 +82,26 @@ pub(crate) enum ErrorRepr { #[error("推論に失敗しました")] InferenceFailed, - #[error("入力テキストからのフルコンテキストラベル抽出に失敗しました,{0}")] + #[error(transparent)] ExtractFullContextLabel(#[from] FullContextLabelError), - #[error("入力テキストをAquesTalk風記法としてパースすることに失敗しました,{0}")] + #[error(transparent)] ParseKana(#[from] KanaParseError), - #[error("ユーザー辞書を読み込めませんでした: {0}")] - LoadUserDict(String), + #[error("ユーザー辞書を読み込めませんでした")] + LoadUserDict(#[source] anyhow::Error), - #[error("ユーザー辞書を書き込めませんでした: {0}")] - SaveUserDict(String), + #[error("ユーザー辞書を書き込めませんでした")] + SaveUserDict(#[source] anyhow::Error), #[error("ユーザー辞書に単語が見つかりませんでした: {0}")] WordNotFound(Uuid), - #[error("OpenJTalkのユーザー辞書の設定に失敗しました: {0}")] - UseUserDict(String), + #[error("OpenJTalkのユーザー辞書の設定に失敗しました")] + UseUserDict(#[source] anyhow::Error), - #[error("ユーザー辞書の単語のバリデーションに失敗しました: {0}")] - InvalidWord(InvalidWordError), + #[error(transparent)] + InvalidWord(#[from] InvalidWordError), } /// エラーの種類。 @@ -154,10 +149,7 @@ pub(crate) type LoadModelResult = std::result::Result; /// 音声モデル読み込みのエラー。 #[derive(Error, Debug)] -#[error( - "`{path}`の読み込みに失敗しました: {context}{}", - source.as_ref().map(|e| format!(": {e}")).unwrap_or_default()) -] +#[error("`{path}`の読み込みに失敗しました: {context}")] pub(crate) struct LoadModelError { pub(crate) path: PathBuf, pub(crate) context: LoadModelErrorKind, diff --git a/crates/voicevox_core/src/user_dict/dict.rs b/crates/voicevox_core/src/user_dict/dict.rs index dd342ddd0..1a7820a78 100644 --- a/crates/voicevox_core/src/user_dict/dict.rs +++ b/crates/voicevox_core/src/user_dict/dict.rs @@ -28,11 +28,10 @@ impl UserDict { pub fn load(&mut self, store_path: &str) -> Result<()> { let store_path = std::path::Path::new(store_path); - let store_file = - File::open(store_path).map_err(|e| ErrorRepr::LoadUserDict(e.to_string()))?; + let store_file = File::open(store_path).map_err(|e| ErrorRepr::LoadUserDict(e.into()))?; - let words: IndexMap = serde_json::from_reader(store_file) - .map_err(|e| ErrorRepr::LoadUserDict(e.to_string()))?; + let words: IndexMap = + serde_json::from_reader(store_file).map_err(|e| ErrorRepr::LoadUserDict(e.into()))?; self.words.extend(words); Ok(()) @@ -72,10 +71,9 @@ impl UserDict { /// ユーザー辞書を保存する。 pub fn save(&self, store_path: &str) -> Result<()> { - let mut file = - File::create(store_path).map_err(|e| ErrorRepr::SaveUserDict(e.to_string()))?; + let mut file = File::create(store_path).map_err(|e| ErrorRepr::SaveUserDict(e.into()))?; serde_json::to_writer(&mut file, &self.words) - .map_err(|e| ErrorRepr::SaveUserDict(e.to_string()))?; + .map_err(|e| ErrorRepr::SaveUserDict(e.into()))?; Ok(()) } diff --git a/crates/voicevox_core/src/user_dict/word.rs b/crates/voicevox_core/src/user_dict/word.rs index 6a43958db..dfa04b046 100644 --- a/crates/voicevox_core/src/user_dict/word.rs +++ b/crates/voicevox_core/src/user_dict/word.rs @@ -58,13 +58,24 @@ impl<'de> Deserialize<'de> for UserDictWord { #[allow(clippy::enum_variant_names)] // FIXME #[derive(thiserror::Error, Debug, PartialEq)] pub(crate) enum InvalidWordError { - #[error("無効な発音です({1}): {0:?}")] + #[error("{}: 無効な発音です({_1}): {_0:?}", Self::BASE_MSG)] InvalidPronunciation(String, &'static str), - #[error("優先度は{MIN_PRIORITY}以上{MAX_PRIORITY}以下である必要があります: {0}")] + #[error( + "{}: 優先度は{MIN_PRIORITY}以上{MAX_PRIORITY}以下である必要があります: {_0}", + Self::BASE_MSG + )] InvalidPriority(u32), - #[error("誤ったアクセント型です({1:?}の範囲から外れています): {0}")] + #[error( + "{}: 誤ったアクセント型です({1:?}の範囲から外れています): {_0}", + Self::BASE_MSG + )] InvalidAccentType(usize, RangeToInclusive), } + +impl InvalidWordError { + const BASE_MSG: &str = "ユーザー辞書の単語のバリデーションに失敗しました"; +} + type InvalidWordResult = std::result::Result; static PRONUNCIATION_REGEX: Lazy = Lazy::new(|| Regex::new(r"^[ァ-ヴー]+$").unwrap()); @@ -105,9 +116,8 @@ impl UserDictWord { if MIN_PRIORITY > priority || priority > MAX_PRIORITY { return Err(ErrorRepr::InvalidWord(InvalidWordError::InvalidPriority(priority)).into()); } - validate_pronunciation(&pronunciation).map_err(ErrorRepr::InvalidWord)?; - let mora_count = - calculate_mora_count(&pronunciation, accent_type).map_err(ErrorRepr::InvalidWord)?; + validate_pronunciation(&pronunciation)?; + let mora_count = calculate_mora_count(&pronunciation, accent_type)?; Ok(Self { surface: to_zenkaku(surface), pronunciation, From 1895ab523220369c7101331c73b3358d6743549d Mon Sep 17 00:00:00 2001 From: Hiroshiba Date: Thu, 5 Oct 2023 04:11:54 +0900 Subject: [PATCH 16/20] =?UTF-8?q?[release-0.14]=20=E3=82=B3=E3=83=BC?= =?UTF-8?q?=E3=83=89=E7=BD=B2=E5=90=8D=E3=82=92eSignerCKA=E3=81=AB=20(#627?= =?UTF-8?q?)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/build_and_deploy.yml | 10 +++-- build_util/codesign.bash | 54 +++++++++++++++++++------- 2 files changed, 47 insertions(+), 17 deletions(-) diff --git a/.github/workflows/build_and_deploy.yml b/.github/workflows/build_and_deploy.yml index 793c71efa..ab54792e1 100644 --- a/.github/workflows/build_and_deploy.yml +++ b/.github/workflows/build_and_deploy.yml @@ -154,8 +154,9 @@ jobs: run: | bash build_util/codesign.bash "artifact/${{ env.ASSET_NAME }}/voicevox_core.dll" env: - CERT_BASE64: ${{ secrets.CERT_BASE64 }} - CERT_PASSWORD: ${{ secrets.CERT_PASSWORD }} + ESIGNERCKA_USERNAME: ${{ secrets.ESIGNERCKA_USERNAME }} + ESIGNERCKA_PASSWORD: ${{ secrets.ESIGNERCKA_PASSWORD }} + ESIGNERCKA_TOTP_SECRET: ${{ secrets.ESIGNERCKA_TOTP_SECRET }} - name: Archive artifact shell: bash run: | @@ -241,8 +242,9 @@ jobs: run: | bash build_util/codesign.bash ./${{ matrix.name }} env: - CERT_BASE64: ${{ secrets.CERT_BASE64 }} - CERT_PASSWORD: ${{ secrets.CERT_PASSWORD }} + ESIGNERCKA_USERNAME: ${{ secrets.ESIGNERCKA_USERNAME }} + ESIGNERCKA_PASSWORD: ${{ secrets.ESIGNERCKA_PASSWORD }} + ESIGNERCKA_TOTP_SECRET: ${{ secrets.ESIGNERCKA_TOTP_SECRET }} - name: Upload to Release if: env.VERSION != 'DEBUG' && env.SKIP_UPLOADING_RELEASE_ASSET == '0' uses: softprops/action-gh-release@v1 diff --git a/build_util/codesign.bash b/build_util/codesign.bash index 8bf3ac8be..5c80cf55f 100755 --- a/build_util/codesign.bash +++ b/build_util/codesign.bash @@ -1,14 +1,20 @@ #!/usr/bin/env bash # !!! コードサイニング証明書を取り扱うので取り扱い注意 !!! +# eSignerCKAを使ってコード署名する + set -eu -if [ ! -v CERT_BASE64 ]; then - echo "CERT_BASE64が未定義です" +if [ ! -v ESIGNERCKA_USERNAME ]; then # eSignerCKAのユーザー名 + echo "ESIGNERCKA_USERNAMEが未定義です" + exit 1 +fi +if [ ! -v ESIGNERCKA_PASSWORD ]; then # eSignerCKAのパスワード + echo "ESIGNERCKA_PASSWORDが未定義です" exit 1 fi -if [ ! -v CERT_PASSWORD ]; then - echo "CERT_PASSWORDが未定義です" +if [ ! -v ESIGNERCKA_TOTP_SECRET ]; then # eSignerCKAのTOTP Secret + echo "ESIGNERCKA_TOTP_SECRETが未定義です" exit 1 fi @@ -18,22 +24,44 @@ if [ $# -ne 1 ]; then fi target_file_glob="$1" -# 証明書 -CERT_PATH=cert.pfx -echo -n "$CERT_BASE64" | base64 -d - > $CERT_PATH +# eSignerCKAのセットアップ +INSTALL_DIR='..\eSignerCKA' +if [ ! -d "$INSTALL_DIR" ]; then + curl -LO "https://github.com/SSLcom/eSignerCKA/releases/download/v1.0.6/SSL.COM-eSigner-CKA_1.0.6.zip" + unzip -o SSL.COM-eSigner-CKA_1.0.6.zip + mv ./*eSigner*CKA_*.exe eSigner_CKA_Installer.exe + powershell " + & ./eSigner_CKA_Installer.exe /CURRENTUSER /VERYSILENT /SUPPRESSMSGBOXES /DIR='$INSTALL_DIR' | Out-Null + & '$INSTALL_DIR\eSignerCKATool.exe' config -mode product -user '$ESIGNERCKA_USERNAME' -pass '$ESIGNERCKA_PASSWORD' -totp '$ESIGNERCKA_TOTP_SECRET' -key '$INSTALL_DIR\master.key' -r + & '$INSTALL_DIR\eSignerCKATool.exe' unload + " + rm SSL.COM-eSigner-CKA_1.0.6.zip eSigner_CKA_Installer.exe +fi + +# 証明書を読み込む +powershell "& '$INSTALL_DIR\eSignerCKATool.exe' load" + +# shellcheck disable=SC2016 +THUMBPRINT=$( + powershell ' + $CodeSigningCert = Get-ChildItem Cert:\CurrentUser\My -CodeSigningCert | Select-Object -First 1 + echo "$($CodeSigningCert.Thumbprint)" + ' +) # 指定ファイルに署名する function codesign() { TARGET="$1" - SIGNTOOL=$(find "C:/Program Files (x86)/Windows Kits/10/App Certification Kit" -name "signtool.exe" | sort -V | tail -n 1) - powershell "& '$SIGNTOOL' sign /fd SHA256 /td SHA256 /tr http://timestamp.digicert.com /f $CERT_PATH /p $CERT_PASSWORD '$TARGET'" + # shellcheck disable=SC2012 + SIGNTOOL=$(ls "C:/Program Files (x86)/Windows Kits/"10/bin/*/x86/signtool.exe | sort -V | tail -n 1) # なぜかこれじゃないと動かない + powershell "& '$SIGNTOOL' sign /fd SHA256 /td SHA256 /tr http://timestamp.digicert.com /sha1 '$THUMBPRINT' '$TARGET'" } # 指定ファイルが署名されているか function is_signed() { TARGET="$1" SIGNTOOL=$(find "C:/Program Files (x86)/Windows Kits/10/App Certification Kit" -name "signtool.exe" | sort -V | tail -n 1) - powershell "& '$SIGNTOOL' verify /pa '$TARGET'" || return 1 + powershell "& '$SIGNTOOL' verify /pa '$TARGET'" >/dev/null 2>&1 || return 1 } # 署名されていなければ署名 @@ -42,10 +70,10 @@ ls $target_file_glob | while read -r target_file; do if is_signed "$target_file"; then echo "署名済み: $target_file" else - echo "署名: $target_file" + echo "署名開始: $target_file" codesign "$target_file" fi done -# 証明書を消去 -rm $CERT_PATH +# 証明書を破棄 +powershell "& '$INSTALL_DIR\eSignerCKATool.exe' unload" From 030748bb92c5859757b11aab75b47c19528eacdc Mon Sep 17 00:00:00 2001 From: Hiroshiba Date: Sun, 8 Oct 2023 15:08:54 +0900 Subject: [PATCH 17/20] =?UTF-8?q?=E3=83=AA=E3=82=BD=E3=83=BC=E3=82=B9?= =?UTF-8?q?=E3=81=AE=E3=83=90=E3=83=BC=E3=82=B8=E3=83=A7=E3=83=B3=E3=82=92?= =?UTF-8?q?=E6=9B=B4=E6=96=B0=20(#630)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/build_and_deploy.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/build_and_deploy.yml b/.github/workflows/build_and_deploy.yml index 8ca54ca82..08745bf63 100644 --- a/.github/workflows/build_and_deploy.yml +++ b/.github/workflows/build_and_deploy.yml @@ -28,8 +28,8 @@ on: push: env: - VOICEVOX_RESOURCE_VERSION: "0.15.0-preview.1" - VOICEVOX_FAT_RESOURCE_VERSION: "0.15.0-preview.1" + VOICEVOX_RESOURCE_VERSION: "0.15.0-preview.3" + VOICEVOX_FAT_RESOURCE_VERSION: "0.15.0-preview.3" # releaseタグ名か、workflow_dispatchでのバージョン名か、'0.0.0'が入る VERSION: ${{ github.event.release.tag_name || github.event.inputs.version || '0.0.0' }} PRODUCTION_REPOSITORY_TAG: "0.15.0-preview.2" # 製品版のタグ名 From 82096331f73301317c08caf633191c3e85f4af66 Mon Sep 17 00:00:00 2001 From: Ryo Yamashita Date: Mon, 9 Oct 2023 01:43:48 +0900 Subject: [PATCH 18/20] =?UTF-8?q?C=20API=E3=81=AE=E3=82=BB=E3=83=BC?= =?UTF-8?q?=E3=83=95=E3=83=86=E3=82=A3=E3=83=8D=E3=83=83=E3=83=88=E3=81=AE?= =?UTF-8?q?=E3=83=A1=E3=83=83=E3=82=BB=E3=83=BC=E3=82=B8=E3=82=92=E6=94=B9?= =?UTF-8?q?=E5=96=84=E3=81=99=E3=82=8B=20(#625)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- crates/voicevox_core_c_api/src/drop_check.rs | 24 +++++++++++++++++-- crates/voicevox_core_c_api/src/slice_owner.rs | 8 ++++++- 2 files changed, 29 insertions(+), 3 deletions(-) diff --git a/crates/voicevox_core_c_api/src/drop_check.rs b/crates/voicevox_core_c_api/src/drop_check.rs index 546739dbd..36acec570 100644 --- a/crates/voicevox_core_c_api/src/drop_check.rs +++ b/crates/voicevox_core_c_api/src/drop_check.rs @@ -46,8 +46,15 @@ impl CStringDropChecker { owned_str_addrs, .. } = &mut *self.0.lock().unwrap(); - let duplicated = !owned_str_addrs.insert(s.as_ptr() as usize); - assert!(!duplicated, "duplicated"); + let ptr = s.as_ptr(); + let duplicated = !owned_str_addrs.insert(ptr as usize); + if duplicated { + panic!( + "別の{ptr:p}が管理下にあります。原因としては以前に別の文字列が{ptr:p}として存在\ + しており、それが誤った形で解放されたことが考えられます。このライブラリで生成した\ + オブジェクトの解放は、このライブラリが提供するAPIで行われなくてはなりません", + ); + } s } @@ -100,8 +107,21 @@ impl CStringDropChecker { mod tests { use std::ffi::{c_char, CStr}; + use cstr::cstr; + use super::CStringDropChecker; + #[test] + #[should_panic( + expected = "このライブラリで生成したオブジェクトの解放は、このライブラリが提供するAPIで\ + 行われなくてはなりません" + )] + fn it_denies_duplicated_char_ptr() { + let checker = CStringDropChecker::new(); + let s = cstr!("").to_owned(); + checker.whitelist(checker.whitelist(s)); + } + #[test] #[should_panic( expected = "解放しようとしたポインタはvoicevox_coreの管理下にありません。誤ったポインタであるか、二重解放になっていることが考えられます" diff --git a/crates/voicevox_core_c_api/src/slice_owner.rs b/crates/voicevox_core_c_api/src/slice_owner.rs index 1d95cf935..fa75add52 100644 --- a/crates/voicevox_core_c_api/src/slice_owner.rs +++ b/crates/voicevox_core_c_api/src/slice_owner.rs @@ -47,7 +47,13 @@ impl SliceOwner { let len = slice.len(); let duplicated = slices.insert(ptr as usize, slice.into()).is_some(); - assert!(!duplicated, "duplicated"); + if duplicated { + panic!( + "別の{ptr:p}が管理下にあります。原因としては以前に別の配列が{ptr:p}として存在\ + しており、それが誤った形で解放されたことが考えられます。このライブラリで生成した\ + オブジェクトの解放は、このライブラリが提供するAPIで行われなくてはなりません", + ); + } out_ptr.as_ptr().write_unaligned(ptr); out_len.as_ptr().write_unaligned(len); From ae8d2473284e3304420e790f3a833de24cfb1c3f Mon Sep 17 00:00:00 2001 From: Nanashi Date: Mon, 9 Oct 2023 14:52:34 +0900 Subject: [PATCH 19/20] =?UTF-8?q?Java=20API=EF=BC=9A=E3=83=93=E3=83=AB?= =?UTF-8?q?=E3=83=89Workflow=E3=82=92=E8=BF=BD=E5=8A=A0=20(#621)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/build_and_deploy.yml | 101 +++++++++++++++++- crates/voicevox_core_java_api/Cargo.toml | 3 + .../lib/build-android.gradle | 20 ++++ .../voicevox_core_java_api/lib/build.gradle | 14 +++ 4 files changed, 137 insertions(+), 1 deletion(-) diff --git a/.github/workflows/build_and_deploy.yml b/.github/workflows/build_and_deploy.yml index 08745bf63..c9ae3e793 100644 --- a/.github/workflows/build_and_deploy.yml +++ b/.github/workflows/build_and_deploy.yml @@ -257,6 +257,9 @@ jobs: run: | cargo set-version "$VERSION" --exclude voicevox_core_python_api --exclude download --exclude xtask if ${{ !!matrix.whl_local_version }}; then cargo set-version "$VERSION+"${{ matrix.whl_local_version }} -p voicevox_core_python_api; fi + - name: cache target + uses: Swatinem/rust-cache@v2 + if: github.event.inputs.is_production != 'true' - name: build voicevox_core_c_api shell: bash run: | @@ -275,6 +278,7 @@ jobs: if: matrix.whl_local_version id: build-voicevox-core-python-api run: | + rm -rf ./target/wheels pip install -r ./crates/voicevox_core_python_api/requirements.txt function build() { maturin build --manifest-path ./crates/voicevox_core_python_api/Cargo.toml --features ${{ matrix.features }}, --target ${{ matrix.target }} --release @@ -287,6 +291,17 @@ jobs: echo "whl=$(find ./target/wheels -type f)" >> "$GITHUB_OUTPUT" env: ORT_USE_CUDA: ${{ matrix.use_cuda }} + - name: build voicevox_core_java_api + if: "contains(matrix.target, 'android')" + run: | + function build() { + cargo build -p voicevox_core_java_api -vv --features ${{ matrix.features }}, --target ${{ matrix.target }} --release + } + if ${{ github.event.inputs.is_production != 'true' }}; then + build + else + build > /dev/null 2>&1 + fi - name: Set ASSET_NAME env var run: echo "ASSET_NAME=voicevox_core-${{ matrix.artifact_name }}-${{ env.VERSION }}" >> "$GITHUB_ENV" - name: Organize artifact @@ -301,6 +316,9 @@ jobs: cp -v README.md "artifact/${{ env.ASSET_NAME }}/README.txt" cp -vr model "artifact/${{ env.ASSET_NAME }}/" echo "${{ env.VERSION }}" > "artifact/${{ env.ASSET_NAME }}/VERSION" + + mkdir java_artifact + cp -v target/${{ matrix.target }}/release/libvoicevox_core_java_api.so java_artifact/ || true - name: Code signing (Windows) if: startsWith(matrix.os, 'windows') && github.event.inputs.code_signing == 'true' run: | @@ -311,7 +329,7 @@ jobs: ESIGNERCKA_TOTP_SECRET: ${{ secrets.ESIGNERCKA_TOTP_SECRET }} - name: Upload artifact to build XCFramework if: contains(matrix.target, 'ios') - uses: actions/upload-artifact@v2 + uses: actions/upload-artifact@v3 with: name: voicevox_core-${{ matrix.target }} path: artifact/${{ env.ASSET_NAME }} @@ -337,6 +355,12 @@ jobs: files: |- ${{ steps.build-voicevox-core-python-api.outputs.whl }} target_commitish: ${{ github.sha }} + - name: Upload voicevox_core_java_api artifact + if: env.VERSION != '0.0.0' && contains(matrix.target, 'android') + uses: actions/upload-artifact@v3 + with: + name: voicevox_core_java_api-${{ matrix.artifact_name }} + path: java_artifact build_xcframework: if: ${{ !(github.event_name != 'release' && github.event_name != 'workflow_dispatch') }} # !env.IS_SIMPLE_TEST と同じ @@ -392,6 +416,81 @@ jobs: ${{ env.ASSET_NAME }}.zip target_commitish: ${{ github.sha }} + build_java_package: + runs-on: ubuntu-latest + if: ${{ !(github.event_name != 'release' && github.event_name != 'workflow_dispatch') }} # !env.IS_SIMPLE_TEST と同じ + needs: + - build_and_deploy + steps: + - uses: actions/checkout@v3 + - name: Set up Rust + uses: ./.github/actions/rust-toolchain-from-file + - name: Set up Java + uses: actions/setup-java@v2 + with: + java-version: "17" + distribution: "adopt" + - uses: nttld/setup-ndk@v1 + id: setup-ndk + with: + ndk-version: r25b + - name: Install cargo-binstall + uses: taiki-e/install-action@cargo-binstall + - name: Install cargo-edit + run: cargo binstall cargo-edit@^0.11 --no-confirm + - name: set cargo version + run: | + cargo set-version "$VERSION" -p voicevox_core_java_api + + - name: "Download artifact (android-arm64-cpu)" + uses: actions/download-artifact@v3 + with: + name: voicevox_core_java_api-android-arm64-cpu + path: artifact/android-arm64-cpu + + - name: "Download artifact (android-x86_64-cpu)" + uses: actions/download-artifact@v3 + with: + name: voicevox_core_java_api-android-x86_64-cpu + path: artifact/android-x86_64-cpu + + - name: Print tree + run: tree artifact + + - name: Build voicevoxcore-android + run: | + rm -rf crates/voicevox_core_java_api/lib/src/main/resources/dll + cat < Date: Tue, 10 Oct 2023 00:29:06 +0900 Subject: [PATCH 20/20] =?UTF-8?q?[docs]=20Rust=E4=BB=A5=E5=A4=96=E3=81=AE?= =?UTF-8?q?=EF=BC=91=E3=81=A4=E3=81=AE=E8=A8=80=E8=AA=9E=E3=81=A7=E3=81=AE?= =?UTF-8?q?=E3=82=B3=E3=82=A2=E6=A9=9F=E8=83=BD=E8=BF=BD=E5=8A=A0=E5=AE=9F?= =?UTF-8?q?=E8=A3=85=E3=81=AF=E3=81=97=E3=81=AA=E3=81=84=E6=96=B9=E9=87=9D?= =?UTF-8?q?=E3=81=A7=E3=81=82=E3=82=8B=E3=81=93=E3=81=A8=E3=82=92=E6=98=8E?= =?UTF-8?q?=E8=A8=98=20(#632)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Ryo Yamashita --- README.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/README.md b/README.md index f43dd9883..41cf622f4 100644 --- a/README.md +++ b/README.md @@ -20,6 +20,11 @@ Issue 側で取り組み始めたことを伝えるか、最初に Draft プル [VOICEVOX 非公式 Discord サーバー](https://discord.gg/WMwWetrzuh)にて、開発の議論や雑談を行っています。気軽にご参加ください。 +### Rust 以外の言語の API に関する方針 + +VOICEVOX CORE の主要機能は Rust で実装されることを前提としており、他の言語のラッパーでのみの機能追加はしない方針としています。これは機能の一貫性を保つための方針です。 +各言語の特性に応じた追加実装(例えば、Python での `style_id` の [`NewType`](https://docs.python.org/ja/3/library/typing.html#newtype) 化など)は許容されます。 + ## 環境構築 Downloader を用いて環境構築を行う場合