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

415 lines
17 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 { LogScheduler } from "../task/logScheduler";
2024-09-12 14:13:09 +08:00
import { BookTaskStatus, BookType, OperateBookType } from "../../../define/enum/bookEnum";
2024-08-18 16:22:19 +08:00
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
logScheduler: LogScheduler
2024-08-18 16:22:19 +08:00
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
2024-09-12 14:13:09 +08:00
* @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')
}
}
2024-09-04 19:49:20 +08:00
//#endregion
2024-08-18 16:22:19 +08:00
2024-09-04 19:49:20 +08:00
//#region 文案相关的操作
2024-08-18 16:22:19 +08:00
/**
*
* @param bookTaskId ID
2024-09-12 14:13:09 +08:00
* @returns
2024-08-18 16:22:19 +08:00
*/
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
}
2024-09-04 19:49:20 +08:00
/**
2024-09-12 14:13:09 +08:00
*
* @param bookId
* @param bookTaskId
* @param txtPath
* @returns
2024-09-04 19:49:20 +08:00
*/
async ImportCopywriting(bookId: string, bookTaskId: string, txtPath: string): Promise<GeneralResponse.ErrorItem | GeneralResponse.SuccessItem> {
try {
let word = await fs.promises.readFile(txtPath, 'utf-8');
let wordLines = word.split('\n').map(line => line.trim()).filter(line => line.length > 0);
// 判断小说类型,是反推的话,文案和分镜数据要对应
let book = await this.bookServiceBasic.GetBookDataById(bookId)
let bookTaskDetail = await this.bookServiceBasic.GetBookTaskDetailData({
bookId: bookId,
bookTaskId: bookTaskId
})
if (book.type == BookType.MJ_REVERSE || book.type == BookType.SD_REVERSE) {
if (wordLines.length != bookTaskDetail.length) {
throw new Error("文案行数和分镜数据不对应,请检查")
}
} else if (book.type == BookType.ORIGINAL) {
// 原创这边也要做些判断,待定
throw new Error("原创小说暂时不支持导入文案");
} else {
throw new Error("未知的小说类型,请检查")
}
let result = []
// 这边开始导入文案,这边使用事务
this.bookServiceBasic.transaction((realm) => {
for (let i = 0; i < bookTaskDetail.length; i++) {
const element = bookTaskDetail[i];
let btd = realm.objectForPrimaryKey("BookTaskDetail", element.id);
let ag = wordLines[i] ? wordLines[i] : ''
btd.afterGpt = ag
result.push({
bookTaskDetailId: element.id,
afterGpt: ag
});
}
})
return successMessage(result, "导入文案成功", "ReverseBook_ImportCopywriting")
} catch (error) {
return errorMessage("导入文案失败,失败信息如下:" + error.message, 'ReverseBook_ImportCopywriting')
}
}
2024-08-21 11:47:05 +08:00
/**
*
* @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)
}
}
2024-09-12 14:13:09 +08:00
// 判断分镜数据和批次数据是不是相同的 好好办办
2024-08-21 11:47:05 +08:00
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
}
2024-09-12 14:13:09 +08:00
/**
*
* @param bookTaskId ID
* @param copywritingData
* @param operateBookType
* @returns
*/
async SaveCopywriting(bookTaskId: string, copywritingData: SubtitleModel.SaveCopywritingData[], operateBookType: OperateBookType): Promise<GeneralResponse.ErrorItem | GeneralResponse.SuccessItem> {
try {
if (operateBookType != OperateBookType.BOOKTASK) {
throw new Error('目前只支持对小说任务的文案保存')
}
let bookTask = await this.bookServiceBasic.GetBookTaskDataById(bookTaskId)
let bookTaskDetails = await this.bookServiceBasic.GetBookTaskDetailData({
bookTaskId: bookTaskId
}, true)
// 获取SD设置
let sdConifg = JSON.parse(await fs.promises.readFile(define.sd_setting, 'utf-8'));
let adetailer = false;
if (sdConifg && sdConifg?.webui?.adetailer) {
adetailer = true;
}
2024-09-12 14:13:09 +08:00
if (bookTaskDetails.length == 0) {
// 新增
for (let i = 0; i < copywritingData.length; i++) {
const element = copywritingData[i];
await this.bookServiceBasic.AddBookTaskDetail({
bookTaskId: bookTaskId,
bookId: bookTask.bookId,
startTime: element.start_time,
endTime: element.end_time,
status: BookTaskStatus.WAIT,
word: element.word,
afterGpt: element.after_gpt,
subValue: JSON.stringify(element.subValue),
timeLimit: element.timeLimit,
// 新增修脸跟随
adetailer: adetailer
2024-09-12 14:13:09 +08:00
})
}
} else {
// 修改,这边就要判断是不是数量一致了
if (bookTaskDetails.length != copywritingData.length) {
throw new Error("已有文案,再导入的时候,文案函数数量和分镜数据不一致,请检查")
}
// 开始修改。修改使用事务吧
this.bookServiceBasic.transaction((realm) => {
for (let i = 0; i < copywritingData.length; i++) {
const element = copywritingData[i];
let btd = realm.objectForPrimaryKey("BookTaskDetail", bookTaskDetails[i].id);
if (btd == null) {
throw new Error("未找到对应的分镜数据,请检查")
}
// 开始修改
btd.startTime = element.start_time;
btd.endTime = element.end_time;
btd.word = element.word;
btd.afterGpt = element.after_gpt;
btd.subValue = JSON.stringify(element.subValue);
btd.timeLimit = element.timeLimit
}
})
}
} catch (error) {
return errorMessage("保存文案数据失败,失败信息如下:" + error.toString(), 'SubtitleService_SaveCopywriting')
}
}
2024-08-18 16:22:19 +08:00
//#endregion
2024-09-12 14:13:09 +08:00
}