Skip to content

Commit

Permalink
Merge branch 'main' into add-morphing
Browse files Browse the repository at this point in the history
  • Loading branch information
qryxip committed Feb 10, 2024
2 parents b6d81fa + 0f2c3b2 commit 3710699
Show file tree
Hide file tree
Showing 11 changed files with 328 additions and 56 deletions.
18 changes: 10 additions & 8 deletions .github/workflows/download_test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,16 @@ on:
description: "テスト対象のコアのバージョン。無指定時はprerelease込みの最新release。"
type: string
required: false
push:
branches:
- main
pull_request:
paths:
- "Cargo.*"
- "crates/downloader/**"
- ".github/workflows/download_test.yml"
# 新しいビルドができるまで、このworkflowが壊れて動かないことを許容する
# https://github.com/VOICEVOX/voicevox_core/issues/741#issuecomment-1935303742
#push:
# branches:
# - main
#pull_request:
# paths:
# - "Cargo.*"
# - "crates/downloader/**"
# - ".github/workflows/download_test.yml"

env:
VERSION: ${{ inputs.version || 'prerelease-latest' }}
Expand Down
2 changes: 1 addition & 1 deletion crates/voicevox_core/src/__internal/interop.rs
Original file line number Diff line number Diff line change
@@ -1 +1 @@
pub use crate::synthesizer::blocking::PerformInference;
pub use crate::{metas::merge as merge_metas, synthesizer::blocking::PerformInference};
21 changes: 12 additions & 9 deletions crates/voicevox_core/src/engine/open_jtalk.rs
Original file line number Diff line number Diff line change
Expand Up @@ -128,9 +128,12 @@ pub(crate) mod blocking {
}

