From 97e711ae4b594d789f53671c815fbc4bb563a27c Mon Sep 17 00:00:00 2001 From: Ryo Yamashita Date: Fri, 13 Oct 2023 02:44:38 +0900 Subject: [PATCH 1/9] =?UTF-8?q?Python=20API=E3=81=A8Java=20API=E3=81=AE?= =?UTF-8?q?=E3=82=A8=E3=83=A9=E3=83=BC=E3=82=92=E8=A9=B3=E7=B4=B0=E3=81=AB?= =?UTF-8?q?=E3=81=99=E3=82=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../test/test_pseudo_raii_for_synthesizer.py | 6 +- .../python/test/test_user_dict_manipulate.py | 3 +- .../python/voicevox_core/__init__.py | 38 ++++- .../python/voicevox_core/_rust.pyi | 89 +++++++++- .../voicevox_core_python_api/src/convert.rs | 93 +++++++++-- crates/voicevox_core_python_api/src/lib.rs | 152 +++++++++++------- 6 files changed, 302 insertions(+), 79 deletions(-) diff --git a/crates/voicevox_core_python_api/python/test/test_pseudo_raii_for_synthesizer.py b/crates/voicevox_core_python_api/python/test/test_pseudo_raii_for_synthesizer.py index 165770dab..6c6b15355 100644 --- a/crates/voicevox_core_python_api/python/test/test_pseudo_raii_for_synthesizer.py +++ b/crates/voicevox_core_python_api/python/test/test_pseudo_raii_for_synthesizer.py @@ -5,7 +5,7 @@ import conftest import pytest import pytest_asyncio -from voicevox_core import OpenJtalk, Synthesizer, VoicevoxError +from voicevox_core import OpenJtalk, Synthesizer def test_enter_returns_workable_self(synthesizer: Synthesizer) -> None: @@ -24,14 +24,14 @@ def test_closing_multiple_times_is_allowed(synthesizer: Synthesizer) -> None: def test_access_after_close_denied(synthesizer: Synthesizer) -> None: synthesizer.close() - with pytest.raises(VoicevoxError, match="^The `Synthesizer` is closed$"): + with pytest.raises(ValueError, match="^The `Synthesizer` is closed$"): _ = synthesizer.metas def test_access_after_exit_denied(synthesizer: Synthesizer) -> None: with synthesizer: pass - with pytest.raises(VoicevoxError, match="^The `Synthesizer` is closed$"): + with pytest.raises(ValueError, match="^The `Synthesizer` is closed$"): _ = synthesizer.metas diff --git a/crates/voicevox_core_python_api/python/test/test_user_dict_manipulate.py b/crates/voicevox_core_python_api/python/test/test_user_dict_manipulate.py index f7902ea94..c283b48f1 100644 --- a/crates/voicevox_core_python_api/python/test/test_user_dict_manipulate.py +++ b/crates/voicevox_core_python_api/python/test/test_user_dict_manipulate.py @@ -5,6 +5,7 @@ import tempfile from uuid import UUID +import pydantic import pytest import voicevox_core # noqa: F401 @@ -69,7 +70,7 @@ async def test_user_dict_load() -> None: assert uuid_c in dict_a.words # 単語のバリデーション - with pytest.raises(voicevox_core.VoicevoxError): + with pytest.raises(pydantic.ValidationError): dict_a.add_word( voicevox_core.UserDictWord( surface="", 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 ce6245bfc..abcd763fe 100644 --- a/crates/voicevox_core_python_api/python/voicevox_core/__init__.py +++ b/crates/voicevox_core_python_api/python/voicevox_core/__init__.py @@ -12,11 +12,28 @@ UserDictWordType, ) from ._rust import ( # noqa: F401 + ExtractFullContextLabelError, + GetSupportedDevicesError, + GpuSupportError, + InferenceFailedError, + InvalidModelDataError, + InvalidWordError, + LoadUserDictError, + ModelAlreadyLoadedError, + ModelNotFoundError, + NotLoadedOpenjtalkDictError, OpenJtalk, + OpenZipFileError, + ParseKanaError, + ReadZipEntryError, + SaveUserDictError, + StyleAlreadyLoadedError, + StyleNotFoundError, Synthesizer, UserDict, + UseUserDictError, VoiceModel, - VoicevoxError, + WordNotFoundError, __version__, supported_devices, ) @@ -26,15 +43,32 @@ "AccelerationMode", "AccentPhrase", "AudioQuery", + "ExtractFullContextLabelError", + "GetSupportedDevicesError", + "GpuSupportError", + "InferenceFailedError", + "InvalidModelDataError", + "InvalidWordError", + "LoadUserDictError", + "ModelAlreadyLoadedError", + "ModelNotFoundError", "Mora", + "NotLoadedOpenjtalkDictError", "OpenJtalk", + "OpenZipFileError", + "ParseKanaError", + "ReadZipEntryError", + "SaveUserDictError", "SpeakerMeta", + "StyleAlreadyLoadedError", + "StyleNotFoundError", "SupportedDevices", "Synthesizer", - "VoicevoxError", "VoiceModel", "supported_devices", + "UseUserDictError", "UserDict", "UserDictWord", "UserDictWordType", + "WordNotFoundError", ] diff --git a/crates/voicevox_core_python_api/python/voicevox_core/_rust.pyi b/crates/voicevox_core_python_api/python/voicevox_core/_rust.pyi index b4e44c8e2..734bfb321 100644 --- a/crates/voicevox_core_python_api/python/voicevox_core/_rust.pyi +++ b/crates/voicevox_core_python_api/python/voicevox_core/_rust.pyi @@ -426,8 +426,93 @@ class UserDict: """ ... -class VoicevoxError(Exception): - """VOICEVOX COREのエラー。""" +class NotLoadedOpenjtalkDictError(Exception): + """open_jtalk辞書ファイルが読み込まれていない。""" + + ... + +class GpuSupportError(Exception): + """GPUモードがサポートされていない。""" + + ... + +class OpenZipFileError(Exception): + """ZIPファイルを開くことに失敗した。""" + + ... + +class ReadZipEntryError(Exception): + """ZIP内のファイルが読めなかった。""" + + ... + +class ModelAlreadyLoadedError(Exception): + """すでに読み込まれている音声モデルを読み込もうとした。""" + + ... + +class StyleAlreadyLoadedError(Exception): + """すでに読み込まれているスタイルを読み込もうとした。""" + + ... + +class InvalidModelDataError(Exception): + """無効なモデルデータ。""" + + ... + +class GetSupportedDevicesError(Exception): + """サポートされているデバイス情報取得に失敗した。""" + + ... + +class StyleNotFoundError(Exception): + """スタイルIDに対するスタイルが見つからなかった。""" + + ... + +class ModelNotFoundError(Exception): + """音声モデルIDに対する音声モデルが見つからなかった。""" + + ... + +class InferenceFailedError(Exception): + """推論に失敗した。""" + + ... + +class ExtractFullContextLabelError(Exception): + """コンテキストラベル出力に失敗した。""" + + ... + +class ParseKanaError(ValueError): + """AquesTalk風記法のテキストの解析に失敗した。""" + + ... + +class LoadUserDictError(Exception): + """ユーザー辞書を読み込めなかった。""" + + ... + +class SaveUserDictError(Exception): + """ユーザー辞書を書き込めなかった。""" + + ... + +class WordNotFoundError(Exception): + """ユーザー辞書に単語が見つからなかった。""" + + ... + +class UseUserDictError(Exception): + """OpenJTalkのユーザー辞書の設定に失敗した。""" + + ... + +class InvalidWordError(ValueError): + """ユーザー辞書の単語のバリデーションに失敗した。""" ... diff --git a/crates/voicevox_core_python_api/src/convert.rs b/crates/voicevox_core_python_api/src/convert.rs index 5eff85fb4..d4f9cb0d7 100644 --- a/crates/voicevox_core_python_api/src/convert.rs +++ b/crates/voicevox_core_python_api/src/convert.rs @@ -1,8 +1,11 @@ -use crate::VoicevoxError; -use std::{fmt::Display, future::Future, path::PathBuf}; +use std::{error::Error as _, future::Future, iter, path::PathBuf}; use easy_ext::ext; -use pyo3::{types::PyList, FromPyObject as _, PyAny, PyObject, PyResult, Python, ToPyObject}; +use pyo3::{ + exceptions::{PyException, PyValueError}, + types::PyList, + FromPyObject as _, PyAny, PyObject, PyResult, Python, ToPyObject, +}; use serde::{de::DeserializeOwned, Serialize}; use serde_json::json; use uuid::Uuid; @@ -10,6 +13,14 @@ use voicevox_core::{ AccelerationMode, AccentPhraseModel, StyleId, UserDictWordType, VoiceModelMeta, }; +use crate::{ + ExtractFullContextLabelError, GetSupportedDevicesError, GpuSupportError, InferenceFailedError, + InvalidModelDataError, InvalidWordError, LoadUserDictError, ModelAlreadyLoadedError, + ModelNotFoundError, NotLoadedOpenjtalkDictError, OpenZipFileError, ParseKanaError, + ReadZipEntryError, SaveUserDictError, StyleAlreadyLoadedError, StyleNotFoundError, + UseUserDictError, WordNotFoundError, +}; + pub fn from_acceleration_mode(ob: &PyAny) -> PyResult { let py = ob.py(); @@ -31,7 +42,7 @@ pub fn from_utf8_path(ob: &PyAny) -> PyResult { PathBuf::extract(ob)? .into_os_string() .into_string() - .map_err(|s| VoicevoxError::new_err(format!("{s:?} cannot be encoded to UTF-8"))) + .map_err(|s| PyValueError::new_err(format!("{s:?} cannot be encoded to UTF-8"))) } pub fn from_dataclass(ob: &PyAny) -> PyResult { @@ -42,7 +53,7 @@ pub fn from_dataclass(ob: &PyAny) -> PyResult { .import("json")? .call_method1("dumps", (ob,))? .extract::()?; - serde_json::from_str(json).into_py_result() + serde_json::from_str(json).into_py_value_result() } pub fn to_pydantic_voice_model_meta<'py>( @@ -63,7 +74,7 @@ pub fn to_pydantic_voice_model_meta<'py>( pub fn to_pydantic_dataclass(x: impl Serialize, class: &PyAny) -> PyResult<&PyAny> { let py = class.py(); - let x = serde_json::to_string(&x).into_py_result()?; + let x = serde_json::to_string(&x).into_py_value_result()?; let x = py.import("json")?.call_method1("loads", (x,))?.downcast()?; class.call((), Some(x)) } @@ -86,11 +97,10 @@ where py, pyo3_asyncio::tokio::get_current_locals(py)?, async move { - let replaced_accent_phrases = method(rust_accent_phrases, speaker_id) - .await - .into_py_result()?; + let replaced_accent_phrases = method(rust_accent_phrases, speaker_id).await; Python::with_gil(|py| { let replaced_accent_phrases = replaced_accent_phrases + .into_py_result(py)? .iter() .map(move |accent_phrase| { to_pydantic_dataclass( @@ -107,7 +117,7 @@ where } pub fn to_rust_uuid(ob: &PyAny) -> PyResult { let uuid = ob.getattr("hex")?.extract::()?; - uuid.parse().into_py_result() + uuid.parse::().into_py_value_result() } pub fn to_py_uuid(py: Python, uuid: Uuid) -> PyResult { let uuid = uuid.hyphenated().to_string(); @@ -122,7 +132,7 @@ pub fn to_rust_user_dict_word(ob: &PyAny) -> PyResult( py: Python<'py>, @@ -137,12 +147,65 @@ pub fn to_py_user_dict_word<'py>( pub fn to_rust_word_type(word_type: &PyAny) -> PyResult { let name = word_type.getattr("name")?.extract::()?; - serde_json::from_value::(json!(name)).into_py_result() + serde_json::from_value::(json!(name)).into_py_value_result() +} + +#[ext] +pub impl voicevox_core::Result { + fn into_py_result(self, py: Python<'_>) -> PyResult { + use voicevox_core::ErrorKind; + + self.map_err(|err| { + let msg = err.to_string(); + let top = match err.kind() { + ErrorKind::NotLoadedOpenjtalkDict => NotLoadedOpenjtalkDictError::new_err(msg), + ErrorKind::GpuSupport => GpuSupportError::new_err(msg), + ErrorKind::OpenZipFile => OpenZipFileError::new_err(msg), + ErrorKind::ReadZipEntry => ReadZipEntryError::new_err(msg), + ErrorKind::ModelAlreadyLoaded => ModelAlreadyLoadedError::new_err(msg), + ErrorKind::StyleAlreadyLoaded => StyleAlreadyLoadedError::new_err(msg), + ErrorKind::InvalidModelData => InvalidModelDataError::new_err(msg), + 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::ExtractFullContextLabel => ExtractFullContextLabelError::new_err(msg), + ErrorKind::ParseKana => ParseKanaError::new_err(msg), + ErrorKind::LoadUserDict => LoadUserDictError::new_err(msg), + ErrorKind::SaveUserDict => SaveUserDictError::new_err(msg), + ErrorKind::WordNotFound => WordNotFoundError::new_err(msg), + ErrorKind::UseUserDict => UseUserDictError::new_err(msg), + ErrorKind::InvalidWord => InvalidWordError::new_err(msg), + }; + + [top] + .into_iter() + .chain( + iter::successors(err.source(), |&source| source.source()) + .map(|source| PyException::new_err(source.to_string())), + ) + .collect::>() + .into_iter() + .rev() + .reduce(|prev, source| { + source.set_cause(py, Some(prev)); + source + }) + .expect("should not be empty") + }) + } +} + +#[ext] +impl std::result::Result { + fn into_py_value_result(self) -> PyResult { + self.map_err(|e| PyValueError::new_err(e.to_string())) + } } #[ext] -pub impl Result { - fn into_py_result(self) -> PyResult { - self.map_err(|e| VoicevoxError::new_err(e.to_string())) +impl serde_json::Result { + fn into_py_value_result(self) -> PyResult { + self.map_err(|e| PyValueError::new_err(e.to_string())) } } diff --git a/crates/voicevox_core_python_api/src/lib.rs b/crates/voicevox_core_python_api/src/lib.rs index 42d3feaeb..e3165189f 100644 --- a/crates/voicevox_core_python_api/src/lib.rs +++ b/crates/voicevox_core_python_api/src/lib.rs @@ -6,7 +6,7 @@ use log::debug; use once_cell::sync::Lazy; use pyo3::{ create_exception, - exceptions::PyException, + exceptions::{PyException, PyValueError}, pyclass, pyfunction, pymethods, pymodule, types::{IntoPyDict as _, PyBytes, PyDict, PyList, PyModule}, wrap_pyfunction, PyAny, PyObject, PyRef, PyResult, PyTypeInfo, Python, ToPyObject, @@ -22,7 +22,7 @@ static RUNTIME: Lazy = Lazy::new(|| Runtime::new().unwrap()); #[pymodule] #[pyo3(name = "_rust")] -fn rust(py: Python<'_>, module: &PyModule) -> PyResult<()> { +fn rust(_: Python<'_>, module: &PyModule) -> PyResult<()> { pyo3_log::init(); module.add("__version__", env!("CARGO_PKG_VERSION"))?; @@ -34,16 +34,45 @@ fn rust(py: Python<'_>, module: &PyModule) -> PyResult<()> { module.add_class::()?; module.add_class::()?; module.add_class::()?; - module.add("VoicevoxError", py.get_type::())?; - Ok(()) + + add_exceptions(module) } -create_exception!( - voicevox_core, - VoicevoxError, - PyException, - "voicevox_core Error." -); +macro_rules! exceptions { + ($($name:ident: $base:ty;)*) => { + $( + create_exception!(voicevox_core, $name, $base); + )* + + fn add_exceptions(module: &PyModule) -> PyResult<()> { + $( + module.add(stringify!($name), module.py().get_type::<$name>())?; + )* + Ok(()) + } + }; +} + +exceptions! { + NotLoadedOpenjtalkDictError: PyException; + GpuSupportError: PyException; + OpenZipFileError: PyException; + ReadZipEntryError: PyException; + ModelAlreadyLoadedError: PyException; + StyleAlreadyLoadedError: PyException; + InvalidModelDataError: PyException; + GetSupportedDevicesError: PyException; + StyleNotFoundError: PyException; + ModelNotFoundError: PyException; + InferenceFailedError: PyException; + ExtractFullContextLabelError: PyException; + ParseKanaError: PyValueError; + LoadUserDictError: PyException; + SaveUserDictError: PyException; + WordNotFoundError: PyException; + UseUserDictError: PyException; + InvalidWordError: PyValueError; +} #[pyclass] #[derive(Clone)] @@ -57,7 +86,7 @@ fn supported_devices(py: Python) -> PyResult<&PyAny> { .import("voicevox_core")? .getattr("SupportedDevices")? .downcast()?; - let s = voicevox_core::SupportedDevices::create().into_py_result()?; + let s = voicevox_core::SupportedDevices::create().into_py_result(py)?; to_pydantic_dataclass(s, class) } @@ -69,9 +98,8 @@ impl VoiceModel { #[pyo3(from_py_with = "from_utf8_path")] path: String, ) -> PyResult<&PyAny> { pyo3_asyncio::tokio::future_into_py(py, async move { - let model = voicevox_core::VoiceModel::from_path(path) - .await - .into_py_result()?; + let model = voicevox_core::VoiceModel::from_path(path).await; + let model = Python::with_gil(|py| model.into_py_result(py))?; Ok(Self { model }) }) } @@ -96,19 +124,22 @@ struct OpenJtalk { #[pymethods] impl OpenJtalk { #[new] - fn new(#[pyo3(from_py_with = "from_utf8_path")] open_jtalk_dict_dir: String) -> PyResult { + fn new( + #[pyo3(from_py_with = "from_utf8_path")] open_jtalk_dict_dir: String, + py: Python<'_>, + ) -> PyResult { Ok(Self { open_jtalk: Arc::new( voicevox_core::OpenJtalk::new_with_initialize(open_jtalk_dict_dir) - .into_py_result()?, + .into_py_result(py)?, ), }) } - fn use_user_dict(&self, user_dict: UserDict) -> PyResult<()> { + fn use_user_dict(&self, user_dict: UserDict, py: Python<'_>) -> PyResult<()> { self.open_jtalk .use_user_dict(&user_dict.dict) - .into_py_result() + .into_py_result(py) } } @@ -139,9 +170,8 @@ impl Synthesizer { cpu_num_threads, }, ) - .await - .into_py_result()? - .into(); + .await; + let synthesizer = Python::with_gil(|py| synthesizer.into_py_result(py))?.into(); Ok(Self { synthesizer: Closable::new(Arc::new(synthesizer)), }) @@ -186,20 +216,21 @@ impl Synthesizer { let model: VoiceModel = model.extract()?; let synthesizer = self.synthesizer.get()?.clone(); pyo3_asyncio::tokio::future_into_py(py, async move { - synthesizer + let synthesizer = synthesizer .lock() .await .load_voice_model(&model.model) - .await - .into_py_result() + .await; + + Python::with_gil(|py| synthesizer.into_py_result(py)) }) } - fn unload_voice_model(&mut self, voice_model_id: &str) -> PyResult<()> { + fn unload_voice_model(&mut self, voice_model_id: &str, py: Python<'_>) -> PyResult<()> { RUNTIME .block_on(self.synthesizer.get()?.lock()) .unload_voice_model(&VoiceModelId::new(voice_model_id.to_string())) - .into_py_result() + .into_py_result(py) } fn is_loaded_voice_model(&self, voice_model_id: &str) -> PyResult { @@ -224,12 +255,11 @@ impl Synthesizer { .lock() .await .audio_query_from_kana(&kana, StyleId::new(style_id)) - .await - .into_py_result()?; + .await; Python::with_gil(|py| { let class = py.import("voicevox_core")?.getattr("AudioQuery")?; - let ret = to_pydantic_dataclass(audio_query, class)?; + let ret = to_pydantic_dataclass(audio_query.into_py_result(py)?, class)?; Ok(ret.to_object(py)) }) }, @@ -247,10 +277,10 @@ impl Synthesizer { .lock() .await .audio_query(&text, StyleId::new(style_id)) - .await - .into_py_result()?; + .await; Python::with_gil(|py| { + let audio_query = audio_query.into_py_result(py)?; let class = py.import("voicevox_core")?.getattr("AudioQuery")?; let ret = to_pydantic_dataclass(audio_query, class)?; Ok(ret.to_object(py)) @@ -275,11 +305,11 @@ impl Synthesizer { .lock() .await .create_accent_phrases_from_kana(&kana, StyleId::new(style_id)) - .await - .into_py_result()?; + .await; Python::with_gil(|py| { let class = py.import("voicevox_core")?.getattr("AccentPhrase")?; let accent_phrases = accent_phrases + .into_py_result(py)? .iter() .map(|ap| to_pydantic_dataclass(ap, class)) .collect::>>(); @@ -306,11 +336,11 @@ impl Synthesizer { .lock() .await .create_accent_phrases(&text, StyleId::new(style_id)) - .await - .into_py_result()?; + .await; Python::with_gil(|py| { let class = py.import("voicevox_core")?.getattr("AccentPhrase")?; let accent_phrases = accent_phrases + .into_py_result(py)? .iter() .map(|ap| to_pydantic_dataclass(ap, class)) .collect::>>(); @@ -389,9 +419,11 @@ impl Synthesizer { enable_interrogative_upspeak, }, ) - .await - .into_py_result()?; - Python::with_gil(|py| Ok(PyBytes::new(py, &wav).to_object(py))) + .await; + Python::with_gil(|py| { + let wav = wav.into_py_result(py)?; + Ok(PyBytes::new(py, &wav).to_object(py)) + }) }, ) } @@ -422,9 +454,12 @@ impl Synthesizer { .lock() .await .tts_from_kana(&kana, style_id, &options) - .await - .into_py_result()?; - Python::with_gil(|py| Ok(PyBytes::new(py, &wav).to_object(py))) + .await; + + Python::with_gil(|py| { + let wav = wav.into_py_result(py)?; + Ok(PyBytes::new(py, &wav).to_object(py)) + }) }, ) } @@ -455,9 +490,12 @@ impl Synthesizer { .lock() .await .tts(&text, style_id, &options) - .await - .into_py_result()?; - Python::with_gil(|py| Ok(PyBytes::new(py, &wav).to_object(py))) + .await; + + Python::with_gil(|py| { + let wav = wav.into_py_result(py)?; + Ok(PyBytes::new(py, &wav).to_object(py)) + }) }, ) } @@ -488,7 +526,7 @@ impl Closable { fn get(&self) -> PyResult<&T> { match &self.content { MaybeClosed::Open(content) => Ok(content), - MaybeClosed::Closed => Err(VoicevoxError::new_err(format!( + MaybeClosed::Closed => Err(PyValueError::new_err(format!( "The `{}` is closed", C::NAME, ))), @@ -510,8 +548,8 @@ impl Drop for Closable { } #[pyfunction] -fn _validate_pronunciation(pronunciation: &str) -> PyResult<()> { - voicevox_core::__internal::validate_pronunciation(pronunciation).into_py_result() +fn _validate_pronunciation(pronunciation: &str, py: Python<'_>) -> PyResult<()> { + voicevox_core::__internal::validate_pronunciation(pronunciation).into_py_result(py) } #[pyfunction] @@ -532,12 +570,12 @@ impl UserDict { Self::default() } - fn load(&mut self, path: &str) -> PyResult<()> { - self.dict.load(path).into_py_result() + fn load(&mut self, path: &str, py: Python<'_>) -> PyResult<()> { + self.dict.load(path).into_py_result(py) } - fn save(&self, path: &str) -> PyResult<()> { - self.dict.save(path).into_py_result() + fn save(&self, path: &str, py: Python<'_>) -> PyResult<()> { + self.dict.save(path).into_py_result(py) } fn add_word( @@ -545,7 +583,7 @@ impl UserDict { #[pyo3(from_py_with = "to_rust_user_dict_word")] word: UserDictWord, py: Python, ) -> PyResult { - let uuid = self.dict.add_word(word).into_py_result()?; + let uuid = self.dict.add_word(word).into_py_result(py)?; to_py_uuid(py, uuid) } @@ -554,21 +592,23 @@ impl UserDict { &mut self, #[pyo3(from_py_with = "to_rust_uuid")] word_uuid: Uuid, #[pyo3(from_py_with = "to_rust_user_dict_word")] word: UserDictWord, + py: Python<'_>, ) -> PyResult<()> { - self.dict.update_word(word_uuid, word).into_py_result()?; + self.dict.update_word(word_uuid, word).into_py_result(py)?; Ok(()) } fn remove_word( &mut self, #[pyo3(from_py_with = "to_rust_uuid")] word_uuid: Uuid, + py: Python<'_>, ) -> PyResult<()> { - self.dict.remove_word(word_uuid).into_py_result()?; + self.dict.remove_word(word_uuid).into_py_result(py)?; Ok(()) } - fn import_dict(&mut self, other: &UserDict) -> PyResult<()> { - self.dict.import(&other.dict).into_py_result()?; + fn import_dict(&mut self, other: &UserDict, py: Python<'_>) -> PyResult<()> { + self.dict.import(&other.dict).into_py_result(py)?; Ok(()) } From 04ac25aeb8ea8d8b877140ebb3424f8e24101f10 Mon Sep 17 00:00:00 2001 From: Ryo Yamashita Date: Fri, 20 Oct 2023 02:46:22 +0900 Subject: [PATCH 2/9] Java API --- Cargo.lock | 4 +- Cargo.toml | 1 + crates/voicevox_core/Cargo.toml | 2 +- crates/voicevox_core_java_api/Cargo.toml | 6 +- .../voicevoxcore/VoicevoxException.java | 8 - .../ExtractFullContextLabelException.java | 12 ++ .../GetSupportedDevicesException.java | 14 ++ .../exceptions/GpuSupportException.java | 12 ++ .../exceptions/InferenceFailedException.java | 14 ++ .../exceptions/InvalidModelDataException.java | 14 ++ .../exceptions/InvalidWordException.java | 12 ++ .../exceptions/LoadUserDictException.java | 14 ++ .../ModelAlreadyLoadedException.java | 12 ++ .../exceptions/ModelNotFoundException.java | 8 + .../NotLoadedOpenjtalkDictException.java | 12 ++ .../exceptions/OpenZipFileException.java | 14 ++ .../exceptions/ParseKanaException.java | 12 ++ .../exceptions/ReadZipEntryException.java | 14 ++ .../exceptions/SaveUserDictException.java | 14 ++ .../StyleAlreadyLoadedException.java | 12 ++ .../exceptions/StyleNotFoundException.java | 8 + .../exceptions/UseUserDictException.java | 12 ++ .../exceptions/WordNotFoundException.java | 8 + crates/voicevox_core_java_api/src/common.rs | 155 +++++++++++++++++- .../voicevox_core_java_api/src/synthesizer.rs | 5 +- .../voicevox_core_java_api/src/voice_model.rs | 2 +- 26 files changed, 377 insertions(+), 24 deletions(-) delete mode 100644 crates/voicevox_core_java_api/lib/src/main/java/jp/hiroshiba/voicevoxcore/VoicevoxException.java create mode 100644 crates/voicevox_core_java_api/lib/src/main/java/jp/hiroshiba/voicevoxcore/exceptions/ExtractFullContextLabelException.java create mode 100644 crates/voicevox_core_java_api/lib/src/main/java/jp/hiroshiba/voicevoxcore/exceptions/GetSupportedDevicesException.java create mode 100644 crates/voicevox_core_java_api/lib/src/main/java/jp/hiroshiba/voicevoxcore/exceptions/GpuSupportException.java create mode 100644 crates/voicevox_core_java_api/lib/src/main/java/jp/hiroshiba/voicevoxcore/exceptions/InferenceFailedException.java create mode 100644 crates/voicevox_core_java_api/lib/src/main/java/jp/hiroshiba/voicevoxcore/exceptions/InvalidModelDataException.java create mode 100644 crates/voicevox_core_java_api/lib/src/main/java/jp/hiroshiba/voicevoxcore/exceptions/InvalidWordException.java create mode 100644 crates/voicevox_core_java_api/lib/src/main/java/jp/hiroshiba/voicevoxcore/exceptions/LoadUserDictException.java create mode 100644 crates/voicevox_core_java_api/lib/src/main/java/jp/hiroshiba/voicevoxcore/exceptions/ModelAlreadyLoadedException.java create mode 100644 crates/voicevox_core_java_api/lib/src/main/java/jp/hiroshiba/voicevoxcore/exceptions/ModelNotFoundException.java create mode 100644 crates/voicevox_core_java_api/lib/src/main/java/jp/hiroshiba/voicevoxcore/exceptions/NotLoadedOpenjtalkDictException.java create mode 100644 crates/voicevox_core_java_api/lib/src/main/java/jp/hiroshiba/voicevoxcore/exceptions/OpenZipFileException.java create mode 100644 crates/voicevox_core_java_api/lib/src/main/java/jp/hiroshiba/voicevoxcore/exceptions/ParseKanaException.java create mode 100644 crates/voicevox_core_java_api/lib/src/main/java/jp/hiroshiba/voicevoxcore/exceptions/ReadZipEntryException.java create mode 100644 crates/voicevox_core_java_api/lib/src/main/java/jp/hiroshiba/voicevoxcore/exceptions/SaveUserDictException.java create mode 100644 crates/voicevox_core_java_api/lib/src/main/java/jp/hiroshiba/voicevoxcore/exceptions/StyleAlreadyLoadedException.java create mode 100644 crates/voicevox_core_java_api/lib/src/main/java/jp/hiroshiba/voicevoxcore/exceptions/StyleNotFoundException.java create mode 100644 crates/voicevox_core_java_api/lib/src/main/java/jp/hiroshiba/voicevoxcore/exceptions/UseUserDictException.java create mode 100644 crates/voicevox_core_java_api/lib/src/main/java/jp/hiroshiba/voicevoxcore/exceptions/WordNotFoundException.java diff --git a/Cargo.lock b/Cargo.lock index e96ad5dbb..a35ad2bf5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4355,15 +4355,17 @@ name = "voicevox_core_java_api" version = "0.0.0" dependencies = [ "android_logger", - "anyhow", "chrono", + "derive_more", "jni", "once_cell", "serde_json", "test_util", + "thiserror", "tokio", "tracing", "tracing-subscriber", + "uuid", "voicevox_core", ] diff --git a/Cargo.toml b/Cargo.toml index 73e6f5cd4..b6237098a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -14,6 +14,7 @@ resolver = "2" anyhow = "1.0.65" async_zip = { version = "0.0.11", features = ["full"] } clap = { version = "4.0.10", features = ["derive"] } +derive_more = "0.99.17" easy-ext = "1.0.1" fs-err = { version = "2.9.0", features = ["tokio"] } futures = "0.3.26" diff --git a/crates/voicevox_core/Cargo.toml b/crates/voicevox_core/Cargo.toml index 988d694cf..c2159782c 100644 --- a/crates/voicevox_core/Cargo.toml +++ b/crates/voicevox_core/Cargo.toml @@ -14,7 +14,7 @@ async_zip.workspace = true cfg-if = "1.0.0" derive-getters.workspace = true derive-new = "0.5.9" -derive_more = "0.99.17" +derive_more.workspace = true duplicate = "1.0.0" easy-ext.workspace = true fs-err.workspace = true diff --git a/crates/voicevox_core_java_api/Cargo.toml b/crates/voicevox_core_java_api/Cargo.toml index bd546a243..8beafb5ef 100644 --- a/crates/voicevox_core_java_api/Cargo.toml +++ b/crates/voicevox_core_java_api/Cargo.toml @@ -12,14 +12,16 @@ directml = ["voicevox_core/directml"] [dependencies] android_logger = "0.13.1" -anyhow.workspace = true chrono = "0.4.26" +derive_more.workspace = true jni = "0.21.1" once_cell.workspace = true serde_json.workspace = true tokio.workspace = true -tracing.workspace = true +thiserror.workspace = true tracing-subscriber.workspace = true +tracing.workspace = true +uuid.workspace = true voicevox_core.workspace = true [dev-dependencies] diff --git a/crates/voicevox_core_java_api/lib/src/main/java/jp/hiroshiba/voicevoxcore/VoicevoxException.java b/crates/voicevox_core_java_api/lib/src/main/java/jp/hiroshiba/voicevoxcore/VoicevoxException.java deleted file mode 100644 index 3bf6f53a4..000000000 --- a/crates/voicevox_core_java_api/lib/src/main/java/jp/hiroshiba/voicevoxcore/VoicevoxException.java +++ /dev/null @@ -1,8 +0,0 @@ -package jp.hiroshiba.voicevoxcore; - -/** VOICEVOX COREのエラー。 */ -public class VoicevoxException extends RuntimeException { - public VoicevoxException(String message) { - super(message); - } -} diff --git a/crates/voicevox_core_java_api/lib/src/main/java/jp/hiroshiba/voicevoxcore/exceptions/ExtractFullContextLabelException.java b/crates/voicevox_core_java_api/lib/src/main/java/jp/hiroshiba/voicevoxcore/exceptions/ExtractFullContextLabelException.java new file mode 100644 index 000000000..2bae2a5e8 --- /dev/null +++ b/crates/voicevox_core_java_api/lib/src/main/java/jp/hiroshiba/voicevoxcore/exceptions/ExtractFullContextLabelException.java @@ -0,0 +1,12 @@ +package jp.hiroshiba.voicevoxcore.exceptions; + +/** コンテキストラベル出力に失敗した。 */ +public class ExtractFullContextLabelException extends IllegalArgumentException { + public ExtractFullContextLabelException(String message) { + super(message); + } + + public ExtractFullContextLabelException(String message, Throwable cause) { + super(message, cause); + } +} diff --git a/crates/voicevox_core_java_api/lib/src/main/java/jp/hiroshiba/voicevoxcore/exceptions/GetSupportedDevicesException.java b/crates/voicevox_core_java_api/lib/src/main/java/jp/hiroshiba/voicevoxcore/exceptions/GetSupportedDevicesException.java new file mode 100644 index 000000000..05037dbfe --- /dev/null +++ b/crates/voicevox_core_java_api/lib/src/main/java/jp/hiroshiba/voicevoxcore/exceptions/GetSupportedDevicesException.java @@ -0,0 +1,14 @@ +package jp.hiroshiba.voicevoxcore.exceptions; + +import java.io.IOException; + +/** サポートされているデバイス情報取得に失敗した。 */ +public class GetSupportedDevicesException extends IOException { + public GetSupportedDevicesException(String message) { + super(message); + } + + public GetSupportedDevicesException(String message, Throwable cause) { + super(message, cause); + } +} diff --git a/crates/voicevox_core_java_api/lib/src/main/java/jp/hiroshiba/voicevoxcore/exceptions/GpuSupportException.java b/crates/voicevox_core_java_api/lib/src/main/java/jp/hiroshiba/voicevoxcore/exceptions/GpuSupportException.java new file mode 100644 index 000000000..ddcb13fa0 --- /dev/null +++ b/crates/voicevox_core_java_api/lib/src/main/java/jp/hiroshiba/voicevoxcore/exceptions/GpuSupportException.java @@ -0,0 +1,12 @@ +package jp.hiroshiba.voicevoxcore.exceptions; + +/** GPUモードがサポートされていない。 */ +public class GpuSupportException extends RuntimeException { + public GpuSupportException(String message) { + super(message); + } + + public GpuSupportException(String message, Throwable cause) { + super(message, cause); + } +} 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 new file mode 100644 index 000000000..499a530df --- /dev/null +++ b/crates/voicevox_core_java_api/lib/src/main/java/jp/hiroshiba/voicevoxcore/exceptions/InferenceFailedException.java @@ -0,0 +1,14 @@ +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/InvalidModelDataException.java b/crates/voicevox_core_java_api/lib/src/main/java/jp/hiroshiba/voicevoxcore/exceptions/InvalidModelDataException.java new file mode 100644 index 000000000..8cf29cbbc --- /dev/null +++ b/crates/voicevox_core_java_api/lib/src/main/java/jp/hiroshiba/voicevoxcore/exceptions/InvalidModelDataException.java @@ -0,0 +1,14 @@ +package jp.hiroshiba.voicevoxcore.exceptions; + +import java.io.IOException; + +/** 無効なモデルデータ。 */ +public class InvalidModelDataException extends IOException { + public InvalidModelDataException(String message) { + super(message); + } + + public InvalidModelDataException(String message, Throwable cause) { + super(message, cause); + } +} diff --git a/crates/voicevox_core_java_api/lib/src/main/java/jp/hiroshiba/voicevoxcore/exceptions/InvalidWordException.java b/crates/voicevox_core_java_api/lib/src/main/java/jp/hiroshiba/voicevoxcore/exceptions/InvalidWordException.java new file mode 100644 index 000000000..a3921bfd5 --- /dev/null +++ b/crates/voicevox_core_java_api/lib/src/main/java/jp/hiroshiba/voicevoxcore/exceptions/InvalidWordException.java @@ -0,0 +1,12 @@ +package jp.hiroshiba.voicevoxcore.exceptions; + +/** ユーザー辞書の単語のバリデーションに失敗した。 */ +public class InvalidWordException extends IllegalArgumentException { + public InvalidWordException(String message) { + super(message); + } + + public InvalidWordException(String message, Throwable cause) { + super(message, cause); + } +} diff --git a/crates/voicevox_core_java_api/lib/src/main/java/jp/hiroshiba/voicevoxcore/exceptions/LoadUserDictException.java b/crates/voicevox_core_java_api/lib/src/main/java/jp/hiroshiba/voicevoxcore/exceptions/LoadUserDictException.java new file mode 100644 index 000000000..8877a1c54 --- /dev/null +++ b/crates/voicevox_core_java_api/lib/src/main/java/jp/hiroshiba/voicevoxcore/exceptions/LoadUserDictException.java @@ -0,0 +1,14 @@ +package jp.hiroshiba.voicevoxcore.exceptions; + +import java.io.IOException; + +/** ユーザー辞書を読み込めなかった。 */ +public class LoadUserDictException extends IOException { + public LoadUserDictException(String message) { + super(message); + } + + public LoadUserDictException(String message, Throwable cause) { + super(message, cause); + } +} diff --git a/crates/voicevox_core_java_api/lib/src/main/java/jp/hiroshiba/voicevoxcore/exceptions/ModelAlreadyLoadedException.java b/crates/voicevox_core_java_api/lib/src/main/java/jp/hiroshiba/voicevoxcore/exceptions/ModelAlreadyLoadedException.java new file mode 100644 index 000000000..ad3763bfd --- /dev/null +++ b/crates/voicevox_core_java_api/lib/src/main/java/jp/hiroshiba/voicevoxcore/exceptions/ModelAlreadyLoadedException.java @@ -0,0 +1,12 @@ +package jp.hiroshiba.voicevoxcore.exceptions; + +/** すでに読み込まれている音声モデルを読み込もうとした。 */ +public class ModelAlreadyLoadedException extends IllegalStateException { + public ModelAlreadyLoadedException(String message) { + super(message); + } + + public ModelAlreadyLoadedException(String message, Throwable cause) { + super(message, cause); + } +} diff --git a/crates/voicevox_core_java_api/lib/src/main/java/jp/hiroshiba/voicevoxcore/exceptions/ModelNotFoundException.java b/crates/voicevox_core_java_api/lib/src/main/java/jp/hiroshiba/voicevoxcore/exceptions/ModelNotFoundException.java new file mode 100644 index 000000000..953a9345f --- /dev/null +++ b/crates/voicevox_core_java_api/lib/src/main/java/jp/hiroshiba/voicevoxcore/exceptions/ModelNotFoundException.java @@ -0,0 +1,8 @@ +package jp.hiroshiba.voicevoxcore.exceptions; + +/** 音声モデルIDに対する音声モデルが見つからなかった。 */ +public class ModelNotFoundException extends IndexOutOfBoundsException { + public ModelNotFoundException(String message) { + super(message); + } +} diff --git a/crates/voicevox_core_java_api/lib/src/main/java/jp/hiroshiba/voicevoxcore/exceptions/NotLoadedOpenjtalkDictException.java b/crates/voicevox_core_java_api/lib/src/main/java/jp/hiroshiba/voicevoxcore/exceptions/NotLoadedOpenjtalkDictException.java new file mode 100644 index 000000000..3bee93b08 --- /dev/null +++ b/crates/voicevox_core_java_api/lib/src/main/java/jp/hiroshiba/voicevoxcore/exceptions/NotLoadedOpenjtalkDictException.java @@ -0,0 +1,12 @@ +package jp.hiroshiba.voicevoxcore.exceptions; + +/** open_jtalk辞書ファイルが読み込まれていない。 */ +public class NotLoadedOpenjtalkDictException extends IllegalStateException { + public NotLoadedOpenjtalkDictException(String message) { + super(message); + } + + public NotLoadedOpenjtalkDictException(String message, Throwable cause) { + super(message, cause); + } +} diff --git a/crates/voicevox_core_java_api/lib/src/main/java/jp/hiroshiba/voicevoxcore/exceptions/OpenZipFileException.java b/crates/voicevox_core_java_api/lib/src/main/java/jp/hiroshiba/voicevoxcore/exceptions/OpenZipFileException.java new file mode 100644 index 000000000..20f2e853a --- /dev/null +++ b/crates/voicevox_core_java_api/lib/src/main/java/jp/hiroshiba/voicevoxcore/exceptions/OpenZipFileException.java @@ -0,0 +1,14 @@ +package jp.hiroshiba.voicevoxcore.exceptions; + +import java.io.IOException; + +/** ZIPファイルを開くことに失敗した。 */ +public class OpenZipFileException extends IOException { + public OpenZipFileException(String message) { + super(message); + } + + public OpenZipFileException(String message, Throwable cause) { + super(message, cause); + } +} diff --git a/crates/voicevox_core_java_api/lib/src/main/java/jp/hiroshiba/voicevoxcore/exceptions/ParseKanaException.java b/crates/voicevox_core_java_api/lib/src/main/java/jp/hiroshiba/voicevoxcore/exceptions/ParseKanaException.java new file mode 100644 index 000000000..e1d47fcec --- /dev/null +++ b/crates/voicevox_core_java_api/lib/src/main/java/jp/hiroshiba/voicevoxcore/exceptions/ParseKanaException.java @@ -0,0 +1,12 @@ +package jp.hiroshiba.voicevoxcore.exceptions; + +/** AquesTalk風記法のテキストの解析に失敗した。 */ +public class ParseKanaException extends IllegalArgumentException { + public ParseKanaException(String message) { + super(message); + } + + public ParseKanaException(String message, Throwable cause) { + super(message, cause); + } +} diff --git a/crates/voicevox_core_java_api/lib/src/main/java/jp/hiroshiba/voicevoxcore/exceptions/ReadZipEntryException.java b/crates/voicevox_core_java_api/lib/src/main/java/jp/hiroshiba/voicevoxcore/exceptions/ReadZipEntryException.java new file mode 100644 index 000000000..df0d75c0d --- /dev/null +++ b/crates/voicevox_core_java_api/lib/src/main/java/jp/hiroshiba/voicevoxcore/exceptions/ReadZipEntryException.java @@ -0,0 +1,14 @@ +package jp.hiroshiba.voicevoxcore.exceptions; + +import java.io.IOException; + +/** ZIP内のファイルが読めなかった。 */ +public class ReadZipEntryException extends IOException { + public ReadZipEntryException(String message) { + super(message); + } + + public ReadZipEntryException(String message, Throwable cause) { + super(message, cause); + } +} diff --git a/crates/voicevox_core_java_api/lib/src/main/java/jp/hiroshiba/voicevoxcore/exceptions/SaveUserDictException.java b/crates/voicevox_core_java_api/lib/src/main/java/jp/hiroshiba/voicevoxcore/exceptions/SaveUserDictException.java new file mode 100644 index 000000000..f6cce2af0 --- /dev/null +++ b/crates/voicevox_core_java_api/lib/src/main/java/jp/hiroshiba/voicevoxcore/exceptions/SaveUserDictException.java @@ -0,0 +1,14 @@ +package jp.hiroshiba.voicevoxcore.exceptions; + +import java.io.IOException; + +/** ユーザー辞書を書き込めなかった。 */ +public class SaveUserDictException extends IOException { + public SaveUserDictException(String message) { + super(message); + } + + public SaveUserDictException(String message, Throwable cause) { + super(message, cause); + } +} diff --git a/crates/voicevox_core_java_api/lib/src/main/java/jp/hiroshiba/voicevoxcore/exceptions/StyleAlreadyLoadedException.java b/crates/voicevox_core_java_api/lib/src/main/java/jp/hiroshiba/voicevoxcore/exceptions/StyleAlreadyLoadedException.java new file mode 100644 index 000000000..05ac4700f --- /dev/null +++ b/crates/voicevox_core_java_api/lib/src/main/java/jp/hiroshiba/voicevoxcore/exceptions/StyleAlreadyLoadedException.java @@ -0,0 +1,12 @@ +package jp.hiroshiba.voicevoxcore.exceptions; + +/** すでに読み込まれているスタイルを読み込もうとした。 */ +public class StyleAlreadyLoadedException extends IllegalStateException { + public StyleAlreadyLoadedException(String message) { + super(message); + } + + public StyleAlreadyLoadedException(String message, Throwable cause) { + super(message, cause); + } +} diff --git a/crates/voicevox_core_java_api/lib/src/main/java/jp/hiroshiba/voicevoxcore/exceptions/StyleNotFoundException.java b/crates/voicevox_core_java_api/lib/src/main/java/jp/hiroshiba/voicevoxcore/exceptions/StyleNotFoundException.java new file mode 100644 index 000000000..826d88cda --- /dev/null +++ b/crates/voicevox_core_java_api/lib/src/main/java/jp/hiroshiba/voicevoxcore/exceptions/StyleNotFoundException.java @@ -0,0 +1,8 @@ +package jp.hiroshiba.voicevoxcore.exceptions; + +/** スタイルIDに対するスタイルが見つからなかった。 */ +public class StyleNotFoundException extends IndexOutOfBoundsException { + public StyleNotFoundException(String message) { + super(message); + } +} diff --git a/crates/voicevox_core_java_api/lib/src/main/java/jp/hiroshiba/voicevoxcore/exceptions/UseUserDictException.java b/crates/voicevox_core_java_api/lib/src/main/java/jp/hiroshiba/voicevoxcore/exceptions/UseUserDictException.java new file mode 100644 index 000000000..d311f4a2b --- /dev/null +++ b/crates/voicevox_core_java_api/lib/src/main/java/jp/hiroshiba/voicevoxcore/exceptions/UseUserDictException.java @@ -0,0 +1,12 @@ +package jp.hiroshiba.voicevoxcore.exceptions; + +/** OpenJTalkのユーザー辞書の設定に失敗した。 */ +public class UseUserDictException extends RuntimeException { + public UseUserDictException(String message) { + super(message); + } + + public UseUserDictException(String message, Throwable cause) { + super(message, cause); + } +} diff --git a/crates/voicevox_core_java_api/lib/src/main/java/jp/hiroshiba/voicevoxcore/exceptions/WordNotFoundException.java b/crates/voicevox_core_java_api/lib/src/main/java/jp/hiroshiba/voicevoxcore/exceptions/WordNotFoundException.java new file mode 100644 index 000000000..5db9de2d4 --- /dev/null +++ b/crates/voicevox_core_java_api/lib/src/main/java/jp/hiroshiba/voicevoxcore/exceptions/WordNotFoundException.java @@ -0,0 +1,8 @@ +package jp.hiroshiba.voicevoxcore.exceptions; + +/** ユーザー辞書に単語が見つからなかった。 */ +public class WordNotFoundException extends IndexOutOfBoundsException { + public WordNotFoundException(String message) { + super(message); + } +} diff --git a/crates/voicevox_core_java_api/src/common.rs b/crates/voicevox_core_java_api/src/common.rs index d7aeb720b..690a9e49a 100644 --- a/crates/voicevox_core_java_api/src/common.rs +++ b/crates/voicevox_core_java_api/src/common.rs @@ -1,5 +1,7 @@ -use anyhow::Result; -use jni::JNIEnv; +use std::{error::Error as _, iter, str::Utf8Error}; + +use derive_more::From; +use jni::{objects::JThrowable, JNIEnv}; use once_cell::sync::Lazy; use tokio::runtime::Runtime; @@ -86,7 +88,7 @@ macro_rules! enum_object { pub fn throw_if_err(mut env: JNIEnv, fallback: T, inner: F) -> T where - F: FnOnce(&mut JNIEnv) -> Result, + F: FnOnce(&mut JNIEnv) -> Result, { match inner(&mut env) { Ok(value) => value as _, @@ -95,13 +97,150 @@ where // env.exception_clear()してもいいが、errorのメッセージは"Java exception was thrown" // となり、デバッグが困難になるため、そのままにしておく。 if !env.exception_check().unwrap_or(false) { - env.throw_new( - "jp/hiroshiba/voicevoxcore/VoicevoxException", - error.to_string(), - ) - .unwrap_or_else(|_| panic!("Failed to throw exception, original error: {}", error)); + macro_rules! throw_new { + ($class:expr, $msg:expr $(,)?) => { + env.throw_new($class, $msg).unwrap_or_else(|_| { + panic!("Failed to throw exception, original error: {error:?}") + }) + }; + } + + macro_rules! throw { + ($e:expr $(,)?) => { + env.throw($e).unwrap_or_else(|_| { + panic!("Failed to throw exception, original error: {error:?}") + }) + }; + } + + match &error { + JavaApiError::RustApi(error) => { + // FIXME: `IndexOutOfBoundsException`を継承する`…NotFound`系に対して`cause` + // を設定しようとすると死んでしまう + + macro_rules! class { + ($($variant:ident),* $(,)?) => { + match error.kind() { + $( + voicevox_core::ErrorKind::$variant => concat!( + "jp/hiroshiba/voicevoxcore/exceptions/", + stringify!($variant), + "Exception", + ), + )* + } + }; + } + + let class = class!( + NotLoadedOpenjtalkDict, + GpuSupport, + OpenZipFile, + ReadZipEntry, + ModelAlreadyLoaded, + StyleAlreadyLoaded, + InvalidModelData, + GetSupportedDevices, + StyleNotFound, + ModelNotFound, + InferenceFailed, + ExtractFullContextLabel, + ParseKana, + LoadUserDict, + SaveUserDict, + WordNotFound, + UseUserDict, + InvalidWord, + ); + + let mut sources = + iter::successors(error.source(), |&source| source.source()) + .collect::>() + .into_iter() + .rev(); + + // FIXME: `.unwrap()`ではなく、ちゃんと`.expect()`とかを書く + + let exc = JThrowable::from(if let Some(innermost) = sources.next() { + let innermost = env + .new_object( + "java/lang/RuntimeException", + "(Ljava/lang/String;)V", + &[(&env.new_string(innermost.to_string()).unwrap()).into()], + ) + .unwrap(); + + let cause = sources.fold(innermost, |cause, source| { + env.new_object( + "java/lang/RuntimeException", + "(Ljava/lang/String;Ljava/lang/Throwable;)V", + &[ + (&env.new_string(source.to_string()).unwrap()).into(), + (&cause).into(), + ], + ) + .unwrap() + }); + + env.new_object( + class, + "(Ljava/lang/String;Ljava/lang/Throwable;)V", + &[ + (&env.new_string(error.to_string()).unwrap()).into(), + (&cause).into(), + ], + ) + .unwrap() + } else { + env.new_object( + class, + "(Ljava/lang/String;)V", + &[(&env.new_string(error.to_string()).unwrap()).into()], + ) + .unwrap() + }); + + throw!(exc); + } + JavaApiError::Jni(error) => { + throw_new!("java/lang/RuntimeException", error.to_string()) + } + JavaApiError::Utf8(error) => { + throw_new!("java/lang/IllegalArgumentException", error.to_string()) + } + JavaApiError::Uuid(error) => { + throw_new!("java/lang/IllegalArgumentException", error.to_string()) + } + JavaApiError::Json(error) => { + throw_new!("java/lang/RuntimeException", error.to_string()) + } + JavaApiError::IllegalAccelerationMode => throw_new!( + "java/lang/IllegalArgumentException", + "Invalid acceleration mode".to_owned(), + ), + }; } fallback } } } + +#[derive(From, Debug)] +pub enum JavaApiError { + #[from] + RustApi(voicevox_core::Error), + + #[from] + Jni(jni::errors::Error), + + #[from] + Utf8(Utf8Error), + + #[from] + Uuid(uuid::Error), + + #[from] + Json(serde_json::Error), + + IllegalAccelerationMode, +} diff --git a/crates/voicevox_core_java_api/src/synthesizer.rs b/crates/voicevox_core_java_api/src/synthesizer.rs index 9f245f8f7..94a5ed16f 100644 --- a/crates/voicevox_core_java_api/src/synthesizer.rs +++ b/crates/voicevox_core_java_api/src/synthesizer.rs @@ -1,9 +1,8 @@ use crate::{ - common::{throw_if_err, RUNTIME}, + common::{throw_if_err, JavaApiError, RUNTIME}, enum_object, object, object_type, }; -use anyhow::anyhow; use jni::{ objects::{JObject, JString}, sys::{jboolean, jint, jobject}, @@ -40,7 +39,7 @@ unsafe extern "system" fn Java_jp_hiroshiba_voicevoxcore_Synthesizer_rsNewWithIn } else if env.is_same_object(&acceleration_mode, gpu)? { voicevox_core::AccelerationMode::Gpu } else { - return Err(anyhow!("invalid acceleration mode".to_string(),)); + return Err(JavaApiError::IllegalAccelerationMode); }; } let cpu_num_threads = env.get_field(&builder, "cpuNumThreads", "I")?; diff --git a/crates/voicevox_core_java_api/src/voice_model.rs b/crates/voicevox_core_java_api/src/voice_model.rs index 33ae01288..d94904877 100644 --- a/crates/voicevox_core_java_api/src/voice_model.rs +++ b/crates/voicevox_core_java_api/src/voice_model.rs @@ -54,7 +54,7 @@ unsafe extern "system" fn Java_jp_hiroshiba_voicevoxcore_VoiceModel_rsGetMetasJs .clone(); let metas = internal.metas(); - let metas_json = serde_json::to_string(&metas)?; + let metas_json = serde_json::to_string(&metas).expect("should not fail"); Ok(env.new_string(metas_json)?.into_raw()) }) } From a9905ccf16b1642bc8468f27d317204a539ff5bf Mon Sep 17 00:00:00 2001 From: Ryo Yamashita Date: Fri, 20 Oct 2023 03:07:25 +0900 Subject: [PATCH 3/9] =?UTF-8?q?`=E2=80=A6NotFoundError`=E3=82=92`KeyError`?= =?UTF-8?q?=E3=81=AB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../python/voicevox_core/_rust.pyi | 6 +++--- crates/voicevox_core_python_api/src/lib.rs | 8 ++++---- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/crates/voicevox_core_python_api/python/voicevox_core/_rust.pyi b/crates/voicevox_core_python_api/python/voicevox_core/_rust.pyi index 734bfb321..bd110f892 100644 --- a/crates/voicevox_core_python_api/python/voicevox_core/_rust.pyi +++ b/crates/voicevox_core_python_api/python/voicevox_core/_rust.pyi @@ -466,12 +466,12 @@ class GetSupportedDevicesError(Exception): ... -class StyleNotFoundError(Exception): +class StyleNotFoundError(KeyError): """スタイルIDに対するスタイルが見つからなかった。""" ... -class ModelNotFoundError(Exception): +class ModelNotFoundError(KeyError): """音声モデルIDに対する音声モデルが見つからなかった。""" ... @@ -501,7 +501,7 @@ class SaveUserDictError(Exception): ... -class WordNotFoundError(Exception): +class WordNotFoundError(KeyError): """ユーザー辞書に単語が見つからなかった。""" ... diff --git a/crates/voicevox_core_python_api/src/lib.rs b/crates/voicevox_core_python_api/src/lib.rs index b0a886d2c..1a492878e 100644 --- a/crates/voicevox_core_python_api/src/lib.rs +++ b/crates/voicevox_core_python_api/src/lib.rs @@ -6,7 +6,7 @@ use log::debug; use once_cell::sync::Lazy; use pyo3::{ create_exception, - exceptions::{PyException, PyValueError}, + exceptions::{PyException, PyKeyError, PyValueError}, pyclass, pyfunction, pymethods, pymodule, types::{IntoPyDict as _, PyBytes, PyDict, PyList, PyModule}, wrap_pyfunction, PyAny, PyObject, PyRef, PyResult, PyTypeInfo, Python, ToPyObject, @@ -62,14 +62,14 @@ exceptions! { StyleAlreadyLoadedError: PyException; InvalidModelDataError: PyException; GetSupportedDevicesError: PyException; - StyleNotFoundError: PyException; - ModelNotFoundError: PyException; + StyleNotFoundError: PyKeyError; + ModelNotFoundError: PyKeyError; InferenceFailedError: PyException; ExtractFullContextLabelError: PyException; ParseKanaError: PyValueError; LoadUserDictError: PyException; SaveUserDictError: PyException; - WordNotFoundError: PyException; + WordNotFoundError: PyKeyError; UseUserDictError: PyException; InvalidWordError: PyValueError; } From 69e2647941a9856f156b96ad777ae2d049e8f3ea Mon Sep 17 00:00:00 2001 From: Ryo Yamashita Date: Fri, 20 Oct 2023 03:17:50 +0900 Subject: [PATCH 4/9] assert --- crates/voicevox_core_java_api/src/common.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/crates/voicevox_core_java_api/src/common.rs b/crates/voicevox_core_java_api/src/common.rs index 690a9e49a..cd59b0f5f 100644 --- a/crates/voicevox_core_java_api/src/common.rs +++ b/crates/voicevox_core_java_api/src/common.rs @@ -115,9 +115,6 @@ where match &error { JavaApiError::RustApi(error) => { - // FIXME: `IndexOutOfBoundsException`を継承する`…NotFound`系に対して`cause` - // を設定しようとすると死んでしまう - macro_rules! class { ($($variant:ident),* $(,)?) => { match error.kind() { @@ -153,6 +150,9 @@ where InvalidWord, ); + // FIXME + assert!(!class.ends_with("NotFoundException") || error.source().is_none()); + let mut sources = iter::successors(error.source(), |&source| source.source()) .collect::>() From 2e28029275b75aecb8f79efc54dcc8e4613f4e77 Mon Sep 17 00:00:00 2001 From: Ryo Yamashita Date: Fri, 20 Oct 2023 03:23:59 +0900 Subject: [PATCH 5/9] Minor refactor --- crates/voicevox_core_java_api/src/common.rs | 32 +++++++++------------ 1 file changed, 14 insertions(+), 18 deletions(-) diff --git a/crates/voicevox_core_java_api/src/common.rs b/crates/voicevox_core_java_api/src/common.rs index cd59b0f5f..001f39c0c 100644 --- a/crates/voicevox_core_java_api/src/common.rs +++ b/crates/voicevox_core_java_api/src/common.rs @@ -97,17 +97,9 @@ where // env.exception_clear()してもいいが、errorのメッセージは"Java exception was thrown" // となり、デバッグが困難になるため、そのままにしておく。 if !env.exception_check().unwrap_or(false) { - macro_rules! throw_new { - ($class:expr, $msg:expr $(,)?) => { - env.throw_new($class, $msg).unwrap_or_else(|_| { - panic!("Failed to throw exception, original error: {error:?}") - }) - }; - } - - macro_rules! throw { - ($e:expr $(,)?) => { - env.throw($e).unwrap_or_else(|_| { + macro_rules! or_panic { + ($result:expr) => { + $result.unwrap_or_else(|_| { panic!("Failed to throw exception, original error: {error:?}") }) }; @@ -200,24 +192,28 @@ where .unwrap() }); - throw!(exc); + or_panic!(env.throw(exc)); } JavaApiError::Jni(error) => { - throw_new!("java/lang/RuntimeException", error.to_string()) + or_panic!(env.throw_new("java/lang/RuntimeException", error.to_string())) } JavaApiError::Utf8(error) => { - throw_new!("java/lang/IllegalArgumentException", error.to_string()) + or_panic!( + env.throw_new("java/lang/IllegalArgumentException", error.to_string()) + ) } JavaApiError::Uuid(error) => { - throw_new!("java/lang/IllegalArgumentException", error.to_string()) + or_panic!( + env.throw_new("java/lang/IllegalArgumentException", error.to_string()) + ) } JavaApiError::Json(error) => { - throw_new!("java/lang/RuntimeException", error.to_string()) + or_panic!(env.throw_new("java/lang/RuntimeException", error.to_string())) } - JavaApiError::IllegalAccelerationMode => throw_new!( + JavaApiError::IllegalAccelerationMode => or_panic!(env.throw_new( "java/lang/IllegalArgumentException", "Invalid acceleration mode".to_owned(), - ), + )), }; } fallback From 38f277df7d433c91e3d09665dadf2c08f16dcdd2 Mon Sep 17 00:00:00 2001 From: Ryo Yamashita Date: Fri, 20 Oct 2023 03:28:45 +0900 Subject: [PATCH 6/9] =?UTF-8?q?=E4=B8=8D=E8=A6=81=E3=81=AA=E4=BE=9D?= =?UTF-8?q?=E5=AD=98=E3=82=92=E5=89=8A=E9=99=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Cargo.lock | 1 - crates/voicevox_core_java_api/Cargo.toml | 3 +-- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index a35ad2bf5..ee536102a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4361,7 +4361,6 @@ dependencies = [ "once_cell", "serde_json", "test_util", - "thiserror", "tokio", "tracing", "tracing-subscriber", diff --git a/crates/voicevox_core_java_api/Cargo.toml b/crates/voicevox_core_java_api/Cargo.toml index 8beafb5ef..ecf6d8f79 100644 --- a/crates/voicevox_core_java_api/Cargo.toml +++ b/crates/voicevox_core_java_api/Cargo.toml @@ -18,9 +18,8 @@ jni = "0.21.1" once_cell.workspace = true serde_json.workspace = true tokio.workspace = true -thiserror.workspace = true -tracing-subscriber.workspace = true tracing.workspace = true +tracing-subscriber.workspace = true uuid.workspace = true voicevox_core.workspace = true From 0098be9e673aa37db065df5e595ff21de82ef267 Mon Sep 17 00:00:00 2001 From: Ryo Yamashita Date: Sat, 21 Oct 2023 02:47:06 +0900 Subject: [PATCH 7/9] =?UTF-8?q?assert=E3=81=AF=E3=81=9B=E3=81=9A=E3=81=AB`?= =?UTF-8?q?UnsupportedOperationException`=E3=82=92=E7=99=BA=E3=81=99?= =?UTF-8?q?=E3=82=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../voicevoxcore/exceptions/ModelNotFoundException.java | 5 +++++ .../voicevoxcore/exceptions/StyleNotFoundException.java | 5 +++++ .../voicevoxcore/exceptions/WordNotFoundException.java | 5 +++++ crates/voicevox_core_java_api/src/common.rs | 3 --- 4 files changed, 15 insertions(+), 3 deletions(-) diff --git a/crates/voicevox_core_java_api/lib/src/main/java/jp/hiroshiba/voicevoxcore/exceptions/ModelNotFoundException.java b/crates/voicevox_core_java_api/lib/src/main/java/jp/hiroshiba/voicevoxcore/exceptions/ModelNotFoundException.java index 953a9345f..a2f750122 100644 --- a/crates/voicevox_core_java_api/lib/src/main/java/jp/hiroshiba/voicevoxcore/exceptions/ModelNotFoundException.java +++ b/crates/voicevox_core_java_api/lib/src/main/java/jp/hiroshiba/voicevoxcore/exceptions/ModelNotFoundException.java @@ -5,4 +5,9 @@ public class ModelNotFoundException extends IndexOutOfBoundsException { public ModelNotFoundException(String message) { super(message); } + + public ModelNotFoundException(String message, Throwable cause) + throws UnsupportedOperationException { + throw new UnsupportedOperationException(); + } } diff --git a/crates/voicevox_core_java_api/lib/src/main/java/jp/hiroshiba/voicevoxcore/exceptions/StyleNotFoundException.java b/crates/voicevox_core_java_api/lib/src/main/java/jp/hiroshiba/voicevoxcore/exceptions/StyleNotFoundException.java index 826d88cda..f8467a019 100644 --- a/crates/voicevox_core_java_api/lib/src/main/java/jp/hiroshiba/voicevoxcore/exceptions/StyleNotFoundException.java +++ b/crates/voicevox_core_java_api/lib/src/main/java/jp/hiroshiba/voicevoxcore/exceptions/StyleNotFoundException.java @@ -5,4 +5,9 @@ public class StyleNotFoundException extends IndexOutOfBoundsException { public StyleNotFoundException(String message) { super(message); } + + public StyleNotFoundException(String message, Throwable cause) + throws UnsupportedOperationException { + throw new UnsupportedOperationException(); + } } diff --git a/crates/voicevox_core_java_api/lib/src/main/java/jp/hiroshiba/voicevoxcore/exceptions/WordNotFoundException.java b/crates/voicevox_core_java_api/lib/src/main/java/jp/hiroshiba/voicevoxcore/exceptions/WordNotFoundException.java index 5db9de2d4..ef52f642f 100644 --- a/crates/voicevox_core_java_api/lib/src/main/java/jp/hiroshiba/voicevoxcore/exceptions/WordNotFoundException.java +++ b/crates/voicevox_core_java_api/lib/src/main/java/jp/hiroshiba/voicevoxcore/exceptions/WordNotFoundException.java @@ -5,4 +5,9 @@ public class WordNotFoundException extends IndexOutOfBoundsException { public WordNotFoundException(String message) { super(message); } + + public WordNotFoundException(String message, Throwable cause) + throws UnsupportedOperationException { + throw new UnsupportedOperationException(); + } } diff --git a/crates/voicevox_core_java_api/src/common.rs b/crates/voicevox_core_java_api/src/common.rs index 001f39c0c..2da808aae 100644 --- a/crates/voicevox_core_java_api/src/common.rs +++ b/crates/voicevox_core_java_api/src/common.rs @@ -142,9 +142,6 @@ where InvalidWord, ); - // FIXME - assert!(!class.ends_with("NotFoundException") || error.source().is_none()); - let mut sources = iter::successors(error.source(), |&source| source.source()) .collect::>() From 48d47de03684ffec7268d96da1d70090fe091c45 Mon Sep 17 00:00:00 2001 From: Ryo Yamashita Date: Sun, 22 Oct 2023 05:47:09 +0900 Subject: [PATCH 8/9] =?UTF-8?q?=E4=B8=8D=E8=A6=81=E3=81=AA=E3=82=A8?= =?UTF-8?q?=E3=83=A9=E3=83=BC=E3=82=92=E6=8E=92=E9=99=A4/=E3=83=91?= =?UTF-8?q?=E3=83=8B=E3=83=83=E3=82=AF=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- crates/voicevox_core_java_api/src/common.rs | 21 +++---------- .../voicevox_core_java_api/src/open_jtalk.rs | 7 +++-- .../voicevox_core_java_api/src/synthesizer.rs | 28 ++++++++++------- .../voicevox_core_java_api/src/user_dict.rs | 31 +++++++++++-------- .../voicevox_core_java_api/src/voice_model.rs | 4 +-- 5 files changed, 45 insertions(+), 46 deletions(-) diff --git a/crates/voicevox_core_java_api/src/common.rs b/crates/voicevox_core_java_api/src/common.rs index 2da808aae..2c6847f73 100644 --- a/crates/voicevox_core_java_api/src/common.rs +++ b/crates/voicevox_core_java_api/src/common.rs @@ -1,4 +1,4 @@ -use std::{error::Error as _, iter, str::Utf8Error}; +use std::{error::Error as _, iter}; use derive_more::From; use jni::{objects::JThrowable, JNIEnv}; @@ -194,23 +194,16 @@ where JavaApiError::Jni(error) => { or_panic!(env.throw_new("java/lang/RuntimeException", error.to_string())) } - JavaApiError::Utf8(error) => { + JavaApiError::Uuid(error) => { or_panic!( env.throw_new("java/lang/IllegalArgumentException", error.to_string()) ) } - JavaApiError::Uuid(error) => { + JavaApiError::DeJson(error) => { or_panic!( env.throw_new("java/lang/IllegalArgumentException", error.to_string()) ) } - JavaApiError::Json(error) => { - or_panic!(env.throw_new("java/lang/RuntimeException", error.to_string())) - } - JavaApiError::IllegalAccelerationMode => or_panic!(env.throw_new( - "java/lang/IllegalArgumentException", - "Invalid acceleration mode".to_owned(), - )), }; } fallback @@ -226,14 +219,8 @@ pub enum JavaApiError { #[from] Jni(jni::errors::Error), - #[from] - Utf8(Utf8Error), - #[from] Uuid(uuid::Error), - #[from] - Json(serde_json::Error), - - IllegalAccelerationMode, + DeJson(serde_json::Error), } diff --git a/crates/voicevox_core_java_api/src/open_jtalk.rs b/crates/voicevox_core_java_api/src/open_jtalk.rs index 98feaacb0..f26128d35 100644 --- a/crates/voicevox_core_java_api/src/open_jtalk.rs +++ b/crates/voicevox_core_java_api/src/open_jtalk.rs @@ -1,4 +1,7 @@ -use std::sync::{Arc, Mutex}; +use std::{ + borrow::Cow, + sync::{Arc, Mutex}, +}; use crate::common::throw_if_err; use jni::{ @@ -26,7 +29,7 @@ unsafe extern "system" fn Java_jp_hiroshiba_voicevoxcore_OpenJtalk_rsNewWithInit ) { throw_if_err(env, (), |env| { let open_jtalk_dict_dir = env.get_string(&open_jtalk_dict_dir)?; - let open_jtalk_dict_dir = open_jtalk_dict_dir.to_str()?; + let open_jtalk_dict_dir = &*Cow::from(&open_jtalk_dict_dir); let internal = voicevox_core::OpenJtalk::new_with_initialize(open_jtalk_dict_dir)?; env.set_rust_field(&this, "handle", Arc::new(internal))?; diff --git a/crates/voicevox_core_java_api/src/synthesizer.rs b/crates/voicevox_core_java_api/src/synthesizer.rs index 94a5ed16f..3ddd1b47f 100644 --- a/crates/voicevox_core_java_api/src/synthesizer.rs +++ b/crates/voicevox_core_java_api/src/synthesizer.rs @@ -39,7 +39,7 @@ unsafe extern "system" fn Java_jp_hiroshiba_voicevoxcore_Synthesizer_rsNewWithIn } else if env.is_same_object(&acceleration_mode, gpu)? { voicevox_core::AccelerationMode::Gpu } else { - return Err(JavaApiError::IllegalAccelerationMode); + panic!("予期しない`AccelerationMode`です: {acceleration_mode:?}"); }; } let cpu_num_threads = env.get_field(&builder, "cpuNumThreads", "I")?; @@ -150,7 +150,7 @@ unsafe extern "system" fn Java_jp_hiroshiba_voicevoxcore_Synthesizer_rsAudioQuer )? }; - let query_json = serde_json::to_string(&audio_query)?; + let query_json = serde_json::to_string(&audio_query).expect("should not fail"); let j_audio_query = env.new_string(query_json)?; @@ -178,7 +178,7 @@ unsafe extern "system" fn Java_jp_hiroshiba_voicevoxcore_Synthesizer_rsAudioQuer RUNTIME.block_on(internal.audio_query(&text, voicevox_core::StyleId::new(style_id)))? }; - let query_json = serde_json::to_string(&audio_query)?; + let query_json = serde_json::to_string(&audio_query).expect("should not fail"); let j_audio_query = env.new_string(query_json)?; @@ -211,7 +211,7 @@ unsafe extern "system" fn Java_jp_hiroshiba_voicevoxcore_Synthesizer_rsAccentPhr )? }; - let query_json = serde_json::to_string(&accent_phrases)?; + let query_json = serde_json::to_string(&accent_phrases).expect("should not fail"); let j_accent_phrases = env.new_string(query_json)?; @@ -241,7 +241,7 @@ unsafe extern "system" fn Java_jp_hiroshiba_voicevoxcore_Synthesizer_rsAccentPhr )? }; - let query_json = serde_json::to_string(&accent_phrases)?; + let query_json = serde_json::to_string(&accent_phrases).expect("should not fail"); let j_accent_phrases = env.new_string(query_json)?; @@ -259,7 +259,7 @@ unsafe extern "system" fn Java_jp_hiroshiba_voicevoxcore_Synthesizer_rsReplaceMo 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 = - serde_json::from_str(&accent_phrases_json)?; + serde_json::from_str(&accent_phrases_json).map_err(JavaApiError::DeJson)?; let style_id = style_id as u32; let internal = env @@ -273,7 +273,8 @@ unsafe extern "system" fn Java_jp_hiroshiba_voicevoxcore_Synthesizer_rsReplaceMo )? }; - let replaced_accent_phrases_json = serde_json::to_string(&replaced_accent_phrases)?; + let replaced_accent_phrases_json = + serde_json::to_string(&replaced_accent_phrases).expect("should not fail"); Ok(env.new_string(replaced_accent_phrases_json)?.into_raw()) }) @@ -291,7 +292,7 @@ unsafe extern "system" fn Java_jp_hiroshiba_voicevoxcore_Synthesizer_rsReplacePh 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 = - serde_json::from_str(&accent_phrases_json)?; + serde_json::from_str(&accent_phrases_json).map_err(JavaApiError::DeJson)?; let style_id = style_id as u32; let internal = env @@ -306,7 +307,8 @@ unsafe extern "system" fn Java_jp_hiroshiba_voicevoxcore_Synthesizer_rsReplacePh )? }; - let replaced_accent_phrases_json = serde_json::to_string(&replaced_accent_phrases)?; + let replaced_accent_phrases_json = + serde_json::to_string(&replaced_accent_phrases).expect("should not fail"); Ok(env.new_string(replaced_accent_phrases_json)?.into_raw()) }) @@ -322,7 +324,7 @@ unsafe extern "system" fn Java_jp_hiroshiba_voicevoxcore_Synthesizer_rsReplaceMo 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 = - serde_json::from_str(&accent_phrases_json)?; + serde_json::from_str(&accent_phrases_json).map_err(JavaApiError::DeJson)?; let style_id = style_id as u32; let internal = env @@ -336,7 +338,8 @@ unsafe extern "system" fn Java_jp_hiroshiba_voicevoxcore_Synthesizer_rsReplaceMo )? }; - let replaced_accent_phrases_json = serde_json::to_string(&replaced_accent_phrases)?; + let replaced_accent_phrases_json = + serde_json::to_string(&replaced_accent_phrases).expect("should not fail"); Ok(env.new_string(replaced_accent_phrases_json)?.into_raw()) }) @@ -352,7 +355,8 @@ 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 = serde_json::from_str(&audio_query)?; + let audio_query: voicevox_core::AudioQueryModel = + serde_json::from_str(&audio_query).map_err(JavaApiError::DeJson)?; let style_id = style_id as u32; let internal = env diff --git a/crates/voicevox_core_java_api/src/user_dict.rs b/crates/voicevox_core_java_api/src/user_dict.rs index 4f7bc78d8..e85085a34 100644 --- a/crates/voicevox_core_java_api/src/user_dict.rs +++ b/crates/voicevox_core_java_api/src/user_dict.rs @@ -1,7 +1,10 @@ use jni::objects::JClass; -use std::sync::{Arc, Mutex}; +use std::{ + borrow::Cow, + sync::{Arc, Mutex}, +}; -use crate::common::throw_if_err; +use crate::common::{throw_if_err, JavaApiError}; use jni::{ objects::{JObject, JString}, sys::jobject, @@ -34,9 +37,10 @@ unsafe extern "system" fn Java_jp_hiroshiba_voicevoxcore_UserDict_rsAddWord<'loc .clone(); let word_json = env.get_string(&word_json)?; - let word_json = word_json.to_str()?; + let word_json = &Cow::from(&word_json); - let word: voicevox_core::UserDictWord = serde_json::from_str(word_json)?; + let word: voicevox_core::UserDictWord = + serde_json::from_str(word_json).map_err(JavaApiError::DeJson)?; let uuid = { let mut internal = internal.lock().unwrap(); @@ -63,11 +67,12 @@ unsafe extern "system" fn Java_jp_hiroshiba_voicevoxcore_UserDict_rsUpdateWord<' .clone(); let uuid = env.get_string(&uuid)?; - let uuid = uuid.to_str()?.parse()?; + let uuid = Cow::from(&uuid).parse()?; let word_json = env.get_string(&word_json)?; - let word_json = word_json.to_str()?; + let word_json = &Cow::from(&word_json); - let word: voicevox_core::UserDictWord = serde_json::from_str(word_json)?; + let word: voicevox_core::UserDictWord = + serde_json::from_str(word_json).map_err(JavaApiError::DeJson)?; { let mut internal = internal.lock().unwrap(); @@ -90,7 +95,7 @@ unsafe extern "system" fn Java_jp_hiroshiba_voicevoxcore_UserDict_rsRemoveWord<' .clone(); let uuid = env.get_string(&uuid)?; - let uuid = uuid.to_str()?.parse()?; + let uuid = Cow::from(&uuid).parse()?; { let mut internal = internal.lock().unwrap(); @@ -137,7 +142,7 @@ unsafe extern "system" fn Java_jp_hiroshiba_voicevoxcore_UserDict_rsLoad<'local> .clone(); let path = env.get_string(&path)?; - let path = path.to_str()?; + let path = &Cow::from(&path); { let mut internal = internal.lock().unwrap(); @@ -160,7 +165,7 @@ unsafe extern "system" fn Java_jp_hiroshiba_voicevoxcore_UserDict_rsSave<'local> .clone(); let path = env.get_string(&path)?; - let path = path.to_str()?; + let path = &Cow::from(&path); { let internal = internal.lock().unwrap(); @@ -183,7 +188,7 @@ unsafe extern "system" fn Java_jp_hiroshiba_voicevoxcore_UserDict_rsGetWords<'lo let words = { let internal = internal.lock().unwrap(); - serde_json::to_string(internal.words())? + serde_json::to_string(internal.words()).expect("should not fail") }; let words = env.new_string(words)?; @@ -211,7 +216,7 @@ extern "system" fn Java_jp_hiroshiba_voicevoxcore_UserDict_rsToZenkaku<'local>( ) -> jobject { throw_if_err(env, std::ptr::null_mut(), |env| { let text = env.get_string(&text)?; - let text = text.to_str()?; + let text = &Cow::from(&text); let text = voicevox_core::__internal::to_zenkaku(text); @@ -228,7 +233,7 @@ extern "system" fn Java_jp_hiroshiba_voicevoxcore_UserDict_rsValidatePronunciati ) { throw_if_err(env, (), |env| { let text = env.get_string(&text)?; - let text = text.to_str()?; + let text = &Cow::from(&text); voicevox_core::__internal::validate_pronunciation(text)?; diff --git a/crates/voicevox_core_java_api/src/voice_model.rs b/crates/voicevox_core_java_api/src/voice_model.rs index d94904877..cd971a1f7 100644 --- a/crates/voicevox_core_java_api/src/voice_model.rs +++ b/crates/voicevox_core_java_api/src/voice_model.rs @@ -1,4 +1,4 @@ -use std::sync::Arc; +use std::{borrow::Cow, sync::Arc}; use crate::common::{throw_if_err, RUNTIME}; use jni::{ @@ -15,7 +15,7 @@ unsafe extern "system" fn Java_jp_hiroshiba_voicevoxcore_VoiceModel_rsFromPath<' ) { throw_if_err(env, (), |env| { let model_path = env.get_string(&model_path)?; - let model_path = model_path.to_str()?; + let model_path = &*Cow::from(&model_path); let internal = RUNTIME.block_on(voicevox_core::VoiceModel::from_path(model_path))?; From 9e87842ec3fdfbc428c56ee6ddc9d30af4c3e125 Mon Sep 17 00:00:00 2001 From: Ryo Yamashita Date: Sun, 22 Oct 2023 11:47:12 +0900 Subject: [PATCH 9/9] =?UTF-8?q?`IOException`=E3=82=92=E7=B6=99=E6=89=BF?= =?UTF-8?q?=E3=81=99=E3=82=8B=E3=82=82=E3=81=AE=E3=81=AF`throws`=E3=81=AB?= =?UTF-8?q?=E3=81=99=E3=82=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../hiroshiba/voicevoxcore/Synthesizer.java | 60 ++++++++++++------- .../jp/hiroshiba/voicevoxcore/UserDict.java | 10 ++-- .../voicevoxcore/SynthesizerTest.java | 10 ++-- .../hiroshiba/voicevoxcore/UserDictTest.java | 6 +- 4 files changed, 55 insertions(+), 31 deletions(-) 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 5f3df9ea8..7204dbdd6 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,6 +5,8 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.List; +import jp.hiroshiba.voicevoxcore.exceptions.InferenceFailedException; +import jp.hiroshiba.voicevoxcore.exceptions.InvalidModelDataException; /** * 音声シンセサイザ。 @@ -28,7 +30,7 @@ protected void finalize() throws Throwable { * * @param voiceModel 読み込むモデル。 */ - public void loadVoiceModel(VoiceModel voiceModel) { + public void loadVoiceModel(VoiceModel voiceModel) throws InvalidModelDataException { rsLoadVoiceModel(voiceModel); } @@ -59,7 +61,8 @@ public boolean isLoadedVoiceModel(String voiceModelId) { * @return {@link AudioQuery}。 */ @Nonnull - public AudioQuery createAudioQueryFromKana(String kana, int styleId) { + public AudioQuery createAudioQueryFromKana(String kana, int styleId) + throws InferenceFailedException { if (!Utils.isU32(styleId)) { throw new IllegalArgumentException("styleId"); } @@ -81,7 +84,7 @@ public AudioQuery createAudioQueryFromKana(String kana, int styleId) { * @return {@link AudioQuery}。 */ @Nonnull - public AudioQuery createAudioQuery(String text, int styleId) { + public AudioQuery createAudioQuery(String text, int styleId) throws InferenceFailedException { if (!Utils.isU32(styleId)) { throw new IllegalArgumentException("styleId"); } @@ -103,7 +106,8 @@ public AudioQuery createAudioQuery(String text, int styleId) { * @return {@link AccentPhrase} のリスト。 */ @Nonnull - public List createAccentPhrasesFromKana(String kana, int styleId) { + public List createAccentPhrasesFromKana(String kana, int styleId) + throws InferenceFailedException { String accentPhrasesJson = rsAccentPhrasesFromKana(kana, styleId); Gson gson = new Gson(); AccentPhrase[] rawAccentPhrases = gson.fromJson(accentPhrasesJson, AccentPhrase[].class); @@ -121,7 +125,8 @@ public List createAccentPhrasesFromKana(String kana, int styleId) * @return {@link AccentPhrase} のリスト。 */ @Nonnull - public List createAccentPhrases(String text, int styleId) { + public List createAccentPhrases(String text, int styleId) + throws InferenceFailedException { String accentPhrasesJson = rsAccentPhrases(text, styleId); Gson gson = new Gson(); AccentPhrase[] rawAccentPhrases = gson.fromJson(accentPhrasesJson, AccentPhrase[].class); @@ -139,7 +144,8 @@ public List createAccentPhrases(String text, int styleId) { * @return 変更後のアクセント句の配列。 */ @Nonnull - public List replaceMoraData(List accentPhrases, int styleId) { + public List replaceMoraData(List accentPhrases, int styleId) + throws InferenceFailedException { if (!Utils.isU32(styleId)) { throw new IllegalArgumentException("styleId"); } @@ -157,7 +163,8 @@ public List replaceMoraData(List accentPhrases, int * @return 変更後のアクセント句の配列。 */ @Nonnull - public List replacePhonemeLength(List accentPhrases, int styleId) { + public List replacePhonemeLength(List accentPhrases, int styleId) + throws InferenceFailedException { if (!Utils.isU32(styleId)) { throw new IllegalArgumentException("styleId"); } @@ -175,7 +182,8 @@ public List replacePhonemeLength(List accentPhrases, * @return 変更後のアクセント句の配列。 */ @Nonnull - public List replaceMoraPitch(List accentPhrases, int styleId) { + public List replaceMoraPitch(List accentPhrases, int styleId) + throws InferenceFailedException { if (!Utils.isU32(styleId)) { throw new IllegalArgumentException("styleId"); } @@ -226,42 +234,50 @@ public TtsConfigurator tts(String text, int styleId) { private native void rsNewWithInitialize(OpenJtalk openJtalk, Builder builder); - private native void rsLoadVoiceModel(VoiceModel voiceModel); + private native void rsLoadVoiceModel(VoiceModel voiceModel) throws InvalidModelDataException; private native void rsUnloadVoiceModel(String voiceModelId); private native boolean rsIsLoadedVoiceModel(String voiceModelId); @Nonnull - private native String rsAudioQueryFromKana(String kana, int styleId); + private native String rsAudioQueryFromKana(String kana, int styleId) + throws InferenceFailedException; @Nonnull - private native String rsAudioQuery(String text, int styleId); + private native String rsAudioQuery(String text, int styleId) throws InferenceFailedException; @Nonnull - private native String rsAccentPhrasesFromKana(String kana, int styleId); + private native String rsAccentPhrasesFromKana(String kana, int styleId) + throws InferenceFailedException; @Nonnull - private native String rsAccentPhrases(String text, int styleId); + private native String rsAccentPhrases(String text, int styleId) throws InferenceFailedException; @Nonnull - private native String rsReplaceMoraData(String accentPhrasesJson, int styleId, boolean kana); + private native String rsReplaceMoraData(String accentPhrasesJson, int styleId, boolean kana) + throws InferenceFailedException; @Nonnull - private native String rsReplacePhonemeLength(String accentPhrasesJson, int styleId, boolean kana); + private native String rsReplacePhonemeLength(String accentPhrasesJson, int styleId, boolean kana) + throws InferenceFailedException; @Nonnull - private native String rsReplaceMoraPitch(String accentPhrasesJson, int styleId, boolean kana); + private native String rsReplaceMoraPitch(String accentPhrasesJson, int styleId, boolean kana) + throws InferenceFailedException; @Nonnull private native byte[] rsSynthesis( - String queryJson, int styleId, boolean enableInterrogativeUpspeak); + String queryJson, int styleId, boolean enableInterrogativeUpspeak) + throws InferenceFailedException; @Nonnull - private native byte[] rsTtsFromKana(String kana, int styleId, boolean enableInterrogativeUpspeak); + private native byte[] rsTtsFromKana(String kana, int styleId, boolean enableInterrogativeUpspeak) + throws InferenceFailedException; @Nonnull - private native byte[] rsTts(String text, int styleId, boolean enableInterrogativeUpspeak); + private native byte[] rsTts(String text, int styleId, boolean enableInterrogativeUpspeak) + throws InferenceFailedException; private native void rsDrop(); @@ -368,7 +384,7 @@ public SynthesisConfigurator interrogativeUpspeak(boolean interrogativeUpspeak) * @return 音声データ。 */ @Nonnull - public byte[] execute() { + public byte[] execute() throws InferenceFailedException { if (!Utils.isU32(styleId)) { throw new IllegalArgumentException("styleId"); } @@ -412,7 +428,7 @@ public TtsFromKanaConfigurator interrogativeUpspeak(boolean interrogativeUpspeak * @return 音声データ。 */ @Nonnull - public byte[] execute() { + public byte[] execute() throws InferenceFailedException { if (!Utils.isU32(styleId)) { throw new IllegalArgumentException("styleId"); } @@ -454,7 +470,7 @@ public TtsConfigurator interrogativeUpspeak(boolean interrogativeUpspeak) { * @return 音声データ。 */ @Nonnull - public byte[] execute() { + public byte[] execute() throws InferenceFailedException { 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 b0b8921a2..de1e612be 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 @@ -8,6 +8,8 @@ import jakarta.validation.constraints.Max; import jakarta.validation.constraints.Min; import java.util.HashMap; +import jp.hiroshiba.voicevoxcore.exceptions.LoadUserDictException; +import jp.hiroshiba.voicevoxcore.exceptions.SaveUserDictException; /** ユーザー辞書。 */ public class UserDict extends Dll { @@ -73,7 +75,7 @@ public void importDict(UserDict dict) { * * @param path ユーザー辞書のパス。 */ - public void load(String path) { + public void load(String path) throws LoadUserDictException { rsLoad(path); } @@ -82,7 +84,7 @@ public void load(String path) { * * @param path ユーザー辞書のパス。 */ - public void save(String path) { + public void save(String path) throws SaveUserDictException { rsSave(path); } @@ -124,9 +126,9 @@ public HashMap toHashMap() { private native void rsImportDict(UserDict dict); - private native void rsLoad(String path); + private native void rsLoad(String path) throws LoadUserDictException; - private native void rsSave(String path); + private native void rsSave(String path) throws SaveUserDictException; @Nonnull private native String rsGetWords(); 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 f5cdaea66..efa91eed8 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,6 +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 org.junit.jupiter.api.Test; class SynthesizerTest extends TestUtils { @@ -34,7 +36,7 @@ boolean checkAllMoras( } @Test - void checkModel() { + void checkModel() throws InvalidModelDataException { VoiceModel model = loadModel(); OpenJtalk openJtalk = loadOpenJtalk(); Synthesizer synthesizer = Synthesizer.builder(openJtalk).build(); @@ -45,7 +47,7 @@ void checkModel() { } @Test - void checkAudioQuery() { + void checkAudioQuery() throws InferenceFailedException, InvalidModelDataException { VoiceModel model = loadModel(); OpenJtalk openJtalk = loadOpenJtalk(); Synthesizer synthesizer = Synthesizer.builder(openJtalk).build(); @@ -56,7 +58,7 @@ void checkAudioQuery() { } @Test - void checkAccentPhrases() { + void checkAccentPhrases() throws InferenceFailedException, InvalidModelDataException { VoiceModel model = loadModel(); OpenJtalk openJtalk = loadOpenJtalk(); Synthesizer synthesizer = Synthesizer.builder(openJtalk).build(); @@ -86,7 +88,7 @@ void checkAccentPhrases() { } @Test - void checkTts() { + void checkTts() throws InferenceFailedException, InvalidModelDataException { VoiceModel model = loadModel(); OpenJtalk openJtalk = loadOpenJtalk(); Synthesizer synthesizer = Synthesizer.builder(openJtalk).build(); 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 9eb1077f5..ce9b7631a 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,6 +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 org.junit.jupiter.api.Test; class UserDictTest extends TestUtils { @@ -11,7 +14,8 @@ class UserDictTest extends TestUtils { // 辞書ロードのテスト。 // 辞書ロード前後でkanaが異なることを確認する @Test - void checkLoad() { + void checkLoad() + throws InferenceFailedException, InvalidModelDataException, LoadUserDictException { VoiceModel model = loadModel(); OpenJtalk openJtalk = loadOpenJtalk(); Synthesizer synthesizer = Synthesizer.builder(openJtalk).build();