LaiTool/src/main/Service/Subtitle/subtitleService.ts

292 lines
12 KiB
TypeScript
Raw Normal View History

2024-08-18 16:22:19 +08:00
import { isEmpty } from "lodash";
import { GetSubtitleType, SubtitleSavePositionType } from "../../../define/enum/waterMarkAndSubtitle"
import { errorMessage, successMessage } from "../../Public/generalTools"
import { SoftWareServiceBasic } from "../ServiceBasic/softwareServiceBasic"
import { ValidateJson } from "../../../define/Tools/validate";
import { GeneralResponse } from "../../../model/generalResponse";
import { SubtitleModel } from "../../../model/subtitle";
import { define } from '../../../define/define'
import path from 'path'
import fs from 'fs'
import { CheckFileOrDirExist } from "../../../define/Tools/file";
import { BookServiceBasic } from "../ServiceBasic/bookServiceBasic";
import { Subtitle } from "./subtitle";
import { TaskScheduler } from "../taskScheduler";
import { OperateBookType } from "../../../define/enum/bookEnum";
import { Book } from "../../../model/book";
2024-08-21 11:47:05 +08:00
import { TimeStringToMilliseconds } from "../../../define/Tools/time";
2024-08-18 16:22:19 +08:00
export class SubtitleService {
softWareServiceBasic: SoftWareServiceBasic
bookServiceBasic: BookServiceBasic
subtitle: Subtitle
taskScheduler: TaskScheduler
constructor() {
this.softWareServiceBasic = new SoftWareServiceBasic();
this.bookServiceBasic = new BookServiceBasic();
this.subtitle = new Subtitle();
}
//#region 设置相关的方法
/**
*
*/
async InitSubtitleSetting(): Promise<SubtitleModel.subtitleSettingModel> {
let defauleSetting = {
selectModel: GetSubtitleType.LAI_WHISPER,
laiWhisper: {
url: 'https://api.laitool.cc/',
apiKey: '你的LAI API KEY',
syncGPTAPIKey: false,
prompt: undefined
}
} as SubtitleModel.subtitleSettingModel
await this.softWareServiceBasic.SaveSoftwarePropertyData("subtitleSetting", JSON.stringify(defauleSetting));
return defauleSetting
}
/**
*
*/
async GetSubtitleSetting(): Promise<GeneralResponse.ErrorItem | GeneralResponse.SuccessItem> {
try {
let subtitleSetting = undefined as SubtitleModel.subtitleSettingModel
let subtitleSettingString = await this.softWareServiceBasic.GetSoftWarePropertyData('subtitleSetting');
if (isEmpty(subtitleSettingString)) {
// 初始化
subtitleSetting = await this.InitSubtitleSetting();
} else {
if (ValidateJson(subtitleSettingString)) {
subtitleSetting = JSON.parse(subtitleSettingString)
} else {
throw new Error("提起字幕设置解析失败,请重置后重新配置")
}
}
return successMessage(subtitleSetting, '获取提取字幕设置成功', "SubtitleService_GetSubtitleSetting")
} catch (error) {
return errorMessage("获取字幕设置失败,失败信息如下:" + error.message, "SubtitleService_GetSubtitleSetting")
}
}
/**
*
*/
async ResetSubtitleSetting(): Promise<GeneralResponse.ErrorItem | GeneralResponse.SuccessItem> {
try {
let subtitleSetting = await this.InitSubtitleSetting();
return successMessage(subtitleSetting, "重置字幕设置成功", "SubtitleService_ResetSubtitleSetting")
} catch (error) {
return errorMessage("重置字幕设置失败,失败信息如下:" + error.message, "SubtitleService_ResetSubtitleSetting")
}
}
/**
*
* @param subtitleSetting
*/
async SaveSubtitleSetting(subtitleSetting: SubtitleModel.subtitleSettingModel): Promise<GeneralResponse.ErrorItem | GeneralResponse.SuccessItem> {
try {
// 判断模式,通过不同的模式判断是不是又必要检查
if (subtitleSetting.selectModel == GetSubtitleType.LOCAL_OCR) {
let localOcrPath = path.join(define.scripts_path, 'LaiOcr/LaiOcr.exe');
let fileIsExists = await CheckFileOrDirExist(localOcrPath);
if (!fileIsExists) {
throw new Error("当前模式未本地OCR但是没有检查到对应的执行文件请查看教程安装对应的拓展");
}
} else if (subtitleSetting.selectModel == GetSubtitleType.LOCAL_WHISPER) {
// let localWhisper = path.join(define.scripts_path,'')
// 这个好像没有什么可以检查的
} else if (subtitleSetting.selectModel == GetSubtitleType.LAI_WHISPER) {
// 判断是不是laitool的不是的话报错
if (!subtitleSetting.laiWhisper.url.includes('laitool')) {
throw new Error('该模式只能试用LAI API的接口请求');
}
if (isEmpty(subtitleSetting.laiWhisper.apiKey)) {
throw new Error("当前模式为LAI API的接口请求请输入LAI API KEY")
}
if (isEmpty(subtitleSetting.laiWhisper.url)) {
throw new Error("当前模式为LAI API的接口请求请输入LAI API URL")
}
} else {
throw new Error("未知的识别字幕模式")
}
// 检查做完,开始保存数据
await this.softWareServiceBasic.SaveSoftwarePropertyData('subtitleSetting', JSON.stringify(subtitleSetting))
return successMessage(null, "保存提取文案设置成功", "SubtitleService_SaveSubtitleSetting");
} catch (error) {
return errorMessage("保存提取文案设置失败,失败信息如下:" + error.message, "SubtitleService_SaveSubtitleSetting")
}
}
//#endregion
//#region 语音转文案或者是字幕识别
/**
2024-08-20 10:37:38 +08:00
*
* @param bookId ID
* @param bookTaskId ID
* @param operateBookType
* @param coverData
* @returns
2024-08-18 16:22:19 +08:00
*/
2024-08-20 10:37:38 +08:00
async GetCopywriting(bookId: string, bookTaskId: string, operateBookType: OperateBookType, coverData: boolean): Promise<GeneralResponse.ErrorItem | GeneralResponse.SuccessItem> {
2024-08-18 16:22:19 +08:00
try {
let subtitleSettingRes = await this.GetSubtitleSetting();
if (subtitleSettingRes.code == 0) {
throw new Error(subtitleSettingRes.message)
}
let subtitleSetting = subtitleSettingRes.data as SubtitleModel.subtitleSettingModel;
let res = undefined as GeneralResponse.ErrorItem | GeneralResponse.SuccessItem
let bookTaskDetails = undefined as Book.SelectBookTaskDetail[]
let tempBookTaskId = bookTaskId
if (operateBookType == OperateBookType.BOOKTASK) {
bookTaskDetails = await this.bookServiceBasic.GetBookTaskDetailData({
bookId: bookId,
bookTaskId: bookTaskId
})
} else if (operateBookType == OperateBookType.BOOKTASKDETAIL) {
let tempBookTaskDetail = await this.bookServiceBasic.GetBookTaskDetailDataById(bookTaskId)
tempBookTaskId = tempBookTaskDetail.bookTaskId
bookTaskDetails = [tempBookTaskDetail]
} else {
throw new Error("未知的操作类型")
}
2024-08-20 10:37:38 +08:00
if (!coverData) { // 不覆盖数据,将已经有的数据过滤掉
bookTaskDetails = bookTaskDetails.filter(item => isEmpty(item.afterGpt) && isEmpty(item.word))
}
2024-08-18 16:22:19 +08:00
if (bookTaskDetails.length <= 0) {
2024-08-20 10:37:38 +08:00
throw new Error("分镜信息不存在 / 已经有文案,无需提取");
2024-08-18 16:22:19 +08:00
}
let { book, bookTask } = await this.bookServiceBasic.GetBookAndTask(bookId, tempBookTaskId)
switch (subtitleSetting.selectModel) {
case GetSubtitleType.LOCAL_OCR:
res = await this.subtitle.GetCopywritingByLocalOcr(book, bookTask, bookTaskDetails)
break;
case GetSubtitleType.LOCAL_WHISPER:
throw new Error("本地Whisper暂时不支持")
break;
case GetSubtitleType.LAI_WHISPER:
res = await this.subtitle.GetCopywritingByLaiWhisper(book, bookTask, bookTaskDetails, subtitleSetting)
break;
default:
throw new Error("未知的识别字幕模式")
}
if (operateBookType == OperateBookType.BOOKTASKDETAIL) {
let bookTaskDetail = await this.bookServiceBasic.GetBookTaskDetailDataById(bookTaskId)
return successMessage(bookTaskDetail.afterGpt, "获取文案成功", "ReverseBook_GetCopywriting")
} else {
return res
}
} catch (error) {
return errorMessage("获取分镜数据失败,失败信息如下:" + error.message, 'ReverseBook_GetCopywriting')
}
}
/**
*
* @param bookTaskId ID
* @returns
*/
async ExportCopywriting(bookTaskId: string): Promise<GeneralResponse.ErrorItem | GeneralResponse.SuccessItem> {
try {
2024-08-21 11:47:05 +08:00
let bookTask = await this.bookServiceBasic.GetBookTaskDataById(bookTaskId)
2024-08-18 16:22:19 +08:00
let book = await this.bookServiceBasic.GetBookDataById(bookTask.bookId)
let bookTaskDetails = await this.bookServiceBasic.GetBookTaskDetailData({
bookId: book.id,
bookTaskId: bookTaskId
})
let emptyList = []
let content = []
// 检查是不是所有的里面都有文案
for (let i = 0; i < bookTaskDetails.length; i++) {
const element = bookTaskDetails[i];
if (isEmpty(element.afterGpt)) {
emptyList.push(element.name)
} else {
content.push(element.afterGpt)
}
}
if (emptyList.length > 0) {
throw new Error(`以下分镜没有文案:${emptyList.join("\n")}`);
}
// 写出文案
let contentStr = content.join("。\n");
contentStr = contentStr + '。'
let wordPath = path.join(book.bookFolderPath, "文案.txt")
await fs.promises.writeFile(wordPath, contentStr, 'utf-8')
return successMessage(wordPath, "导出文案成功", "ReverseBook_ExportCopywriting")
} catch (error) {
return errorMessage("导出文案失败,失败信息如下:" + error.message, 'ReverseBook_ExportCopywriting')
}
2024-08-21 11:47:05 +08:00
}
//#region 文案相关的操作
/**
*
* @param bookTaskId ID
*/
async ClearImportWord(bookTaskId: string): Promise<GeneralResponse.ErrorItem | GeneralResponse.SuccessItem> {
try {
let bookTaskDetails = await this.bookServiceBasic.GetBookTaskDetailData({
bookTaskId: bookTaskId
})
if (bookTaskDetails.length <= 0) {
throw new Error("没有找到对应小说批次任务的的分镜数据")
}
let book = await this.bookServiceBasic.GetBookDataById(bookTaskDetails[0].bookId)
let originalTimePath = path.join(book.bookFolderPath, `data/${book.id}.mp4.json`);
let originalTime = [] as any[];
if (await CheckFileOrDirExist(originalTimePath)) {
let originalTimeString = await fs.promises.readFile(originalTimePath, 'utf-8');
if (ValidateJson(originalTimeString)) {
originalTime = JSON.parse(originalTimeString)
}
}
// 判断分镜数据和批次数据是不是相同的
if (originalTime.length != bookTaskDetails.length) {
originalTime = []
}
2024-08-18 16:22:19 +08:00
2024-08-21 11:47:05 +08:00
// 开始删除,需要做的操作,删除 bookTaskDetail 众的subValue 数据,将时间数据复原
for (let i = 0; i < bookTaskDetails.length; i++) {
const element = bookTaskDetails[i];
let updateObj = {
subValue: undefined
} as Book.SelectBookTaskDetail
// 开始重置时间
if (originalTime[i]) {
let startTime = TimeStringToMilliseconds(originalTime[i][0])
let endTime = TimeStringToMilliseconds(originalTime[i][1])
updateObj.startTime = startTime;
updateObj.endTime = endTime;
updateObj.timeLimit = undefined
}
await this.bookServiceBasic.UpdateBookTaskDetail(element.id, updateObj);
}
let returnBookTaskDetail = await this.bookServiceBasic.GetBookTaskDetailData({
bookTaskId: bookTaskId
})
// 成功
return successMessage(returnBookTaskDetail, "重置导入文案对齐数据成功", "ReverseBook_ClearImportWord")
} catch (error) {
return errorMessage("清除导入的文案失败,失败信息如下:" + error.message, 'ReverseBook_ClearImportWord')
}
2024-08-18 16:22:19 +08:00
}
//#endregion
2024-08-21 11:47:05 +08:00
//#endregion
2024-08-18 16:22:19 +08:00
}