├── .dockerignore
├── .env.sample
├── .github
├── FUNDING.yml
├── ISSUE_TEMPLATE
│ ├── bug_report.md
│ └── feature_request.md
├── dependabot.yml
├── pull_request_template.md
└── workflows
│ ├── build.Dockerfile
│ ├── build.yml
│ └── lint.yml
├── .gitignore
├── Cargo.lock
├── Cargo.toml
├── LICENSE
├── README.md
├── crates
├── sbv2_api
│ ├── Cargo.toml
│ ├── build.rs
│ └── src
│ │ ├── error.rs
│ │ └── main.rs
├── sbv2_bindings
│ ├── Cargo.toml
│ ├── pyproject.toml
│ └── src
│ │ ├── lib.rs
│ │ ├── sbv2.rs
│ │ └── style.rs
├── sbv2_core
│ ├── Cargo.toml
│ ├── build.rs
│ ├── mora_convert.py
│ └── src
│ │ ├── bert.rs
│ │ ├── error.rs
│ │ ├── jtalk.rs
│ │ ├── lib.rs
│ │ ├── main.rs
│ │ ├── model.rs
│ │ ├── mora.rs
│ │ ├── mora_list.json
│ │ ├── nlp.rs
│ │ ├── norm.rs
│ │ ├── sbv2file.rs
│ │ ├── style.rs
│ │ ├── tokenizer.rs
│ │ ├── tts.rs
│ │ ├── tts_util.rs
│ │ └── utils.rs
├── sbv2_editor
│ ├── Cargo.toml
│ ├── README.md
│ ├── query2.json
│ └── src
│ │ ├── error.rs
│ │ └── main.rs
└── sbv2_wasm
│ ├── Cargo.toml
│ ├── README.md
│ ├── biome.json
│ ├── build.sh
│ ├── example.js
│ ├── package.json
│ ├── pnpm-lock.yaml
│ ├── src-js
│ └── index.ts
│ ├── src
│ ├── array_helper.rs
│ └── lib.rs
│ └── tsconfig.json
├── models
└── .gitkeep
├── renovate.json
├── scripts
├── .gitignore
├── convert
│ ├── .python-version
│ ├── README.md
│ ├── convert_deberta.py
│ ├── convert_model.py
│ └── requirements.txt
├── docker
│ ├── cpu.Dockerfile
│ ├── cuda.Dockerfile
│ ├── run_cpu.sh
│ └── run_cuda.sh
├── make_dict.sh
├── sbv2-bindings-colab.ipynb
├── sbv2-test-api.py
└── sbv2-test-bindings.py
└── test.py
/.dockerignore:
--------------------------------------------------------------------------------
1 | target/
2 | models/
3 | docker/
4 | .env*
5 | renovate.json
6 | *.py
--------------------------------------------------------------------------------
/.env.sample:
--------------------------------------------------------------------------------
1 | BERT_MODEL_PATH=models/deberta.onnx
2 | MODEL_PATH=models/tsukuyomi.sbv2
3 | MODELS_PATH=models
4 | TOKENIZER_PATH=models/tokenizer.json
5 | ADDR=localhost:3000
6 | RUST_LOG=warn
7 | HOLDER_MAX_LOADED_MODElS=20
8 |
--------------------------------------------------------------------------------
/.github/FUNDING.yml:
--------------------------------------------------------------------------------
1 | # These are supported funding model platforms
2 |
3 | github: [tuna2134]
4 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/bug_report.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: バグの報告
3 | about: バグを報告する場所です。
4 | title: ''
5 | labels: bug
6 | assignees: ''
7 |
8 | ---
9 |
10 | **バグの説明**
11 | バグのエラーを張ってください
12 |
13 | **再現する方法**
14 | どのようにバグが発生したか時系列でまとめてください。
15 |
16 | **本来の挙動**
17 | 本来動作すべきことについて簡潔にまとめてください。
18 |
19 | **スクリーンショット**
20 | もしもあるならでいいです。
21 |
22 | **端末の情報**
23 | - OS: [e.g. Linux]
24 |
25 | **コード**
26 | ```rs
27 | ここにコード貼ってください
28 | ```
29 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/feature_request.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: 機能追加
3 | about: 機能追加してほしい場合これで作ってください。
4 | title: ''
5 | labels: enhancement
6 | assignees: ''
7 |
8 | ---
9 |
10 | **機能追加の説明**
11 | ここで追加される機能の説明してください。
12 |
13 | **メリット**
14 | ここにメリットを書いてください。
15 |
16 | **デメリット**
17 | ここにデメリットを書いてください。
18 |
--------------------------------------------------------------------------------
/.github/dependabot.yml:
--------------------------------------------------------------------------------
1 | # To get started with Dependabot version updates, you'll need to specify which
2 | # package ecosystems to update and where the package manifests are located.
3 | # Please see the documentation for all configuration options:
4 | # https://docs.github.com/code-security/dependabot/dependabot-version-updates/configuration-options-for-the-dependabot.yml-file
5 |
6 | version: 2
7 | updates:
8 | - package-ecosystem: "cargo" # See documentation for possible values
9 | directory: "/" # Location of package manifests
10 | schedule:
11 | interval: "weekly"
12 |
--------------------------------------------------------------------------------
/.github/pull_request_template.md:
--------------------------------------------------------------------------------
1 | ## 概要
2 |
5 |
6 | ## 関連issue
7 |
11 |
12 | ## 確認
13 | - [ ] 動作確認しましたか?
14 |
--------------------------------------------------------------------------------
/.github/workflows/build.Dockerfile:
--------------------------------------------------------------------------------
1 | FROM ubuntu:latest
2 | RUN apt update && apt install openssl libssl-dev curl pkg-config software-properties-common -y && add-apt-repository ppa:deadsnakes/ppa && apt update && apt install python3.7 python3.8 python3.9 python3.10 python3.11 python3.12 python3.13 python3-pip python3 -y
3 | ENV PIP_BREAK_SYSTEM_PACKAGES=1
4 | RUN mkdir -p /root/.cache/sbv2 && curl https://huggingface.co/neody/sbv2-api-assets/resolve/main/dic/all.bin -o /root/.cache/sbv2/all.bin -L
--------------------------------------------------------------------------------
/.github/workflows/build.yml:
--------------------------------------------------------------------------------
1 | name: Build
2 |
3 | on:
4 | push:
5 | branches:
6 | - main
7 | tags:
8 | - '*'
9 | workflow_dispatch:
10 |
11 | permissions:
12 | contents: write
13 | id-token: write
14 | packages: write
15 |
16 | jobs:
17 | python-linux:
18 | runs-on: ${{ matrix.platform.runner }}
19 | strategy:
20 | matrix:
21 | platform:
22 | - runner: ubuntu-latest
23 | target: x86_64
24 | - runner: ubuntu-24.04-arm
25 | target: aarch64
26 | steps:
27 | - uses: actions/checkout@v4
28 | - uses: actions/setup-python@v5
29 | with:
30 | python-version: 3.x
31 | - run: docker build . -f .github/workflows/build.Dockerfile --tag ci
32 | - name: Build wheels
33 | uses: PyO3/maturin-action@v1
34 | with:
35 | target: ${{ matrix.platform.target }}
36 | args: --release --out dist --find-interpreter
37 | sccache: 'true'
38 | manylinux: auto
39 | container: ci
40 | working-directory: ./crates/sbv2_bindings
41 | - name: Upload wheels
42 | uses: actions/upload-artifact@v4
43 | with:
44 | name: wheels-linux-${{ matrix.platform.target }}
45 | path: ./crates/sbv2_bindings/dist
46 |
47 | python-windows:
48 | runs-on: ${{ matrix.platform.runner }}
49 | strategy:
50 | matrix:
51 | platform:
52 | - runner: windows-latest
53 | target: x64
54 | steps:
55 | - uses: actions/checkout@v4
56 | - uses: actions/setup-python@v5
57 | with:
58 | python-version: 3.x
59 | architecture: ${{ matrix.platform.target }}
60 | - name: Build wheels
61 | uses: PyO3/maturin-action@v1
62 | with:
63 | target: ${{ matrix.platform.target }}
64 | args: --release --out dist --find-interpreter
65 | sccache: 'true'
66 | working-directory: ./crates/sbv2_bindings
67 | - name: Upload wheels
68 | uses: actions/upload-artifact@v4
69 | with:
70 | name: wheels-windows-${{ matrix.platform.target }}
71 | path: ./crates/sbv2_bindings/dist
72 |
73 | python-macos:
74 | runs-on: ${{ matrix.platform.runner }}
75 | strategy:
76 | matrix:
77 | platform:
78 | - runner: macos-14
79 | target: aarch64
80 | steps:
81 | - uses: actions/checkout@v4
82 | - uses: actions/setup-python@v5
83 | with:
84 | python-version: 3.x
85 | - name: Build wheels
86 | uses: PyO3/maturin-action@v1
87 | with:
88 | target: ${{ matrix.platform.target }}
89 | args: --release --out dist --find-interpreter
90 | sccache: 'true'
91 | working-directory: ./crates/sbv2_bindings
92 | - name: Upload wheels
93 | uses: actions/upload-artifact@v4
94 | with:
95 | name: wheels-macos-${{ matrix.platform.target }}
96 | path: ./crates/sbv2_bindings/dist
97 |
98 | python-sdist:
99 | runs-on: ubuntu-latest
100 | steps:
101 | - uses: actions/checkout@v4
102 | - name: Build sdist
103 | uses: PyO3/maturin-action@v1
104 | with:
105 | command: sdist
106 | args: --out dist
107 | working-directory: ./crates/sbv2_bindings
108 | - name: Upload sdist
109 | uses: actions/upload-artifact@v4
110 | with:
111 | name: wheels-sdist
112 | path: ./crates/sbv2_bindings/dist
113 |
114 | python-wheel:
115 | name: Wheel Upload
116 | runs-on: ubuntu-latest
117 | needs: [python-linux, python-windows, python-macos, python-sdist]
118 | env:
119 | GH_TOKEN: ${{ github.token }}
120 | steps:
121 | - uses: actions/checkout@v4
122 | - run: gh run download ${{ github.run_id }} -p wheels-*
123 | - name: release
124 | run: |
125 | gh release create commit-${GITHUB_SHA:0:8} --prerelease wheels-*/*
126 |
127 | python-release:
128 | name: Release
129 | runs-on: ubuntu-latest
130 | if: "startsWith(github.ref, 'refs/tags/')"
131 | needs: [python-linux, python-windows, python-macos, python-sdist]
132 | environment: release
133 | env:
134 | GH_TOKEN: ${{ github.token }}
135 | steps:
136 | - uses: actions/checkout@v4
137 | - run: gh run download ${{ github.run_id }} -p wheels-*
138 | - name: Publish to PyPI
139 | uses: PyO3/maturin-action@v1
140 | with:
141 | command: upload
142 | args: --non-interactive --skip-existing wheels-*/*
143 |
144 | docker:
145 | runs-on: ${{ matrix.machine.runner }}
146 | strategy:
147 | fail-fast: false
148 | matrix:
149 | machine:
150 | - platform: amd64
151 | runner: ubuntu-latest
152 | - platform: arm64
153 | runner: ubuntu-24.04-arm
154 | tag: [cpu, cuda]
155 | steps:
156 | - name: Prepare
157 | run: |
158 | platform=${{ matrix.machine.platform }}
159 | echo "PLATFORM_PAIR=${platform//\//-}" >> $GITHUB_ENV
160 |
161 | - name: Docker meta
162 | id: meta
163 | uses: docker/metadata-action@v5
164 | with:
165 | images: |
166 | ghcr.io/${{ github.repository }}
167 |
168 | - name: Login to GHCR
169 | uses: docker/login-action@v3
170 | with:
171 | registry: ghcr.io
172 | username: ${{ github.repository_owner }}
173 | password: ${{ secrets.GITHUB_TOKEN }}
174 |
175 | - name: Set up QEMU
176 | uses: docker/setup-qemu-action@v3
177 |
178 | - name: Set up Docker Buildx
179 | uses: docker/setup-buildx-action@v3
180 |
181 | - name: Build and push by digest
182 | id: build
183 | uses: docker/build-push-action@v6
184 | with:
185 | labels: ${{ steps.meta.outputs.labels }}
186 | file: ./scripts/docker/${{ matrix.tag }}.Dockerfile
187 | push: true
188 | tags: |
189 | ghcr.io/${{ github.repository }}:latest-${{ matrix.tag }}-${{ matrix.machine.platform }}
190 |
191 | docker-merge:
192 | runs-on: ubuntu-latest
193 | needs:
194 | - docker
195 | steps:
196 | - name: Download digests
197 | uses: actions/download-artifact@v4
198 | with:
199 | path: ${{ runner.temp }}/digests
200 | pattern: digests-*
201 | merge-multiple: true
202 |
203 | - name: Login to GHCR
204 | uses: docker/login-action@v3
205 | with:
206 | registry: ghcr.io
207 | username: ${{ github.repository_owner }}
208 | password: ${{ secrets.GITHUB_TOKEN }}
209 |
210 | - name: Set up Docker Buildx
211 | uses: docker/setup-buildx-action@v3
212 |
213 | - name: Merge
214 | run: |
215 | docker buildx imagetools create -t ghcr.io/${{ github.repository }}:cuda \
216 | ghcr.io/${{ github.repository }}:latest-cuda-amd64 \
217 | ghcr.io/${{ github.repository }}:latest-cuda-arm64
218 | docker buildx imagetools create -t ghcr.io/${{ github.repository }}:cpu \
219 | ghcr.io/${{ github.repository }}:latest-cpu-amd64 \
220 | ghcr.io/${{ github.repository }}:latest-cpu-arm64
221 |
--------------------------------------------------------------------------------
/.github/workflows/lint.yml:
--------------------------------------------------------------------------------
1 | name: Lint
2 |
3 | on:
4 | pull_request:
5 |
6 | jobs:
7 | check:
8 | runs-on: ubuntu-latest
9 | strategy:
10 | fail-fast: false
11 | matrix:
12 | components:
13 | - rustfmt
14 | - clippy
15 | steps:
16 | - name: Setup
17 | uses: actions/checkout@v4
18 | - uses: actions-rust-lang/setup-rust-toolchain@v1
19 | with:
20 | components: ${{ matrix.components }}
21 | - name: Format
22 | if: ${{ matrix.components == 'rustfmt' }}
23 | run: cargo fmt --all -- --check
24 | - name: Lint
25 | if: ${{ matrix.components == 'clippy' }}
26 | run: cargo clippy --all-targets --all-features -- -D warnings
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | target/
2 | models/
3 | !models/.gitkeep
4 | venv/
5 | .env
6 | *.wav
7 | node_modules/
8 | dist/
9 | *.csv
10 | *.bin
--------------------------------------------------------------------------------
/Cargo.toml:
--------------------------------------------------------------------------------
1 | [workspace]
2 | resolver = "3"
3 | members = ["./crates/sbv2_api", "./crates/sbv2_core", "./crates/sbv2_bindings", "./crates/sbv2_wasm"]
4 |
5 | [workspace.package]
6 | version = "0.2.0-alpha6"
7 | edition = "2021"
8 | description = "Style-Bert-VITSの推論ライブラリ"
9 | license = "MIT"
10 | readme = "./README.md"
11 | repository = "https://github.com/neodyland/sbv2-api"
12 | documentation = "https://docs.rs/sbv2_core"
13 |
14 | [workspace.dependencies]
15 | anyhow = "1.0.96"
16 | dotenvy = "0.15.7"
17 | env_logger = "0.11.6"
18 | ndarray = "0.16.1"
19 | once_cell = "1.20.3"
20 |
21 | [profile.release]
22 | strip = true
23 | opt-level = "z"
24 | lto = true
25 | codegen-units = 1
26 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2024 tuna2134
4 | Copyright (c) 2025- neodyland
5 |
6 | Permission is hereby granted, free of charge, to any person obtaining a copy
7 | of this software and associated documentation files (the "Software"), to deal
8 | in the Software without restriction, including without limitation the rights
9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 | copies of the Software, and to permit persons to whom the Software is
11 | furnished to do so, subject to the following conditions:
12 |
13 | The above copyright notice and this permission notice shall be included in all
14 | copies or substantial portions of the Software.
15 |
16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22 | SOFTWARE.
23 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # SBV2-API
2 |
3 | > [!CAUTION]
4 | > 本バージョンはアルファ版です。
5 | >
6 | > 安定版を利用したい場合は[こちら](https://github.com/neodyland/sbv2-api/tree/v0.1.x)をご覧ください。
7 |
8 | > [!CAUTION]
9 | > オプションの辞書はLGPLです。
10 | >
11 | > オプションの辞書を使用する場合、バイナリの内部の辞書部分について、LGPLが適用されます。
12 |
13 | > [!NOTE]
14 | > このレポジトリはメンテナンスの都合上、[tuna2134](https:://github.com/tuna2134)氏の所属する[Neodyland](https://neody.land/)へとリポジトリ所在地を移動しました。
15 | >
16 | > 引き続きtuna2134氏がメインメンテナとして管理しています。
17 |
18 | ## プログラミングに詳しくない方向け
19 |
20 | [こちら](https://github.com/tuna2134/sbv2-gui)を参照してください。
21 |
22 | コマンドやpythonの知識なしで簡単に使えるバージョンです。(できることはほぼ同じ)
23 |
24 | ## このプロジェクトについて
25 |
26 | このプロジェクトは Style-Bert-ViTS2 を ONNX 化したものを Rust で実行するのを目的としたライブラリです。
27 |
28 | JP-Extra しか対応していません。(基本的に対応する予定もありません)
29 |
30 | ## 変換方法
31 |
32 | [こちら](https://github.com/neodyland/sbv2-api/tree/main/scripts/convert)を参照してください。
33 |
34 | ## Todo
35 |
36 | - [x] REST API の実装
37 | - [x] Rust ライブラリの実装
38 | - [x] `.sbv2`フォーマットの開発
39 | - [x] PyO3 を利用し、 Python から使えるようにする
40 | - [ ] 組み込み向けにCライブラリの作成
41 | - [x] GPU 対応(CUDA)
42 | - [x] GPU 対応(DirectML)
43 | - [x] GPU 対応(CoreML)
44 | - [x] WASM 変換
45 | - [x] arm64のdockerサポート
46 | - [x] aivis形式のサポート
47 | - [ ] MeCabを利用する
48 |
49 | ## 構造説明
50 |
51 | - `crates/sbv2_api` - 推論用 REST API
52 | - `crates/sbv2_core` - 推論コア部分
53 | - `scripts/docker` - docker ビルドスクリプト
54 | - `scripts/convert` - onnx, sbv2フォーマットへの変換スクリプト
55 |
56 | ## プログラミングある程度できる人向けREST API起動方法
57 |
58 | ### models をインストール
59 |
60 | https://huggingface.co/neody/sbv2-api-assets/tree/main/deberta
61 | から`tokenizer.json`,`debert.onnx`
62 | https://huggingface.co/neody/sbv2-api-assets/tree/main/model
63 | から`tsukuyomi.sbv2`
64 | を models フォルダに配置
65 |
66 | ### .env ファイルの作成
67 |
68 | ```sh
69 | cp .env.sample .env
70 | ```
71 |
72 | ### 起動
73 |
74 | CPUの場合は
75 | ```sh
76 | docker run -it --rm -p 3000:3000 --name sbv2 \
77 | -v ./models:/work/models --env-file .env \
78 | ghcr.io/neodyland/sbv2-api:cpu
79 | ```
80 |
81 |
82 | Apple Silicon搭載のMac(M1以降)の場合
83 | docker上で動作させる場合、.envのADDRをlocalhostから0.0.0.0に変更してください。
84 |
85 | ```yaml
86 | ADDR=0.0.0.0:3000
87 | ```
88 |
89 | CPUの場合は
90 | ```bash
91 | docker run --platform linux/amd64 -it --rm -p 3000:3000 --name sbv2 \
92 | -v ./models:/work/models --env-file .env \
93 | ghcr.io/neodyland/sbv2-api:cpu
94 | ```
95 |
96 |
97 | CUDAの場合は
98 | ```sh
99 | docker run -it --rm -p 3000:3000 --name sbv2 \
100 | -v ./models:/work/models --env-file .env \
101 | --gpus all \
102 | ghcr.io/neodyland/sbv2-api:cuda
103 | ```
104 |
105 | ### 起動確認
106 |
107 | ```sh
108 | curl -XPOST -H "Content-type: application/json" -d '{"text": "こんにちは","ident": "tsukuyomi"}' 'http://localhost:3000/synthesize' --output "output.wav"
109 | curl http://localhost:3000/models
110 | ```
111 |
112 | ## 開発者向けガイド
113 |
114 | ### Feature flags
115 |
116 | `sbv2_api`、`sbv2_core`共に
117 | - `cuda` featureでcuda
118 | - `cuda_tf32` featureでcudaのtf32機能
119 | - `tensorrt` featureでbert部分のtensorrt利用
120 | - `dynamic` featureで手元のonnxruntime共有ライブラリを利用(`ORT_DYLIB_PATH=./libonnxruntime.dll`などで指定)
121 | - `directml` featureでdirectmlの利用ができます。
122 | - `coreml` featureでcoremlの利用ができます。
123 |
124 | ### 環境変数
125 |
126 | 以下の環境変数はライブラリ側では適用されません。
127 |
128 | ライブラリAPIについては`https://docs.rs/sbv2_core`を参照してください。
129 |
130 | - `ADDR` `localhost:3000`などのようにサーバー起動アドレスをコントロールできます。
131 | - `MODELS_PATH` sbv2モデルの存在するフォルダを指定できます。
132 | - `RUST_LOG` おなじみlog levelです。
133 | - `HOLDER_MAX_LOADED_MODElS` RAMにロードされるモデルの最大数を指定します。
134 |
135 | ## 謝辞
136 |
137 | - [litagin02/Style-Bert-VITS2](https://github.com/litagin02/Style-Bert-VITS2) - このコードを書くにあたり、ベースとなる部分を参考にさせていただきました。
138 | - [Googlefan](https://github.com/Googlefan256) - 彼にモデルを ONNX ヘ変換および効率化をする方法を教わりました。
139 | - [Aivis Project](https://github.com/Aivis-Project/AivisSpeech-Engine) - 辞書部分
140 |
--------------------------------------------------------------------------------
/crates/sbv2_api/Cargo.toml:
--------------------------------------------------------------------------------
1 | [package]
2 | name = "sbv2_api"
3 | version.workspace = true
4 | edition.workspace = true
5 | description.workspace = true
6 | readme.workspace = true
7 | repository.workspace = true
8 | documentation.workspace = true
9 | license.workspace = true
10 |
11 | [dependencies]
12 | anyhow.workspace = true
13 | axum = "0.8.0"
14 | dotenvy.workspace = true
15 | env_logger.workspace = true
16 | log = "0.4.22"
17 | sbv2_core = { version = "0.2.0-alpha6", path = "../sbv2_core", features = ["aivmx"] }
18 | serde = { version = "1.0.210", features = ["derive"] }
19 | tokio = { version = "1.45.1", features = ["full"] }
20 | utoipa = { version = "5.0.0", features = ["axum_extras"] }
21 | utoipa-scalar = { version = "0.3.0", features = ["axum"] }
22 |
23 | [features]
24 | coreml = ["sbv2_core/coreml"]
25 | cuda = ["sbv2_core/cuda"]
26 | cuda_tf32 = ["sbv2_core/cuda_tf32"]
27 | dynamic = ["sbv2_core/dynamic"]
28 | directml = ["sbv2_core/directml"]
29 | tensorrt = ["sbv2_core/tensorrt"]
30 |
--------------------------------------------------------------------------------
/crates/sbv2_api/build.rs:
--------------------------------------------------------------------------------
1 | fn main() {
2 | if cfg!(feature = "coreml") {
3 | println!("cargo:rustc-link-arg=-fapple-link-rtlib");
4 | }
5 | }
6 |
--------------------------------------------------------------------------------
/crates/sbv2_api/src/error.rs:
--------------------------------------------------------------------------------
1 | use axum::{
2 | http::StatusCode,
3 | response::{IntoResponse, Response},
4 | };
5 |
6 | pub type AppResult = std::result::Result;
7 |
8 | pub struct AppError(anyhow::Error);
9 |
10 | impl IntoResponse for AppError {
11 | fn into_response(self) -> Response {
12 | (
13 | StatusCode::INTERNAL_SERVER_ERROR,
14 | format!("Something went wrong: {}", self.0),
15 | )
16 | .into_response()
17 | }
18 | }
19 |
20 | impl From for AppError
21 | where
22 | E: Into,
23 | {
24 | fn from(err: E) -> Self {
25 | Self(err.into())
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/crates/sbv2_api/src/main.rs:
--------------------------------------------------------------------------------
1 | use axum::{
2 | extract::State,
3 | http::header::CONTENT_TYPE,
4 | response::IntoResponse,
5 | routing::{get, post},
6 | Json, Router,
7 | };
8 | use sbv2_core::tts::{SynthesizeOptions, TTSModelHolder};
9 | use serde::Deserialize;
10 | use std::env;
11 | use std::sync::Arc;
12 | use tokio::fs;
13 | use tokio::sync::Mutex;
14 | use utoipa::{OpenApi, ToSchema};
15 | use utoipa_scalar::{Scalar, Servable};
16 |
17 | mod error;
18 | use crate::error::AppResult;
19 |
20 | #[derive(OpenApi)]
21 | #[openapi(paths(models, synthesize), components(schemas(SynthesizeRequest)))]
22 | struct ApiDoc;
23 |
24 | #[utoipa::path(
25 | get,
26 | path = "/models",
27 | responses(
28 | (status = 200, description = "Return model list", body = Vec),
29 | )
30 | )]
31 | async fn models(State(state): State) -> AppResult {
32 | Ok(Json(state.tts_model.lock().await.models()))
33 | }
34 |
35 | fn sdp_default() -> f32 {
36 | 0.0
37 | }
38 |
39 | fn length_default() -> f32 {
40 | 1.0
41 | }
42 |
43 | fn style_id_default() -> i32 {
44 | 0
45 | }
46 |
47 | fn speaker_id_default() -> i64 {
48 | 0
49 | }
50 |
51 | #[derive(Deserialize, ToSchema)]
52 | struct SynthesizeRequest {
53 | text: String,
54 | ident: String,
55 | #[serde(default = "sdp_default")]
56 | #[schema(example = 0.0_f32)]
57 | sdp_ratio: f32,
58 | #[serde(default = "length_default")]
59 | #[schema(example = 1.0_f32)]
60 | length_scale: f32,
61 | #[serde(default = "style_id_default")]
62 | #[schema(example = 0_i32)]
63 | style_id: i32,
64 | #[serde(default = "speaker_id_default")]
65 | #[schema(example = 0_i64)]
66 | speaker_id: i64,
67 | }
68 |
69 | #[utoipa::path(
70 | post,
71 | path = "/synthesize",
72 | request_body = SynthesizeRequest,
73 | responses(
74 | (status = 200, description = "Return audio/wav", body = Vec, content_type = "audio/wav")
75 | )
76 | )]
77 | async fn synthesize(
78 | State(state): State,
79 | Json(SynthesizeRequest {
80 | text,
81 | ident,
82 | sdp_ratio,
83 | length_scale,
84 | style_id,
85 | speaker_id,
86 | }): Json,
87 | ) -> AppResult {
88 | log::debug!("processing request: text={text}, ident={ident}, sdp_ratio={sdp_ratio}, length_scale={length_scale}");
89 | let buffer = {
90 | let mut tts_model = state.tts_model.lock().await;
91 | tts_model.easy_synthesize(
92 | &ident,
93 | &text,
94 | style_id,
95 | speaker_id,
96 | SynthesizeOptions {
97 | sdp_ratio,
98 | length_scale,
99 | ..Default::default()
100 | },
101 | )?
102 | };
103 | Ok(([(CONTENT_TYPE, "audio/wav")], buffer))
104 | }
105 |
106 | #[derive(Clone)]
107 | struct AppState {
108 | tts_model: Arc>,
109 | }
110 |
111 | impl AppState {
112 | pub async fn new() -> anyhow::Result {
113 | let mut tts_model = TTSModelHolder::new(
114 | &fs::read(env::var("BERT_MODEL_PATH")?).await?,
115 | &fs::read(env::var("TOKENIZER_PATH")?).await?,
116 | env::var("HOLDER_MAX_LOADED_MODElS")
117 | .ok()
118 | .and_then(|x| x.parse().ok()),
119 | )?;
120 | let models = env::var("MODELS_PATH").unwrap_or("models".to_string());
121 | let mut f = fs::read_dir(&models).await?;
122 | let mut entries = vec![];
123 | while let Ok(Some(e)) = f.next_entry().await {
124 | let name = e.file_name().to_string_lossy().to_string();
125 | if name.ends_with(".onnx") && name.starts_with("model_") {
126 | let name_len = name.len();
127 | let name = name.chars();
128 | entries.push(
129 | name.collect::>()[6..name_len - 5]
130 | .iter()
131 | .collect::(),
132 | );
133 | } else if name.ends_with(".sbv2") {
134 | let entry = &name[..name.len() - 5];
135 | log::info!("Try loading: {entry}");
136 | let sbv2_bytes = match fs::read(format!("{models}/{entry}.sbv2")).await {
137 | Ok(b) => b,
138 | Err(e) => {
139 | log::warn!("Error loading sbv2_bytes from file {entry}: {e}");
140 | continue;
141 | }
142 | };
143 | if let Err(e) = tts_model.load_sbv2file(entry, sbv2_bytes) {
144 | log::warn!("Error loading {entry}: {e}");
145 | };
146 | log::info!("Loaded: {entry}");
147 | } else if name.ends_with(".aivmx") {
148 | let entry = &name[..name.len() - 6];
149 | log::info!("Try loading: {entry}");
150 | let aivmx_bytes = match fs::read(format!("{models}/{entry}.aivmx")).await {
151 | Ok(b) => b,
152 | Err(e) => {
153 | log::warn!("Error loading aivmx bytes from file {entry}: {e}");
154 | continue;
155 | }
156 | };
157 | if let Err(e) = tts_model.load_aivmx(entry, aivmx_bytes) {
158 | log::error!("Error loading {entry}: {e}");
159 | }
160 | log::info!("Loaded: {entry}");
161 | }
162 | }
163 | for entry in entries {
164 | log::info!("Try loading: {entry}");
165 | let style_vectors_bytes =
166 | match fs::read(format!("{models}/style_vectors_{entry}.json")).await {
167 | Ok(b) => b,
168 | Err(e) => {
169 | log::warn!("Error loading style_vectors_bytes from file {entry}: {e}");
170 | continue;
171 | }
172 | };
173 | let vits2_bytes = match fs::read(format!("{models}/model_{entry}.onnx")).await {
174 | Ok(b) => b,
175 | Err(e) => {
176 | log::warn!("Error loading vits2_bytes from file {entry}: {e}");
177 | continue;
178 | }
179 | };
180 | if let Err(e) = tts_model.load(&entry, style_vectors_bytes, vits2_bytes) {
181 | log::warn!("Error loading {entry}: {e}");
182 | };
183 | log::info!("Loaded: {entry}");
184 | }
185 | Ok(Self {
186 | tts_model: Arc::new(Mutex::new(tts_model)),
187 | })
188 | }
189 | }
190 |
191 | #[tokio::main]
192 | async fn main() -> anyhow::Result<()> {
193 | dotenvy::dotenv_override().ok();
194 | env_logger::init();
195 | let app = Router::new()
196 | .route("/", get(|| async { "Hello, World!" }))
197 | .route("/synthesize", post(synthesize))
198 | .route("/models", get(models))
199 | .with_state(AppState::new().await?)
200 | .merge(Scalar::with_url("/docs", ApiDoc::openapi()));
201 | let addr = env::var("ADDR").unwrap_or("0.0.0.0:3000".to_string());
202 | let listener = tokio::net::TcpListener::bind(&addr).await?;
203 | log::info!("Listening on {addr}");
204 | axum::serve(listener, app).await?;
205 |
206 | Ok(())
207 | }
208 |
--------------------------------------------------------------------------------
/crates/sbv2_bindings/Cargo.toml:
--------------------------------------------------------------------------------
1 | [package]
2 | name = "sbv2_bindings"
3 | version.workspace = true
4 | edition.workspace = true
5 | description.workspace = true
6 | readme.workspace = true
7 | repository.workspace = true
8 | documentation.workspace = true
9 | license.workspace = true
10 |
11 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
12 | [lib]
13 | name = "sbv2_bindings"
14 | crate-type = ["cdylib"]
15 |
16 | [dependencies]
17 | anyhow.workspace = true
18 | ndarray.workspace = true
19 | pyo3 = { version = "0.25.0", features = ["anyhow"] }
20 | sbv2_core = { path = "../sbv2_core", features = ["std"], default-features = false }
21 |
22 | [features]
23 | agpl_dict = ["sbv2_core/agpl_dict"]
24 | default = ["agpl_dict"]
--------------------------------------------------------------------------------
/crates/sbv2_bindings/pyproject.toml:
--------------------------------------------------------------------------------
1 | [build-system]
2 | requires = ["maturin>=1.7,<2.0"]
3 | build-backend = "maturin"
4 |
5 | [project]
6 | name = "sbv2_bindings"
7 | requires-python = ">=3.8"
8 | classifiers = [
9 | "Programming Language :: Rust",
10 | "Programming Language :: Python :: Implementation :: CPython",
11 | "Programming Language :: Python :: Implementation :: PyPy",
12 | ]
13 | dynamic = ["version"]
14 |
15 | [tool.maturin]
16 | features = ["pyo3/extension-module"]
17 | strip = true
--------------------------------------------------------------------------------
/crates/sbv2_bindings/src/lib.rs:
--------------------------------------------------------------------------------
1 | use pyo3::prelude::*;
2 | mod sbv2;
3 | pub mod style;
4 |
5 | /// sbv2 bindings module
6 | #[pymodule]
7 | fn sbv2_bindings(m: &Bound<'_, PyModule>) -> PyResult<()> {
8 | m.add_class::()?;
9 | m.add_class::()?;
10 | Ok(())
11 | }
12 |
--------------------------------------------------------------------------------
/crates/sbv2_bindings/src/sbv2.rs:
--------------------------------------------------------------------------------
1 | use pyo3::prelude::*;
2 | use pyo3::types::PyBytes;
3 | use sbv2_core::tts::{SynthesizeOptions, TTSModelHolder};
4 |
5 | use crate::style::StyleVector;
6 |
7 | use std::fs;
8 |
9 | /// TTSModel class
10 | ///
11 | /// 音声合成するために使うクラス
12 | ///
13 | /// Parameters
14 | /// ----------
15 | /// bert_model_bytes : bytes
16 | /// BERTモデルのバイナリデータ
17 | /// tokenizer_bytes : bytes
18 | /// トークナイザーのバイナリデータ
19 | #[pyclass]
20 | pub struct TTSModel {
21 | pub model: TTSModelHolder,
22 | }
23 |
24 | #[pymethods]
25 | impl TTSModel {
26 | #[pyo3(signature = (bert_model_bytes, tokenizer_bytes, max_loaded_models=None))]
27 | #[new]
28 | fn new(
29 | bert_model_bytes: Vec,
30 | tokenizer_bytes: Vec,
31 | max_loaded_models: Option,
32 | ) -> anyhow::Result {
33 | Ok(Self {
34 | model: TTSModelHolder::new(bert_model_bytes, tokenizer_bytes, max_loaded_models)?,
35 | })
36 | }
37 |
38 | /// パスからTTSModelインスタンスを生成する
39 | ///
40 | /// Parameters
41 | /// ----------
42 | /// bert_model_path : str
43 | /// BERTモデルのパス
44 | /// tokenizer_path : str
45 | /// トークナイザーのパス
46 | /// max_loaded_models: int | None
47 | /// 同時にVRAMに存在するモデルの数
48 | #[pyo3(signature = (bert_model_path, tokenizer_path, max_loaded_models=None))]
49 | #[staticmethod]
50 | fn from_path(
51 | bert_model_path: String,
52 | tokenizer_path: String,
53 | max_loaded_models: Option,
54 | ) -> anyhow::Result {
55 | Ok(Self {
56 | model: TTSModelHolder::new(
57 | fs::read(bert_model_path)?,
58 | fs::read(tokenizer_path)?,
59 | max_loaded_models,
60 | )?,
61 | })
62 | }
63 |
64 | /// SBV2ファイルを読み込む
65 | ///
66 | /// Parameters
67 | /// ----------
68 | /// ident : str
69 | /// 識別子
70 | /// sbv2file_bytes : bytes
71 | /// SBV2ファイルのバイナリデータ
72 | fn load_sbv2file(&mut self, ident: String, sbv2file_bytes: Vec) -> anyhow::Result<()> {
73 | self.model.load_sbv2file(ident, sbv2file_bytes)?;
74 | Ok(())
75 | }
76 |
77 | /// パスからSBV2ファイルを読み込む
78 | ///
79 | /// Parameters
80 | /// ----------
81 | /// ident : str
82 | /// 識別子
83 | /// sbv2file_path : str
84 | /// SBV2ファイルのパス
85 | fn load_sbv2file_from_path(
86 | &mut self,
87 | ident: String,
88 | sbv2file_path: String,
89 | ) -> anyhow::Result<()> {
90 | self.model.load_sbv2file(ident, fs::read(sbv2file_path)?)?;
91 | Ok(())
92 | }
93 |
94 | /// スタイルベクトルを取得する
95 | ///
96 | /// Parameters
97 | /// ----------
98 | /// ident : str
99 | /// 識別子
100 | /// style_id : int
101 | /// スタイルID
102 | /// weight : float
103 | /// 重み
104 | ///
105 | /// Returns
106 | /// -------
107 | /// style_vector : StyleVector
108 | /// スタイルベクトル
109 | fn get_style_vector(
110 | &mut self,
111 | ident: String,
112 | style_id: i32,
113 | weight: f32,
114 | ) -> anyhow::Result {
115 | Ok(StyleVector::new(
116 | self.model.get_style_vector(ident, style_id, weight)?,
117 | ))
118 | }
119 |
120 | /// テキストから音声を合成する
121 | ///
122 | /// Parameters
123 | /// ----------
124 | /// text : str
125 | /// テキスト
126 | /// ident : str
127 | /// 識別子
128 | /// style_id : int
129 | /// スタイルID
130 | /// sdp_ratio : float
131 | /// SDP比率
132 | /// length_scale : float
133 | /// 音声の長さのスケール
134 | ///
135 | /// Returns
136 | /// -------
137 | /// voice_data : bytes
138 | /// 音声データ
139 | #[allow(clippy::too_many_arguments)]
140 | fn synthesize<'p>(
141 | &'p mut self,
142 | py: Python<'p>,
143 | text: String,
144 | ident: String,
145 | style_id: i32,
146 | speaker_id: i64,
147 | sdp_ratio: f32,
148 | length_scale: f32,
149 | ) -> anyhow::Result> {
150 | let data = self.model.easy_synthesize(
151 | ident.as_str(),
152 | &text,
153 | style_id,
154 | speaker_id,
155 | SynthesizeOptions {
156 | sdp_ratio,
157 | length_scale,
158 | ..Default::default()
159 | },
160 | )?;
161 | Ok(PyBytes::new(py, &data))
162 | }
163 |
164 | fn unload(&mut self, ident: String) -> bool {
165 | self.model.unload(ident)
166 | }
167 | }
168 |
--------------------------------------------------------------------------------
/crates/sbv2_bindings/src/style.rs:
--------------------------------------------------------------------------------
1 | use ndarray::Array1;
2 | use pyo3::prelude::*;
3 |
4 | /// StyleVector class
5 | ///
6 | /// スタイルベクトルを表すクラス
7 | #[pyclass]
8 | #[derive(Clone)]
9 | pub struct StyleVector(Array1);
10 |
11 | impl StyleVector {
12 | pub fn new(data: Array1) -> Self {
13 | StyleVector(data)
14 | }
15 |
16 | pub fn get(&self) -> Array1 {
17 | self.0.clone()
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/crates/sbv2_core/Cargo.toml:
--------------------------------------------------------------------------------
1 | [package]
2 | name = "sbv2_core"
3 | version.workspace = true
4 | edition.workspace = true
5 | description.workspace = true
6 | readme.workspace = true
7 | repository.workspace = true
8 | documentation.workspace = true
9 | license.workspace = true
10 |
11 | [dependencies]
12 | anyhow.workspace = true
13 | base64 = { version = "0.22.1", optional = true }
14 | dotenvy.workspace = true
15 | env_logger.workspace = true
16 | hound = "3.5.1"
17 | jpreprocess = { version = "0.12.0", features = ["naist-jdic"] }
18 | ndarray.workspace = true
19 | npyz = { version = "0.8.4", optional = true }
20 | num_cpus = "1.16.0"
21 | once_cell.workspace = true
22 | ort = { git = "https://github.com/pykeio/ort.git", version = "2.0.0-rc.9", optional = true }
23 | regex = "1.10.6"
24 | serde = { version = "1.0.210", features = ["derive"] }
25 | serde_json = "1.0.128"
26 | tar = "0.4.41"
27 | thiserror = "2.0.11"
28 | tokenizers = { version = "0.21.0", default-features = false }
29 | zstd = "0.13.2"
30 |
31 | [features]
32 | cuda = ["ort/cuda", "std"]
33 | cuda_tf32 = ["std", "cuda"]
34 | agpl_dict = []
35 | std = ["dep:ort", "tokenizers/progressbar", "tokenizers/onig", "tokenizers/esaxx_fast"]
36 | dynamic = ["ort/load-dynamic", "std"]
37 | directml = ["ort/directml", "std"]
38 | tensorrt = ["ort/tensorrt", "std"]
39 | coreml = ["ort/coreml", "std"]
40 | default = ["std", "agpl_dict"]
41 | no_std = ["tokenizers/unstable_wasm"]
42 | aivmx = ["npyz", "base64"]
43 | base64 = ["dep:base64"]
44 |
45 | [build-dependencies]
46 | dirs = "6.0.0"
47 | ureq = "3.0.6"
48 |
--------------------------------------------------------------------------------
/crates/sbv2_core/build.rs:
--------------------------------------------------------------------------------
1 | use dirs::home_dir;
2 | use std::env;
3 | use std::fs;
4 | use std::io::copy;
5 | use std::path::PathBuf;
6 |
7 | fn main() -> Result<(), Box> {
8 | let static_dir = home_dir().unwrap().join(".cache/sbv2");
9 | let static_path = static_dir.join("all.bin");
10 | let out_path = PathBuf::from(&env::var("OUT_DIR").unwrap()).join("all.bin");
11 | println!("cargo:rerun-if-changed=build.rs");
12 | if static_path.exists() {
13 | println!("cargo:info=Dictionary file already exists, skipping download.");
14 | } else {
15 | println!("cargo:warning=Downloading dictionary file...");
16 | let mut response =
17 | ureq::get("https://huggingface.co/neody/sbv2-api-assets/resolve/main/dic/all.bin")
18 | .call()?;
19 | let mut response = response.body_mut().as_reader();
20 | if !static_dir.exists() {
21 | fs::create_dir_all(static_dir)?;
22 | }
23 | let mut file = fs::File::create(&static_path)?;
24 | copy(&mut response, &mut file)?;
25 | }
26 | if !out_path.exists() && fs::hard_link(&static_path, &out_path).is_err() {
27 | println!("cargo:warning=Failed to create hard link, copying instead.");
28 | fs::copy(static_path, out_path)?;
29 | }
30 | Ok(())
31 | }
32 |
--------------------------------------------------------------------------------
/crates/sbv2_core/mora_convert.py:
--------------------------------------------------------------------------------
1 | # moraに変換します
2 | import json
3 |
4 |
5 | __MORA_LIST_MINIMUM: list[tuple[str, str | None, str]] = [
6 | ("ヴォ", "v", "o"),
7 | ("ヴェ", "v", "e"),
8 | ("ヴィ", "v", "i"),
9 | ("ヴァ", "v", "a"),
10 | ("ヴ", "v", "u"),
11 | ("ン", None, "N"),
12 | ("ワ", "w", "a"),
13 | ("ロ", "r", "o"),
14 | ("レ", "r", "e"),
15 | ("ル", "r", "u"),
16 | ("リョ", "ry", "o"),
17 | ("リュ", "ry", "u"),
18 | ("リャ", "ry", "a"),
19 | ("リェ", "ry", "e"),
20 | ("リ", "r", "i"),
21 | ("ラ", "r", "a"),
22 | ("ヨ", "y", "o"),
23 | ("ユ", "y", "u"),
24 | ("ヤ", "y", "a"),
25 | ("モ", "m", "o"),
26 | ("メ", "m", "e"),
27 | ("ム", "m", "u"),
28 | ("ミョ", "my", "o"),
29 | ("ミュ", "my", "u"),
30 | ("ミャ", "my", "a"),
31 | ("ミェ", "my", "e"),
32 | ("ミ", "m", "i"),
33 | ("マ", "m", "a"),
34 | ("ポ", "p", "o"),
35 | ("ボ", "b", "o"),
36 | ("ホ", "h", "o"),
37 | ("ペ", "p", "e"),
38 | ("ベ", "b", "e"),
39 | ("ヘ", "h", "e"),
40 | ("プ", "p", "u"),
41 | ("ブ", "b", "u"),
42 | ("フォ", "f", "o"),
43 | ("フェ", "f", "e"),
44 | ("フィ", "f", "i"),
45 | ("ファ", "f", "a"),
46 | ("フ", "f", "u"),
47 | ("ピョ", "py", "o"),
48 | ("ピュ", "py", "u"),
49 | ("ピャ", "py", "a"),
50 | ("ピェ", "py", "e"),
51 | ("ピ", "p", "i"),
52 | ("ビョ", "by", "o"),
53 | ("ビュ", "by", "u"),
54 | ("ビャ", "by", "a"),
55 | ("ビェ", "by", "e"),
56 | ("ビ", "b", "i"),
57 | ("ヒョ", "hy", "o"),
58 | ("ヒュ", "hy", "u"),
59 | ("ヒャ", "hy", "a"),
60 | ("ヒェ", "hy", "e"),
61 | ("ヒ", "h", "i"),
62 | ("パ", "p", "a"),
63 | ("バ", "b", "a"),
64 | ("ハ", "h", "a"),
65 | ("ノ", "n", "o"),
66 | ("ネ", "n", "e"),
67 | ("ヌ", "n", "u"),
68 | ("ニョ", "ny", "o"),
69 | ("ニュ", "ny", "u"),
70 | ("ニャ", "ny", "a"),
71 | ("ニェ", "ny", "e"),
72 | ("ニ", "n", "i"),
73 | ("ナ", "n", "a"),
74 | ("ドゥ", "d", "u"),
75 | ("ド", "d", "o"),
76 | ("トゥ", "t", "u"),
77 | ("ト", "t", "o"),
78 | ("デョ", "dy", "o"),
79 | ("デュ", "dy", "u"),
80 | ("デャ", "dy", "a"),
81 | # ("デェ", "dy", "e"),
82 | ("ディ", "d", "i"),
83 | ("デ", "d", "e"),
84 | ("テョ", "ty", "o"),
85 | ("テュ", "ty", "u"),
86 | ("テャ", "ty", "a"),
87 | ("ティ", "t", "i"),
88 | ("テ", "t", "e"),
89 | ("ツォ", "ts", "o"),
90 | ("ツェ", "ts", "e"),
91 | ("ツィ", "ts", "i"),
92 | ("ツァ", "ts", "a"),
93 | ("ツ", "ts", "u"),
94 | ("ッ", None, "q"), # 「cl」から「q」に変更
95 | ("チョ", "ch", "o"),
96 | ("チュ", "ch", "u"),
97 | ("チャ", "ch", "a"),
98 | ("チェ", "ch", "e"),
99 | ("チ", "ch", "i"),
100 | ("ダ", "d", "a"),
101 | ("タ", "t", "a"),
102 | ("ゾ", "z", "o"),
103 | ("ソ", "s", "o"),
104 | ("ゼ", "z", "e"),
105 | ("セ", "s", "e"),
106 | ("ズィ", "z", "i"),
107 | ("ズ", "z", "u"),
108 | ("スィ", "s", "i"),
109 | ("ス", "s", "u"),
110 | ("ジョ", "j", "o"),
111 | ("ジュ", "j", "u"),
112 | ("ジャ", "j", "a"),
113 | ("ジェ", "j", "e"),
114 | ("ジ", "j", "i"),
115 | ("ショ", "sh", "o"),
116 | ("シュ", "sh", "u"),
117 | ("シャ", "sh", "a"),
118 | ("シェ", "sh", "e"),
119 | ("シ", "sh", "i"),
120 | ("ザ", "z", "a"),
121 | ("サ", "s", "a"),
122 | ("ゴ", "g", "o"),
123 | ("コ", "k", "o"),
124 | ("ゲ", "g", "e"),
125 | ("ケ", "k", "e"),
126 | ("グヮ", "gw", "a"),
127 | ("グ", "g", "u"),
128 | ("クヮ", "kw", "a"),
129 | ("ク", "k", "u"),
130 | ("ギョ", "gy", "o"),
131 | ("ギュ", "gy", "u"),
132 | ("ギャ", "gy", "a"),
133 | ("ギェ", "gy", "e"),
134 | ("ギ", "g", "i"),
135 | ("キョ", "ky", "o"),
136 | ("キュ", "ky", "u"),
137 | ("キャ", "ky", "a"),
138 | ("キェ", "ky", "e"),
139 | ("キ", "k", "i"),
140 | ("ガ", "g", "a"),
141 | ("カ", "k", "a"),
142 | ("オ", None, "o"),
143 | ("エ", None, "e"),
144 | ("ウォ", "w", "o"),
145 | ("ウェ", "w", "e"),
146 | ("ウィ", "w", "i"),
147 | ("ウ", None, "u"),
148 | ("イェ", "y", "e"),
149 | ("イ", None, "i"),
150 | ("ア", None, "a"),
151 | ]
152 | __MORA_LIST_ADDITIONAL: list[tuple[str, str | None, str]] = [
153 | ("ヴョ", "by", "o"),
154 | ("ヴュ", "by", "u"),
155 | ("ヴャ", "by", "a"),
156 | ("ヲ", None, "o"),
157 | ("ヱ", None, "e"),
158 | ("ヰ", None, "i"),
159 | ("ヮ", "w", "a"),
160 | ("ョ", "y", "o"),
161 | ("ュ", "y", "u"),
162 | ("ヅ", "z", "u"),
163 | ("ヂ", "j", "i"),
164 | ("ヶ", "k", "e"),
165 | ("ャ", "y", "a"),
166 | ("ォ", None, "o"),
167 | ("ェ", None, "e"),
168 | ("ゥ", None, "u"),
169 | ("ィ", None, "i"),
170 | ("ァ", None, "a"),
171 | ]
172 |
173 | data = {"minimum": [], "additional": []}
174 |
175 |
176 | for mora, consonant, vowel in __MORA_LIST_MINIMUM:
177 | data["minimum"].append(
178 | {
179 | "mora": mora,
180 | "consonant": consonant,
181 | "vowel": vowel,
182 | }
183 | )
184 |
185 | for mora, consonant, vowel in __MORA_LIST_ADDITIONAL:
186 | data["additional"].append(
187 | {
188 | "mora": mora,
189 | "consonant": consonant,
190 | "vowel": vowel,
191 | }
192 | )
193 |
194 |
195 | with open("src/mora_list.json", "w") as f:
196 | json.dump(data, f, ensure_ascii=False, indent=4)
197 |
--------------------------------------------------------------------------------
/crates/sbv2_core/src/bert.rs:
--------------------------------------------------------------------------------
1 | use crate::error::Result;
2 | use ndarray::{Array2, Ix2};
3 | use ort::session::Session;
4 | use ort::value::TensorRef;
5 |
6 | pub fn predict(
7 | session: &mut Session,
8 | token_ids: Vec,
9 | attention_masks: Vec,
10 | ) -> Result> {
11 | let outputs = session.run(
12 | ort::inputs! {
13 | "input_ids" => TensorRef::from_array_view((vec![1, token_ids.len() as i64], token_ids.as_slice()))?,
14 | "attention_mask" => TensorRef::from_array_view((vec![1, attention_masks.len() as i64], attention_masks.as_slice()))?,
15 | }
16 | )?;
17 | let output = outputs["output"]
18 | .try_extract_array::()?
19 | .into_dimensionality::()?
20 | .to_owned();
21 | Ok(output)
22 | }
23 |
--------------------------------------------------------------------------------
/crates/sbv2_core/src/error.rs:
--------------------------------------------------------------------------------
1 | use thiserror::Error;
2 |
3 | #[derive(Error, Debug)]
4 | pub enum Error {
5 | #[error("Tokenizer error: {0}")]
6 | TokenizerError(#[from] tokenizers::Error),
7 | #[error("JPreprocess error: {0}")]
8 | JPreprocessError(#[from] jpreprocess::error::JPreprocessError),
9 | #[error("Lindera error: {0}")]
10 | LinderaError(String),
11 | #[cfg(feature = "std")]
12 | #[error("ONNX error: {0}")]
13 | OrtError(#[from] ort::Error),
14 | #[error("NDArray error: {0}")]
15 | NdArrayError(#[from] ndarray::ShapeError),
16 | #[error("Value error: {0}")]
17 | ValueError(String),
18 | #[error("Serde_json error: {0}")]
19 | SerdeJsonError(#[from] serde_json::Error),
20 | #[error("IO error: {0}")]
21 | IoError(#[from] std::io::Error),
22 | #[error("hound error: {0}")]
23 | HoundError(#[from] hound::Error),
24 | #[error("model not found error")]
25 | ModelNotFoundError(String),
26 | #[cfg(feature = "base64")]
27 | #[error("base64 error")]
28 | Base64Error(#[from] base64::DecodeError),
29 | #[error("other")]
30 | OtherError(String),
31 | #[error("Style error: {0}")]
32 | StyleError(String),
33 | }
34 |
35 | pub type Result = std::result::Result;
36 |
--------------------------------------------------------------------------------
/crates/sbv2_core/src/jtalk.rs:
--------------------------------------------------------------------------------
1 | use crate::error::{Error, Result};
2 | use crate::mora::{CONSONANTS, MORA_KATA_TO_MORA_PHONEMES, MORA_PHONEMES_TO_MORA_KATA, VOWELS};
3 | use crate::norm::{replace_punctuation, PUNCTUATIONS};
4 | use jpreprocess::{kind, DefaultTokenizer, JPreprocess, SystemDictionaryConfig, UserDictionary};
5 | use once_cell::sync::Lazy;
6 | use regex::Regex;
7 | use std::cmp::Reverse;
8 | use std::collections::HashSet;
9 | use std::sync::Arc;
10 |
11 | type JPreprocessType = JPreprocess;
12 |
13 | #[cfg(feature = "agpl_dict")]
14 | fn agpl_dict() -> Result