2024-08-12 16:26:08 +08:00

327 lines
11 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import { errorMessage, successMessage } from '../Public/generalTools'
import { SoftwareService } from '../../define/db/service/SoftWare/softwareService'
import path from 'path'
import fs from 'fs'
import { define } from '../../define/define'
import { isEmpty } from 'lodash'
import { ValidateJson } from '../../define/Tools/validate'
import { CheckFileOrDirExist, CheckFolderExistsOrCreate, DeleteFolderAllFile } from '../../define/Tools/file'
import { TTSSelectModel } from '../../define/enum/tts'
const { EdgeTTS } = require('node-edge-tts')
const { v4: uuidv4 } = require('uuid')
import { TTSService } from '../../define/db/tts/ttsService'
import { tts } from '../../model/tts'
import { GeneralResponse } from '../../model/generalResponse'
export class TTS {
softService: SoftwareService
ttsService: TTSService
constructor() { }
/**
* 初始化TTS服务
*/
async InitService() {
if (!this.softService) {
this.softService = await SoftwareService.getInstance()
}
if (!this.ttsService) {
this.ttsService = await TTSService.getInstance()
}
}
//#region 设置相关
/**
* 初始化TTS设置
*/
async InitTTSSetting() {
let defaultData = {
selectModel: 'edge-tts',
edgeTTS: {
value: 'zh-CN-XiaoxiaoNeural',
gender: 'Female',
label: '晓晓',
lang: 'zh-CN',
saveSubtitles: true,
pitch: 0, // 语调
rate: 10, // 倍速
volumn: 0 // 音量
}
}
await this.SaveTTSConfig(defaultData)
return defaultData
}
/**
* 获取TTS配置
*/
// @ts-ignore
async GetTTSCOnfig(): Promise<GeneralResponse.SuccessItem | GeneralResponse.ErrorItem> {
try {
await this.InitService()
let res = this.softService.GetSoftWarePropertyData('ttsSetting')
let resObj = undefined
if (isEmpty(res)) {
// 没有数据,需要初始化
resObj = await this.InitTTSSetting()
} else {
let tryParse = ValidateJson(res)
if (!tryParse) {
throw new Error('解析TTS配置失败数据格式不正确')
}
resObj = JSON.parse(res)
}
return successMessage(resObj, '获取TTS配置成功', 'TTS_GetTTSCOnfig')
} catch (error) {
return errorMessage('获取TTS配置失败错误信息如下' + error.toString(), 'TTS_GetTTSCOnfig')
}
}
/**
* 保存TTS配置
* @param {*} data 要保存的数据
*/
// @ts-ignore
async SaveTTSConfig(data: TTSSettingModel.TTSSetting) {
try {
await this.InitService()
let res = this.softService.SaveSoftwarePropertyData('ttsSetting', JSON.stringify(data))
return res
} catch (error) {
return errorMessage('保存TTS配置失败错误信息如下' + error.toString(), 'TTS_SaveTTSConfig')
}
}
//#endregion
//#region 合成音频相关
/**
* 生成音频
* @param text 要生成的文本
*/
async GenerateAudio(text: string) {
try {
await this.InitService()
let ttsSetting = await this.GetTTSCOnfig()
if (ttsSetting.code === 0) {
return ttsSetting
}
let res = undefined
// 生成对应的ID
let thisId = uuidv4()
// 讲text写道本地
let textPath = path.join(define.tts_path, `${thisId}/${thisId}.txt`)
await CheckFolderExistsOrCreate(path.dirname(textPath))
await fs.promises.writeFile(textPath, text, 'utf-8')
let audioPath = path.join(define.tts_path, `${thisId}/${thisId}.mp3`)
let selectModel = ttsSetting.data.selectModel as TTSSelectModel
let hasSrt = true
switch (selectModel) {
case TTSSelectModel.edgeTTS:
hasSrt = ttsSetting.data.edgeTTS.saveSubtitles
res = await this.GenerateAudioByEdgeTTS(text, ttsSetting.data.edgeTTS, audioPath)
break
default:
throw new Error('未知的TTS模式')
}
if (res == undefined) {
throw new Error('生成音频失败,未知错误')
}
// 这边返回成功,保存配音历史
this.ttsService.AddTTSHistory({
name: text.substring(0, 10),
ttsPath: res.mp3Path ? path.relative(define.tts_path, res.mp3Path) : null,
hasSrt: hasSrt,
selectModel: selectModel,
srtJsonPath: res.srtJsonPath ? path.relative(define.tts_path, res.srtJsonPath) : null,
id: thisId,
textPath: textPath ? path.relative(define.tts_path, textPath) : null
})
return res
} catch (error) {
return errorMessage('生成音频失败,错误信息如下:' + error.toString(), 'TTS_GenerateAudio')
}
}
/**
* 使用EdgeTTS生成音频的方法生成完成返回生成的音频路径
* @param text 要生成的文本
* @param edgeTTS edgetts的设置
* @returns
*/
async GenerateAudioByEdgeTTS(text: string, edgeTTS: TTSSettingModel.EdgeTTSSetting, mp3Path: string) {
try {
const tts = new EdgeTTS({
voice: edgeTTS.value,
lang: edgeTTS.lang,
outputFormat: 'audio-24khz-96kbitrate-mono-mp3',
saveSubtitles: true,
pitch: `${edgeTTS.pitch}%`,
rate: `${edgeTTS.rate}%`,
volumn: `${edgeTTS.volumn}%`
})
let ttsRes = await tts.ttsPromise(text, mp3Path)
console.log(ttsRes)
return {
mp3Path: mp3Path,
srtJsonPath: mp3Path + '.json'
};
} catch (error) {
throw error
}
}
//#endregion
//#region 合成字幕
/**
* 通过配音历史ID生成字幕
* @param ttsId 配音历史ID
*/
async GenerateSRT(ttsId: string): Promise<GeneralResponse.ErrorItem | GeneralResponse.SuccessItem> {
try {
// 获取配音历史
let ttsHistory = this.ttsService.GetTTSHistoryById(ttsId)
let selectModel = ttsHistory.selectModel as TTSSelectModel
let res = undefined
switch (selectModel) {
case TTSSelectModel.edgeTTS:
res = await this.GenerateSRTByEdgeTTS(ttsHistory)
break
default:
throw new Error('未知的TTS模式')
}
// 这边重新请求,返回一个完整的
let ttsHistoryData = this.ttsService.GetTTSHistoryById(ttsId)
return successMessage(ttsHistoryData, '生成字幕成功', 'TTS_GenerateSRT')
} catch (error) {
return errorMessage('生成字幕失败,错误信息如下:' + error.toString(), 'TTS_GenerateSRT')
}
}
async GenerateSRTByEdgeTTS(ttsHistory: tts.TTSModel) {
try {
// 一系列的检查文件是不是存在
if (isEmpty(ttsHistory.textPath)) {
throw new Error('生成字幕失败,文本文件不存在')
}
if (isEmpty(ttsHistory.srtJsonPath)) {
throw new Error('生成字幕失败srtJson文件不存在')
}
let checkFileExist = await CheckFileOrDirExist(ttsHistory.textPath)
if (!checkFileExist) {
throw new Error('生成字幕失败,文本文件不存在')
}
checkFileExist = await CheckFileOrDirExist(ttsHistory.srtJsonPath)
if (!checkFileExist) {
throw new Error('生成字幕失败srtJson文件不存在')
}
let text = await fs.promises.readFile(ttsHistory.textPath, 'utf-8');
let srtJson = JSON.parse(await fs.promises.readFile(ttsHistory.srtJsonPath, 'utf-8'));
// 根据标点符号和换行符分割文案
// 更新后的正则表达式,匹配所有中文和英文的标点符号以及换行符
const parts = text.match(
/[^,。!?;:“”()《》,.!?;:"()<>$$$$\n]+[,。!?;:“”()《》,.!?;:"()<>$$$$\n]*/g
);
// 初始化 SRT 内容
let srtContent = "";
let index = 1;
// 函数用于去掉文本末尾的标点符号
const removeTrailingPunctuation = (text: string) => {
return text.replace(/[\s“”《》,.!?;:"()<>$$$$]+$/, "");
};
// 配对时间轴和文案分段
for (let i = 0; i < parts.length; i++) {
const part = parts[i];
let startTime =
srtJson[i * 2]?.start || srtJson[srtJson.length - 1].start;
let endTime =
srtJson[i * 2 + 1]?.end || srtJson[srtJson.length - 1].end;
// 去掉文案末尾的标点符号
const cleanedPart = removeTrailingPunctuation(part.trim());
// 将时间格式化为 SRT 格式
const formatTime = (ms) => {
const date = new Date(ms);
const hours = String(date.getUTCHours()).padStart(2, "0");
const minutes = String(date.getUTCMinutes()).padStart(2, "0");
const seconds = String(date.getUTCSeconds()).padStart(2, "0");
const milliseconds = String(date.getUTCMilliseconds()).padStart(3, "0");
return `${hours}:${minutes}:${seconds},${milliseconds}`;
};
// 生成 SRT 片段
srtContent += `${index}\n${formatTime(startTime)} --> ${formatTime(
endTime
)}\n${cleanedPart}\n\n`;
index++;
}
console.log(srtContent);
// 将数据写入srt文件
let srtPath = path.join(define.tts_path, `${ttsHistory.id}/${ttsHistory.id}.srt`)
await fs.promises.writeFile(srtPath, srtContent, 'utf-8')
// 更新配音历史
this.ttsService.UpdetateTTSHistory(ttsHistory.id, { srtPath: path.relative(define.tts_path, srtPath) })
// 返回成功
return srtPath;
} catch (error) {
throw error
}
}
//#endregion
//#region 配音历史记录相关
/**
* 获取配音的历史记录
* @param queryCondition 查询的条件
* @returns
*/
async GetTTSHistoryData(queryCondition: tts.TTSHistoryQueryParams): Promise<GeneralResponse.SuccessItem | GeneralResponse.ErrorItem> {
try {
await this.InitService()
let res = this.ttsService.GetTTSHistory(queryCondition);
return successMessage(res, "获取配音历史任务成功", 'TTS_GetTTSHistoryData')
} catch (error) {
return errorMessage('查询配音历史失败,错误信息:' + error.message, 'TTS_GetTTSHistoryData')
}
}
/**
* 删除配音历史
* @param ttsId 要删除的ID
* @returns
*/
async DeleteTTSHistory(ttsId: string): Promise<GeneralResponse.ErrorItem | GeneralResponse.SuccessItem> {
try {
await this.InitService()
// 先删除数据库中数据,然后删除文件
let ttsHistory = this.ttsService.GetTTSHistoryById(ttsId)
this.ttsService.DeleteTTSHistory(ttsId);
// 删除文件
let ttsDir = path.join(define.tts_path, ttsId)
await DeleteFolderAllFile(ttsDir, true);
return successMessage(ttsId, '删除配音历史成功', 'TTS_DeleteTTSHistory')
} catch (error) {
return errorMessage('删除配音历史失败,错误信息:' + error.message, 'TTS_DeleteTTSHistory')
}
}
//#endregion
}