349 lines
12 KiB
TypeScript
Raw Normal View History

2024-08-03 12:46:12 +08:00
import { errorMessage, successMessage } from '../Public/generalTools'
import { SoftwareService } from '../../define/db/service/SoftWare/softwareService'
import path from 'path'
2024-08-12 16:26:08 +08:00
import fs from 'fs'
2024-08-03 12:46:12 +08:00
import { define } from '../../define/define'
import { isEmpty } from 'lodash'
import { ValidateJson } from '../../define/Tools/validate'
2024-08-12 16:26:08 +08:00
import { CheckFileOrDirExist, CheckFolderExistsOrCreate, DeleteFolderAllFile } from '../../define/Tools/file'
import { TTSSelectModel } from '../../define/enum/tts'
import { EdgeTTS } from 'node-edge-tts'
2024-08-12 16:26:08 +08:00
const { v4: uuidv4 } = require('uuid')
import { TTSService } from '../../define/db/tts/ttsService'
import { tts } from '../../model/tts'
import { GeneralResponse } from '../../model/generalResponse'
import axios from 'axios'
import { GetEdgeTTSRole } from '../../define/tts/ttsDefine'
import { OptionServices } from "@/main/Service/Options/optionServices"
import { OptionKeyName } from '@/define/enum/option'
import { OptionModel } from '@/model/option/option'
2024-08-03 12:46:12 +08:00
export class TTS {
softService: SoftwareService
2024-08-12 16:26:08 +08:00
ttsService: TTSService
2024-08-03 12:46:12 +08:00
optionServices: OptionServices
constructor() {
this.optionServices = new OptionServices()
}
2024-08-03 12:46:12 +08:00
/**
* TTS服务
*/
async InitService() {
if (!this.softService) {
this.softService = await SoftwareService.getInstance()
}
2024-08-12 16:26:08 +08:00
if (!this.ttsService) {
this.ttsService = await TTSService.getInstance()
}
2024-08-03 12:46:12 +08:00
}
2024-08-12 16:26:08 +08:00
//#region 设置相关
/**
* TTS配置选项
*
* @param {string} key - TTS配置的键值
* @returns {Promise<any>} TTS配置选项的Promise对象
*
* @throws {Error}
*
* @example
* ```typescript
* const options = await GetTTSOptions('someKey');
* console.log(options);
* ```
*/
async GetTTSOptions(key: string): Promise<GeneralResponse.SuccessItem | GeneralResponse.ErrorItem> {
try {
// 开始请求数据
let res = await axios.get(define.lms + '/lms/LaitoolOptions/GetSimpleOptions/ttsrole');
if (res.data.code != 1) {
throw new Error(res.data.message)
}
let data = res.data.data.filter(item => item.key == "EdgeTTsRoles")
if (data.length != 1) {
throw new Error("获取TTS角色配置失败")
}
if (isEmpty(data[0].value) || !ValidateJson(data[0].value)) {
return successMessage(GetEdgeTTSRole(), "获取远程配置失败,获取默认配音角色", "TTS_GetTTSOptions"); // 使用默认值
}
// 返回远程值
return successMessage(JSON.parse(data[0].value), '获取TTS配置成功', 'TTS_GetTTSOptions')
} catch (error) {
return errorMessage('获取TTS配置失败错误信息如下' + error.toString(), 'TTS_GetTTSOptions')
2024-08-03 12:46:12 +08:00
}
}
2024-08-12 16:26:08 +08:00
//#endregion
//#region 合成音频相关
2024-08-03 12:46:12 +08:00
/**
*
* @param text
*/
async GenerateAudio() {
2024-08-03 12:46:12 +08:00
try {
await this.InitService()
let TTS_GlobalSetting = await this.optionServices.GetOptionByKey(OptionKeyName.TTS_GlobalSetting);
if (TTS_GlobalSetting.code == 0) {
throw new Error(TTS_GlobalSetting.message);
}
let TTS_GlobalSettingData = JSON.parse(TTS_GlobalSetting.data.value) as OptionModel.TTS_GlobalSettingModel
let text = TTS_GlobalSettingData.ttsText;
if (isEmpty(text)) {
throw new Error('生成音频失败,文本为空')
2024-08-03 12:46:12 +08:00
}
2024-08-12 16:26:08 +08:00
let res = undefined
2024-08-03 12:46:12 +08:00
2024-08-12 16:26:08 +08:00
// 生成对应的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 = TTS_GlobalSettingData.selectModel;
2024-08-12 16:26:08 +08:00
let hasSrt = true
2024-08-03 12:46:12 +08:00
switch (selectModel) {
2024-08-12 16:26:08 +08:00
case TTSSelectModel.edgeTTS:
hasSrt = TTS_GlobalSettingData.edgeTTS.saveSubtitles
res = await this.GenerateAudioByEdgeTTS(text, TTS_GlobalSettingData.edgeTTS, audioPath)
2024-08-03 12:46:12 +08:00
break
default:
throw new Error('未知的TTS模式')
}
2024-08-12 16:26:08 +08:00
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 successMessage(res, '生成音频成功', 'TTS_GenerateAudio')
2024-08-03 12:46:12 +08:00
} catch (error) {
return errorMessage('生成音频失败,错误信息如下:' + error.toString(), 'TTS_GenerateAudio')
}
}
/**
2024-08-12 16:26:08 +08:00
* 使EdgeTTS生成音频的方法
2024-08-03 12:46:12 +08:00
* @param text
* @param edgeTTS edgetts的设置
* @returns
*/
async GenerateAudioByEdgeTTS(text: string, edgeTTS: OptionModel.TTS_EdgeTTSModel, mp3Path: string) {
2024-08-03 12:46:12 +08:00
try {
const edgeTts = new EdgeTTS({
2024-08-03 12:46:12 +08:00
voice: edgeTTS.value,
lang: edgeTTS.lang,
outputFormat: 'audio-24khz-96kbitrate-mono-mp3',
2024-08-12 16:26:08 +08:00
saveSubtitles: true,
2024-08-03 12:46:12 +08:00
pitch: `${edgeTTS.pitch}%`,
rate: `${edgeTTS.rate}%`,
volume: `${edgeTTS.volumn}%`,
timeout: edgeTTS.timeOut ?? 100000
2024-08-03 12:46:12 +08:00
})
await edgeTts.ttsPromise(text, mp3Path)
2024-08-12 16:26:08 +08:00
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
);
2024-08-13 13:38:13 +08:00
2024-08-12 16:26:08 +08:00
// 初始化 SRT 内容
let srtContent = "";
let index = 1;
// 函数用于去掉文本末尾的标点符号
2024-08-13 13:38:13 +08:00
const removeTrailingPunctuation = (text) => {
return text.replace(/[\s“”《》,.!?;:"()\n]+$/, "");
};
// 函数用于将毫秒格式化为 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}`;
2024-08-12 16:26:08 +08:00
};
2024-08-13 13:38:13 +08:00
// 合并文本和时间轴
let combinedPart = "";
let startTime = null;
let endTime = null;
let j = 0;
let count = 0;
2024-08-12 16:26:08 +08:00
for (let i = 0; i < parts.length; i++) {
2024-08-13 13:38:13 +08:00
let partText = removeTrailingPunctuation(parts[i].trim());
// partText = partText.trim()
let tempText = partText.replaceAll(' ', '');
for (; j < srtJson.length; j++) {
let timePart = srtJson[j].part;
timePart = removeTrailingPunctuation(timePart.trim());
if (tempText.includes(timePart)) {
// 如果匹配到,合并文本片段并更新结束时间
combinedPart += timePart;
if (startTime === null) {
startTime = srtJson[j].start; // 设置开始时间
}
endTime = srtJson[j].end; // 更新结束时间
// 如果下一个文本片段开始匹配下一个标点,则跳出内层循环
if (combinedPart == tempText) {
j++; // 准备下一次对比从下一个时间片段开始
break;
}
} else {
j++; // 准备下一次对比从下一个时间片段开始
break;
}
count++;
console.log(count);
count = 0;
}
// 生成SRT段落
2024-08-12 16:26:08 +08:00
srtContent += `${index}\n${formatTime(startTime)} --> ${formatTime(
endTime
2024-08-13 13:38:13 +08:00
)}\n${partText}\n\n`;
// 重置变量
combinedPart = "";
startTime = null;
endTime = null;
2024-08-12 16:26:08 +08:00
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')
2024-08-03 12:46:12 +08:00
} catch (error) {
2024-08-12 16:26:08 +08:00
return errorMessage('删除配音历史失败,错误信息:' + error.message, 'TTS_DeleteTTSHistory')
2024-08-03 12:46:12 +08:00
}
}
2024-08-12 16:26:08 +08:00
//#endregion
2024-08-03 12:46:12 +08:00
}