From e5ac602aa7063f95d3493ee21cfd06152db3b507 Mon Sep 17 00:00:00 2001 From: Ryo Yamashita Date: Sun, 27 Aug 2023 15:06:28 +0900 Subject: [PATCH] =?UTF-8?q?Rust=20API=E3=81=8C=E5=85=AC=E9=96=8B=E3=81=99?= =?UTF-8?q?=E3=82=8B=E3=82=A8=E3=83=A9=E3=83=BC=E3=81=AE=E7=A8=AE=E9=A1=9E?= =?UTF-8?q?=E3=82=92`ErrorKind`=E3=81=A8=E3=81=97=E3=81=A6=E8=A1=A8?= =?UTF-8?q?=E7=8F=BE=E3=81=99=E3=82=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Cargo.lock | 13 ++- crates/voicevox_core/Cargo.toml | 1 + crates/voicevox_core/src/devices.rs | 2 +- .../src/engine/full_context_label.rs | 12 +-- .../voicevox_core/src/engine/kana_parser.rs | 4 +- crates/voicevox_core/src/engine/open_jtalk.rs | 25 ++--- crates/voicevox_core/src/error.rs | 102 ++++++++++++++++-- crates/voicevox_core/src/inference_core.rs | 8 +- crates/voicevox_core/src/lib.rs | 3 + crates/voicevox_core/src/status.rs | 13 +-- crates/voicevox_core/src/user_dict/dict.rs | 18 ++-- crates/voicevox_core/src/user_dict/word.rs | 17 ++- crates/voicevox_core/src/voice_model.rs | 2 +- crates/voicevox_core/src/voice_synthesizer.rs | 2 +- crates/voicevox_core_c_api/Cargo.toml | 2 - crates/voicevox_core_c_api/src/helpers.rs | 40 ++++--- crates/voicevox_core_c_api/src/lib.rs | 30 ------ crates/voicevox_core_python_api/src/lib.rs | 4 +- 18 files changed, 182 insertions(+), 116 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index bf87b4953..7418454c8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1103,6 +1103,16 @@ dependencies = [ "shared_child", ] +[[package]] +name = "duplicate" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "de78e66ac9061e030587b2a2e75cc88f22304913c907b11307bca737141230cb" +dependencies = [ + "heck", + "proc-macro-error", +] + [[package]] name = "easy-ext" version = "1.0.1" @@ -4050,6 +4060,7 @@ dependencies = [ "derive-getters", "derive-new", "derive_more", + "duplicate", "easy-ext", "flate2", "fs-err", @@ -4098,10 +4109,8 @@ dependencies = [ "ndarray", "ndarray-stats", "once_cell", - "pretty_assertions", "process_path", "regex", - "rstest", "serde", "serde_json", "serde_with", diff --git a/crates/voicevox_core/Cargo.toml b/crates/voicevox_core/Cargo.toml index c542a9860..7627eca4c 100644 --- a/crates/voicevox_core/Cargo.toml +++ b/crates/voicevox_core/Cargo.toml @@ -15,6 +15,7 @@ cfg-if = "1.0.0" derive-getters.workspace = true derive-new = "0.5.9" derive_more = "0.99.17" +duplicate = "1.0.0" easy-ext.workspace = true fs-err.workspace = true futures = "0.3.26" diff --git a/crates/voicevox_core/src/devices.rs b/crates/voicevox_core/src/devices.rs index b8ba51bf2..673144881 100644 --- a/crates/voicevox_core/src/devices.rs +++ b/crates/voicevox_core/src/devices.rs @@ -45,7 +45,7 @@ impl SupportedDevices { let mut cuda_support = false; let mut dml_support = false; for provider in onnxruntime::session::get_available_providers() - .map_err(|e| Error::GetSupportedDevices(e.into()))? + .map_err(|e| ErrorRepr::GetSupportedDevices(e.into()))? .iter() { match provider.as_str() { diff --git a/crates/voicevox_core/src/engine/full_context_label.rs b/crates/voicevox_core/src/engine/full_context_label.rs index f52b84ade..31471c004 100644 --- a/crates/voicevox_core/src/engine/full_context_label.rs +++ b/crates/voicevox_core/src/engine/full_context_label.rs @@ -5,7 +5,7 @@ use once_cell::sync::Lazy; use regex::Regex; #[derive(thiserror::Error, Debug)] -pub enum FullContextLabelError { +pub(crate) enum FullContextLabelError { #[error("label parse error label:{label}")] LabelParse { label: String }, @@ -49,7 +49,7 @@ fn string_feature_by_regex(re: &Regex, label: &str) -> Result { } impl Phoneme { - pub fn from_label(label: impl Into) -> Result { + pub(crate) fn from_label(label: impl Into) -> Result { let mut contexts = HashMap::::with_capacity(10); let label = label.into(); contexts.insert("p3".into(), string_feature_by_regex(&P3_REGEX, &label)?); @@ -116,7 +116,7 @@ pub struct AccentPhrase { } impl AccentPhrase { - pub fn from_phonemes(mut phonemes: Vec) -> Result { + pub(crate) fn from_phonemes(mut phonemes: Vec) -> Result { let mut moras = Vec::with_capacity(phonemes.len()); let mut mora_phonemes = Vec::with_capacity(phonemes.len()); for i in 0..phonemes.len() { @@ -208,7 +208,7 @@ pub struct BreathGroup { } impl BreathGroup { - pub fn from_phonemes(phonemes: Vec) -> Result { + pub(crate) fn from_phonemes(phonemes: Vec) -> Result { let mut accent_phrases = Vec::with_capacity(phonemes.len()); let mut accent_phonemes = Vec::with_capacity(phonemes.len()); for i in 0..phonemes.len() { @@ -256,7 +256,7 @@ pub struct Utterance { } impl Utterance { - pub fn from_phonemes(phonemes: Vec) -> Result { + pub(crate) fn from_phonemes(phonemes: Vec) -> Result { let mut breath_groups = vec![]; let mut group_phonemes = Vec::with_capacity(phonemes.len()); let mut pauses = vec![]; @@ -305,7 +305,7 @@ impl Utterance { self.phonemes().iter().map(|p| p.label().clone()).collect() } - pub fn extract_full_context_label( + pub(crate) fn extract_full_context_label( open_jtalk: &open_jtalk::OpenJtalk, text: impl AsRef, ) -> Result { diff --git a/crates/voicevox_core/src/engine/kana_parser.rs b/crates/voicevox_core/src/engine/kana_parser.rs index 617d76dc8..14a3aa70d 100644 --- a/crates/voicevox_core/src/engine/kana_parser.rs +++ b/crates/voicevox_core/src/engine/kana_parser.rs @@ -11,7 +11,7 @@ const WIDE_INTERROGATION_MARK: char = '?'; const LOOP_LIMIT: usize = 300; #[derive(Clone, Debug, PartialEq, Eq)] -pub struct KanaParseError(String); +pub(crate) struct KanaParseError(String); impl std::fmt::Display for KanaParseError { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { @@ -122,7 +122,7 @@ fn text_to_accent_phrase(phrase: &str) -> KanaParseResult { )) } -pub fn parse_kana(text: &str) -> KanaParseResult> { +pub(crate) fn parse_kana(text: &str) -> KanaParseResult> { const TERMINATOR: char = '\0'; let mut parsed_result = Vec::new(); let chars_of_text = text.chars().chain([TERMINATOR]); diff --git a/crates/voicevox_core/src/engine/open_jtalk.rs b/crates/voicevox_core/src/engine/open_jtalk.rs index 4f8d6df00..b67709b08 100644 --- a/crates/voicevox_core/src/engine/open_jtalk.rs +++ b/crates/voicevox_core/src/engine/open_jtalk.rs @@ -7,10 +7,10 @@ use tempfile::NamedTempFile; use ::open_jtalk::*; -use crate::{Error, UserDict}; +use crate::{error::ErrorRepr, UserDict}; #[derive(thiserror::Error, Debug)] -pub enum OpenJtalkError { +pub(crate) enum OpenJtalkError { #[error("open_jtalk load error")] Load { mecab_dict_dir: PathBuf }, #[error("open_jtalk extract_fullcontext error")] @@ -21,7 +21,7 @@ pub enum OpenJtalkError { }, } -pub type Result = std::result::Result; +type Result = std::result::Result; /// テキスト解析器としてのOpen JTalk。 pub struct OpenJtalk { @@ -54,7 +54,7 @@ impl OpenJtalk { ) -> crate::result::Result { let mut s = Self::new_without_dic(); s.load(open_jtalk_dict_dir) - .map_err(|_| Error::NotLoadedOpenjtalkDict)?; + .map_err(|_| ErrorRepr::NotLoadedOpenjtalkDict)?; Ok(s) } @@ -67,15 +67,16 @@ impl OpenJtalk { .dict_dir .as_ref() .and_then(|dict_dir| dict_dir.to_str()) - .ok_or(Error::NotLoadedOpenjtalkDict)?; + .ok_or(ErrorRepr::NotLoadedOpenjtalkDict)?; // ユーザー辞書用のcsvを作成 - let mut temp_csv = NamedTempFile::new().map_err(|e| Error::UseUserDict(e.to_string()))?; + let mut temp_csv = + NamedTempFile::new().map_err(|e| ErrorRepr::UseUserDict(e.to_string()))?; temp_csv .write_all(user_dict.to_mecab_format().as_bytes()) - .map_err(|e| Error::UseUserDict(e.to_string()))?; + .map_err(|e| ErrorRepr::UseUserDict(e.to_string()))?; let temp_csv_path = temp_csv.into_temp_path(); - let temp_dict = NamedTempFile::new().map_err(|e| Error::UseUserDict(e.to_string()))?; + let temp_dict = NamedTempFile::new().map_err(|e| ErrorRepr::UseUserDict(e.to_string()))?; let temp_dict_path = temp_dict.into_temp_path(); // Mecabでユーザー辞書をコンパイル @@ -99,15 +100,15 @@ impl OpenJtalk { let result = mecab.load_with_userdic(Path::new(dict_dir), Some(Path::new(&temp_dict_path))); if !result { - return Err(Error::UseUserDict( - "辞書のコンパイルに失敗しました".to_string(), - )); + return Err( + ErrorRepr::UseUserDict("辞書のコンパイルに失敗しました".to_string()).into(), + ); } Ok(()) } - pub fn extract_fullcontext(&self, text: impl AsRef) -> Result> { + pub(crate) fn extract_fullcontext(&self, text: impl AsRef) -> Result> { let Resources { mecab, njd, diff --git a/crates/voicevox_core/src/error.rs b/crates/voicevox_core/src/error.rs index fc1f613fa..f7c5aeff2 100644 --- a/crates/voicevox_core/src/error.rs +++ b/crates/voicevox_core/src/error.rs @@ -1,13 +1,59 @@ use self::engine::{FullContextLabelError, KanaParseError}; use super::*; //use engine:: +use duplicate::duplicate_item; use std::path::PathBuf; use thiserror::Error; use uuid::Uuid; /// VOICEVOX COREのエラー。 #[derive(Error, Debug)] -pub enum Error { +#[error(transparent)] +pub struct Error(#[from] ErrorRepr); + +#[duplicate_item( + E; + [ LoadModelError ]; + [ FullContextLabelError ]; + [ KanaParseError ]; +)] +impl From for Error { + fn from(err: E) -> Self { + Self(err.into()) + } +} + +impl Error { + /// 対応する[`ErrorKind`]を返す。 + pub fn kind(&self) -> ErrorKind { + match &self.0 { + ErrorRepr::NotLoadedOpenjtalkDict => ErrorKind::NotLoadedOpenjtalkDict, + ErrorRepr::GpuSupport => ErrorKind::GpuSupport, + ErrorRepr::LoadModel(LoadModelError { context, .. }) => match context { + LoadModelErrorKind::OpenZipFile => ErrorKind::OpenZipFile, + LoadModelErrorKind::ReadZipEntry { .. } => ErrorKind::ReadZipEntry, + LoadModelErrorKind::ModelAlreadyLoaded { .. } => ErrorKind::ModelAlreadyLoaded, + LoadModelErrorKind::StyleAlreadyLoaded { .. } => ErrorKind::StyleAlreadyLoaded, + LoadModelErrorKind::InvalidModelData => ErrorKind::InvalidModelData, + }, + ErrorRepr::UnloadedModel { .. } => ErrorKind::UnloadedModel, + ErrorRepr::GetSupportedDevices(_) => ErrorKind::GetSupportedDevices, + ErrorRepr::InvalidStyleId { .. } => ErrorKind::InvalidStyleId, + ErrorRepr::InvalidModelId { .. } => ErrorKind::InvalidModelId, + ErrorRepr::InferenceFailed => ErrorKind::InferenceFailed, + ErrorRepr::ExtractFullContextLabel(_) => ErrorKind::ExtractFullContextLabel, + ErrorRepr::ParseKana(_) => ErrorKind::ParseKana, + ErrorRepr::LoadUserDict(_) => ErrorKind::LoadUserDict, + ErrorRepr::SaveUserDict(_) => ErrorKind::SaveUserDict, + ErrorRepr::UnknownWord(_) => ErrorKind::UnknownWord, + ErrorRepr::UseUserDict(_) => ErrorKind::UseUserDict, + ErrorRepr::InvalidWord(_) => ErrorKind::InvalidWord, + } + } +} + +#[derive(Error, Debug)] +pub(crate) enum ErrorRepr { #[error("OpenJTalkの辞書が読み込まれていません")] NotLoadedOpenjtalkDict, @@ -26,6 +72,7 @@ pub enum Error { #[error("無効なspeaker_idです: {style_id:?}")] InvalidStyleId { style_id: StyleId }, + #[allow(dead_code)] // FIXME #[error("無効なmodel_idです: {model_id:?}")] InvalidModelId { model_id: VoiceModelId }, @@ -54,6 +101,49 @@ pub enum Error { InvalidWord(InvalidWordError), } +/// エラーの種類。 +#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)] +pub enum ErrorKind { + /// open_jtalk辞書ファイルが読み込まれていない。 + NotLoadedOpenjtalkDict, + /// GPUモードがサポートされていない。 + GpuSupport, + /// ZIPファイルを開くことに失敗した。 + OpenZipFile, + /// ZIP内のファイルが読めなかった。 + ReadZipEntry, + /// すでに読み込まれている音声モデルを読み込もうとした。 + ModelAlreadyLoaded, + /// すでに読み込まれているスタイルを読み込もうとした。 + StyleAlreadyLoaded, + /// 無効なモデルデータ。 + InvalidModelData, + /// Modelが読み込まれていない。 + UnloadedModel, + /// サポートされているデバイス情報取得に失敗した。 + GetSupportedDevices, + /// 無効なstyle_idが指定された。 + InvalidStyleId, + /// 無効なmodel_idが指定された。 + InvalidModelId, + /// 推論に失敗した。 + InferenceFailed, + /// コンテキストラベル出力に失敗した。 + ExtractFullContextLabel, + /// AquesTalk風記法のテキストの解析に失敗した。 + ParseKana, + /// ユーザー辞書を読み込めなかった。 + LoadUserDict, + /// ユーザー辞書を書き込めなかった。 + SaveUserDict, + /// ユーザー辞書に単語が見つからなかった。 + UnknownWord, + /// OpenJTalkのユーザー辞書の設定に失敗した。 + UseUserDict, + /// ユーザー辞書の単語のバリデーションに失敗した。 + InvalidWord, +} + pub(crate) type LoadModelResult = std::result::Result; /// 音声モデル読み込みのエラー。 @@ -62,21 +152,15 @@ pub(crate) type LoadModelResult = std::result::Result; "`{path}`の読み込みに失敗しました: {context}{}", source.as_ref().map(|e| format!(": {e}")).unwrap_or_default()) ] -pub struct LoadModelError { +pub(crate) struct LoadModelError { pub(crate) path: PathBuf, pub(crate) context: LoadModelErrorKind, #[source] pub(crate) source: Option, } -impl LoadModelError { - pub fn context(&self) -> &LoadModelErrorKind { - &self.context - } -} - #[derive(derive_more::Display, Debug)] -pub enum LoadModelErrorKind { +pub(crate) enum LoadModelErrorKind { #[display(fmt = "ZIPファイルとして開くことができませんでした")] OpenZipFile, #[display(fmt = "`{filename}`を読み取れませんでした")] diff --git a/crates/voicevox_core/src/inference_core.rs b/crates/voicevox_core/src/inference_core.rs index d88943fc8..69d97ee85 100644 --- a/crates/voicevox_core/src/inference_core.rs +++ b/crates/voicevox_core/src/inference_core.rs @@ -24,7 +24,7 @@ impl InferenceCore { } Ok(Self { status }) } else { - Err(Error::GpuSupport) + Err(ErrorRepr::GpuSupport.into()) } } @@ -65,7 +65,7 @@ impl InferenceCore { style_id: StyleId, ) -> Result> { if !self.status.validate_speaker_id(style_id) { - return Err(Error::InvalidStyleId { style_id }); + return Err(ErrorRepr::InvalidStyleId { style_id }.into()); } let (model_id, model_inner_id) = self.status.ids_for(style_id)?; @@ -100,7 +100,7 @@ impl InferenceCore { style_id: StyleId, ) -> Result> { if !self.status.validate_speaker_id(style_id) { - return Err(Error::InvalidStyleId { style_id }); + return Err(ErrorRepr::InvalidStyleId { style_id }.into()); } let (model_id, model_inner_id) = self.status.ids_for(style_id)?; @@ -139,7 +139,7 @@ impl InferenceCore { style_id: StyleId, ) -> Result> { if !self.status.validate_speaker_id(style_id) { - return Err(Error::InvalidStyleId { style_id }); + return Err(ErrorRepr::InvalidStyleId { style_id }.into()); } let (model_id, model_inner_id) = self.status.ids_for(style_id)?; diff --git a/crates/voicevox_core/src/lib.rs b/crates/voicevox_core/src/lib.rs index 03ed54702..3ad352486 100644 --- a/crates/voicevox_core/src/lib.rs +++ b/crates/voicevox_core/src/lib.rs @@ -18,6 +18,9 @@ mod version; mod voice_model; mod voice_synthesizer; +#[doc(hidden)] +pub mod __internal; + use self::inference_core::*; #[cfg(test)] diff --git a/crates/voicevox_core/src/status.rs b/crates/voicevox_core/src/status.rs index a92ffae8e..c173d87db 100644 --- a/crates/voicevox_core/src/status.rs +++ b/crates/voicevox_core/src/status.rs @@ -185,7 +185,7 @@ impl Status { let output_tensors = predict_duration .run(vec![&mut phoneme_vector_array, &mut speaker_id_array]) - .map_err(|_| Error::InferenceFailed)?; + .map_err(|_| ErrorRepr::InferenceFailed)?; Ok(output_tensors[0].as_slice().unwrap().to_owned()) }) .await @@ -229,7 +229,7 @@ impl Status { &mut end_accent_phrase_vector_array, &mut speaker_id_array, ]) - .map_err(|_| Error::InferenceFailed)?; + .map_err(|_| ErrorRepr::InferenceFailed)?; Ok(output_tensors[0].as_slice().unwrap().to_owned()) }) .await @@ -261,7 +261,7 @@ impl Status { &mut phoneme_array, &mut speaker_id_array, ]) - .map_err(|_| Error::InferenceFailed)?; + .map_err(|_| ErrorRepr::InferenceFailed)?; Ok(output_tensors[0].as_slice().unwrap().to_owned()) }) .await @@ -305,7 +305,7 @@ impl LoadedModels { .flat_map(SpeakerMeta::styles) .any(|style| *style.id() == style_id) }) - .ok_or(Error::InvalidStyleId { style_id })?; + .ok_or(ErrorRepr::InvalidStyleId { style_id })?; let model_inner_id = *model_inner_ids .get(&style_id) @@ -390,9 +390,10 @@ impl LoadedModels { fn remove(&mut self, model_id: &VoiceModelId) -> Result<()> { if self.0.remove(model_id).is_none() { - return Err(Error::UnloadedModel { + return Err(ErrorRepr::UnloadedModel { model_id: model_id.clone(), - }); + } + .into()); } Ok(()) } diff --git a/crates/voicevox_core/src/user_dict/dict.rs b/crates/voicevox_core/src/user_dict/dict.rs index c923db44f..60dfe3ce4 100644 --- a/crates/voicevox_core/src/user_dict/dict.rs +++ b/crates/voicevox_core/src/user_dict/dict.rs @@ -5,7 +5,7 @@ use itertools::join; use uuid::Uuid; use super::word::*; -use crate::{Error, Result}; +use crate::{error::ErrorRepr, Result}; /// ユーザー辞書。 /// 単語はJSONとの相互変換のために挿入された順序を保つ。 @@ -28,10 +28,11 @@ impl UserDict { pub fn load(&mut self, store_path: &str) -> Result<()> { let store_path = std::path::Path::new(store_path); - let store_file = File::open(store_path).map_err(|e| Error::LoadUserDict(e.to_string()))?; + let store_file = + File::open(store_path).map_err(|e| ErrorRepr::LoadUserDict(e.to_string()))?; - let words: IndexMap = - serde_json::from_reader(store_file).map_err(|e| Error::LoadUserDict(e.to_string()))?; + let words: IndexMap = serde_json::from_reader(store_file) + .map_err(|e| ErrorRepr::LoadUserDict(e.to_string()))?; self.words.extend(words); Ok(()) @@ -47,7 +48,7 @@ impl UserDict { /// ユーザー辞書の単語を変更する。 pub fn update_word(&mut self, word_uuid: Uuid, new_word: UserDictWord) -> Result<()> { if !self.words.contains_key(&word_uuid) { - return Err(Error::UnknownWord(word_uuid)); + return Err(ErrorRepr::UnknownWord(word_uuid).into()); } self.words.insert(word_uuid, new_word); Ok(()) @@ -56,7 +57,7 @@ impl UserDict { /// ユーザー辞書から単語を削除する。 pub fn remove_word(&mut self, word_uuid: Uuid) -> Result { let Some(word) = self.words.remove(&word_uuid) else { - return Err(Error::UnknownWord(word_uuid)); + return Err(ErrorRepr::UnknownWord(word_uuid).into()); }; Ok(word) } @@ -71,9 +72,10 @@ impl UserDict { /// ユーザー辞書を保存する。 pub fn save(&self, store_path: &str) -> Result<()> { - let mut file = File::create(store_path).map_err(|e| Error::SaveUserDict(e.to_string()))?; + let mut file = + File::create(store_path).map_err(|e| ErrorRepr::SaveUserDict(e.to_string()))?; serde_json::to_writer(&mut file, &self.words) - .map_err(|e| Error::SaveUserDict(e.to_string()))?; + .map_err(|e| ErrorRepr::SaveUserDict(e.to_string()))?; Ok(()) } diff --git a/crates/voicevox_core/src/user_dict/word.rs b/crates/voicevox_core/src/user_dict/word.rs index 29e6b2ec1..6a43958db 100644 --- a/crates/voicevox_core/src/user_dict/word.rs +++ b/crates/voicevox_core/src/user_dict/word.rs @@ -1,5 +1,5 @@ use crate::{ - error::Error, + error::ErrorRepr, result::Result, user_dict::part_of_speech_data::{ priority2cost, MAX_PRIORITY, MIN_PRIORITY, PART_OF_SPEECH_DETAIL, @@ -55,8 +55,9 @@ impl<'de> Deserialize<'de> for UserDictWord { } } +#[allow(clippy::enum_variant_names)] // FIXME #[derive(thiserror::Error, Debug, PartialEq)] -pub enum InvalidWordError { +pub(crate) enum InvalidWordError { #[error("無効な発音です({1}): {0:?}")] InvalidPronunciation(String, &'static str), #[error("優先度は{MIN_PRIORITY}以上{MAX_PRIORITY}以下である必要があります: {0}")] @@ -102,13 +103,11 @@ impl UserDictWord { priority: u32, ) -> Result { if MIN_PRIORITY > priority || priority > MAX_PRIORITY { - return Err(Error::InvalidWord(InvalidWordError::InvalidPriority( - priority, - ))); + return Err(ErrorRepr::InvalidWord(InvalidWordError::InvalidPriority(priority)).into()); } - validate_pronunciation(&pronunciation).map_err(Error::InvalidWord)?; + validate_pronunciation(&pronunciation).map_err(ErrorRepr::InvalidWord)?; let mora_count = - calculate_mora_count(&pronunciation, accent_type).map_err(Error::InvalidWord)?; + calculate_mora_count(&pronunciation, accent_type).map_err(ErrorRepr::InvalidWord)?; Ok(Self { surface: to_zenkaku(surface), pronunciation, @@ -121,7 +120,7 @@ impl UserDictWord { } /// カタカナの文字列が発音として有効かどうかを判定する。 -pub fn validate_pronunciation(pronunciation: &str) -> InvalidWordResult<()> { +pub(crate) fn validate_pronunciation(pronunciation: &str) -> InvalidWordResult<()> { // 元実装:https://github.com/VOICEVOX/voicevox_engine/blob/39747666aa0895699e188f3fd03a0f448c9cf746/voicevox_engine/model.py#L190-L210 if !PRONUNCIATION_REGEX.is_match(pronunciation) { return Err(InvalidWordError::InvalidPronunciation( @@ -182,7 +181,7 @@ fn calculate_mora_count(pronunciation: &str, accent_type: usize) -> InvalidWordR /// - "!"から"~"までの範囲の文字(数字やアルファベット)は、対応する全角文字に /// - " "などの目に見えない文字は、まとめて全角スペース(0x3000)に /// 変換する。 -pub fn to_zenkaku(surface: &str) -> String { +pub(crate) fn to_zenkaku(surface: &str) -> String { // 元実装:https://github.com/VOICEVOX/voicevox/blob/69898f5dd001d28d4de355a25766acb0e0833ec2/src/components/DictionaryManageDialog.vue#L379-L387 SPACE_REGEX .replace_all(surface, "\u{3000}") diff --git a/crates/voicevox_core/src/voice_model.rs b/crates/voicevox_core/src/voice_model.rs index 2eb114b6a..6493e3f97 100644 --- a/crates/voicevox_core/src/voice_model.rs +++ b/crates/voicevox_core/src/voice_model.rs @@ -60,7 +60,7 @@ impl VoiceModel { }) } /// VVMファイルから`VoiceModel`をコンストラクトする。 - pub async fn from_path(path: impl AsRef) -> LoadModelResult { + pub async fn from_path(path: impl AsRef) -> Result { let reader = VvmEntryReader::open(path.as_ref()).await?; let manifest = reader.read_vvm_json::("manifest.json").await?; let metas = reader diff --git a/crates/voicevox_core/src/voice_synthesizer.rs b/crates/voicevox_core/src/voice_synthesizer.rs index a798d8e17..34864af8c 100644 --- a/crates/voicevox_core/src/voice_synthesizer.rs +++ b/crates/voicevox_core/src/voice_synthesizer.rs @@ -378,7 +378,7 @@ impl Synthesizer { options: &AccentPhrasesOptions, ) -> Result> { if !self.synthesis_engine.is_openjtalk_dict_loaded() { - return Err(Error::NotLoadedOpenjtalkDict); + return Err(ErrorRepr::NotLoadedOpenjtalkDict.into()); } if options.kana { self.synthesis_engine diff --git a/crates/voicevox_core_c_api/Cargo.toml b/crates/voicevox_core_c_api/Cargo.toml index 01f520bfd..9264fc183 100644 --- a/crates/voicevox_core_c_api/Cargo.toml +++ b/crates/voicevox_core_c_api/Cargo.toml @@ -43,10 +43,8 @@ libloading = "0.7.3" libtest-mimic = "0.6.0" ndarray = "0.15.6" ndarray-stats = "0.5.1" -pretty_assertions = "1.3.0" process_path.workspace = true regex.workspace = true -rstest = "0.15.0" serde.workspace = true serde_with = "3.3.0" strum.workspace = true diff --git a/crates/voicevox_core_c_api/src/helpers.rs b/crates/voicevox_core_c_api/src/helpers.rs index 8792d7ca1..0b889e60c 100644 --- a/crates/voicevox_core_c_api/src/helpers.rs +++ b/crates/voicevox_core_c_api/src/helpers.rs @@ -18,35 +18,33 @@ pub(crate) fn into_result_code_with_error(result: CApiResult<()>) -> VoicevoxRes } fn into_result_code(result: CApiResult<()>) -> VoicevoxResultCode { - use voicevox_core::{Error::*, LoadModelErrorKind::*}; + use voicevox_core::ErrorKind::*; use CApiError::*; use VoicevoxResultCode::*; match result { Ok(()) => VOICEVOX_RESULT_OK, - Err(RustApi(NotLoadedOpenjtalkDict)) => VOICEVOX_RESULT_NOT_LOADED_OPENJTALK_DICT_ERROR, - Err(RustApi(GpuSupport)) => VOICEVOX_RESULT_GPU_SUPPORT_ERROR, - Err(RustApi(LoadModel(err))) => match err.context() { + Err(RustApi(err)) => match err.kind() { + NotLoadedOpenjtalkDict => VOICEVOX_RESULT_NOT_LOADED_OPENJTALK_DICT_ERROR, + GpuSupport => VOICEVOX_RESULT_GPU_SUPPORT_ERROR, OpenZipFile => VOICEVOX_RESULT_OPEN_ZIP_FILE_ERROR, - ReadZipEntry { .. } => VOICEVOX_RESULT_READ_ZIP_ENTRY_ERROR, - ModelAlreadyLoaded { .. } => VOICEVOX_RESULT_MODEL_ALREADY_LOADED_ERROR, - StyleAlreadyLoaded { .. } => VOICEVOX_RESULT_STYLE_ALREADY_LOADED_ERROR, + ReadZipEntry => VOICEVOX_RESULT_READ_ZIP_ENTRY_ERROR, + ModelAlreadyLoaded => VOICEVOX_RESULT_MODEL_ALREADY_LOADED_ERROR, + StyleAlreadyLoaded => VOICEVOX_RESULT_STYLE_ALREADY_LOADED_ERROR, InvalidModelData => VOICEVOX_RESULT_INVALID_MODEL_DATA_ERROR, + UnloadedModel => VOICEVOX_RESULT_UNLOADED_MODEL_ERROR, + GetSupportedDevices => VOICEVOX_RESULT_GET_SUPPORTED_DEVICES_ERROR, + InvalidStyleId => VOICEVOX_RESULT_INVALID_STYLE_ID_ERROR, + InvalidModelId => VOICEVOX_RESULT_INVALID_MODEL_ID_ERROR, + InferenceFailed => VOICEVOX_RESULT_INFERENCE_ERROR, + ExtractFullContextLabel => VOICEVOX_RESULT_EXTRACT_FULL_CONTEXT_LABEL_ERROR, + ParseKana => VOICEVOX_RESULT_PARSE_KANA_ERROR, + LoadUserDict => VOICEVOX_RESULT_LOAD_USER_DICT_ERROR, + SaveUserDict => VOICEVOX_RESULT_SAVE_USER_DICT_ERROR, + UnknownWord => VOICEVOX_RESULT_UNKNOWN_USER_DICT_WORD_ERROR, + UseUserDict => VOICEVOX_RESULT_USE_USER_DICT_ERROR, + InvalidWord => VOICEVOX_RESULT_INVALID_USER_DICT_WORD_ERROR, }, - Err(RustApi(GetSupportedDevices(_))) => VOICEVOX_RESULT_GET_SUPPORTED_DEVICES_ERROR, - Err(RustApi(InvalidStyleId { .. })) => VOICEVOX_RESULT_INVALID_STYLE_ID_ERROR, - Err(RustApi(InvalidModelId { .. })) => VOICEVOX_RESULT_INVALID_MODEL_ID_ERROR, - Err(RustApi(InferenceFailed)) => VOICEVOX_RESULT_INFERENCE_ERROR, - Err(RustApi(ExtractFullContextLabel(_))) => { - VOICEVOX_RESULT_EXTRACT_FULL_CONTEXT_LABEL_ERROR - } - Err(RustApi(UnloadedModel { .. })) => VOICEVOX_RESULT_UNLOADED_MODEL_ERROR, - Err(RustApi(ParseKana(_))) => VOICEVOX_RESULT_PARSE_KANA_ERROR, - Err(RustApi(LoadUserDict(_))) => VOICEVOX_RESULT_LOAD_USER_DICT_ERROR, - Err(RustApi(SaveUserDict(_))) => VOICEVOX_RESULT_SAVE_USER_DICT_ERROR, - Err(RustApi(UnknownWord(_))) => VOICEVOX_RESULT_UNKNOWN_USER_DICT_WORD_ERROR, - Err(RustApi(UseUserDict(_))) => VOICEVOX_RESULT_USE_USER_DICT_ERROR, - Err(RustApi(InvalidWord(_))) => VOICEVOX_RESULT_INVALID_USER_DICT_WORD_ERROR, Err(InvalidUtf8Input) => VOICEVOX_RESULT_INVALID_UTF8_INPUT_ERROR, Err(InvalidAudioQuery(_)) => VOICEVOX_RESULT_INVALID_AUDIO_QUERY_ERROR, Err(InvalidAccentPhrase(_)) => VOICEVOX_RESULT_INVALID_ACCENT_PHRASE_ERROR, diff --git a/crates/voicevox_core_c_api/src/lib.rs b/crates/voicevox_core_c_api/src/lib.rs index b75ee76cd..738c36c8a 100644 --- a/crates/voicevox_core_c_api/src/lib.rs +++ b/crates/voicevox_core_c_api/src/lib.rs @@ -33,9 +33,6 @@ use voicevox_core::{ }; use voicevox_core::{StyleId, SupportedDevices, SynthesisOptions, Synthesizer}; -#[cfg(test)] -use rstest::*; - static RUNTIME: Lazy = Lazy::new(|| { let _ = init_logger(); @@ -1217,30 +1214,3 @@ pub unsafe extern "C" fn voicevox_user_dict_save( pub unsafe extern "C" fn voicevox_user_dict_delete(user_dict: Box) { drop(user_dict); } - -#[cfg(test)] -mod tests { - use super::*; - use anyhow::anyhow; - use pretty_assertions::assert_eq; - use voicevox_core::Error; - use voicevox_core::Result; - - #[rstest] - #[case(Ok(()), VoicevoxResultCode::VOICEVOX_RESULT_OK)] - #[case( - Err(Error::NotLoadedOpenjtalkDict), - VoicevoxResultCode::VOICEVOX_RESULT_NOT_LOADED_OPENJTALK_DICT_ERROR - )] - #[case( - Err(Error::GetSupportedDevices(anyhow!("some get supported devices error"))), - VoicevoxResultCode::VOICEVOX_RESULT_GET_SUPPORTED_DEVICES_ERROR - )] - fn into_result_code_with_error_works( - #[case] result: Result<()>, - #[case] expected: VoicevoxResultCode, - ) { - let actual = into_result_code_with_error(result.map_err(Into::into)); - assert_eq!(expected, actual); - } -} diff --git a/crates/voicevox_core_python_api/src/lib.rs b/crates/voicevox_core_python_api/src/lib.rs index 3c76dd8a0..fa36ad1a2 100644 --- a/crates/voicevox_core_python_api/src/lib.rs +++ b/crates/voicevox_core_python_api/src/lib.rs @@ -438,12 +438,12 @@ impl Drop for Closable { #[pyfunction] fn _validate_pronunciation(pronunciation: &str) -> PyResult<()> { - voicevox_core::validate_pronunciation(pronunciation).into_py_result() + voicevox_core::__internal::validate_pronunciation(pronunciation).into_py_result() } #[pyfunction] fn _to_zenkaku(text: &str) -> PyResult { - Ok(voicevox_core::to_zenkaku(text)) + Ok(voicevox_core::__internal::to_zenkaku(text)) } #[pyclass]