UNPKG

koishi-plugin-beta-dxl-bert-vits

Version:

beta-dxl-bert-vits。

274 lines (262 loc) 9.57 kB
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 });