koishi-plugin-tts-base 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,3 @@
1
+ import { Context } from 'koishi';
2
+ import { Config } from './config';
3
+ export declare function apply(ctx: Context, config: Config): void;
@@ -0,0 +1,8 @@
1
+ import { Context, h, Service } from 'koishi';
2
+ import { TTSInput, TTSProvider } from './types';
3
+ export declare abstract class VitsCompatible extends Service implements TTSProvider {
4
+ constructor(ctx: Context);
5
+ abstract provideVoiceIds(): Promise<string[]>;
6
+ abstract say(input: TTSInput): Promise<h>;
7
+ isSupportVoiceId(voiceId: string | string[]): Promise<boolean>;
8
+ }
@@ -0,0 +1,10 @@
1
+ import { Schema } from 'koishi';
2
+ export declare const name = "tts-base";
3
+ export interface Config {
4
+ defaultSpeaker?: string;
5
+ }
6
+ export declare const Config: Schema<Config>;
7
+ export declare const usage = "\n\u672C\u63D2\u4EF6\u4F5C\u4E3A tts \u670D\u52A1\u7684\u524D\u7F6E\u57FA\u7840\u63D2\u4EF6\u3002\n\u6B64\u63D2\u4EF6\u6CA1\u6709\u5305\u542B\u4EFB\u4F55\u8BED\u97F3\u670D\u52A1\uFF0C\u5B83\u53EA\u662F\u4E00\u4E2A\u524D\u7F6E\u63D2\u4EF6\u3002";
8
+ export declare const inject: {
9
+ optional: string[];
10
+ };
package/lib/index.cjs ADDED
@@ -0,0 +1,240 @@
1
+ var __defProp = Object.defineProperty;
2
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
3
+ var __getOwnPropNames = Object.getOwnPropertyNames;
4
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
5
+ var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
6
+ var __commonJS = (cb, mod) => function __require() {
7
+ return mod || (0, cb[__getOwnPropNames(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports;
8
+ };
9
+ var __export = (target, all) => {
10
+ for (var name2 in all)
11
+ __defProp(target, name2, { get: all[name2], enumerable: true });
12
+ };
13
+ var __copyProps = (to, from, except, desc) => {
14
+ if (from && typeof from === "object" || typeof from === "function") {
15
+ for (let key of __getOwnPropNames(from))
16
+ if (!__hasOwnProp.call(to, key) && key !== except)
17
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
18
+ }
19
+ return to;
20
+ };
21
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
22
+
23
+ // src/locales/zh-CN.yml
24
+ var require_zh_CN = __commonJS({
25
+ "src/locales/zh-CN.yml"(exports2, module2) {
26
+ module2.exports = { commands: { tts: { description: "使用 TTS 朗读文本。", usage: "tts <text> [-s <speaker>]", examples: "tts 你好世界\ntts 你好世界 -s speaker1", options: { speaker: "选择说话人音色。" }, messages: { "no-speaker": "未指定说话人音色,请在配置中设置默认说话人或使用 -s 选项指定。", "invalid-speaker": "无效的说话人音色:{0}。", "generate-failed": "TTS 生成失败,请稍后再试。" } } } };
27
+ }
28
+ });
29
+
30
+ // src/locales/en-US.yml
31
+ var require_en_US = __commonJS({
32
+ "src/locales/en-US.yml"(exports2, module2) {
33
+ module2.exports = { commands: { tts: { description: "Read text aloud using TTS.", usage: "tts <text> [-s <speaker>]", examples: "tts Hello world\ntts Hello world -s speaker1", options: { speaker: "Select speaker voice." }, messages: { "no-speaker": "No speaker specified. Please set a default speaker in config or use -s option.", "invalid-speaker": "Invalid speaker voice: {0}.", "generate-failed": "TTS generation failed, please try again later." } } } };
34
+ }
35
+ });
36
+
37
+ // src/index.ts
38
+ var index_exports = {};
39
+ __export(index_exports, {
40
+ BaseTTSProvider: () => BaseTTSProvider,
41
+ Config: () => Config,
42
+ TTSService: () => TTSService,
43
+ VitsCompatible: () => VitsCompatible,
44
+ apply: () => apply2,
45
+ inject: () => inject,
46
+ name: () => name,
47
+ usage: () => usage
48
+ });
49
+ module.exports = __toCommonJS(index_exports);
50
+
51
+ // src/service.ts
52
+ var import_koishi = require("koishi");
53
+ var TTSService = class extends import_koishi.Service {
54
+ constructor(ctx, config) {
55
+ super(ctx, "tts", true);
56
+ this.ctx = ctx;
57
+ this.config = config;
58
+ if (!ctx["vits"]) {
59
+ ctx.set("vits", this);
60
+ }
61
+ }
62
+ static {
63
+ __name(this, "TTSService");
64
+ }
65
+ _voiceIds = [];
66
+ _voiceHandlers = [];
67
+ _voiceProvider = [];
68
+ registerVoiceId(id) {
69
+ this._voiceIds.push(id);
70
+ this._updateSchema();
71
+ const disposeFn = /* @__PURE__ */ __name(() => {
72
+ this._voiceIds.splice(this._voiceIds.indexOf(id), 1);
73
+ }, "disposeFn");
74
+ return this[import_koishi.Context.current].effect(() => disposeFn);
75
+ }
76
+ hasVoiceId(id) {
77
+ return this._voiceIds.includes(id);
78
+ }
79
+ getVoiceIds() {
80
+ return Object.freeze(this._voiceIds);
81
+ }
82
+ registerVoiceIds(voices) {
83
+ this._voiceIds.push(...voices);
84
+ this._updateSchema();
85
+ const disposeFn = /* @__PURE__ */ __name(() => {
86
+ for (const voice of voices) {
87
+ this._voiceIds.splice(this._voiceIds.indexOf(voice), 1);
88
+ }
89
+ }, "disposeFn");
90
+ return this[import_koishi.Context.current].effect(() => disposeFn);
91
+ }
92
+ registerVoiceProvider(provider) {
93
+ this._voiceProvider.push(provider);
94
+ const disposeFn = /* @__PURE__ */ __name(() => {
95
+ this._voiceProvider.splice(this._voiceProvider.indexOf(provider), 1);
96
+ }, "disposeFn");
97
+ return this[import_koishi.Context.current].effect(() => disposeFn);
98
+ }
99
+ registerHandler(handler) {
100
+ if (typeof handler === "function") {
101
+ handler = { say: handler };
102
+ }
103
+ this._voiceHandlers.push(handler);
104
+ const disposeFn = /* @__PURE__ */ __name(() => {
105
+ this._voiceHandlers.splice(this._voiceHandlers.indexOf(handler), 1);
106
+ }, "disposeFn");
107
+ return this[import_koishi.Context.current].effect(() => disposeFn);
108
+ }
109
+ async say(input) {
110
+ if (typeof input === "string") {
111
+ input = { input, voiceId: this._voiceIds[0] };
112
+ }
113
+ input.speaker_id = input.voiceId;
114
+ await this._lazyInitVoiceProvider();
115
+ for (const handler of this._voiceHandlers) {
116
+ if (handler.isSupportVoiceId && !handler.isSupportVoiceId(input.voiceId)) {
117
+ continue;
118
+ }
119
+ try {
120
+ return await handler.say(input);
121
+ } catch (error) {
122
+ }
123
+ }
124
+ if (this.ctx["vits"]) {
125
+ return await this.ctx["vits"].say(input);
126
+ }
127
+ throw new Error(
128
+ `voice id ${input.voiceId} not found or generation failed`
129
+ );
130
+ }
131
+ async _lazyInitVoiceProvider() {
132
+ for (const provider of this._voiceProvider) {
133
+ if (this._voiceHandlers.indexOf(provider) !== -1) {
134
+ continue;
135
+ }
136
+ this.registerVoiceIds(await provider.provideVoiceIds());
137
+ this._voiceHandlers.push(provider);
138
+ }
139
+ }
140
+ _updateSchema() {
141
+ this.ctx.schema.set(
142
+ "voices",
143
+ import_koishi.Schema.union(
144
+ this._voiceIds.map((id) => import_koishi.Schema.const(id)).concat(import_koishi.Schema.const("????").hidden())
145
+ )
146
+ );
147
+ }
148
+ };
149
+ var BaseTTSProvider = class {
150
+ constructor(ctx, config) {
151
+ this.config = config;
152
+ ctx.tts.registerVoiceProvider(this);
153
+ }
154
+ static {
155
+ __name(this, "BaseTTSProvider");
156
+ }
157
+ async isSupportVoiceId(voiceId) {
158
+ return true;
159
+ }
160
+ };
161
+
162
+ // src/commands.ts
163
+ function apply(ctx, config) {
164
+ ctx.command("tts <text:text>", { checkArgCount: true }).option("speaker", "-s <speaker:string>").action(async ({ session, options }, text) => {
165
+ const voiceId = options.speaker ?? config.defaultSpeaker;
166
+ if (!voiceId) {
167
+ return session.text(".no-speaker");
168
+ }
169
+ if (!ctx.tts.hasVoiceId(voiceId)) {
170
+ return session.text(".invalid-speaker", [voiceId]);
171
+ }
172
+ try {
173
+ const audio = await ctx.tts.say({
174
+ input: text,
175
+ voiceId
176
+ });
177
+ return audio;
178
+ } catch (e) {
179
+ ctx.logger.error(e);
180
+ return session.text(".generate-failed");
181
+ }
182
+ });
183
+ }
184
+ __name(apply, "apply");
185
+
186
+ // src/config.ts
187
+ var import_koishi2 = require("koishi");
188
+ var name = "tts-base";
189
+ var Config = import_koishi2.Schema.object({
190
+ defaultSpeaker: import_koishi2.Schema.dynamic("voices").description(
191
+ "Default speaker voice ID / 默认说话人音色"
192
+ )
193
+ });
194
+ var usage = `
195
+ 本插件作为 tts 服务的前置基础插件。
196
+ 此插件没有包含任何语音服务,它只是一个前置插件。`;
197
+ var inject = {
198
+ optional: ["vits"]
199
+ };
200
+
201
+ // src/compatible.ts
202
+ var import_koishi3 = require("koishi");
203
+ var VitsCompatible = class extends import_koishi3.Service {
204
+ static {
205
+ __name(this, "VitsCompatible");
206
+ }
207
+ constructor(ctx) {
208
+ super(ctx, "vits", true);
209
+ ctx.tts.registerVoiceProvider(this);
210
+ }
211
+ async isSupportVoiceId(voiceId) {
212
+ return true;
213
+ }
214
+ };
215
+
216
+ // src/index.ts
217
+ function apply2(ctx, config) {
218
+ ctx.i18n.define("zh-CN", require_zh_CN());
219
+ ctx.i18n.define("en-US", require_en_US());
220
+ ctx.plugin(TTSService, config);
221
+ ctx.plugin(
222
+ {
223
+ apply,
224
+ inject: ["tts"]
225
+ },
226
+ config
227
+ );
228
+ }
229
+ __name(apply2, "apply");
230
+ // Annotate the CommonJS export names for ESM import in node:
231
+ 0 && (module.exports = {
232
+ BaseTTSProvider,
233
+ Config,
234
+ TTSService,
235
+ VitsCompatible,
236
+ apply,
237
+ inject,
238
+ name,
239
+ usage
240
+ });
package/lib/index.d.ts ADDED
@@ -0,0 +1,7 @@
1
+ import { Context } from 'koishi';
2
+ import { Config } from './config';
3
+ export * from './config';
4
+ export declare function apply(ctx: Context, config: Config): void;
5
+ export * from './service';
6
+ export * from './compatible';
7
+ export * from './types';
package/lib/index.mjs ADDED
@@ -0,0 +1,210 @@
1
+ var __defProp = Object.defineProperty;
2
+ var __getOwnPropNames = Object.getOwnPropertyNames;
3
+ var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
4
+ var __commonJS = (cb, mod) => function __require() {
5
+ return mod || (0, cb[__getOwnPropNames(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports;
6
+ };
7
+
8
+ // src/locales/zh-CN.yml
9
+ var require_zh_CN = __commonJS({
10
+ "src/locales/zh-CN.yml"(exports, module) {
11
+ module.exports = { commands: { tts: { description: "使用 TTS 朗读文本。", usage: "tts <text> [-s <speaker>]", examples: "tts 你好世界\ntts 你好世界 -s speaker1", options: { speaker: "选择说话人音色。" }, messages: { "no-speaker": "未指定说话人音色,请在配置中设置默认说话人或使用 -s 选项指定。", "invalid-speaker": "无效的说话人音色:{0}。", "generate-failed": "TTS 生成失败,请稍后再试。" } } } };
12
+ }
13
+ });
14
+
15
+ // src/locales/en-US.yml
16
+ var require_en_US = __commonJS({
17
+ "src/locales/en-US.yml"(exports, module) {
18
+ module.exports = { commands: { tts: { description: "Read text aloud using TTS.", usage: "tts <text> [-s <speaker>]", examples: "tts Hello world\ntts Hello world -s speaker1", options: { speaker: "Select speaker voice." }, messages: { "no-speaker": "No speaker specified. Please set a default speaker in config or use -s option.", "invalid-speaker": "Invalid speaker voice: {0}.", "generate-failed": "TTS generation failed, please try again later." } } } };
19
+ }
20
+ });
21
+
22
+ // src/service.ts
23
+ import { Context, Schema, Service } from "koishi";
24
+ var TTSService = class extends Service {
25
+ constructor(ctx, config) {
26
+ super(ctx, "tts", true);
27
+ this.ctx = ctx;
28
+ this.config = config;
29
+ if (!ctx["vits"]) {
30
+ ctx.set("vits", this);
31
+ }
32
+ }
33
+ static {
34
+ __name(this, "TTSService");
35
+ }
36
+ _voiceIds = [];
37
+ _voiceHandlers = [];
38
+ _voiceProvider = [];
39
+ registerVoiceId(id) {
40
+ this._voiceIds.push(id);
41
+ this._updateSchema();
42
+ const disposeFn = /* @__PURE__ */ __name(() => {
43
+ this._voiceIds.splice(this._voiceIds.indexOf(id), 1);
44
+ }, "disposeFn");
45
+ return this[Context.current].effect(() => disposeFn);
46
+ }
47
+ hasVoiceId(id) {
48
+ return this._voiceIds.includes(id);
49
+ }
50
+ getVoiceIds() {
51
+ return Object.freeze(this._voiceIds);
52
+ }
53
+ registerVoiceIds(voices) {
54
+ this._voiceIds.push(...voices);
55
+ this._updateSchema();
56
+ const disposeFn = /* @__PURE__ */ __name(() => {
57
+ for (const voice of voices) {
58
+ this._voiceIds.splice(this._voiceIds.indexOf(voice), 1);
59
+ }
60
+ }, "disposeFn");
61
+ return this[Context.current].effect(() => disposeFn);
62
+ }
63
+ registerVoiceProvider(provider) {
64
+ this._voiceProvider.push(provider);
65
+ const disposeFn = /* @__PURE__ */ __name(() => {
66
+ this._voiceProvider.splice(this._voiceProvider.indexOf(provider), 1);
67
+ }, "disposeFn");
68
+ return this[Context.current].effect(() => disposeFn);
69
+ }
70
+ registerHandler(handler) {
71
+ if (typeof handler === "function") {
72
+ handler = { say: handler };
73
+ }
74
+ this._voiceHandlers.push(handler);
75
+ const disposeFn = /* @__PURE__ */ __name(() => {
76
+ this._voiceHandlers.splice(this._voiceHandlers.indexOf(handler), 1);
77
+ }, "disposeFn");
78
+ return this[Context.current].effect(() => disposeFn);
79
+ }
80
+ async say(input) {
81
+ if (typeof input === "string") {
82
+ input = { input, voiceId: this._voiceIds[0] };
83
+ }
84
+ input.speaker_id = input.voiceId;
85
+ await this._lazyInitVoiceProvider();
86
+ for (const handler of this._voiceHandlers) {
87
+ if (handler.isSupportVoiceId && !handler.isSupportVoiceId(input.voiceId)) {
88
+ continue;
89
+ }
90
+ try {
91
+ return await handler.say(input);
92
+ } catch (error) {
93
+ }
94
+ }
95
+ if (this.ctx["vits"]) {
96
+ return await this.ctx["vits"].say(input);
97
+ }
98
+ throw new Error(
99
+ `voice id ${input.voiceId} not found or generation failed`
100
+ );
101
+ }
102
+ async _lazyInitVoiceProvider() {
103
+ for (const provider of this._voiceProvider) {
104
+ if (this._voiceHandlers.indexOf(provider) !== -1) {
105
+ continue;
106
+ }
107
+ this.registerVoiceIds(await provider.provideVoiceIds());
108
+ this._voiceHandlers.push(provider);
109
+ }
110
+ }
111
+ _updateSchema() {
112
+ this.ctx.schema.set(
113
+ "voices",
114
+ Schema.union(
115
+ this._voiceIds.map((id) => Schema.const(id)).concat(Schema.const("????").hidden())
116
+ )
117
+ );
118
+ }
119
+ };
120
+ var BaseTTSProvider = class {
121
+ constructor(ctx, config) {
122
+ this.config = config;
123
+ ctx.tts.registerVoiceProvider(this);
124
+ }
125
+ static {
126
+ __name(this, "BaseTTSProvider");
127
+ }
128
+ async isSupportVoiceId(voiceId) {
129
+ return true;
130
+ }
131
+ };
132
+
133
+ // src/commands.ts
134
+ function apply(ctx, config) {
135
+ ctx.command("tts <text:text>", { checkArgCount: true }).option("speaker", "-s <speaker:string>").action(async ({ session, options }, text) => {
136
+ const voiceId = options.speaker ?? config.defaultSpeaker;
137
+ if (!voiceId) {
138
+ return session.text(".no-speaker");
139
+ }
140
+ if (!ctx.tts.hasVoiceId(voiceId)) {
141
+ return session.text(".invalid-speaker", [voiceId]);
142
+ }
143
+ try {
144
+ const audio = await ctx.tts.say({
145
+ input: text,
146
+ voiceId
147
+ });
148
+ return audio;
149
+ } catch (e) {
150
+ ctx.logger.error(e);
151
+ return session.text(".generate-failed");
152
+ }
153
+ });
154
+ }
155
+ __name(apply, "apply");
156
+
157
+ // src/config.ts
158
+ import { Schema as Schema2 } from "koishi";
159
+ var name = "tts-base";
160
+ var Config = Schema2.object({
161
+ defaultSpeaker: Schema2.dynamic("voices").description(
162
+ "Default speaker voice ID / 默认说话人音色"
163
+ )
164
+ });
165
+ var usage = `
166
+ 本插件作为 tts 服务的前置基础插件。
167
+ 此插件没有包含任何语音服务,它只是一个前置插件。`;
168
+ var inject = {
169
+ optional: ["vits"]
170
+ };
171
+
172
+ // src/compatible.ts
173
+ import { Service as Service2 } from "koishi";
174
+ var VitsCompatible = class extends Service2 {
175
+ static {
176
+ __name(this, "VitsCompatible");
177
+ }
178
+ constructor(ctx) {
179
+ super(ctx, "vits", true);
180
+ ctx.tts.registerVoiceProvider(this);
181
+ }
182
+ async isSupportVoiceId(voiceId) {
183
+ return true;
184
+ }
185
+ };
186
+
187
+ // src/index.ts
188
+ function apply2(ctx, config) {
189
+ ctx.i18n.define("zh-CN", require_zh_CN());
190
+ ctx.i18n.define("en-US", require_en_US());
191
+ ctx.plugin(TTSService, config);
192
+ ctx.plugin(
193
+ {
194
+ apply,
195
+ inject: ["tts"]
196
+ },
197
+ config
198
+ );
199
+ }
200
+ __name(apply2, "apply");
201
+ export {
202
+ BaseTTSProvider,
203
+ Config,
204
+ TTSService,
205
+ VitsCompatible,
206
+ apply2 as apply,
207
+ inject,
208
+ name,
209
+ usage
210
+ };
@@ -0,0 +1,26 @@
1
+ import { Context, h, Service } from 'koishi';
2
+ import { Config, TTSHandler, TTSInput, TTSProvider } from '.';
3
+ export declare class TTSService extends Service {
4
+ ctx: Context;
5
+ config: Config;
6
+ private _voiceIds;
7
+ private _voiceHandlers;
8
+ private _voiceProvider;
9
+ constructor(ctx: Context, config: Config);
10
+ registerVoiceId(id: string): () => void;
11
+ hasVoiceId(id: string): boolean;
12
+ getVoiceIds(): Readonly<string[]>;
13
+ registerVoiceIds(voices: string[]): () => void;
14
+ registerVoiceProvider(provider: TTSProvider): () => void;
15
+ registerHandler(handler: TTSHandler | TTSHandler['say']): () => void;
16
+ say(input: TTSInput | string): Promise<h>;
17
+ private _lazyInitVoiceProvider;
18
+ private _updateSchema;
19
+ }
20
+ export declare abstract class BaseTTSProvider<T = any> implements TTSProvider {
21
+ config: T;
22
+ constructor(ctx: Context, config: T);
23
+ abstract provideVoiceIds(): Promise<string[]>;
24
+ abstract say(input: TTSInput): Promise<h>;
25
+ isSupportVoiceId(voiceId: string | string[]): Promise<boolean>;
26
+ }
package/lib/types.d.ts ADDED
@@ -0,0 +1,20 @@
1
+ import { h, Session } from 'koishi';
2
+ import { TTSService } from './service';
3
+ export interface TTSHandler {
4
+ say(input: TTSInput): Promise<h>;
5
+ isSupportVoiceId?(voiceId: string | string[]): Promise<boolean>;
6
+ }
7
+ export interface TTSProvider extends TTSHandler {
8
+ provideVoiceIds(): Promise<string[]>;
9
+ }
10
+ export interface TTSInput {
11
+ session?: Session;
12
+ input: string | h;
13
+ speaker_id?: string | number;
14
+ voiceId?: string;
15
+ }
16
+ declare module 'koishi' {
17
+ interface Context {
18
+ tts: TTSService;
19
+ }
20
+ }
package/package.json ADDED
@@ -0,0 +1,61 @@
1
+ {
2
+ "name": "koishi-plugin-tts-base",
3
+ "description": "tts 服务基础插件",
4
+ "version": "1.0.0",
5
+ "main": "lib/index.cjs",
6
+ "module": "lib/index.mjs",
7
+ "typings": "lib/index.d.ts",
8
+ "exports": {
9
+ ".": {
10
+ "import": "./lib/index.mjs",
11
+ "require": "./lib/index.cjs",
12
+ "types": "./lib/index.d.ts"
13
+ },
14
+ "./package.json": "./package.json"
15
+ },
16
+ "type": "module",
17
+ "files": [
18
+ "lib",
19
+ "dist"
20
+ ],
21
+ "license": "MIT",
22
+ "keywords": [
23
+ "chatbot",
24
+ "koishi",
25
+ "plugin",
26
+ "tts",
27
+ "tts-base",
28
+ "service"
29
+ ],
30
+ "peerDependencies": {
31
+ "koishi": "^4.18.10"
32
+ },
33
+ "devDependencies": {
34
+ "esbuild": "^0.20.2",
35
+ "esbuild-register": "^3.6.0",
36
+ "eslint": "^8.57.1",
37
+ "eslint-config-prettier": "^9.1.2",
38
+ "eslint-config-standard": "^17.1.0",
39
+ "eslint-plugin-import": "^2.32.0",
40
+ "eslint-plugin-n": "^16.6.2",
41
+ "eslint-plugin-prettier": "^5.5.4",
42
+ "eslint-plugin-promise": "^7.2.1",
43
+ "koishi-plugin-puppeteer": "^3.9.0"
44
+ },
45
+ "koishi": {
46
+ "description": {
47
+ "zh": "tts 服务基础插件"
48
+ },
49
+ "service": {
50
+ "implements": [
51
+ "tts"
52
+ ]
53
+ }
54
+ },
55
+ "bugs": {
56
+ "url": "https://github.com/dingyi222666/koishi-plugin-tts-base/issues"
57
+ },
58
+ "homepage": "https://github.com/dingyi222666/koishi-plugin-tts-base#readme",
59
+ "repository": "git@github.com:dingyi222666/koishi-plugin-tts-base.git",
60
+ "author": "dingyi222666 <dingyi222666@foxmail.com>"
61
+ }
package/readme.md ADDED
@@ -0,0 +1,209 @@
1
+ # koishi-plugin-tts-base
2
+
3
+ [![npm](https://img.shields.io/npm/v/koishi-plugin-tts-base?style=flat-square)](https://www.npmjs.com/package/koishi-plugin-tts-base)
4
+
5
+ TTS 服务的前置基础插件,为 Koishi 提供统一的 TTS 服务接口。
6
+
7
+ ## 功能
8
+
9
+ - 提供统一的 `ctx.tts` 服务接口
10
+ - 支持多个 TTS Provider 同时注册
11
+ - 动态音色选择(配置界面自动显示可用音色)
12
+ - 兼容旧版 `vits` 服务
13
+ - 内置 `tts` 命令,支持 `-s` 选项选择音色
14
+
15
+ ## 用户使用
16
+
17
+ ```
18
+ tts <文本> # 使用默认音色朗读
19
+ tts <文本> -s <音色> # 使用指定音色朗读
20
+ ```
21
+
22
+ ## 开发者指南
23
+
24
+ ### 注册 TTS Provider
25
+
26
+ 推荐使用 `BaseTTSProvider` 基类来实现你的 TTS Provider:
27
+
28
+ ```typescript
29
+ import { Context, h } from 'koishi'
30
+ import { BaseTTSProvider, TTSInput } from 'koishi-plugin-tts-base'
31
+
32
+ export interface Config {
33
+ // 你的配置
34
+ }
35
+
36
+ class MyTTSProvider extends BaseTTSProvider<Config> {
37
+ constructor(ctx: Context, config: Config) {
38
+ super(ctx, config)
39
+ }
40
+
41
+ async provideVoiceIds(): Promise<string[]> {
42
+ // 返回你的 TTS 服务支持的音色列表
43
+ return ['voice1', 'voice2', 'voice3']
44
+ }
45
+
46
+ async say(input: TTSInput): Promise<h> {
47
+ const text = input.input.toString()
48
+ const voiceId = input.voiceId
49
+
50
+ // 调用你的 TTS 服务生成音频
51
+ const audioBuffer = await this.generateAudio(text, voiceId)
52
+
53
+ return h.audio(audioBuffer, 'audio/wav')
54
+ }
55
+
56
+ async isSupportVoiceId(voiceId: string): Promise<boolean> {
57
+ // 可选:判断是否支持该音色
58
+ return ['voice1', 'voice2', 'voice3'].includes(voiceId)
59
+ }
60
+
61
+ private async generateAudio(
62
+ text: string,
63
+ voiceId: string
64
+ ): Promise<Buffer> {
65
+ // 实现你的音频生成逻辑
66
+ }
67
+ }
68
+
69
+ export function apply(ctx: Context, config: Config) {
70
+ ctx.plugin(MyTTSProvider, config)
71
+ }
72
+
73
+ export const inject = ['tts']
74
+ ```
75
+
76
+ ### 手动注册(不使用基类)
77
+
78
+ 如果你需要更灵活的控制,可以手动注册:
79
+
80
+ ```typescript
81
+ import { Context } from 'koishi'
82
+ import { TTSProvider } from 'koishi-plugin-tts-base'
83
+
84
+ export function apply(ctx: Context) {
85
+ const provider: TTSProvider = {
86
+ async provideVoiceIds() {
87
+ return ['voice1', 'voice2']
88
+ },
89
+ async say(input) {
90
+ // 生成音频...
91
+ return h.audio(buffer, 'audio/wav')
92
+ },
93
+ async isSupportVoiceId(voiceId) {
94
+ return true
95
+ }
96
+ }
97
+
98
+ // 注册 provider
99
+ ctx.tts.registerVoiceProvider(provider)
100
+ }
101
+
102
+ export const inject = ['tts']
103
+ ```
104
+
105
+ ### 仅注册 Handler(不提供音色列表)
106
+
107
+ 如果你的服务支持任意音色或者音色由其他插件提供:
108
+
109
+ ```typescript
110
+ ctx.tts.registerHandler({
111
+ async say(input) {
112
+ return h.audio(buffer, 'audio/wav')
113
+ },
114
+ async isSupportVoiceId(voiceId) {
115
+ return voiceId.startsWith('my-prefix-')
116
+ }
117
+ })
118
+
119
+ // 或者简写
120
+ ctx.tts.registerHandler(async (input) => {
121
+ return h.audio(buffer, 'audio/wav')
122
+ })
123
+ ```
124
+
125
+ ### 仅注册音色 ID
126
+
127
+ ```typescript
128
+ // 注册单个音色
129
+ ctx.tts.registerVoiceId('my-voice')
130
+
131
+ // 注册多个音色
132
+ ctx.tts.registerVoiceIds(['voice1', 'voice2', 'voice3'])
133
+ ```
134
+
135
+ ### Vits 兼容模式
136
+
137
+ 如果你需要兼容旧版 `vits` 服务(让依赖 `vits` 的插件也能使用你的 TTS 服务),使用 `VitsCompatible`:
138
+
139
+ ```typescript
140
+ import { Context, h } from 'koishi'
141
+ import { VitsCompatible, TTSInput } from 'koishi-plugin-tts-base'
142
+
143
+ class MyVitsProvider extends VitsCompatible {
144
+ constructor(ctx: Context) {
145
+ super(ctx)
146
+ }
147
+
148
+ async provideVoiceIds(): Promise<string[]> {
149
+ return ['vits-voice1', 'vits-voice2']
150
+ }
151
+
152
+ async say(input: TTSInput): Promise<h> {
153
+ // 兼容旧版 speaker_id 参数
154
+ const speakerId = input.speaker_id ?? input.voiceId
155
+ // ...
156
+ return h.audio(buffer, 'audio/wav')
157
+ }
158
+ }
159
+
160
+ export function apply(ctx: Context) {
161
+ ctx.plugin(MyVitsProvider)
162
+ }
163
+
164
+ export const inject = ['tts']
165
+ ```
166
+
167
+ 使用 `VitsCompatible` 后,你的服务会同时注册为 `ctx.vits` 和 `ctx.tts` 的 provider。
168
+
169
+ ## API 参考
170
+
171
+ ### TTSService
172
+
173
+ | 方法 | 说明 |
174
+ | ---------------------------------------------- | ----------------------- |
175
+ | `say(input: TTSInput \| string)` | 生成语音 |
176
+ | `registerVoiceId(id: string)` | 注册单个音色 ID |
177
+ | `registerVoiceIds(ids: string[])` | 注册多个音色 ID |
178
+ | `registerHandler(handler: TTSHandler)` | 注册语音处理器 |
179
+ | `registerVoiceProvider(provider: TTSProvider)` | 注册语音提供者 |
180
+ | `hasVoiceId(id: string)` | 检查音色是否存在 |
181
+ | `getVoiceIds()` | 获取所有已注册的音色 ID |
182
+
183
+ ### TTSInput
184
+
185
+ ```typescript
186
+ interface TTSInput {
187
+ input: string | h // 要朗读的文本
188
+ voiceId?: string // 音色 ID
189
+ speaker_id?: string // 兼容旧版 vits 的参数
190
+ session?: Session // 可选的会话对象
191
+ }
192
+ ```
193
+
194
+ ### TTSHandler
195
+
196
+ ```typescript
197
+ interface TTSHandler {
198
+ say(input: TTSInput): Promise<h>
199
+ isSupportVoiceId?(voiceId: string): Promise<boolean>
200
+ }
201
+ ```
202
+
203
+ ### TTSProvider
204
+
205
+ ```typescript
206
+ interface TTSProvider extends TTSHandler {
207
+ provideVoiceIds(): Promise<string[]>
208
+ }
209
+ ```