diff --git a/.cargo/config.toml b/.cargo/config.toml index 64fcc6aea..f015cd17a 100644 --- a/.cargo/config.toml +++ b/.cargo/config.toml @@ -4,10 +4,6 @@ xtask = "run -p xtask --" [env] CARGO_WORKSPACE_DIR = { value = "", relative = true } -# Windows環境でテストエラーになるのを防ぐために設定するworkaround -# https://github.com/VOICEVOX/onnxruntime-rs/issues/3#issuecomment-1207381367 -ORT_OUT_DIR = { value = "target/debug/deps", relative = true } - [target.aarch64-unknown-linux-gnu] linker = "aarch64-linux-gnu-gcc" diff --git a/.github/actions/create-venv/action.yml b/.github/actions/create-venv/action.yml index c8883621e..484ad892f 100644 --- a/.github/actions/create-venv/action.yml +++ b/.github/actions/create-venv/action.yml @@ -1,3 +1,6 @@ +name: Create venv +description: Pythonの仮想環境を作成し、$PATHと$VIRTUAL_ENVを設定する。 + runs: using: composite steps: diff --git a/.github/actions/rust-toolchain-from-file/action.yml b/.github/actions/rust-toolchain-from-file/action.yml index d82c9c159..917faed3c 100644 --- a/.github/actions/rust-toolchain-from-file/action.yml +++ b/.github/actions/rust-toolchain-from-file/action.yml @@ -1,3 +1,6 @@ +name: rustup toolchain install from file +description: rust-toolchainファイルをもとにRustのツールチェーンをインストールする。 + inputs: targets: required: false diff --git a/.github/workflows/build_and_deploy.yml b/.github/workflows/build_and_deploy.yml index 80a7f807c..01ddfc434 100644 --- a/.github/workflows/build_and_deploy.yml +++ b/.github/workflows/build_and_deploy.yml @@ -58,7 +58,6 @@ jobs: "target": "x86_64-pc-windows-msvc", "artifact_name": "windows-x64-cpu", "whl_local_version": "cpu", - "use_cuda": false, "can_skip_in_simple_test": true }, { @@ -67,16 +66,14 @@ jobs: "target": "x86_64-pc-windows-msvc", "artifact_name": "windows-x64-directml", "whl_local_version": "directml", - "use_cuda": false, "can_skip_in_simple_test": false }, { "os": "windows-2019", - "features": "", + "features": "cuda", "target": "x86_64-pc-windows-msvc", "artifact_name": "windows-x64-cuda", "whl_local_version": "cuda", - "use_cuda": true, "can_skip_in_simple_test": true }, { @@ -85,7 +82,6 @@ jobs: "target": "i686-pc-windows-msvc", "artifact_name": "windows-x86-cpu", "whl_local_version": "cpu", - "use_cuda": false, "can_skip_in_simple_test": true }, { @@ -94,16 +90,14 @@ jobs: "target": "x86_64-unknown-linux-gnu", "artifact_name": "linux-x64-cpu", "whl_local_version": "cpu", - "use_cuda": false, "can_skip_in_simple_test": true }, { "os": "ubuntu-20.04", - "features": "", + "features": "cuda", "target": "x86_64-unknown-linux-gnu", "artifact_name": "linux-x64-gpu", "whl_local_version": "cuda", - "use_cuda": true, "can_skip_in_simple_test": false }, { @@ -112,7 +106,6 @@ jobs: "target": "aarch64-unknown-linux-gnu", "artifact_name": "linux-arm64-cpu", "whl_local_version": "cpu", - "use_cuda": false, "can_skip_in_simple_test": true }, { @@ -120,7 +113,6 @@ jobs: "features": "", "target": "aarch64-linux-android", "artifact_name": "android-arm64-cpu", - "use_cuda": false, "can_skip_in_simple_test": true }, { @@ -128,7 +120,6 @@ jobs: "features": "", "target": "x86_64-linux-android", "artifact_name": "android-x86_64-cpu", - "use_cuda": false, "can_skip_in_simple_test": true }, { @@ -137,7 +128,6 @@ jobs: "target": "aarch64-apple-darwin", "artifact_name": "osx-arm64-cpu", "whl_local_version": "cpu", - "use_cuda": false, "can_skip_in_simple_test": false }, { @@ -146,7 +136,6 @@ jobs: "target": "x86_64-apple-darwin", "artifact_name": "osx-x64-cpu", "whl_local_version": "cpu", - "use_cuda": false, "can_skip_in_simple_test": true }, { @@ -154,7 +143,6 @@ jobs: "features": "", "target": "aarch64-apple-ios", "artifact_name": "ios-arm64-cpu", - "use_cuda": false, "can_skip_in_simple_test": true }, { @@ -162,7 +150,6 @@ jobs: "features": "", "target": "aarch64-apple-ios-sim", "artifact_name": "ios-arm64-cpu-sim", - "use_cuda": false, "can_skip_in_simple_test": true }, { @@ -170,7 +157,6 @@ jobs: "features": "", "target": "x86_64-apple-ios", "artifact_name": "ios-x64-cpu", - "use_cuda": false, "can_skip_in_simple_test": true } ]' @@ -242,7 +228,7 @@ jobs: - name: Raplace resource if: inputs.is_production shell: bash - run: | + run: mv -f download/resource/core/README.md ./README.md - name: Install cargo-binstall uses: taiki-e/install-action@cargo-binstall @@ -268,7 +254,6 @@ jobs: fi env: RUSTFLAGS: -C panic=abort - ORT_USE_CUDA: ${{ matrix.use_cuda }} - name: build voicevox_core_python_api if: matrix.whl_local_version id: build-voicevox-core-python-api @@ -286,8 +271,6 @@ jobs: build > /dev/null 2>&1 fi 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: | @@ -305,7 +288,7 @@ jobs: cp -v crates/voicevox_core_c_api/include/voicevox_core.h "artifact/${{ env.ASSET_NAME }}" cp -v target/${{ matrix.target }}/release/*voicevox_core.{dll,so,dylib} "artifact/${{ env.ASSET_NAME }}" || true cp -v target/${{ matrix.target }}/release/voicevox_core.dll.lib "artifact/${{ env.ASSET_NAME }}/voicevox_core.lib" || true - cp -v -n target/${{ matrix.target }}/release/build/onnxruntime-sys-*/out/onnxruntime_*/onnxruntime-*/lib/*.{dll,so.*,so,dylib} "artifact/${{ env.ASSET_NAME }}" || true + cp -v -n target/${{ matrix.target }}/release/{,lib}onnxruntime*.{dll,so.*,so,dylib} "artifact/${{ env.ASSET_NAME }}" || true # libonnxruntimeについてはバージョン付のshared libraryを使用するためバージョンがついてないものを削除する rm -f artifact/${{ env.ASSET_NAME }}/libonnxruntime.{so,dylib} cp -v README.md "artifact/${{ env.ASSET_NAME }}/README.txt" @@ -315,7 +298,7 @@ jobs: cp -v target/${{ matrix.target }}/release/libvoicevox_core_java_api.so java_artifact/ || true - name: Code signing (Windows) if: startsWith(matrix.os, 'windows') && inputs.code_signing - run: | + run: bash build_util/codesign.bash "artifact/${{ env.ASSET_NAME }}/voicevox_core.dll" env: ESIGNERCKA_USERNAME: ${{ secrets.ESIGNERCKA_USERNAME }} @@ -323,7 +306,7 @@ jobs: ESIGNERCKA_TOTP_SECRET: ${{ secrets.ESIGNERCKA_TOTP_SECRET }} - name: Upload artifact to build XCFramework if: contains(matrix.target, 'ios') - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: voicevox_core-${{ matrix.target }} path: artifact/${{ env.ASSET_NAME }} @@ -351,7 +334,7 @@ jobs: target_commitish: ${{ github.sha }} - name: Upload voicevox_core_java_api artifact if: fromJson(needs.config.outputs.deploy) && contains(matrix.target, 'android') - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: voicevox_core_java_api-${{ matrix.artifact_name }} path: java_artifact @@ -361,44 +344,34 @@ jobs: needs: [config, build_and_deploy] runs-on: macos-12 env: + IOS_X86_64_PATH: artifact/voicevox_core-x86_64-apple-ios + IOS_AARCH64_SIM_PATH: artifact/voicevox_core-aarch64-apple-ios-sim + IOS_AARCH64_PATH: artifact/voicevox_core-aarch64-apple-ios ASSET_NAME: voicevox_core-ios-xcframework-cpu-${{ needs.config.outputs.version }} steps: - uses: actions/checkout@v3 - - uses: actions/download-artifact@v2 + - uses: actions/download-artifact@v4 with: name: voicevox_core-x86_64-apple-ios - path: artifact/voicevox_core-x86_64-apple-ios - - uses: actions/download-artifact@v2 + path: ${{ env.IOS_X86_64_PATH }} + - uses: actions/download-artifact@v4 with: name: voicevox_core-aarch64-apple-ios-sim - path: artifact/voicevox_core-aarch64-apple-ios-sim - - uses: actions/download-artifact@v2 + path: ${{ env.IOS_AARCH64_SIM_PATH }} + - uses: actions/download-artifact@v4 with: name: voicevox_core-aarch64-apple-ios - path: artifact/voicevox_core-aarch64-apple-ios - - name: Create fat binary + path: ${{ env.IOS_AARCH64_PATH }} + - name: Create xcframework + id: create-xcframework run: | - mkdir -p "artifact/voicevox_core-sim" - lipo -create "artifact/voicevox_core-x86_64-apple-ios/libvoicevox_core.dylib" "artifact/voicevox_core-aarch64-apple-ios-sim/libvoicevox_core.dylib" -output "artifact/voicevox_core-sim/libvoicevox_core.dylib" - - name: Create XCFramework - run: | - mkdir -p "artifact/${{ env.ASSET_NAME }}" - # 必要なファイルだけコピー - mkdir -p "Headers-sim" - cp -v artifact/voicevox_core-x86_64-apple-ios/voicevox_core.h "Headers-sim" - cp -v crates/voicevox_core_c_api/xcframework/Headers/module.modulemap "Headers-sim" - mkdir -p "Headers-aarch64" - cp -v artifact/voicevox_core-aarch64-apple-ios/voicevox_core.h "Headers-aarch64" - cp -v crates/voicevox_core_c_api/xcframework/Headers/module.modulemap "Headers-aarch64" - xcodebuild -create-xcframework \ - -library "artifact/voicevox_core-sim/libvoicevox_core.dylib" \ - -headers "Headers-sim" \ - -library "artifact/voicevox_core-aarch64-apple-ios/libvoicevox_core.dylib" \ - -headers "Headers-aarch64" \ - -output "artifact/${{ env.ASSET_NAME }}/voicevox_core.xcframework" + build_util/make_ios_xcframework.bash + echo "output_asset_path=${OUTPUT_ASSET_PATH}" >> "$GITHUB_OUTPUT" + env: + OUTPUT_ASSET_PATH: artifact/voicevox_core-ios-xcframework-cpu - name: Archive artifact run: | - cd artifact/${{ env.ASSET_NAME }} + cd ${{ steps.create-xcframework.outputs.output_asset_path }} 7z a "../../${{ env.ASSET_NAME }}.zip" "voicevox_core.xcframework" - name: Upload to Release if: fromJson(needs.config.outputs.deploy) @@ -427,7 +400,7 @@ jobs: - name: Raplace resource if: inputs.is_production shell: bash - run: | + run: rm -r ./model; mv download/fat_resource/core/model ./model - name: Create artifact run: | @@ -456,7 +429,7 @@ jobs: - name: Set up Rust uses: ./.github/actions/rust-toolchain-from-file - name: Set up Java - uses: actions/setup-java@v2 + uses: actions/setup-java@v4 with: java-version: "17" distribution: "adopt" @@ -469,7 +442,7 @@ jobs: - name: Install cargo-edit run: cargo binstall cargo-edit@^0.11 --no-confirm - name: set cargo version - run: | + run: cargo set-version "$VERSION" -p voicevox_core_java_api - name: "Download artifact (android-arm64-cpu)" diff --git a/.github/workflows/cargo-deny.yml b/.github/workflows/cargo-deny.yml index ac4978b9a..cf600f535 100644 --- a/.github/workflows/cargo-deny.yml +++ b/.github/workflows/cargo-deny.yml @@ -13,7 +13,7 @@ jobs: - name: Install cargo-binstall uses: taiki-e/install-action@cargo-binstall - name: Install cargo-deny - run: cargo binstall cargo-deny@^0.13 --no-confirm --log-level debug + run: cargo binstall cargo-deny@^0.14 --no-confirm --log-level debug - name: cargo-deny run: | if ${{ !!github.event.release }}; then diff --git a/.github/workflows/download_test.yml b/.github/workflows/download_test.yml index d2e8b718b..2a1190e40 100644 --- a/.github/workflows/download_test.yml +++ b/.github/workflows/download_test.yml @@ -7,14 +7,16 @@ on: description: "テスト対象のコアのバージョン。無指定時はprerelease込みの最新release。" type: string required: false - push: - branches: - - main - pull_request: - paths: - - "Cargo.*" - - "crates/downloader/**" - - ".github/workflows/download_test.yml" + # 新しいビルドができるまで、このworkflowが壊れて動かないことを許容する + # https://github.com/VOICEVOX/voicevox_core/issues/741#issuecomment-1935303742 + #push: + # branches: + # - main + #pull_request: + # paths: + # - "Cargo.*" + # - "crates/downloader/**" + # - ".github/workflows/download_test.yml" env: VERSION: ${{ inputs.version || 'prerelease-latest' }} diff --git a/.github/workflows/generate_document.yml b/.github/workflows/generate_document.yml index e3890d82a..37c39a1dd 100644 --- a/.github/workflows/generate_document.yml +++ b/.github/workflows/generate_document.yml @@ -21,7 +21,7 @@ jobs: with: python-version: "3.8" - name: Setup Java - uses: actions/setup-java@v2 + uses: actions/setup-java@v4 with: java-version: "11" distribution: "adopt" diff --git a/.github/workflows/java_lint.yml b/.github/workflows/java_lint.yml index c75222912..ad408e3a7 100644 --- a/.github/workflows/java_lint.yml +++ b/.github/workflows/java_lint.yml @@ -22,7 +22,7 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 - - uses: actions/setup-java@v2 + - uses: actions/setup-java@v4 with: java-version: "11" distribution: "adopt" diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 3e47256f3..d0639d045 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -72,8 +72,8 @@ jobs: with: python-version: "3.8" - uses: Swatinem/rust-cache@v2 - - run: cargo clippy -vv --all-features --features onnxruntime/disable-sys-build-script --tests -- -D clippy::all -D warnings --no-deps - - run: cargo clippy -vv --all-features --features onnxruntime/disable-sys-build-script -- -D clippy::all -D warnings --no-deps + - run: cargo clippy -vv --all-features --tests -- -D clippy::all -D warnings --no-deps + - run: cargo clippy -vv --all-features -- -D clippy::all -D warnings --no-deps - run: cargo fmt -- --check rust-unit-test: @@ -151,7 +151,8 @@ jobs: metadata=$(cargo metadata --format-version 1) version=$( jq -r ' - (.workspace_members[] | select(startswith("xtask "))) as $xtask + .workspace_members as $workspace_members + | (.packages[] | select(.name == "xtask").id | select(. as $id | $workspace_members | index($id))) as $xtask | (.resolve.nodes[] | select(.id == $xtask).deps[] | select(.name == "cbindgen").pkg) as $cbindgen | .packages[] | select(.id == $cbindgen).version ' <<< "$metadata" @@ -198,8 +199,8 @@ jobs: mkdir -p example/cpp/unix/voicevox_core/ cp -v crates/voicevox_core_c_api/include/voicevox_core.h example/cpp/unix/voicevox_core/ cp -v target/debug/libvoicevox_core.{so,dylib} example/cpp/unix/voicevox_core/ || true - cp -v target/debug/build/onnxruntime-sys-*/out/onnxruntime_*/onnxruntime-*/lib/libonnxruntime.so.* example/cpp/unix/voicevox_core/ || true - cp -v target/debug/build/onnxruntime-sys-*/out/onnxruntime_*/onnxruntime-*/lib/libonnxruntime.*.dylib example/cpp/unix/voicevox_core/ || true + cp -v target/debug/libonnxruntime.so.* example/cpp/unix/voicevox_core/ || true + cp -v target/debug/libonnxruntime.*.dylib example/cpp/unix/voicevox_core/ || true - if: startsWith(matrix.os, 'mac') uses: jwlawson/actions-setup-cmake@v1.13 @@ -276,26 +277,19 @@ jobs: pip install --upgrade poetry poetry install --with dev --with test - run: cargo build -p test_util -vv # build scriptにより/crates/test_util/data/の生成 - # `macos-latest`(=`macos-12`)で次の問題が発生するため、それに対するワークアラウンド - # https://github.com/VOICEVOX/voicevox_core/issues/653#issuecomment-1782108410 - - if: startsWith(matrix.os, 'mac') - name: Build open_jtalk-sys - run: | - poetry run -- cargo build -p voicevox_core_python_api -vv || true - [ -n "$(find ../../target/debug/deps -name 'libopen_jtalk_sys-*.rlib')" ] - run: poetry run maturin build --locked - run: poetry run maturin develop --locked - name: 必要なDLLをコピーしてpytestを実行 run: | - cp -v ../../target/debug/build/onnxruntime-sys-*/out/onnxruntime_*/onnxruntime-*/lib/onnxruntime.dll . || true - cp -v ../../target/debug/build/onnxruntime-sys-*/out/onnxruntime_*/onnxruntime-*/lib/libonnxruntime.so.* . || true - cp -v ../../target/debug/build/onnxruntime-sys-*/out/onnxruntime_*/onnxruntime-*/lib/libonnxruntime.*.dylib . || true + cp -v ../../target/debug/onnxruntime.dll . || true + cp -v ../../target/debug/libonnxruntime.so.* . || true + cp -v ../../target/debug/libonnxruntime.*.dylib . || true poetry run pytest - name: Exampleを実行 run: | for file in ../../example/python/run{,-asyncio}.py; do - poetry run python "$file" ../../model/sample.vvm --dict-dir ../test_util/data/open_jtalk_dic_utf_8-1.11 + poetry run python "$file" ../test_util/data/model/sample.vvm --dict-dir ../test_util/data/open_jtalk_dic_utf_8-1.11 done build-and-test-java-api: strategy: @@ -311,7 +305,7 @@ jobs: - name: Set up Rust uses: ./.github/actions/rust-toolchain-from-file - name: Set up Java - uses: actions/setup-java@v2 + uses: actions/setup-java@v4 with: java-version: "11" distribution: "adopt" diff --git a/Cargo.lock b/Cargo.lock index 72f8b7cca..0f9a78469 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -17,26 +17,6 @@ version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" -[[package]] -name = "aead" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7fc95d1bdb8e6666b2b217308eeeb09f2d6728d104be3e31916cc74d15420331" -dependencies = [ - "generic-array", -] - -[[package]] -name = "aes" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "884391ef1066acaa41e766ba8f596341b96e93ce34f9a43e7d24bf0a0eaf0561" -dependencies = [ - "aes-soft", - "aesni", - "cipher 0.2.5", -] - [[package]] name = "aes" version = "0.7.5" @@ -44,45 +24,11 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9e8b47f52ea9bae42228d07ec09eb676433d7c4ed1ebdf0f1d1c29ed446f1ab8" dependencies = [ "cfg-if", - "cipher 0.3.0", + "cipher", "cpufeatures", "opaque-debug", ] -[[package]] -name = "aes-gcm" -version = "0.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5278b5fabbb9bd46e24aa69b2fdea62c99088e0a950a9be40e3e0101298f88da" -dependencies = [ - "aead", - "aes 0.6.0", - "cipher 0.2.5", - "ctr", - "ghash", - "subtle", -] - -[[package]] -name = "aes-soft" -version = "0.6.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "be14c7498ea50828a38d0e24a765ed2effe92a705885b57d029cd67d45744072" -dependencies = [ - "cipher 0.2.5", - "opaque-debug", -] - -[[package]] -name = "aesni" -version = "0.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ea2e11f5e94c2f7d386164cc2aa1f97823fed6f259e486940a71c174dd01b0ce" -dependencies = [ - "cipher 0.2.5", - "opaque-debug", -] - [[package]] name = "aho-corasick" version = "1.1.2" @@ -117,7 +63,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3fa490e751f3878eb9accb9f18988eca52c2337ce000a8bf31ef50d4c723ca9e" dependencies = [ "android_log-sys", - "env_logger 0.10.0", + "env_logger", "log", "once_cell", ] @@ -196,135 +142,19 @@ dependencies = [ "yansi", ] -[[package]] -name = "async-attributes" -version = "1.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a3203e79f4dd9bdda415ed03cf14dae5a2bf775c683a00f94e9cd1faf0f596e5" -dependencies = [ - "quote", - "syn 1.0.102", -] - -[[package]] -name = "async-channel" -version = "1.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e14485364214912d3b19cc3435dde4df66065127f05fa0d75c712f36f12c2f28" -dependencies = [ - "concurrent-queue", - "event-listener", - "futures-core", -] - [[package]] name = "async-compression" -version = "0.3.15" +version = "0.4.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "942c7cd7ae39e91bde4820d74132e9862e62c2f386c3aa90ccf55949f5bad63a" +checksum = "a116f46a969224200a0a97f29cfd4c50e7534e4b4826bd23ea2c3c533039c82c" dependencies = [ - "bzip2", "flate2", "futures-core", - "memchr", - "pin-project-lite", - "tokio", - "xz2", - "zstd", - "zstd-safe", -] - -[[package]] -name = "async-executor" -version = "1.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "871f9bb5e0a22eeb7e8cf16641feb87c9dc67032ccf8ff49e772eb9941d3a965" -dependencies = [ - "async-task", - "concurrent-queue", - "fastrand", - "futures-lite", - "once_cell", - "slab", -] - -[[package]] -name = "async-global-executor" -version = "2.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0da5b41ee986eed3f524c380e6d64965aea573882a8907682ad100f7859305ca" -dependencies = [ - "async-channel", - "async-executor", - "async-io", - "async-lock", - "blocking", - "futures-lite", - "once_cell", -] - -[[package]] -name = "async-io" -version = "1.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "83e21f3a490c72b3b0cf44962180e60045de2925d8dff97918f7ee43c8f637c7" -dependencies = [ - "autocfg", - "concurrent-queue", - "futures-lite", - "libc", - "log", - "once_cell", - "parking", - "polling", - "slab", - "socket2", - "waker-fn", - "winapi", -] - -[[package]] -name = "async-lock" -version = "2.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e97a171d191782fba31bb902b14ad94e24a68145032b7eedf871ab0bc0d077b6" -dependencies = [ - "event-listener", -] - -[[package]] -name = "async-std" -version = "1.12.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62565bb4402e926b29953c785397c6dc0391b7b446e45008b0049eb43cec6f5d" -dependencies = [ - "async-attributes", - "async-channel", - "async-global-executor", - "async-io", - "async-lock", - "crossbeam-utils", - "futures-channel", - "futures-core", "futures-io", - "futures-lite", - "gloo-timers", - "kv-log-macro", - "log", "memchr", - "once_cell", "pin-project-lite", - "pin-utils", - "slab", - "wasm-bindgen-futures", ] -[[package]] -name = "async-task" -version = "4.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a40729d2133846d9ed0ea60a8b9541bccddab49cd30f0715a1da672fe9a2524" - [[package]] name = "async-trait" version = "0.1.57" @@ -338,25 +168,17 @@ dependencies = [ [[package]] name = "async_zip" -version = "0.0.11" +version = "0.0.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c50d29ab7e2f9e808cca1a69ea56a36f4ff216f54a41a23aae1fd4afc05cc020" +checksum = "527207465fb6dcafbf661b0d4a51d0d2306c9d0c2975423079a6caa807930daf" dependencies = [ "async-compression", - "chrono", "crc32fast", - "log", + "futures-lite", "pin-project", "thiserror", - "tokio", ] -[[package]] -name = "atomic-waker" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "065374052e7df7ee4047b1160cca5e1467a12351a40b3da123c870ba0b8eda2a" - [[package]] name = "atty" version = "0.2.14" @@ -389,12 +211,6 @@ dependencies = [ "rustc-demangle", ] -[[package]] -name = "base-x" -version = "0.2.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4cbbc9d0964165b47557570cce6c952866c2678457aca742aafc9fb771d30270" - [[package]] name = "base64" version = "0.13.0" @@ -415,15 +231,13 @@ checksum = "8a32fd6af2b5827bce66c29053ba0e7c42b9dcab01835835058558c10851a46b" [[package]] name = "bindgen" -version = "0.60.1" +version = "0.62.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "062dddbc1ba4aca46de6338e2bf87771414c335f7b2f2036e8f3e9befebf88e6" +checksum = "c6720a8b7b2d39dd533285ed438d458f65b31b5c257e6ac7bb3d7e82844dd722" dependencies = [ - "bitflags", + "bitflags 1.3.2", "cexpr", "clang-sys", - "clap 3.2.22", - "env_logger 0.9.1", "lazy_static", "lazycell", "log", @@ -433,6 +247,30 @@ dependencies = [ "regex", "rustc-hash", "shlex", + "syn 1.0.102", + "which", +] + +[[package]] +name = "bindgen" +version = "0.69.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a00dc851838a2120612785d195287475a3ac45514741da670b735818822129a0" +dependencies = [ + "bitflags 2.5.0", + "cexpr", + "clang-sys", + "itertools 0.11.0", + "lazy_static", + "lazycell", + "log", + "prettyplease", + "proc-macro2", + "quote", + "regex", + "rustc-hash", + "shlex", + "syn 2.0.48", "which", ] @@ -454,13 +292,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] -name = "block-buffer" -version = "0.9.0" +name = "bitflags" +version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4152116fd6e9dadb291ae18fc1ec3575ed6d84c29642d97890f4b4a3417297e4" -dependencies = [ - "generic-array", -] +checksum = "cf4b9d6a944f767f8e5e0db018570623c85f3d925ac718db4e06d0187adb21c1" [[package]] name = "block-buffer" @@ -471,20 +306,6 @@ dependencies = [ "generic-array", ] -[[package]] -name = "blocking" -version = "1.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c6ccb65d468978a086b69884437ded69a90faab3bbe6e67f242173ea728acccc" -dependencies = [ - "async-channel", - "async-task", - "atomic-waker", - "fastrand", - "futures-lite", - "once_cell", -] - [[package]] name = "bstr" version = "1.2.0" @@ -509,12 +330,6 @@ version = "1.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610" -[[package]] -name = "bytes" -version = "0.5.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0e4cec68f03f32e44924783795810fa50a7035d8c8ebe78580ad7e6c703fba38" - [[package]] name = "bytes" version = "1.1.0" @@ -543,10 +358,10 @@ dependencies = [ ] [[package]] -name = "cache-padded" -version = "1.2.0" +name = "camino" +version = "1.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c1db59621ec70f09c5e9b597b220c7a2b43611f4710dc03ceb8748637775692c" +checksum = "c59e92b5a388f549b863a7bea62612c09f24c8393560709a54558a9abdfb3b9c" [[package]] name = "cbindgen" @@ -569,11 +384,12 @@ dependencies = [ [[package]] name = "cc" -version = "1.0.73" +version = "1.0.83" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2fff2a6927b3bb87f9595d67196a70493f627687a71d87a0d692242c33f58c11" +checksum = "f1174fb0b6ec23863f8b971027804a42614e347eafb0a95bf0b12cdae21fc4d0" dependencies = [ "jobserver", + "libc", ] [[package]] @@ -610,21 +426,6 @@ dependencies = [ "winapi", ] -[[package]] -name = "chunked_transfer" -version = "1.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fff857943da45f546682664a79488be82e69e43c1a7a2307679ab9afb3a66d2e" - -[[package]] -name = "cipher" -version = "0.2.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "12f8e7987cbd042a63249497f41aed09f8e65add917ea6566effbc56578d6801" -dependencies = [ - "generic-array", -] - [[package]] name = "cipher" version = "0.3.0" @@ -636,9 +437,9 @@ dependencies = [ [[package]] name = "clang-sys" -version = "1.4.0" +version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fa2e27ae6ab525c3d369ded447057bca5438d86dc3a68f6faafb8269ba82ebf3" +checksum = "77ed9a53e5d4d9c573ae844bfac6872b159cb1d1585a83b29e7a64b7eef7332a" dependencies = [ "glob", "libc", @@ -652,7 +453,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "86447ad904c7fb335a790c9d7fe3d0d971dc523b8ccd1561a520de9a85302750" dependencies = [ "atty", - "bitflags", + "bitflags 1.3.2", "clap_lex 0.2.4", "indexmap 1.9.1", "strsim", @@ -667,7 +468,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3b1a0a4208c6c483b952ad35c6eed505fc13b46f08f631b81e828084a9318d74" dependencies = [ "atty", - "bitflags", + "bitflags 1.3.2", "clap_derive", "clap_lex 0.3.0", "once_cell", @@ -764,7 +565,7 @@ version = "4.6.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "35ed6e9d84f0b51a7f52daf1c7d71dd136fd7a3f41a8462b8cdb8c78d920fad4" dependencies = [ - "bytes 1.1.0", + "bytes", "memchr", ] @@ -774,7 +575,7 @@ version = "0.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "318d6c16e73b3a900eb212ad6a82fc7d298c5ab8184c7a9998646455bc474a16" dependencies = [ - "bitflags", + "bitflags 1.3.2", "concolor-query", "is-terminal", ] @@ -785,15 +586,6 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "82a90734b3d5dcf656e7624cca6bce9c3a90ee11f900e80141a7427ccfb3d317" -[[package]] -name = "concurrent-queue" -version = "1.2.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af4780a44ab5696ea9e28294517f1fffb421a83a25af521333c838635509db9c" -dependencies = [ - "cache-padded", -] - [[package]] name = "console" version = "0.15.4" @@ -807,12 +599,6 @@ dependencies = [ "windows-sys 0.42.0", ] -[[package]] -name = "const_fn" -version = "0.4.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fbdcdcb6d86f71c5e97409ad45898af11cbc995b4ee8112d59095a28d376c935" - [[package]] name = "constant_time_eq" version = "0.1.5" @@ -825,23 +611,6 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6245d59a3e82a7fc217c5828a6692dbc6dfb63a0c8c90495621f7b9d79704a0e" -[[package]] -name = "cookie" -version = "0.14.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "03a5d7b21829bc7b4bf4754a978a241ae54ea55a40f92bb20216e54096f4b951" -dependencies = [ - "aes-gcm", - "base64 0.13.0", - "hkdf", - "hmac 0.10.1", - "percent-encoding", - "rand 0.8.5", - "sha2 0.9.9", - "time 0.2.27", - "version_check", -] - [[package]] name = "core-foundation-sys" version = "0.8.3" @@ -857,12 +626,6 @@ dependencies = [ "libc", ] -[[package]] -name = "cpuid-bool" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dcb25d077389e53838a8158c8e99174c5a9d902dee4904320db714f3c653ffba" - [[package]] name = "crc32fast" version = "1.3.2" @@ -915,6 +678,12 @@ dependencies = [ "cfg-if", ] +[[package]] +name = "crunchy" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7" + [[package]] name = "crypto-common" version = "0.1.6" @@ -925,16 +694,6 @@ dependencies = [ "typenum", ] -[[package]] -name = "crypto-mac" -version = "0.10.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bff07008ec701e8028e2ceb8f83f0e4274ee62bd2dbdc4fefff2e9a91824081a" -dependencies = [ - "generic-array", - "subtle", -] - [[package]] name = "cstr" version = "0.2.11" @@ -955,46 +714,6 @@ dependencies = [ "syn 1.0.102", ] -[[package]] -name = "ctr" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fb4a30d54f7443bf3d6191dcd486aca19e67cb3c49fa7a06a319966346707e7f" -dependencies = [ - "cipher 0.2.5", -] - -[[package]] -name = "curl" -version = "0.4.44" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "509bd11746c7ac09ebd19f0b17782eae80aadee26237658a6b4808afb5c11a22" -dependencies = [ - "curl-sys", - "libc", - "openssl-probe", - "openssl-sys", - "schannel", - "socket2", - "winapi", -] - -[[package]] -name = "curl-sys" -version = "0.4.56+curl-7.83.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6093e169dd4de29e468fa649fbae11cdcd5551c81fe5bf1b0677adad7ef3d26f" -dependencies = [ - "cc", - "libc", - "libnghttp2-sys", - "libz-sys", - "openssl-sys", - "pkg-config", - "vcpkg", - "winapi", -] - [[package]] name = "cxx" version = "1.0.86" @@ -1060,7 +779,7 @@ dependencies = [ "proc-macro2", "quote", "strsim", - "syn 2.0.38", + "syn 2.0.48", ] [[package]] @@ -1071,7 +790,7 @@ checksum = "836a9bbc7ad63342d6d6e7b815ccab164bc77a2d95d84bc3117a8c0d5c98e2d5" dependencies = [ "darling_core", "quote", - "syn 2.0.38", + "syn 2.0.48", ] [[package]] @@ -1105,7 +824,7 @@ dependencies = [ "convert_case", "proc-macro2", "quote", - "rustc_version 0.4.0", + "rustc_version", "syn 1.0.102", ] @@ -1121,32 +840,17 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6184e33543162437515c2e2b48714794e37845ec9851711914eec9d308f6ebe8" -[[package]] -name = "digest" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d3dd60d1080a57a05ab032377049e0591415d2b31afd7028356dbf3cc6dcb066" -dependencies = [ - "generic-array", -] - [[package]] name = "digest" version = "0.10.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "adfbc57365a37acbd2ebf2b64d7e69bb766e2fea813521ed536f5d0520dcf86c" dependencies = [ - "block-buffer 0.10.3", + "block-buffer", "crypto-common", "subtle", ] -[[package]] -name = "discard" -version = "1.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "212d0f5754cb6769937f4501cc0e67f4f4483c8d2c3e1e922ee9edbe4ab4c7c0" - [[package]] name = "doc-comment" version = "0.3.3" @@ -1159,7 +863,7 @@ version = "0.0.0" dependencies = [ "anyhow", "binstall-tar", - "bytes 1.1.0", + "bytes", "clap 4.0.10", "flate2", "fs-err", @@ -1258,7 +962,7 @@ checksum = "48c69b3965971f5d0ea6a6dd26b55cdd517ae0e1425dc8d94e482a5915bd7ddf" dependencies = [ "proc-macro2", "quote", - "syn 2.0.38", + "syn 2.0.48", ] [[package]] @@ -1271,20 +975,7 @@ dependencies = [ "num-traits", "proc-macro2", "quote", - "syn 2.0.38", -] - -[[package]] -name = "env_logger" -version = "0.9.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c90bf5f19754d10198ccb95b70664fc925bd1fc090a0fd9a6ebc54acc8cd6272" -dependencies = [ - "atty", - "humantime", - "log", - "regex", - "termcolor", + "syn 2.0.48", ] [[package]] @@ -1344,12 +1035,6 @@ dependencies = [ "libc", ] -[[package]] -name = "event-listener" -version = "2.5.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0206175f82b8d6bf6652ff7d71a1e27fd2e4efde587fd368662814d6ec1d9ce0" - [[package]] name = "eyre" version = "0.6.8" @@ -1369,6 +1054,12 @@ dependencies = [ "instant", ] +[[package]] +name = "fastrand" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "25cbce373ec4653f1a01a31e8a5e5ec0c622dc27ff9c4e6606eefef5cbbed4a5" + [[package]] name = "filetime" version = "0.2.17" @@ -1391,17 +1082,6 @@ dependencies = [ "miniz_oxide 0.6.2", ] -[[package]] -name = "flume" -version = "0.9.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1bebadab126f8120d410b677ed95eee4ba6eb7c6dd8e34a5ec88a08050e26132" -dependencies = [ - "futures-core", - "futures-sink", - "spinning_top", -] - [[package]] name = "fnv" version = "1.0.7" @@ -1477,17 +1157,15 @@ checksum = "4fff74096e71ed47f8e023204cfd0aa1289cd54ae5430a9523be060cdb849964" [[package]] name = "futures-lite" -version = "1.12.0" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7694489acd39452c77daa48516b894c153f192c3578d5a839b62c58099fcbf48" +checksum = "445ba825b27408685aaecefd65178908c36c6e96aaf6d8599419d46e624192ba" dependencies = [ - "fastrand", + "fastrand 2.0.1", "futures-core", "futures-io", - "memchr", "parking", "pin-project-lite", - "waker-fn", ] [[package]] @@ -1498,7 +1176,7 @@ checksum = "89ca545a94061b6365f2c7355b4b32bd20df3ff95f02da9329b34ccc3bd6ee72" dependencies = [ "proc-macro2", "quote", - "syn 2.0.38", + "syn 2.0.48", ] [[package]] @@ -1547,17 +1225,6 @@ dependencies = [ "version_check", ] -[[package]] -name = "getrandom" -version = "0.1.16" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8fc3cb4d91f53b50155bdcfd23f6a4c39ae1969c2ae85982b135750cccaf5fce" -dependencies = [ - "cfg-if", - "libc", - "wasi 0.9.0+wasi-snapshot-preview1", -] - [[package]] name = "getrandom" version = "0.2.7" @@ -1566,17 +1233,7 @@ checksum = "4eb1a864a501629691edf6c15a593b7a51eebaa1e8468e9ddc623de7c9b58ec6" dependencies = [ "cfg-if", "libc", - "wasi 0.11.0+wasi-snapshot-preview1", -] - -[[package]] -name = "ghash" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "97304e4cd182c3846f7575ced3890c53012ce534ad9114046b0a9e00bb30a375" -dependencies = [ - "opaque-debug", - "polyval", + "wasi", ] [[package]] @@ -1602,25 +1259,13 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9b919933a397b79c37e33b77bb2aa3dc8eb6e165ad809e58ff75bc7db2e34574" -[[package]] -name = "gloo-timers" -version = "0.2.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5fb7d06c1c8cc2a29bee7ec961009a0b2caa0793ee4900c2ffb348734ba1c8f9" -dependencies = [ - "futures-channel", - "futures-core", - "js-sys", - "wasm-bindgen", -] - [[package]] name = "h2" version = "0.3.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5f9f29bc9dda355256b2916cf526ab02ce0aeaaaf2bad60d65ef3f12f11dd0f4" dependencies = [ - "bytes 1.1.0", + "bytes", "fnv", "futures-core", "futures-sink", @@ -1633,6 +1278,16 @@ dependencies = [ "tracing", ] +[[package]] +name = "half" +version = "2.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc52e53916c08643f1b56ec082790d1e86a32e58dc5268f897f313fbae7b4872" +dependencies = [ + "cfg-if", + "crunchy", +] + [[package]] name = "hashbrown" version = "0.12.3" @@ -1681,33 +1336,13 @@ version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" -[[package]] -name = "hkdf" -version = "0.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "51ab2f639c231793c5f6114bdb9bbe50a7dbbfcd7c7c6bd8475dec2d991e964f" -dependencies = [ - "digest 0.9.0", - "hmac 0.10.1", -] - -[[package]] -name = "hmac" -version = "0.10.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c1441c6b1e930e2817404b5046f1f989899143a12bf92de603b69f4e0aee1e15" -dependencies = [ - "crypto-mac", - "digest 0.9.0", -] - [[package]] name = "hmac" version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" dependencies = [ - "digest 0.10.5", + "digest", ] [[package]] @@ -1716,7 +1351,7 @@ version = "0.2.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "75f43d41e26995c17e71ee126451dd3941010b0514a81a9d11f3b341debc2399" dependencies = [ - "bytes 1.1.0", + "bytes", "fnv", "itoa", ] @@ -1727,47 +1362,11 @@ version = "0.4.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d5f38f16d184e36f2408a55281cd658ecbd3ca05cce6d6510a176eca393e26d1" dependencies = [ - "bytes 1.1.0", + "bytes", "http", "pin-project-lite", ] -[[package]] -name = "http-client" -version = "6.5.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1947510dc91e2bf586ea5ffb412caad7673264e14bb39fb9078da114a94ce1a5" -dependencies = [ - "async-std", - "async-trait", - "cfg-if", - "http-types", - "isahc", - "log", -] - -[[package]] -name = "http-types" -version = "2.12.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e9b187a72d63adbfba487f48095306ac823049cb504ee195541e91c7775f5ad" -dependencies = [ - "anyhow", - "async-channel", - "async-std", - "base64 0.13.0", - "cookie", - "futures-lite", - "infer", - "pin-project-lite", - "rand 0.7.3", - "serde", - "serde_json", - "serde_qs", - "serde_urlencoded", - "url", -] - [[package]] name = "httparse" version = "1.8.0" @@ -1789,19 +1388,13 @@ dependencies = [ "libm", ] -[[package]] -name = "humantime" -version = "2.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4" - [[package]] name = "hyper" version = "0.14.23" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "034711faac9d2166cb1baf1a2fb0b60b1f277f8492fd72176c17f3515e1abd3c" dependencies = [ - "bytes 1.1.0", + "bytes", "futures-channel", "futures-core", "futures-util", @@ -1827,7 +1420,7 @@ checksum = "1788965e61b367cd03a62950836d5cd41560c3577d90e40e0819373194d1661c" dependencies = [ "http", "hyper", - "rustls", + "rustls 0.20.6", "tokio", "tokio-rustls", ] @@ -1909,21 +1502,15 @@ checksum = "cef509aa9bc73864d6756f0d34d35504af3cf0844373afe9b8669a5b8005a729" dependencies = [ "console", "number_prefix", - "portable-atomic", + "portable-atomic 0.3.19", "unicode-width", ] [[package]] name = "indoc" -version = "1.0.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "adab1eaa3408fb7f0c777a73e7465fd5656136fc93b670eb6df3c88c2c1344e3" - -[[package]] -name = "infer" -version = "0.2.3" +version = "2.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "64e9829a50b42bb782c1df523f78d332fe371b10c661e78b7a3c34b0198e9fac" +checksum = "1e186cfbae8084e513daff4240b4797e342f988cecda4fb6c939150f96315fd8" [[package]] name = "instant" @@ -1973,29 +1560,6 @@ dependencies = [ "windows-sys 0.42.0", ] -[[package]] -name = "isahc" -version = "0.9.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2948a0ce43e2c2ef11d7edf6816508998d99e13badd1150be0914205df9388a" -dependencies = [ - "bytes 0.5.6", - "crossbeam-utils", - "curl", - "curl-sys", - "flume", - "futures-lite", - "http", - "log", - "once_cell", - "slab", - "sluice", - "tracing", - "tracing-futures", - "url", - "waker-fn", -] - [[package]] name = "itertools" version = "0.10.5" @@ -2020,6 +1584,15 @@ version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4217ad341ebadf8d8e724e264f13e593e0648f5b3e94b3896a5df283be015ecc" +[[package]] +name = "jlabel" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f040b22c55628977296069dbf8635be49cc510999c048a1f1bdb56d00983148" +dependencies = [ + "thiserror", +] + [[package]] name = "jni" version = "0.21.1" @@ -2074,15 +1647,6 @@ dependencies = [ "simple_asn1", ] -[[package]] -name = "kv-log-macro" -version = "1.0.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0de8b303297635ad57c9f5059fd9cee7a47f8e8daa09df0fcd07dd39fb22977f" -dependencies = [ - "log", -] - [[package]] name = "lazy_static" version = "1.4.0" @@ -2117,16 +1681,6 @@ version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "348108ab3fba42ec82ff6e9564fc4ca0247bdccdc68dd8af9764bbc79c3c8ffb" -[[package]] -name = "libnghttp2-sys" -version = "0.1.7+1.45.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "57ed28aba195b38d5ff02b9170cbff627e336a20925e43b4945390401c5dc93f" -dependencies = [ - "cc", - "libc", -] - [[package]] name = "libtest-mimic" version = "0.6.0" @@ -2138,18 +1692,6 @@ dependencies = [ "threadpool", ] -[[package]] -name = "libz-sys" -version = "1.1.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9702761c3935f8cc2f101793272e202c72b99da8f4224a19ddcf1279a6450bbf" -dependencies = [ - "cc", - "libc", - "pkg-config", - "vcpkg", -] - [[package]] name = "link-cplusplus" version = "1.0.7" @@ -2188,18 +1730,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e" dependencies = [ "cfg-if", - "value-bag", -] - -[[package]] -name = "lzma-sys" -version = "0.1.20" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5fda04ab3764e6cde78b9974eec4f779acaba7c4e84b36eca3cf77c581b85d27" -dependencies = [ - "cc", - "libc", - "pkg-config", ] [[package]] @@ -2256,16 +1786,6 @@ version = "0.3.16" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2a60c7ce501c71e03a9c9c0d35b861413ae925bd979cc7a4e30d060069aaac8d" -[[package]] -name = "mime_guess" -version = "2.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4192263c238a5f0d0c6bfd21f336a313a4ce1c450542449ca191bb657b4642ef" -dependencies = [ - "mime", - "unicase", -] - [[package]] name = "minimal-lexical" version = "0.2.1" @@ -2298,7 +1818,7 @@ checksum = "e5d732bc30207a6423068df043e3d02e0735b155ad7ce1a6f76fe2baa5b158de" dependencies = [ "libc", "log", - "wasi 0.11.0+wasi-snapshot-preview1", + "wasi", "windows-sys 0.42.0", ] @@ -2308,7 +1828,7 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3ffa00dec017b5b1a8b7cf5e2c008bfda1aa7e0697ac1508b491fdf2622fb4d8" dependencies = [ - "rand 0.8.5", + "rand", ] [[package]] @@ -2336,7 +1856,7 @@ dependencies = [ "noisy_float", "num-integer", "num-traits", - "rand 0.8.5", + "rand", ] [[package]] @@ -2459,7 +1979,7 @@ dependencies = [ "arc-swap", "async-trait", "base64 0.21.0", - "bytes 1.1.0", + "bytes", "cfg-if", "chrono", "either", @@ -2483,30 +2003,6 @@ version = "1.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dd8b5dd2ae5ed71462c540258bedcb51965123ad7e7ccf4b9a8cafaa4a63576d" -[[package]] -name = "onnxruntime" -version = "0.1.0" -source = "git+https://github.com/VOICEVOX/onnxruntime-rs.git?rev=ebb9dcb9b26ee681889b52b6db3b4f642b04a250#ebb9dcb9b26ee681889b52b6db3b4f642b04a250" -dependencies = [ - "lazy_static", - "ndarray", - "onnxruntime-sys", - "thiserror", - "tracing", -] - -[[package]] -name = "onnxruntime-sys" -version = "0.0.25" -source = "git+https://github.com/VOICEVOX/onnxruntime-rs.git?rev=ebb9dcb9b26ee681889b52b6db3b4f642b04a250#ebb9dcb9b26ee681889b52b6db3b4f642b04a250" -dependencies = [ - "flate2", - "once_cell", - "tar", - "ureq", - "zip", -] - [[package]] name = "opaque-debug" version = "0.3.0" @@ -2516,8 +2012,9 @@ checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5" [[package]] name = "open_jtalk" version = "0.1.25" -source = "git+https://github.com/VOICEVOX/open_jtalk-rs.git?rev=a16714ce16dec76fd0e3041a7acfa484921db3b5#a16714ce16dec76fd0e3041a7acfa484921db3b5" +source = "git+https://github.com/VOICEVOX/open_jtalk-rs.git?rev=e1940f3fd61a48bed5bbec8cd2645e13923b1f80#e1940f3fd61a48bed5bbec8cd2645e13923b1f80" dependencies = [ + "camino", "open_jtalk-sys", "thiserror", ] @@ -2525,32 +2022,13 @@ dependencies = [ [[package]] name = "open_jtalk-sys" version = "0.16.111" -source = "git+https://github.com/VOICEVOX/open_jtalk-rs.git?rev=a16714ce16dec76fd0e3041a7acfa484921db3b5#a16714ce16dec76fd0e3041a7acfa484921db3b5" +source = "git+https://github.com/VOICEVOX/open_jtalk-rs.git?rev=e1940f3fd61a48bed5bbec8cd2645e13923b1f80#e1940f3fd61a48bed5bbec8cd2645e13923b1f80" dependencies = [ - "bindgen", + "bindgen 0.62.0", "cmake", "link-cplusplus", ] -[[package]] -name = "openssl-probe" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" - -[[package]] -name = "openssl-sys" -version = "0.9.83" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "666416d899cf077260dac8698d60a60b435a46d57e82acb1be3d0dad87284e5b" -dependencies = [ - "autocfg", - "cc", - "libc", - "pkg-config", - "vcpkg", -] - [[package]] name = "os_pipe" version = "1.1.2" @@ -2589,7 +2067,7 @@ dependencies = [ "proc-macro-error", "proc-macro2", "quote", - "syn 2.0.38", + "syn 2.0.48", ] [[package]] @@ -2615,9 +2093,9 @@ checksum = "c1b04fb49957986fdce4d6ee7a65027d55d4b6d2265e5848bbb507b58ccfdb6f" [[package]] name = "parking" -version = "2.0.0" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "427c3892f9e783d91cc128285287e70a59e206ca452770ece88a76f7a3eddd72" +checksum = "bb813b8af86854136c6922af0598d719255ecb2179515e6e7730d468f05c9cae" [[package]] name = "parking_lot" @@ -2665,7 +2143,7 @@ dependencies = [ "regex", "regex-syntax 0.7.5", "structmeta", - "syn 2.0.38", + "syn 2.0.48", ] [[package]] @@ -2675,7 +2153,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7676374caaee8a325c9e7a2ae557f216c5563a171d6997b0ef8a65af35147700" dependencies = [ "base64ct", - "rand_core 0.6.4", + "rand_core", "subtle", ] @@ -2685,10 +2163,10 @@ version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "83a0692ec44e4cf1ef28ca317f14f8f07da2d95ec3fa01f86e4467b725e60917" dependencies = [ - "digest 0.10.5", - "hmac 0.12.1", + "digest", + "hmac", "password-hash", - "sha2 0.10.6", + "sha2", ] [[package]] @@ -2751,35 +2229,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1df8c4ec4b0627e53bdf214615ad287367e482558cf84b109250b37464dc03ae" [[package]] -name = "polling" -version = "2.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "899b00b9c8ab553c743b3e11e87c5c7d423b2a2de229ba95b24a756344748011" -dependencies = [ - "autocfg", - "cfg-if", - "libc", - "log", - "wepoll-ffi", - "winapi", -] - -[[package]] -name = "polyval" -version = "0.4.5" +name = "portable-atomic" +version = "0.3.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eebcc4aa140b9abd2bc40d9c3f7ccec842679cd79045ac3a7ac698c1a064b7cd" -dependencies = [ - "cpuid-bool", - "opaque-debug", - "universal-hash", -] +checksum = "26f6a7b87c2e435a3241addceeeff740ff8b7e76b74c13bf9acb17fa454ea00b" [[package]] name = "portable-atomic" -version = "0.3.19" +version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26f6a7b87c2e435a3241addceeeff740ff8b7e76b74c13bf9acb17fa454ea00b" +checksum = "7170ef9988bc169ba16dd36a7fa041e5c4cbeb6a35b76d4c03daded371eae7c0" [[package]] name = "ppv-lite86" @@ -2828,6 +2287,16 @@ dependencies = [ "yansi", ] +[[package]] +name = "prettyplease" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8d3928fb5db768cb86f891ff014f0144589297e3c6a1aba6ed7cecfdace270c7" +dependencies = [ + "proc-macro2", + "syn 2.0.48", +] + [[package]] name = "proc-macro-error" version = "1.0.4" @@ -2852,17 +2321,11 @@ dependencies = [ "version_check", ] -[[package]] -name = "proc-macro-hack" -version = "0.5.19" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dbf0c48bc1d91375ae5c3cd81e3722dff1abcf81a30960240640d223f59fe0e5" - [[package]] name = "proc-macro2" -version = "1.0.69" +version = "1.0.78" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "134c189feb4956b20f6f547d2cf727d4c0fe06722b20a0eec87ed445a97f92da" +checksum = "e2422ad645d89c99f8f3e6b88a9fdeca7fabeac836b1002371c4367c8f984aae" dependencies = [ "unicode-ident", ] @@ -2878,15 +2341,16 @@ dependencies = [ [[package]] name = "pyo3" -version = "0.19.2" +version = "0.20.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e681a6cfdc4adcc93b4d3cf993749a4552018ee0a9b65fc0ccfad74352c72a38" +checksum = "53bdbb96d49157e65d45cc287af5f32ffadd5f4761438b527b055fb0d4bb8233" dependencies = [ "cfg-if", "indoc", "libc", "memoffset 0.9.0", "parking_lot", + "portable-atomic 1.6.0", "pyo3-build-config", "pyo3-ffi", "pyo3-macros", @@ -2895,9 +2359,9 @@ dependencies = [ [[package]] name = "pyo3-asyncio" -version = "0.19.0" +version = "0.20.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a2cc34c1f907ca090d7add03dc523acdd91f3a4dab12286604951e2f5152edad" +checksum = "6ea6b68e93db3622f3bb3bf363246cf948ed5375afe7abff98ccbdd50b184995" dependencies = [ "futures", "once_cell", @@ -2908,9 +2372,9 @@ dependencies = [ [[package]] name = "pyo3-build-config" -version = "0.19.2" +version = "0.20.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "076c73d0bc438f7a4ef6fdd0c3bb4732149136abd952b110ac93e4edb13a6ba5" +checksum = "deaa5745de3f5231ce10517a1f5dd97d53e5a2fd77aa6b5842292085831d48d7" dependencies = [ "once_cell", "target-lexicon", @@ -2918,9 +2382,9 @@ dependencies = [ [[package]] name = "pyo3-ffi" -version = "0.19.2" +version = "0.20.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e53cee42e77ebe256066ba8aa77eff722b3bb91f3419177cf4cd0f304d3284d9" +checksum = "62b42531d03e08d4ef1f6e85a2ed422eb678b8cd62b762e53891c05faf0d4afa" dependencies = [ "libc", "pyo3-build-config", @@ -2939,49 +2403,38 @@ dependencies = [ [[package]] name = "pyo3-macros" -version = "0.19.2" +version = "0.20.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dfeb4c99597e136528c6dd7d5e3de5434d1ceaf487436a3f03b2d56b6fc9efd1" +checksum = "7305c720fa01b8055ec95e484a6eca7a83c841267f0dd5280f0c8b8551d2c158" dependencies = [ "proc-macro2", "pyo3-macros-backend", "quote", - "syn 1.0.102", + "syn 2.0.48", ] [[package]] name = "pyo3-macros-backend" -version = "0.19.2" +version = "0.20.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "947dc12175c254889edc0c02e399476c2f652b4b9ebd123aa655c224de259536" +checksum = "7c7e9b68bb9c3149c5b0cade5d07f953d6d125eb4337723c4ccdb665f1f96185" dependencies = [ + "heck", "proc-macro2", + "pyo3-build-config", "quote", - "syn 1.0.102", + "syn 2.0.48", ] [[package]] name = "quote" -version = "1.0.33" +version = "1.0.35" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5267fca4496028628a95160fc423a33e8b2e6af8a5302579e322e4b520293cae" +checksum = "291ec9ab5efd934aaf503a6466c5d5251535d108ee747472c3977cc5acc868ef" dependencies = [ "proc-macro2", ] -[[package]] -name = "rand" -version = "0.7.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a6b1679d49b24bbfe0c803429aa1874472f50d9b363131f0e89fc356b544d03" -dependencies = [ - "getrandom 0.1.16", - "libc", - "rand_chacha 0.2.2", - "rand_core 0.5.1", - "rand_hc", -] - [[package]] name = "rand" version = "0.8.5" @@ -2989,18 +2442,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" dependencies = [ "libc", - "rand_chacha 0.3.1", - "rand_core 0.6.4", -] - -[[package]] -name = "rand_chacha" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f4c8ed856279c9737206bf725bf36935d8666ead7aa69b52be55af369d193402" -dependencies = [ - "ppv-lite86", - "rand_core 0.5.1", + "rand_chacha", + "rand_core", ] [[package]] @@ -3010,34 +2453,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" dependencies = [ "ppv-lite86", - "rand_core 0.6.4", -] - -[[package]] -name = "rand_core" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "90bde5296fc891b0cef12a6d03ddccc162ce7b2aff54160af9338f8d40df6d19" -dependencies = [ - "getrandom 0.1.16", + "rand_core", ] [[package]] name = "rand_core" version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" -dependencies = [ - "getrandom 0.2.7", -] - -[[package]] -name = "rand_hc" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ca3129af7b92a17112d59ad498c6f81eaf463253766b90396d39ea7a39d6613c" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" dependencies = [ - "rand_core 0.5.1", + "getrandom", ] [[package]] @@ -3074,7 +2499,7 @@ version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fb5a58c1855b4b6819d59012155603f0b22ad30cad752600aadfcb695265519a" dependencies = [ - "bitflags", + "bitflags 1.3.2", ] [[package]] @@ -3083,7 +2508,7 @@ version = "0.3.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "567664f262709473930a4bf9e51bf2ebf3348f2e748ccc50dea20646858f8f29" dependencies = [ - "bitflags", + "bitflags 1.3.2", ] [[package]] @@ -3143,7 +2568,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "68cc60575865c7831548863cc02356512e3f1dc2f3f82cb837d7fc4cc8f3c97c" dependencies = [ "base64 0.13.0", - "bytes 1.1.0", + "bytes", "encoding_rs", "futures-core", "futures-util", @@ -3159,7 +2584,7 @@ dependencies = [ "once_cell", "percent-encoding", "pin-project-lite", - "rustls", + "rustls 0.20.6", "rustls-pemfile", "serde", "serde_json", @@ -3172,7 +2597,7 @@ dependencies = [ "wasm-bindgen", "wasm-bindgen-futures", "web-sys", - "webpki-roots", + "webpki-roots 0.22.5", "winreg", ] @@ -3200,7 +2625,7 @@ dependencies = [ "futures", "futures-timer", "rstest_macros", - "rustc_version 0.4.0", + "rustc_version", ] [[package]] @@ -3212,10 +2637,22 @@ dependencies = [ "cfg-if", "proc-macro2", "quote", - "rustc_version 0.4.0", + "rustc_version", "syn 1.0.102", ] +[[package]] +name = "rstest_reuse" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88530b681abe67924d42cca181d070e3ac20e0740569441a9e35a7cedd2b34a4" +dependencies = [ + "quote", + "rand", + "rustc_version", + "syn 2.0.48", +] + [[package]] name = "rustc-demangle" version = "0.1.21" @@ -3228,22 +2665,13 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" -[[package]] -name = "rustc_version" -version = "0.2.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "138e3e0acb6c9fb258b19b67cb8abd63c00679d2851805ea151465464fe9030a" -dependencies = [ - "semver 0.9.0", -] - [[package]] name = "rustc_version" version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bfa0f585226d2e68097d4f95d113b15b83a82e819ab25717ec0590d9584ef366" dependencies = [ - "semver 1.0.14", + "semver", ] [[package]] @@ -3252,7 +2680,7 @@ version = "0.36.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d4fdebc4b395b7fbb9ab11e462e20ed9051e7b16e42d24042c776eca0ac81b03" dependencies = [ - "bitflags", + "bitflags 1.3.2", "errno 0.2.8", "io-lifetimes", "libc", @@ -3266,7 +2694,7 @@ version = "0.37.19" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "acf8729d8542766f1b2cf77eb034d52f40d375bb8b615d0b147089946e16613d" dependencies = [ - "bitflags", + "bitflags 1.3.2", "errno 0.3.1", "io-lifetimes", "libc", @@ -3286,6 +2714,18 @@ dependencies = [ "webpki", ] +[[package]] +name = "rustls" +version = "0.21.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd8d6c9f025a446bc4d18ad9632e69aec8f287aa84499ee335599fabd20c3fd8" +dependencies = [ + "log", + "ring", + "rustls-webpki", + "sct", +] + [[package]] name = "rustls-pemfile" version = "1.0.2" @@ -3295,6 +2735,16 @@ dependencies = [ "base64 0.21.0", ] +[[package]] +name = "rustls-webpki" +version = "0.101.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c7d5dece342910d9ba34d259310cae3e0154b873b35408b787b59bce53d34fe" +dependencies = [ + "ring", + "untrusted", +] + [[package]] name = "rustversion" version = "1.0.11" @@ -3316,16 +2766,6 @@ dependencies = [ "winapi-util", ] -[[package]] -name = "schannel" -version = "0.1.20" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "88d6731146462ea25d9244b2ed5fd1d716d25c52e4d54aa4fb0f3c4e9854dbe2" -dependencies = [ - "lazy_static", - "windows-sys 0.36.1", -] - [[package]] name = "scopeguard" version = "1.1.0" @@ -3357,27 +2797,12 @@ dependencies = [ "zeroize", ] -[[package]] -name = "semver" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d7eb9ef2c18661902cc47e535f9bc51b78acd254da71d375c2f6720d9a40403" -dependencies = [ - "semver-parser", -] - [[package]] name = "semver" version = "1.0.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e25dfac463d778e353db5be2449d1cce89bd6fd23c9f1ea21310ce6e5a1b29c4" -[[package]] -name = "semver-parser" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3" - [[package]] name = "serde" version = "1.0.164" @@ -3395,7 +2820,7 @@ checksum = "d9735b638ccc51c28bf6914d90a2e9725b377144fc612c49a611fddd1b631d68" dependencies = [ "proc-macro2", "quote", - "syn 2.0.38", + "syn 2.0.48", ] [[package]] @@ -3419,17 +2844,6 @@ dependencies = [ "serde", ] -[[package]] -name = "serde_qs" -version = "0.8.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c7715380eec75f029a4ef7de39a9200e0a63823176b759d055b613f5a87df6a6" -dependencies = [ - "percent-encoding", - "serde", - "thiserror", -] - [[package]] name = "serde_spanned" version = "0.6.1" @@ -3465,7 +2879,7 @@ dependencies = [ "serde", "serde_json", "serde_with_macros", - "time 0.3.15", + "time", ] [[package]] @@ -3477,16 +2891,7 @@ dependencies = [ "darling", "proc-macro2", "quote", - "syn 2.0.38", -] - -[[package]] -name = "sha1" -version = "0.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c1da05c97445caa12d05e848c4a4fcbbea29e748ac28f7e80e9b010392063770" -dependencies = [ - "sha1_smol", + "syn 2.0.48", ] [[package]] @@ -3497,26 +2902,7 @@ checksum = "f04293dc80c3993519f2d7f6f511707ee7094fe0c6d3406feb330cdb3540eba3" dependencies = [ "cfg-if", "cpufeatures", - "digest 0.10.5", -] - -[[package]] -name = "sha1_smol" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ae1a47186c03a32177042e55dbc5fd5aee900b8e0069a8d70fba96a9375cd012" - -[[package]] -name = "sha2" -version = "0.9.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4d58a1e1bf39749807d89cf2d98ac2dfa0ff1cb3faa38fbb64dd88ac8013d800" -dependencies = [ - "block-buffer 0.9.0", - "cfg-if", - "cpufeatures", - "digest 0.9.0", - "opaque-debug", + "digest", ] [[package]] @@ -3527,7 +2913,7 @@ checksum = "82e6b795fe2e3b1e845bafcb27aa35405c4d47cdfc92af5fc8d3002f76cebdc0" dependencies = [ "cfg-if", "cpufeatures", - "digest 0.10.5", + "digest", ] [[package]] @@ -3564,7 +2950,7 @@ dependencies = [ "num-bigint", "num-traits", "thiserror", - "time 0.3.15", + "time", ] [[package]] @@ -3576,22 +2962,11 @@ dependencies = [ "autocfg", ] -[[package]] -name = "sluice" -version = "0.5.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6d7400c0eff44aa2fcb5e31a5f24ba9716ed90138769e4977a2ba6014ae63eb5" -dependencies = [ - "async-channel", - "futures-core", - "futures-io", -] - [[package]] name = "smallvec" -version = "1.10.0" +version = "1.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a507befe795404456341dfab10cef66ead4c041f62b8b11bbb92bffe5d0953e0" +checksum = "e6ecd384b10a64542d77071bd64bd7b231f4ed5940fba55e98c3de13824cf3d7" [[package]] name = "snafu" @@ -3632,79 +3007,12 @@ version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d" -[[package]] -name = "spinning_top" -version = "0.2.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75adad84ee84b521fb2cca2d4fd0f1dab1d8d026bda3c5bea4ca63b5f9f9293c" -dependencies = [ - "lock_api", -] - -[[package]] -name = "standback" -version = "0.2.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e113fb6f3de07a243d434a56ec6f186dfd51cb08448239fe7bcae73f87ff28ff" -dependencies = [ - "version_check", -] - [[package]] name = "static_assertions" version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" -[[package]] -name = "stdweb" -version = "0.4.20" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d022496b16281348b52d0e30ae99e01a73d737b2f45d38fed4edf79f9325a1d5" -dependencies = [ - "discard", - "rustc_version 0.2.3", - "stdweb-derive", - "stdweb-internal-macros", - "stdweb-internal-runtime", - "wasm-bindgen", -] - -[[package]] -name = "stdweb-derive" -version = "0.5.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c87a60a40fccc84bef0652345bbbbbe20a605bf5d0ce81719fc476f5c03b50ef" -dependencies = [ - "proc-macro2", - "quote", - "serde", - "serde_derive", - "syn 1.0.102", -] - -[[package]] -name = "stdweb-internal-macros" -version = "0.2.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "58fa5ff6ad0d98d1ffa8cb115892b6e69d67799f6763e162a1c9db421dc22e11" -dependencies = [ - "base-x", - "proc-macro2", - "quote", - "serde", - "serde_derive", - "serde_json", - "sha1 0.6.1", - "syn 1.0.102", -] - -[[package]] -name = "stdweb-internal-runtime" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "213701ba3370744dcd1a12960caa4843b3d68b4d1c0a5d575e0d65b2ee9d16c0" - [[package]] name = "strsim" version = "0.10.0" @@ -3720,7 +3028,7 @@ dependencies = [ "proc-macro2", "quote", "structmeta-derive", - "syn 2.0.38", + "syn 2.0.48", ] [[package]] @@ -3731,7 +3039,7 @@ checksum = "a60bcaff7397072dca0017d1db428e30d5002e00b6847703e2e42005c95fbe00" dependencies = [ "proc-macro2", "quote", - "syn 2.0.38", + "syn 2.0.48", ] [[package]] @@ -3762,29 +3070,6 @@ version = "2.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6bdef32e8150c2a081110b42772ffe7d7c9032b606bc226c8260fd97e0976601" -[[package]] -name = "surf" -version = "2.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "718b1ae6b50351982dedff021db0def601677f2120938b070eadb10ba4038dd7" -dependencies = [ - "async-std", - "async-trait", - "cfg-if", - "encoding_rs", - "futures-util", - "getrandom 0.2.7", - "http-client", - "http-types", - "log", - "mime_guess", - "once_cell", - "pin-project-lite", - "serde", - "serde_json", - "web-sys", -] - [[package]] name = "syn" version = "1.0.102" @@ -3798,9 +3083,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.38" +version = "2.0.48" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e96b79aaa137db8f61e26363a0c9b47d8b4ec75da28b7d1d614c2303e232408b" +checksum = "0f3531638e407dfc0814761abb7c00a5b54992b849452a0646b7f65c9f770f3f" dependencies = [ "proc-macro2", "quote", @@ -3832,7 +3117,7 @@ checksum = "31c0432476357e58790aaa47a8efb0c5138f137343f3b5f23bd36a27e3b0a6d6" dependencies = [ "autocfg", "cfg-if", - "fastrand", + "fastrand 1.8.0", "redox_syscall 0.3.5", "rustix 0.37.19", "windows-sys 0.48.0", @@ -3858,16 +3143,19 @@ name = "test_util" version = "0.0.0" dependencies = [ "anyhow", - "async-std", - "async_zip", + "bindgen 0.69.4", + "camino", "flate2", "fs-err", + "indoc", + "libloading", "once_cell", + "reqwest", "serde", "serde_json", - "surf", "tar", "tokio", + "zip", ] [[package]] @@ -3878,22 +3166,22 @@ checksum = "949517c0cf1bf4ee812e2e07e08ab448e3ae0d23472aee8a06c985f0c8815b16" [[package]] name = "thiserror" -version = "1.0.37" +version = "1.0.56" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "10deb33631e3c9018b9baf9dcbbc4f737320d2b576bac10f6aefa048fa407e3e" +checksum = "d54378c645627613241d077a3a79db965db602882668f9136ac42af9ecb730ad" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.37" +version = "1.0.56" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "982d17546b47146b28f7c22e3d08465f6b8903d0ea13c1660d9d84a6e7adcdbb" +checksum = "fa0faa943b50f3db30a20aa7e265dbc66076993efed8463e8de414e5d06d3471" dependencies = [ "proc-macro2", "quote", - "syn 1.0.102", + "syn 2.0.48", ] [[package]] @@ -3914,21 +3202,6 @@ dependencies = [ "num_cpus", ] -[[package]] -name = "time" -version = "0.2.27" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4752a97f8eebd6854ff91f1c1824cd6160626ac4bd44287f7f4ea2035a02a242" -dependencies = [ - "const_fn", - "libc", - "standback", - "stdweb", - "time-macros 0.1.1", - "version_check", - "winapi", -] - [[package]] name = "time" version = "0.3.15" @@ -3939,17 +3212,7 @@ dependencies = [ "libc", "num_threads", "serde", - "time-macros 0.2.4", -] - -[[package]] -name = "time-macros" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "957e9c6e26f12cb6d0dd7fc776bb67a706312e7299aed74c8dd5b17ebb27e2f1" -dependencies = [ - "proc-macro-hack", - "time-macros-impl", + "time-macros", ] [[package]] @@ -3958,19 +3221,6 @@ version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "42657b1a6f4d817cda8e7a0ace261fe0cc946cf3a80314390b22cc61ae080792" -[[package]] -name = "time-macros-impl" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fd3c141a1b43194f3f56a1411225df8646c55781d5f26db825b3d98507eb482f" -dependencies = [ - "proc-macro-hack", - "proc-macro2", - "quote", - "standback", - "syn 1.0.102", -] - [[package]] name = "tinyvec" version = "1.6.0" @@ -3993,7 +3243,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c3c786bf8134e5a3a166db9b29ab8f48134739014a3eca7bc6bfa95d673b136f" dependencies = [ "autocfg", - "bytes 1.1.0", + "bytes", "libc", "mio", "num_cpus", @@ -4011,7 +3261,7 @@ checksum = "630bdcf245f78637c13ec01ffae6187cca34625e8c63150d424b59e55af2675e" dependencies = [ "proc-macro2", "quote", - "syn 2.0.38", + "syn 2.0.48", ] [[package]] @@ -4020,7 +3270,7 @@ version = "0.23.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c43ee83903113e03984cb9e5cebe6c04a5116269e900e3ddba8f068a62adda59" dependencies = [ - "rustls", + "rustls 0.20.6", "tokio", "webpki", ] @@ -4031,7 +3281,7 @@ version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0bb2e075f03b3d66d8d8785356224ba688d2906a371015e225beeb65ca92c740" dependencies = [ - "bytes 1.1.0", + "bytes", "futures-core", "futures-sink", "pin-project-lite", @@ -4132,16 +3382,6 @@ dependencies = [ "tracing-subscriber", ] -[[package]] -name = "tracing-futures" -version = "0.2.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "97d095ae15e245a057c8e8451bab9b3ee1e1f68e9ba2b4fbc18d0ac5237835f2" -dependencies = [ - "pin-project", - "tracing", -] - [[package]] name = "tracing-log" version = "0.1.3" @@ -4207,15 +3447,6 @@ dependencies = [ "syn 1.0.102", ] -[[package]] -name = "unicase" -version = "2.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "50f37be617794602aabbeee0be4f259dc1778fabe05e2d67ee8f79326d5cb4f6" -dependencies = [ - "version_check", -] - [[package]] name = "unicode-bidi" version = "0.3.8" @@ -4245,19 +3476,9 @@ checksum = "c0edd1e5b14653f783770bce4a4dabb4a5108a5370a5f5d8cfe8710c361f6c8b" [[package]] name = "unindent" -version = "0.1.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "58ee9362deb4a96cef4d437d1ad49cffc9b9e92d202b6995674e928ce684f112" - -[[package]] -name = "universal-hash" -version = "0.4.1" +version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9f214e8f697e925001e66ec2c6e37a4ef93f0f78c2eed7814394e10c62025b05" -dependencies = [ - "generic-array", - "subtle", -] +checksum = "c7de7d73e1754487cb58364ee906a499937a0dfabd86bcb980fa99ec8c8fa2ce" [[package]] name = "untrusted" @@ -4267,19 +3488,17 @@ checksum = "a156c684c91ea7d62626509bce3cb4e1d9ed5c4d978f7b4352658f96a4c26b4a" [[package]] name = "ureq" -version = "2.5.0" +version = "2.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b97acb4c28a254fd7a4aeec976c46a7fa404eac4d7c134b30c75144846d7cb8f" +checksum = "f5ccd538d4a604753ebc2f17cd9946e89b77bf87f6a8e2309667c6f2e87855e3" dependencies = [ - "base64 0.13.0", - "chunked_transfer", - "flate2", + "base64 0.21.0", "log", "once_cell", - "rustls", + "rustls 0.21.7", + "rustls-webpki", "url", - "webpki", - "webpki-roots", + "webpki-roots 0.25.4", ] [[package]] @@ -4306,7 +3525,7 @@ version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d023da39d1fde5a8a3fe1f3e01ca9632ada0a63e9797de55a879d6e2236277be" dependencies = [ - "getrandom 0.2.7", + "getrandom", "serde", ] @@ -4317,26 +3536,35 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d" [[package]] -name = "value-bag" -version = "1.0.0-alpha.9" +name = "version_check" +version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2209b78d1249f7e6f3293657c9779fe31ced465df091bbd433a1cf88e916ec55" -dependencies = [ - "ctor", - "version_check", -] +checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" [[package]] -name = "vcpkg" -version = "0.2.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" +name = "voicevox-ort" +version = "2.0.0-rc.2" +source = "git+https://github.com/VOICEVOX/ort.git?rev=a2d6ae22327869e896bf4c16828734d09516d2d9#a2d6ae22327869e896bf4c16828734d09516d2d9" +dependencies = [ + "half", + "js-sys", + "ndarray", + "thiserror", + "tracing", + "voicevox-ort-sys", + "web-sys", +] [[package]] -name = "version_check" -version = "0.9.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" +name = "voicevox-ort-sys" +version = "2.0.0-rc.2" +source = "git+https://github.com/VOICEVOX/ort.git?rev=a2d6ae22327869e896bf4c16828734d09516d2d9#a2d6ae22327869e896bf4c16828734d09516d2d9" +dependencies = [ + "flate2", + "sha2", + "tar", + "ureq", +] [[package]] name = "voicevox_core" @@ -4344,6 +3572,7 @@ version = "0.0.0" dependencies = [ "anyhow", "async_zip", + "camino", "derive-getters", "derive-new", "derive_more", @@ -4357,24 +3586,29 @@ dependencies = [ "humansize", "indexmap 2.0.0", "itertools 0.10.5", + "jlabel", "nanoid", "ndarray", "once_cell", - "onnxruntime", "open_jtalk", "ouroboros", "pretty_assertions", "rayon", "regex", "rstest", + "rstest_reuse", "serde", "serde_json", + "serde_with", + "smallvec", + "strum", "tempfile", "test_util", "thiserror", "tokio", "tracing", "uuid", + "voicevox-ort", "voicevox_core_macros", "windows", "zip", @@ -4388,6 +3622,7 @@ dependencies = [ "anstyle-query", "anyhow", "assert_cmd", + "camino", "chrono", "clap 4.0.10", "colorchoice", @@ -4409,7 +3644,6 @@ dependencies = [ "serde", "serde_json", "serde_with", - "strum", "tempfile", "test_util", "thiserror", @@ -4444,13 +3678,14 @@ dependencies = [ "indexmap 2.0.0", "proc-macro2", "quote", - "syn 2.0.38", + "syn 2.0.48", ] [[package]] name = "voicevox_core_python_api" version = "0.0.0" dependencies = [ + "camino", "easy-ext", "log", "pyo3", @@ -4472,12 +3707,6 @@ dependencies = [ "libc", ] -[[package]] -name = "waker-fn" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d5b2c62b4012a3e1eca5a7e077d13b3bf498c4073e33ccd58626607748ceeca" - [[package]] name = "walkdir" version = "2.3.3" @@ -4498,12 +3727,6 @@ dependencies = [ "try-lock", ] -[[package]] -name = "wasi" -version = "0.9.0+wasi-snapshot-preview1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cccddf32554fecc6acb585f82a32a72e28b48f8c4c1883ddfeeeaa96f7d8e519" - [[package]] name = "wasi" version = "0.11.0+wasi-snapshot-preview1" @@ -4606,13 +3829,10 @@ dependencies = [ ] [[package]] -name = "wepoll-ffi" -version = "0.1.2" +name = "webpki-roots" +version = "0.25.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d743fdedc5c64377b5fc2bc036b01c7fd642205a0d96356034ae3404d49eb7fb" -dependencies = [ - "cc", -] +checksum = "5f20c57d8d7db6d3b86154206ae5d8fba62dd39573114de97c2cb0578251f8e1" [[package]] name = "which" @@ -4890,15 +4110,6 @@ dependencies = [ "fs-err", ] -[[package]] -name = "xz2" -version = "0.1.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "388c44dc09d76f1536602ead6d325eb532f5c122f17782bd57fb47baeeb767e2" -dependencies = [ - "lzma-sys", -] - [[package]] name = "yansi" version = "0.5.1" @@ -4917,17 +4128,17 @@ version = "0.6.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "537ce7411d25e54e8ae21a7ce0b15840e7bfcff15b51d697ec3266cc76bdf080" dependencies = [ - "aes 0.7.5", + "aes", "byteorder", "bzip2", "constant_time_eq", "crc32fast", "crossbeam-utils", "flate2", - "hmac 0.12.1", + "hmac", "pbkdf2", - "sha1 0.10.5", - "time 0.3.15", + "sha1", + "time", "zstd", ] @@ -4952,11 +4163,10 @@ dependencies = [ [[package]] name = "zstd-sys" -version = "2.0.5+zstd.1.5.2" +version = "2.0.9+zstd.1.5.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "edc50ffce891ad571e9f9afe5039c4837bede781ac4bb13052ed7ae695518596" +checksum = "9e16efa8a874a0481a574084d34cc26fdb3b99627480f785888deb6386506656" dependencies = [ "cc", - "libc", "pkg-config", ] diff --git a/Cargo.toml b/Cargo.toml index acaa1300b..7102127d5 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -8,16 +8,17 @@ anstream = { version = "0.5.0", default-features = false } anstyle-query = "1.0.0" anyhow = "1.0.65" assert_cmd = "2.0.8" -async-std = "1.12.0" -async_zip = "0.0.11" +async_zip = "=0.0.16" +bindgen = "0.69.4" binstall-tar = "0.4.39" bytes = "1.1.0" +camino = "1.1.6" cbindgen = "0.24.3" chrono = { version = "0.4.26", default-features = false } clap = "4.0.10" color-eyre = "0.6.2" colorchoice = "1.0.0" -cstr = "0.2.11" +cstr = "0.2.11" # https://github.com/dtolnay/syn/issues/1502 derive-getters = "0.2.0" derive-new = "0.5.9" derive_more = "0.99.17" @@ -32,12 +33,15 @@ fs-err = "2.9.0" futures = "0.3.26" futures-core = "0.3.25" futures-util = "0.3.25" +futures-lite = "2.2.0" heck = "0.4.1" humansize = "2.1.2" indexmap = "2.0.0" indicatif = "0.17.3" +indoc = "2.0.4" inventory = "0.3.4" itertools = "0.10.5" +jlabel = "0.1.2" jni = "0.21.1" libc = "0.2.134" libloading = "0.7.3" @@ -52,19 +56,20 @@ ouroboros = "0.18.0" parse-display = "0.8.2" pretty_assertions = "1.3.0" proc-macro2 = "1.0.69" -pyo3 = "0.19.2" -pyo3-asyncio = "0.19.0" +pyo3 = "0.20.3" +pyo3-asyncio = "0.20.0" pyo3-log = "0.9.0" quote = "1.0.33" rayon = "1.6.1" regex = "1.10.0" reqwest = { version = "0.11.13", default-features = false } rstest = "0.15.0" +rstest_reuse = "0.6.0" serde = "1.0.145" serde_json = "1.0.85" serde_with = "3.3.0" +smallvec = "1.13.1" strum = "0.24.1" -surf = "2.3.2" syn = "2.0.38" tar = "0.4.38" tempfile = "3.6.0" @@ -81,13 +86,13 @@ voicevox_core = { path = "crates/voicevox_core" } windows = "0.43.0" zip = "0.6.3" -[workspace.dependencies.onnxruntime] -git = "https://github.com/VOICEVOX/onnxruntime-rs.git" -rev = "ebb9dcb9b26ee681889b52b6db3b4f642b04a250" +[workspace.dependencies.voicevox-ort] +git = "https://github.com/VOICEVOX/ort.git" +rev = "a2d6ae22327869e896bf4c16828734d09516d2d9" [workspace.dependencies.open_jtalk] git = "https://github.com/VOICEVOX/open_jtalk-rs.git" -rev = "a16714ce16dec76fd0e3041a7acfa484921db3b5" +rev = "e1940f3fd61a48bed5bbec8cd2645e13923b1f80" # FIXME: iOS対応のpull request(https://github.com/wesleywiser/process_path/pull/16)がマージされる見込みが無いため [workspace.dependencies.process_path] diff --git a/build_util/make_ios_xcframework.bash b/build_util/make_ios_xcframework.bash new file mode 100755 index 000000000..2d35fac88 --- /dev/null +++ b/build_util/make_ios_xcframework.bash @@ -0,0 +1,79 @@ +#!/usr/bin/env bash +set -eu + +if [ ! -v IOS_X86_64_PATH ]; then # X86_64用のモジュールのディレクトリ(simulator) + echo "IOS_X86_64_PATHが未定義です" + exit 1 +fi +if [ ! -v IOS_AARCH64_SIM_PATH ]; then # AARCH64_SIM用のモジュールのディレクトリ(simulator) + echo "IOS_AARCH64_SIM_PATHが未定義です" + exit 1 +fi +if [ ! -v IOS_AARCH64_PATH ]; then # AARCH64用のモジュールのディレクトリ(実機) + echo "IOS_AARCH64_PATHが未定義です" + exit 1 +fi +if [ ! -v OUTPUT_ASSET_PATH ]; then # 出力するASSETのディレクトリ + echo "OUTPUT_ASSET_PATHが未定義です" + exit 1 +fi + +echo "* Get original onnxruntime file name from rpath" +output=$(otool -L "${IOS_AARCH64_PATH}/libvoicevox_core.dylib") +matched_line=$(echo "$output" | grep "@rpath" | grep "libonnxruntime") +if [[ $matched_line ]]; then + if [[ $matched_line =~ (@rpath/([^ ]+\.dylib)) ]]; then + dylib_string=${BASH_REMATCH[2]} + else + echo "Expected pattern not found in the matched line" + echo "$output" + exit 1 + fi +else + echo "No line containing '@rpath' and 'libonnxruntime' found" + echo "$output" + exit 1 +fi +echo "Original onnx dylib file name: $dylib_string" + +echo "* Copy Framework template" +arches=("aarch64" "sim") +artifacts=("${IOS_AARCH64_PATH}" "${IOS_AARCH64_SIM_PATH}") +for i in "${!arches[@]}"; do + arch="${arches[$i]}" + artifact="${artifacts[$i]}" + echo "* Copy Framework-${arch} template" + mkdir -p "Framework-${arch}/voicevox_core.framework/Headers" + cp -vr "crates/voicevox_core_c_api/xcframework/Frameworks/${arch}/" "Framework-${arch}/" + cp -v "${artifact}/voicevox_core.h" \ + "Framework-${arch}/voicevox_core.framework/Headers/voicevox_core.h" +done + +echo "* Create dylib" +# aarch64はdylibをコピー +cp -v "${IOS_AARCH64_PATH}/libvoicevox_core.dylib" \ + "Framework-aarch64/voicevox_core.framework/voicevox_core" + +# simはx86_64とarrch64を合わせてdylib作成 +lipo -create "${IOS_X86_64_PATH}/libvoicevox_core.dylib" \ + "${IOS_AARCH64_SIM_PATH}/libvoicevox_core.dylib" \ + -output "Framework-sim/voicevox_core.framework/voicevox_core" + +for arch in "${arches[@]}"; do + echo "* Change ${arch} @rpath" + # 自身への@rpathを変更 + install_name_tool -id "@rpath/voicevox_core.framework/voicevox_core" \ + "Framework-${arch}/voicevox_core.framework/voicevox_core" + + # 依存ライブラリonnxruntimeへの@rpathを変更 + install_name_tool -change "@rpath/$dylib_string" \ + "@rpath/onnxruntime.framework/onnxruntime" \ + "Framework-${arch}/voicevox_core.framework/voicevox_core" +done + +echo "* Create XCFramework" +mkdir -p "${OUTPUT_ASSET_PATH}" +xcodebuild -create-xcframework \ + -framework "Framework-sim/voicevox_core.framework" \ + -framework "Framework-aarch64/voicevox_core.framework" \ + -output "${OUTPUT_ASSET_PATH}/voicevox_core.xcframework" diff --git a/crates/downloader/Cargo.toml b/crates/downloader/Cargo.toml index c0e11a1ba..20d90e087 100644 --- a/crates/downloader/Cargo.toml +++ b/crates/downloader/Cargo.toml @@ -1,8 +1,6 @@ [package] name = "downloader" -version = "0.0.0" edition.workspace = true -publish.workspace = true [[bin]] name = "download" diff --git a/crates/test_util/Cargo.toml b/crates/test_util/Cargo.toml index 0c8f8f41d..d113b57ce 100644 --- a/crates/test_util/Cargo.toml +++ b/crates/test_util/Cargo.toml @@ -1,26 +1,27 @@ [package] name = "test_util" -version = "0.0.0" edition.workspace = true -publish.workspace = true [dependencies] -async_zip = { workspace = true, features = ["full"] } +libloading.workspace = true once_cell.workspace = true serde = { workspace = true, features = ["derive"] } serde_json.workspace = true -tokio = { workspace = true, features = ["fs", "io-util", "sync"] } [build-dependencies] anyhow.workspace = true -async-std = { workspace = true, features = ["attributes"] } +tokio = { workspace = true, features = ["macros", "rt-multi-thread"] } +bindgen.workspace = true +camino.workspace = true flate2.workspace = true fs-err.workspace = true +indoc.workspace = true serde = { workspace = true, features = ["derive"] } serde_json = { workspace = true, features = ["preserve_order"] } -surf.workspace = true +reqwest = { workspace = true, features = ["rustls-tls"] } tar.workspace = true +zip.workspace = true [lints.rust] -unsafe_code = "forbid" +unsafe_code = "allow" # C APIのbindgen rust_2018_idioms = "warn" diff --git a/crates/test_util/build.rs b/crates/test_util/build.rs index eae3ac286..79e3bc90e 100644 --- a/crates/test_util/build.rs +++ b/crates/test_util/build.rs @@ -1,33 +1,94 @@ use std::{ env, - path::{Path, PathBuf}, + io::{self, Cursor, Write as _}, + path::Path, }; -use anyhow::ensure; -use async_std::io::ReadExt as _; +use anyhow::{anyhow, ensure}; +use camino::{Utf8Path, Utf8PathBuf}; use flate2::read::GzDecoder; +use indoc::formatdoc; use tar::Archive; +use zip::{write::FileOptions, ZipWriter}; #[path = "src/typing.rs"] mod typing; const DIC_DIR_NAME: &str = "open_jtalk_dic_utf_8-1.11"; -#[async_std::main] +#[tokio::main] async fn main() -> anyhow::Result<()> { - let mut dist = PathBuf::from(env::var_os("CARGO_MANIFEST_DIR").unwrap()); - dist.push("data"); + let out_dir = &Utf8PathBuf::from(env::var("OUT_DIR").unwrap()); + let dist = &Utf8Path::new(env!("CARGO_MANIFEST_DIR")).join("data"); let dic_dir = dist.join(DIC_DIR_NAME); if !dic_dir.try_exists()? { - download_open_jtalk_dict(&dist).await?; - ensure!(dic_dir.exists(), "`{}` does not exist", dic_dir.display()); + download_open_jtalk_dict(dist.as_ref()).await?; + ensure!(dic_dir.exists(), "`{dic_dir}` does not exist"); } - generate_example_data_json(&dist)?; + create_sample_voice_model_file(out_dir, dist)?; + + generate_example_data_json(dist.as_ref())?; println!("cargo:rerun-if-changed=build.rs"); println!("cargo:rerun-if-changed=src/typing.rs"); + + generate_c_api_rs_bindings(out_dir) +} + +fn create_sample_voice_model_file(out_dir: &Utf8Path, dist: &Utf8Path) -> anyhow::Result<()> { + const SRC: &str = "../../model/sample.vvm"; + + let files = fs_err::read_dir(SRC)? + .map(|entry| { + let entry = entry?; + let md = entry.metadata()?; + ensure!(!md.is_dir(), "directory in {SRC}"); + let mtime = md.modified()?; + let name = entry + .file_name() + .into_string() + .map_err(|name| anyhow!("{name:?}"))?; + Ok((name, entry.path(), mtime)) + }) + .collect::>>()?; + + let output_dir = &dist.join("model"); + let output_file = &output_dir.join("sample.vvm"); + + let up_to_date = fs_err::metadata(output_file) + .and_then(|md| md.modified()) + .map(|t1| files.iter().all(|&(_, _, t2)| t1 >= t2)); + let up_to_date = match up_to_date { + Ok(p) => p, + Err(e) if e.kind() == io::ErrorKind::NotFound => false, + Err(e) => return Err(e.into()), + }; + + if !up_to_date { + let mut zip = ZipWriter::new(Cursor::new(vec![])); + for (name, path, _) in files { + let content = &fs_err::read(path)?; + zip.start_file(name, FileOptions::default().compression_level(Some(0)))?; + zip.write_all(content)?; + } + let zip = zip.finish()?; + fs_err::create_dir_all(output_dir)?; + fs_err::write(output_file, zip.get_ref())?; + } + + fs_err::write( + out_dir.join("sample_voice_model_file.rs"), + formatdoc! {" + pub const SAMPLE_VOICE_MODEL_FILE_PATH: &::std::primitive::str = {output_file:?}; + + const SAMPLE_VOICE_MODEL_FILE_C_PATH: &::std::ffi::CStr = c{output_file:?}; + const VV_MODELS_ROOT_DIR: &::std::primitive::str = {output_dir:?}; + ", + }, + )?; + println!("cargo:rerun-if-changed={SRC}"); Ok(()) } @@ -37,13 +98,11 @@ async fn download_open_jtalk_dict(dist: &Path) -> anyhow::Result<()> { "https://github.com/r9y9/open_jtalk/releases/download/v1.11.1/{DIC_DIR_NAME}.tar.gz" ); - let req = surf::get(download_url); - let client = surf::client().with(surf::middleware::Redirect::default()); - let mut res = client.send(req).await.map_err(surf::Error::into_inner)?; + let res = reqwest::get(&download_url).await?; ensure!(res.status() == 200, "{}", res.status()); - let mut body_bytes = Vec::with_capacity(100 * 1024 * 1024); - res.read_to_end(&mut body_bytes).await?; - let dict_tar = GzDecoder::new(&body_bytes[..]); + + let bytes = res.bytes().await?; + let dict_tar = GzDecoder::new(&*bytes); let mut dict_archive = Archive::new(dict_tar); dict_archive.unpack(dist)?; @@ -120,3 +179,19 @@ fn generate_example_data_json(dist: &Path) -> anyhow::Result<()> { Ok(()) } + +fn generate_c_api_rs_bindings(out_dir: &Utf8Path) -> anyhow::Result<()> { + static C_BINDINGS_PATH: &str = "../voicevox_core_c_api/include/voicevox_core.h"; + static ADDITIONAL_C_BINDINGS_PATH: &str = "./compatible_engine.h"; + + bindgen::Builder::default() + .header(C_BINDINGS_PATH) + .header(ADDITIONAL_C_BINDINGS_PATH) + .parse_callbacks(Box::new(bindgen::CargoCallbacks::new())) + .dynamic_library_name("CApi") + .generate()? + .write_to_file(out_dir.join("c_api.rs"))?; + println!("cargo:rerun-if-changed={C_BINDINGS_PATH}"); + println!("cargo:rerun-if-changed={ADDITIONAL_C_BINDINGS_PATH}"); + Ok(()) +} diff --git a/crates/test_util/compatible_engine.h b/crates/test_util/compatible_engine.h new file mode 100644 index 000000000..254fd8161 --- /dev/null +++ b/crates/test_util/compatible_engine.h @@ -0,0 +1,28 @@ +#include + +bool initialize(bool use_gpu, int cpu_num_threads, bool load_all_models); + +bool load_model(int64_t speaker_id); + +bool is_model_loaded(int64_t speaker_id); + +void finalize(); + +const char *metas(); + +const char *supported_devices(); + +bool yukarin_s_forward(int64_t length, int64_t *phoneme_list, + int64_t *speaker_id, float *output); + +bool yukarin_sa_forward(int64_t length, int64_t *vowel_phoneme_list, + int64_t *consonant_phoneme_list, + int64_t *start_accent_list, int64_t *end_accent_list, + int64_t *start_accent_phrase_list, + int64_t *end_accent_phrase_list, int64_t *speaker_id, + float *output); + +bool decode_forward(int64_t length, int64_t phoneme_size, float *f0, + float *phoneme, int64_t *speaker_id, float *output); + +const char *last_error_message(); diff --git a/crates/test_util/src/lib.rs b/crates/test_util/src/lib.rs index 05da2a33f..f234d7f76 100644 --- a/crates/test_util/src/lib.rs +++ b/crates/test_util/src/lib.rs @@ -1,16 +1,23 @@ mod typing; -use async_zip::{write::ZipFileWriter, Compression, ZipEntryBuilder}; +include!(concat!(env!("OUT_DIR"), "/sample_voice_model_file.rs")); + +#[allow( + non_camel_case_types, + non_snake_case, + non_upper_case_globals, + unused_extern_crates, + clippy::missing_safety_doc, + clippy::too_many_arguments +)] +pub mod c_api { + include!(concat!(env!("OUT_DIR"), "/c_api.rs")); + + pub const SAMPLE_VOICE_MODEL_FILE_PATH: &std::ffi::CStr = super::SAMPLE_VOICE_MODEL_FILE_C_PATH; + pub const VV_MODELS_ROOT_DIR: &str = super::VV_MODELS_ROOT_DIR; +} + use once_cell::sync::Lazy; -use std::{ - collections::HashMap, - path::{Path, PathBuf}, -}; -use tokio::{ - fs::{self, File}, - io::{AsyncReadExt, AsyncWriteExt}, - sync::Mutex, -}; pub use self::typing::{ DecodeExampleData, DurationExampleData, ExampleData, IntonationExampleData, @@ -21,7 +28,7 @@ pub const OPEN_JTALK_DIC_DIR: &str = concat!( "/data/open_jtalk_dic_utf_8-1.11" ); -pub const EXAMPLE_DATA_JSON: &str = include_str!(concat!( +const EXAMPLE_DATA_JSON: &str = include_str!(concat!( env!("CARGO_MANIFEST_DIR"), "/data/example_data.json" )); @@ -29,49 +36,3 @@ pub const EXAMPLE_DATA_JSON: &str = include_str!(concat!( pub static EXAMPLE_DATA: Lazy = Lazy::new(|| { serde_json::from_str(EXAMPLE_DATA_JSON).expect("failed to parse example_data.json") }); - -static PATH_MUTEX: Lazy>>> = - Lazy::new(|| Mutex::new(HashMap::default())); - -pub async fn convert_zip_vvm(dir: impl AsRef) -> PathBuf { - let dir = dir.as_ref(); - let output_file_name = dir.file_name().unwrap().to_str().unwrap().to_owned() + ".vvm"; - - let out_file_path = PathBuf::from(env!("OUT_DIR")) - .join("test_data/models/") - .join(output_file_name); - let mut path_map = PATH_MUTEX.lock().await; - if !path_map.contains_key(&out_file_path) { - path_map.insert(out_file_path.clone(), Mutex::new(())); - } - let _m = path_map.get(&out_file_path).unwrap().lock().await; - - if !out_file_path.exists() { - fs::create_dir_all(out_file_path.parent().unwrap()) - .await - .unwrap(); - let mut out_file = File::create(&out_file_path).await.unwrap(); - let mut writer = ZipFileWriter::new(&mut out_file); - - for entry in dir.read_dir().unwrap().flatten() { - let entry_builder = ZipEntryBuilder::new( - entry - .path() - .file_name() - .unwrap() - .to_str() - .unwrap() - .to_string(), - Compression::Deflate, - ); - let mut entry_writer = writer.write_entry_stream(entry_builder).await.unwrap(); - let mut file = File::open(entry.path()).await.unwrap(); - let mut buf = Vec::with_capacity(entry.metadata().unwrap().len() as usize); - file.read_to_end(&mut buf).await.unwrap(); - entry_writer.write_all(&buf).await.unwrap(); - entry_writer.close().await.unwrap(); - } - writer.close().await.unwrap(); - } - out_file_path -} diff --git a/crates/voicevox_core/Cargo.toml b/crates/voicevox_core/Cargo.toml index 8d7c70e7e..731862356 100644 --- a/crates/voicevox_core/Cargo.toml +++ b/crates/voicevox_core/Cargo.toml @@ -6,11 +6,13 @@ publish.workspace = true [features] default = [] -directml = ["onnxruntime/directml"] +cuda = ["voicevox-ort/cuda"] +directml = ["voicevox-ort/directml"] [dependencies] anyhow.workspace = true -async_zip = { workspace = true, features = ["full"] } +async_zip = { workspace = true, features = ["deflate"] } +camino.workspace = true derive-getters.workspace = true derive-new.workspace = true derive_more.workspace = true @@ -22,28 +24,33 @@ fs-err = { workspace = true, features = ["tokio"] } futures.workspace = true indexmap = { workspace = true, features = ["serde"] } itertools.workspace = true +jlabel.workspace = true nanoid.workspace = true ndarray.workspace = true once_cell.workspace = true -onnxruntime.workspace = true open_jtalk.workspace = true ouroboros.workspace = true rayon.workspace = true regex.workspace = true -serde = { workspace = true, features = ["derive"] } +serde = { workspace = true, features = ["derive", "rc"] } serde_json = { workspace = true, features = ["preserve_order"] } +serde_with.workspace = true +smallvec.workspace = true +strum = { workspace = true, features = ["derive"] } tempfile.workspace = true thiserror.workspace = true tokio = { workspace = true, features = ["rt"] } # FIXME: feature-gateする tracing.workspace = true uuid = { workspace = true, features = ["v4", "serde"] } voicevox_core_macros = { path = "../voicevox_core_macros" } +voicevox-ort = { workspace = true, features = ["ndarray", "download-binaries"] } zip.workspace = true [dev-dependencies] heck.workspace = true pretty_assertions.workspace = true rstest.workspace = true +rstest_reuse.workspace = true test_util.workspace = true tokio = { workspace = true, features = ["rt", "macros"] } @@ -52,5 +59,8 @@ humansize.workspace = true windows = { workspace = true, features = ["Win32_Foundation", "Win32_Graphics_Dxgi"] } [lints.rust] -unsafe_code = "deny" # FIXME: あまり意味が無くなっているため潔く`allow`にする。あるいはunsafeを撲滅する +# FIXME: `unsafe impl Send`のもあるが、以下2つのマージにより消える予定 +# * https://github.com/VOICEVOX/voicevox_core/pull/725 +# * https://github.com/VOICEVOX/voicevox_core/pull/772 +unsafe_code = "allow" # WindowsのGPU情報表示に、Win32を利用 rust_2018_idioms = "warn" diff --git a/crates/voicevox_core/src/__internal/doctest_fixtures.rs b/crates/voicevox_core/src/__internal/doctest_fixtures.rs index a27478d98..f314845fe 100644 --- a/crates/voicevox_core/src/__internal/doctest_fixtures.rs +++ b/crates/voicevox_core/src/__internal/doctest_fixtures.rs @@ -1,9 +1,12 @@ use std::path::Path; +use camino::Utf8Path; + use crate::{AccelerationMode, InitializeOptions}; pub async fn synthesizer_with_sample_voice_model( - open_jtalk_dic_dir: impl AsRef, + voice_model_path: impl AsRef, + open_jtalk_dic_dir: impl AsRef, ) -> anyhow::Result> { let syntesizer = crate::tokio::Synthesizer::new( crate::tokio::OpenJtalk::new(open_jtalk_dic_dir).await?, @@ -13,11 +16,7 @@ pub async fn synthesizer_with_sample_voice_model( }, )?; - let model = &crate::tokio::VoiceModel::from_path(concat!( - env!("CARGO_MANIFEST_DIR"), - "/../../model/sample.vvm", - )) - .await?; + let model = &crate::tokio::VoiceModel::from_path(voice_model_path).await?; syntesizer.load_voice_model(model).await?; Ok(syntesizer) diff --git a/crates/voicevox_core/src/__internal/interop.rs b/crates/voicevox_core/src/__internal/interop.rs index c8cd7101f..fe46d10bc 100644 --- a/crates/voicevox_core/src/__internal/interop.rs +++ b/crates/voicevox_core/src/__internal/interop.rs @@ -1 +1 @@ -pub use crate::synthesizer::blocking::PerformInference; +pub use crate::{metas::merge as merge_metas, synthesizer::blocking::PerformInference}; diff --git a/crates/voicevox_core/src/engine/acoustic_feature_extractor.rs b/crates/voicevox_core/src/engine/acoustic_feature_extractor.rs index 4a30f71a5..5ee7ea540 100644 --- a/crates/voicevox_core/src/engine/acoustic_feature_extractor.rs +++ b/crates/voicevox_core/src/engine/acoustic_feature_extractor.rs @@ -61,7 +61,7 @@ static PHONEME_MAP: Lazy> = Lazy::new(|| { }); #[derive(Debug, Clone, PartialEq, new, Default, Getters)] -pub struct OjtPhoneme { +pub(crate) struct OjtPhoneme { phoneme: String, #[allow(dead_code)] start: f32, @@ -70,15 +70,15 @@ pub struct OjtPhoneme { } impl OjtPhoneme { - pub fn num_phoneme() -> usize { + pub(crate) fn num_phoneme() -> usize { PHONEME_MAP.len() } - pub fn space_phoneme() -> String { + fn space_phoneme() -> String { "pau".into() } - pub fn phoneme_id(&self) -> i64 { + pub(crate) fn phoneme_id(&self) -> i64 { if self.phoneme.is_empty() { -1 } else { @@ -86,7 +86,7 @@ impl OjtPhoneme { } } - pub fn convert(phonemes: &[OjtPhoneme]) -> Vec { + pub(crate) fn convert(phonemes: &[OjtPhoneme]) -> Vec { let mut phonemes = phonemes.to_owned(); if let Some(first_phoneme) = phonemes.first_mut() { if first_phoneme.phoneme.contains("sil") { diff --git a/crates/voicevox_core/src/engine/full_context_label.rs b/crates/voicevox_core/src/engine/full_context_label.rs index efe419579..8b87048e5 100644 --- a/crates/voicevox_core/src/engine/full_context_label.rs +++ b/crates/voicevox_core/src/engine/full_context_label.rs @@ -1,10 +1,11 @@ -use std::collections::HashMap; +use std::str::FromStr; -use crate::engine::open_jtalk::FullcontextExtractor; -use derive_getters::Getters; -use derive_new::new; -use once_cell::sync::Lazy; -use regex::Regex; +use crate::{ + engine::{self, open_jtalk::FullcontextExtractor, MoraModel}, + AccentPhraseModel, +}; +use jlabel::{Label, Mora}; +use smallvec::SmallVec; // FIXME: 入力テキストをここで持って、メッセージに含む #[derive(thiserror::Error, Debug)] @@ -20,321 +21,442 @@ enum ErrorKind { #[display(fmt = "Open JTalkで解釈することができませんでした")] OpenJtalk, - #[display(fmt = "label parse error label: {label}")] - LabelParse { label: String }, + #[display(fmt = "jlabelでラベルを解釈することができませんでした")] + Jlabel, - #[display(fmt = "too long mora mora_phonemes: {mora_phonemes:?}")] - TooLongMora { mora_phonemes: Vec }, - - #[display(fmt = "invalid mora: {mora:?}")] - InvalidMora { mora: Box }, + #[display(fmt = "too long mora")] + TooLongMora, } type Result = std::result::Result; -#[derive(new, Getters, Clone, PartialEq, Eq, Debug)] -pub struct Phoneme { - contexts: HashMap, - label: String, -} - -static P3_REGEX: Lazy = Lazy::new(|| Regex::new(r"(\-(.*?)\+)").unwrap()); -static A2_REGEX: Lazy = Lazy::new(|| Regex::new(r"(\+(\d+|xx)\+)").unwrap()); -static A3_REGEX: Lazy = Lazy::new(|| Regex::new(r"(\+(\d+|xx)/B:)").unwrap()); -static F1_REGEX: Lazy = Lazy::new(|| Regex::new(r"(/F:(\d+|xx)_)").unwrap()); -static F2_REGEX: Lazy = Lazy::new(|| Regex::new(r"(_(\d+|xx)\#)").unwrap()); -static F3_REGEX: Lazy = Lazy::new(|| Regex::new(r"(\#(\d+|xx)_)").unwrap()); -static F5_REGEX: Lazy = Lazy::new(|| Regex::new(r"(@(\d+|xx)_)").unwrap()); -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) -> std::result::Result { - if let Some(caps) = re.captures(label) { - Ok(caps.get(2).unwrap().as_str().to_string()) - } else { - Err(ErrorKind::LabelParse { - label: label.into(), - }) - } -} - -impl Phoneme { - 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)?); - contexts.insert("a2".into(), string_feature_by_regex(&A2_REGEX, &label)?); - contexts.insert("a3".into(), string_feature_by_regex(&A3_REGEX, &label)?); - contexts.insert("f1".into(), string_feature_by_regex(&F1_REGEX, &label)?); - contexts.insert("f2".into(), string_feature_by_regex(&F2_REGEX, &label)?); - contexts.insert("f3".into(), string_feature_by_regex(&F3_REGEX, &label)?); - contexts.insert("f5".into(), string_feature_by_regex(&F5_REGEX, &label)?); - contexts.insert("h1".into(), string_feature_by_regex(&H1_REGEX, &label)?); - contexts.insert("i3".into(), string_feature_by_regex(&I3_REGEX, &label)?); - contexts.insert("j1".into(), string_feature_by_regex(&J1_REGEX, &label)?); - - Ok(Self::new(contexts, label)) - } - - pub fn phoneme(&self) -> &str { - self.contexts.get("p3").unwrap().as_str() - } - - pub fn is_pause(&self) -> bool { - self.contexts.get("f1").unwrap().as_str() == "xx" - } -} - -#[derive(new, Getters, Clone, PartialEq, Eq, Debug)] -pub struct Mora { - consonant: Option, - vowel: Phoneme, +pub(crate) fn extract_full_context_label( + open_jtalk: &impl FullcontextExtractor, + text: impl AsRef, +) -> Result> { + let labels = open_jtalk + .extract_fullcontext(text.as_ref()) + .map_err(|source| FullContextLabelError { + context: ErrorKind::OpenJtalk, + source: Some(source), + })?; + + let parsed_labels = labels + .into_iter() + .map(|s| Label::from_str(&s)) + .collect::, _>>() + .map_err(|source| FullContextLabelError { + context: ErrorKind::Jlabel, + source: Some(source.into()), + })?; + + generate_accent_phrases(&parsed_labels).map_err(|context| FullContextLabelError { + context, + source: None, + }) } -impl Mora { - pub fn set_context(&mut self, key: impl Into, value: impl Into) { - let key = key.into(); - let value = value.into(); - if let Some(ref mut consonant) = self.consonant { - consonant.contexts.insert(key.clone(), value.clone()); +fn generate_accent_phrases( + utterance: &[Label], +) -> std::result::Result, ErrorKind> { + let mut accent_phrases = Vec::with_capacity( + utterance + .first() + .map(|label| label.utterance.accent_phrase_count.into()) + .unwrap_or(0), + ); + + let split = utterance.chunk_by(|a, b| { + a.breath_group_curr == b.breath_group_curr && a.accent_phrase_curr == b.accent_phrase_curr + }); + for labels in split { + let moras = generate_moras(labels)?; + if moras.is_empty() { + continue; } - self.vowel.contexts.insert(key, value); - } - pub fn phonemes(&self) -> Vec { - if self.consonant.is_some() { - vec![ - self.consonant().as_ref().unwrap().clone(), - self.vowel.clone(), - ] + let Some(Label { + accent_phrase_curr: Some(ap_curr), + breath_group_curr: Some(bg_curr), + .. + }) = labels.first() + else { + continue; + }; + + // Breath Groupの中で最後のアクセント句かつ,Utteranceの中で最後のBreath Groupでない場合は次がpauになる + let pause_mora = if ap_curr.accent_phrase_position_backward == 1 + && bg_curr.breath_group_position_backward != 1 + { + Some(MoraModel::new( + "、".into(), + None, + None, + "pau".into(), + 0., + 0., + )) } else { - vec![self.vowel.clone()] - } - } + None + }; - #[allow(dead_code)] - pub fn labels(&self) -> Vec { - self.phonemes().iter().map(|p| p.label().clone()).collect() + // workaround for VOICEVOX/voicevox_engine#55 + let accent = usize::from(ap_curr.accent_position).min(moras.len()); + + accent_phrases.push(AccentPhraseModel::new( + moras, + accent, + pause_mora, + ap_curr.is_interrogative, + )) } + Ok(accent_phrases) } -#[derive(new, Getters, Clone, Debug, PartialEq, Eq)] -pub struct AccentPhrase { - moras: Vec, - accent: usize, - is_interrogative: bool, -} - -impl AccentPhrase { - 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() { - { - let phoneme = phonemes.get_mut(i).unwrap(); - if phoneme.contexts().get("a2").map(|s| s.as_str()) == Some("49") { - break; - } - mora_phonemes.push(phoneme.clone()); +fn generate_moras(accent_phrase: &[Label]) -> std::result::Result, ErrorKind> { + let mut moras = Vec::with_capacity(accent_phrase.len()); + + let split = accent_phrase.chunk_by(|a, b| a.mora == b.mora); + for labels in split { + let labels: SmallVec<[&Label; 3]> = + labels.iter().filter(|label| label.mora.is_some()).collect(); + match labels[..] { + [consonant, vowel] => { + let mora = generate_mora(Some(consonant), vowel); + moras.push(mora); } - - if i + 1 == phonemes.len() - || phonemes.get(i).unwrap().contexts().get("a2").unwrap() - != phonemes.get(i + 1).unwrap().contexts().get("a2").unwrap() - { - if mora_phonemes.len() == 1 { - moras.push(Mora::new(None, mora_phonemes.get(0).unwrap().clone())); - } else if mora_phonemes.len() == 2 { - moras.push(Mora::new( - Some(mora_phonemes.get(0).unwrap().clone()), - mora_phonemes.get(1).unwrap().clone(), - )); - } else { - return Err(ErrorKind::TooLongMora { mora_phonemes }); - } - mora_phonemes.clear(); + [vowel] => { + let mora = generate_mora(None, vowel); + moras.push(mora); + } + // silやpau以外の音素がないモーラは含めない + [] => {} + + // 音素が3つ以上ある場合: + // position_forwardとposition_backwardが飽和している場合は無視する + [Label { + mora: + Some(Mora { + position_forward: 49, + position_backward: 49, + .. + }), + .. + }, ..] => {} + _ => { + return Err(ErrorKind::TooLongMora); } } - - let mora = moras.get(0).unwrap(); - let mut accent: usize = mora - .vowel() - .contexts() - .get("f2") - .ok_or_else(|| ErrorKind::InvalidMora { - mora: mora.clone().into(), - })? - .parse() - .map_err(|_| ErrorKind::InvalidMora { - mora: mora.clone().into(), - })?; - - let is_interrogative = moras - .last() - .unwrap() - .vowel() - .contexts() - .get("f3") - .map(|s| s.as_str()) - == Some("1"); - // workaround for VOICEVOX/voicevox_engine#55 - if accent > moras.len() { - accent = moras.len(); - } - - Ok(Self::new(moras, accent, is_interrogative)) - } - - #[allow(dead_code)] - pub fn set_context(&mut self, key: impl Into, value: impl Into) { - let key = key.into(); - let value = value.into(); - for mora in self.moras.iter_mut() { - mora.set_context(&key, &value); - } - } - - pub fn phonemes(&self) -> Vec { - self.moras.iter().flat_map(|m| m.phonemes()).collect() - } - - #[allow(dead_code)] - pub fn labels(&self) -> Vec { - self.phonemes().iter().map(|p| p.label().clone()).collect() - } - - #[allow(dead_code)] - pub fn merge(&self, accent_phrase: AccentPhrase) -> AccentPhrase { - let mut moras = self.moras().clone(); - let is_interrogative = *accent_phrase.is_interrogative(); - moras.extend(accent_phrase.moras); - AccentPhrase::new(moras, *self.accent(), is_interrogative) } + Ok(moras) } -#[derive(new, Getters, Clone, PartialEq, Eq, Debug)] -pub struct BreathGroup { - accent_phrases: Vec, +fn generate_mora(consonant: Option<&Label>, vowel: &Label) -> MoraModel { + let consonant_phoneme = consonant.and_then(|c| c.phoneme.c.to_owned()); + let vowel_phoneme = vowel.phoneme.c.as_deref().unwrap(); + MoraModel::new( + mora_to_text(consonant_phoneme.as_deref(), vowel_phoneme), + consonant_phoneme, + consonant.and(Some(0.0)), + vowel_phoneme.to_string(), + 0.0, + 0.0, + ) } -impl BreathGroup { - 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() { - accent_phonemes.push(phonemes.get(i).unwrap().clone()); - if i + 1 == phonemes.len() - || phonemes.get(i).unwrap().contexts().get("i3").unwrap() - != phonemes.get(i + 1).unwrap().contexts().get("i3").unwrap() - || phonemes.get(i).unwrap().contexts().get("f5").unwrap() - != phonemes.get(i + 1).unwrap().contexts().get("f5").unwrap() - { - accent_phrases.push(AccentPhrase::from_phonemes(accent_phonemes.clone())?); - accent_phonemes.clear(); - } +pub fn mora_to_text(consonant: Option<&str>, vowel: &str) -> String { + let mora_text = format!( + "{}{}", + consonant.unwrap_or(""), + match vowel { + phoneme @ ("A" | "I" | "U" | "E" | "O") => phoneme.to_lowercase(), + phoneme => phoneme.to_string(), } - - Ok(Self::new(accent_phrases)) - } - - #[allow(dead_code)] - pub fn set_context(&mut self, key: impl Into, value: impl Into) { - let key = key.into(); - let value = value.into(); - for accent_phrase in self.accent_phrases.iter_mut() { - accent_phrase.set_context(&key, &value); - } - } - - pub fn phonemes(&self) -> Vec { - self.accent_phrases() - .iter() - .flat_map(|a| a.phonemes()) - .collect() - } - - #[allow(dead_code)] - pub fn labels(&self) -> Vec { - self.phonemes().iter().map(|p| p.label().clone()).collect() - } -} - -#[derive(new, Getters, Clone, PartialEq, Eq, Debug)] -pub struct Utterance { - breath_groups: Vec, - pauses: Vec, + ); + // もしカタカナに変換できなければ、引数で与えた文字列がそのまま返ってくる + engine::mora2text(&mora_text).to_string() } -impl Utterance { - 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![]; - for phoneme in phonemes.into_iter() { - if !phoneme.is_pause() { - group_phonemes.push(phoneme); - } else { - pauses.push(phoneme); - - if !group_phonemes.is_empty() { - breath_groups.push(BreathGroup::from_phonemes(group_phonemes.clone())?); - group_phonemes.clear(); - } - } - } - Ok(Self::new(breath_groups, pauses)) +#[cfg(test)] +mod tests { + use rstest_reuse::*; + + use ::test_util::OPEN_JTALK_DIC_DIR; + use rstest::rstest; + + use std::str::FromStr; + + use crate::{ + engine::{ + full_context_label::{extract_full_context_label, generate_accent_phrases}, + open_jtalk::FullcontextExtractor, + MoraModel, + }, + AccentPhraseModel, + }; + use jlabel::Label; + + fn mora(text: &str, consonant: Option<&str>, vowel: &str) -> MoraModel { + MoraModel::new( + text.into(), + consonant.map(|c| c.into()), + consonant.and(Some(0.0)), + vowel.into(), + 0.0, + 0.0, + ) } - #[allow(dead_code)] - pub fn set_context(&mut self, key: impl Into, value: impl Into) { - let key = key.into(); - let value = value.into(); - for breath_group in self.breath_groups.iter_mut() { - breath_group.set_context(&key, &value); - } + #[template] + #[rstest] + #[case( + "いぇ", + &[ + "xx^xx-sil+y=e/A:xx+xx+xx/B:xx-xx_xx/C:xx_xx+xx/D:xx+xx_xx/E:xx_xx!xx_xx-xx/F:xx_xx#xx_xx@xx_xx|xx_xx/G:1_1%0_xx_xx/H:xx_xx/I:xx-xx@xx+xx&xx-xx|xx+xx/J:1_1/K:1+1-1", + "xx^sil-y+e=sil/A:0+1+1/B:xx-xx_xx/C:09_xx+xx/D:xx+xx_xx/E:xx_xx!xx_xx-xx/F:1_1#0_xx@1_1|1_1/G:xx_xx%xx_xx_xx/H:xx_xx/I:1-1@1+1&1-1|1+1/J:xx_xx/K:1+1-1", + "sil^y-e+sil=xx/A:0+1+1/B:xx-xx_xx/C:09_xx+xx/D:xx+xx_xx/E:xx_xx!xx_xx-xx/F:1_1#0_xx@1_1|1_1/G:xx_xx%xx_xx_xx/H:xx_xx/I:1-1@1+1&1-1|1+1/J:xx_xx/K:1+1-1", + "y^e-sil+xx=xx/A:xx+xx+xx/B:xx-xx_xx/C:xx_xx+xx/D:xx+xx_xx/E:1_1!0_xx-xx/F:xx_xx#xx_xx@xx_xx|xx_xx/G:xx_xx%xx_xx_xx/H:1_1/I:xx-xx@xx+xx&xx-xx|xx+xx/J:xx_xx/K:1+1-1", + ], + &[ + AccentPhraseModel::new( + vec![mora("イェ", Some("y"), "e")], + 1, + None, + false, + ) + ] + )] + #[case( + "んーっ", + &[ + "xx^xx-sil+N=N/A:xx+xx+xx/B:xx-xx_xx/C:xx_xx+xx/D:09+xx_xx/E:xx_xx!xx_xx-xx/F:xx_xx#xx_xx@xx_xx|xx_xx/G:3_3%0_xx_xx/H:xx_xx/I:xx-xx@xx+xx&xx-xx|xx+xx/J:1_3/K:1+1-3", + "xx^sil-N+N=cl/A:-2+1+3/B:xx-xx_xx/C:09_xx+xx/D:09+xx_xx/E:xx_xx!xx_xx-xx/F:3_3#0_xx@1_1|1_3/G:xx_xx%xx_xx_xx/H:xx_xx/I:1-3@1+1&1-1|1+3/J:xx_xx/K:1+1-3", + "sil^N-N+cl=sil/A:-1+2+2/B:xx-xx_xx/C:09_xx+xx/D:09+xx_xx/E:xx_xx!xx_xx-xx/F:3_3#0_xx@1_1|1_3/G:xx_xx%xx_xx_xx/H:xx_xx/I:1-3@1+1&1-1|1+3/J:xx_xx/K:1+1-3", + "N^N-cl+sil=xx/A:0+3+1/B:09-xx_xx/C:09_xx+xx/D:xx+xx_xx/E:xx_xx!xx_xx-xx/F:3_3#0_xx@1_1|1_3/G:xx_xx%xx_xx_xx/H:xx_xx/I:1-3@1+1&1-1|1+3/J:xx_xx/K:1+1-3", + "N^cl-sil+xx=xx/A:xx+xx+xx/B:09-xx_xx/C:xx_xx+xx/D:xx+xx_xx/E:3_3!0_xx-xx/F:xx_xx#xx_xx@xx_xx|xx_xx/G:xx_xx%xx_xx_xx/H:1_3/I:xx-xx@xx+xx&xx-xx|xx+xx/J:xx_xx/K:1+1-3", + ], + &[ + AccentPhraseModel::new( + vec![ + mora("ン", None, "N"), + mora("ン", None, "N"), + mora("ッ", None, "cl"), + ], + 3, + None, + false, + ), + ] + )] + #[case( + "これはテストです", + &[ + "xx^xx-sil+k=o/A:xx+xx+xx/B:xx-xx_xx/C:xx_xx+xx/D:04+xx_xx/E:xx_xx!xx_xx-xx/F:xx_xx#xx_xx@xx_xx|xx_xx/G:3_3%0_xx_xx/H:xx_xx/I:xx-xx@xx+xx&xx-xx|xx+xx/J:2_8/K:1+2-8", + "xx^sil-k+o=r/A:-2+1+3/B:xx-xx_xx/C:04_xx+xx/D:24+xx_xx/E:xx_xx!xx_xx-xx/F:3_3#0_xx@1_2|1_8/G:5_1%0_xx_1/H:xx_xx/I:2-8@1+1&1-2|1+8/J:xx_xx/K:1+2-8", + "sil^k-o+r=e/A:-2+1+3/B:xx-xx_xx/C:04_xx+xx/D:24+xx_xx/E:xx_xx!xx_xx-xx/F:3_3#0_xx@1_2|1_8/G:5_1%0_xx_1/H:xx_xx/I:2-8@1+1&1-2|1+8/J:xx_xx/K:1+2-8", + "k^o-r+e=w/A:-1+2+2/B:xx-xx_xx/C:04_xx+xx/D:24+xx_xx/E:xx_xx!xx_xx-xx/F:3_3#0_xx@1_2|1_8/G:5_1%0_xx_1/H:xx_xx/I:2-8@1+1&1-2|1+8/J:xx_xx/K:1+2-8", + "o^r-e+w=a/A:-1+2+2/B:xx-xx_xx/C:04_xx+xx/D:24+xx_xx/E:xx_xx!xx_xx-xx/F:3_3#0_xx@1_2|1_8/G:5_1%0_xx_1/H:xx_xx/I:2-8@1+1&1-2|1+8/J:xx_xx/K:1+2-8", + "r^e-w+a=t/A:0+3+1/B:04-xx_xx/C:24_xx+xx/D:03+xx_xx/E:xx_xx!xx_xx-xx/F:3_3#0_xx@1_2|1_8/G:5_1%0_xx_1/H:xx_xx/I:2-8@1+1&1-2|1+8/J:xx_xx/K:1+2-8", + "e^w-a+t=e/A:0+3+1/B:04-xx_xx/C:24_xx+xx/D:03+xx_xx/E:xx_xx!xx_xx-xx/F:3_3#0_xx@1_2|1_8/G:5_1%0_xx_1/H:xx_xx/I:2-8@1+1&1-2|1+8/J:xx_xx/K:1+2-8", + "w^a-t+e=s/A:0+1+5/B:24-xx_xx/C:03_xx+xx/D:10+7_2/E:3_3!0_xx-1/F:5_1#0_xx@2_1|4_5/G:xx_xx%xx_xx_xx/H:xx_xx/I:2-8@1+1&1-2|1+8/J:xx_xx/K:1+2-8", + "a^t-e+s=U/A:0+1+5/B:24-xx_xx/C:03_xx+xx/D:10+7_2/E:3_3!0_xx-1/F:5_1#0_xx@2_1|4_5/G:xx_xx%xx_xx_xx/H:xx_xx/I:2-8@1+1&1-2|1+8/J:xx_xx/K:1+2-8", + "t^e-s+U=t/A:1+2+4/B:24-xx_xx/C:03_xx+xx/D:10+7_2/E:3_3!0_xx-1/F:5_1#0_xx@2_1|4_5/G:xx_xx%xx_xx_xx/H:xx_xx/I:2-8@1+1&1-2|1+8/J:xx_xx/K:1+2-8", + "e^s-U+t=o/A:1+2+4/B:24-xx_xx/C:03_xx+xx/D:10+7_2/E:3_3!0_xx-1/F:5_1#0_xx@2_1|4_5/G:xx_xx%xx_xx_xx/H:xx_xx/I:2-8@1+1&1-2|1+8/J:xx_xx/K:1+2-8", + "s^U-t+o=d/A:2+3+3/B:24-xx_xx/C:03_xx+xx/D:10+7_2/E:3_3!0_xx-1/F:5_1#0_xx@2_1|4_5/G:xx_xx%xx_xx_xx/H:xx_xx/I:2-8@1+1&1-2|1+8/J:xx_xx/K:1+2-8", + "U^t-o+d=e/A:2+3+3/B:24-xx_xx/C:03_xx+xx/D:10+7_2/E:3_3!0_xx-1/F:5_1#0_xx@2_1|4_5/G:xx_xx%xx_xx_xx/H:xx_xx/I:2-8@1+1&1-2|1+8/J:xx_xx/K:1+2-8", + "t^o-d+e=s/A:3+4+2/B:03-xx_xx/C:10_7+2/D:xx+xx_xx/E:3_3!0_xx-1/F:5_1#0_xx@2_1|4_5/G:xx_xx%xx_xx_xx/H:xx_xx/I:2-8@1+1&1-2|1+8/J:xx_xx/K:1+2-8", + "o^d-e+s=U/A:3+4+2/B:03-xx_xx/C:10_7+2/D:xx+xx_xx/E:3_3!0_xx-1/F:5_1#0_xx@2_1|4_5/G:xx_xx%xx_xx_xx/H:xx_xx/I:2-8@1+1&1-2|1+8/J:xx_xx/K:1+2-8", + "d^e-s+U=sil/A:4+5+1/B:03-xx_xx/C:10_7+2/D:xx+xx_xx/E:3_3!0_xx-1/F:5_1#0_xx@2_1|4_5/G:xx_xx%xx_xx_xx/H:xx_xx/I:2-8@1+1&1-2|1+8/J:xx_xx/K:1+2-8", + "e^s-U+sil=xx/A:4+5+1/B:03-xx_xx/C:10_7+2/D:xx+xx_xx/E:3_3!0_xx-1/F:5_1#0_xx@2_1|4_5/G:xx_xx%xx_xx_xx/H:xx_xx/I:2-8@1+1&1-2|1+8/J:xx_xx/K:1+2-8", + "s^U-sil+xx=xx/A:xx+xx+xx/B:10-7_2/C:xx_xx+xx/D:xx+xx_xx/E:5_1!0_xx-xx/F:xx_xx#xx_xx@xx_xx|xx_xx/G:xx_xx%xx_xx_xx/H:2_8/I:xx-xx@xx+xx&xx-xx|xx+xx/J:xx_xx/K:1+2-8", + ], + &[ + AccentPhraseModel::new( + vec![ + mora("コ", Some("k"), "o"), + mora("レ", Some("r"), "e"), + mora("ワ", Some("w"), "a"), + ], + 3, + None, + false, + ), + AccentPhraseModel::new( + vec![ + mora("テ", Some("t"), "e"), + mora("ス", Some("s"), "U"), + mora("ト", Some("t"), "o"), + mora("デ", Some("d"), "e"), + mora("ス", Some("s"), "U"), + ], + 1, + None, + false, + ), + ] + )] + #[case( + "1、1000、100万、1億?", + &[ + "xx^xx-sil+i=ch/A:xx+xx+xx/B:xx-xx_xx/C:xx_xx+xx/D:05+xx_xx/E:xx_xx!xx_xx-xx/F:xx_xx#xx_xx@xx_xx|xx_xx/G:2_2%0_xx_xx/H:xx_xx/I:xx-xx@xx+xx&xx-xx|xx+xx/J:1_2/K:4+4-12", + "xx^sil-i+ch=i/A:-1+1+2/B:xx-xx_xx/C:05_xx+xx/D:05+xx_xx/E:xx_xx!xx_xx-xx/F:2_2#0_xx@1_1|1_2/G:2_1%0_xx_0/H:xx_xx/I:1-2@1+4&1-4|1+12/J:1_2/K:4+4-12", + "sil^i-ch+i=pau/A:0+2+1/B:xx-xx_xx/C:05_xx+xx/D:05+xx_xx/E:xx_xx!xx_xx-xx/F:2_2#0_xx@1_1|1_2/G:2_1%0_xx_0/H:xx_xx/I:1-2@1+4&1-4|1+12/J:1_2/K:4+4-12", + "i^ch-i+pau=s/A:0+2+1/B:xx-xx_xx/C:05_xx+xx/D:05+xx_xx/E:xx_xx!xx_xx-xx/F:2_2#0_xx@1_1|1_2/G:2_1%0_xx_0/H:xx_xx/I:1-2@1+4&1-4|1+12/J:1_2/K:4+4-12", + "ch^i-pau+s=e/A:xx+xx+xx/B:05-xx_xx/C:xx_xx+xx/D:05+xx_xx/E:2_2!0_xx-xx/F:xx_xx#xx_xx@xx_xx|xx_xx/G:2_1%0_xx_xx/H:1_2/I:xx-xx@xx+xx&xx-xx|xx+xx/J:1_2/K:4+4-12", + "i^pau-s+e=N/A:0+1+2/B:05-xx_xx/C:05_xx+xx/D:05+xx_xx/E:2_2!0_xx-0/F:2_1#0_xx@1_1|1_2/G:4_3%0_xx_0/H:1_2/I:1-2@2+3&2-3|3+10/J:1_4/K:4+4-12", + "pau^s-e+N=pau/A:0+1+2/B:05-xx_xx/C:05_xx+xx/D:05+xx_xx/E:2_2!0_xx-0/F:2_1#0_xx@1_1|1_2/G:4_3%0_xx_0/H:1_2/I:1-2@2+3&2-3|3+10/J:1_4/K:4+4-12", + "s^e-N+pau=hy/A:1+2+1/B:05-xx_xx/C:05_xx+xx/D:05+xx_xx/E:2_2!0_xx-0/F:2_1#0_xx@1_1|1_2/G:4_3%0_xx_0/H:1_2/I:1-2@2+3&2-3|3+10/J:1_4/K:4+4-12", + "e^N-pau+hy=a/A:xx+xx+xx/B:05-xx_xx/C:xx_xx+xx/D:05+xx_xx/E:2_1!0_xx-xx/F:xx_xx#xx_xx@xx_xx|xx_xx/G:4_3%0_xx_xx/H:1_2/I:xx-xx@xx+xx&xx-xx|xx+xx/J:1_4/K:4+4-12", + "N^pau-hy+a=k/A:-2+1+4/B:05-xx_xx/C:05_xx+xx/D:05+xx_xx/E:2_1!0_xx-0/F:4_3#0_xx@1_1|1_4/G:4_2%1_xx_0/H:1_2/I:1-4@3+2&3-2|5+8/J:1_4/K:4+4-12", + "pau^hy-a+k=u/A:-2+1+4/B:05-xx_xx/C:05_xx+xx/D:05+xx_xx/E:2_1!0_xx-0/F:4_3#0_xx@1_1|1_4/G:4_2%1_xx_0/H:1_2/I:1-4@3+2&3-2|5+8/J:1_4/K:4+4-12", + "hy^a-k+u=m/A:-1+2+3/B:05-xx_xx/C:05_xx+xx/D:05+xx_xx/E:2_1!0_xx-0/F:4_3#0_xx@1_1|1_4/G:4_2%1_xx_0/H:1_2/I:1-4@3+2&3-2|5+8/J:1_4/K:4+4-12", + "a^k-u+m=a/A:-1+2+3/B:05-xx_xx/C:05_xx+xx/D:05+xx_xx/E:2_1!0_xx-0/F:4_3#0_xx@1_1|1_4/G:4_2%1_xx_0/H:1_2/I:1-4@3+2&3-2|5+8/J:1_4/K:4+4-12", + "k^u-m+a=N/A:0+3+2/B:05-xx_xx/C:05_xx+xx/D:05+xx_xx/E:2_1!0_xx-0/F:4_3#0_xx@1_1|1_4/G:4_2%1_xx_0/H:1_2/I:1-4@3+2&3-2|5+8/J:1_4/K:4+4-12", + "u^m-a+N=pau/A:0+3+2/B:05-xx_xx/C:05_xx+xx/D:05+xx_xx/E:2_1!0_xx-0/F:4_3#0_xx@1_1|1_4/G:4_2%1_xx_0/H:1_2/I:1-4@3+2&3-2|5+8/J:1_4/K:4+4-12", + "m^a-N+pau=i/A:1+4+1/B:05-xx_xx/C:05_xx+xx/D:05+xx_xx/E:2_1!0_xx-0/F:4_3#0_xx@1_1|1_4/G:4_2%1_xx_0/H:1_2/I:1-4@3+2&3-2|5+8/J:1_4/K:4+4-12", + "a^N-pau+i=ch/A:xx+xx+xx/B:05-xx_xx/C:xx_xx+xx/D:05+xx_xx/E:4_3!0_xx-xx/F:xx_xx#xx_xx@xx_xx|xx_xx/G:4_2%1_xx_xx/H:1_4/I:xx-xx@xx+xx&xx-xx|xx+xx/J:1_4/K:4+4-12", + "N^pau-i+ch=i/A:-1+1+4/B:05-xx_xx/C:05_xx+xx/D:05+xx_xx/E:4_3!0_xx-0/F:4_2#1_xx@1_1|1_4/G:xx_xx%xx_xx_xx/H:1_4/I:1-4@4+1&4-1|9+4/J:xx_xx/K:4+4-12", + "pau^i-ch+i=o/A:0+2+3/B:05-xx_xx/C:05_xx+xx/D:05+xx_xx/E:4_3!0_xx-0/F:4_2#1_xx@1_1|1_4/G:xx_xx%xx_xx_xx/H:1_4/I:1-4@4+1&4-1|9+4/J:xx_xx/K:4+4-12", + "i^ch-i+o=k/A:0+2+3/B:05-xx_xx/C:05_xx+xx/D:05+xx_xx/E:4_3!0_xx-0/F:4_2#1_xx@1_1|1_4/G:xx_xx%xx_xx_xx/H:1_4/I:1-4@4+1&4-1|9+4/J:xx_xx/K:4+4-12", + "ch^i-o+k=u/A:1+3+2/B:05-xx_xx/C:05_xx+xx/D:xx+xx_xx/E:4_3!0_xx-0/F:4_2#1_xx@1_1|1_4/G:xx_xx%xx_xx_xx/H:1_4/I:1-4@4+1&4-1|9+4/J:xx_xx/K:4+4-12", + "i^o-k+u=sil/A:2+4+1/B:05-xx_xx/C:05_xx+xx/D:xx+xx_xx/E:4_3!0_xx-0/F:4_2#1_xx@1_1|1_4/G:xx_xx%xx_xx_xx/H:1_4/I:1-4@4+1&4-1|9+4/J:xx_xx/K:4+4-12", + "o^k-u+sil=xx/A:2+4+1/B:05-xx_xx/C:05_xx+xx/D:xx+xx_xx/E:4_3!0_xx-0/F:4_2#1_xx@1_1|1_4/G:xx_xx%xx_xx_xx/H:1_4/I:1-4@4+1&4-1|9+4/J:xx_xx/K:4+4-12", + "k^u-sil+xx=xx/A:xx+xx+xx/B:05-xx_xx/C:xx_xx+xx/D:xx+xx_xx/E:4_2!1_xx-xx/F:xx_xx#xx_xx@xx_xx|xx_xx/G:xx_xx%xx_xx_xx/H:1_4/I:xx-xx@xx+xx&xx-xx|xx+xx/J:xx_xx/K:4+4-12", + ], + &[ + AccentPhraseModel::new( + vec![ + mora("イ", None, "i"), + mora("チ", Some("ch"), "i"), + ], + 2, + Some(mora("、", None, "pau")), + false, + ), + AccentPhraseModel::new( + vec![ + mora("セ", Some("s"), "e"), + mora("ン", None, "N"), + ], + 1, + Some(mora("、", None, "pau")), + false, + ), + AccentPhraseModel::new( + vec![ + mora("ヒャ", Some("hy"), "a"), + mora("ク", Some("k"), "u"), + mora("マ", Some("m"), "a"), + mora("ン", None, "N"), + ], + 3, + Some(mora("、", None, "pau")), + false, + ), + AccentPhraseModel::new( + vec![ + mora("イ", None, "i"), + mora("チ", Some("ch"), "i"), + mora("オ", None, "o"), + mora("ク", Some("k"), "u"), + ], + 2, + None, + true, + ), + ] + )] + #[case( + "クヮルテット。あーあ、。", + &[ + "xx^xx-sil+kw=a/A:xx+xx+xx/B:xx-xx_xx/C:xx_xx+xx/D:02+xx_xx/E:xx_xx!xx_xx-xx/F:xx_xx#xx_xx@xx_xx|xx_xx/G:5_3%0_xx_xx/H:xx_xx/I:xx-xx@xx+xx&xx-xx|xx+xx/J:1_5/K:2+3-8", + "xx^sil-kw+a=r/A:-2+1+5/B:xx-xx_xx/C:02_xx+xx/D:09+xx_xx/E:xx_xx!xx_xx-xx/F:5_3#0_xx@1_1|1_5/G:2_1%0_xx_0/H:xx_xx/I:1-5@1+2&1-3|1+8/J:2_3/K:2+3-8", + "sil^kw-a+r=u/A:-2+1+5/B:xx-xx_xx/C:02_xx+xx/D:09+xx_xx/E:xx_xx!xx_xx-xx/F:5_3#0_xx@1_1|1_5/G:2_1%0_xx_0/H:xx_xx/I:1-5@1+2&1-3|1+8/J:2_3/K:2+3-8", + "kw^a-r+u=t/A:-1+2+4/B:xx-xx_xx/C:02_xx+xx/D:09+xx_xx/E:xx_xx!xx_xx-xx/F:5_3#0_xx@1_1|1_5/G:2_1%0_xx_0/H:xx_xx/I:1-5@1+2&1-3|1+8/J:2_3/K:2+3-8", + "a^r-u+t=e/A:-1+2+4/B:xx-xx_xx/C:02_xx+xx/D:09+xx_xx/E:xx_xx!xx_xx-xx/F:5_3#0_xx@1_1|1_5/G:2_1%0_xx_0/H:xx_xx/I:1-5@1+2&1-3|1+8/J:2_3/K:2+3-8", + "r^u-t+e=cl/A:0+3+3/B:xx-xx_xx/C:02_xx+xx/D:09+xx_xx/E:xx_xx!xx_xx-xx/F:5_3#0_xx@1_1|1_5/G:2_1%0_xx_0/H:xx_xx/I:1-5@1+2&1-3|1+8/J:2_3/K:2+3-8", + "u^t-e+cl=t/A:0+3+3/B:xx-xx_xx/C:02_xx+xx/D:09+xx_xx/E:xx_xx!xx_xx-xx/F:5_3#0_xx@1_1|1_5/G:2_1%0_xx_0/H:xx_xx/I:1-5@1+2&1-3|1+8/J:2_3/K:2+3-8", + "t^e-cl+t=o/A:1+4+2/B:xx-xx_xx/C:02_xx+xx/D:09+xx_xx/E:xx_xx!xx_xx-xx/F:5_3#0_xx@1_1|1_5/G:2_1%0_xx_0/H:xx_xx/I:1-5@1+2&1-3|1+8/J:2_3/K:2+3-8", + "e^cl-t+o=pau/A:2+5+1/B:xx-xx_xx/C:02_xx+xx/D:09+xx_xx/E:xx_xx!xx_xx-xx/F:5_3#0_xx@1_1|1_5/G:2_1%0_xx_0/H:xx_xx/I:1-5@1+2&1-3|1+8/J:2_3/K:2+3-8", + "cl^t-o+pau=a/A:2+5+1/B:xx-xx_xx/C:02_xx+xx/D:09+xx_xx/E:xx_xx!xx_xx-xx/F:5_3#0_xx@1_1|1_5/G:2_1%0_xx_0/H:xx_xx/I:1-5@1+2&1-3|1+8/J:2_3/K:2+3-8", + "t^o-pau+a=a/A:xx+xx+xx/B:02-xx_xx/C:xx_xx+xx/D:09+xx_xx/E:5_3!0_xx-xx/F:xx_xx#xx_xx@xx_xx|xx_xx/G:2_1%0_xx_xx/H:1_5/I:xx-xx@xx+xx&xx-xx|xx+xx/J:2_3/K:2+3-8", + "o^pau-a+a=a/A:0+1+2/B:02-xx_xx/C:09_xx+xx/D:09+xx_xx/E:5_3!0_xx-0/F:2_1#0_xx@1_2|1_3/G:1_1%0_xx_1/H:1_5/I:2-3@2+1&2-2|6+3/J:xx_xx/K:2+3-8", + "pau^a-a+a=sil/A:1+2+1/B:02-xx_xx/C:09_xx+xx/D:09+xx_xx/E:5_3!0_xx-0/F:2_1#0_xx@1_2|1_3/G:1_1%0_xx_1/H:1_5/I:2-3@2+1&2-2|6+3/J:xx_xx/K:2+3-8", + "a^a-a+sil=xx/A:0+1+1/B:09-xx_xx/C:09_xx+xx/D:xx+xx_xx/E:2_1!0_xx-1/F:1_1#0_xx@2_1|3_1/G:xx_xx%xx_xx_xx/H:1_5/I:2-3@2+1&2-2|6+3/J:xx_xx/K:2+3-8", + "a^a-sil+xx=xx/A:xx+xx+xx/B:09-xx_xx/C:xx_xx+xx/D:xx+xx_xx/E:1_1!0_xx-xx/F:xx_xx#xx_xx@xx_xx|xx_xx/G:xx_xx%xx_xx_xx/H:2_3/I:xx-xx@xx+xx&xx-xx|xx+xx/J:xx_xx/K:2+3-8", + ], + &[ + AccentPhraseModel::new( + vec![ + mora("クヮ", Some("kw"), "a"), + mora("ル", Some("r"), "u"), + mora("テ", Some("t"), "e"), + mora("ッ", None, "cl"), + mora("ト", Some("t"), "o"), + ], + 3, + Some(mora("、", None, "pau")), + false, + ), + AccentPhraseModel::new( + vec![ + mora("ア", None, "a"), + mora("ア", None, "a"), + ], + 1, + None, + false, + ), + AccentPhraseModel::new( + vec![mora("ア", None, "a")], + 1, + None, + false, + ), + ] + )] + fn label_cases( + #[case] text: &str, + #[case] labels: &[&str], + #[case] accent_phrase: &[AccentPhraseModel], + ) { } - #[allow(dead_code)] - pub fn phonemes(&self) -> Vec { - // TODO:実装が中途半端なのであとでちゃんと実装する必要があるらしい - // https://github.com/VOICEVOX/voicevox_core/pull/174#discussion_r919982651 - let mut phonemes = Vec::with_capacity(self.breath_groups.len()); - - for i in 0..self.pauses().len() { - phonemes.push(self.pauses().get(i).unwrap().clone()); - if i < self.pauses().len() - 1 { - let p = self.breath_groups().get(i).unwrap().phonemes(); - phonemes.extend(p); - } - } - phonemes + #[apply(label_cases)] + #[tokio::test] + async fn open_jtalk(text: &str, labels: &[&str], _accent_phrase: &[AccentPhraseModel]) { + let open_jtalk = crate::tokio::OpenJtalk::new(OPEN_JTALK_DIC_DIR) + .await + .unwrap(); + assert_eq!(&open_jtalk.extract_fullcontext(text).unwrap(), labels); } - #[allow(dead_code)] - pub fn labels(&self) -> Vec { - self.phonemes().iter().map(|p| p.label().clone()).collect() - } + #[apply(label_cases)] + fn parse_labels(_text: &str, labels: &[&str], accent_phrase: &[AccentPhraseModel]) { + let parsed_labels = labels + .iter() + .map(|s| Label::from_str(s).unwrap()) + .collect::>(); - pub(crate) fn extract_full_context_label( - open_jtalk: &impl FullcontextExtractor, - text: impl AsRef, - ) -> Result { - let labels = open_jtalk - .extract_fullcontext(text.as_ref()) - .map_err(|source| FullContextLabelError { - context: ErrorKind::OpenJtalk, - source: Some(source), - })?; + assert_eq!( + &generate_accent_phrases(&parsed_labels).unwrap(), + accent_phrase + ); + } - labels - .into_iter() - .map(Phoneme::from_label) - .collect::, _>>() - .and_then(Self::from_phonemes) - .map_err(|context| FullContextLabelError { - context, - source: None, - }) + #[apply(label_cases)] + #[tokio::test] + async fn extract_fullcontext( + text: &str, + _labels: &[&str], + accent_phrase: &[AccentPhraseModel], + ) { + let open_jtalk = crate::tokio::OpenJtalk::new(OPEN_JTALK_DIC_DIR) + .await + .unwrap(); + assert_eq!( + &extract_full_context_label(&open_jtalk, text).unwrap(), + accent_phrase + ); } } diff --git a/crates/voicevox_core/src/engine/kana_parser.rs b/crates/voicevox_core/src/engine/kana_parser.rs index fe10c5b3f..eaa3d93c1 100644 --- a/crates/voicevox_core/src/engine/kana_parser.rs +++ b/crates/voicevox_core/src/engine/kana_parser.rs @@ -163,7 +163,7 @@ pub(crate) fn parse_kana(text: &str) -> KanaParseResult> Ok(parsed_result) } -pub fn create_kana(accent_phrases: &[AccentPhraseModel]) -> String { +pub(crate) fn create_kana(accent_phrases: &[AccentPhraseModel]) -> String { let mut text = String::new(); for phrase in accent_phrases { let moras = phrase.moras(); diff --git a/crates/voicevox_core/src/engine/mod.rs b/crates/voicevox_core/src/engine/mod.rs index 1c7422e76..95fe3d562 100644 --- a/crates/voicevox_core/src/engine/mod.rs +++ b/crates/voicevox_core/src/engine/mod.rs @@ -6,7 +6,9 @@ mod mora_list; pub(crate) mod open_jtalk; pub(crate) use self::acoustic_feature_extractor::OjtPhoneme; -pub(crate) use self::full_context_label::{FullContextLabelError, Utterance}; +pub(crate) use self::full_context_label::{ + extract_full_context_label, mora_to_text, FullContextLabelError, +}; pub(crate) use self::kana_parser::{create_kana, parse_kana, KanaParseError}; pub use self::model::{AccentPhraseModel, AudioQueryModel, MoraModel}; pub(crate) use self::mora_list::mora2text; diff --git a/crates/voicevox_core/src/engine/model.rs b/crates/voicevox_core/src/engine/model.rs index 77adbebe7..de0f388f9 100644 --- a/crates/voicevox_core/src/engine/model.rs +++ b/crates/voicevox_core/src/engine/model.rs @@ -5,7 +5,7 @@ use serde::{Deserialize, Serialize}; /* 各フィールドのjsonフィールド名はsnake_caseとする*/ /// モーラ(子音+母音)ごとの情報。 -#[derive(Clone, Debug, new, Getters, Deserialize, Serialize)] +#[derive(Clone, Debug, new, Getters, Deserialize, Serialize, PartialEq)] pub struct MoraModel { /// 文字。 text: String, @@ -22,7 +22,7 @@ pub struct MoraModel { } /// AccentPhrase (アクセント句ごとの情報)。 -#[derive(Clone, Debug, new, Getters, Deserialize, Serialize)] +#[derive(Clone, Debug, new, Getters, Deserialize, Serialize, PartialEq)] pub struct AccentPhraseModel { /// モーラの配列。 moras: Vec, diff --git a/crates/voicevox_core/src/engine/mora_list.rs b/crates/voicevox_core/src/engine/mora_list.rs index 9c2c8d188..6e4b8ba2b 100644 --- a/crates/voicevox_core/src/engine/mora_list.rs +++ b/crates/voicevox_core/src/engine/mora_list.rs @@ -186,7 +186,7 @@ pub(super) const MORA_LIST_MINIMUM: &[[&str; 3]] = &[ ["ア", "", "a"], ]; -pub fn mora2text(mora: &str) -> &str { +pub(crate) fn mora2text(mora: &str) -> &str { for &[text, consonant, vowel] in MORA_LIST_MINIMUM { if mora.len() >= consonant.len() && &mora[..consonant.len()] == consonant diff --git a/crates/voicevox_core/src/engine/open_jtalk.rs b/crates/voicevox_core/src/engine/open_jtalk.rs index ad1f8c19e..88d16f381 100644 --- a/crates/voicevox_core/src/engine/open_jtalk.rs +++ b/crates/voicevox_core/src/engine/open_jtalk.rs @@ -15,11 +15,11 @@ pub trait FullcontextExtractor: Clone + Send + Sync + 'static { pub(crate) mod blocking { use std::{ io::Write as _, - path::Path, sync::{Arc, Mutex}, }; - use anyhow::anyhow; + use anyhow::Context as _; + use camino::{Utf8Path, Utf8PathBuf}; use open_jtalk::{mecab_dict_index, text2mecab, JpCommon, ManagedResource, Mecab, Njd}; use tempfile::NamedTempFile; @@ -32,12 +32,8 @@ pub(crate) mod blocking { pub struct OpenJtalk(pub(super) Arc); impl self::OpenJtalk { - pub fn new(open_jtalk_dict_dir: impl AsRef) -> crate::result::Result { - let dict_dir = open_jtalk_dict_dir - .as_ref() - .to_str() - .unwrap_or_else(|| todo!()) // FIXME: `camino::Utf8Path`を要求するようにする - .to_owned(); + pub fn new(open_jtalk_dict_dir: impl AsRef) -> crate::result::Result { + let dict_dir = open_jtalk_dict_dir.as_ref().to_owned(); // FIXME: この`{}`はGitのdiffを抑えるためだけに存在 { @@ -47,11 +43,12 @@ pub(crate) mod blocking { jpcommon: ManagedResource::initialize(), }; - let result = resources.mecab.load(&*dict_dir); - if !result { - // FIXME: 「システム辞書を読もうとしたけど読めなかった」というエラーをちゃんと用意する - return Err(ErrorRepr::NotLoadedOpenjtalkDict.into()); - } + // FIXME: 「システム辞書を読もうとしたけど読めなかった」というエラーをちゃんと用意する + resources + .mecab + .load(&*dict_dir) + .inspect_err(|e| tracing::error!("{e:?}")) + .map_err(|_| ErrorRepr::NotLoadedOpenjtalkDict)?; Ok(Self(Arc::new(Inner { resources: Mutex::new(resources), @@ -124,13 +121,16 @@ pub(crate) mod blocking { pub(super) struct Inner { resources: std::sync::Mutex, - dict_dir: String, // FIXME: `camino::Utf8PathBuf`にする + dict_dir: Utf8PathBuf, } impl Inner { - // FIXME: 中断可能にする + // TODO: 中断可能にする pub(super) fn use_user_dict(&self, words: &str) -> crate::result::Result<()> { - let result = { + // 空の辞書を読み込もうとするとクラッシュするのでユーザー辞書なしでロード + if words.is_empty() { + self.load_with_userdic(None) + } else { // ユーザー辞書用のcsvを作成 let mut temp_csv = NamedTempFile::new().map_err(|e| ErrorRepr::UseUserDict(e.into()))?; @@ -142,34 +142,37 @@ pub(crate) mod blocking { NamedTempFile::new().map_err(|e| ErrorRepr::UseUserDict(e.into()))?; let temp_dict_path = temp_dict.into_temp_path(); + // FIXME: `.unwrap()`ではなく、エラーとして回収する + let temp_csv_path = Utf8Path::from_path(temp_csv_path.as_ref()).unwrap(); + let temp_dict_path = Utf8Path::from_path(temp_dict_path.as_ref()).unwrap(); + // Mecabでユーザー辞書をコンパイル // TODO: エラー(SEGV)が出るパターンを把握し、それをRust側で防ぐ。 mecab_dict_index(&[ "mecab-dict-index", "-d", - &self.dict_dir, + self.dict_dir.as_ref(), "-u", - temp_dict_path.to_str().unwrap(), + temp_dict_path.as_ref(), "-f", "utf-8", "-t", "utf-8", - temp_csv_path.to_str().unwrap(), + temp_csv_path.as_ref(), "-q", ]); - let Resources { mecab, .. } = &mut *self.resources.lock().unwrap(); - - mecab.load_with_userdic(self.dict_dir.as_ref(), Some(Path::new(&temp_dict_path))) - }; - - if !result { - return Err( - ErrorRepr::UseUserDict(anyhow!("辞書のコンパイルに失敗しました")).into(), - ); + self.load_with_userdic(Some(temp_dict_path)) } - - Ok(()) + } + fn load_with_userdic(&self, dict_path: Option<&Utf8Path>) -> crate::result::Result<()> { + let Resources { mecab, .. } = &mut *self.resources.lock().unwrap(); + + mecab + .load_with_userdic(self.dict_dir.as_ref(), dict_path) + .context("辞書を読み込めませんでした。") + .map_err(ErrorRepr::UseUserDict) + .map_err(Into::into) } } @@ -178,14 +181,10 @@ pub(crate) mod blocking { njd: ManagedResource, jpcommon: ManagedResource, } - - // FIXME: open_jtalk-rs側で宣言する - #[allow(unsafe_code)] - unsafe impl Send for Resources {} } pub(crate) mod tokio { - use std::path::Path; + use camino::Utf8Path; use super::FullcontextExtractor; @@ -194,7 +193,7 @@ pub(crate) mod tokio { pub struct OpenJtalk(super::blocking::OpenJtalk); impl self::OpenJtalk { - pub async fn new(open_jtalk_dict_dir: impl AsRef) -> crate::result::Result { + pub async fn new(open_jtalk_dict_dir: impl AsRef) -> crate::result::Result { let open_jtalk_dict_dir = open_jtalk_dict_dir.as_ref().to_owned(); let blocking = crate::task::asyncify(|| super::blocking::OpenJtalk::new(open_jtalk_dict_dir)) diff --git a/crates/voicevox_core/src/error.rs b/crates/voicevox_core/src/error.rs index 19d464d21..916964429 100644 --- a/crates/voicevox_core/src/error.rs +++ b/crates/voicevox_core/src/error.rs @@ -1,11 +1,12 @@ use crate::{ engine::{FullContextLabelError, KanaParseError}, user_dict::InvalidWordError, - StyleId, VoiceModelId, + StyleId, StyleType, VoiceModelId, }; //use engine:: use duplicate::duplicate_item; -use std::path::PathBuf; +use itertools::Itertools as _; +use std::{collections::BTreeSet, path::PathBuf}; use thiserror::Error; use uuid::Uuid; @@ -38,6 +39,7 @@ impl Error { LoadModelErrorKind::ReadZipEntry { .. } => ErrorKind::ReadZipEntry, LoadModelErrorKind::ModelAlreadyLoaded { .. } => ErrorKind::ModelAlreadyLoaded, LoadModelErrorKind::StyleAlreadyLoaded { .. } => ErrorKind::StyleAlreadyLoaded, + LoadModelErrorKind::InvalidModelFormat { .. } => ErrorKind::InvalidModelFormat, LoadModelErrorKind::InvalidModelData => ErrorKind::InvalidModelData, }, ErrorRepr::GetSupportedDevices(_) => ErrorKind::GetSupportedDevices, @@ -70,10 +72,14 @@ pub(crate) enum ErrorRepr { GetSupportedDevices(#[source] anyhow::Error), #[error( - "`{style_id}`に対するスタイルが見つかりませんでした。音声モデルが読み込まれていないか、読\ - み込みが解除されています" + "`{style_id}` ([{style_types}])に対するスタイルが見つかりませんでした。音声モデルが\ + 読み込まれていないか、読み込みが解除されています", + style_types = style_types.iter().format(", ") )] - StyleNotFound { style_id: StyleId }, + StyleNotFound { + style_id: StyleId, + style_types: &'static BTreeSet, + }, #[error( "`{model_id}`に対する音声モデルが見つかりませんでした。読み込まれていないか、読み込みが既\ @@ -117,6 +123,8 @@ pub enum ErrorKind { OpenZipFile, /// ZIP内のファイルが読めなかった。 ReadZipEntry, + /// モデルの形式が不正。 + InvalidModelFormat, /// すでに読み込まれている音声モデルを読み込もうとした。 ModelAlreadyLoaded, /// すでに読み込まれているスタイルを読み込もうとした。 @@ -165,6 +173,8 @@ pub(crate) enum LoadModelErrorKind { OpenZipFile, #[display(fmt = "`{filename}`を読み取れませんでした")] ReadZipEntry { filename: String }, + #[display(fmt = "モデルの形式が不正です")] + InvalidModelFormat, #[display(fmt = "モデル`{id}`は既に読み込まれています")] ModelAlreadyLoaded { id: VoiceModelId }, #[display(fmt = "スタイル`{id}`は既に読み込まれています")] diff --git a/crates/voicevox_core/src/infer.rs b/crates/voicevox_core/src/infer.rs index c816c9899..c2cad1d7d 100644 --- a/crates/voicevox_core/src/infer.rs +++ b/crates/voicevox_core/src/infer.rs @@ -1,9 +1,9 @@ -pub(crate) mod domain; +pub(crate) mod domains; mod model_file; pub(crate) mod runtimes; -pub(crate) mod status; +pub(crate) mod session_set; -use std::{borrow::Cow, fmt::Debug}; +use std::{borrow::Cow, collections::BTreeSet, fmt::Debug}; use derive_new::new; use duplicate::duplicate_item; @@ -11,9 +11,10 @@ use enum_map::{Enum, EnumMap}; use ndarray::{Array, ArrayD, Dimension, ShapeError}; use thiserror::Error; -use crate::SupportedDevices; +use crate::{StyleType, SupportedDevices}; pub(crate) trait InferenceRuntime: 'static { + // TODO: "session"とは何なのかを定め、ドキュメントを書く。`InferenceSessionSet`も同様。 type Session: Sized + Send + 'static; type RunContext<'a>: From<&'a mut Self::Session> + PushInputTensor; @@ -32,9 +33,17 @@ pub(crate) trait InferenceRuntime: 'static { fn run(ctx: Self::RunContext<'_>) -> anyhow::Result>; } -/// ある`VoiceModel`が提供する推論操作の集合を示す。 -pub(crate) trait InferenceDomain { +/// 共に扱われるべき推論操作の集合を示す。 +pub(crate) trait InferenceDomain: Sized { type Operation: InferenceOperation; + + /// 対応する`StyleType`。 + /// + /// 複数の`InferenceDomain`に対応する`StyleType`があってもよい。 + /// + /// また、どの`InferenceDomain`にも属さない`StyleType`があってもよい。そのような`StyleType`は + /// 音声モデルのロード時に単に拒否されるべきである。 + fn style_types() -> &'static BTreeSet; } /// `InferenceDomain`の推論操作を表す列挙型。 @@ -70,16 +79,20 @@ pub(crate) trait InferenceSignature: Sized + Send + 'static { pub(crate) trait InferenceInputSignature: Send + 'static { type Signature: InferenceSignature; const PARAM_INFOS: &'static [ParamInfo]; - fn make_run_context(self, sess: &mut R::Session) -> R::RunContext<'_>; + fn make_run_context( + self, + sess: &mut R::Session, + ) -> anyhow::Result>; } pub(crate) trait InputScalar: Sized { const KIND: InputScalarKind; + // TODO: `Array`ではなく`ArrayView`を取ることができるかもしれない fn push_tensor_to_ctx( tensor: Array, visitor: &mut impl PushInputTensor, - ); + ) -> anyhow::Result<()>; } #[duplicate_item( @@ -93,8 +106,8 @@ impl InputScalar for T { fn push_tensor_to_ctx( tensor: Array, ctx: &mut impl PushInputTensor, - ) { - ctx.push(tensor); + ) -> anyhow::Result<()> { + ctx.push(tensor) } } @@ -108,8 +121,8 @@ pub(crate) enum InputScalarKind { } pub(crate) trait PushInputTensor { - fn push_int64(&mut self, tensor: Array); - fn push_float32(&mut self, tensor: Array); + fn push_int64(&mut self, tensor: Array) -> anyhow::Result<()>; + fn push_float32(&mut self, tensor: Array) -> anyhow::Result<()>; } /// 推論操作の出力シグネチャ。 diff --git a/crates/voicevox_core/src/infer/domains.rs b/crates/voicevox_core/src/infer/domains.rs new file mode 100644 index 000000000..687550399 --- /dev/null +++ b/crates/voicevox_core/src/infer/domains.rs @@ -0,0 +1,22 @@ +mod talk; + +pub(crate) use self::talk::{ + DecodeInput, DecodeOutput, PredictDurationInput, PredictDurationOutput, PredictIntonationInput, + PredictIntonationOutput, TalkDomain, TalkOperation, +}; + +pub(crate) struct InferenceDomainMap { + pub(crate) talk: V::Talk, +} + +pub(crate) trait InferenceDomainMapValues { + type Talk; +} + +impl InferenceDomainMapValues for (T,) { + type Talk = T; +} + +impl InferenceDomainMapValues for [A] { + type Talk = A; +} diff --git a/crates/voicevox_core/src/infer/domain.rs b/crates/voicevox_core/src/infer/domains/talk.rs similarity index 82% rename from crates/voicevox_core/src/infer/domain.rs rename to crates/voicevox_core/src/infer/domains/talk.rs index bb83886dd..e0716fa50 100644 --- a/crates/voicevox_core/src/infer/domain.rs +++ b/crates/voicevox_core/src/infer/domains/talk.rs @@ -1,22 +1,32 @@ +use std::collections::BTreeSet; + use enum_map::Enum; use macros::{InferenceInputSignature, InferenceOperation, InferenceOutputSignature}; use ndarray::{Array0, Array1, Array2}; +use once_cell::sync::Lazy; + +use crate::StyleType; -use super::{ +use super::super::{ InferenceDomain, InferenceInputSignature as _, InferenceOutputSignature as _, OutputTensor, }; -pub(crate) enum InferenceDomainImpl {} +pub(crate) enum TalkDomain {} + +impl InferenceDomain for TalkDomain { + type Operation = TalkOperation; -impl InferenceDomain for InferenceDomainImpl { - type Operation = InferenceOperationImpl; + fn style_types() -> &'static BTreeSet { + static STYLE_TYPES: Lazy> = Lazy::new(|| [StyleType::Talk].into()); + &STYLE_TYPES + } } #[derive(Clone, Copy, Enum, InferenceOperation)] #[inference_operation( - type Domain = InferenceDomainImpl; + type Domain = TalkDomain; )] -pub(crate) enum InferenceOperationImpl { +pub(crate) enum TalkOperation { #[inference_operation( type Input = PredictDurationInput; type Output = PredictDurationOutput; diff --git a/crates/voicevox_core/src/infer/runtimes/onnxruntime.rs b/crates/voicevox_core/src/infer/runtimes/onnxruntime.rs index a22503055..f8f376837 100644 --- a/crates/voicevox_core/src/infer/runtimes/onnxruntime.rs +++ b/crates/voicevox_core/src/infer/runtimes/onnxruntime.rs @@ -1,18 +1,15 @@ use std::{fmt::Debug, vec}; -use anyhow::anyhow; +use anyhow::{anyhow, bail, ensure}; use duplicate::duplicate_item; use ndarray::{Array, Dimension}; -use once_cell::sync::Lazy; -use onnxruntime::{ - environment::Environment, GraphOptimizationLevel, LoggingLevel, TensorElementDataType, - TypeToTensorElementDataType, +use ort::{ + CPUExecutionProvider, CUDAExecutionProvider, DirectMLExecutionProvider, ExecutionProvider as _, + GraphOptimizationLevel, IntoTensorElementType, TensorElementType, ValueType, }; use crate::{devices::SupportedDevices, error::ErrorRepr}; -use self::assert_send::AssertSend; - use super::super::{ DecryptModelError, InferenceRuntime, InferenceSessionOptions, InputScalarKind, OutputScalarKind, OutputTensor, ParamInfo, PushInputTensor, @@ -22,29 +19,28 @@ use super::super::{ pub(crate) enum Onnxruntime {} impl InferenceRuntime for Onnxruntime { - type Session = AssertSend>; + type Session = ort::Session; type RunContext<'a> = OnnxruntimeRunContext<'a>; fn supported_devices() -> crate::Result { - let mut cuda_support = false; - let mut dml_support = false; - for provider in onnxruntime::session::get_available_providers() - .map_err(Into::into) - .map_err(ErrorRepr::GetSupportedDevices)? - .iter() - { - match provider.as_str() { - "CUDAExecutionProvider" => cuda_support = true, - "DmlExecutionProvider" => dml_support = true, - _ => {} - } - } + // TODO: `InferenceRuntime::init`と`InitInferenceRuntimeError`を作る + build_ort_env_once().unwrap(); + + (|| { + let cpu = CPUExecutionProvider::default().is_available()?; + let cuda = CUDAExecutionProvider::default().is_available()?; + let dml = DirectMLExecutionProvider::default().is_available()?; - Ok(SupportedDevices { - cpu: true, - cuda: cuda_support, - dml: dml_support, - }) + ensure!(cpu, "missing `CPUExecutionProvider`"); + + Ok(SupportedDevices { + cpu: true, + cuda, + dml, + }) + })() + .map_err(ErrorRepr::GetSupportedDevices) + .map_err(Into::into) } fn new_session( @@ -55,48 +51,52 @@ impl InferenceRuntime for Onnxruntime { Vec>, Vec>, )> { - let mut builder = ENVIRONMENT - .new_session_builder()? - .with_optimization_level(GraphOptimizationLevel::Basic)? - .with_intra_op_num_threads(options.cpu_num_threads.into())? - .with_inter_op_num_threads(options.cpu_num_threads.into())?; - - if options.use_gpu { - #[cfg(feature = "directml")] - { - use onnxruntime::ExecutionMode; - - builder = builder - .with_disable_mem_pattern()? - .with_execution_mode(ExecutionMode::ORT_SEQUENTIAL)? - .with_append_execution_provider_directml(0)?; - } - - #[cfg(not(feature = "directml"))] - { - builder = builder.with_append_execution_provider_cuda(Default::default())?; - } + // TODO: `InferenceRuntime::init`と`InitInferenceRuntimeError`を作る + build_ort_env_once().unwrap(); + + let mut builder = ort::Session::builder()? + .with_optimization_level(GraphOptimizationLevel::Level1)? + .with_intra_threads(options.cpu_num_threads.into())?; + + if options.use_gpu && cfg!(feature = "directml") { + builder = builder + .with_parallel_execution(false)? + .with_memory_pattern(false)?; + DirectMLExecutionProvider::default().register(&builder)?; + } else if options.use_gpu && cfg!(feature = "cuda") { + CUDAExecutionProvider::default().register(&builder)?; } let model = model()?; - let sess = AssertSend::from(builder.with_model_from_memory(model)?); + let sess = builder.commit_from_memory(&{ model })?; let input_param_infos = sess .inputs .iter() .map(|info| { - let dt = match info.input_type { - TensorElementDataType::Float => Ok(InputScalarKind::Float32), - TensorElementDataType::Uint8 => Err("ONNX_TENSOR_ELEMENT_DATA_TYPE_UINT8"), - TensorElementDataType::Int8 => Err("ONNX_TENSOR_ELEMENT_DATA_TYPE_INT8"), - TensorElementDataType::Uint16 => Err("ONNX_TENSOR_ELEMENT_DATA_TYPE_UINT16"), - TensorElementDataType::Int16 => Err("ONNX_TENSOR_ELEMENT_DATA_TYPE_INT16"), - TensorElementDataType::Int32 => Err("ONNX_TENSOR_ELEMENT_DATA_TYPE_INT32"), - TensorElementDataType::Int64 => Ok(InputScalarKind::Int64), - TensorElementDataType::String => Err("ONNX_TENSOR_ELEMENT_DATA_TYPE_STRING"), - TensorElementDataType::Double => Err("ONNX_TENSOR_ELEMENT_DATA_TYPE_DOUBLE"), - TensorElementDataType::Uint32 => Err("ONNX_TENSOR_ELEMENT_DATA_TYPE_UINT32"), - TensorElementDataType::Uint64 => Err("ONNX_TENSOR_ELEMENT_DATA_TYPE_UINT64"), + let ValueType::Tensor { ty, .. } = info.input_type else { + bail!( + "unexpected input value type for `{}`. currently `ONNX_TYPE_TENSOR` and \ + `ONNX_TYPE_SPARSETENSOR` is supported", + info.name, + ); + }; + + let dt = match ty { + TensorElementType::Float32 => Ok(InputScalarKind::Float32), + TensorElementType::Uint8 => Err("ONNX_TENSOR_ELEMENT_DATA_TYPE_UINT8"), + TensorElementType::Int8 => Err("ONNX_TENSOR_ELEMENT_DATA_TYPE_INT8"), + TensorElementType::Uint16 => Err("ONNX_TENSOR_ELEMENT_DATA_TYPE_UINT16"), + TensorElementType::Int16 => Err("ONNX_TENSOR_ELEMENT_DATA_TYPE_INT16"), + TensorElementType::Int32 => Err("ONNX_TENSOR_ELEMENT_DATA_TYPE_INT32"), + TensorElementType::Int64 => Ok(InputScalarKind::Int64), + TensorElementType::String => Err("ONNX_TENSOR_ELEMENT_DATA_TYPE_STRING"), + TensorElementType::Bfloat16 => Err("ONNX_TENSOR_ELEMENT_DATA_TYPE_BFLOAT16"), + TensorElementType::Float16 => Err("ONNX_TENSOR_ELEMENT_DATA_TYPE_FLOAT16"), + TensorElementType::Float64 => Err("ONNX_TENSOR_ELEMENT_DATA_TYPE_DOUBLE"), + TensorElementType::Uint32 => Err("ONNX_TENSOR_ELEMENT_DATA_TYPE_UINT32"), + TensorElementType::Uint64 => Err("ONNX_TENSOR_ELEMENT_DATA_TYPE_UINT64"), + TensorElementType::Bool => Err("ONNX_TENSOR_ELEMENT_DATA_TYPE_BOOL"), } .map_err(|actual| { anyhow!("unsupported input datatype `{actual}` for `{}`", info.name) @@ -105,7 +105,7 @@ impl InferenceRuntime for Onnxruntime { Ok(ParamInfo { name: info.name.clone().into(), dt, - ndim: Some(info.dimensions.len()), + ndim: info.input_type.tensor_dimensions().map(Vec::len), }) }) .collect::>()?; @@ -114,18 +114,29 @@ impl InferenceRuntime for Onnxruntime { .outputs .iter() .map(|info| { - let dt = match info.output_type { - TensorElementDataType::Float => Ok(OutputScalarKind::Float32), - TensorElementDataType::Uint8 => Err("ONNX_TENSOR_ELEMENT_DATA_TYPE_UINT8"), - TensorElementDataType::Int8 => Err("ONNX_TENSOR_ELEMENT_DATA_TYPE_INT8"), - TensorElementDataType::Uint16 => Err("ONNX_TENSOR_ELEMENT_DATA_TYPE_UINT16"), - TensorElementDataType::Int16 => Err("ONNX_TENSOR_ELEMENT_DATA_TYPE_INT16"), - TensorElementDataType::Int32 => Err("ONNX_TENSOR_ELEMENT_DATA_TYPE_INT32"), - TensorElementDataType::Int64 => Err("ONNX_TENSOR_ELEMENT_DATA_TYPE_INT64"), - TensorElementDataType::String => Err("ONNX_TENSOR_ELEMENT_DATA_TYPE_STRING"), - TensorElementDataType::Double => Err("ONNX_TENSOR_ELEMENT_DATA_TYPE_DOUBLE"), - TensorElementDataType::Uint32 => Err("ONNX_TENSOR_ELEMENT_DATA_TYPE_UINT32"), - TensorElementDataType::Uint64 => Err("ONNX_TENSOR_ELEMENT_DATA_TYPE_UINT64"), + let ValueType::Tensor { ty, .. } = info.output_type else { + bail!( + "unexpected output value type for `{}`. currently `ONNX_TYPE_TENSOR` and \ + `ONNX_TYPE_SPARSETENSOR` is supported", + info.name, + ); + }; + + let dt = match ty { + TensorElementType::Float32 => Ok(OutputScalarKind::Float32), + TensorElementType::Uint8 => Err("ONNX_TENSOR_ELEMENT_DATA_TYPE_UINT8"), + TensorElementType::Int8 => Err("ONNX_TENSOR_ELEMENT_DATA_TYPE_INT8"), + TensorElementType::Uint16 => Err("ONNX_TENSOR_ELEMENT_DATA_TYPE_UINT16"), + TensorElementType::Int16 => Err("ONNX_TENSOR_ELEMENT_DATA_TYPE_INT16"), + TensorElementType::Int32 => Err("ONNX_TENSOR_ELEMENT_DATA_TYPE_INT32"), + TensorElementType::Int64 => Err("ONNX_TENSOR_ELEMENT_DATA_TYPE_INT64"), + TensorElementType::String => Err("ONNX_TENSOR_ELEMENT_DATA_TYPE_STRING"), + TensorElementType::Bfloat16 => Err("ONNX_TENSOR_ELEMENT_DATA_TYPE_BFLOAT16"), + TensorElementType::Float16 => Err("ONNX_TENSOR_ELEMENT_DATA_TYPE_FLOAT16"), + TensorElementType::Float64 => Err("ONNX_TENSOR_ELEMENT_DATA_TYPE_DOUBLE"), + TensorElementType::Uint32 => Err("ONNX_TENSOR_ELEMENT_DATA_TYPE_UINT32"), + TensorElementType::Uint64 => Err("ONNX_TENSOR_ELEMENT_DATA_TYPE_UINT64"), + TensorElementType::Bool => Err("ONNX_TENSOR_ELEMENT_DATA_TYPE_BOOL"), } .map_err(|actual| { anyhow!("unsupported output datatype `{actual}` for `{}`", info.name) @@ -134,73 +145,69 @@ impl InferenceRuntime for Onnxruntime { Ok(ParamInfo { name: info.name.clone().into(), dt, - ndim: Some(info.dimensions.len()), + ndim: info.output_type.tensor_dimensions().map(|d| d.len()), }) }) .collect::>()?; - return Ok((sess, input_param_infos, output_param_infos)); - - static ENVIRONMENT: Lazy = Lazy::new(|| { - Environment::builder() - .with_name(env!("CARGO_PKG_NAME")) - .with_log_level(LOGGING_LEVEL) - .build() - .unwrap() - }); - - const LOGGING_LEVEL: LoggingLevel = if cfg!(debug_assertions) { - LoggingLevel::Verbose - } else { - LoggingLevel::Warning - }; + Ok((sess, input_param_infos, output_param_infos)) } fn run( - OnnxruntimeRunContext { sess, mut inputs }: OnnxruntimeRunContext<'_>, + OnnxruntimeRunContext { sess, inputs }: OnnxruntimeRunContext<'_>, ) -> anyhow::Result> { - // FIXME: 現状では`f32`のみ対応。実行時にsessionからdatatypeが取れるので、別の型の対応も - // おそらく可能ではあるが、それが必要になるよりもortクレートへの引越しが先になると思われる - // のでこのままにする。 - - if !sess - .outputs - .iter() - .all(|info| matches!(info.output_type, TensorElementDataType::Float)) - { - unimplemented!( - "currently only `ONNX_TENSOR_ELEMENT_DATA_TYPE_FLOAT` is supported for output", - ); - } - - let outputs = sess.run::(inputs.iter_mut().map(|t| &mut **t as &mut _).collect())?; - - Ok(outputs - .iter() - .map(|o| OutputTensor::Float32((*o).clone().into_owned())) - .collect()) + let outputs = sess.run(&*inputs)?; + + (0..outputs.len()) + .map(|i| { + let output = &outputs[i]; + + let ValueType::Tensor { ty, .. } = output.dtype()? else { + bail!( + "unexpected output. currently `ONNX_TYPE_TENSOR` and \ + `ONNX_TYPE_SPARSETENSOR` is supported", + ); + }; + + match ty { + TensorElementType::Float32 => { + let output = output.try_extract_tensor::()?; + Ok(OutputTensor::Float32(output.into_owned())) + } + _ => bail!("unexpected output tensor element data type"), + } + }) + .collect() } } +fn build_ort_env_once() -> ort::Result<()> { + static ONCE: once_cell::sync::OnceCell<()> = once_cell::sync::OnceCell::new(); + ONCE.get_or_try_init(|| ort::init().with_name(env!("CARGO_PKG_NAME")).commit())?; + Ok(()) +} + pub(crate) struct OnnxruntimeRunContext<'sess> { - sess: &'sess mut AssertSend>, - inputs: Vec>, + sess: &'sess ort::Session, + inputs: Vec>, } impl OnnxruntimeRunContext<'_> { fn push_input( &mut self, - input: Array, - ) { - self.inputs - .push(Box::new(onnxruntime::session::NdArray::new(input))); + input: Array< + impl IntoTensorElementType + Debug + Clone + 'static, + impl Dimension + 'static, + >, + ) -> anyhow::Result<()> { + let input = ort::Value::from_array(input)?.into(); + self.inputs.push(input); + Ok(()) } } -impl<'sess> From<&'sess mut AssertSend>> - for OnnxruntimeRunContext<'sess> -{ - fn from(sess: &'sess mut AssertSend>) -> Self { +impl<'sess> From<&'sess mut ort::Session> for OnnxruntimeRunContext<'sess> { + fn from(sess: &'sess mut ort::Session) -> Self { Self { sess, inputs: vec![], @@ -214,41 +221,7 @@ impl PushInputTensor for OnnxruntimeRunContext<'_> { [ push_int64 ] [ i64 ]; [ push_float32 ] [ f32 ]; )] - fn method(&mut self, tensor: Array) { - self.push_input(tensor); - } -} - -// FIXME: 以下のことをちゃんと確認した後、onnxruntime-rs側で`Session`が`Send`であると宣言する。 -// https://github.com/VOICEVOX/voicevox_core/issues/307#issuecomment-1276184614 -mod assert_send { - use std::ops::{Deref, DerefMut}; - - pub(crate) struct AssertSend(T); - - impl From> - for AssertSend> - { - fn from(session: onnxruntime::session::Session<'static>) -> Self { - Self(session) - } + fn method(&mut self, tensor: Array) -> anyhow::Result<()> { + self.push_input(tensor) } - - impl Deref for AssertSend { - type Target = T; - - fn deref(&self) -> &Self::Target { - &self.0 - } - } - - impl DerefMut for AssertSend { - fn deref_mut(&mut self) -> &mut Self::Target { - &mut self.0 - } - } - - // SAFETY: `Session` is probably "send"able. - #[allow(unsafe_code)] - unsafe impl Send for AssertSend {} } diff --git a/crates/voicevox_core/src/infer/session_set.rs b/crates/voicevox_core/src/infer/session_set.rs new file mode 100644 index 000000000..cdd179680 --- /dev/null +++ b/crates/voicevox_core/src/infer/session_set.rs @@ -0,0 +1,101 @@ +use std::{collections::HashMap, fmt::Display, marker::PhantomData, sync::Arc}; + +use anyhow::bail; +use enum_map::{Enum as _, EnumMap}; +use itertools::Itertools as _; + +use crate::error::ErrorRepr; + +use super::{ + model_file, InferenceDomain, InferenceInputSignature, InferenceOperation, InferenceRuntime, + InferenceSessionOptions, InferenceSignature, ParamInfo, +}; + +pub(crate) struct InferenceSessionSet( + EnumMap>>, +); + +impl InferenceSessionSet { + pub(crate) fn new( + model_bytes: &EnumMap>, + options: &EnumMap, + ) -> anyhow::Result { + let mut sessions = model_bytes + .iter() + .map(|(op, model_bytes)| { + let (expected_input_param_infos, expected_output_param_infos) = + ::PARAM_INFOS[op]; + + let (sess, actual_input_param_infos, actual_output_param_infos) = + R::new_session(|| model_file::decrypt(model_bytes), options[op])?; + + check_param_infos(expected_input_param_infos, &actual_input_param_infos)?; + check_param_infos(expected_output_param_infos, &actual_output_param_infos)?; + + Ok((op.into_usize(), std::sync::Mutex::new(sess).into())) + }) + .collect::>>()?; + + return Ok(Self(EnumMap::::from_fn(|k| { + sessions.remove(&k.into_usize()).expect("should exist") + }))); + + fn check_param_infos( + expected: &[ParamInfo], + actual: &[ParamInfo], + ) -> anyhow::Result<()> { + if !(expected.len() == actual.len() + && itertools::zip_eq(expected, actual) + .all(|(expected, actual)| expected.accepts(actual))) + { + let expected = display_param_infos(expected); + let actual = display_param_infos(actual); + bail!("expected {{{expected}}}, got {{{actual}}}") + } + Ok(()) + } + + fn display_param_infos(infos: &[ParamInfo]) -> impl Display { + infos + .iter() + .map(|ParamInfo { name, dt, ndim }| { + let brackets = match *ndim { + Some(ndim) => "[]".repeat(ndim), + None => "[]...".to_owned(), + }; + format!("{name}: {dt}{brackets}") + }) + .join(", ") + } + } +} + +impl InferenceSessionSet { + pub(crate) fn get(&self) -> InferenceSessionCell + where + I: InferenceInputSignature, + I::Signature: InferenceSignature, + { + InferenceSessionCell { + inner: self.0[I::Signature::OPERATION].clone(), + marker: PhantomData, + } + } +} + +pub(crate) struct InferenceSessionCell { + inner: Arc>, + marker: PhantomData, +} + +impl InferenceSessionCell { + pub(crate) fn run( + self, + input: I, + ) -> crate::Result<::Output> { + let inner = &mut self.inner.lock().unwrap(); + (|| R::run(input.make_run_context::(inner)?)?.try_into())() + .map_err(ErrorRepr::InferenceFailed) + .map_err(Into::into) + } +} diff --git a/crates/voicevox_core/src/infer/status.rs b/crates/voicevox_core/src/infer/status.rs deleted file mode 100644 index 12367dda2..000000000 --- a/crates/voicevox_core/src/infer/status.rs +++ /dev/null @@ -1,422 +0,0 @@ -use std::{ - collections::{BTreeMap, HashMap}, - fmt::Display, - marker::PhantomData, - sync::Arc, -}; - -use anyhow::bail; -use educe::Educe; -use enum_map::{Enum as _, EnumMap}; -use itertools::{iproduct, Itertools as _}; - -use crate::{ - error::{ErrorRepr, LoadModelError, LoadModelErrorKind, LoadModelResult}, - infer::{InferenceOperation, ParamInfo}, - manifest::ModelInnerId, - metas::{SpeakerMeta, StyleId, StyleMeta, VoiceModelMeta}, - voice_model::{VoiceModelHeader, VoiceModelId}, - Result, -}; - -use super::{ - model_file, InferenceDomain, InferenceInputSignature, InferenceRuntime, - InferenceSessionOptions, InferenceSignature, -}; - -pub(crate) struct Status { - loaded_models: std::sync::Mutex>, - session_options: EnumMap, -} - -impl Status { - pub fn new(session_options: EnumMap) -> Self { - Self { - loaded_models: Default::default(), - session_options, - } - } - - pub fn insert_model( - &self, - model_header: &VoiceModelHeader, - model_bytes: &EnumMap>, - ) -> Result<()> { - self.loaded_models - .lock() - .unwrap() - .ensure_acceptable(model_header)?; - - let session_set = - SessionSet::new(model_bytes, &self.session_options).map_err(|source| { - LoadModelError { - path: model_header.path.clone(), - context: LoadModelErrorKind::InvalidModelData, - source: Some(source), - } - })?; - - self.loaded_models - .lock() - .unwrap() - .insert(model_header, session_set)?; - Ok(()) - } - - pub fn unload_model(&self, voice_model_id: &VoiceModelId) -> Result<()> { - self.loaded_models.lock().unwrap().remove(voice_model_id) - } - - pub fn metas(&self) -> VoiceModelMeta { - self.loaded_models.lock().unwrap().metas() - } - - pub(crate) fn ids_for(&self, style_id: StyleId) -> Result<(VoiceModelId, ModelInnerId)> { - self.loaded_models.lock().unwrap().ids_for(style_id) - } - - pub fn is_loaded_model(&self, voice_model_id: &VoiceModelId) -> bool { - self.loaded_models - .lock() - .unwrap() - .contains_voice_model(voice_model_id) - } - - pub fn is_loaded_model_by_style_id(&self, style_id: StyleId) -> bool { - self.loaded_models.lock().unwrap().contains_style(style_id) - } - - pub fn validate_speaker_id(&self, style_id: StyleId) -> bool { - self.is_loaded_model_by_style_id(style_id) - } - - /// 推論を実行する。 - /// - /// # Performance - /// - /// CPU/GPU-boundな操作であるため、非同期ランタイム上では直接実行されるべきではない。 - /// - /// # Panics - /// - /// `self`が`model_id`を含んでいないとき、パニックする。 - pub(crate) fn run_session( - &self, - model_id: &VoiceModelId, - input: I, - ) -> Result<::Output> - where - I: InferenceInputSignature, - I::Signature: InferenceSignature, - { - let sess = self.loaded_models.lock().unwrap().get(model_id); - sess.run(input) - } -} - -/// 読み込んだモデルの`Session`とそのメタ情報を保有し、追加/削除/取得の操作を提供する。 -/// -/// この構造体のメソッドは、すべて一瞬で完了すべきである。 -#[derive(Educe)] -#[educe(Default(bound = "R: InferenceRuntime, D: InferenceDomain"))] -struct LoadedModels( - BTreeMap>, -); - -struct LoadedModel { - model_inner_ids: BTreeMap, - metas: VoiceModelMeta, - session_set: SessionSet, -} - -impl LoadedModels { - fn metas(&self) -> VoiceModelMeta { - self.0 - .values() - .flat_map(|LoadedModel { metas, .. }| metas) - .cloned() - .collect() - } - - fn ids_for(&self, style_id: StyleId) -> Result<(VoiceModelId, ModelInnerId)> { - let ( - model_id, - LoadedModel { - model_inner_ids, .. - }, - ) = self - .0 - .iter() - .find(|(_, LoadedModel { metas, .. })| { - metas - .iter() - .flat_map(SpeakerMeta::styles) - .any(|style| *style.id() == style_id) - }) - .ok_or(ErrorRepr::StyleNotFound { style_id })?; - - let model_inner_id = *model_inner_ids - .get(&style_id) - .expect("`model_inner_ids` should contains all of the style IDs in the model"); - - Ok((model_id.clone(), model_inner_id)) - } - - /// # Panics - /// - /// `self`が`model_id`を含んでいないとき、パニックする。 - fn get(&self, model_id: &VoiceModelId) -> SessionCell - where - I: InferenceInputSignature, - I::Signature: InferenceSignature, - { - self.0[model_id].session_set.get() - } - - fn contains_voice_model(&self, model_id: &VoiceModelId) -> bool { - self.0.contains_key(model_id) - } - - fn contains_style(&self, style_id: StyleId) -> bool { - self.styles().any(|style| *style.id() == style_id) - } - - /// 音声モデルを受け入れ可能かをチェックする。 - /// - /// # Errors - /// - /// 音声モデルIDかスタイルIDが`model_header`と重複するとき、エラーを返す。 - fn ensure_acceptable(&self, model_header: &VoiceModelHeader) -> LoadModelResult<()> { - let loaded = self.styles(); - let external = model_header - .metas - .iter() - .flat_map(|speaker| speaker.styles()); - - let error = |context| LoadModelError { - path: model_header.path.clone(), - context, - source: None, - }; - - if self.0.contains_key(&model_header.id) { - return Err(error(LoadModelErrorKind::ModelAlreadyLoaded { - id: model_header.id.clone(), - })); - } - if let Some((style, _)) = - iproduct!(loaded, external).find(|(loaded, external)| loaded.id() == external.id()) - { - return Err(error(LoadModelErrorKind::StyleAlreadyLoaded { - id: *style.id(), - })); - } - Ok(()) - } - - fn insert( - &mut self, - model_header: &VoiceModelHeader, - session_set: SessionSet, - ) -> Result<()> { - self.ensure_acceptable(model_header)?; - - let prev = self.0.insert( - model_header.id.clone(), - LoadedModel { - model_inner_ids: model_header.model_inner_ids(), - metas: model_header.metas.clone(), - session_set, - }, - ); - assert!(prev.is_none()); - Ok(()) - } - - fn remove(&mut self, model_id: &VoiceModelId) -> Result<()> { - if self.0.remove(model_id).is_none() { - return Err(ErrorRepr::ModelNotFound { - model_id: model_id.clone(), - } - .into()); - } - Ok(()) - } - - fn styles(&self) -> impl Iterator { - self.0 - .values() - .flat_map(|LoadedModel { metas, .. }| metas) - .flat_map(|speaker| speaker.styles()) - } -} - -struct SessionSet( - EnumMap>>, -); - -impl SessionSet { - fn new( - model_bytes: &EnumMap>, - options: &EnumMap, - ) -> anyhow::Result { - let mut sessions = model_bytes - .iter() - .map(|(op, model_bytes)| { - let (expected_input_param_infos, expected_output_param_infos) = - ::PARAM_INFOS[op]; - - let (sess, actual_input_param_infos, actual_output_param_infos) = - R::new_session(|| model_file::decrypt(model_bytes), options[op])?; - - check_param_infos(expected_input_param_infos, &actual_input_param_infos)?; - check_param_infos(expected_output_param_infos, &actual_output_param_infos)?; - - Ok((op.into_usize(), std::sync::Mutex::new(sess).into())) - }) - .collect::>>()?; - - return Ok(Self(EnumMap::::from_fn(|k| { - sessions.remove(&k.into_usize()).expect("should exist") - }))); - - fn check_param_infos( - expected: &[ParamInfo], - actual: &[ParamInfo], - ) -> anyhow::Result<()> { - if !(expected.len() == actual.len() - && itertools::zip_eq(expected, actual) - .all(|(expected, actual)| expected.accepts(actual))) - { - let expected = display_param_infos(expected); - let actual = display_param_infos(actual); - bail!("expected {{{expected}}}, got {{{actual}}}") - } - Ok(()) - } - - fn display_param_infos(infos: &[ParamInfo]) -> impl Display { - infos - .iter() - .map(|ParamInfo { name, dt, ndim }| { - let brackets = match *ndim { - Some(ndim) => "[]".repeat(ndim), - None => "[]...".to_owned(), - }; - format!("{name}: {dt}{brackets}") - }) - .join(", ") - } - } -} - -impl SessionSet { - fn get(&self) -> SessionCell - where - I: InferenceInputSignature, - I::Signature: InferenceSignature, - { - SessionCell { - inner: self.0[I::Signature::OPERATION].clone(), - marker: PhantomData, - } - } -} - -struct SessionCell { - inner: Arc>, - marker: PhantomData, -} - -impl SessionCell { - fn run(self, input: I) -> crate::Result<::Output> { - let inner = &mut self.inner.lock().unwrap(); - let ctx = input.make_run_context::(inner); - R::run(ctx) - .and_then(TryInto::try_into) - .map_err(|e| ErrorRepr::InferenceFailed(e).into()) - } -} - -#[cfg(test)] -mod tests { - use enum_map::enum_map; - use pretty_assertions::assert_eq; - use rstest::rstest; - - use crate::{ - infer::domain::{InferenceDomainImpl, InferenceOperationImpl}, - macros::tests::assert_debug_fmt_eq, - synthesizer::InferenceRuntimeImpl, - test_util::open_default_vvm_file, - }; - - use super::{super::InferenceSessionOptions, Status}; - - #[rstest] - #[case(true, 0)] - #[case(true, 1)] - #[case(true, 8)] - #[case(false, 2)] - #[case(false, 4)] - #[case(false, 8)] - #[case(false, 0)] - fn status_new_works(#[case] use_gpu: bool, #[case] cpu_num_threads: u16) { - let light_session_options = InferenceSessionOptions::new(cpu_num_threads, false); - let heavy_session_options = InferenceSessionOptions::new(cpu_num_threads, use_gpu); - let session_options = enum_map! { - InferenceOperationImpl::PredictDuration - | InferenceOperationImpl::PredictIntonation => light_session_options, - InferenceOperationImpl::Decode => heavy_session_options, - }; - let status = Status::::new(session_options); - - assert_eq!( - light_session_options, - status.session_options[InferenceOperationImpl::PredictDuration], - ); - assert_eq!( - light_session_options, - status.session_options[InferenceOperationImpl::PredictIntonation], - ); - assert_eq!( - heavy_session_options, - status.session_options[InferenceOperationImpl::Decode], - ); - - assert!(status.loaded_models.lock().unwrap().0.is_empty()); - } - - #[rstest] - #[tokio::test] - async fn status_load_model_works() { - let status = Status::::new( - enum_map!(_ => InferenceSessionOptions::new(0, false)), - ); - let model = &open_default_vvm_file().await; - let model_bytes = &model.read_inference_models().await.unwrap(); - let result = status.insert_model(model.header(), model_bytes); - assert_debug_fmt_eq!(Ok(()), result); - assert_eq!(1, status.loaded_models.lock().unwrap().0.len()); - } - - #[rstest] - #[tokio::test] - async fn status_is_model_loaded_works() { - let status = Status::::new( - enum_map!(_ => InferenceSessionOptions::new(0, false)), - ); - let vvm = open_default_vvm_file().await; - let model_header = vvm.header(); - let model_bytes = &vvm.read_inference_models().await.unwrap(); - assert!( - !status.is_loaded_model(&model_header.id), - "model should not be loaded" - ); - let result = status.insert_model(model_header, model_bytes); - assert_debug_fmt_eq!(Ok(()), result); - assert!( - status.is_loaded_model(&model_header.id), - "model should be loaded", - ); - } -} diff --git a/crates/voicevox_core/src/lib.rs b/crates/voicevox_core/src/lib.rs index ea74c9f7c..0f34c5962 100644 --- a/crates/voicevox_core/src/lib.rs +++ b/crates/voicevox_core/src/lib.rs @@ -8,10 +8,11 @@ mod infer; mod macros; mod manifest; mod metas; -mod numerics; mod result; +mod status; mod synthesizer; mod task; +mod text_analyzer; mod user_dict; mod version; mod voice_model; @@ -23,12 +24,18 @@ pub mod tokio; #[cfg(test)] mod test_util; +// https://crates.io/crates/rstest_reuse#use-rstest_resuse-at-the-top-of-your-crate +#[allow(clippy::single_component_path_imports)] +#[cfg(test)] +use rstest_reuse; + pub use self::{ devices::SupportedDevices, engine::{AccentPhraseModel, AudioQueryModel, FullcontextExtractor}, error::{Error, ErrorKind}, metas::{ - RawStyleId, RawStyleVersion, SpeakerMeta, StyleId, StyleMeta, StyleVersion, VoiceModelMeta, + RawStyleId, RawStyleVersion, SpeakerMeta, StyleId, StyleMeta, StyleType, StyleVersion, + VoiceModelMeta, }, result::Result, synthesizer::{AccelerationMode, InitializeOptions, SynthesisOptions, TtsOptions}, diff --git a/crates/voicevox_core/src/manifest.rs b/crates/voicevox_core/src/manifest.rs index 650e151d5..3b17ae3f1 100644 --- a/crates/voicevox_core/src/manifest.rs +++ b/crates/voicevox_core/src/manifest.rs @@ -1,8 +1,10 @@ -use std::{collections::BTreeMap, fmt::Display}; +use std::{collections::BTreeMap, fmt::Display, sync::Arc}; use derive_getters::Getters; +use derive_more::Deref; use derive_new::new; use serde::{Deserialize, Serialize}; +use serde_with::{serde_as, DisplayFromStr}; use crate::StyleId; @@ -41,9 +43,27 @@ pub struct Manifest { #[allow(dead_code)] manifest_version: ManifestVersion, metas_filename: String, - decode_filename: String, - predict_duration_filename: String, - predict_intonation_filename: String, + #[serde(flatten)] + domains: ManifestDomains, +} + +#[derive(Deserialize, Clone)] +pub(crate) struct ManifestDomains { + pub(crate) talk: Option, +} + +#[derive(Deserialize, Clone)] +pub(crate) struct TalkManifest { + pub(crate) predict_duration_filename: String, + pub(crate) predict_intonation_filename: String, + pub(crate) decode_filename: String, #[serde(default)] - style_id_to_model_inner_id: BTreeMap, + pub(crate) style_id_to_model_inner_id: StyleIdToModelInnerId, } + +#[serde_as] +#[derive(Default, Clone, Deref, Deserialize)] +#[deref(forward)] +pub(crate) struct StyleIdToModelInnerId( + #[serde_as(as = "Arc>")] Arc>, +); diff --git a/crates/voicevox_core/src/metas.rs b/crates/voicevox_core/src/metas.rs index 77cb3a9fc..b9f274c48 100644 --- a/crates/voicevox_core/src/metas.rs +++ b/crates/voicevox_core/src/metas.rs @@ -1,8 +1,42 @@ -use std::fmt::Display; +use std::fmt::{Debug, Display}; use derive_getters::Getters; use derive_new::new; +use indexmap::IndexMap; +use itertools::Itertools as _; use serde::{Deserialize, Serialize}; +use tracing::warn; + +/// [`speaker_uuid`]をキーとして複数の[`SpeakerMeta`]をマージする。 +/// +/// マージする際話者は[`SpeakerMeta::order`]、スタイルは[`StyleMeta::order`]をもとに安定ソートされる。 +/// `order`が無い話者とスタイルは、そうでないものよりも後ろに置かれる。 +/// +/// [`speaker_uuid`]: SpeakerMeta::speaker_uuid +pub fn merge<'a>(metas: impl IntoIterator) -> Vec { + return metas + .into_iter() + .fold(IndexMap::<_, SpeakerMeta>::new(), |mut acc, speaker| { + acc.entry(&speaker.speaker_uuid) + .and_modify(|acc| acc.styles.extend(speaker.styles.clone())) + .or_insert_with(|| speaker.clone()); + acc + }) + .into_values() + .update(|speaker| { + speaker + .styles + .sort_by_key(|&StyleMeta { order, .. }| key(order)); + }) + .sorted_by_key(|&SpeakerMeta { order, .. }| key(order)) + .collect(); + + fn key(order: Option) -> impl Ord { + order + .map(Into::into) + .unwrap_or_else(|| u64::from(u32::MAX) + 1) + } +} /// [`StyleId`]の実体。 /// @@ -15,7 +49,20 @@ pub type RawStyleId = u32; /// /// [**話者**(_speaker_)]: SpeakerMeta /// [**スタイル**(_style_)]: StyleMeta -#[derive(PartialEq, Eq, Clone, Copy, Ord, PartialOrd, Deserialize, Serialize, new, Debug)] +#[derive( + PartialEq, + Eq, + Clone, + Copy, + Ord, + Hash, + PartialOrd, + derive_more::FromStr, + Deserialize, + Serialize, + new, + Debug, +)] pub struct StyleId(RawStyleId); impl StyleId { @@ -65,6 +112,52 @@ pub struct SpeakerMeta { version: StyleVersion, /// 話者のUUID。 speaker_uuid: String, + /// 話者の順番。 + /// + /// `SpeakerMeta`の列は、この値に対して昇順に並んでいるべきである。 + order: Option, +} + +impl SpeakerMeta { + /// # Panics + /// + /// `speaker_uuid`が異なるときパニックする。 + pub(crate) fn warn_diff_except_styles(&self, other: &Self) { + let Self { + name: name1, + styles: _, + version: version1, + speaker_uuid: speaker_uuid1, + order: order1, + } = self; + + let Self { + name: name2, + styles: _, + version: version2, + speaker_uuid: speaker_uuid2, + order: order2, + } = other; + + if speaker_uuid1 != speaker_uuid2 { + panic!("must be equal: {speaker_uuid1} != {speaker_uuid2:?}"); + } + + warn_diff(speaker_uuid1, "name", name1, name2); + warn_diff(speaker_uuid1, "version", version1, version2); + warn_diff(speaker_uuid1, "order", order1, order2); + + fn warn_diff( + speaker_uuid: &str, + field_name: &str, + left: &T, + right: &T, + ) { + if left != right { + warn!("`{speaker_uuid}`: different `{field_name}` ({left:?} != {right:?})"); + } + } + } } /// **スタイル**(_style_)のメタ情報。 @@ -74,4 +167,156 @@ pub struct StyleMeta { id: StyleId, /// スタイル名。 name: String, + /// スタイルに対応するモデルの種類。 + #[serde(default)] + r#type: StyleType, + /// スタイルの順番。 + /// + /// [`SpeakerMeta::styles`]は、この値に対して昇順に並んでいるべきである。 + order: Option, +} + +/// **スタイル**(_style_)に対応するモデルの種類。 +#[derive( + Default, + Clone, + Copy, + PartialEq, + Eq, + PartialOrd, + Ord, + Hash, + Debug, + strum::Display, + Deserialize, + Serialize, +)] +#[strum(serialize_all = "snake_case")] +#[serde(rename_all = "snake_case")] +pub enum StyleType { + /// 音声合成クエリの作成と音声合成が可能。 + #[default] + Talk, + + /// 歌唱音声合成用のクエリの作成が可能。 + SingingTeacher, + + /// 歌唱音声合成が可能。 + FrameDecode, + + /// 歌唱音声合成用のクエリの作成と歌唱音声合成が可能。 + Sing, +} + +#[cfg(test)] +mod tests { + use once_cell::sync::Lazy; + use serde_json::json; + + #[test] + fn merge_works() -> anyhow::Result<()> { + static INPUT: Lazy = Lazy::new(|| { + json!([ + { + "name": "B", + "styles": [ + { + "id": 3, + "name": "B_1", + "type": "talk", + "order": 0 + } + ], + "version": "0.0.0", + "speaker_uuid": "f34ab151-c0f5-4e0a-9ad2-51ce30dba24d", + "order": 1 + }, + { + "name": "A", + "styles": [ + { + "id": 2, + "name": "A_3", + "type": "talk", + "order": 2 + } + ], + "version": "0.0.0", + "speaker_uuid": "d6fd707c-a451-48e9-8f00-fe9ee3bf6264", + "order": 0 + }, + { + "name": "A", + "styles": [ + { + "id": 1, + "name": "A_1", + "type": "talk", + "order": 0 + }, + { + "id": 0, + "name": "A_2", + "type": "talk", + "order": 1 + } + ], + "version": "0.0.0", + "speaker_uuid": "d6fd707c-a451-48e9-8f00-fe9ee3bf6264", + "order": 0 + } + ]) + }); + + static EXPECTED: Lazy = Lazy::new(|| { + json!([ + { + "name": "A", + "styles": [ + { + "id": 1, + "name": "A_1", + "type": "talk", + "order": 0 + }, + { + "id": 0, + "name": "A_2", + "type": "talk", + "order": 1 + }, + { + "id": 2, + "name": "A_3", + "type": "talk", + "order": 2 + } + ], + "version": "0.0.0", + "speaker_uuid": "d6fd707c-a451-48e9-8f00-fe9ee3bf6264", + "order": 0 + }, + { + "name": "B", + "styles": [ + { + "id": 3, + "name": "B_1", + "type": "talk", + "order": 0 + } + ], + "version": "0.0.0", + "speaker_uuid": "f34ab151-c0f5-4e0a-9ad2-51ce30dba24d", + "order": 1 + } + ]) + }); + + let input = &serde_json::from_value::>(INPUT.clone())?; + let actual = serde_json::to_value(super::merge(input))?; + + pretty_assertions::assert_eq!(*EXPECTED, actual); + Ok(()) + } } diff --git a/crates/voicevox_core/src/numerics.rs b/crates/voicevox_core/src/numerics.rs deleted file mode 100644 index b01c4d6a5..000000000 --- a/crates/voicevox_core/src/numerics.rs +++ /dev/null @@ -1,17 +0,0 @@ -use easy_ext::ext; - -#[ext(F32Ext)] -pub(crate) impl f32 { - /// 偶数丸めを行う。 - /// - /// [`round_ties_even` feature]で追加される予定の`f32::round_ties_even`の代用。 - /// - /// [`round_ties_even` feature]: https://github.com/rust-lang/rust/pull/95317 - fn round_ties_even_(self) -> f32 { - let mut rounded = self.round(); - if (self - rounded).abs() == 0.5 { - rounded = 2. * (self / 2.).round(); - } - rounded - } -} diff --git a/crates/voicevox_core/src/status.rs b/crates/voicevox_core/src/status.rs new file mode 100644 index 000000000..a47de689b --- /dev/null +++ b/crates/voicevox_core/src/status.rs @@ -0,0 +1,428 @@ +use std::any; + +use duplicate::{duplicate, duplicate_item}; +use educe::Educe; +use enum_map::EnumMap; +use indexmap::IndexMap; +use itertools::iproduct; + +use crate::{ + error::{ErrorRepr, LoadModelError, LoadModelErrorKind, LoadModelResult}, + infer::{ + domains::{InferenceDomainMap, TalkDomain, TalkOperation}, + session_set::{InferenceSessionCell, InferenceSessionSet}, + InferenceDomain, InferenceInputSignature, InferenceRuntime, InferenceSessionOptions, + InferenceSignature, + }, + manifest::{ModelInnerId, StyleIdToModelInnerId}, + metas::{self, SpeakerMeta, StyleId, StyleMeta, VoiceModelMeta}, + voice_model::{ModelBytesWithInnerIdsByDomain, VoiceModelHeader, VoiceModelId}, + Result, +}; + +pub(crate) struct Status { + loaded_models: std::sync::Mutex>, + session_options: InferenceDomainMap, +} + +impl Status { + pub(crate) fn new(session_options: InferenceDomainMap) -> Self { + Self { + loaded_models: Default::default(), + session_options, + } + } + + pub(crate) fn insert_model( + &self, + model_header: &VoiceModelHeader, + model_contents: &InferenceDomainMap, + ) -> Result<()> { + self.loaded_models + .lock() + .unwrap() + .ensure_acceptable(model_header)?; + + let session_sets_with_inner_ids = model_contents + .create_session_sets(&self.session_options) + .map_err(|source| LoadModelError { + path: model_header.path.clone(), + context: LoadModelErrorKind::InvalidModelData, + source: Some(source), + })?; + + self.loaded_models + .lock() + .unwrap() + .insert(model_header, session_sets_with_inner_ids)?; + Ok(()) + } + + pub(crate) fn unload_model(&self, voice_model_id: &VoiceModelId) -> Result<()> { + self.loaded_models.lock().unwrap().remove(voice_model_id) + } + + pub(crate) fn metas(&self) -> VoiceModelMeta { + self.loaded_models.lock().unwrap().metas() + } + + /// あるスタイルに対応する`VoiceModelId`と`ModelInnerId`の組を返す。 + /// + /// `StyleId` → `ModelInnerId`のマッピングが存在しない場合は、`ModelInnerId`としては + /// `style_id`と同じ値を返す。 + pub(crate) fn ids_for( + &self, + style_id: StyleId, + ) -> Result<(VoiceModelId, ModelInnerId)> { + self.loaded_models.lock().unwrap().ids_for::(style_id) + } + + pub(crate) fn is_loaded_model(&self, voice_model_id: &VoiceModelId) -> bool { + self.loaded_models + .lock() + .unwrap() + .contains_voice_model(voice_model_id) + } + + // FIXME: この関数はcompatible_engineとテストでのみ使われるが、テストのために`StyleType`を + // 引数に含めるようにする + pub(crate) fn is_loaded_model_by_style_id(&self, style_id: StyleId) -> bool { + self.loaded_models.lock().unwrap().contains_style(style_id) + } + + /// 推論を実行する。 + /// + /// # Performance + /// + /// CPU/GPU-boundな操作であるため、非同期ランタイム上では直接実行されるべきではない。 + /// + /// # Panics + /// + /// `self`が`model_id`を含んでいないとき、パニックする。 + pub(crate) fn run_session( + &self, + model_id: &VoiceModelId, + input: I, + ) -> Result<::Output> + where + I: InferenceInputSignature, + ::Domain: InferenceDomainExt, + { + let sess = self.loaded_models.lock().unwrap().get(model_id); + sess.run(input) + } +} + +/// 読み込んだモデルの`Session`とそのメタ情報を保有し、追加/削除/取得の操作を提供する。 +/// +/// この構造体のメソッドは、すべて一瞬で完了すべきである。 +#[derive(Educe)] +#[educe(Default(bound = "R: InferenceRuntime"))] +struct LoadedModels(IndexMap>); + +struct LoadedModel { + metas: VoiceModelMeta, + session_sets_with_inner_ids: InferenceDomainMap>, +} + +impl LoadedModels { + fn metas(&self) -> VoiceModelMeta { + metas::merge(self.0.values().flat_map(|LoadedModel { metas, .. }| metas)) + } + + fn ids_for( + &self, + style_id: StyleId, + ) -> Result<(VoiceModelId, ModelInnerId)> { + let ( + model_id, + LoadedModel { + session_sets_with_inner_ids, + .. + }, + ) = self + .0 + .iter() + .find(|(_, LoadedModel { metas, .. })| { + metas.iter().flat_map(SpeakerMeta::styles).any(|style| { + *style.id() == style_id && D::style_types().contains(style.r#type()) + }) + }) + .ok_or(ErrorRepr::StyleNotFound { + style_id, + style_types: D::style_types(), + })?; + + let model_inner_id = session_sets_with_inner_ids + .get::() + .as_ref() + .and_then(|(model_inner_ids, _)| model_inner_ids.get(&style_id).copied()) + .unwrap_or_else(|| ModelInnerId::new(style_id.raw_id())); + + Ok((model_id.clone(), model_inner_id)) + } + + /// # Panics + /// + /// 次の場合にパニックする。 + /// + /// - `self`が`model_id`を含んでいないとき + /// - 対応する`InferenceDomain`が欠けているとき + fn get(&self, model_id: &VoiceModelId) -> InferenceSessionCell + where + I: InferenceInputSignature, + ::Domain: InferenceDomainExt, + { + let (_, session_set) = self.0[model_id] + .session_sets_with_inner_ids + .get::<::Domain>() + .as_ref() + .unwrap_or_else(|| { + let type_name = any::type_name::<::Domain>() + .split("::") + .last() + .unwrap(); + panic!( + "missing session set for `{type_name}` (should be checked in \ + `VoiceModelHeader::new` and `ids_for`)", + ); + }); + session_set.get() + } + + fn contains_voice_model(&self, model_id: &VoiceModelId) -> bool { + self.0.contains_key(model_id) + } + + fn contains_style(&self, style_id: StyleId) -> bool { + self.styles().any(|style| *style.id() == style_id) + } + + /// 音声モデルを受け入れ可能かをチェックする。 + /// + /// # Errors + /// + /// 次の場合にエラーを返す。 + /// + /// - 現在持っている音声モデルIDかスタイルIDが`model_header`と重複するとき + /// - 必要であるはずの`InferenceDomain`のモデルデータが欠けているとき + // FIXME: コメントとテストを書く + // - https://github.com/VOICEVOX/voicevox_core/pull/761#discussion_r1589978521 + // - https://github.com/VOICEVOX/voicevox_core/pull/761#discussion_r1589976759 + fn ensure_acceptable(&self, model_header: &VoiceModelHeader) -> LoadModelResult<()> { + let error = |context| LoadModelError { + path: model_header.path.clone(), + context, + source: None, + }; + + if self.0.contains_key(&model_header.id) { + return Err(error(LoadModelErrorKind::ModelAlreadyLoaded { + id: model_header.id.clone(), + })); + } + + // FIXME: https://github.com/VOICEVOX/voicevox_core/pull/761#discussion_r1590200343 + + let loaded = self.speakers(); + let external = model_header.metas.iter(); + for (loaded, external) in iproduct!(loaded, external) { + if loaded.speaker_uuid() == external.speaker_uuid() { + loaded.warn_diff_except_styles(external); + } + } + + let loaded = self.styles(); + let external = model_header + .metas + .iter() + .flat_map(|speaker| speaker.styles()); + if let Some((style, _)) = + iproduct!(loaded, external).find(|(loaded, external)| loaded.id() == external.id()) + { + return Err(error(LoadModelErrorKind::StyleAlreadyLoaded { + id: *style.id(), + })); + } + Ok(()) + } + + fn insert( + &mut self, + model_header: &VoiceModelHeader, + session_sets_with_inner_ids: InferenceDomainMap>, + ) -> Result<()> { + self.ensure_acceptable(model_header)?; + + let prev = self.0.insert( + model_header.id.clone(), + LoadedModel { + metas: model_header.metas.clone(), + session_sets_with_inner_ids, + }, + ); + assert!(prev.is_none()); + Ok(()) + } + + fn remove(&mut self, model_id: &VoiceModelId) -> Result<()> { + if self.0.remove(model_id).is_none() { + return Err(ErrorRepr::ModelNotFound { + model_id: model_id.clone(), + } + .into()); + } + Ok(()) + } + + fn speakers(&self) -> impl Iterator + Clone { + self.0.values().flat_map(|LoadedModel { metas, .. }| metas) + } + + fn styles(&self) -> impl Iterator { + self.speakers().flat_map(|speaker| speaker.styles()) + } +} + +pub(crate) trait InferenceDomainExt: InferenceDomain { + fn visit( + map: &InferenceDomainMap>, + ) -> Option<&(StyleIdToModelInnerId, InferenceSessionSet)>; +} + +#[duplicate_item( + T field; + [ TalkDomain ] [ talk ]; +)] +impl InferenceDomainExt for T { + fn visit( + map: &InferenceDomainMap>, + ) -> Option<&(StyleIdToModelInnerId, InferenceSessionSet)> { + map.field.as_ref() + } +} + +impl InferenceDomainMap> { + fn get( + &self, + ) -> Option<&(StyleIdToModelInnerId, InferenceSessionSet)> { + D::visit(self) + } +} + +impl InferenceDomainMap { + fn create_session_sets( + &self, + session_options: &InferenceDomainMap, + ) -> anyhow::Result>> { + duplicate! { + [ + field; + [ talk ]; + ] + let field = self + .field + .as_ref() + .map(|(model_inner_ids, model_bytes)| { + let session_set = InferenceSessionSet::new(model_bytes, &session_options.field)?; + Ok::<_, anyhow::Error>((model_inner_ids.clone(), session_set)) + }) + .transpose()?; + } + + Ok(InferenceDomainMap { talk }) + } +} + +type SessionOptionsByDomain = (EnumMap,); + +type SessionSetsWithInnerIdsByDomain = + (Option<(StyleIdToModelInnerId, InferenceSessionSet)>,); + +#[cfg(test)] +mod tests { + use enum_map::enum_map; + use pretty_assertions::assert_eq; + use rstest::rstest; + + use crate::{ + infer::{ + domains::{InferenceDomainMap, TalkOperation}, + InferenceSessionOptions, + }, + macros::tests::assert_debug_fmt_eq, + synthesizer::InferenceRuntimeImpl, + }; + + use super::Status; + + #[rstest] + #[case(true, 0)] + #[case(true, 1)] + #[case(true, 8)] + #[case(false, 2)] + #[case(false, 4)] + #[case(false, 8)] + #[case(false, 0)] + fn status_new_works(#[case] use_gpu: bool, #[case] cpu_num_threads: u16) { + let light_session_options = InferenceSessionOptions::new(cpu_num_threads, false); + let heavy_session_options = InferenceSessionOptions::new(cpu_num_threads, use_gpu); + let session_options = InferenceDomainMap { + talk: enum_map! { + TalkOperation::PredictDuration + | TalkOperation::PredictIntonation => light_session_options, + TalkOperation::Decode => heavy_session_options, + }, + }; + let status = Status::::new(session_options); + + assert_eq!( + light_session_options, + status.session_options.talk[TalkOperation::PredictDuration], + ); + assert_eq!( + light_session_options, + status.session_options.talk[TalkOperation::PredictIntonation], + ); + assert_eq!( + heavy_session_options, + status.session_options.talk[TalkOperation::Decode], + ); + + assert!(status.loaded_models.lock().unwrap().0.is_empty()); + } + + #[rstest] + #[tokio::test] + async fn status_load_model_works() { + let status = Status::::new(InferenceDomainMap { + talk: enum_map!(_ => InferenceSessionOptions::new(0, false)), + }); + let model = &crate::tokio::VoiceModel::sample().await.unwrap(); + let model_contents = &model.read_inference_models().await.unwrap(); + let result = status.insert_model(model.header(), model_contents); + assert_debug_fmt_eq!(Ok(()), result); + assert_eq!(1, status.loaded_models.lock().unwrap().0.len()); + } + + #[rstest] + #[tokio::test] + async fn status_is_model_loaded_works() { + let status = Status::::new(InferenceDomainMap { + talk: enum_map!(_ => InferenceSessionOptions::new(0, false)), + }); + let vvm = &crate::tokio::VoiceModel::sample().await.unwrap(); + let model_header = vvm.header(); + let model_contents = &vvm.read_inference_models().await.unwrap(); + assert!( + !status.is_loaded_model(&model_header.id), + "model should not be loaded" + ); + let result = status.insert_model(model_header, model_contents); + assert_debug_fmt_eq!(Ok(()), result); + assert!( + status.is_loaded_model(&model_header.id), + "model should be loaded", + ); + } +} diff --git a/crates/voicevox_core/src/synthesizer.rs b/crates/voicevox_core/src/synthesizer.rs index 202e917c7..5e4894415 100644 --- a/crates/voicevox_core/src/synthesizer.rs +++ b/crates/voicevox_core/src/synthesizer.rs @@ -80,18 +80,18 @@ pub(crate) mod blocking { use enum_map::enum_map; use crate::{ - engine::{self, create_kana, parse_kana, MoraModel, OjtPhoneme, Utterance}, + engine::{create_kana, mora_to_text, MoraModel, OjtPhoneme}, error::ErrorRepr, infer::{ - domain::{ - DecodeInput, DecodeOutput, InferenceDomainImpl, InferenceOperationImpl, - PredictDurationInput, PredictDurationOutput, PredictIntonationInput, - PredictIntonationOutput, + domains::{ + DecodeInput, DecodeOutput, InferenceDomainMap, PredictDurationInput, + PredictDurationOutput, PredictIntonationInput, PredictIntonationOutput, TalkDomain, + TalkOperation, }, - status::Status, InferenceSessionOptions, }, - numerics::F32Ext as _, + status::Status, + text_analyzer::{KanaAnalyzer, OpenJTalkAnalyzer, TextAnalyzer}, AccentPhraseModel, AudioQueryModel, FullcontextExtractor, Result, StyleId, SupportedDevices, SynthesisOptions, VoiceModelId, VoiceModelMeta, }; @@ -102,8 +102,9 @@ pub(crate) mod blocking { /// 音声シンセサイザ。 pub struct Synthesizer { - pub(super) status: Status, - open_jtalk: O, + pub(super) status: Status, + open_jtalk_analyzer: OpenJTalkAnalyzer, + kana_analyzer: KanaAnalyzer, use_gpu: bool, } @@ -168,15 +169,18 @@ pub(crate) mod blocking { let heavy_session_options = InferenceSessionOptions::new(options.cpu_num_threads, use_gpu); - let status = Status::new(enum_map! { - InferenceOperationImpl::PredictDuration - | InferenceOperationImpl::PredictIntonation => light_session_options, - InferenceOperationImpl::Decode => heavy_session_options, + let status = Status::new(InferenceDomainMap { + talk: enum_map! { + TalkOperation::PredictDuration + | TalkOperation::PredictIntonation => light_session_options, + TalkOperation::Decode => heavy_session_options, + }, }); return Ok(Self { status, - open_jtalk, + open_jtalk_analyzer: OpenJTalkAnalyzer::new(open_jtalk), + kana_analyzer: KanaAnalyzer, use_gpu, }); @@ -299,8 +303,8 @@ pub(crate) mod blocking { // VOICEVOX ENGINEと挙動を合わせるため、四捨五入ではなく偶数丸めをする // // https://github.com/VOICEVOX/voicevox_engine/issues/552 - let phoneme_length = ((*phoneme_length * RATE).round_ties_even_() / speed_scale) - .round_ties_even_() as usize; + let phoneme_length = ((*phoneme_length * RATE).round_ties_even() / speed_scale) + .round_ties_even() as usize; let phoneme_id = phoneme_data_list[i].phoneme_id(); for _ in 0..phoneme_length { @@ -373,7 +377,7 @@ pub(crate) mod blocking { let pitch = (*last_mora.pitch() + ADJUST_PITCH).min(MAX_PITCH); MoraModel::new( - mora_to_text(last_mora.vowel()), + mora_to_text(None, last_mora.vowel()), None, None, last_mora.vowel().clone(), @@ -439,6 +443,7 @@ pub(crate) mod blocking { /// # async fn main() -> anyhow::Result<()> { /// # let synthesizer = /// # voicevox_core::__internal::doctest_fixtures::synthesizer_with_sample_voice_model( + /// # test_util::SAMPLE_VOICE_MODEL_FILE_PATH, /// # test_util::OPEN_JTALK_DIC_DIR, /// # ) /// # .await?; @@ -457,7 +462,8 @@ pub(crate) mod blocking { kana: &str, style_id: StyleId, ) -> Result> { - self.replace_mora_data(&parse_kana(kana)?, style_id) + let accent_phrases = self.kana_analyzer.analyze(kana)?; + self.replace_mora_data(&accent_phrases, style_id) } /// AccentPhraseの配列の音高・音素長を、特定の声で生成しなおす。 @@ -677,6 +683,7 @@ pub(crate) mod blocking { /// # async fn main() -> anyhow::Result<()> { /// # let synthesizer = /// # voicevox_core::__internal::doctest_fixtures::synthesizer_with_sample_voice_model( + /// # test_util::SAMPLE_VOICE_MODEL_FILE_PATH, /// # test_util::OPEN_JTALK_DIC_DIR, /// # ) /// # .await?; @@ -725,6 +732,7 @@ pub(crate) mod blocking { /// # async fn main() -> anyhow::Result<()> { /// # let synthesizer = /// # voicevox_core::__internal::doctest_fixtures::synthesizer_with_sample_voice_model( + /// # test_util::SAMPLE_VOICE_MODEL_FILE_PATH, /// # test_util::OPEN_JTALK_DIC_DIR, /// # ) /// # .await?; @@ -743,75 +751,7 @@ pub(crate) mod blocking { text: &str, style_id: StyleId, ) -> Result> { - if text.is_empty() { - return Ok(Vec::new()); - } - - let utterance = Utterance::extract_full_context_label(&self.open_jtalk, text)?; - - let accent_phrases: Vec = utterance - .breath_groups() - .iter() - .enumerate() - .fold(Vec::new(), |mut accum_vec, (i, breath_group)| { - accum_vec.extend(breath_group.accent_phrases().iter().enumerate().map( - |(j, accent_phrase)| { - let moras = accent_phrase - .moras() - .iter() - .map(|mora| { - let mora_text = mora - .phonemes() - .iter() - .map(|phoneme| phoneme.phoneme().to_string()) - .collect::>() - .join(""); - - let (consonant, consonant_length) = - if let Some(consonant) = mora.consonant() { - (Some(consonant.phoneme().to_string()), Some(0.)) - } else { - (None, None) - }; - - MoraModel::new( - mora_to_text(mora_text), - consonant, - consonant_length, - mora.vowel().phoneme().into(), - 0., - 0., - ) - }) - .collect(); - - let pause_mora = if i != utterance.breath_groups().len() - 1 - && j == breath_group.accent_phrases().len() - 1 - { - Some(MoraModel::new( - "、".into(), - None, - None, - "pau".into(), - 0., - 0., - )) - } else { - None - }; - - AccentPhraseModel::new( - moras, - *accent_phrase.accent(), - pause_mora, - *accent_phrase.is_interrogative(), - ) - }, - )); - - accum_vec - }); - + let accent_phrases = self.open_jtalk_analyzer.analyze(text)?; self.replace_mora_data(&accent_phrases, style_id) } @@ -825,6 +765,7 @@ pub(crate) mod blocking { /// # async fn main() -> anyhow::Result<()> { /// # let synthesizer = /// # voicevox_core::__internal::doctest_fixtures::synthesizer_with_sample_voice_model( + /// # test_util::SAMPLE_VOICE_MODEL_FILE_PATH, /// # test_util::OPEN_JTALK_DIC_DIR, /// # ) /// # .await?; @@ -895,12 +836,7 @@ pub(crate) mod blocking { impl PerformInference for self::Synthesizer { fn predict_duration(&self, phoneme_vector: &[i64], style_id: StyleId) -> Result> { - // FIXME: `Status::ids_for`があるため、ここは不要なはず - if !self.status.validate_speaker_id(style_id) { - return Err(ErrorRepr::StyleNotFound { style_id }.into()); - } - - let (model_id, model_inner_id) = self.status.ids_for(style_id)?; + let (model_id, model_inner_id) = self.status.ids_for::(style_id)?; let PredictDurationOutput { phoneme_length: output, @@ -935,12 +871,7 @@ pub(crate) mod blocking { end_accent_phrase_vector: &[i64], style_id: StyleId, ) -> Result> { - // FIXME: `Status::ids_for`があるため、ここは不要なはず - if !self.status.validate_speaker_id(style_id) { - return Err(ErrorRepr::StyleNotFound { style_id }.into()); - } - - let (model_id, model_inner_id) = self.status.ids_for(style_id)?; + let (model_id, model_inner_id) = self.status.ids_for::(style_id)?; let PredictIntonationOutput { f0_list: output } = self.status.run_session( &model_id, @@ -967,12 +898,7 @@ pub(crate) mod blocking { phoneme_vector: &[f32], style_id: StyleId, ) -> Result> { - // FIXME: `Status::ids_for`があるため、ここは不要なはず - if !self.status.validate_speaker_id(style_id) { - return Err(ErrorRepr::StyleNotFound { style_id }.into()); - } - - let (model_id, model_inner_id) = self.status.ids_for(style_id)?; + let (model_id, model_inner_id) = self.status.ids_for::(style_id)?; // 音が途切れてしまうのを避けるworkaround処理が入っている // TODO: 改善したらここのpadding処理を取り除く @@ -1082,7 +1008,6 @@ pub(crate) mod blocking { } fn list_windows_video_cards() -> windows::core::Result> { - #[allow(unsafe_code)] unsafe { let factory = CreateDXGIFactory::()?; (0..) @@ -1175,21 +1100,6 @@ pub(crate) mod blocking { (consonant_phoneme_list, vowel_phoneme_list, vowel_indexes) } - fn mora_to_text(mora: impl AsRef) -> String { - let last_char = mora.as_ref().chars().last().unwrap(); - let mora = if ['A', 'I', 'U', 'E', 'O'].contains(&last_char) { - format!( - "{}{}", - &mora.as_ref()[0..mora.as_ref().len() - 1], - last_char.to_lowercase() - ) - } else { - mora.as_ref().to_string() - }; - // もしカタカナに変換できなければ、引数で与えた文字列がそのまま返ってくる - engine::mora2text(&mora).to_string() - } - impl AudioQueryModel { fn from_accent_phrases(accent_phrases: Vec) -> Self { let kana = create_kana(&accent_phrases); @@ -1385,8 +1295,7 @@ mod tests { use super::{blocking::PerformInference as _, AccelerationMode, InitializeOptions}; use crate::{ - engine::MoraModel, macros::tests::assert_debug_fmt_eq, test_util::open_default_vvm_file, - AccentPhraseModel, Result, StyleId, + engine::MoraModel, macros::tests::assert_debug_fmt_eq, AccentPhraseModel, Result, StyleId, }; use ::test_util::OPEN_JTALK_DIC_DIR; use rstest::rstest; @@ -1405,7 +1314,7 @@ mod tests { .unwrap(); let result = syntesizer - .load_voice_model(&open_default_vvm_file().await) + .load_voice_model(&crate::tokio::VoiceModel::sample().await.unwrap()) .await; assert_debug_fmt_eq!( @@ -1447,7 +1356,7 @@ mod tests { "expected is_model_loaded to return false, but got true", ); syntesizer - .load_voice_model(&open_default_vvm_file().await) + .load_voice_model(&crate::tokio::VoiceModel::sample().await.unwrap()) .await .unwrap(); @@ -1472,7 +1381,7 @@ mod tests { .unwrap(); syntesizer - .load_voice_model(&open_default_vvm_file().await) + .load_voice_model(&crate::tokio::VoiceModel::sample().await.unwrap()) .await .unwrap(); @@ -1502,7 +1411,7 @@ mod tests { ) .unwrap(); syntesizer - .load_voice_model(&open_default_vvm_file().await) + .load_voice_model(&crate::tokio::VoiceModel::sample().await.unwrap()) .await .unwrap(); @@ -1541,7 +1450,7 @@ mod tests { ) .unwrap(); syntesizer - .load_voice_model(&open_default_vvm_file().await) + .load_voice_model(&crate::tokio::VoiceModel::sample().await.unwrap()) .await .unwrap(); diff --git a/crates/voicevox_core/src/test_data/model_sources/load_model_works1/manifest.json b/crates/voicevox_core/src/test_data/model_sources/load_model_works1/manifest.json deleted file mode 100644 index a8a7adebf..000000000 --- a/crates/voicevox_core/src/test_data/model_sources/load_model_works1/manifest.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "manifest_version": "0.0.0", - "metas_filename": "metas.json", - "decode_filename": "decode.onnx", - "predict_duration_filename": "predict_duration.onnx", - "predict_intonation_filename": "predict_intonation.onnx", - "style_id_to_model_inner_id": { - "302": 2, - "303": 3 - } -} diff --git a/crates/voicevox_core/src/test_util.rs b/crates/voicevox_core/src/test_util.rs index 926fe45bb..5b97f21fc 100644 --- a/crates/voicevox_core/src/test_util.rs +++ b/crates/voicevox_core/src/test_util.rs @@ -1,27 +1,9 @@ -use std::path::PathBuf; +use ::test_util::SAMPLE_VOICE_MODEL_FILE_PATH; use crate::Result; -pub async fn open_default_vvm_file() -> crate::tokio::VoiceModel { - crate::tokio::VoiceModel::from_path( - ::test_util::convert_zip_vvm( - PathBuf::from(env!("CARGO_WORKSPACE_DIR")) - .join(file!()) - .parent() - .unwrap() - .join("test_data/model_sources") - .join("load_model_works1"), - ) - .await, - ) - .await - .unwrap() -} - impl crate::tokio::VoiceModel { pub(crate) async fn sample() -> Result { - return Self::from_path(PATH).await; - - static PATH: &str = concat!(env!("CARGO_WORKSPACE_DIR"), "/model/sample.vvm"); + Self::from_path(SAMPLE_VOICE_MODEL_FILE_PATH).await } } diff --git a/crates/voicevox_core/src/text_analyzer.rs b/crates/voicevox_core/src/text_analyzer.rs new file mode 100644 index 000000000..8540f26e0 --- /dev/null +++ b/crates/voicevox_core/src/text_analyzer.rs @@ -0,0 +1,40 @@ +use crate::{ + engine::{extract_full_context_label, parse_kana}, + AccentPhraseModel, FullcontextExtractor, Result, +}; + +pub(crate) trait TextAnalyzer { + fn analyze(&self, text: &str) -> Result>; +} + +/// AquesTalk風記法からAccentPhraseの配列を生成するTextAnalyzer +#[derive(Clone)] +pub(crate) struct KanaAnalyzer; + +impl TextAnalyzer for KanaAnalyzer { + fn analyze(&self, text: &str) -> Result> { + if text.is_empty() { + return Ok(Vec::new()); + } + Ok(parse_kana(text)?) + } +} + +/// OpenJtalkからAccentPhraseの配列を生成するTextAnalyzer +#[derive(Clone)] +pub(crate) struct OpenJTalkAnalyzer(O); + +impl OpenJTalkAnalyzer { + pub(crate) fn new(open_jtalk: O) -> Self { + Self(open_jtalk) + } +} + +impl TextAnalyzer for OpenJTalkAnalyzer { + fn analyze(&self, text: &str) -> Result> { + if text.is_empty() { + return Ok(Vec::new()); + } + Ok(extract_full_context_label(&self.0, text)?) + } +} diff --git a/crates/voicevox_core/src/user_dict/part_of_speech_data.rs b/crates/voicevox_core/src/user_dict/part_of_speech_data.rs index 76e36e389..b7bc95440 100644 --- a/crates/voicevox_core/src/user_dict/part_of_speech_data.rs +++ b/crates/voicevox_core/src/user_dict/part_of_speech_data.rs @@ -1,37 +1,36 @@ -use derive_getters::Getters; use once_cell::sync::Lazy; use std::collections::HashMap; use crate::UserDictWordType; /// 最小の優先度。 -pub static MIN_PRIORITY: u32 = 0; +pub(super) static MIN_PRIORITY: u32 = 0; /// 最大の優先度。 -pub static MAX_PRIORITY: u32 = 10; +pub(super) static MAX_PRIORITY: u32 = 10; /// 品詞ごとの情報。 -#[derive(Debug, Getters)] -pub struct PartOfSpeechDetail { +#[derive(Debug)] +pub(super) struct PartOfSpeechDetail { /// 品詞。 - pub part_of_speech: &'static str, + pub(super) part_of_speech: &'static str, /// 品詞細分類1。 - pub part_of_speech_detail_1: &'static str, + pub(super) part_of_speech_detail_1: &'static str, /// 品詞細分類2。 - pub part_of_speech_detail_2: &'static str, + pub(super) part_of_speech_detail_2: &'static str, /// 品詞細分類3。 - pub part_of_speech_detail_3: &'static str, + pub(super) part_of_speech_detail_3: &'static str, /// 文脈IDは辞書の左・右文脈IDのこと。 /// /// 参考: - pub context_id: i32, + pub(super) context_id: i32, /// コストのパーセンタイル。 - pub cost_candidates: Vec, + cost_candidates: Vec, /// アクセント結合規則の一覧。 - pub accent_associative_rules: Vec<&'static str>, + _accent_associative_rules: Vec<&'static str>, // unused for now } // 元データ: https://github.com/VOICEVOX/voicevox_engine/blob/master/voicevox_engine/part_of_speech_data.py -pub static PART_OF_SPEECH_DETAIL: Lazy> = +pub(super) static PART_OF_SPEECH_DETAIL: Lazy> = Lazy::new(|| { HashMap::from_iter([ ( @@ -45,7 +44,7 @@ pub static PART_OF_SPEECH_DETAIL: Lazy &'static [i32] { .cost_candidates } -pub fn priority2cost(context_id: i32, priority: u32) -> i32 { +pub(super) fn priority2cost(context_id: i32, priority: u32) -> i32 { let cost_candidates = search_cost_candidates(context_id); cost_candidates[(MAX_PRIORITY - priority) as usize] } diff --git a/crates/voicevox_core/src/user_dict/word.rs b/crates/voicevox_core/src/user_dict/word.rs index f2abc905d..7ed98a949 100644 --- a/crates/voicevox_core/src/user_dict/word.rs +++ b/crates/voicevox_core/src/user_dict/word.rs @@ -219,7 +219,7 @@ pub enum UserDictWordType { } impl UserDictWord { - pub fn to_mecab_format(&self) -> String { + pub(super) fn to_mecab_format(&self) -> String { let pos = PART_OF_SPEECH_DETAIL.get(&self.word_type).unwrap(); format!( "{},{},{},{},{},{},{},{},{},{},{},{},{},{}/{},{}", diff --git a/crates/voicevox_core/src/voice_model.rs b/crates/voicevox_core/src/voice_model.rs index 96bf481d1..364c8db0a 100644 --- a/crates/voicevox_core/src/voice_model.rs +++ b/crates/voicevox_core/src/voice_model.rs @@ -1,21 +1,47 @@ +//! 音声モデル( VVM ファイル)。 +//! +//! VVM ファイルの定義と形式は[ドキュメント](../../../docs/vvm.md)を参照。 + +use anyhow::anyhow; use derive_getters::Getters; use derive_new::new; +use easy_ext::ext; +use enum_map::EnumMap; +use itertools::Itertools as _; use serde::Deserialize; use crate::{ - manifest::{Manifest, ModelInnerId}, - SpeakerMeta, StyleId, StyleMeta, VoiceModelMeta, + error::{LoadModelError, LoadModelErrorKind, LoadModelResult}, + infer::{ + domains::{TalkDomain, TalkOperation}, + InferenceDomain, + }, + manifest::{Manifest, ManifestDomains, StyleIdToModelInnerId}, + SpeakerMeta, StyleMeta, StyleType, VoiceModelMeta, }; -use std::{collections::BTreeMap, path::PathBuf}; +use std::path::{Path, PathBuf}; /// [`VoiceModelId`]の実体。 /// /// [`VoiceModelId`]: VoiceModelId pub type RawVoiceModelId = String; +pub(crate) type ModelBytesWithInnerIdsByDomain = + (Option<(StyleIdToModelInnerId, EnumMap>)>,); + /// 音声モデルID。 #[derive( - PartialEq, Eq, Clone, Ord, PartialOrd, Deserialize, new, Getters, derive_more::Display, Debug, + PartialEq, + Eq, + Clone, + Ord, + Hash, + PartialOrd, + Deserialize, + new, + Getters, + derive_more::Display, + Debug, )] pub struct VoiceModelId { raw_voice_model_id: RawVoiceModelId, @@ -31,29 +57,91 @@ pub(crate) struct VoiceModelHeader { pub(crate) id: VoiceModelId, manifest: Manifest, /// メタ情報。 + /// + /// `manifest`が対応していない`StyleType`のスタイルは含まれるべきではない。 pub(crate) metas: VoiceModelMeta, pub(crate) path: PathBuf, } impl VoiceModelHeader { - /// モデル内のすべてのスタイルに対するモデル内IDを取得する。 + fn new( + id: VoiceModelId, + manifest: Manifest, + metas: &[u8], + path: &Path, + ) -> LoadModelResult { + let metas = + serde_json::from_slice::(metas).map_err(|source| LoadModelError { + path: path.to_owned(), + context: LoadModelErrorKind::InvalidModelFormat, + source: Some( + anyhow::Error::from(source) + .context(format!("{}が不正です", manifest.metas_filename())), + ), + })?; + + manifest + .domains() + .check_acceptable(&metas) + .map_err(|style_type| LoadModelError { + path: path.to_owned(), + context: LoadModelErrorKind::InvalidModelFormat, + source: Some(anyhow!( + "{metas_filename}には`{style_type}`のスタイルが存在しますが、manifest.jsonでの\ + 対応がありません", + metas_filename = manifest.metas_filename(), + )), + })?; + + Ok(Self { + id, + manifest, + metas, + path: path.to_owned(), + }) + } +} + +impl ManifestDomains { + /// manifestとして対応していない`StyleType`に対してエラーを発する。 /// - /// モデル内IDのマッピングが存在しない場合はそのままスタイルIDを返す。 - pub(crate) fn model_inner_ids(&self) -> BTreeMap { - self.metas + /// `Status`はこのバリデーションを信頼し、`InferenceDomain`の不足時にパニックする。 + fn check_acceptable(&self, metas: &[SpeakerMeta]) -> std::result::Result<(), StyleType> { + let err = metas .iter() .flat_map(SpeakerMeta::styles) - .map(StyleMeta::id) - .map(|&style_id| { - let model_inner_id = self - .manifest - .style_id_to_model_inner_id() - .get(&style_id) - .copied() - .unwrap_or_else(|| ModelInnerId::new(style_id.raw_id())); - (style_id, model_inner_id) - }) - .collect() + .map(StyleMeta::r#type) + .copied() + .unique() + .find(|&style_type| !self.accepts(style_type)); + + match err { + Some(err) => Err(err), + None => Ok(()), + } + } + + /// メタ情報にタイプが`style_type`のスタイルが含まれることを許容するかどうか。 + /// + /// 例えば`self.talk`が`None`のとき、`StyleType::Talk`に対して`false`を返す。 + fn accepts(&self, style_type: StyleType) -> bool { + let Self { talk } = self; + + return TalkDomain::contains(style_type).implies(|| talk.is_some()); + + #[ext] + impl D { + fn contains(style_type: StyleType) -> bool { + Self::style_types().contains(&style_type) + } + } + + #[ext] + impl bool { + fn implies(self, other: impl FnOnce() -> Self) -> Self { + !self || other() + } + } } } @@ -71,12 +159,12 @@ pub(crate) mod blocking { use crate::{ error::{LoadModelError, LoadModelErrorKind, LoadModelResult}, - infer::domain::InferenceOperationImpl, - manifest::Manifest, + infer::domains::InferenceDomainMap, + manifest::{Manifest, TalkManifest}, VoiceModelMeta, }; - use super::{VoiceModelHeader, VoiceModelId}; + use super::{ModelBytesWithInnerIdsByDomain, VoiceModelHeader, VoiceModelId}; /// 音声モデル。 /// @@ -89,39 +177,52 @@ pub(crate) mod blocking { impl self::VoiceModel { pub(crate) fn read_inference_models( &self, - ) -> LoadModelResult>> { + ) -> LoadModelResult> { let reader = BlockingVvmEntryReader::open(&self.header.path)?; - let model_bytes = [ - self.header.manifest.predict_duration_filename(), - self.header.manifest.predict_intonation_filename(), - self.header.manifest.decode_filename(), - ] - .into_par_iter() - .map(|filename| reader.read_vvm_entry(filename)) - .collect::, _>>()? - .try_into() - .unwrap_or_else(|_| panic!("should be same length")); - - Ok(EnumMap::from_array(model_bytes)) + let talk = self + .header + .manifest + .domains() + .talk + .as_ref() + .map( + |TalkManifest { + predict_duration_filename, + predict_intonation_filename, + decode_filename, + style_id_to_model_inner_id, + }| { + let model_bytes = [ + predict_duration_filename, + predict_intonation_filename, + decode_filename, + ] + .into_par_iter() + .map(|filename| reader.read_vvm_entry(filename)) + .collect::, _>>()? + .try_into() + .unwrap_or_else(|_| panic!("should be same length")); + + let model_bytes = EnumMap::from_array(model_bytes); + + Ok((style_id_to_model_inner_id.clone(), model_bytes)) + }, + ) + .transpose()?; + + Ok(InferenceDomainMap { talk }) } /// VVMファイルから`VoiceModel`をコンストラクトする。 pub fn from_path(path: impl AsRef) -> crate::Result { - let path = path.as_ref().to_owned(); - let reader = BlockingVvmEntryReader::open(&path)?; + let path = path.as_ref(); + let reader = BlockingVvmEntryReader::open(path)?; let manifest = reader.read_vvm_json::("manifest.json")?; - let metas = reader.read_vvm_json(manifest.metas_filename())?; + let metas = &reader.read_vvm_entry(manifest.metas_filename())?; let id = VoiceModelId::new(nanoid!()); - - Ok(Self { - header: VoiceModelHeader { - id, - metas, - manifest, - path, - }, - }) + let header = VoiceModelHeader::new(id, manifest, metas, path)?; + Ok(Self { header }) } /// ID。 @@ -163,12 +264,13 @@ pub(crate) mod blocking { }) } + // FIXME: manifest.json専用になっているので、そういう関数名にする fn read_vvm_json(&self, filename: &str) -> LoadModelResult { let bytes = &self.read_vvm_entry(filename)?; serde_json::from_slice(bytes).map_err(|source| LoadModelError { path: self.borrow_path().clone(), - context: LoadModelErrorKind::OpenZipFile, - source: Some(source.into()), + context: LoadModelErrorKind::InvalidModelFormat, + source: Some(anyhow::Error::from(source).context(format!("{filename}が不正です"))), }) } @@ -194,18 +296,18 @@ pub(crate) mod tokio { use derive_new::new; use enum_map::EnumMap; - use futures::future::join3; + use futures::future::{join3, OptionFuture}; use nanoid::nanoid; use serde::de::DeserializeOwned; use crate::{ error::{LoadModelError, LoadModelErrorKind, LoadModelResult}, - infer::domain::InferenceOperationImpl, - manifest::Manifest, + infer::domains::InferenceDomainMap, + manifest::{Manifest, TalkManifest}, Result, VoiceModelMeta, }; - use super::{VoiceModelHeader, VoiceModelId}; + use super::{ModelBytesWithInnerIdsByDomain, VoiceModelHeader, VoiceModelId}; /// 音声モデル。 /// @@ -218,42 +320,49 @@ pub(crate) mod tokio { impl self::VoiceModel { pub(crate) async fn read_inference_models( &self, - ) -> LoadModelResult>> { + ) -> LoadModelResult> { let reader = AsyncVvmEntryReader::open(&self.header.path).await?; - let ( - decode_model_result, - predict_duration_model_result, - predict_intonation_model_result, - ) = join3( - reader.read_vvm_entry(self.header.manifest.decode_filename()), - reader.read_vvm_entry(self.header.manifest.predict_duration_filename()), - reader.read_vvm_entry(self.header.manifest.predict_intonation_filename()), - ) - .await; - - Ok(EnumMap::from_array([ - predict_duration_model_result?, - predict_intonation_model_result?, - decode_model_result?, - ])) + + let talk = OptionFuture::from(self.header.manifest.domains().talk.as_ref().map( + |TalkManifest { + predict_duration_filename, + predict_intonation_filename, + decode_filename, + style_id_to_model_inner_id, + }| async { + let ( + decode_model_result, + predict_duration_model_result, + predict_intonation_model_result, + ) = join3( + reader.read_vvm_entry(decode_filename), + reader.read_vvm_entry(predict_duration_filename), + reader.read_vvm_entry(predict_intonation_filename), + ) + .await; + + let model_bytes = EnumMap::from_array([ + predict_duration_model_result?, + predict_intonation_model_result?, + decode_model_result?, + ]); + + Ok((style_id_to_model_inner_id.clone(), model_bytes)) + }, + )) + .await + .transpose()?; + + Ok(InferenceDomainMap { talk }) } /// VVMファイルから`VoiceModel`をコンストラクトする。 pub async fn from_path(path: impl AsRef) -> Result { let reader = AsyncVvmEntryReader::open(path.as_ref()).await?; let manifest = reader.read_vvm_json::("manifest.json").await?; - let metas = reader - .read_vvm_json::(manifest.metas_filename()) - .await?; + let metas = &reader.read_vvm_entry(manifest.metas_filename()).await?; let id = VoiceModelId::new(nanoid!()); - - Ok(Self { - header: VoiceModelHeader { - id, - metas, - manifest, - path: path.as_ref().into(), - }, - }) + let header = VoiceModelHeader::new(id, manifest, metas, path.as_ref())?; + Ok(Self { header }) } /// ID。 @@ -277,46 +386,46 @@ pub(crate) mod tokio { } #[derive(new)] - struct AsyncVvmEntryReader { - reader: async_zip::read::fs::ZipFileReader, + struct AsyncVvmEntryReader<'a> { + path: &'a Path, + reader: async_zip::base::read::mem::ZipFileReader, entry_map: HashMap, } - impl AsyncVvmEntryReader { - async fn open(path: &Path) -> LoadModelResult { - let reader = async_zip::read::fs::ZipFileReader::new(path) - .await - .map_err(|source| LoadModelError { - path: path.to_owned(), - context: LoadModelErrorKind::OpenZipFile, - source: Some(source.into()), - })?; + impl<'a> AsyncVvmEntryReader<'a> { + async fn open(path: &'a Path) -> LoadModelResult { + let reader = async { + let file = fs_err::tokio::read(path).await?; + async_zip::base::read::mem::ZipFileReader::new(file).await + } + .await + .map_err(|source| LoadModelError { + path: path.to_owned(), + context: LoadModelErrorKind::OpenZipFile, + source: Some(source.into()), + })?; let entry_map: HashMap<_, _> = reader .file() .entries() .iter() - .filter(|e| !e.entry().dir()) - .enumerate() - .map(|(i, e)| { - ( - e.entry().filename().to_string(), - AsyncVvmEntry { - index: i, - entry: e.entry().clone(), - }, - ) + .flat_map(|e| { + // 非UTF-8のファイルを利用することはないため、無視する + let filename = e.filename().as_str().ok()?; + (!e.dir().ok()?).then_some(())?; + Some((filename.to_owned(), (**e).clone())) }) + .enumerate() + .map(|(i, (filename, entry))| (filename, AsyncVvmEntry { index: i, entry })) .collect(); - Ok(AsyncVvmEntryReader::new(reader, entry_map)) + Ok(AsyncVvmEntryReader::new(path, reader, entry_map)) } + // FIXME: manifest.json専用になっているので、そういう関数名にする async fn read_vvm_json(&self, filename: &str) -> LoadModelResult { let bytes = self.read_vvm_entry(filename).await?; serde_json::from_slice(&bytes).map_err(|source| LoadModelError { - path: self.reader.path().to_owned(), - context: LoadModelErrorKind::ReadZipEntry { - filename: filename.to_owned(), - }, - source: Some(source.into()), + path: self.path.to_owned(), + context: LoadModelErrorKind::InvalidModelFormat, + source: Some(anyhow::Error::from(source).context(format!("{filename}が不正です"))), }) } @@ -326,16 +435,14 @@ pub(crate) mod tokio { .entry_map .get(filename) .ok_or_else(|| io::Error::from(io::ErrorKind::NotFound))?; - let mut manifest_reader = self.reader.entry(me.index).await?; + let mut manifest_reader = self.reader.reader_with_entry(me.index).await?; let mut buf = Vec::with_capacity(me.entry.uncompressed_size() as usize); - manifest_reader - .read_to_end_checked(&mut buf, &me.entry) - .await?; + manifest_reader.read_to_end_checked(&mut buf).await?; Ok::<_, anyhow::Error>(buf) } .await .map_err(|source| LoadModelError { - path: self.reader.path().to_owned(), + path: self.path.to_owned(), context: LoadModelErrorKind::ReadZipEntry { filename: filename.to_owned(), }, @@ -344,3 +451,102 @@ pub(crate) mod tokio { } } } + +#[cfg(test)] +mod tests { + use once_cell::sync::Lazy; + use rstest::{fixture, rstest}; + use serde_json::json; + + use crate::{ + manifest::{ManifestDomains, TalkManifest}, + SpeakerMeta, StyleType, + }; + + #[rstest] + #[case( + &ManifestDomains { + talk: None, + }, + &[], + Ok(()) + )] + #[case( + &ManifestDomains { + talk: Some(TALK_MANIFEST.clone()), + }, + &[speaker(&[StyleType::Talk])], + Ok(()) + )] + #[case( + &ManifestDomains { + talk: Some(TALK_MANIFEST.clone()), + }, + &[speaker(&[StyleType::Talk, StyleType::Sing])], + Ok(()) + )] + #[case( + &ManifestDomains { + talk: None, + }, + &[speaker(&[StyleType::Talk])], + Err(()) + )] + fn check_acceptable_works( + #[case] manifest: &ManifestDomains, + #[case] metas: &[SpeakerMeta], + #[case] expected: std::result::Result<(), ()>, + ) { + let actual = manifest.check_acceptable(metas).map_err(|_| ()); + assert_eq!(expected, actual); + } + + static TALK_MANIFEST: Lazy = Lazy::new(|| TalkManifest { + predict_duration_filename: "".to_owned(), + predict_intonation_filename: "".to_owned(), + decode_filename: "".to_owned(), + style_id_to_model_inner_id: Default::default(), + }); + + #[fixture] + fn talk_speaker() -> SpeakerMeta { + serde_json::from_value(json!({ + "name": "dummy", + "styles": [ + { + "id": 0, + "name": "style1", + "type": "talk", + "order": 0 + } + ], + "version": "0.0.1", + "speaker_uuid": "574bc678-8370-44be-b941-08e46e7b47d7", + "order": 0 + })) + .unwrap() + } + + fn speaker(style_types: &'static [StyleType]) -> SpeakerMeta { + let styles = style_types + .iter() + .map(|style_type| { + json!({ + "id": 0, + "name": "style1", + "type": style_type, + "order": null + }) + }) + .collect::>(); + + serde_json::from_value(json!({ + "name": "dummy", + "styles": styles, + "version": "0.0.1", + "speaker_uuid": "574bc678-8370-44be-b941-08e46e7b47d7", + "order": null + })) + .unwrap() + } +} diff --git a/crates/voicevox_core_c_api/Cargo.toml b/crates/voicevox_core_c_api/Cargo.toml index ee45eee79..ad3a65fa7 100644 --- a/crates/voicevox_core_c_api/Cargo.toml +++ b/crates/voicevox_core_c_api/Cargo.toml @@ -13,11 +13,13 @@ harness = false name = "e2e" [features] +cuda = ["voicevox_core/cuda"] directml = ["voicevox_core/directml"] [dependencies] anstream = { workspace = true, default-features = false, features = ["auto"] } anstyle-query.workspace = true +camino.workspace = true chrono = { workspace = true, default-features = false, features = ["clock"] } colorchoice.workspace = true cstr.workspace = true @@ -48,7 +50,6 @@ ndarray-stats.workspace = true regex.workspace = true serde = { workspace = true, features = ["derive"] } serde_with.workspace = true -strum = { workspace = true, features = ["derive"] } tempfile.workspace = true test_util.workspace = true toml.workspace = true diff --git a/crates/voicevox_core_c_api/include/voicevox_core.h b/crates/voicevox_core_c_api/include/voicevox_core.h index 2275bd1d7..592ec8abb 100644 --- a/crates/voicevox_core_c_api/include/voicevox_core.h +++ b/crates/voicevox_core_c_api/include/voicevox_core.h @@ -142,6 +142,10 @@ enum VoicevoxResultCode * ZIP内のファイルが読めなかった */ VOICEVOX_RESULT_READ_ZIP_ENTRY_ERROR = 17, + /** + * モデルの形式が不正 + */ + VOICEVOX_RESULT_INVALID_MODEL_HEADER_ERROR = 28, /** * すでに読み込まれている音声モデルを読み込もうとした */ diff --git a/crates/voicevox_core_c_api/src/c_impls.rs b/crates/voicevox_core_c_api/src/c_impls.rs index 0b57db3e5..4e73bf0fb 100644 --- a/crates/voicevox_core_c_api/src/c_impls.rs +++ b/crates/voicevox_core_c_api/src/c_impls.rs @@ -1,11 +1,12 @@ use std::{ffi::CString, path::Path}; +use camino::Utf8Path; use voicevox_core::{InitializeOptions, Result, VoiceModelId}; use crate::{helpers::CApiResult, OpenJtalkRc, VoicevoxSynthesizer, VoicevoxVoiceModel}; impl OpenJtalkRc { - pub(crate) fn new(open_jtalk_dic_dir: impl AsRef) -> Result { + pub(crate) fn new(open_jtalk_dic_dir: impl AsRef) -> Result { Ok(Self { open_jtalk: voicevox_core::blocking::OpenJtalk::new(open_jtalk_dic_dir)?, }) diff --git a/crates/voicevox_core_c_api/src/compatible_engine.rs b/crates/voicevox_core_c_api/src/compatible_engine.rs index 5a6f20f76..6755910f5 100644 --- a/crates/voicevox_core_c_api/src/compatible_engine.rs +++ b/crates/voicevox_core_c_api/src/compatible_engine.rs @@ -41,7 +41,9 @@ static VOICE_MODEL_SET: Lazy = Lazy::new(|| { .iter() .map(|vvm| (vvm.id().clone(), vvm.clone())) .collect(); - let metas: Vec<_> = all_vvms.iter().flat_map(|vvm| vvm.metas()).collect(); + let metas = voicevox_core::__internal::interop::merge_metas( + all_vvms.iter().flat_map(|vvm| vvm.metas()), + ); let mut style_model_map = BTreeMap::default(); for vvm in all_vvms.iter() { for meta in vvm.metas().iter() { diff --git a/crates/voicevox_core_c_api/src/helpers.rs b/crates/voicevox_core_c_api/src/helpers.rs index d69641c34..1c163a0d0 100644 --- a/crates/voicevox_core_c_api/src/helpers.rs +++ b/crates/voicevox_core_c_api/src/helpers.rs @@ -37,6 +37,7 @@ pub(crate) fn into_result_code_with_error(result: CApiResult<()>) -> VoicevoxRes GpuSupport => VOICEVOX_RESULT_GPU_SUPPORT_ERROR, OpenZipFile => VOICEVOX_RESULT_OPEN_ZIP_FILE_ERROR, ReadZipEntry => VOICEVOX_RESULT_READ_ZIP_ENTRY_ERROR, + InvalidModelFormat => VOICEVOX_RESULT_INVALID_MODEL_HEADER_ERROR, ModelAlreadyLoaded => VOICEVOX_RESULT_MODEL_ALREADY_LOADED_ERROR, StyleAlreadyLoaded => VOICEVOX_RESULT_STYLE_ALREADY_LOADED_ERROR, InvalidModelData => VOICEVOX_RESULT_INVALID_MODEL_DATA_ERROR, @@ -63,7 +64,7 @@ pub(crate) fn into_result_code_with_error(result: CApiResult<()>) -> VoicevoxRes pub(crate) type CApiResult = std::result::Result; #[derive(Error, Debug)] -pub enum CApiError { +pub(crate) enum CApiError { #[error("{0}")] RustApi(#[from] voicevox_core::Error), #[error("UTF-8として不正な入力です")] diff --git a/crates/voicevox_core_c_api/src/lib.rs b/crates/voicevox_core_c_api/src/lib.rs index d2946f02b..fbb0bf6bf 100644 --- a/crates/voicevox_core_c_api/src/lib.rs +++ b/crates/voicevox_core_c_api/src/lib.rs @@ -59,7 +59,13 @@ fn init_logger_once() { .with_env_filter(if env::var_os(EnvFilter::DEFAULT_ENV).is_some() { EnvFilter::from_default_env() } else { - "error,voicevox_core=info,voicevox_core_c_api=info,onnxruntime=info".into() + pub const ORT_LOGGING_LEVEL: &str = if cfg!(debug_assertions) { + "info" + } else { + "warn" + }; + format!("error,voicevox_core=info,voicevox_core_c_api=info,ort={ORT_LOGGING_LEVEL}") + .into() }) .with_timer(local_time as fn(&mut Writer<'_>) -> _) .with_ansi(ansi) @@ -441,8 +447,10 @@ pub unsafe extern "C" fn voicevox_synthesizer_is_loaded_voice_model( model_id: VoicevoxVoiceModelId, ) -> bool { init_logger_once(); - // FIXME: 不正なUTF-8文字列に対し、正式なエラーとするか黙って`false`を返す - let raw_model_id = ensure_utf8(unsafe { CStr::from_ptr(model_id) }).unwrap(); + let Ok(raw_model_id) = ensure_utf8(unsafe { CStr::from_ptr(model_id) }) else { + // 与えられたIDがUTF-8ではない場合、それに対応する`VoicdModel`は確実に存在しない + return false; + }; synthesizer .synthesizer() .is_loaded_voice_model(&VoiceModelId::new(raw_model_id.into())) diff --git a/crates/voicevox_core_c_api/src/result_code.rs b/crates/voicevox_core_c_api/src/result_code.rs index 65236ada4..0897dfa87 100644 --- a/crates/voicevox_core_c_api/src/result_code.rs +++ b/crates/voicevox_core_c_api/src/result_code.rs @@ -37,6 +37,8 @@ pub enum VoicevoxResultCode { VOICEVOX_RESULT_OPEN_ZIP_FILE_ERROR = 16, /// ZIP内のファイルが読めなかった VOICEVOX_RESULT_READ_ZIP_ENTRY_ERROR = 17, + /// モデルの形式が不正 + VOICEVOX_RESULT_INVALID_MODEL_HEADER_ERROR = 28, /// すでに読み込まれている音声モデルを読み込もうとした VOICEVOX_RESULT_MODEL_ALREADY_LOADED_ERROR = 18, /// すでに読み込まれているスタイルを読み込もうとした @@ -90,6 +92,7 @@ pub(crate) const fn error_result_to_message(result_code: VoicevoxResultCode) -> VOICEVOX_RESULT_READ_ZIP_ENTRY_ERROR => { cstr!("ZIP内のファイルを読むことができませんでした") } + VOICEVOX_RESULT_INVALID_MODEL_HEADER_ERROR => cstr!("モデルの形式が不正です"), VOICEVOX_RESULT_MODEL_ALREADY_LOADED_ERROR => cstr!("同じIDのモデルを読むことはできません"), VOICEVOX_RESULT_STYLE_ALREADY_LOADED_ERROR => { cstr!("同じIDのスタイルを読むことはできません") diff --git a/crates/voicevox_core_c_api/tests/e2e/assert_cdylib.rs b/crates/voicevox_core_c_api/tests/e2e/assert_cdylib.rs index 11da69437..cfbec5c31 100644 --- a/crates/voicevox_core_c_api/tests/e2e/assert_cdylib.rs +++ b/crates/voicevox_core_c_api/tests/e2e/assert_cdylib.rs @@ -36,7 +36,7 @@ pub(crate) fn exec() -> anyhow::Result<()> { let exec_c_api_e2e_test = serde_json::from_str::>(&exec_c_api_e2e_test)?; return unsafe { - let lib = &Library::new(C::cdylib_path())?; + let lib = Library::new(C::cdylib_path())?; exec_c_api_e2e_test.exec(lib) }; } @@ -46,11 +46,7 @@ pub(crate) fn exec() -> anyhow::Result<()> { // テスト対象が無いときに`cargo build`をスキップしたいが、判定部分がプライベート。 // そのためスキップするのはCLIオプションに`--ignored`か`--include-ignored`が無いときのみ if args.ignored || args.include_ignored { - let mut cmd = cmd!(env!("CARGO"), "build", "--release", "--lib"); - for (k, v) in C::BUILD_ENVS { - cmd = cmd.env(k, v); - } - cmd.run()?; + cmd!(env!("CARGO"), "build", "--release", "--lib").run()?; ensure!( C::cdylib_path().exists(), @@ -102,7 +98,6 @@ pub(crate) fn exec() -> anyhow::Result<()> { pub(crate) trait TestContext { const TARGET_DIR: &'static str; const CDYLIB_NAME: &'static str; - const BUILD_ENVS: &'static [(&'static str, &'static str)]; const RUNTIME_ENVS: &'static [(&'static str, &'static str)]; } @@ -113,7 +108,7 @@ pub(crate) trait TestCase: Send { /// /// `exec`は独立したプロセスで実行されるため、stdout/stderrへの出力をしたりグローバルな状態に /// 依存してもよい。 - unsafe fn exec(&self, lib: &Library) -> anyhow::Result<()>; + unsafe fn exec(&self, lib: Library) -> anyhow::Result<()>; /// 別プロセスで実行された`exec`の結果をチェックする。 fn assert_output(&self, output: Utf8Output) -> AssertResult; diff --git a/crates/voicevox_core_c_api/tests/e2e/main.rs b/crates/voicevox_core_c_api/tests/e2e/main.rs index b8d3165c6..43dc3a95e 100644 --- a/crates/voicevox_core_c_api/tests/e2e/main.rs +++ b/crates/voicevox_core_c_api/tests/e2e/main.rs @@ -1,15 +1,14 @@ +use test_util::c_api::VV_MODELS_ROOT_DIR; + mod assert_cdylib; mod float_assert; mod log_mask; mod snapshots; -mod symbols; mod testcases; // voicevox_core_c_apiのcdylibを対象にテストを行う。 // -// C APIの定義を変更する場合: -// 1. symbols.rsの実装を変更する。 -// 2. 1.によってコンパイルが通らなくなったら、適宜修正する。 +// C APIの定義を変更した場合は、テスト実行前に`cargo xtask update-c-header`を実行すること。 // // テストを追加する場合: // 1. testcases/{テスト名}.rsを追加し、testcases.rsでマウントする。 @@ -25,17 +24,7 @@ fn main() -> anyhow::Result<()> { impl assert_cdylib::TestContext for TestContext { const TARGET_DIR: &'static str = "../../target"; const CDYLIB_NAME: &'static str = "voicevox_core"; - const BUILD_ENVS: &'static [(&'static str, &'static str)] = &[ - // 他の単体テストが動いているときにonnxruntime-sysの初回ビルドを行うと、Windows環境だと - // `$ORT_OUT_DIR`のハックが問題を起こす。そのためこのハック自体を無効化する - // - // featuresの差分を出さないように`cargo build`することができればonnxruntime-sysの - // ビルド自体がされないのだが、このバイナリから`cargo build`の状況を知るのは無理に近い - ("ORT_OUT_DIR", ""), - // DirectMLとCUDAは無効化 - ("ORT_USE_CUDA", "0"), - ]; const RUNTIME_ENVS: &'static [(&'static str, &'static str)] = - &[("VV_MODELS_ROOT_DIR", "../../model")]; + &[("VV_MODELS_ROOT_DIR", VV_MODELS_ROOT_DIR)]; } } diff --git a/crates/voicevox_core_c_api/tests/e2e/snapshots.toml b/crates/voicevox_core_c_api/tests/e2e/snapshots.toml index 8f3fa4f3b..151074cb3 100644 --- a/crates/voicevox_core_c_api/tests/e2e/snapshots.toml +++ b/crates/voicevox_core_c_api/tests/e2e/snapshots.toml @@ -6,37 +6,48 @@ metas = ''' "styles": [ { "id": 0, - "name": "style1" + "name": "style1", + "type": "talk", + "order": null } ], "version": "0.0.1", - "speaker_uuid": "574bc678-8370-44be-b941-08e46e7b47d7" + "speaker_uuid": "574bc678-8370-44be-b941-08e46e7b47d7", + "order": null }, { "name": "dummy2", "styles": [ { "id": 1, - "name": "style2" + "name": "style2", + "type": "talk", + "order": null } ], "version": "0.0.1", - "speaker_uuid": "dd9ccd75-75f6-40ce-a3db-960cbed2e905" + "speaker_uuid": "dd9ccd75-75f6-40ce-a3db-960cbed2e905", + "order": null }, { "name": "dummy3", "styles": [ { "id": 302, - "name": "style3-1" + "name": "style3-1", + "type": "talk", + "order": null }, { "id": 303, - "name": "style3-2" + "name": "style3-2", + "type": "talk", + "order": null } ], "version": "0.0.1", - "speaker_uuid": "5d3d9aa9-88e5-4a96-8ef7-f13a3cad1cb3" + "speaker_uuid": "5d3d9aa9-88e5-4a96-8ef7-f13a3cad1cb3", + "order": null } ]''' stderr.windows = ''' @@ -89,37 +100,48 @@ metas = ''' "styles": [ { "id": 0, - "name": "style1" + "name": "style1", + "type": "talk", + "order": null } ], "version": "0.0.1", - "speaker_uuid": "574bc678-8370-44be-b941-08e46e7b47d7" + "speaker_uuid": "574bc678-8370-44be-b941-08e46e7b47d7", + "order": null }, { "name": "dummy2", "styles": [ { "id": 1, - "name": "style2" + "name": "style2", + "type": "talk", + "order": null } ], "version": "0.0.1", - "speaker_uuid": "dd9ccd75-75f6-40ce-a3db-960cbed2e905" + "speaker_uuid": "dd9ccd75-75f6-40ce-a3db-960cbed2e905", + "order": null }, { "name": "dummy3", "styles": [ { "id": 302, - "name": "style3-1" + "name": "style3-1", + "type": "talk", + "order": null }, { "id": 303, - "name": "style3-2" + "name": "style3-2", + "type": "talk", + "order": null } ], "version": "0.0.1", - "speaker_uuid": "5d3d9aa9-88e5-4a96-8ef7-f13a3cad1cb3" + "speaker_uuid": "5d3d9aa9-88e5-4a96-8ef7-f13a3cad1cb3", + "order": null } ]''' stderr.windows = ''' diff --git a/crates/voicevox_core_c_api/tests/e2e/symbols.rs b/crates/voicevox_core_c_api/tests/e2e/symbols.rs deleted file mode 100644 index f6ecd4a3b..000000000 --- a/crates/voicevox_core_c_api/tests/e2e/symbols.rs +++ /dev/null @@ -1,334 +0,0 @@ -use std::ffi::{c_char, c_int, c_void}; - -use libloading::{Library, Symbol}; -use strum::EnumIter; - -/// voicevox\_core\_c\_apiのcdylibのシンボルを集めたもの。 -#[allow(dead_code)] // TODO: WIP -pub(crate) struct Symbols<'lib> { - pub(crate) voicevox_open_jtalk_rc_new: Symbol< - 'lib, - unsafe extern "C" fn(*const c_char, *mut *mut OpenJtalkRc) -> VoicevoxResultCode, - >, - pub(crate) voicevox_open_jtalk_rc_use_user_dict: Symbol< - 'lib, - unsafe extern "C" fn(*mut OpenJtalkRc, *const VoicevoxUserDict) -> VoicevoxResultCode, - >, - pub(crate) voicevox_open_jtalk_rc_delete: Symbol<'lib, unsafe extern "C" fn(*mut OpenJtalkRc)>, - pub(crate) voicevox_make_default_initialize_options: - Symbol<'lib, unsafe extern "C" fn() -> VoicevoxInitializeOptions>, - pub(crate) voicevox_get_version: Symbol<'lib, unsafe extern "C" fn() -> *const c_char>, - pub(crate) voicevox_voice_model_new_from_path: Symbol< - 'lib, - unsafe extern "C" fn(*const c_char, *mut *mut VoicevoxVoiceModel) -> VoicevoxResultCode, - >, - pub(crate) voicevox_voice_model_id: - Symbol<'lib, unsafe extern "C" fn(*const VoicevoxVoiceModel) -> VoicevoxVoiceModelId>, - pub(crate) voicevox_voice_model_get_metas_json: - Symbol<'lib, unsafe extern "C" fn(*const VoicevoxVoiceModel) -> *const c_char>, - pub(crate) voicevox_voice_model_delete: - Symbol<'lib, unsafe extern "C" fn(*mut VoicevoxVoiceModel)>, - pub(crate) voicevox_synthesizer_new: Symbol< - 'lib, - unsafe extern "C" fn( - *const OpenJtalkRc, - VoicevoxInitializeOptions, - *mut *mut VoicevoxSynthesizer, - ) -> VoicevoxResultCode, - >, - pub(crate) voicevox_synthesizer_delete: - Symbol<'lib, unsafe extern "C" fn(*mut VoicevoxSynthesizer)>, - pub(crate) voicevox_synthesizer_load_voice_model: Symbol< - 'lib, - unsafe extern "C" fn( - *mut VoicevoxSynthesizer, - *const VoicevoxVoiceModel, - ) -> VoicevoxResultCode, - >, - pub(crate) voicevox_synthesizer_unload_voice_model: Symbol< - 'lib, - unsafe extern "C" fn(*mut VoicevoxSynthesizer, VoicevoxVoiceModelId) -> VoicevoxResultCode, - >, - pub(crate) voicevox_synthesizer_is_gpu_mode: - Symbol<'lib, unsafe extern "C" fn(*const VoicevoxSynthesizer) -> bool>, - pub(crate) voicevox_synthesizer_is_loaded_voice_model: Symbol< - 'lib, - unsafe extern "C" fn(*const VoicevoxSynthesizer, VoicevoxVoiceModelId) -> bool, - >, - pub(crate) voicevox_synthesizer_create_metas_json: - 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_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, - *mut *mut c_char, - ) -> VoicevoxResultCode, - >, - pub(crate) voicevox_make_default_synthesis_options: - Symbol<'lib, unsafe extern "C" fn() -> VoicevoxSynthesisOptions>, - pub(crate) voicevox_synthesizer_synthesis: Symbol< - 'lib, - unsafe extern "C" fn( - *const VoicevoxSynthesizer, - *const c_char, - VoicevoxStyleId, - VoicevoxSynthesisOptions, - *mut usize, - *mut *mut u8, - ) -> VoicevoxResultCode, - >, - 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( - *const VoicevoxSynthesizer, - *const c_char, - VoicevoxStyleId, - VoicevoxTtsOptions, - *mut usize, - *mut *mut u8, - ) -> VoicevoxResultCode, - >, - pub(crate) voicevox_json_free: Symbol<'lib, unsafe extern "C" fn(*mut c_char)>, - pub(crate) voicevox_wav_free: Symbol<'lib, unsafe extern "C" fn(*mut u8)>, - pub(crate) voicevox_error_result_to_message: - Symbol<'lib, unsafe extern "C" fn(VoicevoxResultCode) -> *const c_char>, - - pub(crate) initialize: Symbol<'lib, unsafe extern "C" fn(bool, c_int, bool) -> bool>, - pub(crate) load_model: Symbol<'lib, unsafe extern "C" fn(i64) -> bool>, - pub(crate) is_model_loaded: Symbol<'lib, unsafe extern "C" fn(i64) -> bool>, - pub(crate) finalize: Symbol<'lib, unsafe extern "C" fn()>, - pub(crate) metas: Symbol<'lib, unsafe extern "C" fn() -> *const c_char>, - pub(crate) last_error_message: Symbol<'lib, unsafe extern "C" fn() -> *const c_char>, - pub(crate) supported_devices: Symbol<'lib, unsafe extern "C" fn() -> *const c_char>, - pub(crate) yukarin_s_forward: - Symbol<'lib, unsafe extern "C" fn(i64, *mut i64, *mut i64, *mut f32) -> bool>, - pub(crate) yukarin_sa_forward: Symbol< - 'lib, - unsafe extern "C" fn( - i64, - *mut i64, - *mut i64, - *mut i64, - *mut i64, - *mut i64, - *mut i64, - *mut i64, - *mut f32, - ) -> bool, - >, - pub(crate) decode_forward: Symbol< - 'lib, - unsafe extern "C" fn(i64, i64, *mut f32, *mut f32, *mut i64, *mut f32) -> bool, - >, - - pub(crate) voicevox_user_dict_word_make: - Symbol<'lib, unsafe extern "C" fn(*const c_char, *const c_char) -> VoicevoxUserDictWord>, - pub(crate) voicevox_user_dict_new: - Symbol<'lib, unsafe extern "C" fn() -> *mut VoicevoxUserDict>, - pub(crate) voicevox_user_dict_load: Symbol< - 'lib, - unsafe extern "C" fn(*const VoicevoxUserDict, *const c_char) -> VoicevoxResultCode, - >, - pub(crate) voicevox_user_dict_add_word: Symbol< - 'lib, - unsafe extern "C" fn( - *const VoicevoxUserDict, - *const VoicevoxUserDictWord, - *mut [u8; 16], - ) -> VoicevoxResultCode, - >, - pub(crate) voicevox_user_dict_update_word: Symbol< - 'lib, - unsafe extern "C" fn( - *const VoicevoxUserDict, - *const [u8; 16], - *const VoicevoxUserDictWord, - ) -> VoicevoxResultCode, - >, - pub(crate) voicevox_user_dict_remove_word: Symbol< - 'lib, - unsafe extern "C" fn(*const VoicevoxUserDict, *const [u8; 16]) -> VoicevoxResultCode, - >, - pub(crate) voicevox_user_dict_to_json: Symbol< - 'lib, - unsafe extern "C" fn(*const VoicevoxUserDict, *mut *mut c_char) -> VoicevoxResultCode, - >, - pub(crate) voicevox_user_dict_import: Symbol< - 'lib, - unsafe extern "C" fn( - *const VoicevoxUserDict, - *const VoicevoxUserDict, - ) -> VoicevoxResultCode, - >, - pub(crate) voicevox_user_dict_save: Symbol< - 'lib, - unsafe extern "C" fn(*const VoicevoxUserDict, *const c_char) -> VoicevoxResultCode, - >, - pub(crate) voicevox_user_dict_delete: - Symbol<'lib, unsafe extern "C" fn(*mut VoicevoxUserDict) -> VoicevoxResultCode>, -} - -impl<'lib> Symbols<'lib> { - pub(crate) unsafe fn new(lib: &'lib Library) -> Result { - macro_rules! new(($($name:ident),* $(,)?) => { - Self { - $( - $name: lib.get(stringify!($name).as_ref())?, - )* - } - }); - - Ok(new!( - voicevox_open_jtalk_rc_new, - voicevox_open_jtalk_rc_use_user_dict, - voicevox_open_jtalk_rc_delete, - voicevox_make_default_initialize_options, - voicevox_get_version, - voicevox_voice_model_new_from_path, - voicevox_voice_model_id, - voicevox_voice_model_get_metas_json, - voicevox_voice_model_delete, - voicevox_synthesizer_new, - voicevox_synthesizer_delete, - voicevox_synthesizer_load_voice_model, - voicevox_synthesizer_unload_voice_model, - voicevox_synthesizer_is_gpu_mode, - voicevox_synthesizer_is_loaded_voice_model, - voicevox_synthesizer_create_metas_json, - voicevox_create_supported_devices_json, - 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, - voicevox_error_result_to_message, - initialize, - load_model, - is_model_loaded, - finalize, - metas, - last_error_message, - supported_devices, - yukarin_s_forward, - yukarin_sa_forward, - decode_forward, - voicevox_user_dict_word_make, - voicevox_user_dict_new, - voicevox_user_dict_load, - voicevox_user_dict_add_word, - voicevox_user_dict_update_word, - voicevox_user_dict_remove_word, - voicevox_user_dict_to_json, - voicevox_user_dict_import, - voicevox_user_dict_save, - voicevox_user_dict_delete, - )) - } -} - -type OpenJtalkRc = c_void; -type VoicevoxVoiceModel = c_void; -type VoicevoxVoiceModelId = *const c_char; -type VoicevoxSynthesizer = c_void; -type VoicevoxStyleId = u32; - -#[repr(i32)] -#[derive(Debug, PartialEq, Eq, Clone, Copy, EnumIter)] -#[allow(non_camel_case_types)] -pub(crate) enum VoicevoxResultCode { - VOICEVOX_RESULT_OK = 0, - VOICEVOX_RESULT_NOT_LOADED_OPENJTALK_DICT_ERROR = 1, - VOICEVOX_RESULT_GET_SUPPORTED_DEVICES_ERROR = 3, - VOICEVOX_RESULT_GPU_SUPPORT_ERROR = 4, - 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, - VOICEVOX_RESULT_PARSE_KANA_ERROR = 13, - VOICEVOX_RESULT_INVALID_AUDIO_QUERY_ERROR = 14, - VOICEVOX_RESULT_INVALID_ACCENT_PHRASE_ERROR = 15, - VOICEVOX_RESULT_OPEN_ZIP_FILE_ERROR = 16, - VOICEVOX_RESULT_READ_ZIP_ENTRY_ERROR = 17, - VOICEVOX_RESULT_MODEL_ALREADY_LOADED_ERROR = 18, - VOICEVOX_RESULT_STYLE_ALREADY_LOADED_ERROR = 26, - VOICEVOX_RESULT_INVALID_MODEL_DATA_ERROR = 27, - VOICEVOX_RESULT_LOAD_USER_DICT_ERROR = 20, - VOICEVOX_RESULT_SAVE_USER_DICT_ERROR = 21, - 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, -} - -#[repr(i32)] -#[allow(non_camel_case_types)] -pub(crate) enum VoicevoxAccelerationMode { - VOICEVOX_ACCELERATION_MODE_CPU = 1, -} - -#[repr(C)] -pub(crate) struct VoicevoxInitializeOptions { - pub(crate) acceleration_mode: VoicevoxAccelerationMode, - pub(crate) _cpu_num_threads: u16, -} - -#[repr(C)] -pub(crate) struct VoicevoxSynthesisOptions { - _enable_interrogative_upspeak: bool, -} - -#[repr(C)] -pub(crate) struct VoicevoxTtsOptions { - _enable_interrogative_upspeak: bool, -} - -#[repr(C)] -pub(crate) struct VoicevoxUserDict { - _private: [u8; 0], -} - -#[repr(C)] -pub(crate) struct VoicevoxUserDictWord { - pub(crate) surface: *const c_char, - pub(crate) pronunciation: *const c_char, - pub(crate) accent_type: usize, - pub(crate) word_type: VoicevoxUserDictWordType, - pub(crate) priority: u32, -} - -#[repr(i32)] -#[allow(non_camel_case_types)] -pub(crate) enum VoicevoxUserDictWordType { - VOICEVOX_USER_DICT_WORD_TYPE_PROPER_NOUN = 0, -} diff --git a/crates/voicevox_core_c_api/tests/e2e/testcases/compatible_engine.rs b/crates/voicevox_core_c_api/tests/e2e/testcases/compatible_engine.rs index 41968aa8f..c2fc211c2 100644 --- a/crates/voicevox_core_c_api/tests/e2e/testcases/compatible_engine.rs +++ b/crates/voicevox_core_c_api/tests/e2e/testcases/compatible_engine.rs @@ -8,12 +8,11 @@ use once_cell::sync::Lazy; use serde::{Deserialize, Serialize}; use voicevox_core::SupportedDevices; -use test_util::EXAMPLE_DATA; +use test_util::{c_api::CApi, EXAMPLE_DATA}; use crate::{ assert_cdylib::{self, case, Utf8Output}, float_assert, snapshots, - symbols::Symbols, }; case!(TestCase); @@ -23,43 +22,32 @@ struct TestCase; #[typetag::serde(name = "compatible_engine")] impl assert_cdylib::TestCase for TestCase { - unsafe fn exec(&self, lib: &Library) -> anyhow::Result<()> { - let Symbols { - initialize, - load_model, - is_model_loaded, - finalize, - metas, - supported_devices, - yukarin_s_forward, - yukarin_sa_forward, - decode_forward, - .. - } = Symbols::new(lib)?; + unsafe fn exec(&self, lib: Library) -> anyhow::Result<()> { + let lib = CApi::from_library(lib)?; let metas_json = { - let metas_json = metas(); + let metas_json = lib.metas(); let metas_json = CStr::from_ptr(metas_json).to_str()?; serde_json::to_string_pretty(&metas_json.parse::()?).unwrap() }; let supported_devices = { - let supported_devices = supported_devices(); + let supported_devices = lib.supported_devices(); CStr::from_ptr(supported_devices) .to_str()? .parse::()? }; - assert!(initialize(false, 0, false)); + assert!(lib.initialize(false, 0, false)); - assert!(!is_model_loaded(EXAMPLE_DATA.speaker_id)); - assert!(load_model(EXAMPLE_DATA.speaker_id)); - assert!(is_model_loaded(EXAMPLE_DATA.speaker_id)); + assert!(!lib.is_model_loaded(EXAMPLE_DATA.speaker_id)); + assert!(lib.load_model(EXAMPLE_DATA.speaker_id)); + assert!(lib.is_model_loaded(EXAMPLE_DATA.speaker_id)); // テスト用テキストは"t e s u t o" let phoneme_length = { let mut phoneme_length = [0.; 8]; - assert!(yukarin_s_forward( + assert!(lib.yukarin_s_forward( EXAMPLE_DATA.duration.length, EXAMPLE_DATA.duration.phoneme_vector.as_ptr() as *mut i64, &mut { EXAMPLE_DATA.speaker_id } as *mut i64, @@ -70,7 +58,7 @@ impl assert_cdylib::TestCase for TestCase { let intonation_list = { let mut intonation_list = [0.; 5]; - assert!(yukarin_sa_forward( + assert!(lib.yukarin_sa_forward( EXAMPLE_DATA.intonation.length, EXAMPLE_DATA.intonation.vowel_phoneme_vector.as_ptr() as *mut i64, EXAMPLE_DATA.intonation.consonant_phoneme_vector.as_ptr() as *mut i64, @@ -86,7 +74,7 @@ impl assert_cdylib::TestCase for TestCase { let wave = { let mut wave = vec![0.; 256 * EXAMPLE_DATA.decode.f0_length as usize]; - assert!(decode_forward( + assert!(lib.decode_forward( EXAMPLE_DATA.decode.f0_length, EXAMPLE_DATA.decode.phoneme_size, EXAMPLE_DATA.decode.f0_vector.as_ptr() as *mut f32, @@ -108,7 +96,7 @@ impl assert_cdylib::TestCase for TestCase { assert!(wave.iter().copied().all(f32::is_normal)); - finalize(); + lib.finalize(); Ok(()) } diff --git a/crates/voicevox_core_c_api/tests/e2e/testcases/compatible_engine_load_model_before_initialize.rs b/crates/voicevox_core_c_api/tests/e2e/testcases/compatible_engine_load_model_before_initialize.rs index 4ea024a98..173e32f8c 100644 --- a/crates/voicevox_core_c_api/tests/e2e/testcases/compatible_engine_load_model_before_initialize.rs +++ b/crates/voicevox_core_c_api/tests/e2e/testcases/compatible_engine_load_model_before_initialize.rs @@ -6,11 +6,11 @@ use assert_cmd::assert::AssertResult; use libloading::Library; use once_cell::sync::Lazy; use serde::{Deserialize, Serialize}; +use test_util::c_api::CApi; use crate::{ assert_cdylib::{self, case, Utf8Output}, snapshots, - symbols::Symbols, }; case!(TestCase); @@ -20,15 +20,11 @@ struct TestCase; #[typetag::serde(name = "compatible_engine_load_model_before_initialize")] impl assert_cdylib::TestCase for TestCase { - unsafe fn exec(&self, lib: &Library) -> anyhow::Result<()> { - let Symbols { - load_model, - last_error_message, - .. - } = Symbols::new(lib)?; - - assert!(!load_model(0)); - let last_error_message = last_error_message(); + unsafe fn exec(&self, lib: Library) -> anyhow::Result<()> { + let lib = CApi::from_library(lib)?; + + assert!(!lib.load_model(0)); + let last_error_message = lib.last_error_message(); let last_error_message = CStr::from_ptr(last_error_message).to_str()?; std::assert_eq!(SNAPSHOTS.last_error_message, last_error_message); diff --git a/crates/voicevox_core_c_api/tests/e2e/testcases/global_info.rs b/crates/voicevox_core_c_api/tests/e2e/testcases/global_info.rs index 200477b02..c6ea390ed 100644 --- a/crates/voicevox_core_c_api/tests/e2e/testcases/global_info.rs +++ b/crates/voicevox_core_c_api/tests/e2e/testcases/global_info.rs @@ -5,13 +5,12 @@ use libloading::Library; use once_cell::sync::Lazy; use serde::{Deserialize, Serialize}; use serde_with::{serde_as, DisplayFromStr}; -use strum::IntoEnumIterator; +use test_util::c_api::{self, CApi, VoicevoxResultCode}; use voicevox_core::SupportedDevices; use crate::{ assert_cdylib::{self, case, Utf8Output}, snapshots, - symbols::{Symbols, VoicevoxResultCode}, }; case!(TestCase); @@ -21,25 +20,17 @@ struct TestCase; #[typetag::serde(name = "global_info")] impl assert_cdylib::TestCase for TestCase { - unsafe fn exec(&self, lib: &Library) -> anyhow::Result<()> { - let Symbols { - voicevox_get_version, - voicevox_create_supported_devices_json, - voicevox_error_result_to_message, - voicevox_json_free, - .. - } = Symbols::new(lib)?; + unsafe fn exec(&self, lib: Library) -> anyhow::Result<()> { + let lib = CApi::from_library(lib)?; std::assert_eq!( env!("CARGO_PKG_VERSION"), - CStr::from_ptr(voicevox_get_version()).to_str()?, + CStr::from_ptr(lib.voicevox_get_version()).to_str()?, ); { let mut supported_devices = MaybeUninit::uninit(); - assert_ok(voicevox_create_supported_devices_json( - supported_devices.as_mut_ptr(), - )); + assert_ok(lib.voicevox_create_supported_devices_json(supported_devices.as_mut_ptr())); let supported_devices = supported_devices.assume_init(); std::assert_eq!( SupportedDevices::create()?.to_json(), @@ -47,21 +38,45 @@ impl assert_cdylib::TestCase for TestCase { .to_str()? .parse::()?, ); - voicevox_json_free(supported_devices); + lib.voicevox_json_free(supported_devices); } - for result_code in VoicevoxResultCode::iter() { + for result_code in [ + c_api::VoicevoxResultCode_VOICEVOX_RESULT_OK, + c_api::VoicevoxResultCode_VOICEVOX_RESULT_NOT_LOADED_OPENJTALK_DICT_ERROR, + c_api::VoicevoxResultCode_VOICEVOX_RESULT_GET_SUPPORTED_DEVICES_ERROR, + c_api::VoicevoxResultCode_VOICEVOX_RESULT_GPU_SUPPORT_ERROR, + c_api::VoicevoxResultCode_VOICEVOX_RESULT_STYLE_NOT_FOUND_ERROR, + c_api::VoicevoxResultCode_VOICEVOX_RESULT_MODEL_NOT_FOUND_ERROR, + c_api::VoicevoxResultCode_VOICEVOX_RESULT_INFERENCE_ERROR, + c_api::VoicevoxResultCode_VOICEVOX_RESULT_EXTRACT_FULL_CONTEXT_LABEL_ERROR, + c_api::VoicevoxResultCode_VOICEVOX_RESULT_INVALID_UTF8_INPUT_ERROR, + c_api::VoicevoxResultCode_VOICEVOX_RESULT_PARSE_KANA_ERROR, + c_api::VoicevoxResultCode_VOICEVOX_RESULT_INVALID_AUDIO_QUERY_ERROR, + c_api::VoicevoxResultCode_VOICEVOX_RESULT_INVALID_ACCENT_PHRASE_ERROR, + c_api::VoicevoxResultCode_VOICEVOX_RESULT_OPEN_ZIP_FILE_ERROR, + c_api::VoicevoxResultCode_VOICEVOX_RESULT_READ_ZIP_ENTRY_ERROR, + c_api::VoicevoxResultCode_VOICEVOX_RESULT_MODEL_ALREADY_LOADED_ERROR, + c_api::VoicevoxResultCode_VOICEVOX_RESULT_STYLE_ALREADY_LOADED_ERROR, + c_api::VoicevoxResultCode_VOICEVOX_RESULT_INVALID_MODEL_DATA_ERROR, + c_api::VoicevoxResultCode_VOICEVOX_RESULT_LOAD_USER_DICT_ERROR, + c_api::VoicevoxResultCode_VOICEVOX_RESULT_SAVE_USER_DICT_ERROR, + c_api::VoicevoxResultCode_VOICEVOX_RESULT_USER_DICT_WORD_NOT_FOUND_ERROR, + c_api::VoicevoxResultCode_VOICEVOX_RESULT_USE_USER_DICT_ERROR, + c_api::VoicevoxResultCode_VOICEVOX_RESULT_INVALID_USER_DICT_WORD_ERROR, + c_api::VoicevoxResultCode_VOICEVOX_RESULT_INVALID_UUID_ERROR, + ] { std::assert_eq!( - SNAPSHOTS.result_messages[&(result_code as _)], + SNAPSHOTS.result_messages[&result_code], str::from_utf8( - CStr::from_ptr(voicevox_error_result_to_message(result_code)).to_bytes() + CStr::from_ptr(lib.voicevox_error_result_to_message(result_code)).to_bytes() )?, ); } return Ok(()); fn assert_ok(result_code: VoicevoxResultCode) { - std::assert_eq!(VoicevoxResultCode::VOICEVOX_RESULT_OK, result_code); + std::assert_eq!(c_api::VoicevoxResultCode_VOICEVOX_RESULT_OK, result_code); } } diff --git a/crates/voicevox_core_c_api/tests/e2e/testcases/simple_tts.rs b/crates/voicevox_core_c_api/tests/e2e/testcases/simple_tts.rs index 9631249fa..4ac4030e1 100644 --- a/crates/voicevox_core_c_api/tests/e2e/testcases/simple_tts.rs +++ b/crates/voicevox_core_c_api/tests/e2e/testcases/simple_tts.rs @@ -1,16 +1,17 @@ use std::{collections::HashMap, ffi::CString, mem::MaybeUninit}; use assert_cmd::assert::AssertResult; -use cstr::cstr; use libloading::Library; use once_cell::sync::Lazy; use serde::{Deserialize, Serialize}; -use test_util::OPEN_JTALK_DIC_DIR; +use test_util::{ + c_api::{self, CApi, VoicevoxInitializeOptions, VoicevoxResultCode}, + OPEN_JTALK_DIC_DIR, +}; use crate::{ assert_cdylib::{self, case, Utf8Output}, snapshots, - symbols::{Symbols, VoicevoxAccelerationMode, VoicevoxInitializeOptions, VoicevoxResultCode}, }; case!(TestCase { @@ -24,26 +25,13 @@ struct TestCase { #[typetag::serde(name = "simple_tts")] impl assert_cdylib::TestCase for TestCase { - unsafe fn exec(&self, lib: &Library) -> anyhow::Result<()> { - let Symbols { - voicevox_open_jtalk_rc_new, - voicevox_open_jtalk_rc_delete, - voicevox_make_default_initialize_options, - voicevox_voice_model_new_from_path, - voicevox_voice_model_delete, - voicevox_synthesizer_new, - voicevox_synthesizer_delete, - voicevox_synthesizer_load_voice_model, - voicevox_make_default_tts_options, - voicevox_synthesizer_tts, - voicevox_wav_free, - .. - } = Symbols::new(lib)?; + unsafe fn exec(&self, lib: Library) -> anyhow::Result<()> { + let lib = CApi::from_library(lib)?; let model = { let mut model = MaybeUninit::uninit(); - assert_ok(voicevox_voice_model_new_from_path( - cstr!("../../model/sample.vvm").as_ptr(), + assert_ok(lib.voicevox_voice_model_new_from_path( + c_api::SAMPLE_VOICE_MODEL_FILE_PATH.as_ptr(), model.as_mut_ptr(), )); model.assume_init() @@ -52,37 +40,37 @@ impl assert_cdylib::TestCase for TestCase { let openjtalk = { let mut openjtalk = MaybeUninit::uninit(); let open_jtalk_dic_dir = CString::new(OPEN_JTALK_DIC_DIR).unwrap(); - assert_ok(voicevox_open_jtalk_rc_new( - open_jtalk_dic_dir.as_ptr(), - openjtalk.as_mut_ptr(), - )); + assert_ok( + lib.voicevox_open_jtalk_rc_new(open_jtalk_dic_dir.as_ptr(), openjtalk.as_mut_ptr()), + ); openjtalk.assume_init() }; let synthesizer = { let mut synthesizer = MaybeUninit::uninit(); - assert_ok(voicevox_synthesizer_new( + assert_ok(lib.voicevox_synthesizer_new( openjtalk, VoicevoxInitializeOptions { - acceleration_mode: VoicevoxAccelerationMode::VOICEVOX_ACCELERATION_MODE_CPU, - ..voicevox_make_default_initialize_options() + acceleration_mode: + c_api::VoicevoxAccelerationMode_VOICEVOX_ACCELERATION_MODE_CPU, + ..lib.voicevox_make_default_initialize_options() }, synthesizer.as_mut_ptr(), )); synthesizer.assume_init() }; - assert_ok(voicevox_synthesizer_load_voice_model(synthesizer, model)); + assert_ok(lib.voicevox_synthesizer_load_voice_model(synthesizer, model)); let (wav_length, wav) = { let mut wav_length = MaybeUninit::uninit(); let mut wav = MaybeUninit::uninit(); let text = CString::new(&*self.text).unwrap(); - assert_ok(voicevox_synthesizer_tts( + assert_ok(lib.voicevox_synthesizer_tts( synthesizer, text.as_ptr(), STYLE_ID, - voicevox_make_default_tts_options(), + lib.voicevox_make_default_tts_options(), wav_length.as_mut_ptr(), wav.as_mut_ptr(), )); @@ -91,17 +79,17 @@ impl assert_cdylib::TestCase for TestCase { std::assert_eq!(SNAPSHOTS.output[&self.text].wav_length, wav_length); - voicevox_voice_model_delete(model); - voicevox_open_jtalk_rc_delete(openjtalk); - voicevox_synthesizer_delete(synthesizer); - voicevox_wav_free(wav); + lib.voicevox_voice_model_delete(model); + lib.voicevox_open_jtalk_rc_delete(openjtalk); + lib.voicevox_synthesizer_delete(synthesizer); + lib.voicevox_wav_free(wav); return Ok(()); const STYLE_ID: u32 = 0; fn assert_ok(result_code: VoicevoxResultCode) { - std::assert_eq!(VoicevoxResultCode::VOICEVOX_RESULT_OK, result_code); + std::assert_eq!(c_api::VoicevoxResultCode_VOICEVOX_RESULT_OK, result_code); } } diff --git a/crates/voicevox_core_c_api/tests/e2e/testcases/synthesizer_new_output_json.rs b/crates/voicevox_core_c_api/tests/e2e/testcases/synthesizer_new_output_json.rs index a836a409c..c27bd4703 100644 --- a/crates/voicevox_core_c_api/tests/e2e/testcases/synthesizer_new_output_json.rs +++ b/crates/voicevox_core_c_api/tests/e2e/testcases/synthesizer_new_output_json.rs @@ -4,17 +4,18 @@ use std::{ }; use assert_cmd::assert::AssertResult; -use cstr::cstr; use libloading::Library; use once_cell::sync::Lazy; use serde::{Deserialize, Serialize}; -use test_util::OPEN_JTALK_DIC_DIR; +use test_util::{ + c_api::{self, CApi, VoicevoxInitializeOptions, VoicevoxResultCode}, + OPEN_JTALK_DIC_DIR, +}; use crate::{ assert_cdylib::{self, case, Utf8Output}, snapshots, - symbols::{Symbols, VoicevoxAccelerationMode, VoicevoxInitializeOptions, VoicevoxResultCode}, }; case!(TestCase); @@ -24,37 +25,26 @@ struct TestCase; #[typetag::serde(name = "synthesizer_new_output_json")] impl assert_cdylib::TestCase for TestCase { - unsafe fn exec(&self, lib: &Library) -> anyhow::Result<()> { - let Symbols { - voicevox_make_default_initialize_options, - voicevox_voice_model_new_from_path, - voicevox_open_jtalk_rc_new, - voicevox_open_jtalk_rc_delete, - voicevox_synthesizer_new, - voicevox_synthesizer_delete, - voicevox_synthesizer_create_metas_json, - voicevox_synthesizer_load_voice_model, - voicevox_json_free, - .. - } = Symbols::new(lib)?; + unsafe fn exec(&self, lib: Library) -> anyhow::Result<()> { + let lib = CApi::from_library(lib)?; let openjtalk = { let mut openjtalk = MaybeUninit::uninit(); let open_jtalk_dic_dir = CString::new(OPEN_JTALK_DIC_DIR).unwrap(); - assert_ok(voicevox_open_jtalk_rc_new( - open_jtalk_dic_dir.as_ptr(), - openjtalk.as_mut_ptr(), - )); + assert_ok( + lib.voicevox_open_jtalk_rc_new(open_jtalk_dic_dir.as_ptr(), openjtalk.as_mut_ptr()), + ); openjtalk.assume_init() }; let synthesizer = { let mut synthesizer = MaybeUninit::uninit(); - assert_ok(voicevox_synthesizer_new( + assert_ok(lib.voicevox_synthesizer_new( openjtalk, VoicevoxInitializeOptions { - acceleration_mode: VoicevoxAccelerationMode::VOICEVOX_ACCELERATION_MODE_CPU, - ..voicevox_make_default_initialize_options() + acceleration_mode: + c_api::VoicevoxAccelerationMode_VOICEVOX_ACCELERATION_MODE_CPU, + ..lib.voicevox_make_default_initialize_options() }, synthesizer.as_mut_ptr(), )); @@ -63,32 +53,32 @@ impl assert_cdylib::TestCase for TestCase { let model = { let mut model = MaybeUninit::uninit(); - assert_ok(voicevox_voice_model_new_from_path( - cstr!("../../model/sample.vvm").as_ptr(), + assert_ok(lib.voicevox_voice_model_new_from_path( + c_api::SAMPLE_VOICE_MODEL_FILE_PATH.as_ptr(), model.as_mut_ptr(), )); model.assume_init() }; - assert_ok(voicevox_synthesizer_load_voice_model(synthesizer, model)); + assert_ok(lib.voicevox_synthesizer_load_voice_model(synthesizer, model)); let metas_json = { - let raw = voicevox_synthesizer_create_metas_json(synthesizer); + let raw = lib.voicevox_synthesizer_create_metas_json(synthesizer); let metas_json = &CStr::from_ptr(raw).to_str()?.parse::()?; let metas_json = serde_json::to_string_pretty(metas_json).unwrap(); - voicevox_json_free(raw); + lib.voicevox_json_free(raw); metas_json }; std::assert_eq!(SNAPSHOTS.metas, metas_json); - voicevox_open_jtalk_rc_delete(openjtalk); - voicevox_synthesizer_delete(synthesizer); + lib.voicevox_open_jtalk_rc_delete(openjtalk); + lib.voicevox_synthesizer_delete(synthesizer); return Ok(()); fn assert_ok(result_code: VoicevoxResultCode) { - std::assert_eq!(VoicevoxResultCode::VOICEVOX_RESULT_OK, result_code); + std::assert_eq!(c_api::VoicevoxResultCode_VOICEVOX_RESULT_OK, result_code); } } 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 6cfe7994e..d380b71b2 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 @@ -1,16 +1,17 @@ use std::{collections::HashMap, ffi::CString, mem::MaybeUninit}; use assert_cmd::assert::AssertResult; -use cstr::cstr; use libloading::Library; use once_cell::sync::Lazy; use serde::{Deserialize, Serialize}; -use test_util::OPEN_JTALK_DIC_DIR; +use test_util::{ + c_api::{self, CApi, VoicevoxInitializeOptions, VoicevoxResultCode}, + OPEN_JTALK_DIC_DIR, +}; use crate::{ assert_cdylib::{self, case, Utf8Output}, snapshots, - symbols::{Symbols, VoicevoxAccelerationMode, VoicevoxInitializeOptions, VoicevoxResultCode}, }; case!(TestCase { @@ -24,28 +25,13 @@ struct TestCase { #[typetag::serde(name = "tts_via_audio_query")] impl assert_cdylib::TestCase for TestCase { - unsafe fn exec(&self, lib: &Library) -> anyhow::Result<()> { - let Symbols { - voicevox_open_jtalk_rc_new, - voicevox_open_jtalk_rc_delete, - voicevox_make_default_initialize_options, - voicevox_voice_model_new_from_path, - voicevox_voice_model_delete, - voicevox_synthesizer_new, - voicevox_synthesizer_delete, - voicevox_synthesizer_load_voice_model, - voicevox_synthesizer_create_audio_query, - voicevox_make_default_synthesis_options, - voicevox_synthesizer_synthesis, - voicevox_json_free, - voicevox_wav_free, - .. - } = Symbols::new(lib)?; + unsafe fn exec(&self, lib: Library) -> anyhow::Result<()> { + let lib = CApi::from_library(lib)?; let model = { let mut model = MaybeUninit::uninit(); - assert_ok(voicevox_voice_model_new_from_path( - cstr!("../../model/sample.vvm").as_ptr(), + assert_ok(lib.voicevox_voice_model_new_from_path( + c_api::SAMPLE_VOICE_MODEL_FILE_PATH.as_ptr(), model.as_mut_ptr(), )); model.assume_init() @@ -54,32 +40,32 @@ impl assert_cdylib::TestCase for TestCase { let openjtalk = { let mut openjtalk = MaybeUninit::uninit(); let open_jtalk_dic_dir = CString::new(OPEN_JTALK_DIC_DIR).unwrap(); - assert_ok(voicevox_open_jtalk_rc_new( - open_jtalk_dic_dir.as_ptr(), - openjtalk.as_mut_ptr(), - )); + assert_ok( + lib.voicevox_open_jtalk_rc_new(open_jtalk_dic_dir.as_ptr(), openjtalk.as_mut_ptr()), + ); openjtalk.assume_init() }; let synthesizer = { let mut synthesizer = MaybeUninit::uninit(); - assert_ok(voicevox_synthesizer_new( + assert_ok(lib.voicevox_synthesizer_new( openjtalk, VoicevoxInitializeOptions { - acceleration_mode: VoicevoxAccelerationMode::VOICEVOX_ACCELERATION_MODE_CPU, - ..voicevox_make_default_initialize_options() + acceleration_mode: + c_api::VoicevoxAccelerationMode_VOICEVOX_ACCELERATION_MODE_CPU, + ..lib.voicevox_make_default_initialize_options() }, synthesizer.as_mut_ptr(), )); synthesizer.assume_init() }; - assert_ok(voicevox_synthesizer_load_voice_model(synthesizer, model)); + assert_ok(lib.voicevox_synthesizer_load_voice_model(synthesizer, model)); let audio_query = { let mut audio_query = MaybeUninit::uninit(); let text = CString::new(&*self.text).unwrap(); - assert_ok(voicevox_synthesizer_create_audio_query( + assert_ok(lib.voicevox_synthesizer_create_audio_query( synthesizer, text.as_ptr(), STYLE_ID, @@ -91,11 +77,11 @@ impl assert_cdylib::TestCase for TestCase { let (wav_length, wav) = { let mut wav_length = MaybeUninit::uninit(); let mut wav = MaybeUninit::uninit(); - assert_ok(voicevox_synthesizer_synthesis( + assert_ok(lib.voicevox_synthesizer_synthesis( synthesizer, audio_query, STYLE_ID, - voicevox_make_default_synthesis_options(), + lib.voicevox_make_default_synthesis_options(), wav_length.as_mut_ptr(), wav.as_mut_ptr(), )); @@ -104,18 +90,18 @@ impl assert_cdylib::TestCase for TestCase { std::assert_eq!(SNAPSHOTS.output[&self.text].wav_length, wav_length); - voicevox_voice_model_delete(model); - voicevox_open_jtalk_rc_delete(openjtalk); - voicevox_synthesizer_delete(synthesizer); - voicevox_json_free(audio_query); - voicevox_wav_free(wav); + lib.voicevox_voice_model_delete(model); + lib.voicevox_open_jtalk_rc_delete(openjtalk); + lib.voicevox_synthesizer_delete(synthesizer); + lib.voicevox_json_free(audio_query); + lib.voicevox_wav_free(wav); return Ok(()); const STYLE_ID: u32 = 0; fn assert_ok(result_code: VoicevoxResultCode) { - std::assert_eq!(VoicevoxResultCode::VOICEVOX_RESULT_OK, result_code); + std::assert_eq!(c_api::VoicevoxResultCode_VOICEVOX_RESULT_OK, result_code); } } 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 7c2e13afe..2e6875e97 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 @@ -1,7 +1,6 @@ // ユーザー辞書の登録によって読みが変化することを確認するテスト。 // 辞書ロード前後でAudioQueryのkanaが変化するかどうかで確認する。 -use crate::symbols::{VoicevoxInitializeOptions, VoicevoxResultCode}; use assert_cmd::assert::AssertResult; use once_cell::sync::Lazy; use std::ffi::{CStr, CString}; @@ -11,11 +10,11 @@ use test_util::OPEN_JTALK_DIC_DIR; use cstr::cstr; use libloading::Library; use serde::{Deserialize, Serialize}; +use test_util::c_api::{self, CApi, VoicevoxInitializeOptions, VoicevoxResultCode}; use crate::{ assert_cdylib::{self, case, Utf8Output}, snapshots, - symbols::{Symbols, VoicevoxAccelerationMode, VoicevoxUserDictWordType}, }; case!(TestCase); @@ -25,46 +24,31 @@ struct TestCase; #[typetag::serde(name = "user_dict_load")] impl assert_cdylib::TestCase for TestCase { - unsafe fn exec(&self, lib: &Library) -> anyhow::Result<()> { - let Symbols { - voicevox_user_dict_word_make, - voicevox_user_dict_new, - voicevox_user_dict_add_word, - voicevox_user_dict_delete, - voicevox_make_default_initialize_options, - voicevox_open_jtalk_rc_new, - voicevox_open_jtalk_rc_use_user_dict, - voicevox_open_jtalk_rc_delete, - voicevox_voice_model_new_from_path, - voicevox_voice_model_delete, - voicevox_synthesizer_new, - voicevox_synthesizer_delete, - voicevox_synthesizer_load_voice_model, - voicevox_synthesizer_create_audio_query, - .. - } = Symbols::new(lib)?; - - let dict = voicevox_user_dict_new(); + unsafe fn exec(&self, lib: Library) -> anyhow::Result<()> { + let lib = CApi::from_library(lib)?; + + let dict = lib.voicevox_user_dict_new(); let mut word_uuid = [0u8; 16]; let word = { - let mut word = voicevox_user_dict_word_make( + let mut word = lib.voicevox_user_dict_word_make( cstr!("this_word_should_not_exist_in_default_dictionary").as_ptr(), cstr!("アイウエオ").as_ptr(), ); - word.word_type = VoicevoxUserDictWordType::VOICEVOX_USER_DICT_WORD_TYPE_PROPER_NOUN; + word.word_type = + c_api::VoicevoxUserDictWordType_VOICEVOX_USER_DICT_WORD_TYPE_PROPER_NOUN; word.priority = 10; word }; - assert_ok(voicevox_user_dict_add_word(dict, &word, &mut word_uuid)); + assert_ok(lib.voicevox_user_dict_add_word(dict, &word, &mut word_uuid)); let model = { let mut model = MaybeUninit::uninit(); - assert_ok(voicevox_voice_model_new_from_path( - cstr!("../../model/sample.vvm").as_ptr(), + assert_ok(lib.voicevox_voice_model_new_from_path( + c_api::SAMPLE_VOICE_MODEL_FILE_PATH.as_ptr(), model.as_mut_ptr(), )); model.assume_init() @@ -73,30 +57,30 @@ impl assert_cdylib::TestCase for TestCase { let openjtalk = { let mut openjtalk = MaybeUninit::uninit(); let open_jtalk_dic_dir = CString::new(OPEN_JTALK_DIC_DIR).unwrap(); - assert_ok(voicevox_open_jtalk_rc_new( - open_jtalk_dic_dir.as_ptr(), - openjtalk.as_mut_ptr(), - )); + assert_ok( + lib.voicevox_open_jtalk_rc_new(open_jtalk_dic_dir.as_ptr(), openjtalk.as_mut_ptr()), + ); openjtalk.assume_init() }; let synthesizer = { let mut synthesizer = MaybeUninit::uninit(); - assert_ok(voicevox_synthesizer_new( + assert_ok(lib.voicevox_synthesizer_new( openjtalk, VoicevoxInitializeOptions { - acceleration_mode: VoicevoxAccelerationMode::VOICEVOX_ACCELERATION_MODE_CPU, - ..voicevox_make_default_initialize_options() + acceleration_mode: + c_api::VoicevoxAccelerationMode_VOICEVOX_ACCELERATION_MODE_CPU, + ..lib.voicevox_make_default_initialize_options() }, synthesizer.as_mut_ptr(), )); synthesizer.assume_init() }; - assert_ok(voicevox_synthesizer_load_voice_model(synthesizer, model)); + assert_ok(lib.voicevox_synthesizer_load_voice_model(synthesizer, model)); let mut audio_query_without_dict = std::ptr::null_mut(); - assert_ok(voicevox_synthesizer_create_audio_query( + assert_ok(lib.voicevox_synthesizer_create_audio_query( synthesizer, cstr!("this_word_should_not_exist_in_default_dictionary").as_ptr(), STYLE_ID, @@ -106,10 +90,10 @@ impl assert_cdylib::TestCase for TestCase { CStr::from_ptr(audio_query_without_dict).to_str()?, )?; - assert_ok(voicevox_open_jtalk_rc_use_user_dict(openjtalk, dict)); + assert_ok(lib.voicevox_open_jtalk_rc_use_user_dict(openjtalk, dict)); let mut audio_query_with_dict = std::ptr::null_mut(); - assert_ok(voicevox_synthesizer_create_audio_query( + assert_ok(lib.voicevox_synthesizer_create_audio_query( synthesizer, cstr!("this_word_should_not_exist_in_default_dictionary").as_ptr(), STYLE_ID, @@ -125,15 +109,15 @@ impl assert_cdylib::TestCase for TestCase { audio_query_with_dict.get("kana") ); - voicevox_voice_model_delete(model); - voicevox_open_jtalk_rc_delete(openjtalk); - voicevox_synthesizer_delete(synthesizer); - voicevox_user_dict_delete(dict); + lib.voicevox_voice_model_delete(model); + lib.voicevox_open_jtalk_rc_delete(openjtalk); + lib.voicevox_synthesizer_delete(synthesizer); + lib.voicevox_user_dict_delete(dict); return Ok(()); fn assert_ok(result_code: VoicevoxResultCode) { - std::assert_eq!(VoicevoxResultCode::VOICEVOX_RESULT_OK, result_code); + std::assert_eq!(c_api::VoicevoxResultCode_VOICEVOX_RESULT_OK, result_code); } const STYLE_ID: u32 = 0; } diff --git a/crates/voicevox_core_c_api/tests/e2e/testcases/user_dict_manipulate.rs b/crates/voicevox_core_c_api/tests/e2e/testcases/user_dict_manipulate.rs index 15b80686f..fd3d575e3 100644 --- a/crates/voicevox_core_c_api/tests/e2e/testcases/user_dict_manipulate.rs +++ b/crates/voicevox_core_c_api/tests/e2e/testcases/user_dict_manipulate.rs @@ -12,11 +12,11 @@ use uuid::Uuid; use cstr::cstr; use libloading::Library; use serde::{Deserialize, Serialize}; +use test_util::c_api::{self, CApi, VoicevoxResultCode, VoicevoxUserDict, VoicevoxUserDictWord}; use crate::{ assert_cdylib::{self, case, Utf8Output}, snapshots, - symbols::{Symbols, VoicevoxResultCode, VoicevoxUserDict, VoicevoxUserDictWord}, }; case!(TestCase); @@ -26,35 +26,19 @@ struct TestCase; #[typetag::serde(name = "user_dict_manipulate")] impl assert_cdylib::TestCase for TestCase { - unsafe fn exec(&self, lib: &Library) -> anyhow::Result<()> { - let Symbols { - voicevox_user_dict_word_make, - voicevox_user_dict_new, - voicevox_user_dict_add_word, - voicevox_user_dict_update_word, - voicevox_user_dict_remove_word, - voicevox_user_dict_to_json, - voicevox_user_dict_import, - voicevox_user_dict_load, - voicevox_user_dict_save, - voicevox_user_dict_delete, - voicevox_json_free, - .. - } = Symbols::new(lib)?; + unsafe fn exec(&self, lib: Library) -> anyhow::Result<()> { + let lib = CApi::from_library(lib)?; let get_json = |dict: &*mut VoicevoxUserDict| -> String { let mut json = MaybeUninit::uninit(); - assert_ok(voicevox_user_dict_to_json( - (*dict) as *const _, - json.as_mut_ptr(), - )); + assert_ok(lib.voicevox_user_dict_to_json((*dict) as *const _, json.as_mut_ptr())); let ret = CStr::from_ptr(json.assume_init()) .to_str() .unwrap() .to_string(); - voicevox_json_free(json.assume_init()); + lib.voicevox_json_free(json.assume_init()); serde_json::from_str::(&ret).expect("invalid json"); @@ -64,20 +48,16 @@ impl assert_cdylib::TestCase for TestCase { let add_word = |dict: *const VoicevoxUserDict, word: &VoicevoxUserDictWord| -> Uuid { let mut word_uuid = [0u8; 16]; - assert_ok(voicevox_user_dict_add_word( - dict, - word as *const _, - &mut word_uuid, - )); + assert_ok(lib.voicevox_user_dict_add_word(dict, word as *const _, &mut word_uuid)); Uuid::from_slice(&word_uuid).expect("invalid uuid") }; // テスト用の辞書ファイルを作成 - let dict = voicevox_user_dict_new(); + let dict = lib.voicevox_user_dict_new(); // 単語の追加のテスト - let word = voicevox_user_dict_word_make(cstr!("hoge").as_ptr(), cstr!("ホゲ").as_ptr()); + let word = lib.voicevox_user_dict_word_make(cstr!("hoge").as_ptr(), cstr!("ホゲ").as_ptr()); let word_uuid = add_word(dict, &word); @@ -88,13 +68,9 @@ impl assert_cdylib::TestCase for TestCase { assert_contains_uuid(&json, &word_uuid); // 単語の変更のテスト - let word = voicevox_user_dict_word_make(cstr!("fuga").as_ptr(), cstr!("フガ").as_ptr()); + let word = lib.voicevox_user_dict_word_make(cstr!("fuga").as_ptr(), cstr!("フガ").as_ptr()); - assert_ok(voicevox_user_dict_update_word( - dict, - &word_uuid.into_bytes(), - &word, - )); + assert_ok(lib.voicevox_user_dict_update_word(dict, &word_uuid.into_bytes(), &word)); let json = get_json(&dict); @@ -105,14 +81,14 @@ impl assert_cdylib::TestCase for TestCase { assert_contains_uuid(&json, &word_uuid); // 辞書のインポートのテスト。 - let other_dict = voicevox_user_dict_new(); + let other_dict = lib.voicevox_user_dict_new(); let other_word = - voicevox_user_dict_word_make(cstr!("piyo").as_ptr(), cstr!("ピヨ").as_ptr()); + lib.voicevox_user_dict_word_make(cstr!("piyo").as_ptr(), cstr!("ピヨ").as_ptr()); let other_word_uuid = add_word(other_dict, &other_word); - assert_ok(voicevox_user_dict_import(dict, other_dict)); + assert_ok(lib.voicevox_user_dict_import(dict, other_dict)); let json = get_json(&dict); assert!(json.contains("fuga")); @@ -123,10 +99,7 @@ impl assert_cdylib::TestCase for TestCase { assert_contains_uuid(&json, &other_word_uuid); // 単語の削除のテスト - assert_ok(voicevox_user_dict_remove_word( - dict, - &word_uuid.into_bytes(), - )); + assert_ok(lib.voicevox_user_dict_remove_word(dict, &word_uuid.into_bytes())); let json = get_json(&dict); assert_not_contains_uuid(&json, &word_uuid); @@ -136,23 +109,23 @@ impl assert_cdylib::TestCase for TestCase { // 辞書のセーブ・ロードのテスト let temp_path = NamedTempFile::new().unwrap().into_temp_path(); let temp_path = CString::new(temp_path.to_str().unwrap()).unwrap(); - let word = voicevox_user_dict_word_make(cstr!("hoge").as_ptr(), cstr!("ホゲ").as_ptr()); + let word = lib.voicevox_user_dict_word_make(cstr!("hoge").as_ptr(), cstr!("ホゲ").as_ptr()); let word_uuid = add_word(dict, &word); - assert_ok(voicevox_user_dict_save(dict, temp_path.as_ptr())); - assert_ok(voicevox_user_dict_load(other_dict, temp_path.as_ptr())); + assert_ok(lib.voicevox_user_dict_save(dict, temp_path.as_ptr())); + assert_ok(lib.voicevox_user_dict_load(other_dict, temp_path.as_ptr())); let json = get_json(&other_dict); assert_contains_uuid(&json, &word_uuid); assert_contains_uuid(&json, &other_word_uuid); - voicevox_user_dict_delete(dict); - voicevox_user_dict_delete(other_dict); + lib.voicevox_user_dict_delete(dict); + lib.voicevox_user_dict_delete(other_dict); return Ok(()); fn assert_ok(result_code: VoicevoxResultCode) { - std::assert_eq!(VoicevoxResultCode::VOICEVOX_RESULT_OK, result_code); + std::assert_eq!(c_api::VoicevoxResultCode_VOICEVOX_RESULT_OK, result_code); } fn assert_contains_uuid(text: &str, pattern: &Uuid) { diff --git a/crates/voicevox_core_c_api/xcframework/Frameworks/aarch64/voicevox_core.framework/Info.plist b/crates/voicevox_core_c_api/xcframework/Frameworks/aarch64/voicevox_core.framework/Info.plist new file mode 100644 index 000000000..f3f359d09 --- /dev/null +++ b/crates/voicevox_core_c_api/xcframework/Frameworks/aarch64/voicevox_core.framework/Info.plist @@ -0,0 +1,55 @@ + + + + + BuildMachineOSBuild + 23B81 + CFBundleDevelopmentRegion + en + CFBundleExecutable + voicevox_core + CFBundleIdentifier + jp.hiroshiba.voicevox.voicevox-core + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + voicevox_core + CFBundlePackageType + FMWK + CFBundleShortVersionString + 1.0 + CFBundleSupportedPlatforms + + iPhoneOS + + CFBundleVersion + 1 + DTCompiler + com.apple.compilers.llvm.clang.1_0 + DTPlatformBuild + 21C52 + DTPlatformName + iphoneos + DTPlatformVersion + 17.2 + DTSDKBuild + 21C52 + DTSDKName + iphoneos17.2 + DTXcode + 1510 + DTXcodeBuild + 15C65 + MinimumOSVersion + 16.2 + UIDeviceFamily + + 1 + 2 + + UIRequiredDeviceCapabilities + + arm64 + + + diff --git a/crates/voicevox_core_c_api/xcframework/Frameworks/aarch64/voicevox_core.framework/Modules/module.modulemap b/crates/voicevox_core_c_api/xcframework/Frameworks/aarch64/voicevox_core.framework/Modules/module.modulemap new file mode 100644 index 000000000..a4812ac0c --- /dev/null +++ b/crates/voicevox_core_c_api/xcframework/Frameworks/aarch64/voicevox_core.framework/Modules/module.modulemap @@ -0,0 +1,6 @@ +framework module voicevox_core { + umbrella header "voicevox_core.h" + export * + + module * { export * } +} diff --git a/crates/voicevox_core_c_api/xcframework/Frameworks/sim/voicevox_core.framework/Info.plist b/crates/voicevox_core_c_api/xcframework/Frameworks/sim/voicevox_core.framework/Info.plist new file mode 100644 index 000000000..dea41a54d --- /dev/null +++ b/crates/voicevox_core_c_api/xcframework/Frameworks/sim/voicevox_core.framework/Info.plist @@ -0,0 +1,51 @@ + + + + + BuildMachineOSBuild + 23B81 + CFBundleDevelopmentRegion + en + CFBundleExecutable + voicevox_core + CFBundleIdentifier + jp.hiroshiba.voicevox.voicevox-core + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + voicevox_core + CFBundlePackageType + FMWK + CFBundleShortVersionString + 1.0 + CFBundleSupportedPlatforms + + iPhoneSimulator + + CFBundleVersion + 1 + DTCompiler + com.apple.compilers.llvm.clang.1_0 + DTPlatformBuild + 21C52 + DTPlatformName + iphonesimulator + DTPlatformVersion + 17.2 + DTSDKBuild + 21C52 + DTSDKName + iphonesimulator17.2 + DTXcode + 1510 + DTXcodeBuild + 15C65 + MinimumOSVersion + 16.2 + UIDeviceFamily + + 1 + 2 + + + diff --git a/crates/voicevox_core_c_api/xcframework/Frameworks/sim/voicevox_core.framework/Modules/module.modulemap b/crates/voicevox_core_c_api/xcframework/Frameworks/sim/voicevox_core.framework/Modules/module.modulemap new file mode 100644 index 000000000..a4812ac0c --- /dev/null +++ b/crates/voicevox_core_c_api/xcframework/Frameworks/sim/voicevox_core.framework/Modules/module.modulemap @@ -0,0 +1,6 @@ +framework module voicevox_core { + umbrella header "voicevox_core.h" + export * + + module * { export * } +} diff --git a/crates/voicevox_core_c_api/xcframework/Headers/README.md b/crates/voicevox_core_c_api/xcframework/Headers/README.md deleted file mode 100644 index cd3b3f6a0..000000000 --- a/crates/voicevox_core_c_api/xcframework/Headers/README.md +++ /dev/null @@ -1,5 +0,0 @@ -# xcframeworkのHeadersに追加されるファイル -## module.modulemap - -C言語やObjective-Cのモジュールが、ライブラリ内の公開インターフェースを表現するために使用されます。 -これにより、外部のコードがこのライブラリを利用する際に、必要なインクルードや参照を容易にすることができます。 diff --git a/crates/voicevox_core_c_api/xcframework/Headers/module.modulemap b/crates/voicevox_core_c_api/xcframework/Headers/module.modulemap deleted file mode 100644 index 1b1680b2d..000000000 --- a/crates/voicevox_core_c_api/xcframework/Headers/module.modulemap +++ /dev/null @@ -1,4 +0,0 @@ -module VoicevoxCore { - header "voicevox_core.h" - export * -} diff --git a/crates/voicevox_core_c_api/xcframework/README.md b/crates/voicevox_core_c_api/xcframework/README.md new file mode 100644 index 000000000..3dda8f3a7 --- /dev/null +++ b/crates/voicevox_core_c_api/xcframework/README.md @@ -0,0 +1,6 @@ +# xcframeworkフォルダの内容について +## Frameworks + +iOS向けの配布ライブラリXCFramework内のFrameworkを作るための雛形です。 +雛形は端末用とシミュレータ用の2種類です。 + diff --git a/crates/voicevox_core_java_api/Cargo.toml b/crates/voicevox_core_java_api/Cargo.toml index 887813685..06b2af618 100644 --- a/crates/voicevox_core_java_api/Cargo.toml +++ b/crates/voicevox_core_java_api/Cargo.toml @@ -8,6 +8,7 @@ publish.workspace = true crate-type = ["cdylib"] [features] +cuda = ["voicevox_core/cuda"] directml = ["voicevox_core/directml"] [dependencies] 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 05c1a11b2..576629515 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 @@ -4,6 +4,7 @@ import com.google.gson.annotations.Expose; import com.google.gson.annotations.SerializedName; import jakarta.annotation.Nonnull; +import jakarta.annotation.Nullable; /** 音声モデル。 */ public class VoiceModel extends Dll { @@ -68,6 +69,16 @@ public static class SpeakerMeta { @Nonnull public final String version; + /** + * 話者の順番。 + * + *

{@code SpeakerMeta}の列は、この値に対して昇順に並んでいるべきである。 + */ + @SerializedName("order") + @Expose + @Nullable + public final Integer order; + private SpeakerMeta() { // GSONからコンストラクトするため、このメソッドは呼ばれることは無い。 // このメソッドは@Nonnullを満たすために必要。 @@ -75,6 +86,7 @@ private SpeakerMeta() { this.styles = new StyleMeta[0]; this.speakerUuid = ""; this.version = ""; + this.order = null; } } @@ -91,9 +103,35 @@ public static class StyleMeta { @Expose public final int id; + /** スタイルに対応するモデルの種類。 */ + @SerializedName("type") + @Expose + @Nonnull + public final StyleType type; + + /** + * 話者の順番。 + * + *

{@link SpeakerMeta#styles}の列は、この値に対して昇順に並んでいるべきである。 + */ + @SerializedName("order") + @Expose + @Nullable + public final Integer order; + private StyleMeta() { this.name = ""; this.id = 0; + this.type = StyleType.TALK; + this.order = null; } } + + /** スタイル(style)に対応するモデルの種類。 */ + public static enum StyleType { + /** 音声合成クエリの作成と音声合成が可能。 */ + @SerializedName("talk") + @Expose + TALK, + } } diff --git a/crates/voicevox_core_java_api/lib/src/main/java/jp/hiroshiba/voicevoxcore/exceptions/InvalidModelFormatException.java b/crates/voicevox_core_java_api/lib/src/main/java/jp/hiroshiba/voicevoxcore/exceptions/InvalidModelFormatException.java new file mode 100644 index 000000000..b82016164 --- /dev/null +++ b/crates/voicevox_core_java_api/lib/src/main/java/jp/hiroshiba/voicevoxcore/exceptions/InvalidModelFormatException.java @@ -0,0 +1,14 @@ +package jp.hiroshiba.voicevoxcore.exceptions; + +import java.io.IOException; + +/** モデルの形式が不正。 */ +public class InvalidModelFormatException extends IOException { + public InvalidModelFormatException(String message) { + super(message); + } + + public InvalidModelFormatException(String message, Throwable cause) { + super(message, cause); + } +} diff --git a/crates/voicevox_core_java_api/lib/src/test/java/jp/hiroshiba/voicevoxcore/MetaTest.java b/crates/voicevox_core_java_api/lib/src/test/java/jp/hiroshiba/voicevoxcore/MetaTest.java index 741f84e79..60df7359f 100644 --- a/crates/voicevox_core_java_api/lib/src/test/java/jp/hiroshiba/voicevoxcore/MetaTest.java +++ b/crates/voicevox_core_java_api/lib/src/test/java/jp/hiroshiba/voicevoxcore/MetaTest.java @@ -13,7 +13,7 @@ class MetaTest { void checkLoad() { // cwdはvoicevox_core/crates/voicevox_core_java_api/lib String cwd = System.getProperty("user.dir"); - File path = new File(cwd + "/../../../model/sample.vvm"); + File path = new File(cwd + "/../../test_util/data/model/sample.vvm"); VoiceModel model = new VoiceModel(path.getAbsolutePath()); assertNotNull(model.metas); } diff --git a/crates/voicevox_core_java_api/lib/src/test/java/jp/hiroshiba/voicevoxcore/TestUtils.java b/crates/voicevox_core_java_api/lib/src/test/java/jp/hiroshiba/voicevoxcore/TestUtils.java index 670eddbdb..032c38a3d 100644 --- a/crates/voicevox_core_java_api/lib/src/test/java/jp/hiroshiba/voicevoxcore/TestUtils.java +++ b/crates/voicevox_core_java_api/lib/src/test/java/jp/hiroshiba/voicevoxcore/TestUtils.java @@ -6,7 +6,7 @@ class TestUtils { VoiceModel loadModel() { // cwdはvoicevox_core/crates/voicevox_core_java_api/lib String cwd = System.getProperty("user.dir"); - File path = new File(cwd + "/../../../model/sample.vvm"); + File path = new File(cwd + "/../../test_util/data/model/sample.vvm"); try { return new VoiceModel(path.getCanonicalPath()); diff --git a/crates/voicevox_core_java_api/settings.gradle b/crates/voicevox_core_java_api/settings.gradle index 20a5e2c6a..75f5810ac 100644 --- a/crates/voicevox_core_java_api/settings.gradle +++ b/crates/voicevox_core_java_api/settings.gradle @@ -40,5 +40,5 @@ gradle.ext { gsonVersion = '2.10.1' jakartaValidationVersion = '3.0.2' jakartaAnnotationVersion = '2.1.1' - onnxruntimeVersion = '1.14.0' + onnxruntimeVersion = '1.17.3' } diff --git a/crates/voicevox_core_java_api/src/common.rs b/crates/voicevox_core_java_api/src/common.rs index c2987e207..bdb37f4ff 100644 --- a/crates/voicevox_core_java_api/src/common.rs +++ b/crates/voicevox_core_java_api/src/common.rs @@ -1,70 +1,7 @@ use std::{error::Error as _, iter}; use derive_more::From; -use jni::{ - objects::{JObject, JThrowable}, - JNIEnv, -}; - -// FIXME: 別ファイルに分離する -#[no_mangle] -extern "system" fn Java_jp_hiroshiba_voicevoxcore_Dll_00024LoggerInitializer_initLogger( - _: JNIEnv<'_>, - _: JObject<'_>, -) { - if cfg!(target_os = "android") { - android_logger::init_once( - android_logger::Config::default() - .with_tag("VoicevoxCore") - .with_filter( - android_logger::FilterBuilder::new() - .parse("error,voicevox_core=info,voicevox_core_java_api=info,onnxruntime=error") - .build(), - ), - ); - } else { - // TODO: Android以外でのログ出力を良い感じにする。(System.Loggerを使う?) - use chrono::SecondsFormat; - use std::{ - env, fmt, - io::{self, IsTerminal, Write}, - }; - use tracing_subscriber::{fmt::format::Writer, EnvFilter}; - - // FIXME: `try_init` → `init` (subscriberは他に存在しないはずなので) - let _ = tracing_subscriber::fmt() - .with_env_filter(if env::var_os(EnvFilter::DEFAULT_ENV).is_some() { - EnvFilter::from_default_env() - } else { - "error,voicevox_core=info,voicevox_core_c_api=info,onnxruntime=error".into() - }) - .with_timer(local_time as fn(&mut Writer<'_>) -> _) - .with_ansi(out().is_terminal() && env_allows_ansi()) - .with_writer(out) - .try_init(); - - fn local_time(wtr: &mut Writer<'_>) -> fmt::Result { - // ローカル時刻で表示はするが、そのフォーマットはtracing-subscriber本来のものに近いようにする。 - // https://github.com/tokio-rs/tracing/blob/tracing-subscriber-0.3.16/tracing-subscriber/src/fmt/time/datetime.rs#L235-L241 - wtr.write_str(&chrono::Local::now().to_rfc3339_opts(SecondsFormat::Micros, false)) - } - - fn out() -> impl IsTerminal + Write { - 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() - } - } -} +use jni::{objects::JThrowable, JNIEnv}; #[macro_export] macro_rules! object { @@ -92,7 +29,7 @@ macro_rules! enum_object { }; } -pub fn throw_if_err(mut env: JNIEnv<'_>, fallback: T, inner: F) -> T +pub(crate) fn throw_if_err(mut env: JNIEnv<'_>, fallback: T, inner: F) -> T where F: FnOnce(&mut JNIEnv<'_>) -> Result, { @@ -132,6 +69,7 @@ where GpuSupport, OpenZipFile, ReadZipEntry, + InvalidModelFormat, ModelAlreadyLoaded, StyleAlreadyLoaded, InvalidModelData, @@ -218,7 +156,7 @@ where } #[derive(From, Debug)] -pub enum JavaApiError { +pub(crate) enum JavaApiError { #[from] RustApi(voicevox_core::Error), diff --git a/crates/voicevox_core_java_api/src/lib.rs b/crates/voicevox_core_java_api/src/lib.rs index 4fdea9cab..9615f0a94 100644 --- a/crates/voicevox_core_java_api/src/lib.rs +++ b/crates/voicevox_core_java_api/src/lib.rs @@ -1,5 +1,6 @@ mod common; mod info; +mod logger; mod open_jtalk; mod synthesizer; mod user_dict; diff --git a/crates/voicevox_core_java_api/src/logger.rs b/crates/voicevox_core_java_api/src/logger.rs new file mode 100644 index 000000000..30545725e --- /dev/null +++ b/crates/voicevox_core_java_api/src/logger.rs @@ -0,0 +1,62 @@ +use jni::{objects::JObject, JNIEnv}; + +#[no_mangle] +extern "system" fn Java_jp_hiroshiba_voicevoxcore_Dll_00024LoggerInitializer_initLogger( + _: JNIEnv<'_>, + _: JObject<'_>, +) { + if cfg!(target_os = "android") { + android_logger::init_once( + android_logger::Config::default() + .with_tag("VoicevoxCore") + .with_filter( + android_logger::FilterBuilder::new() + // FIXME: ortも`warn`は出すべき + .parse("error,voicevox_core=info,voicevox_core_java_api=info,ort=error") + .build(), + ), + ); + } else { + // TODO: Android以外でのログ出力を良い感じにする。(System.Loggerを使う?) + use chrono::SecondsFormat; + use std::{ + env, fmt, + io::{self, IsTerminal, Write}, + }; + use tracing_subscriber::{fmt::format::Writer, EnvFilter}; + + // FIXME: `try_init` → `init` (subscriberは他に存在しないはずなので) + let _ = tracing_subscriber::fmt() + .with_env_filter(if env::var_os(EnvFilter::DEFAULT_ENV).is_some() { + EnvFilter::from_default_env() + } else { + // FIXME: `c_api`じゃないし、ortも`warn`は出すべき + "error,voicevox_core=info,voicevox_core_c_api=info,ort=error".into() + }) + .with_timer(local_time as fn(&mut Writer<'_>) -> _) + .with_ansi(out().is_terminal() && env_allows_ansi()) + .with_writer(out) + .try_init(); + + fn local_time(wtr: &mut Writer<'_>) -> fmt::Result { + // ローカル時刻で表示はするが、そのフォーマットはtracing-subscriber本来のものに近いようにする。 + // https://github.com/tokio-rs/tracing/blob/tracing-subscriber-0.3.16/tracing-subscriber/src/fmt/time/datetime.rs#L235-L241 + wtr.write_str(&chrono::Local::now().to_rfc3339_opts(SecondsFormat::Micros, false)) + } + + fn out() -> impl IsTerminal + Write { + 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() + } + } +} diff --git a/crates/voicevox_core_macros/src/inference_domain.rs b/crates/voicevox_core_macros/src/inference_domain.rs index 72bc4d18a..d24a20ab1 100644 --- a/crates/voicevox_core_macros/src/inference_domain.rs +++ b/crates/voicevox_core_macros/src/inference_domain.rs @@ -223,22 +223,28 @@ pub(crate) fn derive_inference_input_signature( fn make_run_context( self, sess: &mut R::Session, - ) -> R::RunContext<'_> { + ) -> ::anyhow::Result> { let mut ctx = as ::std::convert::From<_>>::from(sess); #( - __ArrayExt::push_to_ctx(self.#field_names, &mut ctx); + __ArrayExt::push_to_ctx(self.#field_names, &mut ctx)?; )* - return ctx; + return ::std::result::Result::Ok(ctx); trait __ArrayExt { - fn push_to_ctx(self, ctx: &mut impl crate::infer::PushInputTensor); + fn push_to_ctx( + self, + ctx: &mut impl crate::infer::PushInputTensor, + ) -> ::anyhow::Result<()>; } impl __ArrayExt for ::ndarray::Array { - fn push_to_ctx(self, ctx: &mut impl crate::infer::PushInputTensor) { - A::push_tensor_to_ctx(self, ctx); + fn push_to_ctx( + self, + ctx: &mut impl crate::infer::PushInputTensor, + ) -> ::anyhow::Result<()> { + A::push_tensor_to_ctx(self, ctx) } } } diff --git a/crates/voicevox_core_macros/src/lib.rs b/crates/voicevox_core_macros/src/lib.rs index 5f2f26809..98a2fdc5c 100644 --- a/crates/voicevox_core_macros/src/lib.rs +++ b/crates/voicevox_core_macros/src/lib.rs @@ -17,17 +17,18 @@ use syn::parse_macro_input; /// use enum_map::Enum; /// use macros::InferenceOperation; /// -/// pub(crate) enum InferenceDomainImpl {} +/// pub(crate) enum TalkDomain {} /// -/// impl InferenceDomain for InferenceDomainImpl { -/// type Operation = InferenceOperationImpl; +/// impl InferenceDomain for TalkDomain { +/// type Operation = TalkOperation; +/// // ... /// } /// /// #[derive(Clone, Copy, Enum, InferenceOperation)] /// #[inference_operation( -/// type Domain = InferenceDomainImpl; +/// type Domain = TalkDomain; /// )] -/// pub(crate) enum InferenceOperationImpl { +/// pub(crate) enum TalkOperation { /// #[inference_operation( /// type Input = PredictDurationInput; /// type Output = PredictDurationOutput; diff --git a/crates/voicevox_core_python_api/Cargo.toml b/crates/voicevox_core_python_api/Cargo.toml index 8e494173d..441625fb7 100644 --- a/crates/voicevox_core_python_api/Cargo.toml +++ b/crates/voicevox_core_python_api/Cargo.toml @@ -8,9 +8,11 @@ publish.workspace = true crate-type = ["cdylib"] [features] +cuda = ["voicevox_core/cuda"] directml = ["voicevox_core/directml"] [dependencies] +camino.workspace = true easy-ext.workspace = true log.workspace = true pyo3 = { workspace = true, features = ["extension-module"] } diff --git a/crates/voicevox_core_python_api/poetry.lock b/crates/voicevox_core_python_api/poetry.lock index fb112ad87..cbf69bec6 100644 --- a/crates/voicevox_core_python_api/poetry.lock +++ b/crates/voicevox_core_python_api/poetry.lock @@ -827,6 +827,7 @@ files = [ {file = "PyYAML-6.0.1-cp311-cp311-win_amd64.whl", hash = "sha256:bf07ee2fef7014951eeb99f56f39c9bb4af143d8aa3c21b1677805985307da34"}, {file = "PyYAML-6.0.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:855fb52b0dc35af121542a76b9a84f8d1cd886ea97c84703eaa6d88e37a2ad28"}, {file = "PyYAML-6.0.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:40df9b996c2b73138957fe23a16a4f0ba614f4c0efce1e9406a184b6d07fa3a9"}, + {file = "PyYAML-6.0.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a08c6f0fe150303c1c6b71ebcd7213c2858041a7e01975da3a99aed1e7a378ef"}, {file = "PyYAML-6.0.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6c22bec3fbe2524cde73d7ada88f6566758a8f7227bfbf93a408a9d86bcc12a0"}, {file = "PyYAML-6.0.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8d4e9c88387b0f5c7d5f281e55304de64cf7f9c0021a3525bd3b1c542da3b0e4"}, {file = "PyYAML-6.0.1-cp312-cp312-win32.whl", hash = "sha256:d483d2cdf104e7c9fa60c544d92981f12ad66a457afae824d146093b8c294c54"}, diff --git a/crates/voicevox_core_python_api/pyproject.toml b/crates/voicevox_core_python_api/pyproject.toml index de1b523b6..edbd472b2 100644 --- a/crates/voicevox_core_python_api/pyproject.toml +++ b/crates/voicevox_core_python_api/pyproject.toml @@ -22,6 +22,9 @@ build-backend = "maturin" [tool.isort] profile = "black" +[tool.pyright] +executionEnvironments = [{ root = "python/test" }, { root = "python" }] + [tool.maturin] module-name = "voicevox_core._rust" bindings = "pyo3" diff --git a/crates/voicevox_core_python_api/python/test/conftest.py b/crates/voicevox_core_python_api/python/test/conftest.py index 628f4dc56..eec642cb9 100644 --- a/crates/voicevox_core_python_api/python/test/conftest.py +++ b/crates/voicevox_core_python_api/python/test/conftest.py @@ -10,7 +10,9 @@ open_jtalk_dic_dir = ( root_dir.parent.parent.parent / "test_util" / "data" / "open_jtalk_dic_utf_8-1.11" ) -model_dir = root_dir.parent.parent.parent.parent / "model" / "sample.vvm" +model_dir = ( + root_dir.parent.parent.parent / "test_util" / "data" / "model" / "sample.vvm" +) class DurationExampleData(TypedDict): diff --git a/crates/voicevox_core_python_api/python/test/test_asyncio_metas.py b/crates/voicevox_core_python_api/python/test/test_asyncio_metas.py new file mode 100644 index 000000000..ec69032b1 --- /dev/null +++ b/crates/voicevox_core_python_api/python/test/test_asyncio_metas.py @@ -0,0 +1,26 @@ +""" +メタ情報の出力が可能かどうかをテストする。 + +``test_blocking_metas`` と対になる。 +""" + +import conftest +import pytest +import pytest_asyncio +from voicevox_core.asyncio import OpenJtalk, Synthesizer, VoiceModel + + +def test_voice_model_metas_works(voice_model: VoiceModel) -> None: + _ = voice_model.metas + + +@pytest.mark.asyncio +async def test_synthesizer_metas_works(voice_model: VoiceModel) -> None: + synthesizer = Synthesizer(await OpenJtalk.new(conftest.open_jtalk_dic_dir)) + await synthesizer.load_voice_model(voice_model) + _ = synthesizer.metas + + +@pytest_asyncio.fixture +async def voice_model() -> VoiceModel: + return await VoiceModel.from_path(conftest.model_dir) diff --git a/crates/voicevox_core_python_api/python/test/test_blocking_metas.py b/crates/voicevox_core_python_api/python/test/test_blocking_metas.py new file mode 100644 index 000000000..c305e2cdb --- /dev/null +++ b/crates/voicevox_core_python_api/python/test/test_blocking_metas.py @@ -0,0 +1,24 @@ +""" +メタ情報の出力が可能かどうかをテストする。 + +``test_asyncio_metas`` と対になる。 +""" + +import conftest +import pytest +from voicevox_core.blocking import OpenJtalk, Synthesizer, VoiceModel + + +def test_voice_model_metas_works(voice_model: VoiceModel) -> None: + _ = voice_model.metas + + +def test_synthesizer_metas_works(voice_model: VoiceModel) -> None: + synthesizer = Synthesizer(OpenJtalk(conftest.open_jtalk_dic_dir)) + synthesizer.load_voice_model(voice_model) + _ = synthesizer.metas + + +@pytest.fixture +def voice_model() -> VoiceModel: + return VoiceModel.from_path(conftest.model_dir) 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 195154629..21a2016fe 100644 --- a/crates/voicevox_core_python_api/python/voicevox_core/_models.py +++ b/crates/voicevox_core_python_api/python/voicevox_core/_models.py @@ -34,6 +34,13 @@ """ +class StyleType(str, Enum): + """**スタイル** (_style_)に対応するモデルの種類。""" + + TALK = "talk" + """音声合成クエリの作成と音声合成が可能。""" + + @pydantic.dataclasses.dataclass class StyleMeta: """**スタイル** (_style_)のメタ情報。""" @@ -44,6 +51,16 @@ class StyleMeta: id: StyleId """スタイルID。""" + type: StyleType = dataclasses.field(default=StyleType.TALK) + """スタイルに対応するモデルの種類。""" + + order: Optional[int] = None + """ + 話者の順番。 + + :attr:`SpeakerMeta.styles` は、この値に対して昇順に並んでいるべきである。 + """ + @pydantic.dataclasses.dataclass class SpeakerMeta: @@ -61,6 +78,13 @@ class SpeakerMeta: version: StyleVersion """話者のUUID。""" + order: Optional[int] = None + """ + 話者の順番。 + + ``SpeakerMeta`` の列は、この値に対して昇順に並んでいるべきである。 + """ + @pydantic.dataclasses.dataclass class SupportedDevices: diff --git a/crates/voicevox_core_python_api/python/voicevox_core/_rust/__init__.pyi b/crates/voicevox_core_python_api/python/voicevox_core/_rust/__init__.pyi index 3a47ef02b..89a50d230 100644 --- a/crates/voicevox_core_python_api/python/voicevox_core/_rust/__init__.pyi +++ b/crates/voicevox_core_python_api/python/voicevox_core/_rust/__init__.pyi @@ -37,6 +37,11 @@ class ReadZipEntryError(Exception): ... +class InvalidModelFormatError(Exception): + """モデルの形式が不正。""" + + ... + class ModelAlreadyLoadedError(Exception): """すでに読み込まれている音声モデルを読み込もうとした。""" diff --git a/crates/voicevox_core_python_api/python/voicevox_core/_rust/asyncio.pyi b/crates/voicevox_core_python_api/python/voicevox_core/_rust/asyncio.pyi index 7a6596008..d8e9f6fe2 100644 --- a/crates/voicevox_core_python_api/python/voicevox_core/_rust/asyncio.pyi +++ b/crates/voicevox_core_python_api/python/voicevox_core/_rust/asyncio.pyi @@ -1,4 +1,4 @@ -from pathlib import Path +from os import PathLike from typing import TYPE_CHECKING, Dict, List, Literal, Union from uuid import UUID @@ -18,7 +18,7 @@ class VoiceModel: 音声モデル。""" @staticmethod - async def from_path(path: Union[Path, str]) -> VoiceModel: + async def from_path(path: Union[str, PathLike[str]]) -> VoiceModel: """ VVMファイルから ``VoiceModel`` を生成する。 @@ -43,7 +43,7 @@ class OpenJtalk: """ @staticmethod - async def new(open_jtalk_dict_dir: Union[Path, str]) -> "OpenJtalk": + async def new(open_jtalk_dict_dir: Union[str, PathLike[str]]) -> "OpenJtalk": """ ``OpenJTalk`` を生成する。 diff --git a/crates/voicevox_core_python_api/python/voicevox_core/_rust/blocking.pyi b/crates/voicevox_core_python_api/python/voicevox_core/_rust/blocking.pyi index 3a208fb33..5584d68bb 100644 --- a/crates/voicevox_core_python_api/python/voicevox_core/_rust/blocking.pyi +++ b/crates/voicevox_core_python_api/python/voicevox_core/_rust/blocking.pyi @@ -1,4 +1,4 @@ -from pathlib import Path +from os import PathLike from typing import TYPE_CHECKING, Dict, List, Literal, Union from uuid import UUID @@ -18,7 +18,7 @@ class VoiceModel: 音声モデル。""" @staticmethod - def from_path(path: Union[Path, str]) -> VoiceModel: + def from_path(path: Union[str, PathLike[str]]) -> VoiceModel: """ VVMファイルから ``VoiceModel`` を生成する。 @@ -47,7 +47,7 @@ class OpenJtalk: Open JTalkの辞書ディレクトリ。 """ - def __init__(self, open_jtalk_dict_dir: Union[Path, str]) -> None: ... + def __init__(self, open_jtalk_dict_dir: Union[str, PathLike[str]]) -> None: ... def use_user_dict(self, user_dict: UserDict) -> None: """ ユーザー辞書を設定する。 diff --git a/crates/voicevox_core_python_api/python/voicevox_core/asyncio.py b/crates/voicevox_core_python_api/python/voicevox_core/asyncio.py index fec0c831b..75b160814 100644 --- a/crates/voicevox_core_python_api/python/voicevox_core/asyncio.py +++ b/crates/voicevox_core_python_api/python/voicevox_core/asyncio.py @@ -1,3 +1,4 @@ +# pyright: reportMissingModuleSource=false from ._rust.asyncio import OpenJtalk, Synthesizer, UserDict, VoiceModel __all__ = ["OpenJtalk", "Synthesizer", "UserDict", "VoiceModel"] diff --git a/crates/voicevox_core_python_api/python/voicevox_core/blocking.py b/crates/voicevox_core_python_api/python/voicevox_core/blocking.py index e378037c3..80f61fdcb 100644 --- a/crates/voicevox_core_python_api/python/voicevox_core/blocking.py +++ b/crates/voicevox_core_python_api/python/voicevox_core/blocking.py @@ -1,3 +1,4 @@ +# pyright: reportMissingModuleSource=false from ._rust.blocking import OpenJtalk, Synthesizer, UserDict, VoiceModel __all__ = ["OpenJtalk", "Synthesizer", "UserDict", "VoiceModel"] diff --git a/crates/voicevox_core_python_api/src/convert.rs b/crates/voicevox_core_python_api/src/convert.rs index 4b908c48b..3cee4186b 100644 --- a/crates/voicevox_core_python_api/src/convert.rs +++ b/crates/voicevox_core_python_api/src/convert.rs @@ -1,5 +1,6 @@ use std::{error::Error as _, future::Future, iter, path::PathBuf}; +use camino::Utf8PathBuf; use easy_ext::ext; use pyo3::{ exceptions::{PyException, PyValueError}, @@ -15,13 +16,13 @@ use voicevox_core::{ use crate::{ ExtractFullContextLabelError, GetSupportedDevicesError, GpuSupportError, InferenceFailedError, - InvalidModelDataError, InvalidWordError, LoadUserDictError, ModelAlreadyLoadedError, - ModelNotFoundError, NotLoadedOpenjtalkDictError, OpenZipFileError, ParseKanaError, - ReadZipEntryError, SaveUserDictError, StyleAlreadyLoadedError, StyleNotFoundError, - UseUserDictError, WordNotFoundError, + InvalidModelDataError, InvalidModelFormatError, InvalidWordError, LoadUserDictError, + ModelAlreadyLoadedError, ModelNotFoundError, NotLoadedOpenjtalkDictError, OpenZipFileError, + ParseKanaError, ReadZipEntryError, SaveUserDictError, StyleAlreadyLoadedError, + StyleNotFoundError, UseUserDictError, WordNotFoundError, }; -pub fn from_acceleration_mode(ob: &PyAny) -> PyResult { +pub(crate) fn from_acceleration_mode(ob: &PyAny) -> PyResult { let py = ob.py(); let class = py.import("voicevox_core")?.getattr("AccelerationMode")?; @@ -38,14 +39,16 @@ pub fn from_acceleration_mode(ob: &PyAny) -> PyResult { } } -pub fn from_utf8_path(ob: &PyAny) -> PyResult { +// FIXME: `UserDict`についてはこれではなく、`PathBuf::extract`を直接使うようにする +pub(crate) fn from_utf8_path(ob: &PyAny) -> PyResult { PathBuf::extract(ob)? .into_os_string() .into_string() + .map(Utf8PathBuf::from) .map_err(|s| PyValueError::new_err(format!("{s:?} cannot be encoded to UTF-8"))) } -pub fn from_dataclass(ob: &PyAny) -> PyResult { +pub(crate) fn from_dataclass(ob: &PyAny) -> PyResult { let py = ob.py(); let ob = py.import("dataclasses")?.call_method1("asdict", (ob,))?; @@ -56,7 +59,7 @@ pub fn from_dataclass(ob: &PyAny) -> PyResult { serde_json::from_str(json).into_py_value_result() } -pub fn to_pydantic_voice_model_meta<'py>( +pub(crate) fn to_pydantic_voice_model_meta<'py>( metas: &VoiceModelMeta, py: Python<'py>, ) -> PyResult> { @@ -71,7 +74,7 @@ pub fn to_pydantic_voice_model_meta<'py>( .collect::>>() } -pub fn to_pydantic_dataclass(x: impl Serialize, class: &PyAny) -> PyResult<&PyAny> { +pub(crate) fn to_pydantic_dataclass(x: impl Serialize, class: &PyAny) -> PyResult<&PyAny> { let py = class.py(); let x = serde_json::to_string(&x).into_py_value_result()?; @@ -105,7 +108,7 @@ pub(crate) fn blocking_modify_accent_phrases<'py>( .collect() } -pub fn async_modify_accent_phrases<'py, Fun, Fut>( +pub(crate) fn async_modify_accent_phrases<'py, Fun, Fut>( accent_phrases: &'py PyList, speaker_id: StyleId, py: Python<'py>, @@ -142,16 +145,16 @@ where ) } -pub fn to_rust_uuid(ob: &PyAny) -> PyResult { +pub(crate) fn to_rust_uuid(ob: &PyAny) -> PyResult { let uuid = ob.getattr("hex")?.extract::()?; uuid.parse::().into_py_value_result() } -pub fn to_py_uuid(py: Python<'_>, uuid: Uuid) -> PyResult { +pub(crate) fn to_py_uuid(py: Python<'_>, uuid: Uuid) -> PyResult { let uuid = uuid.hyphenated().to_string(); let uuid = py.import("uuid")?.call_method1("UUID", (uuid,))?; Ok(uuid.to_object(py)) } -pub fn to_rust_user_dict_word(ob: &PyAny) -> PyResult { +pub(crate) fn to_rust_user_dict_word(ob: &PyAny) -> PyResult { voicevox_core::UserDictWord::new( ob.getattr("surface")?.extract()?, ob.getattr("pronunciation")?.extract()?, @@ -161,7 +164,7 @@ pub fn to_rust_user_dict_word(ob: &PyAny) -> PyResult( +pub(crate) fn to_py_user_dict_word<'py>( py: Python<'py>, word: &voicevox_core::UserDictWord, ) -> PyResult<&'py PyAny> { @@ -171,14 +174,14 @@ pub fn to_py_user_dict_word<'py>( .downcast()?; to_pydantic_dataclass(word, class) } -pub fn to_rust_word_type(word_type: &PyAny) -> PyResult { +pub(crate) fn to_rust_word_type(word_type: &PyAny) -> PyResult { let name = word_type.getattr("name")?.extract::()?; serde_json::from_value::(json!(name)).into_py_value_result() } #[ext(VoicevoxCoreResultExt)] -pub impl voicevox_core::Result { +pub(crate) impl voicevox_core::Result { fn into_py_result(self, py: Python<'_>) -> PyResult { use voicevox_core::ErrorKind; @@ -191,6 +194,7 @@ pub impl voicevox_core::Result { ErrorKind::ReadZipEntry => ReadZipEntryError::new_err(msg), ErrorKind::ModelAlreadyLoaded => ModelAlreadyLoadedError::new_err(msg), ErrorKind::StyleAlreadyLoaded => StyleAlreadyLoadedError::new_err(msg), + ErrorKind::InvalidModelFormat => InvalidModelFormatError::new_err(msg), ErrorKind::InvalidModelData => InvalidModelDataError::new_err(msg), ErrorKind::GetSupportedDevices => GetSupportedDevicesError::new_err(msg), ErrorKind::StyleNotFound => StyleNotFoundError::new_err(msg), diff --git a/crates/voicevox_core_python_api/src/lib.rs b/crates/voicevox_core_python_api/src/lib.rs index 9d36cafcb..492d18f0e 100644 --- a/crates/voicevox_core_python_api/src/lib.rs +++ b/crates/voicevox_core_python_api/src/lib.rs @@ -1,24 +1,15 @@ -use std::{marker::PhantomData, sync::Arc}; +use std::marker::PhantomData; mod convert; -use self::convert::{ - async_modify_accent_phrases, from_acceleration_mode, from_dataclass, from_utf8_path, - to_py_user_dict_word, to_py_uuid, to_pydantic_dataclass, to_pydantic_voice_model_meta, - to_rust_user_dict_word, to_rust_uuid, VoicevoxCoreResultExt as _, -}; +use self::convert::{from_utf8_path, to_pydantic_dataclass, VoicevoxCoreResultExt as _}; use easy_ext::ext; use log::debug; use pyo3::{ create_exception, exceptions::{PyException, PyKeyError, PyValueError}, - pyclass, pyfunction, pymethods, pymodule, - types::{IntoPyDict as _, PyBytes, PyDict, PyList, PyModule}, - wrap_pyfunction, PyAny, PyObject, PyRef, PyResult, PyTypeInfo, Python, ToPyObject, -}; -use uuid::Uuid; -use voicevox_core::{ - AccelerationMode, AudioQueryModel, InitializeOptions, StyleId, SynthesisOptions, TtsOptions, - UserDictWord, VoiceModelId, + pyfunction, pymodule, + types::PyModule, + wrap_pyfunction, PyAny, PyResult, PyTypeInfo, Python, }; #[pymodule] @@ -41,10 +32,10 @@ fn rust(py: Python<'_>, module: &PyModule) -> PyResult<()> { module.add_and_register_submodule(blocking_module)?; let asyncio_module = PyModule::new(py, "voicevox_core._rust.asyncio")?; - asyncio_module.add_class::()?; - asyncio_module.add_class::()?; - asyncio_module.add_class::()?; - asyncio_module.add_class::()?; + asyncio_module.add_class::()?; + asyncio_module.add_class::()?; + asyncio_module.add_class::()?; + asyncio_module.add_class::()?; module.add_and_register_submodule(asyncio_module) } @@ -80,6 +71,7 @@ exceptions! { ReadZipEntryError: PyException; ModelAlreadyLoadedError: PyException; StyleAlreadyLoadedError: PyException; + InvalidModelFormatError: PyException; InvalidModelDataError: PyException; GetSupportedDevicesError: PyException; StyleNotFoundError: PyKeyError; @@ -94,12 +86,6 @@ exceptions! { InvalidWordError: PyValueError; } -#[pyclass] -#[derive(Clone)] -struct VoiceModel { - model: voicevox_core::tokio::VoiceModel, -} - #[pyfunction] fn supported_devices(py: Python<'_>) -> PyResult<&PyAny> { let class = py @@ -110,395 +96,6 @@ fn supported_devices(py: Python<'_>) -> PyResult<&PyAny> { to_pydantic_dataclass(s, class) } -#[pymethods] -impl VoiceModel { - #[staticmethod] - fn from_path( - py: Python<'_>, - #[pyo3(from_py_with = "from_utf8_path")] path: String, - ) -> PyResult<&PyAny> { - pyo3_asyncio::tokio::future_into_py(py, async move { - let model = voicevox_core::tokio::VoiceModel::from_path(path).await; - let model = Python::with_gil(|py| model.into_py_result(py))?; - Ok(Self { model }) - }) - } - - #[getter] - fn id(&self) -> &str { - self.model.id().raw_voice_model_id() - } - - #[getter] - fn metas<'py>(&self, py: Python<'py>) -> Vec<&'py PyAny> { - to_pydantic_voice_model_meta(self.model.metas(), py).unwrap() - } -} - -#[pyclass] -#[derive(Clone)] -struct OpenJtalk { - open_jtalk: voicevox_core::tokio::OpenJtalk, -} - -#[pymethods] -impl OpenJtalk { - #[allow(clippy::new_ret_no_self)] - #[staticmethod] - fn new( - #[pyo3(from_py_with = "from_utf8_path")] open_jtalk_dict_dir: String, - py: Python<'_>, - ) -> PyResult<&PyAny> { - pyo3_asyncio::tokio::future_into_py(py, async move { - let open_jtalk = voicevox_core::tokio::OpenJtalk::new(open_jtalk_dict_dir).await; - let open_jtalk = Python::with_gil(|py| open_jtalk.into_py_result(py))?; - Ok(Self { open_jtalk }) - }) - } - - fn use_user_dict<'py>(&self, user_dict: UserDict, py: Python<'py>) -> PyResult<&'py PyAny> { - let this = self.open_jtalk.clone(); - - pyo3_asyncio::tokio::future_into_py(py, async move { - let result = this.use_user_dict(&user_dict.dict).await; - Python::with_gil(|py| result.into_py_result(py)) - }) - } -} - -#[pyclass] -struct Synthesizer { - synthesizer: Closable, Self>, -} - -#[pymethods] -impl Synthesizer { - #[new] - #[pyo3(signature =( - open_jtalk, - acceleration_mode = InitializeOptions::default().acceleration_mode, - cpu_num_threads = InitializeOptions::default().cpu_num_threads, - ))] - fn new( - open_jtalk: OpenJtalk, - #[pyo3(from_py_with = "from_acceleration_mode")] acceleration_mode: AccelerationMode, - cpu_num_threads: u16, - ) -> PyResult { - let synthesizer = voicevox_core::tokio::Synthesizer::new( - open_jtalk.open_jtalk.clone(), - &InitializeOptions { - acceleration_mode, - cpu_num_threads, - }, - ); - let synthesizer = Python::with_gil(|py| synthesizer.into_py_result(py))?; - let synthesizer = Closable::new(synthesizer); - Ok(Self { synthesizer }) - } - - fn __repr__(&self) -> &'static str { - "Synthesizer { .. }" - } - - fn __enter__(slf: PyRef<'_, Self>) -> PyResult> { - slf.synthesizer.get()?; - Ok(slf) - } - - fn __exit__( - &mut self, - #[allow(unused_variables)] exc_type: &PyAny, - #[allow(unused_variables)] exc_value: &PyAny, - #[allow(unused_variables)] traceback: &PyAny, - ) { - self.close(); - } - - #[getter] - fn is_gpu_mode(&self) -> PyResult { - let synthesizer = self.synthesizer.get()?; - Ok(synthesizer.is_gpu_mode()) - } - - #[getter] - fn metas<'py>(&self, py: Python<'py>) -> PyResult> { - let synthesizer = self.synthesizer.get()?; - to_pydantic_voice_model_meta(&synthesizer.metas(), py) - } - - fn load_voice_model<'py>( - &mut self, - model: &'py PyAny, - py: Python<'py>, - ) -> PyResult<&'py PyAny> { - let model: VoiceModel = model.extract()?; - let synthesizer = self.synthesizer.get()?.clone(); - pyo3_asyncio::tokio::future_into_py(py, async move { - let result = synthesizer.load_voice_model(&model.model).await; - Python::with_gil(|py| result.into_py_result(py)) - }) - } - - fn unload_voice_model(&mut self, voice_model_id: &str, py: Python<'_>) -> PyResult<()> { - self.synthesizer - .get()? - .unload_voice_model(&VoiceModelId::new(voice_model_id.to_string())) - .into_py_result(py) - } - - fn is_loaded_voice_model(&self, voice_model_id: &str) -> PyResult { - Ok(self - .synthesizer - .get()? - .is_loaded_voice_model(&VoiceModelId::new(voice_model_id.to_string()))) - } - - fn audio_query_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 audio_query = synthesizer - .audio_query_from_kana(&kana, StyleId::new(style_id)) - .await; - - Python::with_gil(|py| { - let class = py.import("voicevox_core")?.getattr("AudioQuery")?; - let ret = to_pydantic_dataclass(audio_query.into_py_result(py)?, 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( - py, - pyo3_asyncio::tokio::get_current_locals(py)?, - async move { - let audio_query = synthesizer.audio_query(&text, StyleId::new(style_id)).await; - - Python::with_gil(|py| { - let audio_query = audio_query.into_py_result(py)?; - let class = py.import("voicevox_core")?.getattr("AudioQuery")?; - let ret = to_pydantic_dataclass(audio_query, class)?; - Ok(ret.to_object(py)) - }) - }, - ) - } - - 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 - .create_accent_phrases_from_kana(&kana, StyleId::new(style_id)) - .await; - Python::with_gil(|py| { - let class = py.import("voicevox_core")?.getattr("AccentPhrase")?; - let accent_phrases = accent_phrases - .into_py_result(py)? - .iter() - .map(|ap| to_pydantic_dataclass(ap, class)) - .collect::>>(); - let list = PyList::new(py, accent_phrases); - Ok(list.to_object(py)) - }) - }, - ) - } - - fn create_accent_phrases<'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( - py, - pyo3_asyncio::tokio::get_current_locals(py)?, - async move { - let accent_phrases = synthesizer - .create_accent_phrases(&text, StyleId::new(style_id)) - .await; - Python::with_gil(|py| { - let class = py.import("voicevox_core")?.getattr("AccentPhrase")?; - let accent_phrases = accent_phrases - .into_py_result(py)? - .iter() - .map(|ap| to_pydantic_dataclass(ap, class)) - .collect::>>(); - let list = PyList::new(py, accent_phrases); - Ok(list.to_object(py)) - }) - }, - ) - } - - fn replace_mora_data<'py>( - &self, - accent_phrases: &'py PyList, - style_id: u32, - py: Python<'py>, - ) -> PyResult<&'py PyAny> { - let synthesizer = self.synthesizer.get()?.clone(); - async_modify_accent_phrases( - accent_phrases, - StyleId::new(style_id), - py, - |a, s| async move { synthesizer.replace_mora_data(&a, s).await }, - ) - } - - fn replace_phoneme_length<'py>( - &self, - accent_phrases: &'py PyList, - style_id: u32, - py: Python<'py>, - ) -> PyResult<&'py PyAny> { - let synthesizer = self.synthesizer.get()?.clone(); - async_modify_accent_phrases( - accent_phrases, - StyleId::new(style_id), - py, - |a, s| async move { synthesizer.replace_phoneme_length(&a, s).await }, - ) - } - - fn replace_mora_pitch<'py>( - &self, - accent_phrases: &'py PyList, - style_id: u32, - py: Python<'py>, - ) -> PyResult<&'py PyAny> { - let synthesizer = self.synthesizer.get()?.clone(); - async_modify_accent_phrases( - accent_phrases, - StyleId::new(style_id), - py, - |a, s| async move { synthesizer.replace_mora_pitch(&a, s).await }, - ) - } - - #[pyo3(signature=(audio_query,style_id,enable_interrogative_upspeak = TtsOptions::default().enable_interrogative_upspeak))] - fn synthesis<'py>( - &self, - #[pyo3(from_py_with = "from_dataclass")] audio_query: AudioQueryModel, - style_id: u32, - enable_interrogative_upspeak: bool, - py: Python<'py>, - ) -> PyResult<&'py PyAny> { - let synthesizer = self.synthesizer.get()?.clone(); - pyo3_asyncio::tokio::future_into_py_with_locals( - py, - pyo3_asyncio::tokio::get_current_locals(py)?, - async move { - let wav = synthesizer - .synthesis( - &audio_query, - StyleId::new(style_id), - &SynthesisOptions { - enable_interrogative_upspeak, - }, - ) - .await; - Python::with_gil(|py| { - let wav = wav.into_py_result(py)?; - Ok(PyBytes::new(py, &wav).to_object(py)) - }) - }, - ) - } - - #[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.tts_from_kana(&kana, style_id, &options).await; - - Python::with_gil(|py| { - let wav = wav.into_py_result(py)?; - Ok(PyBytes::new(py, &wav).to_object(py)) - }) - }, - ) - } - - #[pyo3(signature=( - text, - style_id, - enable_interrogative_upspeak = TtsOptions::default().enable_interrogative_upspeak - ))] - fn tts<'py>( - &self, - text: &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 text = text.to_owned(); - pyo3_asyncio::tokio::future_into_py_with_locals( - py, - pyo3_asyncio::tokio::get_current_locals(py)?, - async move { - let wav = synthesizer.tts(&text, style_id, &options).await; - - Python::with_gil(|py| { - let wav = wav.into_py_result(py)?; - Ok(PyBytes::new(py, &wav).to_object(py)) - }) - }, - ) - } - - fn close(&mut self) { - self.synthesizer.close() - } -} - struct Closable { content: MaybeClosed, marker: PhantomData, @@ -551,95 +148,13 @@ fn _to_zenkaku(text: &str) -> PyResult { Ok(voicevox_core::__internal::to_zenkaku(text)) } -#[pyclass] -#[derive(Default, Debug, Clone)] -struct UserDict { - dict: Arc, -} - -#[pymethods] -impl UserDict { - #[new] - fn new() -> Self { - Self::default() - } - - fn load<'py>(&self, path: &str, py: Python<'py>) -> PyResult<&'py PyAny> { - let this = self.dict.clone(); - let path = path.to_owned(); - - pyo3_asyncio::tokio::future_into_py(py, async move { - let result = this.load(&path).await; - Python::with_gil(|py| result.into_py_result(py)) - }) - } - - fn save<'py>(&self, path: &str, py: Python<'py>) -> PyResult<&'py PyAny> { - let this = self.dict.clone(); - let path = path.to_owned(); - - pyo3_asyncio::tokio::future_into_py(py, async move { - let result = this.save(&path).await; - Python::with_gil(|py| result.into_py_result(py)) - }) - } - - fn add_word( - &mut self, - #[pyo3(from_py_with = "to_rust_user_dict_word")] word: UserDictWord, - py: Python<'_>, - ) -> PyResult { - let uuid = self.dict.add_word(word).into_py_result(py)?; - - to_py_uuid(py, uuid) - } - - fn update_word( - &mut self, - #[pyo3(from_py_with = "to_rust_uuid")] word_uuid: Uuid, - #[pyo3(from_py_with = "to_rust_user_dict_word")] word: UserDictWord, - py: Python<'_>, - ) -> PyResult<()> { - self.dict.update_word(word_uuid, word).into_py_result(py)?; - Ok(()) - } - - fn remove_word( - &mut self, - #[pyo3(from_py_with = "to_rust_uuid")] word_uuid: Uuid, - py: Python<'_>, - ) -> PyResult<()> { - self.dict.remove_word(word_uuid).into_py_result(py)?; - Ok(()) - } - - fn import_dict(&mut self, other: &UserDict, py: Python<'_>) -> PyResult<()> { - self.dict.import(&other.dict).into_py_result(py)?; - Ok(()) - } - - #[getter] - fn words<'py>(&self, py: Python<'py>) -> PyResult<&'py PyDict> { - let words = self.dict.with_words(|words| { - words - .iter() - .map(|(&uuid, word)| { - let uuid = to_py_uuid(py, uuid)?; - let word = to_py_user_dict_word(py, word)?; - Ok((uuid, word)) - }) - .collect::>>() - })?; - Ok(words.into_py_dict(py)) - } -} - mod blocking { - use std::sync::Arc; + use std::{path::PathBuf, sync::Arc}; + use camino::Utf8PathBuf; use pyo3::{ pyclass, pymethods, - types::{IntoPyDict as _, PyBytes, PyDict, PyList}, + types::{IntoPyDict as _, PyBytes, PyDict, PyList, PyString}, PyAny, PyObject, PyRef, PyResult, Python, }; use uuid::Uuid; @@ -659,10 +174,7 @@ mod blocking { #[pymethods] impl VoiceModel { #[staticmethod] - fn from_path( - py: Python<'_>, - #[pyo3(from_py_with = "crate::convert::from_utf8_path")] path: String, - ) -> PyResult { + fn from_path(py: Python<'_>, path: PathBuf) -> PyResult { let model = voicevox_core::blocking::VoiceModel::from_path(path).into_py_result(py)?; Ok(Self { model }) } @@ -688,7 +200,7 @@ mod blocking { impl OpenJtalk { #[new] fn new( - #[pyo3(from_py_with = "super::from_utf8_path")] open_jtalk_dict_dir: String, + #[pyo3(from_py_with = "super::from_utf8_path")] open_jtalk_dict_dir: Utf8PathBuf, py: Python<'_>, ) -> PyResult { let open_jtalk = @@ -784,7 +296,12 @@ mod blocking { .into_py_result(py) } - fn is_loaded_voice_model(&self, voice_model_id: &str) -> PyResult { + // C APIの挙動と一貫性を持たせる。 + fn is_loaded_voice_model(&self, voice_model_id: &PyString) -> PyResult { + let Ok(voice_model_id) = voice_model_id.to_str() else { + // 与えられたIDがUTF-8ではない場合、それに対応する`VoicdModel`は確実に存在しない + return Ok(false); + }; Ok(self .synthesizer .get()? @@ -1055,3 +572,512 @@ mod blocking { } } } + +mod asyncio { + use std::{path::PathBuf, sync::Arc}; + + use camino::Utf8PathBuf; + use pyo3::{ + pyclass, pymethods, + types::{IntoPyDict as _, PyBytes, PyDict, PyList, PyString}, + PyAny, PyObject, PyRef, PyResult, Python, ToPyObject as _, + }; + use uuid::Uuid; + use voicevox_core::{ + AccelerationMode, AudioQueryModel, InitializeOptions, StyleId, SynthesisOptions, + TtsOptions, UserDictWord, VoiceModelId, + }; + + use crate::{convert::VoicevoxCoreResultExt as _, Closable}; + + #[pyclass] + #[derive(Clone)] + pub(crate) struct VoiceModel { + model: voicevox_core::tokio::VoiceModel, + } + + #[pymethods] + impl VoiceModel { + #[staticmethod] + fn from_path(py: Python<'_>, path: PathBuf) -> PyResult<&PyAny> { + pyo3_asyncio::tokio::future_into_py(py, async move { + let model = voicevox_core::tokio::VoiceModel::from_path(path).await; + let model = Python::with_gil(|py| model.into_py_result(py))?; + Ok(Self { model }) + }) + } + + #[getter] + fn id(&self) -> &str { + self.model.id().raw_voice_model_id() + } + + #[getter] + fn metas<'py>(&self, py: Python<'py>) -> Vec<&'py PyAny> { + crate::convert::to_pydantic_voice_model_meta(self.model.metas(), py).unwrap() + } + } + + #[pyclass] + #[derive(Clone)] + pub(crate) struct OpenJtalk { + open_jtalk: voicevox_core::tokio::OpenJtalk, + } + + #[pymethods] + impl OpenJtalk { + #[allow(clippy::new_ret_no_self)] + #[staticmethod] + fn new( + #[pyo3(from_py_with = "crate::convert::from_utf8_path")] + open_jtalk_dict_dir: Utf8PathBuf, + py: Python<'_>, + ) -> PyResult<&PyAny> { + pyo3_asyncio::tokio::future_into_py(py, async move { + let open_jtalk = voicevox_core::tokio::OpenJtalk::new(open_jtalk_dict_dir).await; + let open_jtalk = Python::with_gil(|py| open_jtalk.into_py_result(py))?; + Ok(Self { open_jtalk }) + }) + } + + fn use_user_dict<'py>(&self, user_dict: UserDict, py: Python<'py>) -> PyResult<&'py PyAny> { + let this = self.open_jtalk.clone(); + + pyo3_asyncio::tokio::future_into_py(py, async move { + let result = this.use_user_dict(&user_dict.dict).await; + Python::with_gil(|py| result.into_py_result(py)) + }) + } + } + + #[pyclass] + pub(crate) struct Synthesizer { + synthesizer: + Closable, Self>, + } + + #[pymethods] + impl Synthesizer { + #[new] + #[pyo3(signature =( + open_jtalk, + acceleration_mode = InitializeOptions::default().acceleration_mode, + cpu_num_threads = InitializeOptions::default().cpu_num_threads, + ))] + fn new( + open_jtalk: OpenJtalk, + #[pyo3(from_py_with = "crate::convert::from_acceleration_mode")] + acceleration_mode: AccelerationMode, + cpu_num_threads: u16, + ) -> PyResult { + let synthesizer = voicevox_core::tokio::Synthesizer::new( + open_jtalk.open_jtalk.clone(), + &InitializeOptions { + acceleration_mode, + cpu_num_threads, + }, + ); + let synthesizer = Python::with_gil(|py| synthesizer.into_py_result(py))?; + let synthesizer = Closable::new(synthesizer); + Ok(Self { synthesizer }) + } + + fn __repr__(&self) -> &'static str { + "Synthesizer { .. }" + } + + fn __enter__(slf: PyRef<'_, Self>) -> PyResult> { + slf.synthesizer.get()?; + Ok(slf) + } + + fn __exit__( + &mut self, + #[allow(unused_variables)] exc_type: &PyAny, + #[allow(unused_variables)] exc_value: &PyAny, + #[allow(unused_variables)] traceback: &PyAny, + ) { + self.close(); + } + + #[getter] + fn is_gpu_mode(&self) -> PyResult { + let synthesizer = self.synthesizer.get()?; + Ok(synthesizer.is_gpu_mode()) + } + + #[getter] + fn metas<'py>(&self, py: Python<'py>) -> PyResult> { + let synthesizer = self.synthesizer.get()?; + crate::convert::to_pydantic_voice_model_meta(&synthesizer.metas(), py) + } + + fn load_voice_model<'py>( + &mut self, + model: &'py PyAny, + py: Python<'py>, + ) -> PyResult<&'py PyAny> { + let model: VoiceModel = model.extract()?; + let synthesizer = self.synthesizer.get()?.clone(); + pyo3_asyncio::tokio::future_into_py(py, async move { + let result = synthesizer.load_voice_model(&model.model).await; + Python::with_gil(|py| result.into_py_result(py)) + }) + } + + fn unload_voice_model(&mut self, voice_model_id: &str, py: Python<'_>) -> PyResult<()> { + self.synthesizer + .get()? + .unload_voice_model(&VoiceModelId::new(voice_model_id.to_string())) + .into_py_result(py) + } + + // C APIの挙動と一貫性を持たせる。 + fn is_loaded_voice_model(&self, voice_model_id: &PyString) -> PyResult { + let Ok(voice_model_id) = voice_model_id.to_str() else { + // 与えられたIDがUTF-8ではない場合、それに対応する`VoicdModel`は確実に存在しない + return Ok(false); + }; + Ok(self + .synthesizer + .get()? + .is_loaded_voice_model(&VoiceModelId::new(voice_model_id.to_string()))) + } + + fn audio_query_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 audio_query = synthesizer + .audio_query_from_kana(&kana, StyleId::new(style_id)) + .await; + + Python::with_gil(|py| { + let class = py.import("voicevox_core")?.getattr("AudioQuery")?; + let ret = crate::convert::to_pydantic_dataclass( + audio_query.into_py_result(py)?, + 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( + py, + pyo3_asyncio::tokio::get_current_locals(py)?, + async move { + let audio_query = synthesizer.audio_query(&text, StyleId::new(style_id)).await; + + Python::with_gil(|py| { + let audio_query = audio_query.into_py_result(py)?; + let class = py.import("voicevox_core")?.getattr("AudioQuery")?; + let ret = crate::convert::to_pydantic_dataclass(audio_query, class)?; + Ok(ret.to_object(py)) + }) + }, + ) + } + + 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 + .create_accent_phrases_from_kana(&kana, StyleId::new(style_id)) + .await; + Python::with_gil(|py| { + let class = py.import("voicevox_core")?.getattr("AccentPhrase")?; + let accent_phrases = accent_phrases + .into_py_result(py)? + .iter() + .map(|ap| crate::convert::to_pydantic_dataclass(ap, class)) + .collect::>>(); + let list = PyList::new(py, accent_phrases); + Ok(list.to_object(py)) + }) + }, + ) + } + + fn create_accent_phrases<'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( + py, + pyo3_asyncio::tokio::get_current_locals(py)?, + async move { + let accent_phrases = synthesizer + .create_accent_phrases(&text, StyleId::new(style_id)) + .await; + Python::with_gil(|py| { + let class = py.import("voicevox_core")?.getattr("AccentPhrase")?; + let accent_phrases = accent_phrases + .into_py_result(py)? + .iter() + .map(|ap| crate::convert::to_pydantic_dataclass(ap, class)) + .collect::>>(); + let list = PyList::new(py, accent_phrases); + Ok(list.to_object(py)) + }) + }, + ) + } + + fn replace_mora_data<'py>( + &self, + accent_phrases: &'py PyList, + style_id: u32, + py: Python<'py>, + ) -> PyResult<&'py PyAny> { + let synthesizer = self.synthesizer.get()?.clone(); + crate::convert::async_modify_accent_phrases( + accent_phrases, + StyleId::new(style_id), + py, + |a, s| async move { synthesizer.replace_mora_data(&a, s).await }, + ) + } + + fn replace_phoneme_length<'py>( + &self, + accent_phrases: &'py PyList, + style_id: u32, + py: Python<'py>, + ) -> PyResult<&'py PyAny> { + let synthesizer = self.synthesizer.get()?.clone(); + crate::convert::async_modify_accent_phrases( + accent_phrases, + StyleId::new(style_id), + py, + |a, s| async move { synthesizer.replace_phoneme_length(&a, s).await }, + ) + } + + fn replace_mora_pitch<'py>( + &self, + accent_phrases: &'py PyList, + style_id: u32, + py: Python<'py>, + ) -> PyResult<&'py PyAny> { + let synthesizer = self.synthesizer.get()?.clone(); + crate::convert::async_modify_accent_phrases( + accent_phrases, + StyleId::new(style_id), + py, + |a, s| async move { synthesizer.replace_mora_pitch(&a, s).await }, + ) + } + + #[pyo3(signature=(audio_query,style_id,enable_interrogative_upspeak = TtsOptions::default().enable_interrogative_upspeak))] + fn synthesis<'py>( + &self, + #[pyo3(from_py_with = "crate::convert::from_dataclass")] audio_query: AudioQueryModel, + style_id: u32, + enable_interrogative_upspeak: bool, + py: Python<'py>, + ) -> PyResult<&'py PyAny> { + let synthesizer = self.synthesizer.get()?.clone(); + pyo3_asyncio::tokio::future_into_py_with_locals( + py, + pyo3_asyncio::tokio::get_current_locals(py)?, + async move { + let wav = synthesizer + .synthesis( + &audio_query, + StyleId::new(style_id), + &SynthesisOptions { + enable_interrogative_upspeak, + }, + ) + .await; + Python::with_gil(|py| { + let wav = wav.into_py_result(py)?; + Ok(PyBytes::new(py, &wav).to_object(py)) + }) + }, + ) + } + + #[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.tts_from_kana(&kana, style_id, &options).await; + + Python::with_gil(|py| { + let wav = wav.into_py_result(py)?; + Ok(PyBytes::new(py, &wav).to_object(py)) + }) + }, + ) + } + + #[pyo3(signature=( + text, + style_id, + enable_interrogative_upspeak = TtsOptions::default().enable_interrogative_upspeak + ))] + fn tts<'py>( + &self, + text: &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 text = text.to_owned(); + pyo3_asyncio::tokio::future_into_py_with_locals( + py, + pyo3_asyncio::tokio::get_current_locals(py)?, + async move { + let wav = synthesizer.tts(&text, style_id, &options).await; + + Python::with_gil(|py| { + let wav = wav.into_py_result(py)?; + Ok(PyBytes::new(py, &wav).to_object(py)) + }) + }, + ) + } + + fn close(&mut self) { + self.synthesizer.close() + } + } + + #[pyclass] + #[derive(Default, Debug, Clone)] + pub(crate) struct UserDict { + dict: Arc, + } + + #[pymethods] + impl UserDict { + #[new] + fn new() -> Self { + Self::default() + } + + fn load<'py>(&self, path: &str, py: Python<'py>) -> PyResult<&'py PyAny> { + let this = self.dict.clone(); + let path = path.to_owned(); + + pyo3_asyncio::tokio::future_into_py(py, async move { + let result = this.load(&path).await; + Python::with_gil(|py| result.into_py_result(py)) + }) + } + + fn save<'py>(&self, path: &str, py: Python<'py>) -> PyResult<&'py PyAny> { + let this = self.dict.clone(); + let path = path.to_owned(); + + pyo3_asyncio::tokio::future_into_py(py, async move { + let result = this.save(&path).await; + Python::with_gil(|py| result.into_py_result(py)) + }) + } + + fn add_word( + &mut self, + #[pyo3(from_py_with = "crate::convert::to_rust_user_dict_word")] word: UserDictWord, + py: Python<'_>, + ) -> PyResult { + let uuid = self.dict.add_word(word).into_py_result(py)?; + + crate::convert::to_py_uuid(py, uuid) + } + + fn update_word( + &mut self, + #[pyo3(from_py_with = "crate::convert::to_rust_uuid")] word_uuid: Uuid, + #[pyo3(from_py_with = "crate::convert::to_rust_user_dict_word")] word: UserDictWord, + py: Python<'_>, + ) -> PyResult<()> { + self.dict.update_word(word_uuid, word).into_py_result(py)?; + Ok(()) + } + + fn remove_word( + &mut self, + #[pyo3(from_py_with = "crate::convert::to_rust_uuid")] word_uuid: Uuid, + py: Python<'_>, + ) -> PyResult<()> { + self.dict.remove_word(word_uuid).into_py_result(py)?; + Ok(()) + } + + fn import_dict(&mut self, other: &UserDict, py: Python<'_>) -> PyResult<()> { + self.dict.import(&other.dict).into_py_result(py)?; + Ok(()) + } + + #[getter] + fn words<'py>(&self, py: Python<'py>) -> PyResult<&'py PyDict> { + let words = self.dict.with_words(|words| { + words + .iter() + .map(|(&uuid, word)| { + let uuid = crate::convert::to_py_uuid(py, uuid)?; + let word = crate::convert::to_py_user_dict_word(py, word)?; + Ok((uuid, word)) + }) + .collect::>>() + })?; + Ok(words.into_py_dict(py)) + } + } +} diff --git a/crates/xtask/Cargo.toml b/crates/xtask/Cargo.toml index fd4ea9a0f..63d14515a 100644 --- a/crates/xtask/Cargo.toml +++ b/crates/xtask/Cargo.toml @@ -1,8 +1,6 @@ [package] name = "xtask" -version = "0.0.0" edition.workspace = true -publish.workspace = true [dependencies] cbindgen.workspace = true diff --git a/docs/vvm.md b/docs/vvm.md index 572ffee1a..cc7b9232e 100644 --- a/docs/vvm.md +++ b/docs/vvm.md @@ -1,9 +1,24 @@ # VVM ファイル -音声合成するために必要な onnx モデルファイルなどがまとめられた zip 形式のファイル。 -root パスに確定で`manifest.json`を持つ。 +***VVM ファイル*** は、音声合成に必要な声情報を含むファイルである。 + +より正確には、音声合成のモデル重みファイルなどを含む zip 形式のアーカイブである。拡張子は `.vvm`。 +以下の内部ディレクトリ構造を持つ: + +- `{filename}.vvm` + - `manifest.json` + - `metas.json` + - + - + - + +model は `.onnx` や `.bin` など様々ある。例えば `sample.vvm` は `predict_duration.onnx` / `predict_intonation.onnx` / `decode.onnx` を含む。 + +VOICEVOX OSS が提供する VVM には `sample.vvm` がある(ビルドを行うと `crates/test_util/data/model/sample.vvm` が生成される)。 +製品版 VOICEVOX で利用される VVM は [こちらのレポジトリ](https://github.com/VOICEVOX/voicevox_fat_resource/tree/main/core/model) で確認できる。 ## マニフェストファイル -ファイルの構成や、onnx モデルなどを読み込む・利用するのに必要な情報を記述した json ファイル。 -root パスに`manifest.json`として配置する。 +VVM における ***マニフェストファイル*** は、VVM ファイルの構成や、onnx モデルなどを読み込む・利用するのに必要な情報を記述したファイルである。 +json 形式で記述され、root パスに`manifest.json`として配置する。 +[VOICEVOX CORE のソースコード](https://github.com/VOICEVOX/voicevox_core/blob/main/crates/voicevox_core/src/manifest.rs) 内で `Manifest` 構造体としてスキーマが定義されている。 diff --git a/example/kotlin/README.md b/example/kotlin/README.md index 908bdd51f..3bcdafc94 100644 --- a/example/kotlin/README.md +++ b/example/kotlin/README.md @@ -57,9 +57,9 @@ Options: ## 実行例 ```console -❯ ./gradlew run --args="--vvm ../../model/sample.vvm" +❯ ./gradlew run --args="--vvm ../../crates/test_util/data/model/sample.vvm" Inititalizing: AUTO, ./open_jtalk_dic_utf_8-1.11 -Loading: ../../model/sample.vvm +Loading: ../../crates/test_util/data/model/sample.vvm Creating an AudioQuery from the text: この音声は、ボイスボックスを使用して、出力されています。 Synthesizing... Saving the audio to ./output.wav diff --git a/example/python/README.md b/example/python/README.md index 392896030..97303eb81 100644 --- a/example/python/README.md +++ b/example/python/README.md @@ -69,12 +69,12 @@ optional arguments: ## 実行例 ```console -❯ python ./run.py ../../model/sample.vvm +❯ python ./run.py ../../crates/test_util/data/model/sample.vvm [DEBUG] __main__: voicevox_core.supported_devices()=SupportedDevices(cpu=True, cuda=False, dml=False) [INFO] __main__: Initializing (acceleration_mode=, open_jtalk_dict_dir=PosixPath('open_jtalk_dic_utf_8-1.11')) [DEBUG] __main__: synthesizer.metas=[] [DEBUG] __main__: synthesizer.is_gpu_mode=False -[INFO] __main__: Loading `../../model/sample.vvm` +[INFO] __main__: Loading `../../crates/test_util/data/model/sample.vvm` [INFO] __main__: Creating an AudioQuery from 'この音声は、ボイスボックスを使用して、出力されています。' [INFO] __main__: Synthesizing with {"accent_phrases": [{"moras": [{"text": "コ", "consonant": "k", "consonant_length": 0.0556899, "vowel": "o", "vowel_length": 0.075180575, "pitch": 5.542309}, {"text": "ノ", "consonant": "n", "consonant_length": 0.06551014, "vowel": "o", "vowel_length": 0.09984577, "pitch": 5.6173983}], "accent": 2, "pause_mora": null, "is_interrogative": false}, {"moras": [{"text": "オ", "consonant": null, "consonant_length": null, "vowel": "o", "vowel_length": 0.116150305, "pitch": 5.7063766}, {"text": "ン", "consonant": null, "consonant_length": null, "vowel": "N", "vowel_length": 0.044380233, "pitch": 5.785717}, {"text": "セ", "consonant": "s", "consonant_length": 0.07719758, "vowel": "e", "vowel_length": 0.08653869, "pitch": 5.662092}, {"text": "エ", "consonant": null, "consonant_length": null, "vowel": "e", "vowel_length": 0.08311573, "pitch": 5.532917}, {"text": "ワ", "consonant": "w", "consonant_length": 0.06373148, "vowel": "a", "vowel_length": 0.16219379, "pitch": 5.293258}], "accent": 1, "pause_mora": {"text": "、", "consonant": null, "consonant_length": null, "vowel": "pau", "vowel_length": 0.35826492, "pitch": 0.0}, "is_interrogative": false}, {"moras": [{"text": "ボ", "consonant": "b", "consonant_length": 0.047082342, "vowel": "o", "vowel_length": 0.12611786, "pitch": 5.583892}, {"text": "イ", "consonant": null, "consonant_length": null, "vowel": "i", "vowel_length": 0.059451744, "pitch": 5.7947493}, {"text": "ス", "consonant": "s", "consonant_length": 0.089278996, "vowel": "u", "vowel_length": 0.11847979, "pitch": 5.818695}, {"text": "ボ", "consonant": "b", "consonant_length": 0.06535433, "vowel": "o", "vowel_length": 0.120458946, "pitch": 5.7965107}, {"text": "ッ", "consonant": null, "consonant_length": null, "vowel": "cl", "vowel_length": 0.06940381, "pitch": 0.0}, {"text": "ク", "consonant": "k", "consonant_length": 0.053739145, "vowel": "U", "vowel_length": 0.05395376, "pitch": 0.0}, {"text": "ス", "consonant": "s", "consonant_length": 0.10222931, "vowel": "u", "vowel_length": 0.071811065, "pitch": 5.8024883}, {"text": "オ", "consonant": null, "consonant_length": null, "vowel": "o", "vowel_length": 0.11092262, "pitch": 5.5036163}], "accent": 4, "pause_mora": null, "is_interrogative": false}, {"moras": [{"text": "シ", "consonant": "sh", "consonant_length": 0.09327768, "vowel": "i", "vowel_length": 0.09126951, "pitch": 5.369444}, {"text": "ヨ", "consonant": "y", "consonant_length": 0.06251812, "vowel": "o", "vowel_length": 0.07805054, "pitch": 5.5021667}, {"text": "オ", "consonant": null, "consonant_length": null, "vowel": "o", "vowel_length": 0.09904325, "pitch": 5.5219536}], "accent": 3, "pause_mora": null, "is_interrogative": false}, {"moras": [{"text": "シ", "consonant": "sh", "consonant_length": 0.04879771, "vowel": "I", "vowel_length": 0.06514315, "pitch": 0.0}, {"text": "テ", "consonant": "t", "consonant_length": 0.0840496, "vowel": "e", "vowel_length": 0.19438823, "pitch": 5.4875555}], "accent": 2, "pause_mora": {"text": "、", "consonant": null, "consonant_length": null, "vowel": "pau", "vowel_length": 0.35208154, "pitch": 0.0}, "is_interrogative": false}, {"moras": [{"text": "シュ", "consonant": "sh", "consonant_length": 0.05436731, "vowel": "U", "vowel_length": 0.06044446, "pitch": 0.0}, {"text": "ツ", "consonant": "ts", "consonant_length": 0.102865085, "vowel": "u", "vowel_length": 0.057028636, "pitch": 5.6402535}, {"text": "リョ", "consonant": "ry", "consonant_length": 0.058293864, "vowel": "o", "vowel_length": 0.080050275, "pitch": 5.6997967}, {"text": "ク", "consonant": "k", "consonant_length": 0.054767884, "vowel": "U", "vowel_length": 0.042932786, "pitch": 0.0}], "accent": 2, "pause_mora": null, "is_interrogative": false}, {"moras": [{"text": "サ", "consonant": "s", "consonant_length": 0.08067487, "vowel": "a", "vowel_length": 0.07377973, "pitch": 5.652378}, {"text": "レ", "consonant": "r", "consonant_length": 0.040600352, "vowel": "e", "vowel_length": 0.079322875, "pitch": 5.6290326}, {"text": "テ", "consonant": "t", "consonant_length": 0.06773268, "vowel": "e", "vowel_length": 0.08347456, "pitch": 5.6427326}], "accent": 3, "pause_mora": null, "is_interrogative": false}, {"moras": [{"text": "イ", "consonant": null, "consonant_length": null, "vowel": "i", "vowel_length": 0.07542324, "pitch": 5.641289}, {"text": "マ", "consonant": "m", "consonant_length": 0.066299975, "vowel": "a", "vowel_length": 0.107257664, "pitch": 5.6201453}, {"text": "ス", "consonant": "s", "consonant_length": 0.07186453, "vowel": "U", "vowel_length": 0.1163103, "pitch": 0.0}], "accent": 2, "pause_mora": null, "is_interrogative": false}], "speed_scale": 1.0, "pitch_scale": 0.0, "intonation_scale": 1.0, "volume_scale": 1.0, "pre_phoneme_length": 0.1, "post_phoneme_length": 0.1, "output_sampling_rate": 24000, "output_stereo": false, "kana": "コノ'/オ'ンセエワ、ボイスボ'ッ_クスオ/シヨオ'/_シテ'、_シュツ' リョ_ク/サレテ'/イマ'_ス"} [INFO] __main__: Wrote `output.wav` diff --git a/model/sample.vvm b/model/sample.vvm deleted file mode 100644 index 48d23745d..000000000 Binary files a/model/sample.vvm and /dev/null differ diff --git a/crates/voicevox_core/src/test_data/model_sources/load_model_works1/decode.onnx b/model/sample.vvm/decode.onnx similarity index 100% rename from crates/voicevox_core/src/test_data/model_sources/load_model_works1/decode.onnx rename to model/sample.vvm/decode.onnx diff --git a/model/sample.vvm/manifest.json b/model/sample.vvm/manifest.json new file mode 100644 index 000000000..2c6721d08 --- /dev/null +++ b/model/sample.vvm/manifest.json @@ -0,0 +1,13 @@ +{ + "manifest_version": "0.0.0", + "metas_filename": "metas.json", + "talk": { + "predict_duration_filename": "predict_duration.onnx", + "predict_intonation_filename": "predict_intonation.onnx", + "decode_filename": "decode.onnx", + "style_id_to_model_inner_id": { + "302": 2, + "303": 3 + } + } +} diff --git a/crates/voicevox_core/src/test_data/model_sources/load_model_works1/metas.json b/model/sample.vvm/metas.json similarity index 100% rename from crates/voicevox_core/src/test_data/model_sources/load_model_works1/metas.json rename to model/sample.vvm/metas.json diff --git a/crates/voicevox_core/src/test_data/model_sources/load_model_works1/predict_duration.onnx b/model/sample.vvm/predict_duration.onnx similarity index 100% rename from crates/voicevox_core/src/test_data/model_sources/load_model_works1/predict_duration.onnx rename to model/sample.vvm/predict_duration.onnx diff --git a/crates/voicevox_core/src/test_data/model_sources/load_model_works1/predict_intonation.onnx b/model/sample.vvm/predict_intonation.onnx similarity index 100% rename from crates/voicevox_core/src/test_data/model_sources/load_model_works1/predict_intonation.onnx rename to model/sample.vvm/predict_intonation.onnx diff --git a/renovate.json b/renovate.json index 3e6f4eae4..5704cd5f4 100644 --- a/renovate.json +++ b/renovate.json @@ -8,12 +8,12 @@ { "groupSlug": "rust", "groupName": "Rust", - "matchPackagePatterns": "^Rust$" + "matchDepPatterns": "^Rust$" }, { "groupSlug": "others", "groupName": "Others", - "excludePackagePatterns": "^Rust$", + "excludeDepPatterns": "^Rust$", "dependencyDashboardApproval": true } ], diff --git a/rust-toolchain b/rust-toolchain index 80627411d..54227249d 100644 --- a/rust-toolchain +++ b/rust-toolchain @@ -1 +1 @@ -1.74.1 +1.78.0