V 3.0.1 LaiTool V3.0.1-preview.3
This commit is contained in:
parent
f3a25e9474
commit
36178fbe5d
4
package-lock.json
generated
4
package-lock.json
generated
@ -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",
|
||||
|
||||
@ -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",
|
||||
|
||||
BIN
resources/image/c_s/7f403066-7722-4423-853e-17bac07c5565.png
Normal file
BIN
resources/image/c_s/7f403066-7722-4423-853e-17bac07c5565.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 623 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 44 KiB After Width: | Height: | Size: 638 KiB |
Binary file not shown.
Binary file not shown.
Binary file not shown.
@ -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<void> {
|
||||
@ -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({
|
||||
// 创建多个白色的矩形,并进行合成
|
||||
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;
|
||||
}
|
||||
|
||||
const regionBuffer = await sharp({
|
||||
create: {
|
||||
width: region.width,
|
||||
height: region.height,
|
||||
width: Math.ceil(region.width * rate),
|
||||
height: Math.ceil(region.height * rate),
|
||||
channels: 3, // RGB channels
|
||||
background: markColor // Black color
|
||||
background: markColor // 标记颜色
|
||||
}
|
||||
}).png().toBuffer();
|
||||
|
||||
// 在白色背景上叠加黑色矩形
|
||||
await sharp(whiteBackground)
|
||||
.composite([
|
||||
{
|
||||
input: blackRegion,
|
||||
left: region.x,
|
||||
top: Math.floor(region.y)
|
||||
}
|
||||
])
|
||||
.toFile(outputPath);
|
||||
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;
|
||||
|
||||
@ -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',
|
||||
|
||||
@ -24,7 +24,8 @@ export class BookModel extends Realm.Object<BookModel> {
|
||||
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<BookModel> {
|
||||
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<BookModel> {
|
||||
videoConfig: "string?",
|
||||
prefixPrompt: "string?",
|
||||
suffixPrompt: "string?",
|
||||
subtitlePosition: 'string?'
|
||||
subtitlePosition: 'string?',
|
||||
watermarkPosition: "string?"
|
||||
},
|
||||
// 主键为_id
|
||||
primaryKey: 'id'
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -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')
|
||||
|
||||
@ -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"
|
||||
}
|
||||
}
|
||||
|
||||
@ -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()
|
||||
)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@ -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)
|
||||
)
|
||||
|
||||
// 添加反推任务到任务列表
|
||||
|
||||
@ -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
|
||||
|
||||
}
|
||||
|
||||
@ -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 }
|
||||
|
||||
@ -1,24 +1,24 @@
|
||||
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();
|
||||
this.global = global
|
||||
this.pm = new PublicMethod(global)
|
||||
this.tools = new Tools()
|
||||
this.sdApi = new SdApi()
|
||||
}
|
||||
|
||||
/**
|
||||
@ -26,10 +26,10 @@ export class SD {
|
||||
*/
|
||||
async GetAllLoras(baseURL = null) {
|
||||
try {
|
||||
let data = await this.sdApi.getAllLoras(baseURL);
|
||||
return successMessage(data);
|
||||
let data = await this.sdApi.getAllLoras(baseURL)
|
||||
return successMessage(data)
|
||||
} catch (error) {
|
||||
return errorMessage(error.toString());
|
||||
return errorMessage(error.toString())
|
||||
}
|
||||
}
|
||||
|
||||
@ -40,10 +40,10 @@ export class SD {
|
||||
*/
|
||||
async GetAllSDModel(baseURL = null) {
|
||||
try {
|
||||
let data = await this.sdApi.getAllSDModel(baseURL);
|
||||
return successMessage(data);
|
||||
let data = await this.sdApi.getAllSDModel(baseURL)
|
||||
return successMessage(data)
|
||||
} catch (error) {
|
||||
return errorMessage(error.toString());
|
||||
return errorMessage(error.toString())
|
||||
}
|
||||
}
|
||||
|
||||
@ -54,10 +54,10 @@ export class SD {
|
||||
*/
|
||||
async GetAllSamplers(baseURL = null) {
|
||||
try {
|
||||
let data = await this.sdApi.getAllSamplers(baseURL);
|
||||
return successMessage(data);
|
||||
let data = await this.sdApi.getAllSamplers(baseURL)
|
||||
return successMessage(data)
|
||||
} catch (error) {
|
||||
return errorMessage(error.toString());
|
||||
return errorMessage(error.toString())
|
||||
}
|
||||
}
|
||||
|
||||
@ -69,33 +69,33 @@ export class SD {
|
||||
async LoadSDServiceData(baseURL = null) {
|
||||
try {
|
||||
// 加载大模型
|
||||
let sd_model = await this.GetAllSDModel(baseURL);
|
||||
let sd_model = await this.GetAllSDModel(baseURL)
|
||||
// 往sd_model中添加一个默认的选项
|
||||
sd_model.data.data.unshift({
|
||||
title: "无",
|
||||
name: "无",
|
||||
description: "无",
|
||||
title: '无',
|
||||
name: '无',
|
||||
description: '无'
|
||||
})
|
||||
// 加载Lora
|
||||
let lora = await this.GetAllLoras(baseURL);
|
||||
let lora = await this.GetAllLoras(baseURL)
|
||||
lora.data.data.unshift({
|
||||
Key: "无",
|
||||
name: "无",
|
||||
description: "无",
|
||||
Key: '无',
|
||||
name: '无',
|
||||
description: '无'
|
||||
})
|
||||
// 加载采样器
|
||||
let sampler = await this.GetAllSamplers(baseURL);
|
||||
let sampler = await this.GetAllSamplers(baseURL)
|
||||
sampler.data.data.unshift({
|
||||
name: "无",
|
||||
description: "无",
|
||||
name: '无',
|
||||
description: '无'
|
||||
})
|
||||
|
||||
if (!(sd_model.code & lora.code & sampler.code)) {
|
||||
throw new Error("获取SD数据错误,请检查SD WEBUI链接!");
|
||||
throw new Error('获取SD数据错误,请检查SD WEBUI链接!')
|
||||
}
|
||||
|
||||
for (let i = 0; i < lora.data.data.length; i++) {
|
||||
delete lora.data.data[i].metadata;
|
||||
delete lora.data.data[i].metadata
|
||||
}
|
||||
let data = {
|
||||
sd_model: sd_model.data.data,
|
||||
@ -103,14 +103,13 @@ export class SD {
|
||||
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);
|
||||
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());
|
||||
return errorMessage('加载数据失败,错误信息如下:' + error.toString())
|
||||
}
|
||||
}
|
||||
|
||||
@ -121,7 +120,7 @@ export class SD {
|
||||
* */
|
||||
async GetImageStyleMenu() {
|
||||
try {
|
||||
let style = ImageStyleDefine.getImageStyleMenu();
|
||||
let style = ImageStyleDefine.getImageStyleMenu()
|
||||
return {
|
||||
code: 1,
|
||||
data: style
|
||||
@ -129,7 +128,7 @@ export class SD {
|
||||
} catch (error) {
|
||||
return {
|
||||
code: 0,
|
||||
message: "不可能出现错误"
|
||||
message: '不可能出现错误'
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -141,33 +140,32 @@ export class SD {
|
||||
async GetImageStyleInfomation(value) {
|
||||
try {
|
||||
if (value) {
|
||||
value = JSON.parse(value);
|
||||
value = JSON.parse(value)
|
||||
} else {
|
||||
value = [];
|
||||
value = []
|
||||
}
|
||||
value = value ? value : [];
|
||||
let style = ImageStyleDefine.getAllSubStyle();
|
||||
let tmp = [];
|
||||
value = value ? value : []
|
||||
let style = ImageStyleDefine.getAllSubStyle()
|
||||
let tmp = []
|
||||
for (let i = 0; i < value.length; i++) {
|
||||
const element = value[i];
|
||||
const element = value[i]
|
||||
for (let j = 0; j < style.length; j++) {
|
||||
const item = style[j];
|
||||
const item = style[j]
|
||||
if (item.id == element) {
|
||||
tmp.push(item);
|
||||
break;
|
||||
tmp.push(item)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
let newSubStyle = cloneDeep(tmp);
|
||||
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);
|
||||
const element = newSubStyle[i]
|
||||
element.image = path.join(define.image_path, 'style/' + element.image)
|
||||
}
|
||||
return {
|
||||
code: 1,
|
||||
data: newSubStyle
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
return {
|
||||
code: 0,
|
||||
@ -183,17 +181,19 @@ export class SD {
|
||||
*/
|
||||
async GetStyleImageSubList(value) {
|
||||
try {
|
||||
let subStyle = ImageStyleDefine.getImagePathById(value);
|
||||
let newSubStyle = cloneDeep(subStyle);
|
||||
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);
|
||||
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,
|
||||
@ -209,27 +209,27 @@ export class SD {
|
||||
*/
|
||||
async txt2img(value) {
|
||||
try {
|
||||
value = JSON.parse(value);
|
||||
let data = value[0];
|
||||
let res = await this.sdApi.txt2img(data);
|
||||
value = JSON.parse(value)
|
||||
let data = value[0]
|
||||
let res = await this.sdApi.txt2img(data)
|
||||
// 将base· 64的图片转换为图片
|
||||
// 将当前的图片保存到指定的文件夹中,然后返回文件路径,并且可以复制到指定的文件,删除exif信息
|
||||
let image_paths = [];
|
||||
let image_paths = []
|
||||
for (let i = 0; res.data.images && i < res.data.images.length; i++) {
|
||||
const element = res.data.images[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);
|
||||
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);
|
||||
return successMessage(image_paths)
|
||||
} catch (error) {
|
||||
return errorMessage("生图失败,错误信息如下:" + error.toString());
|
||||
return errorMessage('生图失败,错误信息如下:' + error.toString())
|
||||
}
|
||||
}
|
||||
|
||||
@ -239,136 +239,143 @@ export class SD {
|
||||
* @param {任务队列信息} task_list 301198499
|
||||
*/
|
||||
async OneImageGeneration(image, task_list, seed = -1) {
|
||||
let taskPath = path.join(this.global.config.project_path, "scripts/task_list.json")
|
||||
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 = "";
|
||||
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}`)
|
||||
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);
|
||||
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;
|
||||
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})), `;
|
||||
prompt += `((${task_list.image_style})), `
|
||||
}
|
||||
if (task_list.lora != null) {
|
||||
prompt += `${task_list.lora}, `;
|
||||
prompt += `${task_list.lora}, `
|
||||
}
|
||||
prompt += imageJson.webui_config.prompt;
|
||||
prompt += imageJson.webui_config.prompt
|
||||
// 判断当前是不是有开修脸修手
|
||||
let ADetailer = {
|
||||
args: sd_setting.adetailer
|
||||
};
|
||||
if (model == "img2img") {
|
||||
}
|
||||
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')];
|
||||
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.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);
|
||||
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;
|
||||
seed = info.seed
|
||||
}
|
||||
// 目前是单图出图
|
||||
let images = response.data.images;
|
||||
let imageData = Buffer.from(images[0].split(",", 1)[0], 'base64');
|
||||
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);
|
||||
await this.tools.deletePngAndDeleteExifData(image_path, target_image_path)
|
||||
})
|
||||
.catch(err => {
|
||||
throw new Error(err);
|
||||
});
|
||||
return seed;
|
||||
|
||||
} else if (model == "txt2img") {
|
||||
.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,
|
||||
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,
|
||||
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';
|
||||
let web_api = this.global.config.webui_api_url + 'sdapi/v1/txt2img'
|
||||
|
||||
if (imageJson.adetailer) {
|
||||
let ta = {
|
||||
ADetailer: ADetailer
|
||||
}
|
||||
body.alwayson_scripts = ta;
|
||||
body.alwayson_scripts = ta
|
||||
}
|
||||
const response = await axios.post(web_api, body);
|
||||
let info = JSON.parse(response.data.info);
|
||||
const response = await axios.post(web_api, body)
|
||||
let info = JSON.parse(response.data.info)
|
||||
if (seed == -1) {
|
||||
seed = info.seed;
|
||||
seed = info.seed
|
||||
}
|
||||
// 目前是单图出图
|
||||
let images = response.data.images;
|
||||
let imageData = Buffer.from(images[0].split(",", 1)[0], 'base64');
|
||||
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);
|
||||
await this.tools.deletePngAndDeleteExifData(image_path, target_image_path)
|
||||
})
|
||||
.catch(err => {
|
||||
.catch((err) => {
|
||||
// console.log(err)
|
||||
throw new Error(err);
|
||||
});
|
||||
return seed;
|
||||
throw new Error(err)
|
||||
})
|
||||
return seed
|
||||
} else {
|
||||
throw new Error("SD 模式错误");
|
||||
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'));
|
||||
let task_list_json = JSON.parse(await fspromises.readFile(taskPath, 'utf-8'))
|
||||
// 修改指定的列表的数据
|
||||
task_list_json.task_list.map(a => {
|
||||
task_list_json.task_list.map((a) => {
|
||||
if (a.id == task_list.id) {
|
||||
a.status = "error";
|
||||
a.errorMessage = error.toString();
|
||||
a.status = 'error'
|
||||
a.errorMessage = error.toString()
|
||||
}
|
||||
})
|
||||
// 写入
|
||||
await fspromises.writeFile(taskPath, JSON.stringify(task_list_json));
|
||||
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"
|
||||
});
|
||||
status: 'error'
|
||||
})
|
||||
throw error;
|
||||
})
|
||||
throw error
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -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<GeneralResponse.ErrorItem | GeneralResponse.SuccessItem> {
|
||||
async GetCopywriting(bookId: string, bookTaskId: string = null): Promise<GeneralResponse.ErrorItem | GeneralResponse.SuccessItem> {
|
||||
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 反推相关任务
|
||||
|
||||
@ -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<string | Buffer> {
|
||||
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<GeneralResponse.ErrorItem | GeneralResponse.SuccessItem> {
|
||||
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
|
||||
}
|
||||
@ -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,
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
1
src/model/book.d.ts
vendored
1
src/model/book.d.ts
vendored
@ -18,6 +18,7 @@ declare namespace Book {
|
||||
backgroundMusic?: string | null // 背景音乐ID
|
||||
friendlyReminder?: string | null // 友情提示
|
||||
subtitlePosition?: string,
|
||||
watermarkPosition?: string
|
||||
updateTime?: Date,
|
||||
createTime?: Date,
|
||||
version?: string,
|
||||
|
||||
@ -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) =>
|
||||
|
||||
@ -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)
|
||||
},
|
||||
|
||||
@ -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)),
|
||||
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)),
|
||||
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)),
|
||||
}
|
||||
export {
|
||||
img
|
||||
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 }
|
||||
|
||||
@ -6,7 +6,7 @@
|
||||
<n-message-provider>
|
||||
<n-dialog-provider>
|
||||
<n-notification-provider>
|
||||
<n-spin style="z-index: 100;" :show="softwareStore.spin.spinning">
|
||||
<n-spin style="z-index: 1000;" :show="softwareStore.spin.spinning">
|
||||
<RouterView></RouterView>
|
||||
<template #description> {{ softwareStore.spin.tip }} </template>
|
||||
</n-spin>
|
||||
|
||||
32
src/renderer/src/components/APIService/APIIcon.vue
Normal file
32
src/renderer/src/components/APIService/APIIcon.vue
Normal file
@ -0,0 +1,32 @@
|
||||
<template>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:xlink="http://www.w3.org/1999/xlink"
|
||||
viewBox="0 0 32 32"
|
||||
>
|
||||
<path
|
||||
d="M8 9H4a2 2 0 0 0-2 2v12h2v-5h4v5h2V11a2 2 0 0 0-2-2zm-4 7v-5h4v5z"
|
||||
fill="currentColor"
|
||||
></path>
|
||||
<path d="M22 11h3v10h-3v2h8v-2h-3V11h3V9h-8v2z" fill="currentColor"></path>
|
||||
<path
|
||||
d="M14 23h-2V9h6a2 2 0 0 1 2 2v5a2 2 0 0 1-2 2h-4zm0-7h4v-5h-4z"
|
||||
fill="currentColor"
|
||||
></path>
|
||||
</svg>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { ref, onMounted, defineComponent, onUnmounted, toRaw, watch } from 'vue'
|
||||
import { useMessage } from 'naive-ui'
|
||||
|
||||
export default defineComponent({
|
||||
components: {},
|
||||
|
||||
setup() {
|
||||
onMounted(async () => {})
|
||||
|
||||
return {}
|
||||
}
|
||||
})
|
||||
</script>
|
||||
16
src/renderer/src/components/APIService/ApiService.vue
Normal file
16
src/renderer/src/components/APIService/ApiService.vue
Normal file
@ -0,0 +1,16 @@
|
||||
<template>
|
||||
<div style="width: 100%; height: 100%">
|
||||
<webview style="width: 100%; height: 100%" src="https://api.laitool.cc" partition="persist:your-session-name"></webview>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { defineComponent } from 'vue'
|
||||
export default defineComponent({
|
||||
components: {},
|
||||
|
||||
setup() {
|
||||
return {}
|
||||
}
|
||||
})
|
||||
</script>
|
||||
@ -74,7 +74,12 @@
|
||||
>
|
||||
</n-form-item>
|
||||
<n-form-item label="背景音乐" path="srtPath">
|
||||
<n-select v-model:value="reverseManageStore.selectBook.backgroundMusic" :options="backgroundMusicOptions" placeholder="请选择背景音乐"> </n-select>
|
||||
<n-select
|
||||
v-model:value="reverseManageStore.selectBook.backgroundMusic"
|
||||
:options="backgroundMusicOptions"
|
||||
placeholder="请选择背景音乐"
|
||||
>
|
||||
</n-select>
|
||||
</n-form-item>
|
||||
<n-form-item label="小说项目文件夹" path="bookFolderPath">
|
||||
<n-input
|
||||
@ -99,7 +104,7 @@
|
||||
>
|
||||
</n-form-item>
|
||||
<div style="margin-left: 10px; display: flex; justify-content: flex-end">
|
||||
<n-button type="success" @click="AddOrModifyBook">{{
|
||||
<n-button :loading="loading" type="success" @click="AddOrModifyBook">{{
|
||||
type == 'add' ? '添加' : '保存'
|
||||
}}</n-button>
|
||||
</div>
|
||||
@ -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
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
@ -9,7 +9,7 @@
|
||||
</n-dropdown>
|
||||
<n-divider vertical style="height: 30px" />
|
||||
<n-dropdown trigger="hover" :options="frameOptions" @select="handleSelect">
|
||||
<n-button :color="softwareStore.SoftColor.ORANGE" @click=""> 开始分镜 </n-button>
|
||||
<n-button :color="softwareStore.SoftColor.ORANGE" @click="ActionFrame"> 开始分镜 </n-button>
|
||||
</n-dropdown>
|
||||
<n-dropdown trigger="hover" :options="copywritingOptions" @select="handleSelect">
|
||||
<n-button
|
||||
@ -102,18 +102,20 @@ import VideoCanvas from '../../VideoSubtitle/VideoCanvas.vue'
|
||||
import { SubtitleSavePositionType } from '../../../../../define/enum/waterMarkAndSubtitle'
|
||||
import { DEFINE_STRING } from '../../../../../define/define_string'
|
||||
import Setting from './Setting.vue'
|
||||
import { BookType } from '../../../../../define/enum/bookEnum'
|
||||
import { BookType, OperateBookType } from '../../../../../define/enum/bookEnum'
|
||||
import TranslateSetting from '../../Setting/TranslateSetting.vue'
|
||||
import { TranslateType } from '../../../../../define/enum/translate'
|
||||
import { ContainsChineseOrPunctuation } from '../../../../../define/Tools/common'
|
||||
import ImportWordAndSrt from '../../Original/Components/ImportWordAndSrt.vue'
|
||||
import GetWaterMaskRectangle from '../../Watermark/GetWaterMaskRectangle.vue'
|
||||
|
||||
export default defineComponent({
|
||||
components: {
|
||||
NButton,
|
||||
NCheckbox,
|
||||
NDropdown,
|
||||
NDivider
|
||||
NDivider,
|
||||
GetWaterMaskRectangle
|
||||
},
|
||||
|
||||
setup() {
|
||||
@ -268,7 +270,10 @@ export default defineComponent({
|
||||
})
|
||||
return
|
||||
}
|
||||
let res_frame = await window.book.RemoveWatermark(reverseManageStore.selectBook.id)
|
||||
let res_frame = await window.book.RemoveWatermark(
|
||||
reverseManageStore.selectBookTask.id,
|
||||
OperateBookType.BOOKTASK
|
||||
)
|
||||
softwareStore.spin.spinning = false
|
||||
window.api.showGlobalMessageDialog(res_frame)
|
||||
}
|
||||
@ -292,21 +297,45 @@ export default defineComponent({
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取水印位置
|
||||
*/
|
||||
async function GetWatermarkPosition() {
|
||||
// 开始获取水印位置
|
||||
// 判断当前数据是不是存在
|
||||
// 处理数据。获取当前的所有的数据
|
||||
let dialogWidth = window.innerWidth * 0.7
|
||||
let dialogHeight = window.innerHeight * 0.9
|
||||
// ImportWordAndSrt
|
||||
dialog.create({
|
||||
showIcon: false,
|
||||
closeOnEsc: false,
|
||||
content: () => 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: '停止去除',
|
||||
|
||||
@ -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(
|
||||
|
||||
@ -3,16 +3,26 @@
|
||||
<div style="width: 500px; position: relative">
|
||||
<n-form ref="formRef" label-placement="top" :model="characterData" :rules="rules">
|
||||
<n-form-item label="人物名称" path="label">
|
||||
<n-input style="width: 400px;" v-model:value="characterData.label" placeholder="请输入人物名称" />
|
||||
<n-input
|
||||
style="width: 400px"
|
||||
v-model:value="characterData.label"
|
||||
placeholder="请输入人物名称"
|
||||
/>
|
||||
<n-checkbox style="margin-left: 20px" v-model:checked="characterData.isShow"
|
||||
>显示</n-checkbox
|
||||
>
|
||||
<div
|
||||
style="position: absolute; left: 520px; top: 0; display: flex; flex-direction: column"
|
||||
>
|
||||
<n-image
|
||||
style="position: absolute; left: 520px; top: 0"
|
||||
width="120"
|
||||
height="120"
|
||||
:src="png_base64 ? png_base64 : characterData.show_image"
|
||||
/>
|
||||
<n-button color="#e18a3b" style="margin-top: 5px" size="tiny" @click="SelecImage"
|
||||
>本地上传图片</n-button
|
||||
>
|
||||
</div>
|
||||
</n-form-item>
|
||||
<n-form-item label="人物别名(可多个)">
|
||||
<n-dynamic-tags type="success" v-model:value="alias_tags" />
|
||||
@ -36,7 +46,7 @@
|
||||
style="margin-top: 10px"
|
||||
@click="GenerateCharacterImage"
|
||||
:loading="imageLoading"
|
||||
>生成图片</n-button
|
||||
>SD生成图片</n-button
|
||||
>
|
||||
</div>
|
||||
</n-form-item>
|
||||
@ -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,
|
||||
|
||||
@ -3,12 +3,12 @@
|
||||
<n-form label-placement="top" ref="formRef" :rules="rules" :model="styleData">
|
||||
<n-form-item label="风格名称" path="label">
|
||||
<n-input v-model:value="styleData.label" placeholder="请输入风格名称" />
|
||||
<n-image
|
||||
style="position: absolute; left: 520px; top: 0"
|
||||
width="120"
|
||||
height="120"
|
||||
:src="png_base64 ? png_base64 : styleData.show_image"
|
||||
/>
|
||||
<div style="position: absolute; left: 520px; top: 0; display: flex; flex-direction: column">
|
||||
<n-image width="120" height="120" :src="png_base64 ? png_base64 : styleData.show_image" />
|
||||
<n-button color="#e18a3b" style="margin-top: 5px" size="tiny" @click="SelecImage"
|
||||
>本地上传图片</n-button
|
||||
>
|
||||
</div>
|
||||
</n-form-item>
|
||||
<n-form-item label="风格提示词描述(中文)">
|
||||
<n-input
|
||||
@ -29,7 +29,7 @@
|
||||
style="margin-top: 10px"
|
||||
@click="GenerateStyleImage"
|
||||
:loading="imageLoading"
|
||||
>生成图片</n-button
|
||||
>SD生成图片</n-button
|
||||
>
|
||||
</template>
|
||||
使用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,
|
||||
|
||||
@ -83,6 +83,7 @@
|
||||
fit="cover"
|
||||
width="120"
|
||||
height="120"
|
||||
lazy
|
||||
/>
|
||||
<n-button
|
||||
text
|
||||
|
||||
@ -760,7 +760,7 @@ export default defineComponent({
|
||||
}
|
||||
|
||||
// 筛选下面被锁定的数据
|
||||
tempData = tempData.filter((item) => item.imageLock == false)
|
||||
tempData = tempData.filter((item) => item.imageLock == null || item.imageLock == false)
|
||||
|
||||
if (tempData.length <= 0) {
|
||||
message.error('下面的数据都被锁定,无法生图,请检查')
|
||||
|
||||
@ -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' }
|
||||
],
|
||||
|
||||
531
src/renderer/src/components/Watermark/GetWaterMaskRectangle.vue
Normal file
531
src/renderer/src/components/Watermark/GetWaterMaskRectangle.vue
Normal file
@ -0,0 +1,531 @@
|
||||
<template>
|
||||
<div style="margin-top: 30px">
|
||||
<div
|
||||
style="
|
||||
margin-top: 10px;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
position: relative;
|
||||
"
|
||||
id="cnvas_ss"
|
||||
>
|
||||
<canvas ref="imageCanvas" />
|
||||
<canvas
|
||||
ref="tempImageCanvas"
|
||||
@mousemove="draw"
|
||||
@mouseup="stopDrawing"
|
||||
@mousedown="startDrawing"
|
||||
style="position: absolute; top: 1; left: 0"
|
||||
/>
|
||||
</div>
|
||||
<div style="margin-top: 10px; display: flex; justify-content: center; align-items: center">
|
||||
<n-button style="margin-left: 10px" @click="undo" type="success">回撤</n-button>
|
||||
<n-button style="margin-left: 10px" @click="Check" type="success">查看去水印效果</n-button>
|
||||
<n-button style="margin-left: 10px" @click="SaveMask" type="success">保存蒙板位置</n-button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { defineComponent, ref, onMounted, h, watch, toRaw } from 'vue'
|
||||
import {
|
||||
NButton,
|
||||
useMessage,
|
||||
NInput,
|
||||
NTag,
|
||||
NImage,
|
||||
NCheckbox,
|
||||
NSlider,
|
||||
NPopover,
|
||||
NProgress,
|
||||
useDialog
|
||||
} from 'naive-ui'
|
||||
import ImageFileSelect from './ImageFileSelect.vue'
|
||||
import ProgressDialog from '../Components/ProgressDialog.vue'
|
||||
import { DEFINE_STRING } from '../../../../define/define_string'
|
||||
import { cloneDeep, isEmpty } from 'lodash'
|
||||
import { useReverseManageStore } from '../../../../stores/reverseManage'
|
||||
|
||||
export default defineComponent({
|
||||
components: {
|
||||
NButton,
|
||||
NInput,
|
||||
NTag,
|
||||
ImageFileSelect,
|
||||
NImage,
|
||||
NCheckbox,
|
||||
NSlider,
|
||||
NPopover,
|
||||
NProgress
|
||||
},
|
||||
props: ['width', 'height'],
|
||||
setup(props) {
|
||||
let message = useMessage()
|
||||
let dialog = useDialog()
|
||||
const imageCanvas = ref(null)
|
||||
let tempImageCanvas = ref(null)
|
||||
let imageContext = ref(null)
|
||||
let reverseManageStore = useReverseManageStore()
|
||||
|
||||
let height = ref(props.height)
|
||||
let width = ref(props.width)
|
||||
let rectPos = ref(null)
|
||||
let drawing = ref(false)
|
||||
let moving = ref(false)
|
||||
let startPos = ref(null)
|
||||
let offset = ref({ x: 0, y: 0 })
|
||||
|
||||
let isDrawing = false
|
||||
let radius = ref(20) // 圆的半径
|
||||
let canvasHistory = [] // 用于保存画布的历史记录(存放位置信息)
|
||||
let step = 0
|
||||
let mask_setting = ref({
|
||||
isRemote: true,
|
||||
localUrl: null,
|
||||
mask_path: null
|
||||
})
|
||||
let progressDialogRef = ref(null)
|
||||
let total = ref(0)
|
||||
let current = ref(0)
|
||||
let dp
|
||||
let image = ref(null)
|
||||
let initImage = null
|
||||
|
||||
onMounted(async () => {
|
||||
if (imageCanvas.value) {
|
||||
const ctx = imageCanvas.value.getContext('2d', { willReadFrequently: true })
|
||||
if (ctx) {
|
||||
imageContext.value = ctx
|
||||
}
|
||||
}
|
||||
|
||||
let div = document.getElementById('cnvas_ss')
|
||||
div.style.height = height.value - 120 + 'px'
|
||||
|
||||
await window.api.GetDefineConfigJsonByProperty(
|
||||
JSON.stringify(['img_base', 'mask_setting', false, null]),
|
||||
(value) => {
|
||||
if (value.code == 0) {
|
||||
message.error(value.message)
|
||||
return
|
||||
}
|
||||
// 判断是不是有数据
|
||||
if (value.data) {
|
||||
mask_setting.value = Object.assign(mask_setting.value, value.data)
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
window.api.setEventListen([DEFINE_STRING.IMG.BATCH_PROCESS_IMAGE_RESULT], (value) => {
|
||||
if (value.code == 0) {
|
||||
message.error(value.message)
|
||||
return
|
||||
}
|
||||
|
||||
total.value = value.data.total
|
||||
current.value = value.data.current
|
||||
if (progressDialogRef.value == null) {
|
||||
return
|
||||
}
|
||||
progressDialogRef.value?.modifyTotal(total.value)
|
||||
progressDialogRef.value?.modifyCurrent(current.value)
|
||||
|
||||
if (value.data.total == value.data.current) {
|
||||
// 销毁进度组件
|
||||
setTimeout(() => {
|
||||
if (dp) {
|
||||
dp.destroy()
|
||||
}
|
||||
}, 1000)
|
||||
}
|
||||
})
|
||||
|
||||
// 加载图片
|
||||
await loadFile(reverseManageStore.selectBookTaskDetail[0].oldImage)
|
||||
})
|
||||
|
||||
let hasImage = false
|
||||
|
||||
/**
|
||||
* 获取图片的高度并且计算适应的宽高
|
||||
* @param image
|
||||
*/
|
||||
function getCurrentWidthHeight(image) {
|
||||
let image_width = 512
|
||||
let image_height = 512
|
||||
// 设置图片的大小
|
||||
if (image) {
|
||||
image_width = image.width
|
||||
image_height = image.height
|
||||
}
|
||||
|
||||
// 判断图片大小,获取当前的传入的window,判断当前的宽度和高度是不是超出了
|
||||
// let min_size = Math.min(width.value,height.value);
|
||||
let image_height_scale = (height.value - 120) / image_height
|
||||
let image_width_scale = (width.value - 120) / image_width
|
||||
|
||||
let scale = Math.min(image_height_scale, image_width_scale)
|
||||
return [image_width * scale, image_height * scale]
|
||||
}
|
||||
|
||||
function getImage(file) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const reader = new FileReader()
|
||||
reader.onload = () => {
|
||||
const img = new Image()
|
||||
img.onload = () => resolve(img)
|
||||
img.onerror = reject
|
||||
img.src = reader.result
|
||||
}
|
||||
reader.onerror = (error) => reject(error)
|
||||
reader.readAsDataURL(file)
|
||||
})
|
||||
}
|
||||
|
||||
async function loadFile(fileUrl) {
|
||||
try {
|
||||
const response = await fetch(fileUrl)
|
||||
const blob = await response.blob()
|
||||
const file = new File([blob], 'file.jpg', { type: blob.type })
|
||||
const img = await getImage(file)
|
||||
image.value = img
|
||||
modifyImage(img)
|
||||
} catch (error) {
|
||||
console.error('Error loading file:', error)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 选择图片初始化
|
||||
* @param image_d
|
||||
*/
|
||||
function modifyImage(image_d) {
|
||||
let [image_width, image_height] = getCurrentWidthHeight(image_d)
|
||||
imageContext.value.canvas.width = image_width
|
||||
imageContext.value.canvas.height = image_height
|
||||
imageContext.value.drawImage(image_d, 0, 0, image_width, image_height)
|
||||
// canvasHistory.push(imageCanvas.value.toDataURL('image/png'))
|
||||
hasImage = true
|
||||
let w = (width.value - image_width - 56) / 2
|
||||
tempImageCanvas.value.style.left = w + 'px'
|
||||
tempImageCanvas.value.width = imageCanvas.value.width
|
||||
tempImageCanvas.value.height = imageCanvas.value.height
|
||||
tempImageCanvas.value.style.zIndex = 100
|
||||
if (!initImage) {
|
||||
initImage = imageCanvas.value.toDataURL('image/png')
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* canvas绘制矩形
|
||||
* @param rect
|
||||
*/
|
||||
const drawRectangle = (canvas, rect, clear = true) => {
|
||||
const ctx = canvas.value.getContext('2d')
|
||||
if (clear) {
|
||||
ctx.clearRect(0, 0, canvas.value.width, canvas.value.height) // 清除之前的绘制
|
||||
}
|
||||
ctx.strokeStyle = 'red' // 设置矩形颜色
|
||||
ctx.lineWidth = 2 // 设置线宽
|
||||
ctx.strokeRect(rect.startX, rect.startY, rect.width, rect.height)
|
||||
}
|
||||
|
||||
const clearCanvas = (canvas) => {
|
||||
const c = canvas.value
|
||||
const context = c.getContext('2d')
|
||||
context.clearRect(0, 0, c.width, c.height)
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取当前鼠标在canvas中的相对位置
|
||||
* @param e
|
||||
*/
|
||||
function getCanvasRelativePosition(e) {
|
||||
const rect = tempImageCanvas.value.getBoundingClientRect()
|
||||
return {
|
||||
x: e.clientX - rect.left,
|
||||
y: e.clientY - rect.top
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 停止画
|
||||
*/
|
||||
function stopDrawing(e) {
|
||||
// 获取矩形
|
||||
if (drawing.value) {
|
||||
const pos = getCanvasRelativePosition(e)
|
||||
rectPos.value = {
|
||||
startX: Math.min(startPos.value.x, pos.x),
|
||||
startY: Math.min(startPos.value.y, pos.y),
|
||||
width: Math.abs(startPos.value.x - pos.x),
|
||||
height: Math.abs(startPos.value.y - pos.y),
|
||||
imageWidth: tempImageCanvas.value.width,
|
||||
imageHeight: tempImageCanvas.value.height
|
||||
}
|
||||
drawRectangle(tempImageCanvas, rectPos.value)
|
||||
|
||||
// 将当前的矩形画到imageCanvas上
|
||||
drawRectangle(imageCanvas, rectPos.value, false)
|
||||
isDrawing = false
|
||||
// canvasHistory.push(imageCanvas.value.toDataURL('image/png'))
|
||||
canvasHistory.push(rectPos.value)
|
||||
console.log(canvasHistory)
|
||||
step++
|
||||
startPos.value = null
|
||||
drawing.value = false
|
||||
}
|
||||
if (moving.value) {
|
||||
moving.value = false
|
||||
// 将当前的矩形画到imageCanvas上
|
||||
drawRectangle(imageCanvas, rectPos.value, false)
|
||||
// canvasHistory.push(imageCanvas.value.toDataURL('image/png'))
|
||||
canvasHistory.push(rectPos.value)
|
||||
}
|
||||
clearCanvas(tempImageCanvas)
|
||||
}
|
||||
|
||||
/**
|
||||
* 填充颜色
|
||||
* @param e
|
||||
*/
|
||||
function draw(e) {
|
||||
if (drawing.value && startPos.value) {
|
||||
const currentPos = getCanvasRelativePosition(e)
|
||||
const rect = {
|
||||
startX: Math.min(startPos.value.x, currentPos.x),
|
||||
startY: Math.min(startPos.value.y, currentPos.y),
|
||||
width: Math.abs(startPos.value.x - currentPos.x),
|
||||
height: Math.abs(startPos.value.y - currentPos.y)
|
||||
}
|
||||
drawRectangle(tempImageCanvas, rect)
|
||||
} else if (moving.value && rectPos.value) {
|
||||
const currentPos = getCanvasRelativePosition(e)
|
||||
const newRectPos = {
|
||||
startX: currentPos.x - offset.value.x,
|
||||
startY: currentPos.y - offset.value.y,
|
||||
width: rectPos.value.width,
|
||||
height: rectPos.value.height,
|
||||
imageWidth: tempImageCanvas.value.width,
|
||||
imageHeight: tempImageCanvas.value.height
|
||||
}
|
||||
rectPos.value = newRectPos
|
||||
drawRectangle(tempImageCanvas, newRectPos)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 开始话
|
||||
* @param event
|
||||
*/
|
||||
function startDrawing(e) {
|
||||
// 判断是移动还是绘制
|
||||
const pos = getCanvasRelativePosition(e)
|
||||
if (
|
||||
rectPos.value &&
|
||||
pos.x >= rectPos.value.startX &&
|
||||
pos.x <= rectPos.value.startX + rectPos.value.width &&
|
||||
pos.y >= rectPos.value.startY &&
|
||||
pos.y <= rectPos.value.startY + rectPos.value.height
|
||||
) {
|
||||
moving.value = true
|
||||
offset.value = { x: pos.x - rectPos.value.startX, y: pos.y - rectPos.value.startY }
|
||||
} else {
|
||||
startPos.value = pos
|
||||
drawing.value = true
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 回撤
|
||||
*/
|
||||
async function undo() {
|
||||
debugger
|
||||
if (step <= 0) {
|
||||
// 如果没有可以撤销的步骤,就清空画布
|
||||
imageContext.value.clearRect(
|
||||
0,
|
||||
0,
|
||||
imageContext.value.canvas.width,
|
||||
imageContext.value.canvas.height
|
||||
)
|
||||
canvasHistory = []
|
||||
} else {
|
||||
step--
|
||||
clearCanvas(imageCanvas)
|
||||
modifyImage(image.value)
|
||||
canvasHistory.pop()
|
||||
// 重新绘制矩形
|
||||
for (let i = 0; i < canvasHistory.length; i++) {
|
||||
const element = canvasHistory[i]
|
||||
drawRectangle(imageCanvas, element, false)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取当前的蒙板
|
||||
*/
|
||||
function getCanvasMaskBase64() {
|
||||
// 处理最后的图片
|
||||
const imageData = imageContext.value.getImageData(
|
||||
0,
|
||||
0,
|
||||
imageContext.value.canvas.width,
|
||||
imageContext.value.canvas.height
|
||||
)
|
||||
const newImageData = imageContext.value.createImageData(imageData.width, imageData.height)
|
||||
|
||||
for (let i = 0; i < imageData.data.length; i += 4) {
|
||||
if (
|
||||
imageData.data[i] === 255 &&
|
||||
imageData.data[i + 1] === 255 &&
|
||||
imageData.data[i + 2] === 0
|
||||
) {
|
||||
// 如果这个像素点的颜色是 rgba(255, 255, 0, 255),就将它设置为白色
|
||||
newImageData.data[i] = newImageData.data[i + 1] = newImageData.data[i + 2] = 255
|
||||
newImageData.data[i + 3] = 255
|
||||
} else {
|
||||
// 否则,就将它设置为黑色
|
||||
newImageData.data[i] = newImageData.data[i + 1] = newImageData.data[i + 2] = 0
|
||||
newImageData.data[i + 3] = 255
|
||||
}
|
||||
}
|
||||
|
||||
// // 创建一个新的 Canvas 对象
|
||||
const newCanvas = document.createElement('canvas')
|
||||
newCanvas.width = imageData.width
|
||||
newCanvas.height = imageData.height
|
||||
const newContext = newCanvas.getContext('2d')
|
||||
|
||||
// 将新的 ImageData 对象绘制到新的 Canvas 对象上
|
||||
newContext.putImageData(newImageData, 0, 0)
|
||||
|
||||
// 将新的 Canvas 对象转换为一个 base64 编码的图片 URL
|
||||
const base64 = newCanvas.toDataURL('image/png')
|
||||
return base64
|
||||
}
|
||||
|
||||
/**
|
||||
* 查看效果
|
||||
*/
|
||||
async function Check() {
|
||||
debugger
|
||||
// 判断当前是不是有蒙板
|
||||
if (canvasHistory.length == 0) {
|
||||
message.error('请先选择去水印的区域')
|
||||
return
|
||||
}
|
||||
|
||||
let res = await window.img.ProcessImageCheck(
|
||||
initImage,
|
||||
cloneDeep(canvasHistory),
|
||||
reverseManageStore.selectBook.id
|
||||
)
|
||||
const arrayBuffer = res.data
|
||||
const blob = new Blob([arrayBuffer], { type: 'image/png' })
|
||||
const reader = new FileReader()
|
||||
reader.onloadend = function () {
|
||||
let img = new Image()
|
||||
img.src = reader.result
|
||||
img.onload = function () {
|
||||
imageContext.value.drawImage(img, 0, 0)
|
||||
canvasHistory.push({
|
||||
startX: 0,
|
||||
startY: 0,
|
||||
width: 0,
|
||||
height: 0,
|
||||
imageWidth: tempImageCanvas.value.width,
|
||||
imageHeight: tempImageCanvas.value.height
|
||||
})
|
||||
step++
|
||||
}
|
||||
}
|
||||
reader.readAsDataURL(blob)
|
||||
}
|
||||
|
||||
async function SaveMask() {
|
||||
debugger
|
||||
// 判断当前是不是又蒙板
|
||||
if (canvasHistory.length <= 1) {
|
||||
message.error('请先选择去水印的区域')
|
||||
return
|
||||
}
|
||||
let maskPos = canvasHistory.filter((item) => item.width > 0 && item.height > 0)
|
||||
if (maskPos.length == 0) {
|
||||
message.error('请先选择去水印的区域,或者选择的水印区域无效')
|
||||
return
|
||||
}
|
||||
// 开始保存
|
||||
let res = await window.db.UpdateBookData(reverseManageStore.selectBook.id, {
|
||||
watermarkPosition: JSON.stringify(maskPos)
|
||||
})
|
||||
if (res.code == 0) {
|
||||
message.error(res.message)
|
||||
return
|
||||
}
|
||||
message.success('水印位置保存成功')
|
||||
}
|
||||
|
||||
/**
|
||||
*查看蒙板
|
||||
*/
|
||||
async function ViewMaskImage() {
|
||||
window.system.OpenFile(mask_setting.value.mask_path)
|
||||
}
|
||||
|
||||
function OpenTeach(type) {
|
||||
switch (type) {
|
||||
case 'lama':
|
||||
window.api.OpenUrl(
|
||||
'https://rvgyir5wk1c.feishu.cn/docx/RZYCdG7ZpoKsIzxBEzccNEIFn8f#QOIRdJQvEouJB7xv0Xqcx3zInyb'
|
||||
)
|
||||
break
|
||||
case 'iopaint':
|
||||
window.api.OpenUrl(
|
||||
'https://rvgyir5wk1c.feishu.cn/docx/RZYCdG7ZpoKsIzxBEzccNEIFn8f#MLoUdn5cfo6NJTxL0xTcUGyLn43'
|
||||
)
|
||||
default:
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
modifyImage,
|
||||
imageCanvas,
|
||||
tempImageCanvas,
|
||||
height,
|
||||
width,
|
||||
stopDrawing,
|
||||
startDrawing,
|
||||
startPos,
|
||||
draw,
|
||||
undo,
|
||||
Check,
|
||||
offset,
|
||||
SaveMask,
|
||||
mask_setting,
|
||||
ViewMaskImage,
|
||||
radius,
|
||||
progressDialogRef,
|
||||
total,
|
||||
current,
|
||||
OpenTeach,
|
||||
reverseManageStore,
|
||||
image
|
||||
}
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.url_class {
|
||||
color: #e18a3b;
|
||||
}
|
||||
|
||||
.url_class:hover {
|
||||
color: brown;
|
||||
cursor: pointer;
|
||||
}
|
||||
</style>
|
||||
@ -43,6 +43,7 @@ export default defineComponent({
|
||||
}
|
||||
|
||||
async function selectImage(value) {
|
||||
debugger
|
||||
let img = await getBase64(value.file.file)
|
||||
props.modifyImage(img)
|
||||
}
|
||||
|
||||
@ -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',
|
||||
|
||||
13
vue.config.js
Normal file
13
vue.config.js
Normal file
@ -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
|
||||
})
|
||||
}
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user