UNPKG

aivmlib-web

Version:

Aivis Voice Model File (.aivm/.aivmx) Utility Library for Web

377 lines (376 loc) 18.1 kB
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