diff --git a/package-lock.json b/package-lock.json index 2699136..ffef71d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "laitool", - "version": "3.0.1-preview.2", + "version": "3.0.1-preview.3", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "laitool", - "version": "3.0.1-preview.2", + "version": "3.0.1-preview.3", "hasInstallScript": true, "dependencies": { "@alicloud/alimt20181012": "^1.2.0", diff --git a/package.json b/package.json index 8a47729..cd7a671 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "laitool", - "version": "3.0.1-preview.2", + "version": "3.0.1-preview.3", "description": "An AI tool for image processing, video processing, and other functions.", "main": "./out/main/index.js", "author": "laitool.cn", diff --git a/resources/image/c_s/7f403066-7722-4423-853e-17bac07c5565.png b/resources/image/c_s/7f403066-7722-4423-853e-17bac07c5565.png new file mode 100644 index 0000000..a867917 Binary files /dev/null and b/resources/image/c_s/7f403066-7722-4423-853e-17bac07c5565.png differ diff --git a/resources/image/c_s/e314a26a-dc61-4776-b8da-921d5c3544ff.png b/resources/image/c_s/e314a26a-dc61-4776-b8da-921d5c3544ff.png index 3aec420..c3bf31b 100644 Binary files a/resources/image/c_s/e314a26a-dc61-4776-b8da-921d5c3544ff.png and b/resources/image/c_s/e314a26a-dc61-4776-b8da-921d5c3544ff.png differ diff --git a/resources/scripts/db/book.realm.lock b/resources/scripts/db/book.realm.lock index 9452792..6a18fc4 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 2b21388..0700489 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 cc50391..8cb7d48 100644 Binary files a/resources/scripts/db/software.realm.lock and b/resources/scripts/db/software.realm.lock differ diff --git a/src/define/Tools/image.ts b/src/define/Tools/image.ts index 5b9b57c..237a70d 100644 --- a/src/define/Tools/image.ts +++ b/src/define/Tools/image.ts @@ -171,13 +171,13 @@ export function CompressImageToSize(base64: string, maxSizeInBytes: number): Pro * 将选中的区域涂白,其他区域涂黑(这个颜色可以变) * @param inputPath 输入的文件路径 * @param outputPath 输出的文件路径 - * @param region 范围对象,包含x, y, width, height属性 - * @param markColor 标记颜色,默认为黑色 { r: 255, g: 255, b: 255 } - * @param backColor 背景颜色,默认为白色 { r: 0, g: 0, b: 0 } + * @param regions 范围对象,包含x, y, width, height属性 + * @param markColor 标记颜色,默认为白色 { r: 255, g: 255, b: 255 } + * @param backColor 背景颜色,默认为黑色 { r: 0, g: 0, b: 0 } */ export async function ProcessImage(inputPath: string, outputPath: string, - region: { width: any; height: any; x: any; y: any }, + regions: { width: any; height: any; x: any; y: any, imageWidth?: any, imageHeight?: any }[], markColor: { r: number; g: number; b: number } = { r: 255, g: 255, b: 255 }, backColor: { r: number; g: number; b: number } = { r: 0, g: 0, b: 0 }, ): Promise { @@ -188,7 +188,7 @@ export async function ProcessImage(inputPath: string, // 获取图片的元数据 const { width, height } = await image.metadata(); - // 创建一个新的空白图片,背景为白色 + // 创建一个新的黑色图片,背景为白色 const whiteBackground = await sharp({ create: { width, @@ -198,27 +198,42 @@ export async function ProcessImage(inputPath: string, } }).png().toBuffer(); - // 创建一个黑色的矩形 - const blackRegion = await sharp({ - create: { - width: region.width, - height: region.height, - channels: 3, // RGB channels - background: markColor // Black color + // 创建多个白色的矩形,并进行合成 + const composites = await Promise.all(regions.map(async (region) => { + + let rateW = undefined; + let rateY = undefined; + let rate = undefined; + if (region.imageWidth != null && region.imageHeight != null) { + rateY = height / region.imageHeight; + rateW = width / region.imageWidth; + rate = rateY; + } + if (rate == null) { + rate = 1; } - }).png().toBuffer(); - // 在白色背景上叠加黑色矩形 - await sharp(whiteBackground) - .composite([ - { - input: blackRegion, - left: region.x, - top: Math.floor(region.y) + const regionBuffer = await sharp({ + create: { + width: Math.ceil(region.width * rate), + height: Math.ceil(region.height * rate), + channels: 3, // RGB channels + background: markColor // 标记颜色 } - ]) - .toFile(outputPath); + }).png().toBuffer(); + return { + input: regionBuffer, + left: Math.ceil(region.x * rate), + top: Math.ceil(region.y * rate), + }; + + })); + + // 在背景上叠加所有的矩形区域 + await sharp(whiteBackground) + .composite(composites) + .toFile(outputPath); } catch (err) { throw err; diff --git a/src/define/api/apiUrlDefine.js b/src/define/api/apiUrlDefine.js index 5074582..f83d6bb 100644 --- a/src/define/api/apiUrlDefine.js +++ b/src/define/api/apiUrlDefine.js @@ -2,17 +2,17 @@ let apiUrl = [ { label: 'LAI API', value: 'b44c6f24-59e4-4a71-b2c7-3df0c4e35e65', - gpt_url: 'https://laitool.net/v1/chat/completions', + gpt_url: 'https://api.laitool.cc/v1/chat/completions', mj_url: { - imagine: 'https://laitool.net/mj/submit/imagine', - describe: 'https://laitool.net/mj/submit/describe', - update_file: 'https://laitool.net/mj/submit/upload-discord-images', - once_get_task: 'https://laitool.net/mj/task/${id}/fetch' + imagine: 'https://api.laitool.cc/mj/submit/imagine', + describe: 'https://api.laitool.cc/mj/submit/describe', + update_file: 'https://api.laitool.cc/mj/submit/upload-discord-images', + once_get_task: 'https://api.laitool.cc/mj/task/${id}/fetch' }, d3_url: { - image: 'https://laitool.net/v1/images/generations' + image: 'https://api.laitool.cc/v1/images/generations' }, - buy_url: 'https://laitool.net/register?aff=Zmdu' + buy_url: 'https://api.laitool.cc/register?aff=Zmdu' }, { label: 'openai-hk', diff --git a/src/define/db/model/Book/book.ts b/src/define/db/model/Book/book.ts index 2190710..50a29cd 100644 --- a/src/define/db/model/Book/book.ts +++ b/src/define/db/model/Book/book.ts @@ -24,7 +24,8 @@ export class BookModel extends Realm.Object { videoConfig: string | null // 合成视频设置 prefixPrompt: string | null // 前缀 suffixPrompt: string | null // 后缀 - subtitlePosition: string | null + subtitlePosition: string | null // 字幕位置 + watermarkPosition: string | null // 水印位置,一个json数组字符串 static schema: Realm.ObjectSchema = { name: 'Book', @@ -37,7 +38,7 @@ export class BookModel extends Realm.Object { oldVideoPath: 'string?', srtPath: 'string?', audioPath: 'string?', - draftSrtStyle : 'string?', + draftSrtStyle: 'string?', backgroundMusic: 'string?', friendlyReminder: 'string?', imageFolder: 'string?', @@ -50,7 +51,8 @@ export class BookModel extends Realm.Object { videoConfig: "string?", prefixPrompt: "string?", suffixPrompt: "string?", - subtitlePosition: 'string?' + subtitlePosition: 'string?', + watermarkPosition: "string?" }, // 主键为_id primaryKey: 'id' diff --git a/src/define/db/service/Book/bookBasic.ts b/src/define/db/service/Book/bookBasic.ts index 1e49b15..39adf13 100644 --- a/src/define/db/service/Book/bookBasic.ts +++ b/src/define/db/service/Book/bookBasic.ts @@ -155,6 +155,13 @@ const migration = (oldRealm: Realm, newRealm: Realm) => { newBookTask[i].subValue = '[]' } } + if (oldRealm.schemaVersion < 22) { + const oldBookTask = oldRealm.objects('Book') + const newBookTask = newRealm.objects('Book') + for (let i = 0; i < oldBookTask.length; i++) { + newBookTask[i].watermarkPosition = '[]' + } + } } export class BaseRealmService extends BaseService { @@ -196,7 +203,7 @@ export class BaseRealmService extends BaseService { BookTaskDetailModel ], path: this.dbpath, - schemaVersion: 21, + schemaVersion: 22, migration: migration } this.realm = await Realm.open(config) diff --git a/src/define/db/service/Book/bookService.ts b/src/define/db/service/Book/bookService.ts index f00db1b..8a1c665 100644 --- a/src/define/db/service/Book/bookService.ts +++ b/src/define/db/service/Book/bookService.ts @@ -277,7 +277,7 @@ export class BookService extends BaseRealmService { * @param bookId 小说的ID * @param bookData 要修改的小说数据 */ - async UpdateBookData(bookId: string, bookData) { + async UpdateBookData(bookId: string, bookData: Book.SelectBook) { try { if (bookId == null) { throw new Error('修改小说数据失败,缺少小说ID') diff --git a/src/define/define_string.ts b/src/define/define_string.ts index b1c8a63..cf52470 100644 --- a/src/define/define_string.ts +++ b/src/define/define_string.ts @@ -202,7 +202,8 @@ export const DEFINE_STRING = { BASE64_TO_FILE: 'BASE64_TO_FILE', PROCESS_IMAGE: 'PROCESS_IMAGE', BATCH_PROCESS_IMAGE: 'BATCH_PROCESS_IMAGE', - BATCH_PROCESS_IMAGE_RESULT: 'BATCH_PROCESS_IMAGE_RESULT' + BATCH_PROCESS_IMAGE_RESULT: 'BATCH_PROCESS_IMAGE_RESULT', + PROCESS_IMAGE_WATERMASK_CHECK: "PROCESS_IMAGE_WATERMASK_CHECK" }, BOOK: { MAIN_DATA_RETURN: 'MAIN_DATA_RETURN', // 监听任务返回 @@ -291,6 +292,7 @@ export const DEFINE_STRING = { }, DB: { UPDATE_BOOK_TASK_DATA: "UPDATE_BOOK_TASK_DATA", - UPDATE_BOOK_TASK_DETAIL_DATA: "UPDATE_BOOK_TASK_DETAIL_DATA" + UPDATE_BOOK_TASK_DETAIL_DATA: "UPDATE_BOOK_TASK_DETAIL_DATA", + UPDATE_BOOK_DATA: "UPDATE_BOOK_DATA" } } diff --git a/src/define/tagDefine.js b/src/define/tagDefine.js index 898d782..94be5de 100644 --- a/src/define/tagDefine.js +++ b/src/define/tagDefine.js @@ -64,21 +64,30 @@ export class TagDefine { if (res.hasOwnProperty('character_tags')) { res.character_tags.forEach((item) => { if (item.show_image && item.show_image != '') { - item.show_image = path.join(define.image_path, item.show_image) + item.show_image = path.join( + define.image_path, + item.show_image + '?t=' + new Date().getTime() + ) } }) } if (res.hasOwnProperty('scene_tags')) { res.scene_tags.forEach((item) => { if (item.show_image && item.show_image != '') { - item.show_image = path.join(define.image_path, item.show_image) + item.show_image = path.join( + define.image_path, + item.show_image + '?t=' + new Date().getTime() + ) } }) } if (res.hasOwnProperty('style_tags')) { res.style_tags.forEach((item) => { if (item.show_image && item.show_image != '') { - item.show_image = path.join(define.image_path, item.show_image) + item.show_image = path.join( + define.image_path, + item.show_image + '?t=' + new Date().getTime() + ) } }) } diff --git a/src/main/IPCEvent/bookIpc.js b/src/main/IPCEvent/bookIpc.js index 1489854..30a3fda 100644 --- a/src/main/IPCEvent/bookIpc.js +++ b/src/main/IPCEvent/bookIpc.js @@ -9,6 +9,7 @@ import { BookImage } from '../Service/Book/bookImage' import { ImageStyle } from '../Service/Book/imageStyle' import { BookTask } from '../Service/Book/bookTask' import { BookVideo } from '../Service/Book/bookVideo' +import { Watermark } from '../Service/watermark' let reverseBook = new ReverseBook() let basicReverse = new BasicReverse() let subtitle = new Subtitle() @@ -18,6 +19,7 @@ let bookImage = new BookImage() let imageStyle = new ImageStyle() let bookTask = new BookTask() let bookVideo = new BookVideo() +let watermark = new Watermark() export function BookIpc() { // 获取样式图片的子列表 @@ -98,13 +100,13 @@ export function BookIpc() { // 开始执行分镜任务 ipcMain.handle( DEFINE_STRING.BOOK.GET_COPYWRITING, - async (event, bookId) => await reverseBook.GetCopywriting(bookId) + async (event, bookId, bookTaskId) => await reverseBook.GetCopywriting(bookId, bookTaskId) ) // 执行去除水印 ipcMain.handle( DEFINE_STRING.BOOK.REMOVE_WATERMARK, - async (event, bookId) => await reverseBook.RemoveWatermark(bookId) + async (event, id,operateBookType) => await watermark.RemoveWatermark(id,operateBookType) ) // 添加反推任务到任务列表 diff --git a/src/main/IPCEvent/dbIpc.ts b/src/main/IPCEvent/dbIpc.ts index 269572e..34af968 100644 --- a/src/main/IPCEvent/dbIpc.ts +++ b/src/main/IPCEvent/dbIpc.ts @@ -4,11 +4,13 @@ import { errorMessage, successMessage } from '../Public/generalTools' import { Book } from '../../model/book' import { BookTaskService } from '../../define/db/service/Book/bookTaskService' import { BookTaskDetailService } from '../../define/db/service/Book/bookTaskDetailService' +import { BookService } from '../../define/db/service/Book/bookService' async function DBIpc() { let bookTaskService = await BookTaskService.getInstance() let bookTaskDetailService = await BookTaskDetailService.getInstance() + let bookService = await BookService.getInstance() //#region 小说相关的修改 // 修改小说任务的数据 @@ -31,6 +33,18 @@ async function DBIpc() { } }) + /** + * 修改小说的指定属性的数据 + */ + ipcMain.handle(DEFINE_STRING.DB.UPDATE_BOOK_DATA, async (event, bookId: string, data: Book.SelectBook) => { + try { + bookService.UpdateBookData(bookId, data) + return successMessage(null, "修改小说数据成功", "DBIpc_UpdateBookData") + } catch (error) { + return errorMessage("修改小说数据失败", "DBIpc_UpdateBookData") + } + }) + //#endregion } diff --git a/src/main/IPCEvent/imageIpc.js b/src/main/IPCEvent/imageIpc.js index 5f96609..1b78fe7 100644 --- a/src/main/IPCEvent/imageIpc.js +++ b/src/main/IPCEvent/imageIpc.js @@ -3,7 +3,9 @@ import { DEFINE_STRING } from '../../define/define_string' import { Image } from '../Public/Image' import { LOGGER_DEFINE } from '../../define/logger_define' import { errorMessage } from '../Public/generalTools' +import { Watermark } from '../Service/watermark' let image = new Image(global) +let watermark = new Watermark() function ImageIpc() { // 一拆四 @@ -32,5 +34,11 @@ function ImageIpc() { DEFINE_STRING.IMG.BATCH_PROCESS_IMAGE, async (event, value) => await image.BatchProcessImage(value) ) + + ipcMain.handle( + DEFINE_STRING.IMG.PROCESS_IMAGE_WATERMASK_CHECK, + async (event, imageBase64, maskPosition, bookId) => + await watermark.ProcessImageCheck(imageBase64, maskPosition, bookId) + ) } export { ImageIpc } diff --git a/src/main/Public/SD.js b/src/main/Public/SD.js index aa6754d..3e2ab6d 100644 --- a/src/main/Public/SD.js +++ b/src/main/Public/SD.js @@ -1,374 +1,381 @@ -import axios from "axios"; -import path from "path"; -import { DEFINE_STRING } from "../../define/define_string"; -import { define } from "../../define/define"; -import { ImageStyleDefine } from "../../define/iamgeStyleDefine"; -import { cloneDeep } from 'lodash'; -let fspromises = require("fs").promises; -const sharp = require('sharp'); -import { SdSettingDefine } from "../../define/setting/sdSettingDefine"; -import { PublicMethod } from "./publicMethod"; -import { Tools } from "../tools"; -import { errorMessage, successMessage } from "../Public/generalTools"; -import { SdApi } from "../../api/sdApi"; -const { v4: uuidv4 } = require('uuid'); +import axios from 'axios' +import path from 'path' +import { DEFINE_STRING } from '../../define/define_string' +import { define } from '../../define/define' +import { ImageStyleDefine } from '../../define/iamgeStyleDefine' +import { cloneDeep } from 'lodash' +let fspromises = require('fs').promises +const sharp = require('sharp') +import { SdSettingDefine } from '../../define/setting/sdSettingDefine' +import { PublicMethod } from './publicMethod' +import { Tools } from '../tools' +import { errorMessage, successMessage } from '../Public/generalTools' +import { SdApi } from '../../api/sdApi' +const { v4: uuidv4 } = require('uuid') export class SD { - constructor(global) { - this.global = global; - this.pm = new PublicMethod(global); - this.tools = new Tools(); - this.sdApi = new SdApi(); - } + constructor(global) { + this.global = global + this.pm = new PublicMethod(global) + this.tools = new Tools() + this.sdApi = new SdApi() + } - /** - * 获取当前SD服务器所有的lora信息 - */ - async GetAllLoras(baseURL = null) { - try { - let data = await this.sdApi.getAllLoras(baseURL); - return successMessage(data); - } catch (error) { - return errorMessage(error.toString()); + /** + * 获取当前SD服务器所有的lora信息 + */ + async GetAllLoras(baseURL = null) { + try { + let data = await this.sdApi.getAllLoras(baseURL) + return successMessage(data) + } catch (error) { + return errorMessage(error.toString()) + } + } + + /** + * 获取所有的checkpoint模型 + * @param {*} baseURL + * @returns + */ + async GetAllSDModel(baseURL = null) { + try { + let data = await this.sdApi.getAllSDModel(baseURL) + return successMessage(data) + } catch (error) { + return errorMessage(error.toString()) + } + } + + /** + * 获取所有的采样器 + * @param {*} baseURL + * @returns + */ + async GetAllSamplers(baseURL = null) { + try { + let data = await this.sdApi.getAllSamplers(baseURL) + return successMessage(data) + } catch (error) { + return errorMessage(error.toString()) + } + } + + /** + * 加载所有的SD数据 + * @param {*} baseURL + * @returns + */ + async LoadSDServiceData(baseURL = null) { + try { + // 加载大模型 + let sd_model = await this.GetAllSDModel(baseURL) + // 往sd_model中添加一个默认的选项 + sd_model.data.data.unshift({ + title: '无', + name: '无', + description: '无' + }) + // 加载Lora + let lora = await this.GetAllLoras(baseURL) + lora.data.data.unshift({ + Key: '无', + name: '无', + description: '无' + }) + // 加载采样器 + let sampler = await this.GetAllSamplers(baseURL) + sampler.data.data.unshift({ + name: '无', + description: '无' + }) + + if (!(sd_model.code & lora.code & sampler.code)) { + throw new Error('获取SD数据错误,请检查SD WEBUI链接!') + } + + for (let i = 0; i < lora.data.data.length; i++) { + delete lora.data.data[i].metadata + } + let data = { + sd_model: sd_model.data.data, + lora: lora.data.data, + sampler: sampler.data.data + } + // 处理当前获取的数据,保存到配置文件中 + await SdSettingDefine.SavePropertyValue('sd_model', data.sd_model) + await SdSettingDefine.SavePropertyValue('lora', data.lora) + await SdSettingDefine.SavePropertyValue('sampler', data.sampler) + + return successMessage(data) + } catch (error) { + return errorMessage('加载数据失败,错误信息如下:' + error.toString()) + } + } + + /** + * 获取图片风格菜单 + * @returns 返回图片风格菜单 + * + * */ + async GetImageStyleMenu() { + try { + let style = ImageStyleDefine.getImageStyleMenu() + return { + code: 1, + data: style + } + } catch (error) { + return { + code: 0, + message: '不可能出现错误' + } + } + } + + /** + * 获取指定的ID的风格信息,传入的是一个数组 + * @param {*} value id集合 + */ + async GetImageStyleInfomation(value) { + try { + if (value) { + value = JSON.parse(value) + } else { + value = [] + } + value = value ? value : [] + let style = ImageStyleDefine.getAllSubStyle() + let tmp = [] + for (let i = 0; i < value.length; i++) { + const element = value[i] + for (let j = 0; j < style.length; j++) { + const item = style[j] + if (item.id == element) { + tmp.push(item) + break + } } + } + let newSubStyle = cloneDeep(tmp) + for (let i = 0; i < newSubStyle.length; i++) { + const element = newSubStyle[i] + element.image = path.join(define.image_path, 'style/' + element.image) + } + return { + code: 1, + data: newSubStyle + } + } catch (error) { + return { + code: 0, + message: error.toString() + } } + } - /** - * 获取所有的checkpoint模型 - * @param {*} baseURL - * @returns - */ - async GetAllSDModel(baseURL = null) { - try { - let data = await this.sdApi.getAllSDModel(baseURL); - return successMessage(data); - } catch (error) { - return errorMessage(error.toString()); + /** + * 获取指定ID的分类的子风格信息 + * @param {*} value ID + * @returns 返回ID对应的子风格的详细信息 + */ + async GetStyleImageSubList(value) { + try { + let subStyle = ImageStyleDefine.getImagePathById(value) + let newSubStyle = cloneDeep(subStyle) + for (let i = 0; i < newSubStyle.length; i++) { + const element = newSubStyle[i] + element.image = path.join( + define.image_path, + 'style/' + element.image + '?t=' + new Date().getTime() + ) + } + return { + code: 1, + data: newSubStyle + } + } catch (error) { + return { + code: 0, + message: error.toString() + } + } + } + + /** + * 单张生图 + * @param {*} value 0 生图的参数,1 图片的表示,用于保存 ,2 baseUrl + * @returns + */ + async txt2img(value) { + try { + value = JSON.parse(value) + let data = value[0] + let res = await this.sdApi.txt2img(data) + // 将base· 64的图片转换为图片 + // 将当前的图片保存到指定的文件夹中,然后返回文件路径,并且可以复制到指定的文件,删除exif信息 + let image_paths = [] + for (let i = 0; res.data.images && i < res.data.images.length; i++) { + const element = res.data.images[i] + let image_data = { + base64: element } + // 将保存图片添加到队列中 + let image_name = `sd_${Date.now()}_${uuidv4()}.png` + let image_path = path.join(define.temp_sd_image, image_name) + image_path = await this.tools.saveBase64ToImage(element, image_path) + image_data['image_path'] = image_path + image_paths.push(image_data) + } + return successMessage(image_paths) + } catch (error) { + return errorMessage('生图失败,错误信息如下:' + error.toString()) } + } - /** - * 获取所有的采样器 - * @param {*} baseURL - * @returns - */ - async GetAllSamplers(baseURL = null) { - try { - let data = await this.sdApi.getAllSamplers(baseURL); - return successMessage(data); - } catch (error) { - return errorMessage(error.toString()); + /** + * 生成一次图片的方法。可以区分模式 + * @param {图片名称 } image + * @param {任务队列信息} task_list 301198499 + */ + async OneImageGeneration(image, task_list, seed = -1) { + let taskPath = path.join(this.global.config.project_path, 'scripts/task_list.json') + try { + let imageJson = JSON.parse(await fspromises.readFile(image + '.json', 'utf-8')) + let sd_setting = JSON.parse(await fspromises.readFile(define.sd_setting, 'utf-8')) + let model = imageJson.model + let image_json = JSON.parse(await fspromises.readFile(image + '.json', 'utf-8')) + let image_path = '' + let target_image_path = '' + if (image_json.name) { + image_path = path.join( + this.global.config.project_path, + `tmp/${task_list.out_folder}/tmp_${image_json.name}` + ) + target_image_path = path.join( + this.global.config.project_path, + `tmp/${task_list.out_folder}/${image_json.name}` + ) + } else { + image_path = + image.replaceAll('input_crop', task_list.out_folder).split('.png')[0] + '_tmp.png' + target_image_path = image.replaceAll('input_crop', task_list.out_folder) + } + let image_styles = await ImageStyleDefine.getImageStyleStringByIds( + task_list.image_style_list ? task_list.image_style_list : [] + ) + let prompt = sd_setting.webui.prompt + image_styles + // 拼接提示词 + if (task_list.image_style != null) { + prompt += `((${task_list.image_style})), ` + } + if (task_list.lora != null) { + prompt += `${task_list.lora}, ` + } + prompt += imageJson.webui_config.prompt + // 判断当前是不是有开修脸修手 + let ADetailer = { + args: sd_setting.adetailer + } + if (model == 'img2img') { + let web_api = this.global.config.webui_api_url + 'sdapi/v1/img2img' + let sd_config = imageJson['webui_config'] + sd_config.prompt = prompt + sd_config.seed = seed + let im = await fspromises.readFile(image, 'binary') + sd_config.init_images = [new Buffer.from(im, 'binary').toString('base64')] + if (imageJson.adetailer) { + let ta = { + ADetailer: ADetailer + } + sd_config.alwayson_scripts = ta } - } - - /** - * 加载所有的SD数据 - * @param {*} baseURL - * @returns - */ - async LoadSDServiceData(baseURL = null) { - try { - // 加载大模型 - let sd_model = await this.GetAllSDModel(baseURL); - // 往sd_model中添加一个默认的选项 - sd_model.data.data.unshift({ - title: "无", - name: "无", - description: "无", - }) - // 加载Lora - let lora = await this.GetAllLoras(baseURL); - lora.data.data.unshift({ - Key: "无", - name: "无", - description: "无", - }) - // 加载采样器 - let sampler = await this.GetAllSamplers(baseURL); - sampler.data.data.unshift({ - name: "无", - description: "无", - }) - - if (!(sd_model.code & lora.code & sampler.code)) { - throw new Error("获取SD数据错误,请检查SD WEBUI链接!"); - } - - for (let i = 0; i < lora.data.data.length; i++) { - delete lora.data.data[i].metadata; - } - let data = { - sd_model: sd_model.data.data, - lora: lora.data.data, - sampler: sampler.data.data - } - // 处理当前获取的数据,保存到配置文件中 - await SdSettingDefine.SavePropertyValue("sd_model", data.sd_model); - await SdSettingDefine.SavePropertyValue("lora", data.lora); - await SdSettingDefine.SavePropertyValue("sampler", data.sampler); - - return successMessage(data); - - } catch (error) { - return errorMessage("加载数据失败,错误信息如下:" + error.toString()); + sd_config.height = sd_setting.webui.height + sd_config.width = sd_setting.webui.width + const response = await axios.post(web_api, sd_config) + let info = JSON.parse(response.data.info) + if (seed == -1) { + seed = info.seed } - } - - /** - * 获取图片风格菜单 - * @returns 返回图片风格菜单 - * - * */ - async GetImageStyleMenu() { - try { - let style = ImageStyleDefine.getImageStyleMenu(); - return { - code: 1, - data: style - } - } catch (error) { - return { - code: 0, - message: "不可能出现错误" - } + // 目前是单图出图 + let images = response.data.images + let imageData = Buffer.from(images[0].split(',', 1)[0], 'base64') + await sharp(imageData) + .toFile(image_path) + .then(async () => { + // console.log("图生图成功" + image_path); + await this.tools.deletePngAndDeleteExifData(image_path, target_image_path) + }) + .catch((err) => { + throw new Error(err) + }) + return seed + } else if (model == 'txt2img') { + let body = { + prompt: prompt, + negative_prompt: imageJson.webui_config.negative_prompt, + seed: seed, + sampler_name: imageJson.webui_config.sampler_name, + // 提示词相关性 + cfg_scale: imageJson.webui_config.cfg_scale, + width: sd_setting.webui.width, + height: sd_setting.webui.height, + batch_size: 1, + n_iter: 1, + steps: imageJson.webui_config.steps, + save_images: false } - } + let web_api = this.global.config.webui_api_url + 'sdapi/v1/txt2img' - /** - * 获取指定的ID的风格信息,传入的是一个数组 - * @param {*} value id集合 - */ - async GetImageStyleInfomation(value) { - try { - if (value) { - value = JSON.parse(value); - } else { - value = []; - } - value = value ? value : []; - let style = ImageStyleDefine.getAllSubStyle(); - let tmp = []; - for (let i = 0; i < value.length; i++) { - const element = value[i]; - for (let j = 0; j < style.length; j++) { - const item = style[j]; - if (item.id == element) { - tmp.push(item); - break; - } - } - } - let newSubStyle = cloneDeep(tmp); - for (let i = 0; i < newSubStyle.length; i++) { - const element = newSubStyle[i]; - element.image = path.join(define.image_path, "style/" + element.image); - } - return { - code: 1, - data: newSubStyle - } - - } catch (error) { - return { - code: 0, - message: error.toString() - } + if (imageJson.adetailer) { + let ta = { + ADetailer: ADetailer + } + body.alwayson_scripts = ta } - } - - /** - * 获取指定ID的分类的子风格信息 - * @param {*} value ID - * @returns 返回ID对应的子风格的详细信息 - */ - async GetStyleImageSubList(value) { - try { - let subStyle = ImageStyleDefine.getImagePathById(value); - let newSubStyle = cloneDeep(subStyle); - for (let i = 0; i < newSubStyle.length; i++) { - const element = newSubStyle[i]; - element.image = path.join(define.image_path, "style/" + element.image); - } - return { - code: 1, - data: newSubStyle - } - - } catch (error) { - return { - code: 0, - message: error.toString() - } + const response = await axios.post(web_api, body) + let info = JSON.parse(response.data.info) + if (seed == -1) { + seed = info.seed } + // 目前是单图出图 + let images = response.data.images + let imageData = Buffer.from(images[0].split(',', 1)[0], 'base64') + await sharp(imageData) + .toFile(image_path) + .then(async () => { + // console.log("文生图成功" + image_path); + await this.tools.deletePngAndDeleteExifData(image_path, target_image_path) + }) + .catch((err) => { + // console.log(err) + throw new Error(err) + }) + return seed + } else { + throw new Error('SD 模式错误') + } + } catch (error) { + // 当前队列执行失败移除整个批次的任务 + this.global.requestQuene.removeTask(task_list.out_folder, null) + this.global.fileQueue.enqueue(async () => { + // 记录失败状态 + let task_list_json = JSON.parse(await fspromises.readFile(taskPath, 'utf-8')) + // 修改指定的列表的数据 + task_list_json.task_list.map((a) => { + if (a.id == task_list.id) { + a.status = 'error' + a.errorMessage = error.toString() + } + }) + // 写入 + await fspromises.writeFile(taskPath, JSON.stringify(task_list_json)) + this.global.newWindow[0].win.webContents.send(DEFINE_STRING.IMAGE_TASK_STATUS_REFRESH, { + out_folder: task_list.out_folder, + status: 'error' + }) + }) + throw error } - - /** - * 单张生图 - * @param {*} value 0 生图的参数,1 图片的表示,用于保存 ,2 baseUrl - * @returns - */ - async txt2img(value) { - try { - value = JSON.parse(value); - let data = value[0]; - let res = await this.sdApi.txt2img(data); - // 将base· 64的图片转换为图片 - // 将当前的图片保存到指定的文件夹中,然后返回文件路径,并且可以复制到指定的文件,删除exif信息 - let image_paths = []; - for (let i = 0; res.data.images && i < res.data.images.length; i++) { - const element = res.data.images[i]; - let image_data = { - base64: element - } - // 将保存图片添加到队列中 - let image_name = `sd_${Date.now()}_${uuidv4()}.png`; - let image_path = path.join(define.temp_sd_image, image_name); - image_path = await this.tools.saveBase64ToImage(element, image_path); - image_data["image_path"] = image_path; - image_paths.push(image_data); - } - return successMessage(image_paths); - } catch (error) { - return errorMessage("生图失败,错误信息如下:" + error.toString()); - } - } - - /** - * 生成一次图片的方法。可以区分模式 - * @param {图片名称 } image - * @param {任务队列信息} task_list 301198499 - */ - async OneImageGeneration(image, task_list, seed = -1) { - let taskPath = path.join(this.global.config.project_path, "scripts/task_list.json") - try { - let imageJson = JSON.parse(await fspromises.readFile(image + '.json', 'utf-8')); - let sd_setting = JSON.parse(await fspromises.readFile(define.sd_setting, 'utf-8')); - let model = imageJson.model; - let image_json = JSON.parse(await fspromises.readFile(image + '.json', 'utf-8')); - let image_path = ""; - let target_image_path = ""; - if (image_json.name) { - image_path = path.join(this.global.config.project_path, `tmp/${task_list.out_folder}/tmp_${image_json.name}`) - target_image_path = path.join(this.global.config.project_path, `tmp/${task_list.out_folder}/${image_json.name}`) - } else { - image_path = image.replaceAll("input_crop", task_list.out_folder).split(".png")[0] + "_tmp.png"; - target_image_path = image.replaceAll("input_crop", task_list.out_folder); - } - let image_styles = await ImageStyleDefine.getImageStyleStringByIds(task_list.image_style_list ? task_list.image_style_list : []); - let prompt = sd_setting.webui.prompt + image_styles; - // 拼接提示词 - if (task_list.image_style != null) { - prompt += `((${task_list.image_style})), `; - } - if (task_list.lora != null) { - prompt += `${task_list.lora}, `; - } - prompt += imageJson.webui_config.prompt; - // 判断当前是不是有开修脸修手 - let ADetailer = { - args: sd_setting.adetailer - }; - if (model == "img2img") { - let web_api = this.global.config.webui_api_url + 'sdapi/v1/img2img' - let sd_config = imageJson["webui_config"]; - sd_config.prompt = prompt; - sd_config.seed = seed; - let im = await fspromises.readFile(image, 'binary'); - sd_config.init_images = [new Buffer.from(im, 'binary').toString('base64')]; - if (imageJson.adetailer) { - let ta = { - ADetailer: ADetailer - } - sd_config.alwayson_scripts = ta; - } - sd_config.height = sd_setting.webui.height; - sd_config.width = sd_setting.webui.width; - const response = await axios.post(web_api, sd_config); - let info = JSON.parse(response.data.info); - if (seed == -1) { - seed = info.seed; - } - // 目前是单图出图 - let images = response.data.images; - let imageData = Buffer.from(images[0].split(",", 1)[0], 'base64'); - await sharp(imageData) - .toFile(image_path) - .then(async () => { - // console.log("图生图成功" + image_path); - await this.tools.deletePngAndDeleteExifData(image_path, target_image_path); - }) - .catch(err => { - throw new Error(err); - }); - return seed; - - } else if (model == "txt2img") { - let body = { - "prompt": prompt, - "negative_prompt": imageJson.webui_config.negative_prompt, - "seed": seed, - "sampler_name": imageJson.webui_config.sampler_name, - // 提示词相关性 - "cfg_scale": imageJson.webui_config.cfg_scale, - "width": sd_setting.webui.width, - "height": sd_setting.webui.height, - "batch_size": 1, - "n_iter": 1, - "steps": imageJson.webui_config.steps, - "save_images": false, - } - let web_api = this.global.config.webui_api_url + 'sdapi/v1/txt2img'; - - if (imageJson.adetailer) { - let ta = { - ADetailer: ADetailer - } - body.alwayson_scripts = ta; - } - const response = await axios.post(web_api, body); - let info = JSON.parse(response.data.info); - if (seed == -1) { - seed = info.seed; - } - // 目前是单图出图 - let images = response.data.images; - let imageData = Buffer.from(images[0].split(",", 1)[0], 'base64'); - await sharp(imageData) - .toFile(image_path) - .then(async () => { - // console.log("文生图成功" + image_path); - await this.tools.deletePngAndDeleteExifData(image_path, target_image_path); - }) - .catch(err => { - // console.log(err) - throw new Error(err); - }); - return seed; - } else { - throw new Error("SD 模式错误"); - } - - } catch (error) { - // 当前队列执行失败移除整个批次的任务 - this.global.requestQuene.removeTask(task_list.out_folder, null) - this.global.fileQueue.enqueue(async () => { - // 记录失败状态 - let task_list_json = JSON.parse(await fspromises.readFile(taskPath, 'utf-8')); - // 修改指定的列表的数据 - task_list_json.task_list.map(a => { - if (a.id == task_list.id) { - a.status = "error"; - a.errorMessage = error.toString(); - } - }) - // 写入 - await fspromises.writeFile(taskPath, JSON.stringify(task_list_json)); - this.global.newWindow[0].win.webContents.send(DEFINE_STRING.IMAGE_TASK_STATUS_REFRESH, { - out_folder: task_list.out_folder, - status: "error" - }); - }) - throw error; - } - } -} \ No newline at end of file + } +} diff --git a/src/main/Service/Book/ReverseBook.ts b/src/main/Service/Book/ReverseBook.ts index a3c26d1..e9e411f 100644 --- a/src/main/Service/Book/ReverseBook.ts +++ b/src/main/Service/Book/ReverseBook.ts @@ -229,16 +229,27 @@ export class ReverseBook extends BookBasic { } } + /** + * 通过小说ID和小说批次任务ID获取小说和小说批次任务数据 + * @param bookId 小说ID + * @param bookTaskName 小说批次的名字,或者是小说批次任务的ID + * @returns + */ async GetBookAndTask(bookId: string, bookTaskName: string) { let book = this.bookService.GetBookDataById(bookId) if (book == null) { throw new Error("查找小说数据失败"); } // 获取小说对应的批次任务数据,默认初始化为第一个 - let bookTaskRes = await this.bookTaskService.GetBookTaskData({ - bookId: bookId, - name: bookTaskName - }) + let condition = { + bookId: bookId + } as Book.QueryBookBackTaskCondition + if (bookTaskName == "output_00001") { + condition["name"] = bookTaskName + } 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) @@ -322,12 +333,12 @@ export class ReverseBook extends BookBasic { } /** - * 开始执行分镜任务 + * 提起文案 */ - async GetCopywriting(bookId: string): Promise { + async GetCopywriting(bookId: string, bookTaskId: string = null): Promise { try { await this.InitService() - let { book, bookTask } = await this.GetBookAndTask(bookId, 'output_00001') + let { book, bookTask } = await this.GetBookAndTask(bookId, bookTaskId ? bookTaskId : 'output_00001') if (isEmpty(book.subtitlePosition)) { throw new Error("请先设置小说的字幕位置") } @@ -390,95 +401,6 @@ export class ReverseBook extends BookBasic { return errorMessage("获取分镜数据失败,失败信息如下:" + error.message, 'ReverseBook_GetCopywriting') } } - - - /** - * 执行去除水印操作 - * @param bookId 小说ID - */ - async RemoveWatermark(bookId) { - try { - await this.InitService() - let { book, bookTask } = await this.GetBookAndTask(bookId, 'output_00001') - let subtitlePosition = book.subtitlePosition; - if (isEmpty(subtitlePosition)) { - throw new Error("当前没有文案位置的内容,请先进行 ‘开始提取文案 -> 提取文案设置 -> 框选大概位置 -> 保存位置’ 操作保存文案的位置") - } - if (!ValidateJson(subtitlePosition)) { - throw new Error("文案位置格式有误,请先进行 ‘开始提取文案 -> 提取文案设置 -> 框选大概位置 -> 保存位置’ 操作保存文案的位置") - } - let subtitlePositionParse = JSON.parse(subtitlePosition) - if (subtitlePositionParse.length <= 0) { - throw new Error("文案位置格式有误,请先进行 ‘开始提取文案 -> 提取文案设置 -> 框选大概位置 -> 保存位置’ 操作保存文案的位置") - } - let inputImageFolder = path.resolve(define.project_path, `${book.id}/tmp/input`) - if (!(await CheckFileOrDirExist(inputImageFolder))) { - throw new Error("输出文件夹不存在,请先进行抽帧等操作") - } - let iamgePaths = await GetFilesWithExtensions(inputImageFolder, ['.png']) - if (iamgePaths.length <= 0) { - throw new Error("没有检查到抽帧图片,请先进行抽帧等操作") - } - - // 处理位置生成蒙板 - let inputImage = iamgePaths[0] - let outImagePath = path.resolve(define.project_path, `${book.id}/data/mask/mask_temp_${new Date().getTime()}.png`) - await CheckFolderExistsOrCreate(path.dirname(outImagePath)); - await ProcessImage(inputImage, outImagePath, { - x: subtitlePositionParse[0].startX, - y: subtitlePositionParse[0].startY, - width: subtitlePositionParse[0].width, - height: subtitlePositionParse[0].height - }) - - if (!(await CheckFileOrDirExist(outImagePath))) { - throw new Error("生成蒙板失败") - } - - // 开始去除水印 - let bookTaskDetails = this.bookTaskDetailService.GetBookTaskData({ - bookId: bookId, - bookTaskId: bookTask.id - }).data as Book.SelectBookTaskDetail[] - - for (let i = 0; i < bookTaskDetails.length; i++) { - const element = bookTaskDetails[i]; - let bookInputImage = element.oldImage; - let name = path.basename(bookInputImage) - let bak = path.resolve(define.project_path, `${book.id}/tmp/input/bak/${name}`); - // 做个备份吧 - await CopyFileOrFolder(bookInputImage, bak) - await fs.promises.unlink(bookInputImage) // 删除原来的图片 - // 开始调用去除水印的方法 - let imageBase64 = await GetImageBase64(bak) - let maskBase64 = await GetImageBase64(outImagePath) - - let res = await this.watermark.ProcessImage({ - imageBase64: imageBase64, - maskBase64: maskBase64, - type: 'file', - inputFilePath: bak, - maskPath: outImagePath, - outFilePath: bookInputImage - }) - - // 去水印执行完毕 - this.sendReturnMessage({ - code: 1, - id: element.id, - type: ResponseMessageType.REMOVE_WATERMARK, - data: res - }, DEFINE_STRING.BOOK.REMOVE_WATERMARK_RETURN) - - this.taskScheduler.AddLogToDB(bookId, book.type, `${element.name} 去除水印完成`, element.bookTaskId, LoggerStatus.SUCCESS) - } - - // 全部完毕 - return successMessage(null, "全部图片去除水印完成", "ReverseBook_RemoveWatermark") - } catch (error) { - return errorMessage("去除水印失败,错误信息如下:" + error.message, "ReverseBook_RemoveWatermark") - } - } //#endregion //#region 反推相关任务 diff --git a/src/main/Service/watermark.ts b/src/main/Service/watermark.ts index e9599c7..6c3f8f1 100644 --- a/src/main/Service/watermark.ts +++ b/src/main/Service/watermark.ts @@ -3,7 +3,7 @@ import fs from 'fs' import util from 'util' import { exec } from 'child_process' const execAsync = util.promisify(exec); -import { CheckFileOrDirExist, CheckFolderExistsOrCreate } from '../../define/Tools/file' +import { CheckFileOrDirExist, CheckFolderExistsOrCreate, CopyFileOrFolder, GetFilesWithExtensions } from '../../define/Tools/file' import { errorMessage, successMessage } from '../Public/generalTools' import { SoftwareService } from '../../define/db/service/SoftWare/softwareService' import { isEmpty } from 'lodash' @@ -12,15 +12,28 @@ import { ValidateJson } from '../../define/Tools/validate' import { define } from '../../define/define' import { LOGGER_DEFINE } from '../../define/logger_define' import axios from 'axios' -import { Base64ToFile } from '../../define/Tools/image' +import { Base64ToFile, GetImageBase64 } from '../../define/Tools/image' import { TaskScheduler } from './taskScheduler'; -import { LoggerStatus, OtherData } from '../../define/enum/softwareEnum'; +import { LoggerStatus, OtherData, ResponseMessageType } from '../../define/enum/softwareEnum'; import { basicApi } from '../../api/apiBasic'; +import { FfmpegOptions } from './ffmpegOptions'; +import { ProcessImage } from '../../define/Tools/image'; +import { BookService } from '../../define/db/service/Book/bookService'; +import { OperateBookType } from '../../define/enum/bookEnum'; +import { GeneralResponse } from '../../model/generalResponse'; +import { BookTaskDetailService } from '../../define/db/service/Book/bookTaskDetailService'; +import { Book } from '../../model/book'; +import { DEFINE_STRING } from '../../define/define_string'; +import { BookTaskService } from '../../define/db/service/Book/bookTaskService'; export class Watermark { softwareService: SoftwareService taskScheduler: TaskScheduler; - constructor() { } + bookService: BookService + bookTaskDetailService: BookTaskDetailService + bookTaskService: BookTaskService + constructor() { + } async InitService() { if (!this.softwareService) { @@ -29,6 +42,20 @@ export class Watermark { 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.bookTaskService) { + this.bookTaskService = await BookTaskService.getInstance() + } + } + + // 主动返回前端的消息 + sendReturnMessage(data: GeneralResponse.MessageResponse, message_name = DEFINE_STRING.BOOK.GET_COPYWRITING_RETURN) { + global.newWindow[0].win.webContents.send(message_name, data) } //#region 设置 @@ -114,7 +141,7 @@ export class Watermark { //#endregion - //#region 去除水印 + //#region 去除水印相关操作 /** * 去除水印,试用本地lama @@ -234,7 +261,7 @@ export class Watermark { * 去除图片水印,当前只支持本地,后续会支持 iopaint * @param value */ - async ProcessImage(value: ImageModel.ProcessImageParams) { + async ProcessImage(value: ImageModel.ProcessImageParams): Promise { let outDir = path.dirname(value.outFilePath) await CheckFolderExistsOrCreate(outDir); @@ -253,6 +280,182 @@ export class Watermark { throw new Error('未知的去除水印模式') } } + //#endregion + + //#region 开始去除水印 + + + /** + * 查看去除水印设置 去除图片指定位置的水印,这个是要单独做蒙板处理的 + * @param imageBase64 基础图片的base64 + * @param maskPosition 前端勾选的水印的位置 + */ + async ProcessImageCheck(imageBase64: string, maskPosition: [], bookId: string) { + try { + await this.InitService() + // 开始做处理 + console.log(imageBase64, maskPosition); + let book = this.bookService.GetBookDataById(bookId) + if (book == null) { + throw new Error('指定的小说数据不存在') + } + let inputFilePath = path.resolve(book.bookFolderPath, `data/mask/temp/${new Date().getTime()}.png`) + await CheckFolderExistsOrCreate(path.dirname(inputFilePath)) + const base64Image = imageBase64.split(';base64,').pop(); + await fs.promises.writeFile(inputFilePath, base64Image, { encoding: 'base64' }) + let outputPath = path.resolve(book.bookFolderPath, `data/mask/mask_temp_${new Date().getTime()}.png`) + let outImagePath = path.resolve(book.bookFolderPath, `data/mask/temp/out_${new Date().getTime()}.png`) + + await ProcessImage(inputFilePath, outputPath, maskPosition.map((item: any) => { return { x: item.startX, y: item.startY, width: item.width, height: item.height } })) + let markBase64 = await GetImageBase64(outputPath) + + // 开始去谁赢 + let res = await this.ProcessImage({ + imageBase64: imageBase64, + maskBase64: markBase64, // 处理蒙板的base64 + type: 'arrayBuffer', // 返回数据的类型(是直接写道输出文件地址,还是返回Buffer数组) + inputFilePath: inputFilePath, // 输入文件的地址(待处理的) + maskPath: outputPath, // 蒙板文件的地址 + outFilePath: outImagePath // 输出文件的地址 + }) + + // 将得到的buffer返回前端进行渲染 + return successMessage(res, "去除水印成功", "Image_ProcessImageCheck") + + } catch (error) { + return errorMessage('去除图片指定位置的水印失败,失败信息如下:' + error.toString(), 'Image_ProcessImageCheck') + } + + } + + /** + * 执行去除水印操作 + * @param bookId 小说ID + */ + async RemoveWatermark(id: string, operateBookType: OperateBookType): Promise { + try { + await this.InitService() + let book = undefined as Book.SelectBook + let bookTask = undefined as Book.SelectBookTask + let bookTaskDetails = undefined as Book.SelectBookTaskDetail[] + if (operateBookType == OperateBookType.BOOKTASK) { + bookTask = this.bookTaskService.GetBookTaskDataById(id); + if (bookTask == null) { + throw new Error('指定的小说任务数据不存在') + } + book = this.bookService.GetBookDataById(bookTask.bookId) + if (book == null) { + throw new Error("小说数据不存在,请检查") + } + bookTaskDetails = this.bookTaskDetailService.GetBookTaskData({ + bookTaskId: bookTask.id, + bookId: bookTask.bookId + }).data as Book.SelectBookTaskDetail[] + } else if (operateBookType == OperateBookType.BOOKTASKDETAIL) { + bookTask = this.bookTaskService.GetBookTaskDataById(id); + if (bookTask == null) { + throw new Error('指定的小说任务数据不存在') + } + book = this.bookService.GetBookDataById(bookTask.bookId) + if (book == null) { + throw new Error("小说数据不存在,请检查") + } + let bookTaskDetail = this.bookTaskDetailService.GetBookTaskDetailDataById(id) + if (bookTaskDetail == null) { + throw new Error("指定的小说任务分镜信息不存在,请检查") + } + bookTaskDetails = [bookTaskDetail]; + } else { + throw new Error("未知的操作类型") + } + + let watermarkPosition = book.watermarkPosition; + if (isEmpty(watermarkPosition)) { + throw new Error("当前没有文案位置的内容,请先进行 ‘开始去除水印 -> 选择水印位置 -> 框选大概位置 -> 保存蒙板位置’ 操作保存蒙板的位置") + } + if (!ValidateJson(watermarkPosition)) { + throw new Error("文案位置格式有误,请先进行 ‘开始去除水印 -> 选择水印位置 -> 框选大概位置 -> 保存蒙板位置’ 操作保存蒙板的位置") + } + let watermarkPositionParse = JSON.parse(watermarkPosition) as any[] + if (watermarkPositionParse.length <= 0) { + throw new Error("没有检测到去除水印的蒙板位置,请先进行 ‘开始去除水印 -> 选择水印位置 -> 框选大概位置 -> 保存蒙板位置’ 操作保存蒙板的位置") + } + let inputImageFolder = path.resolve(define.project_path, `${book.id}/tmp/input`) + if (!(await CheckFileOrDirExist(inputImageFolder))) { + throw new Error("输出文件夹不存在,请先进行抽帧等操作") + } + let iamgePaths = await GetFilesWithExtensions(inputImageFolder, ['.png']) + if (iamgePaths.length <= 0) { + throw new Error("没有检查到抽帧图片,请先进行抽帧等操作") + } + + // 处理位置生成蒙板 + let inputImage = iamgePaths[0] + let outImagePath = path.resolve(define.project_path, `${book.id}/data/mask/mask_temp_${new Date().getTime()}.png`) + await CheckFolderExistsOrCreate(path.dirname(outImagePath)); + // 这边要计算倍率 + let watermarkPositionArray = watermarkPositionParse.map(item => { + return { + x: item.startX, + y: item.startY, + width: item.width, + height: item.height, + imageWidth: item.imageWidth, + imageHeight: item.imageHeight + } + }) + await ProcessImage(inputImage, outImagePath, watermarkPositionArray) + + if (!(await CheckFileOrDirExist(outImagePath))) { + throw new Error("生成蒙板失败") + } + + // 开始去除水印 + for (let i = 0; i < bookTaskDetails.length; i++) { + const element = bookTaskDetails[i]; + let bookInputImage = element.oldImage; + let name = path.basename(bookInputImage) + let bak = path.resolve(define.project_path, `${book.id}/tmp/input/bak/${name}`); + // 做个备份吧 + await CopyFileOrFolder(bookInputImage, bak) + // 新的输出地址 + let tempOutImagePath = path.join(path.dirname(bookInputImage), "temp_" + path.basename(outImagePath)) + // await fs.promises.unlink(bookInputImage) // 删除原来的图片 + // 开始调用去除水印的方法 + let imageBase64 = await GetImageBase64(bak) + let maskBase64 = await GetImageBase64(outImagePath) + + let res = await this.ProcessImage({ + imageBase64: imageBase64, + maskBase64: maskBase64, + type: 'file', + inputFilePath: bak, + maskPath: outImagePath, + outFilePath: tempOutImagePath + }) + // 执行完成,将缓存的图片删除,复制新的图片到原来的位置 + await fs.promises.unlink(bookInputImage) + await CopyFileOrFolder(tempOutImagePath, bookInputImage) + // 删除临时文件 + await fs.promises.unlink(tempOutImagePath) + + // 去水印执行完毕 + this.sendReturnMessage({ + code: 1, + id: element.id, + type: ResponseMessageType.REMOVE_WATERMARK, + data: bookInputImage + }, DEFINE_STRING.BOOK.REMOVE_WATERMARK_RETURN) + + + this.taskScheduler.AddLogToDB(book.id, book.type, `${element.name} 去除水印完成`, element.bookTaskId, LoggerStatus.SUCCESS) + } + // 全部完毕 + return successMessage(null, "全部图片去除水印完成", "ReverseBook_RemoveWatermark") + } catch (error) { + return errorMessage("去除水印失败,错误信息如下:" + error.message, "ReverseBook_RemoveWatermark") + } + } //#endregion } \ No newline at end of file diff --git a/src/main/index.js b/src/main/index.js index 589abdd..dd9fd39 100644 --- a/src/main/index.js +++ b/src/main/index.js @@ -3,7 +3,7 @@ import { v4 as uuidv4 } from 'uuid' import { version } from '../../package.json' import { graphics } from 'systeminformation' -import { app, shell, BrowserWindow, ipcMain, dialog, nativeTheme } from 'electron' +import { app, shell, BrowserWindow, ipcMain, dialog, nativeTheme, session } from 'electron' import path, { join } from 'path' import { electronApp, optimizer, is } from '@electron-toolkit/utils' import icon from '../../resources/icon.ico?asset' @@ -52,7 +52,7 @@ async function createWindow(hash = 'ShowMessage', data, url = null) { // 判断当前是不是有设置的宽高,用的话记忆 let isRe = global.config.window_wh_bm_remember && hash == 'ShowMessage' && global.config.window_wh_bm - + const ses = session.fromPartition('persist:my-session') let mainWindow = new BrowserWindow({ width: isRe ? global.config.window_wh_bm.width : 900, height: isRe ? global.config.window_wh_bm.height : 675, @@ -69,7 +69,9 @@ async function createWindow(hash = 'ShowMessage', data, url = null) { nodeIntegration: hash == 'discord' ? false : true, // 在网页中集成Node nodeIntegrationInWorker: true, webSecurity: false, - partition: 'persist:my-partition' + partition: 'persist:my-partition', + session: ses, + webviewTag : true, } }) diff --git a/src/model/book.d.ts b/src/model/book.d.ts index 3924acd..8116745 100644 --- a/src/model/book.d.ts +++ b/src/model/book.d.ts @@ -18,6 +18,7 @@ declare namespace Book { backgroundMusic?: string | null // 背景音乐ID friendlyReminder?: string | null // 友情提示 subtitlePosition?: string, + watermarkPosition?: string updateTime?: Date, createTime?: Date, version?: string, diff --git a/src/preload/book.js b/src/preload/book.js index 9aea4a6..ed49783 100644 --- a/src/preload/book.js +++ b/src/preload/book.js @@ -61,12 +61,12 @@ const book = { Framing: async (bookId) => await ipcRenderer.invoke(DEFINE_STRING.BOOK.FRAMING, bookId), // 获取文案信息 - GetCopywriting: async (bookId) => - await ipcRenderer.invoke(DEFINE_STRING.BOOK.GET_COPYWRITING, bookId), + GetCopywriting: async (bookId, bookTaskId) => + await ipcRenderer.invoke(DEFINE_STRING.BOOK.GET_COPYWRITING, bookId, bookTaskId), // 去除所有水印 - RemoveWatermark: async (bookId) => - await ipcRenderer.invoke(DEFINE_STRING.BOOK.REMOVE_WATERMARK, bookId), + RemoveWatermark: async (id,operateBookType) => + await ipcRenderer.invoke(DEFINE_STRING.BOOK.REMOVE_WATERMARK, id,operateBookType), // 添加单句推理的 AddReversePrompt: async (bookTaskDetailIds, type) => diff --git a/src/preload/db.ts b/src/preload/db.ts index 3b10289..1ddb27d 100644 --- a/src/preload/db.ts +++ b/src/preload/db.ts @@ -5,7 +5,12 @@ import { Book } from '../model/book' const db = { //#region 小说相关的修改 - // 修改小说人物的数据 + // 修改小说数据 + UpdateBookData: async (bookId: string, data: Book.SelectBook) => { + return await ipcRenderer.invoke(DEFINE_STRING.DB.UPDATE_BOOK_DATA, bookId, data) + }, + + // 修改小说任务的数据 UpdateBookTaskData: async (bookTaskId: string, data: Book.SelectBookTask) => { return await ipcRenderer.invoke(DEFINE_STRING.DB.UPDATE_BOOK_TASK_DATA, bookTaskId, data) }, diff --git a/src/preload/img.js b/src/preload/img.js index a0be509..9640b3b 100644 --- a/src/preload/img.js +++ b/src/preload/img.js @@ -1,20 +1,30 @@ -import { ipcRenderer } from "electron" -import { DEFINE_STRING } from "../define/define_string" - +import { ipcRenderer } from 'electron' +import { DEFINE_STRING } from '../define/define_string' const img = { - // 加载当前链接的SD服务数据 - OneSplitFour: async (value, callback) => callback(await ipcRenderer.invoke(DEFINE_STRING.IMG.ONE_SPLIT_FOUR, value)), + // 加载当前链接的SD服务数据 + OneSplitFour: async (value, callback) => + callback(await ipcRenderer.invoke(DEFINE_STRING.IMG.ONE_SPLIT_FOUR, value)), - // 将base64的图片转换为文件 - Base64ToFile: async (value, callback) => callback(await ipcRenderer.invoke(DEFINE_STRING.IMG.BASE64_TO_FILE, value)), + // 将base64的图片转换为文件 + Base64ToFile: async (value, callback) => + callback(await ipcRenderer.invoke(DEFINE_STRING.IMG.BASE64_TO_FILE, value)), - // 请求图片处理,去除水印 - ProcessImage: async (value, callback) => callback(await ipcRenderer.invoke(DEFINE_STRING.IMG.PROCESS_IMAGE, value)), + // 请求图片处理,去除水印 + ProcessImage: async (value, callback) => + callback(await ipcRenderer.invoke(DEFINE_STRING.IMG.PROCESS_IMAGE, value)), - //批量处理图片,去除水印 - BatchProcessImage: async (value, callback) => callback(await ipcRenderer.invoke(DEFINE_STRING.IMG.BATCH_PROCESS_IMAGE, value)), + //批量处理图片,去除水印 + BatchProcessImage: async (value, callback) => + callback(await ipcRenderer.invoke(DEFINE_STRING.IMG.BATCH_PROCESS_IMAGE, value)), + + // 检查水印处理 + ProcessImageCheck: async (imageBase64, maskPosition, bookId) => + await ipcRenderer.invoke( + DEFINE_STRING.IMG.PROCESS_IMAGE_WATERMASK_CHECK, + imageBase64, + maskPosition, + bookId + ) } -export { - img -} \ No newline at end of file +export { img } diff --git a/src/renderer/src/App.vue b/src/renderer/src/App.vue index ed61494..827efa0 100644 --- a/src/renderer/src/App.vue +++ b/src/renderer/src/App.vue @@ -6,7 +6,7 @@ - + diff --git a/src/renderer/src/components/APIService/APIIcon.vue b/src/renderer/src/components/APIService/APIIcon.vue new file mode 100644 index 0000000..8354005 --- /dev/null +++ b/src/renderer/src/components/APIService/APIIcon.vue @@ -0,0 +1,32 @@ + + + diff --git a/src/renderer/src/components/APIService/ApiService.vue b/src/renderer/src/components/APIService/ApiService.vue new file mode 100644 index 0000000..1386fb6 --- /dev/null +++ b/src/renderer/src/components/APIService/ApiService.vue @@ -0,0 +1,16 @@ + + + diff --git a/src/renderer/src/components/Book/Components/AddBook.vue b/src/renderer/src/components/Book/Components/AddBook.vue index 51f12de..b4cd013 100644 --- a/src/renderer/src/components/Book/Components/AddBook.vue +++ b/src/renderer/src/components/Book/Components/AddBook.vue @@ -74,7 +74,12 @@ > - + +
- {{ + {{ type == 'add' ? '添加' : '保存' }}
@@ -113,6 +118,7 @@ import { useMessage, NButton, NForm, NFormItem, NInput, NSelect, NIcon } from 'n import { useReverseManageStore } from '../../../../../stores/reverseManage.ts' import { CloseSharp } from '@vicons/ionicons5' import { cloneDeep } from 'lodash' +import { useSoftwareStore } from '../../../../../stores/software' export default defineComponent({ components: { @@ -128,7 +134,9 @@ export default defineComponent({ setup(props) { let message = useMessage() let reverseManageStore = useReverseManageStore() + let softwareStore = useSoftwareStore() let bookRef = ref(null) + let loading = ref(false) let backgroundMusicOptions = ref([]) // let data = ref(reverseManageStore.selectBook) let type = ref(props.type ? props.type : 'add') @@ -220,7 +228,9 @@ export default defineComponent({ return } debugger + loading.value = true let res = await reverseManageStore.SaveSelectBook(cloneDeep(reverseManageStore.selectBook)) + loading.value = false // 判断当前的数据是不是有ID if (res.code == 0) { message.error(res.message) @@ -279,8 +289,9 @@ export default defineComponent({ SelectMP4File, AddOrModifyBook, bookRef, + softwareStore, OpenFolder, - backgroundMusicOptions + backgroundMusicOptions,loading } } }) diff --git a/src/renderer/src/components/Book/Components/ManageBookDetailButton.vue b/src/renderer/src/components/Book/Components/ManageBookDetailButton.vue index 709c4fb..547df16 100644 --- a/src/renderer/src/components/Book/Components/ManageBookDetailButton.vue +++ b/src/renderer/src/components/Book/Components/ManageBookDetailButton.vue @@ -9,7 +9,7 @@ - 开始分镜 + 开始分镜 h(GetWaterMaskRectangle, { width: dialogWidth, height: dialogHeight }), + style: `width : ${dialogWidth}px; height : ${dialogHeight}px`, + maskClosable: false, + onClose: () => {} + }) + } + + // 一些选择方式 async function handleSelect(key) { switch (key) { - case 'compute_frame': + case 'compute_frame': // 计算分镜 await ComputeStoryboard() break - case 'framing': + case 'framing': // 开始分镜 await Framing() break - case 'recognizing_setting': + case 'recognizing_setting': // 文案位置设置 await GetCopywritingSetting() break - case 'mj_reverse': + case 'watermark_position': // 水印位置设置 + await GetWatermarkPosition() + break + case 'mj_reverse': // MJ反推 await ImageReversePrompt(BookType.MJ_REVERSE) break - case 'sd_reverse': + case 'sd_reverse': // SD反推 await ImageReversePrompt(BookType.SD_REVERSE) break case 'remove_reverse': @@ -570,12 +599,17 @@ export default defineComponent({ }) } + async function ActionFrame() { + message.info('请使用下面菜单中的按钮进行对应的操作') + } + return { RetuenMain, AutoAction, ImportWordAndSrtFunc, reverseManageStore, softwareStore, + ActionFrame, switchLogger, ComputeStoryboard, OpenSettingDialog, @@ -606,7 +640,7 @@ export default defineComponent({ ], copywritingOptions: [ { - label: '提取文案设置', + label: '提取文案位置', key: 'recognizing_setting' }, { @@ -616,8 +650,8 @@ export default defineComponent({ ], watermarkOptions: [ { - label: '去除水印设置', - key: 'watermark_setting' + label: '选择水印位置', + key: 'watermark_position' }, { label: '停止去除', diff --git a/src/renderer/src/components/Home/Home.vue b/src/renderer/src/components/Home/Home.vue index b31f589..56f63e1 100644 --- a/src/renderer/src/components/Home/Home.vue +++ b/src/renderer/src/components/Home/Home.vue @@ -52,14 +52,14 @@ import { DuplicateOutline, GridOutline, RadioOutline, - BookOutline + BookOutline, } from '@vicons/ionicons5' import CheckMachineId from '../Components/CheckMachineId.vue' -import axios from 'axios' import { DEFINE_STRING } from '../../../../define/define_string' import ShowMessage from './ShowMessage.vue' import { MD5 } from 'crypto-js' import InputDialogContent from '../Original/Components/InputDialogContent.vue' +import APIIcon from '../APIService/APIIcon.vue' export default defineComponent({ components: { @@ -71,7 +71,8 @@ export default defineComponent({ NIcon, ShowMessage, NSwitch, - NButton + NButton, + APIIcon }, setup() { let collapsed = ref(false) @@ -89,6 +90,7 @@ export default defineComponent({ if (option.key == 'setting') return h(NIcon, null, { default: () => h(SettingsOutline) }) if (option.key == 'gptCopywriting') return h(NIcon, null, { default: () => h(BookOutline) }) if (option.key == 'book_management') return h(NIcon, null, { default: () => h(GridOutline) }) + if (option.key == 'lai_api') return h(NIcon, null, { default: () => h(APIIcon) }) if (option.key == 'backward_matrix') return h(NIcon, null, { default: () => h(DuplicateOutline) }) if (option.key == 'TTS_Services') return h(NIcon, null, { default: () => h(RadioOutline) }) @@ -452,21 +454,21 @@ export default defineComponent({ } ] }, - // { - // label: () => - // h( - // RouterLink, - // { - // to: { - // name: 'test_options' - // } - // }, - // { - // default: () => '测试操作' - // } - // ), - // key: 'test_options' - // }, + { + label: () => + h( + RouterLink, + { + to: { + name: 'lai_api' + } + }, + { + default: () => 'API服务' + } + ), + key: 'lai_api' + }, { label: () => h( diff --git a/src/renderer/src/components/Original/Components/AddCharacterTag.vue b/src/renderer/src/components/Original/Components/AddCharacterTag.vue index 7874b0b..8681073 100644 --- a/src/renderer/src/components/Original/Components/AddCharacterTag.vue +++ b/src/renderer/src/components/Original/Components/AddCharacterTag.vue @@ -3,16 +3,26 @@
- + 显示 - +
+ + 本地上传图片 +
@@ -36,7 +46,7 @@ style="margin-top: 10px" @click="GenerateCharacterImage" :loading="imageLoading" - >生成图片SD生成图片
@@ -188,7 +198,6 @@ export default defineComponent({ await window.mj.SaveTagPropertyData( [JSON.stringify(characterData.value), 'character_tags'], (value) => { - console.log(value) if (value.code == 0) { message.error(value.message) @@ -280,7 +289,6 @@ export default defineComponent({ } imageLoading.value = true await window.sd.txt2img(JSON.stringify([d]), async (value) => { - if (value.code == 0) { message.error(value.message) imageLoading.value = false @@ -298,8 +306,40 @@ export default defineComponent({ }) } + /** + * 选择本地图片 + */ + async function SelecImage() { + await window.api.SelectFile( + [ + 'jpeg', + 'jpg', + 'png', + 'gif', + 'bmp', + 'tiff', + 'tif', + 'webp', + 'svg', + 'raw', + 'cr2', + 'nef', + 'arw' + ], + (value) => { + if (value.code == 0) { + message.error(value.message) + return + } + png_base64.value = null + characterData.value.show_image = value.value + } + ) + } + return { characterData, + SelecImage, alias_tags, SaveCharacterTag, TranslatePrompt, diff --git a/src/renderer/src/components/Original/Components/AddStyleTags.vue b/src/renderer/src/components/Original/Components/AddStyleTags.vue index 0182ef5..5519b66 100644 --- a/src/renderer/src/components/Original/Components/AddStyleTags.vue +++ b/src/renderer/src/components/Original/Components/AddStyleTags.vue @@ -3,12 +3,12 @@ - +
+ + 本地上传图片 +
生成图片
SD生成图片 使用SD出图模式生成风格图片,提示词为1gril加上风格提示词 @@ -149,6 +149,9 @@ export default defineComponent({ return } styleData.value['type'] = 'style_main' + styleData.value['show_image'] = styleData.value.show_image + ? styleData.value.show_image.split('?t')[0] + : null // 直接保存 // 开始保存 await window.mj.SaveTagPropertyData( @@ -244,6 +247,7 @@ export default defineComponent({ } imageLoading.value = true await window.sd.txt2img(JSON.stringify([d]), async (value) => { + debugger if (value.code == 0) { message.error(value.message) imageLoading.value = false @@ -251,7 +255,8 @@ export default defineComponent({ } if (value.data && value.data.length > 0) { let d = value.data[0] - styleData.value.show_image = d.image_path.replaceAll('\\', '/') + styleData.value.show_image = + d.image_path.replaceAll('\\', '/') + '?t=' + new Date().getTime() png_base64.value = 'data:image/png;base64,' + d.base64 console.log(styleData.value.show_image) imageLoading.value = false @@ -261,8 +266,52 @@ export default defineComponent({ }) } + /** + * 选择本地的图片应用到风格图片 + */ + async function SelecImage() { + await window.api.SelectFile( + [ + 'jpeg', + 'jpg', + 'png', + 'gif', + 'bmp', + 'tiff', + 'tif', + 'webp', + 'svg', + 'raw', + 'cr2', + 'nef', + 'arw' + ], + (value) => { + if (value.code == 0) { + message.error(value.message) + return + } + // 读取文件并转换为Base64 + fetch(value.value) + .then((response) => response.blob()) + .then((blob) => { + const reader = new FileReader() + reader.onloadend = () => { + png_base64.value = reader.result + } + reader.readAsDataURL(blob) + }) + // 更新显示的图片 + + png_base64.value = null + styleData.value.show_image = value.value + '?t=' + new Date().getTime() + } + ) + } + return { styleData, + SelecImage, SaveStyleTag, loading, imageLoading, diff --git a/src/renderer/src/components/Original/Components/DataTableShowGenerateImage.vue b/src/renderer/src/components/Original/Components/DataTableShowGenerateImage.vue index 19745e3..d8e97da 100644 --- a/src/renderer/src/components/Original/Components/DataTableShowGenerateImage.vue +++ b/src/renderer/src/components/Original/Components/DataTableShowGenerateImage.vue @@ -83,6 +83,7 @@ fit="cover" width="120" height="120" + lazy /> item.imageLock == false) + tempData = tempData.filter((item) => item.imageLock == null || item.imageLock == false) if (tempData.length <= 0) { message.error('下面的数据都被锁定,无法生图,请检查') diff --git a/src/renderer/src/components/Original/MenuButton.vue b/src/renderer/src/components/Original/MenuButton.vue index 6bfc3fe..690b3c0 100644 --- a/src/renderer/src/components/Original/MenuButton.vue +++ b/src/renderer/src/components/Original/MenuButton.vue @@ -220,10 +220,10 @@ export default defineComponent({ * 生成所有的图片 */ async function GenerateImageAll() { + debugger let tmpData = cloneDeep(toRaw(data.value)) tmpData = tmpData.filter((item) => { - // return item.outImagePath == null || item.outImagePath == '' - return !item.imageLoak + return item.imageLock == null || item.imageLock == false }) if (tmpData.length == 0) { @@ -280,12 +280,24 @@ export default defineComponent({ } } + // 重置GPT提示词 + async function ResetGPTPrompt() { + for (let i = 0; i < data.value.length; i++) { + data.value[i].gpt_prompt = '' + } + } + // 图片数据重置 async function ResetImage() { // 循环data删除数据 for (let i = 0; i < data.value.length; i++) { data.value[i].outImagePath = null data.value[i].subImagePath = [] + data.value[i].imageLock = false + // 判断是不是有mj_message,有的话删除 + if (data.value[i].mj_message) { + data.value[i].mj_message = undefined + } } } @@ -333,6 +345,9 @@ export default defineComponent({ await ResetPrompt() } else if (value == 'resetImage') { await ResetImage() + } else if (value == 'resetGPTPrompt') { + // 重置GPT提示词 + await ResetGPTPrompt() } } @@ -340,6 +355,7 @@ export default defineComponent({ async function resetALLData() { await ResetPrompt() await ResetImage() + await ResetGPTPrompt() } // 导入提示词 @@ -408,6 +424,7 @@ export default defineComponent({ GptButtonOptions: [{ label: '停止推理任务', key: 'stopGptPrompt' }], GenerateImageOptions: [{ label: '停止生成图片任务', key: 'stopGenerateImage' }], ResetDataOptions: [ + { label: '重置GPT提示词', key: 'resetGPTPrompt' }, { label: '重置提示词', key: 'resetPrompt' }, { label: '重置图片', key: 'resetImage' } ], diff --git a/src/renderer/src/components/Watermark/GetWaterMaskRectangle.vue b/src/renderer/src/components/Watermark/GetWaterMaskRectangle.vue new file mode 100644 index 0000000..1ad0fe9 --- /dev/null +++ b/src/renderer/src/components/Watermark/GetWaterMaskRectangle.vue @@ -0,0 +1,531 @@ + + + + + diff --git a/src/renderer/src/components/Watermark/ImageFileSelect.vue b/src/renderer/src/components/Watermark/ImageFileSelect.vue index 11d9c1e..86de304 100644 --- a/src/renderer/src/components/Watermark/ImageFileSelect.vue +++ b/src/renderer/src/components/Watermark/ImageFileSelect.vue @@ -43,6 +43,7 @@ export default defineComponent({ } async function selectImage(value) { + debugger let img = await getBase64(value.file.file) props.modifyImage(img) } diff --git a/src/renderer/src/main.js b/src/renderer/src/main.js index c14b4e9..477f564 100644 --- a/src/renderer/src/main.js +++ b/src/renderer/src/main.js @@ -6,6 +6,9 @@ const app = createApp(App) import { createPinia } from 'pinia' const pinia = createPinia() +// 注册 webview 为自定义元素 +app.config.compilerOptions.isCustomElement = (tag) => tag === 'webview' + const routes = [ { path: '/', @@ -101,6 +104,11 @@ const routes = [ name: 'test_options', component: () => import('./components/VideoSubtitle/VideoCanvas.vue') }, + { + path: '/lai_api', + name: 'lai_api', + component: () => import('./components/APIService/ApiService.vue') + }, { path: '/TTS_Services', name: 'TTS_Services', diff --git a/vue.config.js b/vue.config.js new file mode 100644 index 0000000..7cf0af1 --- /dev/null +++ b/vue.config.js @@ -0,0 +1,13 @@ +export default { + chainWebpack: (config) => { + config.module + .rule('vue') + .use('vue-loader') + .tap((options) => { + options.compilerOptions = { + isCustomElement: (tag) => tag === 'webview' + } + return options + }) + } +}