diff --git a/.gitignore b/.gitignore index 6118292..98a05a8 100644 --- a/.gitignore +++ b/.gitignore @@ -28,3 +28,5 @@ resources/config* *Lai_1.exe* .DS_Store *.log* +resources/scripts/db/book.realm.lock +resources/scripts/db/software.realm.lock diff --git a/package-lock.json b/package-lock.json index af50196..ae77d11 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "laitool", - "version": "3.0.1-preview.6", + "version": "3.0.1-preview.7", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "laitool", - "version": "3.0.1-preview.6", + "version": "3.0.1-preview.7", "hasInstallScript": true, "dependencies": { "@alicloud/alimt20181012": "^1.2.0", diff --git a/package.json b/package.json index cf912a1..e72d627 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "laitool", - "version": "3.0.1-preview.6", + "version": "3.0.1-preview.7", "description": "An AI tool for image processing, video processing, and other functions.", "main": "./out/main/index.js", "author": "laitool.cn", diff --git a/resources/scripts/db/book.realm.lock b/resources/scripts/db/book.realm.lock index d79fac9..014b7e8 100644 Binary files a/resources/scripts/db/book.realm.lock and b/resources/scripts/db/book.realm.lock differ diff --git a/resources/scripts/db/software.realm b/resources/scripts/db/software.realm index 0700489..4702f0f 100644 Binary files a/resources/scripts/db/software.realm and b/resources/scripts/db/software.realm differ diff --git a/resources/scripts/db/software.realm.lock b/resources/scripts/db/software.realm.lock index 1de12a3..651e455 100644 Binary files a/resources/scripts/db/software.realm.lock and b/resources/scripts/db/software.realm.lock differ diff --git a/resources/scripts/db/tts.realm.lock b/resources/scripts/db/tts.realm.lock index 5f5ccef..8ddc843 100644 Binary files a/resources/scripts/db/tts.realm.lock and b/resources/scripts/db/tts.realm.lock differ diff --git a/src/define/Tools/file.ts b/src/define/Tools/file.ts index 133541e..ffd7454 100644 --- a/src/define/Tools/file.ts +++ b/src/define/Tools/file.ts @@ -236,3 +236,20 @@ export async function GetFileSize(filePath: string): Promise { throw error } } + +/** + * 获取文件夹下的所有子文件夹的完整路径 + * @param folderPath 文件夹的路径 + * @returns 返回子文件夹的完整路径数组 + */ +export async function GetSubdirectories(folderPath: string): Promise { + try { + const files = await fs.promises.readdir(folderPath, { withFileTypes: true }); + const directories = files + .filter((fileStat) => fileStat.isDirectory()) + .map((fileStat) => path.join(folderPath, fileStat.name)); + return directories; + } catch (error) { + throw new Error(error); + } +} diff --git a/src/define/db/model/SoftWare/software.ts b/src/define/db/model/SoftWare/software.ts index e6f66df..358cfae 100644 --- a/src/define/db/model/SoftWare/software.ts +++ b/src/define/db/model/SoftWare/software.ts @@ -13,6 +13,7 @@ export class SoftwareModel extends Realm.Object { aiSetting: string | null // AI相关的配置的json字符串 watermarkSetting: string | null // 水印相关的配置的json字符串 translationSetting: string | null // 翻译相关的配置的json字符串 + subtitleSetting: string | null // 字幕相关的配置的json字符串 static schema: ObjectSchema = { name: 'Software', @@ -27,7 +28,8 @@ export class SoftwareModel extends Realm.Object { writeSetting: 'string?', aiSetting: 'string?', watermarkSetting: 'string?', - translationSetting: 'string?' + translationSetting: 'string?', + subtitleSetting: "string?" }, // 主键为_id primaryKey: 'id' diff --git a/src/define/db/service/Book/bookBackTaskListService.ts b/src/define/db/service/Book/bookBackTaskListService.ts index feaa05d..76ed356 100644 --- a/src/define/db/service/Book/bookBackTaskListService.ts +++ b/src/define/db/service/Book/bookBackTaskListService.ts @@ -135,13 +135,13 @@ export class BookBackTaskListService extends BaseRealmService { * 新增一个小说相关的后台任务队列 * @param bookBackTask 要添加的小说数据 */ - async AddBookBackTask( + AddBookBackTask( bookId: string, taskType: BookBackTaskType, executeType = TaskExecuteType.AUTO, bookTaskId = null, bookTaskDetailId = null - ) { + ): GeneralResponse.SuccessItem | GeneralResponse.ErrorItem { try { // 通过bookid获取book信息 let book = this.realm.objectForPrimaryKey('Book', bookId) diff --git a/src/define/db/service/Book/bookService.ts b/src/define/db/service/Book/bookService.ts index 58be02a..5ab9172 100644 --- a/src/define/db/service/Book/bookService.ts +++ b/src/define/db/service/Book/bookService.ts @@ -11,6 +11,7 @@ import { isEmpty } from 'lodash' import { FfmpegOptions } from '../../../../main/Service/ffmpegOptions.js' import { version } from '../../../../../package.json' import { Book } from '../../../../model/book.js' +import { GeneralResponse } from '../../../../model/generalResponse.js' export class BookService extends BaseRealmService { static instance: BookService | null = null @@ -37,7 +38,7 @@ export class BookService extends BaseRealmService { * 获取小说信息,没有找到返回null * @param bookId */ - GetBookDataById(bookId): Book.SelectBook | null { + GetBookDataById(bookId: string): Book.SelectBook | null { try { if (isEmpty(bookId)) { throw new Error('获取小说信息失败,缺少小说ID') @@ -275,7 +276,7 @@ export class BookService extends BaseRealmService { * @param bookId 小说的ID * @param bookData 要修改的小说数据 */ - async UpdateBookData(bookId: string, bookData: Book.SelectBook) { + UpdateBookData(bookId: string, bookData: Book.SelectBook): Book.SelectBook { try { if (bookId == null) { throw new Error('修改小说数据失败,缺少小说ID') @@ -303,8 +304,27 @@ export class BookService extends BaseRealmService { if (bookRes == null) { throw new Error('获取修改后的小说数据失败,小说ID对应的数据不存在') } + // return successMessage(bookRes, '修改小说数据成功', 'ReverseBook_UpdateBookData') + return bookRes; + } catch (error) { + throw error + } + } - return successMessage(bookRes, '修改小说数据成功', 'ReverseBook_UpdateBookData') + /** + * 删除指定的小说任务 + * @param bookId 需要删除的小说的ID + */ + DeleteBookData(bookId: string): void { + try { + this.transaction(() => { + let book = this.realm.objectForPrimaryKey('Book', bookId); + if (book == null) { + throw new Error('未找到对应的小说') + } + // 删除对应的小说 + this.realm.delete(book) + }) } catch (error) { throw error } diff --git a/src/define/db/service/Book/bookTaskDetailService.ts b/src/define/db/service/Book/bookTaskDetailService.ts index e6cd94f..b9e9ae6 100644 --- a/src/define/db/service/Book/bookTaskDetailService.ts +++ b/src/define/db/service/Book/bookTaskDetailService.ts @@ -72,7 +72,7 @@ export class BookTaskDetailService extends BaseRealmService { return JoinPath(define.project_path, subImage) }), characterTags: item.characterTags ? item.characterTags.map((tag) => tag) : null, - subValue: item.subValue, + subValue: isEmpty(item.subValue) ? null : JSON.parse(item.subValue), reversePrompt: item.reversePrompt.map((reversePrompt) => { return { ...reversePrompt @@ -101,7 +101,6 @@ export class BookTaskDetailService extends BaseRealmService { if (bookTaskDetailId == null) { throw new Error('获取小说任务详细信息失败,缺少ID') } - let bookTaskDetails = this.GetBookTaskData({ id: bookTaskDetailId }) if (bookTaskDetails.data.length <= 0) { return null; @@ -165,7 +164,7 @@ export class BookTaskDetailService extends BaseRealmService { * @param bookTaskDetailId * @param updateData */ - UpdateBookTaskDetail(bookTaskDetailId: string, updateData: Book.SelectBookTaskDetail) { + UpdateBookTaskDetail(bookTaskDetailId: string, updateData: Book.SelectBookTaskDetail): Book.SelectBookTaskDetail { try { this.transaction(() => { let bookTaskDetail = this.realm.objectForPrimaryKey('BookTaskDetail', bookTaskDetailId) @@ -178,18 +177,21 @@ export class BookTaskDetailService extends BaseRealmService { } bookTaskDetail.updateTime = new Date() }) - return successMessage( - null, - '修改小说任务详细信息成功', - 'BookTaskDetailService_UpdateBookTaskDetail' - ) + let res = this.GetBookTaskDetailDataById(bookTaskDetailId) + return res; } catch (error) { throw error } } + /** + * 更新指定ID的反推提示词数据 + * @param bookTaskDetailId + * @param mjMessage + */ UpdateBookTaskDetailMjMessage(bookTaskDetailId: string, mjMessage: Book.MJMessage): void { try { + console.log('UpdateBookTaskDetailMjMessage', bookTaskDetailId, mjMessage) this.transaction(() => { let mjMessageRes = this.realm.objectForPrimaryKey('MJMessage', bookTaskDetailId) let bookTaskDetail = this.realm.objectForPrimaryKey('BookTaskDetail', bookTaskDetailId) @@ -276,12 +278,20 @@ export class BookTaskDetailService extends BaseRealmService { * @param bookTaskDetailId 小说分镜的ID */ DeleteBookTaskDetailReversePromptById(bookTaskDetailId: string): void { - let bookTaskDetail = this.realm.objectForPrimaryKey('BookTaskDetail', bookTaskDetailId); - if (bookTaskDetail == null) { - throw new Error('删除小说任务详细信息的反推提示词失败,未找到对应的分镜信息') - } this.transaction(() => { - this.realm.delete(bookTaskDetail.reversePrompt) + let bookTaskDetails = this.realm.objects('BookTaskDetail').filtered('id = $0', bookTaskDetailId); + if (bookTaskDetails.length <= 0) { + throw new Error('删除小说任务详细信息的反推提示词失败,未找到对应的分镜信息') + } + let bookTaskDetail = bookTaskDetails[0]; + + bookTaskDetail.gptPrompt = undefined; + // 删除所有的反推提示词 + if (bookTaskDetail.reversePrompt) { + bookTaskDetail.reversePrompt.forEach(item => { + this.realm.delete(item) + }) + } }) } } diff --git a/src/define/db/service/Book/bookTaskService.ts b/src/define/db/service/Book/bookTaskService.ts index badfdf1..1f93f90 100644 --- a/src/define/db/service/Book/bookTaskService.ts +++ b/src/define/db/service/Book/bookTaskService.ts @@ -132,7 +132,7 @@ export class BookTaskService extends BaseRealmService { * @param bookTaskId 小说批次任务Id * @param status 目标状态 */ - UpdateBookTaskStatus(bookTaskId: string, status: BookTaskStatus, errorMsg: string | null = null) { + UpdateBookTaskStatus(bookTaskId: string, status: BookTaskStatus, errorMsg: string | null = null): GeneralResponse.SuccessItem { try { this.transaction(() => { // 修改对应小说批次任务的状态 diff --git a/src/define/db/service/SoftWare/softwareBasic.ts b/src/define/db/service/SoftWare/softwareBasic.ts index 3e80d51..0ffe2e2 100644 --- a/src/define/db/service/SoftWare/softwareBasic.ts +++ b/src/define/db/service/SoftWare/softwareBasic.ts @@ -147,6 +147,14 @@ const migration = (oldRealm: Realm, newRealm: Realm) => { } }) } + if (oldRealm.schemaVersion < 20) { + newRealm.write(() => { + const newSoftwares = newRealm.objects('Software') + for (let software of newSoftwares) { + software.subtitleSetting = null // 字幕的默认设置 + } + }) + } } export class BaseSoftWareService extends BaseService { @@ -185,7 +193,7 @@ export class BaseSoftWareService extends BaseService { MjSettingModel ], path: dbPath, - schemaVersion: 19, // 当前版本号 + schemaVersion: 21, // 当前版本号 migration: migration } // 判断当前全局是不是又当前这个 diff --git a/src/define/db/service/SoftWare/softwareService.ts b/src/define/db/service/SoftWare/softwareService.ts index bf1d19a..7e6d004 100644 --- a/src/define/db/service/SoftWare/softwareService.ts +++ b/src/define/db/service/SoftWare/softwareService.ts @@ -1,6 +1,7 @@ import Realm, { UpdateMode } from 'realm' import { successMessage } from '../../../../main/Public/generalTools' import { BaseSoftWareService } from './softwareBasic.js' +import { GeneralResponse } from '../../../../model/generalResponse' const { v4: uuidv4 } = require('uuid') export class SoftwareService extends BaseSoftWareService { @@ -24,7 +25,7 @@ export class SoftwareService extends BaseSoftWareService { } // 修改数据库中行中的某个属性数据 - UpdateSoftware(software) { + UpdateSoftware(software: SoftwareSettingModel.SoftwareSetting): GeneralResponse.SuccessItem { try { this.realm.write(() => { this.realm.create('Software', software, UpdateMode.Modified) @@ -41,7 +42,7 @@ export class SoftwareService extends BaseSoftWareService { * @param software 软件配置信息 * @returns */ - AddSfotware(software) { + AddSfotware(software: SoftwareSettingModel.SoftwareSetting): GeneralResponse.SuccessItem { try { software.id = uuidv4() this.realm.write(() => { @@ -56,7 +57,7 @@ export class SoftwareService extends BaseSoftWareService { /** * 或软件基础配置信息 */ - GetSoftwareData() { + GetSoftwareData(): GeneralResponse.SuccessItem { try { let softwares = this.realm.objects('Software') diff --git a/src/define/define_string.ts b/src/define/define_string.ts index 376e3e7..1d84901 100644 --- a/src/define/define_string.ts +++ b/src/define/define_string.ts @@ -123,7 +123,8 @@ export const DEFINE_STRING = { GPT: { INIT_SERVER_GPT_OPTIONS: 'INIT_SERVER_GPT_OPTIONS', GET_AI_SETTING: 'GET_AI_SETTING', - SAVE_AI_SETTING: 'SAVE_AI_SETTING' + SAVE_AI_SETTING: 'SAVE_AI_SETTING', + SYNC_GPT_KEY: "SYNC_GPT_KEY" }, QUEUE_BATCH: { @@ -153,7 +154,6 @@ export const DEFINE_STRING = { SD: { LOAD_SD_SERVICE_DATA: 'LOAD_SD_SERVICE_DATA', TXT2IMG: 'TXT2IMG', - SD_MERGE_PROMPT: "SD_MERGE_PROMPT" }, MJ: { SAVE_WORD_SRT: 'SAVE_WORD_SRT', @@ -175,7 +175,6 @@ export const DEFINE_STRING = { GET_MJ_IMAGE_ROBOT_MODEL: 'GET_MJ_IMAGE_ROBOT_MODEL', MACTH_USER_RETURN: 'MACTH_USER_RETURN', AUTO_MATCH_USER: 'AUTO_MATCH_USER', - MJ_MERGE_PROMPT: "MJ_MERGE_PROMPT", ADD_MJ_GENADD_MJ_GENERATE_IMAGE_TASK: "ADD_MJ_GENADD_MJ_GENERATE_IMAGE_TASK", MJ_IMAGE: "MJ_IMAGE" }, @@ -208,6 +207,7 @@ export const DEFINE_STRING = { BOOK: { MAIN_DATA_RETURN: 'MAIN_DATA_RETURN', // 监听任务返回 + REPLACE_VIDEO_CURRENT_FRAME: 'REPLACE_VIDEO_CURRENT_FRAME', GET_BOOK_TYPE: 'GET_BOOK_TYPE', ADD_OR_MODIFY_BOOK: 'ADD_OR_MODIFY_BOOK', GET_BOOK_DATA: 'GET_BOOK_DATA', @@ -232,6 +232,10 @@ export const DEFINE_STRING = { HD_IMAGE: "HD_IMAGE", USE_BOOK_VIDEO_DATA_TO_BOOK_TASK: "USE_BOOK_VIDEO_DATA_TO_BOOK_TASK", ADD_JIANYING_DRAFT: "ADD_JIANYING_DRAFT", + EXPORT_COPYWRITING: "EXPORT_COPYWRITING", + MERGE_PROMPT: "MERGE_PROMPT", + RESET_BOOK_DATA: "RESET_BOOK_DATA", + DELETE_BOOK_DATA: "DELETE_BOOK_DATA", COMPUTE_STORYBOARD: 'COMPUTE_STORYBOARD', @@ -291,7 +295,10 @@ export const DEFINE_STRING = { WRITE: { GET_WRITE_CONFIG: 'GET_WRITE_CONFIG', SAVE_WRITE_CONFIG: 'SAVE_WRITE_CONFIG', - ACTION_START: 'ACTION_START' + ACTION_START: 'ACTION_START', + GET_SUBTITLE_SETTING: "GET_SUBTITLE_SETTING", + RESET_SUBTITLE_SETTING: "RESET_SUBTITLE_SETTING", + SAVE_SUBTITLE_SETTING: "SAVE_SUBTITLE_SETTING", }, DB: { UPDATE_BOOK_TASK_DATA: "UPDATE_BOOK_TASK_DATA", diff --git a/src/define/enum/bookEnum.ts b/src/define/enum/bookEnum.ts index 6b1faca..9b7bf88 100644 --- a/src/define/enum/bookEnum.ts +++ b/src/define/enum/bookEnum.ts @@ -207,10 +207,6 @@ export enum TagDefineType { SCENE_MAIN = "scene_main", } -export enum MergeType { - BOOKTASK = 'bookTask', // 整个小说批次分镜合并 - BOOKTASKDETAIL = 'bookTaskDetail' // 单个分镜合并 -} export enum OperateBookType { BOOK = 'book', // 这个小说的所有批次 @@ -227,3 +223,14 @@ export enum CopyImageType { // 不包含图 NONE = 'none' } + +export enum PromptMergeType { + // mj 合并 + MJ_MERGE = 'mj_merge', + + // SD 合并 + SD_MERGE = 'sd_merge', + + // D3 合并 + D3_MERGE = 'd3_merge' +} diff --git a/src/define/enum/softwareEnum.ts b/src/define/enum/softwareEnum.ts index fd8e34c..ed0d2bd 100644 --- a/src/define/enum/softwareEnum.ts +++ b/src/define/enum/softwareEnum.ts @@ -60,7 +60,8 @@ export enum ResponseMessageType { GET_TEXT = 'getText', // 获取文案 REMOVE_WATERMARK = "REMOVE_WATERMARK",// 删除水印 MJ_REVERSE = 'MJ_REVERSE',// MJ反推,返回反推结果 - PROMPT_TRANSLATE = 'PROMPT_TRANSLATE',// 提示词翻译 + REVERSE_PROMPT_TRANSLATE = 'REVERSE_PROMPT_TRANSLATE',// 反推提示词翻译 + GPT_PROMPT_TRANSLATE = 'GPT_PROMPT_TRANSLATE', // GPT提示词翻译 MJ_IMAGE = 'MJ_IMAGE',// MJ 生成图片 HD_IMAGE = 'HD_IMAGE',// HD 生成图片 } @@ -72,4 +73,11 @@ export enum LaiAPIType { HK_PROXY = "hk-proxy", // 备用站点 BAK_MAIN = 'bak-main' +} + +// 同步GPTkey的分类 +export enum SyncGptKeyType { + // 字幕设置 + SUBTITLE_SETTING = 'subtitle_setting', + } \ No newline at end of file diff --git a/src/define/enum/translate.ts b/src/define/enum/translate.ts index 14fefb9..07a78bf 100644 --- a/src/define/enum/translate.ts +++ b/src/define/enum/translate.ts @@ -2,7 +2,6 @@ export enum TranslateType { // 反推提示词翻译 REVERSE_PROMPT_TRANSLATE = 'reverse_prompt_translate', - // GPT提示词翻译 GPT_PROMPT_TRANSLATE = 'gpt_prompt_translate', } diff --git a/src/define/enum/waterMarkAndSubtitle.ts b/src/define/enum/waterMarkAndSubtitle.ts index d92b5aa..6cfa65f 100644 --- a/src/define/enum/waterMarkAndSubtitle.ts +++ b/src/define/enum/waterMarkAndSubtitle.ts @@ -27,4 +27,14 @@ export enum WaterMarkResponseDateType { export enum RemoveWatermarkType { LOCAL_LAMA = 'local_lama', IOPAINT = 'iopaint' +} + +// 获取字幕的方法类型 +export enum GetSubtitleType { + // 本地OCR + LOCAL_OCR = 'local_ocr', + // 本地Whisper + LOCAL_WHISPER = 'local_whisper', + // LAI WHISPER + LAI_WHISPER = 'lai_whisper', } \ No newline at end of file diff --git a/src/main/IPCEvent/bookIpc.js b/src/main/IPCEvent/bookIpc.ts similarity index 77% rename from src/main/IPCEvent/bookIpc.js rename to src/main/IPCEvent/bookIpc.ts index 30a3fda..9147d9d 100644 --- a/src/main/IPCEvent/bookIpc.js +++ b/src/main/IPCEvent/bookIpc.ts @@ -2,7 +2,7 @@ import { ipcMain } from 'electron' import { DEFINE_STRING } from '../../define/define_string' import { ReverseBook } from '../Service/Book/ReverseBook' import { BasicReverse } from '../Service/Book/basicReverse' -import { Subtitle } from '../Service/subtitle' +import { Subtitle } from '../Service/Subtitle/subtitle' import { BookBasic } from '../Service/Book/BooKBasic' import { MJOpt } from '../Service/MJ/mj' import { BookImage } from '../Service/Book/bookImage' @@ -10,6 +10,8 @@ import { ImageStyle } from '../Service/Book/imageStyle' import { BookTask } from '../Service/Book/bookTask' import { BookVideo } from '../Service/Book/bookVideo' import { Watermark } from '../Service/watermark' +import { SubtitleService } from '../Service/Subtitle/subtitleService' +import { BookFrame } from '../Service/Book/bookFrame' let reverseBook = new ReverseBook() let basicReverse = new BasicReverse() let subtitle = new Subtitle() @@ -20,14 +22,16 @@ let imageStyle = new ImageStyle() let bookTask = new BookTask() let bookVideo = new BookVideo() let watermark = new Watermark() +let subtitleService = new SubtitleService() +let bookFrame = new BookFrame() export function BookIpc() { // 获取样式图片的子列表 - ipcMain.handle(DEFINE_STRING.BOOK.GET_BOOK_TYPE, async (event) => reverseBook.GetBookType()) + ipcMain.handle(DEFINE_STRING.BOOK.GET_BOOK_TYPE, async (event) => bookBasic.GetBookType()) // 新增或者是修改小说数据 ipcMain.handle(DEFINE_STRING.BOOK.ADD_OR_MODIFY_BOOK, async (event, book) => - reverseBook.AddOrModifyBook(book) + bookBasic.AddOrModifyBook(book) ) // 获取小说数据(通过传递的参数进行筛选) @@ -83,8 +87,7 @@ export function BookIpc() { //#endregion - //#region 一键反推的单个任务 - + //#region 分镜相关 // 开始计算分镜 ipcMain.handle( DEFINE_STRING.BOOK.COMPUTE_STORYBOARD, @@ -97,23 +100,46 @@ export function BookIpc() { async (event, bookId) => await reverseBook.Framing(bookId) ) - // 开始执行分镜任务 + // 替换分镜视频的当前帧 + ipcMain.handle(DEFINE_STRING.BOOK.REPLACE_VIDEO_CURRENT_FRAME, async (event, bookTaskDetailId, currentTime) => + await bookFrame.ReplaceVideoCurrentFrame(bookTaskDetailId, currentTime) + ) + + //#endregion + + //#region 提示词相关 + + // 合并提示词 + ipcMain.handle(DEFINE_STRING.BOOK.MERGE_PROMPT, async (event, id, type, operateBookType) => await reverseBook.MergePrompt(id, type, operateBookType)) + + //#endregion + + //#region 一键反推的单个任务 + + + // 开始执行获取小说文案的方法 ipcMain.handle( DEFINE_STRING.BOOK.GET_COPYWRITING, - async (event, bookId, bookTaskId) => await reverseBook.GetCopywriting(bookId, bookTaskId) + async (event, bookId, bookTaskId, operateBookType) => await subtitleService.GetCopywriting(bookId, bookTaskId, operateBookType) + ) + + // 获取小说的文案数据,然后保存到对应的文件中 + ipcMain.handle( + DEFINE_STRING.BOOK.EXPORT_COPYWRITING, + async (event, bookTaskId) => await subtitleService.ExportCopywriting(bookTaskId) ) // 执行去除水印 ipcMain.handle( DEFINE_STRING.BOOK.REMOVE_WATERMARK, - async (event, id,operateBookType) => await watermark.RemoveWatermark(id,operateBookType) + async (event, id, operateBookType) => await watermark.RemoveWatermark(id, operateBookType) ) // 添加反推任务到任务列表 ipcMain.handle( DEFINE_STRING.BOOK.ADD_REVERSE_PROMPT, - async (event, bookTaskDetailIds, type) => - await reverseBook.AddReversePromptTask(bookTaskDetailIds, type) + async (event, bookTaskDetailIds, operateBookType, type) => + await reverseBook.AddReversePrompt(bookTaskDetailIds, operateBookType, type) ) // 将反推出来的提示词写入到GPT提示词里面 @@ -154,9 +180,19 @@ export function BookIpc() { // 一拆四,将一个任务拆分成四个任务,并且复制对应的图片 ipcMain.handle( DEFINE_STRING.BOOK.ONE_TO_FOUR_BOOK_TASK, - async (event, bookTaskDetailId) => await bookBasic.OneToFourBookTask(bookTaskDetailId) + async (event, bookTaskDetailId) => await bookTask.OneToFourBookTask(bookTaskDetailId) ) + //#region 小说相关 + + // 重置小说数据 + ipcMain.handle(DEFINE_STRING.BOOK.RESET_BOOK_DATA, async (event, bookId) => await bookBasic.ResetBookData(bookId)) + + // 删除小说数据 + ipcMain.handle(DEFINE_STRING.BOOK.DELETE_BOOK_DATA, async (event, bookId) => await bookBasic.DeleteBookData(bookId)) + + //#endregion + //#region 小说批次任务相关 // 重置小说批次数据 diff --git a/src/main/IPCEvent/gptIpc.js b/src/main/IPCEvent/gptIpc.js index 70ff5d4..b7ccc30 100644 --- a/src/main/IPCEvent/gptIpc.js +++ b/src/main/IPCEvent/gptIpc.js @@ -83,6 +83,12 @@ function GptIpc() { DEFINE_STRING.GPT.SAVE_AI_SETTING, async (event, value) => await gptSetting.SaveAISetting(value) ) + + // 同步GPT Key 到指定的设置 + ipcMain.handle( + DEFINE_STRING.GPT.SYNC_GPT_KEY, + async (event, syncType) => await gptSetting.SyncGptKey(syncType) + ) } export { GptIpc } diff --git a/src/main/IPCEvent/index.js b/src/main/IPCEvent/index.js index a96a926..12c5c8d 100644 --- a/src/main/IPCEvent/index.js +++ b/src/main/IPCEvent/index.js @@ -12,7 +12,7 @@ import { MainIpc } from './mainIpc.js' import { GlobalIpc } from './globalIpc.js' import { ImageIpc } from './imageIpc.js' import { SystemIpc } from './systemIpc.js' -import { BookIpc } from './bookIpc.js' +import { BookIpc } from './bookIpc' import { TTSIpc } from './ttsIpc.js' import { DBIpc } from './dbIpc' diff --git a/src/main/IPCEvent/mjIpc.js b/src/main/IPCEvent/mjIpc.js index c52ffe3..9234023 100644 --- a/src/main/IPCEvent/mjIpc.js +++ b/src/main/IPCEvent/mjIpc.js @@ -138,12 +138,6 @@ function MjIpc() { async (event, value) => await discordSimple.DiscordDeleteMessage(value) ) - // MJ合并提示词命令 - ipcMain.handle( - DEFINE_STRING.MJ.MJ_MERGE_PROMPT, - async (event, id, mergeType) => await mjOpt.MergePrompt(id, mergeType) - ) - // MJ出单张图 ipcMain.handle( DEFINE_STRING.MJ.ADD_MJ_GENADD_MJ_GENERATE_IMAGE_TASK, diff --git a/src/main/IPCEvent/sdIpc.js b/src/main/IPCEvent/sdIpc.js index 706ec08..a972e89 100644 --- a/src/main/IPCEvent/sdIpc.js +++ b/src/main/IPCEvent/sdIpc.js @@ -29,11 +29,5 @@ function SdIpc() { // 文生图,单张 ipcMain.handle(DEFINE_STRING.SD.TXT2IMG, async (event, value) => await sd.txt2img(value)) - - // SD合并提示词 - ipcMain.handle( - DEFINE_STRING.SD.SD_MERGE_PROMPT, - async (event, id, mergeType) => await sdOpt.MergePrompt(id, mergeType) - ) } export { SdIpc } diff --git a/src/main/IPCEvent/writingIpc.js b/src/main/IPCEvent/writingIpc.js index 2d52eb2..46c6dc5 100644 --- a/src/main/IPCEvent/writingIpc.js +++ b/src/main/IPCEvent/writingIpc.js @@ -4,6 +4,8 @@ import { Writing } from '../Service/writing' let writing = new Writing(global) import { WritingSetting } from '../setting/writeSetting' let writingSetting = new WritingSetting() +import { SubtitleService } from '../Service/Subtitle/subtitleService' +let subtitleService = new SubtitleService() function WritingIpc() { // 监听分镜时间的保存 @@ -36,7 +38,7 @@ function WritingIpc() { async (event, value) => await writing.ImportSrtAndGetTime(value) ) - // 获取文案相关的配置(数据库) + // 获取文案格式化相关的配置(数据库) ipcMain.handle( DEFINE_STRING.WRITE.GET_WRITE_CONFIG, async (event) => await writingSetting.GetWritingConfig() @@ -48,6 +50,23 @@ function WritingIpc() { async (event, value) => await writingSetting.SaveWriteConfig(value) ) + // 获取提取文案相关的配置(数据库) + ipcMain.handle( + DEFINE_STRING.WRITE.GET_SUBTITLE_SETTING, + async (event) => await subtitleService.GetSubtitleSetting() + ) + + // 重置提取文案相关的配置(数据库) + ipcMain.handle( + DEFINE_STRING.WRITE.RESET_SUBTITLE_SETTING, + async (event) => await subtitleService.ResetSubtitleSetting() + ) + + ipcMain.handle( + DEFINE_STRING.WRITE.SAVE_SUBTITLE_SETTING, + async (event, subtitleSetting) => await subtitleService.SaveSubtitleSetting(subtitleSetting) + ) + ipcMain.handle( DEFINE_STRING.WRITE.ACTION_START, async (event, aiSetting, word) => await writing.ActionStart(aiSetting, word) diff --git a/src/main/Service/Book/BooKBasic.ts b/src/main/Service/Book/BooKBasic.ts index e5aea30..8535708 100644 --- a/src/main/Service/Book/BooKBasic.ts +++ b/src/main/Service/Book/BooKBasic.ts @@ -1,29 +1,19 @@ import { BookType, OperateBookType, TagDefineType } from '../../../define/enum/bookEnum' import { errorMessage, successMessage } from '../../Public/generalTools' import { BookService } from '../../../define/db/service/Book/bookService' -import { BookTaskService } from '../../../define/db/service/Book/bookTaskService' -import { BookTaskDetailService } from '../../../define/db/service/Book/bookTaskDetailService' -import { CopyImageType } from '../../../define/enum/bookEnum' -const { v4: uuidv4 } = require('uuid') -import { define } from '../../../define/define' import path from 'path' -import { CheckFileOrDirExist, CheckFolderExistsOrCreate, CopyFileOrFolder } from '../../../define/Tools/file' -import { Book } from '../../../model/book' +import { CheckFileOrDirExist, CheckFolderExistsOrCreate, CopyFileOrFolder, DeleteFolderAllFile, GetSubdirectories } from '../../../define/Tools/file' import { GeneralResponse } from '../../../model/generalResponse' -import { cloneDeep, isEmpty } from 'lodash' +import { BookServiceBasic } from '../ServiceBasic/bookServiceBasic' +import { BookTask } from './bookTask' +import fs from 'fs' export class BookBasic { - constructor() { } - bookTaskService: BookTaskService - bookTaskDetailService: BookTaskDetailService - - async InitService() { - if (!this.bookTaskService) { - this.bookTaskService = await BookTaskService.getInstance() - } - if (!this.bookTaskDetailService) { - this.bookTaskDetailService = await BookTaskDetailService.getInstance() - } + bookServiceBasic: BookServiceBasic + bookTask: BookTask + constructor() { + this.bookServiceBasic = new BookServiceBasic(); + this.bookTask = new BookTask() } //#region 小说相关操作 @@ -73,209 +63,112 @@ export class BookBasic { //#endregion - //#region 小说批次任务相关操作 + //#region 小说相关操作 - - async OneToFourBookTask(bookTaskId: string) { + /** + * 重置小说数据 + * @param bookId 小说ID + * @returns + */ + async ResetBookData(bookId: string): Promise { try { - console.log(bookTaskId) - await this.InitService(); - let copyCount = 100 - let bookTask = this.bookTaskService.GetBookTaskDataById(bookTaskId) - if (bookTask == null) { - throw new Error("没有找到对应的数小说任务,请检查数据") - } - // 获取所有的出图中最少的 - let bookTaskDetail = this.bookTaskDetailService.GetBookTaskData({ - bookTaskId: bookTaskId - }).data as Book.SelectBookTaskDetail[] - if (bookTaskDetail == null || bookTaskDetail.length <= 0) { - throw new Error("没有对应的小说分镜任务,请先添加分镜任务") + let book = await this.bookServiceBasic.GetBookDataById(bookId) + // 获取所有的小说批次 + let bookTasks = (await this.bookServiceBasic.GetBookTaskData({ + bookId: bookId + })).bookTasks; + // 重置批次任务 + for (let i = 0; i < bookTasks.length; i++) { + const element = bookTasks[i]; + // 第一个重置,后面的删除 + if (i == 0) { + let resetBookTaskData = await this.bookTask.ReSetBookTask(element.id); + if (resetBookTaskData.code == 0) { + throw new Error(resetBookTaskData.message) + } + } else { + let deleteBookTaskData = await this.bookTask.DeleteBookTask(element.id); + if (deleteBookTaskData.code == 0) { + throw new Error(deleteBookTaskData.message) + } + } } - for (let i = 0; i < bookTaskDetail.length; i++) { - const element = bookTaskDetail[i]; - if (isEmpty(element.subImagePath)) { - throw new Error("检测到图片没有出完,请先检查出图") - } - if (element.subImagePath == null || element.subImagePath.length <= 0) { - throw new Error("检测到图片没有出完,请先检查出图") - } - if (element.subImagePath.length < copyCount) { - copyCount = element.subImagePath.length - } + // 开始重置小说数据 + await this.bookServiceBasic.UpdateBookData(bookId, { + srtPath: undefined, + audioPath: undefined, + subtitlePosition: undefined, + imageStyle: undefined, + autoAnalyzeCharacter: undefined, + customizeImageStyle: undefined, + videoConfig: undefined, + prefixPrompt: undefined, + suffixPrompt: undefined, + draftSrtStyle: undefined, + backgroundMusic: undefined, + friendlyReminder: undefined, + watermarkPosition: undefined, + }) + // 文件重置,获取data下面的所有的子文件夹,删除所有的文件夹 + let dirs = await GetSubdirectories(path.join(book.bookFolderPath, 'data')) + for (let i = 0; i < dirs.length; i++) { + const element = dirs[i]; + await DeleteFolderAllFile(element, true) } - if (copyCount <= 0) { - throw new Error("批次设置错误,无法进行一拆四") + let scriptPath = path.join(book.bookFolderPath, 'script') + if (await CheckFileOrDirExist(scriptPath)) { + await DeleteFolderAllFile(scriptPath, true) } - // 开始复制 - let res = await this.CopyNewBookTask(bookTask, bookTaskDetail, copyCount - 1, CopyImageType.ONE) - if (res.code == 0) { - throw new Error(res.message) + // 删掉输入的备份文件和input文件 + let bakPath = path.join(book.bookFolderPath, 'tmp/bak'); + if (await CheckFileOrDirExist(bakPath)) { + await DeleteFolderAllFile(bakPath, true) } - return successMessage(res.data, "一拆四成功", "BookBasic_OneToFourBookTask") + let inputPath = path.join(book.bookFolderPath, 'tmp/input'); + if (await CheckFileOrDirExist(inputPath)) { + await DeleteFolderAllFile(inputPath, true) + } + + // 重置完毕,开始返回 + return successMessage('重置小说数据成功', 'BookBasic_ResetBookData'); } catch (error) { - return errorMessage("一拆四失败,失败信息如下:" + error.message, "BookBasic_OneToFourBookTask") + return errorMessage('重置小说数据失败,失败信息如下:' + error.message, 'BookBasic_ResetBookData'); } } /** - * 复制一个小说批次任务,创建新的小说批次任务 - * @param oldBookTaskId - * @param copyCount 复制的数量 - * @param isCopyImage 是否复制图片 + * 删除指定小说数据 + * @param bookId 要删除的小说ID */ - async CopyNewBookTask(sourceBookTask: Book.SelectBookTask, sourceBookTaskDetail: Book.SelectBookTaskDetail[], copyCount: number, copyImageType: CopyImageType) { + async DeleteBookData(bookId: string): Promise { try { - await this.InitService(); - let addBookTask = [] as Book.SelectBookTask[] - let addBookTaskDetail = [] as Book.SelectBookTaskDetail[] - - // 先处理文件夹的创建,包括小说任务的和小说任务分镜的 - for (let i = 0; i < copyCount; i++) { - let maxNo = this.bookTaskService.realm - .objects('BookTask') - .filtered('bookId = $0', sourceBookTask.bookId) - .max('no') - let no = maxNo == null ? 1 : Number(maxNo) + 1 + i - let name = 'output_0000' + no - let imageFolder = path.join(define.project_path, `${sourceBookTask.bookId}/tmp/${name}`) - await CheckFolderExistsOrCreate(imageFolder) - // 创建对应的文件夹 - let addOneBookTask = { - id: uuidv4(), - bookId: sourceBookTask.bookId, - no: no, - name: name, - generateVideoPath: sourceBookTask.generateVideoPath, - srtPath: sourceBookTask.srtPath, - audioPath: sourceBookTask.audioPath, - draftSrtStyle: sourceBookTask.draftSrtStyle, - backgroundMusic: sourceBookTask.backgroundMusic, - friendlyReminder: sourceBookTask.friendlyReminder, - imageFolder: path.relative(define.project_path, imageFolder), - status: sourceBookTask.status, - errorMsg: sourceBookTask.errorMsg, - updateTime: new Date(), - createTime: new Date(), - isAuto: sourceBookTask.isAuto, - imageStyle: sourceBookTask.imageStyle, - autoAnalyzeCharacter: sourceBookTask.autoAnalyzeCharacter, - customizeImageStyle: sourceBookTask.customizeImageStyle, - videoConfig: sourceBookTask.videoConfig, - prefixPrompt: sourceBookTask.prefixPrompt, - suffixPrompt: sourceBookTask.suffixPrompt, - version: sourceBookTask.version, - imageCategory: sourceBookTask.imageCategory, - } as Book.SelectBookTask - - addBookTask.push(addOneBookTask) - - for (let j = 0; j < sourceBookTaskDetail.length; j++) { - const element = sourceBookTaskDetail[j]; - - let outImagePath = undefined as string - let subImagePath = [] as string[] - - if (copyImageType == CopyImageType.ALL) { // 直接全部复制 - outImagePath = element.outImagePath - subImagePath = element.subImagePath - } else if (copyImageType == CopyImageType.ONE) { // 只复制对应的 - let oldImage = element.subImagePath[i + 1] - outImagePath = path.join(imageFolder, path.basename(element.outImagePath)) - await CopyFileOrFolder(oldImage, outImagePath) - - subImagePath = [] - } - else if (copyImageType == CopyImageType.NONE) { - outImagePath = undefined - subImagePath = [] - } else { - throw new Error("无效的图片复制类型") - } - if (outImagePath) { - // 单独处理一下显示的图片 - let imageBaseName = path.basename(element.outImagePath); - let newImageBaseName = path.join(define.project_path, `${sourceBookTask.bookId}/tmp/${name}/${imageBaseName}`) - await CopyFileOrFolder(outImagePath, newImageBaseName) - } - // 处理SD设置 - let sdConifg = undefined - if (element.sdConifg) { - let sdConifg = cloneDeep(element.sdConifg) - if (sdConifg.webuiConfig) { - let tempSdConfig = cloneDeep(sdConifg.webuiConfig); - tempSdConfig.id = uuidv4() - sdConifg.webuiConfig = tempSdConfig - } - } - - let reverseId = uuidv4() - // 处理反推数据 - let reverseMessage = [] as Book.ReversePrompt[] - if (element.reversePrompt && element.reversePrompt.length > 0) { - reverseMessage = cloneDeep(element.reversePrompt) - for (let k = 0; k < reverseMessage.length; k++) { - reverseMessage[k].id = uuidv4() - reverseMessage[k].bookTaskDetailId = reverseId - } - } - - let addOneBookTaskDetail = { - id: reverseId, - no: element.no, - name: element.name, - bookId: sourceBookTask.bookId, - bookTaskId: addOneBookTask.id, - videoPath: path.relative(define.project_path, element.videoPath), - word: element.word, - oldImage: path.relative(define.project_path, element.oldImage), - afterGpt: element.afterGpt, - startTime: element.startTime, - endTime: element.endTime, - timeLimit: element.timeLimit, - subValue: element.subValue, - characterTags: element.characterTags && element.characterTags.length > 0 ? cloneDeep(element.characterTags) : [], - gptPrompt: element.gptPrompt, - outImagePath: path.relative(define.project_path, outImagePath), - subImagePath: subImagePath || [], - prompt: element.prompt, - adetailer: element.adetailer, - sdConifg: sdConifg, - createTime: new Date(), - updateTime: new Date(), - audioPath: element.audioPath, - subtitlePosition: element.subtitlePosition, - status: element.status, - reversePrompt: reverseMessage, - imageLock: element.imageLock - } as Book.SelectBookTaskDetail - addBookTaskDetail.push(addOneBookTaskDetail) - } + let book = await this.bookServiceBasic.GetBookDataById(bookId); + // 先将所有的数据重置 + let resetRes = await this.ResetBookData(bookId); + if (resetRes.code == 0) { + throw new Error(resetRes.message) + } + let bookTasks = (await this.bookServiceBasic.GetBookTaskData({ + bookId: bookId + })).bookTasks; + // 删除遗留重置的小说批次任务 + for (let i = 0; i < bookTasks.length; i++) { + const element = bookTasks[i]; + await this.bookServiceBasic.DeleteBookTaskData(element.id); } - // 数据处理完毕,开始新增数据 - // 将所有的复制才做,全部放在一个事务中 - this.bookTaskService.transaction(() => { - for (let i = 0; i < addBookTask.length; i++) { - const element = addBookTask[i]; - this.bookTaskService.realm.create('BookTask', element) - } - for (let i = 0; i < addBookTaskDetail.length; i++) { - const element = addBookTaskDetail[i]; - this.bookTaskDetailService.realm.create('BookTaskDetail', element) - } - }) - // 全部创建完成 - // 查找到数据,然后全部返回 - let returnBookTask = this.bookTaskService.GetBookTaskData({ - bookId: sourceBookTask.bookId - }).data as Book.SelectBookTask[] + // 开始删除数据 + await this.bookServiceBasic.DeleteBookData(bookId); - return successMessage(returnBookTask, "复制小说任务成功", "BookBasic_CopyNewBookTask") + // 开始删除文件 + let bookPath = book.bookFolderPath; + if (await CheckFileOrDirExist(bookPath)) { + await DeleteFolderAllFile(bookPath, true) + } + return successMessage(null, '删除小说数据成功', 'BookBasic_DeleteBookData'); } catch (error) { - console.log(error) - throw error + return errorMessage('删除小说数据失败,失败信息如下:' + error.message, 'BookBasic_DeleteBookData'); } } diff --git a/src/main/Service/Book/ReverseBook.ts b/src/main/Service/Book/ReverseBook.ts index e9e411f..7c7d09f 100644 --- a/src/main/Service/Book/ReverseBook.ts +++ b/src/main/Service/Book/ReverseBook.ts @@ -12,65 +12,36 @@ import { TaskScheduler } from "../../Service/taskScheduler" import { Book } from '../../../model/book' import { LoggerStatus, OtherData, ResponseMessageType } from '../../../define/enum/softwareEnum' import { GeneralResponse } from '../../../model/generalResponse' -import { cloneDeep, isEmpty } from 'lodash' -import { Subtitle } from '../../Service/subtitle' +import { Subtitle } from '../Subtitle/subtitle' import { Watermark } from '../watermark' -import { SubtitleSavePositionType } from '../../../define/enum/waterMarkAndSubtitle' -import { CheckFileOrDirExist, CheckFolderExistsOrCreate, CopyFileOrFolder, GetFilesWithExtensions } from '../../../define/Tools/file' -import { ValidateJson } from '../../../define/Tools/validate' -import { GetImageBase64, ProcessImage } from '../../../define/Tools/image' -import { BookBackTaskType, BookType, TagDefineType, TaskExecuteType } from '../../../define/enum/bookEnum' -import { BookBackTaskListService } from '../../../define/db/service/Book/bookBackTaskListService' +import { BookBackTaskType, BookType, OperateBookType, PromptMergeType, TagDefineType, TaskExecuteType } from '../../../define/enum/bookEnum' import { MJOpt } from '../MJ/mj' import { TagDefine } from '../../../define/tagDefine' -import { ImageStyleDefine } from '../../../define/iamgeStyleDefine' +import { BookServiceBasic } from '../ServiceBasic/bookServiceBasic' +import { SDOpt } from '../SD/sd' /** * 一键反推的相关操作 */ -export class ReverseBook extends BookBasic { +export class ReverseBook { basicReverse: BasicReverse - bookTaskService: BookTaskService taskScheduler: TaskScheduler - bookService: BookService - bookTaskDetailService: BookTaskDetailService - bookBackTaskListService: BookBackTaskListService mjOpt: MJOpt = new MJOpt() + sdOpt: SDOpt = new SDOpt() tagDefine: TagDefine subtitle: Subtitle watermark: Watermark + bookServiceBasic: BookServiceBasic constructor() { - super() this.basicReverse = new BasicReverse() this.tagDefine = new TagDefine() + this.subtitle = new Subtitle() + this.watermark = new Watermark() + this.taskScheduler = new TaskScheduler() + this.bookServiceBasic = new BookServiceBasic() } - - async InitService() { - if (!this.bookTaskService) { - this.bookTaskService = await BookTaskService.getInstance() - } - if (!this.taskScheduler) { - this.taskScheduler = new TaskScheduler() - } - if (!this.bookService) { - this.bookService = await BookService.getInstance() - } - if (!this.bookTaskDetailService) { - this.bookTaskDetailService = await BookTaskDetailService.getInstance() - } - if (!this.subtitle) { - this.subtitle = new Subtitle() - } - if (!this.watermark) { - this.watermark = new Watermark() - } - if (!this.bookBackTaskListService) { - this.bookBackTaskListService = await BookBackTaskListService.getInstance() - } - } - // 主动返回前端的消息 sendReturnMessage(data: GeneralResponse.MessageResponse, message_name = DEFINE_STRING.BOOK.GET_COPYWRITING_RETURN) { global.newWindow[0].win.webContents.send(message_name, data) @@ -104,15 +75,9 @@ export class ReverseBook extends BookBasic { * 获取小说的任务列表 * @param {*} bookTaskCondition 查询任务列表的条件 */ - async GetBookTaskData(bookTaskCondition): Promise { + async GetBookTaskData(bookTaskCondition: Book.QueryBookTaskCondition): Promise { try { - await this.InitService() - let _bookTaskService = await BookTaskService.getInstance() - let res = _bookTaskService.GetBookTaskData(bookTaskCondition) - if (res.code == 0) { - throw new Error(res.message) - } - + let res = await this.bookServiceBasic.GetBookTaskData(bookTaskCondition) // //TODO 这个后面是不是将所有的数据都是用数据库 // // 这边加载自定义风格 // let styleLists = await this.tagDefine.getTagDataByTypeAndProperty('dynamic', "style_tags"); @@ -142,10 +107,10 @@ export class ReverseBook extends BookBasic { // } // res.data.bookTasks[index].styleList = styleList; // } - return successMessage(res.data, '获取小说任务成功', 'ReverseBook_GetBookTaskData') + return successMessage(res, '获取小说任务成功', 'ReverseBook_GetBookTaskData') } catch (error) { return errorMessage( - '获取小说对应批次错误,错误信息入校:' + error.message, + '获取小说对应批次错误,错误信息入下:' + error.message, 'ReverseBook_GetBookTaskData' ) } @@ -153,20 +118,15 @@ export class ReverseBook extends BookBasic { /** * 获取小说的所有的任务详情 - * @param bookTaskId + * @param bookTaskId 小说任务的ID */ - async GetBookTaskDetail(bookTaskId: string) { + async GetBookTaskDetail(bookTaskId: string): Promise { try { - await this.InitService() - let _bookTaskDetailService = await BookTaskDetailService.getInstance() - let res = _bookTaskDetailService.GetBookTaskData({ bookTaskId: bookTaskId }) - if (res.code == 0) { - throw new Error(res.message) - } - return res + let res = await this.bookServiceBasic.GetBookTaskDetailData({ bookTaskId: bookTaskId }) + return successMessage(res, '获取小说任务详情成功', 'ReverseBook_GetBookTaskDetail') } catch (error) { return errorMessage( - '获取小说对应批次错误,错误信息入校:' + error.message, + '获取小说对应批次错误,错误信息入下:' + error.message, 'ReverseBook_GetBookTaskData' ) } @@ -236,10 +196,7 @@ export class ReverseBook extends BookBasic { * @returns */ async GetBookAndTask(bookId: string, bookTaskName: string) { - let book = this.bookService.GetBookDataById(bookId) - if (book == null) { - throw new Error("查找小说数据失败"); - } + let book = await this.bookServiceBasic.GetBookDataById(bookId) // 获取小说对应的批次任务数据,默认初始化为第一个 let condition = { bookId: bookId @@ -249,20 +206,14 @@ export class ReverseBook extends BookBasic { } else { condition["id"] = bookTaskName } - let bookTaskRes = await this.bookTaskService.GetBookTaskData(condition) - if (bookTaskRes.data.bookTasks.length <= 0 || bookTaskRes.data.total <= 0) { - let msg = "没有找到对应的小说批次任务数据" - this.taskScheduler.AddLogToDB(bookId, book.type, msg, OtherData.DEFAULT, LoggerStatus.FAIL) - throw new Error(msg) - } - return { book: book as Book.SelectBook, bookTask: bookTaskRes.data.bookTasks[0] as Book.SelectBookTask } + let bookTaskRes = await this.bookServiceBasic.GetBookTaskData(condition) + return { book: book as Book.SelectBook, bookTask: bookTaskRes.bookTasks[0] as Book.SelectBookTask } } /** * 开始分镜任务 */ async ComputeStoryboard(bookId: string): Promise { try { - await this.InitService() let { book, bookTask } = await this.GetBookAndTask(bookId, 'output_00001') let res = await this.basicReverse.ComputeStoryboardFunc(bookId, bookTask.id); // 分镜成功直接返回 @@ -279,15 +230,15 @@ export class ReverseBook extends BookBasic { */ async Framing(bookId: string): Promise { try { - await this.InitService() let { book, bookTask } = await this.GetBookAndTask(bookId, 'output_00001') - - // 获取所有的分镜数据 - let bookTaskDetail = this.bookTaskDetailService.GetBookTaskData({ - bookId: bookId, - bookTaskId: bookTask.id - }) - if (bookTaskDetail.data.length <= 0) { + let bookTaskDetail = undefined as Book.SelectBookTaskDetail[] + try { + // 获取所有的分镜数据 + bookTaskDetail = await this.bookServiceBasic.GetBookTaskDetailData({ + bookId: bookId, + bookTaskId: bookTask.id + }) + } catch (error) { // 传入的分镜数据为空,需要重新获取 await this.taskScheduler.AddLogToDB( bookId, @@ -298,9 +249,7 @@ export class ReverseBook extends BookBasic { ) throw new Error("没有传入分镜数据,请先进行分镜计算"); } - - let bookTaskDetails = bookTaskDetail.data as Book.SelectBookTaskDetail[] - + let bookTaskDetails = bookTaskDetail; for (let i = 0; i < bookTaskDetails.length; i++) { const item = bookTaskDetails[i]; @@ -314,12 +263,12 @@ export class ReverseBook extends BookBasic { LoggerStatus.SUCCESS ) - bookTaskDetail = this.bookTaskDetailService.GetBookTaskData({ + bookTaskDetail = await this.bookServiceBasic.GetBookTaskDetailData({ bookId: bookId, bookTaskId: bookTask.id }) - bookTaskDetails = bookTaskDetail.data as Book.SelectBookTaskDetail[] + bookTaskDetails = bookTaskDetail as Book.SelectBookTaskDetail[] for (let i = 0; i < bookTaskDetails.length; i++) { const item = bookTaskDetails[i]; let res = await this.basicReverse.FrameFunc(book, item); @@ -331,96 +280,70 @@ export class ReverseBook extends BookBasic { return errorMessage("开始切割视频并抽帧失败,失败信息如下:" + error.message, 'ReverseBook_Framing') } } - - /** - * 提起文案 - */ - async GetCopywriting(bookId: string, bookTaskId: string = null): Promise { - try { - await this.InitService() - let { book, bookTask } = await this.GetBookAndTask(bookId, bookTaskId ? bookTaskId : 'output_00001') - if (isEmpty(book.subtitlePosition)) { - throw new Error("请先设置小说的字幕位置") - } - // 获取所有的分镜数据 - let bookTaskDetail = this.bookTaskDetailService.GetBookTaskData({ - bookId: bookId, - bookTaskId: bookTask.id - }) - if (bookTaskDetail.data.length <= 0) { - // 传入的分镜数据为空,需要重新获取 - await this.taskScheduler.AddLogToDB( - bookId, - book.type, - `没有传入分镜数据,请先进行分镜计算`, - OtherData.DEFAULT, - LoggerStatus.DOING - ) - throw new Error("没有传入分镜数据,请先进行分镜计算"); - } - - let bookTaskDetails = bookTaskDetail.data as Book.SelectBookTaskDetail[] - for (let i = 0; i < bookTaskDetails.length; i++) { - const item = bookTaskDetails[i]; - - let res = await this.subtitle.GetVideoFrameText({ - id: item.id, - videoPath: item.videoPath, - type: SubtitleSavePositionType.STORYBOARD_VIDEO, - subtitlePosition: book.subtitlePosition - }) - if (res.code == 0) { - throw new Error(res.message) - } - // 修改数据 - this.bookTaskDetailService.UpdateBookTaskDetail(item.id, { - word: res.data, - afterGpt: res.data - }); - - // let res = await this.basicReverse.GetCopywritingFunc(book, item); - // 将当前的数据实时返回,前端进行修改 - this.sendReturnMessage({ - code: 1, - id: item.id, - type: ResponseMessageType.GET_TEXT, - data: res.data // 返回识别到的文案 - }) - - // 添加日志 - await this.taskScheduler.AddLogToDB( - bookId, - book.type, - `${item.name} 识别文案成功`, - bookTask.id, - LoggerStatus.SUCCESS - ) - } - return successMessage(null, "识别是所有文案成功", "ReverseBook_GetCopywriting") - } catch (error) { - return errorMessage("获取分镜数据失败,失败信息如下:" + error.message, 'ReverseBook_GetCopywriting') - } - } //#endregion //#region 反推相关任务 + /** + * 所有的反推任务的入口 + * @param id 需要反推的ID,可以是小说任务ID,也可以是小说分镜ID + * @param operateBookType 操作的分类 + * @param type 反推的类型 + * @returns + */ + async AddReversePrompt(id: string, operateBookType: OperateBookType, type: BookType): Promise { + try { + let bookTaskDetailIds: string[] = [] + let bookTaskDetails = [] as Book.SelectBookTaskDetail[] + if (operateBookType == OperateBookType.BOOKTASK) { + bookTaskDetails = await this.bookServiceBasic.GetBookTaskDetailData({ + bookTaskId: id + }) + } else if (operateBookType == OperateBookType.BOOKTASKDETAIL) { + let bookTaskDetail = await this.bookServiceBasic.GetBookTaskDetailDataById(id); + bookTaskDetails = [bookTaskDetail] + } else if (operateBookType == OperateBookType.UNDERBOOKTASK) { + // 下生图 + let tempBooktaskDetail = await this.bookServiceBasic.GetBookTaskDetailDataById(id); + let tempBooktaskDetails = await this.bookServiceBasic.GetBookTaskDetailData({ + bookTaskId: tempBooktaskDetail.bookTaskId + }); + for (let i = 0; i < tempBooktaskDetails.length; i++) { + const element = tempBooktaskDetails[i]; + if (tempBooktaskDetail.no <= element.no) { + bookTaskDetails.push(element) + } + } + } else { + throw new Error("未知的操作模式,请检查"); + } + + for (let i = 0; i < bookTaskDetails.length; i++) { + const element = bookTaskDetails[i]; + bookTaskDetailIds.push(element.id) + } + if (bookTaskDetailIds.length <= 0) { + throw new Error("没有需要反推的数据,请检查") + } + await this.AddReversePromptTask(bookTaskDetailIds, type); + return successMessage(null, "添加反推任务成功", 'ReverseBook_AddReversePrompt') + } catch (error) { + return errorMessage("添加反推任务失败,错误信息如下:" + error.message, "ReverseBook_AddReversePrompt") + } + } + /** * 添加单句生图任务 * @param bookTaskDetailId 小说单个分镜的ID * @param type 反推的类型 * @returns */ - async AddReversePromptTask(bookTaskDetailIds: string[], type: BookType): Promise { + async AddReversePromptTask(bookTaskDetailIds: string[], type: BookType): Promise { try { - await this.InitService() for (let index = 0; index < bookTaskDetailIds.length; index++) { const bookTaskDetailId = bookTaskDetailIds[index]; - let bookTaskDetail = this.bookTaskDetailService.GetBookTaskDetailDataById(bookTaskDetailId) - if (bookTaskDetail == null) { - throw new Error("没有找到对应的分镜数据") - } - let book = this.bookService.GetBookDataById(bookTaskDetail.bookId) + let bookTaskDetail = await this.bookServiceBasic.GetBookTaskDetailDataById(bookTaskDetailId) + let book = await this.bookServiceBasic.GetBookDataById(bookTaskDetail.bookId) // 是不是又额外的类型,没有的话试用小说的类型 let task_type = undefined as BookBackTaskType if (!type) { @@ -436,17 +359,14 @@ export class ReverseBook extends BookBasic { default: throw new Error("暂不支持的推理类型") } - let taskRes = await this.bookBackTaskListService.AddBookBackTask(book.id, task_type, TaskExecuteType.AUTO, bookTaskDetail.bookTaskId, bookTaskDetail.id + // 添加后台任务 + await this.bookServiceBasic.AddBookBackTask(book.id, task_type, TaskExecuteType.AUTO, bookTaskDetail.bookTaskId, bookTaskDetail.id ); - if (taskRes.code == 0) { - throw new Error(taskRes.message) - } // 添加返回日志 await this.taskScheduler.AddLogToDB(book.id, book.type, `添加 ${task_type} 反推任务成功`, bookTaskDetail.bookTaskId, LoggerStatus.SUCCESS) } - return successMessage(null, "添加反推任务成功", "ReverseBook_AddReversePromptTask") } catch (error) { - return errorMessage("添加单个反推失败,错误信息如下:" + error.message, "ReverseBook_SingleReversePrompt") + throw error } } @@ -481,14 +401,12 @@ export class ReverseBook extends BookBasic { } } - /** * 删除指定的提示词数据 * @param bookTaskDetailIds 要删除的提示词ID */ async RemoveReverseData(bookTaskDetailIds: string[]) { try { - await this.InitService() // 开始删除 if (bookTaskDetailIds.length <= 0) { throw new Error("没有传入要删除的数据") @@ -496,7 +414,7 @@ export class ReverseBook extends BookBasic { for (let i = 0; i < bookTaskDetailIds.length; i++) { const element = bookTaskDetailIds[i]; - this.bookTaskDetailService.DeleteBookTaskDetailReversePromptById(element) + await this.bookServiceBasic.DeleteBookTaskDetailReversePromptById(element); } // 全部删除完毕 @@ -505,6 +423,30 @@ export class ReverseBook extends BookBasic { return errorMessage("删除反推数据失败,错误信息如下:" + error.message, "ReverseBook_RemoveReverseData") } } + //#endregion + + //#region 提示词相关操作 + + /** + * 合并提示词的入口函数 + * @param id 合并的ID,可以是小说任务ID,也可以是小说分镜ID + * @param operateBookType 操作的类型 + * @param type 合并的类型(MJ。SD 。D3) + * @returns + */ + async MergePrompt(id: string, type: PromptMergeType, operateBookType: OperateBookType): Promise { + try { + if (type == PromptMergeType.MJ_MERGE) { + return await this.mjOpt.MergePrompt(id, operateBookType); + } else if (type == PromptMergeType.SD_MERGE) { + return await this.sdOpt.MergePrompt(id, operateBookType) + } else { + throw new Error("未知的合并模式,请检查") + } + } catch (error) { + return errorMessage("合并提示词失败,错误信息如下:" + error.message, "ReverseBook_MergePrompt") + } + } //#endregion } diff --git a/src/main/Service/Book/bookFrame.ts b/src/main/Service/Book/bookFrame.ts new file mode 100644 index 0000000..bd88638 --- /dev/null +++ b/src/main/Service/Book/bookFrame.ts @@ -0,0 +1,59 @@ +import { isEmpty } from "lodash"; +import { GeneralResponse } from "../../../model/generalResponse"; +import { errorMessage, successMessage } from "../../Public/generalTools"; +import { BookServiceBasic } from "../ServiceBasic/bookServiceBasic"; +import path from 'path'; +import { FfmpegOptions } from "../ffmpegOptions"; +import { CheckFileOrDirExist, CopyFileOrFolder, DeleteFolderAllFile } from "../../../define/Tools/file"; +import fs from 'fs'; + +export class BookFrame { + bookServiceBasic: BookServiceBasic + ffmpegOptions: FfmpegOptions + constructor() { + this.bookServiceBasic = new BookServiceBasic(); + this.ffmpegOptions = new FfmpegOptions(); + } + + + /** + * 替换指定分镜的视频当前帧 + * @param bookTaskDetailId 指定的小说分镜ID + * @param current 要替换的帧 + */ + async ReplaceVideoCurrentFrame(bookTaskDetailId: string, current: number): Promise { + try { + let bookTaskDetail = await this.bookServiceBasic.GetBookTaskDetailDataById(bookTaskDetailId); + let videoPath = bookTaskDetail.videoPath; + if (isEmpty(videoPath)) { + throw new Error('未找到对应的视频路径,请检查'); + } + let oldImagePath = bookTaskDetail.oldImage; + let tempOldImagePath = path.join(path.dirname(oldImagePath), `temp_${bookTaskDetail.name}.png`); + // 删除之前的图片 + if (await CheckFileOrDirExist(tempOldImagePath)) { + await fs.promises.unlink(tempOldImagePath); + } + // 开始裁剪 + let res = await this.ffmpegOptions.FfmpegGetFrame(current, videoPath, tempOldImagePath); + if (res.code == 0) { + // 抽帧失败 + global.logger.error('BookFrame_ReplaceVideoCurrentFrame', '抽取视频指定帧失败,请检查'); + throw new Error('抽取视频指定帧失败,请检查'); + } + // 成功。开始替换 + let fileExist = await CheckFileOrDirExist(tempOldImagePath); + if (!fileExist) { + throw new Error('抽帧出来的图片不存在,请检查'); + } + await fs.promises.unlink(oldImagePath); + // 重命名 + await CopyFileOrFolder(tempOldImagePath, oldImagePath); + await fs.promises.unlink(tempOldImagePath); + return successMessage(oldImagePath + '?t=' + new Date().getTime(), '视频指定的视频帧成功', 'BookFrame_ReplaceVideoCurrentFrame'); + } catch (error) { + return errorMessage('替换指定分镜的视频当前帧失败,失败信息如下:' + error.toString(), 'BookFrame_ReplaceVideoCurrentFrame'); + } + } + +} \ No newline at end of file diff --git a/src/main/Service/Book/bookTask.ts b/src/main/Service/Book/bookTask.ts index 41847f7..ede97db 100644 --- a/src/main/Service/Book/bookTask.ts +++ b/src/main/Service/Book/bookTask.ts @@ -1,10 +1,15 @@ -import { CheckFolderExistsOrCreate, DeleteFolderAllFile } from "../../../define/Tools/file"; +import { CheckFolderExistsOrCreate, CopyFileOrFolder, DeleteFolderAllFile } from "../../../define/Tools/file"; import { BookTaskService } from "../../../define/db/service/Book/bookTaskService"; import { BookTaskDetailService } from "../../../define/db/service/Book/bookTaskDetailService"; import { BookService } from "../../../define/db/service/Book/bookService"; -import { BookTaskStatus, OperateBookType } from "../../../define/enum/bookEnum"; +import { BookTaskStatus, CopyImageType, OperateBookType } from "../../../define/enum/bookEnum"; import { errorMessage, successMessage } from "../../Public/generalTools"; import { Book } from "../../../model/book"; +import path from 'path' +import { cloneDeep, isEmpty } from "lodash"; +import { define } from '../../../define/define' +import { v4 as uuidv4 } from 'uuid' +import { GeneralResponse } from "../../../model/generalResponse"; /** * 小说批次相关的操作 @@ -33,7 +38,7 @@ export class BookTask { * 重置小说任务数据 * @param bookTaskId 小说任务ID */ - async ReSetBookTask(bookTaskId: string) { + async ReSetBookTask(bookTaskId: string): Promise { try { console.log(bookTaskId) await this.InitService() @@ -57,7 +62,7 @@ export class BookTask { * 删除对应的小说批次数据 * @param bookTaskId 小说批次ID */ - async DeleteBookTask(bookTaskId: string) { + async DeleteBookTask(bookTaskId: string): Promise { try { await this.InitService(); let bookTask = this.bookTaskService.GetBookTaskDataById(bookTaskId); @@ -75,4 +80,212 @@ export class BookTask { } } + /** + * 将小说批次任务出图进行一拆四 + * @param bookTaskId 操作的小说批次任务ID + * @returns + */ + async OneToFourBookTask(bookTaskId: string) { + try { + console.log(bookTaskId) + await this.InitService(); + let copyCount = 100 + let bookTask = this.bookTaskService.GetBookTaskDataById(bookTaskId) + if (bookTask == null) { + throw new Error("没有找到对应的数小说任务,请检查数据") + } + // 获取所有的出图中最少的 + let bookTaskDetail = this.bookTaskDetailService.GetBookTaskData({ + bookTaskId: bookTaskId + }).data as Book.SelectBookTaskDetail[] + if (bookTaskDetail == null || bookTaskDetail.length <= 0) { + throw new Error("没有对应的小说分镜任务,请先添加分镜任务") + } + + for (let i = 0; i < bookTaskDetail.length; i++) { + const element = bookTaskDetail[i]; + if (isEmpty(element.subImagePath)) { + throw new Error("检测到图片没有出完,请先检查出图") + } + if (element.subImagePath == null || element.subImagePath.length <= 0) { + throw new Error("检测到图片没有出完,请先检查出图") + } + if (element.subImagePath.length < copyCount) { + copyCount = element.subImagePath.length + } + } + if (copyCount <= 0) { + throw new Error("批次设置错误,无法进行一拆四") + } + // 开始复制 + let res = await this.CopyNewBookTask(bookTask, bookTaskDetail, copyCount - 1, CopyImageType.ONE) + if (res.code == 0) { + throw new Error(res.message) + } + return successMessage(res.data, "一拆四成功", "BookBasic_OneToFourBookTask") + } catch (error) { + return errorMessage("一拆四失败,失败信息如下:" + error.message, "BookBasic_OneToFourBookTask") + } + } + + /** + * 复制一个小说批次任务,创建新的小说批次任务 + * @param oldBookTaskId + * @param copyCount 复制的数量 + * @param isCopyImage 是否复制图片 + */ + async CopyNewBookTask(sourceBookTask: Book.SelectBookTask, sourceBookTaskDetail: Book.SelectBookTaskDetail[], copyCount: number, copyImageType: CopyImageType) { + try { + await this.InitService(); + let addBookTask = [] as Book.SelectBookTask[] + let addBookTaskDetail = [] as Book.SelectBookTaskDetail[] + + // 先处理文件夹的创建,包括小说任务的和小说任务分镜的 + for (let i = 0; i < copyCount; i++) { + let maxNo = this.bookTaskService.realm + .objects('BookTask') + .filtered('bookId = $0', sourceBookTask.bookId) + .max('no') + let no = maxNo == null ? 1 : Number(maxNo) + 1 + i + let name = 'output_0000' + no + let imageFolder = path.join(define.project_path, `${sourceBookTask.bookId}/tmp/${name}`) + await CheckFolderExistsOrCreate(imageFolder) + // 创建对应的文件夹 + let addOneBookTask = { + id: uuidv4(), + bookId: sourceBookTask.bookId, + no: no, + name: name, + generateVideoPath: sourceBookTask.generateVideoPath, + srtPath: sourceBookTask.srtPath, + audioPath: sourceBookTask.audioPath, + draftSrtStyle: sourceBookTask.draftSrtStyle, + backgroundMusic: sourceBookTask.backgroundMusic, + friendlyReminder: sourceBookTask.friendlyReminder, + imageFolder: path.relative(define.project_path, imageFolder), + status: sourceBookTask.status, + errorMsg: sourceBookTask.errorMsg, + updateTime: new Date(), + createTime: new Date(), + isAuto: sourceBookTask.isAuto, + imageStyle: sourceBookTask.imageStyle, + autoAnalyzeCharacter: sourceBookTask.autoAnalyzeCharacter, + customizeImageStyle: sourceBookTask.customizeImageStyle, + videoConfig: sourceBookTask.videoConfig, + prefixPrompt: sourceBookTask.prefixPrompt, + suffixPrompt: sourceBookTask.suffixPrompt, + version: sourceBookTask.version, + imageCategory: sourceBookTask.imageCategory, + } as Book.SelectBookTask + + addBookTask.push(addOneBookTask) + + for (let j = 0; j < sourceBookTaskDetail.length; j++) { + const element = sourceBookTaskDetail[j]; + + let outImagePath = undefined as string + let subImagePath = [] as string[] + + if (copyImageType == CopyImageType.ALL) { // 直接全部复制 + outImagePath = element.outImagePath + subImagePath = element.subImagePath + } else if (copyImageType == CopyImageType.ONE) { // 只复制对应的 + let oldImage = element.subImagePath[i + 1] + outImagePath = path.join(imageFolder, path.basename(element.outImagePath)) + await CopyFileOrFolder(oldImage, outImagePath) + + subImagePath = [] + } + else if (copyImageType == CopyImageType.NONE) { + outImagePath = undefined + subImagePath = [] + } else { + throw new Error("无效的图片复制类型") + } + if (outImagePath) { + // 单独处理一下显示的图片 + let imageBaseName = path.basename(element.outImagePath); + let newImageBaseName = path.join(define.project_path, `${sourceBookTask.bookId}/tmp/${name}/${imageBaseName}`) + await CopyFileOrFolder(outImagePath, newImageBaseName) + } + // 处理SD设置 + let sdConifg = undefined + if (element.sdConifg) { + let sdConifg = cloneDeep(element.sdConifg) + if (sdConifg.webuiConfig) { + let tempSdConfig = cloneDeep(sdConifg.webuiConfig); + tempSdConfig.id = uuidv4() + sdConifg.webuiConfig = tempSdConfig + } + } + + let reverseId = uuidv4() + // 处理反推数据 + let reverseMessage = [] as Book.ReversePrompt[] + if (element.reversePrompt && element.reversePrompt.length > 0) { + reverseMessage = cloneDeep(element.reversePrompt) + for (let k = 0; k < reverseMessage.length; k++) { + reverseMessage[k].id = uuidv4() + reverseMessage[k].bookTaskDetailId = reverseId + } + } + + let addOneBookTaskDetail = { + id: reverseId, + no: element.no, + name: element.name, + bookId: sourceBookTask.bookId, + bookTaskId: addOneBookTask.id, + videoPath: path.relative(define.project_path, element.videoPath), + word: element.word, + oldImage: path.relative(define.project_path, element.oldImage), + afterGpt: element.afterGpt, + startTime: element.startTime, + endTime: element.endTime, + timeLimit: element.timeLimit, + subValue: element.subValue, + characterTags: element.characterTags && element.characterTags.length > 0 ? cloneDeep(element.characterTags) : [], + gptPrompt: element.gptPrompt, + outImagePath: path.relative(define.project_path, outImagePath), + subImagePath: subImagePath || [], + prompt: element.prompt, + adetailer: element.adetailer, + sdConifg: sdConifg, + createTime: new Date(), + updateTime: new Date(), + audioPath: element.audioPath, + subtitlePosition: element.subtitlePosition, + status: element.status, + reversePrompt: reverseMessage, + imageLock: element.imageLock + } as Book.SelectBookTaskDetail + addBookTaskDetail.push(addOneBookTaskDetail) + } + } + + // 数据处理完毕,开始新增数据 + // 将所有的复制才做,全部放在一个事务中 + this.bookTaskService.transaction(() => { + for (let i = 0; i < addBookTask.length; i++) { + const element = addBookTask[i]; + this.bookTaskService.realm.create('BookTask', element) + } + for (let i = 0; i < addBookTaskDetail.length; i++) { + const element = addBookTaskDetail[i]; + this.bookTaskDetailService.realm.create('BookTaskDetail', element) + } + }) + // 全部创建完成 + // 查找到数据,然后全部返回 + let returnBookTask = this.bookTaskService.GetBookTaskData({ + bookId: sourceBookTask.bookId + }).data as Book.SelectBookTask[] + + return successMessage(returnBookTask, "复制小说任务成功", "BookBasic_CopyNewBookTask") + } catch (error) { + console.log(error) + throw error + } + } + } \ No newline at end of file diff --git a/src/main/Service/Book/bookVideo.ts b/src/main/Service/Book/bookVideo.ts index 07169e8..d50e6a7 100644 --- a/src/main/Service/Book/bookVideo.ts +++ b/src/main/Service/Book/bookVideo.ts @@ -10,14 +10,17 @@ import { CheckFolderExistsOrCreate } from "../../../define/Tools/file"; import { BookTaskDetailService } from "../../../define/db/service/Book/bookTaskDetailService"; import fs from 'fs' import { ClipDraft } from '../../Public/clipDraft' +import { BookServiceBasic } from "../ServiceBasic/bookServiceBasic"; export class BookVideo { bookService: BookService bookTaskService: BookTaskService setting: Setting bookTaskDetailService: BookTaskDetailService + bookServiceBasic: BookServiceBasic constructor() { this.setting = new Setting(global) + this.bookServiceBasic = new BookServiceBasic() } async InitService() { @@ -71,17 +74,16 @@ export class BookVideo { } // 将修改数据放在一个事务中 - this.bookService.transaction(() => { - for (let i = 0; i < bookTasks.length; i++) { - const element = bookTasks[i]; - let modifyBookTask = this.bookService.realm.objectForPrimaryKey('BookTask', element.id); - modifyBookTask.backgroundMusic = book.backgroundMusic; - modifyBookTask.friendlyReminder = book.friendlyReminder; - modifyBookTask.draftSrtStyle = book.draftSrtStyle; - modifyBookTask.srtPath = book.srtPath; - modifyBookTask.audioPath = book.audioPath; - } - }) + for (let i = 0; i < bookTasks.length; i++) { + const element = bookTasks[i]; + this.bookServiceBasic.UpdetedBookTaskData(element.id, { + backgroundMusic: book.backgroundMusic, + friendlyReminder: book.friendlyReminder, + draftSrtStyle: book.draftSrtStyle, + srtPath: book.srtPath, + audioPath: book.audioPath, + }) + } return successMessage({ backgroundMusic: book.backgroundMusic, friendlyReminder: book.friendlyReminder, diff --git a/src/main/Service/GPT/gpt.ts b/src/main/Service/GPT/gpt.ts new file mode 100644 index 0000000..eb8d028 --- /dev/null +++ b/src/main/Service/GPT/gpt.ts @@ -0,0 +1,178 @@ +import { isEmpty } from "lodash"; +import { gptDefine } from "../../../define/gptDefine"; +import axios from "axios"; + +/** + * 一些GPT相关的服务都在这边 + */ +export class GptService { + gptUrl: string = undefined + gptModel: string = undefined + gptApiKey: string = undefined + + + //#region GPT 设置 + + /** + * 获取GPT的所有的服务商 + * @param type 获取的类型,就是all + * @param callback 这个是个回调函数,干嘛的不知道 + * @returns + */ + private async GetGPTBusinessOption(type: string, callback: Function = null): Promise { + let res = await gptDefine.getGptDataByTypeAndProperty(type, "gpt_options", []); + if (res.code == 0) { + throw new Error(res.message) + } else { + if (callback) { + callback(res.data) + } + return res.data + } + } + + async RefreshGptSetting() { + let all_options = await this.GetGPTBusinessOption("all", (value) => value.gpt_url); + let index = all_options.findIndex(item => item.value == global.config.gpt_business && item.gpt_url) + if (index < 0) { + throw new Error("没有找到指定的GPT服务商的配置,请检查") + } + this.gptUrl = all_options[index].gpt_url; + this.gptApiKey = global.config.gpt_key; + this.gptModel = global.config.gpt_model; + } + + /** + * 初始化GPT的设置 + */ + async InitGptSetting(refresh = false) { + if (refresh) { + await this.RefreshGptSetting() + } else { + // 判断是不是存在必要信息 + if (isEmpty(this.gptUrl) || isEmpty(this.gptModel) || isEmpty(this.gptApiKey)) { + await this.RefreshGptSetting(); + } + } + } + + /** + * 适配一些请求体中的参数 + * @param data + * @param gpt_url + * @returns + */ + ModifyData(data: any, gpt_url: string = null) { + let res = data; + if (!gpt_url) { + gpt_url = this.gptUrl + } + if (gpt_url.includes("dashscope.aliyuncs.com")) { + res = { + "model": data.model, + "input": { + "messages": data.messages, + }, + "parameters": { + "result_format": "message" + } + } + } + return res; + } + + /** + * 适配返回来的数据 + * @param res 返回的数据 + * @param gpt_url 请求的URL + * @returns + */ + GetResponseContent(res: any, gpt_url: string = null) { + let content = ""; + if (!gpt_url) { + gpt_url = this.gptUrl + } + if (gpt_url.includes("dashscope.aliyuncs.com")) { + content = res.data.output.choices[0].message.content; + } else { + content = res.data.choices[0].message.content; + } + return content; + } + + //#endregion + + /** + * 发送GPT请求 + * @param {*} message 请求的信息 + * @param {*} gpt_url gpt的url,默认在global中取 + * @param {*} gpt_key gpt的key,默认在global中取 + * @param {*} gpt_model gpt的model,默认在global中取 + * @returns + */ + async FetchGpt(message: any, gpt_model: string = null, gpt_key: string = null, gpt_url: string = null): Promise { + try { + await this.InitGptSetting(); + let data = { + "model": gpt_model ? gpt_model : this.gptModel, + "messages": message + }; + + data = this.ModifyData(data, gpt_url); + let config = { + method: 'post', + maxBodyLength: Infinity, + url: gpt_url ? gpt_url : this.gptUrl, + headers: { + 'Authorization': `Bearer ${gpt_key ? gpt_key : this.gptApiKey}`, + 'Content-Type': 'application/json' + }, + data: JSON.stringify(data) + }; + + let res = await axios.request(config); + let content = this.GetResponseContent(res, this.gptUrl); + return content; + } catch (error) { + throw error; + } + } + + //#region 繁体中文 -> 简体中文 + + /** + * 将繁体中文转换为简体中文 + * @param traditionalText 繁体中文文本 + * @param apiKey Lai API的 Key + * @param baseUrl 请求的baseurl + * @returns + */ + async ChineseTraditionalToSimplified(traditionalText: string, apiKey: string, baseUrl: string = null): Promise { + try { + let message = [ + { + "role": "system", + "content": '我想让你充当中文繁体转简体专家,用简体中文100%还原繁体中文,不要加其他的联想,只把原有的繁体中文转换为简体中文,请检查所有信息是否准确,并在回答时保持简活,不需要任何其他反馈。' + }, { + "role": "user", + "content": '上研究生後,發現導師竟然是曾經網戀的前男友。' + }, { + "role": "assistant", + "content": '上研究生后,发现导师竟然是曾经网恋的前男友。' + }, { + "role": "user", + "content": traditionalText + } + ] + + let baseSubUrl = baseUrl ? (baseUrl.endsWith('/') ? baseUrl + 'v1/chat/completions' : baseUrl + '/v1/chat/completions') : null; + let url = baseSubUrl ? baseSubUrl : "https://api.laitool.cc/v1/chat/completions" + // 开始请求,这个默认是使用的是LAI API的gpt-4o-mini + let content = await this.FetchGpt(message, 'gpt-4o-mini', apiKey, url) + return content + } catch (error) { + throw error + } + } + //#endregion +} \ No newline at end of file diff --git a/src/main/Service/MJ/mj.ts b/src/main/Service/MJ/mj.ts index 278d185..d32c53a 100644 --- a/src/main/Service/MJ/mj.ts +++ b/src/main/Service/MJ/mj.ts @@ -7,7 +7,7 @@ import { CheckFolderExistsOrCreate, CopyFileOrFolder, JoinPath } from "../../../ import { define } from "../../../define/define" import { GetImageBase64, ImageSplit } from "../../../define/Tools/image"; import MJApi from "./mjApi" -import { BookBackTaskStatus, BookBackTaskType, BookTaskStatus, BookType, DialogType, MJAction, MergeType, OperateBookType, TaskExecuteType } from "../../../define/enum/bookEnum"; +import { BookBackTaskStatus, BookBackTaskType, BookTaskStatus, BookType, DialogType, MJAction, OperateBookType, TaskExecuteType } from "../../../define/enum/bookEnum"; import { DEFINE_STRING } from "../../../define/define_string"; import { MJ } from "../../../model/mj"; import { MJRespoonseType } from "../../../define/enum/mjEnum"; @@ -21,6 +21,7 @@ import { TaskScheduler } from "../taskScheduler"; import { BookService } from "../../../define/db/service/Book/bookService"; import { Tools } from "../../../main/tools" import { MJSettingService } from "../../../define/db/service/SoftWare/mjSettingService"; +import { BookServiceBasic } from "../ServiceBasic/bookServiceBasic"; import path from "path" const { v4: uuidv4 } = require('uuid') @@ -39,10 +40,12 @@ export class MJOpt { bookService: BookService tools: Tools; mjSettingService: MJSettingService; + bookServiceBasic: BookServiceBasic constructor() { this.imageStyle = new ImageStyle() this.taskScheduler = new TaskScheduler() this.tools = new Tools() + this.bookServiceBasic = new BookServiceBasic(); } async InitService() { if (!this.reverseBook) { @@ -208,7 +211,8 @@ export class MJOpt { this.bookTaskDetail.UpdateBookTaskDetail(task.bookTaskDetailId, { status: BookTaskStatus.REVERSE_DONE, - reversePrompt: reversePrompt + reversePrompt: reversePrompt, + gptPrompt: undefined }); task_res.prompt = JSON.stringify(reversePrompt); @@ -331,20 +335,35 @@ export class MJOpt { * @param id 合并的ID * @param mergeType 合并的类型 */ - async MergePrompt(id: string, mergeType: MergeType): Promise { + async MergePrompt(id: string, operateBookType: OperateBookType): Promise { try { await this.InitService() let bookTaskDetail = undefined as Book.SelectBookTaskDetail[]; let bookTask = undefined as Book.SelectBookTask; - if (mergeType == MergeType.BOOKTASK) { + if (operateBookType == OperateBookType.BOOKTASK) { bookTaskDetail = this.bookTaskDetail.GetBookTaskData({ bookTaskId: id }).data bookTask = this.bookTaskService.GetBookTaskDataById(id); - } else if (mergeType == MergeType.BOOKTASKDETAIL) { - bookTaskDetail = [this.bookTaskDetail.GetBookTaskDetailDataById(id)] + // 判断是不是有为空的 + let emptyName = [] as string[] + for (let i = 0; i < bookTaskDetail.length; i++) { + const element = bookTaskDetail[i]; + if (isEmpty(element.gptPrompt)) { + emptyName.push(element.name) + } + } + if (emptyName.length > 0) { + throw new Error("有空的提示词,请先推理") + } + } else if (operateBookType == OperateBookType.BOOKTASKDETAIL) { + let tempBookTaskDetail = await this.bookServiceBasic.GetBookTaskDetailDataById(id); + if (isEmpty(tempBookTaskDetail.gptPrompt)) { + throw new Error("当前分镜没有推理提示词,请先生成") + } + bookTaskDetail = [tempBookTaskDetail] bookTask = this.bookTaskService.GetBookTaskDataById(bookTaskDetail[0].bookTaskId); } else { throw new Error("未知的合并类型") @@ -371,7 +390,6 @@ export class MJOpt { for (let i = 0; i < bookTaskDetail.length; i++) { const element = bookTaskDetail[i]; - let promptStr = ''; for (let i = 0; i < promptSort.length; i++) { const element = promptSort[i]; diff --git a/src/main/Service/SD/sd.ts b/src/main/Service/SD/sd.ts index baa529e..d184dcb 100644 --- a/src/main/Service/SD/sd.ts +++ b/src/main/Service/SD/sd.ts @@ -1,4 +1,3 @@ -import { MergeType } from "../../../define/enum/bookEnum"; import { Book } from "../../../model/book"; import { GeneralResponse } from "../../../model/generalResponse"; import { checkStringValueAddSuffix, errorMessage, successMessage } from "../../Public/generalTools"; @@ -8,14 +7,19 @@ import { define } from '../../../define/define' import fs from "fs"; import { ImageStyleDefine } from "../../../define/iamgeStyleDefine"; import { ImageStyle } from "../Book/imageStyle"; +import { OperateBookType } from "../../../define/enum/bookEnum"; +import { isEmpty } from "lodash"; +import { BookServiceBasic } from "../ServiceBasic/bookServiceBasic"; const fspromise = fs.promises export class SDOpt { bookTaskDetailService: BookTaskDetailService bookTaskService: BookTaskService imageStyle: ImageStyle - constructor() { + bookServiceBasic: BookServiceBasic + constructor() { + this.bookServiceBasic = new BookServiceBasic() } @@ -34,22 +38,37 @@ export class SDOpt { /** * SD的提示词合并 * @param id 要处理的ID - * @param mergeType 合并的类型(用于判断是单个还是批量) + * @param operateBookType 合并的类型(用于判断是单个还是批量) * @returns */ - async MergePrompt(id: string, mergeType: MergeType): Promise { + async MergePrompt(id: string, operateBookType: OperateBookType): Promise { try { await this.InitService() let bookTaskDetail = undefined as Book.SelectBookTaskDetail[]; let bookTask = undefined as Book.SelectBookTask; - if (mergeType == MergeType.BOOKTASK) { + if (operateBookType == OperateBookType.BOOKTASK) { bookTaskDetail = this.bookTaskDetailService.GetBookTaskData({ bookTaskId: id }).data bookTask = this.bookTaskService.GetBookTaskDataById(id); - } else if (mergeType == MergeType.BOOKTASKDETAIL) { - bookTaskDetail = [this.bookTaskDetailService.GetBookTaskDetailDataById(id)]; + // 判断是不是有为空的 + let emptyName = [] as string[] + for (let i = 0; i < bookTaskDetail.length; i++) { + const element = bookTaskDetail[i]; + if (isEmpty(element.gptPrompt)) { + emptyName.push(element.name) + } + } + if (emptyName.length > 0) { + throw new Error("有空的提示词,请先推理") + } + } else if (operateBookType == OperateBookType.BOOKTASKDETAIL) { + let tempBookTaskDetail = await this.bookServiceBasic.GetBookTaskDetailDataById(id); + if (isEmpty(tempBookTaskDetail.gptPrompt)) { + throw new Error("当前分镜没有推理提示词,请先生成") + } + bookTaskDetail = [tempBookTaskDetail]; bookTask = this.bookTaskService.GetBookTaskDataById(bookTaskDetail[0].bookTaskId); } else { throw new Error("未知的合并类型") diff --git a/src/main/Service/ServiceBasic/bookServiceBasic.ts b/src/main/Service/ServiceBasic/bookServiceBasic.ts new file mode 100644 index 0000000..96e82cd --- /dev/null +++ b/src/main/Service/ServiceBasic/bookServiceBasic.ts @@ -0,0 +1,256 @@ +import { DEFINE_STRING } from "../../../define/define_string"; +import { GeneralResponse } from "../../../model/generalResponse"; +import { BookService } from "../../../define/db/service/Book/bookService"; +import { Book } from "../../../model/book"; +import { BookTaskService } from "../../../define/db/service/Book/bookTaskService"; +import { TaskScheduler } from "../taskScheduler"; +import { LoggerStatus, OtherData } from "../../../define/enum/softwareEnum"; +import { BookTaskDetailService } from "../../../define/db/service/Book/bookTaskDetailService"; +import { BookBackTaskListService } from "../../../define/db/service/Book/bookBackTaskListService"; +import { BookBackTaskType, BookTaskStatus, TaskExecuteType } from "../../../define/enum/bookEnum"; + +/** + * 该类中封装了小说的基础服务,主要是检查每次引入对应的服务类 + * 这边进行一个统一的调用,方便后续的维护 + */ +export class BookServiceBasic { + bookService: BookService + bookTaskService: BookTaskService; + taskScheduler: TaskScheduler + bookTaskDetailService: BookTaskDetailService + bookBackTaskListService: BookBackTaskListService + constructor() { + this.taskScheduler = new TaskScheduler() + } + async InitService() { + if (!this.bookService) { + this.bookService = await BookService.getInstance() + } + if (!this.bookTaskService) { + this.bookTaskService = await BookTaskService.getInstance() + } + if (!this.bookTaskDetailService) { + this.bookTaskDetailService = await BookTaskDetailService.getInstance() + } + if (!this.bookBackTaskListService) { + this.bookBackTaskListService = await BookBackTaskListService.getInstance() + } + } + + // 主动返回前端的消息 + sendReturnMessage(data: GeneralResponse.MessageResponse, message_name = DEFINE_STRING.BOOK.GET_COPYWRITING_RETURN) { + global.newWindow[0].win.webContents.send(message_name, data) + } + + //#region 小说相关的基础服务 + + /** + * 通过小说ID获取小说数据 + * @param bookId 小说ID + * @returns + */ + async GetBookDataById(bookId: string): Promise { + await this.InitService(); + let book = this.bookService.GetBookDataById(bookId); + if (book == null) { + let msg = '未找到对应的小说数据,请检查' + throw new Error(msg); + } + return book + } + + /** + * 更新小说指定ID的数据 + * @param bookId 小说ID + * @param data 小说要更新的数据 + */ + async UpdateBookData(bookId: string, data: Book.SelectBook): Promise { + await this.InitService(); + let res = this.bookService.UpdateBookData(bookId, data) + return res + } + + /** + * 删除指定的小说数据 + * @param bookId 需要删除的小说ID + */ + async DeleteBookData(bookId: string): Promise { + await this.InitService(); + this.bookService.DeleteBookData(bookId) + } + + //#endregion + + //#region 小说批次任务相关的基础服务 + + /** + * 通过小说ID获取小说批次任务数据 + * @param bookTaskId 小说批次任务ID + * @returns + */ + async GetBookTaskDataId(bookTaskId: string): Promise { + await this.InitService(); + let bookTask = this.bookTaskService.GetBookTaskDataById(bookTaskId); + if (bookTask == null) { + let msg = '未找到对应的小说批次任务数据,请检查'; + throw new Error(msg); + } + return bookTask + } + + /** + * 通过查询条件获取小说批次任务数据 + * @param bookTaskCondition 小说批次的查询条件 + */ + async GetBookTaskData(bookTaskCondition: Book.QueryBookTaskCondition): Promise<{ bookTasks: Book.SelectBookTask[], total: number }> { + await this.InitService(); + let bookTasks = this.bookTaskService.GetBookTaskData(bookTaskCondition) + if (bookTasks.data.bookTasks.length <= 0 || bookTasks.data.total <= 0) { + throw new Error("未找到对应的小说批次任务数据,请检查") + } + return bookTasks.data + } + + /** + * 更新小说批次任务的数据 + * @param bookTaskId 小说批次任务ID + * @param data + */ + async UpdetedBookTaskData(bookTaskId: string, data: Book.SelectBookTask): Promise { + await this.InitService(); + this.bookTaskService.UpdetedBookTaskData(bookTaskId, data) + } + + /** + * 删除指定的小说批次任务的数据 + * @param bookTaskId 需要删除的指定的小说批次任务ID + */ + async DeleteBookTaskData(bookTaskId: string): Promise { + await this.InitService(); + this.bookTaskService.DeleteBookTask(bookTaskId) + } + //#endregion + + //#region 小说批次任务对应的分镜的相关的基础服务 + + /** + * 获取指定的小说批次任务分镜数据,通过分镜ID + * @param bookTaskDetailId 小说批次任务分镜ID + * @returns + */ + async GetBookTaskDetailDataById(bookTaskDetailId: string): Promise { + await this.InitService(); + let bookTaskDetail = this.bookTaskDetailService.GetBookTaskDetailDataById(bookTaskDetailId) + if (bookTaskDetail == null) { + let msg = "未找到对应的小说批次任务分镜数据,请检查" + global.logger.error('BookServiceBasic_GetBookTaskDetailDataById', msg); + throw new Error("未找到对应的小说批次任务分镜数据,请检查") + } + return bookTaskDetail + } + + /** + * 获取指定的小说批次任务分镜数据,通过查询条件 + * @param condition + */ + async GetBookTaskDetailData(condition: Book.QueryBookTaskDetailCondition): Promise { + await this.InitService(); + let bookTaskDetails = this.bookTaskDetailService.GetBookTaskData(condition) + if (bookTaskDetails.data.length <= 0) { + let msg = "未找到对应的小说批次任务分镜数据,请检查"; + throw new Error(msg) + } + return bookTaskDetails.data + } + + /** + * 修改小说的分镜详细数据,通过ID + * @param bookTaskDetailId 小说分镜的ID + * @param data 要修改的数据,是个对象,会修改全部 + */ + async UpdateBookTaskDetail(bookTaskDetailId: string, data: Book.SelectBookTaskDetail): Promise { + await this.InitService(); + let res = this.bookTaskDetailService.UpdateBookTaskDetail(bookTaskDetailId, data) + return res + } + + /** + * 更行小说批次的状态 + * @param bookTaskId 小说批次的ID + * @param status 修改后的状态 + * @param errorMsg 错误消息 + */ + async UpdateBookTaskStatus(bookTaskId: string, status: BookTaskStatus, errorMsg: string | null = null): Promise { + await this.InitService(); + this.bookTaskService.UpdateBookTaskStatus(bookTaskId, status, errorMsg); + } + + + /** + * 删除指定的小说分镜的反推提示词 + * @param bookTaskDetail + */ + async DeleteBookTaskDetailReversePromptById(bookTaskDetailId: string): Promise { + await this.InitService(); + this.bookTaskDetailService.DeleteBookTaskDetailReversePromptById(bookTaskDetailId) + } + + //#endregion + + //#region 小说后台任务相关操作 + + /** + * 添加一个后台任务 + * @param bookId 小说ID + * @param taskType 后台任务类型 + * @param executeType 执行的类型,是不是自动执行 + * @param bookTaskId 小说批次任务ID + * @param bookTaskDetailId 小说批次任务分镜ID + */ + async AddBookBackTask(bookId: string, + taskType: BookBackTaskType, + executeType = TaskExecuteType.AUTO, + bookTaskId = null, + bookTaskDetailId = null): Promise { + + await this.InitService(); + let res = this.bookBackTaskListService.AddBookBackTask(bookId, taskType, executeType, bookTaskId, bookTaskDetailId) + if (res.code == 0) { + throw new Error(res.message) + } + return res.data as TaskModal.Task + } + //#endregion + + + + /** + * 通过小说ID和小说批次任务ID获取小说和小说批次任务数据 + * @param bookId 小说ID + * @param bookTaskName 小说批次的名字,或者是小说批次任务的ID + * @returns + */ + async GetBookAndTask(bookId: string, bookTaskName: string) { + await this.InitService(); + let book = this.bookService.GetBookDataById(bookId) + if (book == null) { + throw new Error("查找小说数据失败"); + } + // 获取小说对应的批次任务数据,默认初始化为第一个 + let condition = { + bookId: bookId + } as Book.QueryBookBackTaskCondition + if (bookTaskName == "output_00001") { + condition["name"] = bookTaskName + } else { + condition["id"] = bookTaskName + } + let bookTaskRes = this.bookTaskService.GetBookTaskData(condition) + if (bookTaskRes.data.bookTasks.length <= 0 || bookTaskRes.data.total <= 0) { + let msg = "没有找到对应的小说批次任务数据" + this.taskScheduler.AddLogToDB(bookId, book.type, msg, OtherData.DEFAULT, LoggerStatus.FAIL) + throw new Error(msg) + } + return { book: book as Book.SelectBook, bookTask: bookTaskRes.data.bookTasks[0] as Book.SelectBookTask } + } +} diff --git a/src/main/Service/ServiceBasic/softwareServiceBasic.ts b/src/main/Service/ServiceBasic/softwareServiceBasic.ts new file mode 100644 index 0000000..3637906 --- /dev/null +++ b/src/main/Service/ServiceBasic/softwareServiceBasic.ts @@ -0,0 +1,76 @@ +import { SoftwareService } from '../../../define/db/service/SoftWare/softwareService'; + + +export class SoftWareServiceBasic { + softwareService: SoftwareService + constructor() { } + + async InitService() { + if (!this.softwareService) { + this.softwareService = await SoftwareService.getInstance() + } + } + + //#region software相关的基础服务 + + /** + * 更新软件配置信息 + * @param software + */ + async UpdateSoftware(software: SoftwareSettingModel.SoftwareSetting): Promise { + await this.InitService(); + this.softwareService.UpdateSoftware(software) + } + + /** + * 添加新的软件配置信息 + * @param software + */ + async AddSfotware(software: SoftwareSettingModel.SoftwareSetting): Promise { + await this.InitService(); + this.softwareService.AddSfotware(software); + } + + /** + * 获取软件配置信息 + * 软件配置信息只有一个,所以直接返回第一个 + */ + async GetSoftwareData(): Promise { + await this.InitService(); + let softwares = this.softwareService.GetSoftwareData(); + if (softwares.data.length <= 0) { + let msg = "未找到软件配置信息,请检查"; + global.logger.error( + 'SoftWareServiceBasic_GetSoftwareData', + '获取软件的基础设置失败 ,错误信息如下:' + msg + ) + throw new Error(msg) + } + return softwares.data[0] + } + + /** + * 获取软件配置指定的属性数据 + * @param property 属性的名称 + * @returns + */ + async GetSoftWarePropertyData(property: string): Promise { + await this.InitService(); + let res = this.softwareService.GetSoftWarePropertyData(property) + return res + } + + + /** + * 保存软件的指定属性的设置信息,数据一般是字符串 + * @param property 要保存的属性的名称 + * @param data 要保存的数据信息 + */ + async SaveSoftwarePropertyData(property: string, data: string): Promise { + await this.InitService(); + this.softwareService.SaveSoftwarePropertyData(property, data) + } + + //#endregion + +} \ No newline at end of file diff --git a/src/main/Service/subtitle.ts b/src/main/Service/Subtitle/subtitle.ts similarity index 55% rename from src/main/Service/subtitle.ts rename to src/main/Service/Subtitle/subtitle.ts index 97e0261..465aa8b 100644 --- a/src/main/Service/subtitle.ts +++ b/src/main/Service/Subtitle/subtitle.ts @@ -1,45 +1,47 @@ import { isEmpty } from 'lodash' -import { BookService } from '../../define/db/service/Book/bookService' -import { errorMessage, successMessage } from '../Public/generalTools' -import { FfmpegOptions } from './ffmpegOptions' -import { SubtitleSavePositionType } from '../../define/enum/waterMarkAndSubtitle' -import { BookTaskDetailService } from '../../define/db/service/Book/bookTaskDetailService' -import { define } from '../../define/define' +import { errorMessage, successMessage } from '../../Public/generalTools' +import { FfmpegOptions } from '../ffmpegOptions' +import { SubtitleSavePositionType } from '../../../define/enum/waterMarkAndSubtitle' +import { define } from '../../../define/define' import path from 'path' import { CheckFileOrDirExist, CheckFolderExistsOrCreate, DeleteFolderAllFile, GetFilesWithExtensions -} from '../../define/Tools/file' +} from '../../../define/Tools/file' import { shell } from 'electron' -import { Book } from '../../model/book' +import { Book } from '../../../model/book' import fs from 'fs' +import { GeneralResponse } from '../../../model/generalResponse' +import { BookServiceBasic } from '../ServiceBasic/bookServiceBasic' +import { LoggerStatus, OtherData, ResponseMessageType } from '../../../define/enum/softwareEnum' +import { TaskScheduler } from '../taskScheduler' +import { SubtitleModel } from '../../../model/subtitle' +import { BookTaskStatus, OperateBookType } from '../../../define/enum/bookEnum' +import axios from 'axios' +import { GptService } from '../GPT/gpt' +import FormData from 'form-data' const util = require('util') const { exec } = require('child_process') const execAsync = util.promisify(exec) const fspromises = fs.promises + /** * 去除水印和获取字幕相关操作 */ export class Subtitle { - bookService: BookService - bookTaskDetailService: BookTaskDetailService ffmpegOptions: FfmpegOptions + bookServiceBasic: BookServiceBasic + taskScheduler: TaskScheduler + gptService: GptService - constructor() { } - - async InitService() { - if (!this.bookService) { - this.bookService = await BookService.getInstance() - } - if (!this.bookTaskDetailService) { - this.bookTaskDetailService = await BookTaskDetailService.getInstance() - } - if (!this.ffmpegOptions) { - this.ffmpegOptions = new FfmpegOptions() - } + constructor() { + this.bookServiceBasic = new BookServiceBasic() + this.taskScheduler = new TaskScheduler() + this.ffmpegOptions = new FfmpegOptions() + this.gptService = new GptService() } //#region 通用方法 @@ -50,7 +52,7 @@ export class Subtitle { * @param {*} framesPerSecond 每秒截取多少帧 * @returns */ - GenerateFrameTimes(videoDurationMs, framesPerSecond) { + GenerateFrameTimes(videoDurationMs: number, framesPerSecond: number): number[] { // 直接使用视频总时长(毫秒),不进行向下取整 const videoDurationSec = videoDurationMs / 1000 @@ -67,117 +69,70 @@ export class Subtitle { let timePoint = Math.min(Math.round(interval * i), videoDurationMs) frameTimes.push(timePoint) } - return frameTimes } + /** + * 加载指定的的小说相关的所有的数据 + * @param bookId 小说ID + * @param bookTaskId 小说任务ID + * @returns + */ + async GetBookAllData(bookId: string, bookTaskId: string = null): Promise<{ book: Book.SelectBook, bookTask: Book.SelectBookTask, bookTaskDetails: Book.SelectBookTaskDetail[] }> { + let { book, bookTask } = await this.bookServiceBasic.GetBookAndTask(bookId, bookTaskId ? bookTaskId : 'output_00001') + if (isEmpty(book.subtitlePosition)) { + throw new Error("请先设置小说的字幕位置") + } + // 获取所有的分镜数据 + let bookTaskDetails = await this.bookServiceBasic.GetBookTaskDetailData({ + bookId: bookId, + bookTaskId: bookTask.id + }) + if (bookTaskDetails.length <= 0) { + throw new Error("没有找到对应的分镜数据,请先执行对应的操作") + } + return { book, bookTask, bookTaskDetails } + } + + /** + * 通用的小说获取分案的返回方法 + * @param content 获取的文案内容 + * @param book 小说实体类 + * @param bookTask 小说任务实体类 + * @param bookTaskDetail 小说任务分镜实体类 + */ + async GetSubtitleLoggerAndResponse(content: string, progress: GeneralResponse.ProgressResponse, book: Book.SelectBook, bookTask: Book.SelectBookTask, bookTaskDetail: Book.SelectBookTaskDetail): Promise { + // 修改数据 + await this.bookServiceBasic.UpdateBookTaskDetail(bookTaskDetail.id, { + word: content, + afterGpt: content + }); + + // let res = await this.basicReverse.GetCopywritingFunc(book, item); + // 将当前的数据实时返回,前端进行修改 + this.bookServiceBasic.sendReturnMessage({ + code: 1, + id: bookTaskDetail.id, + type: ResponseMessageType.GET_TEXT, + data: { + content: content, + progress: progress + } as GeneralResponse.SubtitleProgressResponse // 返回识别到的文案 + }) + + // 添加日志 + await this.taskScheduler.AddLogToDB( + book.id, + book.type, + `${bookTaskDetail.name} 识别文案成功`, + bookTask.id, + LoggerStatus.SUCCESS + ) + } + //#endregion - /** - * 获取当前视频中所有的字幕信息 - * @param {*} value 需要的参数的对象,包含下面的参数 - * @param {*} value.id 小说ID/小说分镜详细信息ID/null - * @param {*} value.type 保存的类型(主视频/分镜视频/后续会添加外部单独的视频提取) - * @param {*} value.videoPath 视频路径 - * @param {*} value.subtitlePosition 字幕位置信息 - */ - async GetVideoFrameText(value: Book.GetVideoFrameTextParams) { - try { - await this.InitService() - let videoPath - let tempImageFolder - let position - if (value.type == SubtitleSavePositionType.MAIN_VIDEO) { - let bookRes = this.bookService.GetBookDataById(value.id) - if (bookRes == null) { - throw new Error('没有找到小说对应的的视频地址') - } - let book = bookRes - tempImageFolder = path.join(define.project_path, `${book.id}/data/subtitle/${book.id}/temp`) - if (isEmpty(book.subtitlePosition)) { - throw new Error('请先保存位置信息') - } - position = JSON.parse(book.subtitlePosition) - videoPath = book.oldVideoPath - } else if (value.type == SubtitleSavePositionType.STORYBOARD_VIDEO) { - let bookTaskDetail = this.bookTaskDetailService.GetBookTaskDetailDataById(value.id); - if (bookTaskDetail == null) { - throw new Error("没有找到小说分镜详细信息") - } - - tempImageFolder = path.join(define.project_path, `${bookTaskDetail.bookId}/data/subtitle/${bookTaskDetail.name}_${bookTaskDetail.id}/temp`) - if (isEmpty(value.subtitlePosition)) { - throw new Error('请先保存位置信息') - } - position = JSON.parse(value.subtitlePosition) - videoPath = bookTaskDetail.videoPath - } else { - throw new Error("不支持的操作"); - } - - await CheckFolderExistsOrCreate(tempImageFolder) - // 判断文件夹是不是存在,存在的话,将里面的所有文件删除 - await DeleteFolderAllFile(tempImageFolder) - - // 将视频进行抽帧,(目前是每秒1帧,时间小于一秒,抽一帧) - let getDurationRes = await this.ffmpegOptions.FfmpegGetVideoDuration(videoPath) - if (getDurationRes.code == 0) { - throw new Error(getDurationRes.message) - } - let videoDuration = getDurationRes.data - let frameTime = this.GenerateFrameTimes(videoDuration, 1) - for (let i = 0; i < frameTime.length; i++) { - const item = frameTime[i]; - let name = i.toString().padStart(6, '0') - let imagePath = path.join(tempImageFolder, `frame_${name}.png`) - // 开始抽帧 - let res = await this.ffmpegOptions.FfmpegGetVideoFramdAndClip( - videoPath, - item, - imagePath, - position - ) - if (res.code == 0) { - throw new Error(res.message) - } - } - - // 开始识别 - let textRes = await this.GetCurrentFrameText({ - id: value.id, - type: value.type, - imageFolder: tempImageFolder - }) - - let allTextData = [] as string[] - // 开始获取所有的数据 - let jsonPaths = await GetFilesWithExtensions(tempImageFolder, ['.json']) - for (let i = 0; i < jsonPaths.length; i++) { - const element = jsonPaths[i] - // 开始拼接 - let texts = JSON.parse(await fspromises.readFile(element, 'utf-8')) - for (let j = 0; j < texts.length; j++) { - const text = texts[j][1][0] - allTextData.includes(text) ? null : allTextData.push(text) - } - } - - console.log(allTextData.join(',')) - // 这边计算相似度,返回过于相似的数据 - // let res = await RemoveSimilarTexts(allTextData) - - return successMessage( - allTextData.join(','), - '获取视频的的文案信息成功', - 'WatermarkAndSubtitle_GetVideoFrameText' - ) - } catch (error) { - return errorMessage( - '提取视频的的文案信息失败,错误消息如下:' + error.toString(), - 'WatermarkAndSubtitle_GetCurrentFrameText' - ) - } - } + //#region 获取字幕位置信息相关操作 /** * 获取当前帧的文字信息 @@ -185,9 +140,8 @@ export class Subtitle { * @param {*} value.id 小说ID/小说分镜详细信息ID/null * @param {*} value.type 保存的类型(主视频/分镜视频/后续会添加外部单独的视频提取) */ - async GetCurrentFrameText(value) { + async GetCurrentFrameText(value: { id: any; type?: SubtitleSavePositionType; imageFolder: any }): Promise { try { - await this.InitService() let iamgePaths = [] let imageFolder = value.imageFolder ? value.imageFolder @@ -249,7 +203,7 @@ export class Subtitle { * @param {*} value.id 小说ID/小说分镜详细信息ID/null * @param {*} value.type 保存的类型(主视频/分镜视频/后续会添加外部单独的视频提取) */ - async OpenBookSubtitlePositionScreenshot(value) { + async OpenBookSubtitlePositionScreenshot(value: { type: SubtitleSavePositionType; id: any }) { try { let folder if ( @@ -291,9 +245,8 @@ export class Subtitle { * @param {*} value.type 保存的类型(主视频/分镜视频/后续会添加外部单独的视频提取) * @returns */ - async SaveBookSubtitlePosition(value) { + async SaveBookSubtitlePosition(value: { type: SubtitleSavePositionType; id: string; bookSubtitlePosition: string | any[]; currentTime: number }) { try { - await this.InitService() let saveData = [] let videoPath let outImagePath @@ -307,7 +260,7 @@ export class Subtitle { throw new Error('小说ID不能为空') } // 获取指定的小说 - let bookRes = this.bookService.GetBookDataById(value.id) + let bookRes = await this.bookServiceBasic.GetBookDataById(value.id) if (bookRes == null) { throw new Error('没有找到小说信息') } @@ -353,19 +306,16 @@ export class Subtitle { } // 数据保存 - let saveRes = await this.bookService.UpdateBookData(value.id, { + let saveRes = await this.bookServiceBasic.UpdateBookData(value.id, { subtitlePosition: JSON.stringify(saveData) }) - if (saveRes.code == 0) { - throw new Error(saveRes.message) - } } else if (value.type == SubtitleSavePositionType.STORYBOARD_VIDEO) { // 小说分镜详细信息保存 if (value.id == null) { throw new Error('小说分镜详细信息ID不能为空') } // 获取指定的小说分镜详细信息 - let bookStoryboard = this.bookTaskDetailService.GetBookTaskDetailDataById(value.id) + let bookStoryboard = await this.bookServiceBasic.GetBookTaskDetailDataById(value.id) if (bookStoryboard == null) { throw new Error('没有找到小说分镜信息') } @@ -414,12 +364,9 @@ export class Subtitle { }) } // 数据保存 - let saveRes = this.bookTaskDetailService.UpdateBookTaskDetail(bookStoryboard.id, { + let saveRes = this.bookServiceBasic.UpdateBookTaskDetail(bookStoryboard.id, { subtitlePosition: JSON.stringify(saveData) }) - if (saveRes.code == 0) { - throw new Error(saveRes.message) - } } // 开始设置裁剪出来的图片位置 @@ -445,4 +392,272 @@ export class Subtitle { ) } } + //#endregion + + //#region 本地OCR识别字幕相关操作 + + /** + * 获取当前视频中所有的字幕信息 + * @param {*} value 需要的参数的对象,包含下面的参数 + * @param {*} value.id 小说ID/小说分镜详细信息ID/null + * @param {*} value.type 保存的类型(主视频/分镜视频/后续会添加外部单独的视频提取) + * @param {*} value.videoPath 视频路径 + * @param {*} value.subtitlePosition 字幕位置信息 + */ + async GetVideoFrameText(value: Book.GetVideoFrameTextParams): Promise { + try { + let videoPath = undefined + let tempImageFolder = undefined + let position = undefined + if (value.type == SubtitleSavePositionType.MAIN_VIDEO) { + let bookRes = await this.bookServiceBasic.GetBookDataById(value.id) + if (bookRes == null) { + throw new Error('没有找到小说对应的的视频地址') + } + let book = bookRes + tempImageFolder = path.join(define.project_path, `${book.id}/data/subtitle/${book.id}/temp`) + if (isEmpty(book.subtitlePosition)) { + throw new Error('请先保存位置信息') + } + position = JSON.parse(book.subtitlePosition) + videoPath = book.oldVideoPath + } else if (value.type == SubtitleSavePositionType.STORYBOARD_VIDEO) { + let bookTaskDetail = await this.bookServiceBasic.GetBookTaskDetailDataById(value.id); + if (bookTaskDetail == null) { + throw new Error("没有找到小说分镜详细信息") + } + + tempImageFolder = path.join(define.project_path, `${bookTaskDetail.bookId}/data/subtitle/${bookTaskDetail.name}_${bookTaskDetail.id}/temp`) + if (isEmpty(value.subtitlePosition)) { + throw new Error('请先保存位置信息') + } + position = JSON.parse(value.subtitlePosition) + videoPath = bookTaskDetail.videoPath + } else { + throw new Error("不支持的操作"); + } + + await CheckFolderExistsOrCreate(tempImageFolder) + // 判断文件夹是不是存在,存在的话,将里面的所有文件删除 + await DeleteFolderAllFile(tempImageFolder) + + // 将视频进行抽帧,(目前是每秒1帧,时间小于一秒,抽一帧) + let getDurationRes = await this.ffmpegOptions.FfmpegGetVideoDuration(videoPath) + if (getDurationRes.code == 0) { + throw new Error(getDurationRes.message) + } + let videoDuration = getDurationRes.data + let frameTime = this.GenerateFrameTimes(videoDuration, 1) + for (let i = 0; i < frameTime.length; i++) { + const item = frameTime[i]; + let name = i.toString().padStart(6, '0') + let imagePath = path.join(tempImageFolder, `frame_${name}.png`) + // 开始抽帧 + let res = await this.ffmpegOptions.FfmpegGetVideoFramdAndClip( + videoPath, + item, + imagePath, + position + ) + if (res.code == 0) { + throw new Error(res.message) + } + } + + // 开始识别 + let textRes = await this.GetCurrentFrameText({ + id: value.id, + type: value.type, + imageFolder: tempImageFolder + }) + + let allTextData = [] as string[] + // 开始获取所有的数据 + let jsonPaths = await GetFilesWithExtensions(tempImageFolder, ['.json']) + for (let i = 0; i < jsonPaths.length; i++) { + const element = jsonPaths[i] + // 开始拼接 + let texts = JSON.parse(await fspromises.readFile(element, 'utf-8')) + for (let j = 0; j < texts.length; j++) { + const text = texts[j][1][0] + allTextData.includes(text) ? null : allTextData.push(text) + } + } + // 这边计算相似度,返回过于相似的数据 + // let res = await RemoveSimilarTexts(allTextData) + + return successMessage( + allTextData.join(','), + '获取视频的的文案信息成功', + 'WatermarkAndSubtitle_GetVideoFrameText' + ) + } catch (error) { + return errorMessage( + '提取视频的的文案信息失败,错误消息如下:' + error.toString(), + 'WatermarkAndSubtitle_GetCurrentFrameText' + ) + } + } + + /** + * 使用本地OCR识别字幕文案 + * @param bookId 小说ID + * @param bookTaskId 小说任务ID + * @returns + */ + async GetCopywritingByLocalOcr(book: Book.SelectBook, bookTask: Book.SelectBookTask, bookTaskDetails: Book.SelectBookTaskDetail[]): Promise { + try { + for (let i = 0; i < bookTaskDetails.length; i++) { + const item = bookTaskDetails[i]; + let res = await this.GetVideoFrameText({ + id: item.id, + videoPath: item.videoPath, + type: SubtitleSavePositionType.STORYBOARD_VIDEO, + subtitlePosition: book.subtitlePosition + }) + if (res.code == 0) { + throw new Error(res.message) + } + // 修改数据,并返回 + await this.GetSubtitleLoggerAndResponse(res.data, { + total: bookTaskDetails.length, + current: i + 1 + }, book, bookTask, item) + } + return successMessage(null, "识别是所有文案成功", "Subtitle_GetCopywriting") + } catch (error) { + return errorMessage("获取分镜数据失败,失败信息如下:" + error.message, 'Subtitle_GetCopywriting') + } + } + + //#endregion + + //#region Lai_WHISPER识别字幕相关操作 + + /** + * 单个分离音频的方法 + * @param book 小说数据 + * @param bookTask 小说任务数据 + * @param bookTaskDetail 小说任务详细信息数据 + * @returns + */ + async SplitAudio(book: Book.SelectBook, bookTask: Book.SelectBookTask, bookTaskDetail: Book.SelectBookTaskDetail) { + // 开始分离音频 + let videoPath = bookTaskDetail.videoPath + let audioPath = path.join(path.dirname(videoPath), bookTaskDetail.name + '.mp3'); + let audioRes = await this.ffmpegOptions.FfmpegExtractAudio(bookTaskDetail.videoPath, audioPath) + if (audioRes.code == 0) { + let errorMessage = `分离音频失败,错误信息如下:${audioRes.message}` + await this.bookServiceBasic.UpdateBookTaskStatus( + bookTask.id, + BookTaskStatus.AUDIO_FAIL, + errorMessage + ) + throw new Error(audioRes.message) + } + this.bookServiceBasic.UpdateBookTaskDetail(bookTaskDetail.id, { + audioPath: path.relative(define.project_path, audioPath) + }) + + // 推送成功消息 + await this.taskScheduler.AddLogToDB( + book.id, + book.type, + `${bookTaskDetail.name}分离音频成功,输出地址:${audioPath}`, + OtherData.DEFAULT, + LoggerStatus.SUCCESS + ) + // 修改状态为分离音频成功 + this.bookServiceBasic.UpdateBookTaskStatus(bookTask.id, BookTaskStatus.AUDIO_DONE) + return audioPath; + } + + /** + * 使用LAI Whisper 进行文本识别,然后将繁体转换为简体 + * @param audioPath 要识别的音频地址 + * @param subtitleSetting 识别字幕设置 + */ + async LaiWhisperApi(audioPath: string, subtitleSetting: SubtitleModel.subtitleSettingModel): Promise { + // 开始调用LAI API识别 + let formdata = new FormData() + formdata.append("file", fs.createReadStream(audioPath)); // 如果是Node.js环境,可以使用fs.createReadStream方法 + formdata.append("model", "whisper-1"); + formdata.append("response_format", "srt"); + formdata.append("temperature", "0"); + formdata.append("language", "zh"); + formdata.append("prompt", isEmpty(subtitleSetting.laiWhisper.prompt) ? "eiusmod nulla" : subtitleSetting.laiWhisper.prompt); + let url = subtitleSetting.laiWhisper.url + if (!url.endsWith('/')) { + url = url + '/' + } + const config = { + method: 'post', + url: url + 'v1/audio/transcriptions', + headers: { + 'Accept': 'application/json', + 'Authorization': subtitleSetting.laiWhisper.apiKey, + 'User-Agent': 'Apifox/1.0.0 (https://apifox.com)', + 'Content-Type': 'multipart/form-data', + ...formdata.getHeaders() // 在Node.js环境中需要添加这一行 + }, + data: formdata + }; + + let res = await axios(config) + let text = res.data.text; + // 但是这边是繁体,需要转化为简体 + let simpleText = await this.gptService.ChineseTraditionalToSimplified(text, subtitleSetting.laiWhisper.apiKey, url); + + console.log(res.data) + return simpleText; + } + + /** + * 使用LAI Whisper识别字幕 + * @param bookId 小说ID + * @param bookTaskId 小说任务ID + * @param subtitleSetting 提取文案相关设置 + * @returns + */ + async GetCopywritingByLaiWhisper(book: Book.SelectBook, bookTask: Book.SelectBookTask, bookTaskDetails: Book.SelectBookTaskDetail[], subtitleSetting: SubtitleModel.subtitleSettingModel): Promise { + try { + + let emptyVideoPaths = [] as string[] + for (let i = 0; i < bookTaskDetails.length; i++) { + const element = bookTaskDetails[i]; + // 将所有的分镜视频音频分开 + if (isEmpty(element.videoPath)) { + emptyVideoPaths.push(element.name) + } + } + if (emptyVideoPaths.length > 0) { + throw new Error(`以下分镜视频没有找到对应的视频路径:${emptyVideoPaths.join(",")} \n 请先计算分镜`) + } + // 拆分音频和视频 + for (let i = 0; i < bookTaskDetails.length; i++) { + const bookTaskDetail = bookTaskDetails[i]; + // 开始分离音频 + let audioPath = await this.SplitAudio(book, bookTask, bookTaskDetail) + let fileExist = await CheckFileOrDirExist(audioPath) + if (!fileExist) { + throw new Error('没有找到对应的音频文件'); + } + // 开始调用LAI API识别 + let content = await this.LaiWhisperApi(audioPath, subtitleSetting); + // 向前端发送数据 + await this.GetSubtitleLoggerAndResponse(content, { + total: bookTaskDetails.length, + current: i + 1 + }, book, bookTask, bookTaskDetail) + } + return successMessage( + null, + `所有音频识别成功`, + 'Subtitle_GetCopywritingByLaiWhisper' + ) + } catch (error) { + return errorMessage("获取分镜数据失败,失败信息如下:" + error.message, 'Subtitle_GetCopywritingByLaiWhisper') + } + } + //#endregion } diff --git a/src/main/Service/Subtitle/subtitleService.ts b/src/main/Service/Subtitle/subtitleService.ts new file mode 100644 index 0000000..752c874 --- /dev/null +++ b/src/main/Service/Subtitle/subtitleService.ts @@ -0,0 +1,227 @@ +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 { LoggerStatus, ResponseMessageType } from "../../../define/enum/softwareEnum"; +import { TaskScheduler } from "../taskScheduler"; +import { OperationType } from "realm/dist/public-types/internal"; +import { OperateBookType } from "../../../define/enum/bookEnum"; +import { Book } from "../../../model/book"; + +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 { + 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 { + 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 { + 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 { + 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 语音转文案或者是字幕识别 + /** + * 反推提取文案 + */ + async GetCopywriting(bookId: string, bookTaskId: string, operateBookType: OperateBookType): Promise { + 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("未知的操作类型") + } + if (bookTaskDetails.length <= 0) { + throw new Error("分镜信息不存在"); + } + 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 { + try { + let bookTask = await this.bookServiceBasic.GetBookTaskDataId(bookTaskId) + 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') + } + + } + + //#endregion +} \ No newline at end of file diff --git a/src/main/Service/Translate/TranslateService.ts b/src/main/Service/Translate/TranslateService.ts index d4c3746..7714447 100644 --- a/src/main/Service/Translate/TranslateService.ts +++ b/src/main/Service/Translate/TranslateService.ts @@ -10,6 +10,7 @@ import { ResponseMessageType } from "../../../define/enum/softwareEnum"; import { SoftwareService } from '../../../define/db/service/SoftWare/softwareService' import { isEmpty } from "lodash"; import { ValidateJson } from "../../../define/Tools/validate"; +import { BookServiceBasic } from "../ServiceBasic/bookServiceBasic"; /** * 翻译实现服务 @@ -18,8 +19,10 @@ export class TranslateService { translate: Translate bookTaskDetail: BookTaskDetailService; softwareService: SoftwareService + bookServiceBasic: BookServiceBasic constructor() { + this.bookServiceBasic = new BookServiceBasic(); } async InitService() { @@ -107,7 +110,7 @@ export class TranslateService { // 解析 let tryParse = ValidateJson(translateSettingString) if (!tryParse) { - throw new Error("翻译设置数据解析失败") + throw new Error("翻译设置数据解析失败,请重置后重新配置") } translateSetting = JSON.parse(translateSettingString); } @@ -175,7 +178,7 @@ export class TranslateService { /** - * 反推翻译提示词处理 + * 翻译反推提示词处理 * @param bookTaskDetailId 对应的分镜ID * @param reversePromptId 对应的反推提示词数据ID * @param to 目标语言 @@ -200,6 +203,12 @@ export class TranslateService { case TranslateType.REVERSE_PROMPT_TRANSLATE: await this.TranslateProcessReversePrompt(value.bookTaskDetailId, value.reversePromptId, to, dstString) break; + case TranslateType.GPT_PROMPT_TRANSLATE: + // 这个直接改就行 + await this.bookServiceBasic.UpdateBookTaskDetail(value.bookTaskDetailId, { + gptPrompt: dstString + }) + break; default: throw new Error("未知的翻译类型"); } @@ -228,11 +237,15 @@ export class TranslateService { // 写回数据库 await this.TranslateReturnProcess(element, data.to, srcString); + let responseType = ResponseMessageType.REVERSE_PROMPT_TRANSLATE; + if (element.type == TranslateType.GPT_PROMPT_TRANSLATE) { + responseType = ResponseMessageType.GPT_PROMPT_TRANSLATE + } // 做个返回数据 this.sendTranslateReturn(element.windowId, { code: 1, id: element.bookTaskDetailId, - type: ResponseMessageType.PROMPT_TRANSLATE, + type: responseType, data: { progress: i + 1, total: value.length, diff --git a/src/main/Service/d3.ts b/src/main/Service/d3.ts new file mode 100644 index 0000000..a48f5c4 --- /dev/null +++ b/src/main/Service/d3.ts @@ -0,0 +1,15 @@ + +export class D3 { + constructor() { } + + //#region D3进行画图的基础方法 + + //#endregion + + //#region 软件相关的方法 + + + + //endregion + +} \ No newline at end of file diff --git a/src/main/Service/ffmpegOptions.ts b/src/main/Service/ffmpegOptions.ts index 8fbfef1..1959af6 100644 --- a/src/main/Service/ffmpegOptions.ts +++ b/src/main/Service/ffmpegOptions.ts @@ -172,7 +172,7 @@ export class FfmpegOptions { * @param {*} outAudioPath 输出音频地址 * @returns */ - async FfmpegExtractAudio(videoPath, outAudioPath) { + async FfmpegExtractAudio(videoPath: string, outAudioPath: string): Promise { try { // 判断视频地址是不是存在 let videoIsExist = await CheckFileOrDirExist(videoPath) @@ -205,13 +205,12 @@ export class FfmpegOptions { } /** - * Ffmpeg提取视频帧(只提取一帧) - * 根据point判断提取什么位置的帧 + * Ffmpeg提取视频指定时间的帧(只提取一帧) * @param {*} frameTime 视频的时间点 * @param {*} videoPath 视频地址 * @param {*} outFramePath 输出帧地址 */ - async FfmpegGetFrame(frameTime, videoPath, outFramePath) { + async FfmpegGetFrame(frameTime: number, videoPath: string, outFramePath: string): Promise { try { let videoIsExist = await CheckFileOrDirExist(videoPath) if (videoIsExist == false) { @@ -238,7 +237,7 @@ export class FfmpegOptions { .run() }) let res_msg = `视频抽帧完成,输出地址:${res}` - return successMessage(res, '视频抽帧成功', 'BasicReverse_FfmpegGetFrame') + return successMessage(res, res_msg, 'BasicReverse_FfmpegGetFrame') } catch (error) { return errorMessage(error.message, 'BasicReverse_FfmpegGetFrame') } @@ -328,7 +327,7 @@ export class FfmpegOptions { throw new Error(frameRes.message) } let outImagePaths = [] - if(await CheckFileOrDirExist(outImagePath) == false){ + if (await CheckFileOrDirExist(outImagePath) == false) { return successMessage( outImagePaths, '获取指定位置的帧和裁剪成功', diff --git a/src/main/Service/taskManage.ts b/src/main/Service/taskManage.ts index de5b564..cf97711 100644 --- a/src/main/Service/taskManage.ts +++ b/src/main/Service/taskManage.ts @@ -124,6 +124,12 @@ export class TaskManager { for (let index = 0; index < tasks.data.length; index++) { const element = tasks.data[index]; if (element.type == BookBackTaskType.MJ_IMAGE || element.type == BookBackTaskType.MJ_REVERSE) { + // 判断任务数量是不是又修改 + let taskNumber = global.mjQueue.getConcurrencyLimit(); + if (taskNumber != this.mjOpt.mjSetting.taskCount) { + global.mjQueue.concurrencyLimit = this.mjOpt.mjSetting.taskCount // 重置并发执行的数量 + } + if (global.mjQueue.getWaitingQueue() > 10) { console.log('MJ等待中的任务太多,等待中的任务数量:', global.mjQueue.getWaitingQueue()); this.spaceTime = 20000; @@ -275,11 +281,6 @@ export class TaskManager { * @param task */ async AddImageMJImage(task: TaskModal.Task) { - // 判断任务数量是不是又修改 - let taskNumber = global.mjQueue.getConcurrencyLimit(); - if (taskNumber != this.mjOpt.mjSetting.taskCount) { - global.mjQueue.concurrencyLimit = this.mjOpt.mjSetting.taskCount // 重置并发执行的数量 - } // 判断是不是MJ的任务 let batch = DEFINE_STRING.MJ.MJ_IMAGE; global.mjQueue.enqueue(async () => { diff --git a/src/main/Service/watermark.ts b/src/main/Service/watermark.ts index 6c3f8f1..954fe3d 100644 --- a/src/main/Service/watermark.ts +++ b/src/main/Service/watermark.ts @@ -352,7 +352,11 @@ export class Watermark { bookId: bookTask.bookId }).data as Book.SelectBookTaskDetail[] } else if (operateBookType == OperateBookType.BOOKTASKDETAIL) { - bookTask = this.bookTaskService.GetBookTaskDataById(id); + let bookTaskDetail = this.bookTaskDetailService.GetBookTaskDetailDataById(id) + if (bookTaskDetail == null) { + throw new Error("指定的小说任务分镜信息不存在,请检查") + } + bookTask = this.bookTaskService.GetBookTaskDataById(bookTaskDetail.bookTaskId); if (bookTask == null) { throw new Error('指定的小说任务数据不存在') } @@ -360,10 +364,7 @@ export class Watermark { if (book == null) { throw new Error("小说数据不存在,请检查") } - let bookTaskDetail = this.bookTaskDetailService.GetBookTaskDetailDataById(id) - if (bookTaskDetail == null) { - throw new Error("指定的小说任务分镜信息不存在,请检查") - } + bookTaskDetails = [bookTaskDetail]; } else { throw new Error("未知的操作类型") @@ -451,7 +452,11 @@ export class Watermark { this.taskScheduler.AddLogToDB(book.id, book.type, `${element.name} 去除水印完成`, element.bookTaskId, LoggerStatus.SUCCESS) } // 全部完毕 - return successMessage(null, "全部图片去除水印完成", "ReverseBook_RemoveWatermark") + if (operateBookType == OperateBookType.BOOKTASKDETAIL) { + return successMessage(bookTaskDetails[0].oldImage + '?t=' + new Date().getTime(), "去除水印完成", "ReverseBook_RemoveWatermark") + } else { + return successMessage(null, "全部图片去除水印完成", "ReverseBook_RemoveWatermark") + } } catch (error) { return errorMessage("去除水印失败,错误信息如下:" + error.message, "ReverseBook_RemoveWatermark") } diff --git a/src/main/setting/gptSetting.ts b/src/main/setting/gptSetting.ts index 7d23e40..e44c3df 100644 --- a/src/main/setting/gptSetting.ts +++ b/src/main/setting/gptSetting.ts @@ -6,11 +6,20 @@ import axios from 'axios' import { ServiceBase } from '../../define/db/service/serviceBase' import { isEmpty } from 'lodash' import { ValidateJson } from '../../define/Tools/validate' +import { SyncGptKeyType } from '../../define/enum/softwareEnum' +import { GeneralResponse } from '../../model/generalResponse' +import { SoftWareServiceBasic } from '../Service/ServiceBasic/softwareServiceBasic' +import { SubtitleService } from '../Service/Subtitle/subtitleService' +import { SubtitleModel } from '../../model/subtitle' export class GptSetting extends ServiceBase { + softWareServiceBasic: SoftWareServiceBasic + subtitleService: SubtitleService constructor() { super() axios.defaults.baseURL = define.serverUrl + this.softWareServiceBasic = new SoftWareServiceBasic(); + this.subtitleService = new SubtitleService() } /** @@ -105,4 +114,19 @@ export class GptSetting extends ServiceBase { ) } } + + + /** + * 同步GPT的Key到其他的设置中 + * @param syncTpye + */ + async SyncGptKey(syncTpye: SyncGptKeyType): Promise { + try { + let globalGptKey = global.config.gpt_key; + console.log(globalGptKey); + return successMessage(globalGptKey, '获取全局配置成功', 'GptSetting_SyncGptKey') + } catch (error) { + return errorMessage("同步GPT的Key到其他的设置中失败,失败信息如下:" + error.toString(), "GptSetting_SyncGptKey") + } + } } diff --git a/src/model/Setting/softwareSetting.d.ts b/src/model/Setting/softwareSetting.d.ts new file mode 100644 index 0000000..3aef767 --- /dev/null +++ b/src/model/Setting/softwareSetting.d.ts @@ -0,0 +1,17 @@ + +declare namespace SoftwareSettingModel { + type SoftwareSetting = { + id?: string + theme?: SoftwareThemeType + reverse_display_show?: boolean + reverse_show_book_striped?: boolean + reverse_data_table_size?: ComponentSize + globalSetting?: string + ttsSetting?: string + writeSetting?: string + aiSetting?: string + watermarkSetting?: string + translationSetting?: string + subtitleSetting?: string + } +} \ No newline at end of file diff --git a/src/model/book.d.ts b/src/model/book.d.ts index 8116745..2a578cd 100644 --- a/src/model/book.d.ts +++ b/src/model/book.d.ts @@ -30,6 +30,22 @@ declare namespace Book { suffixPrompt?: string | null // 后缀 } + type BookBackTaskList = { + id?: string + bookId?: string + bookTaskId?: string + bookTaskDetailId?: string + name?: string // 任务名称,小说名+批次名+分镜名 + type?: BookBackTaskType + status?: BookBackTaskStatus + errorMessage?: string + executeType?: TaskExecuteType // 任务执行类型,手动还是自动 + createTime?: Date + updateTime?: Date + startTime?: number + endTime?: number + } + type SelectBookTask = { no?: number, id?: string, diff --git a/src/model/generalResponse.d.ts b/src/model/generalResponse.d.ts index 8e98cac..48a17cf 100644 --- a/src/model/generalResponse.d.ts +++ b/src/model/generalResponse.d.ts @@ -21,6 +21,11 @@ declare namespace GeneralResponse { current: number, // 当前进度 } + type SubtitleProgressResponse = { + content: string, // 文案内容 + progress: ProgressResponse + } + // 主线程主动返回前端的消息类 type MessageResponse = { code: number, @@ -28,6 +33,6 @@ declare namespace GeneralResponse { type: ResponseMessageType, dialogType?: DialogType = DialogType.MESSAGE, message?: string, - data?: MJ.MJResponseToFront | Buffer | string | TranslateModel.TranslateResponseMessageModel | ProgressResponse + data?: MJ.MJResponseToFront | Buffer | string | TranslateModel.TranslateResponseMessageModel | ProgressResponse | SubtitleProgressResponse } } diff --git a/src/model/subtitle.d.ts b/src/model/subtitle.d.ts new file mode 100644 index 0000000..4af06fc --- /dev/null +++ b/src/model/subtitle.d.ts @@ -0,0 +1,13 @@ +import { GetSubtitleType } from "../define/enum/waterMarkAndSubtitle" + +declare namespace SubtitleModel { + type subtitleSettingModel = { + selectModel: GetSubtitleType, + laiWhisper: { + url: string, + apiKey: string, + syncGPTAPIKey: boolean, + prompt: string + } + } +} \ No newline at end of file diff --git a/src/preload/book.js b/src/preload/book.js index ed49783..2823163 100644 --- a/src/preload/book.js +++ b/src/preload/book.js @@ -51,7 +51,7 @@ const book = { //#endregion - //#region 一键反推的单个任务 + //#region 分镜 // 开始计算分镜数据 ComputeStoryboard: async (bookId) => @@ -60,17 +60,57 @@ const book = { // 开始执行分镜,切分视频 Framing: async (bookId) => await ipcRenderer.invoke(DEFINE_STRING.BOOK.FRAMING, bookId), + // 替换指定的分镜的视频的当前帧 + ReplaceVideoCurrentFrame: async (bookTaskDetailId, currentTime) => + await ipcRenderer.invoke( + DEFINE_STRING.BOOK.REPLACE_VIDEO_CURRENT_FRAME, + bookTaskDetailId, + currentTime + ), + //#endregion + + //#region 文案相关信息 + // 获取文案信息 - GetCopywriting: async (bookId, bookTaskId) => - await ipcRenderer.invoke(DEFINE_STRING.BOOK.GET_COPYWRITING, bookId, bookTaskId), + GetCopywriting: async (bookId, bookTaskId, operateBookType) => + await ipcRenderer.invoke( + DEFINE_STRING.BOOK.GET_COPYWRITING, + bookId, + bookTaskId, + operateBookType + ), + + // 将文案信息导出,方便修改 + ExportCopywriting: async (bookTaskId) => + await ipcRenderer.invoke(DEFINE_STRING.BOOK.EXPORT_COPYWRITING, bookTaskId), + + //#endregion + + //#region 水印 // 去除所有水印 - RemoveWatermark: async (id,operateBookType) => - await ipcRenderer.invoke(DEFINE_STRING.BOOK.REMOVE_WATERMARK, id,operateBookType), + RemoveWatermark: async (id, operateBookType) => + await ipcRenderer.invoke(DEFINE_STRING.BOOK.REMOVE_WATERMARK, id, operateBookType), - // 添加单句推理的 - AddReversePrompt: async (bookTaskDetailIds, type) => - await ipcRenderer.invoke(DEFINE_STRING.BOOK.ADD_REVERSE_PROMPT, bookTaskDetailIds, type), + //#endregion + + //#region 提示词 + + MergePrompt: async (id, type, operateBookType) => + await ipcRenderer.invoke(DEFINE_STRING.BOOK.MERGE_PROMPT, id, type, operateBookType), + + //#endregion + + //#region 一键反推的单个任务 + + // 添加单句反推的 + AddReversePrompt: async (bookTaskDetailIds, operateBookType, type) => + await ipcRenderer.invoke( + DEFINE_STRING.BOOK.ADD_REVERSE_PROMPT, + bookTaskDetailIds, + operateBookType, + type + ), // 将反推的数据指定的位置的提示词写入到GPT提示词中 ReversePromptToGptPrompt: async (bookId, bookTaskId, index) => @@ -115,6 +155,18 @@ const book = { OneToFourBookTask: async (bookTaskId) => await ipcRenderer.invoke(DEFINE_STRING.BOOK.ONE_TO_FOUR_BOOK_TASK, bookTaskId), + //#region 小说相关 + + // 重置小说数据 + ResetBookData: async (bookId) => + await ipcRenderer.invoke(DEFINE_STRING.BOOK.RESET_BOOK_DATA, bookId), + + // 删除小说数据 + DeleteBookData: async (bookId) => + await ipcRenderer.invoke(DEFINE_STRING.BOOK.DELETE_BOOK_DATA, bookId), + + //#endregion + //#region 小说批次任务相关 // 重置小说批次数据 @@ -142,7 +194,7 @@ const book = { HDImage: async (id, scale, operateBookType) => await ipcRenderer.invoke(DEFINE_STRING.BOOK.HD_IMAGE, id, scale, operateBookType), - // 讲小说视频相关的设置添加到小说任务批次 + // 将小说视频相关的设置添加到小说任务批次 UseBookVideoDataToBookTask: async (id, operateBookType) => await ipcRenderer.invoke( DEFINE_STRING.BOOK.USE_BOOK_VIDEO_DATA_TO_BOOK_TASK, diff --git a/src/preload/gpt.js b/src/preload/gpt.js index 4c46595..9aceaef 100644 --- a/src/preload/gpt.js +++ b/src/preload/gpt.js @@ -7,7 +7,6 @@ const gpt = { return await ipcRenderer.invoke(DEFINE_STRING.GPT.INIT_SERVER_GPT_OPTIONS) }, - //#region GPT 设置相关 // 获取软件设置里面的GPT设置 GetAISetting: async () => { @@ -17,6 +16,11 @@ const gpt = { // 保存软件设置里面的GPT设置 SaveAISetting: async (data) => { return await ipcRenderer.invoke(DEFINE_STRING.GPT.SAVE_AI_SETTING, data) + }, + + // 同步GPT Key 到指定的设置 + SyncGptKey: async (syncType) => { + return await ipcRenderer.invoke(DEFINE_STRING.GPT.SYNC_GPT_KEY, syncType) } //#endregion diff --git a/src/preload/mj.js b/src/preload/mj.js index 6f8de10..c2f1096 100644 --- a/src/preload/mj.js +++ b/src/preload/mj.js @@ -69,10 +69,6 @@ const mj = { AutoMatchUser: async (value, callback) => callback(await ipcRenderer.invoke(DEFINE_STRING.MJ.AUTO_MATCH_USER, value)), - // 合并提示词 - MergePrompt: async (id, mergeType) => - await ipcRenderer.invoke(DEFINE_STRING.MJ.MJ_MERGE_PROMPT, id, mergeType), - // 单个出图 AddMJGenerateImageTask: async (id, operateBookType) => await ipcRenderer.invoke(DEFINE_STRING.MJ.ADD_MJ_GENADD_MJ_GENERATE_IMAGE_TASK, id, operateBookType) diff --git a/src/preload/sd.js b/src/preload/sd.js index 5f5aeba..14da344 100644 --- a/src/preload/sd.js +++ b/src/preload/sd.js @@ -9,9 +9,5 @@ const sd = { // 文生图,单张 txt2img: async (value, callback) => callback(await ipcRenderer.invoke(DEFINE_STRING.SD.TXT2IMG, value)), - - // 合并提示词 - MergePrompt: async (id, mergeType) => - await ipcRenderer.invoke(DEFINE_STRING.SD.SD_MERGE_PROMPT, id, mergeType) } export { sd } diff --git a/src/preload/write.js b/src/preload/write.js index 974f0fa..382691d 100644 --- a/src/preload/write.js +++ b/src/preload/write.js @@ -11,6 +11,18 @@ const write = { SaveWriteConfig: async (data) => await ipcRenderer.invoke(DEFINE_STRING.WRITE.SAVE_WRITE_CONFIG, data), + // 获取当前的识别字幕设置的数据 + GetSubtitleSetting: async () => + await ipcRenderer.invoke(DEFINE_STRING.WRITE.GET_SUBTITLE_SETTING), + + // 重置识别字幕设置的数据 + ResetSubtitleSetting: async () => + await ipcRenderer.invoke(DEFINE_STRING.WRITE.RESET_SUBTITLE_SETTING), + + // 保存识别字幕设置的数据 + SaveSubtitleSetting: async (subtitleSetting) => + await ipcRenderer.invoke(DEFINE_STRING.WRITE.SAVE_SUBTITLE_SETTING, subtitleSetting), + //#endregion //#region AI相关的任务 diff --git a/src/renderer/src/App.vue b/src/renderer/src/App.vue index 827efa0..c1afe1d 100644 --- a/src/renderer/src/App.vue +++ b/src/renderer/src/App.vue @@ -1,19 +1,19 @@ diff --git a/src/renderer/src/components/Book/Components/BookTaskListAction.vue b/src/renderer/src/components/Book/Components/BookTaskListAction.vue index eee3d13..fa72458 100644 --- a/src/renderer/src/components/Book/Components/BookTaskListAction.vue +++ b/src/renderer/src/components/Book/Components/BookTaskListAction.vue @@ -34,14 +34,11 @@ diff --git a/src/renderer/src/components/Book/Components/ManageBookDetailButton.vue b/src/renderer/src/components/Book/Components/ManageBookDetailButton.vue index 547df16..135dee2 100644 --- a/src/renderer/src/components/Book/Components/ManageBookDetailButton.vue +++ b/src/renderer/src/components/Book/Components/ManageBookDetailButton.vue @@ -134,10 +134,12 @@ export default defineComponent({ code.value = code.value + '\n' + value.data return } + // 修改进度 + softwareStore.spin.tip = `正在提取文案,当前进度 ${value.data.progress.current} / ${value.data.progress.total} 。。。` let index = reverseManageStore.selectBookTaskDetail.findIndex((item) => item.id == value.id) if (index >= 0) { - reverseManageStore.selectBookTaskDetail[index].word = value.data - reverseManageStore.selectBookTaskDetail[index].afterGpt = value.data + reverseManageStore.selectBookTaskDetail[index].word = value.data.content + reverseManageStore.selectBookTaskDetail[index].afterGpt = value.data.content } }) @@ -251,18 +253,39 @@ export default defineComponent({ return } - let copywriting_res = await window.book.GetCopywriting(reverseManageStore.selectBook.id) - if (copywriting_res.code == 0) { - message.error(copywriting_res.message) + let subtitleSettingRes = await window.write.GetSubtitleSetting() + if (subtitleSettingRes.code == 0) { + message.error(subtitleSettingRes.message) return } - message.success('获取文案成功') + + let da = dialog.warning({ + title: '开始提取文案提示', + content: `即将进行文案提取,当前的文案提取模式为 ${subtitleSettingRes.data.selectModel} ,是否继续?`, + positiveText: '继续', + negativeText: '取消', + onPositiveClick: async () => { + da?.destroy() + softwareStore.spin.spinning = true + softwareStore.spin.tip = `正在使用 ${subtitleSettingRes.data.selectModel} 提取文案,正在准备中。。。` + let copywriting_res = await window.book.GetCopywriting( + reverseManageStore.selectBook.id, + reverseManageStore.selectBookTask.id, + OperateBookType.BOOKTASK + ) + softwareStore.spin.spinning = false + if (copywriting_res.code == 0) { + message.error(copywriting_res.message) + return + } + // dialog.success({}) + message.success('获取全部文案成功') + } + }) } // 开始去除水印 async function RemoveWatermark() { - // softwareStore.spin.spinning = true - // softwareStore.spin.tip = '正在去除水印中' if (isEmpty(reverseManageStore.selectBookTask.id)) { window.api.showGlobalMessageDialog({ code: 0, @@ -270,12 +293,23 @@ export default defineComponent({ }) return } - let res_frame = await window.book.RemoveWatermark( - reverseManageStore.selectBookTask.id, - OperateBookType.BOOKTASK - ) - softwareStore.spin.spinning = false - window.api.showGlobalMessageDialog(res_frame) + let da = dialog.warning({ + title: '去除水印提示', + content: '即将去除全部水印,是否继续?', + positiveText: '继续', + negativeText: '取消', + onPositiveClick: async () => { + da?.destroy() + softwareStore.spin.spinning = true + softwareStore.spin.tip = '正在去除水印中。。。' + let res_frame = await window.book.RemoveWatermark( + reverseManageStore.selectBookTask.id, + OperateBookType.BOOKTASK + ) + softwareStore.spin.spinning = false + window.api.showGlobalMessageDialog(res_frame) + } + }) } // 获取文案信息设置 @@ -297,6 +331,28 @@ export default defineComponent({ }) } + // 导出文案 + async function ExportCopywriting() { + debugger + softwareStore.spin.spinning = true + softwareStore.spin.tip = '正在导出文案中...' + let res = await window.book.ExportCopywriting(reverseManageStore.selectBookTask.id) + if (res.code == 0) { + message.error(res.message) + } else { + dialog.success({ + title: '导出文案成功', + content: '导出文案成功,是否打开导出的字幕文件', + positiveText: '打开', + negativeText: '取消', + onPositiveClick: () => { + window.system.OpenFile(res.data) + } + }) + } + softwareStore.spin.spinning = false + } + /** * 获取水印位置 */ @@ -329,6 +385,9 @@ export default defineComponent({ case 'recognizing_setting': // 文案位置设置 await GetCopywritingSetting() break + case 'export_recognizing': // 导出文案 + await ExportCopywriting() + break case 'watermark_position': // 水印位置设置 await GetWatermarkPosition() break @@ -376,7 +435,6 @@ export default defineComponent({ * @param type 反推类型 */ async function ImageReversePrompt(type = undefined) { - debugger if (isEmpty(reverseManageStore.selectBookTask.id)) { window.api.showGlobalMessageDialog({ code: 0, @@ -384,32 +442,36 @@ export default defineComponent({ }) return } + dialog.warning({ + title: '反推提示', + content: `即将进行反推操作,反推方式为 ${ + type ? type : reverseManageStore.selectBook.type + } ,是否继续?`, + positiveText: '继续', + negativeText: '取消', + onPositiveClick: async () => { + if (!type) { + let bookType = reverseManageStore.selectBook.type + type = bookType + } - if (!type) { - let bookType = reverseManageStore.selectBook.type - type = bookType - } - - if (type != BookType.MJ_REVERSE && type != BookType.SD_REVERSE) { - message.error(`该类型 ${bookType} 的小说不支持反推`) - return - } - let reverseIds = [] - // 开始获取需要反推的数据 - for (let i = 0; i < reverseManageStore.selectBookTaskDetail.length; i++) { - const element = reverseManageStore.selectBookTaskDetail[i] - if (!element.reversePrompt || element.reversePrompt.length <= 0) { - reverseIds.push(element.id) + if (type != BookType.MJ_REVERSE && type != BookType.SD_REVERSE) { + message.error(`该类型 ${bookType} 的小说不支持反推`) + return + } + // 开始添加 + let res = await window.book.AddReversePrompt( + reverseManageStore.selectBookTask.id, + OperateBookType.BOOKTASK, + type + ) + if (res.code == 0) { + message.error(res.message) + } else { + message.success('添加所有反推任务成功') + } } - } - - // 开始添加 - let res = await window.book.AddReversePrompt(reverseIds, type) - if (res.code == 0) { - message.error(res.message) - } else { - message.success('添加所有反推任务成功') - } + }) } /** @@ -417,13 +479,16 @@ export default defineComponent({ */ async function RemoveReverseData(id) { let deleteIds = [] - - // 删除全部的 - for (let i = 0; i < reverseManageStore.selectBookTaskDetail.length; i++) { - const element = reverseManageStore.selectBookTaskDetail[i] - if (element.reversePrompt && element.reversePrompt.length > 0) { - deleteIds.push(element.id) + if (id == undefined) { + // 删除全部的 + for (let i = 0; i < reverseManageStore.selectBookTaskDetail.length; i++) { + const element = reverseManageStore.selectBookTaskDetail[i] + if (element.reversePrompt && element.reversePrompt.length > 0) { + deleteIds.push(element.id) + } } + } else { + deleteIds.push(id) } let res = await window.book.RemoveReverseData(deleteIds) @@ -643,6 +708,10 @@ export default defineComponent({ label: '提取文案位置', key: 'recognizing_setting' }, + { + label: '导出文案', + key: 'export_recognizing' + }, { label: '停止提取', key: 'stop_recognizing' @@ -665,7 +734,8 @@ export default defineComponent({ }, { label: 'SD反推', - key: 'sd_reverse' + key: 'sd_reverse', + disabled: reverseManageStore.selectBook.type != BookType.SD_REVERSE }, { label: '清除反推数据', diff --git a/src/renderer/src/components/Book/Components/ManageBookOldImage.vue b/src/renderer/src/components/Book/Components/ManageBookOldImage.vue index c16a8a8..37836fd 100644 --- a/src/renderer/src/components/Book/Components/ManageBookOldImage.vue +++ b/src/renderer/src/components/Book/Components/ManageBookOldImage.vue @@ -1,28 +1,107 @@ - diff --git a/src/renderer/src/components/Book/Components/ManageBookTaskGenerateInformation.vue b/src/renderer/src/components/Book/Components/ManageBookTaskGenerateInformation.vue index a2ad11e..114e845 100644 --- a/src/renderer/src/components/Book/Components/ManageBookTaskGenerateInformation.vue +++ b/src/renderer/src/components/Book/Components/ManageBookTaskGenerateInformation.vue @@ -48,7 +48,7 @@ {{ - type == 'bookTsk' ? '应用主小说相关数据' : '当前就是用的主小说的数据' + type == bookTask ? '应用主小说相关数据' : '当前就是用的主小说的数据' }} 保存数据 @@ -73,6 +73,7 @@ export default defineComponent({ props: ['bookTask', 'type'], setup(props) { let bookTask = ref(props.bookTask) + debugger let type = ref(props.type) let backgroundMusicOptions = ref([]) let message = useMessage() diff --git a/src/renderer/src/components/Book/MJReverse/MJReversePrompt.vue b/src/renderer/src/components/Book/MJReverse/MJReversePrompt.vue index dfd8b9a..1426a14 100644 --- a/src/renderer/src/components/Book/MJReverse/MJReversePrompt.vue +++ b/src/renderer/src/components/Book/MJReverse/MJReversePrompt.vue @@ -1,5 +1,5 @@ + + diff --git a/src/renderer/src/components/Setting/TranslateSetting.vue b/src/renderer/src/components/Setting/TranslateSetting.vue index 6eb4aff..493d905 100644 --- a/src/renderer/src/components/Setting/TranslateSetting.vue +++ b/src/renderer/src/components/Setting/TranslateSetting.vue @@ -39,8 +39,14 @@ + @@ -93,6 +99,7 @@ export default defineComponent({ return } translateSetting.value = translateSettingRes.data + console.log(translateSetting.value) }) function GetTranslationName(name) { diff --git a/src/renderer/src/components/Watermark/GetWaterMaskRectangle.vue b/src/renderer/src/components/Watermark/GetWaterMaskRectangle.vue index 1ad0fe9..17af09d 100644 --- a/src/renderer/src/components/Watermark/GetWaterMaskRectangle.vue +++ b/src/renderer/src/components/Watermark/GetWaterMaskRectangle.vue @@ -448,7 +448,7 @@ export default defineComponent({ async function SaveMask() { debugger // 判断当前是不是又蒙板 - if (canvasHistory.length <= 1) { + if (canvasHistory.length <= 0) { message.error('请先选择去水印的区域') return } diff --git a/src/stores/reverseManage.ts b/src/stores/reverseManage.ts index e57936c..25b3aa8 100644 --- a/src/stores/reverseManage.ts +++ b/src/stores/reverseManage.ts @@ -81,6 +81,8 @@ export const useReverseManageStore = defineStore('reverseManage', { // 获取小说任务数据 async GetBookTaskDataFromDB(condition) { try { + debugger + this.bookTaskData = [] //@ts-ignore let res = await window.book.GetBookTaskData(condition) if (res.code == 0) { @@ -90,7 +92,6 @@ export const useReverseManageStore = defineStore('reverseManage', { this.bookTaskData = res.data.bookTasks this.selectBookTask = res.data.bookTasks[0] } else { - this.bookTaskData = [] this.selectBookTask = { no: null, id: null,