Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

project-vvm-async-api #497

Merged
merged 20 commits into from
Jul 22, 2023
Merged
Show file tree
Hide file tree
Changes from 19 commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
fb24f4f
新クラス設計API (#370)
qwerty2501 May 22, 2023
14aa242
[project-vvm-async-api] ドキュメントの表記ゆれを解消 (#501)
qryxip May 25, 2023
486d3f4
[project-vvm-async-api] C/Python APIクレート側のバージョンを出す (#507)
qryxip Jun 2, 2023
38549d3
[project-vvm-async-api] `get_supported_devices_json`をfallibleに (#502)
qryxip Jun 6, 2023
f4b502a
[project-vvm-async-api] いくつかのC関数を定数にする (#503)
qryxip Jun 7, 2023
2e58dcd
[project-vvm-async-api] ZSTにポインタキャストして提供するのをやめる (#512)
qryxip Jun 9, 2023
d193856
[project-vvm-async-api] `extern "C"`の生ポインタをABI互換のに置き換え (#514)
qryxip Jun 9, 2023
dfbb533
[project-vvm-async-api] `$OUT_DIR`を使うものをtest_utilクレートに移動 (#515)
qryxip Jun 10, 2023
40f6662
Merge branch 'main' into HEAD
qryxip Jun 12, 2023
5f6ee44
[project-vvm-async-api] mainをマージする (#516)
qryxip Jun 12, 2023
2fe08c4
[project-vvm-async-api] mainをsquashせずにマージする (#520)
Hiroshiba Jun 13, 2023
ba553fd
[project-vvm-async-api] Fix up #500 (#521)
qryxip Jun 13, 2023
f58c166
[project-vvm-async-api] whlに"modelディレクトリ"を埋め込むのをやめる (#522)
qryxip Jun 13, 2023
0a0cf99
[project-vvm-async-api] `voicevox_{,synthesizer_}is_loaded_voice_mode…
qryxip Jun 13, 2023
a00ff98
[project-vvm-async-api] "buffer"をRustの世界で保持し続ける (#525)
qryxip Jun 20, 2023
b8b8484
[project-vvm-async-api] `output_`系引数がunalignedであることを許す (#534)
qryxip Jul 1, 2023
fa45b51
[project-vvm-async-api] Fix up #534 (#535)
qryxip Jul 2, 2023
6e31789
Merge branch 'main' into project-vvm-async-api-merge-main-2nd
qryxip Jul 3, 2023
d9aa7a8
[project-vvm-async-api] mainをマージする (#536)
Hiroshiba Jul 4, 2023
7a81862
[project-vvm-async-api] 工事中の案内を書く (#542)
qryxip Jul 17, 2023
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
407 changes: 291 additions & 116 deletions Cargo.lock

Large diffs are not rendered by default.

7 changes: 6 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -10,18 +10,23 @@ members = [

[workspace.dependencies]
anyhow = "1.0.65"
async_zip = { version = "0.0.11", features = ["full"] }
clap = { version = "4.0.10", features = ["derive"] }
const-default = { version = "1.0.0", features = ["derive"] }
easy-ext = "1.0.1"
fs-err = { version = "2.9.0", features = ["tokio"] }
once_cell = "1.15.0"
regex = "1.6.0"
serde = { version = "1.0.145", features = ["derive"] }
serde_json = "1.0.85"
serde_json = { version = "1.0.85", features = ["preserve_order"] }
strum = { version = "0.24.1", features = ["derive"] }
test_util = { path = "crates/test_util" }
thiserror = "1.0.37"
tracing = { version = "0.1.37", features = ["log"] }
tracing-subscriber = { version = "0.3.16", features = ["env-filter"] }
voicevox_core = { path = "crates/voicevox_core" }
tokio = { version = "1.25.0", features = ["rt", "rt-multi-thread", "macros", "sync"] }
derive-getters = "0.2.0"

# FIXME: iOS対応のpull request(https://github.com/wesleywiser/process_path/pull/16)がマージされる見込みが無いため
[workspace.dependencies.process_path]
Expand Down
4 changes: 2 additions & 2 deletions crates/download/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,8 @@ once_cell.workspace = true
platforms = "3.0.2"
rayon = "1.6.1"
reqwest = { version = "0.11.13", default-features = false, features = ["rustls-tls", "stream"] }
strum = { version = "0.24.1", features = ["derive"] }
tokio = { version = "1.24.1", features = ["macros", "rt-multi-thread", "sync"] }
strum.workspace = true
tokio.workspace = true
tracing.workspace = true
tracing-subscriber.workspace = true
url = "2.3.0"
Expand Down
4 changes: 3 additions & 1 deletion crates/test_util/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,11 @@ edition.workspace = true
publish.workspace = true

[dependencies]
async_zip.workspace = true
once_cell.workspace = true
serde.workspace = true
serde_json.workspace = true
once_cell.workspace = true
tokio.workspace = true

[build-dependencies]
anyhow.workspace = true
Expand Down
57 changes: 57 additions & 0 deletions crates/test_util/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,16 @@
mod typing;

use async_zip::{write::ZipFileWriter, Compression, ZipEntryBuilder};
use once_cell::sync::Lazy;
use std::{
collections::HashMap,
path::{Path, PathBuf},
};
use tokio::{
fs::{self, File},
io::{AsyncReadExt, AsyncWriteExt},
sync::Mutex,
};
pub use typing::*;

pub const OPEN_JTALK_DIC_DIR: &str = concat!(
Expand All @@ -15,3 +26,49 @@ pub const EXAMPLE_DATA_JSON: &str = include_str!(concat!(
pub static EXAMPLE_DATA: Lazy<ExampleData> = Lazy::new(|| {
serde_json::from_str(EXAMPLE_DATA_JSON).expect("failed to parse example_data.json")
});

static PATH_MUTEX: Lazy<Mutex<HashMap<PathBuf, Mutex<()>>>> =
Lazy::new(|| Mutex::new(HashMap::default()));

pub async fn convert_zip_vvm(dir: impl AsRef<Path>) -> PathBuf {
let dir = dir.as_ref();
let output_file_name = dir.file_name().unwrap().to_str().unwrap().to_owned() + ".vvm";

let out_file_path = PathBuf::from(env!("OUT_DIR"))
.join("test_data/models/")
.join(output_file_name);
let mut path_map = PATH_MUTEX.lock().await;
if !path_map.contains_key(&out_file_path) {
path_map.insert(out_file_path.clone(), Mutex::new(()));
}
let _m = path_map.get(&out_file_path).unwrap().lock().await;

if !out_file_path.exists() {
fs::create_dir_all(out_file_path.parent().unwrap())
.await
.unwrap();
let mut out_file = File::create(&out_file_path).await.unwrap();
let mut writer = ZipFileWriter::new(&mut out_file);

for entry in dir.read_dir().unwrap().flatten() {
let entry_builder = ZipEntryBuilder::new(
entry
.path()
.file_name()
.unwrap()
.to_str()
.unwrap()
.to_string(),
Compression::Deflate,
);
let mut entry_writer = writer.write_entry_stream(entry_builder).await.unwrap();
let mut file = File::open(entry.path()).await.unwrap();
let mut buf = Vec::with_capacity(entry.metadata().unwrap().len() as usize);
file.read_to_end(&mut buf).await.unwrap();
entry_writer.write_all(&buf).await.unwrap();
entry_writer.close().await.unwrap();
}
writer.close().await.unwrap();
}
out_file_path
}
11 changes: 10 additions & 1 deletion crates/voicevox_core/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -10,17 +10,24 @@ directml = ["onnxruntime/directml"]

[dependencies]
anyhow.workspace = true
async_zip.workspace = true
cfg-if = "1.0.0"
derive-getters = "0.2.0"
const-default.workspace = true
derive-getters.workspace = true
derive-new = "0.5.9"
duplicate = "1.0.0"
easy-ext.workspace = true
fs-err.workspace = true
futures = "0.3.26"
nanoid = "0.4.0"
once_cell.workspace = true
process_path.workspace = true
regex.workspace = true
serde.workspace = true
serde_json.workspace = true
strum.workspace = true
thiserror.workspace = true
tokio.workspace = true
tracing.workspace = true

[dependencies.onnxruntime]
Expand All @@ -32,9 +39,11 @@ git = "https://github.com/VOICEVOX/open_jtalk-rs.git"
rev = "d766a52bad4ccafe18597e57bd6842f59dca881e"

[dev-dependencies]
flate2 = "1.0.24"
heck = "0.4.0"
pretty_assertions = "1.3.0"
rstest = "0.15.0"
tar = "0.4.38"
test_util.workspace = true

[target."cfg(windows)".dependencies]
Expand Down
49 changes: 49 additions & 0 deletions crates/voicevox_core/src/devices.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
use serde::{Deserialize, Serialize};

use super::*;

#[derive(Getters, Debug, Serialize, Deserialize)]
pub struct SupportedDevices {
cpu: bool,
cuda: bool,
dml: bool,
}

impl SupportedDevices {
/// サポートされているデバイス情報を取得する
pub fn create() -> Result<Self> {
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()))?
.iter()
{
match provider.as_str() {
"CUDAExecutionProvider" => cuda_support = true,
"DmlExecutionProvider" => dml_support = true,
_ => {}
}
}

Ok(SupportedDevices {
cpu: true,
cuda: cuda_support,
dml: dml_support,
})
}

pub fn to_json(&self) -> serde_json::Value {
serde_json::to_value(self).expect("should not fail")
}
}

#[cfg(test)]
mod tests {
use super::*;
#[rstest]
fn supported_devices_create_works() {
let result = SupportedDevices::create();
// 環境によって結果が変わるので、関数呼び出しが成功するかどうかの確認のみ行う
assert!(result.is_ok(), "{result:?}");
}
}
2 changes: 1 addition & 1 deletion crates/voicevox_core/src/engine/full_context_label.rs
Original file line number Diff line number Diff line change
Expand Up @@ -306,7 +306,7 @@ impl Utterance {
}

pub fn extract_full_context_label(
open_jtalk: &mut open_jtalk::OpenJtalk,
open_jtalk: &open_jtalk::OpenJtalk,
text: impl AsRef<str>,
) -> Result<Self> {
let labels = open_jtalk.extract_fullcontext(text)?;
Expand Down
97 changes: 62 additions & 35 deletions crates/voicevox_core/src/engine/open_jtalk.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,12 @@
use std::path::{Path, PathBuf};
use std::{
path::{Path, PathBuf},
sync::Mutex,
};

use ::open_jtalk::*;

use crate::Error;

#[derive(thiserror::Error, Debug)]
pub enum OpenJtalkError {
#[error("open_jtalk load error")]
Expand All @@ -17,55 +22,74 @@ pub enum OpenJtalkError {
pub type Result<T> = std::result::Result<T, OpenJtalkError>;

pub struct OpenJtalk {
resources: Mutex<Resources>,
dict_loaded: bool,
}

struct Resources {
mecab: ManagedResource<Mecab>,
njd: ManagedResource<Njd>,
jpcommon: ManagedResource<JpCommon>,
dict_loaded: bool,
}

#[allow(unsafe_code)]
unsafe impl Send for Resources {}

impl OpenJtalk {
pub fn initialize() -> Self {
pub fn new_without_dic() -> Self {
Self {
mecab: ManagedResource::initialize(),
njd: ManagedResource::initialize(),
jpcommon: ManagedResource::initialize(),
resources: Mutex::new(Resources {
mecab: ManagedResource::initialize(),
njd: ManagedResource::initialize(),
jpcommon: ManagedResource::initialize(),
}),
dict_loaded: false,
}
}

pub fn extract_fullcontext(&mut self, text: impl AsRef<str>) -> Result<Vec<String>> {
let result = self.extract_fullcontext_non_reflesh(text);
self.jpcommon.refresh();
self.njd.refresh();
self.mecab.refresh();
result
pub fn new_with_initialize(
open_jtalk_dict_dir: impl AsRef<Path>,
) -> crate::result::Result<Self> {
let mut s = Self::new_without_dic();
s.load(open_jtalk_dict_dir)
.map_err(|_| Error::NotLoadedOpenjtalkDict)?;
Ok(s)
}

fn extract_fullcontext_non_reflesh(&mut self, text: impl AsRef<str>) -> Result<Vec<String>> {
pub fn extract_fullcontext(&self, text: impl AsRef<str>) -> Result<Vec<String>> {
let Resources {
mecab,
njd,
jpcommon,
} = &mut *self.resources.lock().unwrap();

jpcommon.refresh();
njd.refresh();
mecab.refresh();

let mecab_text =
text2mecab(text.as_ref()).map_err(|e| OpenJtalkError::ExtractFullContext {
text: text.as_ref().into(),
source: Some(e.into()),
})?;
if self.mecab.analysis(mecab_text) {
self.njd.mecab2njd(
self.mecab
if mecab.analysis(mecab_text) {
njd.mecab2njd(
mecab
.get_feature()
.ok_or(OpenJtalkError::ExtractFullContext {
text: text.as_ref().into(),
source: None,
})?,
self.mecab.get_size(),
mecab.get_size(),
);
self.njd.set_pronunciation();
self.njd.set_digit();
self.njd.set_accent_phrase();
self.njd.set_accent_type();
self.njd.set_unvoiced_vowel();
self.njd.set_long_vowel();
self.jpcommon.njd2jpcommon(&self.njd);
self.jpcommon.make_label();
self.jpcommon
njd.set_pronunciation();
njd.set_digit();
njd.set_accent_phrase();
njd.set_accent_type();
njd.set_unvoiced_vowel();
njd.set_long_vowel();
jpcommon.njd2jpcommon(njd);
jpcommon.make_label();
jpcommon
.get_label_feature_to_iter()
.ok_or_else(|| OpenJtalkError::ExtractFullContext {
text: text.as_ref().into(),
Expand All @@ -80,15 +104,20 @@ impl OpenJtalk {
}
}

pub fn load(&mut self, mecab_dict_dir: impl AsRef<Path>) -> Result<()> {
let result = self.mecab.load(mecab_dict_dir.as_ref());
fn load(&mut self, open_jtalk_dict_dir: impl AsRef<Path>) -> Result<()> {
let result = self
.resources
.lock()
.unwrap()
.mecab
.load(open_jtalk_dict_dir.as_ref());
if result {
self.dict_loaded = true;
Ok(())
} else {
self.dict_loaded = false;
Err(OpenJtalkError::Load {
mecab_dict_dir: mecab_dict_dir.as_ref().into(),
mecab_dict_dir: open_jtalk_dict_dir.as_ref().into(),
})
}
}
Expand All @@ -101,7 +130,7 @@ impl OpenJtalk {
#[cfg(test)]
mod tests {
use super::*;
use test_util::OPEN_JTALK_DIC_DIR;
use ::test_util::OPEN_JTALK_DIC_DIR;

use crate::{macros::tests::assert_debug_fmt_eq, *};

Expand Down Expand Up @@ -196,8 +225,7 @@ mod tests {
#[case("",Err(OpenJtalkError::ExtractFullContext{text:"".into(),source:None}))]
#[case("こんにちは、ヒホです。", Ok(testdata_hello_hiho()))]
fn extract_fullcontext_works(#[case] text: &str, #[case] expected: super::Result<Vec<String>>) {
let mut open_jtalk = OpenJtalk::initialize();
open_jtalk.load(OPEN_JTALK_DIC_DIR).unwrap();
let open_jtalk = OpenJtalk::new_with_initialize(OPEN_JTALK_DIC_DIR).unwrap();
let result = open_jtalk.extract_fullcontext(text);
assert_debug_fmt_eq!(expected, result);
}
Expand All @@ -208,8 +236,7 @@ mod tests {
#[case] text: &str,
#[case] expected: super::Result<Vec<String>>,
) {
let mut open_jtalk = OpenJtalk::initialize();
open_jtalk.load(OPEN_JTALK_DIC_DIR).unwrap();
let open_jtalk = OpenJtalk::new_with_initialize(OPEN_JTALK_DIC_DIR).unwrap();
for _ in 0..10 {
let result = open_jtalk.extract_fullcontext(text);
assert_debug_fmt_eq!(expected, result);
Expand Down
Loading