diff --git a/Cargo.lock b/Cargo.lock index b09045f..efc6247 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -802,6 +802,12 @@ dependencies = [ "unicode-width", ] +[[package]] +name = "indoc" +version = "2.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b248f5224d1d606005e02c97f5aa4e88eeb230488bcc03bc9ca4d7991399f2b5" + [[package]] name = "instant" version = "0.1.13" @@ -1213,6 +1219,15 @@ version = "2.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" +[[package]] +name = "memoffset" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "488016bfae457b036d996092f6cb448677611ce4449e970ceaf42695203f218a" +dependencies = [ + "autocfg", +] + [[package]] name = "mime" version = "0.3.17" @@ -1375,7 +1390,7 @@ dependencies = [ [[package]] name = "ort" version = "2.0.0-rc.6" -source = "git+https://github.com/pykeio/ort.git#ee5cc2046c346b8e6f71ce543c1f6d31702337de" +source = "git+https://github.com/pykeio/ort.git#7ad5b84e5a42cb6399635d95d88fc81aa38fddf3" dependencies = [ "half", "libloading", @@ -1387,7 +1402,7 @@ dependencies = [ [[package]] name = "ort-sys" version = "2.0.0-rc.6" -source = "git+https://github.com/pykeio/ort.git#ee5cc2046c346b8e6f71ce543c1f6d31702337de" +source = "git+https://github.com/pykeio/ort.git#7ad5b84e5a42cb6399635d95d88fc81aa38fddf3" dependencies = [ "flate2", "pkg-config", @@ -1544,6 +1559,70 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "pyo3" +version = "0.22.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "831e8e819a138c36e212f3af3fd9eeffed6bf1510a805af35b0edee5ffa59433" +dependencies = [ + "anyhow", + "cfg-if", + "indoc", + "libc", + "memoffset", + "once_cell", + "portable-atomic", + "pyo3-build-config", + "pyo3-ffi", + "pyo3-macros", + "unindent", +] + +[[package]] +name = "pyo3-build-config" +version = "0.22.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e8730e591b14492a8945cdff32f089250b05f5accecf74aeddf9e8272ce1fa8" +dependencies = [ + "once_cell", + "target-lexicon", +] + +[[package]] +name = "pyo3-ffi" +version = "0.22.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e97e919d2df92eb88ca80a037969f44e5e70356559654962cbb3316d00300c6" +dependencies = [ + "libc", + "pyo3-build-config", +] + +[[package]] +name = "pyo3-macros" +version = "0.22.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eb57983022ad41f9e683a599f2fd13c3664d7063a3ac5714cae4b7bee7d3f206" +dependencies = [ + "proc-macro2", + "pyo3-macros-backend", + "quote", + "syn", +] + +[[package]] +name = "pyo3-macros-backend" +version = "0.22.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec480c0c51ddec81019531705acac51bcdbeae563557c982aa8263bb96880372" +dependencies = [ + "heck", + "proc-macro2", + "pyo3-build-config", + "quote", + "syn", +] + [[package]] name = "quote" version = "1.0.37" @@ -1750,9 +1829,19 @@ dependencies = [ "tokio", ] +[[package]] +name = "sbv2_bindings" +version = "0.1.0" +dependencies = [ + "anyhow", + "ndarray", + "pyo3", + "sbv2_core", +] + [[package]] name = "sbv2_core" -version = "0.1.1" +version = "0.1.2" dependencies = [ "anyhow", "dotenvy", @@ -1977,6 +2066,12 @@ dependencies = [ "xattr", ] +[[package]] +name = "target-lexicon" +version = "0.12.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61c41af27dd6d1e27b1b16b489db798443478cef1f06a660c96db617ba5de3b1" + [[package]] name = "thiserror" version = "1.0.63" @@ -2135,9 +2230,9 @@ checksum = "08f95100a766bf4f8f28f90d77e0a5461bbdb219042e7679bebe79004fed8d75" [[package]] name = "unicode-ident" -version = "1.0.12" +version = "1.0.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" +checksum = "e91b56cd4cadaeb79bbf1a5645f6b4f8dc5bde8834ad5894a8db35fda9efa1fe" [[package]] name = "unicode-normalization" @@ -2175,6 +2270,12 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "39ec24b3121d976906ece63c9daad25b85969647682eee313cb5779fdd69e14e" +[[package]] +name = "unindent" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7de7d73e1754487cb58364ee906a499937a0dfabd86bcb980fa99ec8c8fa2ce" + [[package]] name = "untrusted" version = "0.9.0" diff --git a/Cargo.toml b/Cargo.toml index beb944f..822de4d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,8 +1,9 @@ [workspace] resolver = "2" -members = ["sbv2_api", "sbv2_core"] +members = ["sbv2_api", "sbv2_core", "sbv2_bindings"] [workspace.dependencies] anyhow = "1.0.86" dotenvy = "0.15.7" env_logger = "0.11.5" +ndarray = "0.16.1" \ No newline at end of file diff --git a/sbv2_bindings/.github/workflows/CI.yml b/sbv2_bindings/.github/workflows/CI.yml new file mode 100644 index 0000000..a4c4c30 --- /dev/null +++ b/sbv2_bindings/.github/workflows/CI.yml @@ -0,0 +1,169 @@ +# This file is autogenerated by maturin v1.7.1 +# To update, run +# +# maturin generate-ci github +# +name: CI + +on: + push: + branches: + - main + - master + tags: + - '*' + pull_request: + workflow_dispatch: + +permissions: + contents: read + +jobs: + linux: + runs-on: ${{ matrix.platform.runner }} + strategy: + matrix: + platform: + - runner: ubuntu-latest + target: x86_64 + - runner: ubuntu-latest + target: x86 + - runner: ubuntu-latest + target: aarch64 + - runner: ubuntu-latest + target: armv7 + - runner: ubuntu-latest + target: s390x + - runner: ubuntu-latest + target: ppc64le + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-python@v5 + with: + python-version: 3.x + - name: Build wheels + uses: PyO3/maturin-action@v1 + with: + target: ${{ matrix.platform.target }} + args: --release --out dist --find-interpreter + sccache: 'true' + manylinux: auto + - name: Upload wheels + uses: actions/upload-artifact@v4 + with: + name: wheels-linux-${{ matrix.platform.target }} + path: dist + + musllinux: + runs-on: ${{ matrix.platform.runner }} + strategy: + matrix: + platform: + - runner: ubuntu-latest + target: x86_64 + - runner: ubuntu-latest + target: x86 + - runner: ubuntu-latest + target: aarch64 + - runner: ubuntu-latest + target: armv7 + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-python@v5 + with: + python-version: 3.x + - name: Build wheels + uses: PyO3/maturin-action@v1 + with: + target: ${{ matrix.platform.target }} + args: --release --out dist --find-interpreter + sccache: 'true' + manylinux: musllinux_1_2 + - name: Upload wheels + uses: actions/upload-artifact@v4 + with: + name: wheels-musllinux-${{ matrix.platform.target }} + path: dist + + windows: + runs-on: ${{ matrix.platform.runner }} + strategy: + matrix: + platform: + - runner: windows-latest + target: x64 + - runner: windows-latest + target: x86 + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-python@v5 + with: + python-version: 3.x + architecture: ${{ matrix.platform.target }} + - name: Build wheels + uses: PyO3/maturin-action@v1 + with: + target: ${{ matrix.platform.target }} + args: --release --out dist --find-interpreter + sccache: 'true' + - name: Upload wheels + uses: actions/upload-artifact@v4 + with: + name: wheels-windows-${{ matrix.platform.target }} + path: dist + + macos: + runs-on: ${{ matrix.platform.runner }} + strategy: + matrix: + platform: + - runner: macos-12 + target: x86_64 + - runner: macos-14 + target: aarch64 + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-python@v5 + with: + python-version: 3.x + - name: Build wheels + uses: PyO3/maturin-action@v1 + with: + target: ${{ matrix.platform.target }} + args: --release --out dist --find-interpreter + sccache: 'true' + - name: Upload wheels + uses: actions/upload-artifact@v4 + with: + name: wheels-macos-${{ matrix.platform.target }} + path: dist + + sdist: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: Build sdist + uses: PyO3/maturin-action@v1 + with: + command: sdist + args: --out dist + - name: Upload sdist + uses: actions/upload-artifact@v4 + with: + name: wheels-sdist + path: dist + + release: + name: Release + runs-on: ubuntu-latest + if: "startsWith(github.ref, 'refs/tags/')" + needs: [linux, musllinux, windows, macos, sdist] + steps: + - uses: actions/download-artifact@v4 + - name: Publish to PyPI + uses: PyO3/maturin-action@v1 + env: + MATURIN_PYPI_TOKEN: ${{ secrets.PYPI_API_TOKEN }} + with: + command: upload + args: --non-interactive --skip-existing wheels-*/* diff --git a/sbv2_bindings/Cargo.toml b/sbv2_bindings/Cargo.toml new file mode 100644 index 0000000..f372191 --- /dev/null +++ b/sbv2_bindings/Cargo.toml @@ -0,0 +1,15 @@ +[package] +name = "sbv2_bindings" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html +[lib] +name = "sbv2_bindings" +crate-type = ["cdylib"] + +[dependencies] +anyhow.workspace = true +ndarray.workspace = true +pyo3 = { version = "0.22.0", features = ["anyhow"] } +sbv2_core = { version = "0.1.0", path = "../sbv2_core" } diff --git a/sbv2_bindings/examples/basic.py b/sbv2_bindings/examples/basic.py new file mode 100644 index 0000000..389ae0d --- /dev/null +++ b/sbv2_bindings/examples/basic.py @@ -0,0 +1,18 @@ +from sbv2_bindings import TTSModel + + +def main(): + print("Loading models...") + model = TTSModel.from_path("../models/debert.onnx", "../models/tokenizer.json") + print("Models loaded!") + + model.load_sbv2file_from_path("amitaro", "../models/amitaro.sbv2") + print("All setup is done!") + + style_vector = model.get_style_vector("amitaro", 0, 1.0) + with open("output.wav", "wb") as f: + f.write(model.synthesize("おはようございます。", "amitaro", style_vector, 0.0, 0.5)) + + +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/sbv2_bindings/pyproject.toml b/sbv2_bindings/pyproject.toml new file mode 100644 index 0000000..9b37eb4 --- /dev/null +++ b/sbv2_bindings/pyproject.toml @@ -0,0 +1,15 @@ +[build-system] +requires = ["maturin>=1.7,<2.0"] +build-backend = "maturin" + +[project] +name = "sbv2_bindings" +requires-python = ">=3.8" +classifiers = [ + "Programming Language :: Rust", + "Programming Language :: Python :: Implementation :: CPython", + "Programming Language :: Python :: Implementation :: PyPy", +] +dynamic = ["version"] +[tool.maturin] +features = ["pyo3/extension-module"] diff --git a/sbv2_bindings/src/lib.rs b/sbv2_bindings/src/lib.rs new file mode 100644 index 0000000..59dace9 --- /dev/null +++ b/sbv2_bindings/src/lib.rs @@ -0,0 +1,11 @@ +use pyo3::prelude::*; +mod sbv2; +pub mod style; + +/// sbv2 bindings module +#[pymodule] +fn sbv2_bindings(m: &Bound<'_, PyModule>) -> PyResult<()> { + m.add_class::()?; + m.add_class::()?; + Ok(()) +} diff --git a/sbv2_bindings/src/sbv2.rs b/sbv2_bindings/src/sbv2.rs new file mode 100644 index 0000000..be670e6 --- /dev/null +++ b/sbv2_bindings/src/sbv2.rs @@ -0,0 +1,145 @@ +use pyo3::prelude::*; +use pyo3::types::PyBytes; +use sbv2_core::tts::TTSModelHolder; + +use crate::style::StyleVector; + +use std::fs; + +/// TTSModel class +/// +/// 音声合成するために使うクラス +/// +/// Parameters +/// ---------- +/// bert_model_bytes : bytes +/// BERTモデルのバイナリデータ +/// tokenizer_bytes : bytes +/// トークナイザーのバイナリデータ +#[pyclass] +pub struct TTSModel { + pub model: TTSModelHolder, +} + +#[pymethods] +impl TTSModel { + #[new] + fn new(bert_model_bytes: Vec, tokenizer_bytes: Vec) -> anyhow::Result { + Ok(Self { + model: TTSModelHolder::new(bert_model_bytes, tokenizer_bytes)?, + }) + } + + /// パスからTTSModelインスタンスを生成する + /// + /// Parameters + /// ---------- + /// bert_model_path : str + /// BERTモデルのパス + /// tokenizer_path : str + /// トークナイザーのパス + #[staticmethod] + fn from_path(bert_model_path: String, tokenizer_path: String) -> anyhow::Result { + Ok(Self { + model: TTSModelHolder::new(fs::read(bert_model_path)?, fs::read(tokenizer_path)?)?, + }) + } + + /// SBV2ファイルを読み込む + /// + /// Parameters + /// ---------- + /// ident : str + /// 識別子 + /// sbv2file_bytes : bytes + /// SBV2ファイルのバイナリデータ + fn load_sbv2file(&mut self, ident: String, sbv2file_bytes: Vec) -> anyhow::Result<()> { + self.model.load_sbv2file(ident, sbv2file_bytes)?; + Ok(()) + } + + /// パスからSBV2ファイルを読み込む + /// + /// Parameters + /// ---------- + /// ident : str + /// 識別子 + /// sbv2file_path : str + /// SBV2ファイルのパス + fn load_sbv2file_from_path( + &mut self, + ident: String, + sbv2file_path: String, + ) -> anyhow::Result<()> { + self.model.load_sbv2file(ident, fs::read(sbv2file_path)?)?; + Ok(()) + } + + /// スタイルベクトルを取得する + /// + /// Parameters + /// ---------- + /// ident : str + /// 識別子 + /// style_id : int + /// スタイルID + /// weight : float + /// 重み + /// + /// Returns + /// ------- + /// style_vector : StyleVector + /// スタイルベクトル + fn get_style_vector( + &self, + ident: String, + style_id: i32, + weight: f32, + ) -> anyhow::Result { + Ok(StyleVector::new( + self.model.get_style_vector(ident, style_id, weight)?, + )) + } + + /// テキストから音声を合成する + /// + /// Parameters + /// ---------- + /// text : str + /// テキスト + /// ident : str + /// 識別子 + /// style_vector : StyleVector + /// スタイルベクトル + /// sdp_ratio : float + /// SDP比率 + /// length_scale : float + /// 音声の長さのスケール + /// + /// Returns + /// ------- + /// voice_data : bytes + /// 音声データ + fn synthesize<'p>( + &'p self, + py: Python<'p>, + text: String, + ident: String, + style_vector: StyleVector, + sdp_ratio: f32, + length_scale: f32, + ) -> anyhow::Result> { + let (bert_ori, phones, tones, lang_ids) = self.model.parse_text(&text)?; + let data = self.model.synthesize( + ident, + bert_ori, + phones, + tones, + lang_ids, + style_vector.get(), + sdp_ratio, + length_scale, + )?; + Ok(PyBytes::new_bound(py, &data)) + } +} diff --git a/sbv2_bindings/src/style.rs b/sbv2_bindings/src/style.rs new file mode 100644 index 0000000..3360133 --- /dev/null +++ b/sbv2_bindings/src/style.rs @@ -0,0 +1,19 @@ +use ndarray::Array1; +use pyo3::prelude::*; + +/// StyleVector class +/// +/// スタイルベクトルを表すクラス +#[pyclass] +#[derive(Clone)] +pub struct StyleVector(Array1); + +impl StyleVector { + pub fn new(data: Array1) -> Self { + StyleVector(data) + } + + pub fn get(&self) -> Array1 { + self.0.clone() + } +} diff --git a/sbv2_core/Cargo.toml b/sbv2_core/Cargo.toml index 63badb6..f361d84 100644 --- a/sbv2_core/Cargo.toml +++ b/sbv2_core/Cargo.toml @@ -1,11 +1,12 @@ [package] name = "sbv2_core" description = "Style-Bert-VITSの推論ライブラリ" -version = "0.1.1" +version = "0.1.2" edition = "2021" license = "MIT" readme = "../README.md" repository = "https://github.com/tuna2134/sbv2-api" +documentation = "https://docs.rs/sbv2_core" [dependencies] anyhow.workspace = true @@ -13,7 +14,7 @@ dotenvy.workspace = true env_logger.workspace = true hound = "3.5.1" jpreprocess = { version = "0.10.0", features = ["naist-jdic"] } -ndarray = "0.16.1" +ndarray.workspace = true num_cpus = "1.16.0" once_cell = "1.19.0" ort = { git = "https://github.com/pykeio/ort.git", version = "2.0.0-rc.6" }