koishi-plugin-beta-dxl-bert-vits
Version:
beta-dxl-bert-vits。
274 lines (262 loc) • 9.57 kB
JavaScript
var __create = Object.create;
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __getProtoOf = Object.getPrototypeOf;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
var __export = (target, all) => {
for (var name in all)
__defProp(target, name, { get: all[name], enumerable: true });
};
var __copyProps = (to, from, except, desc) => {
if (from && typeof from === "object" || typeof from === "function") {
for (let key of __getOwnPropNames(from))
if (!__hasOwnProp.call(to, key) && key !== except)
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
}
return to;
};
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
// If the importer is in node compatibility mode or this is not an ESM
// file that has been converted to a CommonJS file using a Babel-
// compatible transform (i.e. "__esModule" has not been set), then set
// "default" to the CommonJS "module.exports" for node compatibility.
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
mod
));
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
// src/index.ts
var src_exports = {};
__export(src_exports, {
Config: () => Config,
apply: () => apply,
inject: () => inject,
usage: () => usage
});
module.exports = __toCommonJS(src_exports);
var import_koishi3 = require("koishi");
// src/constants.ts
var import_node_path = __toESM(require("node:path"));
var import_node_fs = __toESM(require("node:fs"));
function loadSpeakersData(logger2) {
const resourcesDir = import_node_path.default.resolve(__dirname, "../resources");
if (!import_node_fs.default.existsSync(resourcesDir)) {
import_node_fs.default.mkdirSync(resourcesDir, { recursive: true });
}
const files = import_node_fs.default.readdirSync(resourcesDir).filter(
(file) => file.startsWith("speakers_") && file.endsWith(".json")
);
const speakersMap = /* @__PURE__ */ new Map();
files.forEach((file) => {
try {
const filePath = import_node_path.default.join(resourcesDir, file);
const content = import_node_fs.default.readFileSync(filePath, "utf-8");
const data = JSON.parse(content);
const fileIdentifier = file.replace("speakers_", "").replace(".json", "");
for (const speakerName in data.speakers) {
const uniqueName = `${speakerName} (${fileIdentifier})`;
if (speakersMap.has(uniqueName)) {
logger2.warn(
`Duplicate speaker found, skipping: ${uniqueName} in ${file}`
);
continue;
}
speakersMap.set(uniqueName, {
api: data.api,
fileApi: data.fileApi,
internalName: data.speakers[speakerName].speaker
});
}
} catch (error) {
logger2.error(`Error reading or parsing ${file}:`, error);
}
});
const SpeakersList2 = [...speakersMap.keys()].sort();
let baseSpeakId = 114514;
const SpeakerIdMap = SpeakersList2.reduce(
(acc, speaker, index) => {
acc[baseSpeakId + index] = speaker;
return acc;
},
{}
);
return { speakersMap, SpeakersList: SpeakersList2, SpeakerIdMap };
}
__name(loadSpeakersData, "loadSpeakersData");
// src/service.ts
var import_koishi = require("koishi");
var import_vits = __toESM(require("@initencounter/vits"));
var betavits = class {
constructor(ctx, config, speakersMap, SpeakerIdMap) {
this.ctx = ctx;
this.config = config;
this.speakersMap = speakersMap;
this.SpeakerIdMap = SpeakerIdMap;
this.logger = ctx.logger("bert-vits");
}
static {
__name(this, "betavits");
}
logger;
async say(input, options) {
const option = fallback(options, this.config);
const speakerInfo = this.speakersMap.get(option.speaker);
if (!speakerInfo) {
throw new Error(`找不到讲者 "${option.speaker}"。`);
}
const payload = this._generatePlayLoad(
input,
speakerInfo.internalName,
option
);
try {
return await this._request(
payload,
speakerInfo.api,
speakerInfo.fileApi
);
} catch (error) {
this.logger.error("语音合成请求失败:", error);
throw error;
}
}
_generatePlayLoad(input, internalSpeakerName, options) {
const { sdp_ratio, noise, noisew, length, prompt, weight } = options;
return {
fn_index: 0,
data: [
input,
internalSpeakerName,
sdp_ratio,
noise,
noisew,
length,
"AUTO",
null,
prompt,
"Text prompt",
"",
weight
],
event_data: null
};
}
async _request(payload, api, fileApi) {
const res = await this.ctx.http.post(api, payload);
if (!res.data || !res.data[1] || !res.data[1].name) {
this.logger.error("未能在响应中找到预期的文件名:", res);
throw new Error("API响应格式不正确,无法获取音频文件路径。");
}
const audioFilePath = res.data[1].name;
const audioUrl = `${fileApi}${audioFilePath}`;
const buffer = await this.ctx.http.get(audioUrl, {
responseType: "arraybuffer"
});
return import_koishi.h.audio(buffer, "wav");
}
};
var betavitsService = class extends import_vits.default {
constructor(ctx, impl) {
super(ctx);
this.impl = impl;
}
static {
__name(this, "betavitsService");
}
say(options) {
return this.impl.say(options.input, {
speaker: this.impl["SpeakerIdMap"][options.speaker_id]
});
}
};
function fallback(options, defaultValues) {
if (!options) {
return defaultValues;
}
const result = Object.assign({}, defaultValues);
for (const key in options) {
if (options[key] && options[key] !== "") {
result[key] = options[key];
}
}
return result;
}
__name(fallback, "fallback");
// src/config.ts
var import_koishi2 = require("koishi");
var { SpeakersList } = loadSpeakersData(new import_koishi2.Logger("beta-dxl-bert-vits"));
var Config = import_koishi2.Schema.intersect([
import_koishi2.Schema.object({
speaker: import_koishi2.Schema.union(SpeakersList).description("选择一个模型").default(SpeakersList[0] ?? "")
}).description("基础设置"),
import_koishi2.Schema.object({
prompt: import_koishi2.Schema.union(["Happy", "Sad", "Angry", "Neutral"]).description("选择一个预设的生成风格").default("Happy"),
sdp_ratio: import_koishi2.Schema.number().min(0).max(1).step(0.1).role("slider").description("SDP/DP混合比").default(0.5),
noise: import_koishi2.Schema.number().min(0.1).max(2).step(0.1).role("slider").description("感情").default(0.6),
noisew: import_koishi2.Schema.number().min(0.1).max(2).step(0.1).role("slider").description("音素长度").default(0.9),
length: import_koishi2.Schema.number().min(0.1).max(2).step(0.1).role("slider").description("语速").default(1),
weight: import_koishi2.Schema.number().min(0).max(1).step(0).role("slider").description("主文本和辅助文本的混合比率").default(0.7)
}).description("进阶设置(不一定会生效)"),
import_koishi2.Schema.object({
loggerinfo: import_koishi2.Schema.boolean().default(false).description("日志调试模式`日常使用无需开启`")
}).description("调试设置")
]);
// src/index.ts
var usage = `
---
### 指令用法
\`\`\`
++betavits 你好啊,这是一段测试的模板语音
\`\`\`
---
如何添加新模型?
<a href="https://github.com/shangxueink/koishi-shangxue-apps/tree/main/plugins/beta-dxl-bert-vits)" target="_blank">请查看README.md</a>
---
`;
var logger = new import_koishi3.Logger("beta-dxl-bert-vits");
function apply(ctx, config) {
const { speakersMap, SpeakerIdMap } = loadSpeakersData(logger);
const vits = new betavits(ctx, config, speakersMap, SpeakerIdMap);
ctx.plugin(betavitsService, vits);
ctx.command("betavits <text:text>", "AIbetavits语音合成帮助").option("speaker", "-s <speaker:string> 语音合成的讲者", {
fallback: config.speaker
}).option("sdp_ratio", "-sr <sdp_ratio:number> 语音合成的SDP/DP混合比", {
fallback: config.sdp_ratio
}).option("noise", "-n <noise:number> 语音合成的感情强度", {
fallback: config.noise
}).option("noisew", "-nw <noisew:number> 语音合成的音素长度", {
fallback: config.noisew
}).option("length", "-l <length:number> 语音合成语速", {
fallback: config.length
}).option("prompt", "-p <prompt:string> 辅助语音合成的情感文本", {
fallback: config.prompt
}).option("weight", "-w <weight:number> 主文本和辅助文本的混合比率", {
fallback: config.weight
}).action(async ({ session, options }, text) => {
if (!text) {
await session.execute("betavits -h");
return;
}
if (config.loggerinfo) {
logger.info(`传入的选项: ${JSON.stringify(options)}`);
}
try {
return await vits.say(text, options);
} catch (error) {
logger.error(error);
return "语音合成时出现问题。";
}
});
}
__name(apply, "apply");
var inject = {
optional: ["translator"]
};
// Annotate the CommonJS export names for ESM import in node:
0 && (module.exports = {
Config,
apply,
inject,
usage
});