impl Inner {
// FIXME: 中断可能にする
// TODO: 中断可能にする
pub(super) fn use_user_dict(&self, words: &str) -> crate::result::Result<()> {
let result = {
// 空の辞書を読み込もうとするとクラッシュするのでユーザー辞書なしでロード
if words.is_empty() {
self.load_with_userdic(None)
} else {
// ユーザー辞書用のcsvを作成
let mut temp_csv =
NamedTempFile::new().map_err(|e| ErrorRepr::UseUserDict(e.into()))?;
Expand Down Expand Up @@ -158,17 +161,17 @@ pub(crate) mod blocking {
"-q",
]);

let Resources { mecab, .. } = &mut *self.resources.lock().unwrap();
self.load_with_userdic(Some(temp_dict_path.as_ref()))
}
}
fn load_with_userdic(&self, dict_path: Option<&Path>) -> crate::result::Result<()> {
let Resources { mecab, .. } = &mut *self.resources.lock().unwrap();

mecab.load_with_userdic(self.dict_dir.as_ref(), Some(Path::new(&temp_dict_path)))
};
let result = mecab.load_with_userdic(self.dict_dir.as_ref(), dict_path);

if !result {
return Err(
ErrorRepr::UseUserDict(anyhow!("辞書のコンパイルに失敗しました")).into(),
);
return Err(ErrorRepr::UseUserDict(anyhow!("辞書を読み込めませんでした。")).into());
}

Ok(())
}
}
Expand Down
43 changes: 25 additions & 18 deletions crates/voicevox_core/src/infer/status.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,14 @@ use std::{
use anyhow::bail;
use educe::Educe;
use enum_map::{Enum as _, EnumMap};
use indexmap::IndexMap;
use itertools::{iproduct, Itertools as _};

use crate::{
error::{ErrorRepr, LoadModelError, LoadModelErrorKind, LoadModelResult},
infer::{InferenceOperation, ParamInfo},
manifest::ModelInnerId,
metas::{SpeakerMeta, StyleId, StyleMeta, VoiceModelMeta},
metas::{self, SpeakerMeta, StyleId, StyleMeta, VoiceModelMeta},
voice_model::{VoiceModelHeader, VoiceModelId},
Result,
};
Expand Down Expand Up @@ -119,7 +120,7 @@ impl<R: InferenceRuntime, D: InferenceDomain> Status<R, D> {
#[derive(Educe)]
#[educe(Default(bound = "R: InferenceRuntime, D: InferenceDomain"))]
struct LoadedModels<R: InferenceRuntime, D: InferenceDomain>(
BTreeMap<VoiceModelId, LoadedModel<R, D>>,
IndexMap<VoiceModelId, LoadedModel<R, D>>,
);

struct LoadedModel<R: InferenceRuntime, D: InferenceDomain> {
Expand All @@ -130,11 +131,7 @@ struct LoadedModel<R: InferenceRuntime, D: InferenceDomain> {

impl<R: InferenceRuntime, D: InferenceDomain> LoadedModels<R, D> {
fn metas(&self) -> VoiceModelMeta {
self.0
.values()
.flat_map(|LoadedModel { metas, .. }| metas)
.cloned()
.collect()
metas::merge(self.0.values().flat_map(|LoadedModel { metas, .. }| metas))
}

fn ids_for(&self, style_id: StyleId) -> Result<(VoiceModelId, ModelInnerId)> {
Expand Down Expand Up @@ -184,20 +181,29 @@ impl<R: InferenceRuntime, D: InferenceDomain> LoadedModels<R, D> {
///
/// # Errors
///
/// 音声モデルIDかスタイルIDが`model_header`と重複するとき、エラーを返す。
/// 次の場合にエラーを返す。
///
/// - 音声モデルIDかスタイルIDが`model_header`と重複するとき
fn ensure_acceptable(&self, model_header: &VoiceModelHeader) -> LoadModelResult<()> {
let loaded = self.styles();
let external = model_header
.metas
.iter()
.flat_map(|speaker| speaker.styles());

let error = |context| LoadModelError {
path: model_header.path.clone(),
context,
source: None,
};

let loaded = self.speakers();
let external = model_header.metas.iter();
for (loaded, external) in iproduct!(loaded, external) {
if loaded.speaker_uuid() == external.speaker_uuid() {
loaded.warn_diff_except_styles(external);
}
}

let loaded = self.styles();
let external = model_header
.metas
.iter()
.flat_map(|speaker| speaker.styles());
if self.0.contains_key(&model_header.id) {
return Err(error(LoadModelErrorKind::ModelAlreadyLoaded {
id: model_header.id.clone(),
Expand Down Expand Up @@ -242,11 +248,12 @@ impl<R: InferenceRuntime, D: InferenceDomain> LoadedModels<R, D> {
Ok(())
}

fn speakers(&self) -> impl Iterator<Item = &SpeakerMeta> + Clone {
self.0.values().flat_map(|LoadedModel { metas, .. }| metas)
}

fn styles(&self) -> impl Iterator<Item = &StyleMeta> {
self.0
.values()
.flat_map(|LoadedModel { metas, .. }| metas)
.flat_map(|speaker| speaker.styles())
self.speakers().flat_map(|speaker| speaker.styles())
}
}

Expand Down
203 changes: 200 additions & 3 deletions crates/voicevox_core/src/metas.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,42 @@
use std::fmt::Display;
use std::fmt::{Debug, Display};

use derive_getters::Getters;
use derive_new::new;
use indexmap::IndexMap;
use itertools::Itertools as _;
use serde::{Deserialize, Serialize};
use tracing::warn;

/// [`speaker_uuid`]をキーとして複数の[`SpeakerMeta`]をマージする。
///
/// マージする際話者は[`SpeakerMeta::order`]、スタイルは[`StyleMeta::order`]をもとに安定ソートされる。
/// `order`が無い話者とスタイルは、そうでないものよりも後ろに置かれる。
///
/// [`speaker_uuid`]: SpeakerMeta::speaker_uuid
pub fn merge<'a>(metas: impl IntoIterator<Item = &'a SpeakerMeta>) -> Vec<SpeakerMeta> {
return metas
.into_iter()
.fold(IndexMap::<_, SpeakerMeta>::new(), |mut acc, speaker| {
acc.entry(&speaker.speaker_uuid)
.and_modify(|acc| acc.styles.extend(speaker.styles.clone()))
.or_insert_with(|| speaker.clone());
acc
})
.into_values()
.update(|speaker| {
speaker
.styles
.sort_by_key(|&StyleMeta { order, .. }| key(order));
})
.sorted_by_key(|&SpeakerMeta { order, .. }| key(order))
.collect();

fn key(order: Option<u32>) -> impl Ord {
order
.map(Into::into)
.unwrap_or_else(|| u64::from(u32::MAX) + 1)
}
}

/// [`StyleId`]の実体。
///
Expand All @@ -15,7 +49,7 @@ pub type RawStyleId = u32;
///
/// [**話者**(_speaker_)]: SpeakerMeta
/// [**スタイル**(_style_)]: StyleMeta
#[derive(PartialEq, Eq, Clone, Copy, Ord, PartialOrd, Hash, Deserialize, Serialize, new, Debug)]
#[derive(PartialEq, Eq, Clone, Copy, Ord, Hash, PartialOrd, Deserialize, Serialize, new, Debug)]
pub struct StyleId(RawStyleId);

impl StyleId {
Expand Down Expand Up @@ -68,6 +102,60 @@ pub struct SpeakerMeta {
/// 話者の対応機能。
#[serde(default)]
supported_features: SpeakerSupportedFeatures,
/// 話者の順番。
///
/// `SpeakerMeta`の列は、この値に対して昇順に並んでいるべきである。
order: Option<u32>,
}

impl SpeakerMeta {
/// # Panics
///
/// `speaker_uuid`が異なるときパニックする。
pub(crate) fn warn_diff_except_styles(&self, other: &Self) {
let Self {
name: name1,
styles: _,
version: version1,
speaker_uuid: speaker_uuid1,
supported_features: supported_features1,
order: order1,
} = self;

let Self {
name: name2,
styles: _,
version: version2,
speaker_uuid: speaker_uuid2,
supported_features: supported_features2,
order: order2,
} = other;

if speaker_uuid1 != speaker_uuid2 {
panic!("must be equal: {speaker_uuid1} != {speaker_uuid2:?}");
}

warn_diff(speaker_uuid1, "name", name1, name2);
warn_diff(speaker_uuid1, "version", version1, version2);
warn_diff(
speaker_uuid1,
"supported_features",
supported_features1,
supported_features2,
);
warn_diff(speaker_uuid1, "order", order1, order2);

fn warn_diff<T: PartialEq + Debug>(
speaker_uuid: &str,
field_name: &str,
left: &T,
right: &T,
) {
if left != right {
warn!("`{speaker_uuid}`: different `{field_name}` ({left:?} != {right:?})");
}
}
}
}

/// **スタイル**(_style_)のメタ情報。
Expand All @@ -77,9 +165,118 @@ pub struct StyleMeta {
id: StyleId,
/// スタイル名。
name: String,
/// スタイルの順番。
///
/// [`SpeakerMeta::styles`]は、この値に対して昇順に並んでいるべきである。
order: Option<u32>,
}

#[cfg(test)]
mod tests {
use once_cell::sync::Lazy;
use serde_json::json;

#[test]
fn merge_works() -> anyhow::Result<()> {
static INPUT: Lazy<serde_json::Value> = Lazy::new(|| {
json!([
{
"name": "B",
"styles": [
{
"id": 3,
"name": "B_1",
"order": 0
}
],
"version": "0.0.0",
"speaker_uuid": "f34ab151-c0f5-4e0a-9ad2-51ce30dba24d",
"order": 1
},
{
"name": "A",
"styles": [
{
"id": 2,
"name": "A_3",
"order": 2
}
],
"version": "0.0.0",
"speaker_uuid": "d6fd707c-a451-48e9-8f00-fe9ee3bf6264",
"order": 0
},
{
"name": "A",
"styles": [
{
"id": 1,
"name": "A_1",
"order": 0
},
{
"id": 0,
"name": "A_2",
"order": 1
}
],
"version": "0.0.0",
"speaker_uuid": "d6fd707c-a451-48e9-8f00-fe9ee3bf6264",
"order": 0
}
])
});

static EXPECTED: Lazy<serde_json::Value> = Lazy::new(|| {
json!([
{
"name": "A",
"styles": [
{
"id": 1,
"name": "A_1",
"order": 0
},
{
"id": 0,
"name": "A_2",
"order": 1
},
{
"id": 2,
"name": "A_3",
"order": 2
}
],
"version": "0.0.0",
"speaker_uuid": "d6fd707c-a451-48e9-8f00-fe9ee3bf6264",
"order": 0
},
{
"name": "B",
"styles": [
{
"id": 3,
"name": "B_1",
"order": 0
}
],
"version": "0.0.0",
"speaker_uuid": "f34ab151-c0f5-4e0a-9ad2-51ce30dba24d",
"order": 1
}
])
});

let input = &serde_json::from_value::<Vec<_>>(INPUT.clone())?;
let actual = serde_json::to_value(super::merge(input))?;

pretty_assertions::assert_eq!(*EXPECTED, actual);
Ok(())
}
}

#[derive(Default, Deserialize, Serialize, Clone)]
#[derive(Default, Deserialize, Serialize, Clone, PartialEq, Debug)]
pub struct SpeakerSupportedFeatures {
#[serde(default)]
pub(crate) permitted_synthesis_morphing: PermittedSynthesisMorphing,
Expand Down
Loading

0 comments on commit 3710699

Please sign in to comment.