koishi-plugin-beta-dxl-bert-vits
Version:
beta-dxl-bert-vits。
118 lines (98 loc) • 3.29 kB
text/typescript
import { Context, h, Logger } from 'koishi'
import Vits from '@initencounter/vits'
import { Config } from './config'
import { SpeakConfig } from './types'
import { SpeakerInfo } from './constants'
export class betavits {
private logger: Logger
constructor(
private ctx: Context,
public config: Config,
private speakersMap: Map<string, SpeakerInfo>,
private SpeakerIdMap: Record<number, string>
) {
this.logger = ctx.logger('bert-vits')
}
async say(input: string, options?: Partial<SpeakConfig>): Promise<h> {
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
}
}
private _generatePlayLoad(
input: string,
internalSpeakerName: string,
options: SpeakConfig
) {
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,
}
}
private async _request(payload: any, api: string, fileApi: string) {
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 h.audio(buffer, 'wav')
}
}
export class betavitsService extends Vits {
constructor(ctx: Context, public impl: betavits) {
super(ctx)
}
say(options: Vits.Result): Promise<h> {
return this.impl.say(options.input, {
speaker: this.impl['SpeakerIdMap'][options.speaker_id],
})
}
}
function fallback<T>(options: Partial<T>, defaultValues: any): Required<T> {
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 as Required<T>
}