aivmlib-web
Version:
Aivis Voice Model File (.aivm/.aivmx) Utility Library for Web
377 lines (376 loc) • 18.1 kB
JavaScript
import { Base64 as M } from "js-base64";
import * as v from "protobufjs";
import * as g from "uuid";
import { onnx as j } from "./onnx-protobuf/onnx.mjs";
import { DefaultAivmManifest as O, AivmManifestSchema as x } from "./schemas/aivm-manifest.mjs";
import { ModelArchitectureSchema as U, ModelFormatSchema as z } from "./schemas/aivm-manifest.mjs";
import { DEFAULT_ICON_DATA_URL as E } from "./schemas/aivm-manifest-constants.mjs";
import { StyleBertVITS2HyperParametersSchema as V } from "./schemas/style-bert-vits2.mjs";
class m {
/**
* ハイパーパラメータとスタイルベクトルファイルを読み込み、バリデーションする内部メソッド
* @param model_architecture 音声合成モデルのアーキテクチャ
* @param hyper_parameters_file ハイパーパラメータファイル
* @param style_vectors_file スタイルベクトルファイル
* @returns ハイパーパラメータとスタイルベクトルのデータ
*/
static async loadAndValidateHyperParametersAndStyleVectors(t, a, c) {
if (["Style-Bert-VITS2", "Style-Bert-VITS2 (JP-Extra)"].includes(t)) {
const r = await a.text();
let e;
try {
e = V.parse(JSON.parse(r));
} catch (l) {
throw console.error(l), new Error(`${t} のハイパーパラメータファイルの形式が正しくありません。`, { cause: l });
}
if (Object.keys(e.data.spk2id).length === 0)
throw new Error("ハイパーパラメータに話者情報が含まれていません。");
if (Object.keys(e.data.style2id).length === 0)
throw new Error("ハイパーパラメータにスタイル情報が含まれていません。");
const s = /* @__PURE__ */ new Set();
for (const [l, o] of Object.entries(e.data.spk2id)) {
if (s.has(o))
throw new Error(`話者 ID(${o})が重複しています。複数の話者(${Array.from(Object.entries(e.data.spk2id)).filter(([y, w]) => w === o).map(([y, w]) => `「${y}」`).join("、")})に同じ ID が割り当てられています。`);
s.add(o);
}
const d = /* @__PURE__ */ new Set();
for (const [l, o] of Object.entries(e.data.style2id)) {
if (d.has(o))
throw new Error(`スタイル ID(${o})が重複しています。複数のスタイル(${Array.from(Object.entries(e.data.style2id)).filter(([y, w]) => w === o).map(([y, w]) => `「${y}」`).join("、")})に同じ ID が割り当てられています。`);
d.add(o);
}
for (const [l, o] of Object.entries(e.data.style2id))
if (o < 0 || o > 31)
throw new Error(`スタイル「${l}」の ID(${o})が有効範囲外です。スタイル ID は 0 から 31 の範囲である必要があります。`);
for (const [l, o] of Object.entries(e.data.spk2id))
if (o < 0 || !Number.isInteger(o))
throw new Error(`話者「${l}」の ID(${o})が有効範囲外です。話者 ID は 0 以上の整数である必要があります。`);
if (c === null)
throw new Error("スタイルベクトルファイルが指定されていません。");
const _ = await c.arrayBuffer(), n = new Uint8Array(_);
return { hyper_parameters: e, style_vectors: n };
}
throw new Error(`音声合成モデルアーキテクチャ ${t} には対応していません。`);
}
/**
* ハイパーパラメータファイルとスタイルベクトルファイルから AIVM メタデータを生成する
* @param model_architecture 音声合成モデルのアーキテクチャ
* @param hyper_parameters_file ハイパーパラメータファイル
* @param style_vectors_file スタイルベクトルファイル
* @returns 生成された AIVM メタデータ
*/
static async generateAivmMetadata(t, a, c) {
const { hyper_parameters: r, style_vectors: e } = await m.loadAndValidateHyperParametersAndStyleVectors(
t,
a,
c
);
if (["Style-Bert-VITS2", "Style-Bert-VITS2 (JP-Extra)"].includes(t)) {
const s = structuredClone(O);
return s.name = r.model_name, s.model_architecture = r.data.use_jp_extra ? "Style-Bert-VITS2 (JP-Extra)" : "Style-Bert-VITS2", s.uuid = g.v4(), s.speakers = Object.keys(r.data.spk2id).map((d, _) => ({
// ハイパーパラメータに記載の話者名を使用
name: d,
// デフォルトアイコンを使用
icon: E,
// JP-Extra の場合は日本語のみ、それ以外は日本語・アメリカ英語・標準中国語をサポート
supported_languages: r.data.use_jp_extra ? ["ja"] : ["ja", "en-US", "zh-CN"],
// 話者 UUID はランダムに生成
uuid: g.v4(),
// ローカル ID は spk2id の ID の部分を使用
local_id: _,
// style2id の内容を反映
styles: Object.keys(r.data.style2id).map((n, l) => ({
name: n === "Neutral" && !Object.keys(r.data.style2id).includes("ノーマル") ? "ノーマル" : n,
icon: null,
local_id: l,
voice_samples: []
}))
})), {
manifest: s,
hyper_parameters: r,
style_vectors: e
};
}
throw new Error(`音声合成モデルアーキテクチャ ${t} には対応していません。`);
}
/**
* 既存の AIVM メタデータを、新しいハイパーパラメータとスタイルベクトルで更新する (モデル差し替え用)
* 既存の UUID やユーザー設定メタデータは可能な限り維持される
* @param existing_metadata 既存の AIVM メタデータ
* @param hyper_parameters_file 新しいハイパーパラメータファイル
* @param style_vectors_file 新しいスタイルベクトルファイル
* @returns 更新された AIVM メタデータと警告メッセージの配列
*/
static async updateAivmMetadata(t, a, c) {
const r = [], e = t.manifest.model_architecture, { hyper_parameters: s, style_vectors: d } = await m.loadAndValidateHyperParametersAndStyleVectors(
e,
a,
c
);
if (["Style-Bert-VITS2", "Style-Bert-VITS2 (JP-Extra)"].includes(e)) {
const _ = s.data.spk2id, n = s.data.style2id, l = structuredClone(t.manifest);
l.model_architecture = s.data.use_jp_extra ? "Style-Bert-VITS2 (JP-Extra)" : "Style-Bert-VITS2";
const o = /* @__PURE__ */ new Map();
for (const [i, p] of Object.entries(_))
o.set(p, i);
const y = /* @__PURE__ */ new Map();
for (const [i, p] of Object.entries(n))
y.set(p, i);
const w = /* @__PURE__ */ new Set(), u = [];
for (const i of t.manifest.speakers) {
const p = i.local_id;
if (o.has(p)) {
w.add(p);
const f = /* @__PURE__ */ new Set(), k = [];
for (const h of i.styles) {
const S = h.local_id;
y.has(S) ? (f.add(S), k.push(h)) : r.push(`話者「${i.name}」のスタイル「${h.name}」(ID: ${S}) は、新しいハイパーパラメータに存在しないため削除されます。`);
}
for (const [h, S] of Object.entries(n))
if (!f.has(S)) {
const $ = h === "Neutral" && !Object.keys(s.data.style2id).includes("ノーマル") ? "ノーマル" : h;
k.push({
name: $,
icon: null,
local_id: S,
voice_samples: []
}), r.push(`話者「${i.name}」にスタイル「${h}」(ID: ${S}) が新しく追加されました。`);
}
let I = i.supported_languages;
const A = s.data.use_jp_extra ? ["ja"] : ["ja", "en-US", "zh-CN"];
JSON.stringify(I) !== JSON.stringify(A) && (I = A, r.push(`話者「${i.name}」の対応言語が変更されました: ${I.join(", ")}`)), u.push({
...i,
// 既存の話者情報を維持
supported_languages: I,
// 更新された対応言語情報
styles: k
// 更新されたスタイル情報リスト
});
} else
r.push(`話者「${i.name}」 (ID: ${i.local_id}) は、新しいハイパーパラメータに存在しないため削除されます。`);
}
for (const [i, p] of Object.entries(_))
if (!w.has(p)) {
const f = [];
for (const [k, I] of Object.entries(n)) {
const A = k === "Neutral" && !Object.keys(s.data.style2id).includes("ノーマル") ? "ノーマル" : k;
f.push({
name: A,
icon: null,
local_id: I,
voice_samples: []
});
}
u.push({
// ハイパーパラメータに記載の話者名を使用
name: i,
// デフォルトアイコンを使用
icon: E,
// JP-Extra の場合は日本語のみ、それ以外は日本語・アメリカ英語・標準中国語をサポート
supported_languages: s.data.use_jp_extra ? ["ja"] : ["ja", "en-US", "zh-CN"],
// 話者 UUID はランダムに生成
uuid: g.v4(),
// ローカル ID は spk2id の ID の部分を使用
local_id: p,
// style2id の内容を反映
styles: f
}), r.push(`話者「${i}」(ID: ${p}) が新しく追加されました。`);
}
if (l.speakers = u, u.length === 0)
throw new Error("更新処理の結果、話者情報が空になりました。AIVM マニフェストには少なくとも 1 つの話者が必要です。");
for (const i of u)
if (i.styles.length === 0)
throw new Error(`更新処理の結果、話者「${i.name}」(ID: ${i.local_id}) のスタイル情報が空になりました。各話者には少なくとも 1 つのスタイルが必要です。`);
return {
updated_metadata: {
manifest: l,
hyper_parameters: s,
style_vectors: d
},
warnings: r
};
}
throw new Error(`音声合成モデルアーキテクチャ ${e} には対応していません。`);
}
/**
* AIVM メタデータをバリデーションする
* @param raw_metadata 辞書形式の生のメタデータ
* @returns バリデーションが完了した AIVM メタデータ
*/
static validateAivmMetadata(t) {
if (!t || !t.aivm_manifest)
throw new Error("AIVM マニフェストが見つかりません。");
let a;
try {
a = x.parse(JSON.parse(t.aivm_manifest));
} catch (e) {
throw console.error(e), new Error("AIVM マニフェストの形式が正しくありません。", { cause: e });
}
let c;
if (t.aivm_hyper_parameters)
try {
if (["Style-Bert-VITS2", "Style-Bert-VITS2 (JP-Extra)"].includes(a.model_architecture))
c = V.parse(JSON.parse(t.aivm_hyper_parameters));
else
throw new Error(`モデルアーキテクチャ ${a.model_architecture} のハイパーパラメータには対応していません。`);
} catch (e) {
throw console.error(e), new Error("ハイパーパラメータの形式が正しくありません。", { cause: e });
}
else
throw new Error("ハイパーパラメータが見つかりません。");
let r;
if (t.aivm_style_vectors)
try {
const e = t.aivm_style_vectors;
r = M.toUint8Array(e);
} catch (e) {
throw new Error("スタイルベクトルのデコードに失敗しました。", { cause: e });
}
return {
manifest: a,
hyper_parameters: c,
style_vectors: r
};
}
/**
* AIVM ファイルから AIVM メタデータを読み込む
* @param aivm_file AIVM ファイル
* @returns AIVM メタデータ
*/
static async readAivmMetadata(t) {
const a = await t.arrayBuffer(), r = new DataView(a).getBigUint64(0, !0);
let e;
try {
e = new Uint8Array(a, 8, Number(r));
} catch (n) {
throw console.error(n), new Error("AIVM ファイルの形式が正しくありません。AIVM ファイル以外のファイルが指定されている可能性があります。", { cause: n });
}
const s = new TextDecoder("utf-8").decode(e), _ = JSON.parse(s).__metadata__;
return m.validateAivmMetadata(_);
}
/**
* AIVMX ファイルから AIVM メタデータを読み込む
* @param aivmx_file AIVMX ファイル
* @returns AIVM メタデータ
*/
static async readAivmxMetadata(t) {
const a = await t.arrayBuffer();
let c;
try {
const e = new v.Reader(new Uint8Array(a));
c = j.ModelProto.decode(e);
} catch (e) {
throw console.error(e), new Error("AIVMX ファイルの形式が正しくありません。AIVMX ファイル以外のファイルが指定されている可能性があります。", { cause: e });
}
const r = {};
if (c.metadataProps)
for (const e of c.metadataProps)
e.key && e.value && (r[e.key] = e.value);
return m.validateAivmMetadata(r);
}
/**
* AIVM メタデータを生の辞書形式にシリアライズする
* @param aivm_metadata AIVM メタデータ
* @returns シリアライズされた AIVM メタデータ (文字列から文字列へのマップ)
*/
static serializeAivmMetadata(t) {
const a = {};
return a.aivm_manifest = JSON.stringify(t.manifest), a.aivm_hyper_parameters = JSON.stringify(t.hyper_parameters), t.style_vectors && (a.aivm_style_vectors = M.fromUint8Array(t.style_vectors)), a;
}
/**
* AIVM メタデータを AIVM ファイルに書き込む
* @param aivm_file AIVM ファイル
* @param aivm_metadata AIVM メタデータ
* @returns 書き込みが完了した AIVM ファイル
*/
static async writeAivmMetadata(t, a) {
a.manifest.model_format = "Safetensors", m.applyAivmManifestToHyperParameters(a);
const c = m.serializeAivmMetadata(a);
m.validateAivmMetadata(c);
const r = await t.arrayBuffer(), e = new Uint8Array(r), s = new DataView(r).getBigUint64(0, !0), d = e.slice(8, 8 + Number(s)), _ = new TextDecoder("utf-8").decode(d);
let n;
try {
n = JSON.parse(_);
} catch (f) {
throw console.error(f), new Error("AIVM ファイルの形式が正しくありません。AIVM ファイル以外のファイルが指定されている可能性があります。", { cause: f });
}
const l = n.__metadata__ || {};
for (const f in c)
l[f] = c[f];
n.__metadata__ = l;
const o = JSON.stringify(n), y = new TextEncoder().encode(o), w = BigInt(y.length), u = new Uint8Array(8);
new DataView(u.buffer).setBigUint64(0, w, !0);
const i = new Uint8Array(8 + y.length + (e.length - 8 - Number(s)));
return i.set(u, 0), i.set(y, 8), i.set(e.slice(8 + Number(s)), 8 + y.length), new File([i], t.name, { type: t.type });
}
/**
* AIVM メタデータを AIVMX ファイルに書き込む
* @param aivmx_file AIVMX ファイル
* @param aivm_metadata AIVM メタデータ
* @returns 書き込みが完了した AIVMX ファイル
*/
static async writeAivmxMetadata(t, a) {
a.manifest.model_format = "ONNX", m.applyAivmManifestToHyperParameters(a);
const c = await t.arrayBuffer();
let r;
try {
const n = new v.Reader(new Uint8Array(c));
r = j.ModelProto.decode(n);
} catch (n) {
throw console.error(n), new Error("AIVMX ファイルの形式が正しくありません。AIVMX ファイル以外のファイルが指定されている可能性があります。", { cause: n });
}
const e = m.serializeAivmMetadata(a);
m.validateAivmMetadata(e), r.metadataProps || (r.metadataProps = []);
for (const n in e) {
const l = r.metadataProps.find((o) => o.key === n);
l ? l.value = e[n] : r.metadataProps.push({ key: n, value: e[n] });
}
const d = j.ModelProto.encode(r).finish();
return new File([d], t.name, { type: t.type });
}
/**
* AIVM マニフェストの内容をハイパーパラメータにも反映する
* 結果は AivmMetadata オブジェクトに直接 in-place で反映される
* @param aivm_metadata AIVM メタデータ
*/
static applyAivmManifestToHyperParameters(t) {
if (["Style-Bert-VITS2", "Style-Bert-VITS2 (JP-Extra)"].includes(t.manifest.model_architecture)) {
if (t.style_vectors === void 0)
throw new Error("スタイルベクトルが設定されていません。");
t.hyper_parameters.model_name = t.manifest.name, t.hyper_parameters.data.training_files = "train.list", t.hyper_parameters.data.validation_files = "val.list";
const a = {};
for (const r of t.manifest.speakers) {
const e = r.local_id;
if (Object.keys(t.hyper_parameters.data.spk2id).find(
(d) => t.hyper_parameters.data.spk2id[d] === e
))
a[r.name] = e;
else
throw new Error(`話者 ID "${e}" の話者 "${r.name}" がハイパーパラメータに存在しません。`);
}
t.hyper_parameters.data.spk2id = a;
const c = {};
for (const r of t.manifest.speakers)
for (const e of r.styles) {
const s = e.local_id;
if (Object.keys(t.hyper_parameters.data.style2id).find(
(_) => t.hyper_parameters.data.style2id[_] === s
))
c[e.name] = s;
else
throw new Error(`スタイル ID "${s}" のスタイル "${e.name}" がハイパーパラメータに存在しません。`);
}
t.hyper_parameters.data.style2id = c, t.hyper_parameters.data.num_styles = Object.keys(c).length;
}
}
}
export {
x as AivmManifestSchema,
E as DEFAULT_ICON_DATA_URL,
O as DefaultAivmManifest,
U as ModelArchitectureSchema,
z as ModelFormatSchema,
V as StyleBertVITS2HyperParametersSchema,
m as default
};
//# sourceMappingURL=index.mjs.map