From 61725a27863e7e3e7b06ed3138edefa999941f71 Mon Sep 17 00:00:00 2001 From: Ryo Yamashita Date: Thu, 23 May 2024 23:31:40 +0900 Subject: [PATCH 1/3] =?UTF-8?q?change:=20VVM=E3=81=ABUUID=E3=82=92?= =?UTF-8?q?=E5=89=B2=E3=82=8A=E6=8C=AF=E3=82=8A=E3=80=81=E3=81=9D=E3=82=8C?= =?UTF-8?q?=E3=82=92`VoiceModelId`=E3=81=A8=E3=81=99=E3=82=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Cargo.lock | 11 +--- Cargo.toml | 1 - crates/voicevox_core/Cargo.toml | 1 - .../voicevox_core/src/__internal/interop.rs | 5 +- crates/voicevox_core/src/manifest.rs | 5 +- crates/voicevox_core/src/status.rs | 35 ++++++------ crates/voicevox_core/src/synthesizer.rs | 14 ++--- crates/voicevox_core/src/voice_model.rs | 43 +++++++------- crates/voicevox_core_c_api/Cargo.toml | 1 + .../include/voicevox_core.h | 6 +- crates/voicevox_core_c_api/src/c_impls.rs | 8 ++- .../src/compatible_engine.rs | 7 +-- crates/voicevox_core_c_api/src/helpers.rs | 11 +++- crates/voicevox_core_c_api/src/lib.rs | 41 ++++++-------- crates/voicevox_core_java_api/Cargo.toml | 1 + .../hiroshiba/voicevoxcore/Synthesizer.java | 9 +-- .../jp/hiroshiba/voicevoxcore/VoiceModel.java | 5 +- crates/voicevox_core_java_api/src/common.rs | 27 ++++++++- .../voicevox_core_java_api/src/synthesizer.rs | 14 ++--- .../voicevox_core_java_api/src/voice_model.rs | 6 +- .../python/voicevox_core/_models.py | 5 +- .../python/voicevox_core/_rust/asyncio.pyi | 4 +- .../python/voicevox_core/_rust/blocking.pyi | 4 +- .../voicevox_core_python_api/src/convert.rs | 1 + crates/voicevox_core_python_api/src/lib.rs | 56 +++++++++++-------- model/sample.vvm/manifest.json | 1 + 26 files changed, 174 insertions(+), 148 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 0f9a78469..6d947deb2 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1822,15 +1822,6 @@ dependencies = [ "windows-sys 0.42.0", ] -[[package]] -name = "nanoid" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3ffa00dec017b5b1a8b7cf5e2c008bfda1aa7e0697ac1508b491fdf2622fb4d8" -dependencies = [ - "rand", -] - [[package]] name = "ndarray" version = "0.15.6" @@ -3587,7 +3578,6 @@ dependencies = [ "indexmap 2.0.0", "itertools 0.10.5", "jlabel", - "nanoid", "ndarray", "once_cell", "open_jtalk", @@ -3662,6 +3652,7 @@ dependencies = [ "android_logger", "chrono", "derive_more", + "easy-ext", "jni", "once_cell", "serde_json", diff --git a/Cargo.toml b/Cargo.toml index 7102127d5..ec32522f6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -47,7 +47,6 @@ libc = "0.2.134" libloading = "0.7.3" libtest-mimic = "0.6.0" log = "0.4.17" -nanoid = "0.4.0" ndarray = "0.15.6" ndarray-stats = "0.5.1" octocrab = { version = "0.19.0", default-features = false } diff --git a/crates/voicevox_core/Cargo.toml b/crates/voicevox_core/Cargo.toml index 731862356..9957c1373 100644 --- a/crates/voicevox_core/Cargo.toml +++ b/crates/voicevox_core/Cargo.toml @@ -25,7 +25,6 @@ futures.workspace = true indexmap = { workspace = true, features = ["serde"] } itertools.workspace = true jlabel.workspace = true -nanoid.workspace = true ndarray.workspace = true once_cell.workspace = true open_jtalk.workspace = true diff --git a/crates/voicevox_core/src/__internal/interop.rs b/crates/voicevox_core/src/__internal/interop.rs index fe46d10bc..677f5515a 100644 --- a/crates/voicevox_core/src/__internal/interop.rs +++ b/crates/voicevox_core/src/__internal/interop.rs @@ -1 +1,4 @@ -pub use crate::{metas::merge as merge_metas, synthesizer::blocking::PerformInference}; +pub use crate::{ + metas::merge as merge_metas, synthesizer::blocking::PerformInference, + voice_model::blocking::IdRef, +}; diff --git a/crates/voicevox_core/src/manifest.rs b/crates/voicevox_core/src/manifest.rs index 3b17ae3f1..eba2a3dd3 100644 --- a/crates/voicevox_core/src/manifest.rs +++ b/crates/voicevox_core/src/manifest.rs @@ -6,7 +6,7 @@ use derive_new::new; use serde::{Deserialize, Serialize}; use serde_with::{serde_as, DisplayFromStr}; -use crate::StyleId; +use crate::{StyleId, VoiceModelId}; pub type RawManifestVersion = String; #[derive(Deserialize, Clone, Debug, PartialEq, new)] @@ -38,10 +38,9 @@ impl Display for ModelInnerId { #[derive(Deserialize, Getters, Clone)] pub struct Manifest { - // FIXME: UUIDにする - // https://github.com/VOICEVOX/voicevox_core/issues/581 #[allow(dead_code)] manifest_version: ManifestVersion, + pub(crate) id: VoiceModelId, metas_filename: String, #[serde(flatten)] domains: ManifestDomains, diff --git a/crates/voicevox_core/src/status.rs b/crates/voicevox_core/src/status.rs index a47de689b..9aa4d3271 100644 --- a/crates/voicevox_core/src/status.rs +++ b/crates/voicevox_core/src/status.rs @@ -58,7 +58,7 @@ impl Status { Ok(()) } - pub(crate) fn unload_model(&self, voice_model_id: &VoiceModelId) -> Result<()> { + pub(crate) fn unload_model(&self, voice_model_id: VoiceModelId) -> Result<()> { self.loaded_models.lock().unwrap().remove(voice_model_id) } @@ -77,7 +77,7 @@ impl Status { self.loaded_models.lock().unwrap().ids_for::(style_id) } - pub(crate) fn is_loaded_model(&self, voice_model_id: &VoiceModelId) -> bool { + pub(crate) fn is_loaded_model(&self, voice_model_id: VoiceModelId) -> bool { self.loaded_models .lock() .unwrap() @@ -101,7 +101,7 @@ impl Status { /// `self`が`model_id`を含んでいないとき、パニックする。 pub(crate) fn run_session( &self, - model_id: &VoiceModelId, + model_id: VoiceModelId, input: I, ) -> Result<::Output> where @@ -159,7 +159,7 @@ impl LoadedModels { .and_then(|(model_inner_ids, _)| model_inner_ids.get(&style_id).copied()) .unwrap_or_else(|| ModelInnerId::new(style_id.raw_id())); - Ok((model_id.clone(), model_inner_id)) + Ok((*model_id, model_inner_id)) } /// # Panics @@ -168,12 +168,12 @@ impl LoadedModels { /// /// - `self`が`model_id`を含んでいないとき /// - 対応する`InferenceDomain`が欠けているとき - fn get(&self, model_id: &VoiceModelId) -> InferenceSessionCell + fn get(&self, model_id: VoiceModelId) -> InferenceSessionCell where I: InferenceInputSignature, ::Domain: InferenceDomainExt, { - let (_, session_set) = self.0[model_id] + let (_, session_set) = self.0[&model_id] .session_sets_with_inner_ids .get::<::Domain>() .as_ref() @@ -190,8 +190,8 @@ impl LoadedModels { session_set.get() } - fn contains_voice_model(&self, model_id: &VoiceModelId) -> bool { - self.0.contains_key(model_id) + fn contains_voice_model(&self, model_id: VoiceModelId) -> bool { + self.0.contains_key(&model_id) } fn contains_style(&self, style_id: StyleId) -> bool { @@ -216,9 +216,9 @@ impl LoadedModels { source: None, }; - if self.0.contains_key(&model_header.id) { + if self.0.contains_key(&model_header.manifest.id) { return Err(error(LoadModelErrorKind::ModelAlreadyLoaded { - id: model_header.id.clone(), + id: model_header.manifest.id, })); } @@ -255,7 +255,7 @@ impl LoadedModels { self.ensure_acceptable(model_header)?; let prev = self.0.insert( - model_header.id.clone(), + model_header.manifest.id, LoadedModel { metas: model_header.metas.clone(), session_sets_with_inner_ids, @@ -265,12 +265,9 @@ impl LoadedModels { Ok(()) } - fn remove(&mut self, model_id: &VoiceModelId) -> Result<()> { - if self.0.remove(model_id).is_none() { - return Err(ErrorRepr::ModelNotFound { - model_id: model_id.clone(), - } - .into()); + fn remove(&mut self, model_id: VoiceModelId) -> Result<()> { + if self.0.remove(&model_id).is_none() { + return Err(ErrorRepr::ModelNotFound { model_id }.into()); } Ok(()) } @@ -415,13 +412,13 @@ mod tests { let model_header = vvm.header(); let model_contents = &vvm.read_inference_models().await.unwrap(); assert!( - !status.is_loaded_model(&model_header.id), + !status.is_loaded_model(model_header.manifest.id), "model should not be loaded" ); let result = status.insert_model(model_header, model_contents); assert_debug_fmt_eq!(Ok(()), result); assert!( - status.is_loaded_model(&model_header.id), + status.is_loaded_model(model_header.manifest.id), "model should be loaded", ); } diff --git a/crates/voicevox_core/src/synthesizer.rs b/crates/voicevox_core/src/synthesizer.rs index 5e4894415..99cdcf420 100644 --- a/crates/voicevox_core/src/synthesizer.rs +++ b/crates/voicevox_core/src/synthesizer.rs @@ -207,12 +207,12 @@ pub(crate) mod blocking { } /// 音声モデルの読み込みを解除する。 - pub fn unload_voice_model(&self, voice_model_id: &VoiceModelId) -> Result<()> { + pub fn unload_voice_model(&self, voice_model_id: VoiceModelId) -> Result<()> { self.status.unload_model(voice_model_id) } /// 指定したIDの音声モデルが読み込まれているか判定する。 - pub fn is_loaded_voice_model(&self, voice_model_id: &VoiceModelId) -> bool { + pub fn is_loaded_voice_model(&self, voice_model_id: VoiceModelId) -> bool { self.status.is_loaded_model(voice_model_id) } @@ -841,7 +841,7 @@ pub(crate) mod blocking { let PredictDurationOutput { phoneme_length: output, } = self.status.run_session( - &model_id, + model_id, PredictDurationInput { phoneme_list: ndarray::arr1(phoneme_vector), speaker_id: ndarray::arr1(&[model_inner_id.raw_id().into()]), @@ -874,7 +874,7 @@ pub(crate) mod blocking { let (model_id, model_inner_id) = self.status.ids_for::(style_id)?; let PredictIntonationOutput { f0_list: output } = self.status.run_session( - &model_id, + model_id, PredictIntonationInput { length: ndarray::arr0(length as i64), vowel_phoneme_list: ndarray::arr1(vowel_phoneme_vector), @@ -917,7 +917,7 @@ pub(crate) mod blocking { ); let DecodeOutput { wave: output } = self.status.run_session( - &model_id, + model_id, DecodeInput { f0: ndarray::arr1(&f0_with_padding) .into_shape([length_with_padding, 1]) @@ -1150,11 +1150,11 @@ pub(crate) mod tokio { self.0.status.insert_model(model.header(), model_bytes) } - pub fn unload_voice_model(&self, voice_model_id: &VoiceModelId) -> Result<()> { + pub fn unload_voice_model(&self, voice_model_id: VoiceModelId) -> Result<()> { self.0.unload_voice_model(voice_model_id) } - pub fn is_loaded_voice_model(&self, voice_model_id: &VoiceModelId) -> bool { + pub fn is_loaded_voice_model(&self, voice_model_id: VoiceModelId) -> bool { self.0.is_loaded_voice_model(voice_model_id) } diff --git a/crates/voicevox_core/src/voice_model.rs b/crates/voicevox_core/src/voice_model.rs index 364c8db0a..6277b0046 100644 --- a/crates/voicevox_core/src/voice_model.rs +++ b/crates/voicevox_core/src/voice_model.rs @@ -4,11 +4,13 @@ use anyhow::anyhow; use derive_getters::Getters; +use derive_more::From; use derive_new::new; use easy_ext::ext; use enum_map::EnumMap; use itertools::Itertools as _; use serde::Deserialize; +use uuid::Uuid; use crate::{ error::{LoadModelError, LoadModelErrorKind, LoadModelResult}, @@ -24,7 +26,7 @@ use std::path::{Path, PathBuf}; /// [`VoiceModelId`]の実体。 /// /// [`VoiceModelId`]: VoiceModelId -pub type RawVoiceModelId = String; +pub type RawVoiceModelId = Uuid; pub(crate) type ModelBytesWithInnerIdsByDomain = (Option<(StyleIdToModelInnerId, EnumMap>)>,); @@ -34,6 +36,7 @@ pub(crate) type ModelBytesWithInnerIdsByDomain = PartialEq, Eq, Clone, + Copy, Ord, Hash, PartialOrd, @@ -42,7 +45,9 @@ pub(crate) type ModelBytesWithInnerIdsByDomain = Getters, derive_more::Display, Debug, + From, )] +#[serde(transparent)] pub struct VoiceModelId { raw_voice_model_id: RawVoiceModelId, } @@ -53,9 +58,7 @@ pub struct VoiceModelId { /// モデルの`[u8]`と分けて`Status`に渡す。 #[derive(Clone)] pub(crate) struct VoiceModelHeader { - /// ID。 - pub(crate) id: VoiceModelId, - manifest: Manifest, + pub(crate) manifest: Manifest, /// メタ情報。 /// /// `manifest`が対応していない`StyleType`のスタイルは含まれるべきではない。 @@ -64,12 +67,7 @@ pub(crate) struct VoiceModelHeader { } impl VoiceModelHeader { - fn new( - id: VoiceModelId, - manifest: Manifest, - metas: &[u8], - path: &Path, - ) -> LoadModelResult { + fn new(manifest: Manifest, metas: &[u8], path: &Path) -> LoadModelResult { let metas = serde_json::from_slice::(metas).map_err(|source| LoadModelError { path: path.to_owned(), @@ -94,7 +92,6 @@ impl VoiceModelHeader { })?; Ok(Self { - id, manifest, metas, path: path.to_owned(), @@ -151,8 +148,8 @@ pub(crate) mod blocking { path::Path, }; + use easy_ext::ext; use enum_map::EnumMap; - use nanoid::nanoid; use ouroboros::self_referencing; use rayon::iter::{IntoParallelIterator as _, ParallelIterator as _}; use serde::de::DeserializeOwned; @@ -220,14 +217,13 @@ pub(crate) mod blocking { let reader = BlockingVvmEntryReader::open(path)?; let manifest = reader.read_vvm_json::("manifest.json")?; let metas = &reader.read_vvm_entry(manifest.metas_filename())?; - let id = VoiceModelId::new(nanoid!()); - let header = VoiceModelHeader::new(id, manifest, metas, path)?; + let header = VoiceModelHeader::new(manifest, metas, path)?; Ok(Self { header }) } /// ID。 - pub fn id(&self) -> &VoiceModelId { - &self.header.id + pub fn id(&self) -> VoiceModelId { + self.header.manifest.id } /// メタ情報。 @@ -289,6 +285,13 @@ pub(crate) mod blocking { }) } } + + #[ext(IdRef)] + pub impl VoiceModel { + fn id_ref(&self) -> &VoiceModelId { + &self.header.manifest.id + } + } } pub(crate) mod tokio { @@ -297,7 +300,6 @@ pub(crate) mod tokio { use derive_new::new; use enum_map::EnumMap; use futures::future::{join3, OptionFuture}; - use nanoid::nanoid; use serde::de::DeserializeOwned; use crate::{ @@ -360,14 +362,13 @@ pub(crate) mod tokio { let reader = AsyncVvmEntryReader::open(path.as_ref()).await?; let manifest = reader.read_vvm_json::("manifest.json").await?; let metas = &reader.read_vvm_entry(manifest.metas_filename()).await?; - let id = VoiceModelId::new(nanoid!()); - let header = VoiceModelHeader::new(id, manifest, metas, path.as_ref())?; + let header = VoiceModelHeader::new(manifest, metas, path.as_ref())?; Ok(Self { header }) } /// ID。 - pub fn id(&self) -> &VoiceModelId { - &self.header.id + pub fn id(&self) -> VoiceModelId { + self.header.manifest.id } /// メタ情報。 diff --git a/crates/voicevox_core_c_api/Cargo.toml b/crates/voicevox_core_c_api/Cargo.toml index ad3a65fa7..f10e2a6f4 100644 --- a/crates/voicevox_core_c_api/Cargo.toml +++ b/crates/voicevox_core_c_api/Cargo.toml @@ -24,6 +24,7 @@ chrono = { workspace = true, default-features = false, features = ["clock"] } colorchoice.workspace = true cstr.workspace = true derive-getters.workspace = true +easy-ext.workspace = true futures.workspace = true itertools.workspace = true libc.workspace = true diff --git a/crates/voicevox_core_c_api/include/voicevox_core.h b/crates/voicevox_core_c_api/include/voicevox_core.h index 592ec8abb..da921c869 100644 --- a/crates/voicevox_core_c_api/include/voicevox_core.h +++ b/crates/voicevox_core_c_api/include/voicevox_core.h @@ -277,7 +277,7 @@ typedef struct VoicevoxInitializeOptions { /** * 音声モデルID。 */ -typedef const char *VoicevoxVoiceModelId; +typedef const uint8_t (*VoicevoxVoiceModelId)[16]; /** * スタイルID。 @@ -554,7 +554,7 @@ VoicevoxResultCode voicevox_synthesizer_load_voice_model(const struct VoicevoxSy * * \safety{ * - `synthesizer`は ::voicevox_synthesizer_new で得たものでなければならず、また ::voicevox_synthesizer_delete で解放されていてはいけない。 - * - `model_id`はヌル終端文字列を指し、かつ読み込みについて有効でなければならない。 + * - `model_id`は読み込みについて有効でなければならない。 * } */ #ifdef _WIN32 @@ -589,7 +589,7 @@ bool voicevox_synthesizer_is_gpu_mode(const struct VoicevoxSynthesizer *synthesi * * \safety{ * - `synthesizer`は ::voicevox_synthesizer_new で得たものでなければならず、また ::voicevox_synthesizer_delete で解放されていてはいけない。 - * - `model_id`はヌル終端文字列を指し、かつ読み込みについて有効でなければならない。 + * - `model_id`は読み込みについて有効でなければならない。 * } */ #ifdef _WIN32 diff --git a/crates/voicevox_core_c_api/src/c_impls.rs b/crates/voicevox_core_c_api/src/c_impls.rs index 4e73bf0fb..fe4afcf65 100644 --- a/crates/voicevox_core_c_api/src/c_impls.rs +++ b/crates/voicevox_core_c_api/src/c_impls.rs @@ -5,6 +5,9 @@ use voicevox_core::{InitializeOptions, Result, VoiceModelId}; use crate::{helpers::CApiResult, OpenJtalkRc, VoicevoxSynthesizer, VoicevoxVoiceModel}; +// FIXME: 中身(Rust API)を直接操作するかラッパーメソッド越しにするのかが混在していて、一貫性を +// 欠いている + impl OpenJtalkRc { pub(crate) fn new(open_jtalk_dic_dir: impl AsRef) -> Result { Ok(Self { @@ -28,7 +31,7 @@ impl VoicevoxSynthesizer { Ok(()) } - pub(crate) fn unload_voice_model(&self, model_id: &VoiceModelId) -> Result<()> { + pub(crate) fn unload_voice_model(&self, model_id: VoiceModelId) -> Result<()> { self.synthesizer.unload_voice_model(model_id)?; Ok(()) } @@ -42,8 +45,7 @@ impl VoicevoxSynthesizer { impl VoicevoxVoiceModel { pub(crate) fn from_path(path: impl AsRef) -> Result { let model = voicevox_core::blocking::VoiceModel::from_path(path)?; - let id = CString::new(model.id().raw_voice_model_id().as_str()).unwrap(); let metas = CString::new(serde_json::to_string(model.metas()).unwrap()).unwrap(); - Ok(Self { model, id, metas }) + Ok(Self { model, metas }) } } diff --git a/crates/voicevox_core_c_api/src/compatible_engine.rs b/crates/voicevox_core_c_api/src/compatible_engine.rs index 6755910f5..ae4d21a93 100644 --- a/crates/voicevox_core_c_api/src/compatible_engine.rs +++ b/crates/voicevox_core_c_api/src/compatible_engine.rs @@ -37,10 +37,7 @@ struct VoiceModelSet { static VOICE_MODEL_SET: Lazy = Lazy::new(|| { let all_vvms = get_all_models(); - let model_map: BTreeMap<_, _> = all_vvms - .iter() - .map(|vvm| (vvm.id().clone(), vvm.clone())) - .collect(); + let model_map: BTreeMap<_, _> = all_vvms.iter().map(|vvm| (vvm.id(), vvm.clone())).collect(); let metas = voicevox_core::__internal::interop::merge_metas( all_vvms.iter().flat_map(|vvm| vvm.metas()), ); @@ -48,7 +45,7 @@ static VOICE_MODEL_SET: Lazy = Lazy::new(|| { for vvm in all_vvms.iter() { for meta in vvm.metas().iter() { for style in meta.styles().iter() { - style_model_map.insert(*style.id(), vvm.id().clone()); + style_model_map.insert(*style.id(), vvm.id()); } } } diff --git a/crates/voicevox_core_c_api/src/helpers.rs b/crates/voicevox_core_c_api/src/helpers.rs index 1c163a0d0..ac0cab286 100644 --- a/crates/voicevox_core_c_api/src/helpers.rs +++ b/crates/voicevox_core_c_api/src/helpers.rs @@ -1,5 +1,7 @@ +use easy_ext::ext; use std::{error::Error as _, ffi::CStr, fmt::Debug, iter}; -use voicevox_core::{AudioQueryModel, UserDictWord}; +use uuid::Uuid; +use voicevox_core::{AudioQueryModel, UserDictWord, VoiceModelId}; use thiserror::Error; use tracing::error; @@ -163,6 +165,13 @@ impl Default for VoicevoxSynthesisOptions { } } +#[ext(UuidBytesExt)] +pub(crate) impl uuid::Bytes { + fn to_model_id(self) -> VoiceModelId { + Uuid::from_bytes(self).into() + } +} + impl VoicevoxUserDictWord { pub(crate) unsafe fn try_into_word(&self) -> CApiResult { Ok(UserDictWord::new( diff --git a/crates/voicevox_core_c_api/src/lib.rs b/crates/voicevox_core_c_api/src/lib.rs index fbb0bf6bf..08a54b11a 100644 --- a/crates/voicevox_core_c_api/src/lib.rs +++ b/crates/voicevox_core_c_api/src/lib.rs @@ -12,7 +12,7 @@ mod slice_owner; use self::drop_check::C_STRING_DROP_CHECKER; use self::helpers::{ accent_phrases_to_json, audio_query_model_to_json, ensure_utf8, into_result_code_with_error, - CApiError, + CApiError, UuidBytesExt as _, }; use self::result_code::VoicevoxResultCode; use self::slice_owner::U8_SLICE_OWNER; @@ -30,7 +30,8 @@ use std::sync::{Arc, Once}; use tracing_subscriber::fmt::format::Writer; use tracing_subscriber::EnvFilter; use uuid::Uuid; -use voicevox_core::{AccentPhraseModel, AudioQueryModel, TtsOptions, UserDictWord, VoiceModelId}; +use voicevox_core::__internal::interop::IdRef as _; +use voicevox_core::{AccentPhraseModel, AudioQueryModel, TtsOptions, UserDictWord}; use voicevox_core::{StyleId, SupportedDevices, SynthesisOptions}; fn init_logger_once() { @@ -238,12 +239,11 @@ pub extern "C" fn voicevox_get_version() -> *const c_char { #[derive(Getters)] pub struct VoicevoxVoiceModel { model: voicevox_core::blocking::VoiceModel, - id: CString, metas: CString, } /// 音声モデルID。 -pub type VoicevoxVoiceModelId = *const c_char; +pub type VoicevoxVoiceModelId<'a> = &'a [u8; 16]; /// スタイルID。 /// @@ -285,9 +285,9 @@ pub unsafe extern "C" fn voicevox_voice_model_new_from_path( /// - `model`は ::voicevox_voice_model_new_from_path で得たものでなければならず、また ::voicevox_voice_model_delete で解放されていてはいけない。 /// } #[no_mangle] -pub extern "C" fn voicevox_voice_model_id(model: &VoicevoxVoiceModel) -> VoicevoxVoiceModelId { +pub extern "C" fn voicevox_voice_model_id(model: &VoicevoxVoiceModel) -> VoicevoxVoiceModelId<'_> { init_logger_once(); - model.id().as_ptr() + model.model.id_ref().raw_voice_model_id().as_bytes() } /// ::VoicevoxVoiceModel からメタ情報を取得する。 @@ -399,20 +399,16 @@ pub extern "C" fn voicevox_synthesizer_load_voice_model( /// /// \safety{ /// - `synthesizer`は ::voicevox_synthesizer_new で得たものでなければならず、また ::voicevox_synthesizer_delete で解放されていてはいけない。 -/// - `model_id`はヌル終端文字列を指し、かつ読み込みについて有効でなければならない。 +/// - `model_id`は読み込みについて有効でなければならない。 /// } #[no_mangle] -pub unsafe extern "C" fn voicevox_synthesizer_unload_voice_model( +pub extern "C" fn voicevox_synthesizer_unload_voice_model( synthesizer: &VoicevoxSynthesizer, - model_id: VoicevoxVoiceModelId, + model_id: VoicevoxVoiceModelId<'_>, ) -> VoicevoxResultCode { init_logger_once(); - into_result_code_with_error((|| { - let raw_model_id = ensure_utf8(unsafe { CStr::from_ptr(model_id) })?; - synthesizer - .unload_voice_model(&VoiceModelId::new(raw_model_id.to_string())) - .map_err(Into::into) - })()) + let model_id = model_id.to_model_id(); + into_result_code_with_error(synthesizer.unload_voice_model(model_id).map_err(Into::into)) } /// ハードウェアアクセラレーションがGPUモードか判定する。 @@ -439,21 +435,16 @@ pub extern "C" fn voicevox_synthesizer_is_gpu_mode(synthesizer: &VoicevoxSynthes /// /// \safety{ /// - `synthesizer`は ::voicevox_synthesizer_new で得たものでなければならず、また ::voicevox_synthesizer_delete で解放されていてはいけない。 -/// - `model_id`はヌル終端文字列を指し、かつ読み込みについて有効でなければならない。 +/// - `model_id`は読み込みについて有効でなければならない。 /// } #[no_mangle] -pub unsafe extern "C" fn voicevox_synthesizer_is_loaded_voice_model( +pub extern "C" fn voicevox_synthesizer_is_loaded_voice_model( synthesizer: &VoicevoxSynthesizer, - model_id: VoicevoxVoiceModelId, + model_id: VoicevoxVoiceModelId<'_>, ) -> bool { init_logger_once(); - let Ok(raw_model_id) = ensure_utf8(unsafe { CStr::from_ptr(model_id) }) else { - // 与えられたIDがUTF-8ではない場合、それに対応する`VoicdModel`は確実に存在しない - return false; - }; - synthesizer - .synthesizer() - .is_loaded_voice_model(&VoiceModelId::new(raw_model_id.into())) + let model_id = model_id.to_model_id(); + synthesizer.synthesizer().is_loaded_voice_model(model_id) } /// 今読み込んでいる音声モデルのメタ情報を、JSONで取得する。 diff --git a/crates/voicevox_core_java_api/Cargo.toml b/crates/voicevox_core_java_api/Cargo.toml index 06b2af618..17808660f 100644 --- a/crates/voicevox_core_java_api/Cargo.toml +++ b/crates/voicevox_core_java_api/Cargo.toml @@ -15,6 +15,7 @@ directml = ["voicevox_core/directml"] android_logger.workspace = true chrono = { workspace = true, default-features = false, features = ["clock"] } derive_more.workspace = true +easy-ext.workspace = true jni.workspace = true once_cell.workspace = true serde_json = { workspace = true, features = ["preserve_order"] } diff --git a/crates/voicevox_core_java_api/lib/src/main/java/jp/hiroshiba/voicevoxcore/Synthesizer.java b/crates/voicevox_core_java_api/lib/src/main/java/jp/hiroshiba/voicevoxcore/Synthesizer.java index a3fe0de6c..6ec6d9108 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,7 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.List; +import java.util.UUID; import jp.hiroshiba.voicevoxcore.exceptions.InferenceFailedException; import jp.hiroshiba.voicevoxcore.exceptions.InvalidModelDataException; @@ -65,7 +66,7 @@ public void loadVoiceModel(VoiceModel voiceModel) throws InvalidModelDataExcepti * * @param voiceModelId 読み込みを解除する音声モデルのID。 */ - public void unloadVoiceModel(String voiceModelId) { + public void unloadVoiceModel(UUID voiceModelId) { rsUnloadVoiceModel(voiceModelId); } @@ -75,7 +76,7 @@ public void unloadVoiceModel(String voiceModelId) { * @param voiceModelId 音声モデルのID。 * @return 指定した音声モデルのIDが読み込まれているかどうか。 */ - public boolean isLoadedVoiceModel(String voiceModelId) { + public boolean isLoadedVoiceModel(UUID voiceModelId) { return rsIsLoadedVoiceModel(voiceModelId); } @@ -274,9 +275,9 @@ public TtsConfigurator tts(String text, int styleId) { private native void rsLoadVoiceModel(VoiceModel voiceModel) throws InvalidModelDataException; - private native void rsUnloadVoiceModel(String voiceModelId); + private native void rsUnloadVoiceModel(UUID voiceModelId); - private native boolean rsIsLoadedVoiceModel(String voiceModelId); + private native boolean rsIsLoadedVoiceModel(UUID voiceModelId); @Nonnull private native String rsAudioQueryFromKana(String kana, int styleId) diff --git a/crates/voicevox_core_java_api/lib/src/main/java/jp/hiroshiba/voicevoxcore/VoiceModel.java b/crates/voicevox_core_java_api/lib/src/main/java/jp/hiroshiba/voicevoxcore/VoiceModel.java index 576629515..d8c002f0f 100644 --- a/crates/voicevox_core_java_api/lib/src/main/java/jp/hiroshiba/voicevoxcore/VoiceModel.java +++ b/crates/voicevox_core_java_api/lib/src/main/java/jp/hiroshiba/voicevoxcore/VoiceModel.java @@ -5,13 +5,14 @@ import com.google.gson.annotations.SerializedName; import jakarta.annotation.Nonnull; import jakarta.annotation.Nullable; +import java.util.UUID; /** 音声モデル。 */ public class VoiceModel extends Dll { private long handle; /** ID。 */ - @Nonnull public final String id; + @Nonnull public final UUID id; /** メタ情報。 */ @Nonnull public final SpeakerMeta[] metas; @@ -36,7 +37,7 @@ protected void finalize() throws Throwable { private native void rsFromPath(String modelPath); @Nonnull - private native String rsGetId(); + private native UUID rsGetId(); @Nonnull private native String rsGetMetasJson(); diff --git a/crates/voicevox_core_java_api/src/common.rs b/crates/voicevox_core_java_api/src/common.rs index bdb37f4ff..2550629c3 100644 --- a/crates/voicevox_core_java_api/src/common.rs +++ b/crates/voicevox_core_java_api/src/common.rs @@ -1,7 +1,12 @@ use std::{error::Error as _, iter}; use derive_more::From; -use jni::{objects::JThrowable, JNIEnv}; +use easy_ext::ext; +use jni::{ + objects::{JObject, JThrowable}, + JNIEnv, +}; +use uuid::Uuid; #[macro_export] macro_rules! object { @@ -168,3 +173,23 @@ pub(crate) enum JavaApiError { DeJson(serde_json::Error), } + +#[ext(JNIEnvExt)] +pub(crate) impl JNIEnv<'_> { + fn new_uuid(&mut self, uuid: Uuid) -> jni::errors::Result> { + let uuid = uuid.as_u128(); + let msbs = i64::try_from(uuid >> 64).expect("128 - 64 = 64").into(); + let lsbs = (uuid as i64).into(); + self.new_object("java/util/UUID", "(JJ)V", &[msbs, lsbs]) + } + + fn get_uuid(&mut self, obj: &JObject<'_>) -> jni::errors::Result { + let mut get_bits = |name| { + let v = self.call_method(obj, name, "()J", &[])?.j()?; + Ok::<_, jni::errors::Error>(u128::from(v as u64)) + }; + let msbs = get_bits("getMostSignificantBits")?; + let lsbs = get_bits("getLeastSignificantBits")?; + Ok(Uuid::from_u128((msbs << 64) + lsbs)) + } +} diff --git a/crates/voicevox_core_java_api/src/synthesizer.rs b/crates/voicevox_core_java_api/src/synthesizer.rs index fee5bc132..dc5dc971d 100644 --- a/crates/voicevox_core_java_api/src/synthesizer.rs +++ b/crates/voicevox_core_java_api/src/synthesizer.rs @@ -1,5 +1,5 @@ use crate::{ - common::{throw_if_err, JavaApiError}, + common::{throw_if_err, JNIEnvExt as _, JavaApiError}, enum_object, object, object_type, }; @@ -115,10 +115,10 @@ unsafe extern "system" fn Java_jp_hiroshiba_voicevoxcore_Synthesizer_rsLoadVoice unsafe extern "system" fn Java_jp_hiroshiba_voicevoxcore_Synthesizer_rsUnloadVoiceModel<'local>( env: JNIEnv<'local>, this: JObject<'local>, - model_id: JString<'local>, + model_id: JObject<'local>, ) { throw_if_err(env, (), |env| { - let model_id: String = env.get_string(&model_id)?.into(); + let model_id = env.get_uuid(&model_id)?.into(); let internal = env .get_rust_field::<_, _, Arc>>( @@ -126,7 +126,7 @@ unsafe extern "system" fn Java_jp_hiroshiba_voicevoxcore_Synthesizer_rsUnloadVoi )? .clone(); - internal.unload_voice_model(&voicevox_core::VoiceModelId::new(model_id))?; + internal.unload_voice_model(model_id)?; Ok(()) }) @@ -138,10 +138,10 @@ unsafe extern "system" fn Java_jp_hiroshiba_voicevoxcore_Synthesizer_rsIsLoadedV >( env: JNIEnv<'local>, this: JObject<'local>, - model_id: JString<'local>, + model_id: JObject<'local>, ) -> jboolean { throw_if_err(env, false, |env| { - let model_id: String = env.get_string(&model_id)?.into(); + let model_id = env.get_uuid(&model_id)?.into(); let internal = env .get_rust_field::<_, _, Arc>>( @@ -149,7 +149,7 @@ unsafe extern "system" fn Java_jp_hiroshiba_voicevoxcore_Synthesizer_rsIsLoadedV )? .clone(); - let is_loaded = internal.is_loaded_voice_model(&voicevox_core::VoiceModelId::new(model_id)); + let is_loaded = internal.is_loaded_voice_model(model_id); Ok(is_loaded) }) diff --git a/crates/voicevox_core_java_api/src/voice_model.rs b/crates/voicevox_core_java_api/src/voice_model.rs index 42a20544a..546ab9bf6 100644 --- a/crates/voicevox_core_java_api/src/voice_model.rs +++ b/crates/voicevox_core_java_api/src/voice_model.rs @@ -1,6 +1,6 @@ use std::{borrow::Cow, sync::Arc}; -use crate::common::throw_if_err; +use crate::common::{throw_if_err, JNIEnvExt as _}; use jni::{ objects::{JObject, JString}, sys::jobject, @@ -35,9 +35,7 @@ unsafe extern "system" fn Java_jp_hiroshiba_voicevoxcore_VoiceModel_rsGetId<'loc .get_rust_field::<_, _, Arc>(&this, "handle")? .clone(); - let id = internal.id().raw_voice_model_id(); - - let id = env.new_string(id)?; + let id = env.new_uuid(*internal.id().raw_voice_model_id())?; Ok(id.into_raw()) }) diff --git a/crates/voicevox_core_python_api/python/voicevox_core/_models.py b/crates/voicevox_core_python_api/python/voicevox_core/_models.py index 21a2016fe..f7929fae2 100644 --- a/crates/voicevox_core_python_api/python/voicevox_core/_models.py +++ b/crates/voicevox_core_python_api/python/voicevox_core/_models.py @@ -1,6 +1,7 @@ import dataclasses from enum import Enum from typing import List, NewType, Optional +from uuid import UUID import pydantic @@ -24,13 +25,13 @@ x : str """ -VoiceModelId = NewType("VoiceModelId", str) +VoiceModelId = NewType("VoiceModelId", UUID) """ 音声モデルID。 Parameters ---------- -x : str +x : UUID """ diff --git a/crates/voicevox_core_python_api/python/voicevox_core/_rust/asyncio.pyi b/crates/voicevox_core_python_api/python/voicevox_core/_rust/asyncio.pyi index d8e9f6fe2..468d885ee 100644 --- a/crates/voicevox_core_python_api/python/voicevox_core/_rust/asyncio.pyi +++ b/crates/voicevox_core_python_api/python/voicevox_core/_rust/asyncio.pyi @@ -109,7 +109,7 @@ class Synthesizer: 読み込むモデルのスタイルID。 """ ... - def unload_voice_model(self, voice_model_id: Union[VoiceModelId, str]) -> None: + def unload_voice_model(self, voice_model_id: Union[VoiceModelId, UUID]) -> None: """ 音声モデルの読み込みを解除する。 @@ -119,7 +119,7 @@ class Synthesizer: 音声モデルID。 """ ... - def is_loaded_voice_model(self, voice_model_id: Union[VoiceModelId, str]) -> bool: + def is_loaded_voice_model(self, voice_model_id: Union[VoiceModelId, UUID]) -> bool: """ 指定したvoice_model_idのモデルが読み込まれているか判定する。 diff --git a/crates/voicevox_core_python_api/python/voicevox_core/_rust/blocking.pyi b/crates/voicevox_core_python_api/python/voicevox_core/_rust/blocking.pyi index 5584d68bb..fd09eb8cd 100644 --- a/crates/voicevox_core_python_api/python/voicevox_core/_rust/blocking.pyi +++ b/crates/voicevox_core_python_api/python/voicevox_core/_rust/blocking.pyi @@ -104,7 +104,7 @@ class Synthesizer: 読み込むモデルのスタイルID。 """ ... - def unload_voice_model(self, voice_model_id: Union[VoiceModelId, str]) -> None: + def unload_voice_model(self, voice_model_id: Union[VoiceModelId, UUID]) -> None: """ 音声モデルの読み込みを解除する。 @@ -114,7 +114,7 @@ class Synthesizer: 音声モデルID。 """ ... - def is_loaded_voice_model(self, voice_model_id: Union[VoiceModelId, str]) -> bool: + def is_loaded_voice_model(self, voice_model_id: Union[VoiceModelId, UUID]) -> bool: """ 指定したvoice_model_idのモデルが読み込まれているか判定する。 diff --git a/crates/voicevox_core_python_api/src/convert.rs b/crates/voicevox_core_python_api/src/convert.rs index 3cee4186b..f40b2d449 100644 --- a/crates/voicevox_core_python_api/src/convert.rs +++ b/crates/voicevox_core_python_api/src/convert.rs @@ -149,6 +149,7 @@ pub(crate) fn to_rust_uuid(ob: &PyAny) -> PyResult { let uuid = ob.getattr("hex")?.extract::()?; uuid.parse::().into_py_value_result() } +// FIXME: `to_object`は必要無いのでは? pub(crate) fn to_py_uuid(py: Python<'_>, uuid: Uuid) -> PyResult { let uuid = uuid.hyphenated().to_string(); let uuid = py.import("uuid")?.call_method1("UUID", (uuid,))?; diff --git a/crates/voicevox_core_python_api/src/lib.rs b/crates/voicevox_core_python_api/src/lib.rs index 492d18f0e..81a1bbc95 100644 --- a/crates/voicevox_core_python_api/src/lib.rs +++ b/crates/voicevox_core_python_api/src/lib.rs @@ -154,13 +154,13 @@ mod blocking { use camino::Utf8PathBuf; use pyo3::{ pyclass, pymethods, - types::{IntoPyDict as _, PyBytes, PyDict, PyList, PyString}, + types::{IntoPyDict as _, PyBytes, PyDict, PyList}, PyAny, PyObject, PyRef, PyResult, Python, }; use uuid::Uuid; use voicevox_core::{ AccelerationMode, AudioQueryModel, InitializeOptions, StyleId, SynthesisOptions, - TtsOptions, UserDictWord, VoiceModelId, + TtsOptions, UserDictWord, }; use crate::{convert::VoicevoxCoreResultExt as _, Closable}; @@ -180,8 +180,9 @@ mod blocking { } #[getter] - fn id(&self) -> &str { - self.model.id().raw_voice_model_id() + fn id(&self, py: Python<'_>) -> PyResult { + let id = *self.model.id().raw_voice_model_id(); + crate::convert::to_py_uuid(py, id) } #[getter] @@ -289,23 +290,26 @@ mod blocking { .into_py_result(py) } - fn unload_voice_model(&mut self, voice_model_id: &str, py: Python<'_>) -> PyResult<()> { + fn unload_voice_model( + &mut self, + #[pyo3(from_py_with = "crate::convert::to_rust_uuid")] voice_model_id: Uuid, + py: Python<'_>, + ) -> PyResult<()> { self.synthesizer .get()? - .unload_voice_model(&VoiceModelId::new(voice_model_id.to_string())) + .unload_voice_model(voice_model_id.into()) .into_py_result(py) } // C APIの挙動と一貫性を持たせる。 - fn is_loaded_voice_model(&self, voice_model_id: &PyString) -> PyResult { - let Ok(voice_model_id) = voice_model_id.to_str() else { - // 与えられたIDがUTF-8ではない場合、それに対応する`VoicdModel`は確実に存在しない - return Ok(false); - }; + fn is_loaded_voice_model( + &self, + #[pyo3(from_py_with = "crate::convert::to_rust_uuid")] voice_model_id: Uuid, + ) -> PyResult { Ok(self .synthesizer .get()? - .is_loaded_voice_model(&VoiceModelId::new(voice_model_id.to_string()))) + .is_loaded_voice_model(voice_model_id.into())) } fn audio_query_from_kana<'py>( @@ -579,13 +583,13 @@ mod asyncio { use camino::Utf8PathBuf; use pyo3::{ pyclass, pymethods, - types::{IntoPyDict as _, PyBytes, PyDict, PyList, PyString}, + types::{IntoPyDict as _, PyBytes, PyDict, PyList}, PyAny, PyObject, PyRef, PyResult, Python, ToPyObject as _, }; use uuid::Uuid; use voicevox_core::{ AccelerationMode, AudioQueryModel, InitializeOptions, StyleId, SynthesisOptions, - TtsOptions, UserDictWord, VoiceModelId, + TtsOptions, UserDictWord, }; use crate::{convert::VoicevoxCoreResultExt as _, Closable}; @@ -608,8 +612,9 @@ mod asyncio { } #[getter] - fn id(&self) -> &str { - self.model.id().raw_voice_model_id() + fn id(&self, py: Python<'_>) -> PyResult { + let id = *self.model.id().raw_voice_model_id(); + crate::convert::to_py_uuid(py, id) } #[getter] @@ -725,23 +730,26 @@ mod asyncio { }) } - fn unload_voice_model(&mut self, voice_model_id: &str, py: Python<'_>) -> PyResult<()> { + fn unload_voice_model( + &mut self, + #[pyo3(from_py_with = "crate::convert::to_rust_uuid")] voice_model_id: Uuid, + py: Python<'_>, + ) -> PyResult<()> { self.synthesizer .get()? - .unload_voice_model(&VoiceModelId::new(voice_model_id.to_string())) + .unload_voice_model(voice_model_id.into()) .into_py_result(py) } // C APIの挙動と一貫性を持たせる。 - fn is_loaded_voice_model(&self, voice_model_id: &PyString) -> PyResult { - let Ok(voice_model_id) = voice_model_id.to_str() else { - // 与えられたIDがUTF-8ではない場合、それに対応する`VoicdModel`は確実に存在しない - return Ok(false); - }; + fn is_loaded_voice_model( + &self, + #[pyo3(from_py_with = "crate::convert::to_rust_uuid")] voice_model_id: Uuid, + ) -> PyResult { Ok(self .synthesizer .get()? - .is_loaded_voice_model(&VoiceModelId::new(voice_model_id.to_string()))) + .is_loaded_voice_model(voice_model_id.into())) } fn audio_query_from_kana<'py>( diff --git a/model/sample.vvm/manifest.json b/model/sample.vvm/manifest.json index 2c6721d08..c6ccedd0d 100644 --- a/model/sample.vvm/manifest.json +++ b/model/sample.vvm/manifest.json @@ -1,5 +1,6 @@ { "manifest_version": "0.0.0", + "id": "018fa5b1-146c-71e9-b523-6f6dabcf05fe", "metas_filename": "metas.json", "talk": { "predict_duration_filename": "predict_duration.onnx", From f32c8023ed416783998171b5142420a596150d52 Mon Sep 17 00:00:00 2001 From: Ryo Yamashita Date: Fri, 24 May 2024 00:25:51 +0900 Subject: [PATCH 2/3] =?UTF-8?q?=E4=B8=8D=E8=A6=81=E3=81=A8=E3=81=AA?= =?UTF-8?q?=E3=81=A3=E3=81=9F=E3=82=B3=E3=83=A1=E3=83=B3=E3=83=88=E3=82=92?= =?UTF-8?q?=E5=89=8A=E9=99=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- crates/voicevox_core_python_api/src/lib.rs | 2 -- 1 file changed, 2 deletions(-) diff --git a/crates/voicevox_core_python_api/src/lib.rs b/crates/voicevox_core_python_api/src/lib.rs index 81a1bbc95..e43fa4476 100644 --- a/crates/voicevox_core_python_api/src/lib.rs +++ b/crates/voicevox_core_python_api/src/lib.rs @@ -301,7 +301,6 @@ mod blocking { .into_py_result(py) } - // C APIの挙動と一貫性を持たせる。 fn is_loaded_voice_model( &self, #[pyo3(from_py_with = "crate::convert::to_rust_uuid")] voice_model_id: Uuid, @@ -741,7 +740,6 @@ mod asyncio { .into_py_result(py) } - // C APIの挙動と一貫性を持たせる。 fn is_loaded_voice_model( &self, #[pyo3(from_py_with = "crate::convert::to_rust_uuid")] voice_model_id: Uuid, From 4b906cd2053df504288814525c346bdcda2796b2 Mon Sep 17 00:00:00 2001 From: Ryo Yamashita Date: Fri, 24 May 2024 22:11:54 +0900 Subject: [PATCH 3/3] =?UTF-8?q?UUID=E3=82=92=E5=88=86=E8=A7=A3=E3=81=99?= =?UTF-8?q?=E3=82=8B=E9=83=A8=E5=88=86=E3=82=92=E4=BF=AE=E6=AD=A3=E3=81=97?= =?UTF-8?q?=E3=80=81=E3=83=86=E3=82=B9=E3=83=88=E3=82=82=E8=BF=BD=E5=8A=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 以下のためuuidもアップデートする。 --- Cargo.lock | 6 ++- Cargo.toml | 2 +- crates/voicevox_core_java_api/Cargo.toml | 2 + crates/voicevox_core_java_api/src/common.rs | 46 +++++++++++++++++---- 4 files changed, 44 insertions(+), 12 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 6d947deb2..d3846b8a0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3512,9 +3512,9 @@ checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a" [[package]] name = "uuid" -version = "1.4.0" +version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d023da39d1fde5a8a3fe1f3e01ca9632ada0a63e9797de55a879d6e2236277be" +checksum = "a183cf7feeba97b4dd1c0d46788634f6221d87fa961b305bed08c851829efcc0" dependencies = [ "getrandom", "serde", @@ -3655,6 +3655,8 @@ dependencies = [ "easy-ext", "jni", "once_cell", + "pretty_assertions", + "rstest", "serde_json", "tracing", "tracing-subscriber", diff --git a/Cargo.toml b/Cargo.toml index ec32522f6..4ea3d3757 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -80,7 +80,7 @@ tracing = "0.1.37" tracing-subscriber = "0.3.16" typetag = "0.2.5" url = "2.3.0" -uuid = "1.4.0" +uuid = "1.6.1" voicevox_core = { path = "crates/voicevox_core" } windows = "0.43.0" zip = "0.6.3" diff --git a/crates/voicevox_core_java_api/Cargo.toml b/crates/voicevox_core_java_api/Cargo.toml index 17808660f..b39a98b2a 100644 --- a/crates/voicevox_core_java_api/Cargo.toml +++ b/crates/voicevox_core_java_api/Cargo.toml @@ -18,6 +18,8 @@ derive_more.workspace = true easy-ext.workspace = true jni.workspace = true once_cell.workspace = true +pretty_assertions = "1.3.0" +rstest.workspace = true serde_json = { workspace = true, features = ["preserve_order"] } tracing = { workspace = true, features = ["log"] } tracing-subscriber = { workspace = true, features = ["env-filter"] } diff --git a/crates/voicevox_core_java_api/src/common.rs b/crates/voicevox_core_java_api/src/common.rs index 2550629c3..6e13cee89 100644 --- a/crates/voicevox_core_java_api/src/common.rs +++ b/crates/voicevox_core_java_api/src/common.rs @@ -177,19 +177,47 @@ pub(crate) enum JavaApiError { #[ext(JNIEnvExt)] pub(crate) impl JNIEnv<'_> { fn new_uuid(&mut self, uuid: Uuid) -> jni::errors::Result> { - let uuid = uuid.as_u128(); - let msbs = i64::try_from(uuid >> 64).expect("128 - 64 = 64").into(); - let lsbs = (uuid as i64).into(); - self.new_object("java/util/UUID", "(JJ)V", &[msbs, lsbs]) + let (msbs, lsbs) = split_uuid(uuid); + self.new_object("java/util/UUID", "(JJ)V", &[msbs.into(), lsbs.into()]) } fn get_uuid(&mut self, obj: &JObject<'_>) -> jni::errors::Result { - let mut get_bits = |name| { - let v = self.call_method(obj, name, "()J", &[])?.j()?; - Ok::<_, jni::errors::Error>(u128::from(v as u64)) - }; + let mut get_bits = |method_name| self.call_method(obj, method_name, "()J", &[])?.j(); let msbs = get_bits("getMostSignificantBits")?; let lsbs = get_bits("getLeastSignificantBits")?; - Ok(Uuid::from_u128((msbs << 64) + lsbs)) + Ok(construct_uuid(msbs, lsbs)) + } +} + +fn split_uuid(uuid: Uuid) -> (i64, i64) { + let uuid = uuid.as_u128(); + let msbs = (uuid >> 64) as _; + let lsbs = uuid as _; + (msbs, lsbs) +} + +fn construct_uuid(msbs: i64, lsbs: i64) -> Uuid { + return Uuid::from_u128((to_u128(msbs) << 64) + to_u128(lsbs)); + + fn to_u128(bits: i64) -> u128 { + (bits as u64).into() + } +} + +#[cfg(test)] +mod tests { + use pretty_assertions::assert_eq; + use rstest::rstest; + use uuid::{uuid, Uuid}; + + #[rstest] + #[case(uuid!("a1a2a3a4-b1b2-c1c2-d1d2-e1e2e3e4e5e6"))] + #[case(uuid!("00000000-0000-0000-0000-000000000000"))] + #[case(uuid!("00000000-0000-0000-ffff-ffffffffffff"))] + #[case(uuid!("ffffffff-ffff-ffff-0000-000000000000"))] + #[case(uuid!("ffffffff-ffff-ffff-ffff-ffffffffffff"))] + fn uuid_conversion_works(#[case] uuid: Uuid) { + let (msbs, lsbs) = super::split_uuid(uuid); + assert_eq!(uuid, super::construct_uuid(msbs, lsbs)); } }