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 263e287f9..9283b273d 100644 --- a/.github/workflows/build_and_deploy.yml +++ b/.github/workflows/build_and_deploy.yml @@ -41,7 +41,14 @@ defaults: shell: bash jobs: - config: # 全 jobs で利用する定数の定義。実行対象の条件をフィルタリングする。 + # 全 jobs で利用する定数の定義。実行対象の条件をフィルタリングする。 + # + # c_release_format = plain-cdylib | ios-xcframework + # + # `plain-cdylib`の場合、動的ライブラリとその付属物をZIPに固めたものをC APIとしてリリースする。 + # `ios-xcframework`の場合はiOS用のXCFrameworkをC APIとしてリリースする。また、ONNX Runtimeの + # リンク方法に関わるCargoフィーチャも`c_release_format`によって選択される。 + config: runs-on: ubuntu-latest outputs: includes: ${{ steps.strategy_matrix.outputs.includes }} @@ -54,123 +61,90 @@ jobs: includes='[ { "os": "windows-2019", - "features": "", "target": "x86_64-pc-windows-msvc", - "artifact_name": "windows-x64-cpu", - "whl_local_version": "cpu", - "use_cuda": false, - "can_skip_in_simple_test": true - }, - { - "os": "windows-2019", - "features": "directml", - "target": "x86_64-pc-windows-msvc", - "artifact_name": "windows-x64-directml", - "whl_local_version": "directml", - "use_cuda": false, + "artifact_name": "windows-x64", + "c_release_format": "plain-cdylib", + "python_whl": true, "can_skip_in_simple_test": false }, { "os": "windows-2019", - "features": "", - "target": "x86_64-pc-windows-msvc", - "artifact_name": "windows-x64-cuda", - "whl_local_version": "cuda", - "use_cuda": true, - "can_skip_in_simple_test": true - }, - { - "os": "windows-2019", - "features": "", "target": "i686-pc-windows-msvc", - "artifact_name": "windows-x86-cpu", - "whl_local_version": "cpu", - "use_cuda": false, + "artifact_name": "windows-x86", + "c_release_format": "plain-cdylib", + "python_whl": true, "can_skip_in_simple_test": true }, { "os": "ubuntu-20.04", - "features": "", "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": "", - "target": "x86_64-unknown-linux-gnu", - "artifact_name": "linux-x64-gpu", - "whl_local_version": "cuda", - "use_cuda": true, + "artifact_name": "linux-x64", + "c_release_format": "plain-cdylib", + "python_whl": true, "can_skip_in_simple_test": false }, { "os": "ubuntu-20.04", - "features": "", "target": "aarch64-unknown-linux-gnu", - "artifact_name": "linux-arm64-cpu", - "whl_local_version": "cpu", - "use_cuda": false, + "artifact_name": "linux-arm64", + "c_release_format": "plain-cdylib", + "python_whl": true, "can_skip_in_simple_test": true }, { "os": "ubuntu-20.04", - "features": "", "target": "aarch64-linux-android", - "artifact_name": "android-arm64-cpu", - "use_cuda": false, + "artifact_name": "android-arm64", + "c_release_format": "plain-cdylib", + "python_whl": false, "can_skip_in_simple_test": true }, { "os": "ubuntu-20.04", - "features": "", "target": "x86_64-linux-android", - "artifact_name": "android-x86_64-cpu", - "use_cuda": false, + "artifact_name": "android-x86_64", + "c_release_format": "plain-cdylib", + "python_whl": false, "can_skip_in_simple_test": true }, { - "os": "macos-11", - "features": "", + "os": "macos-12", "target": "aarch64-apple-darwin", - "artifact_name": "osx-arm64-cpu", - "whl_local_version": "cpu", - "use_cuda": false, + "artifact_name": "osx-arm64", + "c_release_format": "plain-cdylib", + "python_whl": true, "can_skip_in_simple_test": false }, { - "os": "macos-11", - "features": "", + "os": "macos-12", "target": "x86_64-apple-darwin", - "artifact_name": "osx-x64-cpu", - "whl_local_version": "cpu", - "use_cuda": false, + "artifact_name": "osx-x64", + "c_release_format": "plain-cdylib", + "python_whl": true, "can_skip_in_simple_test": true }, { "os": "macos-12", - "features": "", "target": "aarch64-apple-ios", "artifact_name": "ios-arm64-cpu", - "use_cuda": false, + "c_release_format": "ios-xcframework", + "python_whl": false, "can_skip_in_simple_test": true }, { "os": "macos-12", - "features": "", "target": "aarch64-apple-ios-sim", "artifact_name": "ios-arm64-cpu-sim", - "use_cuda": false, + "c_release_format": "ios-xcframework", + "python_whl": false, "can_skip_in_simple_test": true }, { "os": "macos-12", - "features": "", "target": "x86_64-apple-ios", "artifact_name": "ios-x64-cpu", - "use_cuda": false, + "c_release_format": "ios-xcframework", + "python_whl": false, "can_skip_in_simple_test": true } ]' @@ -192,9 +166,9 @@ jobs: env: ASSET_NAME: voicevox_core-${{ matrix.artifact_name }}-${{ needs.config.outputs.version }} steps: - - uses: actions/checkout@v3 # 製品版ではない場合 + - uses: actions/checkout@v4 # 製品版ではない場合 if: ${{ !inputs.is_production }} - - uses: actions/checkout@v3 # 製品版の場合 + - uses: actions/checkout@v4 # 製品版の場合 if: inputs.is_production with: fetch-depth: 0 # 全履歴取得 @@ -209,8 +183,8 @@ jobs: git -c user.name=dummy -c user.email=dummy@dummy.dummy merge FETCH_HEAD ) > /dev/null 2>&1 - name: Set up Python 3.8 - if: matrix.whl_local_version - uses: actions/setup-python@v4 + if: matrix.python_whl + uses: actions/setup-python@v5 with: python-version: "3.8" architecture: ${{ contains(matrix.artifact_name,'x86') && 'x86' || 'x64' }} @@ -234,7 +208,7 @@ jobs: echo "AR_${{ matrix.target }}=llvm-ar" >> "$GITHUB_ENV" - name: Checkout VOICEVOX RESOURCE if: inputs.is_production - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: repository: VOICEVOX/voicevox_resource ref: ${{ env.VOICEVOX_RESOURCE_VERSION }} @@ -251,15 +225,19 @@ jobs: - name: set cargo version run: | cargo set-version "$VERSION" --exclude voicevox_core_python_api --exclude downloader --exclude xtask - if ${{ !!matrix.whl_local_version }}; then cargo set-version "$VERSION+"${{ matrix.whl_local_version }} -p voicevox_core_python_api; fi + if ${{ matrix.python_whl }}; then cargo set-version "$VERSION" -p voicevox_core_python_api; fi - name: cache target uses: Swatinem/rust-cache@v2 if: ${{ !inputs.is_production }} - name: build voicevox_core_c_api shell: bash run: | + case ${{ matrix.c_release_format }} in + plain-cdylib) linking=load-onnxruntime ;; + ios-xcframework) linking=link-onnxruntime ;; + esac function build() { - cargo build -p voicevox_core_c_api -vv --features ${{ matrix.features }}, --target ${{ matrix.target }} --release + cargo build -p voicevox_core_c_api -vv --features "$linking" --target ${{ matrix.target }} --release } if ${{ !inputs.is_production }}; then build @@ -268,9 +246,8 @@ jobs: fi env: RUSTFLAGS: -C panic=abort - ORT_USE_CUDA: ${{ matrix.use_cuda }} - name: build voicevox_core_python_api - if: matrix.whl_local_version + if: matrix.python_whl id: build-voicevox-core-python-api run: | rm -rf ./target/wheels @@ -278,7 +255,7 @@ jobs: poetry config virtualenvs.create false (cd crates/voicevox_core_python_api && poetry install --with dev) function build() { - maturin build --manifest-path ./crates/voicevox_core_python_api/Cargo.toml --features ${{ matrix.features }}, --target ${{ matrix.target }} --release + maturin build --manifest-path ./crates/voicevox_core_python_api/Cargo.toml --target ${{ matrix.target }} --release } if ${{ !inputs.is_production }}; then build @@ -286,13 +263,11 @@ 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: | function build() { - cargo build -p voicevox_core_java_api -vv --features ${{ matrix.features }}, --target ${{ matrix.target }} --release + cargo build -p voicevox_core_java_api -vv --target ${{ matrix.target }} --release } if ${{ !inputs.is_production }}; then build @@ -302,12 +277,14 @@ jobs: - name: Organize artifact run: | mkdir -p "artifact/${{ env.ASSET_NAME }}" - cp -v crates/voicevox_core_c_api/include/voicevox_core.h "artifact/${{ env.ASSET_NAME }}" + case ${{ matrix.c_release_format }} in + plain-cdylib) feature=VOICEVOX_LOAD_ONNXRUNTIME ;; + ios-xcframework) feature=VOICEVOX_LINK_ONNXRUNTIME ;; + esac + sed 's:^//\(#define '"$feature"'\)$:\1:' crates/voicevox_core_c_api/include/voicevox_core.h \ + > "artifact/${{ env.ASSET_NAME }}/voicevox_core.h" 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 - # libonnxruntimeについてはバージョン付のshared libraryを使用するためバージョンがついてないものを削除する - rm -f artifact/${{ env.ASSET_NAME }}/libonnxruntime.{so,dylib} cp -v README.md "artifact/${{ env.ASSET_NAME }}/README.txt" echo "${{ env.VERSION }}" > "artifact/${{ env.ASSET_NAME }}/VERSION" @@ -322,8 +299,8 @@ jobs: ESIGNERCKA_PASSWORD: ${{ secrets.ESIGNERCKA_PASSWORD }} ESIGNERCKA_TOTP_SECRET: ${{ secrets.ESIGNERCKA_TOTP_SECRET }} - name: Upload artifact to build XCFramework - if: contains(matrix.target, 'ios') - uses: actions/upload-artifact@v3 + if: matrix.c_release_format == 'ios-xcframework' + uses: actions/upload-artifact@v4 with: name: voicevox_core-${{ matrix.target }} path: artifact/${{ env.ASSET_NAME }} @@ -332,8 +309,8 @@ jobs: cd artifact 7z a "../${{ env.ASSET_NAME }}.zip" "${{ env.ASSET_NAME }}" - name: Upload to Release - if: fromJson(needs.config.outputs.deploy) && !contains(matrix.target, 'ios') - uses: softprops/action-gh-release@v1 + if: fromJson(needs.config.outputs.deploy) && matrix.c_release_format == 'plain-cdylib' + uses: softprops/action-gh-release@v2 with: prerelease: true tag_name: ${{ env.VERSION }} @@ -341,8 +318,8 @@ jobs: ${{ env.ASSET_NAME }}.zip target_commitish: ${{ github.sha }} - name: Upload Python whl to Release - if: fromJson(needs.config.outputs.deploy) && matrix.whl_local_version - uses: softprops/action-gh-release@v1 + if: fromJson(needs.config.outputs.deploy) && matrix.python_whl + uses: softprops/action-gh-release@v2 with: prerelease: true tag_name: ${{ env.VERSION }} @@ -351,7 +328,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 @@ -366,24 +343,24 @@ jobs: 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/checkout@v4 + - uses: actions/download-artifact@v4 with: name: voicevox_core-x86_64-apple-ios path: ${{ env.IOS_X86_64_PATH }} - - uses: actions/download-artifact@v2 + - uses: actions/download-artifact@v4 with: name: voicevox_core-aarch64-apple-ios-sim path: ${{ env.IOS_AARCH64_SIM_PATH }} - - uses: actions/download-artifact@v2 + - uses: actions/download-artifact@v4 with: name: voicevox_core-aarch64-apple-ios path: ${{ env.IOS_AARCH64_PATH }} - name: Create xcframework - id: create-xcframework + id: create-xcframework run: | build_util/make_ios_xcframework.bash - echo "output_asset_path=${OUTPUT_ASSET_PATH}" >> "$GITHUB_OUTPUT" + echo "output_asset_path=${OUTPUT_ASSET_PATH}" >> "$GITHUB_OUTPUT" env: OUTPUT_ASSET_PATH: artifact/voicevox_core-ios-xcframework-cpu - name: Archive artifact @@ -392,7 +369,7 @@ jobs: 7z a "../../${{ env.ASSET_NAME }}.zip" "voicevox_core.xcframework" - name: Upload to Release if: fromJson(needs.config.outputs.deploy) - uses: softprops/action-gh-release@v1 + uses: softprops/action-gh-release@v2 with: prerelease: true tag_name: ${{ env.VERSION }} @@ -406,10 +383,10 @@ jobs: env: ASSET_NAME: model-${{ needs.config.outputs.version }} steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Checkout VOICEVOX FAT RESOURCE if: inputs.is_production - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: repository: VOICEVOX/voicevox_fat_resource ref: ${{ env.VOICEVOX_FAT_RESOURCE_VERSION }} @@ -429,7 +406,7 @@ jobs: 7z a "../${{ env.ASSET_NAME }}.zip" "${{ env.ASSET_NAME }}" - name: Upload to Release if: fromJson(needs.config.outputs.deploy) - uses: softprops/action-gh-release@v1 + uses: softprops/action-gh-release@v2 with: prerelease: true tag_name: ${{ env.VERSION }} @@ -442,11 +419,11 @@ jobs: if: ${{ !(github.event_name != 'release' && github.event_name != 'workflow_dispatch') }} # !env.IS_SIMPLE_TEST と同じ needs: [config, build_and_deploy] steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - 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" @@ -462,17 +439,17 @@ jobs: run: cargo set-version "$VERSION" -p voicevox_core_java_api - - name: "Download artifact (android-arm64-cpu)" - uses: actions/download-artifact@v3 + - name: "Download artifact (android-arm64)" + uses: actions/download-artifact@v4 with: - name: voicevox_core_java_api-android-arm64-cpu - path: artifact/android-arm64-cpu + name: voicevox_core_java_api-android-arm64 + path: artifact/android-arm64 - - name: "Download artifact (android-x86_64-cpu)" - uses: actions/download-artifact@v3 + - name: "Download artifact (android-x86_64)" + uses: actions/download-artifact@v4 with: - name: voicevox_core_java_api-android-x86_64-cpu - path: artifact/android-x86_64-cpu + name: voicevox_core_java_api-android-x86_64 + path: artifact/android-x86_64 - name: Print tree run: tree artifact @@ -481,8 +458,8 @@ jobs: run: | rm -rf crates/voicevox_core_java_api/lib/src/main/resources/dll cat <rustc 1\.[0-9]\+\.[0-9]\+-nightly ([0-9a-f]\{9\} \([0-9]\{4\}-[0-9]\{2\}-[0-9]\{2\}\)).*$:\1:p' \ + <<< "$page" + ) + echo "rust-toolchain=nightly-$date" >> "$GITHUB_OUTPUT" + - name: Set up nightly Rust + uses: dtolnay/rust-toolchain@master + with: + toolchain: ${{ steps.docsrs-rust-version.outputs.rust-toolchain }} - name: Set up Python - uses: actions/setup-python@v4 + uses: actions/setup-python@v5 with: python-version: "3.8" - name: Setup Java - uses: actions/setup-java@v2 + uses: actions/setup-java@v4 with: java-version: "11" distribution: "adopt" - - name: Install cargo-binstall - uses: taiki-e/install-action@cargo-binstall + - name: Install cargo-docs-rs + uses: dtolnay/install@cargo-docs-rs - name: Create a venv uses: ./.github/actions/create-venv - name: Install python dependencies @@ -39,19 +53,18 @@ jobs: run: mkdir -p public/apis/c_api - name: cp docs/apis/index.html run: cp docs/apis/index.html public/apis/ + - name: Generate rustdoc + run: | + cargo +${{ steps.docsrs-rust-version.outputs.rust-toolchain }} docs-rs -p voicevox_core + mv target/x86_64-unknown-linux-gnu/doc public/apis/rust_api - name: cp crates/voicevox_core_c_api/include/voicevox_core.h run: cp crates/voicevox_core_c_api/include/voicevox_core.h docs/apis/c_api/doxygen/ - name: Generate doxygen document - uses: mattnotmitt/doxygen-action@v1.9.4 + uses: mattnotmitt/doxygen-action@v1.9.8 with: working-directory: "docs/apis/c_api/doxygen" - name: Build voicevox_core_python_api - run: | - cargo build -p voicevox_core_c_api -vv - maturin develop --manifest-path ./crates/voicevox_core_python_api/Cargo.toml --locked - # https://github.com/readthedocs/sphinx-autoapi/issues/405 - - name: Workaround to make Sphinx recognize `_rust` as a module - run: touch ./crates/voicevox_core_python_api/python/voicevox_core/_rust/__init__.py + run: maturin develop --manifest-path ./crates/voicevox_core_python_api/Cargo.toml --locked - name: Generate Sphinx document run: sphinx-build docs/apis/python_api public/apis/python_api - name: Generate Javadoc @@ -60,7 +73,7 @@ jobs: mkdir -p public/apis/java_api cp -r crates/voicevox_core_java_api/lib/build/docs/javadoc/* public/apis/java_api - name: Uplaod api document - uses: actions/upload-pages-artifact@v1 + uses: actions/upload-pages-artifact@v3 if: ${{ github.ref_name == 'main' }} with: path: public @@ -78,4 +91,4 @@ jobs: steps: - name: Deploy to GitHub pages id: api - uses: actions/deploy-pages@v1 + uses: actions/deploy-pages@v4 diff --git a/.github/workflows/java_lint.yml b/.github/workflows/java_lint.yml index c75222912..6b7ff9e46 100644 --- a/.github/workflows/java_lint.yml +++ b/.github/workflows/java_lint.yml @@ -21,8 +21,8 @@ jobs: java-lint: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 - - uses: actions/setup-java@v2 + - uses: actions/checkout@v4 + - uses: actions/setup-java@v4 with: java-version: "11" distribution: "adopt" diff --git a/.github/workflows/labeler.yml b/.github/workflows/labeler.yml index 25d78ec6a..3299f1586 100644 --- a/.github/workflows/labeler.yml +++ b/.github/workflows/labeler.yml @@ -10,7 +10,7 @@ jobs: triage: runs-on: ubuntu-latest steps: - - uses: github/issue-labeler@v2.5 + - uses: github/issue-labeler@v3.4 with: repo-token: "${{ secrets.GITHUB_TOKEN }}" configuration-path: .github/labeler.yml diff --git a/.github/workflows/python_lint.yml b/.github/workflows/python_lint.yml index 22449c391..486119e2c 100644 --- a/.github/workflows/python_lint.yml +++ b/.github/workflows/python_lint.yml @@ -21,8 +21,8 @@ jobs: python: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 - - uses: actions/setup-python@v4 + - uses: actions/checkout@v4 + - uses: actions/setup-python@v5 with: python-version: "3.8" - name: Install Poetry diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 1e38b1492..d84d16442 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -18,7 +18,7 @@ jobs: shellcheck: runs-on: ubuntu-22.04 steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Update ShellCheck run: | sudo apt-get update @@ -29,7 +29,7 @@ jobs: actionlint: runs-on: ubuntu-22.04 steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 # ShellCheckとPyflakesをインストールしておくと、shell: bashとshell: pythonのコードを検査してくれるようになる # # 参考: @@ -40,7 +40,7 @@ jobs: sudo apt-get update sudo apt-get install -y shellcheck - name: Set up Python 3.8 - uses: actions/setup-python@v4 + uses: actions/setup-python@v5 with: python-version: "3.8" - name: Install Pyflakes @@ -53,7 +53,7 @@ jobs: validate-cargo-lock: runs-on: ubuntu-22.04 steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Set up Rust uses: ./.github/actions/rust-toolchain-from-file - name: Validate Cargo.lock @@ -62,31 +62,33 @@ jobs: rust-lint: runs-on: windows-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Set up Rust uses: ./.github/actions/rust-toolchain-from-file with: components: clippy,rustfmt - name: Set up Python 3.8 - uses: actions/setup-python@v4 + uses: actions/setup-python@v5 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 --tests -- -D clippy::all -D warnings --no-deps + - run: cargo clippy -vv -- -D clippy::all -D warnings --no-deps + - run: cargo clippy -vv -p voicevox_core -p voicevox_core_c_api --features link-onnxruntime --tests -- -D clippy::all -D warnings --no-deps + - run: cargo clippy -vv -p voicevox_core -p voicevox_core_c_api --features link-onnxruntime -- -D clippy::all -D warnings --no-deps - run: cargo fmt -- --check rust-unit-test: runs-on: windows-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Set up Rust uses: ./.github/actions/rust-toolchain-from-file - uses: Swatinem/rust-cache@v2 with: key: "cargo-unit-test-cache" - name: Run cargo unit test - run: RUST_BACKTRACE=full cargo test --lib --bins -vv --features , -- --include-ignored + run: RUST_BACKTRACE=full cargo test --lib --bins -vv -- --include-ignored - name: Run cargo documentation test run: RUST_BACKTRACE=full cargo test --doc -vv @@ -99,14 +101,12 @@ jobs: id: strategy-matrix run: | includes='[ - { "os": "windows-2019", "features": "", "can_skip_in_simple_test": true }, - { "os": "windows-2022", "features": "", "can_skip_in_simple_test": true }, - { "os": "windows-2019", "features": "directml", "can_skip_in_simple_test": false }, - { "os": "windows-2022", "features": "directml", "can_skip_in_simple_test": true }, - { "os": "macos-11", "features": "", "can_skip_in_simple_test": false }, - { "os": "macos-12", "features": "", "can_skip_in_simple_test": true }, - { "os": "ubuntu-20.04", "features": "", "can_skip_in_simple_test": false }, - { "os": "ubuntu-22.04", "features": "", "can_skip_in_simple_test": true } + { "os": "windows-2019", "can_skip_in_simple_test": true }, + { "os": "windows-2022", "can_skip_in_simple_test": true }, + { "os": "macos-12", "can_skip_in_simple_test": false }, + { "os": "macos-13", "can_skip_in_simple_test": true }, + { "os": "ubuntu-20.04", "can_skip_in_simple_test": false }, + { "os": "ubuntu-22.04", "can_skip_in_simple_test": true } ]' # FIXME: composite action に切り出す @@ -124,23 +124,23 @@ jobs: include: ${{ fromJson(needs.rust-integration-test-strategy-matrix.outputs.includes) }} runs-on: ${{ matrix.os }} steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Set up Python 3.8 - uses: actions/setup-python@v4 + uses: actions/setup-python@v5 with: python-version: "3.8" - name: Set up Rust uses: ./.github/actions/rust-toolchain-from-file - uses: Swatinem/rust-cache@v2 with: - key: "cargo-integration-test-cache-${{ matrix.features }}-${{ matrix.os }}" - - name: Run cargo integration test - run: RUST_BACKTRACE=full cargo test --test "*" -vv --features ,${{ matrix.features }} -- --include-ignored + key: "cargo-integration-test-cache-${{ matrix.os }}" + - name: Run cargo integration test (load-onnxruntime) + run: RUST_BACKTRACE=full cargo test --test "*" -vv -- --include-ignored c-header: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Set up Rust uses: ./.github/actions/rust-toolchain-from-file - name: Install cargo-binstall @@ -187,23 +187,25 @@ jobs: artifact_name: linux-x64-cpu-cpp-shared runs-on: ${{ matrix.os }} steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Set up Rust uses: ./.github/actions/rust-toolchain-from-file - name: Install cargo-binstall uses: taiki-e/install-action@cargo-binstall - name: build voicevox_core_c_api - run: cargo build -p voicevox_core_c_api -vv + run: cargo build -p voicevox_core_c_api --features load-onnxruntime -vv - name: 必要なfileをunix用exampleのディレクトリに移動させる run: | mkdir -p example/cpp/unix/voicevox_core/ - cp -v crates/voicevox_core_c_api/include/voicevox_core.h example/cpp/unix/voicevox_core/ + sed 's:^//\(#define VOICEVOX_LOAD_ONNXRUNTIME\)$:\1:' \ + crates/voicevox_core_c_api/include/voicevox_core.h \ + > example/cpp/unix/voicevox_core/voicevox_core.h 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 + uses: jwlawson/actions-setup-cmake@v2 - name: Install build dependencies if: startsWith(matrix.os, 'ubuntu') run: | @@ -229,21 +231,24 @@ jobs: shell: pwsh steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Set up Rust uses: ./.github/actions/rust-toolchain-from-file - name: Install cargo-binstall uses: taiki-e/install-action@cargo-binstall - name: build voicevox_core_c_api - run: cargo build -p voicevox_core_c_api -vv + run: cargo build -p voicevox_core_c_api --features load-onnxruntime -vv - name: 必要なfileをexampleのディレクトリに移動させる + shell: bash run: | mkdir -p example/cpp/windows/simple_tts/lib/x64 - cp -v crates/voicevox_core_c_api/include/voicevox_core.h example/cpp/windows/simple_tts/ + sed 's:^//\(#define VOICEVOX_LOAD_ONNXRUNTIME\)$:\1:' \ + crates/voicevox_core_c_api/include/voicevox_core.h \ + > example/cpp/windows/simple_tts/voicevox_core.h cp target/debug/voicevox_core.dll.lib example/cpp/windows/simple_tts/lib/x64/voicevox_core.lib - name: Add MSBuild to PATH - uses: microsoft/setup-msbuild@v1.0.2 + uses: microsoft/setup-msbuild@v2 - name: Restore NuGet packages working-directory: ${{env.GITHUB_WORKSPACE}} run: nuget restore ${{env.SOLUTION_FILE_PATH}} @@ -266,9 +271,9 @@ jobs: shell: bash working-directory: ./crates/voicevox_core_python_api steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Set up Python 3.8 - uses: actions/setup-python@v4 + uses: actions/setup-python@v5 with: python-version: "3.8" - name: Set up Rust @@ -281,15 +286,15 @@ jobs: - 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: @@ -301,7 +306,7 @@ jobs: - os: ubuntu-latest runs-on: ${{ matrix.os }} steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Set up Rust uses: ./.github/actions/rust-toolchain-from-file - name: Set up Java diff --git a/.github/workflows/typos.yml b/.github/workflows/typos.yml index a42e05dcf..b4fd11b01 100644 --- a/.github/workflows/typos.yml +++ b/.github/workflows/typos.yml @@ -16,7 +16,7 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: typos-action - uses: crate-ci/typos@v1.12.12 + uses: crate-ci/typos@v1.25.0 diff --git a/Cargo.lock b/Cargo.lock index 18a647e8a..335b33d38 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -18,24 +18,10 @@ 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" +name = "adler2" +version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "884391ef1066acaa41e766ba8f596341b96e93ce34f9a43e7d24bf0a0eaf0561" -dependencies = [ - "aes-soft", - "aesni", - "cipher 0.2.5", -] +checksum = "512761e0bb2578dd7380c6baaa0f4ce03e84f95e960231d1dec8bf4d7d6e2627" [[package]] name = "aes" @@ -44,43 +30,22 @@ 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" +name = "ahash" +version = "0.8.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5278b5fabbb9bd46e24aa69b2fdea62c99088e0a950a9be40e3e0101298f88da" +checksum = "e89da841a80418a9b391ebaea17f5c112ffaaa96f621d2c285b5174da76b9011" 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", + "cfg-if", + "getrandom", + "once_cell", + "version_check", + "zerocopy", ] [[package]] @@ -144,11 +109,26 @@ dependencies = [ "utf8parse", ] +[[package]] +name = "anstream" +version = "0.6.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "64e15c1ab1f89faffbf04a634d5e1962e9074f2741eef6d97f3c4e322426d526" +dependencies = [ + "anstyle", + "anstyle-parse", + "anstyle-query", + "anstyle-wincon", + "colorchoice", + "is_terminal_polyfill", + "utf8parse", +] + [[package]] name = "anstyle" -version = "1.0.3" +version = "1.0.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b84bf0a05bbb2a83e5eb6fa36bb6e87baa08193c35ff52bbf6b38d8af2890e46" +checksum = "1bec1de6f59aedf83baf9ff929c98f2ad654b97c9510f4e70cf6f661d49fd5b1" [[package]] name = "anstyle-parse" @@ -161,18 +141,28 @@ dependencies = [ [[package]] name = "anstyle-query" -version = "1.0.0" +version = "1.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ca11d4be1bab0c8bc8734a9aa7bf4ee8316d462a08c6ac5052f888fef5b494b" +checksum = "6d36fc52c7f6c869915e99412912f22093507da8d9e942ceaf66fe4b7c14422a" dependencies = [ - "windows-sys 0.48.0", + "windows-sys 0.52.0", +] + +[[package]] +name = "anstyle-wincon" +version = "3.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5bf74e1b6e971609db8ca7a9ce79fd5768ab6ae46441c572e46cf596f59e57f8" +dependencies = [ + "anstyle", + "windows-sys 0.52.0", ] [[package]] name = "anyhow" -version = "1.0.65" +version = "1.0.89" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "98161a4e3e2184da77bb14f02184cdd111e83bbbcc9979dfee3c44b9a85f5602" +checksum = "86fdf8605db99b54d3cd748a44c6d04df638eb5dafb219b135d0149bd0db01f6" [[package]] name = "arc-swap" @@ -182,39 +172,31 @@ checksum = "983cd8b9d4b02a6dc6ffa557262eb5858a27a0038ffffe21a0f133eaa819a164" [[package]] name = "assert_cmd" -version = "2.0.8" +version = "2.0.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9834fcc22e0874394a010230586367d4a3e9f11b560f469262678547e1d2575e" +checksum = "dc1835b7f27878de8525dc71410b5a31cdcc5f230aed5ba5df968e09c201b23d" dependencies = [ + "anstream 0.6.15", + "anstyle", "bstr", - "concolor", "doc-comment", + "libc", "predicates", "predicates-core", "predicates-tree", "wait-timeout", - "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" +version = "2.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e14485364214912d3b19cc3435dde4df66065127f05fa0d75c712f36f12c2f28" +checksum = "89b47800b0be77592da0afd425cc03468052844aff33b84e33cc696f64e77b6a" dependencies = [ "concurrent-queue", - "event-listener", + "event-listener-strategy", "futures-core", + "pin-project-lite", ] [[package]] @@ -231,95 +213,32 @@ dependencies = [ ] [[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 1.8.0", - "futures-lite 1.12.0", - "once_cell", - "slab", -] - -[[package]] -name = "async-global-executor" -version = "2.3.0" +name = "async-fs" +version = "2.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0da5b41ee986eed3f524c380e6d64965aea573882a8907682ad100f7859305ca" +checksum = "ebcd09b382f40fcd159c2d695175b2ae620ffa5f3bd6f664131efff4e8b9e04a" dependencies = [ - "async-channel", - "async-executor", - "async-io", "async-lock", "blocking", - "futures-lite 1.12.0", - "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 1.12.0", - "libc", - "log", - "once_cell", - "parking", - "polling", - "slab", - "socket2", - "waker-fn", - "winapi", + "futures-lite", ] [[package]] name = "async-lock" -version = "2.5.0" +version = "3.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e97a171d191782fba31bb902b14ad94e24a68145032b7eedf871ab0bc0d077b6" +checksum = "ff6e472cdea888a4bd64f342f09b3f50e1886d32afe8df3d663c01140b811b18" 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 1.12.0", - "gloo-timers", - "kv-log-macro", - "log", - "memchr", - "once_cell", + "event-listener-strategy", "pin-project-lite", - "pin-utils", - "slab", - "wasm-bindgen-futures", ] [[package]] name = "async-task" -version = "4.3.0" +version = "4.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a40729d2133846d9ed0ea60a8b9541bccddab49cd30f0715a1da672fe9a2524" +checksum = "8b75356056920673b02621b35afd0f7dda9306d03c79a30f5c56c44cf256e3de" [[package]] name = "async-trait" @@ -340,27 +259,16 @@ checksum = "527207465fb6dcafbf661b0d4a51d0d2306c9d0c2975423079a6caa807930daf" dependencies = [ "async-compression", "crc32fast", - "futures-lite 2.2.0", + "futures-lite", "pin-project", "thiserror", ] [[package]] name = "atomic-waker" -version = "1.0.0" +version = "1.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "065374052e7df7ee4047b1160cca5e1467a12351a40b3da123c870ba0b8eda2a" - -[[package]] -name = "atty" -version = "0.2.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" -dependencies = [ - "hermit-abi 0.1.19", - "libc", - "winapi", -] +checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" [[package]] name = "autocfg" @@ -383,12 +291,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" @@ -401,12 +303,27 @@ version = "0.21.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a4a4ddaa51a5bc52a6948f74c06d20aaaddb71924eab79b8c97a8c556e942d6a" +[[package]] +name = "base64" +version = "0.22.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" + [[package]] name = "base64ct" version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8a32fd6af2b5827bce66c29053ba0e7c42b9dcab01835835058558c10851a46b" +[[package]] +name = "bincode" +version = "1.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1f45e9417d87227c7a56d22e471c6206462cba514c7590c09aff4cf6d1ddcad" +dependencies = [ + "serde", +] + [[package]] name = "bindgen" version = "0.62.0" @@ -438,7 +355,7 @@ dependencies = [ "bitflags 2.5.0", "cexpr", "clang-sys", - "itertools 0.11.0", + "itertools 0.12.1", "lazy_static", "lazycell", "log", @@ -448,21 +365,36 @@ dependencies = [ "regex", "rustc-hash", "shlex", - "syn 2.0.59", + "syn 2.0.79", "which", ] [[package]] name = "binstall-tar" -version = "0.4.39" +version = "0.4.42" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "01db907e07c37309ea816c183ffe548daaa66ef640a291408f232d6ca4089dbb" +checksum = "e3620d72763b5d8df3384f3b2ec47dc5885441c2abbd94dd32197167d08b014a" dependencies = [ "filetime", "libc", "xattr", ] +[[package]] +name = "bit-set" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0700ddab506f33b20a03b13996eccd309a48e5ff77d0d95926aa0210fb4e95f1" +dependencies = [ + "bit-vec", +] + +[[package]] +name = "bit-vec" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "349f9b6a179ed607305526ca489b34ad0a41aed5f7980fa90eb03160b69598fb" + [[package]] name = "bitflags" version = "1.3.2" @@ -475,15 +407,6 @@ version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cf4b9d6a944f767f8e5e0db018570623c85f3d925ac718db4e06d0187adb21c1" -[[package]] -name = "block-buffer" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4152116fd6e9dadb291ae18fc1ec3575ed6d84c29642d97890f4b4a3417297e4" -dependencies = [ - "generic-array", -] - [[package]] name = "block-buffer" version = "0.10.3" @@ -495,16 +418,15 @@ dependencies = [ [[package]] name = "blocking" -version = "1.2.0" +version = "1.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c6ccb65d468978a086b69884437ded69a90faab3bbe6e67f242173ea728acccc" +checksum = "703f41c54fc768e63e091340b424302bb1c29ef4aa0c7f10fe849dfb114d29ea" dependencies = [ "async-channel", "async-task", - "atomic-waker", - "fastrand 1.8.0", - "futures-lite 1.12.0", - "once_cell", + "futures-io", + "futures-lite", + "piper", ] [[package]] @@ -533,15 +455,9 @@ checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610" [[package]] name = "bytes" -version = "0.5.6" +version = "1.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0e4cec68f03f32e44924783795810fa50a7035d8c8ebe78580ad7e6c703fba38" - -[[package]] -name = "bytes" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c4872d67bab6358e59559027aa3b9157c53d9358c51423c17554809a8858e0f8" +checksum = "428d9aa8fbc0670b7b8d6030a7fadd0f86151cae55e4dbbece15f3780a3dfaf3" [[package]] name = "bzip2" @@ -565,34 +481,64 @@ dependencies = [ ] [[package]] -name = "cache-padded" -version = "1.2.0" +name = "camino" +version = "1.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c1db59621ec70f09c5e9b597b220c7a2b43611f4710dc03ceb8748637775692c" +checksum = "8b96ec4966b5813e2c0507c1f86115c8c5abaadc3980879c3424042a02fd1ad3" +dependencies = [ + "serde", +] [[package]] -name = "camino" -version = "1.1.6" +name = "cargo-platform" +version = "0.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c59e92b5a388f549b863a7bea62612c09f24c8393560709a54558a9abdfb3b9c" +checksum = "24b1f0365a6c6bb4020cd05806fd0d33c44d38046b8bd7f0e40814b9763cabfc" +dependencies = [ + "serde", +] + +[[package]] +name = "cargo_metadata" +version = "0.18.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2d886547e41f740c616ae73108f6eb70afe6d940c7bc697cb30f13daec073037" +dependencies = [ + "camino", + "cargo-platform", + "semver", + "serde", + "serde_json", + "thiserror", +] + +[[package]] +name = "caseless" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "808dab3318747be122cb31d36de18d4d1c81277a76f8332a02b81a3d73463d7f" +dependencies = [ + "regex", + "unicode-normalization", +] [[package]] name = "cbindgen" -version = "0.24.3" +version = "0.27.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a6358dedf60f4d9b8db43ad187391afe959746101346fe51bb978126bec61dfb" +checksum = "3fce8dd7fcfcbf3a0a87d8f515194b49d6135acab73e18bd380d1d93bb1a15eb" dependencies = [ - "clap 3.2.22", - "heck", - "indexmap 1.9.1", + "clap", + "heck 0.4.1", + "indexmap 2.6.0", "log", "proc-macro2", "quote", "serde", "serde_json", - "syn 1.0.102", + "syn 2.0.79", "tempfile", - "toml 0.5.9", + "toml 0.8.19", ] [[package]] @@ -628,30 +574,15 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] name = "chrono" -version = "0.4.26" +version = "0.4.38" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec837a71355b28f6556dbd569b37b3f363091c0bd4b2e735674521b4c5fd9bc5" +checksum = "a21f936df1771bf62b77f047b726c4625ff2e8aa607c01ec06e5a05bd8463401" dependencies = [ "android-tzdata", "iana-time-zone", "num-traits", "serde", - "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", + "windows-targets 0.52.6", ] [[package]] @@ -671,69 +602,49 @@ checksum = "77ed9a53e5d4d9c573ae844bfac6872b159cb1d1585a83b29e7a64b7eef7332a" dependencies = [ "glob", "libc", - "libloading", + "libloading 0.7.3", ] [[package]] name = "clap" -version = "3.2.22" +version = "4.5.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "86447ad904c7fb335a790c9d7fe3d0d971dc523b8ccd1561a520de9a85302750" +checksum = "7be5744db7978a28d9df86a214130d106a89ce49644cbc4e3f0c22c3fba30615" dependencies = [ - "atty", - "bitflags 1.3.2", - "clap_lex 0.2.4", - "indexmap 1.9.1", - "strsim", - "termcolor", - "textwrap", + "clap_builder", + "clap_derive", ] [[package]] -name = "clap" -version = "4.0.10" +name = "clap_builder" +version = "4.5.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b1a0a4208c6c483b952ad35c6eed505fc13b46f08f631b81e828084a9318d74" +checksum = "a5fbc17d3ef8278f55b282b2a2e75ae6f6c7d4bb70ed3d0382375104bfafdb4b" dependencies = [ - "atty", - "bitflags 1.3.2", - "clap_derive", - "clap_lex 0.3.0", - "once_cell", + "anstream 0.6.15", + "anstyle", + "clap_lex", "strsim", - "termcolor", + "terminal_size", ] [[package]] name = "clap_derive" -version = "4.0.10" +version = "4.5.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "db342ce9fda24fb191e2ed4e102055a4d381c1086a06630174cd8da8d5d917ce" +checksum = "4ac6a0c7b1a9e9a5186361f67dfa1b88213572f427fb9ab038efb2bd8c582dab" dependencies = [ - "heck", - "proc-macro-error", + "heck 0.5.0", "proc-macro2", "quote", - "syn 1.0.102", + "syn 2.0.79", ] [[package]] name = "clap_lex" -version = "0.2.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2850f2f5a82cbf437dd5af4d49848fbdfc27c157c3d010345776f952765261c5" -dependencies = [ - "os_str_bytes", -] - -[[package]] -name = "clap_lex" -version = "0.3.0" +version = "0.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0d4198f73e42b4936b35b5bb248d81d2b595ecb170da0bac7655c54eedfa8da8" -dependencies = [ - "os_str_bytes", -] +checksum = "1462739cb27611015575c0c11df5df7601141071f07518d56fcc1be504cbec97" [[package]] name = "cmake" @@ -756,9 +667,9 @@ dependencies = [ [[package]] name = "color-eyre" -version = "0.6.2" +version = "0.6.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a667583cca8c4f8436db8de46ea8233c42a7d9ae424a82d338f2e4675229204" +checksum = "55146f5e46f237f7423d74111267d4597b59b0dad0ffaf7303bce9945d843ad5" dependencies = [ "backtrace", "color-spantrace", @@ -783,9 +694,9 @@ dependencies = [ [[package]] name = "colorchoice" -version = "1.0.0" +version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7" +checksum = "d3fd119d74b830634cea2a0f58bbd0d54540518a14397557951e79340abc28c0" [[package]] name = "combine" @@ -793,34 +704,38 @@ version = "4.6.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "35ed6e9d84f0b51a7f52daf1c7d71dd136fd7a3f41a8462b8cdb8c78d920fad4" dependencies = [ - "bytes 1.1.0", + "bytes", "memchr", ] [[package]] -name = "concolor" -version = "0.0.11" +name = "comrak" +version = "0.26.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "318d6c16e73b3a900eb212ad6a82fc7d298c5ab8184c7a9998646455bc474a16" +checksum = "395ab67843c57df5a4ee29d610740828dbc928cc64ecf0f2a1d5cd0e98e107a9" dependencies = [ - "bitflags 1.3.2", - "concolor-query", - "is-terminal", + "caseless", + "clap", + "derive_builder", + "entities", + "memchr", + "once_cell", + "regex", + "shell-words", + "slug", + "syntect", + "typed-arena", + "unicode_categories", + "xdg", ] -[[package]] -name = "concolor-query" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "82a90734b3d5dcf656e7624cca6bce9c3a90ee11f900e80141a7427ccfb3d317" - [[package]] name = "concurrent-queue" -version = "1.2.4" +version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af4780a44ab5696ea9e28294517f1fffb421a83a25af521333c838635509db9c" +checksum = "4ca0197aee26d1ae37445ee532fefce43251d24cc7c166799f4d46817f1d3973" dependencies = [ - "cache-padded", + "crossbeam-utils", ] [[package]] @@ -837,10 +752,24 @@ dependencies = [ ] [[package]] -name = "const_fn" -version = "0.4.9" +name = "const_format" +version = "0.2.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "50c655d81ff1114fb0dcdea9225ea9f0cc712a6f8d189378e82bdf62a473a64b" +dependencies = [ + "const_format_proc_macros", +] + +[[package]] +name = "const_format_proc_macros" +version = "0.2.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fbdcdcb6d86f71c5e97409ad45898af11cbc995b4ee8112d59095a28d376c935" +checksum = "eff1a44b93f47b1bac19a27932f5c591e43d1ba357ee4f61526c8a25603f0eb1" +dependencies = [ + "proc-macro2", + "quote", + "unicode-xid", +] [[package]] name = "constant_time_eq" @@ -855,27 +784,20 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6245d59a3e82a7fc217c5828a6692dbc6dfb63a0c8c90495621f7b9d79704a0e" [[package]] -name = "cookie" -version = "0.14.4" +name = "core-foundation" +version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "03a5d7b21829bc7b4bf4754a978a241ae54ea55a40f92bb20216e54096f4b951" +checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f" 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", + "core-foundation-sys", + "libc", ] [[package]] name = "core-foundation-sys" -version = "0.8.3" +version = "0.8.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5827cebf4670468b8772dd191856768aedcb1b0278a04f989f7766351917b9dc" +checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" [[package]] name = "cpufeatures" @@ -886,12 +808,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" @@ -901,16 +817,6 @@ dependencies = [ "cfg-if", ] -[[package]] -name = "crossbeam-channel" -version = "0.5.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c2dd04ddaf88237dc3b8d8f9a3c1004b506b54b3313403944054d23c0870c521" -dependencies = [ - "cfg-if", - "crossbeam-utils", -] - [[package]] name = "crossbeam-deque" version = "0.8.2" @@ -944,6 +850,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" @@ -955,63 +867,26 @@ dependencies = [ ] [[package]] -name = "crypto-mac" -version = "0.10.1" +name = "cssparser" +version = "0.31.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bff07008ec701e8028e2ceb8f83f0e4274ee62bd2dbdc4fefff2e9a91824081a" +checksum = "5b3df4f93e5fbbe73ec01ec8d3f68bba73107993a5b1e7519273c32db9b0d5be" dependencies = [ - "generic-array", - "subtle", + "cssparser-macros", + "dtoa-short", + "itoa", + "phf 0.11.2", + "smallvec", ] [[package]] -name = "ctor" -version = "0.1.23" +name = "cssparser-macros" +version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cdffe87e1d521a10f9696f833fe502293ea446d7f256c06128293a4119bdf4cb" +checksum = "13b588ba4ac1a99f7f2964d24b3d896ddc6bf847ee3855dbd4366f058cfcd331" dependencies = [ "quote", - "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", + "syn 2.0.79", ] [[package]] @@ -1060,9 +935,9 @@ dependencies = [ [[package]] name = "darling" -version = "0.20.3" +version = "0.20.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0209d94da627ab5605dcccf08bb18afa5009cfbef48d8a8b7d7bdbc79be25c5e" +checksum = "6f63b86c8a8826a49b8c21f08a2d07338eec8d900540f8630dc76284be802989" dependencies = [ "darling_core", "darling_macro", @@ -1070,27 +945,37 @@ dependencies = [ [[package]] name = "darling_core" -version = "0.20.3" +version = "0.20.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "177e3443818124b357d8e76f53be906d60937f0d3a90773a664fa63fa253e621" +checksum = "95133861a8032aaea082871032f5815eb9e98cef03fa916ab4500513994df9e5" dependencies = [ "fnv", "ident_case", "proc-macro2", "quote", "strsim", - "syn 2.0.59", + "syn 2.0.79", ] [[package]] name = "darling_macro" -version = "0.20.3" +version = "0.20.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "836a9bbc7ad63342d6d6e7b815ccab164bc77a2d95d84bc3117a8c0d5c98e2d5" +checksum = "d336a2a514f6ccccaa3e09b02d41d35330c07ddf03a62165fcec10bb561c7806" dependencies = [ "darling_core", "quote", - "syn 2.0.59", + "syn 2.0.79", +] + +[[package]] +name = "deranged" +version = "0.3.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b42b6fa04a440b495c8b04d0e71b707c585f83cb9cb28cf8cd0d976c315e31b4" +dependencies = [ + "powerfmt", + "serde", ] [[package]] @@ -1115,6 +1000,48 @@ dependencies = [ "syn 1.0.102", ] +[[package]] +name = "derive-syn-parse" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d65d7ce8132b7c0e54497a4d9a55a1c2a0912a0d786cf894472ba818fba45762" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.79", +] + +[[package]] +name = "derive_builder" +version = "0.20.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0350b5cb0331628a5916d6c5c0b72e97393b8b6b03b47a9284f4e7f5a405ffd7" +dependencies = [ + "derive_builder_macro", +] + +[[package]] +name = "derive_builder_core" +version = "0.20.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d48cda787f839151732d396ac69e3473923d54312c070ee21e9effcaa8ca0b1d" +dependencies = [ + "darling", + "proc-macro2", + "quote", + "syn 2.0.79", +] + +[[package]] +name = "derive_builder_macro" +version = "0.20.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "206868b8242f27cecce124c19fd88157fbd0dd334df2587f36417bafbc85097b" +dependencies = [ + "derive_builder_core", + "syn 2.0.79", +] + [[package]] name = "derive_more" version = "0.99.17" @@ -1124,30 +1051,27 @@ dependencies = [ "convert_case", "proc-macro2", "quote", - "rustc_version 0.4.0", + "rustc_version", "syn 1.0.102", ] [[package]] -name = "diff" -version = "0.1.13" +name = "deunicode" +version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "56254986775e3233ffa9c4d7d3faaf6d36a2c09d30b20687e9f88bc8bafc16c8" +checksum = "339544cc9e2c4dc3fc7149fd630c5f22263a4fdf18a98afd0075784968b5cf00" [[package]] -name = "difflib" -version = "0.4.0" +name = "diff" +version = "0.1.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6184e33543162437515c2e2b48714794e37845ec9851711914eec9d308f6ebe8" +checksum = "56254986775e3233ffa9c4d7d3faaf6d36a2c09d30b20687e9f88bc8bafc16c8" [[package]] -name = "digest" -version = "0.9.0" +name = "difflib" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d3dd60d1080a57a05ab032377049e0591415d2b31afd7028356dbf3cc6dcb066" -dependencies = [ - "generic-array", -] +checksum = "6184e33543162437515c2e2b48714794e37845ec9851711914eec9d308f6ebe8" [[package]] name = "digest" @@ -1155,17 +1079,11 @@ 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" @@ -1178,19 +1096,21 @@ version = "0.0.0" dependencies = [ "anyhow", "binstall-tar", - "bytes 1.1.0", - "clap 4.0.10", + "bytes", + "clap", + "comrak", "flate2", "fs-err", "futures-core", "futures-util", "indicatif", + "itertools 0.10.5", "octocrab", - "once_cell", "parse-display", "rayon", "reqwest", "rstest", + "scraper", "strum", "tokio", "tracing", @@ -1199,11 +1119,26 @@ dependencies = [ "zip", ] +[[package]] +name = "dtoa" +version = "1.0.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dcbb2bf8e87535c23f7a8a321e364ce21462d0ff10cb6407820e8e96dfff6653" + +[[package]] +name = "dtoa-short" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd1511a7b6a56299bd043a9c167a6d2bfb37bf84a6dfceaba651168adfb43c87" +dependencies = [ + "dtoa", +] + [[package]] name = "duct" -version = "0.13.6" +version = "0.13.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "37ae3fc31835f74c2a7ceda3aeede378b0ae2e74c8f1c36559fcc9ae2a4e7d3e" +checksum = "e4ab5718d1224b63252cd0c6f74f6480f9ffeb117438a2e0f5cf6d9a4798929c" dependencies = [ "libc", "once_cell", @@ -1213,19 +1148,20 @@ dependencies = [ [[package]] name = "duplicate" -version = "1.0.0" +version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "de78e66ac9061e030587b2a2e75cc88f22304913c907b11307bca737141230cb" +checksum = "97af9b5f014e228b33e77d75ee0e6e87960124f0f4b16337b586a6bec91867b1" dependencies = [ - "heck", - "proc-macro-error", + "heck 0.5.0", + "proc-macro2", + "proc-macro2-diagnostics", ] [[package]] name = "easy-ext" -version = "1.0.1" +version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49457524c7e65648794c98283282a0b7c73b10018e7091f1cdcfff314fd7ae59" +checksum = "cc5d6d6a8504f8caedd7de14576464383900cd3840b7033a7a3dce5ac00121ca" [[package]] name = "educe" @@ -1239,6 +1175,12 @@ dependencies = [ "syn 1.0.102", ] +[[package]] +name = "ego-tree" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3a68a4904193147e0a8dec3314640e6db742afd5f6e634f428a6af230d9b3591" + [[package]] name = "either" version = "1.8.0" @@ -1260,24 +1202,30 @@ dependencies = [ "cfg-if", ] +[[package]] +name = "entities" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5320ae4c3782150d900b79807611a59a99fc9a1d61d686faafc24b93fc8d7ca" + [[package]] name = "enum-map" -version = "3.0.0-beta.1" +version = "3.0.0-beta.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4e698c4fb1d30d2aeaf3b169ca72fbc019a049d7c85acc7f91d5f58a22e3ee13" +checksum = "fb2a23ad36148a32085addb3ef1aa39805d044d4532ff258360d523a4eff38e5" dependencies = [ "enum-map-derive", ] [[package]] name = "enum-map-derive" -version = "1.0.0-0.gat.0" +version = "1.0.0-beta.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "48c69b3965971f5d0ea6a6dd26b55cdd517ae0e1425dc8d94e482a5915bd7ddf" +checksum = "44600091ce205df4f8b661e98617d49c37b2dd609e449ec82b0fb5d7b33e2eeb" dependencies = [ "proc-macro2", "quote", - "syn 2.0.59", + "syn 2.0.79", ] [[package]] @@ -1290,7 +1238,7 @@ dependencies = [ "num-traits", "proc-macro2", "quote", - "syn 2.0.59", + "syn 2.0.79", ] [[package]] @@ -1311,75 +1259,70 @@ checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" [[package]] name = "erased-serde" -version = "0.3.25" +version = "0.4.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4f2b0c2380453a92ea8b6c8e5f64ecaafccddde8ceab55ff7a8ac1029f894569" +checksum = "24e2389d65ab4fab27dc2a5de7b191e1f6617d1f1c8855c0dc569c94a4cbb18d" dependencies = [ "serde", + "typeid", ] [[package]] name = "errno" -version = "0.2.8" +version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f639046355ee4f37944e44f60642c6f3a7efa3cf6b78c78a0d989a8ce6c396a1" +checksum = "534c5cf6194dfab3db3242765c03bbe257cf92f22b38f6bc0c58d59108a820ba" dependencies = [ - "errno-dragonfly", "libc", - "winapi", + "windows-sys 0.52.0", ] [[package]] -name = "errno" -version = "0.3.1" +name = "event-listener" +version = "5.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4bcfec3a70f97c962c307b2d2c56e358cf1d00b558d74262b5f929ee8cc7e73a" +checksum = "6032be9bd27023a771701cc49f9f053c751055f71efb2e0ae5c15809093675ba" dependencies = [ - "errno-dragonfly", - "libc", - "windows-sys 0.48.0", + "concurrent-queue", + "parking", + "pin-project-lite", ] [[package]] -name = "errno-dragonfly" -version = "0.1.2" +name = "event-listener-strategy" +version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aa68f1b12764fab894d2755d2518754e71b4fd80ecfb822714a1206c2aab39bf" +checksum = "0f214dc438f977e6d4e3500aaa277f5ad94ca83fbbd9b1a15713ce2344ccc5a1" dependencies = [ - "cc", - "libc", + "event-listener", + "pin-project-lite", ] -[[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" +version = "0.6.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c2b6b5a29c02cdc822728b7d7b8ae1bab3e3b05d44522770ddd49722eeac7eb" +checksum = "7cd915d99f24784cdc19fd37ef22b97e3ff0ae756c7e492e9fbfe897d61e2aec" dependencies = [ "indenter", "once_cell", ] [[package]] -name = "fastrand" -version = "1.8.0" +name = "fancy-regex" +version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a7a407cfaa3385c4ae6b23e84623d48c2798d06e3e6a1878f7f59f17b3f86499" +checksum = "b95f7c0680e4142284cf8b22c14a476e87d61b004a3a0861872b32ef7ead40a2" dependencies = [ - "instant", + "bit-set", + "regex", ] [[package]] name = "fastrand" -version = "2.0.1" +version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "25cbce373ec4653f1a01a31e8a5e5ec0c622dc27ff9c4e6606eefef5cbbed4a5" +checksum = "e8c02a5121d4ea3eb16a80748c74f5549a5665e4c21333c6098f283870fbdea6" [[package]] name = "filetime" @@ -1389,29 +1332,27 @@ checksum = "e94a7bbaa59354bc20dd75b67f23e2797b4490e9d6928203fb105c79e448c86c" dependencies = [ "cfg-if", "libc", - "redox_syscall 0.2.16", + "redox_syscall", "windows-sys 0.36.1", ] [[package]] name = "flate2" -version = "1.0.25" +version = "1.0.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a8a2db397cb1c8772f31494cb8917e48cd1e64f0fa7efac59fbd741a0a8ce841" +checksum = "a1b589b4dc103969ad3cf85c950899926ec64300a1a46d76c03a6072957036f0" dependencies = [ "crc32fast", - "miniz_oxide 0.6.2", + "miniz_oxide 0.8.0", ] [[package]] -name = "flume" -version = "0.9.2" +name = "float-cmp" +version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1bebadab126f8120d410b677ed95eee4ba6eb7c6dd8e34a5ec88a08050e26132" +checksum = "98de4bbd547a563b716d8dfa9aad1cb19bfab00f4fa09a6a4ed21dbcf44ce9c4" dependencies = [ - "futures-core", - "futures-sink", - "spinning_top", + "num-traits", ] [[package]] @@ -1422,23 +1363,33 @@ checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" [[package]] name = "form_urlencoded" -version = "1.0.1" +version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5fc25a87fa4fd2094bffb06925852034d90a17f0d1e05197d4956d3555752191" +checksum = "e13624c2627564efccf4934284bdd98cbaa14e79b0b5a141218e507b3a823456" dependencies = [ - "matches", "percent-encoding", ] [[package]] name = "fs-err" -version = "2.9.0" +version = "2.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0845fa252299212f0389d64ba26f34fa32cfe41588355f21ed507c59a0f64541" +checksum = "88a41f105fe1d5b6b34b2055e3dc59bb79b46b48b2040b9e6c7b4b5de097aa41" dependencies = [ + "autocfg", "tokio", ] +[[package]] +name = "futf" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df420e2e84819663797d1ec6544b13c5be84629e7bb00dc960d6917db2987843" +dependencies = [ + "mac", + "new_debug_unreachable", +] + [[package]] name = "futures" version = "0.3.28" @@ -1456,9 +1407,9 @@ dependencies = [ [[package]] name = "futures-channel" -version = "0.3.28" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "955518d47e09b25bbebc7a18df10b81f0c766eaf4c4f1cccef2fca5f2a4fb5f2" +checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10" dependencies = [ "futures-core", "futures-sink", @@ -1466,9 +1417,9 @@ dependencies = [ [[package]] name = "futures-core" -version = "0.3.28" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4bca583b7e26f571124fe5b7561d49cb2868d79116cfa0eefce955557c6fee8c" +checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" [[package]] name = "futures-executor" @@ -1483,32 +1434,17 @@ dependencies = [ [[package]] name = "futures-io" -version = "0.3.28" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4fff74096e71ed47f8e023204cfd0aa1289cd54ae5430a9523be060cdb849964" - -[[package]] -name = "futures-lite" -version = "1.12.0" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7694489acd39452c77daa48516b894c153f192c3578d5a839b62c58099fcbf48" -dependencies = [ - "fastrand 1.8.0", - "futures-core", - "futures-io", - "memchr", - "parking", - "pin-project-lite", - "waker-fn", -] +checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6" [[package]] name = "futures-lite" -version = "2.2.0" +version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "445ba825b27408685aaecefd65178908c36c6e96aaf6d8599419d46e624192ba" +checksum = "52527eb5074e35e9339c6b4e8d12600c7128b68fb25dcb9fa9dec18f7c25f3a5" dependencies = [ - "fastrand 2.0.1", + "fastrand", "futures-core", "futures-io", "parking", @@ -1517,26 +1453,26 @@ dependencies = [ [[package]] name = "futures-macro" -version = "0.3.28" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "89ca545a94061b6365f2c7355b4b32bd20df3ff95f02da9329b34ccc3bd6ee72" +checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" dependencies = [ "proc-macro2", "quote", - "syn 2.0.59", + "syn 2.0.79", ] [[package]] name = "futures-sink" -version = "0.3.28" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f43be4fe21a13b9781a69afa4985b0f6ee0e1afab2c6f454a8cf30e2b2237b6e" +checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7" [[package]] name = "futures-task" -version = "0.3.28" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "76d3d132be6c0e6aa1534069c705a74a5997a356c0dc2f86a47765e5617c5b65" +checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988" [[package]] name = "futures-timer" @@ -1546,9 +1482,9 @@ checksum = "e64b03909df88034c26dc1547e8970b91f98bdb65165d6a4e9110d94263dbb2c" [[package]] name = "futures-util" -version = "0.3.28" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26b01e40b772d54cf6c6d721c1d1abd0647a0106a12ecaa1c186273392a69533" +checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81" dependencies = [ "futures-channel", "futures-core", @@ -1562,6 +1498,15 @@ dependencies = [ "slab", ] +[[package]] +name = "fxhash" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c31b6d751ae2c7f11320402d34e41349dd1016f8d5d45e48c4312bc8625af50c" +dependencies = [ + "byteorder", +] + [[package]] name = "generic-array" version = "0.14.6" @@ -1573,46 +1518,23 @@ dependencies = [ ] [[package]] -name = "getrandom" -version = "0.1.16" +name = "getopts" +version = "0.2.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8fc3cb4d91f53b50155bdcfd23f6a4c39ae1969c2ae85982b135750cccaf5fce" +checksum = "14dbbfd5c71d70241ecf9e6f13737f7b5ce823821063188d7e46c41d371eebd5" dependencies = [ - "cfg-if", - "libc", - "wasi 0.9.0+wasi-snapshot-preview1", + "unicode-width", ] [[package]] name = "getrandom" -version = "0.2.7" +version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4eb1a864a501629691edf6c15a593b7a51eebaa1e8468e9ddc623de7c9b58ec6" +checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7" 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", -] - -[[package]] -name = "ghost" -version = "0.1.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41973d4c45f7a35af8753ba3457cc99d406d863941fd7f52663cff54a5ab99b3" -dependencies = [ - "proc-macro2", - "quote", - "syn 1.0.102", + "wasi", ] [[package]] @@ -1627,37 +1549,35 @@ 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" +version = "0.3.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f9f29bc9dda355256b2916cf526ab02ce0aeaaaf2bad60d65ef3f12f11dd0f4" +checksum = "81fe527a889e1532da5c525686d96d4c2e74cdd345badf8dfef9f6b39dd5f5e8" dependencies = [ - "bytes 1.1.0", + "bytes", "fnv", "futures-core", "futures-sink", "futures-util", "http", - "indexmap 1.9.1", + "indexmap 2.6.0", "slab", "tokio", "tokio-util", "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" @@ -1666,9 +1586,9 @@ checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" [[package]] name = "hashbrown" -version = "0.14.0" +version = "0.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2c6201b9ff9fd90a5a3bac2e56a830d0caa509576f0e503818ee82c181b3437a" +checksum = "1e087f84d4f86bf4b218b927129862374b72199ae7d8657835f1e89000eea4fb" [[package]] name = "heck" @@ -1677,13 +1597,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" [[package]] -name = "hermit-abi" -version = "0.1.19" +name = "heck" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33" -dependencies = [ - "libc", -] +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" [[package]] name = "hermit-abi" @@ -1696,9 +1613,9 @@ dependencies = [ [[package]] name = "hermit-abi" -version = "0.3.2" +version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "443144c8cdadd93ebf52ddb4056d257f5b52c04d3c804e657d19eb73fc33668b" +checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024" [[package]] name = "hex" @@ -1706,33 +1623,27 @@ 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" +version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c1441c6b1e930e2817404b5046f1f989899143a12bf92de603b69f4e0aee1e15" +checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" dependencies = [ - "crypto-mac", - "digest 0.9.0", + "digest", ] [[package]] -name = "hmac" -version = "0.12.1" +name = "html5ever" +version = "0.27.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" +checksum = "c13771afe0e6e846f1e67d038d4cb29998a6779f93c809212e4e9c32efd244d4" dependencies = [ - "digest 0.10.5", + "log", + "mac", + "markup5ever", + "proc-macro2", + "quote", + "syn 2.0.79", ] [[package]] @@ -1741,7 +1652,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", ] @@ -1752,47 +1663,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 1.12.0", - "infer", - "pin-project-lite", - "rand 0.7.3", - "serde", - "serde_json", - "serde_qs", - "serde_urlencoded", - "url", -] - [[package]] name = "httparse" version = "1.8.0" @@ -1807,9 +1682,9 @@ checksum = "c4a1e36c821dbe04574f602848a19f742f4fb3c98d40449f11bcad18d6b17421" [[package]] name = "humansize" -version = "2.1.2" +version = "2.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4e682e2bd70ecbcce5209f11a992a4ba001fea8e60acf7860ce007629e6d2756" +checksum = "6cb51c9a029ddc91b07a787f1d86b53ccfa49b0e86688c946ebe8d3555685dd7" dependencies = [ "libm", ] @@ -1820,7 +1695,7 @@ 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", @@ -1831,7 +1706,7 @@ dependencies = [ "httpdate", "itoa", "pin-project-lite", - "socket2", + "socket2 0.4.9", "tokio", "tower-service", "tracing", @@ -1840,10 +1715,11 @@ dependencies = [ [[package]] name = "hyper-rustls" -version = "0.23.2" +version = "0.24.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1788965e61b367cd03a62950836d5cd41560c3577d90e40e0819373194d1661c" +checksum = "ec3efd23720e2049821a693cbc7e65ea87c72f1c58ff2f9522ff332b1491e590" dependencies = [ + "futures-util", "http", "hyper", "rustls", @@ -1883,11 +1759,10 @@ checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" [[package]] name = "idna" -version = "0.2.3" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "418a0a6fab821475f634efe3ccc45c013f742efe03d853e8d3355d5cb850ecf8" +checksum = "634d9b1461af396cad843f47fdba5597a4f9e6ddd4bfb6ff5d85028c25cb12f6" dependencies = [ - "matches", "unicode-bidi", "unicode-normalization", ] @@ -1911,38 +1786,33 @@ dependencies = [ [[package]] name = "indexmap" -version = "2.0.0" +version = "2.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d5477fe2230a79769d8dc68e0eabf5437907c0457a5614a9e8dddb67f65eb65d" +checksum = "707907fe3c25f5424cce2cb7e1cbcafee6bdbe735ca90ef77c29e84591e5b9da" dependencies = [ "equivalent", - "hashbrown 0.14.0", + "hashbrown 0.15.0", "serde", ] [[package]] name = "indicatif" -version = "0.17.3" +version = "0.17.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cef509aa9bc73864d6756f0d34d35504af3cf0844373afe9b8669a5b8005a729" +checksum = "763a5a8f45087d6bcea4222e7b72c291a054edf80e4ef6efd2a4979878c7bea3" dependencies = [ "console", + "instant", "number_prefix", - "portable-atomic 0.3.19", + "portable-atomic", "unicode-width", ] [[package]] name = "indoc" -version = "2.0.4" +version = "2.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e186cfbae8084e513daff4240b4797e342f988cecda4fb6c939150f96315fd8" - -[[package]] -name = "infer" -version = "0.2.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "64e9829a50b42bb782c1df523f78d332fe371b10c661e78b7a3c34b0198e9fac" +checksum = "b248f5224d1d606005e02c97f5aa4e88eeb230488bcc03bc9ca4d7991399f2b5" [[package]] name = "instant" @@ -1955,24 +1825,9 @@ dependencies = [ [[package]] name = "inventory" -version = "0.3.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "498ae1c9c329c7972b917506239b557a60386839192f1cf0ca034f345b65db99" -dependencies = [ - "ctor", - "ghost", -] - -[[package]] -name = "io-lifetimes" -version = "1.0.11" +version = "0.3.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eae7b9aee968036d54dce06cebaefd919e4472e753296daccd6d344e3e2df0c2" -dependencies = [ - "hermit-abi 0.3.2", - "libc", - "windows-sys 0.48.0", -] +checksum = "f958d3d68f4167080a18141e10381e7634563984a537f2a49a30fd8e53ac5767" [[package]] name = "ipnet" @@ -1981,39 +1836,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "30e22bd8629359895450b59ea7a776c850561b96a3b1d31321c1949d9e6c9146" [[package]] -name = "is-terminal" -version = "0.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "28dfb6c8100ccc63462345b67d1bbc3679177c75ee4bf59bf29c8b1d110b8189" -dependencies = [ - "hermit-abi 0.2.6", - "io-lifetimes", - "rustix 0.36.7", - "windows-sys 0.42.0", -] - -[[package]] -name = "isahc" -version = "0.9.14" +name = "is_terminal_polyfill" +version = "1.70.1" 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 1.12.0", - "http", - "log", - "once_cell", - "slab", - "sluice", - "tracing", - "tracing-futures", - "url", - "waker-fn", -] +checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf" [[package]] name = "itertools" @@ -2026,9 +1852,9 @@ dependencies = [ [[package]] name = "itertools" -version = "0.11.0" +version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1c173a5686ce8bfa551b3563d0c2170bf24ca44da99c7ca4bfdab5418c3fe57" +checksum = "ba291022dbbd398a455acf126c1e341954079855bc60dfdda641363bd6922569" dependencies = [ "either", ] @@ -2041,9 +1867,9 @@ checksum = "4217ad341ebadf8d8e724e264f13e593e0648f5b3e94b3896a5df283be015ecc" [[package]] name = "jlabel" -version = "0.1.2" +version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1f040b22c55628977296069dbf8635be49cc510999c048a1f1bdb56d00983148" +checksum = "145ee6f495871a0cde6d49ddfa0d103d07430c449d95b6d92fbfb032d622f0b7" dependencies = [ "thiserror", ] @@ -2081,9 +1907,9 @@ dependencies = [ [[package]] name = "js-sys" -version = "0.3.60" +version = "0.3.72" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49409df3e3bf0856b916e2ceaca09ee28e6871cf7d9ce97a692cacfdb2a25a47" +checksum = "6a88f1bda2bd75b0452a14784937d796722fdebfe50df998aeb3f0b7603019a9" dependencies = [ "wasm-bindgen", ] @@ -2096,21 +1922,12 @@ checksum = "09f4f04699947111ec1733e71778d763555737579e44b85844cae8e1940a1828" dependencies = [ "base64 0.13.0", "pem", - "ring", + "ring 0.16.20", "serde", "serde_json", "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" @@ -2125,9 +1942,9 @@ checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55" [[package]] name = "libc" -version = "0.2.142" +version = "0.2.159" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a987beff54b60ffa6d51982e1aa1146bc42f19bd26be28b0586f252fccf5317" +checksum = "561d97a539a36e26a9a5fad1ea11a3039a67714694aaa379433e580854bc3dc5" [[package]] name = "libloading" @@ -2140,20 +1957,20 @@ dependencies = [ ] [[package]] -name = "libm" -version = "0.2.6" +name = "libloading" +version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "348108ab3fba42ec82ff6e9564fc4ca0247bdccdc68dd8af9764bbc79c3c8ffb" +checksum = "0c2a198fb6b0eada2a8df47933734e6d35d350665a33a3593d7164fa52c75c19" +dependencies = [ + "cfg-if", + "windows-targets 0.48.5", +] [[package]] -name = "libnghttp2-sys" -version = "0.1.7+1.45.0" +name = "libm" +version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "57ed28aba195b38d5ff02b9170cbff627e336a20925e43b4945390401c5dc93f" -dependencies = [ - "cc", - "libc", -] +checksum = "348108ab3fba42ec82ff6e9564fc4ca0247bdccdc68dd8af9764bbc79c3c8ffb" [[package]] name = "libtest-mimic" @@ -2161,21 +1978,18 @@ version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d7b603516767d1ab23d0de09d023e62966c3322f7148297c35cf3d97aa8b37fa" dependencies = [ - "clap 4.0.10", + "clap", "termcolor", "threadpool", ] [[package]] -name = "libz-sys" -version = "1.1.8" +name = "line-wrap" +version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9702761c3935f8cc2f101793272e202c72b99da8f4224a19ddcf1279a6450bbf" -dependencies = [ - "cc", - "libc", - "pkg-config", - "vcpkg", +checksum = "f30344350a2a51da54c1d53be93fade8a237e545dbcc4bdbe635413f2117cab9" +dependencies = [ + "safemem", ] [[package]] @@ -2188,16 +2002,16 @@ dependencies = [ ] [[package]] -name = "linux-raw-sys" -version = "0.1.4" +name = "linked-hash-map" +version = "0.5.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f051f77a7c8e6957c0696eac88f26b0117e54f52d3fc682ab19397a8812846a4" +checksum = "0717cef1bc8b636c6e1c1bbdefc09e6322da8a9321966e8928ef80d20f7f770f" [[package]] name = "linux-raw-sys" -version = "0.3.8" +version = "0.4.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ef53942eb7bf7ff43a617b3e2c1c4a5ecf5944a7c1bc12d7ee39bbb15e5c1519" +checksum = "78b3ae25bc7c8c38cec158d1f2757ee79e9b3740fbc7ccf0e59e4b08d793fa89" [[package]] name = "lock_api" @@ -2211,12 +2025,28 @@ dependencies = [ [[package]] name = "log" -version = "0.4.17" +version = "0.4.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24" + +[[package]] +name = "mac" +version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e" +checksum = "c41e0c4fef86961ac6d6f8a82609f55f31b05e4fce149ac5710e439df7619ba4" + +[[package]] +name = "markup5ever" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "16ce3abbeba692c8b8441d036ef91aea6df8da2c6b6e21c7e14d3c18e526be45" dependencies = [ - "cfg-if", - "value-bag", + "log", + "phf 0.11.2", + "phf_codegen 0.11.2", + "string_cache", + "string_cache_codegen", + "tendril", ] [[package]] @@ -2228,12 +2058,6 @@ dependencies = [ "regex-automata 0.1.10", ] -[[package]] -name = "matches" -version = "0.1.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a3e378b66a060d48947b590737b30a1be76706c8dd7b8ba0f2fe3989c68a853f" - [[package]] name = "matrixmultiply" version = "0.3.2" @@ -2273,16 +2097,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" @@ -2300,32 +2114,23 @@ dependencies = [ [[package]] name = "miniz_oxide" -version = "0.6.2" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b275950c28b37e794e8c55d88aeb5e139d0ce23fdbbeda68f8d7174abdf9e8fa" +checksum = "e2d80299ef12ff69b16a84bb182e3b9df68b5a91574d3d4fa6e41b65deec4df1" dependencies = [ - "adler", + "adler2", ] [[package]] name = "mio" -version = "0.8.5" +version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e5d732bc30207a6423068df043e3d02e0735b155ad7ce1a6f76fe2baa5b158de" +checksum = "80e04d1dcff3aae0704555fe5fee3bcfaf3d1fdf8a7e521d5b9d2b42acb52cec" dependencies = [ + "hermit-abi 0.3.9", "libc", - "log", - "wasi 0.11.0+wasi-snapshot-preview1", - "windows-sys 0.42.0", -] - -[[package]] -name = "nanoid" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3ffa00dec017b5b1a8b7cf5e2c008bfda1aa7e0697ac1508b491fdf2622fb4d8" -dependencies = [ - "rand 0.8.5", + "wasi", + "windows-sys 0.52.0", ] [[package]] @@ -2353,9 +2158,15 @@ dependencies = [ "noisy_float", "num-integer", "num-traits", - "rand 0.8.5", + "rand", ] +[[package]] +name = "new_debug_unreachable" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "650eef8c711430f1a879fdd01d4745a7deea475becfb90269c06775983bbf086" + [[package]] name = "noisy_float" version = "0.2.0" @@ -2384,6 +2195,12 @@ dependencies = [ "memchr", ] +[[package]] +name = "normalize-line-endings" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61807f77802ff30975e01f4f071c8ba10c022052f98b3294119f3e615d13e5be" + [[package]] name = "nu-ansi-term" version = "0.46.0" @@ -2414,6 +2231,12 @@ dependencies = [ "num-traits", ] +[[package]] +name = "num-conv" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9" + [[package]] name = "num-integer" version = "0.1.45" @@ -2443,15 +2266,6 @@ dependencies = [ "libc", ] -[[package]] -name = "num_threads" -version = "0.1.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2819ce041d2ee131036f4fc9d6ae7ae125a3a40e97ba64d04fe799ad9dabbb44" -dependencies = [ - "libc", -] - [[package]] name = "number_prefix" version = "0.4.0" @@ -2476,7 +2290,7 @@ dependencies = [ "arc-swap", "async-trait", "base64 0.21.0", - "bytes 1.1.0", + "bytes", "cfg-if", "chrono", "either", @@ -2496,32 +2310,33 @@ dependencies = [ [[package]] name = "once_cell" -version = "1.18.0" +version = "1.20.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd8b5dd2ae5ed71462c540258bedcb51965123ad7e7ccf4b9a8cafaa4a63576d" +checksum = "82881c4be219ab5faaf2ad5e5e5ecdff8c66bd7402ca3160975c93b24961afd1" +dependencies = [ + "portable-atomic", +] [[package]] -name = "onnxruntime" -version = "0.1.0" -source = "git+https://github.com/VOICEVOX/onnxruntime-rs.git?rev=ebb9dcb9b26ee681889b52b6db3b4f642b04a250#ebb9dcb9b26ee681889b52b6db3b4f642b04a250" +name = "onig" +version = "6.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8c4b31c8722ad9171c6d77d3557db078cab2bd50afcc9d09c8b315c59df8ca4f" dependencies = [ - "lazy_static", - "ndarray", - "onnxruntime-sys", - "thiserror", - "tracing", + "bitflags 1.3.2", + "libc", + "once_cell", + "onig_sys", ] [[package]] -name = "onnxruntime-sys" -version = "0.0.25" -source = "git+https://github.com/VOICEVOX/onnxruntime-rs.git?rev=ebb9dcb9b26ee681889b52b6db3b4f642b04a250#ebb9dcb9b26ee681889b52b6db3b4f642b04a250" +name = "onig_sys" +version = "69.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b829e3d7e9cc74c7e315ee8edb185bf4190da5acde74afd7fc59c35b1f086e7" dependencies = [ - "flate2", - "once_cell", - "tar", - "ureq", - "zip", + "cc", + "pkg-config", ] [[package]] @@ -2550,25 +2365,6 @@ dependencies = [ "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" @@ -2579,17 +2375,11 @@ dependencies = [ "windows-sys 0.42.0", ] -[[package]] -name = "os_str_bytes" -version = "6.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ff7415e9ae3fff1225851df9e0d9e4e5479f947619774677a63572e55e80eff" - [[package]] name = "ouroboros" -version = "0.18.0" +version = "0.18.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1c86de06555b970aec45229b27291b53154f21a5743a163419f4e4c0b065dcde" +checksum = "944fa20996a25aded6b4795c6d63f10014a7a83f8be9828a11860b08c5fc4a67" dependencies = [ "aliasable", "ouroboros_macro", @@ -2598,25 +2388,16 @@ dependencies = [ [[package]] name = "ouroboros_macro" -version = "0.18.0" +version = "0.18.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8cad0c4b129e9696e37cb712b243777b90ef489a0bfaa0ac34e7d9b860e4f134" +checksum = "39b0deead1528fd0e5947a8546a9642a9777c25f6e1e26f34c97b204bbb465bd" dependencies = [ - "heck", - "itertools 0.11.0", - "proc-macro-error", + "heck 0.4.1", + "itertools 0.12.1", "proc-macro2", + "proc-macro2-diagnostics", "quote", - "syn 2.0.59", -] - -[[package]] -name = "output_vt100" -version = "0.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "628223faebab4e3e40667ee0b2336d34a5b960ff60ea743ddfdbcf7770bcfb66" -dependencies = [ - "winapi", + "syn 2.0.79", ] [[package]] @@ -2655,7 +2436,7 @@ checksum = "09a279cbf25cb0757810394fbc1e359949b59e348145c643a939a525692e6929" dependencies = [ "cfg-if", "libc", - "redox_syscall 0.2.16", + "redox_syscall", "smallvec", "windows-sys 0.36.1", ] @@ -2683,7 +2464,7 @@ dependencies = [ "regex", "regex-syntax 0.7.5", "structmeta", - "syn 2.0.59", + "syn 2.0.79", ] [[package]] @@ -2693,7 +2474,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7676374caaee8a325c9e7a2ae557f216c5563a171d6997b0ef8a65af35147700" dependencies = [ "base64ct", - "rand_core 0.6.4", + "rand_core", "subtle", ] @@ -2703,10 +2484,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]] @@ -2726,9 +2507,99 @@ dependencies = [ [[package]] name = "percent-encoding" -version = "2.1.0" +version = "2.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" + +[[package]] +name = "phf" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fabbf1ead8a5bcbc20f5f8b939ee3f5b0f6f281b6ad3468b84656b658b455259" +dependencies = [ + "phf_shared 0.10.0", +] + +[[package]] +name = "phf" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ade2d8b8f33c7333b51bcf0428d37e217e9f32192ae4772156f65063b8ce03dc" +dependencies = [ + "phf_macros", + "phf_shared 0.11.2", +] + +[[package]] +name = "phf_codegen" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fb1c3a8bc4dd4e5cfce29b44ffc14bedd2ee294559a294e2a4d4c9e9a6a13cd" +dependencies = [ + "phf_generator 0.10.0", + "phf_shared 0.10.0", +] + +[[package]] +name = "phf_codegen" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8d39688d359e6b34654d328e262234662d16cc0f60ec8dcbe5e718709342a5a" +dependencies = [ + "phf_generator 0.11.2", + "phf_shared 0.11.2", +] + +[[package]] +name = "phf_generator" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d5285893bb5eb82e6aaf5d59ee909a06a16737a8970984dd7746ba9283498d6" +dependencies = [ + "phf_shared 0.10.0", + "rand", +] + +[[package]] +name = "phf_generator" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48e4cc64c2ad9ebe670cb8fd69dd50ae301650392e81c05f9bfcb2d5bdbc24b0" +dependencies = [ + "phf_shared 0.11.2", + "rand", +] + +[[package]] +name = "phf_macros" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3444646e286606587e49f3bcf1679b8cef1dc2c5ecc29ddacaffc305180d464b" +dependencies = [ + "phf_generator 0.11.2", + "phf_shared 0.11.2", + "proc-macro2", + "quote", + "syn 2.0.79", +] + +[[package]] +name = "phf_shared" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6796ad771acdc0123d2a88dc428b5e38ef24456743ddb1744ed628f9815c096" +dependencies = [ + "siphasher", +] + +[[package]] +name = "phf_shared" +version = "0.11.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d4fd5641d01c8f18a23da7b6fe29298ff4b55afcccdf78973b24cf3175fee32e" +checksum = "90fcb95eef784c2ac79119d1dd819e162b5da872ce6f3c3abe1e8ca1c082f72b" +dependencies = [ + "siphasher", +] [[package]] name = "pin-project" @@ -2752,9 +2623,9 @@ dependencies = [ [[package]] name = "pin-project-lite" -version = "0.2.9" +version = "0.2.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e0a7ae3ac2f1173085d398531c705756c94a4c56843785df85a60c1a0afac116" +checksum = "bda66fc9667c18cb2758a2ac84d1167245054bcf85d5d1aaa6923f45801bdd02" [[package]] name = "pin-utils" @@ -2762,48 +2633,68 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" +[[package]] +name = "piper" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96c8c490f422ef9a4efd2cb5b42b76c8613d7e7dfc1caf667b8a3350a5acc066" +dependencies = [ + "atomic-waker", + "fastrand", + "futures-io", +] + [[package]] name = "pkg-config" -version = "0.3.25" +version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1df8c4ec4b0627e53bdf214615ad287367e482558cf84b109250b37464dc03ae" +checksum = "d231b230927b5e4ad203db57bbcbee2802f6bce620b1e4a9024a07d94e2907ec" [[package]] -name = "polling" -version = "2.3.0" +name = "plist" +version = "1.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "899b00b9c8ab553c743b3e11e87c5c7d423b2a2de229ba95b24a756344748011" +checksum = "9a4a0cfc5fb21a09dc6af4bf834cf10d4a32fccd9e2ea468c4b1751a097487aa" dependencies = [ - "autocfg", - "cfg-if", - "libc", - "log", - "wepoll-ffi", - "winapi", + "base64 0.21.0", + "indexmap 1.9.1", + "line-wrap", + "quick-xml", + "serde", + "time", ] [[package]] -name = "polyval" -version = "0.4.5" +name = "pollster" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eebcc4aa140b9abd2bc40d9c3f7ccec842679cd79045ac3a7ac698c1a064b7cd" +checksum = "22686f4785f02a4fcc856d3b3bb19bf6c8160d103f7a99cc258bddd0251dc7f2" dependencies = [ - "cpuid-bool", - "opaque-debug", - "universal-hash", + "pollster-macro", ] [[package]] -name = "portable-atomic" -version = "0.3.19" +name = "pollster-macro" +version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26f6a7b87c2e435a3241addceeeff740ff8b7e76b74c13bf9acb17fa454ea00b" +checksum = "ea78f0ef4193055a4b09814ce6bcb572ad1174d6023e2f00a9ea1a798d18d301" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.102", +] [[package]] name = "portable-atomic" -version = "1.6.0" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc9c68a3f6da06753e9335d63e27f6b9754dd1920d941135b7ea8224f141adb2" + +[[package]] +name = "powerfmt" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7170ef9988bc169ba16dd36a7fa041e5c4cbeb6a35b76d4c03daded371eae7c0" +checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" [[package]] name = "ppv-lite86" @@ -2811,24 +2702,31 @@ version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "eb9f9e6e233e5c4a35559a617bf40a4ec447db2e84c20b55a6f83167b7e57872" +[[package]] +name = "precomputed-hash" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "925383efa346730478fb4838dbe9137d2a47675ad789c546d150a6e1dd4ab31c" + [[package]] name = "predicates" -version = "2.1.5" +version = "3.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "59230a63c37f3e18569bdb90e4a89cbf5bf8b06fea0b84e65ea10cc4df47addd" +checksum = "7e9086cc7640c29a356d1a29fd134380bee9d8f79a17410aa76e7ad295f42c97" dependencies = [ - "concolor", + "anstyle", "difflib", - "itertools 0.10.5", + "float-cmp", + "normalize-line-endings", "predicates-core", - "yansi", + "regex", ] [[package]] name = "predicates-core" -version = "1.0.5" +version = "1.0.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72f883590242d3c6fc5bf50299011695fa6590c2c70eac95ee1bdb9a733ad1a2" +checksum = "ae8177bee8e75d6846599c6b9ff679ed51e882816914eec639944d7c9aa11931" [[package]] name = "predicates-tree" @@ -2842,13 +2740,11 @@ dependencies = [ [[package]] name = "pretty_assertions" -version = "1.3.0" +version = "1.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a25e9bcb20aa780fd0bb16b72403a9064d6b3f22f026946029acb941a50af755" +checksum = "3ae130e2f271fbc2ac3a40fb1d07180839cdbbe443c7a27e1e3c13c5cac0116d" dependencies = [ - "ctor", "diff", - "output_vt100", "yansi", ] @@ -2859,46 +2755,29 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8d3928fb5db768cb86f891ff014f0144589297e3c6a1aba6ed7cecfdace270c7" dependencies = [ "proc-macro2", - "syn 2.0.59", + "syn 2.0.79", ] [[package]] -name = "proc-macro-error" -version = "1.0.4" +name = "proc-macro2" +version = "1.0.86" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" +checksum = "5e719e8df665df0d1c8fbfd238015744736151d4445ec0836b8e628aae103b77" dependencies = [ - "proc-macro-error-attr", - "proc-macro2", - "quote", - "syn 1.0.102", - "version_check", + "unicode-ident", ] [[package]] -name = "proc-macro-error-attr" -version = "1.0.4" +name = "proc-macro2-diagnostics" +version = "0.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" +checksum = "af066a9c399a26e020ada66a034357a868728e72cd426f3adcd35f80d88d88c8" dependencies = [ "proc-macro2", "quote", + "syn 2.0.79", "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.80" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a56dea16b0a29e94408b9aa5e2940a4eedbd128a1ba20e8f7ae60fd3d465af0e" -dependencies = [ - "unicode-ident", + "yansi", ] [[package]] @@ -2921,7 +2800,7 @@ dependencies = [ "libc", "memoffset 0.9.0", "parking_lot", - "portable-atomic 1.6.0", + "portable-atomic", "pyo3-build-config", "pyo3-ffi", "pyo3-macros", @@ -2981,7 +2860,7 @@ dependencies = [ "proc-macro2", "pyo3-macros-backend", "quote", - "syn 2.0.59", + "syn 2.0.79", ] [[package]] @@ -2990,33 +2869,29 @@ version = "0.20.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7c7e9b68bb9c3149c5b0cade5d07f953d6d125eb4337723c4ccdb665f1f96185" dependencies = [ - "heck", + "heck 0.4.1", "proc-macro2", "pyo3-build-config", "quote", - "syn 2.0.59", + "syn 2.0.79", ] [[package]] -name = "quote" -version = "1.0.35" +name = "quick-xml" +version = "0.30.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "291ec9ab5efd934aaf503a6466c5d5251535d108ee747472c3977cc5acc868ef" +checksum = "eff6510e86862b57b210fd8cbe8ed3f0d7d600b9c2863cd4549a2e033c66e956" dependencies = [ - "proc-macro2", + "memchr", ] [[package]] -name = "rand" -version = "0.7.3" +name = "quote" +version = "1.0.37" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a6b1679d49b24bbfe0c803429aa1874472f50d9b363131f0e89fc356b544d03" +checksum = "b5b9d34b8991d19d98081b46eacdd8eb58c6f2b201139f7c5f643cc155a633af" dependencies = [ - "getrandom 0.1.16", - "libc", - "rand_chacha 0.2.2", - "rand_core 0.5.1", - "rand_hc", + "proc-macro2", ] [[package]] @@ -3026,18 +2901,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]] @@ -3047,16 +2912,7 @@ 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]] @@ -3065,16 +2921,7 @@ 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" -dependencies = [ - "rand_core 0.5.1", + "getrandom", ] [[package]] @@ -3085,9 +2932,9 @@ checksum = "60a357793950651c4ed0f3f52338f53b2f809f32d83a07f72909fa13e4c6c1e3" [[package]] name = "rayon" -version = "1.6.1" +version = "1.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6db3a213adf02b3bcfd2d3846bb41cb22857d131789e01df434fb7e7bc0759b7" +checksum = "b418a60154510ca1a002a752ca9714984e21e4241e804d32555251faf8b78ffa" dependencies = [ "either", "rayon-core", @@ -3095,14 +2942,12 @@ dependencies = [ [[package]] name = "rayon-core" -version = "1.10.1" +version = "1.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cac410af5d00ab6884528b4ab69d1e8e146e8d471201800fa1b4524126de6ad3" +checksum = "1465873a3dfdaa8ae7cb14b4383657caab0b3e8a0aa9ae8e04b044854c8dfce2" dependencies = [ - "crossbeam-channel", "crossbeam-deque", "crossbeam-utils", - "num_cpus", ] [[package]] @@ -3115,24 +2960,35 @@ dependencies = [ ] [[package]] -name = "redox_syscall" -version = "0.3.5" +name = "ref-cast" +version = "1.0.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "567664f262709473930a4bf9e51bf2ebf3348f2e748ccc50dea20646858f8f29" +checksum = "ccf0a6f84d5f1d581da8b41b47ec8600871962f2a528115b542b362d4b744931" dependencies = [ - "bitflags 1.3.2", + "ref-cast-impl", +] + +[[package]] +name = "ref-cast-impl" +version = "1.0.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bcc303e793d3734489387d205e9b186fac9c6cfacedd98cbb2e8a5943595f3e6" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.79", ] [[package]] name = "regex" -version = "1.10.0" +version = "1.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d119d7c7ca818f8a53c300863d4f87566aac09943aef5b355bb83969dae75d87" +checksum = "38200e5ee88914975b69f657f0801b6f6dccafd44fd9326302a4aaeecfacb1d8" dependencies = [ "aho-corasick", "memchr", - "regex-automata 0.4.1", - "regex-syntax 0.8.1", + "regex-automata 0.4.8", + "regex-syntax 0.8.5", ] [[package]] @@ -3146,13 +3002,13 @@ dependencies = [ [[package]] name = "regex-automata" -version = "0.4.1" +version = "0.4.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "465c6fc0621e4abc4187a2bda0937bfd4f722c2730b29562e19689ea796c9a4b" +checksum = "368758f23274712b504848e9d5a6f010445cc8b87a7cdb4d7cbee666c1288da3" dependencies = [ "aho-corasick", "memchr", - "regex-syntax 0.8.1", + "regex-syntax 0.8.5", ] [[package]] @@ -3169,18 +3025,18 @@ checksum = "dbb5fb1acd8a1a18b3dd5be62d25485eb770e05afb408a9627d14d451bae12da" [[package]] name = "regex-syntax" -version = "0.8.1" +version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "56d84fdd47036b038fc80dd333d10b6aab10d5d31f4a366e20014def75328d33" +checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" [[package]] name = "reqwest" -version = "0.11.13" +version = "0.11.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "68cc60575865c7831548863cc02356512e3f1dc2f3f82cb837d7fc4cc8f3c97c" +checksum = "dd67538700a17451e7cba03ac727fb961abb7607553461627b97de0b89cf4a62" dependencies = [ - "base64 0.13.0", - "bytes 1.1.0", + "base64 0.21.0", + "bytes", "encoding_rs", "futures-core", "futures-util", @@ -3201,6 +3057,8 @@ dependencies = [ "serde", "serde_json", "serde_urlencoded", + "sync_wrapper", + "system-configuration", "tokio", "tokio-rustls", "tokio-util", @@ -3208,6 +3066,7 @@ dependencies = [ "url", "wasm-bindgen", "wasm-bindgen-futures", + "wasm-streams", "web-sys", "webpki-roots", "winreg", @@ -3222,12 +3081,27 @@ dependencies = [ "cc", "libc", "once_cell", - "spin", - "untrusted", + "spin 0.5.2", + "untrusted 0.7.1", "web-sys", "winapi", ] +[[package]] +name = "ring" +version = "0.17.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c17fa4cb658e3583423e915b9f3acc01cceaee1860e33d59ebae66adc3a2dc0d" +dependencies = [ + "cc", + "cfg-if", + "getrandom", + "libc", + "spin 0.9.8", + "untrusted 0.9.0", + "windows-sys 0.52.0", +] + [[package]] name = "rstest" version = "0.15.0" @@ -3237,7 +3111,7 @@ dependencies = [ "futures", "futures-timer", "rstest_macros", - "rustc_version 0.4.0", + "rustc_version", ] [[package]] @@ -3249,7 +3123,7 @@ dependencies = [ "cfg-if", "proc-macro2", "quote", - "rustc_version 0.4.0", + "rustc_version", "syn 1.0.102", ] @@ -3260,9 +3134,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "88530b681abe67924d42cca181d070e3ac20e0740569441a9e35a7cedd2b34a4" dependencies = [ "quote", - "rand 0.8.5", - "rustc_version 0.4.0", - "syn 2.0.59", + "rand", + "rustc_version", + "syn 2.0.79", ] [[package]] @@ -3277,62 +3151,38 @@ 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", -] - -[[package]] -name = "rustix" -version = "0.36.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d4fdebc4b395b7fbb9ab11e462e20ed9051e7b16e42d24042c776eca0ac81b03" -dependencies = [ - "bitflags 1.3.2", - "errno 0.2.8", - "io-lifetimes", - "libc", - "linux-raw-sys 0.1.4", - "windows-sys 0.42.0", + "semver", ] [[package]] name = "rustix" -version = "0.37.19" +version = "0.38.37" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "acf8729d8542766f1b2cf77eb034d52f40d375bb8b615d0b147089946e16613d" +checksum = "8acb788b847c24f28525660c4d7758620a7210875711f79e7f663cc152726811" dependencies = [ - "bitflags 1.3.2", - "errno 0.3.1", - "io-lifetimes", + "bitflags 2.5.0", + "errno", "libc", - "linux-raw-sys 0.3.8", - "windows-sys 0.48.0", + "linux-raw-sys", + "windows-sys 0.52.0", ] [[package]] name = "rustls" -version = "0.20.6" +version = "0.21.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5aab8ee6c7097ed6057f43c187a62418d0c05a4bd5f18b3571db50ee0f9ce033" +checksum = "7fecbfb7b1444f477b345853b1fce097a2c6fb637b2bfb87e6bc5db0f043fae4" dependencies = [ "log", - "ring", + "ring 0.17.8", + "rustls-webpki", "sct", - "webpki", ] [[package]] @@ -3344,6 +3194,16 @@ dependencies = [ "base64 0.21.0", ] +[[package]] +name = "rustls-webpki" +version = "0.101.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b6275d1ee7a1cd780b64aca7726599a1dbc893b1e64144529e55c3c2f745765" +dependencies = [ + "ring 0.17.8", + "untrusted 0.9.0", +] + [[package]] name = "rustversion" version = "1.0.11" @@ -3356,6 +3216,12 @@ version = "1.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4501abdff3ae82a1c1b477a17252eb69cee9e66eb915c1abaa4f44d873df9f09" +[[package]] +name = "safemem" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef703b7cb59335eae2eb93ceb664c0eb7ea6bf567079d843e09420219668e072" + [[package]] name = "same-file" version = "1.0.6" @@ -3365,22 +3231,28 @@ 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" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" +[[package]] +name = "scraper" +version = "0.19.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "761fb705fdf625482d2ed91d3f0559dcfeab2798fe2771c69560a774865d0802" +dependencies = [ + "ahash", + "cssparser", + "ego-tree", + "getopts", + "html5ever", + "once_cell", + "selectors", + "tendril", +] + [[package]] name = "scratch" version = "1.0.3" @@ -3393,8 +3265,8 @@ version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d53dcdb7c9f8158937a7981b48accfd39a43af418591a5d008c7b22b5e1b7ca4" dependencies = [ - "ring", - "untrusted", + "ring 0.16.20", + "untrusted 0.7.1", ] [[package]] @@ -3407,12 +3279,22 @@ dependencies = [ ] [[package]] -name = "semver" -version = "0.9.0" +name = "selectors" +version = "0.25.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d7eb9ef2c18661902cc47e535f9bc51b78acd254da71d375c2f6720d9a40403" +checksum = "4eb30575f3638fc8f6815f448d50cb1a2e255b0897985c8c59f4d37b72a07b06" dependencies = [ - "semver-parser", + "bitflags 2.5.0", + "cssparser", + "derive_more", + "fxhash", + "log", + "new_debug_unreachable", + "phf 0.10.1", + "phf_codegen 0.10.0", + "precomputed-hash", + "servo_arc", + "smallvec", ] [[package]] @@ -3420,41 +3302,39 @@ 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" +dependencies = [ + "serde", +] [[package]] name = "serde" -version = "1.0.164" +version = "1.0.210" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e8c8cf938e98f769bc164923b06dce91cea1751522f46f8466461af04c9027d" +checksum = "c8e3592472072e6e22e0a54d5904d9febf8508f65fb8552499a1abc7d1078c3a" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.164" +version = "1.0.210" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d9735b638ccc51c28bf6914d90a2e9725b377144fc612c49a611fddd1b631d68" +checksum = "243902eda00fad750862fc144cea25caca5e20d615af0a81bee94ca738f1df1f" dependencies = [ "proc-macro2", "quote", - "syn 2.0.59", + "syn 2.0.79", ] [[package]] name = "serde_json" -version = "1.0.85" +version = "1.0.128" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e55a28e3aaef9d5ce0506d0a14dbba8054ddc7e499ef522dd8b26859ec9d4a44" +checksum = "6ff5456707a1de34e7e37f2a6fd3d3f808c318259cbd01ab6377795054b483d8" dependencies = [ - "indexmap 1.9.1", + "indexmap 2.6.0", "itoa", + "memchr", "ryu", "serde", ] @@ -3468,22 +3348,11 @@ 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" +version = "0.6.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0efd8caf556a6cebd3b285caf480045fcc1ac04f6bd786b09a6f11af30c4fcf4" +checksum = "87607cb1398ed59d48732e575a4c28a7a8ebf2454b964fe3f224f2afc07909e1" dependencies = [ "serde", ] @@ -3502,40 +3371,41 @@ dependencies = [ [[package]] name = "serde_with" -version = "3.3.0" +version = "3.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ca3b16a3d82c4088f343b7480a93550b3eabe1a358569c2dfe38bbcead07237" +checksum = "9720086b3357bcb44fce40117d769a4d068c70ecfa190850a980a71755f66fcc" dependencies = [ - "base64 0.21.0", + "base64 0.22.1", "chrono", "hex", "indexmap 1.9.1", - "indexmap 2.0.0", + "indexmap 2.6.0", "serde", + "serde_derive", "serde_json", "serde_with_macros", - "time 0.3.15", + "time", ] [[package]] name = "serde_with_macros" -version = "3.3.0" +version = "3.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2e6be15c453eb305019bfa438b1593c731f36a289a7853f7707ee29e870b3b3c" +checksum = "5f1abbfe725f27678f4663bcacb75a83e829fd464c25d78dd038a3a29e307cec" dependencies = [ "darling", "proc-macro2", "quote", - "syn 2.0.59", + "syn 2.0.79", ] [[package]] -name = "sha1" -version = "0.6.1" +name = "servo_arc" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c1da05c97445caa12d05e848c4a4fcbbea29e748ac28f7e80e9b010392063770" +checksum = "d036d71a959e00c77a63538b90a6c2390969f9772b096ea837205c6bd0491a44" dependencies = [ - "sha1_smol", + "stable_deref_trait", ] [[package]] @@ -3546,26 +3416,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]] @@ -3576,7 +3427,7 @@ checksum = "82e6b795fe2e3b1e845bafcb27aa35405c4d47cdfc92af5fc8d3002f76cebdc0" dependencies = [ "cfg-if", "cpufeatures", - "digest 0.10.5", + "digest", ] [[package]] @@ -3599,10 +3450,16 @@ dependencies = [ ] [[package]] -name = "shlex" +name = "shell-words" version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "43b2853a4d09f215c24cc5489c992ce46052d359b5109343cbafbf26bc62f8a3" +checksum = "24188a676b6ae68c3b2cb3a01be17fbf7240ce009799bb56d5b1409051e78fde" + +[[package]] +name = "shlex" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" [[package]] name = "simple_asn1" @@ -3613,9 +3470,15 @@ dependencies = [ "num-bigint", "num-traits", "thiserror", - "time 0.3.15", + "time", ] +[[package]] +name = "siphasher" +version = "0.3.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38b58827f4464d87d377d175e90bf58eb00fd8716ff0a62f80356b5e61555d0d" + [[package]] name = "slab" version = "0.4.7" @@ -3626,21 +3489,20 @@ dependencies = [ ] [[package]] -name = "sluice" -version = "0.5.5" +name = "slug" +version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6d7400c0eff44aa2fcb5e31a5f24ba9716ed90138769e4977a2ba6014ae63eb5" +checksum = "3bd94acec9c8da640005f8e135a39fc0372e74535e6b368b7a04b875f784c8c4" dependencies = [ - "async-channel", - "futures-core", - "futures-io", + "deunicode", + "wasm-bindgen", ] [[package]] name = "smallvec" -version = "1.13.1" +version = "1.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e6ecd384b10a64542d77071bd64bd7b231f4ed5940fba55e98c3de13824cf3d7" +checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" [[package]] name = "snafu" @@ -3659,7 +3521,7 @@ version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "475b3bbe5245c26f2d8a6f62d67c1f30eb9fffeccee721c45d162c3ebbdf81b2" dependencies = [ - "heck", + "heck 0.4.1", "proc-macro2", "quote", "syn 1.0.102", @@ -3675,6 +3537,27 @@ dependencies = [ "winapi", ] +[[package]] +name = "socket2" +version = "0.5.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce305eb0b4296696835b71df73eb912e0f1ffd2556a501fcede6e0c50349191c" +dependencies = [ + "libc", + "windows-sys 0.52.0", +] + +[[package]] +name = "socks" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0c3dbbd9ae980613c6dd8e28a9407b50509d3803b57624d5dfe8315218cd58b" +dependencies = [ + "byteorder", + "libc", + "winapi", +] + [[package]] name = "spin" version = "0.5.2" @@ -3682,22 +3565,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d" [[package]] -name = "spinning_top" -version = "0.2.4" +name = "spin" +version = "0.9.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75adad84ee84b521fb2cca2d4fd0f1dab1d8d026bda3c5bea4ca63b5f9f9293c" -dependencies = [ - "lock_api", -] +checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" [[package]] -name = "standback" -version = "0.2.17" +name = "stable_deref_trait" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e113fb6f3de07a243d434a56ec6f186dfd51cb08448239fe7bcae73f87ff28ff" -dependencies = [ - "version_check", -] +checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" [[package]] name = "static_assertions" @@ -3706,59 +3583,36 @@ 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" +name = "string_cache" +version = "0.8.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c87a60a40fccc84bef0652345bbbbbe20a605bf5d0ce81719fc476f5c03b50ef" +checksum = "f91138e76242f575eb1d3b38b4f1362f10d3a43f47d182a5b359af488a02293b" dependencies = [ - "proc-macro2", - "quote", + "new_debug_unreachable", + "once_cell", + "parking_lot", + "phf_shared 0.10.0", + "precomputed-hash", "serde", - "serde_derive", - "syn 1.0.102", ] [[package]] -name = "stdweb-internal-macros" -version = "0.2.9" +name = "string_cache_codegen" +version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "58fa5ff6ad0d98d1ffa8cb115892b6e69d67799f6763e162a1c9db421dc22e11" +checksum = "6bb30289b722be4ff74a408c3cc27edeaad656e06cb1fe8fa9231fa59c728988" dependencies = [ - "base-x", + "phf_generator 0.10.0", + "phf_shared 0.10.0", "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" +version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" +checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" [[package]] name = "structmeta" @@ -3769,7 +3623,7 @@ dependencies = [ "proc-macro2", "quote", "structmeta-derive", - "syn 2.0.59", + "syn 2.0.79", ] [[package]] @@ -3780,7 +3634,7 @@ checksum = "a60bcaff7397072dca0017d1db428e30d5002e00b6847703e2e42005c95fbe00" dependencies = [ "proc-macro2", "quote", - "syn 2.0.59", + "syn 2.0.79", ] [[package]] @@ -3798,7 +3652,7 @@ version = "0.24.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e385be0d24f186b4ce2f9982191e7101bb737312ad61c1f2f984f34bcf85d59" dependencies = [ - "heck", + "heck 0.4.1", "proc-macro2", "quote", "rustversion", @@ -3811,29 +3665,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" @@ -3847,20 +3678,70 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.59" +version = "2.0.79" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4a6531ffc7b071655e4ce2e04bd464c4830bb585a61cabb96cf808f05172615a" +checksum = "89132cd0bf050864e1d38dc3bbc07a0eb8e7530af26344d3d2bbbef83499f590" dependencies = [ "proc-macro2", "quote", "unicode-ident", ] +[[package]] +name = "sync_wrapper" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2047c6ded9c721764247e62cd3b03c09ffc529b2ba5b10ec482ae507a4a70160" + +[[package]] +name = "syntect" +version = "5.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "874dcfa363995604333cf947ae9f751ca3af4522c60886774c4963943b4746b1" +dependencies = [ + "bincode", + "bitflags 1.3.2", + "fancy-regex", + "flate2", + "fnv", + "once_cell", + "onig", + "plist", + "regex-syntax 0.8.5", + "serde", + "serde_derive", + "serde_json", + "thiserror", + "walkdir", + "yaml-rust", +] + +[[package]] +name = "system-configuration" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba3a3adc5c275d719af8cb4272ea1c4a6d668a777f37e115f6d11ddbc1c8e0e7" +dependencies = [ + "bitflags 1.3.2", + "core-foundation", + "system-configuration-sys", +] + +[[package]] +name = "system-configuration-sys" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a75fb188eb626b924683e3b95e3a48e63551fcfb51949de2f06a9d91dbee93c9" +dependencies = [ + "core-foundation-sys", + "libc", +] + [[package]] name = "tar" -version = "0.4.38" +version = "0.4.42" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4b55807c0344e1e6c04d7c965f5289c39a8d94ae23ed5c0b57aabac549f871c6" +checksum = "4ff6c40d3aedb5e06b57c6f669ad17ab063dd1e63d977c6a88e7f4dfa4f04020" dependencies = [ "filetime", "libc", @@ -3875,16 +3756,26 @@ checksum = "c02424087780c9b71cc96799eaeddff35af2bc513278cda5c99fc1f5d026d3c1" [[package]] name = "tempfile" -version = "3.6.0" +version = "3.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "31c0432476357e58790aaa47a8efb0c5138f137343f3b5f23bd36a27e3b0a6d6" +checksum = "f0f2c9fc62d0beef6951ccffd757e241266a2c833136efbe35af6cd2567dca5b" dependencies = [ - "autocfg", "cfg-if", - "fastrand 1.8.0", - "redox_syscall 0.3.5", - "rustix 0.37.19", - "windows-sys 0.48.0", + "fastrand", + "once_cell", + "rustix", + "windows-sys 0.59.0", +] + +[[package]] +name = "tendril" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d24a120c5fc464a3458240ee02c299ebcb9d67b5249c8848b09d639dca8d7bb0" +dependencies = [ + "futf", + "mac", + "utf-8", ] [[package]] @@ -3896,6 +3787,16 @@ dependencies = [ "winapi-util", ] +[[package]] +name = "terminal_size" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4f599bd7ca042cfdf8f4512b277c02ba102247820f9d9d4a9f521f496751a6ef" +dependencies = [ + "rustix", + "windows-sys 0.59.0", +] + [[package]] name = "termtree" version = "0.4.0" @@ -3907,46 +3808,40 @@ name = "test_util" version = "0.0.0" dependencies = [ "anyhow", - "async-std", - "async_zip", "bindgen 0.69.4", "camino", + "cargo_metadata", "flate2", "fs-err", - "futures-lite 2.2.0", - "libloading", - "once_cell", + "indoc", + "libloading 0.7.3", + "reqwest", "serde", "serde_json", - "surf", "tar", "tokio", + "voicevox-ort", + "zip", ] -[[package]] -name = "textwrap" -version = "0.15.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "949517c0cf1bf4ee812e2e07e08ab448e3ae0d23472aee8a06c985f0c8815b16" - [[package]] name = "thiserror" -version = "1.0.56" +version = "1.0.64" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d54378c645627613241d077a3a79db965db602882668f9136ac42af9ecb730ad" +checksum = "d50af8abc119fb8bb6dbabcfa89656f46f84aa0ac7688088608076ad2b459a84" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.56" +version = "1.0.64" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fa0faa943b50f3db30a20aa7e265dbc66076993efed8463e8de414e5d06d3471" +checksum = "08904e7672f5eb876eaaf87e0ce17857500934f4981c4a0ab2b4aa98baac7fc3" dependencies = [ "proc-macro2", "quote", - "syn 2.0.59", + "syn 2.0.79", ] [[package]] @@ -3969,59 +3864,33 @@ dependencies = [ [[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" +version = "0.3.36" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d634a985c4d4238ec39cacaed2e7ae552fbd3c476b552c1deac3021b7d7eaf0c" +checksum = "5dfd88e563464686c916c7e46e623e520ddc6d79fa6641390f2e3fa86e83e885" dependencies = [ + "deranged", "itoa", - "libc", - "num_threads", + "num-conv", + "powerfmt", "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-core", + "time-macros", ] [[package]] -name = "time-macros" -version = "0.2.4" +name = "time-core" +version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "42657b1a6f4d817cda8e7a0ace261fe0cc946cf3a80314390b22cc61ae080792" +checksum = "ef927ca75afb808a4d64dd374f00a2adf8d0fcff8e7b184af886c3c87ec4a3f3" [[package]] -name = "time-macros-impl" -version = "0.1.2" +name = "time-macros" +version = "0.2.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fd3c141a1b43194f3f56a1411225df8646c55781d5f26db825b3d98507eb482f" +checksum = "3f252a68540fde3a3877aeea552b832b40ab9a69e318efd078774a01ddee1ccf" dependencies = [ - "proc-macro-hack", - "proc-macro2", - "quote", - "standback", - "syn 1.0.102", + "num-conv", + "time-core", ] [[package]] @@ -4041,41 +3910,39 @@ checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c" [[package]] name = "tokio" -version = "1.28.0" +version = "1.40.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3c786bf8134e5a3a166db9b29ab8f48134739014a3eca7bc6bfa95d673b136f" +checksum = "e2b070231665d27ad9ec9b8df639893f46727666c6767db40317fbe920a5d998" dependencies = [ - "autocfg", - "bytes 1.1.0", + "backtrace", + "bytes", "libc", "mio", - "num_cpus", "pin-project-lite", - "socket2", + "socket2 0.5.7", "tokio-macros", - "windows-sys 0.48.0", + "windows-sys 0.52.0", ] [[package]] name = "tokio-macros" -version = "2.1.0" +version = "2.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "630bdcf245f78637c13ec01ffae6187cca34625e8c63150d424b59e55af2675e" +checksum = "693d596312e88961bc67d7f1f97af8a70227d9f90c31bba5806eec004978d752" dependencies = [ "proc-macro2", "quote", - "syn 2.0.59", + "syn 2.0.79", ] [[package]] name = "tokio-rustls" -version = "0.23.4" +version = "0.24.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c43ee83903113e03984cb9e5cebe6c04a5116269e900e3ddba8f068a62adda59" +checksum = "c28327cf380ac148141087fbfb9de9d7bd4e84ab5d2c28fbc911d753de8a7081" dependencies = [ "rustls", "tokio", - "webpki", ] [[package]] @@ -4084,7 +3951,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", @@ -4094,30 +3961,33 @@ dependencies = [ [[package]] name = "toml" -version = "0.5.9" +version = "0.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8d82e1a7758622a465f8cee077614c73484dac5b836c02ff6a40d5d1010324d7" +checksum = "f7afcae9e3f0fe2c370fd4657108972cbb2fa9db1b9f84849cefd80741b01cb6" dependencies = [ "serde", + "serde_spanned", + "toml_datetime", + "toml_edit 0.19.3", ] [[package]] name = "toml" -version = "0.7.2" +version = "0.8.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f7afcae9e3f0fe2c370fd4657108972cbb2fa9db1b9f84849cefd80741b01cb6" +checksum = "a1ed1f98e3fdc28d6d910e6737ae6ab1a93bf1985935a1193e68f93eeb68d24e" dependencies = [ "serde", "serde_spanned", "toml_datetime", - "toml_edit", + "toml_edit 0.22.22", ] [[package]] name = "toml_datetime" -version = "0.6.1" +version = "0.6.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3ab8ed2edee10b50132aed5f331333428b011c99402b5a534154ed15746f9622" +checksum = "0dd7358ecb8fc2f8d014bf86f6f638ce72ba252a2c3a2572f2a795f1d23efb41" dependencies = [ "serde", ] @@ -4135,6 +4005,19 @@ dependencies = [ "toml_datetime", ] +[[package]] +name = "toml_edit" +version = "0.22.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ae48d6208a266e853d946088ed816055e556cc6028c5e8e2b84d9fa5dd7c7f5" +dependencies = [ + "indexmap 2.6.0", + "serde", + "serde_spanned", + "toml_datetime", + "winnow", +] + [[package]] name = "tower-service" version = "0.3.2" @@ -4143,11 +4026,10 @@ checksum = "b6bc1c9ce2b5135ac7f93c72918fc37feb872bdc6a5533a8b85eb4b86bfdae52" [[package]] name = "tracing" -version = "0.1.37" +version = "0.1.40" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ce8c33a8d48bd45d624a6e523445fd21ec13d3653cd51f681abf67418f54eb8" +checksum = "c3523ab5a71916ccf420eebdf5521fcef02141234bbc0b8a49f2fdc4544364ef" dependencies = [ - "cfg-if", "log", "pin-project-lite", "tracing-attributes", @@ -4156,20 +4038,20 @@ dependencies = [ [[package]] name = "tracing-attributes" -version = "0.1.23" +version = "0.1.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4017f8f45139870ca7e672686113917c71c7a6e02d4924eda67186083c03081a" +checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7" dependencies = [ "proc-macro2", "quote", - "syn 1.0.102", + "syn 2.0.79", ] [[package]] name = "tracing-core" -version = "0.1.30" +version = "0.1.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "24eb03ba0eab1fd845050058ce5e616558e8f8d8fca633e6b163fe25c797213a" +checksum = "c06d3da6113f116aaee68e4d601191614c9053067f9ab7f6edbcb161237daa54" dependencies = [ "once_cell", "valuable", @@ -4185,32 +4067,22 @@ 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" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "78ddad33d2d10b1ed7eb9d1f518a5674713876e97e5bb9b7345a7984fbb4f922" +checksum = "ee855f1f400bd0e5c02d150ae5de3840039a3f54b025156404e34c23c03f47c3" dependencies = [ - "lazy_static", "log", + "once_cell", "tracing-core", ] [[package]] name = "tracing-subscriber" -version = "0.3.17" +version = "0.3.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "30a651bc37f915e81f087d86e62a18eec5f79550c7faff886f7090b4ea757c77" +checksum = "ad0f048c97dbd9faa9b7df56362b8ebcaa52adb06b498c050d2f4e32f90a7a8b" dependencies = [ "matchers", "nu-ansi-term", @@ -4230,6 +4102,18 @@ version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3528ecfd12c466c6f163363caf2d02a71161dd5e1cc6ae7b34207ea2d42d81ed" +[[package]] +name = "typed-arena" +version = "2.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6af6ae20167a9ece4bcb41af5b80f8a1f1df981f6391189ce00fd257af04126a" + +[[package]] +name = "typeid" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0e13db2e0ccd5e14a544e8a246ba2312cd25223f616442d7f2cb0e3db614236e" + [[package]] name = "typenum" version = "1.15.0" @@ -4238,9 +4122,9 @@ checksum = "dcf81ac59edc17cc8697ff311e8f5ef2d99fcbd9817b34cec66f90b6c3dfd987" [[package]] name = "typetag" -version = "0.2.7" +version = "0.2.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "edc3ebbaab23e6cc369cb48246769d031f5bd85f1b28141f32982e3c0c7b33cf" +checksum = "52ba3b6e86ffe0054b2c44f2d86407388b933b16cb0a70eea3929420db1d9bbe" dependencies = [ "erased-serde", "inventory", @@ -4251,29 +4135,20 @@ dependencies = [ [[package]] name = "typetag-impl" -version = "0.2.7" +version = "0.2.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bb01b60fcc3f5e17babb1a9956263f3ccd2cadc3e52908400231441683283c1d" +checksum = "70b20a22c42c8f1cd23ce5e34f165d4d37038f5b663ad20fb6adbdf029172483" dependencies = [ "proc-macro2", "quote", - "syn 2.0.59", -] - -[[package]] -name = "unicase" -version = "2.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "50f37be617794602aabbeee0be4f259dc1778fabe05e2d67ee8f79326d5cb4f6" -dependencies = [ - "version_check", + "syn 2.0.79", ] [[package]] name = "unicode-bidi" -version = "0.3.8" +version = "0.3.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "099b7128301d285f79ddd55b9a83d5e6b9e97c92e0ea0daebee7263e932de992" +checksum = "5ab17db44d7388991a428b2ee655ce0c212e862eff1768a455c58f9aad6e7893" [[package]] name = "unicode-ident" @@ -4296,6 +4171,18 @@ version = "0.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c0edd1e5b14653f783770bce4a4dabb4a5108a5370a5f5d8cfe8710c361f6c8b" +[[package]] +name = "unicode-xid" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f962df74c8c05a667b5ee8bcf162993134c104e96440b663c8daa176dc772d8c" + +[[package]] +name = "unicode_categories" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "39ec24b3121d976906ece63c9daad25b85969647682eee313cb5779fdd69e14e" + [[package]] name = "unindent" version = "0.2.3" @@ -4303,43 +4190,38 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c7de7d73e1754487cb58364ee906a499937a0dfabd86bcb980fa99ec8c8fa2ce" [[package]] -name = "universal-hash" -version = "0.4.1" +name = "untrusted" +version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9f214e8f697e925001e66ec2c6e37a4ef93f0f78c2eed7814394e10c62025b05" -dependencies = [ - "generic-array", - "subtle", -] +checksum = "a156c684c91ea7d62626509bce3cb4e1d9ed5c4d978f7b4352658f96a4c26b4a" [[package]] name = "untrusted" -version = "0.7.1" +version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a156c684c91ea7d62626509bce3cb4e1d9ed5c4d978f7b4352658f96a4c26b4a" +checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" [[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-webpki", + "socks", "url", - "webpki", "webpki-roots", ] [[package]] name = "url" -version = "2.3.0" +version = "2.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22fe195a4f217c25b25cb5058ced57059824a678474874038dc88d211bf508d3" +checksum = "22784dbdf76fdde8af1aeda5622b546b422b6fc585325248a2bf9f5e41e94d6c" dependencies = [ "form_urlencoded", "idna", @@ -4347,6 +4229,12 @@ dependencies = [ "serde", ] +[[package]] +name = "utf-8" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9" + [[package]] name = "utf8parse" version = "0.2.1" @@ -4355,11 +4243,11 @@ checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a" [[package]] name = "uuid" -version = "1.4.0" +version = "1.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d023da39d1fde5a8a3fe1f3e01ca9632ada0a63e9797de55a879d6e2236277be" +checksum = "81dfa00651efa65069b0b6b651f4aaa31ba9e3c3ce0137aaad053604ee7e0314" dependencies = [ - "getrandom 0.2.7", + "getrandom", "serde", ] @@ -4370,34 +4258,51 @@ 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.4" +source = "git+https://github.com/VOICEVOX/ort.git?rev=8627833456a69e7841ae2a29fd184752df8de8d9#8627833456a69e7841ae2a29fd184752df8de8d9" +dependencies = [ + "anyhow", + "half", + "js-sys", + "libloading 0.8.3", + "ndarray", + "once_cell", + "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.4" +source = "git+https://github.com/VOICEVOX/ort.git?rev=8627833456a69e7841ae2a29fd184752df8de8d9#8627833456a69e7841ae2a29fd184752df8de8d9" +dependencies = [ + "flate2", + "pkg-config", + "sha2", + "tar", + "ureq", +] [[package]] name = "voicevox_core" version = "0.0.0" dependencies = [ "anyhow", + "async-fs", + "async-lock", "async_zip", + "blocking", "camino", + "const_format", "derive-getters", "derive-new", "derive_more", @@ -4406,20 +4311,20 @@ dependencies = [ "educe", "enum-map", "fs-err", - "futures", - "heck", + "futures-io", + "futures-lite", + "futures-util", + "heck 0.4.1", "humansize", - "indexmap 2.0.0", + "indexmap 2.6.0", "itertools 0.10.5", "jlabel", - "nanoid", "ndarray", - "once_cell", - "onnxruntime", "open_jtalk", "ouroboros", + "pollster", "pretty_assertions", - "rayon", + "ref-cast", "regex", "rstest", "rstest_reuse", @@ -4434,36 +4339,40 @@ dependencies = [ "tokio", "tracing", "uuid", + "voicevox-ort", "voicevox_core_macros", "windows", - "zip", ] [[package]] name = "voicevox_core_c_api" version = "0.0.0" dependencies = [ - "anstream", + "anstream 0.5.0", "anstyle-query", "anyhow", "assert_cmd", "camino", "chrono", - "clap 4.0.10", + "clap", "colorchoice", - "derive-getters", + "const_format", "duct", + "duplicate", "easy-ext", - "futures", + "educe", + "indexmap 2.6.0", "inventory", "itertools 0.10.5", "libc", - "libloading", + "libloading 0.7.3", "libtest-mimic", "ndarray", "ndarray-stats", - "once_cell", + "parking_lot", + "predicates", "process_path", + "ref-cast", "regex", "serde", "serde_json", @@ -4476,6 +4385,7 @@ dependencies = [ "tracing-subscriber", "typetag", "uuid", + "voicevox-ort", "voicevox_core", ] @@ -4486,8 +4396,11 @@ dependencies = [ "android_logger", "chrono", "derive_more", + "duplicate", + "easy-ext", "jni", - "once_cell", + "pretty_assertions", + "rstest", "serde_json", "tracing", "tracing-subscriber", @@ -4499,10 +4412,11 @@ dependencies = [ name = "voicevox_core_macros" version = "0.0.0" dependencies = [ - "indexmap 2.0.0", + "derive-syn-parse", + "indexmap 2.6.0", "proc-macro2", "quote", - "syn 2.0.59", + "syn 2.0.79", ] [[package]] @@ -4511,12 +4425,15 @@ version = "0.0.0" dependencies = [ "camino", "easy-ext", + "futures-lite", "log", + "once_cell", "pyo3", "pyo3-asyncio", "pyo3-log", "serde", "serde_json", + "tokio", "tracing", "uuid", "voicevox_core", @@ -4531,12 +4448,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" @@ -4557,12 +4468,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" @@ -4571,34 +4476,35 @@ checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" [[package]] name = "wasm-bindgen" -version = "0.2.83" +version = "0.2.95" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eaf9f5aceeec8be17c128b2e93e031fb8a4d469bb9c4ae2d7dc1888b26887268" +checksum = "128d1e363af62632b8eb57219c8fd7877144af57558fb2ef0368d0087bddeb2e" dependencies = [ "cfg-if", + "once_cell", "wasm-bindgen-macro", ] [[package]] name = "wasm-bindgen-backend" -version = "0.2.83" +version = "0.2.95" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c8ffb332579b0557b52d268b91feab8df3615f265d5270fec2a8c95b17c1142" +checksum = "cb6dd4d3ca0ddffd1dd1c9c04f94b868c37ff5fac97c30b97cff2d74fce3a358" dependencies = [ "bumpalo", "log", "once_cell", "proc-macro2", "quote", - "syn 1.0.102", + "syn 2.0.79", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-futures" -version = "0.4.33" +version = "0.4.45" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "23639446165ca5a5de86ae1d8896b737ae80319560fbaa4c2887b7da6e7ebd7d" +checksum = "cc7ec4f8827a71586374db3e87abdb5a2bb3a15afed140221307c3ec06b1f63b" dependencies = [ "cfg-if", "js-sys", @@ -4608,9 +4514,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.83" +version = "0.2.95" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "052be0f94026e6cbc75cdefc9bae13fd6052cdcaf532fa6c45e7ae33a1e6c810" +checksum = "e79384be7f8f5a9dd5d7167216f022090cf1f9ec128e6e6a482a2cb5c5422c56" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -4618,60 +4524,51 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.83" +version = "0.2.95" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07bc0c051dc5f23e307b13285f9d75df86bfdf816c5721e573dec1f9b8aa193c" +checksum = "26c6ab57572f7a24a4985830b120de1594465e5d500f24afe89e16b4e833ef68" dependencies = [ "proc-macro2", "quote", - "syn 1.0.102", + "syn 2.0.79", "wasm-bindgen-backend", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-shared" -version = "0.2.83" +version = "0.2.95" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1c38c045535d93ec4f0b4defec448e4291638ee608530863b1e2ba115d4fff7f" +checksum = "65fc09f10666a9f147042251e0dda9c18f166ff7de300607007e96bdebc1068d" [[package]] -name = "web-sys" -version = "0.3.60" +name = "wasm-streams" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bcda906d8be16e728fd5adc5b729afad4e444e106ab28cd1c7256e54fa61510f" +checksum = "4e072d4e72f700fb3443d8fe94a39315df013eef1104903cdb0a2abd322bbecd" dependencies = [ + "futures-util", "js-sys", "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", ] [[package]] -name = "webpki" -version = "0.22.0" +name = "web-sys" +version = "0.3.72" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f095d78192e208183081cc07bc5515ef55216397af48b873e5edcd72637fa1bd" +checksum = "f6488b90108c040df0fe62fa815cbdee25124641df01814dd7282749234c6112" dependencies = [ - "ring", - "untrusted", + "js-sys", + "wasm-bindgen", ] [[package]] name = "webpki-roots" -version = "0.22.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "368bfe657969fb01238bb756d351dcade285e0f6fcbd36dcb23359a5169975be" -dependencies = [ - "webpki", -] - -[[package]] -name = "wepoll-ffi" -version = "0.1.2" +version = "0.25.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d743fdedc5c64377b5fc2bc036b01c7fd642205a0d96356034ae3404d49eb7fb" -dependencies = [ - "cc", -] +checksum = "5f20c57d8d7db6d3b86154206ae5d8fba62dd39573114de97c2cb0578251f8e1" [[package]] name = "which" @@ -4773,7 +4670,25 @@ version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" dependencies = [ - "windows-targets 0.48.0", + "windows-targets 0.48.5", +] + +[[package]] +name = "windows-sys" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" +dependencies = [ + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-sys" +version = "0.59.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" +dependencies = [ + "windows-targets 0.52.6", ] [[package]] @@ -4793,17 +4708,33 @@ dependencies = [ [[package]] name = "windows-targets" -version = "0.48.0" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" +dependencies = [ + "windows_aarch64_gnullvm 0.48.5", + "windows_aarch64_msvc 0.48.5", + "windows_i686_gnu 0.48.5", + "windows_i686_msvc 0.48.5", + "windows_x86_64_gnu 0.48.5", + "windows_x86_64_gnullvm 0.48.5", + "windows_x86_64_msvc 0.48.5", +] + +[[package]] +name = "windows-targets" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b1eb6f0cd7c80c79759c929114ef071b87354ce476d9d94271031c0497adfd5" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" dependencies = [ - "windows_aarch64_gnullvm 0.48.0", - "windows_aarch64_msvc 0.48.0", - "windows_i686_gnu 0.48.0", - "windows_i686_msvc 0.48.0", - "windows_x86_64_gnu 0.48.0", - "windows_x86_64_gnullvm 0.48.0", - "windows_x86_64_msvc 0.48.0", + "windows_aarch64_gnullvm 0.52.6", + "windows_aarch64_msvc 0.52.6", + "windows_i686_gnu 0.52.6", + "windows_i686_gnullvm", + "windows_i686_msvc 0.52.6", + "windows_x86_64_gnu 0.52.6", + "windows_x86_64_gnullvm 0.52.6", + "windows_x86_64_msvc 0.52.6", ] [[package]] @@ -4814,9 +4745,15 @@ checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8" [[package]] name = "windows_aarch64_gnullvm" -version = "0.48.0" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "91ae572e1b79dba883e0d315474df7305d12f569b400fcf90581b06062f7e1bc" +checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" [[package]] name = "windows_aarch64_msvc" @@ -4832,9 +4769,15 @@ checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43" [[package]] name = "windows_aarch64_msvc" -version = "0.48.0" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b2ef27e0d7bdfcfc7b868b317c1d32c641a6fe4629c171b8928c7b08d98d7cf3" +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" [[package]] name = "windows_i686_gnu" @@ -4850,9 +4793,21 @@ checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f" [[package]] name = "windows_i686_gnu" -version = "0.48.0" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" + +[[package]] +name = "windows_i686_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "622a1962a7db830d6fd0a69683c80a18fda201879f0f447f065a3b7467daa241" +checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" [[package]] name = "windows_i686_msvc" @@ -4868,9 +4823,15 @@ checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060" [[package]] name = "windows_i686_msvc" -version = "0.48.0" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" + +[[package]] +name = "windows_i686_msvc" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4542c6e364ce21bf45d69fdd2a8e455fa38d316158cfd43b3ac1c5b1b19f8e00" +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" [[package]] name = "windows_x86_64_gnu" @@ -4886,9 +4847,15 @@ checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36" [[package]] name = "windows_x86_64_gnu" -version = "0.48.0" +version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ca2b8a661f7628cbd23440e50b05d705db3686f894fc9580820623656af974b1" +checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" [[package]] name = "windows_x86_64_gnullvm" @@ -4898,9 +4865,15 @@ checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3" [[package]] name = "windows_x86_64_gnullvm" -version = "0.48.0" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7896dbc1f41e08872e9d5e8f8baa8fdd2677f29468c4e156210174edc7f7b953" +checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" [[package]] name = "windows_x86_64_msvc" @@ -4916,44 +4889,97 @@ checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0" [[package]] name = "windows_x86_64_msvc" -version = "0.48.0" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" + +[[package]] +name = "winnow" +version = "0.6.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a515f5799fe4961cb532f983ce2b23082366b898e52ffbce459c86f67c8378a" +checksum = "36c1fec1a2bb5866f07c25f68c26e565c4c200aebb96d7e55710c19d3e8ac49b" +dependencies = [ + "memchr", +] [[package]] name = "winreg" -version = "0.10.1" +version = "0.50.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "80d0f4e272c85def139476380b12f9ac60926689dd2e01d4923222f40580869d" +checksum = "524e57b2c537c0f9b1e69f1965311ec12182b4122e45035b1508cd24d2adadb1" dependencies = [ - "winapi", + "cfg-if", + "windows-sys 0.48.0", ] [[package]] name = "xattr" -version = "0.2.3" +version = "1.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6d1526bbe5aaeb5eb06885f4d987bcdfa5e23187055de9b83fe00156a821fabc" +checksum = "8da84f1a25939b27f6820d92aed108f83ff920fdf11a7b19366c27c4cda81d4f" dependencies = [ "libc", + "linux-raw-sys", + "rustix", ] +[[package]] +name = "xdg" +version = "2.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "213b7324336b53d2414b2db8537e56544d981803139155afa84f76eeebb7a546" + [[package]] name = "xtask" version = "0.0.0" dependencies = [ "cbindgen", - "clap 4.0.10", + "clap", "color-eyre", "eyre", "fs-err", ] +[[package]] +name = "yaml-rust" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56c1936c4cc7a1c9ab21a1ebb602eb942ba868cbd44a99cb7cdc5892335e1c85" +dependencies = [ + "linked-hash-map", +] + [[package]] name = "yansi" -version = "0.5.1" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfe53a6657fd280eaa890a3bc59152892ffa3e30101319d168b781ed6529b049" + +[[package]] +name = "zerocopy" +version = "0.7.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b9b4fd18abc82b8136838da5d50bae7bdea537c574d8dc1a34ed098d6c166f0" +dependencies = [ + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.7.35" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09041cd90cf85f7f8b2df60c646f853b7f535ce68f85244eb6731cf89fa498ec" +checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.79", +] [[package]] name = "zeroize" @@ -4967,17 +4993,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", ] diff --git a/Cargo.toml b/Cargo.toml index 600b993a6..610affaa8 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -5,90 +5,100 @@ resolver = "2" [workspace.dependencies] android_logger = "0.13.1" 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" +anstyle-query = "1.1.1" +anyhow = "1.0.89" +assert_cmd = "2.0.16" +async-fs = "2.1.2" +async-lock = "3.4.0" 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" +binstall-tar = "0.4.42" +blocking = "1.6.1" +bytes = "1.7.2" +camino = "1.1.9" +cargo_metadata = "0.18.1" +cbindgen = "0.27.0" +chrono = { version = "0.4.38", default-features = false } +clap = "4.5.19" +color-eyre = "0.6.3" +colorchoice = "1.0.2" +comrak = "0.26.0" +const_format = "0.2.33" derive-getters = "0.2.0" derive-new = "0.5.9" +derive-syn-parse = "0.2.0" derive_more = "0.99.17" -duct = "0.13.6" -duplicate = "1.0.0" -easy-ext = "1.0.1" +duct = "0.13.7" +duplicate = "2.0.0" +easy-ext = "1.0.2" educe = "0.4.23" -enum-map = "3.0.0-beta.1" -eyre = "0.6.8" -flate2 = "1.0.25" -fs-err = "2.9.0" -futures = "0.3.26" -futures-core = "0.3.25" -futures-util = "0.3.25" -futures-lite = "2.2.0" +enum-map = "3.0.0-beta.2" +eyre = "0.6.12" +flate2 = "1.0.34" +fs-err = "2.11.0" +futures-core = "0.3.31" +futures-util = "0.3.31" +futures-lite = "2.3.0" +futures-io = "0.3.31" heck = "0.4.1" -humansize = "2.1.2" -indexmap = "2.0.0" -indicatif = "0.17.3" -inventory = "0.3.4" +humansize = "2.1.3" +indexmap = "2.6.0" +indicatif = "0.17.8" +indoc = "2.0.5" +inventory = "0.3.15" itertools = "0.10.5" -jlabel = "0.1.2" +jlabel = "0.1.4" jni = "0.21.1" -libc = "0.2.134" +libc = "0.2.159" libloading = "0.7.3" libtest-mimic = "0.6.0" -log = "0.4.17" -nanoid = "0.4.0" +log = "0.4.22" ndarray = "0.15.6" ndarray-stats = "0.5.1" octocrab = { version = "0.19.0", default-features = false } -once_cell = "1.18.0" -ouroboros = "0.18.0" +once_cell = "1.20.1" +ouroboros = "0.18.4" +parking_lot = "0.12.1" parse-display = "0.8.2" -pretty_assertions = "1.3.0" -proc-macro2 = "1.0.80" +pollster = "0.3.0" +predicates = "3.1.2" +pretty_assertions = "1.4.1" +proc-macro2 = "1.0.86" 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 } +quote = "1.0.37" +rayon = "1.10.0" +ref-cast = "1.0.23" +regex = "1.11.0" +reqwest = { version = "0.11.27", 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" +scraper = "0.19.1" +serde = "1.0.210" +serde_json = "1.0.128" +serde_with = "3.10.0" +smallvec = "1.13.2" strum = "0.24.1" -surf = "2.3.2" -syn = "2.0.59" -tar = "0.4.38" -tempfile = "3.6.0" +syn = "2.0.79" +tar = "0.4.42" +tempfile = "3.13.0" test_util = { path = "crates/test_util" } -thiserror = "1.0.37" -tokio = "1.25.0" +thiserror = "1.0.64" +tokio = "1.40.0" toml = "0.7.2" -tracing = "0.1.37" -tracing-subscriber = "0.3.16" -typetag = "0.2.7" -url = "2.3.0" -uuid = "1.4.0" +tracing = "0.1.40" +tracing-subscriber = "0.3.18" +typetag = "0.2.18" +url = "2.5.2" +uuid = "1.10.0" 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 = "8627833456a69e7841ae2a29fd184752df8de8d9" [workspace.dependencies.open_jtalk] git = "https://github.com/VOICEVOX/open_jtalk-rs.git" @@ -103,6 +113,7 @@ rev = "de226a26e8e18edbdb1d6f986afe37bbbf35fbf4" version = "0.0.0" edition = "2021" publish = false +rust-version = "1.81.0" # min-sized-rustを元にrelease buildのサイズが小さくなるようにした # https://github.com/johnthagen/min-sized-rust diff --git a/README.md b/README.md index 4024e9721..129854da8 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # VOICEVOX CORE -## **現在の main ブランチは工事中なので正しく動かないことがあります。[バージョン 0.14.4](https://github.com/VOICEVOX/voicevox_core/tree/0.14.4)をご利用ください。** +## **現在の main ブランチは工事中なので正しく動かないことがあります。[バージョン 0.15.4](https://github.com/VOICEVOX/voicevox_core/tree/0.15.4)をご利用ください。** [![releases](https://img.shields.io/github/v/release/VOICEVOX/voicevox_core?label=release)](https://github.com/VOICEVOX/voicevox_core/releases) [![test](https://github.com/VOICEVOX/voicevox_core/actions/workflows/test.yml/badge.svg)](https://github.com/VOICEVOX/voicevox_core/actions/workflows/test.yml) @@ -16,7 +16,7 @@ ## API -[API ドキュメント](https://voicevox.github.io/voicevox_core/apis/c_api/globals_func.html)をご覧ください。 +[API ドキュメント](https://voicevox.github.io/voicevox_core/apis/)をご覧ください。 ## ユーザーガイド @@ -144,16 +144,24 @@ VOICEVOX CORE の主要機能は Rust で実装されることを前提として ## コアライブラリのビルド -[Releases](https://github.com/VOICEVOX/voicevox_core/releases) にあるビルド済みのコアライブラリを利用せず、自分で一からビルドする場合こちらを参照してください。ビルドには [Rust](https://www.rust-lang.org/ja) ([Windows での Rust 開発環境構築手順はこちら](https://docs.microsoft.com/ja-jp/windows/dev-environment/rust/setup)) と [cmake](https://cmake.org/download/) が必要です。 - -model フォルダにある onnx モデルはダミーのため、ノイズの混じった音声が出力されます +ビルドには [Rust](https://www.rust-lang.org/ja) ([Windows での Rust 開発環境構築手順はこちら](https://docs.microsoft.com/ja-jp/windows/dev-environment/rust/setup)) と [cmake](https://cmake.org/download/) が必要です。 +[Releases](https://github.com/VOICEVOX/voicevox_core/releases) にあるビルド済みのコアライブラリを利用せず、自分で一からビルドした場合は、model フォルダにある onnx モデルのみが利用できます。 +このモデルはダミーのため、ノイズの混じった音声が出力されます。 ```bash # DLLをビルド -cargo build --release -p voicevox_core_c_api +cargo build --release -p voicevox_core_c_api --features load-onnxruntime ``` -DLL 用のヘッダファイルは [crates/voicevox_core_c_api/include/voicevox_core.h](https://github.com/VOICEVOX/voicevox_core/tree/main/crates/voicevox_core_c_api/include/voicevox_core.h) にあります。 +DLL 用のヘッダファイルの雛形は [crates/voicevox_core_c_api/include/voicevox_core.h](https://github.com/VOICEVOX/voicevox_core/tree/main/crates/voicevox_core_c_api/include/voicevox_core.h) にあります。 +詳しくは[feature-options.md](./docs/feature-options.md)を参照してください。 + +```bash +# ヘッダファイルを加工し、マクロ`VOICEVOX_LOAD_ONNXRUNTIME`を宣言 +sed 's:^//\(#define VOICEVOX_LOAD_ONNXRUNTIME\)$:\1:' \ + crates/voicevox_core_c_api/include/voicevox_core.h \ + > ./voicevox_core.h +``` ## コアライブラリのテスト diff --git a/_typos.toml b/_typos.toml index b70ddc608..4c3b0f48b 100644 --- a/_typos.toml +++ b/_typos.toml @@ -1,6 +1,11 @@ # Files for typos # Instruction: https://github.com/marketplace/actions/typos-action#getting-started +[default] +extend-ignore-identifiers-re = [ + "\\bPNGs\\b", # https://github.com/crate-ci/typos/issues/745 +] + [default.extend-identifiers] NdArray="NdArray" # onnxruntime::session::NdArray diff --git a/crates/downloader/Cargo.toml b/crates/downloader/Cargo.toml index 20d90e087..d86d4f7a8 100644 --- a/crates/downloader/Cargo.toml +++ b/crates/downloader/Cargo.toml @@ -1,6 +1,7 @@ [package] name = "downloader" edition.workspace = true +rust-version.workspace = true [[bin]] name = "download" @@ -11,16 +12,18 @@ anyhow.workspace = true binstall-tar.workspace = true bytes.workspace = true clap = { workspace = true, features = ["derive"] } +comrak.workspace = true flate2.workspace = true fs-err = { workspace = true, features = ["tokio"] } futures-core.workspace = true futures-util.workspace = true indicatif.workspace = true +itertools.workspace = true octocrab = { workspace = true, default-features = false, features = ["rustls-tls", "stream"] } -once_cell.workspace = true parse-display.workspace = true rayon.workspace = true reqwest = { workspace = true, default-features = false, features = ["rustls-tls", "stream"] } +scraper.workspace = true strum = { workspace = true, features = ["derive"] } tokio = { workspace = true, features = ["macros", "rt", "rt-multi-thread", "sync"] } tracing.workspace = true diff --git a/crates/downloader/src/main.rs b/crates/downloader/src/main.rs index 03a426478..6ece6d054 100644 --- a/crates/downloader/src/main.rs +++ b/crates/downloader/src/main.rs @@ -1,21 +1,22 @@ use std::{ borrow::Cow, - collections::HashSet, + collections::{BTreeSet, HashSet}, env, future::Future, io::{self, Cursor, Read}, path::{Path, PathBuf}, - sync::Arc, + sync::{Arc, LazyLock}, time::Duration, }; -use anyhow::{bail, Context as _}; +use anyhow::{anyhow, bail, Context as _}; use bytes::Bytes; use clap::{Parser as _, ValueEnum}; use flate2::read::GzDecoder; use futures_core::Stream; -use futures_util::{future::OptionFuture, StreamExt as _, TryStreamExt as _}; +use futures_util::{stream::FuturesOrdered, StreamExt as _, TryStreamExt as _}; use indicatif::{MultiProgress, ProgressBar, ProgressStyle}; +use itertools::Itertools as _; use octocrab::{ models::{ repos::{Asset, Release}, @@ -23,7 +24,6 @@ use octocrab::{ }, Octocrab, }; -use once_cell::sync::Lazy; use rayon::iter::{IntoParallelIterator as _, ParallelIterator as _}; use strum::{Display, IntoStaticStr}; use tokio::task::{JoinError, JoinSet}; @@ -39,9 +39,10 @@ const DEFAULT_OUTPUT: &str = if cfg!(windows) { const LIB_NAME: &str = "voicevox_core"; const DEFAULT_CORE_REPO: &str = "VOICEVOX/voicevox_core"; +const DEFAULT_ONNXRUNTIME_BUILDER_REPO: &str = "VOICEVOX/onnxruntime-builder"; const DEFAULT_ADDITIONAL_LIBRARIES_REPO: &str = "VOICEVOX/voicevox_additional_libraries"; -static OPEN_JTALK_DIC_URL: Lazy = Lazy::new(|| { +static OPEN_JTALK_DIC_URL: LazyLock = LazyLock::new(|| { "https://jaist.dl.sourceforge.net/project/open-jtalk/Dictionary/open_jtalk_dic-1.11/open_jtalk_dic_utf_8-1.11.tar.gz" .parse() .unwrap() @@ -74,13 +75,17 @@ struct Args { #[arg(short, long, value_name("GIT_TAG_OR_LATEST"), default_value("latest"))] version: String, + /// ダウンロードするONNX Runtimeのバージョンの指定 + #[arg(long, value_name("GIT_TAG_OR_LATEST"), default_value("latest"))] + onnxruntime_version: String, + /// 追加でダウンロードするライブラリのバージョン #[arg(long, value_name("GIT_TAG_OR_LATEST"), default_value("latest"))] additional_libraries_version: String, /// ダウンロードするデバイスを指定する(cudaはlinuxのみ) - #[arg(value_enum, long, default_value(<&str>::from(Device::default())))] - device: Device, + #[arg(value_enum, long, num_args(1..), default_value(<&str>::from(Device::default())))] + devices: Vec, /// ダウンロードするcpuのアーキテクチャを指定する #[arg(value_enum, long, default_value(CpuArch::default_opt().map(<&str>::from)))] @@ -93,6 +98,13 @@ struct Args { #[arg(long, value_name("REPOSITORY"), default_value(DEFAULT_CORE_REPO))] core_repo: RepoName, + #[arg( + long, + value_name("REPOSITORY"), + default_value(DEFAULT_ONNXRUNTIME_BUILDER_REPO) + )] + onnxruntime_builder_repo: RepoName, + #[arg( long, value_name("REPOSITORY"), @@ -105,11 +117,14 @@ struct Args { enum DownloadTarget { Core, Models, + Onnxruntime, AdditionalLibraries, Dict, } -#[derive(Default, ValueEnum, Display, IntoStaticStr, Clone, Copy, PartialEq)] +#[derive( + Default, ValueEnum, Display, IntoStaticStr, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, +)] #[strum(serialize_all = "kebab-case")] enum Device { #[default] @@ -156,7 +171,7 @@ impl Os { } #[derive(parse_display::FromStr, parse_display::Display, Clone)] -#[from_str(regex = "(?[a-zA-Z0-9_]+)/(?[a-zA-Z0-9_]+)")] +#[from_str(regex = "(?[a-zA-Z0-9_-]+)/(?[a-zA-Z0-9_-]+)")] #[display("{owner}/{repo}")] struct RepoName { owner: String, @@ -173,13 +188,16 @@ async fn main() -> anyhow::Result<()> { min, output, version, + onnxruntime_version, additional_libraries_version, - device, + devices, cpu_arch, os, core_repo, + onnxruntime_builder_repo, additional_libraries_repo, } = Args::parse(); + let devices = devices.into_iter().collect::>(); let targets: HashSet<_> = if !only.is_empty() { assert!(exclude.is_empty() && !min); @@ -224,9 +242,9 @@ async fn main() -> anyhow::Result<()> { `additional-libraries-version`はダウンロード対象から除外されています", ); } - if device == Device::Cpu { + if devices == [Device::Cpu].into() { warn!( - "`--device`が指定されていない、もしくは`--device=cpu`が指定されていますが、\ + "`--devices`が指定されていない、もしくは`--devices=cpu`が指定されていますが、\ `additional-libraries-version`はダウンロード対象から除外されています", ); } @@ -234,44 +252,67 @@ async fn main() -> anyhow::Result<()> { let octocrab = &octocrab()?; - let core = find_gh_asset(octocrab, &core_repo, &version, |tag| { - let device = match (os, device) { - (Os::Linux, Device::Cuda) => "gpu", - (_, device) => device.into(), - }; - format!("{LIB_NAME}-{os}-{cpu_arch}-{device}-{tag}.zip") + let core = find_gh_asset(octocrab, &core_repo, &version, |tag, _| { + Ok(format!("{LIB_NAME}-{os}-{cpu_arch}-{tag}.zip")) }) .await?; - let model = find_gh_asset(octocrab, &core_repo, &version, |tag| { - format!("model-{tag}.zip") + let model = find_gh_asset(octocrab, &core_repo, &version, |tag, _| { + Ok(format!("model-{tag}.zip")) }) .await?; - let additional_libraries = OptionFuture::from((device != Device::Cpu).then(|| { - find_gh_asset( - octocrab, - &additional_libraries_repo, - &additional_libraries_version, - |_| { - let device = match device { - Device::Cpu => unreachable!(), - Device::Cuda => "CUDA", - Device::Directml => "DirectML", - }; - format!("{device}-{os}-{cpu_arch}.zip") - }, - ) - })) - .await - .transpose()?; + let onnxruntime = find_gh_asset( + octocrab, + &onnxruntime_builder_repo, + &onnxruntime_version, + |_, body| { + let body = body.with_context(|| "リリースノートがありません")?; + find_onnxruntime(body, os, cpu_arch, &devices) + }, + ) + .await?; + + let additional_libraries = devices + .iter() + .filter(|&&device| device != Device::Cpu) + .map(|&device| { + find_gh_asset( + octocrab, + &additional_libraries_repo, + &additional_libraries_version, + move |_, _| { + Ok({ + let device = match device { + Device::Cpu => unreachable!(), + Device::Cuda => "CUDA", + Device::Directml => "DirectML", + }; + format!("{device}-{os}-{cpu_arch}.zip") + }) + }, + ) + }) + .collect::>() + .try_collect::>() + .await?; info!("対象OS: {os}"); info!("対象CPUアーキテクチャ: {cpu_arch}"); - info!("ダウンロードデバイスタイプ: {device}"); + info!( + "ダウンロードデバイスタイプ: {}", + devices.iter().format(", "), + ); info!("ダウンロード{LIB_NAME}バージョン: {}", core.tag); - if let Some(GhAsset { tag, .. }) = &additional_libraries { - info!("ダウンロード追加ライブラリバージョン: {tag}"); + info!("ダウンロードONNX Runtimeバージョン: {}", onnxruntime.tag); + if !additional_libraries.is_empty() { + info!( + "ダウンロード追加ライブラリバージョン: {}", + additional_libraries + .iter() + .map(|GhAsset { tag, .. }| tag) + .format(", "), + ); } let progresses = MultiProgress::new(); @@ -294,8 +335,16 @@ async fn main() -> anyhow::Result<()> { &progresses, )?); } + if targets.contains(&DownloadTarget::Onnxruntime) { + tasks.spawn(download_and_extract_from_gh( + onnxruntime, + Stripping::FirstDir, + &output.join("onnxruntime"), + &progresses, + )?); + } if targets.contains(&DownloadTarget::AdditionalLibraries) { - if let Some(additional_libraries) = additional_libraries { + for additional_libraries in additional_libraries { tasks.spawn(download_and_extract_from_gh( additional_libraries, Stripping::FirstDir, @@ -348,11 +397,15 @@ async fn find_gh_asset( octocrab: &Arc, repo: &RepoName, git_tag_or_latest: &str, - asset_name: impl FnOnce(&str) -> String, + asset_name: impl FnOnce( + &str, // タグ名 + Option<&str>, // リリースノートの内容 + ) -> anyhow::Result, ) -> anyhow::Result { let Release { html_url, tag_name, + body, assets, .. } = { @@ -364,7 +417,11 @@ async fn find_gh_asset( }? }; - let asset_name = asset_name(&tag_name); + let asset_name = asset_name(&tag_name, body.as_deref()).with_context(|| { + format!( + "`{repo}`の`{tag_name}`の中から条件に合致するビルドが見つけることができませんでした", + ) + })?; let Asset { id, name, size, .. } = assets .into_iter() .find(|Asset { name, .. }| *name == asset_name) @@ -380,6 +437,82 @@ async fn find_gh_asset( }) } +/// `find_gh_asset`に用いる。 +/// +/// 候補が複数あった場合、「デバイス」の数が最も小さいもののうち最初のものを選ぶ。 +fn find_onnxruntime( + body: &str, // リリースの"body" (i.e. リリースノートの内容) + os: Os, + cpu_arch: CpuArch, + devices: &BTreeSet, +) -> anyhow::Result { + macro_rules! selector { + ($expr:expr $(,)?) => {{ + static SELECTOR: LazyLock = + LazyLock::new(|| scraper::Selector::parse($expr).expect("should be valid")); + &SELECTOR + }}; + } + + const TARGET: &str = "table\ + [data-voicevox-onnxruntime-specs-format-version=\"1\"]\ + [data-voicevox-onnxruntime-specs-type=\"dylibs\"]"; + + comrak::parse_document(&Default::default(), body, &Default::default()) + .descendants() + .flat_map(|node| match &node.data.borrow().value { + comrak::nodes::NodeValue::HtmlBlock(comrak::nodes::NodeHtmlBlock { + literal, .. + }) => Some(scraper::Html::parse_fragment(literal)), + _ => None, + }) + .collect::>() + .iter() + .flat_map(|html_block| html_block.select(selector!(TARGET))) + .exactly_one() + .map_err(|err| match err.count() { + 0 => anyhow!("リリースノートの中に`{TARGET}`が見つかりませんでした"), + _ => anyhow!("リリースノートの中に`{TARGET}`が複数ありました"), + })? + .select(selector!("tbody > tr")) + .map(|tr| { + tr.select(selector!("td")) + .map(|td| td.text().exactly_one().ok()) + .collect::>>() + .and_then(|text| text.try_into().ok()) + .with_context(|| format!("リリースノート中の`{TARGET}`をパースできませんでした")) + }) + .collect::, _>>()? + .into_iter() + .filter(|&[spec_os, spec_cpu_arch, spec_devices, _]| { + spec_os + == match os { + Os::Windows => "Windows", + Os::Linux => "Linux", + Os::Osx => "macOS", + } + && spec_cpu_arch + == match cpu_arch { + CpuArch::X86 => "x86", + CpuArch::X64 => "x86_64", + CpuArch::Arm64 => "AArch64", + } + && devices.iter().all(|device| { + spec_devices.split('/').any(|spec_device| { + spec_device + == match device { + Device::Cpu => "CPU", + Device::Cuda => "CUDA", + Device::Directml => "DirectML", + } + }) + }) + }) + .min_by_key(|&[.., spec_devices, _]| spec_devices.split('/').count()) + .map(|[.., name]| name.to_owned()) + .with_context(|| "指定されたOS, アーキテクチャ, デバイスを含むものが見つかりませんでした") +} + fn download_and_extract_from_gh( GhAsset { octocrab, @@ -461,8 +594,8 @@ fn add_progress_bar( const INTERVAL: Duration = Duration::from_millis(100); - static PROGRESS_STYLE: Lazy = - Lazy::new(|| ProgressStyle::with_template("{prefix}").unwrap()); + static PROGRESS_STYLE: LazyLock = + LazyLock::new(|| ProgressStyle::with_template("{prefix}").unwrap()); } async fn download_and_extract( @@ -480,15 +613,15 @@ async fn download_and_extract( let files = &read_archive(archive, archive_kind, pb.clone()).await?; return extract(files, stripping, output, pb).await; - static PROGRESS_STYLE1: Lazy = Lazy::new(|| { + static PROGRESS_STYLE1: LazyLock = LazyLock::new(|| { ProgressStyle::with_template( "{prefix:55} {bytes:>11} {bytes_per_sec:>13} {elapsed_precise} {bar} {percent:>3}%", ) .unwrap() }); - static PROGRESS_STYLE2: Lazy = - Lazy::new(|| ProgressStyle::with_template("{prefix:55} {spinner} {msg}").unwrap()); + static PROGRESS_STYLE2: LazyLock = + LazyLock::new(|| ProgressStyle::with_template("{prefix:55} {spinner} {msg}").unwrap()); async fn with_style( pb: ProgressBar, diff --git a/crates/test_util/Cargo.toml b/crates/test_util/Cargo.toml index dc873b7e8..5a6bbd7a4 100644 --- a/crates/test_util/Cargo.toml +++ b/crates/test_util/Cargo.toml @@ -1,27 +1,28 @@ [package] name = "test_util" edition.workspace = true +rust-version.workspace = true [dependencies] -async_zip = { workspace = true, features = ["deflate"] } -futures-lite.workspace = true 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 +cargo_metadata.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 +voicevox-ort.workspace = true +zip.workspace = true [lints.rust] unsafe_code = "allow" # C APIのbindgen diff --git a/crates/test_util/build.rs b/crates/test_util/build.rs index 9a2e0f1e5..3cdc88d78 100644 --- a/crates/test_util/build.rs +++ b/crates/test_util/build.rs @@ -1,36 +1,127 @@ use std::{ env, - path::{Path, PathBuf}, + io::{self, Cursor, Write as _}, + path::Path, }; -use anyhow::ensure; -use async_std::io::ReadExt as _; -use camino::Utf8PathBuf; +use anyhow::{anyhow, ensure}; +use camino::{Utf8Path, Utf8PathBuf}; +use cargo_metadata::MetadataCommand; 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)?; + copy_onnxruntime(out_dir.as_ref(), 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() + 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(()) +} + +fn copy_onnxruntime(out_dir: &Path, dist: &Utf8Path) -> anyhow::Result<()> { + use std::env::consts::{DLL_PREFIX, DLL_SUFFIX}; + + let cargo_metadata::Metadata { + target_directory, .. + } = MetadataCommand::new() + .manifest_path(Path::new(env!("CARGO_MANIFEST_DIR")).join("Cargo.toml")) + .exec()?; + + const VERSION: &str = ort::downloaded_version!(); + let filename = &if cfg!(target_os = "linux") { + format!("libonnxruntime.so.{VERSION}") + } else if cfg!(any(target_os = "macos", target_os = "ios")) { + format!("libonnxruntime.{VERSION}.dylib") + } else { + format!("{DLL_PREFIX}onnxruntime{DLL_SUFFIX}") + }; + let src = &target_directory.join("debug").join(filename); + let dst_dir = &dist.join("lib"); + let dst = &dst_dir.join(filename); + fs_err::create_dir_all(dst_dir)?; + fs_err::copy(src, dst)?; + println!("cargo:rerun-if-changed={src}"); + + fs_err::write(out_dir.join("onnxruntime-dylib-path.txt"), dst.as_str())?; + + Ok(()) } /// OpenJTalkの辞書をダウンロードして展開する。 @@ -39,13 +130,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)?; @@ -123,14 +212,15 @@ fn generate_example_data_json(dist: &Path) -> anyhow::Result<()> { Ok(()) } -fn generate_c_api_rs_bindings() -> anyhow::Result<()> { +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"; - let out_dir = Utf8PathBuf::from(env::var("OUT_DIR").unwrap()); bindgen::Builder::default() .header(C_BINDINGS_PATH) .header(ADDITIONAL_C_BINDINGS_PATH) + // we test for `--feature load-onnxruntime` + .clang_arg("-DVOICEVOX_LOAD_ONNXRUNTIME=") .parse_callbacks(Box::new(bindgen::CargoCallbacks::new())) .dynamic_library_name("CApi") .generate()? diff --git a/crates/test_util/src/lib.rs b/crates/test_util/src/lib.rs index c80f3d575..92e8e941a 100644 --- a/crates/test_util/src/lib.rs +++ b/crates/test_util/src/lib.rs @@ -1,34 +1,33 @@ mod typing; +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 + clippy::too_many_arguments, + reason = "bindgenが生成するコードのため。`#[expect]`ではなく`#[allow]`なのは、bindgenが生成\ + するコードがOSにより変わるため" )] 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 async_zip::{base::write::ZipFileWriter, Compression, ZipEntryBuilder}; -use futures_lite::AsyncWriteExt as _; -use once_cell::sync::Lazy; -use std::{ - collections::HashMap, - path::{Path, PathBuf}, -}; -use tokio::{ - fs::{self, File}, - io::AsyncReadExt, - sync::Mutex, -}; +use std::sync::LazyLock; pub use self::typing::{ DecodeExampleData, DurationExampleData, ExampleData, IntonationExampleData, }; +pub const ONNXRUNTIME_DYLIB_PATH: &str = + include_str!(concat!(env!("OUT_DIR"), "/onnxruntime-dylib-path.txt")); + pub const OPEN_JTALK_DIC_DIR: &str = concat!( env!("CARGO_MANIFEST_DIR"), "/data/open_jtalk_dic_utf_8-1.11" @@ -39,46 +38,6 @@ const EXAMPLE_DATA_JSON: &str = include_str!(concat!( "/data/example_data.json" )); -pub static EXAMPLE_DATA: Lazy = Lazy::new(|| { +pub static EXAMPLE_DATA: LazyLock = LazyLock::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 writer = ZipFileWriter::new(vec![]); - - for entry in dir.read_dir().unwrap().flatten() { - let entry_builder = ZipEntryBuilder::new( - entry.path().file_name().unwrap().to_str().unwrap().into(), - 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(); - } - let zip = writer.close().await.unwrap(); - fs::write(&out_file_path, zip).await.unwrap(); - } - out_file_path -} diff --git a/crates/voicevox_core/Cargo.toml b/crates/voicevox_core/Cargo.toml index 5c8dd440d..8052b312a 100644 --- a/crates/voicevox_core/Cargo.toml +++ b/crates/voicevox_core/Cargo.toml @@ -3,15 +3,26 @@ name = "voicevox_core" version.workspace = true edition.workspace = true publish.workspace = true +rust-version.workspace = true +[package.metadata.docs.rs] +features = ["load-onnxruntime", "link-onnxruntime"] +rustdoc-args = ["--cfg", "docsrs"] + +# rustdocを参照 [features] default = [] -directml = ["onnxruntime/directml"] +load-onnxruntime = ["voicevox-ort/load-dynamic"] +link-onnxruntime = [] [dependencies] anyhow.workspace = true +async-fs.workspace = true +async-lock.workspace = true async_zip = { workspace = true, features = ["deflate"] } +blocking.workspace = true camino.workspace = true +const_format.workspace = true derive-getters.workspace = true derive-new.workspace = true derive_more.workspace = true @@ -19,18 +30,17 @@ duplicate.workspace = true easy-ext.workspace = true educe.workspace = true enum-map.workspace = true -fs-err = { workspace = true, features = ["tokio"] } -futures.workspace = true +fs-err.workspace = true +futures-io.workspace = true +futures-lite.workspace = true +futures-util = { workspace = true, features = ["io"] } 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 +ref-cast.workspace = true regex.workspace = true serde = { workspace = true, features = ["derive", "rc"] } serde_json = { workspace = true, features = ["preserve_order"] } @@ -39,27 +49,24 @@ 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-ort = { workspace = true, features = ["download-binaries", "__init-for-voicevox"] } voicevox_core_macros = { path = "../voicevox_core_macros" } -zip.workspace = true [dev-dependencies] heck.workspace = true +pollster = { workspace = true, features = ["macro"] } pretty_assertions.workspace = true rstest.workspace = true rstest_reuse.workspace = true test_util.workspace = true -tokio = { workspace = true, features = ["rt", "macros"] } +tokio = { workspace = true, features = ["rt-multi-thread", "macros"] } [target."cfg(windows)".dependencies] humansize.workspace = true windows = { workspace = true, features = ["Win32_Foundation", "Win32_Graphics_Dxgi"] } [lints.rust] -# 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 426f6cd09..9f4f91d35 100644 --- a/crates/voicevox_core/src/__internal/doctest_fixtures.rs +++ b/crates/voicevox_core/src/__internal/doctest_fixtures.rs @@ -1,23 +1,34 @@ +use std::{ffi::OsString, path::Path}; + use camino::Utf8Path; use crate::{AccelerationMode, InitializeOptions}; +pub use crate::synthesizer::nonblocking::IntoBlocking; + pub async fn synthesizer_with_sample_voice_model( + voice_model_path: impl AsRef, + #[cfg_attr(feature = "link-onnxruntime", allow(unused_variables))] onnxruntime_dylib_path: impl Into< + OsString, + >, open_jtalk_dic_dir: impl AsRef, -) -> anyhow::Result> { - let syntesizer = crate::tokio::Synthesizer::new( - crate::tokio::OpenJtalk::new(open_jtalk_dic_dir).await?, +) -> anyhow::Result> { + let syntesizer = crate::nonblocking::Synthesizer::new( + #[cfg(feature = "load-onnxruntime")] + crate::nonblocking::Onnxruntime::load_once() + .filename(onnxruntime_dylib_path) + .exec() + .await?, + #[cfg(feature = "link-onnxruntime")] + crate::nonblocking::Onnxruntime::init_once().await?, + crate::nonblocking::OpenJtalk::new(open_jtalk_dic_dir).await?, &InitializeOptions { acceleration_mode: AccelerationMode::Cpu, ..Default::default() }, )?; - let model = &crate::tokio::VoiceModel::from_path(concat!( - env!("CARGO_MANIFEST_DIR"), - "/../../model/sample.vvm", - )) - .await?; + let model = &crate::nonblocking::VoiceModelFile::open(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 fe46d10bc..61ea5f1ae 100644 --- a/crates/voicevox_core/src/__internal/interop.rs +++ b/crates/voicevox_core/src/__internal/interop.rs @@ -1 +1,3 @@ +pub mod raii; + pub use crate::{metas::merge as merge_metas, synthesizer::blocking::PerformInference}; diff --git a/crates/voicevox_core/src/__internal/interop/raii.rs b/crates/voicevox_core/src/__internal/interop/raii.rs new file mode 100644 index 000000000..220188ad2 --- /dev/null +++ b/crates/voicevox_core/src/__internal/interop/raii.rs @@ -0,0 +1,43 @@ +use std::{marker::PhantomData, ops::Deref}; + +use ouroboros::self_referencing; + +pub enum MaybeClosed { + Open(T), + Closed, +} + +// [`mapped_lock_guards`]のようなことをやるためのユーティリティ。 +// +// [`mapped_lock_guards`]: https://github.com/rust-lang/rust/issues/117108 +pub fn try_map_guard<'lock, G, F, T, E>(guard: G, f: F) -> Result + 'lock, E> +where + G: 'lock, + F: FnOnce(&G) -> Result<&T, E>, + T: 'lock, +{ + return MappedLockTryBuilder { + guard, + target_builder: f, + marker: PhantomData, + } + .try_build(); + + #[self_referencing] + struct MappedLock<'lock, G: 'lock, T> { + guard: G, + + #[borrows(guard)] + target: &'this T, + + marker: PhantomData<&'lock T>, + } + + impl<'lock, G: 'lock, T: 'lock> Deref for MappedLock<'lock, G, T> { + type Target = T; + + fn deref(&self) -> &Self::Target { + self.borrow_target() + } + } +} diff --git a/crates/voicevox_core/src/asyncs.rs b/crates/voicevox_core/src/asyncs.rs new file mode 100644 index 000000000..24fdd82b4 --- /dev/null +++ b/crates/voicevox_core/src/asyncs.rs @@ -0,0 +1,200 @@ +//! 非同期操作の実装の切り替えを行う。 +//! +//! 「[ブロッキング版API]」と「[非同期版API]」との違いはここに集約される +//! …予定。現在は[`crate::voice_model`]のみで利用している。 +//! +//! # Motivation +//! +//! [blocking]クレートで駆動する非同期処理はランタイムが無くても動作する。そのため非同期版APIを +//! もとにブロッキング版APIを構成することはできる。しかし将来WASMビルドすることを考えると、スレッド +//! がまともに扱えないため機能しなくなってしまう。そのためWASM化を見越したブロッキング版APIのため +//! に[`SingleTasked`]を用意している。 +//! +//! [ブロッキング版API]: crate::blocking +//! [非同期版API]: crate::nonblocking + +use std::{ + io::{self, Read as _, Seek as _, SeekFrom}, + ops::DerefMut, + path::Path, + pin::Pin, + task::{self, Poll}, +}; + +use blocking::Unblock; +use futures_io::{AsyncRead, AsyncSeek}; +use futures_util::ready; + +pub(crate) trait Async: 'static { + type Mutex: Mutex; + type RoFile: AsyncRead + AsyncSeek + Send + Sync + Unpin; + + /// ファイルを読み取り専用(RO)で開く。 + /// + /// `io::Error`は素(`i32`相当)のままにしておき、この関数を呼び出す側でfs-err風のメッセージを付 + /// ける。 + async fn open_file_ro(path: impl AsRef) -> io::Result; + + async fn read(path: impl AsRef) -> io::Result>; + + async fn write(path: impl AsRef, content: impl AsRef<[u8]>) -> io::Result<()>; +} + +pub(crate) trait Mutex: From + Send + Sync + Unpin { + async fn lock(&self) -> impl DerefMut; +} + +/// エグゼキュータが非同期タスクの並行実行をしないことを仮定する、[`Async`]の実装。 +/// +/// [ブロッキング版API]用。 +/// +/// # Performance +/// +/// `async`の中でブロッキング操作を直接行う。そのためTokioやasync-stdのような通常の非同期ランタイム +/// 上で動くべきではない。 +/// +/// [ブロッキング版API]: crate::blocking +pub(crate) enum SingleTasked {} + +impl Async for SingleTasked { + type Mutex = StdMutex; + type RoFile = StdFile; + + async fn open_file_ro(path: impl AsRef) -> io::Result { + std::fs::File::open(path).map(StdFile) + } + + async fn read(path: impl AsRef) -> io::Result> { + std::fs::read(path) + } + + async fn write(path: impl AsRef, content: impl AsRef<[u8]>) -> io::Result<()> { + std::fs::write(path, content) + } +} + +pub(crate) struct StdMutex(std::sync::Mutex); + +impl From for StdMutex { + fn from(inner: T) -> Self { + Self(inner.into()) + } +} + +impl Mutex for StdMutex { + async fn lock(&self) -> impl DerefMut { + self.0.lock().unwrap_or_else(|e| panic!("{e}")) + } +} + +pub(crate) struct StdFile(std::fs::File); + +impl AsyncRead for StdFile { + fn poll_read( + mut self: Pin<&mut Self>, + _: &mut task::Context<'_>, + buf: &mut [u8], + ) -> Poll> { + Poll::Ready(self.0.read(buf)) + } +} + +impl AsyncSeek for StdFile { + fn poll_seek( + mut self: Pin<&mut Self>, + _: &mut task::Context<'_>, + pos: SeekFrom, + ) -> Poll> { + Poll::Ready(self.0.seek(pos)) + } +} + +/// [blocking]クレートで駆動する[`Async`]の実装。 +/// +/// [非同期版API]用。 +/// +/// [非同期版API]: crate::nonblocking +pub(crate) enum BlockingThreadPool {} + +impl Async for BlockingThreadPool { + type Mutex = async_lock::Mutex; + type RoFile = AsyncRoFile; + + async fn open_file_ro(path: impl AsRef) -> io::Result { + AsyncRoFile::open(path).await + } + + async fn read(path: impl AsRef) -> io::Result> { + async_fs::read(path).await + } + + async fn write(path: impl AsRef, content: impl AsRef<[u8]>) -> io::Result<()> { + async_fs::write(path, content).await + } +} + +impl Mutex for async_lock::Mutex { + async fn lock(&self) -> impl DerefMut { + self.lock().await + } +} + +// TODO: `async_fs::File::into_std_file`みたいなのがあればこんなの↓は作らなくていいはず。PR出す? +pub(crate) struct AsyncRoFile { + // `poll_read`と`poll_seek`しかしない + unblock: Unblock, + + // async-fsの実装がやっているように「正しい」シーク位置を保持する。ただしファイルはパイプではな + // いことがわかっているため smol-rs/async-fs#4 は考えない + real_seek_pos: Option, +} + +impl AsyncRoFile { + async fn open(path: impl AsRef) -> io::Result { + let path = path.as_ref().to_owned(); + let unblock = Unblock::new(blocking::unblock(|| std::fs::File::open(path)).await?); + Ok(Self { + unblock, + real_seek_pos: None, + }) + } + + pub(crate) async fn close(self) { + let file = self.unblock.into_inner().await; + blocking::unblock(|| drop(file)).await; + } +} + +impl AsyncRead for AsyncRoFile { + fn poll_read( + mut self: Pin<&mut Self>, + cx: &mut task::Context<'_>, + buf: &mut [u8], + ) -> Poll> { + if self.real_seek_pos.is_none() { + self.real_seek_pos = Some(ready!( + Pin::new(&mut self.unblock).poll_seek(cx, SeekFrom::Current(0)) + )?); + } + let n = ready!(Pin::new(&mut self.unblock).poll_read(cx, buf))?; + *self.real_seek_pos.as_mut().expect("should be present") += n as u64; + Poll::Ready(Ok(n)) + } +} + +impl AsyncSeek for AsyncRoFile { + fn poll_seek( + mut self: Pin<&mut Self>, + cx: &mut task::Context<'_>, + pos: SeekFrom, + ) -> Poll> { + // async-fsの実装がやっているような"reposition"を行う。 + // https://github.com/smol-rs/async-fs/issues/2#issuecomment-675595170 + if let Some(real_seek_pos) = self.real_seek_pos { + ready!(Pin::new(&mut self.unblock).poll_seek(cx, SeekFrom::Start(real_seek_pos)))?; + } + self.real_seek_pos = None; + + Pin::new(&mut self.unblock).poll_seek(cx, pos) + } +} diff --git a/crates/voicevox_core/src/blocking.rs b/crates/voicevox_core/src/blocking.rs index aa600c598..3443e3085 100644 --- a/crates/voicevox_core/src/blocking.rs +++ b/crates/voicevox_core/src/blocking.rs @@ -1,6 +1,13 @@ //! ブロッキング版API。 pub use crate::{ - engine::open_jtalk::blocking::OpenJtalk, synthesizer::blocking::Synthesizer, - user_dict::dict::blocking::UserDict, voice_model::blocking::VoiceModel, + engine::open_jtalk::blocking::OpenJtalk, infer::runtimes::onnxruntime::blocking::Onnxruntime, + synthesizer::blocking::Synthesizer, user_dict::dict::blocking::UserDict, + voice_model::blocking::VoiceModelFile, }; + +pub mod onnxruntime { + #[cfg(feature = "load-onnxruntime")] + #[cfg_attr(docsrs, doc(cfg(feature = "load-onnxruntime")))] + pub use crate::infer::runtimes::onnxruntime::blocking::LoadOnce; +} diff --git a/crates/voicevox_core/src/devices.rs b/crates/voicevox_core/src/devices.rs index 54b3de2f7..ebffcb360 100644 --- a/crates/voicevox_core/src/devices.rs +++ b/crates/voicevox_core/src/devices.rs @@ -1,13 +1,76 @@ -use derive_getters::Getters; +use std::{ + collections::BTreeMap, + fmt::{self, Display}, + ops::Index, +}; + +use derive_more::BitAnd; use serde::{Deserialize, Serialize}; -use crate::{infer::InferenceRuntime, synthesizer::InferenceRuntimeImpl, Result}; +pub(crate) fn test_gpus( + gpus: impl IntoIterator, + inference_rt_name: &'static str, + devices_supported_by_inference_rt: SupportedDevices, + test: impl Fn(GpuSpec) -> anyhow::Result<()>, +) -> DeviceAvailabilities { + DeviceAvailabilities( + gpus.into_iter() + .map(|gpu| { + let availability = test_gpu( + gpu, + inference_rt_name, + devices_supported_by_inference_rt, + &test, + ); + (gpu, availability) + }) + .collect(), + ) +} + +fn test_gpu( + gpu: GpuSpec, + inference_rt_name: &'static str, + devices_supported_by_inference_rt: SupportedDevices, + test: impl Fn(GpuSpec) -> anyhow::Result<()>, +) -> DeviceAvailability { + if !SupportedDevices::THIS[gpu] { + DeviceAvailability::NotSupportedByThisLib + } else if !devices_supported_by_inference_rt[gpu] { + DeviceAvailability::NotSupportedByCurrentLoadedInferenceRuntime(inference_rt_name) + } else { + match test(gpu) { + Ok(()) => DeviceAvailability::Ok, + Err(err) => DeviceAvailability::Err(err), + } + } +} -/// このライブラリで利用可能なデバイスの情報。 +/// 利用可能なデバイスの情報。 /// -/// あくまで本ライブラリが対応しているデバイスの情報であることに注意。GPUが使える環境ではなかったと +/// あくまで本ライブラリもしくはONNX Runtimeが対応しているデバイスの情報であることに注意。GPUが使える環境ではなかったと /// しても`cuda`や`dml`は`true`を示しうる。 -#[derive(Getters, Debug, Serialize, Deserialize)] +/// +/// ``` +/// # #[pollster::main] +/// # async fn main() -> anyhow::Result<()> { +/// use voicevox_core::{nonblocking::Onnxruntime, SupportedDevices}; +/// +/// # voicevox_core::blocking::Onnxruntime::load_once() +/// # .filename(if cfg!(windows) { +/// # // Windows\System32\onnxruntime.dllを回避 +/// # test_util::ONNXRUNTIME_DYLIB_PATH +/// # } else { +/// # voicevox_core::blocking::Onnxruntime::LIB_VERSIONED_FILENAME +/// # }) +/// # .exec()?; +/// # +/// let onnxruntime = Onnxruntime::get().unwrap(); +/// dbg!(SupportedDevices::THIS & onnxruntime.supported_devices()?); +/// # Ok(()) +/// # } +/// ``` +#[derive(Clone, Copy, PartialEq, Eq, Debug, BitAnd, Serialize, Deserialize)] pub struct SupportedDevices { /// CPUが利用可能。 /// @@ -30,37 +93,149 @@ pub struct SupportedDevices { } impl SupportedDevices { - /// `SupportedDevices`をコンストラクトする。 + /// このライブラリで利用可能なデバイスの情報。 /// - /// # Example + /// `load-onnxruntime`のフィーチャが有効化されているときはすべて`true`となる。 + /// + #[cfg_attr(feature = "load-onnxruntime", doc = "```")] + #[cfg_attr(not(feature = "load-onnxruntime"), doc = "```no_run")] + /// # use voicevox_core::SupportedDevices; + /// assert!(SupportedDevices::THIS.cuda); + /// assert!(SupportedDevices::THIS.dml); + /// ``` /// - #[cfg_attr(windows, doc = "```no_run")] // https://github.com/VOICEVOX/voicevox_core/issues/537 - #[cfg_attr(not(windows), doc = "```")] - /// use voicevox_core::SupportedDevices; + /// `link-onnxruntime`のフィーチャが有効化されているときは`cpu`を除き`false`となる。 /// - /// let supported_devices = SupportedDevices::create()?; - /// # - /// # Result::<_, anyhow::Error>::Ok(()) + #[cfg_attr(feature = "link-onnxruntime", doc = "```")] + #[cfg_attr(not(feature = "link-onnxruntime"), doc = "```no_run")] + /// # use voicevox_core::SupportedDevices; + /// assert!(!SupportedDevices::THIS.cuda); + /// assert!(!SupportedDevices::THIS.dml); /// ``` - pub fn create() -> Result { - ::supported_devices() - } + pub const THIS: Self = if cfg!(feature = "load-onnxruntime") { + Self { + cpu: true, + cuda: true, + dml: true, + } + } else if cfg!(feature = "link-onnxruntime") { + Self { + cpu: true, + cuda: false, + dml: false, + } + } else { + panic!("either `load-onnxruntime` or `link-onnxruntime` must be enabled"); + }; - pub fn to_json(&self) -> serde_json::Value { + pub fn to_json(self) -> serde_json::Value { serde_json::to_value(self).expect("should not fail") } } +#[derive(Debug)] +pub(crate) struct DeviceAvailabilities(BTreeMap); + +impl DeviceAvailabilities { + pub(crate) fn oks(&self) -> Vec { + self.0 + .iter() + .filter(|(_, result)| matches!(result, DeviceAvailability::Ok)) + .map(|(&gpu, _)| gpu) + .collect() + } +} + +impl Display for DeviceAvailabilities { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + for (gpu, availability) in &self.0 { + match availability { + DeviceAvailability::Ok => writeln!(f, "* {gpu}: OK"), + DeviceAvailability::Err(err) => { + writeln!(f, "* {gpu}: {err}", err = err.to_string().trim_end()) + } + DeviceAvailability::NotSupportedByThisLib => { + writeln!( + f, + "* {gpu}: この`{name}`のビルドでは利用できません", + name = env!("CARGO_PKG_NAME"), + ) + } + DeviceAvailability::NotSupportedByCurrentLoadedInferenceRuntime(name) => { + writeln!(f, "* {gpu}: {name}では利用できません") + } + }?; + } + Ok(()) + } +} + +#[derive(Debug)] +enum DeviceAvailability { + Ok, + Err(anyhow::Error), + NotSupportedByThisLib, + NotSupportedByCurrentLoadedInferenceRuntime(&'static str), +} + +#[derive(Clone, Copy, PartialEq, Debug, derive_more::Display)] +pub(crate) enum DeviceSpec { + #[display(fmt = "CPU")] + Cpu, + + #[display(fmt = "{_0}")] + Gpu(GpuSpec), +} + +#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Debug, derive_more::Display)] +pub(crate) enum GpuSpec { + #[display(fmt = "CUDA (device_id=0)")] + Cuda, + + #[display(fmt = "DirectML (device_id=0)")] + Dml, +} + +impl GpuSpec { + pub(crate) fn defaults() -> Vec { + vec![Self::Cuda, Self::Dml] + } +} + +impl Index for SupportedDevices { + type Output = bool; + + fn index(&self, gpu: GpuSpec) -> &Self::Output { + match gpu { + GpuSpec::Cuda => &self.cuda, + GpuSpec::Dml => &self.dml, + } + } +} + #[cfg(test)] mod tests { - use rstest::rstest; + use pretty_assertions::assert_eq; + + use super::{GpuSpec, SupportedDevices}; - use super::SupportedDevices; + #[test] + fn gpu_spec_defaults_is_exhaustive() { + static SUPPORTED_DEVICES: SupportedDevices = SupportedDevices::THIS; // whatever - #[rstest] - fn supported_devices_create_works() { - let result = SupportedDevices::create(); - // 環境によって結果が変わるので、関数呼び出しが成功するかどうかの確認のみ行う - assert!(result.is_ok(), "{result:?}"); + assert_eq!( + { + #[forbid( + unused_variables, + reason = "比較対象としてここは網羅されてなければなりません" + )] + let SupportedDevices { cpu: _, cuda, dml } = &SUPPORTED_DEVICES; + [cuda as *const _, dml as *const _] + }, + *GpuSpec::defaults() + .into_iter() + .map(|gpu| &SUPPORTED_DEVICES[gpu] as *const _) + .collect::>(), + ); } } diff --git a/crates/voicevox_core/src/engine/acoustic_feature_extractor.rs b/crates/voicevox_core/src/engine/acoustic_feature_extractor.rs index 5ee7ea540..02b66f903 100644 --- a/crates/voicevox_core/src/engine/acoustic_feature_extractor.rs +++ b/crates/voicevox_core/src/engine/acoustic_feature_extractor.rs @@ -1,7 +1,7 @@ +use std::{collections::HashMap, sync::LazyLock}; + use derive_getters::Getters; use derive_new::new; -use once_cell::sync::Lazy; -use std::collections::HashMap; #[rustfmt::skip] const PHONEME_LIST: &[&str] = &[ @@ -52,7 +52,7 @@ const PHONEME_LIST: &[&str] = &[ "z", ]; -static PHONEME_MAP: Lazy> = Lazy::new(|| { +static PHONEME_MAP: LazyLock> = LazyLock::new(|| { let mut m = HashMap::new(); for (i, s) in PHONEME_LIST.iter().enumerate() { m.insert(*s, i as i64); @@ -63,15 +63,11 @@ static PHONEME_MAP: Lazy> = Lazy::new(|| { #[derive(Debug, Clone, PartialEq, new, Default, Getters)] pub(crate) struct OjtPhoneme { phoneme: String, - #[allow(dead_code)] - start: f32, - #[allow(dead_code)] - end: f32, } impl OjtPhoneme { - pub(crate) fn num_phoneme() -> usize { - PHONEME_MAP.len() + pub(crate) const fn num_phoneme() -> usize { + PHONEME_LIST.len() // == PHONEME_MAP.len() } fn space_phoneme() -> String { @@ -114,8 +110,8 @@ mod tests { fn base_hello_hiho() -> Vec { STR_HELLO_HIHO .split_whitespace() - .enumerate() - .map(|(i, s)| OjtPhoneme::new(s.into(), i as f32, (i + 1) as f32)) + .map(ToOwned::to_owned) + .map(OjtPhoneme::new) .collect() } @@ -155,9 +151,8 @@ mod tests { } #[rstest] - #[case(ojt_hello_hiho(), 9, OjtPhoneme::new("a".into(), 9., 10.), true)] - #[case(ojt_hello_hiho(), 9, OjtPhoneme::new("k".into(), 9., 10.), false)] - #[case(ojt_hello_hiho(), 9, OjtPhoneme::new("a".into(), 10., 11.), false)] + #[case(ojt_hello_hiho(), 9, OjtPhoneme::new("a".into()), true)] + #[case(ojt_hello_hiho(), 9, OjtPhoneme::new("k".into()), false)] fn test_ojt_phoneme_equality( #[case] ojt_phonemes: Vec, #[case] index: usize, diff --git a/crates/voicevox_core/src/engine/full_context_label.rs b/crates/voicevox_core/src/engine/full_context_label.rs index 8b87048e5..dab5cbae5 100644 --- a/crates/voicevox_core/src/engine/full_context_label.rs +++ b/crates/voicevox_core/src/engine/full_context_label.rs @@ -1,10 +1,10 @@ use std::str::FromStr; use crate::{ - engine::{self, open_jtalk::FullcontextExtractor, MoraModel}, - AccentPhraseModel, + engine::{self, open_jtalk::FullcontextExtractor}, + AccentPhrase, }; -use jlabel::{Label, Mora}; +use jlabel::Label; use smallvec::SmallVec; // FIXME: 入力テキストをここで持って、メッセージに含む @@ -33,7 +33,7 @@ type Result = std::result::Result; pub(crate) fn extract_full_context_label( open_jtalk: &impl FullcontextExtractor, text: impl AsRef, -) -> Result> { +) -> Result> { let labels = open_jtalk .extract_fullcontext(text.as_ref()) .map_err(|source| FullContextLabelError { @@ -58,7 +58,7 @@ pub(crate) fn extract_full_context_label( fn generate_accent_phrases( utterance: &[Label], -) -> std::result::Result, ErrorKind> { +) -> std::result::Result, ErrorKind> { let mut accent_phrases = Vec::with_capacity( utterance .first() @@ -88,14 +88,14 @@ fn generate_accent_phrases( 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., - )) + Some(crate::Mora { + text: "、".into(), + consonant: None, + consonant_length: None, + vowel: "pau".into(), + vowel_length: 0., + pitch: 0., + }) } else { None }; @@ -103,17 +103,17 @@ fn generate_accent_phrases( // workaround for VOICEVOX/voicevox_engine#55 let accent = usize::from(ap_curr.accent_position).min(moras.len()); - accent_phrases.push(AccentPhraseModel::new( + accent_phrases.push(AccentPhrase { moras, accent, pause_mora, - ap_curr.is_interrogative, - )) + is_interrogative: ap_curr.is_interrogative, + }) } Ok(accent_phrases) } -fn generate_moras(accent_phrase: &[Label]) -> std::result::Result, ErrorKind> { +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); @@ -136,7 +136,7 @@ fn generate_moras(accent_phrase: &[Label]) -> std::result::Result // position_forwardとposition_backwardが飽和している場合は無視する [Label { mora: - Some(Mora { + Some(jlabel::Mora { position_forward: 49, position_backward: 49, .. @@ -151,17 +151,17 @@ fn generate_moras(accent_phrase: &[Label]) -> std::result::Result Ok(moras) } -fn generate_mora(consonant: Option<&Label>, vowel: &Label) -> MoraModel { +fn generate_mora(consonant: Option<&Label>, vowel: &Label) -> crate::Mora { 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, - ) + let vowel = vowel.phoneme.c.clone().unwrap(); + crate::Mora { + text: mora_to_text(consonant_phoneme.as_deref(), &vowel), + consonant: consonant_phoneme, + consonant_length: consonant.and(Some(0.0)), + vowel, + vowel_length: 0.0, + pitch: 0.0, + } } pub fn mora_to_text(consonant: Option<&str>, vowel: &str) -> String { @@ -190,21 +190,21 @@ mod tests { engine::{ full_context_label::{extract_full_context_label, generate_accent_phrases}, open_jtalk::FullcontextExtractor, - MoraModel, + Mora, }, - AccentPhraseModel, + AccentPhrase, }; 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, - ) + fn mora(text: &str, consonant: Option<&str>, vowel: &str) -> Mora { + Mora { + text: text.into(), + consonant: consonant.map(|c| c.into()), + consonant_length: consonant.and(Some(0.0)), + vowel: vowel.into(), + vowel_length: 0.0, + pitch: 0.0, + } } #[template] @@ -218,12 +218,12 @@ mod tests { "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, - ) + AccentPhrase { + moras: vec![mora("イェ", Some("y"), "e")], + accent: 1, + pause_mora: None, + is_interrogative: false, + } ] )] #[case( @@ -236,16 +236,16 @@ mod tests { "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![ + AccentPhrase { + moras: vec![ mora("ン", None, "N"), mora("ン", None, "N"), mora("ッ", None, "cl"), ], - 3, - None, - false, - ), + accent: 3, + pause_mora: None, + is_interrogative: false, + }, ] )] #[case( @@ -271,28 +271,28 @@ mod tests { "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![ + AccentPhrase { + moras: vec![ mora("コ", Some("k"), "o"), mora("レ", Some("r"), "e"), mora("ワ", Some("w"), "a"), ], - 3, - None, - false, - ), - AccentPhraseModel::new( - vec![ + accent: 3, + pause_mora: None, + is_interrogative: false, + }, + AccentPhrase { + moras: vec![ mora("テ", Some("t"), "e"), mora("ス", Some("s"), "U"), mora("ト", Some("t"), "o"), mora("デ", Some("d"), "e"), mora("ス", Some("s"), "U"), ], - 1, - None, - false, - ), + accent: 1, + pause_mora: None, + is_interrogative: false, + }, ] )] #[case( @@ -324,46 +324,46 @@ mod tests { "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![ + AccentPhrase { + moras: vec![ mora("イ", None, "i"), mora("チ", Some("ch"), "i"), ], - 2, - Some(mora("、", None, "pau")), - false, - ), - AccentPhraseModel::new( - vec![ + accent: 2, + pause_mora: Some(mora("、", None, "pau")), + is_interrogative: false, + }, + AccentPhrase { + moras: vec![ mora("セ", Some("s"), "e"), mora("ン", None, "N"), ], - 1, - Some(mora("、", None, "pau")), - false, - ), - AccentPhraseModel::new( - vec![ + accent: 1, + pause_mora: Some(mora("、", None, "pau")), + is_interrogative: false, + }, + AccentPhrase { + moras: 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![ + accent: 3, + pause_mora: Some(mora("、", None, "pau")), + is_interrogative: false, + }, + AccentPhrase { + moras: vec![ mora("イ", None, "i"), mora("チ", Some("ch"), "i"), mora("オ", None, "o"), mora("ク", Some("k"), "u"), ], - 2, - None, - true, - ), + accent: 2, + pause_mora: None, + is_interrogative: true, + }, ] )] #[case( @@ -386,53 +386,53 @@ mod tests { "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![ + AccentPhrase { + moras: 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![ + accent: 3, + pause_mora: Some(mora("、", None, "pau")), + is_interrogative: false, + }, + AccentPhrase { + moras: vec![ mora("ア", None, "a"), mora("ア", None, "a"), ], - 1, - None, - false, - ), - AccentPhraseModel::new( - vec![mora("ア", None, "a")], - 1, - None, - false, - ), + accent: 1, + pause_mora: None, + is_interrogative: false, + }, + AccentPhrase { + moras: vec![mora("ア", None, "a")], + accent: 1, + pause_mora: None, + is_interrogative: false, + }, ] )] fn label_cases( #[case] text: &str, #[case] labels: &[&str], - #[case] accent_phrase: &[AccentPhraseModel], + #[case] accent_phrase: &[AccentPhrase], ) { } #[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) + async fn open_jtalk(text: &str, labels: &[&str], _accent_phrase: &[AccentPhrase]) { + let open_jtalk = crate::nonblocking::OpenJtalk::new(OPEN_JTALK_DIC_DIR) .await .unwrap(); assert_eq!(&open_jtalk.extract_fullcontext(text).unwrap(), labels); } #[apply(label_cases)] - fn parse_labels(_text: &str, labels: &[&str], accent_phrase: &[AccentPhraseModel]) { + fn parse_labels(_text: &str, labels: &[&str], accent_phrase: &[AccentPhrase]) { let parsed_labels = labels .iter() .map(|s| Label::from_str(s).unwrap()) @@ -446,12 +446,8 @@ mod tests { #[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) + async fn extract_fullcontext(text: &str, _labels: &[&str], accent_phrase: &[AccentPhrase]) { + let open_jtalk = crate::nonblocking::OpenJtalk::new(OPEN_JTALK_DIC_DIR) .await .unwrap(); assert_eq!( diff --git a/crates/voicevox_core/src/engine/kana_parser.rs b/crates/voicevox_core/src/engine/kana_parser.rs index eaa3d93c1..67aa276cb 100644 --- a/crates/voicevox_core/src/engine/kana_parser.rs +++ b/crates/voicevox_core/src/engine/kana_parser.rs @@ -1,7 +1,10 @@ -use crate::engine::model::{AccentPhraseModel, MoraModel}; -use crate::engine::mora_list::MORA_LIST_MINIMUM; -use once_cell::sync::Lazy; use std::collections::HashMap; +use std::sync::LazyLock; + +use crate::engine::{ + model::{AccentPhrase, Mora}, + mora_list::MORA_LIST_MINIMUM, +}; const UNVOICE_SYMBOL: char = '_'; const ACCENT_SYMBOL: char = '\''; @@ -16,7 +19,7 @@ pub(crate) struct KanaParseError(String); type KanaParseResult = std::result::Result; -static TEXT2MORA_WITH_UNVOICE: Lazy> = Lazy::new(|| { +static TEXT2MORA_WITH_UNVOICE: LazyLock> = LazyLock::new(|| { let mut text2mora_with_unvoice = HashMap::new(); for [text, consonant, vowel] in MORA_LIST_MINIMUM { let consonant = if !consonant.is_empty() { @@ -27,35 +30,36 @@ static TEXT2MORA_WITH_UNVOICE: Lazy> = Lazy::new(|| { let consonant_length = if consonant.is_some() { Some(0.0) } else { None }; if ["a", "i", "u", "e", "o"].contains(vowel) { - let upper_vowel = vowel.to_uppercase(); - let unvoice_mora = MoraModel::new( - text.to_string(), - consonant.clone(), + let vowel = vowel.to_uppercase(); + + let unvoice_mora = Mora { + text: text.to_string(), + consonant: consonant.clone(), consonant_length, - upper_vowel, - 0., - 0., - ); + vowel, + vowel_length: 0., + pitch: 0., + }; text2mora_with_unvoice.insert(UNVOICE_SYMBOL.to_string() + text, unvoice_mora); } - let mora = MoraModel::new( - text.to_string(), + let mora = Mora { + text: text.to_string(), consonant, consonant_length, - vowel.to_string(), - 0., - 0., - ); + vowel: vowel.to_string(), + vowel_length: 0., + pitch: 0., + }; text2mora_with_unvoice.insert(text.to_string(), mora); } text2mora_with_unvoice }); -fn text_to_accent_phrase(phrase: &str) -> KanaParseResult { +fn text_to_accent_phrase(phrase: &str) -> KanaParseResult { let phrase_vec: Vec = phrase.chars().collect(); let mut accent_index: Option = None; - let mut moras: Vec = Vec::new(); + let mut moras: Vec = Vec::new(); let mut stack = String::new(); let mut matched_text: Option = None; let text2mora = &TEXT2MORA_WITH_UNVOICE; @@ -107,15 +111,15 @@ fn text_to_accent_phrase(phrase: &str) -> KanaParseResult { "accent not found in accent phrase: {phrase}" ))); } - Ok(AccentPhraseModel::new( + Ok(AccentPhrase { moras, - accent_index.unwrap(), - None, - false, - )) + accent: accent_index.unwrap(), + pause_mora: None, + is_interrogative: false, + }) } -pub(crate) fn parse_kana(text: &str) -> KanaParseResult> { +pub(crate) fn parse_kana(text: &str) -> KanaParseResult> { const TERMINATOR: char = '\0'; let mut parsed_result = Vec::new(); let chars_of_text = text.chars().chain([TERMINATOR]); @@ -142,14 +146,14 @@ pub(crate) fn parse_kana(text: &str) -> KanaParseResult> let accent_phrase = { let mut accent_phrase = text_to_accent_phrase(&phrase)?; if letter == PAUSE_DELIMITER { - accent_phrase.set_pause_mora(Some(MoraModel::new( - PAUSE_DELIMITER.to_string(), - None, - None, - "pau".to_string(), - 0., - 0., - ))); + accent_phrase.set_pause_mora(Some(Mora { + text: PAUSE_DELIMITER.to_string(), + consonant: None, + consonant_length: None, + vowel: "pau".to_string(), + vowel_length: 0., + pitch: 0., + })); } accent_phrase.set_is_interrogative(is_interrogative); accent_phrase @@ -163,23 +167,22 @@ pub(crate) fn parse_kana(text: &str) -> KanaParseResult> Ok(parsed_result) } -pub(crate) fn create_kana(accent_phrases: &[AccentPhraseModel]) -> String { +pub(crate) fn create_kana(accent_phrases: &[AccentPhrase]) -> String { let mut text = String::new(); for phrase in accent_phrases { - let moras = phrase.moras(); - for (index, mora) in moras.iter().enumerate() { - if ["A", "E", "I", "O", "U"].contains(&(*mora.vowel()).as_ref()) { + for (index, mora) in phrase.moras.iter().enumerate() { + if ["A", "E", "I", "O", "U"].contains(&&*mora.vowel) { text.push(UNVOICE_SYMBOL); } - text.push_str(mora.text()); - if index + 1 == *phrase.accent() { + text.push_str(&mora.text); + if index + 1 == phrase.accent { text.push(ACCENT_SYMBOL); } } - if *phrase.is_interrogative() { + if phrase.is_interrogative { text.push(WIDE_INTERROGATION_MARK); } - text.push(if phrase.pause_mora().is_some() { + text.push(if phrase.pause_mora.is_some() { PAUSE_DELIMITER } else { NOPAUSE_DELIMITER @@ -212,10 +215,10 @@ mod tests { assert_eq!(mora.is_some(), res.is_some()); if let Some(res) = res { let mut m = String::new(); - if let Some(ref c) = *res.consonant() { + if let Some(c) = &res.consonant { m.push_str(c); } - m.push_str(res.vowel()); + m.push_str(&res.vowel); assert_eq!(m, mora.unwrap()); } } diff --git a/crates/voicevox_core/src/engine/mod.rs b/crates/voicevox_core/src/engine/mod.rs index 95fe3d562..d446a7253 100644 --- a/crates/voicevox_core/src/engine/mod.rs +++ b/crates/voicevox_core/src/engine/mod.rs @@ -10,6 +10,6 @@ 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 use self::model::{AccentPhrase, AudioQuery, Mora}; pub(crate) use self::mora_list::mora2text; pub use self::open_jtalk::FullcontextExtractor; diff --git a/crates/voicevox_core/src/engine/model.rs b/crates/voicevox_core/src/engine/model.rs index de0f388f9..20203feb3 100644 --- a/crates/voicevox_core/src/engine/model.rs +++ b/crates/voicevox_core/src/engine/model.rs @@ -1,42 +1,40 @@ -use derive_getters::Getters; -use derive_new::new; use serde::{Deserialize, Serialize}; /* 各フィールドのjsonフィールド名はsnake_caseとする*/ /// モーラ(子音+母音)ごとの情報。 -#[derive(Clone, Debug, new, Getters, Deserialize, Serialize, PartialEq)] -pub struct MoraModel { +#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)] +pub struct Mora { /// 文字。 - text: String, + pub text: String, /// 子音の音素。 - consonant: Option, + pub consonant: Option, /// 子音の音長。 - consonant_length: Option, + pub consonant_length: Option, /// 母音の音素。 - vowel: String, + pub vowel: String, /// 母音の音長。 - vowel_length: f32, + pub vowel_length: f32, /// 音高。 - pitch: f32, + pub pitch: f32, } /// AccentPhrase (アクセント句ごとの情報)。 -#[derive(Clone, Debug, new, Getters, Deserialize, Serialize, PartialEq)] -pub struct AccentPhraseModel { +#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)] +pub struct AccentPhrase { /// モーラの配列。 - moras: Vec, + pub moras: Vec, /// アクセント箇所。 - accent: usize, + pub accent: usize, /// 後ろに無音を付けるかどうか。 - pause_mora: Option, + pub pause_mora: Option, /// 疑問系かどうか。 #[serde(default)] - is_interrogative: bool, + pub is_interrogative: bool, } -impl AccentPhraseModel { - pub(super) fn set_pause_mora(&mut self, pause_mora: Option) { +impl AccentPhrase { + pub(super) fn set_pause_mora(&mut self, pause_mora: Option) { self.pause_mora = pause_mora; } @@ -46,37 +44,36 @@ impl AccentPhraseModel { } /// AudioQuery (音声合成用のクエリ)。 -#[allow(clippy::too_many_arguments)] -#[derive(Clone, new, Getters, Deserialize, Serialize)] -pub struct AudioQueryModel { +#[derive(Clone, Deserialize, Serialize)] +pub struct AudioQuery { /// アクセント句の配列。 - accent_phrases: Vec, + pub accent_phrases: Vec, /// 全体の話速。 - speed_scale: f32, + pub speed_scale: f32, /// 全体の音高。 - pitch_scale: f32, + pub pitch_scale: f32, /// 全体の抑揚。 - intonation_scale: f32, + pub intonation_scale: f32, /// 全体の音量。 - volume_scale: f32, + pub volume_scale: f32, /// 音声の前の無音時間。 - pre_phoneme_length: f32, + pub pre_phoneme_length: f32, /// 音声の後の無音時間。 - post_phoneme_length: f32, + pub post_phoneme_length: f32, /// 音声データの出力サンプリングレート。 - output_sampling_rate: u32, + pub output_sampling_rate: u32, /// 音声データをステレオ出力するか否か。 - output_stereo: bool, + pub output_stereo: bool, /// \[読み取り専用\] AquesTalk風記法。 /// /// [`Synthesizer::audio_query`]が返すもののみ`Some`となる。入力としてのAudioQueryでは無視され /// る。 /// - /// [`Synthesizer::audio_query`]: crate::Synthesizer::audio_query - kana: Option, + /// [`Synthesizer::audio_query`]: crate::blocking::Synthesizer::audio_query + pub kana: Option, } -impl AudioQueryModel { +impl AudioQuery { pub(crate) fn with_kana(self, kana: Option) -> Self { Self { kana, ..self } } @@ -88,12 +85,22 @@ mod tests { use rstest::rstest; use serde_json::json; - use super::AudioQueryModel; + use super::AudioQuery; #[rstest] fn check_audio_query_model_json_field_snake_case() { - let audio_query_model = - AudioQueryModel::new(vec![], 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0, false, None); + let audio_query_model = AudioQuery { + accent_phrases: vec![], + speed_scale: 0.0, + pitch_scale: 0.0, + intonation_scale: 0.0, + volume_scale: 0.0, + pre_phoneme_length: 0.0, + post_phoneme_length: 0.0, + output_sampling_rate: 0, + output_stereo: false, + kana: None, + }; let val = serde_json::to_value(audio_query_model).unwrap(); check_json_field_snake_case(&val); } @@ -120,7 +127,7 @@ mod tests { #[rstest] fn it_accepts_json_without_optional_fields() -> anyhow::Result<()> { - serde_json::from_value::(json!({ + serde_json::from_value::(json!({ "accent_phrases": [ { "moras": [ diff --git a/crates/voicevox_core/src/engine/open_jtalk.rs b/crates/voicevox_core/src/engine/open_jtalk.rs index 88d16f381..3cad9c99a 100644 --- a/crates/voicevox_core/src/engine/open_jtalk.rs +++ b/crates/voicevox_core/src/engine/open_jtalk.rs @@ -1,3 +1,16 @@ +// TODO: `VoiceModelFile`のように、次のような設計にする。 +// +// ``` +// pub(crate) mod blocking { +// pub struct OpenJtalk(Inner); +// // … +// } +// pub(crate) mod nonblocking { +// pub struct OpenJtalk(Inner); +// // … +// } +// ``` + use ::open_jtalk::Text2MecabError; #[derive(thiserror::Error, Debug)] @@ -35,26 +48,23 @@ pub(crate) mod blocking { 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を抑えるためだけに存在 - { - let mut resources = Resources { - mecab: ManagedResource::initialize(), - njd: ManagedResource::initialize(), - jpcommon: ManagedResource::initialize(), - }; - - // 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), - dict_dir, - }))) - } + let mut resources = Resources { + mecab: ManagedResource::initialize(), + njd: ManagedResource::initialize(), + jpcommon: ManagedResource::initialize(), + }; + + // 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), + dict_dir, + }))) } /// ユーザー辞書を設定する。 @@ -183,12 +193,19 @@ pub(crate) mod blocking { } } -pub(crate) mod tokio { +pub(crate) mod nonblocking { use camino::Utf8Path; use super::FullcontextExtractor; /// テキスト解析器としてのOpen JTalk。 + /// + /// # Performance + /// + /// [blocking]クレートにより動いている。詳しくは[`nonblocking`モジュールのドキュメント]を参照。 + /// + /// [blocking]: https://docs.rs/crate/blocking + /// [`nonblocking`モジュールのドキュメント]: crate::nonblocking #[derive(Clone)] pub struct OpenJtalk(super::blocking::OpenJtalk); @@ -206,7 +223,7 @@ pub(crate) mod tokio { /// この関数を呼び出した後にユーザー辞書を変更した場合は、再度この関数を呼ぶ必要がある。 pub async fn use_user_dict( &self, - user_dict: &crate::tokio::UserDict, + user_dict: &crate::nonblocking::UserDict, ) -> crate::result::Result<()> { let inner = self.0 .0.clone(); let words = user_dict.to_mecab_format(); @@ -325,7 +342,7 @@ mod tests { #[case] text: &str, #[case] expected: anyhow::Result>, ) { - let open_jtalk = super::tokio::OpenJtalk::new(OPEN_JTALK_DIC_DIR) + let open_jtalk = super::nonblocking::OpenJtalk::new(OPEN_JTALK_DIC_DIR) .await .unwrap(); let result = open_jtalk.extract_fullcontext(text); @@ -339,7 +356,7 @@ mod tests { #[case] text: &str, #[case] expected: anyhow::Result>, ) { - let open_jtalk = super::tokio::OpenJtalk::new(OPEN_JTALK_DIC_DIR) + let open_jtalk = super::nonblocking::OpenJtalk::new(OPEN_JTALK_DIC_DIR) .await .unwrap(); for _ in 0..10 { diff --git a/crates/voicevox_core/src/error.rs b/crates/voicevox_core/src/error.rs index 916964429..33775bda0 100644 --- a/crates/voicevox_core/src/error.rs +++ b/crates/voicevox_core/src/error.rs @@ -1,4 +1,5 @@ use crate::{ + devices::DeviceAvailabilities, engine::{FullContextLabelError, KanaParseError}, user_dict::InvalidWordError, StyleId, StyleType, VoiceModelId, @@ -33,7 +34,8 @@ impl Error { pub fn kind(&self) -> ErrorKind { match &self.0 { ErrorRepr::NotLoadedOpenjtalkDict => ErrorKind::NotLoadedOpenjtalkDict, - ErrorRepr::GpuSupport => ErrorKind::GpuSupport, + ErrorRepr::GpuSupport(_) => ErrorKind::GpuSupport, + ErrorRepr::InitInferenceRuntime { .. } => ErrorKind::InitInferenceRuntime, ErrorRepr::LoadModel(LoadModelError { context, .. }) => match context { LoadModelErrorKind::OpenZipFile => ErrorKind::OpenZipFile, LoadModelErrorKind::ReadZipEntry { .. } => ErrorKind::ReadZipEntry, @@ -45,7 +47,7 @@ impl Error { ErrorRepr::GetSupportedDevices(_) => ErrorKind::GetSupportedDevices, ErrorRepr::StyleNotFound { .. } => ErrorKind::StyleNotFound, ErrorRepr::ModelNotFound { .. } => ErrorKind::ModelNotFound, - ErrorRepr::InferenceFailed { .. } => ErrorKind::InferenceFailed, + ErrorRepr::RunModel { .. } => ErrorKind::RunModel, ErrorRepr::ExtractFullContextLabel(_) => ErrorKind::ExtractFullContextLabel, ErrorRepr::ParseKana(_) => ErrorKind::ParseKana, ErrorRepr::LoadUserDict(_) => ErrorKind::LoadUserDict, @@ -62,8 +64,15 @@ pub(crate) enum ErrorRepr { #[error("OpenJTalkの辞書が読み込まれていません")] NotLoadedOpenjtalkDict, - #[error("GPU機能をサポートすることができません")] - GpuSupport, + #[error("GPU機能をサポートすることができません:\n{_0}")] + GpuSupport(DeviceAvailabilities), + + #[error("{runtime_display_name}のロードまたは初期化ができませんでした")] + InitInferenceRuntime { + runtime_display_name: &'static str, + #[source] + source: anyhow::Error, + }, #[error(transparent)] LoadModel(#[from] LoadModelError), @@ -88,7 +97,7 @@ pub(crate) enum ErrorRepr { ModelNotFound { model_id: VoiceModelId }, #[error("推論に失敗しました")] - InferenceFailed(#[source] anyhow::Error), + RunModel(#[source] anyhow::Error), #[error(transparent)] ExtractFullContextLabel(#[from] FullContextLabelError), @@ -119,6 +128,8 @@ pub enum ErrorKind { NotLoadedOpenjtalkDict, /// GPUモードがサポートされていない。 GpuSupport, + /// 推論ライブラリのロードまたは初期化ができなかった。 + InitInferenceRuntime, /// ZIPファイルを開くことに失敗した。 OpenZipFile, /// ZIP内のファイルが読めなかった。 @@ -138,7 +149,7 @@ pub enum ErrorKind { /// 音声モデルIDに対する音声モデルが見つからなかった。 ModelNotFound, /// 推論に失敗した。 - InferenceFailed, + RunModel, /// コンテキストラベル出力に失敗した。 ExtractFullContextLabel, /// AquesTalk風記法のテキストの解析に失敗した。 diff --git a/crates/voicevox_core/src/future.rs b/crates/voicevox_core/src/future.rs new file mode 100644 index 000000000..4ddbf3303 --- /dev/null +++ b/crates/voicevox_core/src/future.rs @@ -0,0 +1,16 @@ +use std::future::Future; + +use easy_ext::ext; + +/// `futures_lite::future::block_on`を、[pollster]のように`.block_on()`という形で使えるようにする。 +/// +/// [pollster]: https://docs.rs/crate/pollster +#[ext(FutureExt)] +impl F { + pub(crate) fn block_on(self) -> Self::Output + where + Self: Sized, + { + futures_lite::future::block_on(self) + } +} diff --git a/crates/voicevox_core/src/infer.rs b/crates/voicevox_core/src/infer.rs index fc8954e7d..e827ddd7d 100644 --- a/crates/voicevox_core/src/infer.rs +++ b/crates/voicevox_core/src/infer.rs @@ -3,7 +3,7 @@ mod model_file; pub(crate) mod runtimes; pub(crate) mod session_set; -use std::{borrow::Cow, collections::BTreeSet, fmt::Debug}; +use std::{borrow::Cow, collections::BTreeSet, fmt::Debug, ops::Index, sync::Arc}; use derive_new::new; use duplicate::duplicate_item; @@ -11,17 +11,32 @@ use enum_map::{Enum, EnumMap}; use ndarray::{Array, ArrayD, Dimension, ShapeError}; use thiserror::Error; -use crate::{StyleType, SupportedDevices}; +use crate::{ + devices::{DeviceSpec, GpuSpec}, + StyleType, SupportedDevices, +}; pub(crate) trait InferenceRuntime: 'static { // TODO: "session"とは何なのかを定め、ドキュメントを書く。`InferenceSessionSet`も同様。 type Session: Sized + Send + 'static; type RunContext<'a>: From<&'a mut Self::Session> + PushInputTensor; - fn supported_devices() -> crate::Result; + /// 名前。 + const DISPLAY_NAME: &'static str; - #[allow(clippy::type_complexity)] + /// このランタイムで利用可能なデバイスの情報を取得する。 + fn supported_devices(&self) -> crate::Result; + + /// GPUが実際に利用できそうかどうか判定する。 + fn test_gpu(&self, gpu: GpuSpec) -> anyhow::Result<()>; + + #[expect( + clippy::type_complexity, + reason = "ここを呼び出すのは現状一箇所なので、可読性が著しく落ちてはいないことを考えると\ + 別にこのままでいいはず" + )] fn new_session( + &self, model: impl FnOnce() -> std::result::Result, DecryptModelError>, options: InferenceSessionOptions, ) -> anyhow::Result<( @@ -36,6 +51,7 @@ pub(crate) trait InferenceRuntime: 'static { /// 共に扱われるべき推論操作の集合を示す。 pub(crate) trait InferenceDomain: Sized { type Operation: InferenceOperation; + type Manifest: Index>; /// 対応する`StyleType`。 /// @@ -53,7 +69,11 @@ pub(crate) trait InferenceDomain: Sized { /// `::macros::InferenceOperation`により導出される。 pub(crate) trait InferenceOperation: Copy + Enum { /// `{InferenceInputSignature,InferenceOutputSignature}::PARAM_INFOS`を集めたもの。 - #[allow(clippy::type_complexity)] + #[expect( + clippy::type_complexity, + reason = "ここを参照するのは現状一箇所なので、可読性が著しく落ちてはいないことを考えると\ + 別にこのままでいいはず" + )] const PARAM_INFOS: EnumMap< Self, ( @@ -79,16 +99,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( @@ -102,8 +126,8 @@ impl InputScalar for T { fn push_tensor_to_ctx( tensor: Array, ctx: &mut impl PushInputTensor, - ) { - ctx.push(tensor); + ) -> anyhow::Result<()> { + ctx.push(tensor) } } @@ -117,8 +141,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<()>; } /// 推論操作の出力シグネチャ。 @@ -181,7 +205,7 @@ impl ParamInfo { #[derive(new, Clone, Copy, PartialEq, Debug)] pub(crate) struct InferenceSessionOptions { pub(crate) cpu_num_threads: u16, - pub(crate) use_gpu: bool, + pub(crate) device: DeviceSpec, } #[derive(Error, Debug)] diff --git a/crates/voicevox_core/src/infer/domains.rs b/crates/voicevox_core/src/infer/domains.rs index 687550399..8383d931c 100644 --- a/crates/voicevox_core/src/infer/domains.rs +++ b/crates/voicevox_core/src/infer/domains.rs @@ -1,14 +1,62 @@ mod talk; +use educe::Educe; +use serde::{Deserialize, Deserializer}; + pub(crate) use self::talk::{ - DecodeInput, DecodeOutput, PredictDurationInput, PredictDurationOutput, PredictIntonationInput, - PredictIntonationOutput, TalkDomain, TalkOperation, + GenerateFullIntermediateInput, GenerateFullIntermediateOutput, PredictDurationInput, + PredictDurationOutput, PredictIntonationInput, PredictIntonationOutput, + RenderAudioSegmentInput, RenderAudioSegmentOutput, TalkDomain, TalkOperation, }; +#[derive(Educe)] +// TODO: `bounds`に`V: ?Sized`も入れようとすると、よくわからない理由で弾かれる。最新版のeduce +// でもそうなのか?また最新版でも駄目だとしたら、弾いている理由は何なのか? +#[educe(Clone(bound = "V: InferenceDomainMapValues, V::Talk: Clone"))] pub(crate) struct InferenceDomainMap { pub(crate) talk: V::Talk, } +impl InferenceDomainMap<(T,)> { + pub(crate) fn each_ref(&self) -> InferenceDomainMap<(&T,)> { + let talk = &self.talk; + InferenceDomainMap { talk } + } + + pub(crate) fn map T2>( + self, + fs: InferenceDomainMap<(Ft,)>, + ) -> InferenceDomainMap<(T2,)> { + let talk = (fs.talk)(self.talk); + InferenceDomainMap { talk } + } +} + +impl InferenceDomainMap<(Result,)> { + pub(crate) fn collect(self) -> Result, E> { + let talk = self.talk?; + Ok(InferenceDomainMap { talk }) + } +} + +impl<'de, V: InferenceDomainMapValues + ?Sized> Deserialize<'de> for InferenceDomainMap +where + V::Talk: Deserialize<'de>, +{ + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + let Repr { talk } = Repr::deserialize(deserializer)?; + return Ok(Self { talk }); + + #[derive(Deserialize)] + struct Repr { + talk: T, + } + } +} + pub(crate) trait InferenceDomainMapValues { type Talk; } @@ -17,6 +65,11 @@ impl InferenceDomainMapValues for (T,) { type Talk = T; } -impl InferenceDomainMapValues for [A] { - type Talk = A; +macro_rules! inference_domain_map_values { + (for<$arg:ident> $body:ty) => { + (::macros::substitute_type!( + $body where $arg = crate::infer::domains::TalkDomain as crate::infer::InferenceDomain + ),) + }; } +pub(crate) use inference_domain_map_values; diff --git a/crates/voicevox_core/src/infer/domains/talk.rs b/crates/voicevox_core/src/infer/domains/talk.rs index e0716fa50..3dbeac762 100644 --- a/crates/voicevox_core/src/infer/domains/talk.rs +++ b/crates/voicevox_core/src/infer/domains/talk.rs @@ -1,11 +1,10 @@ -use std::collections::BTreeSet; +use std::{collections::BTreeSet, sync::LazyLock}; use enum_map::Enum; use macros::{InferenceInputSignature, InferenceOperation, InferenceOutputSignature}; use ndarray::{Array0, Array1, Array2}; -use once_cell::sync::Lazy; -use crate::StyleType; +use crate::{manifest::TalkManifest, StyleType}; use super::super::{ InferenceDomain, InferenceInputSignature as _, InferenceOutputSignature as _, OutputTensor, @@ -15,9 +14,11 @@ pub(crate) enum TalkDomain {} impl InferenceDomain for TalkDomain { type Operation = TalkOperation; + type Manifest = TalkManifest; fn style_types() -> &'static BTreeSet { - static STYLE_TYPES: Lazy> = Lazy::new(|| [StyleType::Talk].into()); + static STYLE_TYPES: LazyLock> = + LazyLock::new(|| [StyleType::Talk].into()); &STYLE_TYPES } } @@ -40,10 +41,16 @@ pub(crate) enum TalkOperation { PredictIntonation, #[inference_operation( - type Input = DecodeInput; - type Output = DecodeOutput; + type Input = GenerateFullIntermediateInput; + type Output = GenerateFullIntermediateOutput; )] - Decode, + GenerateFullIntermediate, + + #[inference_operation( + type Input = RenderAudioSegmentInput; + type Output = RenderAudioSegmentOutput; + )] + RenderAudioSegment, } #[derive(InferenceInputSignature)] @@ -82,15 +89,28 @@ pub(crate) struct PredictIntonationOutput { #[derive(InferenceInputSignature)] #[inference_input_signature( - type Signature = Decode; + type Signature = GenerateFullIntermediate; )] -pub(crate) struct DecodeInput { +pub(crate) struct GenerateFullIntermediateInput { pub(crate) f0: Array2, pub(crate) phoneme: Array2, pub(crate) speaker_id: Array1, } #[derive(InferenceOutputSignature)] -pub(crate) struct DecodeOutput { +pub(crate) struct GenerateFullIntermediateOutput { + pub(crate) spec: Array2, +} + +#[derive(InferenceInputSignature)] +#[inference_input_signature( + type Signature = RenderAudioSegment; +)] +pub(crate) struct RenderAudioSegmentInput { + pub(crate) spec: Array2, +} + +#[derive(InferenceOutputSignature)] +pub(crate) struct RenderAudioSegmentOutput { pub(crate) wave: Array1, } diff --git a/crates/voicevox_core/src/infer/runtimes.rs b/crates/voicevox_core/src/infer/runtimes.rs index 7934027b6..e9d3d31c4 100644 --- a/crates/voicevox_core/src/infer/runtimes.rs +++ b/crates/voicevox_core/src/infer/runtimes.rs @@ -1,3 +1 @@ -mod onnxruntime; - -pub(crate) use self::onnxruntime::Onnxruntime; +pub(crate) mod onnxruntime; diff --git a/crates/voicevox_core/src/infer/runtimes/onnxruntime.rs b/crates/voicevox_core/src/infer/runtimes/onnxruntime.rs index 0556b6d51..f7f92355e 100644 --- a/crates/voicevox_core/src/infer/runtimes/onnxruntime.rs +++ b/crates/voicevox_core/src/infer/runtimes/onnxruntime.rs @@ -1,53 +1,77 @@ +// TODO: `VoiceModelFile`のように、次のような設計にする。 +// +// ``` +// pub(crate) mod blocking { +// pub struct Onnxruntime(Inner); +// // … +// } +// pub(crate) mod nonblocking { +// pub struct Onnxruntime(Inner); +// // … +// } +// ``` + 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, PrimitiveTensorElementType, TensorElementType, ValueType, }; -use crate::{devices::SupportedDevices, error::ErrorRepr}; - -use self::assert_send::AssertSend; +use crate::{ + devices::{DeviceSpec, GpuSpec, SupportedDevices}, + error::ErrorRepr, +}; use super::super::{ DecryptModelError, InferenceRuntime, InferenceSessionOptions, InputScalarKind, OutputScalarKind, OutputTensor, ParamInfo, PushInputTensor, }; -#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)] -pub(crate) enum Onnxruntime {} - -impl InferenceRuntime for Onnxruntime { - type Session = AssertSend>; +impl InferenceRuntime for self::blocking::Onnxruntime { + 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, - _ => {} - } - } + const DISPLAY_NAME: &'static str = if cfg!(feature = "load-onnxruntime") { + "現在ロードされているONNX Runtime" + } else if cfg!(feature = "link-onnxruntime") { + "現在リンクされているONNX Runtime" + } else { + panic!("either `load-onnxruntime` or `link-onnxruntime` must be enabled"); + }; + + fn supported_devices(&self) -> crate::Result { + (|| { + let cpu = CPUExecutionProvider::default().is_available()?; + let cuda = CUDAExecutionProvider::default().is_available()?; + let dml = DirectMLExecutionProvider::default().is_available()?; + + ensure!(cpu, "missing `CPUExecutionProvider`"); + + Ok(SupportedDevices { + cpu: true, + cuda, + dml, + }) + })() + .map_err(ErrorRepr::GetSupportedDevices) + .map_err(Into::into) + } - Ok(SupportedDevices { - cpu: true, - cuda: cuda_support, - dml: dml_support, - }) + fn test_gpu(&self, gpu: GpuSpec) -> anyhow::Result<()> { + let sess_builder = &ort::SessionBuilder::new()?; + match gpu { + GpuSpec::Cuda => CUDAExecutionProvider::default().register(sess_builder), + GpuSpec::Dml => DirectMLExecutionProvider::default().register(sess_builder), + } + .map_err(Into::into) } fn new_session( + &self, model: impl FnOnce() -> std::result::Result, DecryptModelError>, options: InferenceSessionOptions, ) -> anyhow::Result<( @@ -55,48 +79,53 @@ 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)?; + let mut builder = ort::Session::builder()? + .with_optimization_level(GraphOptimizationLevel::Level1)? + .with_intra_threads(options.cpu_num_threads.into())?; + + match options.device { + DeviceSpec::Cpu => {} + DeviceSpec::Gpu(GpuSpec::Cuda) => { + CUDAExecutionProvider::default().register(&builder)?; } - - #[cfg(not(feature = "directml"))] - { - builder = builder.with_append_execution_provider_cuda(Default::default())?; + DeviceSpec::Gpu(GpuSpec::Dml) => { + builder = builder + .with_parallel_execution(false)? + .with_memory_pattern(false)?; + DirectMLExecutionProvider::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 +134,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 +143,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 +174,63 @@ 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() } } 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 PrimitiveTensorElementType + 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,40 +244,377 @@ impl PushInputTensor for OnnxruntimeRunContext<'_> { [ push_int64 ] [ i64 ]; [ push_float32 ] [ f32 ]; )] - fn method(&mut self, tensor: Array) { - self.push_input(tensor); + fn method(&mut self, tensor: Array) -> anyhow::Result<()> { + 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) mod blocking { + use ort::EnvHandle; + use ref_cast::{ref_cast_custom, RefCastCustom}; + + use crate::{error::ErrorRepr, SupportedDevices}; + + use super::super::super::InferenceRuntime; + + /// ONNX Runtime。 + /// + /// シングルトンであり、インスタンスは高々一つ。 + /// + /// # Rust APIにおけるインスタンスの共有 + /// + /// インスタンスは[voicevox-ort]側に作られる。Rustのクレートとしてこのライブラリを利用する場合、 + /// 非同期版APIやvoicevox-ortを利用する他クレートともインスタンスが共有される。 + /// + #[cfg_attr(feature = "load-onnxruntime", doc = "```")] + #[cfg_attr(not(feature = "load-onnxruntime"), doc = "```compile_fail")] + /// # use voicevox_core as another_lib; + /// # + /// # fn main() -> anyhow::Result<()> { + /// # if cfg!(windows) { + /// # // Windows\System32\onnxruntime.dllを回避 + /// # voicevox_core::blocking::Onnxruntime::load_once() + /// # .filename(test_util::ONNXRUNTIME_DYLIB_PATH) + /// # .exec()?; + /// # } + /// let ort1 = voicevox_core::blocking::Onnxruntime::load_once().exec()?; + /// let ort2 = another_lib::nonblocking::Onnxruntime::get().expect("`ort1`と同一のはず"); + /// assert_eq!(ptr_addr(ort1), ptr_addr(ort2)); + /// + /// fn ptr_addr(obj: &impl Sized) -> usize { + /// obj as *const _ as _ + /// } + /// # Ok(()) + /// # } + /// ``` + /// + /// [voicevox-ort]: https://github.com/VOICEVOX/ort + #[derive(Debug, RefCastCustom)] + #[repr(transparent)] + pub struct Onnxruntime { + _inner: EnvHandle, + } - pub(crate) struct AssertSend(T); + impl Onnxruntime { + /// ONNX Runtimeのライブラリ名。 + #[cfg(feature = "load-onnxruntime")] + #[cfg_attr(docsrs, doc(cfg(feature = "load-onnxruntime")))] + pub const LIB_NAME: &'static str = "onnxruntime"; + + /// 推奨されるONNX Runtimeのバージョン。 + #[cfg(feature = "load-onnxruntime")] + #[cfg_attr(docsrs, doc(cfg(feature = "load-onnxruntime")))] + pub const LIB_VERSION: &'static str = ort::downloaded_version!(); + + /// [`LIB_NAME`]と[`LIB_VERSION`]からなる動的ライブラリのファイル名。 + /// + /// WindowsとAndroidでは[`LIB_UNVERSIONED_FILENAME`]と同じ。 + /// + /// [`LIB_NAME`]: Self::LIB_NAME + /// [`LIB_VERSION`]: Self::LIB_VERSION + /// [`LIB_UNVERSIONED_FILENAME`]: Self::LIB_UNVERSIONED_FILENAME + #[cfg(feature = "load-onnxruntime")] + #[cfg_attr(docsrs, doc(cfg(feature = "load-onnxruntime")))] + pub const LIB_VERSIONED_FILENAME: &'static str = if cfg!(target_os = "linux") { + const_format::concatcp!( + "lib", + Onnxruntime::LIB_NAME, + ".so.", + Onnxruntime::LIB_VERSION, + ) + } else if cfg!(any(target_os = "macos", target_os = "ios")) { + const_format::concatcp!( + "lib", + Onnxruntime::LIB_NAME, + ".", + Onnxruntime::LIB_VERSION, + ".dylib", + ) + } else { + Self::LIB_UNVERSIONED_FILENAME + }; + + /// [`LIB_NAME`]からなる動的ライブラリのファイル名。 + /// + /// [`LIB_NAME`]: Self::LIB_NAME + #[cfg(feature = "load-onnxruntime")] + #[cfg_attr(docsrs, doc(cfg(feature = "load-onnxruntime")))] + pub const LIB_UNVERSIONED_FILENAME: &'static str = const_format::concatcp!( + std::env::consts::DLL_PREFIX, + Onnxruntime::LIB_NAME, + std::env::consts::DLL_SUFFIX, + ); + + #[ref_cast_custom] + const fn new(inner: &EnvHandle) -> &Self; + + /// インスタンスが既に作られているならそれを得る。 + /// + /// 作られていなければ`None`を返す。 + pub fn get() -> Option<&'static Self> { + EnvHandle::get().map(Self::new) + } - impl From> - for AssertSend> - { - fn from(session: onnxruntime::session::Session<'static>) -> Self { - Self(session) + fn once( + init: impl FnOnce() -> anyhow::Result<&'static EnvHandle>, + ) -> crate::Result<&'static Self> { + let inner = init().map_err(|source| ErrorRepr::InitInferenceRuntime { + runtime_display_name: "ONNX Runtime", + source, + })?; + Ok(Self::new(inner)) + } + + /// ONNX Runtimeをロードして初期化する。 + /// + /// 一度成功したら、以後は引数を無視して同じ参照を返す。 + #[cfg(feature = "load-onnxruntime")] + #[cfg_attr(docsrs, doc(cfg(feature = "load-onnxruntime")))] + pub fn load_once() -> LoadOnce { + LoadOnce::default() + } + + /// ONNX Runtimeを初期化する。 + /// + /// 一度成功したら以後は同じ参照を返す。 + #[cfg(feature = "link-onnxruntime")] + #[cfg_attr(docsrs, doc(cfg(feature = "link-onnxruntime")))] + pub fn init_once() -> crate::Result<&'static Self> { + Self::once(|| ort::try_init(None)) + } + + #[cfg(test)] + pub(crate) fn from_test_util_data() -> anyhow::Result<&'static Self> { + #[cfg(feature = "load-onnxruntime")] + { + Self::load_once() + .filename(test_util::ONNXRUNTIME_DYLIB_PATH) + .exec() + .map_err(Into::into) + } + + #[cfg(feature = "link-onnxruntime")] + { + Self::init_once().map_err(Into::into) + } + } + + /// ONNX Runtimeとして利用可能なデバイスの情報を取得する。 + pub fn supported_devices(&self) -> crate::Result { + ::supported_devices(self) + } + } + + /// [`Onnxruntime::load_once`]のビルダー。 + #[cfg(feature = "load-onnxruntime")] + pub struct LoadOnce { + filename: std::ffi::OsString, + } + + #[cfg(feature = "load-onnxruntime")] + impl Default for LoadOnce { + fn default() -> Self { + let filename = Onnxruntime::LIB_VERSIONED_FILENAME.into(); + Self { filename } + } + } + + #[cfg(feature = "load-onnxruntime")] + impl LoadOnce { + /// ONNX Runtimeのファイル名(モジュール名)もしくはファイルパスを指定する。 + /// + /// `dlopen`/[`LoadLibraryExW`]の引数に使われる。デフォルト + /// は[`Onnxruntime::LIB_VERSIONED_FILENAME`]。 + /// + /// [`LoadLibraryExW`]: + /// https://learn.microsoft.com/en-us/windows/win32/api/libloaderapi/nf-libloaderapi-loadlibraryexw + pub fn filename(mut self, filename: impl Into) -> Self { + self.filename = filename.into(); + self + } + + /// 実行する。 + pub fn exec(self) -> crate::Result<&'static Onnxruntime> { + Onnxruntime::once(|| ort::try_init_from(&self.filename, None)) } } +} + +pub(crate) mod nonblocking { + use ref_cast::{ref_cast_custom, RefCastCustom}; + + use crate::SupportedDevices; + + /// ONNX Runtime。 + /// + /// シングルトンであり、インスタンスは高々一つ。 + /// + /// # Rust APIにおけるインスタンスの共有 + /// + /// インスタンスは[voicevox-ort]側に作られる。Rustのクレートとしてこのライブラリを利用する場合、 + /// ブロッキング版APIやvoicevox-ortを利用する他クレートともインスタンスが共有される。 + /// + #[cfg_attr(feature = "load-onnxruntime", doc = "```")] + #[cfg_attr(not(feature = "load-onnxruntime"), doc = "```compile_fail")] + /// # use voicevox_core as another_lib; + /// # + /// # #[pollster::main] + /// # async fn main() -> anyhow::Result<()> { + /// # if cfg!(windows) { + /// # // Windows\System32\onnxruntime.dllを回避 + /// # voicevox_core::blocking::Onnxruntime::load_once() + /// # .filename(test_util::ONNXRUNTIME_DYLIB_PATH) + /// # .exec()?; + /// # } + /// let ort1 = voicevox_core::nonblocking::Onnxruntime::load_once() + /// .exec() + /// .await?; + /// let ort2 = another_lib::blocking::Onnxruntime::get().expect("`ort1`と同一のはず"); + /// assert_eq!(ptr_addr(ort1), ptr_addr(ort2)); + /// + /// fn ptr_addr(obj: &impl Sized) -> usize { + /// obj as *const _ as _ + /// } + /// # Ok(()) + /// # } + /// ``` + /// + /// # Performance + /// + /// [blocking]クレートにより動いている。詳しくは[`nonblocking`モジュールのドキュメント]を参照。 + /// + /// [voicevox-ort]: https://github.com/VOICEVOX/ort + /// [blocking]: https://docs.rs/crate/blocking + /// [`nonblocking`モジュールのドキュメント]: crate::nonblocking + #[derive(Debug, RefCastCustom)] + #[repr(transparent)] + pub struct Onnxruntime(pub(crate) super::blocking::Onnxruntime); + + impl Onnxruntime { + /// ONNX Runtimeのライブラリ名。 + #[cfg(feature = "load-onnxruntime")] + #[cfg_attr(docsrs, doc(cfg(feature = "load-onnxruntime")))] + // ブロッキング版と等しいことはテストで担保 + pub const LIB_NAME: &'static str = "onnxruntime"; + + /// 推奨されるONNX Runtimeのバージョン。 + #[cfg(feature = "load-onnxruntime")] + #[cfg_attr(docsrs, doc(cfg(feature = "load-onnxruntime")))] + // ブロッキング版と等しいことはテストで担保 + pub const LIB_VERSION: &'static str = ort::downloaded_version!(); + + /// [`LIB_NAME`]と[`LIB_VERSION`]からなる動的ライブラリのファイル名。 + /// + /// WindowsとAndroidでは[`LIB_UNVERSIONED_FILENAME`]と同じ。 + /// + /// [`LIB_NAME`]: Self::LIB_NAME + /// [`LIB_VERSION`]: Self::LIB_VERSION + /// [`LIB_UNVERSIONED_FILENAME`]: Self::LIB_UNVERSIONED_FILENAME + #[cfg(feature = "load-onnxruntime")] + #[cfg_attr(docsrs, doc(cfg(feature = "load-onnxruntime")))] + pub const LIB_VERSIONED_FILENAME: &'static str = + super::blocking::Onnxruntime::LIB_VERSIONED_FILENAME; + + /// [`LIB_NAME`]からなる動的ライブラリのファイル名。 + /// + /// [`LIB_NAME`]: Self::LIB_NAME + #[cfg(feature = "load-onnxruntime")] + #[cfg_attr(docsrs, doc(cfg(feature = "load-onnxruntime")))] + pub const LIB_UNVERSIONED_FILENAME: &'static str = + super::blocking::Onnxruntime::LIB_UNVERSIONED_FILENAME; + + #[ref_cast_custom] + pub(crate) const fn from_blocking(blocking: &super::blocking::Onnxruntime) -> &Self; + + /// インスタンスが既に作られているならそれを得る。 + /// + /// 作られていなければ`None`を返す。 + pub fn get() -> Option<&'static Self> { + super::blocking::Onnxruntime::get().map(Self::from_blocking) + } + + /// ONNX Runtimeをロードして初期化する。 + /// + /// 一度成功したら、以後は引数を無視して同じ参照を返す。 + #[cfg(feature = "load-onnxruntime")] + #[cfg_attr(docsrs, doc(cfg(feature = "load-onnxruntime")))] + pub fn load_once() -> LoadOnce { + LoadOnce::default() + } - impl Deref for AssertSend { - type Target = T; + /// ONNX Runtimeを初期化する。 + /// + /// 一度成功したら以後は同じ参照を返す。 + #[cfg(feature = "link-onnxruntime")] + #[cfg_attr(docsrs, doc(cfg(feature = "link-onnxruntime")))] + pub async fn init_once() -> crate::Result<&'static Self> { + let inner = crate::task::asyncify(super::blocking::Onnxruntime::init_once).await?; + Ok(Self::from_blocking(inner)) + } + + #[cfg(test)] + pub(crate) async fn from_test_util_data() -> anyhow::Result<&'static Self> { + crate::task::asyncify(super::blocking::Onnxruntime::from_test_util_data) + .await + .map(Self::from_blocking) + } - fn deref(&self) -> &Self::Target { - &self.0 + /// ONNX Runtimeとして利用可能なデバイスの情報を取得する。 + pub fn supported_devices(&self) -> crate::Result { + self.0.supported_devices() } } - impl DerefMut for AssertSend { - fn deref_mut(&mut self) -> &mut Self::Target { - &mut self.0 + /// [`Onnxruntime::load_once`]のビルダー。 + #[cfg(feature = "load-onnxruntime")] + #[derive(Default)] + pub struct LoadOnce(super::blocking::LoadOnce); + + #[cfg(feature = "load-onnxruntime")] + impl LoadOnce { + /// ONNX Runtimeのファイル名(モジュール名)もしくはファイルパスを指定する。 + /// + /// `dlopen`/[`LoadLibraryExW`]の引数に使われる。デフォルト + /// は[`Onnxruntime::LIB_VERSIONED_FILENAME`]。 + /// + /// [`LoadLibraryExW`]: + /// https://learn.microsoft.com/en-us/windows/win32/api/libloaderapi/nf-libloaderapi-loadlibraryexw + pub fn filename(self, filename: impl Into) -> Self { + Self(self.0.filename(filename)) + } + + /// 実行する。 + pub async fn exec(self) -> crate::Result<&'static Onnxruntime> { + let inner = crate::task::asyncify(|| self.0.exec()).await?; + Ok(Onnxruntime::from_blocking(inner)) } } +} + +#[cfg(test)] +mod tests { + use rstest::rstest; + + #[cfg(feature = "load-onnxruntime")] + #[test] + fn assert_same_lib_names_and_versions() { + use pretty_assertions::assert_eq; + + assert_eq!( + super::blocking::Onnxruntime::LIB_NAME, + super::nonblocking::Onnxruntime::LIB_NAME, + ); + assert_eq!( + super::blocking::Onnxruntime::LIB_VERSION, + super::nonblocking::Onnxruntime::LIB_VERSION, + ); + } - // SAFETY: `Session` is probably "send"able. - unsafe impl Send for AssertSend {} + #[rstest] + fn supported_devices_works() { + let result = super::blocking::Onnxruntime::from_test_util_data() + .and_then(|o| o.supported_devices().map_err(Into::into)); + // 環境によって結果が変わるので、関数呼び出しが成功するかどうかの確認のみ行う + assert!(result.is_ok(), "{result:?}"); + } } diff --git a/crates/voicevox_core/src/infer/session_set.rs b/crates/voicevox_core/src/infer/session_set.rs index 56d570f98..e94fff962 100644 --- a/crates/voicevox_core/src/infer/session_set.rs +++ b/crates/voicevox_core/src/infer/session_set.rs @@ -17,6 +17,7 @@ pub(crate) struct InferenceSessionSet( impl InferenceSessionSet { pub(crate) fn new( + rt: &R, model_bytes: &EnumMap>, options: &EnumMap, ) -> anyhow::Result { @@ -27,7 +28,7 @@ impl InferenceSessionSet { ::PARAM_INFOS[op]; let (sess, actual_input_param_infos, actual_output_param_infos) = - R::new_session(|| model_file::decrypt(model_bytes), options[op])?; + rt.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)?; @@ -60,8 +61,8 @@ impl InferenceSessionSet { .iter() .map(|ParamInfo { name, dt, ndim }| { let brackets = match *ndim { - Some(ndim) => "[]".repeat(ndim), - None => "[]...".to_owned(), + Some(ndim) => &"[]".repeat(ndim), + None => "[]...", }; format!("{name}: {dt}{brackets}") }) @@ -73,8 +74,7 @@ impl InferenceSessionSet { impl InferenceSessionSet { pub(crate) fn get(&self) -> InferenceSessionCell where - I: InferenceInputSignature, - I::Signature: InferenceSignature, + I: InferenceInputSignature>, { InferenceSessionCell { inner: self.0[I::Signature::OPERATION].clone(), @@ -94,9 +94,8 @@ impl InferenceSessionCell 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()) + (|| R::run(input.make_run_context::(inner)?)?.try_into())() + .map_err(ErrorRepr::RunModel) + .map_err(Into::into) } } diff --git a/crates/voicevox_core/src/lib.rs b/crates/voicevox_core/src/lib.rs index 0f34c5962..c5ab200d7 100644 --- a/crates/voicevox_core/src/lib.rs +++ b/crates/voicevox_core/src/lib.rs @@ -1,9 +1,59 @@ //! 無料で使える中品質なテキスト読み上げソフトウェア、VOICEVOXのコア。 +//! +//! # Feature flags +//! +//! このクレートの利用にあたっては以下の二つの[Cargoフィーチャ]のうちどちらかを有効にしなければなり +//! ません。両方の有効化はコンパイルエラーとなります。[`Onnxruntime`]の初期化方法はこれらの +//! フィーチャによって決まります。 +//! +//! - **`load-onnxruntime`**: ONNX Runtimeを`dlopen`/`LoadLibraryExW`で +//! 開きます。[CUDA]と[DirectML]が利用できます。 +//! - **`link-onnxruntime`**: ONNX Runtimeをロード時動的リンクします。iOSのような`dlopen`の利用が +//! 困難な環境でのみこちらを利用するべきです。_Note_: +//! [動的リンク対象のライブラリ名]は`onnxruntime`で固定です。変更 +//! は`patchelf(1)`や`install_name_tool(1)`で行ってください。また、[ONNX RuntimeのGPU機能]を使う +//! ことはできません。 +//! +//! [Cargoフィーチャ]: https://doc.rust-lang.org/stable/cargo/reference/features.html +//! [CUDA]: https://onnxruntime.ai/docs/execution-providers/CUDA-ExecutionProvider.html +//! [DirectML]: https://onnxruntime.ai/docs/execution-providers/DirectML-ExecutionProvider.html +//! [動的リンク対象のライブラリ名]: +//! https://doc.rust-lang.org/cargo/reference/build-scripts.html#rustc-link-lib +//! [`Onnxruntime`]: blocking::Onnxruntime +//! [ONNX RuntimeのGPU機能]: https://onnxruntime.ai/docs/execution-providers/ +#![cfg_attr(docsrs, feature(doc_cfg))] + +#[cfg(not(any(feature = "load-onnxruntime", feature = "link-onnxruntime")))] +compile_error!("either `load-onnxruntime` or `link-onnxruntime` must be enabled"); + +#[cfg(not(doc))] +const _: () = { + #[cfg(all(feature = "load-onnxruntime", feature = "link-onnxruntime"))] + compile_error!("`load-onnxruntime` and `link-onnxruntime` cannot be enabled at the same time"); + + // Rust APIでvoicevox-ortを他のクレートが利用する可能性を考え、voicevox-ort側とfeatureがズレ + // ないようにする + + #[cfg(feature = "load-onnxruntime")] + ort::assert_feature!( + cfg(feature = "load-dynamic"), + "when `load-onnxruntime` is enabled,`voicevox-ort/load-dynamic` must be also enabled", + ); + + #[cfg(feature = "link-onnxruntime")] + ort::assert_feature!( + cfg(not(feature = "load-dynamic")), + "when `link-onnxruntime` is enabled,`voicevox-ort/load-dynamic` must be disabled", + ); +}; + +mod asyncs; mod devices; /// cbindgen:ignore mod engine; mod error; +mod future; mod infer; mod macros; mod manifest; @@ -19,19 +69,21 @@ mod voice_model; pub mod __internal; pub mod blocking; -pub mod tokio; +pub mod nonblocking; #[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)] +#[expect( + clippy::single_component_path_imports, + reason = "https://crates.io/crates/rstest_reuse/0.6.0#use-rstest_resuse-at-the-top-of-your-crate" +)] #[cfg(test)] use rstest_reuse; pub use self::{ devices::SupportedDevices, - engine::{AccentPhraseModel, AudioQueryModel, FullcontextExtractor}, + engine::{AccentPhrase, AudioQuery, FullcontextExtractor, Mora}, error::{Error, ErrorKind}, metas::{ RawStyleId, RawStyleVersion, SpeakerMeta, StyleId, StyleMeta, StyleType, StyleVersion, diff --git a/crates/voicevox_core/src/manifest.rs b/crates/voicevox_core/src/manifest.rs index 3b17ae3f1..8a8290887 100644 --- a/crates/voicevox_core/src/manifest.rs +++ b/crates/voicevox_core/src/manifest.rs @@ -1,69 +1,144 @@ -use std::{collections::BTreeMap, fmt::Display, sync::Arc}; +use std::{ + collections::BTreeMap, + fmt::{self, Display}, + sync::Arc, +}; use derive_getters::Getters; use derive_more::Deref; use derive_new::new; -use serde::{Deserialize, Serialize}; +use macros::IndexForFields; +use serde::{de, Deserialize, Deserializer, Serialize}; use serde_with::{serde_as, DisplayFromStr}; -use crate::StyleId; +use crate::{ + infer::domains::{inference_domain_map_values, InferenceDomainMap, TalkOperation}, + StyleId, VoiceModelId, +}; -pub type RawManifestVersion = String; -#[derive(Deserialize, Clone, Debug, PartialEq, new)] -pub struct ManifestVersion(RawManifestVersion); +#[derive(Clone)] +struct FormatVersionV1; -impl Display for ManifestVersion { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(f, "{}", self.0) +impl<'de> Deserialize<'de> for FormatVersionV1 { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + return deserializer.deserialize_any(Visitor); + + struct Visitor; + + impl<'de> de::Visitor<'de> for Visitor { + type Value = FormatVersionV1; + + fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { + formatter.write_str("an unsigned integer") + } + + fn visit_u64(self, v: u64) -> Result + where + E: de::Error, + { + match v { + 1 => Ok(FormatVersionV1), + v => Err(E::custom(format!( + "未知の形式です(`vvm_format_version={v}`)。新しいバージョンのVOICEVOX \ + COREであれば対応しているかもしれません", + ))), + } + } + } } } /// モデル内IDの実体 -pub type RawModelInnerId = u32; +pub type RawInnerVoiceId = u32; /// モデル内ID #[derive(PartialEq, Eq, Clone, Copy, Ord, PartialOrd, Deserialize, Serialize, new, Debug)] -pub struct ModelInnerId(RawModelInnerId); +pub struct InnerVoiceId(RawInnerVoiceId); -impl ModelInnerId { - pub fn raw_id(self) -> RawModelInnerId { +impl InnerVoiceId { + pub fn raw_id(self) -> RawInnerVoiceId { self.0 } } -impl Display for ModelInnerId { +impl Display for InnerVoiceId { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!(f, "{}", self.raw_id()) } } -#[derive(Deserialize, Getters, Clone)] +#[derive(Deserialize, Getters)] pub struct Manifest { - // FIXME: UUIDにする - // https://github.com/VOICEVOX/voicevox_core/issues/581 - #[allow(dead_code)] - manifest_version: ManifestVersion, + #[expect(dead_code, reason = "現状はバリデーションのためだけに存在")] + vvm_format_version: FormatVersionV1, + pub(crate) id: VoiceModelId, metas_filename: String, #[serde(flatten)] - domains: ManifestDomains, + domains: InferenceDomainMap, } -#[derive(Deserialize, Clone)] -pub(crate) struct ManifestDomains { - pub(crate) talk: Option, -} +pub(crate) type ManifestDomains = inference_domain_map_values!(for Option); -#[derive(Deserialize, Clone)] +#[derive(Deserialize, IndexForFields)] +#[cfg_attr(test, derive(Default))] +#[index_for_fields(TalkOperation)] pub(crate) struct TalkManifest { - pub(crate) predict_duration_filename: String, - pub(crate) predict_intonation_filename: String, - pub(crate) decode_filename: String, + #[index_for_fields(TalkOperation::PredictDuration)] + pub(crate) predict_duration_filename: Arc, + + #[index_for_fields(TalkOperation::PredictIntonation)] + pub(crate) predict_intonation_filename: Arc, + + #[index_for_fields(TalkOperation::GenerateFullIntermediate)] + pub(crate) generate_full_intermediate_filename: Arc, + + #[index_for_fields(TalkOperation::RenderAudioSegment)] + pub(crate) render_audio_segment_filename: Arc, + #[serde(default)] - pub(crate) style_id_to_model_inner_id: StyleIdToModelInnerId, + pub(crate) style_id_to_inner_voice_id: StyleIdToInnerVoiceId, } #[serde_as] #[derive(Default, Clone, Deref, Deserialize)] #[deref(forward)] -pub(crate) struct StyleIdToModelInnerId( - #[serde_as(as = "Arc>")] Arc>, +pub(crate) struct StyleIdToInnerVoiceId( + #[serde_as(as = "Arc>")] Arc>, ); + +#[cfg(test)] +mod tests { + use std::ops::Deref; + + use rstest::rstest; + use serde::Deserialize; + + use super::FormatVersionV1; + + #[rstest] + #[case("{\"vvm_format_version\":1}", Ok(()))] + #[case( + "{\"vvm_format_version\":2}", + Err( + "未知の形式です(`vvm_format_version=2`)。新しいバージョンのVOICEVOX COREであれば対応\ + しているかもしれません at line 1 column 23", + ) + )] + fn vvm_format_version_works( + #[case] input: &str, + #[case] expected: Result<(), &str>, + ) -> anyhow::Result<()> { + let actual = serde_json::from_str::(input).map_err(|e| e.to_string()); + let actual = actual.as_ref().map(|_| ()).map_err(Deref::deref); + assert_eq!(expected, actual); + return Ok(()); + + #[derive(Deserialize)] + struct ManifestPart { + #[expect(dead_code, reason = "バリデーションのためだけに存在")] + vvm_format_version: FormatVersionV1, + } + } +} diff --git a/crates/voicevox_core/src/metas.rs b/crates/voicevox_core/src/metas.rs index b9f274c48..97c3922c7 100644 --- a/crates/voicevox_core/src/metas.rs +++ b/crates/voicevox_core/src/metas.rs @@ -1,6 +1,5 @@ use std::fmt::{Debug, Display}; -use derive_getters::Getters; use derive_new::new; use indexmap::IndexMap; use itertools::Itertools as _; @@ -102,20 +101,20 @@ impl Display for StyleVersion { pub type VoiceModelMeta = Vec; /// **話者**(_speaker_)のメタ情報。 -#[derive(Deserialize, Serialize, Getters, Clone)] +#[derive(Deserialize, Serialize, Clone)] pub struct SpeakerMeta { /// 話者名。 - name: String, + pub name: String, /// 話者に属するスタイル。 - styles: Vec, + pub styles: Vec, /// 話者のバージョン。 - version: StyleVersion, + pub version: StyleVersion, /// 話者のUUID。 - speaker_uuid: String, + pub speaker_uuid: String, /// 話者の順番。 /// /// `SpeakerMeta`の列は、この値に対して昇順に並んでいるべきである。 - order: Option, + pub order: Option, } impl SpeakerMeta { @@ -161,19 +160,19 @@ impl SpeakerMeta { } /// **スタイル**(_style_)のメタ情報。 -#[derive(Deserialize, Serialize, Getters, Clone)] +#[derive(Deserialize, Serialize, Clone)] pub struct StyleMeta { /// スタイルID。 - id: StyleId, + pub id: StyleId, /// スタイル名。 - name: String, + pub name: String, /// スタイルに対応するモデルの種類。 #[serde(default)] - r#type: StyleType, + pub r#type: StyleType, /// スタイルの順番。 /// /// [`SpeakerMeta::styles`]は、この値に対して昇順に並んでいるべきである。 - order: Option, + pub order: Option, } /// **スタイル**(_style_)に対応するモデルの種類。 @@ -210,12 +209,13 @@ pub enum StyleType { #[cfg(test)] mod tests { - use once_cell::sync::Lazy; + use std::sync::LazyLock; + use serde_json::json; #[test] fn merge_works() -> anyhow::Result<()> { - static INPUT: Lazy = Lazy::new(|| { + static INPUT: LazyLock = LazyLock::new(|| { json!([ { "name": "B", @@ -268,7 +268,7 @@ mod tests { ]) }); - static EXPECTED: Lazy = Lazy::new(|| { + static EXPECTED: LazyLock = LazyLock::new(|| { json!([ { "name": "A", diff --git a/crates/voicevox_core/src/nonblocking.rs b/crates/voicevox_core/src/nonblocking.rs new file mode 100644 index 000000000..7187c57fa --- /dev/null +++ b/crates/voicevox_core/src/nonblocking.rs @@ -0,0 +1,25 @@ +//! 非同期版API。 +//! +//! # Performance +//! +//! これらは[blocking]クレートにより動いている。特定の非同期ランタイムを必要とせず、[pollster]など +//! でも動かすことができる。 +//! +//! スレッドプールおよびエグゼキュータはblockingクレートに依存するすべてのプログラム間で共有される。 +//! スレッドプールのサイズは、blockingクレートの説明にある通り`$BLOCKING_MAX_THREADS`で調整すること +//! ができる。 +//! +//! [blocking]: https://docs.rs/crate/blocking +//! [pollster]: https://docs.rs/crate/pollster + +pub use crate::{ + engine::open_jtalk::nonblocking::OpenJtalk, + infer::runtimes::onnxruntime::nonblocking::Onnxruntime, synthesizer::nonblocking::Synthesizer, + user_dict::dict::nonblocking::UserDict, voice_model::nonblocking::VoiceModelFile, +}; + +pub mod onnxruntime { + #[cfg(feature = "load-onnxruntime")] + #[cfg_attr(docsrs, doc(cfg(feature = "load-onnxruntime")))] + pub use crate::infer::runtimes::onnxruntime::nonblocking::LoadOnce; +} diff --git a/crates/voicevox_core/src/status.rs b/crates/voicevox_core/src/status.rs index 40eef430e..3234c27c4 100644 --- a/crates/voicevox_core/src/status.rs +++ b/crates/voicevox_core/src/status.rs @@ -9,25 +9,30 @@ use itertools::iproduct; use crate::{ error::{ErrorRepr, LoadModelError, LoadModelErrorKind, LoadModelResult}, infer::{ - domains::{InferenceDomainMap, TalkDomain, TalkOperation}, + domains::{inference_domain_map_values, InferenceDomainMap, TalkDomain}, session_set::{InferenceSessionCell, InferenceSessionSet}, InferenceDomain, InferenceInputSignature, InferenceRuntime, InferenceSessionOptions, InferenceSignature, }, - manifest::{ModelInnerId, StyleIdToModelInnerId}, + manifest::{InnerVoiceId, StyleIdToInnerVoiceId}, metas::{self, SpeakerMeta, StyleId, StyleMeta, VoiceModelMeta}, - voice_model::{ModelBytesWithInnerIdsByDomain, VoiceModelHeader, VoiceModelId}, + voice_model::{ModelBytesWithInnerVoiceIdsByDomain, VoiceModelHeader, VoiceModelId}, Result, }; pub(crate) struct Status { + pub(crate) rt: &'static R, loaded_models: std::sync::Mutex>, session_options: InferenceDomainMap, } impl Status { - pub(crate) fn new(session_options: InferenceDomainMap) -> Self { + pub(crate) fn new( + rt: &'static R, + session_options: InferenceDomainMap, + ) -> Self { Self { + rt, loaded_models: Default::default(), session_options, } @@ -36,7 +41,7 @@ impl Status { pub(crate) fn insert_model( &self, model_header: &VoiceModelHeader, - model_contents: &InferenceDomainMap, + model_contents: &InferenceDomainMap, ) -> Result<()> { self.loaded_models .lock() @@ -44,7 +49,7 @@ impl Status { .ensure_acceptable(model_header)?; let session_sets_with_inner_ids = model_contents - .create_session_sets(&self.session_options) + .create_session_sets(self.rt, &self.session_options) .map_err(|source| LoadModelError { path: model_header.path.clone(), context: LoadModelErrorKind::InvalidModelData, @@ -58,7 +63,7 @@ impl Status { Ok(()) } - pub(crate) fn unload_model(&self, voice_model_id: &VoiceModelId) -> Result<()> { + pub(crate) fn unload_model(&self, voice_model_id: VoiceModelId) -> Result<()> { self.loaded_models.lock().unwrap().remove(voice_model_id) } @@ -66,18 +71,18 @@ impl Status { self.loaded_models.lock().unwrap().metas() } - /// あるスタイルに対応する`VoiceModelId`と`ModelInnerId`の組を返す。 + /// あるスタイルに対応する`VoiceModelId`と`InnerVoiceId`の組を返す。 /// - /// `StyleId` → `ModelInnerId`のマッピングが存在しない場合は、`ModelInnerId`としては + /// `StyleId` → `InnerVoiceId`のマッピングが存在しない場合は、`InnerVoiceId`としては /// `style_id`と同じ値を返す。 pub(crate) fn ids_for( &self, style_id: StyleId, - ) -> Result<(VoiceModelId, ModelInnerId)> { + ) -> Result<(VoiceModelId, InnerVoiceId)> { self.loaded_models.lock().unwrap().ids_for::(style_id) } - pub(crate) fn is_loaded_model(&self, voice_model_id: &VoiceModelId) -> bool { + pub(crate) fn is_loaded_model(&self, voice_model_id: VoiceModelId) -> bool { self.loaded_models .lock() .unwrap() @@ -101,7 +106,7 @@ impl Status { /// `self`が`model_id`を含んでいないとき、パニックする。 pub(crate) fn run_session( &self, - model_id: &VoiceModelId, + model_id: VoiceModelId, input: I, ) -> Result<::Output> where @@ -122,7 +127,7 @@ struct LoadedModels(IndexMap>) struct LoadedModel { metas: VoiceModelMeta, - session_sets_with_inner_ids: InferenceDomainMap>, + session_sets_with_inner_ids: InferenceDomainMap>, } impl LoadedModels { @@ -133,7 +138,7 @@ impl LoadedModels { fn ids_for( &self, style_id: StyleId, - ) -> Result<(VoiceModelId, ModelInnerId)> { + ) -> Result<(VoiceModelId, InnerVoiceId)> { let ( model_id, LoadedModel { @@ -144,22 +149,23 @@ impl LoadedModels { .0 .iter() .find(|(_, LoadedModel { metas, .. })| { - metas.iter().flat_map(SpeakerMeta::styles).any(|style| { - *style.id() == style_id && D::style_types().contains(style.r#type()) - }) + metas + .iter() + .flat_map(|SpeakerMeta { styles, .. }| 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 + let inner_voice_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())); + .and_then(|(inner_voice_ids, _)| inner_voice_ids.get(&style_id).copied()) + .unwrap_or_else(|| InnerVoiceId::new(style_id.raw_id())); - Ok((model_id.clone(), model_inner_id)) + Ok((*model_id, inner_voice_id)) } /// # Panics @@ -168,12 +174,12 @@ impl LoadedModels { /// /// - `self`が`model_id`を含んでいないとき /// - 対応する`InferenceDomain`が欠けているとき - fn get(&self, model_id: &VoiceModelId) -> InferenceSessionCell + fn get(&self, model_id: VoiceModelId) -> InferenceSessionCell where I: InferenceInputSignature, ::Domain: InferenceDomainExt, { - let (_, session_set) = self.0[model_id] + let (_, session_set) = self.0[&model_id] .session_sets_with_inner_ids .get::<::Domain>() .as_ref() @@ -190,12 +196,12 @@ impl LoadedModels { session_set.get() } - fn contains_voice_model(&self, model_id: &VoiceModelId) -> bool { - self.0.contains_key(model_id) + 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) + self.styles().any(|style| style.id == style_id) } /// 音声モデルを受け入れ可能かをチェックする。 @@ -216,9 +222,9 @@ impl LoadedModels { source: None, }; - if self.0.contains_key(&model_header.id) { + if self.0.contains_key(&model_header.manifest.id) { return Err(error(LoadModelErrorKind::ModelAlreadyLoaded { - id: model_header.id.clone(), + id: model_header.manifest.id, })); } @@ -227,22 +233,21 @@ impl LoadedModels { let loaded = self.speakers(); let external = model_header.metas.iter(); for (loaded, external) in iproduct!(loaded, external) { - if loaded.speaker_uuid() == external.speaker_uuid() { + if loaded.speaker_uuid == external.speaker_uuid { loaded.warn_diff_except_styles(external); } } - let loaded = self.styles(); + let loaded = self.styles().map(|&StyleMeta { id, .. }| id); 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()) + .flat_map(|speaker| &speaker.styles) + .map(|&StyleMeta { id, .. }| id); + if let Some((id, _)) = + iproduct!(loaded, external).find(|(loaded, external)| loaded == external) { - return Err(error(LoadModelErrorKind::StyleAlreadyLoaded { - id: *style.id(), - })); + return Err(error(LoadModelErrorKind::StyleAlreadyLoaded { id })); } Ok(()) } @@ -250,12 +255,12 @@ impl LoadedModels { fn insert( &mut self, model_header: &VoiceModelHeader, - session_sets_with_inner_ids: InferenceDomainMap>, + session_sets_with_inner_ids: InferenceDomainMap>, ) -> Result<()> { self.ensure_acceptable(model_header)?; let prev = self.0.insert( - model_header.id.clone(), + model_header.manifest.id, LoadedModel { metas: model_header.metas.clone(), session_sets_with_inner_ids, @@ -265,12 +270,9 @@ impl LoadedModels { 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()); + fn remove(&mut self, model_id: VoiceModelId) -> Result<()> { + if self.0.shift_remove(&model_id).is_none() { + return Err(ErrorRepr::ModelNotFound { model_id }.into()); } Ok(()) } @@ -280,14 +282,15 @@ impl LoadedModels { } fn styles(&self) -> impl Iterator { - self.speakers().flat_map(|speaker| speaker.styles()) + self.speakers() + .flat_map(|SpeakerMeta { styles, .. }| styles) } } pub(crate) trait InferenceDomainExt: InferenceDomain { fn visit( - map: &InferenceDomainMap>, - ) -> Option<&(StyleIdToModelInnerId, InferenceSessionSet)>; + map: &InferenceDomainMap>, + ) -> Option<&(StyleIdToInnerVoiceId, InferenceSessionSet)>; } #[duplicate_item( @@ -296,25 +299,26 @@ pub(crate) trait InferenceDomainExt: InferenceDomain { )] impl InferenceDomainExt for T { fn visit( - map: &InferenceDomainMap>, - ) -> Option<&(StyleIdToModelInnerId, InferenceSessionSet)> { + map: &InferenceDomainMap>, + ) -> Option<&(StyleIdToInnerVoiceId, InferenceSessionSet)> { map.field.as_ref() } } -impl InferenceDomainMap> { +impl InferenceDomainMap> { fn get( &self, - ) -> Option<&(StyleIdToModelInnerId, InferenceSessionSet)> { + ) -> Option<&(StyleIdToInnerVoiceId, InferenceSessionSet)> { D::visit(self) } } -impl InferenceDomainMap { +impl InferenceDomainMap { fn create_session_sets( &self, + rt: &R, session_options: &InferenceDomainMap, - ) -> anyhow::Result>> { + ) -> anyhow::Result>> { duplicate! { [ field; @@ -323,9 +327,9 @@ impl InferenceDomainMap { 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)) + .map(|(inner_voice_ids, model_bytes)| { + let session_set = InferenceSessionSet::new(rt, model_bytes, &session_options.field)?; + Ok::<_, anyhow::Error>((inner_voice_ids.clone(), session_set)) }) .transpose()?; } @@ -334,10 +338,11 @@ impl InferenceDomainMap { } } -type SessionOptionsByDomain = (EnumMap,); +type SessionOptionsByDomain = + inference_domain_map_values!(for EnumMap); -type SessionSetsWithInnerIdsByDomain = - (Option<(StyleIdToModelInnerId, InferenceSessionSet)>,); +type SessionSetsWithInnerVoiceIdsByDomain = + inference_domain_map_values!(for Option<(StyleIdToInnerVoiceId, InferenceSessionSet)>); #[cfg(test)] mod tests { @@ -346,36 +351,39 @@ mod tests { use rstest::rstest; use crate::{ + devices::{DeviceSpec, GpuSpec}, infer::{ domains::{InferenceDomainMap, TalkOperation}, InferenceSessionOptions, }, macros::tests::assert_debug_fmt_eq, - synthesizer::InferenceRuntimeImpl, - test_util::open_default_vvm_file, }; 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); + #[case(DeviceSpec::Gpu(GpuSpec::Cuda), 0)] + #[case(DeviceSpec::Gpu(GpuSpec::Cuda), 1)] + #[case(DeviceSpec::Gpu(GpuSpec::Cuda), 8)] + #[case(DeviceSpec::Cpu, 2)] + #[case(DeviceSpec::Cpu, 4)] + #[case(DeviceSpec::Cpu, 8)] + #[case(DeviceSpec::Cpu, 0)] + fn status_new_works(#[case] device_for_heavy: DeviceSpec, #[case] cpu_num_threads: u16) { + let light_session_options = InferenceSessionOptions::new(cpu_num_threads, DeviceSpec::Cpu); + let heavy_session_options = InferenceSessionOptions::new(cpu_num_threads, device_for_heavy); let session_options = InferenceDomainMap { talk: enum_map! { TalkOperation::PredictDuration - | TalkOperation::PredictIntonation => light_session_options, - TalkOperation::Decode => heavy_session_options, + | TalkOperation::PredictIntonation + | TalkOperation::GenerateFullIntermediate => light_session_options, + TalkOperation::RenderAudioSegment => heavy_session_options, }, }; - let status = Status::::new(session_options); + let status = Status::new( + crate::blocking::Onnxruntime::from_test_util_data().unwrap(), + session_options, + ); assert_eq!( light_session_options, @@ -385,9 +393,13 @@ mod tests { light_session_options, status.session_options.talk[TalkOperation::PredictIntonation], ); + assert_eq!( + light_session_options, + status.session_options.talk[TalkOperation::GenerateFullIntermediate], + ); assert_eq!( heavy_session_options, - status.session_options.talk[TalkOperation::Decode], + status.session_options.talk[TalkOperation::RenderAudioSegment], ); assert!(status.loaded_models.lock().unwrap().0.is_empty()); @@ -396,10 +408,13 @@ mod tests { #[rstest] #[tokio::test] async fn status_load_model_works() { - let status = Status::::new(InferenceDomainMap { - talk: enum_map!(_ => InferenceSessionOptions::new(0, false)), - }); - let model = &open_default_vvm_file().await; + let status = Status::new( + crate::blocking::Onnxruntime::from_test_util_data().unwrap(), + InferenceDomainMap { + talk: enum_map!(_ => InferenceSessionOptions::new(0, DeviceSpec::Cpu)), + }, + ); + let model = &crate::nonblocking::VoiceModelFile::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); @@ -409,20 +424,23 @@ mod tests { #[rstest] #[tokio::test] async fn status_is_model_loaded_works() { - let status = Status::::new(InferenceDomainMap { - talk: enum_map!(_ => InferenceSessionOptions::new(0, false)), - }); - let vvm = open_default_vvm_file().await; + let status = Status::new( + crate::blocking::Onnxruntime::from_test_util_data().unwrap(), + InferenceDomainMap { + talk: enum_map!(_ => InferenceSessionOptions::new(0, DeviceSpec::Cpu)), + }, + ); + let vvm = &crate::nonblocking::VoiceModelFile::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), + !status.is_loaded_model(model_header.manifest.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), + status.is_loaded_model(model_header.manifest.id), "model should be loaded", ); } diff --git a/crates/voicevox_core/src/synthesizer.rs b/crates/voicevox_core/src/synthesizer.rs index d90cdce3c..adeb010a0 100644 --- a/crates/voicevox_core/src/synthesizer.rs +++ b/crates/voicevox_core/src/synthesizer.rs @@ -1,9 +1,20 @@ -use crate::infer::runtimes::Onnxruntime; - -/// [`blocking::Synthesizer::synthesis`]および[`tokio::Synthesizer::synthesis`]のオプション。 +// TODO: `VoiceModelFile`のように、次のような設計にする。 +// +// ``` +// pub(crate) mod blocking { +// pub struct Synthesizer(Inner); +// // … +// } +// pub(crate) mod nonblocking { +// pub struct Synthesizer(Inner); +// // … +// } +// ``` + +/// [`blocking::Synthesizer::synthesis`]および[`nonblocking::Synthesizer::synthesis`]のオプション。 /// /// [`blocking::Synthesizer::synthesis`]: blocking::Synthesizer::synthesis -/// [`tokio::Synthesizer::synthesis`]: tokio::Synthesizer::synthesis +/// [`nonblocking::Synthesizer::synthesis`]: nonblocking::Synthesizer::synthesis #[derive(Clone)] pub struct SynthesisOptions { pub enable_interrogative_upspeak: bool, @@ -23,10 +34,10 @@ impl From<&TtsOptions> for SynthesisOptions { } } -/// [`blocking::Synthesizer::tts`]および[`tokio::Synthesizer::tts`]のオプション。 +/// [`blocking::Synthesizer::tts`]および[`nonblocking::Synthesizer::tts`]のオプション。 /// /// [`blocking::Synthesizer::tts`]: blocking::Synthesizer::tts -/// [`tokio::Synthesizer::tts`]: tokio::Synthesizer::tts +/// [`nonblocking::Synthesizer::tts`]: nonblocking::Synthesizer::tts #[derive(Clone)] pub struct TtsOptions { pub enable_interrogative_upspeak: bool, @@ -47,7 +58,7 @@ impl Default for TtsOptions { } /// ハードウェアアクセラレーションモードを設定する設定値。 -#[derive(Default, Debug, PartialEq, Eq)] +#[derive(Default, Clone, Copy, Debug, PartialEq, Eq)] pub enum AccelerationMode { /// 実行環境に合った適切なハードウェアアクセラレーションモードを選択する。 #[default] @@ -58,51 +69,48 @@ pub enum AccelerationMode { Gpu, } -/// [`blocking::Synthesizer::new`]および[`tokio::Synthesizer::new`]のオプション。 +/// [`blocking::Synthesizer::new`]および[`nonblocking::Synthesizer::new`]のオプション。 /// /// [`blocking::Synthesizer::new`]: blocking::Synthesizer::new -/// [`tokio::Synthesizer::new`]: tokio::Synthesizer::new +/// [`nonblocking::Synthesizer::new`]: nonblocking::Synthesizer::new #[derive(Default)] pub struct InitializeOptions { pub acceleration_mode: AccelerationMode, pub cpu_num_threads: u16, } -pub(crate) type InferenceRuntimeImpl = Onnxruntime; - pub(crate) mod blocking { - // FIXME: ここのdocのコードブロックはasync版のものなので、`tokio`モジュールの方に移した上で、 - // (ブロッキング版をpublic APIにするならの話ではあるが)ブロッキング版はブロッキング版でコード例 - // を用意する - use std::io::{Cursor, Write as _}; use enum_map::enum_map; + use tracing::info; use crate::{ - engine::{create_kana, mora_to_text, MoraModel, OjtPhoneme}, + devices::{DeviceSpec, GpuSpec}, + engine::{create_kana, mora_to_text, Mora, OjtPhoneme}, error::ErrorRepr, infer::{ domains::{ - DecodeInput, DecodeOutput, InferenceDomainMap, PredictDurationInput, - PredictDurationOutput, PredictIntonationInput, PredictIntonationOutput, TalkDomain, - TalkOperation, + GenerateFullIntermediateInput, GenerateFullIntermediateOutput, InferenceDomainMap, + PredictDurationInput, PredictDurationOutput, PredictIntonationInput, + PredictIntonationOutput, RenderAudioSegmentInput, RenderAudioSegmentOutput, + TalkDomain, TalkOperation, }, - InferenceSessionOptions, + InferenceRuntime as _, InferenceSessionOptions, }, status::Status, text_analyzer::{KanaAnalyzer, OpenJTalkAnalyzer, TextAnalyzer}, - AccentPhraseModel, AudioQueryModel, FullcontextExtractor, Result, StyleId, - SupportedDevices, SynthesisOptions, VoiceModelId, VoiceModelMeta, + AccentPhrase, AudioQuery, FullcontextExtractor, Result, StyleId, SynthesisOptions, + VoiceModelId, VoiceModelMeta, }; - use super::{AccelerationMode, InferenceRuntimeImpl, InitializeOptions, TtsOptions}; + use super::{AccelerationMode, InitializeOptions, TtsOptions}; const DEFAULT_SAMPLING_RATE: u32 = 24000; /// 音声シンセサイザ。 pub struct Synthesizer { - pub(super) status: Status, + pub(super) status: Status, open_jtalk_analyzer: OpenJTalkAnalyzer, kana_analyzer: KanaAnalyzer, use_gpu: bool, @@ -113,23 +121,29 @@ pub(crate) mod blocking { /// /// # Example /// - #[cfg_attr(windows, doc = "```no_run")] // https://github.com/VOICEVOX/voicevox_core/issues/537 - #[cfg_attr(not(windows), doc = "```")] - /// # #[tokio::main] - /// # async fn main() -> anyhow::Result<()> { - /// # use test_util::OPEN_JTALK_DIC_DIR; + #[cfg_attr(feature = "load-onnxruntime", doc = "```")] + #[cfg_attr(not(feature = "load-onnxruntime"), doc = "```compile_fail")] + /// # fn main() -> anyhow::Result<()> { + /// # use test_util::{ONNXRUNTIME_DYLIB_PATH, OPEN_JTALK_DIC_DIR}; /// # /// # const ACCELERATION_MODE: AccelerationMode = AccelerationMode::Cpu; /// # /// use std::sync::Arc; /// /// use voicevox_core::{ - /// tokio::{OpenJtalk, Synthesizer}, + /// blocking::{Onnxruntime, OpenJtalk, Synthesizer}, /// AccelerationMode, InitializeOptions, /// }; /// + /// # if cfg!(windows) { + /// # // Windows\System32\onnxruntime.dllを回避 + /// # voicevox_core::blocking::Onnxruntime::load_once() + /// # .filename(test_util::ONNXRUNTIME_DYLIB_PATH) + /// # .exec()?; + /// # } /// let mut syntesizer = Synthesizer::new( - /// Arc::new(OpenJtalk::new(OPEN_JTALK_DIC_DIR).await.unwrap()), + /// Onnxruntime::load_once().exec()?, + /// Arc::new(OpenJtalk::new(OPEN_JTALK_DIC_DIR).unwrap()), /// &InitializeOptions { /// acceleration_mode: ACCELERATION_MODE, /// ..Default::default() @@ -139,60 +153,77 @@ pub(crate) mod blocking { /// # Ok(()) /// # } /// ``` - pub fn new(open_jtalk: O, options: &InitializeOptions) -> Result { + pub fn new( + onnxruntime: &'static crate::blocking::Onnxruntime, + open_jtalk: O, + options: &InitializeOptions, + ) -> Result { #[cfg(windows)] list_windows_video_cards(); - let use_gpu = match options.acceleration_mode { - AccelerationMode::Auto => { - let supported_devices = SupportedDevices::create()?; + let test_gpus = || { + info!("GPUをテストします:"); + let availabilities = crate::devices::test_gpus( + GpuSpec::defaults(), + crate::blocking::Onnxruntime::DISPLAY_NAME, + onnxruntime.supported_devices()?, + |gpu| onnxruntime.test_gpu(gpu), + ); + for line in availabilities.to_string().lines() { + info!(" {line}"); + } + crate::Result::Ok(availabilities) + }; - if cfg!(feature = "directml") { - *supported_devices.dml() - } else { - *supported_devices.cuda() + let device_for_heavy = match options.acceleration_mode { + AccelerationMode::Auto => match *test_gpus()?.oks() { + [] => DeviceSpec::Cpu, + [gpu, ..] => DeviceSpec::Gpu(gpu), + }, + AccelerationMode::Cpu => DeviceSpec::Cpu, + AccelerationMode::Gpu => { + let availabilities = test_gpus()?; + match *availabilities.oks() { + [] => return Err(ErrorRepr::GpuSupport(availabilities).into()), + [gpu, ..] => DeviceSpec::Gpu(gpu), } } - AccelerationMode::Cpu => false, - AccelerationMode::Gpu => true, }; - if use_gpu && !can_support_gpu_feature()? { - return Err(ErrorRepr::GpuSupport.into()); - } + info!("{device_for_heavy}を利用します"); // 軽いモデルはこちらを使う let light_session_options = - InferenceSessionOptions::new(options.cpu_num_threads, false); + InferenceSessionOptions::new(options.cpu_num_threads, DeviceSpec::Cpu); // 重いモデルはこちらを使う let heavy_session_options = - InferenceSessionOptions::new(options.cpu_num_threads, use_gpu); - - let status = Status::new(InferenceDomainMap { - talk: enum_map! { - TalkOperation::PredictDuration - | TalkOperation::PredictIntonation => light_session_options, - TalkOperation::Decode => heavy_session_options, + InferenceSessionOptions::new(options.cpu_num_threads, device_for_heavy); + + let status = Status::new( + onnxruntime, + InferenceDomainMap { + talk: enum_map! { + TalkOperation::PredictDuration + | TalkOperation::PredictIntonation + | TalkOperation::GenerateFullIntermediate => light_session_options, + TalkOperation::RenderAudioSegment => heavy_session_options, + }, }, - }); + ); + + let use_gpu = matches!(device_for_heavy, DeviceSpec::Gpu(_)); - return Ok(Self { + Ok(Self { status, open_jtalk_analyzer: OpenJTalkAnalyzer::new(open_jtalk), kana_analyzer: KanaAnalyzer, use_gpu, - }); - - fn can_support_gpu_feature() -> Result { - let supported_devices = SupportedDevices::create()?; + }) + } - if cfg!(feature = "directml") { - Ok(*supported_devices.dml()) - } else { - Ok(*supported_devices.cuda()) - } - } + pub fn onnxruntime(&self) -> &'static crate::blocking::Onnxruntime { + self.status.rt } /// ハードウェアアクセラレーションがGPUモードか判定する。 @@ -201,18 +232,18 @@ pub(crate) mod blocking { } /// 音声モデルを読み込む。 - pub fn load_voice_model(&self, model: &crate::blocking::VoiceModel) -> Result<()> { + pub fn load_voice_model(&self, model: &crate::blocking::VoiceModelFile) -> Result<()> { let model_bytes = &model.read_inference_models()?; self.status.insert_model(model.header(), model_bytes) } /// 音声モデルの読み込みを解除する。 - pub fn unload_voice_model(&self, voice_model_id: &VoiceModelId) -> Result<()> { + pub fn unload_voice_model(&self, voice_model_id: VoiceModelId) -> Result<()> { self.status.unload_model(voice_model_id) } /// 指定したIDの音声モデルが読み込まれているか判定する。 - pub fn is_loaded_voice_model(&self, voice_model_id: &VoiceModelId) -> bool { + pub fn is_loaded_voice_model(&self, voice_model_id: VoiceModelId) -> bool { self.status.is_loaded_model(voice_model_id) } @@ -229,42 +260,48 @@ pub(crate) mod blocking { /// AudioQueryから音声合成を行う。 pub fn synthesis( &self, - audio_query: &AudioQueryModel, + audio_query: &AudioQuery, style_id: StyleId, options: &SynthesisOptions, ) -> Result> { - let speed_scale = *audio_query.speed_scale(); - let pitch_scale = *audio_query.pitch_scale(); - let intonation_scale = *audio_query.intonation_scale(); - let pre_phoneme_length = *audio_query.pre_phoneme_length(); - let post_phoneme_length = *audio_query.post_phoneme_length(); + let AudioQuery { + accent_phrases, + speed_scale, + pitch_scale, + intonation_scale, + pre_phoneme_length, + post_phoneme_length, + .. + } = audio_query; let accent_phrases = if options.enable_interrogative_upspeak { - adjust_interrogative_accent_phrases(audio_query.accent_phrases().as_slice()) + &adjust_interrogative_accent_phrases(accent_phrases) } else { - audio_query.accent_phrases().clone() + accent_phrases }; - let (flatten_moras, phoneme_data_list) = initial_process(&accent_phrases); + let (flatten_moras, phoneme_data_list) = initial_process(accent_phrases); - let mut phoneme_length_list = vec![pre_phoneme_length]; + let mut phoneme_length_list = vec![*pre_phoneme_length]; let mut f0_list = vec![0.]; let mut voiced_list = vec![false]; { let mut sum_of_f0_bigger_than_zero = 0.; let mut count_of_f0_bigger_than_zero = 0; - for mora in flatten_moras { - let consonant_length = *mora.consonant_length(); - let vowel_length = *mora.vowel_length(); - let pitch = *mora.pitch(); - + for Mora { + consonant_length, + vowel_length, + pitch, + .. + } in flatten_moras + { if let Some(consonant_length) = consonant_length { phoneme_length_list.push(consonant_length); } phoneme_length_list.push(vowel_length); - let f0_single = pitch * 2.0_f32.powf(pitch_scale); + let f0_single = pitch * 2.0_f32.powf(*pitch_scale); f0_list.push(f0_single); let bigger_than_zero = f0_single > 0.; @@ -275,7 +312,7 @@ pub(crate) mod blocking { count_of_f0_bigger_than_zero += 1; } } - phoneme_length_list.push(post_phoneme_length); + phoneme_length_list.push(*post_phoneme_length); f0_list.push(0.); voiced_list.push(false); let mean_f0 = sum_of_f0_bigger_than_zero / (count_of_f0_bigger_than_zero as f32); @@ -291,7 +328,7 @@ pub(crate) mod blocking { let (_, _, vowel_indexes) = split_mora(&phoneme_data_list); - let mut phoneme: Vec> = Vec::new(); + let mut phoneme = Vec::new(); let mut f0: Vec = Vec::new(); { const RATE: f32 = 24000. / 256.; @@ -308,7 +345,7 @@ pub(crate) mod blocking { let phoneme_id = phoneme_data_list[i].phoneme_id(); for _ in 0..phoneme_length { - let mut phonemes_vec = vec![0.; OjtPhoneme::num_phoneme()]; + let mut phonemes_vec = [0.; OjtPhoneme::num_phoneme()]; phonemes_vec[phoneme_id as usize] = 1.; phoneme.push(phonemes_vec) } @@ -325,41 +362,38 @@ pub(crate) mod blocking { } } - // 2次元のvectorを1次元に変換し、アドレスを連続させる - let flatten_phoneme = phoneme.into_iter().flatten().collect::>(); - let wave = &self.decode( f0.len(), OjtPhoneme::num_phoneme(), &f0, - &flatten_phoneme, + phoneme.as_flattened(), style_id, )?; return Ok(to_wav(wave, audio_query)); fn adjust_interrogative_accent_phrases( - accent_phrases: &[AccentPhraseModel], - ) -> Vec { + accent_phrases: &[AccentPhrase], + ) -> Vec { accent_phrases .iter() - .map(|accent_phrase| { - AccentPhraseModel::new( - adjust_interrogative_moras(accent_phrase), - *accent_phrase.accent(), - accent_phrase.pause_mora().clone(), - *accent_phrase.is_interrogative(), - ) + .map(|accent_phrase| AccentPhrase { + moras: adjust_interrogative_moras(accent_phrase), + ..accent_phrase.clone() }) .collect() } - fn adjust_interrogative_moras(accent_phrase: &AccentPhraseModel) -> Vec { - let moras = accent_phrase.moras(); - if *accent_phrase.is_interrogative() && !moras.is_empty() { + fn adjust_interrogative_moras( + AccentPhrase { + moras, + is_interrogative, + .. + }: &AccentPhrase, + ) -> Vec { + if *is_interrogative && !moras.is_empty() { let last_mora = moras.last().unwrap(); - let last_mora_pitch = *last_mora.pitch(); - if last_mora_pitch != 0.0 { - let mut new_moras: Vec = Vec::with_capacity(moras.len() + 1); + if last_mora.pitch != 0.0 { + let mut new_moras: Vec = Vec::with_capacity(moras.len() + 1); new_moras.extend_from_slice(moras.as_slice()); let interrogative_mora = make_interrogative_mora(last_mora); new_moras.push(interrogative_mora); @@ -369,28 +403,32 @@ pub(crate) mod blocking { moras.clone() } - fn make_interrogative_mora(last_mora: &MoraModel) -> MoraModel { + fn make_interrogative_mora(last_mora: &Mora) -> Mora { const FIX_VOWEL_LENGTH: f32 = 0.15; const ADJUST_PITCH: f32 = 0.3; const MAX_PITCH: f32 = 6.5; - let pitch = (*last_mora.pitch() + ADJUST_PITCH).min(MAX_PITCH); + let pitch = (last_mora.pitch + ADJUST_PITCH).min(MAX_PITCH); - MoraModel::new( - mora_to_text(None, last_mora.vowel()), - None, - None, - last_mora.vowel().clone(), - FIX_VOWEL_LENGTH, + Mora { + text: mora_to_text(None, &last_mora.vowel), + consonant: None, + consonant_length: None, + vowel: last_mora.vowel.clone(), + vowel_length: FIX_VOWEL_LENGTH, pitch, - ) + } } - fn to_wav(wave: &[f32], audio_query: &AudioQueryModel) -> Vec { - let volume_scale = *audio_query.volume_scale(); - let output_stereo = *audio_query.output_stereo(); - let output_sampling_rate = *audio_query.output_sampling_rate(); - + fn to_wav( + wave: &[f32], + &AudioQuery { + volume_scale, + output_sampling_rate, + output_stereo, + .. + }: &AudioQuery, + ) -> Vec { // TODO: 44.1kHzなどの対応 let num_channels: u16 = if output_stereo { 2 } else { 1 }; @@ -437,21 +475,24 @@ pub(crate) mod blocking { /// /// # Example /// - #[cfg_attr(windows, doc = "```no_run")] // https://github.com/VOICEVOX/voicevox_core/issues/537 - #[cfg_attr(not(windows), doc = "```")] - /// # #[tokio::main] - /// # async fn main() -> anyhow::Result<()> { + /// ``` + /// # fn main() -> anyhow::Result<()> { + /// # use pollster::FutureExt as _; + /// # use voicevox_core::__internal::doctest_fixtures::IntoBlocking as _; + /// # /// # let synthesizer = /// # voicevox_core::__internal::doctest_fixtures::synthesizer_with_sample_voice_model( + /// # test_util::SAMPLE_VOICE_MODEL_FILE_PATH, + /// # test_util::ONNXRUNTIME_DYLIB_PATH, /// # test_util::OPEN_JTALK_DIC_DIR, /// # ) - /// # .await?; + /// # .block_on()? + /// # .into_blocking(); /// # /// use voicevox_core::StyleId; /// /// let accent_phrases = synthesizer - /// .create_accent_phrases_from_kana("コンニチワ'", StyleId::new(302)) - /// .await?; + /// .create_accent_phrases_from_kana("コンニチワ'", StyleId::new(302))?; /// # /// # Ok(()) /// # } @@ -460,7 +501,7 @@ pub(crate) mod blocking { &self, kana: &str, style_id: StyleId, - ) -> Result> { + ) -> Result> { let accent_phrases = self.kana_analyzer.analyze(kana)?; self.replace_mora_data(&accent_phrases, style_id) } @@ -468,9 +509,9 @@ pub(crate) mod blocking { /// AccentPhraseの配列の音高・音素長を、特定の声で生成しなおす。 pub fn replace_mora_data( &self, - accent_phrases: &[AccentPhraseModel], + accent_phrases: &[AccentPhrase], style_id: StyleId, - ) -> Result> { + ) -> Result> { let accent_phrases = self.replace_phoneme_length(accent_phrases, style_id)?; self.replace_mora_pitch(&accent_phrases, style_id) } @@ -478,9 +519,9 @@ pub(crate) mod blocking { /// AccentPhraseの配列の音素長を、特定の声で生成しなおす。 pub fn replace_phoneme_length( &self, - accent_phrases: &[AccentPhraseModel], + accent_phrases: &[AccentPhrase], style_id: StyleId, - ) -> Result> { + ) -> Result> { let (_, phoneme_data_list) = initial_process(accent_phrases); let (_, _, vowel_indexes_data) = split_mora(&phoneme_data_list); @@ -494,41 +535,32 @@ pub(crate) mod blocking { let mut index = 0; let new_accent_phrases = accent_phrases .iter() - .map(|accent_phrase| { - AccentPhraseModel::new( - accent_phrase - .moras() - .iter() - .map(|mora| { - let new_mora = MoraModel::new( - mora.text().clone(), - mora.consonant().clone(), - mora.consonant().as_ref().map(|_| { - phoneme_length[vowel_indexes_data[index + 1] as usize - 1] - }), - mora.vowel().clone(), - phoneme_length[vowel_indexes_data[index + 1] as usize], - *mora.pitch(), - ); - index += 1; - new_mora - }) - .collect(), - *accent_phrase.accent(), - accent_phrase.pause_mora().as_ref().map(|pause_mora| { - let new_pause_mora = MoraModel::new( - pause_mora.text().clone(), - pause_mora.consonant().clone(), - *pause_mora.consonant_length(), - pause_mora.vowel().clone(), - phoneme_length[vowel_indexes_data[index + 1] as usize], - *pause_mora.pitch(), - ); + .map(|accent_phrase| AccentPhrase { + moras: accent_phrase + .moras + .iter() + .map(|mora| { + let new_mora = Mora { + consonant_length: mora.consonant.as_ref().map(|_| { + phoneme_length[vowel_indexes_data[index + 1] as usize - 1] + }), + vowel_length: phoneme_length + [vowel_indexes_data[index + 1] as usize], + ..mora.clone() + }; index += 1; - new_pause_mora - }), - *accent_phrase.is_interrogative(), - ) + new_mora + }) + .collect(), + pause_mora: accent_phrase.pause_mora.as_ref().map(|pause_mora| { + let new_pause_mora = Mora { + vowel_length: phoneme_length[vowel_indexes_data[index + 1] as usize], + ..pause_mora.clone() + }; + index += 1; + new_pause_mora + }), + ..accent_phrase.clone() }) .collect(); @@ -538,9 +570,9 @@ pub(crate) mod blocking { /// AccentPhraseの配列の音高を、特定の声で生成しなおす。 pub fn replace_mora_pitch( &self, - accent_phrases: &[AccentPhraseModel], + accent_phrases: &[AccentPhrase], style_id: StyleId, - ) -> Result> { + ) -> Result> { let (_, phoneme_data_list) = initial_process(accent_phrases); let mut base_start_accent_list = vec![0]; @@ -548,10 +580,10 @@ pub(crate) mod blocking { let mut base_start_accent_phrase_list = vec![0]; let mut base_end_accent_phrase_list = vec![0]; for accent_phrase in accent_phrases { - let mut accent = usize::from(*accent_phrase.accent() != 1); + let mut accent = usize::from(accent_phrase.accent != 1); create_one_accent_list(&mut base_start_accent_list, accent_phrase, accent as i32); - accent = *accent_phrase.accent() - 1; + accent = accent_phrase.accent - 1; create_one_accent_list(&mut base_end_accent_list, accent_phrase, accent as i32); create_one_accent_list(&mut base_start_accent_phrase_list, accent_phrase, 0); create_one_accent_list(&mut base_end_accent_phrase_list, accent_phrase, -1); @@ -610,39 +642,28 @@ pub(crate) mod blocking { let mut index = 0; let new_accent_phrases = accent_phrases .iter() - .map(|accent_phrase| { - AccentPhraseModel::new( - accent_phrase - .moras() - .iter() - .map(|mora| { - let new_mora = MoraModel::new( - mora.text().clone(), - mora.consonant().clone(), - *mora.consonant_length(), - mora.vowel().clone(), - *mora.vowel_length(), - f0_list[index + 1], - ); - index += 1; - new_mora - }) - .collect(), - *accent_phrase.accent(), - accent_phrase.pause_mora().as_ref().map(|pause_mora| { - let new_pause_mora = MoraModel::new( - pause_mora.text().clone(), - pause_mora.consonant().clone(), - *pause_mora.consonant_length(), - pause_mora.vowel().clone(), - *pause_mora.vowel_length(), - f0_list[index + 1], - ); + .map(|accent_phrase| AccentPhrase { + moras: accent_phrase + .moras + .iter() + .map(|mora| { + let new_mora = Mora { + pitch: f0_list[index + 1], + ..mora.clone() + }; index += 1; - new_pause_mora - }), - *accent_phrase.is_interrogative(), - ) + new_mora + }) + .collect(), + pause_mora: accent_phrase.pause_mora.as_ref().map(|pause_mora| { + let new_pause_mora = Mora { + pitch: f0_list[index + 1], + ..pause_mora.clone() + }; + index += 1; + new_pause_mora + }), + ..accent_phrase.clone() }) .collect(); @@ -650,22 +671,21 @@ pub(crate) mod blocking { fn create_one_accent_list( accent_list: &mut Vec, - accent_phrase: &AccentPhraseModel, + accent_phrase: &AccentPhrase, point: i32, ) { let mut one_accent_list: Vec = Vec::new(); - for (i, mora) in accent_phrase.moras().iter().enumerate() { + for (i, mora) in accent_phrase.moras.iter().enumerate() { let value = (i as i32 == point - || (point < 0 - && i == (accent_phrase.moras().len() as i32 + point) as usize)) + || (point < 0 && i == (accent_phrase.moras.len() as i32 + point) as usize)) .into(); one_accent_list.push(value); - if mora.consonant().is_some() { + if mora.consonant.is_some() { one_accent_list.push(value); } } - if accent_phrase.pause_mora().is_some() { + if accent_phrase.pause_mora.is_some() { one_accent_list.push(0); } accent_list.extend(one_accent_list) @@ -676,35 +696,32 @@ pub(crate) mod blocking { /// /// # Example /// - #[cfg_attr(windows, doc = "```no_run")] // https://github.com/VOICEVOX/voicevox_core/issues/537 - #[cfg_attr(not(windows), doc = "```")] - /// # #[tokio::main] - /// # async fn main() -> anyhow::Result<()> { + /// ``` + /// # fn main() -> anyhow::Result<()> { + /// # use pollster::FutureExt as _; + /// # use voicevox_core::__internal::doctest_fixtures::IntoBlocking as _; + /// # /// # let synthesizer = /// # voicevox_core::__internal::doctest_fixtures::synthesizer_with_sample_voice_model( + /// # test_util::SAMPLE_VOICE_MODEL_FILE_PATH, + /// # test_util::ONNXRUNTIME_DYLIB_PATH, /// # test_util::OPEN_JTALK_DIC_DIR, /// # ) - /// # .await?; + /// # .block_on()? + /// # .into_blocking(); /// # /// use voicevox_core::StyleId; /// - /// let audio_query = synthesizer - /// .audio_query_from_kana("コンニチワ'", StyleId::new(302)) - /// .await?; + /// let audio_query = synthesizer.audio_query_from_kana("コンニチワ'", StyleId::new(302))?; /// # /// # Ok(()) /// # } /// ``` /// - /// [AudioQuery]: crate::AudioQueryModel - pub fn audio_query_from_kana( - &self, - kana: &str, - style_id: StyleId, - ) -> Result { + /// [AudioQuery]: crate::AudioQuery + pub fn audio_query_from_kana(&self, kana: &str, style_id: StyleId) -> Result { let accent_phrases = self.create_accent_phrases_from_kana(kana, style_id)?; - Ok(AudioQueryModel::from_accent_phrases(accent_phrases) - .with_kana(Some(kana.to_owned()))) + Ok(AudioQuery::from_accent_phrases(accent_phrases).with_kana(Some(kana.to_owned()))) } /// AquesTalk風記法から音声合成を行う。 @@ -724,21 +741,23 @@ pub(crate) mod blocking { /// /// # Example /// - #[cfg_attr(windows, doc = "```no_run")] // https://github.com/VOICEVOX/voicevox_core/issues/537 - #[cfg_attr(not(windows), doc = "```")] - /// # #[tokio::main] - /// # async fn main() -> anyhow::Result<()> { + /// ``` + /// # fn main() -> anyhow::Result<()> { + /// # use pollster::FutureExt as _; + /// # use voicevox_core::__internal::doctest_fixtures::IntoBlocking as _; + /// # /// # let synthesizer = /// # voicevox_core::__internal::doctest_fixtures::synthesizer_with_sample_voice_model( + /// # test_util::SAMPLE_VOICE_MODEL_FILE_PATH, + /// # test_util::ONNXRUNTIME_DYLIB_PATH, /// # test_util::OPEN_JTALK_DIC_DIR, /// # ) - /// # .await?; + /// # .block_on()? + /// # .into_blocking(); /// # /// use voicevox_core::StyleId; /// - /// let accent_phrases = synthesizer - /// .create_accent_phrases("こんにちは", StyleId::new(302)) - /// .await?; + /// let accent_phrases = synthesizer.create_accent_phrases("こんにちは", StyleId::new(302))?; /// # /// # Ok(()) /// # } @@ -747,7 +766,7 @@ pub(crate) mod blocking { &self, text: &str, style_id: StyleId, - ) -> Result> { + ) -> Result> { let accent_phrases = self.open_jtalk_analyzer.analyze(text)?; self.replace_mora_data(&accent_phrases, style_id) } @@ -756,30 +775,32 @@ pub(crate) mod blocking { /// /// # Examples /// - #[cfg_attr(windows, doc = "```no_run")] // https://github.com/VOICEVOX/voicevox_core/issues/537 - #[cfg_attr(not(windows), doc = "```")] - /// # #[tokio::main] - /// # async fn main() -> anyhow::Result<()> { + /// ``` + /// # fn main() -> anyhow::Result<()> { + /// # use pollster::FutureExt as _; + /// # use voicevox_core::__internal::doctest_fixtures::IntoBlocking as _; + /// # /// # let synthesizer = /// # voicevox_core::__internal::doctest_fixtures::synthesizer_with_sample_voice_model( + /// # test_util::SAMPLE_VOICE_MODEL_FILE_PATH, + /// # test_util::ONNXRUNTIME_DYLIB_PATH, /// # test_util::OPEN_JTALK_DIC_DIR, /// # ) - /// # .await?; + /// # .block_on()? + /// # .into_blocking(); /// # /// use voicevox_core::StyleId; /// - /// let audio_query = synthesizer - /// .audio_query("こんにちは", StyleId::new(302)) - /// .await?; + /// let audio_query = synthesizer.audio_query("こんにちは", StyleId::new(302))?; /// # /// # Ok(()) /// # } /// ``` /// - /// [AudioQuery]: crate::AudioQueryModel - pub fn audio_query(&self, text: &str, style_id: StyleId) -> Result { + /// [AudioQuery]: crate::AudioQuery + pub fn audio_query(&self, text: &str, style_id: StyleId) -> Result { let accent_phrases = self.create_accent_phrases(text, style_id)?; - Ok(AudioQueryModel::from_accent_phrases(accent_phrases)) + Ok(AudioQuery::from_accent_phrases(accent_phrases)) } /// 日本語のテキストから音声合成を行う。 @@ -802,7 +823,11 @@ pub(crate) mod blocking { /// # Performance /// /// CPU-boundな操作であるため、非同期ランタイム上では直接実行されるべきではない。 - #[allow(clippy::too_many_arguments)] + #[expect( + clippy::too_many_arguments, + reason = "compatible_engineでの`predict_intonation`の形を考えると、ここの引数を構造体に\ + まとめたりしても可読性に寄与しない" + )] fn predict_intonation( &self, length: usize, @@ -832,15 +857,15 @@ pub(crate) mod blocking { impl PerformInference for self::Synthesizer { fn predict_duration(&self, phoneme_vector: &[i64], style_id: StyleId) -> Result> { - let (model_id, model_inner_id) = self.status.ids_for::(style_id)?; + let (model_id, inner_voice_id) = self.status.ids_for::(style_id)?; let PredictDurationOutput { phoneme_length: output, } = self.status.run_session( - &model_id, + model_id, PredictDurationInput { phoneme_list: ndarray::arr1(phoneme_vector), - speaker_id: ndarray::arr1(&[model_inner_id.raw_id().into()]), + speaker_id: ndarray::arr1(&[inner_voice_id.raw_id().into()]), }, )?; let mut output = output.into_raw_vec(); @@ -867,10 +892,10 @@ pub(crate) mod blocking { end_accent_phrase_vector: &[i64], style_id: StyleId, ) -> Result> { - let (model_id, model_inner_id) = self.status.ids_for::(style_id)?; + let (model_id, inner_voice_id) = self.status.ids_for::(style_id)?; let PredictIntonationOutput { f0_list: output } = self.status.run_session( - &model_id, + model_id, PredictIntonationInput { length: ndarray::arr0(length as i64), vowel_phoneme_list: ndarray::arr1(vowel_phoneme_vector), @@ -879,7 +904,7 @@ pub(crate) mod blocking { end_accent_list: ndarray::arr1(end_accent_vector), start_accent_phrase_list: ndarray::arr1(start_accent_phrase_vector), end_accent_phrase_list: ndarray::arr1(end_accent_phrase_vector), - speaker_id: ndarray::arr1(&[model_inner_id.raw_id().into()]), + speaker_id: ndarray::arr1(&[inner_voice_id.raw_id().into()]), }, )?; @@ -894,7 +919,7 @@ pub(crate) mod blocking { phoneme_vector: &[f32], style_id: StyleId, ) -> Result> { - let (model_id, model_inner_id) = self.status.ids_for::(style_id)?; + let (model_id, inner_voice_id) = self.status.ids_for::(style_id)?; // 音が途切れてしまうのを避けるworkaround処理が入っている // TODO: 改善したらここのpadding処理を取り除く @@ -912,19 +937,23 @@ pub(crate) mod blocking { padding_size, ); - let DecodeOutput { wave: output } = self.status.run_session( - &model_id, - DecodeInput { + let GenerateFullIntermediateOutput { spec } = self.status.run_session( + model_id, + GenerateFullIntermediateInput { f0: ndarray::arr1(&f0_with_padding) .into_shape([length_with_padding, 1]) .unwrap(), phoneme: ndarray::arr1(&phoneme_with_padding) .into_shape([length_with_padding, phoneme_size]) .unwrap(), - speaker_id: ndarray::arr1(&[model_inner_id.raw_id().into()]), + speaker_id: ndarray::arr1(&[inner_voice_id.raw_id().into()]), }, )?; + let RenderAudioSegmentOutput { wave: output } = self + .status + .run_session(model_id, RenderAudioSegmentInput { spec })?; + return Ok(trim_padding_from_output( output.into_raw_vec(), padding_size, @@ -991,13 +1020,13 @@ pub(crate) mod blocking { CreateDXGIFactory, IDXGIFactory, DXGI_ADAPTER_DESC, DXGI_ERROR_NOT_FOUND, }; - info!("検出されたGPU (DirectMLには1番目のGPUが使われます):"); + info!("検出されたGPU (DirectMLにはGPU 0が使われます):"); match list_windows_video_cards() { Ok(descs) => { - for desc in descs { + for (device_id, desc) in descs.into_iter().enumerate() { let description = OsString::from_wide(trim_nul(&desc.Description)); let vram = humansize::format_size(desc.DedicatedVideoMemory, BINARY); - info!(" - {description:?} ({vram})"); + info!(" GPU {device_id}: {description:?} ({vram})"); } } Err(err) => error!("{err}"), @@ -1018,15 +1047,15 @@ pub(crate) mod blocking { } } - fn initial_process(accent_phrases: &[AccentPhraseModel]) -> (Vec, Vec) { + fn initial_process(accent_phrases: &[AccentPhrase]) -> (Vec, Vec) { let flatten_moras = to_flatten_moras(accent_phrases); let mut phoneme_strings = vec!["pau".to_string()]; for mora in flatten_moras.iter() { - if let Some(consonant) = mora.consonant() { + if let Some(consonant) = &mora.consonant { phoneme_strings.push(consonant.clone()) } - phoneme_strings.push(mora.vowel().clone()); + phoneme_strings.push(mora.vowel.clone()); } phoneme_strings.push("pau".to_string()); @@ -1034,15 +1063,17 @@ pub(crate) mod blocking { return (flatten_moras, phoneme_data_list); - fn to_flatten_moras(accent_phrases: &[AccentPhraseModel]) -> Vec { + fn to_flatten_moras(accent_phrases: &[AccentPhrase]) -> Vec { let mut flatten_moras = Vec::new(); - for accent_phrase in accent_phrases { - let moras = accent_phrase.moras(); + for AccentPhrase { + moras, pause_mora, .. + } in accent_phrases + { for mora in moras { flatten_moras.push(mora.clone()); } - if let Some(pause_mora) = accent_phrase.pause_mora() { + if let Some(pause_mora) = pause_mora { flatten_moras.push(pause_mora.clone()); } } @@ -1054,8 +1085,9 @@ pub(crate) mod blocking { OjtPhoneme::convert( phoneme_str_list .iter() - .enumerate() - .map(|(i, s)| OjtPhoneme::new(s.as_ref().to_string(), i as f32, i as f32 + 1.)) + .map(AsRef::as_ref) + .map(ToOwned::to_owned) + .map(OjtPhoneme::new) .collect::>() .as_slice(), ) @@ -1096,61 +1128,121 @@ pub(crate) mod blocking { (consonant_phoneme_list, vowel_phoneme_list, vowel_indexes) } - impl AudioQueryModel { - fn from_accent_phrases(accent_phrases: Vec) -> Self { + impl AudioQuery { + fn from_accent_phrases(accent_phrases: Vec) -> Self { let kana = create_kana(&accent_phrases); - Self::new( + Self { accent_phrases, - 1., - 0., - 1., - 1., - 0.1, - 0.1, - DEFAULT_SAMPLING_RATE, - false, - Some(kana), - ) + speed_scale: 1., + pitch_scale: 0., + intonation_scale: 1., + volume_scale: 1., + pre_phoneme_length: 0.1, + post_phoneme_length: 0.1, + output_sampling_rate: DEFAULT_SAMPLING_RATE, + output_stereo: false, + kana: Some(kana), + } } } } -pub(crate) mod tokio { +pub(crate) mod nonblocking { use std::sync::Arc; + use easy_ext::ext; + use crate::{ - AccentPhraseModel, AudioQueryModel, FullcontextExtractor, Result, StyleId, - SynthesisOptions, VoiceModelId, VoiceModelMeta, + AccentPhrase, AudioQuery, FullcontextExtractor, Result, StyleId, SynthesisOptions, + VoiceModelId, VoiceModelMeta, }; use super::{InitializeOptions, TtsOptions}; /// 音声シンセサイザ。 + /// + /// # Performance + /// + /// [blocking]クレートにより動いている。詳しくは[`nonblocking`モジュールのドキュメント]を参照。 + /// + /// [blocking]: https://docs.rs/crate/blocking + /// [`nonblocking`モジュールのドキュメント]: crate::nonblocking #[derive(Clone)] pub struct Synthesizer(pub(super) Arc>); - // FIXME: docを書く impl self::Synthesizer { - pub fn new(open_jtalk: O, options: &InitializeOptions) -> Result { - super::blocking::Synthesizer::new(open_jtalk, options) + /// `Synthesizer`をコンストラクトする。 + /// + /// # Example + /// + #[cfg_attr(feature = "load-onnxruntime", doc = "```")] + #[cfg_attr(not(feature = "load-onnxruntime"), doc = "```compile_fail")] + /// # #[pollster::main] + /// # async fn main() -> anyhow::Result<()> { + /// # use test_util::{ONNXRUNTIME_DYLIB_PATH, OPEN_JTALK_DIC_DIR}; + /// # + /// # const ACCELERATION_MODE: AccelerationMode = AccelerationMode::Cpu; + /// # + /// use std::sync::Arc; + /// + /// use voicevox_core::{ + /// nonblocking::{Onnxruntime, OpenJtalk, Synthesizer}, + /// AccelerationMode, InitializeOptions, + /// }; + /// + /// # if cfg!(windows) { + /// # // Windows\System32\onnxruntime.dllを回避 + /// # voicevox_core::blocking::Onnxruntime::load_once() + /// # .filename(test_util::ONNXRUNTIME_DYLIB_PATH) + /// # .exec()?; + /// # } + /// let mut syntesizer = Synthesizer::new( + /// Onnxruntime::load_once().exec().await?, + /// Arc::new(OpenJtalk::new(OPEN_JTALK_DIC_DIR).await.unwrap()), + /// &InitializeOptions { + /// acceleration_mode: ACCELERATION_MODE, + /// ..Default::default() + /// }, + /// )?; + /// # + /// # Ok(()) + /// # } + /// ``` + pub fn new( + onnxruntime: &'static crate::nonblocking::Onnxruntime, + open_jtalk: O, + options: &InitializeOptions, + ) -> Result { + super::blocking::Synthesizer::new(&onnxruntime.0, open_jtalk, options) .map(Into::into) .map(Self) } + pub fn onnxruntime(&self) -> &'static crate::nonblocking::Onnxruntime { + crate::nonblocking::Onnxruntime::from_blocking(self.0.onnxruntime()) + } + + /// ハードウェアアクセラレーションがGPUモードか判定する。 pub fn is_gpu_mode(&self) -> bool { self.0.is_gpu_mode() } - pub async fn load_voice_model(&self, model: &crate::tokio::VoiceModel) -> Result<()> { + /// 音声モデルを読み込む。 + pub async fn load_voice_model( + &self, + model: &crate::nonblocking::VoiceModelFile, + ) -> Result<()> { let model_bytes = &model.read_inference_models().await?; self.0.status.insert_model(model.header(), model_bytes) } - pub fn unload_voice_model(&self, voice_model_id: &VoiceModelId) -> Result<()> { + /// 音声モデルの読み込みを解除する。 + pub fn unload_voice_model(&self, voice_model_id: VoiceModelId) -> Result<()> { self.0.unload_voice_model(voice_model_id) } - pub fn is_loaded_voice_model(&self, voice_model_id: &VoiceModelId) -> bool { + /// 指定したIDの音声モデルが読み込まれているか判定する。 + pub fn is_loaded_voice_model(&self, voice_model_id: VoiceModelId) -> bool { self.0.is_loaded_voice_model(voice_model_id) } @@ -1159,13 +1251,15 @@ pub(crate) mod tokio { self.0.is_loaded_model_by_style_id(style_id) } + /// 今読み込んでいる音声モデルのメタ情報を返す。 pub fn metas(&self) -> VoiceModelMeta { self.0.metas() } + /// AudioQueryから音声合成を行う。 pub async fn synthesis( &self, - audio_query: &AudioQueryModel, + audio_query: &AudioQuery, style_id: StyleId, options: &SynthesisOptions, ) -> Result> { @@ -1177,11 +1271,35 @@ pub(crate) mod tokio { .await } + /// AquesTalk風記法からAccentPhrase (アクセント句)の配列を生成する。 + /// + /// # Example + /// + /// ``` + /// # #[pollster::main] + /// # 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::ONNXRUNTIME_DYLIB_PATH, + /// # test_util::OPEN_JTALK_DIC_DIR, + /// # ) + /// # .await?; + /// # + /// use voicevox_core::StyleId; + /// + /// let accent_phrases = synthesizer + /// .create_accent_phrases_from_kana("コンニチワ'", StyleId::new(302)) + /// .await?; + /// # + /// # Ok(()) + /// # } + /// ``` pub async fn create_accent_phrases_from_kana( &self, kana: &str, style_id: StyleId, - ) -> Result> { + ) -> Result> { let blocking = self.0.clone(); let kana = kana.to_owned(); @@ -1189,11 +1307,12 @@ pub(crate) mod tokio { .await } + /// AccentPhraseの配列の音高・音素長を、特定の声で生成しなおす。 pub async fn replace_mora_data( &self, - accent_phrases: &[AccentPhraseModel], + accent_phrases: &[AccentPhrase], style_id: StyleId, - ) -> Result> { + ) -> Result> { let blocking = self.0.clone(); let accent_phrases = accent_phrases.to_owned(); @@ -1201,11 +1320,12 @@ pub(crate) mod tokio { .await } + /// AccentPhraseの配列の音素長を、特定の声で生成しなおす。 pub async fn replace_phoneme_length( &self, - accent_phrases: &[AccentPhraseModel], + accent_phrases: &[AccentPhrase], style_id: StyleId, - ) -> Result> { + ) -> Result> { let blocking = self.0.clone(); let accent_phrases = accent_phrases.to_owned(); @@ -1215,11 +1335,12 @@ pub(crate) mod tokio { .await } + /// AccentPhraseの配列の音高を、特定の声で生成しなおす。 pub async fn replace_mora_pitch( &self, - accent_phrases: &[AccentPhraseModel], + accent_phrases: &[AccentPhrase], style_id: StyleId, - ) -> Result> { + ) -> Result> { let blocking = self.0.clone(); let accent_phrases = accent_phrases.to_owned(); @@ -1227,17 +1348,44 @@ pub(crate) mod tokio { .await } + /// AquesTalk風記法から[AudioQuery]を生成する。 + /// + /// # Example + /// + /// ``` + /// # #[pollster::main] + /// # 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::ONNXRUNTIME_DYLIB_PATH, + /// # test_util::OPEN_JTALK_DIC_DIR, + /// # ) + /// # .await?; + /// # + /// use voicevox_core::StyleId; + /// + /// let audio_query = synthesizer + /// .audio_query_from_kana("コンニチワ'", StyleId::new(302)) + /// .await?; + /// # + /// # Ok(()) + /// # } + /// ``` + /// + /// [AudioQuery]: crate::AudioQuery pub async fn audio_query_from_kana( &self, kana: &str, style_id: StyleId, - ) -> Result { + ) -> Result { let blocking = self.0.clone(); let kana = kana.to_owned(); crate::task::asyncify(move || blocking.audio_query_from_kana(&kana, style_id)).await } + /// AquesTalk風記法から音声合成を行う。 pub async fn tts_from_kana( &self, kana: &str, @@ -1253,24 +1401,75 @@ pub(crate) mod tokio { } impl self::Synthesizer { + /// 日本語のテキストからAccentPhrase (アクセント句)の配列を生成する。 + /// + /// # Example + /// + /// ``` + /// # #[pollster::main] + /// # 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::ONNXRUNTIME_DYLIB_PATH, + /// # test_util::OPEN_JTALK_DIC_DIR, + /// # ) + /// # .await?; + /// # + /// use voicevox_core::StyleId; + /// + /// let accent_phrases = synthesizer + /// .create_accent_phrases("こんにちは", StyleId::new(302)) + /// .await?; + /// # + /// # Ok(()) + /// # } + /// ``` pub async fn create_accent_phrases( &self, text: &str, style_id: StyleId, - ) -> Result> { + ) -> Result> { let blocking = self.0.clone(); let text = text.to_owned(); crate::task::asyncify(move || blocking.create_accent_phrases(&text, style_id)).await } - pub async fn audio_query(&self, text: &str, style_id: StyleId) -> Result { + /// 日本語のテキストから[AudioQuery]を生成する。 + /// + /// # Examples + /// + /// ``` + /// # #[pollster::main] + /// # 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::ONNXRUNTIME_DYLIB_PATH, + /// # test_util::OPEN_JTALK_DIC_DIR, + /// # ) + /// # .await?; + /// # + /// use voicevox_core::StyleId; + /// + /// let audio_query = synthesizer + /// .audio_query("こんにちは", StyleId::new(302)) + /// .await?; + /// # + /// # Ok(()) + /// # } + /// ``` + /// + /// [AudioQuery]: crate::AudioQuery + pub async fn audio_query(&self, text: &str, style_id: StyleId) -> Result { let blocking = self.0.clone(); let text = text.to_owned(); crate::task::asyncify(move || blocking.audio_query(&text, style_id)).await } + /// 日本語のテキストから音声合成を行う。 pub async fn tts( &self, text: &str, @@ -1284,16 +1483,20 @@ pub(crate) mod tokio { crate::task::asyncify(move || blocking.tts(&text, style_id, &options)).await } } + + #[ext(IntoBlocking)] + impl self::Synthesizer { + pub fn into_blocking(self) -> Arc> { + self.0 + } + } } #[cfg(test)] 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, - }; + use crate::{engine::Mora, macros::tests::assert_debug_fmt_eq, AccentPhrase, Result, StyleId}; use ::test_util::OPEN_JTALK_DIC_DIR; use rstest::rstest; @@ -1301,7 +1504,10 @@ mod tests { #[case(Ok(()))] #[tokio::test] async fn load_model_works(#[case] expected_result_at_initialized: Result<()>) { - let syntesizer = super::tokio::Synthesizer::new( + let syntesizer = super::nonblocking::Synthesizer::new( + crate::nonblocking::Onnxruntime::from_test_util_data() + .await + .unwrap(), (), &InitializeOptions { acceleration_mode: AccelerationMode::Cpu, @@ -1311,7 +1517,7 @@ mod tests { .unwrap(); let result = syntesizer - .load_voice_model(&open_default_vvm_file().await) + .load_voice_model(&crate::nonblocking::VoiceModelFile::sample().await.unwrap()) .await; assert_debug_fmt_eq!( @@ -1324,7 +1530,10 @@ mod tests { #[rstest] #[tokio::test] async fn is_use_gpu_works() { - let syntesizer = super::tokio::Synthesizer::new( + let syntesizer = super::nonblocking::Synthesizer::new( + crate::nonblocking::Onnxruntime::from_test_util_data() + .await + .unwrap(), (), &InitializeOptions { acceleration_mode: AccelerationMode::Cpu, @@ -1340,7 +1549,10 @@ mod tests { #[tokio::test] async fn is_loaded_model_by_style_id_works(#[case] style_id: u32, #[case] expected: bool) { let style_id = StyleId::new(style_id); - let syntesizer = super::tokio::Synthesizer::new( + let syntesizer = super::nonblocking::Synthesizer::new( + crate::nonblocking::Onnxruntime::from_test_util_data() + .await + .unwrap(), (), &InitializeOptions { acceleration_mode: AccelerationMode::Cpu, @@ -1353,7 +1565,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::nonblocking::VoiceModelFile::sample().await.unwrap()) .await .unwrap(); @@ -1368,7 +1580,10 @@ mod tests { #[rstest] #[tokio::test] async fn predict_duration_works() { - let syntesizer = super::tokio::Synthesizer::new( + let syntesizer = super::nonblocking::Synthesizer::new( + crate::nonblocking::Onnxruntime::from_test_util_data() + .await + .unwrap(), (), &InitializeOptions { acceleration_mode: AccelerationMode::Cpu, @@ -1378,7 +1593,7 @@ mod tests { .unwrap(); syntesizer - .load_voice_model(&open_default_vvm_file().await) + .load_voice_model(&crate::nonblocking::VoiceModelFile::sample().await.unwrap()) .await .unwrap(); @@ -1399,7 +1614,10 @@ mod tests { #[rstest] #[tokio::test] async fn predict_intonation_works() { - let syntesizer = super::tokio::Synthesizer::new( + let syntesizer = super::nonblocking::Synthesizer::new( + crate::nonblocking::Onnxruntime::from_test_util_data() + .await + .unwrap(), (), &InitializeOptions { acceleration_mode: AccelerationMode::Cpu, @@ -1408,7 +1626,7 @@ mod tests { ) .unwrap(); syntesizer - .load_voice_model(&open_default_vvm_file().await) + .load_voice_model(&crate::nonblocking::VoiceModelFile::sample().await.unwrap()) .await .unwrap(); @@ -1438,7 +1656,10 @@ mod tests { #[rstest] #[tokio::test] async fn decode_works() { - let syntesizer = super::tokio::Synthesizer::new( + let syntesizer = super::nonblocking::Synthesizer::new( + crate::nonblocking::Onnxruntime::from_test_util_data() + .await + .unwrap(), (), &InitializeOptions { acceleration_mode: AccelerationMode::Cpu, @@ -1447,7 +1668,7 @@ mod tests { ) .unwrap(); syntesizer - .load_voice_model(&open_default_vvm_file().await) + .load_voice_model(&crate::nonblocking::VoiceModelFile::sample().await.unwrap()) .await .unwrap(); @@ -1530,8 +1751,11 @@ mod tests { #[case] expected_text_consonant_vowel_data: &TextConsonantVowelData, #[case] expected_kana_text: &str, ) { - let syntesizer = super::tokio::Synthesizer::new( - crate::tokio::OpenJtalk::new(OPEN_JTALK_DIC_DIR) + let syntesizer = super::nonblocking::Synthesizer::new( + crate::nonblocking::Onnxruntime::from_test_util_data() + .await + .unwrap(), + crate::nonblocking::OpenJtalk::new(OPEN_JTALK_DIC_DIR) .await .unwrap(), &InitializeOptions { @@ -1541,7 +1765,7 @@ mod tests { ) .unwrap(); - let model = &crate::tokio::VoiceModel::sample().await.unwrap(); + let model = &crate::nonblocking::VoiceModelFile::sample().await.unwrap(); syntesizer.load_voice_model(model).await.unwrap(); let query = match input { @@ -1555,41 +1779,39 @@ mod tests { .unwrap(); assert_eq!( - query.accent_phrases().len(), + query.accent_phrases.len(), expected_text_consonant_vowel_data.len() ); - for (accent_phrase, (text_consonant_vowel_slice, accent_pos)) in - std::iter::zip(query.accent_phrases(), expected_text_consonant_vowel_data) - { - assert_eq!( - accent_phrase.moras().len(), - text_consonant_vowel_slice.len() - ); - assert_eq!(accent_phrase.accent(), accent_pos); + for (accent_phrase, (text_consonant_vowel_slice, accent_pos)) in std::iter::zip( + query.accent_phrases, + expected_text_consonant_vowel_data.iter().copied(), + ) { + assert_eq!(accent_phrase.moras.len(), text_consonant_vowel_slice.len()); + assert_eq!(accent_phrase.accent, accent_pos); - for (mora, (text, consonant, vowel)) in - std::iter::zip(accent_phrase.moras(), *text_consonant_vowel_slice) - { - assert_eq!(mora.text(), text); + for (mora, (text, consonant, vowel)) in std::iter::zip( + accent_phrase.moras, + text_consonant_vowel_slice.iter().copied(), + ) { + assert_eq!(mora.text, text); // NOTE: 子音の長さが必ず非ゼロになるテストケースを想定している assert_ne!( - mora.consonant_length(), - &Some(0.), + mora.consonant_length, + Some(0.), "expected mora.consonant_length is not Some(0.0), but got Some(0.0)." ); - assert_eq!(mora.consonant(), &Some(consonant.to_string())); - assert_eq!(mora.vowel(), vowel); + assert_eq!(mora.consonant, Some(consonant.to_string())); + assert_eq!(mora.vowel, vowel); // NOTE: 母音の長さが必ず非ゼロになるテストケースを想定している assert_ne!( - mora.vowel_length(), - &0., + mora.vowel_length, 0., "expected mora.vowel_length is not 0.0, but got 0.0." ); } } - assert_eq!(query.kana().as_deref(), Some(expected_kana_text)); + assert_eq!(query.kana.as_deref(), Some(expected_kana_text)); } #[rstest] @@ -1600,8 +1822,11 @@ mod tests { #[case] input: Input, #[case] expected_text_consonant_vowel_data: &TextConsonantVowelData, ) { - let syntesizer = super::tokio::Synthesizer::new( - crate::tokio::OpenJtalk::new(OPEN_JTALK_DIC_DIR) + let syntesizer = super::nonblocking::Synthesizer::new( + crate::nonblocking::Onnxruntime::from_test_util_data() + .await + .unwrap(), + crate::nonblocking::OpenJtalk::new(OPEN_JTALK_DIC_DIR) .await .unwrap(), &InitializeOptions { @@ -1611,7 +1836,7 @@ mod tests { ) .unwrap(); - let model = &crate::tokio::VoiceModel::sample().await.unwrap(); + let model = &crate::nonblocking::VoiceModelFile::sample().await.unwrap(); syntesizer.load_voice_model(model).await.unwrap(); let accent_phrases = match input { @@ -1633,31 +1858,29 @@ mod tests { expected_text_consonant_vowel_data.len() ); - for (accent_phrase, (text_consonant_vowel_slice, accent_pos)) in - std::iter::zip(accent_phrases, expected_text_consonant_vowel_data) - { - assert_eq!( - accent_phrase.moras().len(), - text_consonant_vowel_slice.len() - ); - assert_eq!(accent_phrase.accent(), accent_pos); + for (accent_phrase, (text_consonant_vowel_slice, accent_pos)) in std::iter::zip( + accent_phrases, + expected_text_consonant_vowel_data.iter().copied(), + ) { + assert_eq!(accent_phrase.moras.len(), text_consonant_vowel_slice.len()); + assert_eq!(accent_phrase.accent, accent_pos); - for (mora, (text, consonant, vowel)) in - std::iter::zip(accent_phrase.moras(), *text_consonant_vowel_slice) - { - assert_eq!(mora.text(), text); + for (mora, (text, consonant, vowel)) in std::iter::zip( + accent_phrase.moras, + text_consonant_vowel_slice.iter().copied(), + ) { + assert_eq!(mora.text, text); // NOTE: 子音の長さが必ず非ゼロになるテストケースを想定している assert_ne!( - mora.consonant_length(), - &Some(0.), + mora.consonant_length, + Some(0.), "expected mora.consonant_length is not Some(0.0), but got Some(0.0)." ); - assert_eq!(mora.consonant(), &Some(consonant.to_string())); - assert_eq!(mora.vowel(), vowel); + assert_eq!(mora.consonant, Some(consonant.to_string())); + assert_eq!(mora.vowel, vowel); // NOTE: 母音の長さが必ず非ゼロになるテストケースを想定している assert_ne!( - mora.vowel_length(), - &0., + mora.vowel_length, 0., "expected mora.vowel_length is not 0.0, but got 0.0." ); } @@ -1667,8 +1890,11 @@ mod tests { #[rstest] #[tokio::test] async fn create_accent_phrases_works_for_japanese_commas_and_periods() { - let syntesizer = super::tokio::Synthesizer::new( - crate::tokio::OpenJtalk::new(OPEN_JTALK_DIC_DIR) + let syntesizer = super::nonblocking::Synthesizer::new( + crate::nonblocking::Onnxruntime::from_test_util_data() + .await + .unwrap(), + crate::nonblocking::OpenJtalk::new(OPEN_JTALK_DIC_DIR) .await .unwrap(), &InitializeOptions { @@ -1678,7 +1904,7 @@ mod tests { ) .unwrap(); - let model = &crate::tokio::VoiceModel::sample().await.unwrap(); + let model = &crate::nonblocking::VoiceModelFile::sample().await.unwrap(); syntesizer.load_voice_model(model).await.unwrap(); let accent_phrases = syntesizer @@ -1688,39 +1914,38 @@ mod tests { assert_eq!(accent_phrases.len(), 5); // 入力テキストに「、」や「。」などの句読点が含まれていたときに - // AccentPhraseModel の pause_mora に期待する値をテスト + // AccentPhraseの pause_mora に期待する値をテスト assert!( - accent_phrases[0].pause_mora().is_some(), - "accent_phrases[0].pause_mora() is None" + accent_phrases[0].pause_mora.is_some(), + "accent_phrases[0].pause_mora is None" ); assert!( - accent_phrases[1].pause_mora().is_some(), - "accent_phrases[1].pause_mora() is None" + accent_phrases[1].pause_mora.is_some(), + "accent_phrases[1].pause_mora is None" ); assert!( - accent_phrases[2].pause_mora().is_some(), - "accent_phrases[2].pause_mora() is None" + accent_phrases[2].pause_mora.is_some(), + "accent_phrases[2].pause_mora is None" ); assert!( - accent_phrases[3].pause_mora().is_some(), - "accent_phrases[3].pause_mora() is None" + accent_phrases[3].pause_mora.is_some(), + "accent_phrases[3].pause_mora is None" ); assert!( - accent_phrases[4].pause_mora().is_none(), // 文末の句読点は削除される - "accent_phrases[4].pause_mora() is not None" + accent_phrases[4].pause_mora.is_none(), // 文末の句読点は削除される + "accent_phrases[4].pause_mora is not None" ); for accent_phrase in accent_phrases.iter().take(4) { - let pause_mora = accent_phrase.pause_mora().clone().unwrap(); - assert_eq!(pause_mora.text(), "、"); - assert_eq!(pause_mora.consonant(), &None); - assert_eq!(pause_mora.consonant_length(), &None); - assert_eq!(pause_mora.vowel(), "pau"); + let pause_mora = accent_phrase.pause_mora.clone().unwrap(); + assert_eq!(pause_mora.text, "、"); + assert_eq!(pause_mora.consonant, None); + assert_eq!(pause_mora.consonant_length, None); + assert_eq!(pause_mora.vowel, "pau"); assert_ne!( - pause_mora.vowel_length(), - &0.0, - "pause_mora.vowel_length() should not be 0.0" + pause_mora.vowel_length, 0.0, + "pause_mora.vowel_length should not be 0.0", ); } } @@ -1728,8 +1953,11 @@ mod tests { #[rstest] #[tokio::test] async fn mora_length_works() { - let syntesizer = super::tokio::Synthesizer::new( - crate::tokio::OpenJtalk::new(OPEN_JTALK_DIC_DIR) + let syntesizer = super::nonblocking::Synthesizer::new( + crate::nonblocking::Onnxruntime::from_test_util_data() + .await + .unwrap(), + crate::nonblocking::OpenJtalk::new(OPEN_JTALK_DIC_DIR) .await .unwrap(), &InitializeOptions { @@ -1739,7 +1967,7 @@ mod tests { ) .unwrap(); - let model = &crate::tokio::VoiceModel::sample().await.unwrap(); + let model = &crate::nonblocking::VoiceModelFile::sample().await.unwrap(); syntesizer.load_voice_model(model).await.unwrap(); let accent_phrases = syntesizer @@ -1757,17 +1985,20 @@ mod tests { any_mora_param_changed( &accent_phrases, &modified_accent_phrases, - MoraModel::vowel_length + |Mora { vowel_length, .. }| vowel_length, ), - "mora_length() does not work: mora.vowel_length() is not changed." + "mora_length() does not work: mora.vowel_length is not changed.", ); } #[rstest] #[tokio::test] async fn mora_pitch_works() { - let syntesizer = super::tokio::Synthesizer::new( - crate::tokio::OpenJtalk::new(OPEN_JTALK_DIC_DIR) + let syntesizer = super::nonblocking::Synthesizer::new( + crate::nonblocking::Onnxruntime::from_test_util_data() + .await + .unwrap(), + crate::nonblocking::OpenJtalk::new(OPEN_JTALK_DIC_DIR) .await .unwrap(), &InitializeOptions { @@ -1777,7 +2008,7 @@ mod tests { ) .unwrap(); - let model = &crate::tokio::VoiceModel::sample().await.unwrap(); + let model = &crate::nonblocking::VoiceModelFile::sample().await.unwrap(); syntesizer.load_voice_model(model).await.unwrap(); let accent_phrases = syntesizer @@ -1792,16 +2023,23 @@ mod tests { // NOTE: 一つでも音高が変わっていれば、動作しているとみなす assert!( - any_mora_param_changed(&accent_phrases, &modified_accent_phrases, MoraModel::pitch), - "mora_pitch() does not work: mora.pitch() is not changed." + any_mora_param_changed( + &accent_phrases, + &modified_accent_phrases, + |Mora { pitch, .. }| pitch + ), + "mora_pitch() does not work: mora.pitch is not changed.", ); } #[rstest] #[tokio::test] async fn mora_data_works() { - let syntesizer = super::tokio::Synthesizer::new( - crate::tokio::OpenJtalk::new(OPEN_JTALK_DIC_DIR) + let syntesizer = super::nonblocking::Synthesizer::new( + crate::nonblocking::Onnxruntime::from_test_util_data() + .await + .unwrap(), + crate::nonblocking::OpenJtalk::new(OPEN_JTALK_DIC_DIR) .await .unwrap(), &InitializeOptions { @@ -1811,7 +2049,7 @@ mod tests { ) .unwrap(); - let model = &crate::tokio::VoiceModel::sample().await.unwrap(); + let model = &crate::nonblocking::VoiceModelFile::sample().await.unwrap(); syntesizer.load_voice_model(model).await.unwrap(); let accent_phrases = syntesizer @@ -1826,27 +2064,31 @@ mod tests { // NOTE: 一つでも音高が変わっていれば、動作しているとみなす assert!( - any_mora_param_changed(&accent_phrases, &modified_accent_phrases, MoraModel::pitch), - "mora_data() does not work: mora.pitch() is not changed." + any_mora_param_changed( + &accent_phrases, + &modified_accent_phrases, + |Mora { pitch, .. }| pitch, + ), + "mora_data() does not work: mora.pitch is not changed.", ); // NOTE: 一つでも母音の長さが変わっていれば、動作しているとみなす assert!( any_mora_param_changed( &accent_phrases, &modified_accent_phrases, - MoraModel::vowel_length + |Mora { vowel_length, .. }| vowel_length, ), - "mora_data() does not work: mora.vowel_length() is not changed." + "mora_data() does not work: mora.vowel_length is not changed.", ); } fn any_mora_param_changed( - before: &[AccentPhraseModel], - after: &[AccentPhraseModel], - param: fn(&MoraModel) -> &T, + before: &[AccentPhrase], + after: &[AccentPhrase], + param: fn(&Mora) -> &T, ) -> bool { std::iter::zip(before, after) - .flat_map(move |(before, after)| std::iter::zip(before.moras(), after.moras())) + .flat_map(|(before, after)| std::iter::zip(&before.moras, &after.moras)) .any(|(before, after)| param(before) != param(after)) } diff --git a/crates/voicevox_core/src/task.rs b/crates/voicevox_core/src/task.rs index 951e3c19e..233c0de85 100644 --- a/crates/voicevox_core/src/task.rs +++ b/crates/voicevox_core/src/task.rs @@ -1,16 +1,6 @@ -use std::panic; +// TODO: `Async::unblock`として取り回す /// ブロッキング操作を非同期化する。 -/// -/// # Panics -/// -/// - `f`がパニックした場合、パニックがそのままunwindされる。 -/// - tokioのランタイムの都合で`f`の実行が"cancel"された場合パニックする。 pub(crate) async fn asyncify R + Send + 'static, R: Send + 'static>(f: F) -> R { - tokio::task::spawn_blocking(f) - .await - .unwrap_or_else(|err| match err.try_into_panic() { - Ok(panic) => panic::resume_unwind(panic), - Err(err) => panic!("{err}"), // FIXME: エラーとして回収する - }) + blocking::unblock(f).await } 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 2c6721d08..000000000 --- a/crates/voicevox_core/src/test_data/model_sources/load_model_works1/manifest.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "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_util.rs b/crates/voicevox_core/src/test_util.rs index d60785246..e38f14c5c 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(crate) 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 { +impl crate::nonblocking::VoiceModelFile { pub(crate) async fn sample() -> Result { - return Self::from_path(PATH).await; - - static PATH: &str = concat!(env!("CARGO_WORKSPACE_DIR"), "/model/sample.vvm"); + Self::open(SAMPLE_VOICE_MODEL_FILE_PATH).await } } diff --git a/crates/voicevox_core/src/text_analyzer.rs b/crates/voicevox_core/src/text_analyzer.rs index 8540f26e0..f0811ed91 100644 --- a/crates/voicevox_core/src/text_analyzer.rs +++ b/crates/voicevox_core/src/text_analyzer.rs @@ -1,10 +1,10 @@ use crate::{ engine::{extract_full_context_label, parse_kana}, - AccentPhraseModel, FullcontextExtractor, Result, + AccentPhrase, FullcontextExtractor, Result, }; pub(crate) trait TextAnalyzer { - fn analyze(&self, text: &str) -> Result>; + fn analyze(&self, text: &str) -> Result>; } /// AquesTalk風記法からAccentPhraseの配列を生成するTextAnalyzer @@ -12,7 +12,7 @@ pub(crate) trait TextAnalyzer { pub(crate) struct KanaAnalyzer; impl TextAnalyzer for KanaAnalyzer { - fn analyze(&self, text: &str) -> Result> { + fn analyze(&self, text: &str) -> Result> { if text.is_empty() { return Ok(Vec::new()); } @@ -31,7 +31,7 @@ impl OpenJTalkAnalyzer { } impl TextAnalyzer for OpenJTalkAnalyzer { - fn analyze(&self, text: &str) -> Result> { + fn analyze(&self, text: &str) -> Result> { if text.is_empty() { return Ok(Vec::new()); } diff --git a/crates/voicevox_core/src/tokio.rs b/crates/voicevox_core/src/tokio.rs deleted file mode 100644 index 49451a310..000000000 --- a/crates/voicevox_core/src/tokio.rs +++ /dev/null @@ -1,6 +0,0 @@ -//! Tokio版API。 - -pub use crate::{ - engine::open_jtalk::tokio::OpenJtalk, synthesizer::tokio::Synthesizer, - user_dict::dict::tokio::UserDict, voice_model::tokio::VoiceModel, -}; diff --git a/crates/voicevox_core/src/user_dict/dict.rs b/crates/voicevox_core/src/user_dict/dict.rs index 6997620f0..91ec5ad9c 100644 --- a/crates/voicevox_core/src/user_dict/dict.rs +++ b/crates/voicevox_core/src/user_dict/dict.rs @@ -1,19 +1,128 @@ +use std::{marker::PhantomData, path::Path}; + +use anyhow::Context as _; +use easy_ext::ext; +use educe::Educe; +use indexmap::IndexMap; +use itertools::Itertools as _; +use uuid::Uuid; + +use crate::{asyncs::Async, error::ErrorRepr}; + +use super::UserDictWord; + +#[derive(Educe)] +#[educe(Default(bound = "A:"))] +#[educe(Debug(bound = "A:"))] +struct Inner { + words: std::sync::Mutex>, + _marker: PhantomData, +} + +impl Inner { + fn to_json(&self) -> String { + self.with_words(|words| serde_json::to_string(words).expect("should not fail")) + } + + fn with_words(&self, f: F) -> R + where + F: FnOnce(&mut IndexMap) -> R, + { + f(&mut self.words.lock().unwrap_or_else(|e| panic!("{e}"))) + } + + async fn load(&self, store_path: impl AsRef) -> crate::Result<()> { + let words = async { + let words = &A::fs_err_read(store_path).await?; + let words = serde_json::from_slice::>(words)?; + Ok(words) + } + .await + .map_err(ErrorRepr::LoadUserDict)?; + + self.with_words(|words_| words_.extend(words)); + Ok(()) + } + + fn add_word(&self, word: UserDictWord) -> crate::Result { + let word_uuid = Uuid::new_v4(); + self.with_words(|word_| word_.insert(word_uuid, word)); + Ok(word_uuid) + } + + fn update_word(&self, word_uuid: Uuid, new_word: UserDictWord) -> crate::Result<()> { + self.with_words(|words| { + if !words.contains_key(&word_uuid) { + return Err(ErrorRepr::WordNotFound(word_uuid).into()); + } + words.insert(word_uuid, new_word); + Ok(()) + }) + } + + fn remove_word(&self, word_uuid: Uuid) -> crate::Result { + let Some(word) = self.with_words(|words| words.shift_remove(&word_uuid)) else { + return Err(ErrorRepr::WordNotFound(word_uuid).into()); + }; + Ok(word) + } + + fn import(&self, other: &Self) -> crate::Result<()> { + self.with_words(|self_words| { + other.with_words(|other_words| { + for (word_uuid, word) in other_words { + self_words.insert(*word_uuid, word.clone()); + } + Ok(()) + }) + }) + } + + async fn save(&self, store_path: impl AsRef) -> crate::Result<()> { + A::fs_err_write( + store_path, + serde_json::to_vec(&self.words).expect("should not fail"), + ) + .await + .map_err(ErrorRepr::SaveUserDict) + .map_err(Into::into) + } + + fn to_mecab_format(&self) -> String { + self.with_words(|words| words.values().map(UserDictWord::to_mecab_format).join("\n")) + } +} + +#[ext] +impl A { + async fn fs_err_read(path: impl AsRef) -> anyhow::Result> { + Self::read(&path) + .await + .with_context(|| format!("failed to read from file `{}`", path.as_ref().display())) + } + + async fn fs_err_write(path: impl AsRef, content: impl AsRef<[u8]>) -> anyhow::Result<()> { + Self::write(&path, content) + .await + .with_context(|| format!("failed to write to file `{}`", path.as_ref().display())) + } +} + pub(crate) mod blocking { + use std::path::Path; + use indexmap::IndexMap; - use itertools::join; use uuid::Uuid; - use crate::{error::ErrorRepr, Result}; + use crate::{asyncs::SingleTasked, future::FutureExt as _, Result}; - use super::super::word::UserDictWord; + use super::{super::word::UserDictWord, Inner}; /// ユーザー辞書。 /// /// 単語はJSONとの相互変換のために挿入された順序を保つ。 #[derive(Debug, Default)] - pub struct UserDict { - words: std::sync::Mutex>, - } + pub struct UserDict(Inner); impl self::UserDict { /// ユーザー辞書を作成する。 @@ -22,11 +131,11 @@ pub(crate) mod blocking { } pub fn to_json(&self) -> String { - serde_json::to_string(&*self.words.lock().unwrap()).expect("should not fail") + self.0.to_json() } - pub fn with_words(&self, f: impl FnOnce(&IndexMap) -> R) -> R { - f(&self.words.lock().unwrap()) + pub fn with_words(&self, f: impl FnOnce(&mut IndexMap) -> R) -> R { + self.0.with_words(f) } /// ユーザー辞書をファイルから読み込む。 @@ -34,101 +143,76 @@ pub(crate) mod blocking { /// # Errors /// /// ファイルが読めなかった、または内容が不正だった場合はエラーを返す。 - pub fn load(&self, store_path: &str) -> Result<()> { - let words = (|| { - let words = &fs_err::read(store_path)?; - let words = serde_json::from_slice::>(words)?; - Ok(words) - })() - .map_err(ErrorRepr::LoadUserDict)?; - - self.words.lock().unwrap().extend(words); - Ok(()) + pub fn load(&self, store_path: impl AsRef) -> Result<()> { + self.0.load(store_path).block_on() } /// ユーザー辞書に単語を追加する。 pub fn add_word(&self, word: UserDictWord) -> Result { - let word_uuid = Uuid::new_v4(); - self.words.lock().unwrap().insert(word_uuid, word); - Ok(word_uuid) + self.0.add_word(word) } /// ユーザー辞書の単語を変更する。 pub fn update_word(&self, word_uuid: Uuid, new_word: UserDictWord) -> Result<()> { - let mut words = self.words.lock().unwrap(); - if !words.contains_key(&word_uuid) { - return Err(ErrorRepr::WordNotFound(word_uuid).into()); - } - words.insert(word_uuid, new_word); - Ok(()) + self.0.update_word(word_uuid, new_word) } /// ユーザー辞書から単語を削除する。 pub fn remove_word(&self, word_uuid: Uuid) -> Result { - let Some(word) = self.words.lock().unwrap().remove(&word_uuid) else { - return Err(ErrorRepr::WordNotFound(word_uuid).into()); - }; - Ok(word) + self.0.remove_word(word_uuid) } /// 他のユーザー辞書をインポートする。 pub fn import(&self, other: &Self) -> Result<()> { - for (word_uuid, word) in &*other.words.lock().unwrap() { - self.words.lock().unwrap().insert(*word_uuid, word.clone()); - } - Ok(()) + self.0.import(&other.0) } /// ユーザー辞書を保存する。 - pub fn save(&self, store_path: &str) -> Result<()> { - fs_err::write( - store_path, - serde_json::to_vec(&self.words).expect("should not fail"), - ) - .map_err(|e| ErrorRepr::SaveUserDict(e.into()).into()) + pub fn save(&self, store_path: impl AsRef) -> Result<()> { + self.0.save(store_path).block_on() } /// MeCabで使用する形式に変換する。 pub(crate) fn to_mecab_format(&self) -> String { - join( - self.words - .lock() - .unwrap() - .values() - .map(UserDictWord::to_mecab_format), - "\n", - ) + self.0.to_mecab_format() } } } -pub(crate) mod tokio { - use std::sync::Arc; +pub(crate) mod nonblocking { + use std::path::Path; use indexmap::IndexMap; use uuid::Uuid; - use crate::Result; + use crate::{asyncs::BlockingThreadPool, Result}; - use super::super::word::UserDictWord; + use super::{super::word::UserDictWord, Inner}; /// ユーザー辞書。 /// /// 単語はJSONとの相互変換のために挿入された順序を保つ。 + /// + /// # Performance + /// + /// [blocking]クレートにより動いている。詳しくは[`nonblocking`モジュールのドキュメント]を参照。 + /// + /// [blocking]: https://docs.rs/crate/blocking + /// [`nonblocking`モジュールのドキュメント]: crate::nonblocking #[derive(Debug, Default)] - pub struct UserDict(Arc); + pub struct UserDict(Inner); impl self::UserDict { /// ユーザー辞書を作成する。 pub fn new() -> Self { - Self(super::blocking::UserDict::new().into()) + Default::default() } pub fn to_json(&self) -> String { self.0.to_json() } - pub fn with_words(&self, f: impl FnOnce(&IndexMap) -> R) -> R { + pub fn with_words(&self, f: impl FnOnce(&mut IndexMap) -> R) -> R { self.0.with_words(f) } @@ -137,10 +221,8 @@ pub(crate) mod tokio { /// # Errors /// /// ファイルが読めなかった、または内容が不正だった場合はエラーを返す。 - pub async fn load(&self, store_path: &str) -> Result<()> { - let blocking = self.0.clone(); - let store_path = store_path.to_owned(); - crate::task::asyncify(move || blocking.load(&store_path)).await + pub async fn load(&self, store_path: impl AsRef) -> Result<()> { + self.0.load(store_path).await } /// ユーザー辞書に単語を追加する。 @@ -164,10 +246,8 @@ pub(crate) mod tokio { } /// ユーザー辞書を保存する。 - pub async fn save(&self, store_path: &str) -> Result<()> { - let blocking = self.0.clone(); - let store_path = store_path.to_owned(); - crate::task::asyncify(move || blocking.save(&store_path)).await + pub async fn save(&self, store_path: impl AsRef) -> Result<()> { + self.0.save(store_path).await } /// MeCabで使用する形式に変換する。 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 b7bc95440..908885c65 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,5 +1,5 @@ -use once_cell::sync::Lazy; use std::collections::HashMap; +use std::sync::LazyLock; use crate::UserDictWordType; @@ -30,8 +30,8 @@ pub(super) struct PartOfSpeechDetail { } // 元データ: https://github.com/VOICEVOX/voicevox_engine/blob/master/voicevox_engine/part_of_speech_data.py -pub(super) static PART_OF_SPEECH_DETAIL: Lazy> = - Lazy::new(|| { +pub(super) static PART_OF_SPEECH_DETAIL: LazyLock> = + LazyLock::new(|| { HashMap::from_iter([ ( UserDictWordType::ProperNoun, diff --git a/crates/voicevox_core/src/user_dict/word.rs b/crates/voicevox_core/src/user_dict/word.rs index 7ed98a949..afc023669 100644 --- a/crates/voicevox_core/src/user_dict/word.rs +++ b/crates/voicevox_core/src/user_dict/word.rs @@ -1,3 +1,8 @@ +use std::{ops::RangeToInclusive, sync::LazyLock}; + +use regex::Regex; +use serde::{de::Error as _, Deserialize, Serialize}; + use crate::{ error::ErrorRepr, result::Result, @@ -5,25 +10,20 @@ use crate::{ priority2cost, MAX_PRIORITY, MIN_PRIORITY, PART_OF_SPEECH_DETAIL, }, }; -use derive_getters::Getters; -use once_cell::sync::Lazy; -use regex::Regex; -use serde::{de::Error as _, Deserialize, Serialize}; -use std::ops::RangeToInclusive; /// ユーザー辞書の単語。 -#[derive(Clone, Debug, Getters, Serialize)] +#[derive(Clone, Debug, Serialize)] pub struct UserDictWord { /// 単語の表記。 - pub surface: String, + surface: String, /// 単語の読み。 - pub pronunciation: String, + pronunciation: String, /// アクセント型。 - pub accent_type: usize, + accent_type: usize, /// 単語の種類。 - pub word_type: UserDictWordType, + word_type: UserDictWordType, /// 単語の優先度。 - pub priority: u32, + priority: u32, /// モーラ数。 mora_count: usize, @@ -55,7 +55,7 @@ impl<'de> Deserialize<'de> for UserDictWord { } } -#[allow(clippy::enum_variant_names)] // FIXME +#[expect(clippy::enum_variant_names, reason = "特に理由はないので正されるべき")] // FIXME #[derive(thiserror::Error, Debug, PartialEq)] pub(crate) enum InvalidWordError { #[error("{}: 無効な発音です({_1}): {_0:?}", Self::BASE_MSG)] @@ -78,8 +78,9 @@ impl InvalidWordError { type InvalidWordResult = std::result::Result; -static PRONUNCIATION_REGEX: Lazy = Lazy::new(|| Regex::new(r"^[ァ-ヴー]+$").unwrap()); -static MORA_REGEX: Lazy = Lazy::new(|| { +static PRONUNCIATION_REGEX: LazyLock = + LazyLock::new(|| Regex::new(r"^[ァ-ヴー]+$").unwrap()); +static MORA_REGEX: LazyLock = LazyLock::new(|| { Regex::new(concat!( "(?:", "[イ][ェ]|[ヴ][ャュョ]|[トド][ゥ]|[テデ][ィャュョ]|[デ][ェ]|[クグ][ヮ]|", // rule_others @@ -90,7 +91,7 @@ static MORA_REGEX: Lazy = Lazy::new(|| { )) .unwrap() }); -static SPACE_REGEX: Lazy = Lazy::new(|| Regex::new(r"\p{Z}").unwrap()); +static SPACE_REGEX: LazyLock = LazyLock::new(|| Regex::new(r"\p{Z}").unwrap()); impl Default for UserDictWord { fn default() -> Self { @@ -127,6 +128,31 @@ impl UserDictWord { mora_count, }) } + + /// 単語の表記。 + pub fn surface(&self) -> &str { + &self.surface + } + + /// 単語の読み。 + pub fn pronunciation(&self) -> &str { + &self.pronunciation + } + + /// アクセント型。 + pub fn accent_type(&self) -> usize { + self.accent_type + } + + /// 単語の種類。 + pub fn word_type(&self) -> UserDictWordType { + self.word_type + } + + /// 単語の優先度。 + pub fn priority(&self) -> u32 { + self.priority + } } /// カタカナの文字列が発音として有効かどうかを判定する。 @@ -188,8 +214,10 @@ fn calculate_mora_count(pronunciation: &str, accent_type: usize) -> InvalidWordR /// 一部の種類の文字を、全角文字に置き換える。 /// /// 具体的には +/// /// - "!"から"~"までの範囲の文字(数字やアルファベット)は、対応する全角文字に /// - " "などの目に見えない文字は、まとめて全角スペース(0x3000)に +/// /// 変換する。 pub(crate) fn to_zenkaku(surface: &str) -> String { // 元実装:https://github.com/VOICEVOX/voicevox/blob/69898f5dd001d28d4de355a25766acb0e0833ec2/src/components/DictionaryManageDialog.vue#L379-L387 @@ -203,7 +231,7 @@ pub(crate) fn to_zenkaku(surface: &str) -> String { .collect() } /// ユーザー辞書の単語の種類。 -#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, Hash)] +#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize, Hash)] #[serde(rename_all = "SCREAMING_SNAKE_CASE")] pub enum UserDictWordType { /// 固有名詞。 diff --git a/crates/voicevox_core/src/voice_model.rs b/crates/voicevox_core/src/voice_model.rs index 364c8db0a..96ff1045d 100644 --- a/crates/voicevox_core/src/voice_model.rs +++ b/crates/voicevox_core/src/voice_model.rs @@ -2,60 +2,326 @@ //! //! VVM ファイルの定義と形式は[ドキュメント](../../../docs/vvm.md)を参照。 -use anyhow::anyhow; -use derive_getters::Getters; -use derive_new::new; +use std::{ + collections::HashMap, + path::{Path, PathBuf}, + sync::Arc, +}; + +use anyhow::{anyhow, Context as _}; +use derive_more::From; use easy_ext::ext; -use enum_map::EnumMap; +use enum_map::{enum_map, EnumMap}; +use futures_io::{AsyncBufRead, AsyncRead, AsyncSeek}; +use futures_util::future::{OptionFuture, TryFutureExt as _}; use itertools::Itertools as _; +use ouroboros::self_referencing; use serde::Deserialize; +use uuid::Uuid; use crate::{ + asyncs::{Async, Mutex as _}, error::{LoadModelError, LoadModelErrorKind, LoadModelResult}, infer::{ - domains::{TalkDomain, TalkOperation}, + domains::{inference_domain_map_values, InferenceDomainMap, TalkDomain, TalkOperation}, InferenceDomain, }, - manifest::{Manifest, ManifestDomains, StyleIdToModelInnerId}, + manifest::{Manifest, ManifestDomains, StyleIdToInnerVoiceId}, SpeakerMeta, StyleMeta, StyleType, VoiceModelMeta, }; -use std::path::{Path, PathBuf}; /// [`VoiceModelId`]の実体。 /// /// [`VoiceModelId`]: VoiceModelId -pub type RawVoiceModelId = String; +pub type RawVoiceModelId = Uuid; -pub(crate) type ModelBytesWithInnerIdsByDomain = - (Option<(StyleIdToModelInnerId, EnumMap>)>,); +pub(crate) type ModelBytesWithInnerVoiceIdsByDomain = inference_domain_map_values!( + for Option<(StyleIdToInnerVoiceId, EnumMap>)> +); /// 音声モデルID。 #[derive( PartialEq, Eq, Clone, + Copy, Ord, Hash, PartialOrd, Deserialize, - new, - Getters, derive_more::Display, Debug, + From, )] -pub struct VoiceModelId { - raw_voice_model_id: RawVoiceModelId, +pub struct VoiceModelId(RawVoiceModelId); + +impl VoiceModelId { + pub fn raw_voice_model_id(self) -> RawVoiceModelId { + self.0 + } +} + +#[self_referencing] +struct Inner { + header: VoiceModelHeader, + + #[borrows(header)] + #[not_covariant] + inference_model_entries: InferenceDomainMap>, + + zip: A::Mutex, +} + +impl Inner { + async fn open(path: impl AsRef) -> crate::Result { + const MANIFEST_FILENAME: &str = "manifest.json"; + + let path = path.as_ref(); + + let error = |context, source| LoadModelError { + path: path.to_owned(), + context, + source: Some(source), + }; + + let mut zip = A::open_zip(path) + .await + .map_err(|source| error(LoadModelErrorKind::OpenZipFile, source))?; + + let indices = zip.entry_indices_by_utf8_filenames(); + let find_entry_index = |filename: &str| { + indices + .get(filename) + .with_context(|| "could not find `{filename}`") + .copied() + }; + + let manifest = &async { + let idx = find_entry_index(MANIFEST_FILENAME)?; + zip.read_file(idx).await + } + .await + .map_err(|source| { + error( + LoadModelErrorKind::ReadZipEntry { + filename: MANIFEST_FILENAME.to_owned(), + }, + source, + ) + })?; + let manifest = serde_json::from_slice::(manifest) + .map_err(|source| error(LoadModelErrorKind::InvalidModelFormat, source.into()))?; + + let metas = &async { + let idx = find_entry_index(manifest.metas_filename())?; + zip.read_file(idx).await + } + .await + .map_err(|source| { + error( + LoadModelErrorKind::ReadZipEntry { + filename: manifest.metas_filename().clone(), + }, + source, + ) + })?; + + let header = VoiceModelHeader::new(manifest, metas, path)?; + + InnerTryBuilder { + header, + inference_model_entries_builder: |VoiceModelHeader { manifest, .. }| { + manifest + .domains() + .each_ref() + .map(InferenceDomainMap { + talk: |talk| { + talk.as_ref() + .map(|manifest| { + let indices = enum_map! { + TalkOperation::PredictDuration => { + find_entry_index(&manifest.predict_duration_filename)? + } + TalkOperation::PredictIntonation => { + find_entry_index(&manifest.predict_intonation_filename)? + } + TalkOperation::GenerateFullIntermediate => { + find_entry_index(&manifest.generate_full_intermediate_filename)? + } + TalkOperation::RenderAudioSegment => { + find_entry_index(&manifest.render_audio_segment_filename)? + } + }; + + Ok(InferenceModelEntry { indices, manifest }) + }) + .transpose() + .map_err(move |source| { + error( + LoadModelErrorKind::ReadZipEntry { + filename: MANIFEST_FILENAME.to_owned(), + }, + source, + ) + }) + }, + }) + .collect() + .map_err(crate::Error::from) + }, + zip: zip.into_inner().into_inner().into(), + } + .try_build() + } + + fn id(&self) -> VoiceModelId { + self.borrow_header().manifest.id + } + + fn metas(&self) -> &VoiceModelMeta { + &self.borrow_header().metas + } + + fn header(&self) -> &VoiceModelHeader { + self.borrow_header() + } + + async fn read_inference_models( + &self, + ) -> LoadModelResult> { + let path = &self.borrow_header().path; + + let error = |context, source| LoadModelError { + path: path.to_owned(), + context, + source: Some(source), + }; + + let zip = &mut *self.borrow_zip().lock().await; + let mut zip = async_zip::base::read::seek::ZipFileReader::with_bufreader(zip) + .await + .map_err(|source| error(LoadModelErrorKind::OpenZipFile, source.into()))?; + + macro_rules! read_file { + ($entry:expr $(,)?) => {{ + let (index, filename): (usize, Arc) = $entry; + zip.read_file(index) + .map_err(move |source| { + error( + LoadModelErrorKind::ReadZipEntry { + filename: (*filename).to_owned(), + }, + source, + ) + }) + .await? + }}; + } + + let InferenceDomainMap { talk } = + self.with_inference_model_entries(|inference_model_entries| { + inference_model_entries.each_ref().map(InferenceDomainMap { + talk: |talk| { + talk.as_ref() + .map(|InferenceModelEntry { indices, manifest }| { + ( + indices.map(|op, i| (i, manifest[op].clone())), + manifest.style_id_to_inner_voice_id.clone(), + ) + }) + }, + }) + }); + + let talk = OptionFuture::from(talk.map( + |(entries, style_id_to_inner_voice_id)| async move { + let [predict_duration, predict_intonation, predict_spectrogram, run_vocoder] = + entries.into_array(); + + let predict_duration = read_file!(predict_duration); + let predict_intonation = read_file!(predict_intonation); + let predict_spectrogram = read_file!(predict_spectrogram); + let run_vocoder = read_file!(run_vocoder); + + let model_bytes = EnumMap::from_array([ + predict_duration, + predict_intonation, + predict_spectrogram, + run_vocoder, + ]); + + Ok((style_id_to_inner_voice_id, model_bytes)) + }, + )) + .await + .transpose()?; + + Ok(InferenceDomainMap { talk }) + } +} + +type InferenceModelEntries<'manifest> = + inference_domain_map_values!(for Option>); + +struct InferenceModelEntry { + indices: EnumMap, + manifest: M, +} + +#[ext] +impl A { + async fn open_zip( + path: &Path, + ) -> anyhow::Result< + async_zip::base::read::seek::ZipFileReader>, + > { + let zip = Self::open_file_ro(path).await.with_context(|| { + // fs-errのと同じにする + format!("failed to open file `{}`", path.display()) + })?; + let zip = async_zip::base::read::seek::ZipFileReader::with_bufreader(zip).await?; + Ok(zip) + } +} + +// `BufReader`はasync_zip v0.0.16では不要、v0.0.17では必要 +#[ext] +impl + async_zip::base::read::seek::ZipFileReader> +{ + async fn with_bufreader(rdr: R) -> async_zip::error::Result + where + Self: Sized, // trivial + { + Self::new(futures_util::io::BufReader::new(rdr)).await + } +} + +#[ext] +impl async_zip::base::read::seek::ZipFileReader { + fn entry_indices_by_utf8_filenames(&self) -> HashMap { + self.file() + .entries() + .iter() + .enumerate() + .flat_map(|(i, e)| e.filename().as_str().map(|s| (s.to_owned(), i))) + .collect() + } + + async fn read_file(&mut self, index: usize) -> anyhow::Result> { + let mut rdr = self.reader_with_entry(index).await?; + let mut buf = Vec::with_capacity(rdr.entry().uncompressed_size() as usize); + rdr.read_to_end_checked(&mut buf).await?; + Ok(buf) + } } // FIXME: "header"といいつつ、VVMのファイルパスを持っている状態になっている。 /// 音声モデルが持つ、各モデルファイルの実体を除く情報。 /// /// モデルの`[u8]`と分けて`Status`に渡す。 -#[derive(Clone)] pub(crate) struct VoiceModelHeader { - /// ID。 - pub(crate) id: VoiceModelId, - manifest: Manifest, + pub(crate) manifest: Manifest, /// メタ情報。 /// /// `manifest`が対応していない`StyleType`のスタイルは含まれるべきではない。 @@ -64,37 +330,36 @@ pub(crate) struct VoiceModelHeader { } impl VoiceModelHeader { - 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())), - ), - })?; + fn new(manifest: Manifest, metas: &[u8], path: &Path) -> LoadModelResult { + let error = |context, source| LoadModelError { + path: path.to_owned(), + context, + source: Some(source), + }; + + let metas = serde_json::from_slice::(metas).map_err(|source| { + error( + LoadModelErrorKind::InvalidModelFormat, + 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(), - )), + .map_err(|style_type| { + error( + LoadModelErrorKind::InvalidModelFormat, + anyhow!( + "{metas_filename}には`{style_type}`のスタイルが存在しますが、manifest.json\ + での対応がありません", + metas_filename = manifest.metas_filename(), + ), + ) })?; Ok(Self { - id, manifest, metas, path: path.to_owned(), @@ -102,16 +367,15 @@ impl VoiceModelHeader { } } -impl ManifestDomains { +impl InferenceDomainMap { /// manifestとして対応していない`StyleType`に対してエラーを発する。 /// /// `Status`はこのバリデーションを信頼し、`InferenceDomain`の不足時にパニックする。 fn check_acceptable(&self, metas: &[SpeakerMeta]) -> std::result::Result<(), StyleType> { let err = metas .iter() - .flat_map(SpeakerMeta::styles) - .map(StyleMeta::r#type) - .copied() + .flat_map(|SpeakerMeta { styles, .. }| styles) + .map(|StyleMeta { r#type, .. }| *r#type) .unique() .find(|&style_type| !self.accepts(style_type)); @@ -146,354 +410,145 @@ impl ManifestDomains { } pub(crate) mod blocking { - use std::{ - io::{self, Cursor}, - path::Path, - }; - - use enum_map::EnumMap; - use nanoid::nanoid; - use ouroboros::self_referencing; - use rayon::iter::{IntoParallelIterator as _, ParallelIterator as _}; - use serde::de::DeserializeOwned; + use std::path::Path; use crate::{ - error::{LoadModelError, LoadModelErrorKind, LoadModelResult}, - infer::domains::InferenceDomainMap, - manifest::{Manifest, TalkManifest}, - VoiceModelMeta, + asyncs::SingleTasked, error::LoadModelResult, future::FutureExt as _, + infer::domains::InferenceDomainMap, VoiceModelMeta, }; - use super::{ModelBytesWithInnerIdsByDomain, VoiceModelHeader, VoiceModelId}; + use super::{Inner, ModelBytesWithInnerVoiceIdsByDomain, VoiceModelHeader, VoiceModelId}; - /// 音声モデル。 + /// 音声モデルファイル。 /// /// VVMファイルと対応する。 - #[derive(Clone)] - pub struct VoiceModel { - header: VoiceModelHeader, - } + pub struct VoiceModelFile(Inner); - impl self::VoiceModel { + impl self::VoiceModelFile { pub(crate) fn read_inference_models( &self, - ) -> LoadModelResult> { - let reader = BlockingVvmEntryReader::open(&self.header.path)?; - - 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 }) + ) -> LoadModelResult> { + self.0.read_inference_models().block_on() } - /// VVMファイルから`VoiceModel`をコンストラクトする。 - pub fn from_path(path: impl AsRef) -> crate::Result { - let path = path.as_ref(); - let reader = BlockingVvmEntryReader::open(path)?; - let manifest = reader.read_vvm_json::("manifest.json")?; - let metas = &reader.read_vvm_entry(manifest.metas_filename())?; - let id = VoiceModelId::new(nanoid!()); - let header = VoiceModelHeader::new(id, manifest, metas, path)?; - Ok(Self { header }) + /// VVMファイルを開く。 + pub fn open(path: impl AsRef) -> crate::Result { + Inner::open(path).block_on().map(Self) } /// ID。 - pub fn id(&self) -> &VoiceModelId { - &self.header.id + pub fn id(&self) -> VoiceModelId { + self.0.id() } /// メタ情報。 pub fn metas(&self) -> &VoiceModelMeta { - &self.header.metas + self.0.metas() } pub(crate) fn header(&self) -> &VoiceModelHeader { - &self.header - } - } - - #[self_referencing] - struct BlockingVvmEntryReader { - path: std::path::PathBuf, - zip: Vec, - #[covariant] - #[borrows(zip)] - reader: zip::ZipArchive>, - } - - impl BlockingVvmEntryReader { - fn open(path: &Path) -> LoadModelResult { - (|| { - let zip = std::fs::read(path)?; - Self::try_new(path.to_owned(), zip, |zip| { - zip::ZipArchive::new(Cursor::new(zip)) - }) - })() - .map_err(|source| LoadModelError { - path: path.to_owned(), - context: LoadModelErrorKind::OpenZipFile, - source: Some(source.into()), - }) - } - - // 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::InvalidModelFormat, - source: Some(anyhow::Error::from(source).context(format!("{filename}が不正です"))), - }) - } - - fn read_vvm_entry(&self, filename: &str) -> LoadModelResult> { - (|| { - let mut reader = self.borrow_reader().clone(); - let mut entry = reader.by_name(filename)?; - let mut buf = Vec::with_capacity(entry.size() as _); - io::copy(&mut entry, &mut buf)?; - Ok(buf) - })() - .map_err(|source| LoadModelError { - path: self.borrow_path().clone(), - context: LoadModelErrorKind::OpenZipFile, - source: Some(source), - }) + self.0.header() } } } -pub(crate) mod tokio { - use std::{collections::HashMap, io, path::Path}; - - use derive_new::new; - use enum_map::EnumMap; - use futures::future::{join3, OptionFuture}; - use nanoid::nanoid; - use serde::de::DeserializeOwned; +pub(crate) mod nonblocking { + use std::path::Path; use crate::{ - error::{LoadModelError, LoadModelErrorKind, LoadModelResult}, - infer::domains::InferenceDomainMap, - manifest::{Manifest, TalkManifest}, + asyncs::BlockingThreadPool, error::LoadModelResult, infer::domains::InferenceDomainMap, Result, VoiceModelMeta, }; - use super::{ModelBytesWithInnerIdsByDomain, VoiceModelHeader, VoiceModelId}; + use super::{Inner, ModelBytesWithInnerVoiceIdsByDomain, VoiceModelHeader, VoiceModelId}; - /// 音声モデル。 + /// 音声モデルファイル。 /// /// VVMファイルと対応する。 - #[derive(Clone)] - pub struct VoiceModel { - header: VoiceModelHeader, - } + /// + /// # Performance + /// + /// [blocking]クレートにより動いている。詳しくは[`nonblocking`モジュールのドキュメント]を参照。 + /// + /// [blocking]: https://docs.rs/crate/blocking + /// [`nonblocking`モジュールのドキュメント]: crate::nonblocking + pub struct VoiceModelFile(Inner); - impl self::VoiceModel { + impl self::VoiceModelFile { pub(crate) async fn read_inference_models( &self, - ) -> LoadModelResult> { - let reader = AsyncVvmEntryReader::open(&self.header.path).await?; - - 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()?; + ) -> LoadModelResult> { + self.0.read_inference_models().await + } - Ok(InferenceDomainMap { talk }) + /// VVMファイルを開く。 + pub async fn open(path: impl AsRef) -> Result { + Inner::open(path).await.map(Self) } - /// 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_entry(manifest.metas_filename()).await?; - let id = VoiceModelId::new(nanoid!()); - let header = VoiceModelHeader::new(id, manifest, metas, path.as_ref())?; - Ok(Self { header }) + + /// VVMファイルを閉じる。 + pub async fn close(self) { + self.0.into_heads().zip.into_inner().close().await; } /// ID。 - pub fn id(&self) -> &VoiceModelId { - &self.header.id + pub fn id(&self) -> VoiceModelId { + self.0.id() } /// メタ情報。 pub fn metas(&self) -> &VoiceModelMeta { - &self.header.metas + self.0.metas() } pub(crate) fn header(&self) -> &VoiceModelHeader { - &self.header - } - } - - struct AsyncVvmEntry { - index: usize, - entry: async_zip::ZipEntry, - } - - #[derive(new)] - struct AsyncVvmEntryReader<'a> { - path: &'a Path, - reader: async_zip::base::read::mem::ZipFileReader, - entry_map: HashMap, - } - - 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() - .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(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.path.to_owned(), - context: LoadModelErrorKind::InvalidModelFormat, - source: Some(anyhow::Error::from(source).context(format!("{filename}が不正です"))), - }) - } - - async fn read_vvm_entry(&self, filename: &str) -> LoadModelResult> { - async { - let me = self - .entry_map - .get(filename) - .ok_or_else(|| io::Error::from(io::ErrorKind::NotFound))?; - 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).await?; - Ok::<_, anyhow::Error>(buf) - } - .await - .map_err(|source| LoadModelError { - path: self.path.to_owned(), - context: LoadModelErrorKind::ReadZipEntry { - filename: filename.to_owned(), - }, - source: Some(source), - }) + self.0.header() } } } #[cfg(test)] mod tests { - use once_cell::sync::Lazy; - use rstest::{fixture, rstest}; + use rstest::rstest; use serde_json::json; use crate::{ + infer::domains::InferenceDomainMap, manifest::{ManifestDomains, TalkManifest}, SpeakerMeta, StyleType, }; #[rstest] #[case( - &ManifestDomains { + &InferenceDomainMap { talk: None, }, &[], Ok(()) )] #[case( - &ManifestDomains { - talk: Some(TALK_MANIFEST.clone()), + &InferenceDomainMap { + talk: Some(TalkManifest::default()), }, &[speaker(&[StyleType::Talk])], Ok(()) )] #[case( - &ManifestDomains { - talk: Some(TALK_MANIFEST.clone()), + &InferenceDomainMap { + talk: Some(TalkManifest::default()), }, &[speaker(&[StyleType::Talk, StyleType::Sing])], Ok(()) )] #[case( - &ManifestDomains { + &InferenceDomainMap { talk: None, }, &[speaker(&[StyleType::Talk])], Err(()) )] fn check_acceptable_works( - #[case] manifest: &ManifestDomains, + #[case] manifest: &InferenceDomainMap, #[case] metas: &[SpeakerMeta], #[case] expected: std::result::Result<(), ()>, ) { @@ -501,32 +556,6 @@ mod tests { 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() diff --git a/crates/voicevox_core_c_api/Cargo.toml b/crates/voicevox_core_c_api/Cargo.toml index 2142b37d8..bec06ef7c 100644 --- a/crates/voicevox_core_c_api/Cargo.toml +++ b/crates/voicevox_core_c_api/Cargo.toml @@ -3,6 +3,7 @@ name = "voicevox_core_c_api" version.workspace = true edition.workspace = true publish.workspace = true +rust-version.workspace = true [lib] crate-type = ["cdylib"] @@ -13,7 +14,8 @@ harness = false name = "e2e" [features] -directml = ["voicevox_core/directml"] +load-onnxruntime = ["voicevox_core/load-onnxruntime"] +link-onnxruntime = ["voicevox_core/link-onnxruntime"] [dependencies] anstream = { workspace = true, default-features = false, features = ["auto"] } @@ -21,12 +23,15 @@ anstyle-query.workspace = true camino.workspace = true chrono = { workspace = true, default-features = false, features = ["clock"] } colorchoice.workspace = true -derive-getters.workspace = true -futures.workspace = true +const_format.workspace = true +duplicate.workspace = true +easy-ext.workspace = true +educe.workspace = true itertools.workspace = true libc.workspace = true -once_cell.workspace = true +parking_lot = { workspace = true, features = ["arc_lock"] } process_path.workspace = true +ref-cast.workspace = true serde_json = { workspace = true, features = ["preserve_order"] } thiserror.workspace = true tracing.workspace = true @@ -41,10 +46,12 @@ clap = { workspace = true, features = ["derive"] } duct.workspace = true easy-ext.workspace = true inventory.workspace = true +indexmap = { workspace = true, features = ["serde"] } libloading.workspace = true libtest-mimic.workspace = true ndarray.workspace = true ndarray-stats.workspace = true +predicates.workspace = true regex.workspace = true serde = { workspace = true, features = ["derive"] } serde_with.workspace = true @@ -52,6 +59,7 @@ tempfile.workspace = true test_util.workspace = true toml.workspace = true typetag.workspace = true +voicevox-ort.workspace = true [lints.rust] unsafe_code = "allow" # C APIのための操作 diff --git a/crates/voicevox_core_c_api/build.rs b/crates/voicevox_core_c_api/build.rs index 535e73676..28eb7c04c 100644 --- a/crates/voicevox_core_c_api/build.rs +++ b/crates/voicevox_core_c_api/build.rs @@ -1,3 +1,5 @@ +// TODO: #802 の時点でiOS以外不要になっているはずなので、このbuild.rsは丸ごと消す +// (iOSのためにbuild_util/make_ios_xcframework.bashの修正は必要) fn main() { #[cfg(target_os = "linux")] println!("cargo:rustc-link-arg=-Wl,-rpath,$ORIGIN"); diff --git a/crates/voicevox_core_c_api/cbindgen.toml b/crates/voicevox_core_c_api/cbindgen.toml index 7615280f6..c377daa20 100644 --- a/crates/voicevox_core_c_api/cbindgen.toml +++ b/crates/voicevox_core_c_api/cbindgen.toml @@ -9,6 +9,19 @@ header = """ * 無料で使える中品質なテキスト読み上げソフトウェア、VOICEVOXのコア。 * *
+ *
+ * Availability + *
+ * + *
+ * ヘッダによって次の二つのマクロのうちどちらかが存在する。[リリース](https://github.com/voicevox/voicevox_core/releases)されているライブラリではiOSでのみ`VOICEVOX_LINK_ONNXRUNTIME`が、他のプラットフォームでは`VOICEVOX_LOAD_ONNXRUNTIME`が存在する。 + * + * - `VOICEVOX_LOAD_ONNXRUNTIME`: ::voicevox_onnxruntime_load_once と、それに付属するアイテムが利用可能になる。 + * - `VOICEVOX_LINK_ONNXRUNTIME`: ::voicevox_onnxruntime_init_once が利用可能になる。またこのマクロが存在するなら、このライブラリはONNX Runtimeをロード時動的リンクする。 + *
+ *
+ * + *
*
* ⚠️ Safety *
@@ -55,7 +68,18 @@ after_includes = """ #else // __cplusplus #include #include -#endif // __cplusplus""" +#endif // __cplusplus + +//#define VOICEVOX_LINK_ONNXRUNTIME +//#define VOICEVOX_LOAD_ONNXRUNTIME + +#if !(defined(VOICEVOX_LINK_ONNXRUNTIME) || defined(VOICEVOX_LOAD_ONNXRUNTIME)) +#error "either `VOICEVOX_LINK_ONNXRUNTIME` or `VOICEVOX_LOAD_ONNXRUNTIME` must be enabled" +#endif + +#if defined(VOICEVOX_LINK_ONNXRUNTIME) && defined(VOICEVOX_LOAD_ONNXRUNTIME) +#error "`VOICEVOX_LINK_ONNXRUNTIME` or `VOICEVOX_LOAD_ONNXRUNTIME` cannot be enabled at the same time" +#endif""" # Code Style Options @@ -78,3 +102,7 @@ rename_variants = "ScreamingSnakeCase" [parse] parse_deps = true include = ["voicevox_core"] + +[defines] +"feature = load-onnxruntime" = "VOICEVOX_LOAD_ONNXRUNTIME" +"feature = link-onnxruntime" = "VOICEVOX_LINK_ONNXRUNTIME" diff --git a/crates/voicevox_core_c_api/include/voicevox_core.h b/crates/voicevox_core_c_api/include/voicevox_core.h index 592ec8abb..f7de66b49 100644 --- a/crates/voicevox_core_c_api/include/voicevox_core.h +++ b/crates/voicevox_core_c_api/include/voicevox_core.h @@ -4,6 +4,19 @@ * 無料で使える中品質なテキスト読み上げソフトウェア、VOICEVOXのコア。 * *
+ *
+ * Availability + *
+ * + *
+ * ヘッダによって次の二つのマクロのうちどちらかが存在する。[リリース](https://github.com/voicevox/voicevox_core/releases)されているライブラリではiOSでのみ`VOICEVOX_LINK_ONNXRUNTIME`が、他のプラットフォームでは`VOICEVOX_LOAD_ONNXRUNTIME`が存在する。 + * + * - `VOICEVOX_LOAD_ONNXRUNTIME`: ::voicevox_onnxruntime_load_once と、それに付属するアイテムが利用可能になる。 + * - `VOICEVOX_LINK_ONNXRUNTIME`: ::voicevox_onnxruntime_init_once が利用可能になる。またこのマクロが存在するなら、このライブラリはONNX Runtimeをロード時動的リンクする。 + *
+ *
+ * + *
*
* ⚠️ Safety *
@@ -44,7 +57,7 @@ #ifndef VOICEVOX_CORE_INCLUDE_GUARD #define VOICEVOX_CORE_INCLUDE_GUARD -/* Generated with cbindgen:0.24.3 */ +/* Generated with cbindgen:0.27.0 */ #ifdef __cplusplus #include @@ -53,6 +66,17 @@ #include #endif // __cplusplus +//#define VOICEVOX_LINK_ONNXRUNTIME +//#define VOICEVOX_LOAD_ONNXRUNTIME + +#if !(defined(VOICEVOX_LINK_ONNXRUNTIME) || defined(VOICEVOX_LOAD_ONNXRUNTIME)) +#error "either `VOICEVOX_LINK_ONNXRUNTIME` or `VOICEVOX_LOAD_ONNXRUNTIME` must be enabled" +#endif + +#if defined(VOICEVOX_LINK_ONNXRUNTIME) && defined(VOICEVOX_LOAD_ONNXRUNTIME) +#error "`VOICEVOX_LINK_ONNXRUNTIME` or `VOICEVOX_LOAD_ONNXRUNTIME` cannot be enabled at the same time" +#endif + /** * ハードウェアアクセラレーションモードを設定する設定値。 */ @@ -102,6 +126,10 @@ enum VoicevoxResultCode * GPUモードがサポートされていない */ VOICEVOX_RESULT_GPU_SUPPORT_ERROR = 4, + /** + * 推論ライブラリのロードまたは初期化ができなかった + */ + VOICEVOX_RESULT_INIT_INFERENCE_RUNTIME_ERROR = 29, /** * スタイルIDに対するスタイルが見つからなかった */ @@ -113,7 +141,7 @@ enum VoicevoxResultCode /** * 推論に失敗した */ - VOICEVOX_RESULT_INFERENCE_ERROR = 8, + VOICEVOX_RESULT_RUN_MODEL_ERROR = 8, /** * コンテキストラベル出力に失敗した */ @@ -239,6 +267,21 @@ typedef int32_t VoicevoxUserDictWordType; */ typedef struct OpenJtalkRc OpenJtalkRc; +/** + * ONNX Runtime。 + * + * シングルトンであり、インスタンスは高々一つ。 + * + * ```c + * const VoicevoxOnnxruntime *ort1; + * voicevox_onnxruntime_load_once(voicevox_make_default_load_onnxruntime_options, + * &ort1); + * const VoicevoxOnnxruntime *ort2 = voicevox_onnxruntime_get(); + * assert(ort1 == ort2); + * ``` + */ +typedef struct VoicevoxOnnxruntime VoicevoxOnnxruntime; + /** * 音声シンセサイザ。 * @@ -252,12 +295,30 @@ typedef struct VoicevoxSynthesizer VoicevoxSynthesizer; typedef struct VoicevoxUserDict VoicevoxUserDict; /** - * 音声モデル。 + * 音声モデルファイル。 * * VVMファイルと対応する。 - * 構築(_construction_)は ::voicevox_voice_model_new_from_path で行い、破棄(_destruction_)は ::voicevox_voice_model_delete で行う。 + * 構築(_construction_)は ::voicevox_voice_model_file_open で行い、破棄(_destruction_)は ::voicevox_voice_model_file_close で行う。 */ -typedef struct VoicevoxVoiceModel VoicevoxVoiceModel; +typedef struct VoicevoxVoiceModelFile VoicevoxVoiceModelFile; + +#if defined(VOICEVOX_LOAD_ONNXRUNTIME) +/** + * ::voicevox_onnxruntime_load_once のオプション。 + * + * \availability{ + * [リリース](https://github.com/voicevox/voicevox_core/releases)されているライブラリではiOSを除くプラットフォームで利用可能。詳細はファイルレベルの"Availability"の節を参照。 + * } + */ +typedef struct VoicevoxLoadOnnxruntimeOptions { + /** + * ONNX Runtimeのファイル名(モジュール名)もしくはファイルパスを指定する。 + * + * `dlopen`/[`LoadLibraryExW`](https://learn.microsoft.com/en-us/windows/win32/api/libloaderapi/nf-libloaderapi-loadlibraryexw)の引数に使われる。デフォルトは ::voicevox_get_onnxruntime_lib_versioned_filename と同じ。 + */ + const char *filename; +} VoicevoxLoadOnnxruntimeOptions; +#endif /** * ::voicevox_synthesizer_new のオプション。 @@ -277,7 +338,7 @@ typedef struct VoicevoxInitializeOptions { /** * 音声モデルID。 */ -typedef const char *VoicevoxVoiceModelId; +typedef const uint8_t (*VoicevoxVoiceModelId)[16]; /** * スタイルID。 @@ -336,6 +397,115 @@ typedef struct VoicevoxUserDictWord { extern "C" { #endif // __cplusplus +#if defined(VOICEVOX_LOAD_ONNXRUNTIME) +/** + * ONNX Runtimeの動的ライブラリの、バージョン付きのファイル名。 + * + * WindowsとAndroidでは ::voicevox_get_onnxruntime_lib_unversioned_filename と同じ。 + * + * \availability{ + * [リリース](https://github.com/voicevox/voicevox_core/releases)されているライブラリではiOSを除くプラットフォームで利用可能。詳細はファイルレベルの"Availability"の節を参照。 + * } + */ +#ifdef _WIN32 +__declspec(dllimport) +#endif +const char *voicevox_get_onnxruntime_lib_versioned_filename(void); +#endif + +#if defined(VOICEVOX_LOAD_ONNXRUNTIME) +/** + * ONNX Runtimeの動的ライブラリの、バージョン無しのファイル名。 + * + * \availability{ + * [リリース](https://github.com/voicevox/voicevox_core/releases)されているライブラリではiOSを除くプラットフォームで利用可能。詳細はファイルレベルの"Availability"の節を参照。 + * } + */ +#ifdef _WIN32 +__declspec(dllimport) +#endif +const char *voicevox_get_onnxruntime_lib_unversioned_filename(void); +#endif + +#if defined(VOICEVOX_LOAD_ONNXRUNTIME) +/** + * デフォルトの ::voicevox_onnxruntime_load_once のオプションを生成する。 + * + * @return デフォルトの ::voicevox_onnxruntime_load_once のオプション + * + * \availability{ + * [リリース](https://github.com/voicevox/voicevox_core/releases)されているライブラリではiOSを除くプラットフォームで利用可能。詳細はファイルレベルの"Availability"の節を参照。 + * } + */ +#ifdef _WIN32 +__declspec(dllimport) +#endif +struct VoicevoxLoadOnnxruntimeOptions voicevox_make_default_load_onnxruntime_options(void); +#endif + +/** + * ::VoicevoxOnnxruntime のインスタンスが既に作られているならそれを得る。 + * + * 作られていなければ`NULL`を返す。 + * + * @returns ::VoicevoxOnnxruntime のインスタンス + */ +#ifdef _WIN32 +__declspec(dllimport) +#endif +const struct VoicevoxOnnxruntime *voicevox_onnxruntime_get(void); + +#if defined(VOICEVOX_LOAD_ONNXRUNTIME) +/** + * ONNX Runtimeをロードして初期化する。 + * + * 一度成功したら、以後は引数を無視して同じ参照を返す。 + * + * @param [in] options オプション + * @param [out] out_onnxruntime ::VoicevoxOnnxruntime のインスタンス + * + * @returns 結果コード + * + * \availability{ + * [リリース](https://github.com/voicevox/voicevox_core/releases)されているライブラリではiOSを除くプラットフォームで利用可能。詳細はファイルレベルの"Availability"の節を参照。 + * } + * + * \safety{ + * - `options.filename`はヌル終端文字列を指し、かつ読み込みについて有効でなければならない。 + * - `out_onnxruntime`は書き込みについて有効でなければならない。 + * } + */ +#ifdef _WIN32 +__declspec(dllimport) +#endif +VoicevoxResultCode voicevox_onnxruntime_load_once(struct VoicevoxLoadOnnxruntimeOptions options, + const struct VoicevoxOnnxruntime **out_onnxruntime); +#endif + +#if defined(VOICEVOX_LINK_ONNXRUNTIME) +/** + * ONNX Runtimeを初期化する。 + * + * 一度成功したら以後は同じ参照を返す。 + * + * @param [out] out_onnxruntime ::VoicevoxOnnxruntime のインスタンス + * + * @returns 結果コード + * + * \availability{ + * [リリース](https://github.com/voicevox/voicevox_core/releases)されているライブラリではiOSでのみ利用可能。詳細はファイルレベルの"Availability"の節を参照。 + * } + * + * \safety{ + * - `out_onnxruntime`は書き込みについて有効でなければならない。 + * } + */ +#ifdef _WIN32 +__declspec(dllimport) +#endif +VoicevoxResultCode voicevox_onnxruntime_init_once(const struct VoicevoxOnnxruntime **out_onnxruntime); +#endif + /** * ::OpenJtalkRc を構築(_construct_)する。 * @@ -371,11 +541,6 @@ VoicevoxResultCode voicevox_open_jtalk_rc_new(const char *open_jtalk_dic_dir, * * @param [in] open_jtalk Open JTalkのオブジェクト * @param [in] user_dict ユーザー辞書 - * - * \safety{ - * - `open_jtalk`は ::voicevox_open_jtalk_rc_new で得たものでなければならず、また ::voicevox_open_jtalk_rc_delete で解放されていてはいけない。 - * - `user_dict`は ::voicevox_user_dict_new で得たものでなければならず、また ::voicevox_user_dict_delete で解放されていてはいけない。 - * } */ #ifdef _WIN32 __declspec(dllimport) @@ -386,6 +551,10 @@ VoicevoxResultCode voicevox_open_jtalk_rc_use_user_dict(const struct OpenJtalkRc /** * ::OpenJtalkRc を破棄(_destruct_)する。 * + * 破棄対象への他スレッドでのアクセスが存在する場合、それらがすべて終わるのを待ってから破棄する。 + * + * この関数の呼び出し後に破棄し終えた対象にアクセスすると、プロセスを異常終了する。 + * * @param [in] open_jtalk 破棄対象 * * \example{ @@ -393,11 +562,6 @@ VoicevoxResultCode voicevox_open_jtalk_rc_use_user_dict(const struct OpenJtalkRc * voicevox_open_jtalk_rc_delete(open_jtalk); * ``` * } - * - * \safety{ - * - `open_jtalk`は ::voicevox_open_jtalk_rc_new で得たものでなければならず、また既にこの関数で解放されていてはいけない。 - * - `open_jtalk`は以後ダングリングポインタ(_dangling pointer_)として扱われなくてはならない。 - * } */ #ifdef _WIN32 __declspec(dllimport) @@ -423,7 +587,7 @@ __declspec(dllimport) const char *voicevox_get_version(void); /** - * VVMファイルから ::VoicevoxVoiceModel を構築(_construct_)する。 + * VVMファイルを開く。 * * @param [in] path vvmファイルへのUTF-8のファイルパス * @param [out] out_model 構築先 @@ -438,60 +602,57 @@ const char *voicevox_get_version(void); #ifdef _WIN32 __declspec(dllimport) #endif -VoicevoxResultCode voicevox_voice_model_new_from_path(const char *path, - struct VoicevoxVoiceModel **out_model); +VoicevoxResultCode voicevox_voice_model_file_open(const char *path, + struct VoicevoxVoiceModelFile **out_model); /** - * ::VoicevoxVoiceModel からIDを取得する。 + * ::VoicevoxVoiceModelFile からIDを取得する。 * * @param [in] model 音声モデル - * - * @returns 音声モデルID + * @param [out] output_voice_model_id 音声モデルID * * \safety{ - * - `model`は ::voicevox_voice_model_new_from_path で得たものでなければならず、また ::voicevox_voice_model_delete で解放されていてはいけない。 + * - `output_voice_model_id`は書き込みについて有効でなければならない。 * } */ #ifdef _WIN32 __declspec(dllimport) #endif -VoicevoxVoiceModelId voicevox_voice_model_id(const struct VoicevoxVoiceModel *model); +void voicevox_voice_model_file_id(const struct VoicevoxVoiceModelFile *model, + uint8_t (*output_voice_model_id)[16]); /** - * ::VoicevoxVoiceModel からメタ情報を取得する。 + * ::VoicevoxVoiceModelFile からメタ情報を取得する。 + * + * JSONの解放は ::voicevox_json_free で行う。 * * @param [in] model 音声モデル * * @returns メタ情報のJSON文字列 - * - * \safety{ - * - `model`は ::voicevox_voice_model_new_from_path で得たものでなければならず、また ::voicevox_voice_model_delete で解放されていてはいけない。 - * - 戻り値の文字列の生存期間(_lifetime_)は次にこの関数が呼ばれるか、`model`が破棄されるまでである。この生存期間を越えて文字列にアクセスしてはならない。 - * } */ #ifdef _WIN32 __declspec(dllimport) #endif -const char *voicevox_voice_model_get_metas_json(const struct VoicevoxVoiceModel *model); +char *voicevox_voice_model_file_create_metas_json(const struct VoicevoxVoiceModelFile *model); /** - * ::VoicevoxVoiceModel を破棄(_destruct_)する。 + * ::VoicevoxVoiceModelFile を、所有しているファイルディスクリプタを閉じた上で破棄(_destruct_)する。 * - * @param [in] model 破棄対象 + * 破棄対象への他スレッドでのアクセスが存在する場合、それらがすべて終わるのを待ってから破棄する。 * - * \safety{ - * - `model`は ::voicevox_voice_model_new_from_path で得たものでなければならず、また既にこの関数で解放されていてはいけない。 - * - `model`は以後ダングリングポインタ(_dangling pointer_)として扱われなくてはならない。 - * } + * この関数の呼び出し後に破棄し終えた対象にアクセスすると、プロセスを異常終了する。 + * + * @param [in] model 破棄対象 */ #ifdef _WIN32 __declspec(dllimport) #endif -void voicevox_voice_model_delete(struct VoicevoxVoiceModel *model); +void voicevox_voice_model_file_close(struct VoicevoxVoiceModelFile *model); /** * ::VoicevoxSynthesizer を構築(_construct_)する。 * + * @param [in] onnxruntime * @param [in] open_jtalk Open JTalkのオブジェクト * @param [in] options オプション * @param [out] out_synthesizer 構築先 @@ -499,26 +660,26 @@ void voicevox_voice_model_delete(struct VoicevoxVoiceModel *model); * @returns 結果コード * * \safety{ - * - `open_jtalk`は ::voicevox_voice_model_new_from_path で得たものでなければならず、また ::voicevox_open_jtalk_rc_new で解放されていてはいけない。 + * - `onnxruntime`は ::voicevox_onnxruntime_load_once または ::voicevox_onnxruntime_init_once で得たものでなければならない。 * - `out_synthesizer`は書き込みについて有効でなければならない。 * } */ #ifdef _WIN32 __declspec(dllimport) #endif -VoicevoxResultCode voicevox_synthesizer_new(const struct OpenJtalkRc *open_jtalk, +VoicevoxResultCode voicevox_synthesizer_new(const struct VoicevoxOnnxruntime *onnxruntime, + const struct OpenJtalkRc *open_jtalk, struct VoicevoxInitializeOptions options, struct VoicevoxSynthesizer **out_synthesizer); /** * ::VoicevoxSynthesizer を破棄(_destruct_)する。 * - * @param [in] synthesizer 破棄対象 + * 破棄対象への他スレッドでのアクセスが存在する場合、それらがすべて終わるのを待ってから破棄する。 * - * \safety{ - * - `synthesizer`は ::voicevox_synthesizer_new で得たものでなければならず、また既にこの関数で解放されていてはいけない。 - * - `synthesizer`は以後ダングリングポインタ(_dangling pointer_)として扱われなくてはならない。 - * } + * この関数の呼び出し後に破棄し終えた対象にアクセスすると、プロセスを異常終了する。 + * + * @param [in] synthesizer 破棄対象 */ #ifdef _WIN32 __declspec(dllimport) @@ -532,17 +693,12 @@ void voicevox_synthesizer_delete(struct VoicevoxSynthesizer *synthesizer); * @param [in] model 音声モデル * * @returns 結果コード - * - * \safety{ - * - `synthesizer`は ::voicevox_synthesizer_new で得たものでなければならず、また ::voicevox_synthesizer_delete で解放されていてはいけない。 - * - `model`は ::voicevox_voice_model_new_from_path で得たものでなければならず、また ::voicevox_voice_model_delete で解放されていてはいけない。 - * } */ #ifdef _WIN32 __declspec(dllimport) #endif VoicevoxResultCode voicevox_synthesizer_load_voice_model(const struct VoicevoxSynthesizer *synthesizer, - const struct VoicevoxVoiceModel *model); + const struct VoicevoxVoiceModelFile *model); /** * 音声モデルの読み込みを解除する。 @@ -553,8 +709,7 @@ VoicevoxResultCode voicevox_synthesizer_load_voice_model(const struct VoicevoxSy * @returns 結果コード * * \safety{ - * - `synthesizer`は ::voicevox_synthesizer_new で得たものでなければならず、また ::voicevox_synthesizer_delete で解放されていてはいけない。 - * - `model_id`はヌル終端文字列を指し、かつ読み込みについて有効でなければならない。 + * - `model_id`は読み込みについて有効でなければならない。 * } */ #ifdef _WIN32 @@ -563,16 +718,24 @@ __declspec(dllimport) VoicevoxResultCode voicevox_synthesizer_unload_voice_model(const struct VoicevoxSynthesizer *synthesizer, VoicevoxVoiceModelId model_id); +/** + * ::VoicevoxOnnxruntime のインスタンスを得る。 + * + * @param [in] synthesizer 音声シンセサイザ + * + * @returns ::VoicevoxOnnxruntime のインスタンス + */ +#ifdef _WIN32 +__declspec(dllimport) +#endif +const struct VoicevoxOnnxruntime *voicevox_synthesizer_get_onnxruntime(const struct VoicevoxSynthesizer *synthesizer); + /** * ハードウェアアクセラレーションがGPUモードか判定する。 * * @param [in] synthesizer 音声シンセサイザ * * @returns GPUモードかどうか - * - * \safety{ - * - `synthesizer`は ::voicevox_synthesizer_new で得たものでなければならず、また ::voicevox_synthesizer_delete で解放されていてはいけない。 - * } */ #ifdef _WIN32 __declspec(dllimport) @@ -588,8 +751,7 @@ bool voicevox_synthesizer_is_gpu_mode(const struct VoicevoxSynthesizer *synthesi * @returns モデルが読み込まれているかどうか * * \safety{ - * - `synthesizer`は ::voicevox_synthesizer_new で得たものでなければならず、また ::voicevox_synthesizer_delete で解放されていてはいけない。 - * - `model_id`はヌル終端文字列を指し、かつ読み込みについて有効でなければならない。 + * - `model_id`は読み込みについて有効でなければならない。 * } */ #ifdef _WIN32 @@ -606,10 +768,6 @@ bool voicevox_synthesizer_is_loaded_voice_model(const struct VoicevoxSynthesizer * @param [in] synthesizer 音声シンセサイザ * * @return メタ情報のJSON文字列 - * - * \safety{ - * - `synthesizer`は ::voicevox_synthesizer_new で得たものでなければならず、また ::voicevox_synthesizer_delete で解放されていてはいけない。 - * } */ #ifdef _WIN32 __declspec(dllimport) @@ -617,12 +775,13 @@ __declspec(dllimport) char *voicevox_synthesizer_create_metas_json(const struct VoicevoxSynthesizer *synthesizer); /** - * このライブラリで利用可能なデバイスの情報を、JSONで取得する。 + * ONNX Runtimeとして利用可能なデバイスの情報を、JSONで取得する。 * * JSONの解放は ::voicevox_json_free で行う。 * - * あくまで本ライブラリが対応しているデバイスの情報であることに注意。GPUが使える環境ではなかったとしても`cuda`や`dml`は`true`を示しうる。 + * あくまでONNX Runtimeが対応しているデバイスの情報であることに注意。GPUが使える環境ではなかったとしても`cuda`や`dml`は`true`を示しうる。 * + * @param [in] onnxruntime * @param [out] output_supported_devices_json サポートデバイス情報のJSON文字列 * * @returns 結果コード @@ -630,18 +789,20 @@ char *voicevox_synthesizer_create_metas_json(const struct VoicevoxSynthesizer *s * \example{ * ```c * char *supported_devices; - * VoicevoxResultCode result = voicevox_create_supported_devices_json(&supported_devices); + * VoicevoxResultCode result = voicevox_onnxruntime_create_supported_devices_json(onnxruntime, &supported_devices); * ``` * } * * \safety{ + * - `onnxruntime`は ::voicevox_onnxruntime_load_once または ::voicevox_onnxruntime_init_once で得たものでなければならない。 * - `output_supported_devices_json`は書き込みについて有効でなければならない。 * } */ #ifdef _WIN32 __declspec(dllimport) #endif -VoicevoxResultCode voicevox_create_supported_devices_json(char **output_supported_devices_json); +VoicevoxResultCode voicevox_onnxruntime_create_supported_devices_json(const struct VoicevoxOnnxruntime *onnxruntime, + char **output_supported_devices_json); /** * AquesTalk風記法から、AudioQueryをJSONとして生成する。 @@ -665,7 +826,6 @@ VoicevoxResultCode voicevox_create_supported_devices_json(char **output_supporte * } * * \safety{ - * - `synthesizer`は ::voicevox_synthesizer_new で得たものでなければならず、また ::voicevox_synthesizer_delete で解放されていてはいけない。 * - `kana`はヌル終端文字列を指し、かつ読み込みについて有効でなければならない。 * - `output_audio_query_json`は書き込みについて有効でなければならない。 * } @@ -700,7 +860,6 @@ VoicevoxResultCode voicevox_synthesizer_create_audio_query_from_kana(const struc * } * * \safety{ - * - `synthesizer`は ::voicevox_synthesizer_new で得たものでなければならず、また ::voicevox_synthesizer_delete で解放されていてはいけない。 * - `text`はヌル終端文字列を指し、かつ読み込みについて有効でなければならない。 * - `output_audio_query_json`は書き込みについて有効でなければならない。 * } @@ -736,7 +895,6 @@ VoicevoxResultCode voicevox_synthesizer_create_audio_query(const struct Voicevox * } * * \safety{ - * - `synthesizer`は ::voicevox_synthesizer_new で得たものでなければならず、また ::voicevox_synthesizer_delete で解放されていてはいけない。 * - `kana`はヌル終端文字列を指し、かつ読み込みについて有効でなければならない。 * - `output_audio_query_json`は書き込みについて有効でなければならない。 * } @@ -771,7 +929,6 @@ VoicevoxResultCode voicevox_synthesizer_create_accent_phrases_from_kana(const st * } * * \safety{ - * - `synthesizer`は ::voicevox_synthesizer_new で得たものでなければならず、また ::voicevox_synthesizer_delete で解放されていてはいけない。 * - `text`はヌル終端文字列を指し、かつ読み込みについて有効でなければならない。 * - `output_audio_query_json`は書き込みについて有効でなければならない。 * } @@ -797,7 +954,6 @@ VoicevoxResultCode voicevox_synthesizer_create_accent_phrases(const struct Voice * @returns 結果コード * * \safety{ - * - `synthesizer`は ::voicevox_synthesizer_new で得たものでなければならず、また ::voicevox_synthesizer_delete で解放されていてはいけない。 * - `accent_phrases_json`はヌル終端文字列を指し、かつ読み込みについて有効でなければならない。 * - `output_audio_query_json`は書き込みについて有効でなければならない。 * } @@ -823,7 +979,6 @@ VoicevoxResultCode voicevox_synthesizer_replace_mora_data(const struct VoicevoxS * @returns 結果コード * * \safety{ - * - `synthesizer`は ::voicevox_synthesizer_new で得たものでなければならず、また ::voicevox_synthesizer_delete で解放されていてはいけない。 * - `accent_phrases_json`はヌル終端文字列を指し、かつ読み込みについて有効でなければならない。 * - `output_audio_query_json`は書き込みについて有効でなければならない。 * } @@ -849,7 +1004,6 @@ VoicevoxResultCode voicevox_synthesizer_replace_phoneme_length(const struct Voic * @returns 結果コード * * \safety{ - * - `synthesizer`は ::voicevox_synthesizer_new で得たものでなければならず、また ::voicevox_synthesizer_delete で解放されていてはいけない。 * - `accent_phrases_json`はヌル終端文字列を指し、かつ読み込みについて有効でなければならない。 * - `output_audio_query_json`は書き込みについて有効でなければならない。 * } @@ -886,7 +1040,6 @@ struct VoicevoxSynthesisOptions voicevox_make_default_synthesis_options(void); * @returns 結果コード * * \safety{ - * - `synthesizer`は ::voicevox_synthesizer_new で得たものでなければならず、また ::voicevox_synthesizer_delete で解放されていてはいけない。 * - `audio_query_json`はヌル終端文字列を指し、かつ読み込みについて有効でなければならない。 * - `output_wav_length`は書き込みについて有効でなければならない。 * - `output_wav`は書き込みについて有効でなければならない。 @@ -926,7 +1079,6 @@ struct VoicevoxTtsOptions voicevox_make_default_tts_options(void); * @returns 結果コード * * \safety{ - * - `synthesizer`は ::voicevox_synthesizer_new で得たものでなければならず、また ::voicevox_synthesizer_delete で解放されていてはいけない。 * - `kana`はヌル終端文字列を指し、かつ読み込みについて有効でなければならない。 * - `output_wav_length`は書き込みについて有効でなければならない。 * - `output_wav`は書き込みについて有効でなければならない。 @@ -957,7 +1109,6 @@ VoicevoxResultCode voicevox_synthesizer_tts_from_kana(const struct VoicevoxSynth * @returns 結果コード * * \safety{ - * - `synthesizer`は ::voicevox_synthesizer_new で得たものでなければならず、また ::voicevox_synthesizer_delete で解放されていてはいけない。 * - `text`はヌル終端文字列を指し、かつ読み込みについて有効でなければならない。 * - `output_wav_length`は書き込みについて有効でなければならない。 * - `output_wav`は書き込みについて有効でなければならない。 @@ -980,7 +1131,8 @@ VoicevoxResultCode voicevox_synthesizer_tts(const struct VoicevoxSynthesizer *sy * * \safety{ * - `json`は以下のAPIで得られたポインタでなくてはいけない。 - * - ::voicevox_create_supported_devices_json + * - ::voicevox_onnxruntime_create_supported_devices_json + * - ::voicevox_voice_model_file_create_metas_json * - ::voicevox_synthesizer_create_metas_json * - ::voicevox_synthesizer_create_audio_query * - ::voicevox_synthesizer_create_accent_phrases @@ -1074,7 +1226,6 @@ struct VoicevoxUserDict *voicevox_user_dict_new(void); * @returns 結果コード * * \safety{ - * - `user_dict`は ::voicevox_user_dict_new で得たものでなければならず、また ::voicevox_user_dict_delete で解放されていてはいけない。 * - `dict_path`はヌル終端文字列を指し、かつ読み込みについて有効でなければならない。 * } */ @@ -1096,7 +1247,6 @@ VoicevoxResultCode voicevox_user_dict_load(const struct VoicevoxUserDict *user_d * @param user_dict は有効な :VoicevoxUserDict のポインタであること * * \safety{ - * - `user_dict`は ::voicevox_user_dict_new で得たものでなければならず、また ::voicevox_user_dict_delete で解放されていてはいけない。 * - `word->surface`と`word->pronunciation`はヌル終端文字列を指し、かつ読み込みについて有効でなければならない。 * - `output_word_uuid`は書き込みについて有効でなければならない。 * } @@ -1117,7 +1267,6 @@ VoicevoxResultCode voicevox_user_dict_add_word(const struct VoicevoxUserDict *us * @returns 結果コード * * \safety{ - * - `user_dict`は ::voicevox_user_dict_new で得たものでなければならず、また ::voicevox_user_dict_delete で解放されていてはいけない。 * - `word_uuid`は読み込みについて有効でなければならない。 * - `word->surface`と`word->pronunciation`はヌル終端文字列を指し、かつ読み込みについて有効でなければならない。 * } @@ -1137,7 +1286,6 @@ VoicevoxResultCode voicevox_user_dict_update_word(const struct VoicevoxUserDict * @returns 結果コード * * \safety{ - * - `user_dict`は ::voicevox_user_dict_new で得たものでなければならず、また ::voicevox_user_dict_delete で解放されていてはいけない。 * - `word_uuid`は読み込みについて有効でなければならない。 * } */ @@ -1157,7 +1305,6 @@ VoicevoxResultCode voicevox_user_dict_remove_word(const struct VoicevoxUserDict * @returns 結果コード * * \safety{ - * - `user_dict`は ::voicevox_user_dict_new で得たものでなければならず、また ::voicevox_user_dict_delete で解放されていてはいけない。 * - `output_json`は書き込みについて有効でなければならない。 * } */ @@ -1173,10 +1320,6 @@ VoicevoxResultCode voicevox_user_dict_to_json(const struct VoicevoxUserDict *use * @param [in] user_dict ユーザー辞書 * @param [in] other_dict インポートするユーザー辞書 * @returns 結果コード - * - * \safety{ - * - `user_dict`と`other_dict`は ::voicevox_user_dict_new で得たものでなければならず、また ::voicevox_user_dict_delete で解放されていてはいけない。 - * } */ #ifdef _WIN32 __declspec(dllimport) @@ -1191,7 +1334,6 @@ VoicevoxResultCode voicevox_user_dict_import(const struct VoicevoxUserDict *user * @param [in] path 保存先のファイルパス * * \safety{ - * - `user_dict`は ::voicevox_user_dict_new で得たものでなければならず、また ::voicevox_user_dict_delete で解放されていてはいけない。 * - `path`はヌル終端文字列を指し、かつ読み込みについて有効でなければならない。 * } */ @@ -1204,11 +1346,11 @@ VoicevoxResultCode voicevox_user_dict_save(const struct VoicevoxUserDict *user_d /** * ユーザー辞書を破棄(_destruct_)する。 * - * @param [in] user_dict 破棄対象 + * 破棄対象への他スレッドでのアクセスが存在する場合、それらがすべて終わるのを待ってから破棄する。 * - * \safety{ - * - `user_dict`は ::voicevox_user_dict_new で得たものでなければならず、また既にこの関数で解放されていてはいけない。 - * } + * この関数の呼び出し後に破棄し終えた対象にアクセスすると、プロセスを異常終了する。 + * + * @param [in] user_dict 破棄対象 */ #ifdef _WIN32 __declspec(dllimport) @@ -1216,7 +1358,7 @@ __declspec(dllimport) void voicevox_user_dict_delete(struct VoicevoxUserDict *user_dict); #ifdef __cplusplus -} // extern "C" -#endif // __cplusplus +} // extern "C" +#endif // __cplusplus -#endif /* VOICEVOX_CORE_INCLUDE_GUARD */ +#endif /* VOICEVOX_CORE_INCLUDE_GUARD */ diff --git a/crates/voicevox_core_c_api/src/c_impls.rs b/crates/voicevox_core_c_api/src/c_impls.rs index 4e73bf0fb..c657d0908 100644 --- a/crates/voicevox_core_c_api/src/c_impls.rs +++ b/crates/voicevox_core_c_api/src/c_impls.rs @@ -1,49 +1,163 @@ -use std::{ffi::CString, path::Path}; +use std::{ + collections::HashMap, + ffi::CString, + path::Path, + ptr::NonNull, + sync::{Arc, LazyLock}, +}; use camino::Utf8Path; -use voicevox_core::{InitializeOptions, Result, VoiceModelId}; +use duplicate::duplicate_item; +use easy_ext::ext; +use ref_cast::ref_cast_custom; +use voicevox_core::{InitializeOptions, Result, SpeakerMeta, VoiceModelId}; -use crate::{helpers::CApiResult, OpenJtalkRc, VoicevoxSynthesizer, VoicevoxVoiceModel}; +use crate::{ + helpers::CApiResult, + object::{CApiObject, CApiObjectPtrExt as _}, + OpenJtalkRc, VoicevoxOnnxruntime, VoicevoxSynthesizer, VoicevoxUserDict, + VoicevoxVoiceModelFile, +}; + +// FIXME: 中身(Rust API)を直接操作するかラッパーメソッド越しにするのかが混在していて、一貫性を +// 欠いている + +impl VoicevoxOnnxruntime { + #[cfg(feature = "load-onnxruntime")] + pub(crate) fn lib_versioned_filename() -> &'static std::ffi::CStr { + to_cstr!(voicevox_core::blocking::Onnxruntime::LIB_VERSIONED_FILENAME) + } + + #[cfg(feature = "load-onnxruntime")] + pub(crate) fn lib_unversioned_filename() -> &'static std::ffi::CStr { + to_cstr!(voicevox_core::blocking::Onnxruntime::LIB_UNVERSIONED_FILENAME) + } + + #[ref_cast_custom] + fn new(rust: &voicevox_core::blocking::Onnxruntime) -> &Self; + + pub(crate) fn get() -> Option<&'static Self> { + voicevox_core::blocking::Onnxruntime::get().map(Self::new) + } + + #[cfg(feature = "load-onnxruntime")] + pub(crate) fn load_once(filename: &std::ffi::CStr) -> CApiResult<&'static Self> { + use crate::helpers::ensure_utf8; + + let inner = voicevox_core::blocking::Onnxruntime::load_once() + .filename(ensure_utf8(filename)?) + .exec()?; + Ok(Self::new(inner)) + } + + #[cfg(feature = "link-onnxruntime")] + pub(crate) fn init_once() -> CApiResult<&'static Self> { + let inner = voicevox_core::blocking::Onnxruntime::init_once()?; + Ok(Self::new(inner)) + } +} + +#[cfg(feature = "load-onnxruntime")] +macro_rules! to_cstr { + ($s:expr) => {{ + const __RUST_STR: &str = $s; + static __C_STR: &[u8] = const_format::concatcp!(__RUST_STR, '\0').as_bytes(); + + std::ffi::CStr::from_bytes_with_nul(__C_STR) + .unwrap_or_else(|e| panic!("{__RUST_STR:?} should not contain `\\0`: {e}")) + }}; +} +#[cfg(feature = "load-onnxruntime")] +use to_cstr; impl OpenJtalkRc { - pub(crate) fn new(open_jtalk_dic_dir: impl AsRef) -> Result { - Ok(Self { - open_jtalk: voicevox_core::blocking::OpenJtalk::new(open_jtalk_dic_dir)?, - }) + pub(crate) fn new(open_jtalk_dic_dir: impl AsRef) -> Result> { + let body = voicevox_core::blocking::OpenJtalk::new(open_jtalk_dic_dir)?; + Ok(::new(body)) } } impl VoicevoxSynthesizer { - pub(crate) fn new(open_jtalk: &OpenJtalkRc, options: &InitializeOptions) -> Result { - let synthesizer = - voicevox_core::blocking::Synthesizer::new(open_jtalk.open_jtalk.clone(), options)?; - Ok(Self { synthesizer }) + pub(crate) fn new( + onnxruntime: &'static VoicevoxOnnxruntime, + open_jtalk: *const OpenJtalkRc, + options: &InitializeOptions, + ) -> Result> { + let body = voicevox_core::blocking::Synthesizer::new( + &onnxruntime.0, + open_jtalk.body().clone(), + options, + )?; + Ok(::new(body)) + } +} + +#[ext(VoicevoxSynthesizerPtrExt)] +impl *const VoicevoxSynthesizer { + pub(crate) fn onnxruntime(self) -> &'static VoicevoxOnnxruntime { + VoicevoxOnnxruntime::new(self.body().onnxruntime()) } pub(crate) fn load_voice_model( - &self, - model: &voicevox_core::blocking::VoiceModel, + self, + model: &voicevox_core::blocking::VoiceModelFile, ) -> CApiResult<()> { - self.synthesizer.load_voice_model(model)?; + self.body().load_voice_model(model)?; Ok(()) } - pub(crate) fn unload_voice_model(&self, model_id: &VoiceModelId) -> Result<()> { - self.synthesizer.unload_voice_model(model_id)?; + pub(crate) fn unload_voice_model(self, model_id: VoiceModelId) -> Result<()> { + self.body().unload_voice_model(model_id)?; Ok(()) } - pub(crate) fn metas(&self) -> CString { - let metas = &self.synthesizer.metas(); - CString::new(serde_json::to_string(metas).unwrap()).unwrap() + pub(crate) fn metas(self) -> CString { + metas_to_json(&self.body().metas()) + } +} + +impl VoicevoxVoiceModelFile { + pub(crate) fn open(path: impl AsRef) -> Result> { + let model = voicevox_core::blocking::VoiceModelFile::open(path)?; + Ok(Self::new(model)) + } +} + +#[ext(VoicevoxVoiceModelFilePtrExt)] +impl *const VoicevoxVoiceModelFile { + pub(crate) fn metas(self) -> CString { + metas_to_json(self.body().metas()) } } -impl VoicevoxVoiceModel { - pub(crate) fn from_path(path: impl AsRef) -> Result { - let model = voicevox_core::blocking::VoiceModel::from_path(path)?; - let id = CString::new(model.id().raw_voice_model_id().as_str()).unwrap(); - let metas = CString::new(serde_json::to_string(model.metas()).unwrap()).unwrap(); - Ok(Self { model, id, metas }) +fn metas_to_json(metas: &[SpeakerMeta]) -> CString { + let metas = serde_json::to_string(metas).expect("should not fail"); + CString::new(metas).expect("should not contain NUL") +} + +#[duplicate_item( + H B; + [ OpenJtalkRc ] [ voicevox_core::blocking::OpenJtalk ]; + [ VoicevoxUserDict ] [ voicevox_core::blocking::UserDict ]; + [ VoicevoxSynthesizer ] [ voicevox_core::blocking::Synthesizer ]; + [ VoicevoxVoiceModelFile ] [ voicevox_core::blocking::VoiceModelFile ]; +)] +impl CApiObject for H { + type RustApiObject = B; + + fn heads() -> &'static std::sync::Mutex> { + static HEADS: std::sync::Mutex> = std::sync::Mutex::new(vec![]); + &HEADS + } + + fn bodies() -> &'static std::sync::Mutex< + HashMap>>>, + > { + #[expect(clippy::type_complexity, reason = "`CApiObject::bodies`と同様")] + static BODIES: LazyLock< + std::sync::Mutex>>>>, + > = LazyLock::new(Default::default); + + &BODIES } } diff --git a/crates/voicevox_core_c_api/src/compatible_engine.rs b/crates/voicevox_core_c_api/src/compatible_engine.rs index 6755910f5..7b1a03e7e 100644 --- a/crates/voicevox_core_c_api/src/compatible_engine.rs +++ b/crates/voicevox_core_c_api/src/compatible_engine.rs @@ -2,17 +2,14 @@ use std::{ collections::BTreeMap, env, ffi::{c_char, CString}, - sync::{Mutex, MutexGuard}, + sync::{Arc, LazyLock, Mutex, MutexGuard}, }; use libc::c_int; -use once_cell::sync::Lazy; -use voicevox_core::{ - StyleId, SupportedDevices, VoiceModelId, __internal::interop::PerformInference as _, -}; +use voicevox_core::{StyleId, VoiceModelId, __internal::interop::PerformInference as _}; -use crate::init_logger_once; +use crate::{helpers::display_error, init_logger_once}; macro_rules! ensure_initialized { ($synthesizer:expr $(,)?) => { @@ -26,29 +23,35 @@ macro_rules! ensure_initialized { }; } -static ERROR_MESSAGE: Lazy> = Lazy::new(|| Mutex::new(String::new())); +static ERROR_MESSAGE: LazyLock> = LazyLock::new(|| Mutex::new(String::new())); + +static ONNXRUNTIME: LazyLock<&'static voicevox_core::blocking::Onnxruntime> = LazyLock::new(|| { + voicevox_core::blocking::Onnxruntime::load_once() + .exec() + .unwrap_or_else(|err| { + display_error(&err); + panic!("ONNX Runtimeをロードもしくは初期化ができなかったため、クラッシュします"); + }) +}); struct VoiceModelSet { - all_vvms: Vec, + all_vvms: Vec>, all_metas_json: CString, style_model_map: BTreeMap, - model_map: BTreeMap, + model_map: BTreeMap>, } -static VOICE_MODEL_SET: Lazy = Lazy::new(|| { +static VOICE_MODEL_SET: LazyLock = LazyLock::new(|| { let all_vvms = get_all_models(); - let model_map: BTreeMap<_, _> = all_vvms - .iter() - .map(|vvm| (vvm.id().clone(), vvm.clone())) - .collect(); + let model_map: BTreeMap<_, _> = all_vvms.iter().map(|vvm| (vvm.id(), vvm.clone())).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() { - for style in meta.styles().iter() { - style_model_map.insert(*style.id(), vvm.id().clone()); + for style in meta.styles.iter() { + style_model_map.insert(style.id, vvm.id()); } } } @@ -63,7 +66,7 @@ static VOICE_MODEL_SET: Lazy = Lazy::new(|| { /// # Panics /// /// 失敗したらパニックする - fn get_all_models() -> Vec { + fn get_all_models() -> Vec> { let root_dir = if let Some(root_dir) = env::var_os(ROOT_DIR_ENV_NAME) { root_dir.into() } else { @@ -81,7 +84,7 @@ static VOICE_MODEL_SET: Lazy = Lazy::new(|| { .unwrap_or_else(|e| panic!("{}が読めませんでした: {e}", root_dir.display())) .into_iter() .filter(|entry| entry.path().extension().map_or(false, |ext| ext == "vvm")) - .map(|entry| voicevox_core::blocking::VoiceModel::from_path(entry.path())) + .map(|entry| voicevox_core::blocking::VoiceModelFile::open(entry.path()).map(Arc::new)) .collect::>() .unwrap() } @@ -95,8 +98,8 @@ fn voice_model_set() -> &'static VoiceModelSet { &VOICE_MODEL_SET } -static SYNTHESIZER: Lazy>>> = - Lazy::new(|| Mutex::new(None)); +static SYNTHESIZER: LazyLock>>> = + LazyLock::new(|| Mutex::new(None)); fn lock_synthesizer() -> MutexGuard<'static, Option>> { SYNTHESIZER.lock().unwrap() @@ -114,6 +117,7 @@ pub extern "C" fn initialize(use_gpu: bool, cpu_num_threads: c_int, load_all_mod init_logger_once(); let result = (|| { let synthesizer = voicevox_core::blocking::Synthesizer::new( + *ONNXRUNTIME, (), &voicevox_core::InitializeOptions { acceleration_mode: if use_gpu { @@ -198,8 +202,15 @@ pub extern "C" fn supported_devices() -> *const c_char { init_logger_once(); return SUPPORTED_DEVICES.as_ptr(); - static SUPPORTED_DEVICES: Lazy = Lazy::new(|| { - CString::new(SupportedDevices::create().unwrap().to_json().to_string()).unwrap() + static SUPPORTED_DEVICES: LazyLock = LazyLock::new(|| { + CString::new( + ONNXRUNTIME + .supported_devices() + .unwrap() + .to_json() + .to_string(), + ) + .unwrap() }); } diff --git a/crates/voicevox_core_c_api/src/drop_check.rs b/crates/voicevox_core_c_api/src/drop_check.rs index ae02d6aa2..2dd85f3f3 100644 --- a/crates/voicevox_core_c_api/src/drop_check.rs +++ b/crates/voicevox_core_c_api/src/drop_check.rs @@ -13,7 +13,7 @@ use std::{ /// `CString`は`Box`と同様Cの世界でもポインタ一つで実体を表すことができるため、こちら側 /// で管理すべきものは本来無い。しかしながら本クレートが提供するAPIには「解放不要」な文字列を返すも /// のが含まれている。ユーザーが誤ってそのような文字列を解放するのは未定義動作 (undefined behavior) -/// であるため、綺麗にSEGVするとも限らない。`once_cell::sync::Lazy`由来の文字列の場合、最悪解放が成 +/// であるため、綺麗にSEGVするとも限らない。`std::sync::LazyLock`由来の文字列の場合、最悪解放が成 /// 功してしまう。 /// /// この構造体はCの世界から帰ってきた`*mut c_char`を`CString`としてdropする際、それが本当にこちら側 diff --git a/crates/voicevox_core_c_api/src/helpers.rs b/crates/voicevox_core_c_api/src/helpers.rs index 1c163a0d0..26a60d033 100644 --- a/crates/voicevox_core_c_api/src/helpers.rs +++ b/crates/voicevox_core_c_api/src/helpers.rs @@ -1,10 +1,12 @@ -use std::{error::Error as _, ffi::CStr, fmt::Debug, iter}; -use voicevox_core::{AudioQueryModel, UserDictWord}; +use easy_ext::ext; +use std::{ffi::CStr, fmt::Debug, iter}; +use uuid::Uuid; +use voicevox_core::{AudioQuery, UserDictWord, VoiceModelId}; use thiserror::Error; use tracing::error; -use voicevox_core::AccentPhraseModel; +use voicevox_core::AccentPhrase; use crate::{ result_code::VoicevoxResultCode, VoicevoxAccelerationMode, VoicevoxInitializeOptions, @@ -17,14 +19,6 @@ pub(crate) fn into_result_code_with_error(result: CApiResult<()>) -> VoicevoxRes } return into_result_code(result); - fn display_error(err: &CApiError) { - itertools::chain( - [err.to_string()], - iter::successors(err.source(), |&e| e.source()).map(|e| format!("Caused by: {e}")), - ) - .for_each(|msg| error!("{msg}")); - } - fn into_result_code(result: CApiResult<()>) -> VoicevoxResultCode { use voicevox_core::ErrorKind::*; use CApiError::*; @@ -35,6 +29,7 @@ pub(crate) fn into_result_code_with_error(result: CApiResult<()>) -> VoicevoxRes Err(RustApi(err)) => match err.kind() { NotLoadedOpenjtalkDict => VOICEVOX_RESULT_NOT_LOADED_OPENJTALK_DICT_ERROR, GpuSupport => VOICEVOX_RESULT_GPU_SUPPORT_ERROR, + InitInferenceRuntime => VOICEVOX_RESULT_INIT_INFERENCE_RUNTIME_ERROR, OpenZipFile => VOICEVOX_RESULT_OPEN_ZIP_FILE_ERROR, ReadZipEntry => VOICEVOX_RESULT_READ_ZIP_ENTRY_ERROR, InvalidModelFormat => VOICEVOX_RESULT_INVALID_MODEL_HEADER_ERROR, @@ -44,7 +39,7 @@ pub(crate) fn into_result_code_with_error(result: CApiResult<()>) -> VoicevoxRes GetSupportedDevices => VOICEVOX_RESULT_GET_SUPPORTED_DEVICES_ERROR, StyleNotFound => VOICEVOX_RESULT_STYLE_NOT_FOUND_ERROR, ModelNotFound => VOICEVOX_RESULT_MODEL_NOT_FOUND_ERROR, - InferenceFailed => VOICEVOX_RESULT_INFERENCE_ERROR, + RunModel => VOICEVOX_RESULT_RUN_MODEL_ERROR, ExtractFullContextLabel => VOICEVOX_RESULT_EXTRACT_FULL_CONTEXT_LABEL_ERROR, ParseKana => VOICEVOX_RESULT_PARSE_KANA_ERROR, LoadUserDict => VOICEVOX_RESULT_LOAD_USER_DICT_ERROR, @@ -61,6 +56,14 @@ pub(crate) fn into_result_code_with_error(result: CApiResult<()>) -> VoicevoxRes } } +pub(crate) fn display_error(err: &impl std::error::Error) { + itertools::chain( + [err.to_string()], + iter::successors(err.source(), |&e| e.source()).map(|e| format!("Caused by: {e}")), + ) + .for_each(|msg| error!("{msg}")); +} + pub(crate) type CApiResult = std::result::Result; #[derive(Error, Debug)] @@ -77,11 +80,11 @@ pub(crate) enum CApiError { InvalidUuid(uuid::Error), } -pub(crate) fn audio_query_model_to_json(audio_query_model: &AudioQueryModel) -> String { +pub(crate) fn audio_query_model_to_json(audio_query_model: &AudioQuery) -> String { serde_json::to_string(audio_query_model).expect("should be always valid") } -pub(crate) fn accent_phrases_to_json(audio_query_model: &[AccentPhraseModel]) -> String { +pub(crate) fn accent_phrases_to_json(audio_query_model: &[AccentPhrase]) -> String { serde_json::to_string(audio_query_model).expect("should be always valid") } @@ -163,6 +166,13 @@ impl Default for VoicevoxSynthesisOptions { } } +#[ext(UuidBytesExt)] +pub(crate) impl uuid::Bytes { + fn to_model_id(self) -> VoiceModelId { + Uuid::from_bytes(self).into() + } +} + impl VoicevoxUserDictWord { pub(crate) unsafe fn try_into_word(&self) -> CApiResult { Ok(UserDictWord::new( diff --git a/crates/voicevox_core_c_api/src/lib.rs b/crates/voicevox_core_c_api/src/lib.rs index a5da9b6d3..76a23a442 100644 --- a/crates/voicevox_core_c_api/src/lib.rs +++ b/crates/voicevox_core_c_api/src/lib.rs @@ -1,37 +1,45 @@ -// ここにあるRustdocはcbindgen向けのものである。safety documentation自体は書くが、Doxygenの慣習に従 -// い`
`で書く。 -#![allow(clippy::missing_safety_doc)] +// ここにある`#[doc]`はすべてrustdocではなくDoxygen向けのものである。 +#![allow( + clippy::doc_lazy_continuation, + clippy::missing_safety_doc // safety documentation自体は書くが、Doxygenの慣習に従い`
`で書く +)] mod c_impls; /// cbindgen:ignore +#[cfg(feature = "load-onnxruntime")] mod compatible_engine; mod drop_check; mod helpers; +mod object; mod result_code; mod slice_owner; use self::drop_check::C_STRING_DROP_CHECKER; use self::helpers::{ accent_phrases_to_json, audio_query_model_to_json, ensure_utf8, into_result_code_with_error, - CApiError, + CApiError, UuidBytesExt as _, }; +use self::object::{CApiObject as _, CApiObjectPtrExt as _}; use self::result_code::VoicevoxResultCode; use self::slice_owner::U8_SLICE_OWNER; use anstream::{AutoStream, RawStream}; +use c_impls::{VoicevoxSynthesizerPtrExt as _, VoicevoxVoiceModelFilePtrExt as _}; use chrono::SecondsFormat; use colorchoice::ColorChoice; -use derive_getters::Getters; +use educe::Educe; +use ref_cast::RefCastCustom; use std::env; use std::ffi::{CStr, CString}; use std::fmt; use std::io; +use std::mem::MaybeUninit; use std::os::raw::c_char; use std::ptr::NonNull; -use std::sync::{Arc, Once}; +use std::sync::Once; use tracing_subscriber::fmt::format::Writer; use tracing_subscriber::EnvFilter; use uuid::Uuid; -use voicevox_core::{AccentPhraseModel, AudioQueryModel, TtsOptions, UserDictWord, VoiceModelId}; -use voicevox_core::{StyleId, SupportedDevices, SynthesisOptions}; +use voicevox_core::{AccentPhrase, AudioQuery, TtsOptions, UserDictWord}; +use voicevox_core::{StyleId, SynthesisOptions}; fn init_logger_once() { static ONCE: Once = Once::new(); @@ -54,17 +62,16 @@ fn init_logger_once() { && anstyle_query::windows::enable_ansi_colors().unwrap_or(true) }; - // FIXME: `try_init` → `init` (subscriberは他に存在しないはずなので) - let _ = tracing_subscriber::fmt() + 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=info".into() + "error,voicevox_core=info,voicevox_core_c_api=info,ort=info".into() }) .with_timer(local_time as fn(&mut Writer<'_>) -> _) .with_ansi(ansi) .with_writer(out) - .try_init(); + .init(); }); fn local_time(wtr: &mut Writer<'_>) -> fmt::Result { @@ -78,12 +85,170 @@ fn init_logger_once() { } } -/* - * Cの関数として公開するための型や関数を定義するこれらの実装はvoicevox_core/publish.rsに定義してある対応する関数にある - * この関数ではvoicevox_core/publish.rsにある対応する関数の呼び出しと、その戻り値をCの形式に変換する処理のみとする - * これはC文脈の処理と実装をわけるためと、内部実装の変更がAPIに影響を与えにくくするためである - * voicevox_core/publish.rsにある対応する関数とはこのファイルに定義してある公開関数からvoicevoxプレフィックスを取り除いた名前の関数である - */ +// TODO: https://github.com/mozilla/cbindgen/issues/927 +//#[cfg(feature = "load-onnxruntime")] +//pub const VOICEVOX_ONNXRUNTIME_LIB_NAME: &CStr = ..; +//#[cfg(feature = "load-onnxruntime")] +//pub const VOICEVOX_ONNXRUNTIME_LIB_VERSION: &CStr = ..; + +/// ONNX Runtimeの動的ライブラリの、バージョン付きのファイル名。 +/// +/// WindowsとAndroidでは ::voicevox_get_onnxruntime_lib_unversioned_filename と同じ。 +/// +/// \availability{ +/// [リリース](https://github.com/voicevox/voicevox_core/releases)されているライブラリではiOSを除くプラットフォームで利用可能。詳細はファイルレベルの"Availability"の節を参照。 +/// } +#[cfg(feature = "load-onnxruntime")] +#[no_mangle] +pub extern "C" fn voicevox_get_onnxruntime_lib_versioned_filename() -> *const c_char { + init_logger_once(); + let filename = VoicevoxOnnxruntime::lib_versioned_filename(); + C_STRING_DROP_CHECKER.blacklist(filename).as_ptr() +} + +/// ONNX Runtimeの動的ライブラリの、バージョン無しのファイル名。 +/// +/// \availability{ +/// [リリース](https://github.com/voicevox/voicevox_core/releases)されているライブラリではiOSを除くプラットフォームで利用可能。詳細はファイルレベルの"Availability"の節を参照。 +/// } +#[cfg(feature = "load-onnxruntime")] +#[no_mangle] +pub extern "C" fn voicevox_get_onnxruntime_lib_unversioned_filename() -> *const c_char { + init_logger_once(); + let filename = VoicevoxOnnxruntime::lib_unversioned_filename(); + C_STRING_DROP_CHECKER.blacklist(filename).as_ptr() +} + +/// ::voicevox_onnxruntime_load_once のオプション。 +/// +/// \availability{ +/// [リリース](https://github.com/voicevox/voicevox_core/releases)されているライブラリではiOSを除くプラットフォームで利用可能。詳細はファイルレベルの"Availability"の節を参照。 +/// } +#[cfg(feature = "load-onnxruntime")] +#[repr(C)] +pub struct VoicevoxLoadOnnxruntimeOptions { + /// ONNX Runtimeのファイル名(モジュール名)もしくはファイルパスを指定する。 + /// + /// `dlopen`/[`LoadLibraryExW`](https://learn.microsoft.com/en-us/windows/win32/api/libloaderapi/nf-libloaderapi-loadlibraryexw)の引数に使われる。デフォルトは ::voicevox_get_onnxruntime_lib_versioned_filename と同じ。 + filename: *const c_char, +} + +/// デフォルトの ::voicevox_onnxruntime_load_once のオプションを生成する。 +/// +/// @return デフォルトの ::voicevox_onnxruntime_load_once のオプション +/// +/// \availability{ +/// [リリース](https://github.com/voicevox/voicevox_core/releases)されているライブラリではiOSを除くプラットフォームで利用可能。詳細はファイルレベルの"Availability"の節を参照。 +/// } +#[cfg(feature = "load-onnxruntime")] +#[no_mangle] +pub extern "C" fn voicevox_make_default_load_onnxruntime_options() -> VoicevoxLoadOnnxruntimeOptions +{ + init_logger_once(); + let filename = VoicevoxOnnxruntime::lib_versioned_filename(); + let filename = C_STRING_DROP_CHECKER.blacklist(filename).as_ptr(); + VoicevoxLoadOnnxruntimeOptions { filename } +} + +// https://github.com/mozilla/cbindgen/issues/967 +// FIXME: このコードブロックのコードが動くかどうか未確認 +/// ONNX Runtime。 +/// +/// シングルトンであり、インスタンスは高々一つ。 +/// +/// ```c +/// const VoicevoxOnnxruntime *ort1; +/// voicevox_onnxruntime_load_once(voicevox_make_default_load_onnxruntime_options, +/// &ort1); +/// const VoicevoxOnnxruntime *ort2 = voicevox_onnxruntime_get(); +/// assert(ort1 == ort2); +/// ``` +#[cfg(any())] +pub struct VoicevoxOnnxruntime(!); + +/// cbindgen:ignore +#[derive(RefCastCustom)] +#[repr(transparent)] +pub struct VoicevoxOnnxruntime(voicevox_core::blocking::Onnxruntime); + +/// ::VoicevoxOnnxruntime のインスタンスが既に作られているならそれを得る。 +/// +/// 作られていなければ`NULL`を返す。 +/// +/// @returns ::VoicevoxOnnxruntime のインスタンス +#[no_mangle] +pub extern "C" fn voicevox_onnxruntime_get() -> Option<&'static VoicevoxOnnxruntime> { + VoicevoxOnnxruntime::get() +} + +/// ONNX Runtimeをロードして初期化する。 +/// +/// 一度成功したら、以後は引数を無視して同じ参照を返す。 +/// +/// @param [in] options オプション +/// @param [out] out_onnxruntime ::VoicevoxOnnxruntime のインスタンス +/// +/// @returns 結果コード +/// +/// \availability{ +/// [リリース](https://github.com/voicevox/voicevox_core/releases)されているライブラリではiOSを除くプラットフォームで利用可能。詳細はファイルレベルの"Availability"の節を参照。 +/// } +/// +/// \safety{ +/// - `options.filename`はヌル終端文字列を指し、かつ読み込みについて有効でなければならない。 +/// - `out_onnxruntime`は書き込みについて有効でなければならない。 +/// } +#[cfg(feature = "load-onnxruntime")] +#[no_mangle] +pub unsafe extern "C" fn voicevox_onnxruntime_load_once( + options: VoicevoxLoadOnnxruntimeOptions, + out_onnxruntime: NonNull<&'static VoicevoxOnnxruntime>, +) -> VoicevoxResultCode { + init_logger_once(); + let filename = unsafe { + // SAFETY: ユーザーに要求している条件で十分 + CStr::from_ptr(options.filename) + }; + into_result_code_with_error((|| { + let instance = VoicevoxOnnxruntime::load_once(filename)?; + unsafe { + // SAFETY: ユーザーに要求している条件で十分 + out_onnxruntime.write_unaligned(instance); + } + Ok(()) + })()) +} + +/// ONNX Runtimeを初期化する。 +/// +/// 一度成功したら以後は同じ参照を返す。 +/// +/// @param [out] out_onnxruntime ::VoicevoxOnnxruntime のインスタンス +/// +/// @returns 結果コード +/// +/// \availability{ +/// [リリース](https://github.com/voicevox/voicevox_core/releases)されているライブラリではiOSでのみ利用可能。詳細はファイルレベルの"Availability"の節を参照。 +/// } +/// +/// \safety{ +/// - `out_onnxruntime`は書き込みについて有効でなければならない。 +/// } +#[cfg(feature = "link-onnxruntime")] +#[no_mangle] +pub unsafe extern "C" fn voicevox_onnxruntime_init_once( + out_onnxruntime: NonNull<&'static VoicevoxOnnxruntime>, +) -> VoicevoxResultCode { + init_logger_once(); + into_result_code_with_error((|| { + let instance = VoicevoxOnnxruntime::init_once()?; + unsafe { + // SAFETY: ユーザーに要求している条件で十分 + out_onnxruntime.write_unaligned(instance); + } + Ok(()) + })()) +} /// テキスト解析器としてのOpen JTalk。 /// @@ -100,8 +265,10 @@ fn init_logger_once() { /// voicevox_open_jtalk_rc_delete(open_jtalk); /// ``` /// } +#[derive(Debug, Educe)] +#[educe(Default(expression = "Self { _padding: MaybeUninit::uninit() }"))] pub struct OpenJtalkRc { - open_jtalk: voicevox_core::blocking::OpenJtalk, + _padding: MaybeUninit<[u8; 1]>, } /// ::OpenJtalkRc を構築(_construct_)する。 @@ -127,13 +294,13 @@ pub struct OpenJtalkRc { #[no_mangle] pub unsafe extern "C" fn voicevox_open_jtalk_rc_new( open_jtalk_dic_dir: *const c_char, - out_open_jtalk: NonNull>, + out_open_jtalk: NonNull>, ) -> VoicevoxResultCode { init_logger_once(); into_result_code_with_error((|| { let open_jtalk_dic_dir = ensure_utf8(CStr::from_ptr(open_jtalk_dic_dir))?; - let open_jtalk = OpenJtalkRc::new(open_jtalk_dic_dir)?.into(); - out_open_jtalk.as_ptr().write_unaligned(open_jtalk); + let open_jtalk = OpenJtalkRc::new(open_jtalk_dic_dir)?; + out_open_jtalk.write_unaligned(open_jtalk); Ok(()) })()) } @@ -144,25 +311,24 @@ pub unsafe extern "C" fn voicevox_open_jtalk_rc_new( /// /// @param [in] open_jtalk Open JTalkのオブジェクト /// @param [in] user_dict ユーザー辞書 -/// -/// \safety{ -/// - `open_jtalk`は ::voicevox_open_jtalk_rc_new で得たものでなければならず、また ::voicevox_open_jtalk_rc_delete で解放されていてはいけない。 -/// - `user_dict`は ::voicevox_user_dict_new で得たものでなければならず、また ::voicevox_user_dict_delete で解放されていてはいけない。 -/// } #[no_mangle] pub extern "C" fn voicevox_open_jtalk_rc_use_user_dict( - open_jtalk: &OpenJtalkRc, - user_dict: &VoicevoxUserDict, + open_jtalk: *const OpenJtalkRc, + user_dict: *const VoicevoxUserDict, ) -> VoicevoxResultCode { init_logger_once(); into_result_code_with_error((|| { - open_jtalk.open_jtalk.use_user_dict(&user_dict.dict)?; + open_jtalk.body().use_user_dict(&user_dict.body())?; Ok(()) })()) } /// ::OpenJtalkRc を破棄(_destruct_)する。 /// +/// 破棄対象への他スレッドでのアクセスが存在する場合、それらがすべて終わるのを待ってから破棄する。 +/// +/// この関数の呼び出し後に破棄し終えた対象にアクセスすると、プロセスを異常終了する。 +/// /// @param [in] open_jtalk 破棄対象 /// /// \example{ @@ -170,21 +336,19 @@ pub extern "C" fn voicevox_open_jtalk_rc_use_user_dict( /// voicevox_open_jtalk_rc_delete(open_jtalk); /// ``` /// } -/// -/// \safety{ -/// - `open_jtalk`は ::voicevox_open_jtalk_rc_new で得たものでなければならず、また既にこの関数で解放されていてはいけない。 -/// - `open_jtalk`は以後ダングリングポインタ(_dangling pointer_)として扱われなくてはならない。 -/// } #[no_mangle] -pub extern "C" fn voicevox_open_jtalk_rc_delete(open_jtalk: Box) { +pub extern "C" fn voicevox_open_jtalk_rc_delete(open_jtalk: *mut OpenJtalkRc) { init_logger_once(); - drop(open_jtalk); + open_jtalk.drop_body(); } /// ハードウェアアクセラレーションモードを設定する設定値。 #[repr(i32)] #[derive(Debug, PartialEq, Eq)] -#[allow(non_camel_case_types)] +#[allow( + non_camel_case_types, + reason = "実際に公開するC APIとの差異をできるだけ少なくするため" +)] pub enum VoicevoxAccelerationMode { /// 実行環境に合った適切なハードウェアアクセラレーションモードを選択する VOICEVOX_ACCELERATION_MODE_AUTO = 0, @@ -219,32 +383,34 @@ pub extern "C" fn voicevox_get_version() -> *const c_char { init_logger_once(); return C_STRING_DROP_CHECKER.blacklist(VERSION).as_ptr(); - const VERSION: &CStr = unsafe { - // SAFETY: The package version is a SemVer, so it should not contain '\0' - CStr::from_bytes_with_nul_unchecked(concat!(env!("CARGO_PKG_VERSION"), '\0').as_bytes()) + const VERSION: &CStr = if let Ok(version) = + CStr::from_bytes_with_nul(concat!(env!("CARGO_PKG_VERSION"), '\0').as_bytes()) + { + version + } else { + panic!("`$CARGO_PKG_VERSION` should be a SemVer, so it should not contain `\\0`"); }; } -/// 音声モデル。 +/// 音声モデルファイル。 /// /// VVMファイルと対応する。 -/// 構築(_construction_)は ::voicevox_voice_model_new_from_path で行い、破棄(_destruction_)は ::voicevox_voice_model_delete で行う。 -#[derive(Getters)] -pub struct VoicevoxVoiceModel { - model: voicevox_core::blocking::VoiceModel, - id: CString, - metas: CString, +/// 構築(_construction_)は ::voicevox_voice_model_file_open で行い、破棄(_destruction_)は ::voicevox_voice_model_file_close で行う。 +#[derive(Debug, Educe)] +#[educe(Default(expression = "Self { _padding: MaybeUninit::uninit() }"))] +pub struct VoicevoxVoiceModelFile { + _padding: MaybeUninit<[u8; 1]>, } /// 音声モデルID。 -pub type VoicevoxVoiceModelId = *const c_char; +pub type VoicevoxVoiceModelId<'a> = &'a [u8; 16]; /// スタイルID。 /// /// VOICEVOXにおける、ある話者(_speaker_)のあるスタイル(_style_)を指す。 pub type VoicevoxStyleId = u32; -/// VVMファイルから ::VoicevoxVoiceModel を構築(_construct_)する。 +/// VVMファイルを開く。 /// /// @param [in] path vvmファイルへのUTF-8のファイルパス /// @param [out] out_model 構築先 @@ -256,74 +422,77 @@ pub type VoicevoxStyleId = u32; /// - `out_model`は書き込みについて有効でなければならない。 /// } #[no_mangle] -pub unsafe extern "C" fn voicevox_voice_model_new_from_path( +pub unsafe extern "C" fn voicevox_voice_model_file_open( path: *const c_char, - out_model: NonNull>, + out_model: NonNull>, ) -> VoicevoxResultCode { init_logger_once(); into_result_code_with_error((|| { let path = ensure_utf8(CStr::from_ptr(path))?; - let model = VoicevoxVoiceModel::from_path(path)?.into(); - out_model.as_ptr().write_unaligned(model); + let model = VoicevoxVoiceModelFile::open(path)?; + out_model.write_unaligned(model); Ok(()) })()) } -/// ::VoicevoxVoiceModel からIDを取得する。 +/// ::VoicevoxVoiceModelFile からIDを取得する。 /// /// @param [in] model 音声モデル -/// -/// @returns 音声モデルID +/// @param [out] output_voice_model_id 音声モデルID /// /// \safety{ -/// - `model`は ::voicevox_voice_model_new_from_path で得たものでなければならず、また ::voicevox_voice_model_delete で解放されていてはいけない。 +/// - `output_voice_model_id`は書き込みについて有効でなければならない。 /// } #[no_mangle] -pub extern "C" fn voicevox_voice_model_id(model: &VoicevoxVoiceModel) -> VoicevoxVoiceModelId { +pub unsafe extern "C" fn voicevox_voice_model_file_id( + model: *const VoicevoxVoiceModelFile, + output_voice_model_id: NonNull<[u8; 16]>, +) { init_logger_once(); - model.id().as_ptr() + let id = model.body().id().raw_voice_model_id().into_bytes(); + unsafe { output_voice_model_id.write_unaligned(id) }; } -/// ::VoicevoxVoiceModel からメタ情報を取得する。 +/// ::VoicevoxVoiceModelFile からメタ情報を取得する。 +/// +/// JSONの解放は ::voicevox_json_free で行う。 /// /// @param [in] model 音声モデル /// /// @returns メタ情報のJSON文字列 -/// -/// \safety{ -/// - `model`は ::voicevox_voice_model_new_from_path で得たものでなければならず、また ::voicevox_voice_model_delete で解放されていてはいけない。 -/// - 戻り値の文字列の生存期間(_lifetime_)は次にこの関数が呼ばれるか、`model`が破棄されるまでである。この生存期間を越えて文字列にアクセスしてはならない。 -/// } #[no_mangle] -pub extern "C" fn voicevox_voice_model_get_metas_json(model: &VoicevoxVoiceModel) -> *const c_char { +pub extern "C" fn voicevox_voice_model_file_create_metas_json( + model: *const VoicevoxVoiceModelFile, +) -> *mut c_char { init_logger_once(); - model.metas().as_ptr() + C_STRING_DROP_CHECKER.whitelist(model.metas()).into_raw() } -/// ::VoicevoxVoiceModel を破棄(_destruct_)する。 +/// ::VoicevoxVoiceModelFile を、所有しているファイルディスクリプタを閉じた上で破棄(_destruct_)する。 /// -/// @param [in] model 破棄対象 +/// 破棄対象への他スレッドでのアクセスが存在する場合、それらがすべて終わるのを待ってから破棄する。 /// -/// \safety{ -/// - `model`は ::voicevox_voice_model_new_from_path で得たものでなければならず、また既にこの関数で解放されていてはいけない。 -/// - `model`は以後ダングリングポインタ(_dangling pointer_)として扱われなくてはならない。 -/// } +/// この関数の呼び出し後に破棄し終えた対象にアクセスすると、プロセスを異常終了する。 +/// +/// @param [in] model 破棄対象 #[no_mangle] -pub extern "C" fn voicevox_voice_model_delete(model: Box) { +pub extern "C" fn voicevox_voice_model_file_close(model: *mut VoicevoxVoiceModelFile) { init_logger_once(); - drop(model); + model.drop_body(); } /// 音声シンセサイザ。 /// /// 構築(_construction_)は ::voicevox_synthesizer_new で行い、破棄(_destruction_)は ::voicevox_synthesizer_delete で行う。 -#[derive(Getters)] +#[derive(Debug, Educe)] +#[educe(Default(expression = "Self { _padding: MaybeUninit::uninit() }"))] pub struct VoicevoxSynthesizer { - synthesizer: voicevox_core::blocking::Synthesizer, + _padding: MaybeUninit<[u8; 1]>, } /// ::VoicevoxSynthesizer を構築(_construct_)する。 /// +/// @param [in] onnxruntime /// @param [in] open_jtalk Open JTalkのオブジェクト /// @param [in] options オプション /// @param [out] out_synthesizer 構築先 @@ -331,37 +500,37 @@ pub struct VoicevoxSynthesizer { /// @returns 結果コード /// /// \safety{ -/// - `open_jtalk`は ::voicevox_voice_model_new_from_path で得たものでなければならず、また ::voicevox_open_jtalk_rc_new で解放されていてはいけない。 +/// - `onnxruntime`は ::voicevox_onnxruntime_load_once または ::voicevox_onnxruntime_init_once で得たものでなければならない。 /// - `out_synthesizer`は書き込みについて有効でなければならない。 /// } #[no_mangle] pub unsafe extern "C" fn voicevox_synthesizer_new( - open_jtalk: &OpenJtalkRc, + onnxruntime: &'static VoicevoxOnnxruntime, + open_jtalk: *const OpenJtalkRc, options: VoicevoxInitializeOptions, - out_synthesizer: NonNull>, + out_synthesizer: NonNull>, ) -> VoicevoxResultCode { init_logger_once(); into_result_code_with_error((|| { let options = options.into(); - let synthesizer = VoicevoxSynthesizer::new(open_jtalk, &options)?.into(); - out_synthesizer.as_ptr().write_unaligned(synthesizer); + let synthesizer = VoicevoxSynthesizer::new(onnxruntime, open_jtalk, &options)?; + out_synthesizer.write_unaligned(synthesizer); Ok(()) })()) } /// ::VoicevoxSynthesizer を破棄(_destruct_)する。 /// -/// @param [in] synthesizer 破棄対象 +/// 破棄対象への他スレッドでのアクセスが存在する場合、それらがすべて終わるのを待ってから破棄する。 /// -/// \safety{ -/// - `synthesizer`は ::voicevox_synthesizer_new で得たものでなければならず、また既にこの関数で解放されていてはいけない。 -/// - `synthesizer`は以後ダングリングポインタ(_dangling pointer_)として扱われなくてはならない。 -/// } +/// この関数の呼び出し後に破棄し終えた対象にアクセスすると、プロセスを異常終了する。 +/// +/// @param [in] synthesizer 破棄対象 #[no_mangle] -pub extern "C" fn voicevox_synthesizer_delete(synthesizer: Box) { +pub extern "C" fn voicevox_synthesizer_delete(synthesizer: *mut VoicevoxSynthesizer) { init_logger_once(); - drop(synthesizer); + synthesizer.drop_body(); } /// 音声モデルを読み込む。 @@ -370,18 +539,13 @@ pub extern "C" fn voicevox_synthesizer_delete(synthesizer: Box VoicevoxResultCode { init_logger_once(); - into_result_code_with_error(synthesizer.load_voice_model(model.model())) + into_result_code_with_error(synthesizer.load_voice_model(&model.body())) } /// 音声モデルの読み込みを解除する。 @@ -392,21 +556,28 @@ pub extern "C" fn voicevox_synthesizer_load_voice_model( /// @returns 結果コード /// /// \safety{ -/// - `synthesizer`は ::voicevox_synthesizer_new で得たものでなければならず、また ::voicevox_synthesizer_delete で解放されていてはいけない。 -/// - `model_id`はヌル終端文字列を指し、かつ読み込みについて有効でなければならない。 +/// - `model_id`は読み込みについて有効でなければならない。 /// } #[no_mangle] -pub unsafe extern "C" fn voicevox_synthesizer_unload_voice_model( - synthesizer: &VoicevoxSynthesizer, - model_id: VoicevoxVoiceModelId, +pub extern "C" fn voicevox_synthesizer_unload_voice_model( + synthesizer: *const VoicevoxSynthesizer, + model_id: VoicevoxVoiceModelId<'_>, ) -> VoicevoxResultCode { init_logger_once(); - into_result_code_with_error((|| { - let raw_model_id = ensure_utf8(unsafe { CStr::from_ptr(model_id) })?; - synthesizer - .unload_voice_model(&VoiceModelId::new(raw_model_id.to_string())) - .map_err(Into::into) - })()) + let model_id = model_id.to_model_id(); + into_result_code_with_error(synthesizer.unload_voice_model(model_id).map_err(Into::into)) +} + +/// ::VoicevoxOnnxruntime のインスタンスを得る。 +/// +/// @param [in] synthesizer 音声シンセサイザ +/// +/// @returns ::VoicevoxOnnxruntime のインスタンス +#[no_mangle] +pub extern "C" fn voicevox_synthesizer_get_onnxruntime( + synthesizer: *const VoicevoxSynthesizer, +) -> &'static VoicevoxOnnxruntime { + synthesizer.onnxruntime() } /// ハードウェアアクセラレーションがGPUモードか判定する。 @@ -414,14 +585,12 @@ pub unsafe extern "C" fn voicevox_synthesizer_unload_voice_model( /// @param [in] synthesizer 音声シンセサイザ /// /// @returns GPUモードかどうか -/// -/// \safety{ -/// - `synthesizer`は ::voicevox_synthesizer_new で得たものでなければならず、また ::voicevox_synthesizer_delete で解放されていてはいけない。 -/// } #[no_mangle] -pub extern "C" fn voicevox_synthesizer_is_gpu_mode(synthesizer: &VoicevoxSynthesizer) -> bool { +pub extern "C" fn voicevox_synthesizer_is_gpu_mode( + synthesizer: *const VoicevoxSynthesizer, +) -> bool { init_logger_once(); - synthesizer.synthesizer().is_gpu_mode() + synthesizer.body().is_gpu_mode() } /// 指定したIDの音声モデルが読み込まれているか判定する。 @@ -432,22 +601,16 @@ pub extern "C" fn voicevox_synthesizer_is_gpu_mode(synthesizer: &VoicevoxSynthes /// @returns モデルが読み込まれているかどうか /// /// \safety{ -/// - `synthesizer`は ::voicevox_synthesizer_new で得たものでなければならず、また ::voicevox_synthesizer_delete で解放されていてはいけない。 -/// - `model_id`はヌル終端文字列を指し、かつ読み込みについて有効でなければならない。 +/// - `model_id`は読み込みについて有効でなければならない。 /// } #[no_mangle] -pub unsafe extern "C" fn voicevox_synthesizer_is_loaded_voice_model( - synthesizer: &VoicevoxSynthesizer, - model_id: VoicevoxVoiceModelId, +pub extern "C" fn voicevox_synthesizer_is_loaded_voice_model( + synthesizer: *const VoicevoxSynthesizer, + model_id: VoicevoxVoiceModelId<'_>, ) -> bool { init_logger_once(); - 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())) + let model_id = model_id.to_model_id(); + synthesizer.body().is_loaded_voice_model(model_id) } /// 今読み込んでいる音声モデルのメタ情報を、JSONで取得する。 @@ -457,25 +620,22 @@ pub unsafe extern "C" fn voicevox_synthesizer_is_loaded_voice_model( /// @param [in] synthesizer 音声シンセサイザ /// /// @return メタ情報のJSON文字列 -/// -/// \safety{ -/// - `synthesizer`は ::voicevox_synthesizer_new で得たものでなければならず、また ::voicevox_synthesizer_delete で解放されていてはいけない。 -/// } #[no_mangle] pub extern "C" fn voicevox_synthesizer_create_metas_json( - synthesizer: &VoicevoxSynthesizer, + synthesizer: *const VoicevoxSynthesizer, ) -> *mut c_char { init_logger_once(); let metas = synthesizer.metas(); C_STRING_DROP_CHECKER.whitelist(metas).into_raw() } -/// このライブラリで利用可能なデバイスの情報を、JSONで取得する。 +/// ONNX Runtimeとして利用可能なデバイスの情報を、JSONで取得する。 /// /// JSONの解放は ::voicevox_json_free で行う。 /// -/// あくまで本ライブラリが対応しているデバイスの情報であることに注意。GPUが使える環境ではなかったとしても`cuda`や`dml`は`true`を示しうる。 +/// あくまでONNX Runtimeが対応しているデバイスの情報であることに注意。GPUが使える環境ではなかったとしても`cuda`や`dml`は`true`を示しうる。 /// +/// @param [in] onnxruntime /// @param [out] output_supported_devices_json サポートデバイス情報のJSON文字列 /// /// @returns 結果コード @@ -483,22 +643,24 @@ pub extern "C" fn voicevox_synthesizer_create_metas_json( /// \example{ /// ```c /// char *supported_devices; -/// VoicevoxResultCode result = voicevox_create_supported_devices_json(&supported_devices); +/// VoicevoxResultCode result = voicevox_onnxruntime_create_supported_devices_json(onnxruntime, &supported_devices); /// ``` /// } /// /// \safety{ +/// - `onnxruntime`は ::voicevox_onnxruntime_load_once または ::voicevox_onnxruntime_init_once で得たものでなければならない。 /// - `output_supported_devices_json`は書き込みについて有効でなければならない。 /// } #[no_mangle] -pub unsafe extern "C" fn voicevox_create_supported_devices_json( +pub unsafe extern "C" fn voicevox_onnxruntime_create_supported_devices_json( + onnxruntime: &'static VoicevoxOnnxruntime, output_supported_devices_json: NonNull<*mut c_char>, ) -> VoicevoxResultCode { init_logger_once(); into_result_code_with_error((|| { let supported_devices = - CString::new(SupportedDevices::create()?.to_json().to_string()).unwrap(); - output_supported_devices_json.as_ptr().write_unaligned( + CString::new(onnxruntime.0.supported_devices()?.to_json().to_string()).unwrap(); + output_supported_devices_json.write_unaligned( C_STRING_DROP_CHECKER .whitelist(supported_devices) .into_raw(), @@ -528,13 +690,12 @@ pub unsafe extern "C" fn voicevox_create_supported_devices_json( /// } /// /// \safety{ -/// - `synthesizer`は ::voicevox_synthesizer_new で得たものでなければならず、また ::voicevox_synthesizer_delete で解放されていてはいけない。 /// - `kana`はヌル終端文字列を指し、かつ読み込みについて有効でなければならない。 /// - `output_audio_query_json`は書き込みについて有効でなければならない。 /// } #[no_mangle] pub unsafe extern "C" fn voicevox_synthesizer_create_audio_query_from_kana( - synthesizer: &VoicevoxSynthesizer, + synthesizer: *const VoicevoxSynthesizer, kana: *const c_char, style_id: VoicevoxStyleId, output_audio_query_json: NonNull<*mut c_char>, @@ -545,12 +706,11 @@ pub unsafe extern "C" fn voicevox_synthesizer_create_audio_query_from_kana( let kana = ensure_utf8(kana)?; let audio_query = synthesizer - .synthesizer() + .body() .audio_query_from_kana(kana, StyleId::new(style_id))?; let audio_query = CString::new(audio_query_model_to_json(&audio_query)) .expect("should not contain '\\0'"); output_audio_query_json - .as_ptr() .write_unaligned(C_STRING_DROP_CHECKER.whitelist(audio_query).into_raw()); Ok(()) })()) @@ -577,13 +737,12 @@ pub unsafe extern "C" fn voicevox_synthesizer_create_audio_query_from_kana( /// } /// /// \safety{ -/// - `synthesizer`は ::voicevox_synthesizer_new で得たものでなければならず、また ::voicevox_synthesizer_delete で解放されていてはいけない。 /// - `text`はヌル終端文字列を指し、かつ読み込みについて有効でなければならない。 /// - `output_audio_query_json`は書き込みについて有効でなければならない。 /// } #[no_mangle] pub unsafe extern "C" fn voicevox_synthesizer_create_audio_query( - synthesizer: &VoicevoxSynthesizer, + synthesizer: *const VoicevoxSynthesizer, text: *const c_char, style_id: VoicevoxStyleId, output_audio_query_json: NonNull<*mut c_char>, @@ -594,12 +753,11 @@ pub unsafe extern "C" fn voicevox_synthesizer_create_audio_query( let text = ensure_utf8(text)?; let audio_query = synthesizer - .synthesizer() + .body() .audio_query(text, StyleId::new(style_id))?; let audio_query = CString::new(audio_query_model_to_json(&audio_query)) .expect("should not contain '\\0'"); output_audio_query_json - .as_ptr() .write_unaligned(C_STRING_DROP_CHECKER.whitelist(audio_query).into_raw()); Ok(()) })()) @@ -627,13 +785,12 @@ pub unsafe extern "C" fn voicevox_synthesizer_create_audio_query( /// } /// /// \safety{ -/// - `synthesizer`は ::voicevox_synthesizer_new で得たものでなければならず、また ::voicevox_synthesizer_delete で解放されていてはいけない。 /// - `kana`はヌル終端文字列を指し、かつ読み込みについて有効でなければならない。 /// - `output_audio_query_json`は書き込みについて有効でなければならない。 /// } #[no_mangle] pub unsafe extern "C" fn voicevox_synthesizer_create_accent_phrases_from_kana( - synthesizer: &VoicevoxSynthesizer, + synthesizer: *const VoicevoxSynthesizer, kana: *const c_char, style_id: VoicevoxStyleId, output_accent_phrases_json: NonNull<*mut c_char>, @@ -642,12 +799,11 @@ pub unsafe extern "C" fn voicevox_synthesizer_create_accent_phrases_from_kana( into_result_code_with_error((|| { let kana = ensure_utf8(CStr::from_ptr(kana))?; let accent_phrases = synthesizer - .synthesizer() + .body() .create_accent_phrases_from_kana(kana, StyleId::new(style_id))?; let accent_phrases = CString::new(accent_phrases_to_json(&accent_phrases)) .expect("should not contain '\\0'"); output_accent_phrases_json - .as_ptr() .write_unaligned(C_STRING_DROP_CHECKER.whitelist(accent_phrases).into_raw()); Ok(()) })()) @@ -674,13 +830,12 @@ pub unsafe extern "C" fn voicevox_synthesizer_create_accent_phrases_from_kana( /// } /// /// \safety{ -/// - `synthesizer`は ::voicevox_synthesizer_new で得たものでなければならず、また ::voicevox_synthesizer_delete で解放されていてはいけない。 /// - `text`はヌル終端文字列を指し、かつ読み込みについて有効でなければならない。 /// - `output_audio_query_json`は書き込みについて有効でなければならない。 /// } #[no_mangle] pub unsafe extern "C" fn voicevox_synthesizer_create_accent_phrases( - synthesizer: &VoicevoxSynthesizer, + synthesizer: *const VoicevoxSynthesizer, text: *const c_char, style_id: VoicevoxStyleId, output_accent_phrases_json: NonNull<*mut c_char>, @@ -689,12 +844,11 @@ pub unsafe extern "C" fn voicevox_synthesizer_create_accent_phrases( into_result_code_with_error((|| { let text = ensure_utf8(CStr::from_ptr(text))?; let accent_phrases = synthesizer - .synthesizer() + .body() .create_accent_phrases(text, StyleId::new(style_id))?; let accent_phrases = CString::new(accent_phrases_to_json(&accent_phrases)) .expect("should not contain '\\0'"); output_accent_phrases_json - .as_ptr() .write_unaligned(C_STRING_DROP_CHECKER.whitelist(accent_phrases).into_raw()); Ok(()) })()) @@ -712,29 +866,27 @@ pub unsafe extern "C" fn voicevox_synthesizer_create_accent_phrases( /// @returns 結果コード /// /// \safety{ -/// - `synthesizer`は ::voicevox_synthesizer_new で得たものでなければならず、また ::voicevox_synthesizer_delete で解放されていてはいけない。 /// - `accent_phrases_json`はヌル終端文字列を指し、かつ読み込みについて有効でなければならない。 /// - `output_audio_query_json`は書き込みについて有効でなければならない。 /// } #[no_mangle] pub unsafe extern "C" fn voicevox_synthesizer_replace_mora_data( - synthesizer: &VoicevoxSynthesizer, + synthesizer: *const VoicevoxSynthesizer, accent_phrases_json: *const c_char, style_id: VoicevoxStyleId, output_accent_phrases_json: NonNull<*mut c_char>, ) -> VoicevoxResultCode { init_logger_once(); into_result_code_with_error((|| { - let accent_phrases: Vec = + let accent_phrases: Vec = serde_json::from_str(ensure_utf8(CStr::from_ptr(accent_phrases_json))?) .map_err(CApiError::InvalidAccentPhrase)?; let accent_phrases = synthesizer - .synthesizer() + .body() .replace_mora_data(&accent_phrases, StyleId::new(style_id))?; let accent_phrases = CString::new(accent_phrases_to_json(&accent_phrases)) .expect("should not contain '\\0'"); output_accent_phrases_json - .as_ptr() .write_unaligned(C_STRING_DROP_CHECKER.whitelist(accent_phrases).into_raw()); Ok(()) })()) @@ -752,29 +904,27 @@ pub unsafe extern "C" fn voicevox_synthesizer_replace_mora_data( /// @returns 結果コード /// /// \safety{ -/// - `synthesizer`は ::voicevox_synthesizer_new で得たものでなければならず、また ::voicevox_synthesizer_delete で解放されていてはいけない。 /// - `accent_phrases_json`はヌル終端文字列を指し、かつ読み込みについて有効でなければならない。 /// - `output_audio_query_json`は書き込みについて有効でなければならない。 /// } #[no_mangle] pub unsafe extern "C" fn voicevox_synthesizer_replace_phoneme_length( - synthesizer: &VoicevoxSynthesizer, + synthesizer: *const VoicevoxSynthesizer, accent_phrases_json: *const c_char, style_id: VoicevoxStyleId, output_accent_phrases_json: NonNull<*mut c_char>, ) -> VoicevoxResultCode { init_logger_once(); into_result_code_with_error((|| { - let accent_phrases: Vec = + let accent_phrases: Vec = serde_json::from_str(ensure_utf8(CStr::from_ptr(accent_phrases_json))?) .map_err(CApiError::InvalidAccentPhrase)?; let accent_phrases = synthesizer - .synthesizer() + .body() .replace_phoneme_length(&accent_phrases, StyleId::new(style_id))?; let accent_phrases = CString::new(accent_phrases_to_json(&accent_phrases)) .expect("should not contain '\\0'"); output_accent_phrases_json - .as_ptr() .write_unaligned(C_STRING_DROP_CHECKER.whitelist(accent_phrases).into_raw()); Ok(()) })()) @@ -792,29 +942,27 @@ pub unsafe extern "C" fn voicevox_synthesizer_replace_phoneme_length( /// @returns 結果コード /// /// \safety{ -/// - `synthesizer`は ::voicevox_synthesizer_new で得たものでなければならず、また ::voicevox_synthesizer_delete で解放されていてはいけない。 /// - `accent_phrases_json`はヌル終端文字列を指し、かつ読み込みについて有効でなければならない。 /// - `output_audio_query_json`は書き込みについて有効でなければならない。 /// } #[no_mangle] pub unsafe extern "C" fn voicevox_synthesizer_replace_mora_pitch( - synthesizer: &VoicevoxSynthesizer, + synthesizer: *const VoicevoxSynthesizer, accent_phrases_json: *const c_char, style_id: VoicevoxStyleId, output_accent_phrases_json: NonNull<*mut c_char>, ) -> VoicevoxResultCode { init_logger_once(); into_result_code_with_error((|| { - let accent_phrases: Vec = + let accent_phrases: Vec = serde_json::from_str(ensure_utf8(CStr::from_ptr(accent_phrases_json))?) .map_err(CApiError::InvalidAccentPhrase)?; let accent_phrases = synthesizer - .synthesizer() + .body() .replace_mora_pitch(&accent_phrases, StyleId::new(style_id))?; let accent_phrases = CString::new(accent_phrases_to_json(&accent_phrases)) .expect("should not contain '\\0'"); output_accent_phrases_json - .as_ptr() .write_unaligned(C_STRING_DROP_CHECKER.whitelist(accent_phrases).into_raw()); Ok(()) })()) @@ -849,14 +997,13 @@ pub extern "C" fn voicevox_make_default_synthesis_options() -> VoicevoxSynthesis /// @returns 結果コード /// /// \safety{ -/// - `synthesizer`は ::voicevox_synthesizer_new で得たものでなければならず、また ::voicevox_synthesizer_delete で解放されていてはいけない。 /// - `audio_query_json`はヌル終端文字列を指し、かつ読み込みについて有効でなければならない。 /// - `output_wav_length`は書き込みについて有効でなければならない。 /// - `output_wav`は書き込みについて有効でなければならない。 /// } #[no_mangle] pub unsafe extern "C" fn voicevox_synthesizer_synthesis( - synthesizer: &VoicevoxSynthesizer, + synthesizer: *const VoicevoxSynthesizer, audio_query_json: *const c_char, style_id: VoicevoxStyleId, options: VoicevoxSynthesisOptions, @@ -868,9 +1015,9 @@ pub unsafe extern "C" fn voicevox_synthesizer_synthesis( let audio_query_json = CStr::from_ptr(audio_query_json) .to_str() .map_err(|_| CApiError::InvalidUtf8Input)?; - let audio_query: AudioQueryModel = + let audio_query: AudioQuery = serde_json::from_str(audio_query_json).map_err(CApiError::InvalidAudioQuery)?; - let wav = synthesizer.synthesizer().synthesis( + let wav = synthesizer.body().synthesis( &audio_query, StyleId::new(style_id), &SynthesisOptions::from(options), @@ -909,14 +1056,13 @@ pub extern "C" fn voicevox_make_default_tts_options() -> VoicevoxTtsOptions { /// @returns 結果コード /// /// \safety{ -/// - `synthesizer`は ::voicevox_synthesizer_new で得たものでなければならず、また ::voicevox_synthesizer_delete で解放されていてはいけない。 /// - `kana`はヌル終端文字列を指し、かつ読み込みについて有効でなければならない。 /// - `output_wav_length`は書き込みについて有効でなければならない。 /// - `output_wav`は書き込みについて有効でなければならない。 /// } #[no_mangle] pub unsafe extern "C" fn voicevox_synthesizer_tts_from_kana( - synthesizer: &VoicevoxSynthesizer, + synthesizer: *const VoicevoxSynthesizer, kana: *const c_char, style_id: VoicevoxStyleId, options: VoicevoxTtsOptions, @@ -926,7 +1072,7 @@ pub unsafe extern "C" fn voicevox_synthesizer_tts_from_kana( init_logger_once(); into_result_code_with_error((|| { let kana = ensure_utf8(CStr::from_ptr(kana))?; - let output = synthesizer.synthesizer().tts_from_kana( + let output = synthesizer.body().tts_from_kana( kana, StyleId::new(style_id), &TtsOptions::from(options), @@ -950,14 +1096,13 @@ pub unsafe extern "C" fn voicevox_synthesizer_tts_from_kana( /// @returns 結果コード /// /// \safety{ -/// - `synthesizer`は ::voicevox_synthesizer_new で得たものでなければならず、また ::voicevox_synthesizer_delete で解放されていてはいけない。 /// - `text`はヌル終端文字列を指し、かつ読み込みについて有効でなければならない。 /// - `output_wav_length`は書き込みについて有効でなければならない。 /// - `output_wav`は書き込みについて有効でなければならない。 /// } #[no_mangle] pub unsafe extern "C" fn voicevox_synthesizer_tts( - synthesizer: &VoicevoxSynthesizer, + synthesizer: *const VoicevoxSynthesizer, text: *const c_char, style_id: VoicevoxStyleId, options: VoicevoxTtsOptions, @@ -967,11 +1112,10 @@ pub unsafe extern "C" fn voicevox_synthesizer_tts( init_logger_once(); into_result_code_with_error((|| { let text = ensure_utf8(CStr::from_ptr(text))?; - let output = synthesizer.synthesizer().tts( - text, - StyleId::new(style_id), - &TtsOptions::from(options), - )?; + let output = + synthesizer + .body() + .tts(text, StyleId::new(style_id), &TtsOptions::from(options))?; U8_SLICE_OWNER.own_and_lend(output, output_wav, output_wav_length); Ok(()) })()) @@ -983,7 +1127,8 @@ pub unsafe extern "C" fn voicevox_synthesizer_tts( /// /// \safety{ /// - `json`は以下のAPIで得られたポインタでなくてはいけない。 -/// - ::voicevox_create_supported_devices_json +/// - ::voicevox_onnxruntime_create_supported_devices_json +/// - ::voicevox_voice_model_file_create_metas_json /// - ::voicevox_synthesizer_create_metas_json /// - ::voicevox_synthesizer_create_audio_query /// - ::voicevox_synthesizer_create_accent_phrases @@ -1048,9 +1193,10 @@ pub extern "C" fn voicevox_error_result_to_message( } /// ユーザー辞書。 -#[derive(Default)] +#[derive(Debug, Educe)] +#[educe(Default(expression = "Self { _padding: MaybeUninit::uninit() }"))] pub struct VoicevoxUserDict { - dict: Arc, + _padding: MaybeUninit<[u8; 1]>, } /// ユーザー辞書の単語。 @@ -1071,7 +1217,10 @@ pub struct VoicevoxUserDictWord { /// ユーザー辞書の単語の種類。 #[repr(i32)] -#[allow(non_camel_case_types)] +#[allow( + non_camel_case_types, + reason = "実際に公開するC APIとの差異をできるだけ少なくするため" +)] #[derive(Copy, Clone)] pub enum VoicevoxUserDictWordType { /// 固有名詞。 @@ -1100,9 +1249,9 @@ pub extern "C" fn voicevox_user_dict_word_make( VoicevoxUserDictWord { surface, pronunciation, - accent_type: UserDictWord::default().accent_type, - word_type: UserDictWord::default().word_type.into(), - priority: UserDictWord::default().priority, + accent_type: UserDictWord::default().accent_type(), + word_type: UserDictWord::default().word_type().into(), + priority: UserDictWord::default().priority(), } } @@ -1110,9 +1259,9 @@ pub extern "C" fn voicevox_user_dict_word_make( /// /// @returns ::VoicevoxUserDict #[no_mangle] -pub extern "C" fn voicevox_user_dict_new() -> Box { +pub extern "C" fn voicevox_user_dict_new() -> NonNull { init_logger_once(); - Default::default() + VoicevoxUserDict::new(Default::default()) } /// ユーザー辞書にファイルを読み込ませる。 @@ -1122,18 +1271,17 @@ pub extern "C" fn voicevox_user_dict_new() -> Box { /// @returns 結果コード /// /// \safety{ -/// - `user_dict`は ::voicevox_user_dict_new で得たものでなければならず、また ::voicevox_user_dict_delete で解放されていてはいけない。 /// - `dict_path`はヌル終端文字列を指し、かつ読み込みについて有効でなければならない。 /// } #[no_mangle] pub unsafe extern "C" fn voicevox_user_dict_load( - user_dict: &VoicevoxUserDict, + user_dict: *const VoicevoxUserDict, dict_path: *const c_char, ) -> VoicevoxResultCode { init_logger_once(); into_result_code_with_error((|| { let dict_path = ensure_utf8(unsafe { CStr::from_ptr(dict_path) })?; - user_dict.dict.load(dict_path)?; + user_dict.body().load(dict_path)?; Ok(()) })()) @@ -1150,21 +1298,20 @@ pub unsafe extern "C" fn voicevox_user_dict_load( /// @param user_dict は有効な :VoicevoxUserDict のポインタであること /// /// \safety{ -/// - `user_dict`は ::voicevox_user_dict_new で得たものでなければならず、また ::voicevox_user_dict_delete で解放されていてはいけない。 /// - `word->surface`と`word->pronunciation`はヌル終端文字列を指し、かつ読み込みについて有効でなければならない。 /// - `output_word_uuid`は書き込みについて有効でなければならない。 /// } #[no_mangle] pub unsafe extern "C" fn voicevox_user_dict_add_word( - user_dict: &VoicevoxUserDict, + user_dict: *const VoicevoxUserDict, word: *const VoicevoxUserDictWord, output_word_uuid: NonNull<[u8; 16]>, ) -> VoicevoxResultCode { init_logger_once(); into_result_code_with_error((|| { let word = word.read_unaligned().try_into_word()?; - let uuid = user_dict.dict.add_word(word)?; - output_word_uuid.as_ptr().copy_from(uuid.as_bytes(), 16); + let uuid = user_dict.body().add_word(word)?; + output_word_uuid.write_unaligned(uuid.into_bytes()); Ok(()) })()) @@ -1178,13 +1325,12 @@ pub unsafe extern "C" fn voicevox_user_dict_add_word( /// @returns 結果コード /// /// \safety{ -/// - `user_dict`は ::voicevox_user_dict_new で得たものでなければならず、また ::voicevox_user_dict_delete で解放されていてはいけない。 /// - `word_uuid`は読み込みについて有効でなければならない。 /// - `word->surface`と`word->pronunciation`はヌル終端文字列を指し、かつ読み込みについて有効でなければならない。 /// } #[no_mangle] pub unsafe extern "C" fn voicevox_user_dict_update_word( - user_dict: &VoicevoxUserDict, + user_dict: *const VoicevoxUserDict, word_uuid: &[u8; 16], word: *const VoicevoxUserDictWord, ) -> VoicevoxResultCode { @@ -1192,7 +1338,7 @@ pub unsafe extern "C" fn voicevox_user_dict_update_word( into_result_code_with_error((|| { let word_uuid = Uuid::from_slice(word_uuid).map_err(CApiError::InvalidUuid)?; let word = word.read_unaligned().try_into_word()?; - user_dict.dict.update_word(word_uuid, word)?; + user_dict.body().update_word(word_uuid, word)?; Ok(()) })()) @@ -1205,18 +1351,17 @@ pub unsafe extern "C" fn voicevox_user_dict_update_word( /// @returns 結果コード /// /// \safety{ -/// - `user_dict`は ::voicevox_user_dict_new で得たものでなければならず、また ::voicevox_user_dict_delete で解放されていてはいけない。 /// - `word_uuid`は読み込みについて有効でなければならない。 /// } #[no_mangle] pub extern "C" fn voicevox_user_dict_remove_word( - user_dict: &VoicevoxUserDict, + user_dict: *const VoicevoxUserDict, word_uuid: &[u8; 16], ) -> VoicevoxResultCode { init_logger_once(); into_result_code_with_error((|| { let word_uuid = Uuid::from_slice(word_uuid).map_err(CApiError::InvalidUuid)?; - user_dict.dict.remove_word(word_uuid)?; + user_dict.body().remove_word(word_uuid)?; Ok(()) })()) } @@ -1231,20 +1376,17 @@ pub extern "C" fn voicevox_user_dict_remove_word( /// @returns 結果コード /// /// \safety{ -/// - `user_dict`は ::voicevox_user_dict_new で得たものでなければならず、また ::voicevox_user_dict_delete で解放されていてはいけない。 /// - `output_json`は書き込みについて有効でなければならない。 /// } #[no_mangle] pub unsafe extern "C" fn voicevox_user_dict_to_json( - user_dict: &VoicevoxUserDict, + user_dict: *const VoicevoxUserDict, output_json: NonNull<*mut c_char>, ) -> VoicevoxResultCode { init_logger_once(); - let json = user_dict.dict.to_json(); + let json = user_dict.body().to_json(); let json = CString::new(json).expect("\\0を含まない文字列であることが保証されている"); - output_json - .as_ptr() - .write_unaligned(C_STRING_DROP_CHECKER.whitelist(json).into_raw()); + output_json.write_unaligned(C_STRING_DROP_CHECKER.whitelist(json).into_raw()); VoicevoxResultCode::VOICEVOX_RESULT_OK } @@ -1253,18 +1395,14 @@ pub unsafe extern "C" fn voicevox_user_dict_to_json( /// @param [in] user_dict ユーザー辞書 /// @param [in] other_dict インポートするユーザー辞書 /// @returns 結果コード -/// -/// \safety{ -/// - `user_dict`と`other_dict`は ::voicevox_user_dict_new で得たものでなければならず、また ::voicevox_user_dict_delete で解放されていてはいけない。 -/// } #[no_mangle] pub extern "C" fn voicevox_user_dict_import( - user_dict: &VoicevoxUserDict, - other_dict: &VoicevoxUserDict, + user_dict: *const VoicevoxUserDict, + other_dict: *const VoicevoxUserDict, ) -> VoicevoxResultCode { init_logger_once(); into_result_code_with_error((|| { - user_dict.dict.import(&other_dict.dict)?; + user_dict.body().import(&other_dict.body())?; Ok(()) })()) } @@ -1275,31 +1413,30 @@ pub extern "C" fn voicevox_user_dict_import( /// @param [in] path 保存先のファイルパス /// /// \safety{ -/// - `user_dict`は ::voicevox_user_dict_new で得たものでなければならず、また ::voicevox_user_dict_delete で解放されていてはいけない。 /// - `path`はヌル終端文字列を指し、かつ読み込みについて有効でなければならない。 /// } #[no_mangle] pub unsafe extern "C" fn voicevox_user_dict_save( - user_dict: &VoicevoxUserDict, + user_dict: *const VoicevoxUserDict, path: *const c_char, ) -> VoicevoxResultCode { init_logger_once(); into_result_code_with_error((|| { let path = ensure_utf8(CStr::from_ptr(path))?; - user_dict.dict.save(path)?; + user_dict.body().save(path)?; Ok(()) })()) } /// ユーザー辞書を破棄(_destruct_)する。 /// -/// @param [in] user_dict 破棄対象 +/// 破棄対象への他スレッドでのアクセスが存在する場合、それらがすべて終わるのを待ってから破棄する。 /// -/// \safety{ -/// - `user_dict`は ::voicevox_user_dict_new で得たものでなければならず、また既にこの関数で解放されていてはいけない。 -/// } +/// この関数の呼び出し後に破棄し終えた対象にアクセスすると、プロセスを異常終了する。 +/// +/// @param [in] user_dict 破棄対象 #[no_mangle] -pub unsafe extern "C" fn voicevox_user_dict_delete(user_dict: Box) { +pub extern "C" fn voicevox_user_dict_delete(user_dict: *mut VoicevoxUserDict) { init_logger_once(); - drop(user_dict); + user_dict.drop_body(); } diff --git a/crates/voicevox_core_c_api/src/object.rs b/crates/voicevox_core_c_api/src/object.rs new file mode 100644 index 000000000..a3aea19f1 --- /dev/null +++ b/crates/voicevox_core_c_api/src/object.rs @@ -0,0 +1,145 @@ +use std::{ + any, + collections::HashMap, + fmt::{Debug, Display}, + mem, + ops::{Deref, DerefMut}, + ptr::NonNull, + sync::Arc, +}; + +use easy_ext::ext; +use tracing::warn; + +/// プロセスの終わりまでデストラクトされない、ユーザーにオブジェクトとして貸し出す1-bit長の構造体。 +/// +/// インスタンスは次のような形。 +/// +/// ``` +/// pub struct VoicevoxSynthesizer { +/// _padding: MaybeUninit<[u8; 1]>, +/// } +/// ``` +/// +/// `RustApiObject`そのものではなくこのトレイトのインスタンスをユーザーに渡すようにすることで、次のことを実現する。 +/// +/// 1. "delete"時に対象オブジェクトに対するアクセスがあった場合、アクセスが終わるまで待つ +/// 2. 次のユーザー操作に対するセーフティネットを張り、パニックするようにする +/// 1. "delete"後に他の通常のメソッド関数の利用を試みる +/// 2. "delete"後に"delete"を試みる +/// 3. そもそもオブジェクトとして変なダングリングポインタが渡される +pub(crate) trait CApiObject: Default + Debug + 'static { + type RustApiObject: 'static; + + // 書き込み操作としては`push`のみ + fn heads() -> &'static std::sync::Mutex>; + + #[expect( + clippy::type_complexity, + reason = "型を分離するとかえって可読性を失う。その代わりコメントを入れている" + )] + fn bodies() -> &'static std::sync::Mutex< + HashMap< + usize, // `heads`の要素へのポインタのアドレス + Arc< + parking_lot::RwLock< + Option, // `RwLock`をdropする直前まで`Some` + >, + >, + >, + >; + + fn new(body: Self::RustApiObject) -> NonNull { + assert!(mem::size_of::() > 0); + + let this = { + let mut heads = Self::lock_heads(); + heads.push(Default::default()); + NonNull::from(heads.last().expect("just pushed")) + }; + let body = parking_lot::RwLock::new(body.into()).into(); + Self::lock_bodies().insert(this.as_ptr() as _, body); + this + } +} + +#[ext(CApiObjectPtrExt)] +impl *const T { + /// # Panics + /// + /// 同じ対象に対して`drop_body`を呼んでいるとパニックする。 + pub(crate) fn body(self) -> impl Deref { + self.validate(); + + let body = T::lock_bodies() + .get(&(self as _)) + .unwrap_or_else(|| self.panic_for_deleted()) + .read_arc(); + + voicevox_core::__internal::interop::raii::try_map_guard(body, |body| { + body.as_ref().ok_or(()) + }) + .unwrap_or_else(|()| self.panic_for_deleted()) + } + + /// # Panics + /// + /// 同じ対象に対してこの関数を二度呼ぶとパニックする。 + pub(crate) fn drop_body(self) { + self.validate(); + + let body = T::lock_bodies() + .remove(&(self as _)) + .unwrap_or_else(|| self.panic_for_deleted()); + + drop( + body.try_write_arc() + .unwrap_or_else(|| { + warn!( + "{this} is still in use. Waiting before closing", + this = self.display(), + ); + body.write_arc() + }) + .take() + .unwrap_or_else(|| self.panic_for_deleted()), + ); + } +} + +#[ext] +impl *const T { + fn validate(self) { + if self.is_null() { + panic!("the argument must not be null"); + } + if !T::lock_heads().as_ptr_range().contains(&self) { + panic!("{self:018p} does not seem to be valid object"); + } + } + + fn display(self) -> impl Display { + let type_name = any::type_name::() + .split("::") + .last() + .expect("should not empty"); + format!("`{type_name}` ({self:018p})") + } + + fn panic_for_deleted(self) -> ! { + panic!("{}は既に破棄されています", self.display()); + } +} + +#[ext] +impl T { + fn lock_heads() -> impl DerefMut> { + Self::heads().lock().unwrap_or_else(|e| panic!("{e}")) + } + + fn lock_bodies( + ) -> impl DerefMut>>>> + { + Self::bodies().lock().unwrap_or_else(|e| panic!("{e}")) + } +} diff --git a/crates/voicevox_core_c_api/src/result_code.rs b/crates/voicevox_core_c_api/src/result_code.rs index b35f9bc23..645eb289b 100644 --- a/crates/voicevox_core_c_api/src/result_code.rs +++ b/crates/voicevox_core_c_api/src/result_code.rs @@ -3,10 +3,11 @@ use std::ffi::CStr; /// 処理結果を示す結果コード。 #[repr(i32)] #[derive(Debug, PartialEq, Eq, Clone, Copy)] -#[allow(non_camel_case_types)] +#[allow( + non_camel_case_types, + reason = "実際に公開するC APIとの差異をできるだけ少なくするため" +)] pub enum VoicevoxResultCode { - // C でのenum定義に合わせて大文字で定義している - // 出力フォーマットを変更すればRustでよく使われているUpperCamelにできるが、実際に出力されるコードとの差異をできるだけ少なくするため /// 成功 VOICEVOX_RESULT_OK = 0, /// open_jtalk辞書ファイルが読み込まれていない @@ -15,12 +16,14 @@ pub enum VoicevoxResultCode { VOICEVOX_RESULT_GET_SUPPORTED_DEVICES_ERROR = 3, /// GPUモードがサポートされていない VOICEVOX_RESULT_GPU_SUPPORT_ERROR = 4, + /// 推論ライブラリのロードまたは初期化ができなかった + VOICEVOX_RESULT_INIT_INFERENCE_RUNTIME_ERROR = 29, /// スタイルIDに対するスタイルが見つからなかった VOICEVOX_RESULT_STYLE_NOT_FOUND_ERROR = 6, /// 音声モデルIDに対する音声モデルが見つからなかった VOICEVOX_RESULT_MODEL_NOT_FOUND_ERROR = 7, /// 推論に失敗した - VOICEVOX_RESULT_INFERENCE_ERROR = 8, + VOICEVOX_RESULT_RUN_MODEL_ERROR = 8, /// コンテキストラベル出力に失敗した VOICEVOX_RESULT_EXTRACT_FULL_CONTEXT_LABEL_ERROR = 11, /// 無効なutf8文字列が入力された @@ -65,16 +68,19 @@ pub(crate) const fn error_result_to_message(result_code: VoicevoxResultCode) -> VOICEVOX_RESULT_GET_SUPPORTED_DEVICES_ERROR => { c"サポートされているデバイス情報取得中にエラーが発生しました" } + VOICEVOX_RESULT_INIT_INFERENCE_RUNTIME_ERROR => { + c"推論ライブラリのロードまたは初期化ができませんでした" + } VOICEVOX_RESULT_OK => c"エラーが発生しませんでした", VOICEVOX_RESULT_STYLE_NOT_FOUND_ERROR => { - c"指定されたIDに対するスタイルが見つかりませんでした。音声モデルが読み込まれていないか\ - 、読み込みが解除されています" + c"指定されたIDに対するスタイルが見つかりませんでした。音声モデルが読み込まれていない\ + か、読み込みが解除されています" } VOICEVOX_RESULT_MODEL_NOT_FOUND_ERROR => { c"指定されたIDに対する音声モデルが見つかりませんでした。読み込まれていないか、読み込み\ が既に解除されています" } - VOICEVOX_RESULT_INFERENCE_ERROR => c"推論に失敗しました", + VOICEVOX_RESULT_RUN_MODEL_ERROR => c"推論に失敗しました", VOICEVOX_RESULT_EXTRACT_FULL_CONTEXT_LABEL_ERROR => { c"入力テキストからのフルコンテキストラベル抽出に失敗しました" } diff --git a/crates/voicevox_core_c_api/src/slice_owner.rs b/crates/voicevox_core_c_api/src/slice_owner.rs index fa75add52..239847cb7 100644 --- a/crates/voicevox_core_c_api/src/slice_owner.rs +++ b/crates/voicevox_core_c_api/src/slice_owner.rs @@ -55,8 +55,8 @@ impl SliceOwner { ); } - out_ptr.as_ptr().write_unaligned(ptr); - out_len.as_ptr().write_unaligned(len); + out_ptr.write_unaligned(ptr); + out_len.write_unaligned(len); } /// `own_and_lend`でC API利用者に貸し出したポインタに対応する`Box<[u8]>`をデストラクトする。 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 1e4958eda..e779fdc23 100644 --- a/crates/voicevox_core_c_api/tests/e2e/assert_cdylib.rs +++ b/crates/voicevox_core_c_api/tests/e2e/assert_cdylib.rs @@ -8,6 +8,7 @@ use assert_cmd::assert::{Assert, AssertResult, OutputAssertExt as _}; use clap::Parser as _; use duct::cmd; use easy_ext::ext; +use itertools::Itertools as _; use libloading::Library; use libtest_mimic::{Failed, Trial}; @@ -46,11 +47,15 @@ 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", + "--features", + &format!(",{}", C::FEATURES.iter().format(",")), + ) + .run()?; ensure!( C::cdylib_path().exists(), @@ -100,9 +105,9 @@ pub(crate) fn exec() -> anyhow::Result<()> { } pub(crate) trait TestContext { + const FEATURES: &'static [&'static str]; 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)]; } diff --git a/crates/voicevox_core_c_api/tests/e2e/log_mask.rs b/crates/voicevox_core_c_api/tests/e2e/log_mask.rs index b28442bdb..4e6d26482 100644 --- a/crates/voicevox_core_c_api/tests/e2e/log_mask.rs +++ b/crates/voicevox_core_c_api/tests/e2e/log_mask.rs @@ -1,11 +1,12 @@ -use once_cell::sync::Lazy; +use std::sync::LazyLock; + use regex::{Regex, Replacer}; use crate::assert_cdylib::Utf8Output; macro_rules! static_regex { ($regex:expr $(,)?) => {{ - static REGEX: Lazy = Lazy::new(|| $regex.parse().unwrap()); + static REGEX: LazyLock = LazyLock::new(|| $regex.parse().unwrap()); ®EX }}; } @@ -20,10 +21,17 @@ impl Utf8Output { ) } + pub(crate) fn mask_onnxruntime_version(self) -> Self { + self.mask_stderr( + static_regex!(regex::escape(ort::downloaded_version!())), + "{onnxruntime_version}", + ) + } + pub(crate) fn mask_windows_video_cards(self) -> Self { self.mask_stderr( static_regex!( - r#"(?m)^\{timestamp\} INFO voicevox_core::synthesizer::blocking: 検出されたGPU \(DirectMLには1番目のGPUが使われます\):(\n\{timestamp\} INFO voicevox_core::synthesizer::blocking: - "[^"]+" \([0-9.]+ [a-zA-Z]+\))+"#, + r#"(?m)^\{timestamp\} INFO voicevox_core::synthesizer::blocking: 検出されたGPU \(DirectMLにはGPU 0が使われます\):(\n\{timestamp\} INFO voicevox_core::synthesizer::blocking: GPU [0-9]+: "[^"]+" \([0-9.]+ [a-zA-Z]+\))+"#, ), "{windows-video-cards}", ) diff --git a/crates/voicevox_core_c_api/tests/e2e/main.rs b/crates/voicevox_core_c_api/tests/e2e/main.rs index 5e8e19a9c..0a7520c76 100644 --- a/crates/voicevox_core_c_api/tests/e2e/main.rs +++ b/crates/voicevox_core_c_api/tests/e2e/main.rs @@ -1,3 +1,5 @@ +use test_util::c_api::VV_MODELS_ROOT_DIR; + mod assert_cdylib; mod float_assert; mod log_mask; @@ -20,19 +22,10 @@ fn main() -> anyhow::Result<()> { enum TestContext {} impl assert_cdylib::TestContext for TestContext { + const FEATURES: &'static [&'static str] = &["load-onnxruntime"]; 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.rs b/crates/voicevox_core_c_api/tests/e2e/snapshots.rs index 249bc5f09..c4c181603 100644 --- a/crates/voicevox_core_c_api/tests/e2e/snapshots.rs +++ b/crates/voicevox_core_c_api/tests/e2e/snapshots.rs @@ -10,7 +10,7 @@ macro_rules! section { $section_name: T, } - ::once_cell::sync::Lazy::new(|| { + ::std::sync::LazyLock::new(|| { let Snapshots { $section_name } = ::toml::from_str(crate::snapshots::SNAPSHOTS_TOML).unwrap(); $section_name diff --git a/crates/voicevox_core_c_api/tests/e2e/snapshots.toml b/crates/voicevox_core_c_api/tests/e2e/snapshots.toml index 151074cb3..602824543 100644 --- a/crates/voicevox_core_c_api/tests/e2e/snapshots.toml +++ b/crates/voicevox_core_c_api/tests/e2e/snapshots.toml @@ -51,14 +51,43 @@ metas = ''' } ]''' stderr.windows = ''' +{timestamp} INFO ort: Loaded ONNX Runtime dylib with version '{onnxruntime_version}' {windows-video-cards} +{timestamp} INFO voicevox_core::synthesizer::blocking: CPUを利用します +''' +stderr.unix = ''' +{timestamp} INFO ort: Loaded ONNX Runtime dylib with version '{onnxruntime_version}' +{timestamp} INFO voicevox_core::synthesizer::blocking: CPUを利用します ''' -stderr.unix = "" [compatible_engine_load_model_before_initialize] last_error_message = "Statusが初期化されていません" stderr = "" +[double_delete_openjtalk] +stderr_matches_all = [ + '\n`OpenJtalkRc` \(0x[0-9a-f]{16}\)は既に破棄されています\n', + "\nthread caused non-unwinding panic. aborting.\n", +] + +[double_delete_synthesizer] +stderr_matches_all = [ + '\n`VoicevoxSynthesizer` \(0x[0-9a-f]{16}\)は既に破棄されています\n', + "\nthread caused non-unwinding panic. aborting.\n", +] + +[double_delete_user_dict] +stderr_matches_all = [ + '\n`VoicevoxUserDict` \(0x[0-9a-f]{16}\)は既に破棄されています\n', + "\nthread caused non-unwinding panic. aborting.\n", +] + +[double_delete_voice_model_file] +stderr_matches_all = [ + '\n`VoicevoxVoiceModelFile` \(0x[0-9a-f]{16}\)は既に破棄されています\n', + "\nthread caused non-unwinding panic. aborting.\n", +] + [global_info] result_messages.0 = "エラーが発生しませんでした" result_messages.1 = "OpenJTalkの辞書が読み込まれていません" @@ -83,14 +112,23 @@ result_messages.22 = "ユーザー辞書に単語が見つかりませんでし result_messages.23 = "OpenJTalkのユーザー辞書の設定に失敗しました" result_messages.24 = "ユーザー辞書の単語のバリデーションに失敗しました" result_messages.25 = "UUIDの変換に失敗しました" -stderr = "" +result_messages.28 = "モデルの形式が不正です" +result_messages.29 = "推論ライブラリのロードまたは初期化ができませんでした" +stderr = ''' +{timestamp} INFO ort: Loaded ONNX Runtime dylib with version '{onnxruntime_version}' +''' [simple_tts] output."こんにちは、音声合成の世界へようこそ".wav_length = 176172 stderr.windows = ''' +{timestamp} INFO ort: Loaded ONNX Runtime dylib with version '{onnxruntime_version}' {windows-video-cards} +{timestamp} INFO voicevox_core::synthesizer::blocking: CPUを利用します +''' +stderr.unix = ''' +{timestamp} INFO ort: Loaded ONNX Runtime dylib with version '{onnxruntime_version}' +{timestamp} INFO voicevox_core::synthesizer::blocking: CPUを利用します ''' -stderr.unix = "" [synthesizer_new_output_json] metas = ''' @@ -145,22 +183,37 @@ metas = ''' } ]''' stderr.windows = ''' +{timestamp} INFO ort: Loaded ONNX Runtime dylib with version '{onnxruntime_version}' {windows-video-cards} +{timestamp} INFO voicevox_core::synthesizer::blocking: CPUを利用します +''' +stderr.unix = ''' +{timestamp} INFO ort: Loaded ONNX Runtime dylib with version '{onnxruntime_version}' +{timestamp} INFO voicevox_core::synthesizer::blocking: CPUを利用します ''' -stderr.unix = "" [tts_via_audio_query] output."こんにちは、音声合成の世界へようこそ".wav_length = 176172 stderr.windows = ''' +{timestamp} INFO ort: Loaded ONNX Runtime dylib with version '{onnxruntime_version}' {windows-video-cards} +{timestamp} INFO voicevox_core::synthesizer::blocking: CPUを利用します +''' +stderr.unix = ''' +{timestamp} INFO ort: Loaded ONNX Runtime dylib with version '{onnxruntime_version}' +{timestamp} INFO voicevox_core::synthesizer::blocking: CPUを利用します ''' -stderr.unix = "" -[user_dict] +[user_dict_load] stderr.windows = ''' +{timestamp} INFO ort: Loaded ONNX Runtime dylib with version '{onnxruntime_version}' {windows-video-cards} +{timestamp} INFO voicevox_core::synthesizer::blocking: CPUを利用します +''' +stderr.unix = ''' +{timestamp} INFO ort: Loaded ONNX Runtime dylib with version '{onnxruntime_version}' +{timestamp} INFO voicevox_core::synthesizer::blocking: CPUを利用します ''' -stderr.unix = "" [user_dict_manipulate] stderr = "" diff --git a/crates/voicevox_core_c_api/tests/e2e/testcases.rs b/crates/voicevox_core_c_api/tests/e2e/testcases.rs index 31eb9cdfe..677d2855f 100644 --- a/crates/voicevox_core_c_api/tests/e2e/testcases.rs +++ b/crates/voicevox_core_c_api/tests/e2e/testcases.rs @@ -1,5 +1,9 @@ mod compatible_engine; mod compatible_engine_load_model_before_initialize; +mod double_delete_openjtalk; +mod double_delete_synthesizer; +mod double_delete_user_dict; +mod double_delete_voice_model_file; mod global_info; mod simple_tts; mod synthesizer_new_output_json; 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 c2fc211c2..e69ad68fd 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 @@ -1,10 +1,10 @@ // エンジンを起動してyukarin_s・yukarin_sa・decodeの推論を行う use std::ffi::CStr; +use std::sync::LazyLock; use assert_cmd::assert::AssertResult; use libloading::Library; -use once_cell::sync::Lazy; use serde::{Deserialize, Serialize}; use voicevox_core::SupportedDevices; @@ -31,12 +31,10 @@ impl assert_cdylib::TestCase for TestCase { serde_json::to_string_pretty(&metas_json.parse::()?).unwrap() }; - let supported_devices = { + { let supported_devices = lib.supported_devices(); - CStr::from_ptr(supported_devices) - .to_str()? - .parse::()? - }; + serde_json::from_str::(CStr::from_ptr(supported_devices).to_str()?)?; + } assert!(lib.initialize(false, 0, false)); @@ -86,10 +84,6 @@ impl assert_cdylib::TestCase for TestCase { }; std::assert_eq!(SNAPSHOTS.metas, metas_json); - std::assert_eq!( - SupportedDevices::create().unwrap().to_json(), - supported_devices, - ); float_assert::close_l1(&phoneme_length, &EXAMPLE_DATA.duration.result, 0.01); float_assert::close_l1(&intonation_list, &EXAMPLE_DATA.intonation.result, 0.01); @@ -103,6 +97,7 @@ impl assert_cdylib::TestCase for TestCase { fn assert_output(&self, output: Utf8Output) -> AssertResult { output .mask_timestamps() + .mask_onnxruntime_version() .mask_windows_video_cards() .assert() .try_success()? @@ -111,7 +106,7 @@ impl assert_cdylib::TestCase for TestCase { } } -static SNAPSHOTS: Lazy = snapshots::section!(compatible_engine); +static SNAPSHOTS: LazyLock = snapshots::section!(compatible_engine); #[derive(Deserialize)] struct Snapshots { 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 173e32f8c..7b709a83d 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 @@ -1,10 +1,9 @@ // initialize前にモデルを読み込むとエラーになるテスト -use std::ffi::CStr; +use std::{ffi::CStr, sync::LazyLock}; use assert_cmd::assert::AssertResult; use libloading::Library; -use once_cell::sync::Lazy; use serde::{Deserialize, Serialize}; use test_util::c_api::CApi; @@ -34,6 +33,7 @@ impl assert_cdylib::TestCase for TestCase { fn assert_output(&self, output: Utf8Output) -> AssertResult { output .mask_timestamps() + .mask_onnxruntime_version() .mask_windows_video_cards() .assert() .try_success()? @@ -42,7 +42,7 @@ impl assert_cdylib::TestCase for TestCase { } } -static SNAPSHOTS: Lazy = +static SNAPSHOTS: LazyLock = snapshots::section!(compatible_engine_load_model_before_initialize); #[derive(Deserialize)] diff --git a/crates/voicevox_core_c_api/tests/e2e/testcases/double_delete_openjtalk.rs b/crates/voicevox_core_c_api/tests/e2e/testcases/double_delete_openjtalk.rs new file mode 100644 index 000000000..511ae3b41 --- /dev/null +++ b/crates/voicevox_core_c_api/tests/e2e/testcases/double_delete_openjtalk.rs @@ -0,0 +1,62 @@ +//! `voicevox_open_jtalk_rc_delete`を二度呼ぶとクラッシュすることを確認する。 + +use std::{ffi::CString, mem::MaybeUninit, sync::LazyLock}; + +use assert_cmd::assert::AssertResult; +use indexmap::IndexSet; +use libloading::Library; +use serde::{Deserialize, Serialize}; +use test_util::{ + c_api::{self, CApi, VoicevoxResultCode}, + OPEN_JTALK_DIC_DIR, +}; + +use crate::{ + assert_cdylib::{self, case, Utf8Output}, + snapshots, +}; + +case!(TestCase); + +#[derive(Serialize, Deserialize)] +struct TestCase; + +#[typetag::serde(name = "double_delete_openjtalk")] +impl assert_cdylib::TestCase for TestCase { + 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( + lib.voicevox_open_jtalk_rc_new(open_jtalk_dic_dir.as_ptr(), openjtalk.as_mut_ptr()), + ); + openjtalk.assume_init() + }; + + lib.voicevox_open_jtalk_rc_delete(openjtalk); + lib.voicevox_open_jtalk_rc_delete(openjtalk); + unreachable!(); + + fn assert_ok(result_code: VoicevoxResultCode) { + std::assert_eq!(c_api::VoicevoxResultCode_VOICEVOX_RESULT_OK, result_code); + } + } + + fn assert_output(&self, output: Utf8Output) -> AssertResult { + let mut assert = output.assert().try_failure()?.try_stdout("")?; + for s in &SNAPSHOTS.stderr_matches_all { + let p = predicates::str::is_match(s).unwrap_or_else(|e| panic!("{e}")); + assert = assert.try_stderr(p)?; + } + Ok(assert) + } +} + +static SNAPSHOTS: LazyLock = snapshots::section!(double_delete_openjtalk); + +#[derive(Deserialize)] +struct Snapshots { + stderr_matches_all: IndexSet, +} diff --git a/crates/voicevox_core_c_api/tests/e2e/testcases/double_delete_synthesizer.rs b/crates/voicevox_core_c_api/tests/e2e/testcases/double_delete_synthesizer.rs new file mode 100644 index 000000000..170ee718f --- /dev/null +++ b/crates/voicevox_core_c_api/tests/e2e/testcases/double_delete_synthesizer.rs @@ -0,0 +1,86 @@ +//! `voicevox_synthesizer_delete`を二度呼ぶとクラッシュすることを確認する。 + +use std::{ffi::CString, mem::MaybeUninit, sync::LazyLock}; + +use assert_cmd::assert::AssertResult; +use indexmap::IndexSet; +use libloading::Library; +use serde::{Deserialize, Serialize}; +use test_util::{ + c_api::{self, CApi, VoicevoxInitializeOptions, VoicevoxResultCode}, + OPEN_JTALK_DIC_DIR, +}; + +use crate::{ + assert_cdylib::{self, case, Utf8Output}, + snapshots, +}; + +case!(TestCase); + +#[derive(Serialize, Deserialize)] +struct TestCase; + +#[typetag::serde(name = "double_delete_synthesizer")] +impl assert_cdylib::TestCase for TestCase { + unsafe fn exec(&self, lib: Library) -> anyhow::Result<()> { + let lib = CApi::from_library(lib)?; + + let onnxruntime = { + let mut onnxruntime = MaybeUninit::uninit(); + assert_ok(lib.voicevox_onnxruntime_load_once( + lib.voicevox_make_default_load_onnxruntime_options(), + onnxruntime.as_mut_ptr(), + )); + onnxruntime.assume_init() + }; + + let openjtalk = { + let mut openjtalk = MaybeUninit::uninit(); + let open_jtalk_dic_dir = CString::new(OPEN_JTALK_DIC_DIR).unwrap(); + 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(lib.voicevox_synthesizer_new( + onnxruntime, + openjtalk, + VoicevoxInitializeOptions { + acceleration_mode: + c_api::VoicevoxAccelerationMode_VOICEVOX_ACCELERATION_MODE_CPU, + ..lib.voicevox_make_default_initialize_options() + }, + synthesizer.as_mut_ptr(), + )); + synthesizer.assume_init() + }; + + lib.voicevox_synthesizer_delete(synthesizer); + lib.voicevox_synthesizer_delete(synthesizer); + unreachable!(); + + fn assert_ok(result_code: VoicevoxResultCode) { + std::assert_eq!(c_api::VoicevoxResultCode_VOICEVOX_RESULT_OK, result_code); + } + } + + fn assert_output(&self, output: Utf8Output) -> AssertResult { + let mut assert = output.assert().try_failure()?.try_stdout("")?; + for s in &SNAPSHOTS.stderr_matches_all { + let p = predicates::str::is_match(s).unwrap_or_else(|e| panic!("{e}")); + assert = assert.try_stderr(p)?; + } + Ok(assert) + } +} + +static SNAPSHOTS: LazyLock = snapshots::section!(double_delete_synthesizer); + +#[derive(Deserialize)] +struct Snapshots { + stderr_matches_all: IndexSet, +} diff --git a/crates/voicevox_core_c_api/tests/e2e/testcases/double_delete_user_dict.rs b/crates/voicevox_core_c_api/tests/e2e/testcases/double_delete_user_dict.rs new file mode 100644 index 000000000..6256b63e0 --- /dev/null +++ b/crates/voicevox_core_c_api/tests/e2e/testcases/double_delete_user_dict.rs @@ -0,0 +1,47 @@ +//! `voicevox_user_dict_delete`を二度呼ぶとクラッシュすることを確認する。 + +use std::sync::LazyLock; + +use assert_cmd::assert::AssertResult; +use indexmap::IndexSet; +use libloading::Library; +use serde::{Deserialize, Serialize}; +use test_util::c_api::CApi; + +use crate::{ + assert_cdylib::{self, case, Utf8Output}, + snapshots, +}; + +case!(TestCase); + +#[derive(Serialize, Deserialize)] +struct TestCase; + +#[typetag::serde(name = "double_delete_user_dict")] +impl assert_cdylib::TestCase for TestCase { + unsafe fn exec(&self, lib: Library) -> anyhow::Result<()> { + let lib = CApi::from_library(lib)?; + + let dict = lib.voicevox_user_dict_new(); + lib.voicevox_user_dict_delete(dict); + lib.voicevox_user_dict_delete(dict); + unreachable!(); + } + + fn assert_output(&self, output: Utf8Output) -> AssertResult { + let mut assert = output.assert().try_failure()?.try_stdout("")?; + for s in &SNAPSHOTS.stderr_matches_all { + let p = predicates::str::is_match(s).unwrap_or_else(|e| panic!("{e}")); + assert = assert.try_stderr(p)?; + } + Ok(assert) + } +} + +static SNAPSHOTS: LazyLock = snapshots::section!(double_delete_user_dict); + +#[derive(Deserialize)] +struct Snapshots { + stderr_matches_all: IndexSet, +} diff --git a/crates/voicevox_core_c_api/tests/e2e/testcases/double_delete_voice_model_file.rs b/crates/voicevox_core_c_api/tests/e2e/testcases/double_delete_voice_model_file.rs new file mode 100644 index 000000000..1ea6bdc19 --- /dev/null +++ b/crates/voicevox_core_c_api/tests/e2e/testcases/double_delete_voice_model_file.rs @@ -0,0 +1,59 @@ +//! `voicevox_voice_model_file_close`を二度呼ぶとクラッシュすることを確認する。 + +use std::{mem::MaybeUninit, sync::LazyLock}; + +use assert_cmd::assert::AssertResult; +use indexmap::IndexSet; +use libloading::Library; +use serde::{Deserialize, Serialize}; +use test_util::c_api::{self, CApi, VoicevoxResultCode}; + +use crate::{ + assert_cdylib::{self, case, Utf8Output}, + snapshots, +}; + +case!(TestCase); + +#[derive(Serialize, Deserialize)] +struct TestCase; + +#[typetag::serde(name = "double_delete_voice_model_file")] +impl assert_cdylib::TestCase for TestCase { + unsafe fn exec(&self, lib: Library) -> anyhow::Result<()> { + let lib = CApi::from_library(lib)?; + + let model = { + let mut model = MaybeUninit::uninit(); + assert_ok(lib.voicevox_voice_model_file_open( + c_api::SAMPLE_VOICE_MODEL_FILE_PATH.as_ptr(), + model.as_mut_ptr(), + )); + model.assume_init() + }; + + lib.voicevox_voice_model_file_close(model); + lib.voicevox_voice_model_file_close(model); + unreachable!(); + + fn assert_ok(result_code: VoicevoxResultCode) { + std::assert_eq!(c_api::VoicevoxResultCode_VOICEVOX_RESULT_OK, result_code); + } + } + + fn assert_output(&self, output: Utf8Output) -> AssertResult { + let mut assert = output.assert().try_failure()?.try_stdout("")?; + for s in &SNAPSHOTS.stderr_matches_all { + let p = predicates::str::is_match(s).unwrap_or_else(|e| panic!("{e}")); + assert = assert.try_stderr(p)?; + } + Ok(assert) + } +} + +static SNAPSHOTS: LazyLock = snapshots::section!(double_delete_voice_model_file); + +#[derive(Deserialize)] +struct Snapshots { + stderr_matches_all: IndexSet, +} 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 c6ea390ed..a36a7d9bd 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 @@ -1,8 +1,7 @@ -use std::{collections::HashMap, ffi::CStr, mem::MaybeUninit, str}; +use std::{collections::HashMap, ffi::CStr, mem::MaybeUninit, str, sync::LazyLock}; use assert_cmd::assert::AssertResult; use libloading::Library; -use once_cell::sync::Lazy; use serde::{Deserialize, Serialize}; use serde_with::{serde_as, DisplayFromStr}; use test_util::c_api::{self, CApi, VoicevoxResultCode}; @@ -28,16 +27,23 @@ impl assert_cdylib::TestCase for TestCase { CStr::from_ptr(lib.voicevox_get_version()).to_str()?, ); + let onnxruntime = { + let mut onnxruntime = MaybeUninit::uninit(); + assert_ok(lib.voicevox_onnxruntime_load_once( + lib.voicevox_make_default_load_onnxruntime_options(), + onnxruntime.as_mut_ptr(), + )); + onnxruntime.assume_init() + }; + { let mut supported_devices = MaybeUninit::uninit(); - assert_ok(lib.voicevox_create_supported_devices_json(supported_devices.as_mut_ptr())); + assert_ok(lib.voicevox_onnxruntime_create_supported_devices_json( + onnxruntime, + supported_devices.as_mut_ptr(), + )); let supported_devices = supported_devices.assume_init(); - std::assert_eq!( - SupportedDevices::create()?.to_json(), - CStr::from_ptr(supported_devices) - .to_str()? - .parse::()?, - ); + serde_json::from_str::(CStr::from_ptr(supported_devices).to_str()?)?; lib.voicevox_json_free(supported_devices); } @@ -48,7 +54,7 @@ impl assert_cdylib::TestCase for TestCase { 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_RUN_MODEL_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, @@ -83,6 +89,7 @@ impl assert_cdylib::TestCase for TestCase { fn assert_output(&self, output: Utf8Output) -> AssertResult { output .mask_timestamps() + .mask_onnxruntime_version() .mask_windows_video_cards() .assert() .try_success()? @@ -91,7 +98,7 @@ impl assert_cdylib::TestCase for TestCase { } } -static SNAPSHOTS: Lazy = snapshots::section!(global_info); +static SNAPSHOTS: LazyLock = snapshots::section!(global_info); #[serde_as] #[derive(Deserialize)] 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 8c834609e..1997d30e9 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,8 +1,7 @@ -use std::{collections::HashMap, ffi::CString, mem::MaybeUninit}; +use std::{collections::HashMap, ffi::CString, mem::MaybeUninit, sync::LazyLock}; use assert_cmd::assert::AssertResult; use libloading::Library; -use once_cell::sync::Lazy; use serde::{Deserialize, Serialize}; use test_util::{ c_api::{self, CApi, VoicevoxInitializeOptions, VoicevoxResultCode}, @@ -30,13 +29,22 @@ impl assert_cdylib::TestCase for TestCase { let model = { let mut model = MaybeUninit::uninit(); - assert_ok(lib.voicevox_voice_model_new_from_path( - c"../../model/sample.vvm".as_ptr(), + assert_ok(lib.voicevox_voice_model_file_open( + c_api::SAMPLE_VOICE_MODEL_FILE_PATH.as_ptr(), model.as_mut_ptr(), )); model.assume_init() }; + let onnxruntime = { + let mut onnxruntime = MaybeUninit::uninit(); + assert_ok(lib.voicevox_onnxruntime_load_once( + lib.voicevox_make_default_load_onnxruntime_options(), + onnxruntime.as_mut_ptr(), + )); + onnxruntime.assume_init() + }; + let openjtalk = { let mut openjtalk = MaybeUninit::uninit(); let open_jtalk_dic_dir = CString::new(OPEN_JTALK_DIC_DIR).unwrap(); @@ -49,6 +57,7 @@ impl assert_cdylib::TestCase for TestCase { let synthesizer = { let mut synthesizer = MaybeUninit::uninit(); assert_ok(lib.voicevox_synthesizer_new( + onnxruntime, openjtalk, VoicevoxInitializeOptions { acceleration_mode: @@ -79,7 +88,7 @@ impl assert_cdylib::TestCase for TestCase { std::assert_eq!(SNAPSHOTS.output[&self.text].wav_length, wav_length); - lib.voicevox_voice_model_delete(model); + lib.voicevox_voice_model_file_close(model); lib.voicevox_open_jtalk_rc_delete(openjtalk); lib.voicevox_synthesizer_delete(synthesizer); lib.voicevox_wav_free(wav); @@ -96,6 +105,7 @@ impl assert_cdylib::TestCase for TestCase { fn assert_output(&self, output: Utf8Output) -> AssertResult { output .mask_timestamps() + .mask_onnxruntime_version() .mask_windows_video_cards() .assert() .try_success()? @@ -104,7 +114,7 @@ impl assert_cdylib::TestCase for TestCase { } } -static SNAPSHOTS: Lazy = snapshots::section!(simple_tts); +static SNAPSHOTS: LazyLock = snapshots::section!(simple_tts); #[derive(Deserialize)] struct Snapshots { 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 7c7c57bf9..ac662d06e 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 @@ -1,11 +1,11 @@ use std::{ ffi::{CStr, CString}, mem::MaybeUninit, + sync::LazyLock, }; use assert_cmd::assert::AssertResult; use libloading::Library; -use once_cell::sync::Lazy; use serde::{Deserialize, Serialize}; use test_util::{ @@ -28,6 +28,15 @@ impl assert_cdylib::TestCase for TestCase { unsafe fn exec(&self, lib: Library) -> anyhow::Result<()> { let lib = CApi::from_library(lib)?; + let onnxruntime = { + let mut onnxruntime = MaybeUninit::uninit(); + assert_ok(lib.voicevox_onnxruntime_load_once( + lib.voicevox_make_default_load_onnxruntime_options(), + onnxruntime.as_mut_ptr(), + )); + onnxruntime.assume_init() + }; + let openjtalk = { let mut openjtalk = MaybeUninit::uninit(); let open_jtalk_dic_dir = CString::new(OPEN_JTALK_DIC_DIR).unwrap(); @@ -40,6 +49,7 @@ impl assert_cdylib::TestCase for TestCase { let synthesizer = { let mut synthesizer = MaybeUninit::uninit(); assert_ok(lib.voicevox_synthesizer_new( + onnxruntime, openjtalk, VoicevoxInitializeOptions { acceleration_mode: @@ -53,8 +63,8 @@ impl assert_cdylib::TestCase for TestCase { let model = { let mut model = MaybeUninit::uninit(); - assert_ok(lib.voicevox_voice_model_new_from_path( - c"../../model/sample.vvm".as_ptr(), + assert_ok(lib.voicevox_voice_model_file_open( + c_api::SAMPLE_VOICE_MODEL_FILE_PATH.as_ptr(), model.as_mut_ptr(), )); model.assume_init() @@ -85,6 +95,7 @@ impl assert_cdylib::TestCase for TestCase { fn assert_output(&self, output: Utf8Output) -> AssertResult { output .mask_timestamps() + .mask_onnxruntime_version() .mask_windows_video_cards() .assert() .try_success()? @@ -93,7 +104,7 @@ impl assert_cdylib::TestCase for TestCase { } } -static SNAPSHOTS: Lazy = snapshots::section!(synthesizer_new_output_json); +static SNAPSHOTS: LazyLock = snapshots::section!(synthesizer_new_output_json); #[derive(Deserialize)] struct Snapshots { 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 e1c9be664..2536a73d3 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,8 +1,7 @@ -use std::{collections::HashMap, ffi::CString, mem::MaybeUninit}; +use std::{collections::HashMap, ffi::CString, mem::MaybeUninit, sync::LazyLock}; use assert_cmd::assert::AssertResult; use libloading::Library; -use once_cell::sync::Lazy; use serde::{Deserialize, Serialize}; use test_util::{ c_api::{self, CApi, VoicevoxInitializeOptions, VoicevoxResultCode}, @@ -30,13 +29,22 @@ impl assert_cdylib::TestCase for TestCase { let model = { let mut model = MaybeUninit::uninit(); - assert_ok(lib.voicevox_voice_model_new_from_path( - c"../../model/sample.vvm".as_ptr(), + assert_ok(lib.voicevox_voice_model_file_open( + c_api::SAMPLE_VOICE_MODEL_FILE_PATH.as_ptr(), model.as_mut_ptr(), )); model.assume_init() }; + let onnxruntime = { + let mut onnxruntime = MaybeUninit::uninit(); + assert_ok(lib.voicevox_onnxruntime_load_once( + lib.voicevox_make_default_load_onnxruntime_options(), + onnxruntime.as_mut_ptr(), + )); + onnxruntime.assume_init() + }; + let openjtalk = { let mut openjtalk = MaybeUninit::uninit(); let open_jtalk_dic_dir = CString::new(OPEN_JTALK_DIC_DIR).unwrap(); @@ -49,6 +57,7 @@ impl assert_cdylib::TestCase for TestCase { let synthesizer = { let mut synthesizer = MaybeUninit::uninit(); assert_ok(lib.voicevox_synthesizer_new( + onnxruntime, openjtalk, VoicevoxInitializeOptions { acceleration_mode: @@ -90,7 +99,7 @@ impl assert_cdylib::TestCase for TestCase { std::assert_eq!(SNAPSHOTS.output[&self.text].wav_length, wav_length); - lib.voicevox_voice_model_delete(model); + lib.voicevox_voice_model_file_close(model); lib.voicevox_open_jtalk_rc_delete(openjtalk); lib.voicevox_synthesizer_delete(synthesizer); lib.voicevox_json_free(audio_query); @@ -108,6 +117,7 @@ impl assert_cdylib::TestCase for TestCase { fn assert_output(&self, output: Utf8Output) -> AssertResult { output .mask_timestamps() + .mask_onnxruntime_version() .mask_windows_video_cards() .assert() .try_success()? @@ -116,7 +126,7 @@ impl assert_cdylib::TestCase for TestCase { } } -static SNAPSHOTS: Lazy = snapshots::section!(tts_via_audio_query); +static SNAPSHOTS: LazyLock = snapshots::section!(tts_via_audio_query); #[derive(Deserialize)] struct Snapshots { 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 64369d248..25915bc10 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,15 +1,15 @@ // ユーザー辞書の登録によって読みが変化することを確認するテスト。 // 辞書ロード前後でAudioQueryのkanaが変化するかどうかで確認する。 -use assert_cmd::assert::AssertResult; -use once_cell::sync::Lazy; use std::ffi::{CStr, CString}; use std::mem::MaybeUninit; -use test_util::OPEN_JTALK_DIC_DIR; +use std::sync::LazyLock; +use assert_cmd::assert::AssertResult; use libloading::Library; use serde::{Deserialize, Serialize}; use test_util::c_api::{self, CApi, VoicevoxInitializeOptions, VoicevoxResultCode}; +use test_util::OPEN_JTALK_DIC_DIR; use crate::{ assert_cdylib::{self, case, Utf8Output}, @@ -46,13 +46,22 @@ impl assert_cdylib::TestCase for TestCase { let model = { let mut model = MaybeUninit::uninit(); - assert_ok(lib.voicevox_voice_model_new_from_path( - c"../../model/sample.vvm".as_ptr(), + assert_ok(lib.voicevox_voice_model_file_open( + c_api::SAMPLE_VOICE_MODEL_FILE_PATH.as_ptr(), model.as_mut_ptr(), )); model.assume_init() }; + let onnxruntime = { + let mut onnxruntime = MaybeUninit::uninit(); + assert_ok(lib.voicevox_onnxruntime_load_once( + lib.voicevox_make_default_load_onnxruntime_options(), + onnxruntime.as_mut_ptr(), + )); + onnxruntime.assume_init() + }; + let openjtalk = { let mut openjtalk = MaybeUninit::uninit(); let open_jtalk_dic_dir = CString::new(OPEN_JTALK_DIC_DIR).unwrap(); @@ -65,6 +74,7 @@ impl assert_cdylib::TestCase for TestCase { let synthesizer = { let mut synthesizer = MaybeUninit::uninit(); assert_ok(lib.voicevox_synthesizer_new( + onnxruntime, openjtalk, VoicevoxInitializeOptions { acceleration_mode: @@ -108,7 +118,7 @@ impl assert_cdylib::TestCase for TestCase { audio_query_with_dict.get("kana") ); - lib.voicevox_voice_model_delete(model); + lib.voicevox_voice_model_file_close(model); lib.voicevox_open_jtalk_rc_delete(openjtalk); lib.voicevox_synthesizer_delete(synthesizer); lib.voicevox_user_dict_delete(dict); @@ -124,6 +134,7 @@ impl assert_cdylib::TestCase for TestCase { fn assert_output(&self, output: Utf8Output) -> AssertResult { output .mask_timestamps() + .mask_onnxruntime_version() .mask_windows_video_cards() .assert() .try_success()? @@ -132,7 +143,7 @@ impl assert_cdylib::TestCase for TestCase { } } -static SNAPSHOTS: Lazy = snapshots::section!(user_dict); +static SNAPSHOTS: LazyLock = snapshots::section!(user_dict_load); #[derive(Deserialize)] struct Snapshots { 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 ba85895af..1bd6484fe 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 @@ -1,11 +1,12 @@ // ユーザー辞書の操作をテストする。 -use assert_cmd::assert::AssertResult; -use once_cell::sync::Lazy; use std::{ ffi::{CStr, CString}, mem::MaybeUninit, + sync::LazyLock, }; + +use assert_cmd::assert::AssertResult; use tempfile::NamedTempFile; use uuid::Uuid; @@ -138,6 +139,7 @@ impl assert_cdylib::TestCase for TestCase { fn assert_output(&self, output: Utf8Output) -> AssertResult { output .mask_timestamps() + .mask_onnxruntime_version() .mask_windows_video_cards() .assert() .try_success()? @@ -146,7 +148,7 @@ impl assert_cdylib::TestCase for TestCase { } } -static SNAPSHOTS: Lazy = snapshots::section!(user_dict_manipulate); +static SNAPSHOTS: LazyLock = snapshots::section!(user_dict_manipulate); #[derive(Deserialize)] struct Snapshots { diff --git a/crates/voicevox_core_java_api/Cargo.toml b/crates/voicevox_core_java_api/Cargo.toml index 887813685..96d2c1ce7 100644 --- a/crates/voicevox_core_java_api/Cargo.toml +++ b/crates/voicevox_core_java_api/Cargo.toml @@ -3,24 +3,25 @@ name = "voicevox_core_java_api" version.workspace = true edition.workspace = true publish.workspace = true +rust-version.workspace = true [lib] crate-type = ["cdylib"] -[features] -directml = ["voicevox_core/directml"] - [dependencies] android_logger.workspace = true chrono = { workspace = true, default-features = false, features = ["clock"] } derive_more.workspace = true +duplicate.workspace = true +easy-ext.workspace = true jni.workspace = true -once_cell.workspace = true +pretty_assertions = "1.4.1" +rstest.workspace = true serde_json = { workspace = true, features = ["preserve_order"] } tracing = { workspace = true, features = ["log"] } tracing-subscriber = { workspace = true, features = ["env-filter"] } uuid.workspace = true -voicevox_core.workspace = true +voicevox_core = { workspace = true, features = ["load-onnxruntime"] } [lints.rust] unsafe_code = "allow" # jni-rsが要求 diff --git a/crates/voicevox_core_java_api/gradle/wrapper/gradle-wrapper.jar b/crates/voicevox_core_java_api/gradle/wrapper/gradle-wrapper.jar index 033e24c4c..a4b76b953 100644 Binary files a/crates/voicevox_core_java_api/gradle/wrapper/gradle-wrapper.jar and b/crates/voicevox_core_java_api/gradle/wrapper/gradle-wrapper.jar differ diff --git a/crates/voicevox_core_java_api/gradle/wrapper/gradle-wrapper.properties b/crates/voicevox_core_java_api/gradle/wrapper/gradle-wrapper.properties index 9f4197d5f..df97d72b8 100644 --- a/crates/voicevox_core_java_api/gradle/wrapper/gradle-wrapper.properties +++ b/crates/voicevox_core_java_api/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.2.1-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.10.2-bin.zip networkTimeout=10000 validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME diff --git a/crates/voicevox_core_java_api/gradlew b/crates/voicevox_core_java_api/gradlew index fcb6fca14..f5feea6d6 100755 --- a/crates/voicevox_core_java_api/gradlew +++ b/crates/voicevox_core_java_api/gradlew @@ -15,6 +15,8 @@ # See the License for the specific language governing permissions and # limitations under the License. # +# SPDX-License-Identifier: Apache-2.0 +# ############################################################################## # @@ -55,7 +57,7 @@ # Darwin, MinGW, and NonStop. # # (3) This script is generated from the Groovy template -# https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt # within the Gradle project. # # You can find Gradle at https://github.com/gradle/gradle/. @@ -83,7 +85,9 @@ done # This is normally unused # shellcheck disable=SC2034 APP_BASE_NAME=${0##*/} -APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit +# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) +APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s +' "$PWD" ) || exit # Use the maximum available, or set MAX_FD != -1 to use that value. MAX_FD=maximum @@ -144,7 +148,7 @@ if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then case $MAX_FD in #( max*) # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. - # shellcheck disable=SC3045 + # shellcheck disable=SC2039,SC3045 MAX_FD=$( ulimit -H -n ) || warn "Could not query maximum file descriptor limit" esac @@ -152,7 +156,7 @@ if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then '' | soft) :;; #( *) # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. - # shellcheck disable=SC3045 + # shellcheck disable=SC2039,SC3045 ulimit -n "$MAX_FD" || warn "Could not set maximum file descriptor limit to $MAX_FD" esac @@ -201,11 +205,11 @@ fi # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' -# Collect all arguments for the java command; -# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of -# shell script including quotes and variable substitutions, so put them in -# double quotes to make sure that they get re-expanded; and -# * put everything else in single quotes, so that it's not re-expanded. +# Collect all arguments for the java command: +# * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, +# and any embedded shellness will be escaped. +# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be +# treated as '${Hostname}' itself on the command line. set -- \ "-Dorg.gradle.appname=$APP_BASE_NAME" \ diff --git a/crates/voicevox_core_java_api/gradlew.bat b/crates/voicevox_core_java_api/gradlew.bat index 93e3f59f1..9d21a2183 100644 --- a/crates/voicevox_core_java_api/gradlew.bat +++ b/crates/voicevox_core_java_api/gradlew.bat @@ -13,6 +13,8 @@ @rem See the License for the specific language governing permissions and @rem limitations under the License. @rem +@rem SPDX-License-Identifier: Apache-2.0 +@rem @if "%DEBUG%"=="" @echo off @rem ########################################################################## @@ -43,11 +45,11 @@ set JAVA_EXE=java.exe %JAVA_EXE% -version >NUL 2>&1 if %ERRORLEVEL% equ 0 goto execute -echo. -echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. +echo. 1>&2 +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 goto fail @@ -57,11 +59,11 @@ set JAVA_EXE=%JAVA_HOME%/bin/java.exe if exist "%JAVA_EXE%" goto execute -echo. -echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. +echo. 1>&2 +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 goto fail diff --git a/crates/voicevox_core_java_api/lib/build-android.gradle b/crates/voicevox_core_java_api/lib/build-android.gradle index 124689f94..6ef58135f 100644 --- a/crates/voicevox_core_java_api/lib/build-android.gradle +++ b/crates/voicevox_core_java_api/lib/build-android.gradle @@ -1,5 +1,5 @@ plugins { - id 'com.android.library' version '8.1.1' + id 'com.android.library' version '8.7.0' id 'maven-publish' id 'org.jetbrains.kotlin.android' version '1.9.10' } @@ -13,7 +13,7 @@ repositories { dependencies { // Use JUnit Jupiter for testing. - testImplementation 'org.junit.jupiter:junit-jupiter:5.9.2' + testImplementation 'org.junit.jupiter:junit-jupiter:5.11.2' testRuntimeOnly 'org.junit.platform:junit-platform-launcher' diff --git a/crates/voicevox_core_java_api/lib/build.gradle b/crates/voicevox_core_java_api/lib/build.gradle index 06ceaeb14..5e9191a5f 100644 --- a/crates/voicevox_core_java_api/lib/build.gradle +++ b/crates/voicevox_core_java_api/lib/build.gradle @@ -9,7 +9,7 @@ plugins { // Apply the java-library plugin for API and implementation separation. id 'java-library' id 'maven-publish' - id "com.diffplug.spotless" version "6.20.0" + id "com.diffplug.spotless" version "6.25.0" } def boolean isGpu = ['cuda', 'directml'].contains(gradle.ext.targetDevice) @@ -23,7 +23,7 @@ repositories { dependencies { // Use JUnit Jupiter for testing. - testImplementation 'org.junit.jupiter:junit-jupiter:5.9.2' + testImplementation 'org.junit.jupiter:junit-jupiter:5.11.2' testRuntimeOnly 'org.junit.platform:junit-platform-launcher' diff --git a/crates/voicevox_core_java_api/lib/src/main/java/jp/hiroshiba/voicevoxcore/GlobalInfo.java b/crates/voicevox_core_java_api/lib/src/main/java/jp/hiroshiba/voicevoxcore/GlobalInfo.java index 26f9ccddd..496c2ccc4 100644 --- a/crates/voicevox_core_java_api/lib/src/main/java/jp/hiroshiba/voicevoxcore/GlobalInfo.java +++ b/crates/voicevox_core_java_api/lib/src/main/java/jp/hiroshiba/voicevoxcore/GlobalInfo.java @@ -1,6 +1,5 @@ package jp.hiroshiba.voicevoxcore; -import com.google.gson.Gson; import com.google.gson.annotations.Expose; import com.google.gson.annotations.SerializedName; import jakarta.annotation.Nonnull; @@ -17,33 +16,18 @@ public static String getVersion() { return rsGetVersion(); } - /** - * このライブラリで利用可能なデバイスの情報を取得する。 - * - * @return {@link SupportedDevices}。 - */ - @Nonnull - public static SupportedDevices getSupportedDevices() { - Gson gson = new Gson(); - String supportedDevicesJson = rsGetSupportedDevicesJson(); - SupportedDevices supportedDevices = gson.fromJson(supportedDevicesJson, SupportedDevices.class); - if (supportedDevices == null) { - throw new NullPointerException("supported_devices"); - } - return supportedDevices; - } - @Nonnull private static native String rsGetVersion(); @Nonnull private static native String rsGetSupportedDevicesJson(); + // FIXME: `Onnxruntime`に移すか、独立させる /** - * このライブラリで利用可能なデバイスの情報。 + * ONNX Runtime利用可能なデバイスの情報。 * - *

あくまで本ライブラリが対応しているデバイスの情報であることに注意。GPUが使える環境ではなかったとしても {@link #cuda} や {@link #dml} は {@code - * true} を示しうる。 + *

あくまでONNX Runtimeが対応しているデバイスの情報であることに注意。GPUが使える環境ではなかったとしても {@link #cuda} や {@link #dml} は + * {@code true} を示しうる。 */ public static class SupportedDevices { /** diff --git a/crates/voicevox_core_java_api/lib/src/main/java/jp/hiroshiba/voicevoxcore/Onnxruntime.java b/crates/voicevox_core_java_api/lib/src/main/java/jp/hiroshiba/voicevoxcore/Onnxruntime.java new file mode 100644 index 000000000..9a9cbe133 --- /dev/null +++ b/crates/voicevox_core_java_api/lib/src/main/java/jp/hiroshiba/voicevoxcore/Onnxruntime.java @@ -0,0 +1,132 @@ +package jp.hiroshiba.voicevoxcore; + +import static jp.hiroshiba.voicevoxcore.GlobalInfo.SupportedDevices; + +import com.google.gson.Gson; +import jakarta.annotation.Nonnull; +import jakarta.annotation.Nullable; +import java.util.Optional; + +/** + * ONNX Runtime。 + * + *

シングルトンであり、インスタンスは高々一つ。 + * + *

+ * Onnxruntime ort1 = Onnxruntime.loadOnce().exec();
+ * Onnxruntime ort2 = Onnxruntime.get().get();
+ * assert ort1 == ort2;
+ * 
+ */ +public class Onnxruntime extends Dll { + /** ONNX Runtimeのライブラリ名。 */ + public static final String LIB_NAME = "onnxruntime"; + + /** 推奨されるONNX Runtimeのバージョン。 */ + public static final String LIB_VERSION = "1.17.3"; + + /** + * {@link LIB_NAME}と{@link LIB_VERSION}からなる動的ライブラリのファイル名。 + * + *

WindowsとAndroidでは{@link LIB_UNVERSIONED_FILENAME}と同じ。 + */ + public static final String LIB_VERSIONED_FILENAME = rsLibVersionedFilename(); + + /** {@link LIB_NAME}からなる動的ライブラリのファイル名。 */ + public static final String LIB_UNVERSIONED_FILENAME = rsLibUnversionedFilename(); + + @Nullable private static Onnxruntime instance = null; + + /** + * インスタンスが既に作られているならそれを得る。 + * + * @return インスタンスがあるなら{@code Optional.of(…)}、そうでなければ{@code Optional.empty()}。 + */ + public static Optional get() { + synchronized (Onnxruntime.class) { + return Optional.ofNullable(instance); + } + } + + /** + * ONNX Runtimeをロードして初期化する。 + * + *

一度成功したら、以後は引数を無視して同じインスタンスを返す。 + * + * @return {@link LoadOnce}。 + */ + public static LoadOnce loadOnce() { + return new LoadOnce(); + } + + private static native String rsLibName(); + + private static native String rsLibVersion(); + + private static native String rsLibVersionedFilename(); + + private static native String rsLibUnversionedFilename(); + + static { + assert LIB_NAME.equals(rsLibName()) && LIB_VERSION.equals(rsLibVersion()); + } + + /** {@link #loadOnce}のビルダー。 */ + public static class LoadOnce { + /** + * ONNX Runtimeのファイル名(モジュール名)もしくはファイルパスを指定する。 + * + * @param filename {@code dlopen}/{@code + * LoadLibraryExW}の引数に使われる。デフォルトは{@link LIB_VERSIONED_FILENAME}。 + * @return このオブジェクト。 + */ + public LoadOnce filename(@Nonnull String filename) { + this.filename = filename; + return this; + } + + /** + * 実行する。 + * + * @return {@link Onnxruntime}。 + */ + public Onnxruntime exec() { + synchronized (Onnxruntime.class) { + if (instance == null) { + instance = new Onnxruntime(filename); + } + } + return instance; + } + + private LoadOnce() {} + + @Nonnull private String filename = LIB_VERSIONED_FILENAME; + } + + private long handle; + + private Onnxruntime(@Nullable String filename) { + rsNew(filename); + } + + /** + * このライブラリで利用可能なデバイスの情報を取得する。 + * + * @return {@link SupportedDevices}。 + */ + public SupportedDevices supportedDevices() { + Gson gson = new Gson(); + String supportedDevicesJson = rsSupportedDevices(); + SupportedDevices supportedDevices = gson.fromJson(supportedDevicesJson, SupportedDevices.class); + if (supportedDevices == null) { + throw new NullPointerException("supported_devices"); + } + return supportedDevices; + } + + private native void rsNew(@Nullable String filename); + + private native String rsSupportedDevices(); +} diff --git a/crates/voicevox_core_java_api/lib/src/main/java/jp/hiroshiba/voicevoxcore/Synthesizer.java b/crates/voicevox_core_java_api/lib/src/main/java/jp/hiroshiba/voicevoxcore/Synthesizer.java index a3fe0de6c..c59f8ca1e 100644 --- a/crates/voicevox_core_java_api/lib/src/main/java/jp/hiroshiba/voicevoxcore/Synthesizer.java +++ b/crates/voicevox_core_java_api/lib/src/main/java/jp/hiroshiba/voicevoxcore/Synthesizer.java @@ -5,8 +5,10 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.List; -import jp.hiroshiba.voicevoxcore.exceptions.InferenceFailedException; +import java.util.Optional; +import java.util.UUID; import jp.hiroshiba.voicevoxcore.exceptions.InvalidModelDataException; +import jp.hiroshiba.voicevoxcore.exceptions.RunModelException; /** * 音声シンセサイザ。 @@ -16,8 +18,8 @@ public class Synthesizer extends Dll { private long handle; - private Synthesizer(OpenJtalk openJtalk, Builder builder) { - rsNew(openJtalk, builder); + private Synthesizer(Onnxruntime onnxruntime, OpenJtalk openJtalk, Builder builder) { + rsNew(onnxruntime, openJtalk, builder); } protected void finalize() throws Throwable { @@ -25,6 +27,18 @@ protected void finalize() throws Throwable { super.finalize(); } + /** + * ONNX Runtime。 + * + * @return {@link Onnxruntime}。 + */ + @Nonnull + public Onnxruntime getOnnxruntime() { + Optional onnxruntime = Onnxruntime.get(); + assert onnxruntime.isPresent() : "`Synthesizer`のコンストラクタで要求しているはず"; + return onnxruntime.get(); + } + /** * ハードウェアアクセラレーションがGPUモードかどうかを返す。 * @@ -40,10 +54,11 @@ public boolean isGpuMode() { * @return メタ情報。 */ @Nonnull - public VoiceModel.SpeakerMeta[] metas() { + public VoiceModelFile.SpeakerMeta[] metas() { Gson gson = new Gson(); String metasJson = rsGetMetasJson(); - VoiceModel.SpeakerMeta[] rawMetas = gson.fromJson(metasJson, VoiceModel.SpeakerMeta[].class); + VoiceModelFile.SpeakerMeta[] rawMetas = + gson.fromJson(metasJson, VoiceModelFile.SpeakerMeta[].class); if (rawMetas == null) { throw new NullPointerException("metas"); } @@ -56,7 +71,7 @@ public VoiceModel.SpeakerMeta[] metas() { * @param voiceModel 読み込むモデル。 * @throws InvalidModelDataException 無効なモデルデータの場合。 */ - public void loadVoiceModel(VoiceModel voiceModel) throws InvalidModelDataException { + public void loadVoiceModel(VoiceModelFile voiceModel) throws InvalidModelDataException { rsLoadVoiceModel(voiceModel); } @@ -65,7 +80,7 @@ public void loadVoiceModel(VoiceModel voiceModel) throws InvalidModelDataExcepti * * @param voiceModelId 読み込みを解除する音声モデルのID。 */ - public void unloadVoiceModel(String voiceModelId) { + public void unloadVoiceModel(UUID voiceModelId) { rsUnloadVoiceModel(voiceModelId); } @@ -75,7 +90,7 @@ public void unloadVoiceModel(String voiceModelId) { * @param voiceModelId 音声モデルのID。 * @return 指定した音声モデルのIDが読み込まれているかどうか。 */ - public boolean isLoadedVoiceModel(String voiceModelId) { + public boolean isLoadedVoiceModel(UUID voiceModelId) { return rsIsLoadedVoiceModel(voiceModelId); } @@ -85,11 +100,10 @@ public boolean isLoadedVoiceModel(String voiceModelId) { * @param kana AquesTalk風記法。 * @param styleId スタイルID。 * @return {@link AudioQuery}。 - * @throws InferenceFailedException 推論に失敗した場合。 + * @throws RunModelException 推論に失敗した場合。 */ @Nonnull - public AudioQuery createAudioQueryFromKana(String kana, int styleId) - throws InferenceFailedException { + public AudioQuery createAudioQueryFromKana(String kana, int styleId) throws RunModelException { if (!Utils.isU32(styleId)) { throw new IllegalArgumentException("styleId"); } @@ -109,10 +123,10 @@ public AudioQuery createAudioQueryFromKana(String kana, int styleId) * @param text 日本語のテキスト。 * @param styleId スタイルID。 * @return {@link AudioQuery}。 - * @throws InferenceFailedException 推論に失敗した場合。 + * @throws RunModelException 推論に失敗した場合。 */ @Nonnull - public AudioQuery createAudioQuery(String text, int styleId) throws InferenceFailedException { + public AudioQuery createAudioQuery(String text, int styleId) throws RunModelException { if (!Utils.isU32(styleId)) { throw new IllegalArgumentException("styleId"); } @@ -132,11 +146,11 @@ public AudioQuery createAudioQuery(String text, int styleId) throws InferenceFai * @param kana AquesTalk風記法。 * @param styleId スタイルID。 * @return {@link AccentPhrase} のリスト。 - * @throws InferenceFailedException 推論に失敗した場合。 + * @throws RunModelException 推論に失敗した場合。 */ @Nonnull public List createAccentPhrasesFromKana(String kana, int styleId) - throws InferenceFailedException { + throws RunModelException { String accentPhrasesJson = rsAccentPhrasesFromKana(kana, styleId); Gson gson = new Gson(); AccentPhrase[] rawAccentPhrases = gson.fromJson(accentPhrasesJson, AccentPhrase[].class); @@ -152,11 +166,10 @@ public List createAccentPhrasesFromKana(String kana, int styleId) * @param text 日本語のテキスト。 * @param styleId スタイルID。 * @return {@link AccentPhrase} のリスト。 - * @throws InferenceFailedException 推論に失敗した場合。 + * @throws RunModelException 推論に失敗した場合。 */ @Nonnull - public List createAccentPhrases(String text, int styleId) - throws InferenceFailedException { + public List createAccentPhrases(String text, int styleId) throws RunModelException { String accentPhrasesJson = rsAccentPhrases(text, styleId); Gson gson = new Gson(); AccentPhrase[] rawAccentPhrases = gson.fromJson(accentPhrasesJson, AccentPhrase[].class); @@ -172,11 +185,11 @@ public List createAccentPhrases(String text, int styleId) * @param accentPhrases 変更元のアクセント句の配列。 * @param styleId スタイルID。 * @return 変更後のアクセント句の配列。 - * @throws InferenceFailedException 推論に失敗した場合。 + * @throws RunModelException 推論に失敗した場合。 */ @Nonnull public List replaceMoraData(List accentPhrases, int styleId) - throws InferenceFailedException { + throws RunModelException { if (!Utils.isU32(styleId)) { throw new IllegalArgumentException("styleId"); } @@ -192,11 +205,11 @@ public List replaceMoraData(List accentPhrases, int * @param accentPhrases 変更元のアクセント句の配列。 * @param styleId スタイルID。 * @return 変更後のアクセント句の配列。 - * @throws InferenceFailedException 推論に失敗した場合。 + * @throws RunModelException 推論に失敗した場合。 */ @Nonnull public List replacePhonemeLength(List accentPhrases, int styleId) - throws InferenceFailedException { + throws RunModelException { if (!Utils.isU32(styleId)) { throw new IllegalArgumentException("styleId"); } @@ -212,11 +225,11 @@ public List replacePhonemeLength(List accentPhrases, * @param accentPhrases 変更元のアクセント句の配列。 * @param styleId スタイルID。 * @return 変更後のアクセント句の配列。 - * @throws InferenceFailedException 推論に失敗した場合。 + * @throws RunModelException 推論に失敗した場合。 */ @Nonnull public List replaceMoraPitch(List accentPhrases, int styleId) - throws InferenceFailedException { + throws RunModelException { if (!Utils.isU32(styleId)) { throw new IllegalArgumentException("styleId"); } @@ -265,62 +278,59 @@ public TtsConfigurator tts(String text, int styleId) { return new TtsConfigurator(this, text, styleId); } - private native void rsNew(OpenJtalk openJtalk, Builder builder); + private native void rsNew(Onnxruntime onnxruntime, OpenJtalk openJtalk, Builder builder); private native boolean rsIsGpuMode(); @Nonnull private native String rsGetMetasJson(); - private native void rsLoadVoiceModel(VoiceModel voiceModel) throws InvalidModelDataException; + private native void rsLoadVoiceModel(VoiceModelFile voiceModel) throws InvalidModelDataException; - private native void rsUnloadVoiceModel(String voiceModelId); + private native void rsUnloadVoiceModel(UUID voiceModelId); - private native boolean rsIsLoadedVoiceModel(String voiceModelId); + private native boolean rsIsLoadedVoiceModel(UUID voiceModelId); @Nonnull - private native String rsAudioQueryFromKana(String kana, int styleId) - throws InferenceFailedException; + private native String rsAudioQueryFromKana(String kana, int styleId) throws RunModelException; @Nonnull - private native String rsAudioQuery(String text, int styleId) throws InferenceFailedException; + private native String rsAudioQuery(String text, int styleId) throws RunModelException; @Nonnull - private native String rsAccentPhrasesFromKana(String kana, int styleId) - throws InferenceFailedException; + private native String rsAccentPhrasesFromKana(String kana, int styleId) throws RunModelException; @Nonnull - private native String rsAccentPhrases(String text, int styleId) throws InferenceFailedException; + private native String rsAccentPhrases(String text, int styleId) throws RunModelException; @Nonnull private native String rsReplaceMoraData(String accentPhrasesJson, int styleId, boolean kana) - throws InferenceFailedException; + throws RunModelException; @Nonnull private native String rsReplacePhonemeLength(String accentPhrasesJson, int styleId, boolean kana) - throws InferenceFailedException; + throws RunModelException; @Nonnull private native String rsReplaceMoraPitch(String accentPhrasesJson, int styleId, boolean kana) - throws InferenceFailedException; + throws RunModelException; @Nonnull private native byte[] rsSynthesis( - String queryJson, int styleId, boolean enableInterrogativeUpspeak) - throws InferenceFailedException; + String queryJson, int styleId, boolean enableInterrogativeUpspeak) throws RunModelException; @Nonnull private native byte[] rsTtsFromKana(String kana, int styleId, boolean enableInterrogativeUpspeak) - throws InferenceFailedException; + throws RunModelException; @Nonnull private native byte[] rsTts(String text, int styleId, boolean enableInterrogativeUpspeak) - throws InferenceFailedException; + throws RunModelException; private native void rsDrop(); - public static Builder builder(OpenJtalk openJtalk) { - return new Builder(openJtalk); + public static Builder builder(Onnxruntime onnxruntime, OpenJtalk openJtalk) { + return new Builder(onnxruntime, openJtalk); } /** @@ -329,6 +339,7 @@ public static Builder builder(OpenJtalk openJtalk) { * @see Synthesizer#builder */ public static class Builder { + private Onnxruntime onnxruntime; private OpenJtalk openJtalk; @SuppressWarnings("unused") @@ -337,7 +348,8 @@ public static class Builder { @SuppressWarnings("unused") private int cpuNumThreads; - public Builder(OpenJtalk openJtalk) { + public Builder(Onnxruntime onnxruntime, OpenJtalk openJtalk) { + this.onnxruntime = onnxruntime; this.openJtalk = openJtalk; } @@ -372,7 +384,7 @@ public Builder cpuNumThreads(int cpuNumThreads) { * @return {@link Synthesizer}。 */ public Synthesizer build() { - Synthesizer synthesizer = new Synthesizer(openJtalk, this); + Synthesizer synthesizer = new Synthesizer(onnxruntime, openJtalk, this); return synthesizer; } } @@ -420,10 +432,10 @@ public SynthesisConfigurator interrogativeUpspeak(boolean interrogativeUpspeak) * {@link AudioQuery} から音声合成する。 * * @return 音声データ。 - * @throws InferenceFailedException 推論に失敗した場合。 + * @throws RunModelException 推論に失敗した場合。 */ @Nonnull - public byte[] execute() throws InferenceFailedException { + public byte[] execute() throws RunModelException { if (!Utils.isU32(styleId)) { throw new IllegalArgumentException("styleId"); } @@ -465,10 +477,10 @@ public TtsFromKanaConfigurator interrogativeUpspeak(boolean interrogativeUpspeak * {@link AudioQuery} から音声合成する。 * * @return 音声データ。 - * @throws InferenceFailedException 推論に失敗した場合。 + * @throws RunModelException 推論に失敗した場合。 */ @Nonnull - public byte[] execute() throws InferenceFailedException { + public byte[] execute() throws RunModelException { if (!Utils.isU32(styleId)) { throw new IllegalArgumentException("styleId"); } @@ -508,10 +520,10 @@ public TtsConfigurator interrogativeUpspeak(boolean interrogativeUpspeak) { * {@link AudioQuery} から音声合成する。 * * @return 音声データ。 - * @throws InferenceFailedException 推論に失敗した場合。 + * @throws RunModelException 推論に失敗した場合。 */ @Nonnull - public byte[] execute() throws InferenceFailedException { + public byte[] execute() throws RunModelException { if (!Utils.isU32(styleId)) { throw new IllegalArgumentException("styleId"); } diff --git a/crates/voicevox_core_java_api/lib/src/main/java/jp/hiroshiba/voicevoxcore/UserDict.java b/crates/voicevox_core_java_api/lib/src/main/java/jp/hiroshiba/voicevoxcore/UserDict.java index 21f6843dd..7135365ff 100644 --- a/crates/voicevox_core_java_api/lib/src/main/java/jp/hiroshiba/voicevoxcore/UserDict.java +++ b/crates/voicevox_core_java_api/lib/src/main/java/jp/hiroshiba/voicevoxcore/UserDict.java @@ -7,6 +7,8 @@ import jakarta.annotation.Nonnull; import jakarta.validation.constraints.Max; import jakarta.validation.constraints.Min; +import java.io.File; +import java.nio.file.Path; import java.util.HashMap; import jp.hiroshiba.voicevoxcore.exceptions.LoadUserDictException; import jp.hiroshiba.voicevoxcore.exceptions.SaveUserDictException; @@ -70,6 +72,26 @@ public void importDict(UserDict dict) { rsImportDict(dict); } + /** + * ユーザー辞書を読み込む。 + * + * @param path ユーザー辞書のパス。 + * @throws LoadUserDictException ユーザー辞書を読み込めなかった場合。 + */ + public void load(Path path) throws LoadUserDictException { + load(path.toString()); + } + + /** + * ユーザー辞書を読み込む。 + * + * @param path ユーザー辞書のパス。 + * @throws LoadUserDictException ユーザー辞書を読み込めなかった場合。 + */ + public void load(File path) throws LoadUserDictException { + load(path.toString()); + } + /** * ユーザー辞書を読み込む。 * @@ -80,6 +102,26 @@ public void load(String path) throws LoadUserDictException { rsLoad(path); } + /** + * ユーザー辞書を保存する。 + * + * @param path ユーザー辞書のパス。 + * @throws SaveUserDictException ユーザー辞書を保存できなかった場合。 + */ + public void save(Path path) throws SaveUserDictException { + rsSave(path.toString()); + } + + /** + * ユーザー辞書を保存する。 + * + * @param path ユーザー辞書のパス。 + * @throws SaveUserDictException ユーザー辞書を保存できなかった場合。 + */ + public void save(File path) throws SaveUserDictException { + rsSave(path.toString()); + } + /** * ユーザー辞書を保存する。 * 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/VoiceModelFile.java similarity index 82% rename from crates/voicevox_core_java_api/lib/src/main/java/jp/hiroshiba/voicevoxcore/VoiceModel.java rename to crates/voicevox_core_java_api/lib/src/main/java/jp/hiroshiba/voicevoxcore/VoiceModelFile.java index 576629515..b2cceca3f 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/VoiceModelFile.java @@ -5,19 +5,21 @@ import com.google.gson.annotations.SerializedName; import jakarta.annotation.Nonnull; import jakarta.annotation.Nullable; +import java.io.Closeable; +import java.util.UUID; -/** 音声モデル。 */ -public class VoiceModel extends Dll { +/** 音声モデルファイル。 */ +public class VoiceModelFile extends Dll implements Closeable { private long handle; /** ID。 */ - @Nonnull public final String id; + @Nonnull public final UUID id; /** メタ情報。 */ @Nonnull public final SpeakerMeta[] metas; - public VoiceModel(String modelPath) { - rsFromPath(modelPath); + public VoiceModelFile(String modelPath) { + rsOpen(modelPath); id = rsGetId(); String metasJson = rsGetMetasJson(); Gson gson = new Gson(); @@ -28,19 +30,32 @@ public VoiceModel(String modelPath) { metas = rawMetas; } + /** + * VVMファイルを閉じる。 + * + *

このメソッドが呼ばれた段階で{@link Synthesizer#loadVoiceModel}からのアクセスが継続中の場合、アクセスが終わるまで待つ。 + */ + @Override + public void close() { + rsClose(); + } + + @Override protected void finalize() throws Throwable { rsDrop(); super.finalize(); } - private native void rsFromPath(String modelPath); + private native void rsOpen(String modelPath); @Nonnull - private native String rsGetId(); + private native UUID rsGetId(); @Nonnull private native String rsGetMetasJson(); + private native void rsClose(); + private native void rsDrop(); /** 話者(speaker)のメタ情報。 */ diff --git a/crates/voicevox_core_java_api/lib/src/main/java/jp/hiroshiba/voicevoxcore/exceptions/InferenceFailedException.java b/crates/voicevox_core_java_api/lib/src/main/java/jp/hiroshiba/voicevoxcore/exceptions/InferenceFailedException.java deleted file mode 100644 index 499a530df..000000000 --- a/crates/voicevox_core_java_api/lib/src/main/java/jp/hiroshiba/voicevoxcore/exceptions/InferenceFailedException.java +++ /dev/null @@ -1,14 +0,0 @@ -package jp.hiroshiba.voicevoxcore.exceptions; - -import java.io.IOException; - -/** 推論に失敗した。 */ -public class InferenceFailedException extends IOException { - public InferenceFailedException(String message) { - super(message); - } - - public InferenceFailedException(String message, Throwable cause) { - super(message, cause); - } -} diff --git a/crates/voicevox_core_java_api/lib/src/main/java/jp/hiroshiba/voicevoxcore/exceptions/InitInferenceRuntimeException.java b/crates/voicevox_core_java_api/lib/src/main/java/jp/hiroshiba/voicevoxcore/exceptions/InitInferenceRuntimeException.java new file mode 100644 index 000000000..c981ea034 --- /dev/null +++ b/crates/voicevox_core_java_api/lib/src/main/java/jp/hiroshiba/voicevoxcore/exceptions/InitInferenceRuntimeException.java @@ -0,0 +1,14 @@ +package jp.hiroshiba.voicevoxcore.exceptions; + +import java.io.IOException; + +/** 推論ライブラリのロードまたは初期化ができなかった。 */ +public class InitInferenceRuntimeException extends IOException { + public InitInferenceRuntimeException(String message) { + super(message); + } + + public InitInferenceRuntimeException(String message, Throwable cause) { + super(message, cause); + } +} diff --git a/crates/voicevox_core_java_api/lib/src/main/java/jp/hiroshiba/voicevoxcore/exceptions/RunModelException.java b/crates/voicevox_core_java_api/lib/src/main/java/jp/hiroshiba/voicevoxcore/exceptions/RunModelException.java new file mode 100644 index 000000000..67d7f061f --- /dev/null +++ b/crates/voicevox_core_java_api/lib/src/main/java/jp/hiroshiba/voicevoxcore/exceptions/RunModelException.java @@ -0,0 +1,14 @@ +package jp.hiroshiba.voicevoxcore.exceptions; + +import java.io.IOException; + +/** 推論に失敗した。 */ +public class RunModelException extends IOException { + public RunModelException(String message) { + super(message); + } + + public RunModelException(String message, Throwable cause) { + super(message, cause); + } +} diff --git a/crates/voicevox_core_java_api/lib/src/test/java/jp/hiroshiba/voicevoxcore/InfoTest.java b/crates/voicevox_core_java_api/lib/src/test/java/jp/hiroshiba/voicevoxcore/InfoTest.java index 52915abad..c9e71ed1c 100644 --- a/crates/voicevox_core_java_api/lib/src/test/java/jp/hiroshiba/voicevoxcore/InfoTest.java +++ b/crates/voicevox_core_java_api/lib/src/test/java/jp/hiroshiba/voicevoxcore/InfoTest.java @@ -8,15 +8,16 @@ import org.junit.jupiter.api.Test; -class InfoTest { +class InfoTest extends TestUtils { @Test void checkVersion() { assertNotNull(GlobalInfo.getVersion()); } + // TODO: 別の場所に移す @Test void checkSupportedDevices() { - GlobalInfo.SupportedDevices supportedDevices = GlobalInfo.getSupportedDevices(); + GlobalInfo.SupportedDevices supportedDevices = loadOnnxruntime().supportedDevices(); assertNotNull(supportedDevices); assertTrue(supportedDevices.cpu); 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..ece3a87ff 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,8 +13,9 @@ 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"); - VoiceModel model = new VoiceModel(path.getAbsolutePath()); - assertNotNull(model.metas); + File path = new File(cwd + "/../../test_util/data/model/sample.vvm"); + try (VoiceModelFile model = new VoiceModelFile(path.getAbsolutePath())) { + assertNotNull(model.metas); + } } } diff --git a/crates/voicevox_core_java_api/lib/src/test/java/jp/hiroshiba/voicevoxcore/SynthesizerTest.java b/crates/voicevox_core_java_api/lib/src/test/java/jp/hiroshiba/voicevoxcore/SynthesizerTest.java index 1eb8fe057..4c7d16f56 100644 --- a/crates/voicevox_core_java_api/lib/src/test/java/jp/hiroshiba/voicevoxcore/SynthesizerTest.java +++ b/crates/voicevox_core_java_api/lib/src/test/java/jp/hiroshiba/voicevoxcore/SynthesizerTest.java @@ -8,8 +8,8 @@ import static org.junit.jupiter.api.Assertions.assertTrue; import java.util.List; -import jp.hiroshiba.voicevoxcore.exceptions.InferenceFailedException; import jp.hiroshiba.voicevoxcore.exceptions.InvalidModelDataException; +import jp.hiroshiba.voicevoxcore.exceptions.RunModelException; import org.junit.jupiter.api.Test; class SynthesizerTest extends TestUtils { @@ -20,9 +20,12 @@ interface MoraCheckCallback { @Test void checkIsGpuMode() { + Onnxruntime onnxruntime = loadOnnxruntime(); OpenJtalk openJtalk = loadOpenJtalk(); Synthesizer synthesizer = - Synthesizer.builder(openJtalk).accelerationMode(Synthesizer.AccelerationMode.CPU).build(); + Synthesizer.builder(onnxruntime, openJtalk) + .accelerationMode(Synthesizer.AccelerationMode.CPU) + .build(); assertFalse(synthesizer.isGpuMode()); } @@ -45,56 +48,63 @@ boolean checkAllMoras( @Test void checkModel() throws InvalidModelDataException { - VoiceModel model = loadModel(); + Onnxruntime onnxruntime = loadOnnxruntime(); OpenJtalk openJtalk = loadOpenJtalk(); - Synthesizer synthesizer = Synthesizer.builder(openJtalk).build(); + Synthesizer synthesizer = Synthesizer.builder(onnxruntime, openJtalk).build(); assertTrue(synthesizer.metas().length == 0); - synthesizer.loadVoiceModel(model); + try (VoiceModelFile model = openModel()) { + synthesizer.loadVoiceModel(model); - assertTrue(synthesizer.metas().length >= 1); - assertTrue(synthesizer.isLoadedVoiceModel(model.id)); + assertTrue(synthesizer.metas().length >= 1); + assertTrue(synthesizer.isLoadedVoiceModel(model.id)); - synthesizer.unloadVoiceModel(model.id); + synthesizer.unloadVoiceModel(model.id); - assertTrue(synthesizer.metas().length == 0); - assertFalse(synthesizer.isLoadedVoiceModel(model.id)); + assertTrue(synthesizer.metas().length == 0); + assertFalse(synthesizer.isLoadedVoiceModel(model.id)); + } } @Test - void checkAudioQuery() throws InferenceFailedException, InvalidModelDataException { - VoiceModel model = loadModel(); + void checkAudioQuery() throws RunModelException, InvalidModelDataException { + Onnxruntime onnxruntime = loadOnnxruntime(); OpenJtalk openJtalk = loadOpenJtalk(); - Synthesizer synthesizer = Synthesizer.builder(openJtalk).build(); - synthesizer.loadVoiceModel(model); - AudioQuery query = synthesizer.createAudioQuery("こんにちは", model.metas[0].styles[0].id); + Synthesizer synthesizer = Synthesizer.builder(onnxruntime, openJtalk).build(); + + try (VoiceModelFile model = openModel()) { + synthesizer.loadVoiceModel(model); + } - synthesizer.synthesis(query, model.metas[0].styles[0].id).execute(); + AudioQuery query = synthesizer.createAudioQuery("こんにちは", synthesizer.metas()[0].styles[0].id); + synthesizer.synthesis(query, synthesizer.metas()[0].styles[0].id).execute(); } @Test - void checkAccentPhrases() throws InferenceFailedException, InvalidModelDataException { - VoiceModel model = loadModel(); + void checkAccentPhrases() throws RunModelException, InvalidModelDataException { OpenJtalk openJtalk = loadOpenJtalk(); - Synthesizer synthesizer = Synthesizer.builder(openJtalk).build(); - synthesizer.loadVoiceModel(model); + Onnxruntime onnxruntime = loadOnnxruntime(); + Synthesizer synthesizer = Synthesizer.builder(onnxruntime, openJtalk).build(); + try (VoiceModelFile model = openModel()) { + synthesizer.loadVoiceModel(model); + } List accentPhrases = - synthesizer.createAccentPhrases("こんにちは", model.metas[0].styles[0].id); + synthesizer.createAccentPhrases("こんにちは", synthesizer.metas()[0].styles[0].id); List accentPhrases2 = - synthesizer.replaceMoraPitch(accentPhrases, model.metas[1].styles[0].id); + synthesizer.replaceMoraPitch(accentPhrases, synthesizer.metas()[1].styles[0].id); assertTrue( checkAllMoras( accentPhrases, accentPhrases2, (mora, otherMora) -> mora.pitch != otherMora.pitch)); List accentPhrases3 = - synthesizer.replacePhonemeLength(accentPhrases, model.metas[1].styles[0].id); + synthesizer.replacePhonemeLength(accentPhrases, synthesizer.metas()[1].styles[0].id); assertTrue( checkAllMoras( accentPhrases, accentPhrases3, (mora, otherMora) -> mora.vowelLength != otherMora.vowelLength)); List accentPhrases4 = - synthesizer.replaceMoraData(accentPhrases, model.metas[1].styles[0].id); + synthesizer.replaceMoraData(accentPhrases, synthesizer.metas()[1].styles[0].id); assertTrue( checkAllMoras( accentPhrases, @@ -104,11 +114,13 @@ void checkAccentPhrases() throws InferenceFailedException, InvalidModelDataExcep } @Test - void checkTts() throws InferenceFailedException, InvalidModelDataException { - VoiceModel model = loadModel(); + void checkTts() throws RunModelException, InvalidModelDataException { + Onnxruntime onnxruntime = loadOnnxruntime(); OpenJtalk openJtalk = loadOpenJtalk(); - Synthesizer synthesizer = Synthesizer.builder(openJtalk).build(); - synthesizer.loadVoiceModel(model); - synthesizer.tts("こんにちは", model.metas[0].styles[0].id); + Synthesizer synthesizer = Synthesizer.builder(onnxruntime, openJtalk).build(); + try (VoiceModelFile model = openModel()) { + synthesizer.loadVoiceModel(model); + } + synthesizer.tts("こんにちは", synthesizer.metas()[0].styles[0].id); } } 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..f505c327f 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 @@ -3,13 +3,23 @@ import java.io.File; class TestUtils { - VoiceModel loadModel() { + VoiceModelFile openModel() { // 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()); + return new VoiceModelFile(path.getCanonicalPath()); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + Onnxruntime loadOnnxruntime() { + final String FILENAME = "../../test_util/data/lib/" + Onnxruntime.LIB_VERSIONED_FILENAME; + + try { + return Onnxruntime.loadOnce().filename(FILENAME).exec(); } catch (Exception e) { throw new RuntimeException(e); } diff --git a/crates/voicevox_core_java_api/lib/src/test/java/jp/hiroshiba/voicevoxcore/UserDictTest.java b/crates/voicevox_core_java_api/lib/src/test/java/jp/hiroshiba/voicevoxcore/UserDictTest.java index ce9b7631a..ed9a94e8e 100644 --- a/crates/voicevox_core_java_api/lib/src/test/java/jp/hiroshiba/voicevoxcore/UserDictTest.java +++ b/crates/voicevox_core_java_api/lib/src/test/java/jp/hiroshiba/voicevoxcore/UserDictTest.java @@ -4,9 +4,9 @@ import java.nio.file.Files; import java.nio.file.Path; -import jp.hiroshiba.voicevoxcore.exceptions.InferenceFailedException; import jp.hiroshiba.voicevoxcore.exceptions.InvalidModelDataException; import jp.hiroshiba.voicevoxcore.exceptions.LoadUserDictException; +import jp.hiroshiba.voicevoxcore.exceptions.RunModelException; import org.junit.jupiter.api.Test; class UserDictTest extends TestUtils { @@ -14,22 +14,25 @@ class UserDictTest extends TestUtils { // 辞書ロードのテスト。 // 辞書ロード前後でkanaが異なることを確認する @Test - void checkLoad() - throws InferenceFailedException, InvalidModelDataException, LoadUserDictException { - VoiceModel model = loadModel(); + void checkLoad() throws RunModelException, InvalidModelDataException, LoadUserDictException { + Onnxruntime onnxruntime = loadOnnxruntime(); OpenJtalk openJtalk = loadOpenJtalk(); - Synthesizer synthesizer = Synthesizer.builder(openJtalk).build(); + Synthesizer synthesizer = Synthesizer.builder(onnxruntime, openJtalk).build(); UserDict userDict = new UserDict(); - synthesizer.loadVoiceModel(model); + try (VoiceModelFile model = openModel()) { + synthesizer.loadVoiceModel(model); + } AudioQuery query1 = synthesizer.createAudioQuery( - "this_word_should_not_exist_in_default_dictionary", model.metas[0].styles[0].id); + "this_word_should_not_exist_in_default_dictionary", + synthesizer.metas()[0].styles[0].id); userDict.addWord(new UserDict.Word("this_word_should_not_exist_in_default_dictionary", "テスト")); openJtalk.useUserDict(userDict); AudioQuery query2 = synthesizer.createAudioQuery( - "this_word_should_not_exist_in_default_dictionary", model.metas[0].styles[0].id); + "this_word_should_not_exist_in_default_dictionary", + synthesizer.metas()[0].styles[0].id); assertTrue(query1.kana != query2.kana); } diff --git a/crates/voicevox_core_java_api/lib/src/test/java/jp/hiroshiba/voicevoxcore/VoiceModelTest.java b/crates/voicevox_core_java_api/lib/src/test/java/jp/hiroshiba/voicevoxcore/VoiceModelTest.java new file mode 100644 index 000000000..2bdba9c28 --- /dev/null +++ b/crates/voicevox_core_java_api/lib/src/test/java/jp/hiroshiba/voicevoxcore/VoiceModelTest.java @@ -0,0 +1,39 @@ +package jp.hiroshiba.voicevoxcore; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import com.google.gson.Gson; +import com.google.gson.annotations.Expose; +import com.google.gson.annotations.SerializedName; +import jakarta.annotation.Nonnull; +import java.io.File; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.UUID; +import org.junit.jupiter.api.Test; + +class VoiceModelTest extends TestUtils { + @Test + void idShouldBePreservedAsIs() throws IOException { + UUID expected = UUID.fromString(Manifest.readJson().id); + UUID actual; + try (VoiceModelFile model = openModel()) { + actual = model.id; + } + assertEquals(expected, actual); + } + + private static class Manifest { + @SerializedName("id") + @Expose + @Nonnull + String id; + + static Manifest readJson() throws IOException { + Path path = new File("../../../model/sample.vvm/manifest.json").toPath(); + String json = new String(Files.readAllBytes(path)); + return new Gson().fromJson(json, Manifest.class); + } + } +} 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 bdb37f4ff..cb2a89a7f 100644 --- a/crates/voicevox_core_java_api/src/common.rs +++ b/crates/voicevox_core_java_api/src/common.rs @@ -1,7 +1,14 @@ -use std::{error::Error as _, iter}; +use std::{error::Error as _, iter, mem, ops::Deref}; use derive_more::From; -use jni::{objects::JThrowable, JNIEnv}; +use easy_ext::ext; +use jni::{ + objects::{JObject, JThrowable}, + JNIEnv, +}; +use tracing::{debug, warn}; +use uuid::Uuid; +use voicevox_core::__internal::interop::raii::MaybeClosed; #[macro_export] macro_rules! object { @@ -67,6 +74,7 @@ where let class = class!( NotLoadedOpenjtalkDict, GpuSupport, + InitInferenceRuntime, OpenZipFile, ReadZipEntry, InvalidModelFormat, @@ -76,7 +84,7 @@ where GetSupportedDevices, StyleNotFound, ModelNotFound, - InferenceFailed, + RunModel, ExtractFullContextLabel, ParseKana, LoadUserDict, @@ -148,6 +156,9 @@ where env.throw_new("java/lang/IllegalArgumentException", error.to_string()) ) } + JavaApiError::IllegalState(msg) => { + or_panic!(env.throw_new("java/lang/IllegalStateException", msg)) + } }; } fallback @@ -155,6 +166,8 @@ where } } +type JavaApiResult = Result; + #[derive(From, Debug)] pub(crate) enum JavaApiError { #[from] @@ -167,4 +180,115 @@ pub(crate) enum JavaApiError { Uuid(uuid::Error), DeJson(serde_json::Error), + + IllegalState(String), +} + +pub(crate) struct Closable(std::sync::RwLock>); + +impl Closable { + pub(crate) fn new(content: T) -> Self { + Self(MaybeClosed::Open(content).into()) + } + + pub(crate) fn read(&self) -> JavaApiResult + '_> { + let lock = self.0.try_read().map_err(|e| match e { + std::sync::TryLockError::Poisoned(e) => panic!("{e}"), + std::sync::TryLockError::WouldBlock => { + JavaApiError::IllegalState(format!("The `{}` is being closed", T::JAVA_CLASS_IDENT)) + } + })?; + + voicevox_core::__internal::interop::raii::try_map_guard(lock, |lock| match &**lock { + MaybeClosed::Open(content) => Ok(content), + MaybeClosed::Closed => Err(JavaApiError::IllegalState(format!( + "The `{}` is closed", + T::JAVA_CLASS_IDENT, + ))), + }) + } + + pub(crate) fn close(&self) { + let lock = &mut *match self.0.try_write() { + Ok(lock) => lock, + Err(std::sync::TryLockError::Poisoned(e)) => panic!("{e}"), + Err(std::sync::TryLockError::WouldBlock) => { + self.0.write().unwrap_or_else(|e| panic!("{e}")) + } + }; + + if matches!(*lock, MaybeClosed::Open(_)) { + debug!("Closing a `{}`", T::JAVA_CLASS_IDENT); + } + drop(mem::replace(lock, MaybeClosed::Closed)); + } +} + +impl Drop for Closable { + fn drop(&mut self) { + let content = mem::replace( + self.0.get_mut().unwrap_or_else(|e| panic!("{e}")), + MaybeClosed::Closed, + ); + if let MaybeClosed::Open(content) = content { + warn!( + "デストラクタにより`{}`のクローズを行います。通常は、可能な限り`close`でクローズす\ + るようにして下さい", + T::JAVA_CLASS_IDENT, + ); + drop(content); + } + } +} + +pub(crate) trait HasJavaClassIdent { + const JAVA_CLASS_IDENT: &str; +} + +#[ext(JNIEnvExt)] +pub(crate) impl JNIEnv<'_> { + fn new_uuid(&mut self, uuid: Uuid) -> jni::errors::Result> { + let (msbs, lsbs) = split_uuid(uuid); + self.new_object("java/util/UUID", "(JJ)V", &[msbs.into(), lsbs.into()]) + } + + fn get_uuid(&mut self, obj: &JObject<'_>) -> jni::errors::Result { + let mut get_bits = |method_name| self.call_method(obj, method_name, "()J", &[])?.j(); + let msbs = get_bits("getMostSignificantBits")?; + let lsbs = get_bits("getLeastSignificantBits")?; + Ok(construct_uuid(msbs, lsbs)) + } +} + +fn split_uuid(uuid: Uuid) -> (i64, i64) { + let uuid = uuid.as_u128(); + let msbs = (uuid >> 64) as _; + let lsbs = uuid as _; + (msbs, lsbs) +} + +fn construct_uuid(msbs: i64, lsbs: i64) -> Uuid { + return Uuid::from_u128((to_u128(msbs) << 64) + to_u128(lsbs)); + + fn to_u128(bits: i64) -> u128 { + (bits as u64).into() + } +} + +#[cfg(test)] +mod tests { + use pretty_assertions::assert_eq; + use rstest::rstest; + use uuid::{uuid, Uuid}; + + #[rstest] + #[case(uuid!("a1a2a3a4-b1b2-c1c2-d1d2-e1e2e3e4e5e6"))] + #[case(uuid!("00000000-0000-0000-0000-000000000000"))] + #[case(uuid!("00000000-0000-0000-ffff-ffffffffffff"))] + #[case(uuid!("ffffffff-ffff-ffff-0000-000000000000"))] + #[case(uuid!("ffffffff-ffff-ffff-ffff-ffffffffffff"))] + fn uuid_conversion_works(#[case] uuid: Uuid) { + let (msbs, lsbs) = super::split_uuid(uuid); + assert_eq!(uuid, super::construct_uuid(msbs, lsbs)); + } } diff --git a/crates/voicevox_core_java_api/src/info.rs b/crates/voicevox_core_java_api/src/info.rs index 71b8db228..7328d4de8 100644 --- a/crates/voicevox_core_java_api/src/info.rs +++ b/crates/voicevox_core_java_api/src/info.rs @@ -9,14 +9,3 @@ extern "system" fn Java_jp_hiroshiba_voicevoxcore_GlobalInfo_rsGetVersion( Ok(version.into_raw()) }) } -#[no_mangle] -extern "system" fn Java_jp_hiroshiba_voicevoxcore_GlobalInfo_rsGetSupportedDevicesJson( - env: JNIEnv<'_>, -) -> jobject { - throw_if_err(env, std::ptr::null_mut(), |env| { - let supported_devices = voicevox_core::SupportedDevices::create()?; - let json = serde_json::to_string(&supported_devices).expect("Should not fail"); - let json = env.new_string(json)?; - Ok(json.into_raw()) - }) -} diff --git a/crates/voicevox_core_java_api/src/lib.rs b/crates/voicevox_core_java_api/src/lib.rs index 9615f0a94..4d61414e8 100644 --- a/crates/voicevox_core_java_api/src/lib.rs +++ b/crates/voicevox_core_java_api/src/lib.rs @@ -1,6 +1,7 @@ mod common; mod info; mod logger; +mod onnxruntime; 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 index 4800452ca..d7feb6d65 100644 --- a/crates/voicevox_core_java_api/src/logger.rs +++ b/crates/voicevox_core_java_api/src/logger.rs @@ -10,10 +10,11 @@ extern "system" fn Java_jp_hiroshiba_voicevoxcore_Dll_00024LoggerInitializer_ini 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(), - ), + 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を使う?) @@ -24,17 +25,17 @@ extern "system" fn Java_jp_hiroshiba_voicevoxcore_Dll_00024LoggerInitializer_ini }; use tracing_subscriber::{fmt::format::Writer, EnvFilter}; - // FIXME: `try_init` → `init` (subscriberは他に存在しないはずなので) - let _ = tracing_subscriber::fmt() + 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() + // 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(); + .init(); fn local_time(wtr: &mut Writer<'_>) -> fmt::Result { // ローカル時刻で表示はするが、そのフォーマットはtracing-subscriber本来のものに近いようにする。 diff --git a/crates/voicevox_core_java_api/src/onnxruntime.rs b/crates/voicevox_core_java_api/src/onnxruntime.rs new file mode 100644 index 000000000..004ff4d97 --- /dev/null +++ b/crates/voicevox_core_java_api/src/onnxruntime.rs @@ -0,0 +1,56 @@ +use std::ptr; + +use duplicate::duplicate_item; +use jni::{ + objects::{JObject, JString}, + sys::jobject, + JNIEnv, +}; + +use crate::common::throw_if_err; + +#[duplicate_item( + f CONST; + [ Java_jp_hiroshiba_voicevoxcore_Onnxruntime_rsLibName ] [ LIB_NAME ]; + [ Java_jp_hiroshiba_voicevoxcore_Onnxruntime_rsLibVersion ] [ LIB_VERSION ]; + [ Java_jp_hiroshiba_voicevoxcore_Onnxruntime_rsLibVersionedFilename ] [ LIB_VERSIONED_FILENAME ]; + [ Java_jp_hiroshiba_voicevoxcore_Onnxruntime_rsLibUnversionedFilename ] [ LIB_UNVERSIONED_FILENAME ]; +)] +#[no_mangle] +extern "system" fn f(env: JNIEnv<'_>) -> jobject { + throw_if_err(env, ptr::null_mut(), |env| { + let s = env.new_string(voicevox_core::blocking::Onnxruntime::CONST)?; + Ok(s.into_raw()) + }) +} + +#[no_mangle] +unsafe extern "system" fn Java_jp_hiroshiba_voicevoxcore_Onnxruntime_rsNew<'local>( + env: JNIEnv<'local>, + this: JObject<'local>, + filename: JString<'local>, +) { + throw_if_err(env, (), |env| { + let filename = String::from(env.get_string(&filename)?); + let internal = voicevox_core::blocking::Onnxruntime::load_once() + .filename(filename) + .exec()?; + env.set_rust_field(&this, "handle", internal)?; + Ok(()) + }) +} + +#[no_mangle] +unsafe extern "system" fn Java_jp_hiroshiba_voicevoxcore_Onnxruntime_rsSupportedDevices<'local>( + env: JNIEnv<'local>, + this: JObject<'local>, +) -> jobject { + throw_if_err(env, ptr::null_mut(), |env| { + let this = *env.get_rust_field::<_, _, &'static voicevox_core::blocking::Onnxruntime>( + &this, "handle", + )?; + let json = this.supported_devices()?.to_json().to_string(); + let json = env.new_string(json)?; + Ok(json.into_raw()) + }) +} diff --git a/crates/voicevox_core_java_api/src/synthesizer.rs b/crates/voicevox_core_java_api/src/synthesizer.rs index fee5bc132..32cdf1200 100644 --- a/crates/voicevox_core_java_api/src/synthesizer.rs +++ b/crates/voicevox_core_java_api/src/synthesizer.rs @@ -1,5 +1,5 @@ use crate::{ - common::{throw_if_err, JavaApiError}, + common::{throw_if_err, JNIEnvExt as _, JavaApiError}, enum_object, object, object_type, }; @@ -14,6 +14,7 @@ use std::sync::Arc; unsafe extern "system" fn Java_jp_hiroshiba_voicevoxcore_Synthesizer_rsNew<'local>( env: JNIEnv<'local>, this: JObject<'local>, + onnxruntime: JObject<'local>, open_jtalk: JObject<'local>, builder: JObject<'local>, ) { @@ -45,11 +46,18 @@ unsafe extern "system" fn Java_jp_hiroshiba_voicevoxcore_Synthesizer_rsNew<'loca let cpu_num_threads = env.get_field(&builder, "cpuNumThreads", "I")?; options.cpu_num_threads = cpu_num_threads.i().expect("cpuNumThreads is not integer") as u16; + let onnxruntime = *env + .get_rust_field::<_, _, &'static voicevox_core::blocking::Onnxruntime>( + &onnxruntime, + "handle", + )?; let open_jtalk = env .get_rust_field::<_, _, voicevox_core::blocking::OpenJtalk>(&open_jtalk, "handle")? .clone(); let internal = Arc::new(voicevox_core::blocking::Synthesizer::new( - open_jtalk, &options, + onnxruntime, + open_jtalk, + &options, )?); env.set_rust_field(&this, "handle", internal)?; Ok(()) @@ -99,8 +107,9 @@ unsafe extern "system" fn Java_jp_hiroshiba_voicevoxcore_Synthesizer_rsLoadVoice ) { throw_if_err(env, (), |env| { let model = env - .get_rust_field::<_, _, Arc>(&model, "handle")? + .get_rust_field::<_, _, crate::voice_model::VoiceModelFile>(&model, "handle")? .clone(); + let model = model.read()?; let internal = env .get_rust_field::<_, _, Arc>>( &this, "handle", @@ -115,10 +124,10 @@ unsafe extern "system" fn Java_jp_hiroshiba_voicevoxcore_Synthesizer_rsLoadVoice unsafe extern "system" fn Java_jp_hiroshiba_voicevoxcore_Synthesizer_rsUnloadVoiceModel<'local>( env: JNIEnv<'local>, this: JObject<'local>, - model_id: JString<'local>, + model_id: JObject<'local>, ) { throw_if_err(env, (), |env| { - let model_id: String = env.get_string(&model_id)?.into(); + let model_id = env.get_uuid(&model_id)?.into(); let internal = env .get_rust_field::<_, _, Arc>>( @@ -126,7 +135,7 @@ unsafe extern "system" fn Java_jp_hiroshiba_voicevoxcore_Synthesizer_rsUnloadVoi )? .clone(); - internal.unload_voice_model(&voicevox_core::VoiceModelId::new(model_id))?; + internal.unload_voice_model(model_id)?; Ok(()) }) @@ -138,10 +147,10 @@ unsafe extern "system" fn Java_jp_hiroshiba_voicevoxcore_Synthesizer_rsIsLoadedV >( env: JNIEnv<'local>, this: JObject<'local>, - model_id: JString<'local>, + model_id: JObject<'local>, ) -> jboolean { throw_if_err(env, false, |env| { - let model_id: String = env.get_string(&model_id)?.into(); + let model_id = env.get_uuid(&model_id)?.into(); let internal = env .get_rust_field::<_, _, Arc>>( @@ -149,7 +158,7 @@ unsafe extern "system" fn Java_jp_hiroshiba_voicevoxcore_Synthesizer_rsIsLoadedV )? .clone(); - let is_loaded = internal.is_loaded_voice_model(&voicevox_core::VoiceModelId::new(model_id)); + let is_loaded = internal.is_loaded_voice_model(model_id); Ok(is_loaded) }) @@ -280,7 +289,7 @@ unsafe extern "system" fn Java_jp_hiroshiba_voicevoxcore_Synthesizer_rsReplaceMo ) -> jobject { throw_if_err(env, std::ptr::null_mut(), |env| { let accent_phrases_json: String = env.get_string(&accent_phrases_json)?.into(); - let accent_phrases: Vec = + let accent_phrases: Vec = serde_json::from_str(&accent_phrases_json).map_err(JavaApiError::DeJson)?; let style_id = style_id as u32; @@ -311,7 +320,7 @@ unsafe extern "system" fn Java_jp_hiroshiba_voicevoxcore_Synthesizer_rsReplacePh ) -> jobject { throw_if_err(env, std::ptr::null_mut(), |env| { let accent_phrases_json: String = env.get_string(&accent_phrases_json)?.into(); - let accent_phrases: Vec = + let accent_phrases: Vec = serde_json::from_str(&accent_phrases_json).map_err(JavaApiError::DeJson)?; let style_id = style_id as u32; @@ -340,7 +349,7 @@ unsafe extern "system" fn Java_jp_hiroshiba_voicevoxcore_Synthesizer_rsReplaceMo ) -> jobject { throw_if_err(env, std::ptr::null_mut(), |env| { let accent_phrases_json: String = env.get_string(&accent_phrases_json)?.into(); - let accent_phrases: Vec = + let accent_phrases: Vec = serde_json::from_str(&accent_phrases_json).map_err(JavaApiError::DeJson)?; let style_id = style_id as u32; @@ -370,7 +379,7 @@ unsafe extern "system" fn Java_jp_hiroshiba_voicevoxcore_Synthesizer_rsSynthesis ) -> jobject { throw_if_err(env, std::ptr::null_mut(), |env| { let audio_query: String = env.get_string(&query_json)?.into(); - let audio_query: voicevox_core::AudioQueryModel = + let audio_query: voicevox_core::AudioQuery = serde_json::from_str(&audio_query).map_err(JavaApiError::DeJson)?; let style_id = style_id as u32; diff --git a/crates/voicevox_core_java_api/src/user_dict.rs b/crates/voicevox_core_java_api/src/user_dict.rs index df8b8270b..ceee1f42e 100644 --- a/crates/voicevox_core_java_api/src/user_dict.rs +++ b/crates/voicevox_core_java_api/src/user_dict.rs @@ -124,7 +124,7 @@ unsafe extern "system" fn Java_jp_hiroshiba_voicevoxcore_UserDict_rsLoad<'local> .clone(); let path = env.get_string(&path)?; - let path = &Cow::from(&path); + let path = &*Cow::from(&path); internal.load(path)?; @@ -144,7 +144,7 @@ unsafe extern "system" fn Java_jp_hiroshiba_voicevoxcore_UserDict_rsSave<'local> .clone(); let path = env.get_string(&path)?; - let path = &Cow::from(&path); + let path = &*Cow::from(&path); internal.save(path)?; diff --git a/crates/voicevox_core_java_api/src/voice_model.rs b/crates/voicevox_core_java_api/src/voice_model.rs index 42a20544a..ef24edbfe 100644 --- a/crates/voicevox_core_java_api/src/voice_model.rs +++ b/crates/voicevox_core_java_api/src/voice_model.rs @@ -1,14 +1,20 @@ use std::{borrow::Cow, sync::Arc}; -use crate::common::throw_if_err; +use crate::common::{throw_if_err, Closable, HasJavaClassIdent, JNIEnvExt as _}; use jni::{ objects::{JObject, JString}, sys::jobject, JNIEnv, }; +pub(crate) type VoiceModelFile = Arc>; + +impl HasJavaClassIdent for voicevox_core::blocking::VoiceModelFile { + const JAVA_CLASS_IDENT: &str = "VoiceModelFile"; +} + #[no_mangle] -unsafe extern "system" fn Java_jp_hiroshiba_voicevoxcore_VoiceModel_rsFromPath<'local>( +unsafe extern "system" fn Java_jp_hiroshiba_voicevoxcore_VoiceModelFile_rsOpen<'local>( env: JNIEnv<'local>, this: JObject<'local>, model_path: JString<'local>, @@ -17,41 +23,41 @@ unsafe extern "system" fn Java_jp_hiroshiba_voicevoxcore_VoiceModel_rsFromPath<' let model_path = env.get_string(&model_path)?; let model_path = &*Cow::from(&model_path); - let internal = voicevox_core::blocking::VoiceModel::from_path(model_path)?; - - env.set_rust_field(&this, "handle", Arc::new(internal))?; + let internal = voicevox_core::blocking::VoiceModelFile::open(model_path)?; + let internal = Arc::new(Closable::new(internal)); + env.set_rust_field(&this, "handle", internal)?; Ok(()) }) } #[no_mangle] -unsafe extern "system" fn Java_jp_hiroshiba_voicevoxcore_VoiceModel_rsGetId<'local>( +unsafe extern "system" fn Java_jp_hiroshiba_voicevoxcore_VoiceModelFile_rsGetId<'local>( env: JNIEnv<'local>, this: JObject<'local>, ) -> jobject { throw_if_err(env, std::ptr::null_mut(), |env| { let internal = env - .get_rust_field::<_, _, Arc>(&this, "handle")? + .get_rust_field::<_, _, VoiceModelFile>(&this, "handle")? .clone(); + let internal = internal.read()?; - let id = internal.id().raw_voice_model_id(); - - let id = env.new_string(id)?; + let id = env.new_uuid(internal.id().raw_voice_model_id())?; Ok(id.into_raw()) }) } #[no_mangle] -unsafe extern "system" fn Java_jp_hiroshiba_voicevoxcore_VoiceModel_rsGetMetasJson<'local>( +unsafe extern "system" fn Java_jp_hiroshiba_voicevoxcore_VoiceModelFile_rsGetMetasJson<'local>( env: JNIEnv<'local>, this: JObject<'local>, ) -> jobject { throw_if_err(env, std::ptr::null_mut(), |env| { let internal = env - .get_rust_field::<_, _, Arc>(&this, "handle")? + .get_rust_field::<_, _, VoiceModelFile>(&this, "handle")? .clone(); + let internal = internal.read()?; let metas = internal.metas(); let metas_json = serde_json::to_string(&metas).expect("should not fail"); @@ -60,7 +66,19 @@ unsafe extern "system" fn Java_jp_hiroshiba_voicevoxcore_VoiceModel_rsGetMetasJs } #[no_mangle] -unsafe extern "system" fn Java_jp_hiroshiba_voicevoxcore_VoiceModel_rsDrop<'local>( +unsafe extern "system" fn Java_jp_hiroshiba_voicevoxcore_VoiceModelFile_rsClose<'local>( + env: JNIEnv<'local>, + this: JObject<'local>, +) { + throw_if_err(env, (), |env| { + env.take_rust_field::<_, _, VoiceModelFile>(&this, "handle")? + .close(); + Ok(()) + }) +} + +#[no_mangle] +unsafe extern "system" fn Java_jp_hiroshiba_voicevoxcore_VoiceModelFile_rsDrop<'local>( env: JNIEnv<'local>, this: JObject<'local>, ) { diff --git a/crates/voicevox_core_macros/Cargo.toml b/crates/voicevox_core_macros/Cargo.toml index 0f8362e77..f0613f291 100644 --- a/crates/voicevox_core_macros/Cargo.toml +++ b/crates/voicevox_core_macros/Cargo.toml @@ -3,16 +3,18 @@ name = "voicevox_core_macros" version.workspace = true edition.workspace = true publish.workspace = true +rust-version.workspace = true [lib] name = "macros" proc-macro = true [dependencies] +derive-syn-parse.workspace = true indexmap.workspace = true proc-macro2.workspace = true quote.workspace = true -syn = { workspace = true, features = ["extra-traits", "full"] } +syn = { workspace = true, features = ["extra-traits", "full", "visit-mut"] } [lints.rust] unsafe_code = "forbid" diff --git a/crates/voicevox_core_macros/src/extract.rs b/crates/voicevox_core_macros/src/extract.rs new file mode 100644 index 000000000..e9b480630 --- /dev/null +++ b/crates/voicevox_core_macros/src/extract.rs @@ -0,0 +1,31 @@ +use syn::{ + spanned::Spanned as _, Attribute, Data, DataEnum, DataStruct, DataUnion, Field, Fields, Type, +}; + +pub(crate) fn struct_fields(data: &Data) -> syn::Result> { + let fields = match data { + Data::Struct(DataStruct { + fields: Fields::Named(fields), + .. + }) => fields, + Data::Struct(DataStruct { fields, .. }) => { + return Err(syn::Error::new(fields.span(), "expect named fields")); + } + Data::Enum(DataEnum { enum_token, .. }) => { + return Err(syn::Error::new(enum_token.span(), "expected a struct")); + } + Data::Union(DataUnion { union_token, .. }) => { + return Err(syn::Error::new(union_token.span(), "expected a struct")); + } + }; + + Ok(fields + .named + .iter() + .map( + |Field { + attrs, ident, ty, .. + }| (&**attrs, ident.as_ref().expect("should be named"), ty), + ) + .collect()) +} diff --git a/crates/voicevox_core_macros/src/inference_domain.rs b/crates/voicevox_core_macros/src/inference_domain.rs index 72bc4d18a..f959982e4 100644 --- a/crates/voicevox_core_macros/src/inference_domain.rs +++ b/crates/voicevox_core_macros/src/inference_domain.rs @@ -3,8 +3,8 @@ use quote::quote; use syn::{ parse::{Parse, ParseStream}, spanned::Spanned as _, - Attribute, Data, DataEnum, DataStruct, DataUnion, DeriveInput, Field, Fields, Generics, - ItemType, Type, Variant, + Attribute, Data, DataEnum, DataStruct, DataUnion, DeriveInput, Fields, Generics, ItemType, + Type, Variant, }; pub(crate) fn derive_inference_operation( @@ -178,11 +178,11 @@ pub(crate) fn derive_inference_input_signature( let (impl_generics, ty_generics, where_clause) = generics.split_for_impl(); - let fields = struct_fields(data)?; + let fields = crate::extract::struct_fields(data)?; let param_infos = fields .iter() - .map(|(name, ty)| { + .map(|(_, name, ty)| { let name = name.to_string(); quote! { crate::infer::ParamInfo { @@ -194,7 +194,7 @@ pub(crate) fn derive_inference_input_signature( }) .collect::(); - let field_names = fields.iter().map(|(name, _)| name); + let field_names = fields.iter().map(|(_, name, _)| name); return Ok(quote! { impl #impl_generics crate::infer::InferenceInputSignature for #ident #ty_generics @@ -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) } } } @@ -271,12 +277,12 @@ pub(crate) fn derive_inference_output_signature( let (impl_generics, ty_generics, where_clause) = generics.split_for_impl(); - let fields = struct_fields(data)?; + let fields = crate::extract::struct_fields(data)?; let num_fields = fields.len(); let param_infos = fields .iter() - .map(|(name, ty)| { + .map(|(_, name, ty)| { let name = name.to_string(); quote! { crate::infer::ParamInfo { @@ -288,7 +294,7 @@ pub(crate) fn derive_inference_output_signature( }) .collect::(); - let field_names = fields.iter().map(|(name, _)| name); + let field_names = fields.iter().map(|(_, name, _)| name); Ok(quote! { impl #impl_generics crate::infer::InferenceOutputSignature for #ident #ty_generics @@ -343,30 +349,6 @@ pub(crate) fn derive_inference_output_signature( }) } -fn struct_fields(data: &Data) -> syn::Result> { - let fields = match data { - Data::Struct(DataStruct { - fields: Fields::Named(fields), - .. - }) => fields, - Data::Struct(DataStruct { fields, .. }) => { - return Err(syn::Error::new(fields.span(), "expect named fields")); - } - Data::Enum(DataEnum { enum_token, .. }) => { - return Err(syn::Error::new(enum_token.span(), "expected a struct")); - } - Data::Union(DataUnion { union_token, .. }) => { - return Err(syn::Error::new(union_token.span(), "expected a struct")); - } - }; - - Ok(fields - .named - .iter() - .map(|Field { ident, ty, .. }| (ident.as_ref().expect("should be named"), ty)) - .collect()) -} - fn unit_enum_variants(data: &Data) -> syn::Result> { let variants = match data { Data::Struct(DataStruct { struct_token, .. }) => { diff --git a/crates/voicevox_core_macros/src/inference_domains.rs b/crates/voicevox_core_macros/src/inference_domains.rs new file mode 100644 index 000000000..18b67fa95 --- /dev/null +++ b/crates/voicevox_core_macros/src/inference_domains.rs @@ -0,0 +1,83 @@ +use derive_syn_parse::Parse; +use quote::ToTokens as _; +use syn::{ + parse_quote, + visit_mut::{self, VisitMut}, + Path, PathArguments, PathSegment, Token, Type, TypePath, +}; + +pub(crate) fn substitute_type(input: Substitution) -> syn::Result { + let Substitution { + mut body, + arg, + replacement, + replacement_as, + .. + } = input; + + Substitute { + arg, + replacement, + replacement_as, + } + .visit_type_mut(&mut body); + + return Ok(body.to_token_stream()); + + struct Substitute { + arg: syn::Ident, + replacement: Path, + replacement_as: Path, + } + + impl VisitMut for Substitute { + fn visit_type_mut(&mut self, i: &mut Type) { + visit_mut::visit_type_mut(self, i); + + let Type::Path(TypePath { + qself: None, + path: + Path { + leading_colon: None, + segments, + }, + }) = i + else { + return; + }; + + match &mut *segments.iter_mut().collect::>() { + [PathSegment { + ident, + arguments: PathArguments::None, + }] if *ident == self.arg => { + let replacement = self.replacement.clone(); + *i = parse_quote!(#replacement); + } + [PathSegment { + ident: ident1, + arguments: PathArguments::None, + }, seg] + if *ident1 == self.arg => + { + let replacement = self.replacement.clone(); + let replacement_as = self.replacement_as.clone(); + *i = parse_quote!(<#replacement as #replacement_as>::#seg); + } + _ => {} + } + } + } +} + +/// `$body:ty where $arg:ident = $replacement:path as $replacement_as:path` +#[derive(Parse)] +pub(crate) struct Substitution { + body: Type, + _where_token: Token![where], + arg: syn::Ident, + _eq_token: Token![=], + replacement: Path, + _as_token: Token![as], + replacement_as: Path, +} diff --git a/crates/voicevox_core_macros/src/lib.rs b/crates/voicevox_core_macros/src/lib.rs index 98a2fdc5c..e96456ea8 100644 --- a/crates/voicevox_core_macros/src/lib.rs +++ b/crates/voicevox_core_macros/src/lib.rs @@ -1,6 +1,9 @@ #![warn(rust_2018_idioms)] +mod extract; mod inference_domain; +mod inference_domains; +mod manifest; use syn::parse_macro_input; @@ -100,6 +103,59 @@ pub fn derive_inference_output_signature( from_syn(inference_domain::derive_inference_output_signature(input)) } +/// 構造体のフィールドを取得できる`std::ops::Index`の実装を導出する。 +/// +/// # Example +/// +/// ``` +/// use macros::IndexForFields; +/// +/// #[derive(IndexForFields)] +/// #[index_for_fields(TalkOperation)] +/// pub(crate) struct TalkManifest { +/// #[index_for_fields(TalkOperation::PredictDuration)] +/// pub(crate) predict_duration_filename: Arc, +/// +/// #[index_for_fields(TalkOperation::PredictIntonation)] +/// pub(crate) predict_intonation_filename: Arc, +/// +/// #[index_for_fields(TalkOperation::GenerateFullIntermediate)] +/// pub(crate) generate_full_intermediate_filename: Arc, +/// +/// #[index_for_fields(TalkOperation::RenderAudioSegment)] +/// pub(crate) render_audio_segment_filename: Arc, +/// +/// // … +/// } +/// ``` +#[cfg(not(doctest))] +#[proc_macro_derive(IndexForFields, attributes(index_for_fields))] +pub fn derive_index_for_fields(input: proc_macro::TokenStream) -> proc_macro::TokenStream { + let input = &parse_macro_input!(input); + from_syn(manifest::derive_index_for_fields(input)) +} + +/// # Example +/// +/// ``` +/// type ManifestDomains = +/// (substitute_type!(Option where D = TalkDomain as InferenceDomain),); +/// ``` +/// +/// ↓ +/// +/// ``` +/// type ManifestDomains = (Option<::Manifest>,); +/// // ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +/// // T ← +/// ``` +#[cfg(not(doctest))] +#[proc_macro] +pub fn substitute_type(input: proc_macro::TokenStream) -> proc_macro::TokenStream { + let input = parse_macro_input!(input); + from_syn(inference_domains::substitute_type(input)) +} + fn from_syn(result: syn::Result) -> proc_macro::TokenStream { result.unwrap_or_else(|e| e.to_compile_error()).into() } diff --git a/crates/voicevox_core_macros/src/manifest.rs b/crates/voicevox_core_macros/src/manifest.rs new file mode 100644 index 000000000..9560b1fd4 --- /dev/null +++ b/crates/voicevox_core_macros/src/manifest.rs @@ -0,0 +1,72 @@ +use proc_macro2::Span; +use quote::quote; +use syn::{Attribute, DeriveInput, Expr, Meta, Type}; + +pub(crate) fn derive_index_for_fields( + input: &DeriveInput, +) -> syn::Result { + const ATTR_NAME: &str = "index_for_fields"; + + let DeriveInput { + attrs, + ident, + generics, + data, + .. + } = input; + + let idx = attrs + .iter() + .find_map(|Attribute { meta, .. }| match meta { + Meta::List(list) if list.path.is_ident(ATTR_NAME) => Some(list), + _ => None, + }) + .ok_or_else(|| { + syn::Error::new( + Span::call_site(), + format!("missing `#[{ATTR_NAME}(…)]` in the struct itself"), + ) + })? + .parse_args::()?; + + let (impl_generics, ty_generics, where_clause) = generics.split_for_impl(); + + let targets = crate::extract::struct_fields(data)? + .into_iter() + .flat_map(|(attrs, name, output)| { + let meta = attrs.iter().find_map(|Attribute { meta, .. }| match meta { + Meta::List(meta) if meta.path.is_ident(ATTR_NAME) => Some(meta), + _ => None, + })?; + Some((meta, name, output)) + }) + .map(|(meta, name, output)| { + let key = meta.parse_args::()?; + Ok((key, name, output)) + }) + .collect::>>()?; + + let (_, _, output) = targets.first().ok_or_else(|| { + syn::Error::new( + Span::call_site(), + format!("no fields have `#[{ATTR_NAME}(…)]`"), + ) + })?; + + let arms = targets + .iter() + .map(|(key, name, _)| Ok(quote!(#key => &self.#name))) + .collect::>>()?; + + Ok(quote! { + impl #impl_generics ::std::ops::Index<#idx> for #ident #ty_generics #where_clause { + type Output = #output; + + fn index(&self, index: #idx) -> &Self::Output { + match index { + #(#arms),* + } + } + } + }) +} diff --git a/crates/voicevox_core_python_api/Cargo.toml b/crates/voicevox_core_python_api/Cargo.toml index be3ecbf27..00e1e0d4e 100644 --- a/crates/voicevox_core_python_api/Cargo.toml +++ b/crates/voicevox_core_python_api/Cargo.toml @@ -3,25 +3,26 @@ name = "voicevox_core_python_api" version = "0.0.0" edition.workspace = true publish.workspace = true +rust-version.workspace = true [lib] crate-type = ["cdylib"] -[features] -directml = ["voicevox_core/directml"] - [dependencies] camino.workspace = true easy-ext.workspace = true +futures-lite.workspace = true log.workspace = true +once_cell.workspace = true pyo3 = { workspace = true, features = ["abi3-py38", "extension-module"] } pyo3-asyncio = { workspace = true, features = ["tokio-runtime"] } pyo3-log.workspace = true serde = { workspace = true, features = ["derive"] } serde_json.workspace = true +tokio = { workspace = true, features = ["rt", "sync"] } tracing = { workspace = true, features = ["log"] } uuid.workspace = true -voicevox_core.workspace = true +voicevox_core = { workspace = true, features = ["load-onnxruntime"] } [lints.rust] unsafe_code = "forbid" diff --git a/crates/voicevox_core_python_api/poetry.lock b/crates/voicevox_core_python_api/poetry.lock index 0decb2a6a..f98bf859d 100644 --- a/crates/voicevox_core_python_api/poetry.lock +++ b/crates/voicevox_core_python_api/poetry.lock @@ -1,4 +1,4 @@ -# This file is automatically @generated by Poetry 1.7.1 and should not be changed by hand. +# This file is automatically @generated by Poetry 1.8.3 and should not be changed by hand. [[package]] name = "accessible-pygments" @@ -39,17 +39,6 @@ files = [ [package.dependencies] typing-extensions = {version = ">=4.0.0", markers = "python_version < \"3.9\""} -[[package]] -name = "anyascii" -version = "0.3.2" -description = "Unicode to ASCII transliteration" -optional = false -python-versions = ">=3.3" -files = [ - {file = "anyascii-0.3.2-py3-none-any.whl", hash = "sha256:3b3beef6fc43d9036d3b0529050b0c48bfad8bc960e9e562d7223cfb94fe45d4"}, - {file = "anyascii-0.3.2.tar.gz", hash = "sha256:9d5d32ef844fe225b8bc7cba7f950534fae4da27a9bf3a6bea2cb0ea46ce4730"}, -] - [[package]] name = "astroid" version = "3.0.1" @@ -362,20 +351,17 @@ files = [ [[package]] name = "isort" -version = "5.12.0" +version = "5.13.2" description = "A Python utility / library to sort Python imports." optional = false python-versions = ">=3.8.0" files = [ - {file = "isort-5.12.0-py3-none-any.whl", hash = "sha256:f84c2818376e66cf843d497486ea8fed8700b340f308f076c6fb1229dff318b6"}, - {file = "isort-5.12.0.tar.gz", hash = "sha256:8bef7dde241278824a6d83f44a544709b065191b95b6e50894bdc722fcba0504"}, + {file = "isort-5.13.2-py3-none-any.whl", hash = "sha256:8ca5e72a8d85860d5a3fa69b8745237f2939afe12dbf656afbcb47fe72d947a6"}, + {file = "isort-5.13.2.tar.gz", hash = "sha256:48fdfcb9face5d58a4f6dde2e72a1fb8dcaf8ab26f95ab49fab84c2ddefb0109"}, ] [package.extras] -colors = ["colorama (>=0.4.3)"] -pipfile-deprecated-finder = ["pip-shims (>=0.5.2)", "pipreqs", "requirementslib"] -plugins = ["setuptools"] -requirements-deprecated-finder = ["pip-api", "pipreqs"] +colors = ["colorama (>=0.4.6)"] [[package]] name = "jinja2" @@ -465,24 +451,24 @@ files = [ [[package]] name = "maturin" -version = "1.3.1" -description = "Build and publish crates with pyo3, rust-cpython and cffi bindings as well as rust binaries as python packages" +version = "1.7.4" +description = "Build and publish crates with pyo3, cffi and uniffi bindings as well as rust binaries as python packages" optional = false python-versions = ">=3.7" files = [ - {file = "maturin-1.3.1-py3-none-linux_armv6l.whl", hash = "sha256:925f8324d9bbe8fad90b73ebc6c7f6f594645e7f13af50bded72606b6c233208"}, - {file = "maturin-1.3.1-py3-none-macosx_10_7_x86_64.whl", hash = "sha256:f3c2c694b76e63e78c353e4a2a74f8baff5ac6becf807f23435d28e47a567d63"}, - {file = "maturin-1.3.1-py3-none-macosx_10_9_x86_64.macosx_11_0_arm64.macosx_10_9_universal2.whl", hash = "sha256:13188296f389d05043b8bd2265df66692490b61ba219ae7d5abc09e81e5659ce"}, - {file = "maturin-1.3.1-py3-none-manylinux_2_12_i686.manylinux2010_i686.musllinux_1_1_i686.whl", hash = "sha256:11bbf9978695ac066729af08bc24123317ca3fad51757b0fbdfe811212896714"}, - {file = "maturin-1.3.1-py3-none-manylinux_2_12_x86_64.manylinux2010_x86_64.musllinux_1_1_x86_64.whl", hash = "sha256:d566d2f424fa3f5cd6bd3033fd300b80654884abd061bd53c68f4717753e7d58"}, - {file = "maturin-1.3.1-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.musllinux_1_1_aarch64.whl", hash = "sha256:c0e2b8171f1a70270b86ed72397ba548e3bf4d914f24cd50a228ed85a9d5e914"}, - {file = "maturin-1.3.1-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.musllinux_1_1_armv7l.whl", hash = "sha256:804c6706588a9ca78f18f1a10adf9d24099b9cd3c2917628063ba8d4418b8a50"}, - {file = "maturin-1.3.1-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.musllinux_1_1_ppc64le.whl", hash = "sha256:c533d02563bb185125488cdcf161cc6ba2cdd4812ebff1b6504d1b29880ba1f8"}, - {file = "maturin-1.3.1-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:066b0c515505d4a4c526a9afc26fad4f010972d60937530dc4949dda0f49aa61"}, - {file = "maturin-1.3.1-py3-none-win32.whl", hash = "sha256:4835c332e3b632547db12e6a70abc18cb90ba9df06200dd9a7cc73424919901d"}, - {file = "maturin-1.3.1-py3-none-win_amd64.whl", hash = "sha256:871268417d6b3e2b46018c54a0522efc018bc4918b885d005df90b338e0674c7"}, - {file = "maturin-1.3.1-py3-none-win_arm64.whl", hash = "sha256:659a601c27984a50350f792447ff65ec60309423747f5304c98cb7b7fbb63d39"}, - {file = "maturin-1.3.1.tar.gz", hash = "sha256:efa194e99ae5fff185263d8244acacb12ae256ea73aba62c9446f6075ffc7ac1"}, + {file = "maturin-1.7.4-py3-none-linux_armv6l.whl", hash = "sha256:eb7b7753b733ae302c08f80bca7b0c3fda1eea665c2b1922c58795f35a54c833"}, + {file = "maturin-1.7.4-py3-none-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:0182a9638399c8835afd39d2aeacf56908e37cba3f7abb15816b9df6774fab81"}, + {file = "maturin-1.7.4-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:41a29c5b23f3ebdfe7633637e3de256579a1b2700c04cd68c16ed46934440c5a"}, + {file = "maturin-1.7.4-py3-none-manylinux_2_12_i686.manylinux2010_i686.musllinux_1_1_i686.whl", hash = "sha256:23fae44e345a2da5cb391ae878726fb793394826e2f97febe41710bd4099460e"}, + {file = "maturin-1.7.4-py3-none-manylinux_2_12_x86_64.manylinux2010_x86_64.musllinux_1_1_x86_64.whl", hash = "sha256:8b441521c151f0dbe70ed06fb1feb29b855d787bda038ff4330ca962e5d56641"}, + {file = "maturin-1.7.4-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.musllinux_1_1_aarch64.whl", hash = "sha256:7ccb66d0c5297cf06652c5f72cb398f447d3a332eccf5d1e73b3fe14dbc9498c"}, + {file = "maturin-1.7.4-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.musllinux_1_1_armv7l.whl", hash = "sha256:71f668f19e719048605dbca6a1f4d0dc03b987c922ad9c4bf5be03b9b278e4c3"}, + {file = "maturin-1.7.4-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.musllinux_1_1_ppc64le.whl", hash = "sha256:c179fcb2b494f19186781b667320e43d95b3e71fcb1c98fffad9ef6bd6e276b3"}, + {file = "maturin-1.7.4-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:fd5b4b95286f2f376437340f8a4908f4761587212170263084455be8099099a7"}, + {file = "maturin-1.7.4-py3-none-win32.whl", hash = "sha256:35487a424467d1fda4567cbb02d21f09febb10eda22f5fd647b130bc0767dc61"}, + {file = "maturin-1.7.4-py3-none-win_amd64.whl", hash = "sha256:f70c1c8ec9bd4749a53c0f3ae8fdbb326ce45be4f1c5551985ee25a6d7150328"}, + {file = "maturin-1.7.4-py3-none-win_arm64.whl", hash = "sha256:f3d38a6d0c7fd7b04bec30dd470b2173cf9bd184ab6220c1acaf49df6b48faf5"}, + {file = "maturin-1.7.4.tar.gz", hash = "sha256:2b349d742a07527d236f0b4b6cab26f53ebecad0ceabfc09ec4c6a396e3176f9"}, ] [package.dependencies] @@ -490,7 +476,7 @@ tomli = {version = ">=1.1.0", markers = "python_version < \"3.11\""} [package.extras] patchelf = ["patchelf"] -zig = ["ziglang (>=0.10.0,<0.11.0)"] +zig = ["ziglang (>=0.10.0,<0.13.0)"] [[package]] name = "mypy-extensions" @@ -557,135 +543,120 @@ testing = ["pytest", "pytest-benchmark"] [[package]] name = "pydantic" -version = "2.5.2" +version = "2.9.2" description = "Data validation using Python type hints" optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" files = [ - {file = "pydantic-2.5.2-py3-none-any.whl", hash = "sha256:80c50fb8e3dcecfddae1adbcc00ec5822918490c99ab31f6cf6140ca1c1429f0"}, - {file = "pydantic-2.5.2.tar.gz", hash = "sha256:ff177ba64c6faf73d7afa2e8cad38fd456c0dbe01c9954e71038001cd15a6edd"}, + {file = "pydantic-2.9.2-py3-none-any.whl", hash = "sha256:f048cec7b26778210e28a0459867920654d48e5e62db0958433636cde4254f12"}, + {file = "pydantic-2.9.2.tar.gz", hash = "sha256:d155cef71265d1e9807ed1c32b4c8deec042a44a50a4188b25ac67ecd81a9c0f"}, ] [package.dependencies] -annotated-types = ">=0.4.0" -pydantic-core = "2.14.5" -typing-extensions = ">=4.6.1" +annotated-types = ">=0.6.0" +pydantic-core = "2.23.4" +typing-extensions = {version = ">=4.6.1", markers = "python_version < \"3.13\""} [package.extras] email = ["email-validator (>=2.0.0)"] +timezone = ["tzdata"] [[package]] name = "pydantic-core" -version = "2.14.5" -description = "" +version = "2.23.4" +description = "Core functionality for Pydantic validation and serialization" optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" files = [ - {file = "pydantic_core-2.14.5-cp310-cp310-macosx_10_7_x86_64.whl", hash = "sha256:7e88f5696153dc516ba6e79f82cc4747e87027205f0e02390c21f7cb3bd8abfd"}, - {file = "pydantic_core-2.14.5-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:4641e8ad4efb697f38a9b64ca0523b557c7931c5f84e0fd377a9a3b05121f0de"}, - {file = "pydantic_core-2.14.5-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:774de879d212db5ce02dfbf5b0da9a0ea386aeba12b0b95674a4ce0593df3d07"}, - {file = "pydantic_core-2.14.5-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ebb4e035e28f49b6f1a7032920bb9a0c064aedbbabe52c543343d39341a5b2a3"}, - {file = "pydantic_core-2.14.5-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b53e9ad053cd064f7e473a5f29b37fc4cc9dc6d35f341e6afc0155ea257fc911"}, - {file = "pydantic_core-2.14.5-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8aa1768c151cf562a9992462239dfc356b3d1037cc5a3ac829bb7f3bda7cc1f9"}, - {file = "pydantic_core-2.14.5-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:eac5c82fc632c599f4639a5886f96867ffced74458c7db61bc9a66ccb8ee3113"}, - {file = "pydantic_core-2.14.5-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:d2ae91f50ccc5810b2f1b6b858257c9ad2e08da70bf890dee02de1775a387c66"}, - {file = "pydantic_core-2.14.5-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:6b9ff467ffbab9110e80e8c8de3bcfce8e8b0fd5661ac44a09ae5901668ba997"}, - {file = "pydantic_core-2.14.5-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:61ea96a78378e3bd5a0be99b0e5ed00057b71f66115f5404d0dae4819f495093"}, - {file = "pydantic_core-2.14.5-cp310-none-win32.whl", hash = "sha256:bb4c2eda937a5e74c38a41b33d8c77220380a388d689bcdb9b187cf6224c9720"}, - {file = "pydantic_core-2.14.5-cp310-none-win_amd64.whl", hash = "sha256:b7851992faf25eac90bfcb7bfd19e1f5ffa00afd57daec8a0042e63c74a4551b"}, - {file = "pydantic_core-2.14.5-cp311-cp311-macosx_10_7_x86_64.whl", hash = "sha256:4e40f2bd0d57dac3feb3a3aed50f17d83436c9e6b09b16af271b6230a2915459"}, - {file = "pydantic_core-2.14.5-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:ab1cdb0f14dc161ebc268c09db04d2c9e6f70027f3b42446fa11c153521c0e88"}, - {file = "pydantic_core-2.14.5-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:aae7ea3a1c5bb40c93cad361b3e869b180ac174656120c42b9fadebf685d121b"}, - {file = "pydantic_core-2.14.5-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:60b7607753ba62cf0739177913b858140f11b8af72f22860c28eabb2f0a61937"}, - {file = "pydantic_core-2.14.5-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2248485b0322c75aee7565d95ad0e16f1c67403a470d02f94da7344184be770f"}, - {file = "pydantic_core-2.14.5-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:823fcc638f67035137a5cd3f1584a4542d35a951c3cc68c6ead1df7dac825c26"}, - {file = "pydantic_core-2.14.5-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:96581cfefa9123accc465a5fd0cc833ac4d75d55cc30b633b402e00e7ced00a6"}, - {file = "pydantic_core-2.14.5-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:a33324437018bf6ba1bb0f921788788641439e0ed654b233285b9c69704c27b4"}, - {file = "pydantic_core-2.14.5-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:9bd18fee0923ca10f9a3ff67d4851c9d3e22b7bc63d1eddc12f439f436f2aada"}, - {file = "pydantic_core-2.14.5-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:853a2295c00f1d4429db4c0fb9475958543ee80cfd310814b5c0ef502de24dda"}, - {file = "pydantic_core-2.14.5-cp311-none-win32.whl", hash = "sha256:cb774298da62aea5c80a89bd58c40205ab4c2abf4834453b5de207d59d2e1651"}, - {file = "pydantic_core-2.14.5-cp311-none-win_amd64.whl", hash = "sha256:e87fc540c6cac7f29ede02e0f989d4233f88ad439c5cdee56f693cc9c1c78077"}, - {file = "pydantic_core-2.14.5-cp311-none-win_arm64.whl", hash = "sha256:57d52fa717ff445cb0a5ab5237db502e6be50809b43a596fb569630c665abddf"}, - {file = "pydantic_core-2.14.5-cp312-cp312-macosx_10_7_x86_64.whl", hash = "sha256:e60f112ac88db9261ad3a52032ea46388378034f3279c643499edb982536a093"}, - {file = "pydantic_core-2.14.5-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:6e227c40c02fd873c2a73a98c1280c10315cbebe26734c196ef4514776120aeb"}, - {file = "pydantic_core-2.14.5-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f0cbc7fff06a90bbd875cc201f94ef0ee3929dfbd5c55a06674b60857b8b85ed"}, - {file = "pydantic_core-2.14.5-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:103ef8d5b58596a731b690112819501ba1db7a36f4ee99f7892c40da02c3e189"}, - {file = "pydantic_core-2.14.5-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c949f04ecad823f81b1ba94e7d189d9dfb81edbb94ed3f8acfce41e682e48cef"}, - {file = "pydantic_core-2.14.5-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c1452a1acdf914d194159439eb21e56b89aa903f2e1c65c60b9d874f9b950e5d"}, - {file = "pydantic_core-2.14.5-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cb4679d4c2b089e5ef89756bc73e1926745e995d76e11925e3e96a76d5fa51fc"}, - {file = "pydantic_core-2.14.5-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:cf9d3fe53b1ee360e2421be95e62ca9b3296bf3f2fb2d3b83ca49ad3f925835e"}, - {file = "pydantic_core-2.14.5-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:70f4b4851dbb500129681d04cc955be2a90b2248d69273a787dda120d5cf1f69"}, - {file = "pydantic_core-2.14.5-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:59986de5710ad9613ff61dd9b02bdd2f615f1a7052304b79cc8fa2eb4e336d2d"}, - {file = "pydantic_core-2.14.5-cp312-none-win32.whl", hash = "sha256:699156034181e2ce106c89ddb4b6504c30db8caa86e0c30de47b3e0654543260"}, - {file = "pydantic_core-2.14.5-cp312-none-win_amd64.whl", hash = "sha256:5baab5455c7a538ac7e8bf1feec4278a66436197592a9bed538160a2e7d11e36"}, - {file = "pydantic_core-2.14.5-cp312-none-win_arm64.whl", hash = "sha256:e47e9a08bcc04d20975b6434cc50bf82665fbc751bcce739d04a3120428f3e27"}, - {file = "pydantic_core-2.14.5-cp37-cp37m-macosx_10_7_x86_64.whl", hash = "sha256:af36f36538418f3806048f3b242a1777e2540ff9efaa667c27da63d2749dbce0"}, - {file = "pydantic_core-2.14.5-cp37-cp37m-macosx_11_0_arm64.whl", hash = "sha256:45e95333b8418ded64745f14574aa9bfc212cb4fbeed7a687b0c6e53b5e188cd"}, - {file = "pydantic_core-2.14.5-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4e47a76848f92529879ecfc417ff88a2806438f57be4a6a8bf2961e8f9ca9ec7"}, - {file = "pydantic_core-2.14.5-cp37-cp37m-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:d81e6987b27bc7d101c8597e1cd2bcaa2fee5e8e0f356735c7ed34368c471550"}, - {file = "pydantic_core-2.14.5-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:34708cc82c330e303f4ce87758828ef6e457681b58ce0e921b6e97937dd1e2a3"}, - {file = "pydantic_core-2.14.5-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:652c1988019752138b974c28f43751528116bcceadad85f33a258869e641d753"}, - {file = "pydantic_core-2.14.5-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6e4d090e73e0725b2904fdbdd8d73b8802ddd691ef9254577b708d413bf3006e"}, - {file = "pydantic_core-2.14.5-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:5c7d5b5005f177764e96bd584d7bf28d6e26e96f2a541fdddb934c486e36fd59"}, - {file = "pydantic_core-2.14.5-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:a71891847f0a73b1b9eb86d089baee301477abef45f7eaf303495cd1473613e4"}, - {file = "pydantic_core-2.14.5-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:a717aef6971208f0851a2420b075338e33083111d92041157bbe0e2713b37325"}, - {file = "pydantic_core-2.14.5-cp37-none-win32.whl", hash = "sha256:de790a3b5aa2124b8b78ae5faa033937a72da8efe74b9231698b5a1dd9be3405"}, - {file = "pydantic_core-2.14.5-cp37-none-win_amd64.whl", hash = "sha256:6c327e9cd849b564b234da821236e6bcbe4f359a42ee05050dc79d8ed2a91588"}, - {file = "pydantic_core-2.14.5-cp38-cp38-macosx_10_7_x86_64.whl", hash = "sha256:ef98ca7d5995a82f43ec0ab39c4caf6a9b994cb0b53648ff61716370eadc43cf"}, - {file = "pydantic_core-2.14.5-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:c6eae413494a1c3f89055da7a5515f32e05ebc1a234c27674a6956755fb2236f"}, - {file = "pydantic_core-2.14.5-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dcf4e6d85614f7a4956c2de5a56531f44efb973d2fe4a444d7251df5d5c4dcfd"}, - {file = "pydantic_core-2.14.5-cp38-cp38-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:6637560562134b0e17de333d18e69e312e0458ee4455bdad12c37100b7cad706"}, - {file = "pydantic_core-2.14.5-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:77fa384d8e118b3077cccfcaf91bf83c31fe4dc850b5e6ee3dc14dc3d61bdba1"}, - {file = "pydantic_core-2.14.5-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:16e29bad40bcf97aac682a58861249ca9dcc57c3f6be22f506501833ddb8939c"}, - {file = "pydantic_core-2.14.5-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:531f4b4252fac6ca476fbe0e6f60f16f5b65d3e6b583bc4d87645e4e5ddde331"}, - {file = "pydantic_core-2.14.5-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:074f3d86f081ce61414d2dc44901f4f83617329c6f3ab49d2bc6c96948b2c26b"}, - {file = "pydantic_core-2.14.5-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:c2adbe22ab4babbca99c75c5d07aaf74f43c3195384ec07ccbd2f9e3bddaecec"}, - {file = "pydantic_core-2.14.5-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:0f6116a558fd06d1b7c2902d1c4cf64a5bd49d67c3540e61eccca93f41418124"}, - {file = "pydantic_core-2.14.5-cp38-none-win32.whl", hash = "sha256:fe0a5a1025eb797752136ac8b4fa21aa891e3d74fd340f864ff982d649691867"}, - {file = "pydantic_core-2.14.5-cp38-none-win_amd64.whl", hash = "sha256:079206491c435b60778cf2b0ee5fd645e61ffd6e70c47806c9ed51fc75af078d"}, - {file = "pydantic_core-2.14.5-cp39-cp39-macosx_10_7_x86_64.whl", hash = "sha256:a6a16f4a527aae4f49c875da3cdc9508ac7eef26e7977952608610104244e1b7"}, - {file = "pydantic_core-2.14.5-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:abf058be9517dc877227ec3223f0300034bd0e9f53aebd63cf4456c8cb1e0863"}, - {file = "pydantic_core-2.14.5-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:49b08aae5013640a3bfa25a8eebbd95638ec3f4b2eaf6ed82cf0c7047133f03b"}, - {file = "pydantic_core-2.14.5-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:c2d97e906b4ff36eb464d52a3bc7d720bd6261f64bc4bcdbcd2c557c02081ed2"}, - {file = "pydantic_core-2.14.5-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3128e0bbc8c091ec4375a1828d6118bc20404883169ac95ffa8d983b293611e6"}, - {file = "pydantic_core-2.14.5-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:88e74ab0cdd84ad0614e2750f903bb0d610cc8af2cc17f72c28163acfcf372a4"}, - {file = "pydantic_core-2.14.5-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c339dabd8ee15f8259ee0f202679b6324926e5bc9e9a40bf981ce77c038553db"}, - {file = "pydantic_core-2.14.5-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:3387277f1bf659caf1724e1afe8ee7dbc9952a82d90f858ebb931880216ea955"}, - {file = "pydantic_core-2.14.5-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:ba6b6b3846cfc10fdb4c971980a954e49d447cd215ed5a77ec8190bc93dd7bc5"}, - {file = "pydantic_core-2.14.5-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:ca61d858e4107ce5e1330a74724fe757fc7135190eb5ce5c9d0191729f033209"}, - {file = "pydantic_core-2.14.5-cp39-none-win32.whl", hash = "sha256:ec1e72d6412f7126eb7b2e3bfca42b15e6e389e1bc88ea0069d0cc1742f477c6"}, - {file = "pydantic_core-2.14.5-cp39-none-win_amd64.whl", hash = "sha256:c0b97ec434041827935044bbbe52b03d6018c2897349670ff8fe11ed24d1d4ab"}, - {file = "pydantic_core-2.14.5-pp310-pypy310_pp73-macosx_10_7_x86_64.whl", hash = "sha256:79e0a2cdbdc7af3f4aee3210b1172ab53d7ddb6a2d8c24119b5706e622b346d0"}, - {file = "pydantic_core-2.14.5-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:678265f7b14e138d9a541ddabbe033012a2953315739f8cfa6d754cc8063e8ca"}, - {file = "pydantic_core-2.14.5-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:95b15e855ae44f0c6341ceb74df61b606e11f1087e87dcb7482377374aac6abe"}, - {file = "pydantic_core-2.14.5-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:09b0e985fbaf13e6b06a56d21694d12ebca6ce5414b9211edf6f17738d82b0f8"}, - {file = "pydantic_core-2.14.5-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:3ad873900297bb36e4b6b3f7029d88ff9829ecdc15d5cf20161775ce12306f8a"}, - {file = "pydantic_core-2.14.5-pp310-pypy310_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:2d0ae0d8670164e10accbeb31d5ad45adb71292032d0fdb9079912907f0085f4"}, - {file = "pydantic_core-2.14.5-pp310-pypy310_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:d37f8ec982ead9ba0a22a996129594938138a1503237b87318392a48882d50b7"}, - {file = "pydantic_core-2.14.5-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:35613015f0ba7e14c29ac6c2483a657ec740e5ac5758d993fdd5870b07a61d8b"}, - {file = "pydantic_core-2.14.5-pp37-pypy37_pp73-macosx_10_7_x86_64.whl", hash = "sha256:ab4ea451082e684198636565224bbb179575efc1658c48281b2c866bfd4ddf04"}, - {file = "pydantic_core-2.14.5-pp37-pypy37_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4ce601907e99ea5b4adb807ded3570ea62186b17f88e271569144e8cca4409c7"}, - {file = "pydantic_core-2.14.5-pp37-pypy37_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fb2ed8b3fe4bf4506d6dab3b93b83bbc22237e230cba03866d561c3577517d18"}, - {file = "pydantic_core-2.14.5-pp37-pypy37_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:70f947628e074bb2526ba1b151cee10e4c3b9670af4dbb4d73bc8a89445916b5"}, - {file = "pydantic_core-2.14.5-pp37-pypy37_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:4bc536201426451f06f044dfbf341c09f540b4ebdb9fd8d2c6164d733de5e634"}, - {file = "pydantic_core-2.14.5-pp37-pypy37_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:f4791cf0f8c3104ac668797d8c514afb3431bc3305f5638add0ba1a5a37e0d88"}, - {file = "pydantic_core-2.14.5-pp38-pypy38_pp73-macosx_10_7_x86_64.whl", hash = "sha256:038c9f763e650712b899f983076ce783175397c848da04985658e7628cbe873b"}, - {file = "pydantic_core-2.14.5-pp38-pypy38_pp73-macosx_11_0_arm64.whl", hash = "sha256:27548e16c79702f1e03f5628589c6057c9ae17c95b4c449de3c66b589ead0520"}, - {file = "pydantic_core-2.14.5-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c97bee68898f3f4344eb02fec316db93d9700fb1e6a5b760ffa20d71d9a46ce3"}, - {file = "pydantic_core-2.14.5-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b9b759b77f5337b4ea024f03abc6464c9f35d9718de01cfe6bae9f2e139c397e"}, - {file = "pydantic_core-2.14.5-pp38-pypy38_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:439c9afe34638ace43a49bf72d201e0ffc1a800295bed8420c2a9ca8d5e3dbb3"}, - {file = "pydantic_core-2.14.5-pp38-pypy38_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:ba39688799094c75ea8a16a6b544eb57b5b0f3328697084f3f2790892510d144"}, - {file = "pydantic_core-2.14.5-pp38-pypy38_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:ccd4d5702bb90b84df13bd491be8d900b92016c5a455b7e14630ad7449eb03f8"}, - {file = "pydantic_core-2.14.5-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:81982d78a45d1e5396819bbb4ece1fadfe5f079335dd28c4ab3427cd95389944"}, - {file = "pydantic_core-2.14.5-pp39-pypy39_pp73-macosx_10_7_x86_64.whl", hash = "sha256:7f8210297b04e53bc3da35db08b7302a6a1f4889c79173af69b72ec9754796b8"}, - {file = "pydantic_core-2.14.5-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:8c8a8812fe6f43a3a5b054af6ac2d7b8605c7bcab2804a8a7d68b53f3cd86e00"}, - {file = "pydantic_core-2.14.5-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:206ed23aecd67c71daf5c02c3cd19c0501b01ef3cbf7782db9e4e051426b3d0d"}, - {file = "pydantic_core-2.14.5-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c2027d05c8aebe61d898d4cffd774840a9cb82ed356ba47a90d99ad768f39789"}, - {file = "pydantic_core-2.14.5-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:40180930807ce806aa71eda5a5a5447abb6b6a3c0b4b3b1b1962651906484d68"}, - {file = "pydantic_core-2.14.5-pp39-pypy39_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:615a0a4bff11c45eb3c1996ceed5bdaa2f7b432425253a7c2eed33bb86d80abc"}, - {file = "pydantic_core-2.14.5-pp39-pypy39_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:f5e412d717366e0677ef767eac93566582518fe8be923361a5c204c1a62eaafe"}, - {file = "pydantic_core-2.14.5-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:513b07e99c0a267b1d954243845d8a833758a6726a3b5d8948306e3fe14675e3"}, - {file = "pydantic_core-2.14.5.tar.gz", hash = "sha256:6d30226dfc816dd0fdf120cae611dd2215117e4f9b124af8c60ab9093b6e8e71"}, + {file = "pydantic_core-2.23.4-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:b10bd51f823d891193d4717448fab065733958bdb6a6b351967bd349d48d5c9b"}, + {file = "pydantic_core-2.23.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:4fc714bdbfb534f94034efaa6eadd74e5b93c8fa6315565a222f7b6f42ca1166"}, + {file = "pydantic_core-2.23.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:63e46b3169866bd62849936de036f901a9356e36376079b05efa83caeaa02ceb"}, + {file = "pydantic_core-2.23.4-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ed1a53de42fbe34853ba90513cea21673481cd81ed1be739f7f2efb931b24916"}, + {file = "pydantic_core-2.23.4-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:cfdd16ab5e59fc31b5e906d1a3f666571abc367598e3e02c83403acabc092e07"}, + {file = "pydantic_core-2.23.4-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:255a8ef062cbf6674450e668482456abac99a5583bbafb73f9ad469540a3a232"}, + {file = "pydantic_core-2.23.4-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4a7cd62e831afe623fbb7aabbb4fe583212115b3ef38a9f6b71869ba644624a2"}, + {file = "pydantic_core-2.23.4-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:f09e2ff1f17c2b51f2bc76d1cc33da96298f0a036a137f5440ab3ec5360b624f"}, + {file = "pydantic_core-2.23.4-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:e38e63e6f3d1cec5a27e0afe90a085af8b6806ee208b33030e65b6516353f1a3"}, + {file = "pydantic_core-2.23.4-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:0dbd8dbed2085ed23b5c04afa29d8fd2771674223135dc9bc937f3c09284d071"}, + {file = "pydantic_core-2.23.4-cp310-none-win32.whl", hash = "sha256:6531b7ca5f951d663c339002e91aaebda765ec7d61b7d1e3991051906ddde119"}, + {file = "pydantic_core-2.23.4-cp310-none-win_amd64.whl", hash = "sha256:7c9129eb40958b3d4500fa2467e6a83356b3b61bfff1b414c7361d9220f9ae8f"}, + {file = "pydantic_core-2.23.4-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:77733e3892bb0a7fa797826361ce8a9184d25c8dffaec60b7ffe928153680ba8"}, + {file = "pydantic_core-2.23.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:1b84d168f6c48fabd1f2027a3d1bdfe62f92cade1fb273a5d68e621da0e44e6d"}, + {file = "pydantic_core-2.23.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:df49e7a0861a8c36d089c1ed57d308623d60416dab2647a4a17fe050ba85de0e"}, + {file = "pydantic_core-2.23.4-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ff02b6d461a6de369f07ec15e465a88895f3223eb75073ffea56b84d9331f607"}, + {file = "pydantic_core-2.23.4-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:996a38a83508c54c78a5f41456b0103c30508fed9abcad0a59b876d7398f25fd"}, + {file = "pydantic_core-2.23.4-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d97683ddee4723ae8c95d1eddac7c192e8c552da0c73a925a89fa8649bf13eea"}, + {file = "pydantic_core-2.23.4-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:216f9b2d7713eb98cb83c80b9c794de1f6b7e3145eef40400c62e86cee5f4e1e"}, + {file = "pydantic_core-2.23.4-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:6f783e0ec4803c787bcea93e13e9932edab72068f68ecffdf86a99fd5918878b"}, + {file = "pydantic_core-2.23.4-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:d0776dea117cf5272382634bd2a5c1b6eb16767c223c6a5317cd3e2a757c61a0"}, + {file = "pydantic_core-2.23.4-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:d5f7a395a8cf1621939692dba2a6b6a830efa6b3cee787d82c7de1ad2930de64"}, + {file = "pydantic_core-2.23.4-cp311-none-win32.whl", hash = "sha256:74b9127ffea03643e998e0c5ad9bd3811d3dac8c676e47db17b0ee7c3c3bf35f"}, + {file = "pydantic_core-2.23.4-cp311-none-win_amd64.whl", hash = "sha256:98d134c954828488b153d88ba1f34e14259284f256180ce659e8d83e9c05eaa3"}, + {file = "pydantic_core-2.23.4-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:f3e0da4ebaef65158d4dfd7d3678aad692f7666877df0002b8a522cdf088f231"}, + {file = "pydantic_core-2.23.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:f69a8e0b033b747bb3e36a44e7732f0c99f7edd5cea723d45bc0d6e95377ffee"}, + {file = "pydantic_core-2.23.4-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:723314c1d51722ab28bfcd5240d858512ffd3116449c557a1336cbe3919beb87"}, + {file = "pydantic_core-2.23.4-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:bb2802e667b7051a1bebbfe93684841cc9351004e2badbd6411bf357ab8d5ac8"}, + {file = "pydantic_core-2.23.4-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d18ca8148bebe1b0a382a27a8ee60350091a6ddaf475fa05ef50dc35b5df6327"}, + {file = "pydantic_core-2.23.4-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:33e3d65a85a2a4a0dc3b092b938a4062b1a05f3a9abde65ea93b233bca0e03f2"}, + {file = "pydantic_core-2.23.4-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:128585782e5bfa515c590ccee4b727fb76925dd04a98864182b22e89a4e6ed36"}, + {file = "pydantic_core-2.23.4-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:68665f4c17edcceecc112dfed5dbe6f92261fb9d6054b47d01bf6371a6196126"}, + {file = "pydantic_core-2.23.4-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:20152074317d9bed6b7a95ade3b7d6054845d70584216160860425f4fbd5ee9e"}, + {file = "pydantic_core-2.23.4-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:9261d3ce84fa1d38ed649c3638feefeae23d32ba9182963e465d58d62203bd24"}, + {file = "pydantic_core-2.23.4-cp312-none-win32.whl", hash = "sha256:4ba762ed58e8d68657fc1281e9bb72e1c3e79cc5d464be146e260c541ec12d84"}, + {file = "pydantic_core-2.23.4-cp312-none-win_amd64.whl", hash = "sha256:97df63000f4fea395b2824da80e169731088656d1818a11b95f3b173747b6cd9"}, + {file = "pydantic_core-2.23.4-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:7530e201d10d7d14abce4fb54cfe5b94a0aefc87da539d0346a484ead376c3cc"}, + {file = "pydantic_core-2.23.4-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:df933278128ea1cd77772673c73954e53a1c95a4fdf41eef97c2b779271bd0bd"}, + {file = "pydantic_core-2.23.4-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0cb3da3fd1b6a5d0279a01877713dbda118a2a4fc6f0d821a57da2e464793f05"}, + {file = "pydantic_core-2.23.4-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:42c6dcb030aefb668a2b7009c85b27f90e51e6a3b4d5c9bc4c57631292015b0d"}, + {file = "pydantic_core-2.23.4-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:696dd8d674d6ce621ab9d45b205df149399e4bb9aa34102c970b721554828510"}, + {file = "pydantic_core-2.23.4-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2971bb5ffe72cc0f555c13e19b23c85b654dd2a8f7ab493c262071377bfce9f6"}, + {file = "pydantic_core-2.23.4-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8394d940e5d400d04cad4f75c0598665cbb81aecefaca82ca85bd28264af7f9b"}, + {file = "pydantic_core-2.23.4-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:0dff76e0602ca7d4cdaacc1ac4c005e0ce0dcfe095d5b5259163a80d3a10d327"}, + {file = "pydantic_core-2.23.4-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:7d32706badfe136888bdea71c0def994644e09fff0bfe47441deaed8e96fdbc6"}, + {file = "pydantic_core-2.23.4-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:ed541d70698978a20eb63d8c5d72f2cc6d7079d9d90f6b50bad07826f1320f5f"}, + {file = "pydantic_core-2.23.4-cp313-none-win32.whl", hash = "sha256:3d5639516376dce1940ea36edf408c554475369f5da2abd45d44621cb616f769"}, + {file = "pydantic_core-2.23.4-cp313-none-win_amd64.whl", hash = "sha256:5a1504ad17ba4210df3a045132a7baeeba5a200e930f57512ee02909fc5c4cb5"}, + {file = "pydantic_core-2.23.4-cp38-cp38-macosx_10_12_x86_64.whl", hash = "sha256:d4488a93b071c04dc20f5cecc3631fc78b9789dd72483ba15d423b5b3689b555"}, + {file = "pydantic_core-2.23.4-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:81965a16b675b35e1d09dd14df53f190f9129c0202356ed44ab2728b1c905658"}, + {file = "pydantic_core-2.23.4-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4ffa2ebd4c8530079140dd2d7f794a9d9a73cbb8e9d59ffe24c63436efa8f271"}, + {file = "pydantic_core-2.23.4-cp38-cp38-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:61817945f2fe7d166e75fbfb28004034b48e44878177fc54d81688e7b85a3665"}, + {file = "pydantic_core-2.23.4-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:29d2c342c4bc01b88402d60189f3df065fb0dda3654744d5a165a5288a657368"}, + {file = "pydantic_core-2.23.4-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5e11661ce0fd30a6790e8bcdf263b9ec5988e95e63cf901972107efc49218b13"}, + {file = "pydantic_core-2.23.4-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9d18368b137c6295db49ce7218b1a9ba15c5bc254c96d7c9f9e924a9bc7825ad"}, + {file = "pydantic_core-2.23.4-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:ec4e55f79b1c4ffb2eecd8a0cfba9955a2588497d96851f4c8f99aa4a1d39b12"}, + {file = "pydantic_core-2.23.4-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:374a5e5049eda9e0a44c696c7ade3ff355f06b1fe0bb945ea3cac2bc336478a2"}, + {file = "pydantic_core-2.23.4-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:5c364564d17da23db1106787675fc7af45f2f7b58b4173bfdd105564e132e6fb"}, + {file = "pydantic_core-2.23.4-cp38-none-win32.whl", hash = "sha256:d7a80d21d613eec45e3d41eb22f8f94ddc758a6c4720842dc74c0581f54993d6"}, + {file = "pydantic_core-2.23.4-cp38-none-win_amd64.whl", hash = "sha256:5f5ff8d839f4566a474a969508fe1c5e59c31c80d9e140566f9a37bba7b8d556"}, + {file = "pydantic_core-2.23.4-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:a4fa4fc04dff799089689f4fd502ce7d59de529fc2f40a2c8836886c03e0175a"}, + {file = "pydantic_core-2.23.4-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:0a7df63886be5e270da67e0966cf4afbae86069501d35c8c1b3b6c168f42cb36"}, + {file = "pydantic_core-2.23.4-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dcedcd19a557e182628afa1d553c3895a9f825b936415d0dbd3cd0bbcfd29b4b"}, + {file = "pydantic_core-2.23.4-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:5f54b118ce5de9ac21c363d9b3caa6c800341e8c47a508787e5868c6b79c9323"}, + {file = "pydantic_core-2.23.4-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:86d2f57d3e1379a9525c5ab067b27dbb8a0642fb5d454e17a9ac434f9ce523e3"}, + {file = "pydantic_core-2.23.4-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:de6d1d1b9e5101508cb37ab0d972357cac5235f5c6533d1071964c47139257df"}, + {file = "pydantic_core-2.23.4-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1278e0d324f6908e872730c9102b0112477a7f7cf88b308e4fc36ce1bdb6d58c"}, + {file = "pydantic_core-2.23.4-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:9a6b5099eeec78827553827f4c6b8615978bb4b6a88e5d9b93eddf8bb6790f55"}, + {file = "pydantic_core-2.23.4-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:e55541f756f9b3ee346b840103f32779c695a19826a4c442b7954550a0972040"}, + {file = "pydantic_core-2.23.4-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:a5c7ba8ffb6d6f8f2ab08743be203654bb1aaa8c9dcb09f82ddd34eadb695605"}, + {file = "pydantic_core-2.23.4-cp39-none-win32.whl", hash = "sha256:37b0fe330e4a58d3c58b24d91d1eb102aeec675a3db4c292ec3928ecd892a9a6"}, + {file = "pydantic_core-2.23.4-cp39-none-win_amd64.whl", hash = "sha256:1498bec4c05c9c787bde9125cfdcc63a41004ff167f495063191b863399b1a29"}, + {file = "pydantic_core-2.23.4-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:f455ee30a9d61d3e1a15abd5068827773d6e4dc513e795f380cdd59932c782d5"}, + {file = "pydantic_core-2.23.4-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:1e90d2e3bd2c3863d48525d297cd143fe541be8bbf6f579504b9712cb6b643ec"}, + {file = "pydantic_core-2.23.4-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2e203fdf807ac7e12ab59ca2bfcabb38c7cf0b33c41efeb00f8e5da1d86af480"}, + {file = "pydantic_core-2.23.4-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e08277a400de01bc72436a0ccd02bdf596631411f592ad985dcee21445bd0068"}, + {file = "pydantic_core-2.23.4-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:f220b0eea5965dec25480b6333c788fb72ce5f9129e8759ef876a1d805d00801"}, + {file = "pydantic_core-2.23.4-pp310-pypy310_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:d06b0c8da4f16d1d1e352134427cb194a0a6e19ad5db9161bf32b2113409e728"}, + {file = "pydantic_core-2.23.4-pp310-pypy310_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:ba1a0996f6c2773bd83e63f18914c1de3c9dd26d55f4ac302a7efe93fb8e7433"}, + {file = "pydantic_core-2.23.4-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:9a5bce9d23aac8f0cf0836ecfc033896aa8443b501c58d0602dbfd5bd5b37753"}, + {file = "pydantic_core-2.23.4-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:78ddaaa81421a29574a682b3179d4cf9e6d405a09b99d93ddcf7e5239c742e21"}, + {file = "pydantic_core-2.23.4-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:883a91b5dd7d26492ff2f04f40fbb652de40fcc0afe07e8129e8ae779c2110eb"}, + {file = "pydantic_core-2.23.4-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:88ad334a15b32a791ea935af224b9de1bf99bcd62fabf745d5f3442199d86d59"}, + {file = "pydantic_core-2.23.4-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:233710f069d251feb12a56da21e14cca67994eab08362207785cf8c598e74577"}, + {file = "pydantic_core-2.23.4-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:19442362866a753485ba5e4be408964644dd6a09123d9416c54cd49171f50744"}, + {file = "pydantic_core-2.23.4-pp39-pypy39_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:624e278a7d29b6445e4e813af92af37820fafb6dcc55c012c834f9e26f9aaaef"}, + {file = "pydantic_core-2.23.4-pp39-pypy39_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:f5ef8f42bec47f21d07668a043f077d507e5bf4e668d5c6dfe6aaba89de1a5b8"}, + {file = "pydantic_core-2.23.4-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:aea443fffa9fbe3af1a9ba721a87f926fe548d32cab71d188a6ede77d0ff244e"}, + {file = "pydantic_core-2.23.4.tar.gz", hash = "sha256:2584f7cf844ac4d970fba483a717dbe10c1c1c96a969bf65d61ffe94df1b2863"}, ] [package.dependencies] @@ -923,17 +894,16 @@ test = ["cython", "filelock", "html5lib", "pytest (>=4.6)"] [[package]] name = "sphinx-autoapi" -version = "3.0.0" +version = "3.3.2" description = "Sphinx API documentation generator" optional = false python-versions = ">=3.8" files = [ - {file = "sphinx-autoapi-3.0.0.tar.gz", hash = "sha256:09ebd674a32b44467222b0fb8a917b97c89523f20dbf05b52cb8a3f0e15714de"}, - {file = "sphinx_autoapi-3.0.0-py2.py3-none-any.whl", hash = "sha256:ea207793cba1feff7b2ded0e29364f2995a4d157303a98603cee0ce94cea2688"}, + {file = "sphinx_autoapi-3.3.2-py2.py3-none-any.whl", hash = "sha256:08afa656f7fcd45fe7dd64bf9f44698ddb8ca7c2d5cd0614c7455912ed580324"}, + {file = "sphinx_autoapi-3.3.2.tar.gz", hash = "sha256:ebf8b44b2ebab5c28f0263ec6c2f8acdd156e9b2d539a58eca39d2f368445173"}, ] [package.dependencies] -anyascii = "*" astroid = [ {version = ">=2.7", markers = "python_version < \"3.12\""}, {version = ">=3.0.0a1", markers = "python_version >= \"3.12\""}, @@ -941,6 +911,7 @@ astroid = [ Jinja2 = "*" PyYAML = "*" sphinx = ">=6.1.0" +stdlib-list = {version = "*", markers = "python_version < \"3.10\""} [package.extras] docs = ["furo", "sphinx", "sphinx-design"] @@ -1034,6 +1005,24 @@ files = [ lint = ["docutils-stubs", "flake8", "mypy"] test = ["pytest"] +[[package]] +name = "stdlib-list" +version = "0.10.0" +description = "A list of Python Standard Libraries (2.7 through 3.12)." +optional = false +python-versions = ">=3.7" +files = [ + {file = "stdlib_list-0.10.0-py3-none-any.whl", hash = "sha256:b3a911bc441d03e0332dd1a9e7d0870ba3bb0a542a74d7524f54fb431256e214"}, + {file = "stdlib_list-0.10.0.tar.gz", hash = "sha256:6519c50d645513ed287657bfe856d527f277331540691ddeaf77b25459964a14"}, +] + +[package.extras] +dev = ["build", "stdlib-list[doc,lint,test]"] +doc = ["furo", "sphinx"] +lint = ["black", "mypy", "ruff"] +support = ["sphobjinv"] +test = ["coverage[toml]", "pytest", "pytest-cov"] + [[package]] name = "tomli" version = "2.0.1" @@ -1091,4 +1080,4 @@ testing = ["big-O", "jaraco.functools", "jaraco.itertools", "more-itertools", "p [metadata] lock-version = "2.0" python-versions = ">=3.8,<3.13" -content-hash = "42faee9d02ea5ad43eaef485f2127f0ffd77a5f8951ef71529eb1e85249e9c04" +content-hash = "606f144007cf0821a15152a43f89d742691cf677deacb2497b2af87eee0d864d" diff --git a/crates/voicevox_core_python_api/pyproject.toml b/crates/voicevox_core_python_api/pyproject.toml index 428e7695f..042c91091 100644 --- a/crates/voicevox_core_python_api/pyproject.toml +++ b/crates/voicevox_core_python_api/pyproject.toml @@ -28,7 +28,6 @@ executionEnvironments = [{ root = "python/test" }, { root = "python" }] [tool.maturin] module-name = "voicevox_core._rust" bindings = "pyo3" -skip-auditwheel = true # Linuxでlibonnxruntime.so.*の不在を許してもらう python-source = "python" [tool.poetry] @@ -45,13 +44,13 @@ pydantic = ">=2.5.2,<3" [tool.poetry.group.docs.dependencies] sphinx = "6.2.1" pydata-sphinx-theme = "0.14.1" -sphinx-autoapi = "3.0.0" +sphinx-autoapi = "3.3.2" [tool.poetry.group.dev.dependencies] -maturin = "1.3.1" +maturin = "1.7.4" [tool.poetry.group.test.dependencies] pytest = "7.3.1" pytest-asyncio = "0.21.0" black = "23.3.0" -isort = "5.12.0" +isort = "5.13.2" diff --git a/crates/voicevox_core_python_api/python/test/conftest.py b/crates/voicevox_core_python_api/python/test/conftest.py index 628f4dc56..430e415c1 100644 --- a/crates/voicevox_core_python_api/python/test/conftest.py +++ b/crates/voicevox_core_python_api/python/test/conftest.py @@ -4,13 +4,23 @@ from typing import List, TypedDict import pytest +import voicevox_core root_dir = Path(os.path.dirname(os.path.abspath(__file__))) +onnxruntime_filename = str( + root_dir.parent.parent.parent + / "test_util" + / "data" + / "lib" + / voicevox_core.blocking.Onnxruntime.LIB_VERSIONED_FILENAME +) 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 index ec69032b1..3b6f857e3 100644 --- a/crates/voicevox_core_python_api/python/test/test_asyncio_metas.py +++ b/crates/voicevox_core_python_api/python/test/test_asyncio_metas.py @@ -7,20 +7,23 @@ import conftest import pytest import pytest_asyncio -from voicevox_core.asyncio import OpenJtalk, Synthesizer, VoiceModel +from voicevox_core.asyncio import Onnxruntime, OpenJtalk, Synthesizer, VoiceModelFile -def test_voice_model_metas_works(voice_model: VoiceModel) -> None: +def test_voice_model_metas_works(voice_model: VoiceModelFile) -> 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)) +async def test_synthesizer_metas_works(voice_model: VoiceModelFile) -> None: + synthesizer = Synthesizer( + await Onnxruntime.load_once(filename=conftest.onnxruntime_filename), + 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) +async def voice_model() -> VoiceModelFile: + return await VoiceModelFile.open(conftest.model_dir) diff --git a/crates/voicevox_core_python_api/python/test/test_asyncio_user_dict_load.py b/crates/voicevox_core_python_api/python/test/test_asyncio_user_dict_load.py index c509b8c2d..b6fe50986 100644 --- a/crates/voicevox_core_python_api/python/test/test_asyncio_user_dict_load.py +++ b/crates/voicevox_core_python_api/python/test/test_asyncio_user_dict_load.py @@ -15,9 +15,12 @@ @pytest.mark.asyncio async def test_user_dict_load() -> None: + onnxruntime = await voicevox_core.asyncio.Onnxruntime.load_once( + filename=conftest.onnxruntime_filename + ) open_jtalk = await voicevox_core.asyncio.OpenJtalk.new(conftest.open_jtalk_dic_dir) - model = await voicevox_core.asyncio.VoiceModel.from_path(conftest.model_dir) - synthesizer = voicevox_core.asyncio.Synthesizer(open_jtalk) + model = await voicevox_core.asyncio.VoiceModelFile.open(conftest.model_dir) + synthesizer = voicevox_core.asyncio.Synthesizer(onnxruntime, open_jtalk) await synthesizer.load_voice_model(model) 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 index c305e2cdb..a6aa6441d 100644 --- a/crates/voicevox_core_python_api/python/test/test_blocking_metas.py +++ b/crates/voicevox_core_python_api/python/test/test_blocking_metas.py @@ -6,19 +6,22 @@ import conftest import pytest -from voicevox_core.blocking import OpenJtalk, Synthesizer, VoiceModel +from voicevox_core.blocking import Onnxruntime, OpenJtalk, Synthesizer, VoiceModelFile -def test_voice_model_metas_works(voice_model: VoiceModel) -> None: +def test_voice_model_metas_works(voice_model: VoiceModelFile) -> None: _ = voice_model.metas -def test_synthesizer_metas_works(voice_model: VoiceModel) -> None: - synthesizer = Synthesizer(OpenJtalk(conftest.open_jtalk_dic_dir)) +def test_synthesizer_metas_works(voice_model: VoiceModelFile) -> None: + synthesizer = Synthesizer( + Onnxruntime.load_once(filename=conftest.onnxruntime_filename), + 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) +def voice_model() -> VoiceModelFile: + return VoiceModelFile.open(conftest.model_dir) diff --git a/crates/voicevox_core_python_api/python/test/test_blocking_user_dict_load.py b/crates/voicevox_core_python_api/python/test/test_blocking_user_dict_load.py index ef94d9742..e8a5bd350 100644 --- a/crates/voicevox_core_python_api/python/test/test_blocking_user_dict_load.py +++ b/crates/voicevox_core_python_api/python/test/test_blocking_user_dict_load.py @@ -13,9 +13,12 @@ def test_user_dict_load() -> None: + onnxruntime = voicevox_core.blocking.Onnxruntime.load_once( + filename=conftest.onnxruntime_filename + ) open_jtalk = voicevox_core.blocking.OpenJtalk(conftest.open_jtalk_dic_dir) - model = voicevox_core.blocking.VoiceModel.from_path(conftest.model_dir) - synthesizer = voicevox_core.blocking.Synthesizer(open_jtalk) + model = voicevox_core.blocking.VoiceModelFile.open(conftest.model_dir) + synthesizer = voicevox_core.blocking.Synthesizer(onnxruntime, open_jtalk) synthesizer.load_voice_model(model) diff --git a/crates/voicevox_core_python_api/python/test/test_nop.py b/crates/voicevox_core_python_api/python/test/test_nop.py deleted file mode 100644 index 43956957d..000000000 --- a/crates/voicevox_core_python_api/python/test/test_nop.py +++ /dev/null @@ -1,8 +0,0 @@ -# FIXME: ちゃんとしたテストを用意する - -import conftest # noqa: F401 -import voicevox_core # noqa: F401 - - -def test_nop() -> None: - pass diff --git a/crates/voicevox_core_python_api/python/test/test_pseudo_raii_for_asyncio_synthesizer.py b/crates/voicevox_core_python_api/python/test/test_pseudo_raii_for_asyncio_synthesizer.py index 93d92ad28..bfadf8471 100644 --- a/crates/voicevox_core_python_api/python/test/test_pseudo_raii_for_asyncio_synthesizer.py +++ b/crates/voicevox_core_python_api/python/test/test_pseudo_raii_for_asyncio_synthesizer.py @@ -7,39 +7,48 @@ import conftest import pytest import pytest_asyncio -from voicevox_core.asyncio import OpenJtalk, Synthesizer +from voicevox_core.asyncio import Onnxruntime, OpenJtalk, Synthesizer -def test_enter_returns_workable_self(synthesizer: Synthesizer) -> None: - with synthesizer as ctx: +@pytest.mark.asyncio +async def test_enter_returns_workable_self(synthesizer: Synthesizer) -> None: + async with synthesizer as ctx: assert ctx is synthesizer _ = synthesizer.metas -def test_closing_multiple_times_is_allowed(synthesizer: Synthesizer) -> None: - with synthesizer: - with synthesizer: +@pytest.mark.asyncio +async def test_closing_multiple_times_is_allowed(synthesizer: Synthesizer) -> None: + async with synthesizer: + async with synthesizer: pass - synthesizer.close() - synthesizer.close() + await synthesizer.close() + await synthesizer.close() -def test_access_after_close_denied(synthesizer: Synthesizer) -> None: - synthesizer.close() +@pytest.mark.asyncio +async def test_access_after_close_denied(synthesizer: Synthesizer) -> None: + await synthesizer.close() with pytest.raises(ValueError, match="^The `Synthesizer` is closed$"): _ = synthesizer.metas -def test_access_after_exit_denied(synthesizer: Synthesizer) -> None: - with synthesizer: +@pytest.mark.asyncio +async def test_access_after_exit_denied(synthesizer: Synthesizer) -> None: + async with synthesizer: pass with pytest.raises(ValueError, match="^The `Synthesizer` is closed$"): _ = synthesizer.metas @pytest_asyncio.fixture -async def synthesizer(open_jtalk: OpenJtalk) -> Synthesizer: - return Synthesizer(open_jtalk) +async def synthesizer(onnxruntime: Onnxruntime, open_jtalk: OpenJtalk) -> Synthesizer: + return Synthesizer(onnxruntime, open_jtalk) + + +@pytest_asyncio.fixture(scope="function") +async def onnxruntime() -> Onnxruntime: + return await Onnxruntime.load_once(filename=conftest.onnxruntime_filename) @pytest_asyncio.fixture(scope="function") diff --git a/crates/voicevox_core_python_api/python/test/test_pseudo_raii_for_blocking_synthesizer.py b/crates/voicevox_core_python_api/python/test/test_pseudo_raii_for_blocking_synthesizer.py index 3e3f5f823..dc55eafc4 100644 --- a/crates/voicevox_core_python_api/python/test/test_pseudo_raii_for_blocking_synthesizer.py +++ b/crates/voicevox_core_python_api/python/test/test_pseudo_raii_for_blocking_synthesizer.py @@ -6,7 +6,7 @@ import conftest import pytest -from voicevox_core.blocking import OpenJtalk, Synthesizer +from voicevox_core.blocking import Onnxruntime, OpenJtalk, Synthesizer def test_enter_returns_workable_self(synthesizer: Synthesizer) -> None: @@ -37,8 +37,13 @@ def test_access_after_exit_denied(synthesizer: Synthesizer) -> None: @pytest.fixture -def synthesizer(open_jtalk: OpenJtalk) -> Synthesizer: - return Synthesizer(open_jtalk) +def synthesizer(onnxruntime: Onnxruntime, open_jtalk: OpenJtalk) -> Synthesizer: + return Synthesizer(onnxruntime, open_jtalk) + + +@pytest.fixture(scope="session") +def onnxruntime() -> Onnxruntime: + return Onnxruntime.load_once(filename=conftest.onnxruntime_filename) @pytest.fixture(scope="session") diff --git a/crates/voicevox_core_python_api/python/test/test_type_stub_consts.py b/crates/voicevox_core_python_api/python/test/test_type_stub_consts.py new file mode 100644 index 000000000..6a44d2771 --- /dev/null +++ b/crates/voicevox_core_python_api/python/test/test_type_stub_consts.py @@ -0,0 +1,50 @@ +"""pyiに書かれている定数の値が、本物と合致しているかをテストする。""" + +import ast +from ast import AnnAssign, ClassDef, Constant, Name +from pathlib import Path +from typing import Tuple + +import voicevox_core + + +def test() -> None: + REAL_BLOCKING = ( + voicevox_core.blocking.Onnxruntime.LIB_NAME, + voicevox_core.blocking.Onnxruntime.LIB_VERSION, + ) + REAL_ASYNCIO = ( + voicevox_core.asyncio.Onnxruntime.LIB_NAME, + voicevox_core.asyncio.Onnxruntime.LIB_VERSION, + ) + stub_blocking = extract(Path("./python/voicevox_core/_rust/blocking.pyi")) + stub_asyncio = extract(Path("./python/voicevox_core/_rust/asyncio.pyi")) + assert len({REAL_BLOCKING, REAL_ASYNCIO, stub_blocking, stub_asyncio}) == 1 + + +def extract(pyi: Path) -> Tuple[str, str]: + module = ast.parse(pyi.read_text(encoding="utf-8")) + class_def = next( + stmt + for stmt in module.body + if isinstance(stmt, ClassDef) and stmt.name == "Onnxruntime" + ) + lib_name_value = next( + stmt.value.value + for stmt in class_def.body + if isinstance(stmt, AnnAssign) + and isinstance(stmt.target, Name) + and stmt.target.id == "LIB_NAME" + and isinstance(stmt.value, Constant) + and isinstance(stmt.value.value, str) + ) + lib_version_value = next( + stmt.value.value + for stmt in class_def.body + if isinstance(stmt, AnnAssign) + and isinstance(stmt.target, Name) + and stmt.target.id == "LIB_VERSION" + and isinstance(stmt.value, Constant) + and isinstance(stmt.value.value, str) + ) + return (lib_name_value, lib_version_value) diff --git a/crates/voicevox_core_python_api/python/voicevox_core/__init__.py b/crates/voicevox_core_python_api/python/voicevox_core/__init__.py index 4ccbad3fe..ea1f246b9 100644 --- a/crates/voicevox_core_python_api/python/voicevox_core/__init__.py +++ b/crates/voicevox_core_python_api/python/voicevox_core/__init__.py @@ -18,7 +18,7 @@ ExtractFullContextLabelError, GetSupportedDevicesError, GpuSupportError, - InferenceFailedError, + InitInferenceRuntimeError, InvalidModelDataError, InvalidWordError, LoadUserDictError, @@ -28,13 +28,13 @@ OpenZipFileError, ParseKanaError, ReadZipEntryError, + RunModelError, SaveUserDictError, StyleAlreadyLoadedError, StyleNotFoundError, UseUserDictError, WordNotFoundError, __version__, - supported_devices, ) from . import asyncio, blocking # noqa: F401 isort: skip @@ -49,7 +49,7 @@ "ExtractFullContextLabelError", "GetSupportedDevicesError", "GpuSupportError", - "InferenceFailedError", + "InitInferenceRuntimeError", "InvalidModelDataError", "InvalidWordError", "LoadUserDictError", @@ -60,6 +60,7 @@ "OpenZipFileError", "ParseKanaError", "ReadZipEntryError", + "RunModelError", "SaveUserDictError", "SpeakerMeta", "StyleAlreadyLoadedError", @@ -67,7 +68,6 @@ "StyleNotFoundError", "StyleVersion", "SupportedDevices", - "supported_devices", "UseUserDictError", "UserDictWord", "UserDictWordType", diff --git a/crates/voicevox_core_python_api/python/voicevox_core/_load_dlls.py b/crates/voicevox_core_python_api/python/voicevox_core/_load_dlls.py index db8b4cdc5..2b90b9988 100644 --- a/crates/voicevox_core_python_api/python/voicevox_core/_load_dlls.py +++ b/crates/voicevox_core_python_api/python/voicevox_core/_load_dlls.py @@ -1,3 +1,5 @@ +# TODO: ここは #803 の時点でさほど必要性が無くなっているはずなので、(ドキュメントでの案内 +# はした上で)やめる import glob import platform from ctypes import CDLL 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 21a2016fe..941ed84fc 100644 --- a/crates/voicevox_core_python_api/python/voicevox_core/_models.py +++ b/crates/voicevox_core_python_api/python/voicevox_core/_models.py @@ -1,6 +1,7 @@ import dataclasses from enum import Enum from typing import List, NewType, Optional +from uuid import UUID import pydantic @@ -24,13 +25,13 @@ x : str """ -VoiceModelId = NewType("VoiceModelId", str) +VoiceModelId = NewType("VoiceModelId", UUID) """ 音声モデルID。 Parameters ---------- -x : str +x : UUID """ @@ -89,9 +90,9 @@ class SpeakerMeta: @pydantic.dataclasses.dataclass class SupportedDevices: """ - このライブラリで利用可能なデバイスの情報。 + ONNX Runtimeとして利用可能なデバイスの情報。 - あくまで本ライブラリが対応しているデバイスの情報であることに注意。GPUが使える環境ではなかったとしても + あくまでONNX Runtimeが対応しているデバイスの情報であることに注意。GPUが使える環境ではなかったとしても ``cuda`` や ``dml`` は ``True`` を示しうる。 """ 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 89a50d230..a456b1162 100644 --- a/crates/voicevox_core_python_api/python/voicevox_core/_rust/__init__.pyi +++ b/crates/voicevox_core_python_api/python/voicevox_core/_rust/__init__.pyi @@ -1,22 +1,5 @@ -from typing import TYPE_CHECKING - -if TYPE_CHECKING: - from voicevox_core import SupportedDevices - __version__: str -def supported_devices() -> SupportedDevices: - """ - このライブラリで利用可能なデバイスの情報を取得する。 - - .. code-block:: - - import voicevox_core - - supported_devices = voicevox_core.supported_devices() - """ - ... - class NotLoadedOpenjtalkDictError(Exception): """open_jtalk辞書ファイルが読み込まれていない。""" @@ -27,6 +10,11 @@ class GpuSupportError(Exception): ... +class InitInferenceRuntimeError(Exception): + """推論ライブラリのロードまたは初期化ができなかった。""" + + ... + class OpenZipFileError(Exception): """ZIPファイルを開くことに失敗した。""" @@ -72,7 +60,7 @@ class ModelNotFoundError(KeyError): ... -class InferenceFailedError(Exception): +class RunModelError(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 d8e9f6fe2..d6359e038 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 @@ -9,18 +9,19 @@ if TYPE_CHECKING: AudioQuery, SpeakerMeta, StyleId, + SupportedDevices, UserDictWord, VoiceModelId, ) -class VoiceModel: +class VoiceModelFile: """ - 音声モデル。""" + 音声モデルファイル。""" @staticmethod - async def from_path(path: Union[str, PathLike[str]]) -> VoiceModel: + async def open(path: Union[str, PathLike[str]]) -> VoiceModelFile: """ - VVMファイルから ``VoiceModel`` を生成する。 + VVMファイルを開く。 Parameters ---------- @@ -28,6 +29,14 @@ class VoiceModel: VVMファイルへのパス。 """ ... + async def close(self) -> None: + """ + VVMファイルを閉じる。 + + このメソッドが呼ばれた段階で :attr:`Synthesizer.load_voice_model` + からのアクセスが継続中の場合、アクセスが終わるまで待つ。 + """ + ... @property def id(self) -> VoiceModelId: """ID。""" @@ -36,6 +45,75 @@ class VoiceModel: def metas(self) -> List[SpeakerMeta]: """メタ情報。""" ... + async def __aenter__(self) -> "VoiceModelFile": ... + async def __aexit__(self, exc_type, exc_value, traceback) -> None: ... + +class Onnxruntime: + """ + ONNX Runtime。 + + シングルトンであり、インスタンスは高々一つ。 + + .. code-block:: + + ort1 = await Onnxruntime.load_once() + ort2 = Onnxruntime.get() + assert ort2 + assert ort2 is ort1 + + .. code-block:: + + ort = await voicevox_core.asyncio.Onnxruntime.load_once() + assert voicevox_core.blocking.Onnxruntime.get() + """ + + # ここの定数値が本物と合致するかどうかは、test_type_stub_consts.pyで担保する。 + + LIB_NAME: str = "onnxruntime" + """ONNX Runtimeのライブラリ名。""" + + LIB_VERSION: str = "1.17.3" + """推奨されるONNX Runtimeのバージョン。""" + + LIB_VERSIONED_FILENAME: str + """ + :attr:`LIB_NAME` と :attr:`LIB_VERSION` からなる動的ライブラリのファイル名。 + + WindowsとAndroidでは :attr:`LIB_UNVERSIONED_FILENAME` と同じ。 + """ + + LIB_UNVERSIONED_FILENAME: str + """:attr:`LIB_NAME` からなる動的ライブラリのファイル名。""" + + @staticmethod + def get() -> Union["Onnxruntime", None]: + """ + インスタンスが既に作られているならそれを得る。 + + 作られていなければ ``None`` を返す。 + """ + ... + @staticmethod + async def load_once(*, filename: str = LIB_VERSIONED_FILENAME) -> "Onnxruntime": + """ + ONNX Runtimeをロードして初期化する。 + + 一度成功したら、以後は引数を無視して同じインスタンスを返す。 + + Parameters + ---------- + filename + ONNX Runtimeのファイル名(モジュール名)もしくはファイルパス。 + ``dlopen``/`LoadLibraryExW + `_ + の引数に使われる。 + """ + ... + def supported_devices(self) -> SupportedDevices: + """ + このライブラリで利用可能なデバイスの情報を取得する。 + """ + ... class OpenJtalk: """ @@ -72,6 +150,8 @@ class Synthesizer: Parameters ---------- + onnxruntime + ONNX Runtime。 open_jtalk Open JTalk。 acceleration_mode @@ -82,6 +162,7 @@ class Synthesizer: def __init__( self, + onnxruntime: Onnxruntime, open_jtalk: OpenJtalk, acceleration_mode: Union[ AccelerationMode, Literal["AUTO", "CPU", "GPU"] @@ -89,8 +170,12 @@ class Synthesizer: cpu_num_threads: int = 0, ) -> None: ... def __repr__(self) -> str: ... - def __enter__(self) -> "Synthesizer": ... - def __exit__(self, exc_type, exc_value, traceback) -> None: ... + async def __aenter__(self) -> "Synthesizer": ... + async def __aexit__(self, exc_type, exc_value, traceback) -> None: ... + @property + def onnxruntime(self) -> Onnxruntime: + """ONNX Runtime。""" + ... @property def is_gpu_mode(self) -> bool: """ハードウェアアクセラレーションがGPUモードかどうか。""" @@ -99,7 +184,7 @@ class Synthesizer: def metas(self) -> List[SpeakerMeta]: """メタ情報。""" ... - async def load_voice_model(self, model: VoiceModel) -> None: + async def load_voice_model(self, model: VoiceModelFile) -> None: """ モデルを読み込む。 @@ -109,7 +194,7 @@ class Synthesizer: 読み込むモデルのスタイルID。 """ ... - def unload_voice_model(self, voice_model_id: Union[VoiceModelId, str]) -> None: + def unload_voice_model(self, voice_model_id: Union[VoiceModelId, UUID]) -> None: """ 音声モデルの読み込みを解除する。 @@ -119,7 +204,7 @@ class Synthesizer: 音声モデルID。 """ ... - def is_loaded_voice_model(self, voice_model_id: Union[VoiceModelId, str]) -> bool: + def is_loaded_voice_model(self, voice_model_id: Union[VoiceModelId, UUID]) -> bool: """ 指定したvoice_model_idのモデルが読み込まれているか判定する。 @@ -336,7 +421,7 @@ class Synthesizer: WAVデータ。 """ ... - def close(self) -> None: ... + async def close(self) -> None: ... class UserDict: """ユーザー辞書。""" @@ -346,7 +431,7 @@ class UserDict: """このオプジェクトの :class:`dict` としての表現。""" ... def __init__(self) -> None: ... - async def load(self, path: str) -> None: + async def load(self, path: Union[str, PathLike[str]]) -> None: """ファイルに保存されたユーザー辞書を読み込む。 Parameters @@ -355,7 +440,7 @@ class UserDict: ユーザー辞書のパス。 """ ... - async def save(self, path: str) -> None: + async def save(self, path: Union[str, PathLike[str]]) -> None: """ ユーザー辞書をファイルに保存する。 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 5584d68bb..cf4f1f5c6 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 @@ -9,18 +9,19 @@ if TYPE_CHECKING: AudioQuery, SpeakerMeta, StyleId, + SupportedDevices, UserDictWord, VoiceModelId, ) -class VoiceModel: +class VoiceModelFile: """ - 音声モデル。""" + 音声モデルファイル。""" @staticmethod - def from_path(path: Union[str, PathLike[str]]) -> VoiceModel: + def open(path: Union[str, PathLike[str]]) -> VoiceModelFile: """ - VVMファイルから ``VoiceModel`` を生成する。 + VVMファイルを開く。 Parameters ---------- @@ -28,6 +29,14 @@ class VoiceModel: VVMファイルへのパス。 """ ... + def close(self) -> None: + """ + VVMファイルを閉じる。 + + このメソッドが呼ばれた段階で :attr:`Synthesizer.load_voice_model` + からのアクセスが継続中の場合、アクセスが終わるまで待つ。 + """ + ... @property def id(self) -> VoiceModelId: """ID。""" @@ -36,6 +45,75 @@ class VoiceModel: def metas(self) -> List[SpeakerMeta]: """メタ情報。""" ... + def __enter__(self) -> "VoiceModelFile": ... + def __exit__(self, exc_type, exc_value, traceback) -> None: ... + +class Onnxruntime: + """ + ONNX Runtime。 + + シングルトンであり、インスタンスは高々一つ。 + + .. code-block:: + + ort1 = Onnxruntime.load_once() + ort2 = Onnxruntime.get() + assert ort2 + assert ort2 is ort1 + + .. code-block:: + + ort = voicevox_core.blocking.Onnxruntime.load_once() + assert voicevox_core.asyncio.Onnxruntime.get() + """ + + # ここの定数値が本物と合致するかどうかは、test_type_stub_consts.pyで担保する。 + + LIB_NAME: str = "onnxruntime" + """ONNX Runtimeのライブラリ名。""" + + LIB_VERSION: str = "1.17.3" + """推奨されるONNX Runtimeのバージョン。""" + + LIB_VERSIONED_FILENAME: str + """ + :attr:`LIB_NAME` と :attr:`LIB_VERSION` からなる動的ライブラリのファイル名。 + + WindowsとAndroidでは :attr:`LIB_UNVERSIONED_FILENAME` と同じ。 + """ + + LIB_UNVERSIONED_FILENAME: str + """:attr:`LIB_NAME` からなる動的ライブラリのファイル名。""" + + @staticmethod + def get() -> Union["Onnxruntime", None]: + """ + インスタンスが既に作られているならそれを得る。 + + 作られていなければ ``None`` を返す。 + """ + ... + @staticmethod + def load_once(*, filename: str = LIB_VERSIONED_FILENAME) -> "Onnxruntime": + """ + ONNX Runtimeをロードして初期化する。 + + 一度成功したら、以後は引数を無視して同じインスタンスを返す。 + + Parameters + ---------- + filename + ONNX Runtimeのファイル名(モジュール名)もしくはファイルパス。 + ``dlopen``/`LoadLibraryExW + `_ + の引数に使われる。 + """ + ... + def supported_devices(self) -> SupportedDevices: + """ + このライブラリで利用可能なデバイスの情報を取得する。 + """ + ... class OpenJtalk: """ @@ -67,6 +145,8 @@ class Synthesizer: Parameters ---------- + onnxruntime + ONNX Runtime。 open_jtalk Open JTalk。 acceleration_mode @@ -77,6 +157,7 @@ class Synthesizer: def __init__( self, + onnxruntime: Onnxruntime, open_jtalk: OpenJtalk, acceleration_mode: Union[ AccelerationMode, Literal["AUTO", "CPU", "GPU"] @@ -87,6 +168,10 @@ class Synthesizer: def __enter__(self) -> "Synthesizer": ... def __exit__(self, exc_type, exc_value, traceback) -> None: ... @property + def onnxruntime(self) -> Onnxruntime: + """ONNX Runtime。""" + ... + @property def is_gpu_mode(self) -> bool: """ハードウェアアクセラレーションがGPUモードかどうか。""" ... @@ -94,7 +179,7 @@ class Synthesizer: def metas(self) -> List[SpeakerMeta]: """メタ情報。""" ... - def load_voice_model(self, model: VoiceModel) -> None: + def load_voice_model(self, model: VoiceModelFile) -> None: """ モデルを読み込む。 @@ -104,7 +189,7 @@ class Synthesizer: 読み込むモデルのスタイルID。 """ ... - def unload_voice_model(self, voice_model_id: Union[VoiceModelId, str]) -> None: + def unload_voice_model(self, voice_model_id: Union[VoiceModelId, UUID]) -> None: """ 音声モデルの読み込みを解除する。 @@ -114,7 +199,7 @@ class Synthesizer: 音声モデルID。 """ ... - def is_loaded_voice_model(self, voice_model_id: Union[VoiceModelId, str]) -> bool: + def is_loaded_voice_model(self, voice_model_id: Union[VoiceModelId, UUID]) -> bool: """ 指定したvoice_model_idのモデルが読み込まれているか判定する。 @@ -341,7 +426,7 @@ class UserDict: """このオプジェクトの :class:`dict` としての表現。""" ... def __init__(self) -> None: ... - def load(self, path: str) -> None: + def load(self, path: Union[str, PathLike[str]]) -> None: """ファイルに保存されたユーザー辞書を読み込む。 Parameters @@ -350,7 +435,7 @@ class UserDict: ユーザー辞書のパス。 """ ... - def save(self, path: str) -> None: + def save(self, path: Union[str, PathLike[str]]) -> 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 75b160814..0dc5e0adb 100644 --- a/crates/voicevox_core_python_api/python/voicevox_core/asyncio.py +++ b/crates/voicevox_core_python_api/python/voicevox_core/asyncio.py @@ -1,4 +1,4 @@ # pyright: reportMissingModuleSource=false -from ._rust.asyncio import OpenJtalk, Synthesizer, UserDict, VoiceModel +from ._rust.asyncio import Onnxruntime, OpenJtalk, Synthesizer, UserDict, VoiceModelFile -__all__ = ["OpenJtalk", "Synthesizer", "UserDict", "VoiceModel"] +__all__ = ["Onnxruntime", "OpenJtalk", "Synthesizer", "UserDict", "VoiceModelFile"] 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 80f61fdcb..01ea45029 100644 --- a/crates/voicevox_core_python_api/python/voicevox_core/blocking.py +++ b/crates/voicevox_core_python_api/python/voicevox_core/blocking.py @@ -1,4 +1,10 @@ # pyright: reportMissingModuleSource=false -from ._rust.blocking import OpenJtalk, Synthesizer, UserDict, VoiceModel +from ._rust.blocking import ( + Onnxruntime, + OpenJtalk, + Synthesizer, + UserDict, + VoiceModelFile, +) -__all__ = ["OpenJtalk", "Synthesizer", "UserDict", "VoiceModel"] +__all__ = ["Onnxruntime", "OpenJtalk", "Synthesizer", "UserDict", "VoiceModelFile"] diff --git a/crates/voicevox_core_python_api/src/convert.rs b/crates/voicevox_core_python_api/src/convert.rs index 3cee4186b..d4a867606 100644 --- a/crates/voicevox_core_python_api/src/convert.rs +++ b/crates/voicevox_core_python_api/src/convert.rs @@ -1,25 +1,23 @@ -use std::{error::Error as _, future::Future, iter, path::PathBuf}; +use std::{error::Error as _, future::Future, iter, panic, path::PathBuf}; use camino::Utf8PathBuf; use easy_ext::ext; use pyo3::{ - exceptions::{PyException, PyValueError}, - types::PyList, - FromPyObject as _, PyAny, PyObject, PyResult, Python, ToPyObject, + exceptions::{PyException, PyRuntimeError, PyValueError}, + types::{IntoPyDict as _, PyList}, + FromPyObject as _, IntoPy, PyAny, PyObject, PyResult, Python, ToPyObject, }; use serde::{de::DeserializeOwned, Serialize}; use serde_json::json; use uuid::Uuid; -use voicevox_core::{ - AccelerationMode, AccentPhraseModel, StyleId, UserDictWordType, VoiceModelMeta, -}; +use voicevox_core::{AccelerationMode, AccentPhrase, StyleId, UserDictWordType, VoiceModelMeta}; use crate::{ - ExtractFullContextLabelError, GetSupportedDevicesError, GpuSupportError, InferenceFailedError, - InvalidModelDataError, InvalidModelFormatError, InvalidWordError, LoadUserDictError, - ModelAlreadyLoadedError, ModelNotFoundError, NotLoadedOpenjtalkDictError, OpenZipFileError, - ParseKanaError, ReadZipEntryError, SaveUserDictError, StyleAlreadyLoadedError, - StyleNotFoundError, UseUserDictError, WordNotFoundError, + ExtractFullContextLabelError, GetSupportedDevicesError, GpuSupportError, + InitInferenceRuntimeError, InvalidModelDataError, InvalidModelFormatError, InvalidWordError, + LoadUserDictError, ModelAlreadyLoadedError, ModelNotFoundError, NotLoadedOpenjtalkDictError, + OpenZipFileError, ParseKanaError, ReadZipEntryError, RunModelError, SaveUserDictError, + StyleAlreadyLoadedError, StyleNotFoundError, UseUserDictError, WordNotFoundError, }; pub(crate) fn from_acceleration_mode(ob: &PyAny) -> PyResult { @@ -39,7 +37,6 @@ pub(crate) fn from_acceleration_mode(ob: &PyAny) -> PyResult { } } -// FIXME: `UserDict`についてはこれではなく、`PathBuf::extract`を直接使うようにする pub(crate) fn from_utf8_path(ob: &PyAny) -> PyResult { PathBuf::extract(ob)? .into_os_string() @@ -62,16 +59,17 @@ pub(crate) fn from_dataclass(ob: &PyAny) -> PyResult { pub(crate) fn to_pydantic_voice_model_meta<'py>( metas: &VoiceModelMeta, py: Python<'py>, -) -> PyResult> { +) -> PyResult<&'py PyList> { let class = py .import("voicevox_core")? .getattr("SpeakerMeta")? .downcast()?; - metas + let metas = metas .iter() .map(|m| to_pydantic_dataclass(m, class)) - .collect::>>() + .collect::>>()?; + Ok(PyList::new(py, metas)) } pub(crate) fn to_pydantic_dataclass(x: impl Serialize, class: &PyAny) -> PyResult<&PyAny> { @@ -86,15 +84,12 @@ pub(crate) fn blocking_modify_accent_phrases<'py>( accent_phrases: &'py PyList, speaker_id: StyleId, py: Python<'py>, - method: impl FnOnce( - Vec, - StyleId, - ) -> voicevox_core::Result>, + method: impl FnOnce(Vec, StyleId) -> voicevox_core::Result>, ) -> PyResult> { let rust_accent_phrases = accent_phrases .iter() .map(from_dataclass) - .collect::>>()?; + .collect::>>()?; method(rust_accent_phrases, speaker_id) .into_py_result(py)? @@ -115,13 +110,13 @@ pub(crate) fn async_modify_accent_phrases<'py, Fun, Fut>( method: Fun, ) -> PyResult<&'py PyAny> where - Fun: FnOnce(Vec, StyleId) -> Fut + Send + 'static, - Fut: Future>> + Send + 'static, + Fun: FnOnce(Vec, StyleId) -> Fut + Send + 'static, + Fut: Future>> + Send + 'static, { let rust_accent_phrases = accent_phrases .iter() .map(from_dataclass) - .collect::>>()?; + .collect::>>()?; pyo3_asyncio::tokio::future_into_py_with_locals( py, pyo3_asyncio::tokio::get_current_locals(py)?, @@ -180,6 +175,45 @@ pub(crate) fn to_rust_word_type(word_type: &PyAny) -> PyResult serde_json::from_value::(json!(name)).into_py_value_result() } +/// おおよそ以下のコードにおける`f(x)`のようなものを得る。 +/// +/// ```py +/// async def f(x_): +/// return x_ +/// +/// return f(x) +/// ``` +pub(crate) fn ready(x: impl IntoPy, py: Python<'_>) -> PyResult<&PyAny> { + // ```py + // from asyncio import Future + // + // running_loop = asyncio.get_running_loop() + // fut = Future(loop=running_loop) + // fut.set_result(x) + // return fut + // ``` + + let asyncio_future = py.import("asyncio")?.getattr("Future")?; + + let running_loop = pyo3_asyncio::get_running_loop(py)?; + let fut = asyncio_future.call((), Some([("loop", running_loop)].into_py_dict(py)))?; + fut.call_method1("set_result", (x,))?; + Ok(fut) +} + +pub(crate) async fn run_in_executor(f: F) -> PyResult +where + F: FnOnce() -> R + Send + 'static, + R: Send + 'static, +{ + tokio::task::spawn_blocking(f) + .await + .map_err(|e| match e.try_into_panic() { + Ok(p) => panic::resume_unwind(p), + Err(e) => PyRuntimeError::new_err(e.to_string()), + }) +} + #[ext(VoicevoxCoreResultExt)] pub(crate) impl voicevox_core::Result { fn into_py_result(self, py: Python<'_>) -> PyResult { @@ -190,6 +224,7 @@ pub(crate) impl voicevox_core::Result { let top = match err.kind() { ErrorKind::NotLoadedOpenjtalkDict => NotLoadedOpenjtalkDictError::new_err(msg), ErrorKind::GpuSupport => GpuSupportError::new_err(msg), + ErrorKind::InitInferenceRuntime => InitInferenceRuntimeError::new_err(msg), ErrorKind::OpenZipFile => OpenZipFileError::new_err(msg), ErrorKind::ReadZipEntry => ReadZipEntryError::new_err(msg), ErrorKind::ModelAlreadyLoaded => ModelAlreadyLoadedError::new_err(msg), @@ -199,7 +234,7 @@ pub(crate) impl voicevox_core::Result { ErrorKind::GetSupportedDevices => GetSupportedDevicesError::new_err(msg), ErrorKind::StyleNotFound => StyleNotFoundError::new_err(msg), ErrorKind::ModelNotFound => ModelNotFoundError::new_err(msg), - ErrorKind::InferenceFailed => InferenceFailedError::new_err(msg), + ErrorKind::RunModel => RunModelError::new_err(msg), ErrorKind::ExtractFullContextLabel => ExtractFullContextLabelError::new_err(msg), ErrorKind::ParseKana => ParseKanaError::new_err(msg), ErrorKind::LoadUserDict => LoadUserDictError::new_err(msg), diff --git a/crates/voicevox_core_python_api/src/lib.rs b/crates/voicevox_core_python_api/src/lib.rs index 492d18f0e..24d28261d 100644 --- a/crates/voicevox_core_python_api/src/lib.rs +++ b/crates/voicevox_core_python_api/src/lib.rs @@ -1,16 +1,21 @@ -use std::marker::PhantomData; +use std::{ + marker::PhantomData, + mem, + ops::{Deref, DerefMut}, +}; mod convert; -use self::convert::{from_utf8_path, to_pydantic_dataclass, VoicevoxCoreResultExt as _}; +use self::convert::{from_utf8_path, VoicevoxCoreResultExt as _}; use easy_ext::ext; -use log::debug; +use log::{debug, warn}; use pyo3::{ create_exception, exceptions::{PyException, PyKeyError, PyValueError}, pyfunction, pymodule, - types::PyModule, - wrap_pyfunction, PyAny, PyResult, PyTypeInfo, Python, + types::{PyList, PyModule}, + wrap_pyfunction, Py, PyObject, PyResult, PyTypeInfo, Python, }; +use voicevox_core::__internal::interop::raii::MaybeClosed; #[pymodule] #[pyo3(name = "_rust")] @@ -18,7 +23,6 @@ fn rust(py: Python<'_>, module: &PyModule) -> PyResult<()> { pyo3_log::init(); module.add("__version__", env!("CARGO_PKG_VERSION"))?; - module.add_wrapped(wrap_pyfunction!(supported_devices))?; module.add_wrapped(wrap_pyfunction!(_validate_pronunciation))?; module.add_wrapped(wrap_pyfunction!(_to_zenkaku))?; @@ -26,15 +30,17 @@ fn rust(py: Python<'_>, module: &PyModule) -> PyResult<()> { let blocking_module = PyModule::new(py, "voicevox_core._rust.blocking")?; blocking_module.add_class::()?; + blocking_module.add_class::()?; blocking_module.add_class::()?; - blocking_module.add_class::()?; + blocking_module.add_class::()?; blocking_module.add_class::()?; module.add_and_register_submodule(blocking_module)?; let asyncio_module = PyModule::new(py, "voicevox_core._rust.asyncio")?; asyncio_module.add_class::()?; + asyncio_module.add_class::()?; asyncio_module.add_class::()?; - asyncio_module.add_class::()?; + asyncio_module.add_class::()?; asyncio_module.add_class::()?; module.add_and_register_submodule(asyncio_module) } @@ -67,6 +73,7 @@ macro_rules! exceptions { exceptions! { NotLoadedOpenjtalkDictError: PyException; GpuSupportError: PyException; + InitInferenceRuntimeError: PyException; OpenZipFileError: PyException; ReadZipEntryError: PyException; ModelAlreadyLoadedError: PyException; @@ -76,7 +83,7 @@ exceptions! { GetSupportedDevicesError: PyException; StyleNotFoundError: PyKeyError; ModelNotFoundError: PyKeyError; - InferenceFailedError: PyException; + RunModelError: PyException; ExtractFullContextLabelError: PyException; ParseKanaError: PyValueError; LoadUserDictError: PyException; @@ -86,58 +93,165 @@ exceptions! { InvalidWordError: PyValueError; } -#[pyfunction] -fn supported_devices(py: Python<'_>) -> PyResult<&PyAny> { - let class = py - .import("voicevox_core")? - .getattr("SupportedDevices")? - .downcast()?; - let s = voicevox_core::SupportedDevices::create().into_py_result(py)?; - to_pydantic_dataclass(s, class) -} - -struct Closable { - content: MaybeClosed, - marker: PhantomData, -} - -enum MaybeClosed { - Open(T), - Closed, +struct Closable { + content: A::RwLock>, + marker: PhantomData<(C, A)>, } -impl Closable { +impl Closable { fn new(content: T) -> Self { Self { - content: MaybeClosed::Open(content), + content: MaybeClosed::Open(content).into(), marker: PhantomData, } } - fn get(&self) -> PyResult<&T> { - match &self.content { + fn read(&self) -> PyResult + '_> { + let lock = self + .content + .try_read_() + .map_err(|_| PyValueError::new_err(format!("The `{}` is being closed", C::NAME)))?; + + voicevox_core::__internal::interop::raii::try_map_guard(lock, |lock| match &**lock { MaybeClosed::Open(content) => Ok(content), MaybeClosed::Closed => Err(PyValueError::new_err(format!( "The `{}` is closed", C::NAME, ))), - } + }) } - fn close(&mut self) { - if matches!(self.content, MaybeClosed::Open(_)) { + async fn close_(&self) -> Option { + let lock = &mut *match self.content.try_write_() { + Ok(lock) => lock, + Err(()) => { + warn!("The `{}` is still in use. Waiting before closing", C::NAME); + self.content.write_().await + } + }; + + if matches!(*lock, MaybeClosed::Open(_)) { debug!("Closing a {}", C::NAME); } - self.content = MaybeClosed::Closed; + match mem::replace(lock, MaybeClosed::Closed) { + MaybeClosed::Open(content) => Some(content), + MaybeClosed::Closed => None, + } } } -impl Drop for Closable { +impl Closable { + #[must_use = "中身は明示的に`drop`でdropすること"] + fn close(&self) -> Option { + futures_lite::future::block_on(self.close_()) + } +} + +impl Closable { + #[must_use = "中身は明示的に`drop`でdropすること"] + async fn close(&self) -> Option { + self.close_().await + } +} + +impl Drop for Closable { fn drop(&mut self) { - self.close(); + let content = mem::replace(self.content.get_mut_(), MaybeClosed::Closed); + if matches!(content, MaybeClosed::Open(_)) { + warn!( + "デストラクタにより`{}`のクローズを行います。通常は、可能な限り`{}`でクローズする\ + ようにして下さい", + C::NAME, + A::EXIT_METHOD, + ); + drop(content); + } + } +} + +trait Async { + const EXIT_METHOD: &str; + type RwLock: RwLock; +} + +enum SingleTasked {} +enum Tokio {} + +impl Async for SingleTasked { + const EXIT_METHOD: &str = "__exit__"; + type RwLock = std::sync::RwLock; +} + +impl Async for Tokio { + const EXIT_METHOD: &str = "__aexit__"; + type RwLock = tokio::sync::RwLock; +} + +trait RwLock: From { + type Item; + type RwLockWriteGuard<'a>: DerefMut + where + Self: 'a; + fn try_read_(&self) -> Result, ()>; + async fn write_(&self) -> Self::RwLockWriteGuard<'_>; + fn try_write_(&self) -> Result, ()>; + fn get_mut_(&mut self) -> &mut Self::Item; +} + +impl RwLock for std::sync::RwLock { + type Item = T; + type RwLockWriteGuard<'a> = std::sync::RwLockWriteGuard<'a, Self::Item> where Self: 'a; + + fn try_read_(&self) -> Result, ()> { + self.try_read().map_err(|e| match e { + std::sync::TryLockError::Poisoned(e) => panic!("{e}"), + std::sync::TryLockError::WouldBlock => (), + }) + } + + async fn write_(&self) -> Self::RwLockWriteGuard<'_> { + self.write().unwrap_or_else(|e| panic!("{e}")) + } + + fn try_write_(&self) -> Result, ()> { + self.try_write().map_err(|e| match e { + std::sync::TryLockError::Poisoned(e) => panic!("{e}"), + std::sync::TryLockError::WouldBlock => (), + }) + } + + fn get_mut_(&mut self) -> &mut Self::Item { + self.get_mut().unwrap_or_else(|e| panic!("{e}")) } } +impl RwLock for tokio::sync::RwLock { + type Item = T; + type RwLockWriteGuard<'a> = tokio::sync::RwLockWriteGuard<'a, Self::Item> where Self: 'a; + + fn try_read_(&self) -> Result, ()> { + self.try_read().map_err(|_| ()) + } + + async fn write_(&self) -> Self::RwLockWriteGuard<'_> { + self.write().await + } + + fn try_write_(&self) -> Result, ()> { + self.try_write().map_err(|_| ()) + } + + fn get_mut_(&mut self) -> &mut Self::Item { + self.get_mut() + } +} + +#[derive(Clone)] +struct VoiceModelFilePyFields { + id: PyObject, // `NewType("VoiceModelId", UUID)` + metas: Py, // `list[SpeakerMeta]` +} + #[pyfunction] fn _validate_pronunciation(pronunciation: &str, py: Python<'_>) -> PyResult<()> { voicevox_core::__internal::validate_pronunciation(pronunciation).into_py_result(py) @@ -149,44 +263,139 @@ fn _to_zenkaku(text: &str) -> PyResult { } mod blocking { - use std::{path::PathBuf, sync::Arc}; + use std::{ffi::OsString, path::PathBuf, sync::Arc}; use camino::Utf8PathBuf; use pyo3::{ pyclass, pymethods, - types::{IntoPyDict as _, PyBytes, PyDict, PyList, PyString}, - PyAny, PyObject, PyRef, PyResult, Python, + types::{IntoPyDict as _, PyBytes, PyDict, PyList}, + Py, PyAny, PyObject, PyRef, PyResult, Python, }; use uuid::Uuid; use voicevox_core::{ - AccelerationMode, AudioQueryModel, InitializeOptions, StyleId, SynthesisOptions, - TtsOptions, UserDictWord, VoiceModelId, + AccelerationMode, AudioQuery, InitializeOptions, StyleId, SynthesisOptions, TtsOptions, + UserDictWord, }; - use crate::{convert::VoicevoxCoreResultExt as _, Closable}; + use crate::{ + convert::VoicevoxCoreResultExt as _, Closable, SingleTasked, VoiceModelFilePyFields, + }; #[pyclass] #[derive(Clone)] - pub(crate) struct VoiceModel { - model: voicevox_core::blocking::VoiceModel, + pub(crate) struct VoiceModelFile { + model: Arc>, + fields: VoiceModelFilePyFields, } #[pymethods] - impl VoiceModel { + impl VoiceModelFile { #[staticmethod] - fn from_path(py: Python<'_>, path: PathBuf) -> PyResult { - let model = voicevox_core::blocking::VoiceModel::from_path(path).into_py_result(py)?; - Ok(Self { model }) + fn open(py: Python<'_>, path: PathBuf) -> PyResult { + let model = voicevox_core::blocking::VoiceModelFile::open(path).into_py_result(py)?; + + let id = crate::convert::to_py_uuid(py, model.id().raw_voice_model_id())?; + let metas = crate::convert::to_pydantic_voice_model_meta(model.metas(), py)?.into(); + + let model = Closable::new(model).into(); + + Ok(Self { + model, + fields: VoiceModelFilePyFields { id, metas }, + }) + } + + fn close(&self) { + let this = self.model.close(); + drop(this); } #[getter] - fn id(&self) -> &str { - self.model.id().raw_voice_model_id() + fn id(&self) -> PyObject { + self.fields.id.clone() } #[getter] - fn metas<'py>(&self, py: Python<'py>) -> Vec<&'py PyAny> { - crate::convert::to_pydantic_voice_model_meta(self.model.metas(), py).unwrap() + fn metas(&self) -> Py { + self.fields.metas.clone() + } + + fn __enter__(slf: PyRef<'_, Self>) -> PyResult> { + slf.model.read()?; + Ok(slf) + } + + fn __exit__( + &self, + #[expect(unused_variables, reason = "`__exit__`としては必要")] exc_type: &PyAny, + #[expect(unused_variables, reason = "`__exit__`としては必要")] exc_value: &PyAny, + #[expect(unused_variables, reason = "`__exit__`としては必要")] traceback: &PyAny, + ) { + self.close(); + } + } + + static ONNXRUNTIME: once_cell::sync::OnceCell> = + once_cell::sync::OnceCell::new(); + + #[pyclass] + #[derive(Clone)] + pub(crate) struct Onnxruntime(&'static voicevox_core::blocking::Onnxruntime); + + #[pymethods] + impl Onnxruntime { + #[classattr] + const LIB_NAME: &'static str = voicevox_core::blocking::Onnxruntime::LIB_NAME; + + #[classattr] + const LIB_VERSION: &'static str = voicevox_core::blocking::Onnxruntime::LIB_VERSION; + + #[classattr] + const LIB_VERSIONED_FILENAME: &'static str = + voicevox_core::blocking::Onnxruntime::LIB_VERSIONED_FILENAME; + + #[classattr] + const LIB_UNVERSIONED_FILENAME: &'static str = + voicevox_core::blocking::Onnxruntime::LIB_UNVERSIONED_FILENAME; + + #[staticmethod] + fn get(py: Python<'_>) -> PyResult>> { + let result = ONNXRUNTIME.get_or_try_init(|| { + match voicevox_core::blocking::Onnxruntime::get().map(|o| Py::new(py, Self(o))) { + Some(Ok(this)) => Ok(this), + Some(Err(err)) => Err(Some(err)), + None => Err(None), + } + }); + + match result { + Ok(this) => Ok(Some(this.clone())), + Err(Some(err)) => Err(err), + Err(None) => Ok(None), + } + } + + #[staticmethod] + #[pyo3(signature = (*, filename = Self::LIB_VERSIONED_FILENAME.into()))] + fn load_once(filename: OsString, py: Python<'_>) -> PyResult> { + ONNXRUNTIME + .get_or_try_init(|| { + let inner = voicevox_core::blocking::Onnxruntime::load_once() + .filename(filename) + .exec() + .into_py_result(py)?; + Py::new(py, Self(inner)) + }) + .cloned() + } + + fn supported_devices<'py>(&self, py: Python<'py>) -> PyResult<&'py PyAny> { + let class = py + .import("voicevox_core")? + .getattr("SupportedDevices")? + .downcast()?; + let s = self.0.supported_devices().into_py_result(py)?; + crate::convert::to_pydantic_dataclass(s, class) } } @@ -220,6 +429,7 @@ mod blocking { synthesizer: Closable< voicevox_core::blocking::Synthesizer, Self, + SingleTasked, >, } @@ -227,11 +437,13 @@ mod blocking { impl Synthesizer { #[new] #[pyo3(signature =( + onnxruntime, open_jtalk, acceleration_mode = InitializeOptions::default().acceleration_mode, cpu_num_threads = InitializeOptions::default().cpu_num_threads, ))] fn new( + onnxruntime: Onnxruntime, open_jtalk: OpenJtalk, #[pyo3(from_py_with = "crate::convert::from_acceleration_mode")] acceleration_mode: AccelerationMode, @@ -239,6 +451,7 @@ mod blocking { py: Python<'_>, ) -> PyResult { let inner = voicevox_core::blocking::Synthesizer::new( + onnxruntime.0, open_jtalk.open_jtalk.clone(), &InitializeOptions { acceleration_mode, @@ -256,56 +469,62 @@ mod blocking { } fn __enter__(slf: PyRef<'_, Self>) -> PyResult> { - slf.synthesizer.get()?; + slf.synthesizer.read()?; Ok(slf) } fn __exit__( &mut self, - #[allow(unused_variables)] exc_type: &PyAny, - #[allow(unused_variables)] exc_value: &PyAny, - #[allow(unused_variables)] traceback: &PyAny, + #[expect(unused_variables, reason = "`__exit__`としては必要")] exc_type: &PyAny, + #[expect(unused_variables, reason = "`__exit__`としては必要")] exc_value: &PyAny, + #[expect(unused_variables, reason = "`__exit__`としては必要")] traceback: &PyAny, ) { self.close(); } + #[getter] + fn onnxruntime(&self) -> Py { + ONNXRUNTIME.get().expect("should be initialized").clone() + } + #[getter] fn is_gpu_mode(&self) -> PyResult { - let synthesizer = self.synthesizer.get()?; + let synthesizer = self.synthesizer.read()?; Ok(synthesizer.is_gpu_mode()) } #[getter] - fn metas<'py>(&self, py: Python<'py>) -> PyResult> { - let synthesizer = self.synthesizer.get()?; + fn metas<'py>(&self, py: Python<'py>) -> PyResult<&'py PyList> { + let synthesizer = self.synthesizer.read()?; crate::convert::to_pydantic_voice_model_meta(&synthesizer.metas(), py) } fn load_voice_model(&mut self, model: &PyAny, py: Python<'_>) -> PyResult<()> { - let model: VoiceModel = model.extract()?; - self.synthesizer - .get()? - .load_voice_model(&model.model) - .into_py_result(py) + let this = self.synthesizer.read()?; + let model = model.extract::()?; + let model = &model.model.read()?; + this.load_voice_model(model).into_py_result(py) } - fn unload_voice_model(&mut self, voice_model_id: &str, py: Python<'_>) -> PyResult<()> { + fn unload_voice_model( + &mut self, + #[pyo3(from_py_with = "crate::convert::to_rust_uuid")] voice_model_id: Uuid, + py: Python<'_>, + ) -> PyResult<()> { self.synthesizer - .get()? - .unload_voice_model(&VoiceModelId::new(voice_model_id.to_string())) + .read()? + .unload_voice_model(voice_model_id.into()) .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); - }; + fn is_loaded_voice_model( + &self, + #[pyo3(from_py_with = "crate::convert::to_rust_uuid")] voice_model_id: Uuid, + ) -> PyResult { Ok(self .synthesizer - .get()? - .is_loaded_voice_model(&VoiceModelId::new(voice_model_id.to_string()))) + .read()? + .is_loaded_voice_model(voice_model_id.into())) } fn audio_query_from_kana<'py>( @@ -314,7 +533,7 @@ mod blocking { style_id: u32, py: Python<'py>, ) -> PyResult<&'py PyAny> { - let synthesizer = self.synthesizer.get()?; + let synthesizer = self.synthesizer.read()?; let audio_query = synthesizer .audio_query_from_kana(kana, StyleId::new(style_id)) @@ -330,7 +549,7 @@ mod blocking { style_id: u32, py: Python<'py>, ) -> PyResult<&'py PyAny> { - let synthesizesr = self.synthesizer.get()?; + let synthesizesr = self.synthesizer.read()?; let audio_query = synthesizesr .audio_query(text, StyleId::new(style_id)) @@ -346,7 +565,7 @@ mod blocking { style_id: u32, py: Python<'py>, ) -> PyResult> { - let synthesizer = self.synthesizer.get()?; + let synthesizer = self.synthesizer.read()?; let accent_phrases = synthesizer .create_accent_phrases_from_kana(kana, StyleId::new(style_id)) @@ -365,7 +584,7 @@ mod blocking { style_id: u32, py: Python<'py>, ) -> PyResult> { - let synthesizer = self.synthesizer.get()?; + let synthesizer = self.synthesizer.read()?; let accent_phrases = synthesizer .create_accent_phrases(text, StyleId::new(style_id)) @@ -384,7 +603,7 @@ mod blocking { style_id: u32, py: Python<'py>, ) -> PyResult> { - let synthesizer = self.synthesizer.get()?; + let synthesizer = self.synthesizer.read()?; crate::convert::blocking_modify_accent_phrases( accent_phrases, StyleId::new(style_id), @@ -399,7 +618,7 @@ mod blocking { style_id: u32, py: Python<'py>, ) -> PyResult> { - let synthesizer = self.synthesizer.get()?; + let synthesizer = self.synthesizer.read()?; crate::convert::blocking_modify_accent_phrases( accent_phrases, StyleId::new(style_id), @@ -414,7 +633,7 @@ mod blocking { style_id: u32, py: Python<'py>, ) -> PyResult> { - let synthesizer = self.synthesizer.get()?; + let synthesizer = self.synthesizer.read()?; crate::convert::blocking_modify_accent_phrases( accent_phrases, StyleId::new(style_id), @@ -430,14 +649,14 @@ mod blocking { ))] fn synthesis<'py>( &self, - #[pyo3(from_py_with = "crate::convert::from_dataclass")] audio_query: AudioQueryModel, + #[pyo3(from_py_with = "crate::convert::from_dataclass")] audio_query: AudioQuery, style_id: u32, enable_interrogative_upspeak: bool, py: Python<'py>, ) -> PyResult<&'py PyBytes> { let wav = &self .synthesizer - .get()? + .read()? .synthesis( &audio_query, StyleId::new(style_id), @@ -467,7 +686,7 @@ mod blocking { }; let wav = &self .synthesizer - .get()? + .read()? .tts_from_kana(kana, style_id, options) .into_py_result(py)?; Ok(PyBytes::new(py, wav)) @@ -491,14 +710,14 @@ mod blocking { }; let wav = &self .synthesizer - .get()? + .read()? .tts(text, style_id, options) .into_py_result(py)?; Ok(PyBytes::new(py, wav)) } fn close(&mut self) { - self.synthesizer.close() + drop(self.synthesizer.close()); } } @@ -515,11 +734,11 @@ mod blocking { Self::default() } - fn load(&self, path: &str, py: Python<'_>) -> PyResult<()> { + fn load(&self, path: PathBuf, py: Python<'_>) -> PyResult<()> { self.dict.load(path).into_py_result(py) } - fn save(&self, path: &str, py: Python<'_>) -> PyResult<()> { + fn save(&self, path: PathBuf, py: Python<'_>) -> PyResult<()> { self.dict.save(path).into_py_result(py) } @@ -574,59 +793,167 @@ mod blocking { } mod asyncio { - use std::{path::PathBuf, sync::Arc}; + use std::{ffi::OsString, 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 _, + types::{IntoPyDict as _, PyBytes, PyDict, PyList}, + Py, PyAny, PyErr, PyObject, PyRef, PyResult, Python, ToPyObject as _, }; use uuid::Uuid; use voicevox_core::{ - AccelerationMode, AudioQueryModel, InitializeOptions, StyleId, SynthesisOptions, - TtsOptions, UserDictWord, VoiceModelId, + AccelerationMode, AudioQuery, InitializeOptions, StyleId, SynthesisOptions, TtsOptions, + UserDictWord, }; - use crate::{convert::VoicevoxCoreResultExt as _, Closable}; + use crate::{convert::VoicevoxCoreResultExt as _, Closable, Tokio, VoiceModelFilePyFields}; #[pyclass] #[derive(Clone)] - pub(crate) struct VoiceModel { - model: voicevox_core::tokio::VoiceModel, + pub(crate) struct VoiceModelFile { + model: Arc>, + fields: VoiceModelFilePyFields, } #[pymethods] - impl VoiceModel { + impl VoiceModelFile { #[staticmethod] - fn from_path(py: Python<'_>, path: PathBuf) -> PyResult<&PyAny> { + fn open(py: Python<'_>, path: PathBuf) -> PyResult<&PyAny> { + pyo3_asyncio::tokio::future_into_py(py, async move { + let model = voicevox_core::nonblocking::VoiceModelFile::open(path).await; + let (model, id, metas) = Python::with_gil(|py| { + let model = Python::with_gil(|py| model.into_py_result(py))?; + let id = crate::convert::to_py_uuid(py, model.id().raw_voice_model_id())?; + let metas = + crate::convert::to_pydantic_voice_model_meta(model.metas(), py)?.into(); + Ok::<_, PyErr>((model, id, metas)) + })?; + + let model = Closable::new(model).into(); + + Ok(Self { + model, + fields: VoiceModelFilePyFields { id, metas }, + }) + }) + } + + fn close<'py>(&self, py: Python<'py>) -> PyResult<&'py PyAny> { + let this = self.model.clone(); 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 }) + if let Some(this) = this.close().await { + this.close().await; + } + Ok(()) }) } #[getter] - fn id(&self) -> &str { - self.model.id().raw_voice_model_id() + fn id(&self) -> PyObject { + self.fields.id.clone() } #[getter] - fn metas<'py>(&self, py: Python<'py>) -> Vec<&'py PyAny> { - crate::convert::to_pydantic_voice_model_meta(self.model.metas(), py).unwrap() + fn metas(&self) -> Py { + self.fields.metas.clone() + } + + fn __aenter__(slf: PyRef<'_, Self>) -> PyResult<&PyAny> { + slf.model.read()?; + + let py = slf.py(); + crate::convert::ready(slf, py) + } + + fn __aexit__<'py>( + &self, + #[expect(unused_variables, reason = "`__aexit__`としては必要")] exc_type: &'py PyAny, + #[expect(unused_variables, reason = "`__aexit__`としては必要")] exc_value: &'py PyAny, + #[expect(unused_variables, reason = "`__aexit__`としては必要")] traceback: &'py PyAny, + py: Python<'py>, + ) -> PyResult<&'py PyAny> { + self.close(py) + } + } + + static ONNXRUNTIME: once_cell::sync::OnceCell> = + once_cell::sync::OnceCell::new(); + + #[pyclass] + #[derive(Clone)] + pub(crate) struct Onnxruntime(&'static voicevox_core::nonblocking::Onnxruntime); + + #[pymethods] + impl Onnxruntime { + #[classattr] + const LIB_NAME: &'static str = voicevox_core::nonblocking::Onnxruntime::LIB_NAME; + + #[classattr] + const LIB_VERSION: &'static str = voicevox_core::nonblocking::Onnxruntime::LIB_VERSION; + + #[classattr] + const LIB_VERSIONED_FILENAME: &'static str = + voicevox_core::nonblocking::Onnxruntime::LIB_VERSIONED_FILENAME; + + #[classattr] + const LIB_UNVERSIONED_FILENAME: &'static str = + voicevox_core::nonblocking::Onnxruntime::LIB_UNVERSIONED_FILENAME; + + #[staticmethod] + fn get(py: Python<'_>) -> PyResult>> { + let result = + ONNXRUNTIME.get_or_try_init( + || match voicevox_core::nonblocking::Onnxruntime::get() + .map(|o| Py::new(py, Self(o))) + { + Some(Ok(this)) => Ok(this), + Some(Err(err)) => Err(Some(err)), + None => Err(None), + }, + ); + + match result { + Ok(this) => Ok(Some(this.clone())), + Err(Some(err)) => Err(err), + Err(None) => Ok(None), + } + } + + #[staticmethod] + #[pyo3(signature = (*, filename = Self::LIB_VERSIONED_FILENAME.into()))] + fn load_once(filename: OsString, py: Python<'_>) -> PyResult<&PyAny> { + pyo3_asyncio::tokio::future_into_py(py, async move { + let inner = voicevox_core::nonblocking::Onnxruntime::load_once() + .filename(filename) + .exec() + .await; + + ONNXRUNTIME.get_or_try_init(|| { + Python::with_gil(|py| Py::new(py, Self(inner.into_py_result(py)?))) + }) + }) + } + + fn supported_devices<'py>(&self, py: Python<'py>) -> PyResult<&'py PyAny> { + let class = py + .import("voicevox_core")? + .getattr("SupportedDevices")? + .downcast()?; + let s = self.0.supported_devices().into_py_result(py)?; + crate::convert::to_pydantic_dataclass(s, class) } } #[pyclass] #[derive(Clone)] pub(crate) struct OpenJtalk { - open_jtalk: voicevox_core::tokio::OpenJtalk, + open_jtalk: voicevox_core::nonblocking::OpenJtalk, } #[pymethods] impl OpenJtalk { - #[allow(clippy::new_ret_no_self)] + #[expect(clippy::new_ret_no_self, reason = "これはPython API")] #[staticmethod] fn new( #[pyo3(from_py_with = "crate::convert::from_utf8_path")] @@ -634,7 +961,8 @@ mod asyncio { 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 = + voicevox_core::nonblocking::OpenJtalk::new(open_jtalk_dict_dir).await; let open_jtalk = Python::with_gil(|py| open_jtalk.into_py_result(py))?; Ok(Self { open_jtalk }) }) @@ -652,25 +980,33 @@ mod asyncio { #[pyclass] pub(crate) struct Synthesizer { - synthesizer: - Closable, Self>, + synthesizer: Arc< + Closable< + voicevox_core::nonblocking::Synthesizer, + Self, + Tokio, + >, + >, } #[pymethods] impl Synthesizer { #[new] #[pyo3(signature =( + onnxruntime, open_jtalk, acceleration_mode = InitializeOptions::default().acceleration_mode, cpu_num_threads = InitializeOptions::default().cpu_num_threads, ))] fn new( + onnxruntime: Onnxruntime, 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( + let synthesizer = voicevox_core::nonblocking::Synthesizer::new( + onnxruntime.0, open_jtalk.open_jtalk.clone(), &InitializeOptions { acceleration_mode, @@ -678,7 +1014,7 @@ mod asyncio { }, ); let synthesizer = Python::with_gil(|py| synthesizer.into_py_result(py))?; - let synthesizer = Closable::new(synthesizer); + let synthesizer = Closable::new(synthesizer).into(); Ok(Self { synthesizer }) } @@ -686,29 +1022,37 @@ mod asyncio { "Synthesizer { .. }" } - fn __enter__(slf: PyRef<'_, Self>) -> PyResult> { - slf.synthesizer.get()?; - Ok(slf) + fn __aenter__(slf: PyRef<'_, Self>) -> PyResult<&PyAny> { + slf.synthesizer.read()?; + + let py = slf.py(); + crate::convert::ready(slf, py) } - fn __exit__( + fn __aexit__<'py>( &mut self, - #[allow(unused_variables)] exc_type: &PyAny, - #[allow(unused_variables)] exc_value: &PyAny, - #[allow(unused_variables)] traceback: &PyAny, - ) { - self.close(); + #[expect(unused_variables, reason = "`__aexit__`としては必要")] exc_type: &'py PyAny, + #[expect(unused_variables, reason = "`__aexit__`としては必要")] exc_value: &'py PyAny, + #[expect(unused_variables, reason = "`__aexit__`としては必要")] traceback: &'py PyAny, + py: Python<'py>, + ) -> PyResult<&'py PyAny> { + self.close(py) + } + + #[getter] + fn onnxruntime(&self) -> Py { + ONNXRUNTIME.get().expect("should be initialized").clone() } #[getter] fn is_gpu_mode(&self) -> PyResult { - let synthesizer = self.synthesizer.get()?; + let synthesizer = self.synthesizer.read()?; Ok(synthesizer.is_gpu_mode()) } #[getter] - fn metas<'py>(&self, py: Python<'py>) -> PyResult> { - let synthesizer = self.synthesizer.get()?; + fn metas<'py>(&self, py: Python<'py>) -> PyResult<&'py PyList> { + let synthesizer = self.synthesizer.read()?; crate::convert::to_pydantic_voice_model_meta(&synthesizer.metas(), py) } @@ -717,31 +1061,33 @@ mod asyncio { model: &'py PyAny, py: Python<'py>, ) -> PyResult<&'py PyAny> { - let model: VoiceModel = model.extract()?; - let synthesizer = self.synthesizer.get()?.clone(); + let model: VoiceModelFile = model.extract()?; + let synthesizer = self.synthesizer.read()?.clone(); pyo3_asyncio::tokio::future_into_py(py, async move { - let result = synthesizer.load_voice_model(&model.model).await; + let result = synthesizer.load_voice_model(&*model.model.read()?).await; Python::with_gil(|py| result.into_py_result(py)) }) } - fn unload_voice_model(&mut self, voice_model_id: &str, py: Python<'_>) -> PyResult<()> { + fn unload_voice_model( + &mut self, + #[pyo3(from_py_with = "crate::convert::to_rust_uuid")] voice_model_id: Uuid, + py: Python<'_>, + ) -> PyResult<()> { self.synthesizer - .get()? - .unload_voice_model(&VoiceModelId::new(voice_model_id.to_string())) + .read()? + .unload_voice_model(voice_model_id.into()) .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); - }; + fn is_loaded_voice_model( + &self, + #[pyo3(from_py_with = "crate::convert::to_rust_uuid")] voice_model_id: Uuid, + ) -> PyResult { Ok(self .synthesizer - .get()? - .is_loaded_voice_model(&VoiceModelId::new(voice_model_id.to_string()))) + .read()? + .is_loaded_voice_model(voice_model_id.into())) } fn audio_query_from_kana<'py>( @@ -750,7 +1096,7 @@ mod asyncio { style_id: u32, py: Python<'py>, ) -> PyResult<&'py PyAny> { - let synthesizer = self.synthesizer.get()?.clone(); + let synthesizer = self.synthesizer.read()?.clone(); let kana = kana.to_owned(); pyo3_asyncio::tokio::future_into_py_with_locals( py, @@ -778,7 +1124,7 @@ mod asyncio { style_id: u32, py: Python<'py>, ) -> PyResult<&'py PyAny> { - let synthesizer = self.synthesizer.get()?.clone(); + let synthesizer = self.synthesizer.read()?.clone(); let text = text.to_owned(); pyo3_asyncio::tokio::future_into_py_with_locals( py, @@ -802,7 +1148,7 @@ mod asyncio { style_id: u32, py: Python<'py>, ) -> PyResult<&'py PyAny> { - let synthesizer = self.synthesizer.get()?.clone(); + let synthesizer = self.synthesizer.read()?.clone(); let kana = kana.to_owned(); pyo3_asyncio::tokio::future_into_py_with_locals( py, @@ -831,7 +1177,7 @@ mod asyncio { style_id: u32, py: Python<'py>, ) -> PyResult<&'py PyAny> { - let synthesizer = self.synthesizer.get()?.clone(); + let synthesizer = self.synthesizer.read()?.clone(); let text = text.to_owned(); pyo3_asyncio::tokio::future_into_py_with_locals( py, @@ -860,7 +1206,7 @@ mod asyncio { style_id: u32, py: Python<'py>, ) -> PyResult<&'py PyAny> { - let synthesizer = self.synthesizer.get()?.clone(); + let synthesizer = self.synthesizer.read()?.clone(); crate::convert::async_modify_accent_phrases( accent_phrases, StyleId::new(style_id), @@ -875,7 +1221,7 @@ mod asyncio { style_id: u32, py: Python<'py>, ) -> PyResult<&'py PyAny> { - let synthesizer = self.synthesizer.get()?.clone(); + let synthesizer = self.synthesizer.read()?.clone(); crate::convert::async_modify_accent_phrases( accent_phrases, StyleId::new(style_id), @@ -890,7 +1236,7 @@ mod asyncio { style_id: u32, py: Python<'py>, ) -> PyResult<&'py PyAny> { - let synthesizer = self.synthesizer.get()?.clone(); + let synthesizer = self.synthesizer.read()?.clone(); crate::convert::async_modify_accent_phrases( accent_phrases, StyleId::new(style_id), @@ -902,12 +1248,12 @@ mod asyncio { #[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, + #[pyo3(from_py_with = "crate::convert::from_dataclass")] audio_query: AudioQuery, style_id: u32, enable_interrogative_upspeak: bool, py: Python<'py>, ) -> PyResult<&'py PyAny> { - let synthesizer = self.synthesizer.get()?.clone(); + let synthesizer = self.synthesizer.read()?.clone(); pyo3_asyncio::tokio::future_into_py_with_locals( py, pyo3_asyncio::tokio::get_current_locals(py)?, @@ -945,7 +1291,7 @@ mod asyncio { let options = TtsOptions { enable_interrogative_upspeak, }; - let synthesizer = self.synthesizer.get()?.clone(); + let synthesizer = self.synthesizer.read()?.clone(); let kana = kana.to_owned(); pyo3_asyncio::tokio::future_into_py_with_locals( py, @@ -977,7 +1323,7 @@ mod asyncio { let options = TtsOptions { enable_interrogative_upspeak, }; - let synthesizer = self.synthesizer.get()?.clone(); + let synthesizer = self.synthesizer.read()?.clone(); let text = text.to_owned(); pyo3_asyncio::tokio::future_into_py_with_locals( py, @@ -993,15 +1339,21 @@ mod asyncio { ) } - fn close(&mut self) { - self.synthesizer.close() + fn close<'py>(&self, py: Python<'py>) -> PyResult<&'py PyAny> { + let this = self.synthesizer.clone(); + pyo3_asyncio::tokio::future_into_py(py, async move { + if let Some(this) = this.close().await { + crate::convert::run_in_executor(|| drop(this)).await?; + } + Ok(()) + }) } } #[pyclass] #[derive(Default, Debug, Clone)] pub(crate) struct UserDict { - dict: Arc, + dict: Arc, } #[pymethods] @@ -1011,9 +1363,8 @@ mod asyncio { Self::default() } - fn load<'py>(&self, path: &str, py: Python<'py>) -> PyResult<&'py PyAny> { + fn load<'py>(&self, path: PathBuf, 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; @@ -1021,9 +1372,8 @@ mod asyncio { }) } - fn save<'py>(&self, path: &str, py: Python<'py>) -> PyResult<&'py PyAny> { + fn save<'py>(&self, path: PathBuf, 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; diff --git a/crates/xtask/Cargo.toml b/crates/xtask/Cargo.toml index 63d14515a..9fafb2aec 100644 --- a/crates/xtask/Cargo.toml +++ b/crates/xtask/Cargo.toml @@ -1,6 +1,7 @@ [package] name = "xtask" edition.workspace = true +rust-version.workspace = true [dependencies] cbindgen.workspace = true diff --git a/deny.toml b/deny.toml index 38e915eb7..fa620a797 100644 --- a/deny.toml +++ b/deny.toml @@ -1,172 +1,155 @@ +[graph] targets = [ { triple = "x86_64-pc-windows-msvc" }, { triple = "i686-pc-windows-msvc" }, { triple = "x86_64-unknown-linux-gnu" }, + { triple = "aarch64-unknown-linux-gnu" }, + { triple = "aarch64-linux-android" }, + { triple = "x86_64-linux-android" }, { triple = "aarch64-apple-darwin" }, { triple = "x86_64-apple-darwin" }, + { triple = "aarch64-apple-ios" }, + { triple = "aarch64-apple-ios-sim" }, + { triple = "x86_64-apple-ios" }, ] [bans] multiple-versions = "allow" -# 更新方法は https://github.com/VOICEVOX/voicevox_core/pull/328/files#r1024148598 + +[bans.build] + # deny build scripts that are not listed in `bypass` (except `typenum`. see the fixme) allow-build-scripts = [ - { name = "ahash", version = "0.7" }, # https://docs.rs/crate/ahash/0.7/source/build.rs - { name = "anyhow", version = "1" }, # https://docs.rs/crate/anyhow/1/source/build.rs - { name = "async-io", version = "1" }, # https://docs.rs/crate/async-io/1/source/build.rs - { name = "async-trait", version = "0.1" }, # https://docs.rs/crate/async-trait/0.1/source/build.rs - { name = "backtrace", version = "0.3" }, # https://docs.rs/crate/backtrace/0.3/source/build.rs - { name = "bindgen", version = "0.60" }, # https://docs.rs/crate/bindgen/0.60/source/build.rs - { name = "cbindgen", version = "0.24" }, # https://docs.rs/crate/cbindgen/0.24/source/build.rs - { name = "const_fn", version = "0.4" }, # https://docs.rs/crate/const_fn/0.4/source/build.rs - { name = "cookie", version = "0.14" }, # https://docs.rs/crate/cookie/0.14/source/build.rs - { name = "core-foundation-sys", version = "0.8" }, # https://docs.rs/crate/core-foundation-sys/0.8.3/source/build.rs - { name = "crc32fast", version = "1" }, # https://docs.rs/crate/crc32fast/1/source/build.rs - { name = "crossbeam-epoch", version = "0.9" }, # https://docs.rs/crate/crossbeam-epoch/0.9.13/source/build.rs - { name = "crossbeam-utils", version = "0.8" }, # https://docs.rs/crate/crossbeam-utils/0.8/source/build.rs - { name = "curl", version = "0.4" }, # https://docs.rs/crate/curl/0.4/source/build.rs - { name = "doc-comment", version = "0.3" }, # https://docs.rs/crate/doc-comment/0.3.3/source/build.rs - { name = "encoding_rs", version = "0.8" }, # https://docs.rs/crate/encoding_rs/0.8/source/build.rs - { name = "eyre", version = "0.6" }, # https://docs.rs/crate/eyre/0.6/source/build.rs - { name = "futures-channel", version = "0.3" }, # https://docs.rs/crate/futures-channel/0.3/source/build.rs - { name = "futures-core", version = "0.3" }, # https://docs.rs/crate/futures-core/0.3/source/build.rs - { name = "futures-task", version = "0.3" }, # https://docs.rs/crate/futures-task/0.3/source/build.rs - { name = "futures-util", version = "0.3" }, # https://docs.rs/crate/futures-util/0.3/source/build.rs - { name = "httparse", version = "1" }, # https://docs.rs/crate/httparse/1.8.0/source/build.rs - { name = "hyperx", version = "1" }, # https://docs.rs/crate/hyperx/1.4.0/source/build.rs - { name = "generic-array", version = "0.14" }, # https://docs.rs/crate/generic-array/0.14/source/build.rs - { name = "getrandom", version = "0.1" }, # https://docs.rs/crate/getrandom/0.1/source/build.rs - { name = "http-types", version = "2" }, # https://docs.rs/crate/http-types/2/source/build.rs - { name = "indexmap", version = "1" }, # https://docs.rs/crate/indexmap/1/source/build.rs - { name = "io-lifetimes", version = "1" }, # https://docs.rs/crate/io-lifetimes/1.0.4/source/build.rs - { name = "isahc", version = "0.9" }, # https://docs.rs/crate/isahc/0.9/source/build.rs - { name = "libc", version = "0.2" }, # https://docs.rs/crate/libc/0.2/source/build.rs - { name = "libm", version = "0.2" }, # https://docs.rs/crate/libm/0.2.6/source/build.rs - { name = "lock_api", version = "0.4" }, # https://docs.rs/crate/lock_api/0.4/source/build.rs - { name = "log", version = "0.4" }, # https://docs.rs/crate/log/0.4/source/build.rs - { name = "memchr", version = "2" }, # https://docs.rs/crate/memchr/2/source/build.rs - { name = "memoffset", version = "0.6" }, # https://docs.rs/crate/memoffset/0.6/source/build.rs - { name = "memoffset", version = "0.7" }, # https://docs.rs/crate/memoffset/0.7.1/source/build.rs - { name = "mime_guess", version = "2" }, # https://docs.rs/crate/mime_guess/2/source/build.rs - { name = "num-bigint", version = "0.4" }, # https://docs.rs/crate/num-bigint/0.4.3/source/build.rs - { name = "num-integer", version = "0.1" }, # https://docs.rs/crate/num-integer/0.1/source/build.rs - { name = "num-traits", version = "0.2" }, # https://docs.rs/crate/num-traits/0.2/source/build.rs - { name = "onnxruntime", version = "=0.1.0" }, # https://github.com/VOICEVOX/onnxruntime-rs/blob/405f62fb53df1b59b0e69adafbd1c28e4d5c2787/onnxruntime/build.rs - { name = "parking_lot_core", version = "0.9" }, # https://docs.rs/crate/parking_lot_core/0.9/source/build.rs - { name = "polling", version = "2" }, # https://docs.rs/crate/polling/2/source/build.rs - { name = "portable-atomic", version = "0.3" }, # https://docs.rs/crate/portable-atomic/0.3.19/source/build.rs - { name = "proc-macro-error", version = "1" }, # https://docs.rs/crate/proc-macro-error/1/source/build.rs - { name = "proc-macro-error-attr", version = "1" }, # https://docs.rs/crate/proc-macro-error-attr/1/source/build.rs - { name = "proc-macro-hack", version = "0.5" }, # https://docs.rs/crate/proc-macro-hack/0.5/source/build.rs - { name = "proc-macro2", version = "1" }, # https://docs.rs/crate/proc-macro2/1/source/build.rs - { name = "pyo3", version = "0.17" }, # https://docs.rs/crate/pyo3/0.17/source/build.rs - { name = "pyo3-build-config", version = "0.17" }, # https://docs.rs/crate/pyo3-build-config/0.17/source/build.rs - { name = "quote", version = "1" }, # https://docs.rs/crate/quote/1/source/build.rs - { name = "rayon-core", version = "1" }, # https://docs.rs/crate/rayon-core/1.10.1/source/build.rs - { name = "rstest_macros", version = "0.14" }, # https://docs.rs/crate/rstest_macros/0.14/source/build.rs - { name = "rustix", version = "0.36" }, # https://docs.rs/crate/rustix/0.36.7/source/build.rs - { name = "rustls", version = "0.20" }, # https://docs.rs/crate/rustls/0.20/source/build.rs - { name = "rustversion", version = "1" }, # https://docs.rs/crate/rustversion/1.0.11/source/build/build.rs - { name = "semver", version = "1" }, # https://docs.rs/crate/semver/1/source/build.rs - { name = "serde", version = "1" }, # https://docs.rs/crate/serde/1/source/build.rs - { name = "serde_derive", version = "1" }, # https://docs.rs/crate/serde_derive/1/source/build.rs - { name = "serde_json", version = "1" }, # https://docs.rs/crate/serde_json/1/source/build.rs - { name = "slab", version = "0.4" }, # https://docs.rs/crate/slab/0.4/source/build.rs - { name = "standback", version = "0.2" }, # https://docs.rs/crate/standback/0.2.0/source/build.rs - { name = "syn", version = "1" }, # https://docs.rs/crate/syn/1.0.0/source/build.rs - { name = "target-lexicon", version = "0.12" }, # https://docs.rs/crate/target-lexicon/0.12.0/source/build.rs - { name = "thiserror", version = "1" }, # https://docs.rs/crate/thiserror/1.0.0/source/build.rs - { name = "time", version = "0.2" }, # https://docs.rs/crate/time/0.2/source/build.rs - { name = "tokio", version = "1" }, # https://docs.rs/crate/tokio/1.24.1/source/build.rs - { name = "typenum", version = "1" }, # https://docs.rs/crate/typenum/1/source/src/__private/build.rs - { name = "unicase", version = "2" }, # https://docs.rs/crate/unicase/2/source/build.rs - { name = "value-bag", version = "1.0.0-alpha.9" }, # https://docs.rs/crate/value-bag/1.0.0-alpha.9/source/build.rs - { name = "voicevox_core" }, # ./crates/voicevox_core/build.rs - { name = "voicevox_core_c_api" }, # ./crates/voicevox_core_c_api/build.rs - { name = "voicevox_core_python_api" }, # ./crates/voicevox_core_python_api/build.rs - { name = "wasm-bindgen", version = "0.2" }, # https://docs.rs/crate/wasm-bindgen/0.2/source/build.rs - { name = "wasm-bindgen-shared", version = "0.2" }, # https://docs.rs/crate/wasm-bindgen-shared/0.2/source/build.rs - { name = "winapi", version = "0.3" }, # https://docs.rs/crate/winapi/0.3/source/build.rs - { name = "windows_i686_msvc", version = "0.36" }, # https://docs.rs/crate/windows_i686_msvc/0.36/source/build.rs - { name = "windows_i686_msvc", version = "0.42" }, # https://docs.rs/crate/windows_i686_msvc/0.42.0/source/build.rs - { name = "windows_x86_64_msvc", version = "0.36" }, # https://docs.rs/crate/windows_x86_64_msvc/0.36/source/build.rs - { name = "windows_x86_64_msvc", version = "0.42" }, # https://docs.rs/crate/windows_x86_64_msvc/0.42.0/source/build.rs - { name = "winreg", version = "0.10" }, # https://docs.rs/crate/winreg/0.10.1/source/build.rs - { name = "zstd-safe", version = "5" }, # https://docs.rs/crate/zstd-safe/5.0.2/source/build.rs - - # https://docs.rs/crate/bzip2-sys/0.1/source/build.rs + # FIXME: build/main.rsのような場所に置かれていると駄目なのか、`bypass`からは認識してくれない + # SHA256: b4dd86261a70df757efa53f06ce7543a4dc9c51178b9b023c92069fddee97a29 + { name = "typenum", version = "1" }, # https://docs.rs/crate/typenum/1.15.0/source/build/main.rs +] +bypass = [ + { name = "ahash", version = "0.8", build-script = "23cbf4cf1b742e2c4da8bc58d06d1d021479dec80cec6a0bc3704c7172e2864a" }, # https://docs.rs/crate/ahash/0.8.1/source/build.rs + { name = "anyhow", version = "1", build-script = "1de78cc91e63321318aa336cb550e3acdcda9b39f0648436a884d783247cfcd2" }, # https://docs.rs/crate/anyhow/1.0.89/source/build.rs + { name = "assert_cmd", version = "2", build-script = "367a36318cd9bb47aeb730f8a8ddad39c10b926175465393f0d5b01cbd993d44" }, # https://docs.rs/crate/assert_cmd/2.0.16/source/build.rs + { name = "async-trait", version = "0.1", build-script = "b45aa3a5c177cbeaeb4847163088924491ac27b79534f8ea4c53ed3e10c163ea" }, # https://docs.rs/crate/async-trait/0.1.57/source/build.rs + { name = "backtrace", version = "0.3", build-script = "8d5e860da109f86c67596b10b5613ff6d19f9d24c2970f491a55261fb1973692" }, # https://docs.rs/crate/backtrace/0.3.66/source/build.rs + { name = "bindgen", version = "0.62", build-script = "4a9c4ac3759572e17de312a9d3f4ced3b6fd3c71811729e5a8d06bfbd1ac8f82" }, # https://docs.rs/crate/bindgen/0.62.0/source/build.rs + { name = "bindgen", version = "0.69", build-script = "4a9c4ac3759572e17de312a9d3f4ced3b6fd3c71811729e5a8d06bfbd1ac8f82" }, # https://docs.rs/crate/bindgen/0.69.4/source/build.rs + { name = "camino", version = "1", build-script = "cbdfaa56ff8e211896e75fc7867e3230aa8aa09fdda901111db957c65306f1d8" }, # https://docs.rs/crate/camino/1.1.9/source/build.rs + { name = "caseless", version = "0.2", build-script = "8ab1dc9ef269f28202fe1156c5c655f286cbc03b6dd4fb20a2f9f9e00763b6f5" }, # https://docs.rs/crate/caseless/0.2.1/source/src/build.rs + { name = "cbindgen", version = "0.27", build-script = "f0fa57ad2c0dca5855cc2636a2e95acbadb52fa887609216022bdf71ed996dec" }, # https://docs.rs/crate/cbindgen/0.27.0/source/build.rs + { name = "crc32fast", version = "1", build-script = "4ccc50c3da67eb27f0b622440d2b7aee2f73fa9c71884571f3c041122231d105" }, # https://docs.rs/crate/crc32fast/1.3.2/source/build.rs + { name = "crossbeam-epoch", version = "0.9", build-script = "901be3c21843440be5c456ff049f57f72ee5ec365918a772ad2a4751e52f69c5" }, # https://docs.rs/crate/crossbeam-epoch/0.9.13/source/build.rs + { name = "crossbeam-utils", version = "0.8", build-script = "4859f9c926c230023e861bf01c4b225b460035faf8cf6240108530efedbb747f" }, # https://docs.rs/crate/crossbeam-utils/0.8.2/source/build.rs + { name = "doc-comment", version = "0.3", build-script = "a342cd0a760b7e04b13406c5de82a9b6b39d9b8495a274c2f78d56d676aeca3a" }, # https://docs.rs/crate/doc-comment/0.3.3/source/build.rs + { name = "encoding_rs", version = "0.8", build-script = "9276ee24ef71433d46323c15296b3fbbb29c0b37c4b1ca45416587f14ba8e777" }, # https://docs.rs/crate/encoding_rs/0.8.31/source/build.rs + { name = "eyre", version = "0.6", build-script = "fbd0d04cc64884da6b65ad460084ad49e56f8a14fba24a256e161cb18b15441c" }, # https://docs.rs/crate/eyre/0.6.12/source/build.rs + { name = "fs-err", version = "2", build-script = "f1d5a299d68f91e26fbe9dd642dfcd00e122ec9cb999d4a4b38c6d7200fb9763" }, # https://docs.rs/crate/fs-err/2.11.0/source/build.rs + { name = "generic-array", version = "0.14", build-script = "08fa30c4a2c1ad24fe5f987e721dfb20131f45ea5b5dc3e836dcf88a8e33248c" }, # https://docs.rs/crate/generic-array/0.14.6/source/build.rs + { name = "html5ever", version = "0.27", build-script = "c3aa75b3b3dcea627b2158405b3ed597cab24f5b6220396f1ee9800e9fa40b7d" }, # https://docs.rs/crate/html5ever/0.27.0/source/build.rs + { name = "httparse", version = "1", build-script = "8ae7a55b0cca81a9997a151bd52e4658af9c6a5c176e65bbec532a20ab23360a" }, # https://docs.rs/crate/httparse/1.8.0/source/build.rs + { name = "indexmap", version = "1", build-script = "558b4d0b9e9b3a44f7e1a2b69f7a7567ea721cd45cb54f4e458e850bf702f35c" }, # https://docs.rs/crate/indexmap/1.9.1/source/build.rs + { name = "libc", version = "0.2", build-script = "e36af1b89db29ba25f301d5e572f2727f06590fcb27d9f535e90923c1ec34574" }, # https://docs.rs/crate/libc/0.2.159/source/build.rs + { name = "libm", version = "0.2", build-script = "2e1393133eb5f84f5a9278b3d68acb31552da924b0c1fdf77b4af583f82afb22" }, # https://docs.rs/crate/libm/0.2.6/source/build.rs + { name = "lock_api", version = "0.4", build-script = "af84139c71d151adead0b4398c394a7dd16087bb2db44b14a0ed970ce868a6c6" }, # https://docs.rs/crate/lock_api/0.4.9/source/build.rs + { name = "markup5ever", version = "0.12", build-script = "bd7d2c0e564d71953b40504081f934541c60270b78db6b1e59230cad73607462" }, # https://docs.rs/crate/markup5ever/0.12.1/source/build.rs + { name = "memoffset", version = "0.7", build-script = "6d677e33a1c98d588c97ec7985d4d5c3b954683e0a73c3dc53d79db4fbb5e638" }, # https://docs.rs/crate/memoffset/0.7.1/source/build.rs + { name = "memoffset", version = "0.9", build-script = "df34c830dbb08eba3474304eed481bc2c8a29e897bc50f46d37b5dbb6e443a2b" }, # https://docs.rs/crate/memoffset/0.9.0/source/build.rs + { name = "num-bigint", version = "0.4", build-script = "4955639b370d3636b8c44cb7743e6c5fb129077b069d78becbc135eba37e1ece" }, # https://docs.rs/crate/num-bigint/0.4.3/source/build.rs + { name = "num-integer", version = "0.1", build-script = "575b157527243fe355a7c8d7d874a1f790c3fb0177beba9032076a7803c5b9dd" }, # https://docs.rs/crate/num-integer/0.1.45/source/build.rs + { name = "num-traits", version = "0.2", build-script = "cf682b2322303196e241048cb56d873597b78a3b4e3f275f6f761dadb33a65f5" }, # https://docs.rs/crate/num-traits/0.2.15/source/build.rs + { name = "parking_lot_core", version = "0.9", build-script = "29e629057144d1238dcd8ea70ad6cbb6ec14ca742797af3fa9335710ff5cbaaa" }, # https://docs.rs/crate/parking_lot_core/0.9.3/source/build.rs + { name = "prettyplease", version = "0.2", build-script = "fdf8aa9b5441b298c72ae23645e227adc52ac69d2decc1bda04e1a91f70ff87d" }, # https://docs.rs/crate/prettyplease/0.2.17/source/build.rs + { name = "portable-atomic", version = "1", build-script = "0d5e11d1d1376259bbd99269b52728a5a7e3f93403d82fa4ee1cfbd11ed892dd" }, # https://docs.rs/crate/portable-atomic/1.9.0/source/build.rs + { name = "proc-macro2", version = "1", build-script = "cf78c0005f11d54ca42dbaee77cb76a440e6fa2e0b64798d3f74c04770a0ad2b" }, # https://docs.rs/crate/proc-macro2/1.0.86/source/build.rs + { name = "proc-macro2-diagnostics", version = "0.10", build-script = "66fcc487972086f42011c84a1949861799dc7cfde1e56201d22cf8e71b59b8b1" }, # https://docs.rs/crate/proc-macro2-diagnostics/0.10.1/source/build.rs + { name = "pyo3", version = "0.20", build-script = "9d56905da256ac81e171d4cc6c07789f4cfba73a6fc32ed10e29a4f1f6b6f852" }, # https://docs.rs/crate/pyo3/0.20.3/source/build.rs + { name = "pyo3-build-config", version = "0.20", build-script = "905cbe245028aa0a6841ce7543dd8fc3e289872e7a630247d4fc81759fc938c3" }, # https://docs.rs/crate/pyo3-build-config/0.20.3/source/build.rs + { name = "rayon-core", version = "1", build-script = "fa31cb198b772600d100a7c403ddedccef637d2e6b2da431fa7f02ca41307fc6" }, # https://docs.rs/crate/rayon-core/1.12.1/source/build.rs + { name = "ref-cast", version = "1", build-script = "606c77f4c4497ccb44841cbf7c13bf7f0d46a887163f7476b77722accc04acae" }, # https://docs.rs/crate/ref-cast/1.0.23/source/build.rs + { name = "rstest_macros", version = "0.14", build-script = "d2973e71d6322a29cf96a47e957e60ed0ce83822878c436f22bda0d33253c5a6" }, # https://docs.rs/crate/rstest_macros/0.14.0/source/build.rs + { name = "rstest_reuse", version = "0.6", build-script = "c4db8df109a7a9870259a8e63ebceee46ea7eb64ab288433ca4dd3d512278086" }, # https://docs.rs/crate/rstest_reuse/0.6.0/source/build.rs + { name = "rustix", version = "0.38", build-script = "adc4bc868a30a902f328af6ebd0bfc72868b8e388beb13c6d69d826646931b17" }, # https://docs.rs/crate/rustix/0.38.37/source/build.rs + { name = "rustls", version = "0.21", build-script = "83af94fa10c4be7653b2b69e1a4656239ecf6fbdfc225341f10e1ec7121b383e" }, # https://docs.rs/crate/rustls/0.21.7/source/build.rs + { name = "rustversion", version = "1", build-script = "a5ac3f88a152167bdf624d18346b6db6459828bdbd1162a275fcde9c36e3ade6" }, # https://docs.rs/crate/rustversion/1.0.11/source/build/build.rs + { name = "selectors", version = "0.25", build-script = "36ba09a8d2089d0cae8e310829ecf0e94bcbaa87e775a6578c7d2f0459a5b6ca" }, # https://docs.rs/crate/selectors/0.25.0/source/build.rs + { name = "semver", version = "1", build-script = "eedfc19afa205955347175916974cdad121b55cb940e40c61931e5e7629f0e65" }, # https://docs.rs/crate/semver/1.0.14/source/build.rs + { name = "serde", version = "1", build-script = "a98eaa82c783fdb4169d1646c06028ec5cb82937d39ee127ec8ed33651d2f238" }, # https://docs.rs/crate/serde/1.0.210/source/build.rs + { name = "serde_json", version = "1", build-script = "1630d0bbfc936b0975d840cec5cfb5910d861b6afeeeeabe11000a2c202d571d" }, # https://docs.rs/crate/serde_json/1.0.128/source/build.rs + { name = "slab", version = "0.4", build-script = "2c008232a3ae7c83c166f61c2942314717976776a4dba38e9063cd8e57a1b9bd" }, # https://docs.rs/crate/slab/0.4.7/source/build.rs + { name = "syn", version = "1", build-script = "b815649fd2929d3debd93a58f5da2fb8eba506047a6a5ba538347305828a87b0" }, # https://docs.rs/crate/syn/1.0.102/source/build.rs + { name = "target-lexicon", version = "0.12", build-script = "4716b4f955c7a4cb39cb3b7521c1745d5110c1cbd1e054bca906e37f5e974675" }, # https://docs.rs/crate/target-lexicon/0.12.4/source/build.rs + { name = "thiserror", version = "1", build-script = "14f51456047fbf92c32020daea746f15482a0832a752edbbe3e809075d97674b" }, # https://docs.rs/crate/thiserror/1.0.64/source/build.rs + { name = "typeid", version = "1", build-script = "688afbcaa398ea159c3481b26d74fde6ce3a675d48364d772557c8e91100de46" }, # https://docs.rs/crate/typeid/1.0.2/source/build.rs + { name = "winapi", version = "0.3", build-script = "fa1782968d33345772093666220c7841c2fb4f6dd32fa47951c68a3a400a1a98" }, # https://docs.rs/crate/winapi/0.3.9/source/build.rs + { name = "windows_i686_msvc", version = "0.36", build-script = "d5df812ba7add22771644473db37d7de40c1e7479a30f81ae3ccb0d7be3fabe4" }, # https://docs.rs/crate/windows_i686_msvc/0.36.1/source/build.rs + { name = "windows_i686_msvc", version = "0.42", build-script = "d5df812ba7add22771644473db37d7de40c1e7479a30f81ae3ccb0d7be3fabe4" }, # https://docs.rs/crate/windows_i686_msvc/0.42.2/source/build.rs + { name = "windows_i686_msvc", version = "0.48", build-script = "d5df812ba7add22771644473db37d7de40c1e7479a30f81ae3ccb0d7be3fabe4" }, # https://docs.rs/crate/windows_i686_msvc/0.48.5/source/build.rs + { name = "windows_i686_msvc", version = "0.52", build-script = "6d40bd2c0ed4cbea5126dfcd89d72f229c7d986540cbf0dc34acc1017f1de20f" }, # https://docs.rs/crate/windows_i686_msvc/0.52.6/source/build.rs + { name = "windows_x86_64_gnu", version = "0.48", build-script = "d1d816121af1bba70471e982bf98fa9cd2d8cf695d15d2837f592458410ff597" }, # https://docs.rs/crate/windows_x86_64_msvc/0.48.5/source/build.rs + { name = "windows_x86_64_gnu", version = "0.52", build-script = "6d40bd2c0ed4cbea5126dfcd89d72f229c7d986540cbf0dc34acc1017f1de20f" }, # https://docs.rs/crate/windows_x86_64_msvc/0.52.6/source/build.rs + { name = "windows_x86_64_msvc", version = "0.36", build-script = "c801bf00de0978fd5252930993878c0b0eed65336c82c89289efaf9a9ad5ac22" }, # https://docs.rs/crate/windows_x86_64_msvc/0.36.1/source/build.rs + { name = "windows_x86_64_msvc", version = "0.42", build-script = "c801bf00de0978fd5252930993878c0b0eed65336c82c89289efaf9a9ad5ac22" }, # https://docs.rs/crate/windows_x86_64_msvc/0.42.2/source/build.rs + { name = "windows_x86_64_msvc", version = "0.48", build-script = "c801bf00de0978fd5252930993878c0b0eed65336c82c89289efaf9a9ad5ac22" }, # https://docs.rs/crate/windows_x86_64_msvc/0.48.5/source/build.rs + { name = "windows_x86_64_msvc", version = "0.52", build-script = "6d40bd2c0ed4cbea5126dfcd89d72f229c7d986540cbf0dc34acc1017f1de20f" }, # https://docs.rs/crate/windows_x86_64_msvc/0.52.6/source/build.rs + { name = "zstd-safe", version = "5", build-script = "2342e59833e2ebca2980884d4f242a6bf1b0143037c212e05514626ad5213505" }, # https://docs.rs/crate/zstd-safe/5.0.2/source/build.rs + + # https://docs.rs/crate/bzip2-sys/0.1.11+1.0.8/source/build.rs # # bzip is licensed under: https://sourceware.org/git/?p=bzip2.git;a=history;f=LICENSE - { name = "bzip2-sys", version = "0.1" }, + { name = "bzip2-sys", version = "0.1", build-script = "5ea6bf7f9cfaa58d2fa4405309d77c9742de45bbc03af9c9f6f1a699b8b82281" }, - # https://docs.rs/crate/clang-sys/1/source/build.rs + # https://docs.rs/crate/clang-sys/1.6.0/source/build.rs # # libclang is licensed under `Apache-2.0 WITH LLVM-exception` (https://raw.githubusercontent.com/llvm/llvm-project/main/llvm/LICENSE.TXT) - { name = "clang-sys", version = "1" }, - - # https://docs.rs/crate/curl-sys/0.4.0/source/build.rs - # - # Curl is licensed under `curl` (https://github.com/curl/curl/blob/master/COPYING) - { name = "curl-sys", version = "0.4" }, - - # https://docs.rs/crate/libnghttp2-sys/0.1/source/build.rs - # - # libnghttp2 is licensed under `MIT` (https://docs.rs/crate/libnghttp2-sys/0.1/source/nghttp2/COPYING) - { name = "libnghttp2-sys", version = "0.1" }, - - # https://docs.rs/crate/libz-sys/1/source/build.rs - # - # zlib is licensed under `Zlib` (https://docs.rs/crate/libz-sys/1/source/src/zlib/zlib.h) - # zlib-ng is licensed under `Zlib` (https://docs.rs/crate/libz-sys/1/source/src/zlib-ng/LICENSE.md) - { name = "libz-sys", version = "1" }, + { name = "clang-sys", version = "1", build-script = "da53087156a235fe65cab1ee34e12d97b6c3e540e2c8e3ae9b2aeac71efcf1ce" }, - # https://docs.rs/crate/link-cplusplus/1/source/build.rs + # https://docs.rs/crate/link-cplusplus/1.0.7/source/build.rs # # link-cplusplus links libc++ and libstdc++ - { name = "link-cplusplus", version = "1" }, + { name = "link-cplusplus", version = "1", build-script = "1590afe3a11449e69028849ea8d5df9183e388c0b5ee50c322f3c4c9044917f8" }, - # https://github.com/VOICEVOX/onnxruntime-rs/blob/bee215aaf6d5e346d96e0724acd02a51f612a72e/onnxruntime-sys/build.rs + # https://docs.rs/crate/onig_sys/69.8.1/source/build.rs # - # ONNX Runtime is licensed under `MIT` (https://github.com/microsoft/onnxruntime/blob/v1.11.1/LICENSE) - { name = "onnxruntime-sys", version = "=0.0.25" }, + # Oniguruma is licensed under `BSD-2-Clause`: https://docs.rs/crate/onig_sys/69.8.1/source/oniguruma/COPYING + { name = "onig_sys", version = "69", build-script = "c84df2a143d2ffb0fbb8f9f6102992f1b38f44037edbee74f2d364616ea4863f" }, - # https://github.com/VOICEVOX/open_jtalk-rs/blob/c77112b470874a6a963426ed6c2fb21f12394a78/crates/open_jtalk-sys/build.rs + # https://github.com/VOICEVOX/open_jtalk-rs/blob/e1940f3fd61a48bed5bbec8cd2645e13923b1f80/crates/open_jtalk-sys/build.rs # # Open JTalk is licensed under: https://github.com/VOICEVOX/open_jtalk/blob/1.11/src/COPYING - { name = "open_jtalk-sys", version = "0.16" }, + { name = "open_jtalk-sys", version = "=0.16.111", build-script = "0dd3780d0500bb42759eb0e6bd2d00f1d5fc92fb6f47f2abe59af135d4353ee3" }, - # https://docs.rs/crate/openssl-sys/0.9/source/build/main.rs + # https://docs.rs/crate/pyo3-ffi/0.20.3/source/build.rs # - # OpenSSL is licensed under `OpenSSL` (https://github.com/sfackler/rust-openssl/blob/44528bb7c908884fe68f1622d968c295fcae2667/THIRD_PARTY) - { name = "openssl-sys", version = "0.9" }, + # pyo3-ffi **dynamically** links Python, which is licensed under: https://docs.python.org/3/license.html + { name = "pyo3-ffi", version = "0.20", build-script = "8c72b9eff0d71295daef54a999fa0d0cf4700793cdb69c97065c65402016bcbc" }, - # https://docs.rs/crate/pyo3-ffi/0.17/source/build.rs + # https://docs.rs/crate/ring/0.16.20/source/build.rs # - # pyo3-ffi **dynamically** links Python, which is licensed under: https://docs.python.org/3/license.html - { name = "pyo3-ffi", version = "0.17" }, + # ring inherits licenses of C libraries: https://docs.rs/crate/ring/0.16.20/source/LICENSE + { name = "ring", version = "0.16", build-script = "1a850d791184374f614d01c86c8d6c9ba0500e64cb746edc9720ceaaa1cd8eaf" }, - # https://docs.rs/crate/ring/0.16.0/source/build.rs + # https://docs.rs/crate/ring/0.17.8/source/build.rs # - # ring inherits licenses of C libraries: https://docs.rs/crate/ring/0.16.0/source/LICENSE - { name = "ring", version = "0.16" }, + # ring inherits licenses of C libraries: https://docs.rs/crate/ring/0.17.8/source/LICENSE + { name = "ring", version = "0.17", build-script = "8f11e761b5f93266bacdd1b966bd6c738ea3b7ecc323b71436b015d5a2500817" }, - # https://docs.rs/crate/wepoll-ffi/0.1.0/source/build.rs + # https://docs.rs/crate/system-configuration-sys/0.5.0/source/build.rs # - # wepoll-ffi inherits licenses of C libraries: https://docs.rs/crate/wepoll-ffi/0.1.0/source/NOTICE - { name = "wepoll-ffi", version = "0.1" }, + # system-configuration-sys links System Configuration framework on macOS. + { name = "system-configuration-sys", version = "0.5", build-script = "cf4c21c898e9671345d4684c75014189623574f9ec96414999a9db2d73b1e40f" }, + + # https://github.com/VOICEVOX/ort/blob/8627833456a69e7841ae2a29fd184752df8de8d9/ort-sys/build.rs + # + # ONNX Runtime is licensed under `MIT` (https://github.com/microsoft/onnxruntime/blob/v1.11.1/LICENSE) + { name = "voicevox-ort-sys", version = "=2.0.0-rc.4", build-script = "69dc8169473b04b8fbd9f0430a9b0c6057bc477fd4e971fe9d981173b073985c" }, - # https://docs.rs/crate/zstd-sys/1/source/build.rs + # https://docs.rs/crate/zstd-sys/2.0.9+zstd.1.5.5/source/build.rs # # libzstd is licensed under `GPL-2.0+ OR BSD-3-Clause` (https://github.com/facebook/zstd/blob/v1.5.2/lib/zstd.h#L1-L9) - { name = "zstd-sys", version = "2" }, + { name = "zstd-sys", version = "2", build-script = "f11bc2108439f43e6365bb21bb9d9142c0fac376a17702b04da7f19efdbb9b69" }, ] [sources] unknown-registry = "deny" unknown-git = "deny" -allow-org = { github = [ - "VOICEVOX", - "wesleywiser", # https://github.com/VOICEVOX/voicevox_core/issues/437#issuecomment-1465078889 -] } +allow-org.github = ["VOICEVOX"] [licenses] allow = [ @@ -180,16 +163,21 @@ allow = [ "MPL-2.0", "OpenSSL", "Unicode-DFS-2016", + "Zlib", ] clarify = [ { name = "ring", version = "0.16", expression = "MIT AND ISC AND OpenSSL", license-files = [{ path = "LICENSE", hash = 0xbd0eed23 }] }, + { name = "ring", version = "0.17", expression = "MIT AND ISC AND OpenSSL", license-files = [{ path = "LICENSE", hash = 0xbd0eed23 }] }, # TODO: `package.license`を書く { name = "downloader", expression = "MIT", license-files = [] }, { name = "open_jtalk", expression = "BSD-3-Clause", license-files = [] }, { name = "open_jtalk-sys", expression = "BSD-3-Clause", license-files = [] }, + { name = "test_util", expression = "MIT", license-files = [] }, { name = "voicevox_core", expression = "MIT", license-files = [] }, { name = "voicevox_core_c_api", expression = "MIT", license-files = [] }, + { name = "voicevox_core_java_api", expression = "MIT", license-files = [] }, + { name = "voicevox_core_macros", expression = "MIT", license-files = [] }, { name = "voicevox_core_python_api", expression = "MIT", license-files = [] }, { name = "xtask", expression = "MIT", license-files = [] }, ] diff --git a/docs/apis/c_api/doxygen/Doxyfile b/docs/apis/c_api/doxygen/Doxyfile index c42bd60e0..c845adbd4 100644 --- a/docs/apis/c_api/doxygen/Doxyfile +++ b/docs/apis/c_api/doxygen/Doxyfile @@ -2257,7 +2257,7 @@ INCLUDE_FILE_PATTERNS = # recursively expanded use the := operator instead of the = operator. # This tag requires that the tag ENABLE_PREPROCESSING is set to YES. -PREDEFINED = +PREDEFINED = VOICEVOX_LOAD_ONNXRUNTIME= VOICEVOX_LINK_ONNXRUNTIME= # If the MACRO_EXPANSION and EXPAND_ONLY_PREDEF tags are set to YES then this # tag can be used to specify a list of macro names that should be expanded. The @@ -2660,6 +2660,7 @@ GENERATE_LEGEND = YES DOT_CLEANUP = YES +ALIASES += availability{1}="

Availability
\1
" ALIASES += example{1}="
Example
\1
" ALIASES += examples{1}="
Examples
\1
" diff --git a/docs/apis/index.html b/docs/apis/index.html index 639602f9f..efae64b13 100644 --- a/docs/apis/index.html +++ b/docs/apis/index.html @@ -4,10 +4,12 @@ - +

TODO: まともなページを用意する

+

VOICEVOX/voicevox_core#496

diff --git a/docs/apis/python_api/conf.py b/docs/apis/python_api/conf.py index b1695b750..60f9df90e 100644 --- a/docs/apis/python_api/conf.py +++ b/docs/apis/python_api/conf.py @@ -32,7 +32,7 @@ ] # templates_path = ['_templates'] -exclude_patterns = [] +exclude_patterns = ["autoapi/*/_rust/*"] # パブリックAPIを意図した部分ではなく、またorphan扱いとなって警告が出るため # -- Options for HTML output ------------------------------------------------- diff --git a/docs/downloader.md b/docs/downloader.md index aeff5b4a8..76148197f 100644 --- a/docs/downloader.md +++ b/docs/downloader.md @@ -49,7 +49,7 @@ download または ``` -download --device cpu +download --devices cpu ``` @@ -57,7 +57,7 @@ download --device cpu ## DirectML 版をダウンロードする場合 ``` -download --device directml +download --devices directml ``` @@ -65,7 +65,7 @@ download --device directml ## CUDA 版をダウンロードする場合 ``` -download --device cuda +download --devices cuda ``` diff --git a/docs/feature-options.md b/docs/feature-options.md new file mode 100644 index 000000000..16487d3bf --- /dev/null +++ b/docs/feature-options.md @@ -0,0 +1,26 @@ +## ONNX Runtimeのリンク方法のオプション + +Rust API(`voicevox_core`)およびC API(`voicevox_core_c_api`)においては、ビルド時に +次のCargoフィーチャのうちどちらかを選択しなければなりません。 +詳しくは[voicevox_core/Cargo.toml](../crates/voicevox_core/Cargo.toml)のコメントを参照して +下さい。Python APIやJava APIでは`load-onnxruntime`のみに限定しています。 + +- `load-onnxruntime` +- `link-onnxruntime` + +```console +❯ cargo build --release -p voicevox_core_c_api --features load-onnxruntime +❯ sed 's:^//\(#define VOICEVOX_LOAD_ONNXRUNTIME\)$:\1:' \ + crates/voicevox_core_c_api/include/voicevox_core.h \ + > ./voicevox_core.h +``` + +```console +❯ cargo build --release -p voicevox_core_c_api --features link-onnxruntime +❯ sed 's:^//\(#define VOICEVOX_LINK_ONNXRUNTIME\)$:\1:' \ + crates/voicevox_core_c_api/include/voicevox_core.h \ + > ./voicevox_core.h +``` + +C APIのリリースでは`dlopen`の利用が厳しいiOSでのみ`link-onnxruntime`で、その他は`load-onnxruntime`で +ビルドしています。 diff --git a/docs/usage.md b/docs/usage.md index 91da1c62f..26ed50810 100644 --- a/docs/usage.md +++ b/docs/usage.md @@ -31,10 +31,10 @@ chmod +x download ./download # DirectML版を利用する場合 -./download --device directml +./download --devices directml # CUDA版を利用する場合 -./download --device cuda +./download --devices cuda ``` `voicevox_core`ディレクトリにファイル一式がダウンロードされています。以降の説明ではこのディレクトリで作業を行います。 @@ -62,15 +62,15 @@ VOICEVOX コアでは`Synthesizer`に音声モデルを読み込むことでテ ```python from pprint import pprint -from voicevox_core.blocking import OpenJtalk, Synthesizer, VoiceModel +from voicevox_core.blocking import Onnxruntime, OpenJtalk, Synthesizer, VoiceModelFile # 1. Synthesizerの初期化 open_jtalk_dict_dir = "open_jtalk_dic_utf_8-1.11" -synthesizer = Synthesizer(OpenJtalk(open_jtalk_dict_dir)) +synthesizer = Synthesizer(Onnxruntime.load_once(), OpenJtalk(open_jtalk_dict_dir)) # 2. 音声モデルの読み込み -model = VoiceModel.from_path("model/0.vvm") -synthesizer.load_voice_model(model) +with VoiceModelFile.open("model/0.vvm") as model: + synthesizer.load_voice_model(model) # 3. テキスト音声合成 text = "サンプル音声です" @@ -82,15 +82,15 @@ with open("output.wav", "wb") as f: ### 1. Synthesizer の初期化 -辞書などを取り扱う`OpenJtalk`のインスタンスを引数に渡して`Synthesizer`を初期化します。`Synthesizer`は音声合成だけでなく、音声モデルを複数読み込んだり、イントネーションのみを生成することもできます。 +AIエンジンの`Onnxruntime`のインスタンスと、辞書などを取り扱う`OpenJtalk`のインスタンスを引数に渡して`Synthesizer`を初期化します。`Synthesizer`は音声合成だけでなく、音声モデルを複数読み込んだり、イントネーションのみを生成することもできます。 ### 2. 音声モデルの読み込み -VVM ファイルから`VoiceModel`インスタンスを作成し、`Synthesizer`に読み込ませます。その VVM ファイルにどの声が含まれているかは`VoiceModel`の`.metas`や[音声モデルと声の対応表](https://github.com/VOICEVOX/voicevox_fat_resource/blob/main/core/model/README.md#%E9%9F%B3%E5%A3%B0%E3%83%A2%E3%83%87%E3%83%ABvvm%E3%83%95%E3%82%A1%E3%82%A4%E3%83%AB%E3%81%A8%E5%A3%B0%E3%82%AD%E3%83%A3%E3%83%A9%E3%82%AF%E3%82%BF%E3%83%BC%E3%82%B9%E3%82%BF%E3%82%A4%E3%83%AB%E5%90%8D%E3%81%A8%E3%82%B9%E3%82%BF%E3%82%A4%E3%83%AB-id-%E3%81%AE%E5%AF%BE%E5%BF%9C%E8%A1%A8)で確認できます。 +VVM ファイルから`VoiceModelFile`インスタンスを作成し、`Synthesizer`に読み込ませます。その VVM ファイルにどの声が含まれているかは`VoiceModelFile`の`.metas`や[音声モデルと声の対応表](https://github.com/VOICEVOX/voicevox_fat_resource/blob/main/core/model/README.md#%E9%9F%B3%E5%A3%B0%E3%83%A2%E3%83%87%E3%83%ABvvm%E3%83%95%E3%82%A1%E3%82%A4%E3%83%AB%E3%81%A8%E5%A3%B0%E3%82%AD%E3%83%A3%E3%83%A9%E3%82%AF%E3%82%BF%E3%83%BC%E3%82%B9%E3%82%BF%E3%82%A4%E3%83%AB%E5%90%8D%E3%81%A8%E3%82%B9%E3%82%BF%E3%82%A4%E3%83%AB-id-%E3%81%AE%E5%AF%BE%E5%BF%9C%E8%A1%A8)で確認できます。 ```python -model = VoiceModel.from_path("model/0.vvm") -pprint(model.metas) +with VoiceModelFile.open("model/0.vvm") as model: + pprint(model.metas) ``` ```txt diff --git a/docs/vvm.md b/docs/vvm.md index c2de7fb41..cc7b9232e 100644 --- a/docs/vvm.md +++ b/docs/vvm.md @@ -14,7 +14,7 @@ model は `.onnx` や `.bin` など様々ある。例えば `sample.vvm` は `predict_duration.onnx` / `predict_intonation.onnx` / `decode.onnx` を含む。 -VOICEVOX OSS が提供する VVM には [`sample.vvm`](https://github.com/VOICEVOX/voicevox_core/tree/main/model) がある。 +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) で確認できる。 ## マニフェストファイル diff --git a/example/cpp/unix/simple_tts.cpp b/example/cpp/unix/simple_tts.cpp index c7683d49a..210df1549 100644 --- a/example/cpp/unix/simple_tts.cpp +++ b/example/cpp/unix/simple_tts.cpp @@ -20,14 +20,21 @@ int main(int argc, char *argv[]) { std::cout << "coreの初期化中..." << std::endl; auto initialize_options = voicevox_make_default_initialize_options(); + const VoicevoxOnnxruntime* onnxruntime; + auto load_ort_options = voicevox_make_default_load_onnxruntime_options(); + auto result = voicevox_onnxruntime_load_once(load_ort_options, &onnxruntime); + if (result != VOICEVOX_RESULT_OK){ + std::cerr << voicevox_error_result_to_message(result) << std::endl; + return 1; + } OpenJtalkRc* open_jtalk; - auto result = voicevox_open_jtalk_rc_new(open_jtalk_dict_path.c_str(),&open_jtalk); + result = voicevox_open_jtalk_rc_new(open_jtalk_dict_path.c_str(),&open_jtalk); if (result != VOICEVOX_RESULT_OK){ std::cerr << voicevox_error_result_to_message(result) << std::endl; return 1; } VoicevoxSynthesizer* synthesizer; - result = voicevox_synthesizer_new(open_jtalk,initialize_options,&synthesizer); + result = voicevox_synthesizer_new(onnxruntime,open_jtalk,initialize_options,&synthesizer); if (result != VOICEVOX_RESULT_OK) { std::cerr << voicevox_error_result_to_message(result) << std::endl; return 1; @@ -40,8 +47,8 @@ int main(int argc, char *argv[]) { if (path.extension() != ".vvm") { continue; } - VoicevoxVoiceModel* model; - result = voicevox_voice_model_new_from_path(path.c_str(), &model); + VoicevoxVoiceModelFile* model; + result = voicevox_voice_model_file_open(path.c_str(), &model); if (result != VoicevoxResultCode::VOICEVOX_RESULT_OK) { std::cerr << voicevox_error_result_to_message(result) << std::endl; return 0; @@ -51,7 +58,7 @@ int main(int argc, char *argv[]) { std::cerr << voicevox_error_result_to_message(result) << std::endl; return 0; } - voicevox_voice_model_delete(model); + voicevox_voice_model_file_close(model); } std::cout << "音声生成中..." << std::endl; diff --git a/example/cpp/windows/README.md b/example/cpp/windows/README.md index 660d4190c..4012acdf9 100644 --- a/example/cpp/windows/README.md +++ b/example/cpp/windows/README.md @@ -14,7 +14,7 @@ Visual Studio Installerを使用しインストールしてください。 出力フォルダを作成するために、一度ビルドします。「windows_example.sln」をVisual Studioで開き、メニューの「ビルド」→「ソリューションのビルド」を押します。 この段階では、ビルドは失敗します。「bin」フォルダと「lib」フォルダが生成されていればOKです。 -[Releases](https://github.com/VOICEVOX/voicevox_core/releases/latest)から「voicevox_core-windows-x64-cpu-{バージョン名}.zip」をダウンロードし、展開します。[ダウンローダー](https://github.com/VOICEVOX/voicevox_core/blob/main/docs/downloader.md)を使うと便利です。 +[Releases](https://github.com/VOICEVOX/voicevox_core/releases/latest)から「voicevox_core-windows-x64-{バージョン名}.zip」をダウンロードし、展開します。[ダウンローダー](https://github.com/VOICEVOX/voicevox_core/blob/main/docs/downloader.md)を使うと便利です。 展開してできたファイル・フォルダをそれぞれ下記のフォルダへ配置します。 - simple_tts に配置 diff --git a/example/cpp/windows/simple_tts/simple_tts.cpp b/example/cpp/windows/simple_tts/simple_tts.cpp index bd070505f..2bdc947c6 100644 --- a/example/cpp/windows/simple_tts/simple_tts.cpp +++ b/example/cpp/windows/simple_tts/simple_tts.cpp @@ -33,14 +33,21 @@ int main() { VoicevoxInitializeOptions initializeOptions = voicevox_make_default_initialize_options(); std::string dict = GetOpenJTalkDict(); + const VoicevoxOnnxruntime* onnxruntime; + auto load_ort_options = voicevox_make_default_load_onnxruntime_options(); + auto result = voicevox_onnxruntime_load_once(load_ort_options, &onnxruntime); + if (result != VoicevoxResultCode::VOICEVOX_RESULT_OK) { + OutErrorMessage(result); + return 0; + } OpenJtalkRc* open_jtalk; - auto result = voicevox_open_jtalk_rc_new(dict.c_str(),&open_jtalk); + result = voicevox_open_jtalk_rc_new(dict.c_str(),&open_jtalk); if (result != VoicevoxResultCode::VOICEVOX_RESULT_OK) { OutErrorMessage(result); return 0; } VoicevoxSynthesizer* synthesizer; - result = voicevox_synthesizer_new(open_jtalk,initializeOptions,&synthesizer); + result = voicevox_synthesizer_new(onnxruntime,open_jtalk,initializeOptions,&synthesizer); if (result != VoicevoxResultCode::VOICEVOX_RESULT_OK) { OutErrorMessage(result); return 0; @@ -52,9 +59,8 @@ int main() { if (path.extension() != ".vvm") { continue; } - VoicevoxVoiceModel* model; - result = voicevox_voice_model_new_from_path(path.generic_u8string().c_str(), - &model); + VoicevoxVoiceModelFile* model; + result = voicevox_voice_model_file_open(path.generic_u8string().c_str(), &model); if (result != VoicevoxResultCode::VOICEVOX_RESULT_OK) { OutErrorMessage(result); return 0; @@ -64,7 +70,7 @@ int main() { OutErrorMessage(result); return 0; } - voicevox_voice_model_delete(model); + voicevox_voice_model_file_close(model); } std::wcout << L"音声生成中" << std::endl; diff --git a/example/kotlin/README.md b/example/kotlin/README.md index 908bdd51f..7dc5b637c 100644 --- a/example/kotlin/README.md +++ b/example/kotlin/README.md @@ -47,6 +47,7 @@ Usage: voicevoxcoreexample options_list Options: --mode [AUTO] -> モード { Value should be one of [auto, cpu, gpu] } --vvm -> vvmファイルへのパス (always required) { String } + --onnxruntime [libonnxruntime.so.1.17.3] -> ONNX Runtimeのファイル名(モジュール名)もしくはファイルパス { String } --dictDir [./open_jtalk_dic_utf_8-1.11] -> Open JTalkの辞書ディレクトリ { String } --text [この音声は、ボイスボックスを使用して、出力されています。] -> 読み上げさせたい文章 { String } --out [./output.wav] -> 出力wavファイルのパス { String } @@ -56,10 +57,14 @@ Options: ## 実行例 + + + ```console -❯ ./gradlew run --args="--vvm ../../model/sample.vvm" -Inititalizing: AUTO, ./open_jtalk_dic_utf_8-1.11 -Loading: ../../model/sample.vvm +❯ # Linuxの場合 +❯ ./gradlew run --args="--vvm ../../crates/test_util/data/model/sample.vvm --onnxruntime ../../crates/test_util/data/lib/libonnxruntime.so.1.17.3" +Inititalizing: AUTO, ../../crates/test_util/data/lib/libonnxruntime.so.1.17.3, ./open_jtalk_dic_utf_8-1.11 +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/kotlin/app/build.gradle b/example/kotlin/app/build.gradle index 81ac24a49..92a8c5036 100644 --- a/example/kotlin/app/build.gradle +++ b/example/kotlin/app/build.gradle @@ -16,7 +16,7 @@ dependencies { testImplementation 'org.jetbrains.kotlin:kotlin-test-junit5' // Use the JUnit 5 integration. - testImplementation 'org.junit.jupiter:junit-jupiter-engine:5.9.2' + testImplementation 'org.junit.jupiter:junit-jupiter-engine:5.11.2' testRuntimeOnly 'org.junit.platform:junit-platform-launcher' @@ -25,12 +25,12 @@ dependencies { // TODO: ちゃんと配布したらそれに置き換える implementation files("../../../crates/voicevox_core_java_api/lib/build/libs/lib-0.0.0.jar") // https://mvnrepository.com/artifact/com.google.code.gson/gson - implementation group: 'com.google.code.gson', name: 'gson', version: "2.10.1" + implementation group: 'com.google.code.gson', name: 'gson', version: "2.11.0" // https://mvnrepository.com/artifact/jakarta.validation/jakarta.validation-api - implementation group: 'jakarta.validation', name: 'jakarta.validation-api', version: "3.0.2" + implementation group: 'jakarta.validation', name: 'jakarta.validation-api', version: "3.1.0" // https://mvnrepository.com/artifact/jakarta.annotation/jakarta.annotation-api implementation group: 'jakarta.annotation', name: 'jakarta.annotation-api', version: "2.1.1" - implementation group: 'com.microsoft.onnxruntime', name: 'onnxruntime', version: "1.14.0" + implementation group: 'com.microsoft.onnxruntime', name: 'onnxruntime', version: "1.19.2" } // Apply a specific Java toolchain to ease working on different environments. diff --git a/example/kotlin/app/src/main/kotlin/app/App.kt b/example/kotlin/app/src/main/kotlin/app/App.kt index 3e76043f4..0b8d05e33 100644 --- a/example/kotlin/app/src/main/kotlin/app/App.kt +++ b/example/kotlin/app/src/main/kotlin/app/App.kt @@ -15,6 +15,10 @@ fun main(args: Array) { val mode by parser.option(ArgType.Choice(), description = "モード").default(Mode.AUTO) val vvmPath by parser.option(ArgType.String, fullName = "vvm", description = "vvmファイルへのパス").required() + val onnxruntime by + parser + .option(ArgType.String, description = "ONNX Runtimeのファイル名(モジュール名)もしくはファイルパス") + .default(Onnxruntime.LIB_VERSIONED_FILENAME) val dictDir by parser .option(ArgType.String, description = "Open JTalkの辞書ディレクトリ") @@ -28,10 +32,11 @@ fun main(args: Array) { parser.parse(args) - println("Inititalizing: ${mode}, ${dictDir}") + println("Inititalizing: ${mode}, ${onnxruntime}, ${dictDir}") + val ort = Onnxruntime.loadOnce().filename(onnxruntime).exec() val openJtalk = OpenJtalk(dictDir) val synthesizer = - Synthesizer.builder(openJtalk) + Synthesizer.builder(ort, openJtalk) .accelerationMode( when (mode) { Mode.AUTO -> Synthesizer.AccelerationMode.AUTO @@ -42,7 +47,7 @@ fun main(args: Array) { .build() println("Loading: ${vvmPath}") - val vvm = VoiceModel(vvmPath) + val vvm = VoiceModelFile(vvmPath) synthesizer.loadVoiceModel(vvm) println("Creating an AudioQuery from the text: ${text}") diff --git a/example/kotlin/gradle/wrapper/gradle-wrapper.properties b/example/kotlin/gradle/wrapper/gradle-wrapper.properties index 9f4197d5f..df97d72b8 100644 --- a/example/kotlin/gradle/wrapper/gradle-wrapper.properties +++ b/example/kotlin/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.2.1-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.10.2-bin.zip networkTimeout=10000 validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME diff --git a/example/python/README.md b/example/python/README.md index 392896030..48678edb2 100644 --- a/example/python/README.md +++ b/example/python/README.md @@ -18,6 +18,8 @@ https://github.com/VOICEVOX/voicevox_core/releases/latest 2. ダウンローダーを使って環境構築します。 +FIXME: 今は`--exclude core`がある + linux/mac の場合 download-linux-x64 のところはアーキテクチャや OS によって適宜読み替えてください。 @@ -69,12 +71,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/example/python/run-asyncio.py b/example/python/run-asyncio.py index 70d204a92..176ac290f 100644 --- a/example/python/run-asyncio.py +++ b/example/python/run-asyncio.py @@ -8,9 +8,8 @@ from pathlib import Path from typing import Tuple -import voicevox_core from voicevox_core import AccelerationMode, AudioQuery -from voicevox_core.asyncio import OpenJtalk, Synthesizer, VoiceModel +from voicevox_core.asyncio import Onnxruntime, OpenJtalk, Synthesizer, VoiceModelFile async def main() -> None: @@ -23,25 +22,31 @@ async def main() -> None: ( acceleration_mode, vvm_path, + onnxruntime_filename, open_jtalk_dict_dir, text, out, style_id, ) = parse_args() - logger.debug("%s", f"{voicevox_core.supported_devices()=}") + logger.info("%s", f"Loading ONNX Runtime ({onnxruntime_filename=})") + onnxruntime = await Onnxruntime.load_once(filename=onnxruntime_filename) + + logger.debug("%s", f"{onnxruntime.supported_devices()=}") logger.info("%s", f"Initializing ({acceleration_mode=}, {open_jtalk_dict_dir=})") synthesizer = Synthesizer( - await OpenJtalk.new(open_jtalk_dict_dir), acceleration_mode=acceleration_mode + onnxruntime, + await OpenJtalk.new(open_jtalk_dict_dir), + acceleration_mode=acceleration_mode, ) logger.debug("%s", f"{synthesizer.metas=}") logger.debug("%s", f"{synthesizer.is_gpu_mode=}") logger.info("%s", f"Loading `{vvm_path}`") - model = await VoiceModel.from_path(vvm_path) - await synthesizer.load_voice_model(model) + async with await VoiceModelFile.open(vvm_path) as model: + await synthesizer.load_voice_model(model) logger.info("%s", f"Creating an AudioQuery from {text!r}") audio_query = await synthesizer.audio_query(text, style_id) @@ -53,7 +58,7 @@ async def main() -> None: logger.info("%s", f"Wrote `{out}`") -def parse_args() -> Tuple[AccelerationMode, Path, Path, str, Path, int]: +def parse_args() -> Tuple[AccelerationMode, Path, str, Path, str, Path, int]: argparser = ArgumentParser() argparser.add_argument( "--mode", @@ -66,6 +71,11 @@ def parse_args() -> Tuple[AccelerationMode, Path, Path, str, Path, int]: type=Path, help="vvmファイルへのパス", ) + argparser.add_argument( + "--onnxruntime", + default=Onnxruntime.LIB_VERSIONED_FILENAME, + help="ONNX Runtimeのライブラリのfilename", + ) argparser.add_argument( "--dict-dir", default="./open_jtalk_dic_utf_8-1.11", @@ -90,7 +100,16 @@ def parse_args() -> Tuple[AccelerationMode, Path, Path, str, Path, int]: help="話者IDを指定", ) args = argparser.parse_args() - return (args.mode, args.vvm, args.dict_dir, args.text, args.out, args.style_id) + # FIXME: 流石に多くなってきたので、`dataclass`化する + return ( + args.mode, + args.vvm, + args.onnxruntime, + args.dict_dir, + args.text, + args.out, + args.style_id, + ) def display_as_json(audio_query: AudioQuery) -> str: diff --git a/example/python/run.py b/example/python/run.py index a57139b1c..5f11a1a62 100644 --- a/example/python/run.py +++ b/example/python/run.py @@ -5,9 +5,8 @@ from pathlib import Path from typing import Tuple -import voicevox_core from voicevox_core import AccelerationMode, AudioQuery -from voicevox_core.blocking import OpenJtalk, Synthesizer, VoiceModel +from voicevox_core.blocking import Onnxruntime, OpenJtalk, Synthesizer, VoiceModelFile def main() -> None: @@ -20,25 +19,31 @@ def main() -> None: ( acceleration_mode, vvm_path, + onnxruntime_filename, open_jtalk_dict_dir, text, out, style_id, ) = parse_args() - logger.debug("%s", f"{voicevox_core.supported_devices()=}") + logger.info("%s", f"Loading ONNX Runtime ({onnxruntime_filename=})") + onnxruntime = Onnxruntime.load_once(filename=onnxruntime_filename) + + logger.debug("%s", f"{onnxruntime.supported_devices()=}") logger.info("%s", f"Initializing ({acceleration_mode=}, {open_jtalk_dict_dir=})") synthesizer = Synthesizer( - OpenJtalk(open_jtalk_dict_dir), acceleration_mode=acceleration_mode + onnxruntime, + OpenJtalk(open_jtalk_dict_dir), + acceleration_mode=acceleration_mode, ) logger.debug("%s", f"{synthesizer.metas=}") logger.debug("%s", f"{synthesizer.is_gpu_mode=}") logger.info("%s", f"Loading `{vvm_path}`") - model = VoiceModel.from_path(vvm_path) - synthesizer.load_voice_model(model) + with VoiceModelFile.open(vvm_path) as model: + synthesizer.load_voice_model(model) logger.info("%s", f"Creating an AudioQuery from {text!r}") audio_query = synthesizer.audio_query(text, style_id) @@ -50,7 +55,7 @@ def main() -> None: logger.info("%s", f"Wrote `{out}`") -def parse_args() -> Tuple[AccelerationMode, Path, Path, str, Path, int]: +def parse_args() -> Tuple[AccelerationMode, Path, str, Path, str, Path, int]: argparser = ArgumentParser() argparser.add_argument( "--mode", @@ -63,6 +68,11 @@ def parse_args() -> Tuple[AccelerationMode, Path, Path, str, Path, int]: type=Path, help="vvmファイルへのパス", ) + argparser.add_argument( + "--onnxruntime", + default=Onnxruntime.LIB_VERSIONED_FILENAME, + help="ONNX Runtimeのライブラリのfilename", + ) argparser.add_argument( "--dict-dir", default="./open_jtalk_dic_utf_8-1.11", @@ -87,7 +97,16 @@ def parse_args() -> Tuple[AccelerationMode, Path, Path, str, Path, int]: help="話者IDを指定", ) args = argparser.parse_args() - return (args.mode, args.vvm, args.dict_dir, args.text, args.out, args.style_id) + # FIXME: 流石に多くなってきたので、`dataclass`化する + return ( + args.mode, + args.vvm, + args.onnxruntime, + args.dict_dir, + args.text, + args.out, + args.style_id, + ) def display_as_json(audio_query: AudioQuery) -> str: diff --git a/model/sample.vvm b/model/sample.vvm deleted file mode 100644 index f541ae082..000000000 Binary files a/model/sample.vvm and /dev/null differ diff --git a/model/sample.vvm/manifest.json b/model/sample.vvm/manifest.json new file mode 100644 index 000000000..1075a0797 --- /dev/null +++ b/model/sample.vvm/manifest.json @@ -0,0 +1,15 @@ +{ + "vvm_format_version": 1, + "id": "018fa5b1-146c-71e9-b523-6f6dabcf05fe", + "metas_filename": "metas.json", + "talk": { + "predict_duration_filename": "predict_duration.onnx", + "predict_intonation_filename": "predict_intonation.onnx", + "generate_full_intermediate_filename": "predict_spectrogram.onnx", + "render_audio_segment_filename": "vocoder.onnx", + "style_id_to_inner_voice_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/model/sample.vvm/predict_spectrogram.onnx b/model/sample.vvm/predict_spectrogram.onnx new file mode 100644 index 000000000..ad9da06c2 Binary files /dev/null and b/model/sample.vvm/predict_spectrogram.onnx differ diff --git a/crates/voicevox_core/src/test_data/model_sources/load_model_works1/decode.onnx b/model/sample.vvm/vocoder.onnx similarity index 97% rename from crates/voicevox_core/src/test_data/model_sources/load_model_works1/decode.onnx rename to model/sample.vvm/vocoder.onnx index 0551a8c16..f405f6832 100644 Binary files a/crates/voicevox_core/src/test_data/model_sources/load_model_works1/decode.onnx and b/model/sample.vvm/vocoder.onnx differ diff --git a/renovate.json b/renovate.json deleted file mode 100644 index 5704cd5f4..000000000 --- a/renovate.json +++ /dev/null @@ -1,34 +0,0 @@ -{ - "$schema": "https://docs.renovatebot.com/renovate-schema.json", - "extends": [ - "config:recommended" - ], - "timezone": "Asia/Tokyo", - "packageRules": [ - { - "groupSlug": "rust", - "groupName": "Rust", - "matchDepPatterns": "^Rust$" - }, - { - "groupSlug": "others", - "groupName": "Others", - "excludeDepPatterns": "^Rust$", - "dependencyDashboardApproval": true - } - ], - "customManagers": [ - { - "customType": "regex", - "fileMatch": [ - "^rust-toolchain$" - ], - "matchStrings": [ - "(?\\d+\\.\\d+\\.\\d+)" - ], - "depNameTemplate": "Rust", - "packageNameTemplate": "rust-lang/rust", - "datasourceTemplate": "github-releases" - } - ] -} diff --git a/renovate.json5 b/renovate.json5 new file mode 100644 index 000000000..ae5cc787d --- /dev/null +++ b/renovate.json5 @@ -0,0 +1,100 @@ +{ + $schema: "https://docs.renovatebot.com/renovate-schema.json", + extends: [ + "config:recommended", + ], + timezone: "Asia/Tokyo", + separateMajorMinor: false, + dependencyDashboardApproval: true, + packageRules: [ + // `separateMajorMinor`を無効化した上で次の二つのgroupにすべてをまとめる。 + // + // * "major dependencies" (`renovate/major-dependencies`) + // * "non-major dependencies" (`renovate/non-major-dependencies`) + // + // バージョン0.y.z (y≧1)のyとバージョン0.0.zのzの変更は"major dependencies"の方に含むようにする。 + + // メジャーバージョンの更新 + { + groupName: "major dependencies", + matchUpdateTypes: [ + "major", + ], + dependencyDashboardApproval: false, + }, + { + groupName: "major dependencies", + matchUpdateTypes: [ + "minor", + ], + matchCurrentVersion: "/^v?0\\./", + dependencyDashboardApproval: false, + }, + { + groupName: "major dependencies", + matchUpdateTypes: [ + "patch", + ], + matchCurrentVersion: "/^v?0\\.0\\./", + dependencyDashboardApproval: false, + }, + + // メジャーバージョン以外の更新 + { + groupName: "non-major dependencies", + matchUpdateTypes: [ + "minor", + ], + matchCurrentVersion: "!/^v?0\\./", + }, + { + groupName: "non-major dependencies", + matchUpdateTypes: [ + "patch", + ], + matchCurrentVersion: "!/^v?0\\.0\\./", + }, + + // GHAのrunnerに対しては無効化する + { + matchDatasources: [ + "github-runners", + ], + matchPackageNames: [ + "windows", + "macos", + "ubuntu", + ], + enabled: false, + }, + ], + cargo: { + rangeStrategy: "bump", + }, + customManagers: [ + { + customType: "regex", + fileMatch: [ + "^rust-toolchain$", + ], + matchStrings: [ + "(?\\d+\\.\\d+\\.\\d+)", + ], + depNameTemplate: "Rust", + packageNameTemplate: "rust-lang/rust", + datasourceTemplate: "github-tags", + }, + { + customType: "regex", + fileMatch: [ + "^Cargo.toml$", + ], + matchStrings: [ + "rust-version = \"(?\\d+\\.\\d+\\.\\d+)\"", + ], + depNameTemplate: "Rust", + packageNameTemplate: "rust-lang/rust", + datasourceTemplate: "github-tags", + }, + ], +} diff --git a/rust-toolchain b/rust-toolchain index 54227249d..dbd41264a 100644 --- a/rust-toolchain +++ b/rust-toolchain @@ -1 +1 @@ -1.78.0 +1.81.0