3.1.9 2024.10.28

1. (聚合推文)MJ反推、SD反推 添加剪映分镜
2. (聚合推文)完善SD反推分类(界面同MJ反推,些许不一致)
3. (聚合推文)完成一键合成视频(单个和批量)
4. 修改聚合推文进入界面小说批次任务表格样式
5. (聚合推文)完善一键重置
6. (聚合推文)完善一键删除
This commit is contained in:
lq1405 2024-10-28 18:38:11 +08:00
parent 0c5988ed41
commit 22cfe65dde
78 changed files with 4982 additions and 2440 deletions

1
.gitignore vendored
View File

@ -40,3 +40,4 @@ resources/scripts/db/*
resources/scripts/localWhisper/.venv/*
resources/scripts/localWhisper/_internal/*
resources/scripts/localWhisper/local_whisper.exe
resources/scripts/joyCaption/.venv/*

View File

@ -2,16 +2,17 @@ import { resolve } from 'path'
import { defineConfig, externalizeDepsPlugin, bytecodePlugin } from 'electron-vite'
import vue from '@vitejs/plugin-vue'
import Jsx from '@vitejs/plugin-vue-jsx'
import tsconfigPaths from 'vite-tsconfig-paths';
export default defineConfig({
main: {
plugins: [externalizeDepsPlugin(), bytecodePlugin()]
plugins: [externalizeDepsPlugin(), bytecodePlugin(),tsconfigPaths()]
},
discord: {
plugins: [externalizeDepsPlugin(), bytecodePlugin()]
plugins: [externalizeDepsPlugin(), bytecodePlugin(),tsconfigPaths()]
},
preload: {
plugins: [externalizeDepsPlugin(), bytecodePlugin()]
plugins: [externalizeDepsPlugin(), bytecodePlugin(),tsconfigPaths()]
},
renderer: {
resolve: {

50
package-lock.json generated
View File

@ -1,12 +1,12 @@
{
"name": "laitool",
"version": "3.1.8",
"version": "3.1.9",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "laitool",
"version": "3.1.8",
"version": "3.1.9",
"hasInstallScript": true,
"dependencies": {
"@alicloud/alimt20181012": "^1.2.0",
@ -39,6 +39,7 @@
"systeminformation": "^5.22.10",
"tencentcloud-sdk-nodejs": "^4.0.821",
"uuid": "^9.0.1",
"vite-tsconfig-paths": "^5.0.1",
"vue-router": "^4.2.5",
"wav-file-info": "^0.0.10",
"winston": "^3.13.0",
@ -5084,6 +5085,12 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/globrex": {
"version": "0.1.2",
"resolved": "https://registry.npmmirror.com/globrex/-/globrex-0.1.2.tgz",
"integrity": "sha512-uHJgbwAMwNFf5mLst7IWLNg14x1CkeqglJb/K3doi4dw6q2IvAAmM/Y81kevy83wP+Sst+nutFTYOGg3d1lsxg==",
"license": "MIT"
},
"node_modules/gopd": {
"version": "1.0.1",
"license": "MIT",
@ -10341,6 +10348,26 @@
"typescript": ">=4.2.0"
}
},
"node_modules/tsconfck": {
"version": "3.1.4",
"resolved": "https://registry.npmmirror.com/tsconfck/-/tsconfck-3.1.4.tgz",
"integrity": "sha512-kdqWFGVJqe+KGYvlSO9NIaWn9jT1Ny4oKVzAJsKii5eoE9snzTJzL4+MMVOMn+fikWGFmKEylcXL710V/kIPJQ==",
"license": "MIT",
"bin": {
"tsconfck": "bin/tsconfck.js"
},
"engines": {
"node": "^18 || >=20"
},
"peerDependencies": {
"typescript": "^5.0.0"
},
"peerDependenciesMeta": {
"typescript": {
"optional": true
}
}
},
"node_modules/tslib": {
"version": "2.6.2",
"license": "0BSD",
@ -10532,6 +10559,25 @@
}
}
},
"node_modules/vite-tsconfig-paths": {
"version": "5.0.1",
"resolved": "https://registry.npmmirror.com/vite-tsconfig-paths/-/vite-tsconfig-paths-5.0.1.tgz",
"integrity": "sha512-yqwv+LstU7NwPeNqajZzLEBVpUFU6Dugtb2P84FXuvaoYA+/70l9MHE+GYfYAycVyPSDYZ7mjOFuYBRqlEpTig==",
"license": "MIT",
"dependencies": {
"debug": "^4.1.1",
"globrex": "^0.1.2",
"tsconfck": "^3.0.3"
},
"peerDependencies": {
"vite": "*"
},
"peerDependenciesMeta": {
"vite": {
"optional": true
}
}
},
"node_modules/vooks": {
"version": "0.2.12",
"dev": true,

View File

@ -1,6 +1,6 @@
{
"name": "laitool",
"version": "3.1.8",
"version": "3.1.9",
"description": "An AI tool for image processing, video processing, and other functions.",
"main": "./out/main/index.js",
"author": "laitool.cn",
@ -47,6 +47,7 @@
"systeminformation": "^5.22.10",
"tencentcloud-sdk-nodejs": "^4.0.821",
"uuid": "^9.0.1",
"vite-tsconfig-paths": "^5.0.1",
"vue-router": "^4.2.5",
"wav-file-info": "^0.0.10",
"winston": "^3.13.0",

View File

@ -16,9 +16,33 @@ if len(sys.argv) < 2:
sys.argv = [
"C:\\Users\\27698\\Desktop\\LAITool\\resources\\scripts\\Lai.exe",
"-c",
"D:/来推项目集/7.4/娱乐:江湖大哥退休,去拍电影/scripts/output_crop_00001.json",
"NVIDIA",
"C:/Users/27698/Desktop/LAITool/project/73bca563-fd8f-42e3-b08b-e4b2ecbd0d71/scripts/output_00008_video_config.json",
"OTHER",
]
# sys.argv = [
# "C:\\Users\\27698\\Desktop\\LAITool\\resources\\scripts\\Lai.exe",
# "-p",
# "C:/Users/27698/Desktop/LAITool/resources/config/sd_config.json",
# "input",
# "C:\\Users\\27698\\Desktop\\测试\\123",
# ]
# sys.argv = [
# "C:\\Users\\27698\\Desktop\\LAITool\\resources\\scripts\\Lai.exe",
# "-pf",
# "C:\\Users\\27698\\Desktop\\测试\\123\\tmp\\input_crop",
# ]
# sys.argv = [
# "C:\\Users\\27698\\Desktop\\LAITool\\resources\\scripts\\Lai.exe",
# "-ps",
# "C:\\Users\\27698\\Desktop\\测试\\123\\tmp\\input_crop\\00008.png",
# ]
# sys.argv = [
# "C:\\Users\\27698\\Desktop\\LAITool\\resources\\scripts\\Lai.exe",
# "-pt",
# "C:\\Users\\27698\\Desktop\\测试\\123\\tmp\\input_crop\\1.txt",
# ]
print(sys.argv)
@ -73,11 +97,26 @@ elif sys.argv[1] == "-f":
with open(sys.argv[2], "w", encoding="utf-8") as file:
json.dump(data, file, ensure_ascii=False, indent=4)
# 反推提示词
# 反推提示词指定项目下面的input_crop文件夹
elif sys.argv[1] == "-p":
Push_back_Prompt.init(sys.argv[2], sys.argv[3], sys.argv[4])
pass
# 反推指定的文件夹
elif sys.argv[1] == "-pf":
Push_back_Prompt.getAssignDir(sys.argv[2])
pass
# 反推指定的图片文件
elif sys.argv[1] == "-ps":
Push_back_Prompt.getAssignImage(sys.argv[2])
pass
# 反推指定的图片文件
elif sys.argv[1] == "-pt":
Push_back_Prompt.getAssignTxt(sys.argv[2])
pass
elif sys.argv[1] == "-ka":
shotSplit.get_fram(sys.argv[2], sys.argv[3], sys.argv[4])
pass

View File

@ -148,10 +148,11 @@ class WaifuDiffusionInterrogator(Interrogator):
Dict[str, float], Dict[str, float] # rating confidents # tag confidents
]:
# init model
if not hasattr(self, 'model') or self.model is None:
if not hasattr(self, "model") or self.model is None:
model_path = os.path.join(cript_directory, "model/tag/model.onnx")
tags_path = os.path.join(cript_directory, "model/tag/selected_tags.csv")
from onnxruntime import InferenceSession
providers = ["CUDAExecutionProvider", "CPUExecutionProvider"]
self.model = InferenceSession(str(model_path), providers=providers)
print(f"{model_path} 读取 {self.name}模型")
@ -223,7 +224,89 @@ def filter_action(tag_actions: [], tags: []):
return action_tags, other_tags
def init(sd_setting,m,project_path):
def getAssignTxt(txtPath):
if not os.path.exists(txtPath):
os.makedirs(txtPath)
# load model
model = WaifuDiffusionInterrogator(
"wd14-convnextv2-v2",
repo_id="SmilingWolf/wd-v1-4-convnextv2-tagger-v2",
revision="v2.0",
)
frame_files = []
with open(txtPath, 'r', encoding='utf-8') as file:
for line in file:
frame_files.append(line.strip()) # 使用 strip() 去除每行的换行符和多余的空白
# 轮询开始输出
frame_files.sort()
for frame_file in frame_files:
txt = getTags(model, frame_file)
# tags = txt.split(",")
# save tag
txt_file = os.path.join(os.path.dirname(frame_file), f"{Path(frame_file).stem}.txt")
with open(txt_file, "w", encoding="utf-8") as tags:
tags.write(txt)
print(f"{frame_file} 提示词反推完成")
sys.stdout.flush()
def getAssignImage(imagePath):
if not os.path.exists(imagePath):
os.makedirs(imagePath)
# load model
model = WaifuDiffusionInterrogator(
"wd14-convnextv2-v2",
repo_id="SmilingWolf/wd-v1-4-convnextv2-tagger-v2",
revision="v2.0",
)
txt = getTags(model, imagePath)
# tags = txt.split(",")
# save tag
txt_file = os.path.join(os.path.dirname(imagePath), f"{Path(imagePath).stem}.txt")
with open(txt_file, "w", encoding="utf-8") as tags:
tags.write(txt)
print(f"{imagePath} 提示词反推完成")
sys.stdout.flush()
def getAssignDir(imagePath):
if not os.path.exists(imagePath):
os.makedirs(imagePath)
# load model
model = WaifuDiffusionInterrogator(
"wd14-convnextv2-v2",
repo_id="SmilingWolf/wd-v1-4-convnextv2-tagger-v2",
revision="v2.0",
)
# 轮询开始输出
frame_files = [f for f in os.listdir(imagePath) if f.endswith(".png")]
frame_files.sort()
for frame in frame_files:
frame_file = os.path.join(imagePath, frame)
txt = getTags(model, frame_file)
# tags = txt.split(",")
# save tag
txt_file = os.path.join(imagePath, f"{Path(frame_file).stem}.txt")
with open(txt_file, "w", encoding="utf-8") as tags:
tags.write(txt)
print(f"{frame} 提示词反推完成")
sys.stdout.flush()
def init(sd_setting, m, project_path):
try:
setting_json = public_tools.read_config(sd_setting, webui=False)
except Exception as e:

Binary file not shown.

View File

@ -0,0 +1,29 @@
Collecting accelerate
Downloading accelerate-1.0.1-py3-none-any.whl.metadata (19 kB)
Requirement already satisfied: numpy<3.0.0,>=1.17 in c:\users\27698\desktop\laitool\resources\scripts\joycaption\.venv\lib\site-packages (from accelerate) (2.1.2)
Requirement already satisfied: packaging>=20.0 in c:\users\27698\desktop\laitool\resources\scripts\joycaption\.venv\lib\site-packages (from accelerate) (24.1)
Collecting psutil (from accelerate)
Downloading psutil-6.1.0-cp37-abi3-win_amd64.whl.metadata (23 kB)
Requirement already satisfied: pyyaml in c:\users\27698\desktop\laitool\resources\scripts\joycaption\.venv\lib\site-packages (from accelerate) (6.0.2)
Requirement already satisfied: torch>=1.10.0 in c:\users\27698\desktop\laitool\resources\scripts\joycaption\.venv\lib\site-packages (from accelerate) (2.5.0)
Requirement already satisfied: huggingface-hub>=0.21.0 in c:\users\27698\desktop\laitool\resources\scripts\joycaption\.venv\lib\site-packages (from accelerate) (0.26.1)
Requirement already satisfied: safetensors>=0.4.3 in c:\users\27698\desktop\laitool\resources\scripts\joycaption\.venv\lib\site-packages (from accelerate) (0.4.5)
Requirement already satisfied: filelock in c:\users\27698\desktop\laitool\resources\scripts\joycaption\.venv\lib\site-packages (from huggingface-hub>=0.21.0->accelerate) (3.16.1)
Requirement already satisfied: fsspec>=2023.5.0 in c:\users\27698\desktop\laitool\resources\scripts\joycaption\.venv\lib\site-packages (from huggingface-hub>=0.21.0->accelerate) (2024.10.0)
Requirement already satisfied: requests in c:\users\27698\desktop\laitool\resources\scripts\joycaption\.venv\lib\site-packages (from huggingface-hub>=0.21.0->accelerate) (2.32.3)
Requirement already satisfied: tqdm>=4.42.1 in c:\users\27698\desktop\laitool\resources\scripts\joycaption\.venv\lib\site-packages (from huggingface-hub>=0.21.0->accelerate) (4.66.5)
Requirement already satisfied: typing-extensions>=3.7.4.3 in c:\users\27698\desktop\laitool\resources\scripts\joycaption\.venv\lib\site-packages (from huggingface-hub>=0.21.0->accelerate) (4.12.2)
Requirement already satisfied: networkx in c:\users\27698\desktop\laitool\resources\scripts\joycaption\.venv\lib\site-packages (from torch>=1.10.0->accelerate) (3.4.2)
Requirement already satisfied: jinja2 in c:\users\27698\desktop\laitool\resources\scripts\joycaption\.venv\lib\site-packages (from torch>=1.10.0->accelerate) (3.1.4)
Requirement already satisfied: sympy==1.13.1 in c:\users\27698\desktop\laitool\resources\scripts\joycaption\.venv\lib\site-packages (from torch>=1.10.0->accelerate) (1.13.1)
Requirement already satisfied: mpmath<1.4,>=1.1.0 in c:\users\27698\desktop\laitool\resources\scripts\joycaption\.venv\lib\site-packages (from sympy==1.13.1->torch>=1.10.0->accelerate) (1.3.0)
Requirement already satisfied: colorama in c:\users\27698\desktop\laitool\resources\scripts\joycaption\.venv\lib\site-packages (from tqdm>=4.42.1->huggingface-hub>=0.21.0->accelerate) (0.4.6)
Requirement already satisfied: MarkupSafe>=2.0 in c:\users\27698\desktop\laitool\resources\scripts\joycaption\.venv\lib\site-packages (from jinja2->torch>=1.10.0->accelerate) (3.0.2)
Requirement already satisfied: charset-normalizer<4,>=2 in c:\users\27698\desktop\laitool\resources\scripts\joycaption\.venv\lib\site-packages (from requests->huggingface-hub>=0.21.0->accelerate) (3.4.0)
Requirement already satisfied: idna<4,>=2.5 in c:\users\27698\desktop\laitool\resources\scripts\joycaption\.venv\lib\site-packages (from requests->huggingface-hub>=0.21.0->accelerate) (3.10)
Requirement already satisfied: urllib3<3,>=1.21.1 in c:\users\27698\desktop\laitool\resources\scripts\joycaption\.venv\lib\site-packages (from requests->huggingface-hub>=0.21.0->accelerate) (2.2.3)
Requirement already satisfied: certifi>=2017.4.17 in c:\users\27698\desktop\laitool\resources\scripts\joycaption\.venv\lib\site-packages (from requests->huggingface-hub>=0.21.0->accelerate) (2024.8.30)
Downloading accelerate-1.0.1-py3-none-any.whl (330 kB)
Downloading psutil-6.1.0-cp37-abi3-win_amd64.whl (254 kB)
Installing collected packages: psutil, accelerate
Successfully installed accelerate-1.0.1 psutil-6.1.0

View File

View File

@ -0,0 +1,336 @@
import spaces
import gradio as gr
from huggingface_hub import InferenceClient
from torch import nn
from transformers import AutoModel, AutoProcessor, AutoTokenizer, PreTrainedTokenizer, PreTrainedTokenizerFast, AutoModelForCausalLM
from pathlib import Path
import torch
import torch.amp.autocast_mode
from PIL import Image
import os
import torchvision.transforms.functional as TVF
CLIP_PATH = "google/siglip-so400m-patch14-384"
CHECKPOINT_PATH = Path("cgrkzexw-599808")
TITLE = "<h1><center>JoyCaption Alpha Two (2024-09-26a)</center></h1>"
CAPTION_TYPE_MAP = {
"Descriptive": [
"Write a descriptive caption for this image in a formal tone.",
"Write a descriptive caption for this image in a formal tone within {word_count} words.",
"Write a {length} descriptive caption for this image in a formal tone.",
],
"Descriptive (Informal)": [
"Write a descriptive caption for this image in a casual tone.",
"Write a descriptive caption for this image in a casual tone within {word_count} words.",
"Write a {length} descriptive caption for this image in a casual tone.",
],
"Training Prompt": [
"Write a stable diffusion prompt for this image.",
"Write a stable diffusion prompt for this image within {word_count} words.",
"Write a {length} stable diffusion prompt for this image.",
],
"MidJourney": [
"Write a MidJourney prompt for this image.",
"Write a MidJourney prompt for this image within {word_count} words.",
"Write a {length} MidJourney prompt for this image.",
],
"Booru tag list": [
"Write a list of Booru tags for this image.",
"Write a list of Booru tags for this image within {word_count} words.",
"Write a {length} list of Booru tags for this image.",
],
"Booru-like tag list": [
"Write a list of Booru-like tags for this image.",
"Write a list of Booru-like tags for this image within {word_count} words.",
"Write a {length} list of Booru-like tags for this image.",
],
"Art Critic": [
"Analyze this image like an art critic would with information about its composition, style, symbolism, the use of color, light, any artistic movement it might belong to, etc.",
"Analyze this image like an art critic would with information about its composition, style, symbolism, the use of color, light, any artistic movement it might belong to, etc. Keep it within {word_count} words.",
"Analyze this image like an art critic would with information about its composition, style, symbolism, the use of color, light, any artistic movement it might belong to, etc. Keep it {length}.",
],
"Product Listing": [
"Write a caption for this image as though it were a product listing.",
"Write a caption for this image as though it were a product listing. Keep it under {word_count} words.",
"Write a {length} caption for this image as though it were a product listing.",
],
"Social Media Post": [
"Write a caption for this image as if it were being used for a social media post.",
"Write a caption for this image as if it were being used for a social media post. Limit the caption to {word_count} words.",
"Write a {length} caption for this image as if it were being used for a social media post.",
],
}
HF_TOKEN = os.environ.get("HF_TOKEN", None)
class ImageAdapter(nn.Module):
def __init__(self, input_features: int, output_features: int, ln1: bool, pos_emb: bool, num_image_tokens: int, deep_extract: bool):
super().__init__()
self.deep_extract = deep_extract
if self.deep_extract:
input_features = input_features * 5
self.linear1 = nn.Linear(input_features, output_features)
self.activation = nn.GELU()
self.linear2 = nn.Linear(output_features, output_features)
self.ln1 = nn.Identity() if not ln1 else nn.LayerNorm(input_features)
self.pos_emb = None if not pos_emb else nn.Parameter(torch.zeros(num_image_tokens, input_features))
# Other tokens (<|image_start|>, <|image_end|>, <|eot_id|>)
self.other_tokens = nn.Embedding(3, output_features)
self.other_tokens.weight.data.normal_(mean=0.0, std=0.02) # Matches HF's implementation of llama3
def forward(self, vision_outputs: torch.Tensor):
if self.deep_extract:
x = torch.concat((
vision_outputs[-2],
vision_outputs[3],
vision_outputs[7],
vision_outputs[13],
vision_outputs[20],
), dim=-1)
assert len(x.shape) == 3, f"Expected 3, got {len(x.shape)}" # batch, tokens, features
assert x.shape[-1] == vision_outputs[-2].shape[-1] * 5, f"Expected {vision_outputs[-2].shape[-1] * 5}, got {x.shape[-1]}"
else:
x = vision_outputs[-2]
x = self.ln1(x)
if self.pos_emb is not None:
assert x.shape[-2:] == self.pos_emb.shape, f"Expected {self.pos_emb.shape}, got {x.shape[-2:]}"
x = x + self.pos_emb
x = self.linear1(x)
x = self.activation(x)
x = self.linear2(x)
# <|image_start|>, IMAGE, <|image_end|>
other_tokens = self.other_tokens(torch.tensor([0, 1], device=self.other_tokens.weight.device).expand(x.shape[0], -1))
assert other_tokens.shape == (x.shape[0], 2, x.shape[2]), f"Expected {(x.shape[0], 2, x.shape[2])}, got {other_tokens.shape}"
x = torch.cat((other_tokens[:, 0:1], x, other_tokens[:, 1:2]), dim=1)
return x
def get_eot_embedding(self):
return self.other_tokens(torch.tensor([2], device=self.other_tokens.weight.device)).squeeze(0)
# Load CLIP
print("Loading CLIP")
clip_processor = AutoProcessor.from_pretrained(CLIP_PATH)
clip_model = AutoModel.from_pretrained(CLIP_PATH)
clip_model = clip_model.vision_model
assert (CHECKPOINT_PATH / "clip_model.pt").exists()
print("Loading VLM's custom vision model")
checkpoint = torch.load(CHECKPOINT_PATH / "clip_model.pt", map_location='cpu')
checkpoint = {k.replace("_orig_mod.module.", ""): v for k, v in checkpoint.items()}
clip_model.load_state_dict(checkpoint)
del checkpoint
clip_model.eval()
clip_model.requires_grad_(False)
clip_model.to("cuda")
# Tokenizer
print("Loading tokenizer")
tokenizer = AutoTokenizer.from_pretrained(CHECKPOINT_PATH / "text_model", use_fast=True)
assert isinstance(tokenizer, PreTrainedTokenizer) or isinstance(tokenizer, PreTrainedTokenizerFast), f"Tokenizer is of type {type(tokenizer)}"
# LLM
print("Loading LLM")
print("Loading VLM's custom text model")
text_model = AutoModelForCausalLM.from_pretrained(CHECKPOINT_PATH / "text_model", device_map=0, torch_dtype=torch.bfloat16)
text_model.eval()
# Image Adapter
print("Loading image adapter")
image_adapter = ImageAdapter(clip_model.config.hidden_size, text_model.config.hidden_size, False, False, 38, False)
image_adapter.load_state_dict(torch.load(CHECKPOINT_PATH / "image_adapter.pt", map_location="cpu"))
image_adapter.eval()
image_adapter.to("cuda")
@spaces.GPU()
@torch.no_grad()
def stream_chat(input_image: Image.Image, caption_type: str, caption_length: str | int, extra_options: list[str], name_input: str, custom_prompt: str) -> tuple[str, str]:
torch.cuda.empty_cache()
# 'any' means no length specified
length = None if caption_length == "any" else caption_length
if isinstance(length, str):
try:
length = int(length)
except ValueError:
pass
# Build prompt
if length is None:
map_idx = 0
elif isinstance(length, int):
map_idx = 1
elif isinstance(length, str):
map_idx = 2
else:
raise ValueError(f"Invalid caption length: {length}")
prompt_str = CAPTION_TYPE_MAP[caption_type][map_idx]
# Add extra options
if len(extra_options) > 0:
prompt_str += " " + " ".join(extra_options)
# Add name, length, word_count
prompt_str = prompt_str.format(name=name_input, length=caption_length, word_count=caption_length)
if custom_prompt.strip() != "":
prompt_str = custom_prompt.strip()
# For debugging
print(f"Prompt: {prompt_str}")
# Preprocess image
# NOTE: I found the default processor for so400M to have worse results than just using PIL directly
#image = clip_processor(images=input_image, return_tensors='pt').pixel_values
image = input_image.resize((384, 384), Image.LANCZOS)
pixel_values = TVF.pil_to_tensor(image).unsqueeze(0) / 255.0
pixel_values = TVF.normalize(pixel_values, [0.5], [0.5])
pixel_values = pixel_values.to('cuda')
# Embed image
# This results in Batch x Image Tokens x Features
with torch.amp.autocast_mode.autocast('cuda', enabled=True):
vision_outputs = clip_model(pixel_values=pixel_values, output_hidden_states=True)
embedded_images = image_adapter(vision_outputs.hidden_states)
embedded_images = embedded_images.to('cuda')
# Build the conversation
convo = [
{
"role": "system",
"content": "You are a helpful image captioner.",
},
{
"role": "user",
"content": prompt_str,
},
]
# Format the conversation
convo_string = tokenizer.apply_chat_template(convo, tokenize = False, add_generation_prompt = True)
assert isinstance(convo_string, str)
# Tokenize the conversation
# prompt_str is tokenized separately so we can do the calculations below
convo_tokens = tokenizer.encode(convo_string, return_tensors="pt", add_special_tokens=False, truncation=False)
prompt_tokens = tokenizer.encode(prompt_str, return_tensors="pt", add_special_tokens=False, truncation=False)
assert isinstance(convo_tokens, torch.Tensor) and isinstance(prompt_tokens, torch.Tensor)
convo_tokens = convo_tokens.squeeze(0) # Squeeze just to make the following easier
prompt_tokens = prompt_tokens.squeeze(0)
# Calculate where to inject the image
eot_id_indices = (convo_tokens == tokenizer.convert_tokens_to_ids("<|eot_id|>")).nonzero(as_tuple=True)[0].tolist()
assert len(eot_id_indices) == 2, f"Expected 2 <|eot_id|> tokens, got {len(eot_id_indices)}"
preamble_len = eot_id_indices[1] - prompt_tokens.shape[0] # Number of tokens before the prompt
# Embed the tokens
convo_embeds = text_model.model.embed_tokens(convo_tokens.unsqueeze(0).to('cuda'))
# Construct the input
input_embeds = torch.cat([
convo_embeds[:, :preamble_len], # Part before the prompt
embedded_images.to(dtype=convo_embeds.dtype), # Image
convo_embeds[:, preamble_len:], # The prompt and anything after it
], dim=1).to('cuda')
input_ids = torch.cat([
convo_tokens[:preamble_len].unsqueeze(0),
torch.zeros((1, embedded_images.shape[1]), dtype=torch.long), # Dummy tokens for the image (TODO: Should probably use a special token here so as not to confuse any generation algorithms that might be inspecting the input)
convo_tokens[preamble_len:].unsqueeze(0),
], dim=1).to('cuda')
attention_mask = torch.ones_like(input_ids)
# Debugging
print(f"Input to model: {repr(tokenizer.decode(input_ids[0]))}")
#generate_ids = text_model.generate(input_ids, inputs_embeds=inputs_embeds, attention_mask=attention_mask, max_new_tokens=300, do_sample=False, suppress_tokens=None)
#generate_ids = text_model.generate(input_ids, inputs_embeds=inputs_embeds, attention_mask=attention_mask, max_new_tokens=300, do_sample=True, top_k=10, temperature=0.5, suppress_tokens=None)
generate_ids = text_model.generate(input_ids, inputs_embeds=input_embeds, attention_mask=attention_mask, max_new_tokens=300, do_sample=True, suppress_tokens=None) # Uses the default which is temp=0.6, top_p=0.9
# Trim off the prompt
generate_ids = generate_ids[:, input_ids.shape[1]:]
if generate_ids[0][-1] == tokenizer.eos_token_id or generate_ids[0][-1] == tokenizer.convert_tokens_to_ids("<|eot_id|>"):
generate_ids = generate_ids[:, :-1]
caption = tokenizer.batch_decode(generate_ids, skip_special_tokens=False, clean_up_tokenization_spaces=False)[0]
return prompt_str, caption.strip()
with gr.Blocks() as demo:
gr.HTML(TITLE)
with gr.Row():
with gr.Column():
input_image = gr.Image(type="pil", label="Input Image")
caption_type = gr.Dropdown(
choices=["Descriptive", "Descriptive (Informal)", "Training Prompt", "MidJourney", "Booru tag list", "Booru-like tag list", "Art Critic", "Product Listing", "Social Media Post"],
label="Caption Type",
value="Descriptive",
)
caption_length = gr.Dropdown(
choices=["any", "very short", "short", "medium-length", "long", "very long"] +
[str(i) for i in range(20, 261, 10)],
label="Caption Length",
value="long",
)
extra_options = gr.CheckboxGroup(
choices=[
"If there is a person/character in the image you must refer to them as {name}.",
"Do NOT include information about people/characters that cannot be changed (like ethnicity, gender, etc), but do still include changeable attributes (like hair style).",
"Include information about lighting.",
"Include information about camera angle.",
"Include information about whether there is a watermark or not.",
"Include information about whether there are JPEG artifacts or not.",
"If it is a photo you MUST include information about what camera was likely used and details such as aperture, shutter speed, ISO, etc.",
"Do NOT include anything sexual; keep it PG.",
"Do NOT mention the image's resolution.",
"You MUST include information about the subjective aesthetic quality of the image from low to very high.",
"Include information on the image's composition style, such as leading lines, rule of thirds, or symmetry.",
"Do NOT mention any text that is in the image.",
"Specify the depth of field and whether the background is in focus or blurred.",
"If applicable, mention the likely use of artificial or natural lighting sources.",
"Do NOT use any ambiguous language.",
"Include whether the image is sfw, suggestive, or nsfw.",
"ONLY describe the most important elements of the image."
],
label="Extra Options"
)
name_input = gr.Textbox(label="Person/Character Name (if applicable)")
gr.Markdown("**Note:** Name input is only used if an Extra Option is selected that requires it.")
custom_prompt = gr.Textbox(label="Custom Prompt (optional, will override all other settings)")
gr.Markdown("**Note:** Alpha Two is not a general instruction follower and will not follow prompts outside its training data well. Use this feature with caution.")
run_button = gr.Button("Caption")
with gr.Column():
output_prompt = gr.Textbox(label="Prompt that was used")
output_caption = gr.Textbox(label="Caption")
run_button.click(fn=stream_chat, inputs=[input_image, caption_type, caption_length, extra_options, name_input, custom_prompt], outputs=[output_prompt, output_caption])
if __name__ == "__main__":
demo.launch()

View File

@ -0,0 +1,423 @@
#!/usr/bin/env python3
"""
Use JoyCaption to caption images.
"""
import argparse
import dataclasses
import json
import logging
import os
import random
from pathlib import Path
import PIL.Image
import torch
import torch.amp
import torchvision.transforms.functional as TVF
from PIL import Image
from torch.utils.data import DataLoader, Dataset
from tqdm import tqdm
from transformers import (
AutoTokenizer,
LlavaForConditionalGeneration,
PreTrainedTokenizer,
PreTrainedTokenizerFast,
)
def none_or_type(value, desired_type):
if value == "None":
return None
return desired_type(value)
parser = argparse.ArgumentParser()
parser.add_argument("--glob", type=str, help="Glob pattern to find images")
parser.add_argument("--filelist", type=str, help="File containing list of images")
parser.add_argument("--prompt", type=str, help="Prompt to use")
parser.add_argument(
"--prompt-file", type=str, help="JSON file containing prompts to use"
)
parser.add_argument("--batch-size", type=int, default=1, help="Batch size")
parser.add_argument(
"--greedy", action="store_true", help="Use greedy decoding instead of sampling"
)
parser.add_argument(
"--temperature", type=float, default=0.6, help="Sampling temperature"
)
parser.add_argument(
"--top-p", type=lambda x: none_or_type(x, float), default=0.9, help="Top-p sampling"
)
parser.add_argument(
"--top-k", type=lambda x: none_or_type(x, int), default=None, help="Top-k sampling"
)
parser.add_argument(
"--max-new-tokens",
type=int,
default=256,
help="Maximum length of the generated caption (in tokens)",
)
parser.add_argument(
"--num-workers",
type=int,
default=4,
help="Number of workers loading images in parallel",
)
parser.add_argument(
"--model",
type=str,
default="fancyfeast/llama-joycaption-alpha-two-hf-llava",
help="Model to use",
)
PIL.Image.MAX_IMAGE_PIXELS = 933120000 # Quiets Pillow from giving warnings on really large images (WARNING: Exposes a risk of DoS from malicious images)
@dataclasses.dataclass
class Prompt:
prompt: str
weight: float
@torch.no_grad()
def main():
# Logging
logging.basicConfig(
level=logging.INFO, format="%(asctime)s %(levelname)s: %(message)s"
)
# Parse arguments
args = parser.parse_args()
logging.info(f"Arguments: {args}")
args.prompt = "Please describe the image."
# Make sure we have a prompt or a prompt file
prompts = parse_prompts(args.prompt, args.prompt_file)
args.filelist = (
"C:\\Users\\27698\\Desktop\\node\\12\\12.txt"
)
# Find the images
image_paths = find_images(args.glob, args.filelist)
if len(image_paths) == 0:
logging.warning("No images found")
return
logging.info(f"Found {len(image_paths)} images")
# Ignore all images that already have captions
image_paths = [
path for path in image_paths if not Path(path).with_suffix(".txt").exists()
]
# Load JoyCaption
tokenizer = AutoTokenizer.from_pretrained(args.model, use_fast=True)
assert isinstance(tokenizer, PreTrainedTokenizer) or isinstance(
tokenizer, PreTrainedTokenizerFast
), f"Tokenizer is of type {type(tokenizer)}"
llava_model = LlavaForConditionalGeneration.from_pretrained(
args.model, torch_dtype="bfloat16"
)
assert isinstance(llava_model, LlavaForConditionalGeneration)
dataset = ImageDataset(
prompts,
image_paths,
tokenizer,
llava_model.config.image_token_index,
llava_model.config.image_seq_length,
)
dataloader = DataLoader(
dataset,
collate_fn=dataset.collate_fn,
num_workers=args.num_workers,
shuffle=False,
drop_last=False,
batch_size=args.batch_size,
)
end_of_header_id = tokenizer.convert_tokens_to_ids("<|end_header_id|>")
end_of_turn_id = tokenizer.convert_tokens_to_ids("<|eot_id|>")
assert isinstance(end_of_header_id, int) and isinstance(end_of_turn_id, int)
pbar = tqdm(total=len(image_paths), desc="Captioning images...", dynamic_ncols=True)
for batch in dataloader:
vision_dtype = (
llava_model.vision_tower.vision_model.embeddings.patch_embedding.weight.dtype
)
vision_device = (
llava_model.vision_tower.vision_model.embeddings.patch_embedding.weight.device
)
language_device = (
llava_model.language_model.get_input_embeddings().weight.device
)
# Move to GPU
pixel_values = batch["pixel_values"].to(vision_device, non_blocking=True)
input_ids = batch["input_ids"].to(language_device, non_blocking=True)
attention_mask = batch["attention_mask"].to(language_device, non_blocking=True)
# Normalize the image
pixel_values = pixel_values / 255.0
pixel_values = TVF.normalize(pixel_values, [0.5], [0.5])
pixel_values = pixel_values.to(vision_dtype)
# Generate the captions
generate_ids = llava_model.generate(
input_ids=input_ids,
pixel_values=pixel_values,
attention_mask=attention_mask,
max_new_tokens=args.max_new_tokens,
do_sample=not args.greedy,
suppress_tokens=None,
use_cache=True,
temperature=args.temperature,
top_k=args.top_k,
top_p=args.top_p,
)
# Trim off the prompts
assert isinstance(generate_ids, torch.Tensor)
generate_ids = generate_ids.tolist()
generate_ids = [
trim_off_prompt(ids, end_of_header_id, end_of_turn_id)
for ids in generate_ids
]
# Decode the captions
captions = tokenizer.batch_decode(
generate_ids, skip_special_tokens=False, clean_up_tokenization_spaces=False
)
captions = [c.strip() for c in captions]
for path, caption in zip(batch["paths"], captions):
write_caption(Path(path), caption)
pbar.update(len(captions))
def trim_off_prompt(input_ids: list[int], eoh_id: int, eot_id: int) -> list[int]:
# Trim off the prompt
while True:
try:
i = input_ids.index(eoh_id)
except ValueError:
break
input_ids = input_ids[i + 1 :]
# Trim off the end
try:
i = input_ids.index(eot_id)
except ValueError:
return input_ids
return input_ids[:i]
def write_caption(image_path: Path, caption: str):
caption_path = image_path.with_suffix(".txt")
try:
f = os.open(
caption_path, os.O_WRONLY | os.O_CREAT | os.O_EXCL
) # Write-only, create if not exist, fail if exists
except FileExistsError:
logging.warning(f"Caption file '{caption_path}' already exists")
return
except Exception as e:
logging.error(f"Failed to open caption file '{caption_path}': {e}")
return
try:
os.write(f, caption.encode("utf-8"))
os.close(f)
except Exception as e:
logging.error(f"Failed to write caption to '{caption_path}': {e}")
return
class ImageDataset(Dataset):
def __init__(
self,
prompts: list[Prompt],
paths: list[Path],
tokenizer: PreTrainedTokenizer | PreTrainedTokenizerFast,
image_token_id: int,
image_seq_length: int,
):
self.prompts = prompts
self.paths = paths
self.tokenizer = tokenizer
self.image_token_id = image_token_id
self.image_seq_length = image_seq_length
self.pad_token_id = tokenizer.pad_token_id
def __len__(self):
return len(self.paths)
def __getitem__(self, idx: int) -> dict:
path = self.paths[idx]
# Pick a prompt
prompt_str = random.choices(
self.prompts, weights=[p.weight for p in self.prompts]
)[0].prompt
# Preprocess image
# NOTE: I don't use the Processor here and instead do it manually.
# This is because in my testing a simple resize in Pillow yields higher quality results than the Processor,
# and the Processor had some buggy behavior on some images.
# And yes, with the so400m model, the model expects the image to be squished into a square, not padded.
try:
image = Image.open(path)
if image.size != (384, 384):
image = image.resize((384, 384), Image.LANCZOS)
image = image.convert("RGB")
pixel_values = TVF.pil_to_tensor(image)
except Exception as e:
logging.error(f"Failed to load image '{path}': {e}")
pixel_values = None # Will be filtered out later
# Build the conversation
convo = [
{
"role": "system",
"content": "You are a helpful image captioner.",
},
{
"role": "user",
"content": prompt_str,
},
]
# Format the conversation
convo_string = self.tokenizer.apply_chat_template(
convo, tokenize=False, add_generation_prompt=True
)
assert isinstance(convo_string, str)
# Tokenize the conversation
convo_tokens = self.tokenizer.encode(
convo_string, add_special_tokens=False, truncation=False
)
# Repeat the image tokens
input_tokens = []
for token in convo_tokens:
if token == self.image_token_id:
input_tokens.extend([self.image_token_id] * self.image_seq_length)
else:
input_tokens.append(token)
input_ids = torch.tensor(input_tokens, dtype=torch.long)
attention_mask = torch.ones_like(input_ids)
return {
"path": path,
"pixel_values": pixel_values,
"input_ids": input_ids,
"attention_mask": attention_mask,
}
def collate_fn(self, batch: list[dict]) -> dict:
# Filter out images that failed to load
batch = [item for item in batch if item["pixel_values"] is not None]
# Pad input_ids and attention_mask
# Have to use left padding because HF's generate can't handle right padding it seems
max_length = max(item["input_ids"].shape[0] for item in batch)
n_pad = [max_length - item["input_ids"].shape[0] for item in batch]
input_ids = torch.stack(
[
torch.nn.functional.pad(
item["input_ids"], (n, 0), value=self.pad_token_id
)
for item, n in zip(batch, n_pad)
]
)
attention_mask = torch.stack(
[
torch.nn.functional.pad(item["attention_mask"], (n, 0), value=0)
for item, n in zip(batch, n_pad)
]
)
# Stack pixel values
pixel_values = torch.stack([item["pixel_values"] for item in batch])
# Paths
paths = [item["path"] for item in batch]
return {
"paths": paths,
"pixel_values": pixel_values,
"input_ids": input_ids,
"attention_mask": attention_mask,
}
def parse_prompts(prompt_str: str | None, prompt_file: str | None) -> list[Prompt]:
if prompt_str is not None and prompt_file is not None:
raise ValueError("Cannot specify both --prompt and --prompt-file")
if prompt_str is not None:
return [Prompt(prompt=prompt_str, weight=1.0)]
if prompt_file is None:
raise ValueError("Must specify either --prompt or --prompt-file")
data = json.loads(Path(prompt_file).read_text())
if not isinstance(data, list):
raise ValueError("Expected JSON file to contain a list of prompts")
prompts = []
for item in data:
if isinstance(item, str):
prompts.append(Prompt(prompt=item, weight=1.0))
elif (
isinstance(item, dict)
and "prompt" in item
and "weight" in item
and isinstance(item["prompt"], str)
and isinstance(item["weight"], (int, float))
):
prompts.append(Prompt(prompt=item["prompt"], weight=item["weight"]))
else:
raise ValueError(
f"Invalid prompt in JSON file. Should be either a string or an object with 'prompt' and 'weight' fields: {item}"
)
if len(prompts) == 0:
raise ValueError("No prompts found in JSON file")
if sum(p.weight for p in prompts) <= 0.0:
raise ValueError("Prompt weights must sum to a positive number")
return prompts
def find_images(glob: str | None, filelist: str | Path | None) -> list[Path]:
if glob is None and filelist is None:
raise ValueError("Must specify either --glob or --filelist")
paths = []
if glob is not None:
paths.extend(Path(".").glob(glob))
if filelist is not None:
paths.extend(
(
Path(line.strip())
for line in Path(filelist).read_text().strip().splitlines()
if line.strip() != ""
)
)
return paths
if __name__ == "__main__":
main()

View File

@ -0,0 +1,75 @@
import torch
import torch.amp
import torchvision.transforms.functional as TVF
from PIL import Image
from transformers import AutoTokenizer, LlavaForConditionalGeneration
IMAGE_PATH = "C:/Users/27698/Desktop/node/12/00001.png"
PROMPT = "Write a long descriptive caption for this image in a formal tone."
MODEL_NAME = "fancyfeast/llama-joycaption-alpha-two-hf-llava"
# Load JoyCaption
# bfloat16 is the native dtype of the LLM used in JoyCaption (Llama 3.1)
# device_map=0 loads the model into the first GPU
tokenizer = AutoTokenizer.from_pretrained(MODEL_NAME, use_fast=True)
llava_model = LlavaForConditionalGeneration.from_pretrained(MODEL_NAME, torch_dtype="bfloat16", device_map="cuda:0")
llava_model.eval()
with torch.no_grad():
# Load and preprocess image
# Normally you would use the Processor here, but the image module's processor
# has some buggy behavior and a simple resize in Pillow yields higher quality results
image = Image.open(IMAGE_PATH)
if image.size != (384, 384):
image = image.resize((384, 384), Image.LANCZOS)
image = image.convert("RGB")
pixel_values = TVF.pil_to_tensor(image)
# Normalize the image
pixel_values = pixel_values / 255.0
pixel_values = TVF.normalize(pixel_values, [0.5], [0.5])
pixel_values = pixel_values.to(torch.bfloat16).unsqueeze(0)
# Build the conversation
convo = [
{
"role": "system",
"content": "You are a helpful image captioner.",
},
{
"role": "user",
"content": PROMPT,
},
]
# Format the conversation
convo_string = tokenizer.apply_chat_template(convo, tokenize=False, add_generation_prompt=True)
# Tokenize the conversation
convo_tokens = tokenizer.encode(convo_string, add_special_tokens=False, truncation=False)
# Repeat the image tokens
input_tokens = []
for token in convo_tokens:
if token == llava_model.config.image_token_index:
input_tokens.extend([llava_model.config.image_token_index] * llava_model.config.image_seq_length)
else:
input_tokens.append(token)
input_ids = torch.tensor(input_tokens, dtype=torch.long).unsqueeze(0)
attention_mask = torch.ones_like(input_ids)
# Generate the caption
generate_ids = llava_model.generate(input_ids=input_ids.to('cuda'), pixel_values=pixel_values.to('cuda'), attention_mask=attention_mask.to('cuda'), max_new_tokens=300, do_sample=True, suppress_tokens=None, use_cache=True)[0]
# Trim off the prompt
generate_ids = generate_ids[input_ids.shape[1]:]
# Decode the caption
caption = tokenizer.decode(generate_ids, skip_special_tokens=True, clean_up_tokenization_spaces=False)
caption = caption.strip()
print(caption)

View File

@ -1,3 +1,4 @@
import { escapeRegExp } from "lodash";
/**
*
@ -67,3 +68,24 @@ export async function ExecuteConcurrently(tasks: Array<() => Promise<any>>, conc
}
return Promise.all(results);
}
/**
*
* @param mainString
* @param substringArray
* @returns
*/
export function ReplaceSubstrings(mainString: string, substringArray: string[], replacement: string = " "): string {
// 按长度降序排序子字符串数组,以确保较长的子字符串先被替换
substringArray.sort((a, b) => b.length - a.length);
// 对每个子字符串进行替换
for (const substring of substringArray) {
// 创建一个正则表达式,用于全局匹配子字符串
const regex = new RegExp(escapeRegExp(substring), 'g');
// 将匹配到的子字符串替换为等长的空格
mainString = mainString.replace(regex, replacement);
}
return mainString;
}

View File

@ -5,7 +5,6 @@
* @returns truefalse
*/
export function ValidateJson(str: string): boolean {
try {
JSON.parse(str);
return true

View File

@ -244,7 +244,7 @@ export class BookTaskDetailService extends BaseRealmService {
* ID和小说任务ID
* @param condition bookIdbookTaskIdnameid
*/
DeleteBookTaskDetail(condition) {
DeleteBookTaskDetail(condition: Book.DeleteBookTaskDetailCondition) {
try {
if (isEmpty(condition.id) && isEmpty(condition.bookTaskId) && isEmpty(condition.bookId)) {
throw new Error('删除小说分镜信息失败,没有必要参数')

View File

@ -0,0 +1,164 @@
const BOOK = {
MAIN_DATA_RETURN: 'MAIN_DATA_RETURN', // 监听任务返回
REPLACE_VIDEO_CURRENT_FRAME: 'REPLACE_VIDEO_CURRENT_FRAME',
GET_BOOK_TYPE: 'GET_BOOK_TYPE',
ADD_OR_MODIFY_BOOK: 'ADD_OR_MODIFY_BOOK',
GET_BOOK_DATA: 'GET_BOOK_DATA',
GET_FRAME_DATA: 'GET_FRAME_DATA',
GET_BOOK_TASK_DATA: 'GET_BOOK_TASK_DATA',
AUTO_ACTION: 'AUTO_ACTION',
SAVE_BOOK_SUBTITLE_POSITION: 'SAVE_BOOK_SUBTITLE_POSITION',
OPEN_BOOK_SUBTITLE_POSITION_SCREENSHOT: 'OPEN_BOOK_SUBTITLE_POSITION_SCREENSHOT',
GET_CURRENT_FRAME_TEXT: 'GET_CURRENT_FRAME_TEXT',
GET_VIDEO_FRAME_TEXT: 'GET_VIDEO_FRAME_TEXT',
GET_BOOK_TASK_DETAIL: 'GET_BOOK_TASK_DETAIL',
REVERSE_PROMPT_TO_GPT_PROMPT: 'REVERSE_PROMPT_TO_GPT_PROMPT',
SINGLE_REVERSE_TO_GPT_PROMPT: 'SINGLE_REVERSE_TO_GPT_PROMPT',
SAVE_IMAGE_STYLE: 'SAVE_IMAGE_STYLE',
IMAGE_LOCK_OPERATION: "IMAGE_LOCK_OPERATION",
DOWNLOAD_IMAGE_AND_SPLIT: "DOWNLOAD_IMAGE_AND_SPLIT",
ONE_TO_FOUR_BOOK_TASK: "ONE_TO_FOUR_BOOK_TASK",
RESET_BOOK_TASK: "RESET_BOOK_TASK",
DELETE_BOOK_TASK: "DELETE_BOOK_TASK",
GENERATE_IMAGE_ALL: "GENERATE_IMAGE_ALL",
CHECK_IMAGE_FILE_SIZE: "CHECK_IMAGE_FILE_SIZE",
HD_IMAGE: "HD_IMAGE",
USE_BOOK_VIDEO_DATA_TO_BOOK_TASK: "USE_BOOK_VIDEO_DATA_TO_BOOK_TASK",
ADD_JIANYING_DRAFT: "ADD_JIANYING_DRAFT",
EXPORT_COPYWRITING: "EXPORT_COPYWRITING",
IMPORT_COPYWRITING: 'IMPORT_COPYWRITING',
MERGE_PROMPT: "MERGE_PROMPT",
RESET_BOOK_DATA: "RESET_BOOK_DATA",
DELETE_BOOK_DATA: "DELETE_BOOK_DATA",
CLEAR_IMPORT_WORD: "CLEAR_IMPORT_WORD",
RESET_GPT_REVERSE_DATA: "RESET_GPT_REVERSE_DATA",
REMOVE_MERGE_PROMPT_DATA: "REMOVE_MERGE_PROMPT_DATA",
REMOVE_GENERATE_IMAGE: 'REMOVE_GENERATE_IMAGE',
ADD_NEW_BOOK_TASK: "ADD_NEW_BOOK_TASK",
REPLACE_BOOK_DATA: "REPLACE_BOOK_DATA",
SAVE_COPYWRITING: 'SAVE_COPYWRITING',
//#region 通用
/** 重置小说批次详细信息的数据 */
RESET_BOOK_TASK_DETAIL_DATA: "RESET_BOOK_TASK_DETAIL_DATA",
//#endregion
//#region 分镜
/** 剪映抽帧,并判断是不是切割视频 */
JIANYING_FRAME: "JIANYING_FRAME",
//#endregion
//#region 提示词
/**
*
*/
ORIGINAL_GPT_PROMPT: "ORIGINAL_GPT_PROMPT",
/** * 原创推理提示词返回 */
ORIGINAL_GPT_PROMPT_RETURN: "ORIGINAL_GPT_PROMPT_RETURN",
/** * 导入提示词,通用 */
IMPORT_GPT_PROMPT: "IMPORT_GPT_PROMPT",
/** 移除不想要的提示词 */
REMOVE_BAD_PROMPT: "REMOVE_BAD_PROMPT",
//#endregion
//#region 生图返回相关
/**
* MJ生图返回信息
*/
MJ_IMAGE_GENERATE_RETURN: 'MJ_IMAGE_GENERATE_RETURN',
/**
* SD生图返回信息
*/
SD_IMAGE_GENERATE_RETURN: 'SD_IMAGE_GENERATE_RETURN',
/**
* D3
*/
D3_IMAGE_GENERATE_RETURN: 'D3_IMAGE_GENERATE_RETURN',
/**
* flux forge
*/
FLUX_FORGE_IMAGE_GENERATE_RETURN: "FLUX_FORGE_IMAGE_GENERATE_RETURN",
/**
* flux api
*/
FLUX_API_IMAGE_GENERATE_RETURN: "FLUX_API_IMAGE_GENERATE_RETURN",
//#endregion
//#region 图片相关
/**
* MJ的消息ID
*/
GET_IMAGE_URL_AND_DOWNLOAD: "GET_IMAGE_URL_AND_DOWNLOAD",
//#endregion
COMPUTE_STORYBOARD: 'COMPUTE_STORYBOARD',
GET_FRAME: 'GET_FRAME',
FRAMING: 'FRAMING',
//#region 字幕相关
/**
* copywriting
*/
GET_COPYWRITING: 'GET_COPYWRITING',
/**
* copywriting的返回信息
*/
GET_COPYWRITING_RETURN: 'GET_COPYWRITING_RETURN',
//#endregion
REMOVE_WATERMARK: 'REMOVE_WATERMARK',
REMOVE_WATERMARK_RETURN: 'REMOVE_WATERMARK_RETURN',
SPLI_TAUDIO: 'SPLI_TAUDIO',
SPLI_TAUDIO_RETURN: 'SPLI_TAUDIO_RETURN',
//#region 反推相关
/**
*
*/
ADD_REVERSE_PROMPT: 'ADD_REVERSE_PROMPT',
/**
*
*/
REVERSE_PROMPT_RETURN: 'REVERSE_PROMPT_RETURN',
//#endregion
//#region 翻译相关
/**
*
*/
REVERSE_PROMPT_TRANSLATE_RETURN: 'REVERSE_PROMPT_TRANSLATE_RETURN',
//#endregion
//#region
/** 合成视频相关 */
ADD_GENERATE_VIDEO_TASK: "ADD_GENERATE_VIDEO_TASK",
/** 生成视频返回 */
GENERATE_VIDEO_RETURN: "GENERATE_VIDEO_RETURN",
//#endregion
}
export default BOOK;

View File

@ -2,11 +2,13 @@ import { SYSTEM } from "./systemDefineString"
import TASK from "./taskDefineString"
import TTS from "./ttsDefineString"
import SETTING from "./settingDefineString"
import BOOK from "./bookDefineString"
export const DEFINE_STRING = {
SYSTEM: SYSTEM,
TASK: TASK,
TTS: TTS,
BOOK: BOOK,
SETTING: SETTING,
SHOW_GLOBAL_MESSAGE: "SHOW_GLOBAL_MESSAGE",
SHOW_GLOBAL_MAIN_NOTIFICATION: 'SHOW_GLOBAL_MAIN_NOTIFICATION',
@ -225,146 +227,6 @@ export const DEFINE_STRING = {
BATCH_PROCESS_IMAGE_RESULT: 'BATCH_PROCESS_IMAGE_RESULT',
PROCESS_IMAGE_WATERMASK_CHECK: "PROCESS_IMAGE_WATERMASK_CHECK"
},
BOOK: {
MAIN_DATA_RETURN: 'MAIN_DATA_RETURN', // 监听任务返回
REPLACE_VIDEO_CURRENT_FRAME: 'REPLACE_VIDEO_CURRENT_FRAME',
GET_BOOK_TYPE: 'GET_BOOK_TYPE',
ADD_OR_MODIFY_BOOK: 'ADD_OR_MODIFY_BOOK',
GET_BOOK_DATA: 'GET_BOOK_DATA',
GET_FRAME_DATA: 'GET_FRAME_DATA',
GET_BOOK_TASK_DATA: 'GET_BOOK_TASK_DATA',
AUTO_ACTION: 'AUTO_ACTION',
SAVE_BOOK_SUBTITLE_POSITION: 'SAVE_BOOK_SUBTITLE_POSITION',
OPEN_BOOK_SUBTITLE_POSITION_SCREENSHOT: 'OPEN_BOOK_SUBTITLE_POSITION_SCREENSHOT',
GET_CURRENT_FRAME_TEXT: 'GET_CURRENT_FRAME_TEXT',
GET_VIDEO_FRAME_TEXT: 'GET_VIDEO_FRAME_TEXT',
GET_BOOK_TASK_DETAIL: 'GET_BOOK_TASK_DETAIL',
REVERSE_PROMPT_TO_GPT_PROMPT: 'REVERSE_PROMPT_TO_GPT_PROMPT',
SINGLE_REVERSE_TO_GPT_PROMPT: 'SINGLE_REVERSE_TO_GPT_PROMPT',
SAVE_IMAGE_STYLE: 'SAVE_IMAGE_STYLE',
IMAGE_LOCK_OPERATION: "IMAGE_LOCK_OPERATION",
DOWNLOAD_IMAGE_AND_SPLIT: "DOWNLOAD_IMAGE_AND_SPLIT",
ONE_TO_FOUR_BOOK_TASK: "ONE_TO_FOUR_BOOK_TASK",
RESET_BOOK_TASK: "RESET_BOOK_TASK",
DELETE_BOOK_TASK: "DELETE_BOOK_TASK",
GENERATE_IMAGE_ALL: "GENERATE_IMAGE_ALL",
CHECK_IMAGE_FILE_SIZE: "CHECK_IMAGE_FILE_SIZE",
HD_IMAGE: "HD_IMAGE",
USE_BOOK_VIDEO_DATA_TO_BOOK_TASK: "USE_BOOK_VIDEO_DATA_TO_BOOK_TASK",
ADD_JIANYING_DRAFT: "ADD_JIANYING_DRAFT",
EXPORT_COPYWRITING: "EXPORT_COPYWRITING",
IMPORT_COPYWRITING: 'IMPORT_COPYWRITING',
MERGE_PROMPT: "MERGE_PROMPT",
RESET_BOOK_DATA: "RESET_BOOK_DATA",
DELETE_BOOK_DATA: "DELETE_BOOK_DATA",
CLEAR_IMPORT_WORD: "CLEAR_IMPORT_WORD",
RESET_GPT_REVERSE_DATA: "RESET_GPT_REVERSE_DATA",
REMOVE_MERGE_PROMPT_DATA: "REMOVE_MERGE_PROMPT_DATA",
REMOVE_GENERATE_IMAGE: 'REMOVE_GENERATE_IMAGE',
ADD_NEW_BOOK_TASK: "ADD_NEW_BOOK_TASK",
REPLACE_BOOK_DATA: "REPLACE_BOOK_DATA",
SAVE_COPYWRITING: 'SAVE_COPYWRITING',
//#region 提示词
/**
*
*/
ORIGINAL_GPT_PROMPT: "ORIGINAL_GPT_PROMPT",
/**
*
*/
ORIGINAL_GPT_PROMPT_RETURN: "ORIGINAL_GPT_PROMPT_RETURN",
/**
*
*/
IMPORT_GPT_PROMPT: "IMPORT_GPT_PROMPT",
//#endregion
//#region 生图返回相关
/**
* MJ生图返回信息
*/
MJ_IMAGE_GENERATE_RETURN: 'MJ_IMAGE_GENERATE_RETURN',
/**
* SD生图返回信息
*/
SD_IMAGE_GENERATE_RETURN: 'SD_IMAGE_GENERATE_RETURN',
/**
* D3
*/
D3_IMAGE_GENERATE_RETURN: 'D3_IMAGE_GENERATE_RETURN',
/**
* flux forge
*/
FLUX_FORGE_IMAGE_GENERATE_RETURN: "FLUX_FORGE_IMAGE_GENERATE_RETURN",
/**
* flux api
*/
FLUX_API_IMAGE_GENERATE_RETURN: "FLUX_API_IMAGE_GENERATE_RETURN",
//#endregion
//#region 图片相关
/**
* MJ的消息ID
*/
GET_IMAGE_URL_AND_DOWNLOAD: "GET_IMAGE_URL_AND_DOWNLOAD",
//#endregion
COMPUTE_STORYBOARD: 'COMPUTE_STORYBOARD',
GET_FRAME: 'GET_FRAME',
FRAMING: 'FRAMING',
//#region 字幕相关
/**
* copywriting
*/
GET_COPYWRITING: 'GET_COPYWRITING',
/**
* copywriting的返回信息
*/
GET_COPYWRITING_RETURN: 'GET_COPYWRITING_RETURN',
//#endregion
REMOVE_WATERMARK: 'REMOVE_WATERMARK',
REMOVE_WATERMARK_RETURN: 'REMOVE_WATERMARK_RETURN',
SPLI_TAUDIO: 'SPLI_TAUDIO',
SPLI_TAUDIO_RETURN: 'SPLI_TAUDIO_RETURN',
//#region 反推相关
/**
*
*/
ADD_REVERSE_PROMPT: 'ADD_REVERSE_PROMPT',
/**
*
*/
REVERSE_PROMPT_RETURN: 'REVERSE_PROMPT_RETURN',
//#endregion
//#region 翻译相关
/**
*
*/
REVERSE_PROMPT_TRANSLATE_RETURN: 'REVERSE_PROMPT_TRANSLATE_RETURN',
//#endregion
},
PROMPT: {
GET_SORT_OPTIONS: 'GET_SORT_OPTIONS',
SAVE_PROMPT_SORT_DATA: 'SAVE_PROMPT_SORT_DATA',

View File

@ -208,7 +208,12 @@ export enum BookTaskStatus {
COMPOSING_DONE = 'composing_done',
// 合成视频失败
COMPOSING_FAIL = 'composing_fail'
COMPOSING_FAIL = 'composing_fail',
/** 添加草稿完成 */
DRAFT_DONE = 'draft_done',
/** 添加草稿失败 */
DRAFT_FAIL = 'draft_fail',
}
export enum TagDefineType {

View File

@ -60,6 +60,7 @@ export enum ResponseMessageType {
GET_TEXT = 'getText', // 获取文案
REMOVE_WATERMARK = "REMOVE_WATERMARK",// 删除水印
MJ_REVERSE = 'MJ_REVERSE',// MJ反推返回反推结果
SD_REVERSE = 'SD_REVERSE',// MJ反推返回反推结果
REVERSE_PROMPT_TRANSLATE = 'REVERSE_PROMPT_TRANSLATE',// 反推提示词翻译
GPT_PROMPT_TRANSLATE = 'GPT_PROMPT_TRANSLATE', // GPT提示词翻译
MJ_IMAGE = 'MJ_IMAGE',// MJ 生成图片

View File

@ -92,10 +92,11 @@ export function BookIpc() {
//#endregion
//#region 小说通用操作
//#region 小说详情通用操作
ipcMain.handle(DEFINE_STRING.BOOK.REPLACE_BOOK_DATA, async (event, bookTaskId, replaceData) => await bookGeneral.ReplaceBookData(bookTaskId, replaceData))
//#endregion
//#region 分镜相关
@ -116,6 +117,9 @@ export function BookIpc() {
await bookFrame.ReplaceVideoCurrentFrame(bookTaskDetailId, currentTime)
)
// 剪映抽帧,并判断是不是切割视频
ipcMain.handle(DEFINE_STRING.BOOK.JIANYING_FRAME, async (event, bookTaskId, draftName) => await bookFrame.JianyingFrame(bookTaskId, draftName))
//#endregion
//#region 提示词相关
@ -154,6 +158,12 @@ export function BookIpc() {
async (event, bookTaskId: string, txtPath: string) => await bookPrompt.ImportGPTPrompt(bookTaskId, txtPath)
)
/** 移除不想要的提示词 */
ipcMain.handle(
DEFINE_STRING.BOOK.REMOVE_BAD_PROMPT,
async (event, bookTaskId: string) => await bookPrompt.RemoveBadPrompt(bookTaskId)
)
//#endregion
//#region 文案相关
@ -290,13 +300,13 @@ export function BookIpc() {
// 重置小说批次数据
ipcMain.handle(
DEFINE_STRING.BOOK.RESET_BOOK_TASK,
async (event, bookTaskId) => await bookTask.ReSetBookTask(bookTaskId)
async (event, bookTaskId, operateBookType) => await bookTask.ReSetBookTask(bookTaskId, operateBookType)
)
// 删除小说批次数据
ipcMain.handle(
DEFINE_STRING.BOOK.DELETE_BOOK_TASK,
async (event, bookTaskId) => await bookTask.DeleteBookTask(bookTaskId)
async (event, bookTaskId, operateBookType) => await bookTask.DeleteBookTask(bookTaskId, operateBookType)
)
@ -312,5 +322,10 @@ export function BookIpc() {
DEFINE_STRING.BOOK.ADD_JIANYING_DRAFT,
async (event, id: string | string[], operateBookType: OperateBookType) => await bookVideo.AddJianyingDraft(id, operateBookType)
)
ipcMain.handle(
DEFINE_STRING.BOOK.ADD_GENERATE_VIDEO_TASK,
async (event, id: string | string[], operateBookType: OperateBookType) => await bookVideo.AddGenerateVideoTask(id, operateBookType)
)
//#endregion
}

View File

@ -236,7 +236,7 @@ export class ReverseBook {
if (operateBookType == OperateBookType.BOOKTASK) {
bookTaskDetails = await this.bookServiceBasic.GetBookTaskDetailData({
bookTaskId: id
})
});
} else if (operateBookType == OperateBookType.BOOKTASKDETAIL) {
let bookTaskDetail = await this.bookServiceBasic.GetBookTaskDetailDataById(id);
bookTaskDetails = [bookTaskDetail]
@ -263,7 +263,11 @@ export class ReverseBook {
if (bookTaskDetailIds.length <= 0) {
throw new Error("没有需要反推的数据,请检查")
}
await this.AddReversePromptTask(bookTaskDetailIds, type);
// 判断是不是SD反推并且是反推的全部的数据
if (type == BookType.SD_REVERSE && operateBookType == OperateBookType.BOOKTASK) {
bookTaskDetailIds = [null];
}
await this.AddReversePromptTask(bookTaskDetailIds, type, id);
return successMessage(null, "添加反推任务成功", 'ReverseBook_AddReversePrompt')
} catch (error) {
return errorMessage("添加反推任务失败,错误信息如下:" + error.message, "ReverseBook_AddReversePrompt")
@ -276,8 +280,14 @@ export class ReverseBook {
* @param type
* @returns
*/
async AddReversePromptTask(bookTaskDetailIds: string[], type: BookType): Promise<void> {
async AddReversePromptTask(bookTaskDetailIds: string[], type: BookType, bookTaskId: string): Promise<void> {
try {
if (bookTaskDetailIds.length == 1 && bookTaskDetailIds[0] == null && type == BookType.SD_REVERSE) {
let bookTask = await this.bookServiceBasic.GetBookTaskDataById(bookTaskId);
await this.bookServiceBasic.AddBookBackTask(bookTask.bookId, BookBackTaskType.SD_REVERSE, TaskExecuteType.AUTO, bookTaskId, bookTaskDetailIds[0], DEFINE_STRING.BOOK.REVERSE_PROMPT_RETURN
);
return;
}
for (let index = 0; index < bookTaskDetailIds.length; index++) {
const bookTaskDetailId = bookTaskDetailIds[index];
let bookTaskDetail = await this.bookServiceBasic.GetBookTaskDetailDataById(bookTaskDetailId)
@ -327,7 +337,7 @@ export class ReverseBook {
res = await this.mjOpt.MJImage2Text(task);
break
case BookBackTaskType.SD_REVERSE:
res = await this.SDReversePrompt(task);
res = await this.sdOpt.SDReversePrompt(task);
break
default:
throw new Error("未知的任务类型")

View File

@ -11,6 +11,10 @@ import { LogScheduler } from '../task/logScheduler';
import { BookBasic } from "./BooKBasic";
import { LoggerStatus, OtherData } from "../../../define/enum/softwareEnum";
import { BasicReverse } from "./basicReverse";
import JianyingService from "../jianying/jianyingService";
import { ValidateJson } from "../../../define/Tools/validate";
import { BookTaskStatus } from "../../../define/enum/bookEnum";
import { define } from "../../../define/define";
export class BookFrame {
bookServiceBasic: BookServiceBasic
@ -18,14 +22,86 @@ export class BookFrame {
logScheduler: LogScheduler
basicReverse: BasicReverse
bookBasic: BookBasic
jianyingService: JianyingService
constructor() {
this.bookServiceBasic = new BookServiceBasic();
this.ffmpegOptions = new FfmpegOptions();
this.logScheduler = new LogScheduler()
this.bookBasic = new BookBasic()
this.basicReverse = new BasicReverse()
this.jianyingService = new JianyingService()
}
/**
*
* @param draftName 稿
* @param clipVideo
*/
async JianyingFrame(bookTaskId: string, draftName: string) {
try {
console.log(draftName);
let draftPath = path.join(global.config.draft_path, draftName);
if (!await CheckFileOrDirExist(draftPath)) {
throw new Error("未找到对应的草稿文件,请检查");
}
let bookTask = await this.bookServiceBasic.GetBookTaskDataById(bookTaskId);
let book = await this.bookServiceBasic.GetBookDataById(bookTask.bookId);
// 在执行操作之前,先删除之前的文件
await this.bookServiceBasic.DeleteBookTaskDetailData({
bookTaskId: bookTask.id
})
// 在删除对应的图片和视频文件
let input = path.resolve(book.bookFolderPath, "tmp/input");
if (await CheckFileOrDirExist(input)) {
await DeleteFolderAllFile(input, true);
}
await this.jianyingService.GetDraftFrameAndText(draftPath, book.bookFolderPath, 'input');
let draftTimeLinePath = path.resolve(book.bookFolderPath, "draftFrameData.json");
if (!await CheckFileOrDirExist(draftTimeLinePath)) {
throw new Error("没有找到对应的草稿时间轴数据,请检查");
}
let draftTimeLineString = await fs.promises.readFile(draftTimeLinePath, "utf-8");
if (!ValidateJson(draftTimeLineString)) {
throw new Error("草稿时间轴数据格式错误,请检查");
}
let draftTimeLine = JSON.parse(draftTimeLineString);
// 开始修改数据
// 获取SD设置
let sdConifg = JSON.parse(await fs.promises.readFile(define.sd_setting, 'utf-8'));
let adetailer = false;
if (sdConifg && sdConifg?.webui?.adetailer) {
adetailer = true;
}
// 写入数据
for (let i = 0; i < draftTimeLine.length; i++) {
const element = draftTimeLine[i];
await this.bookServiceBasic.AddBookTaskDetail({
bookTaskId: bookTaskId,
bookId: bookTask.bookId,
startTime: Math.ceil(element.startTime / 1000),
endTime: Math.ceil(element.endTime / 1000),
status: BookTaskStatus.WAIT,
word: element.word,
videoPath: element.videoPath,
oldImage: path.relative(define.project_path, element.framePath),
afterGpt: element.text,
subValue: undefined,
timeLimit: `${element.startTime} -- ${element.endTime}`,
// 新增修脸跟随
adetailer: adetailer
})
}
return successMessage(null, '剪映抽帧成功', 'BookFrame_JianyingFrame')
} catch (error) {
return errorMessage('剪映抽帧失败,失败信息如下:' + error.toString(), 'BookFrame_JianyingFrame')
}
}
//#region 替换指定分镜的视频当前帧
/**
*
* @param bookTaskDetailId ID
@ -65,6 +141,8 @@ export class BookFrame {
return errorMessage('替换指定分镜的视频当前帧失败,失败信息如下:' + error.toString(), 'BookFrame_ReplaceVideoCurrentFrame');
}
}
//#endregion
//#region 进行分镜截取的一些操作

View File

@ -73,14 +73,6 @@ export class BookImage {
for (let i = 0; i < bookTaskDetails.length; i++) {
const element = bookTaskDetails[i];
await this.bookServiceBasic.DeleteBoookTaskDetailGenerateImage(element.id);
// if (bookTask.imageCategory == BookImageCategory.MJ) {
// } else if (bookTask.imageCategory == BookImageCategory.D3) {
// throw new Error('暂时不支持D3生成的图片删除')
// } else if (bookTask.imageCategory == BookImageCategory.SD) {
// await this.bookServiceBasic.DeleteBoookTaskDetailGenerateImage(element.id);
// } else {
// throw new Error('未知的小说类型,请检查')
// }
// 上面的信息重置完毕之后,开始删除文件信息
let outImage = element.outImagePath;
if (await CheckFileOrDirExist(outImage)) {

View File

@ -5,12 +5,14 @@ import { GeneralResponse } from "../../../model/generalResponse";
import { errorMessage, SendReturnMessage, successMessage } from "../../Public/generalTools";
import { BookServiceBasic } from "../ServiceBasic/bookServiceBasic";
import { GptService } from "../GPT/gpt";
import { ExecuteConcurrently } from "../../../define/Tools/common";
import { ExecuteConcurrently, ReplaceSubstrings } from "../../../define/Tools/common";
import { DEFINE_STRING } from "../../../define/define_string";
import { CheckFileOrDirExist } from "../../../define/Tools/file";
import fs from 'fs';
import path from 'path'
import readline from 'readline';
import { define } from "../../../define/define";
import { ValidateJson } from "../../../define/Tools/validate";
export class BookPrompt {
@ -21,6 +23,51 @@ export class BookPrompt {
this.gptService = new GptService()
}
//#region 移除不想要的提示词
/**
*
* @param bookTaskId ID
*/
async RemoveBadPrompt(bookTaskId: string) {
try {
let bookTaskDetails = await this.bookServiceBasic.GetBookTaskDetailData({
bookTaskId: bookTaskId
})
let sdConfigString = await fs.promises.readFile(define.sd_setting, 'utf-8')
if (isEmpty(sdConfigString)) {
throw new Error('读取SD配置文件失败')
}
if (!ValidateJson(sdConfigString)) {
throw new Error('SD配置文件格式错误')
}
let sdConfig = JSON.parse(sdConfigString)
let badPrompt = sdConfig.tag.badPrompt;
let badPromptList = badPrompt.split(',')
let result = []
for (let i = 0; i < bookTaskDetails.length; i++) {
const element = bookTaskDetails[i];
if (isEmpty(element.gptPrompt)) {
continue;
}
let newText = ReplaceSubstrings(element.gptPrompt, badPromptList, "");
newText.replaceAll(",,", ",");
await this.bookServiceBasic.UpdateBookTaskDetail(element.id, {
gptPrompt: newText
})
result.push({
id: element.id,
gptPrompt: newText
})
}
return successMessage(result, "移除不想要的提示词成功", "Book_RemoveBadPrompt")
} catch (error) {
return errorMessage("移除不想要的提示词失败,错误信息如下:" + error.message, "Book_RemoveBadPrompt")
}
}
//#endregion
//#region 提示词通用
/**
@ -191,7 +238,13 @@ export class BookPrompt {
}
}
})
} else if (type == BookType.SD_REVERSE) { }
} else if (type == BookType.SD_REVERSE) {
// SD反推
for (let i = 0; i < bookTaskDetails.length; i++) {
const element = bookTaskDetails[i];
await this.bookServiceBasic.UpdateBookTaskDetail(element.id, { gptPrompt: undefined });
}
}
else {
throw new Error("SD反推删除还不支持")
}

View File

@ -233,17 +233,36 @@ export class BookTask {
*
* @param bookTaskId ID
*/
async ReSetBookTask(bookTaskId: string): Promise<GeneralResponse.ErrorItem | GeneralResponse.SuccessItem> {
async ReSetBookTask(bookTaskId: string | string[], operateBookType = OperateBookType.BOOKTASK as OperateBookType): Promise<GeneralResponse.ErrorItem | GeneralResponse.SuccessItem> {
try {
let bookTask = await this.bookServiceBasic.GetBookTaskDataById(bookTaskId);
await this.bookServiceBasic.ResetBookTask(bookTaskId);
let bookTasks = [] as Book.SelectBookTask[]
if (operateBookType == OperateBookType.BOOKTASK) {
let tempBookTask = await this.bookServiceBasic.GetBookTaskDataById(bookTaskId as string);
bookTasks.push(tempBookTask)
} else if (operateBookType == OperateBookType.ASSIGNBOOKTASK) {
for (let i = 0; i < bookTaskId.length; i++) {
const element = bookTaskId[i];
let tempBookTask = await this.bookServiceBasic.GetBookTaskDataById(element);
bookTasks.push(tempBookTask);
}
}
else {
throw new Error("无效的操作类型")
}
if (bookTasks.length <= 0) {
throw new Error("没有找到对应的小说任务")
}
for (let i = 0; i < bookTasks.length; i++) {
const element = bookTasks[i];
await this.bookServiceBasic.ResetBookTask(element.id);
// 数据库删除完毕,看是删除对应的文件(主要是图片)
let imageFolder = bookTask.imageFolder;
let imageFolder = element.imageFolder;
// 整个删掉在重建
if (await CheckFileOrDirExist(imageFolder)) {
await DeleteFolderAllFile(imageFolder)
}
await CheckFolderExistsOrCreate(imageFolder)
}
return successMessage(null, "重置小说数据成功", "BookTask_ReSetBookTask")
} catch (error) {
return errorMessage('重置小说批次数据失败,错误信息如下' + error.toString(), "BookTask_ReSetBookTask");
@ -254,16 +273,33 @@ export class BookTask {
*
* @param bookTaskId ID
*/
async DeleteBookTask(bookTaskId: string): Promise<GeneralResponse.ErrorItem | GeneralResponse.SuccessItem> {
async DeleteBookTask(bookTaskId: string | string[], operateBookType = OperateBookType.BOOKTASK as OperateBookType): Promise<GeneralResponse.ErrorItem | GeneralResponse.SuccessItem> {
try {
let bookTask = await this.bookServiceBasic.GetBookTaskDataById(bookTaskId);
let bookTasks = [] as Book.SelectBookTask[]
if (operateBookType == OperateBookType.BOOKTASK) {
let tempBookTask = await this.bookServiceBasic.GetBookTaskDataById(bookTaskId as string);
bookTasks.push(tempBookTask)
} else if (operateBookType == OperateBookType.ASSIGNBOOKTASK) {
for (let i = 0; i < bookTaskId.length; i++) {
const element = bookTaskId[i];
let tempBookTask = await this.bookServiceBasic.GetBookTaskDataById(element);
bookTasks.push(tempBookTask);
}
} else {
throw new Error("无效的操作类型")
}
for (let i = 0; i < bookTasks.length; i++) {
const element = bookTasks[i];
let bookTask = await this.bookServiceBasic.GetBookTaskDataById(element.id);
let imageFolder = bookTask.imageFolder;
// 先删除每个批次对应的数据,然后删除批次
await this.bookServiceBasic.DeleteBookTaskData(bookTaskId);
await this.bookServiceBasic.DeleteBookTaskData(element.id);
// 删除成功,直接把对应的出图文件夹删掉
if (await CheckFileOrDirExist(imageFolder)) {
await DeleteFolderAllFile(imageFolder, true)
}
}
return successMessage(null, "删除小说批次数据成功", "BookTask_DeleteBookTask")
} catch (error) {
return errorMessage('删除小说批次数据失败,错误信息如下' + error.toString(), "BookTask_DeleteBookTask");

View File

@ -1,26 +1,36 @@
import { OperateBookType } from "../../../define/enum/bookEnum";
import { BookBackTaskStatus, BookBackTaskType, BookTaskStatus, OperateBookType, TaskExecuteType } from "../../../define/enum/bookEnum";
import { Book } from "../../../model/book";
import { errorMessage, successMessage } from "../../Public/generalTools";
import { GeneralResponse } from "../../../model/generalResponse";
import { Setting } from '../../../main/setting/setting'
import path from 'path'
import { CheckFolderExistsOrCreate } from "../../../define/Tools/file";
import { CheckFileOrDirExist, CheckFolderExistsOrCreate, CopyFileOrFolder, GetFilesWithExtensions } from "../../../define/Tools/file";
import fs from 'fs'
import { ClipDraft } from '../../Public/clipDraft'
import { BookServiceBasic } from "../ServiceBasic/bookServiceBasic";
import { isEmpty } from "lodash";
import JianyingService from "../jianying/jianyingService";
import { DEFINE_STRING } from "../../../define/define_string";
import { define } from "../../../define/define";
import { ValidateJson } from "../../../define/Tools/validate";
import BookSetting from "@/main/setting/bookSetting";
import util from 'util';
import { spawn, exec } from 'child_process';
import { SendMessageToRenderer } from "../globalService";
const execAsync = util.promisify(exec);
export class BookVideo {
setting: Setting
bookServiceBasic: BookServiceBasic
jianyingService: JianyingService
bookSetting: BookSetting
constructor() {
this.setting = new Setting(global)
this.bookServiceBasic = new BookServiceBasic()
this.jianyingService = new JianyingService()
}
//#region 引用主小说相关数据
/**
*
@ -29,7 +39,6 @@ export class BookVideo {
*/
async UseBookVideoDataToBookTask(id: string, operateBookType: OperateBookType) {
try {
console.log(id, operateBookType)
let book = undefined as Book.SelectBook;
let bookTasks = undefined as Book.SelectBookTask[];
if (operateBookType == OperateBookType.BOOK) {
@ -61,7 +70,7 @@ export class BookVideo {
// 将修改数据放在一个事务中
for (let i = 0; i < bookTasks.length; i++) {
const element = bookTasks[i];
this.bookServiceBasic.UpdetedBookTaskData(element.id, {
await this.bookServiceBasic.UpdetedBookTaskData(element.id, {
backgroundMusic: book.backgroundMusic,
friendlyReminder: book.friendlyReminder,
draftSrtStyle: book.draftSrtStyle,
@ -81,6 +90,10 @@ export class BookVideo {
}
}
//#endregion
//#region 生成配置文件,用于生成草稿或者是合成视频
/**
* 稿
* @param book
@ -98,7 +111,7 @@ export class BookVideo {
}
// 开始生成配置文件
let configPath = path.join(book.bookFolderPath, "scripts/config.json");
let configPath = path.join(book.bookFolderPath, `scripts/${bookTask.name}_config.json`);
await CheckFolderExistsOrCreate(path.dirname(configPath));
// 开始配置
@ -145,11 +158,16 @@ export class BookVideo {
}
// 完毕,将数据写出
await fs.promises.writeFile(configPath, JSON.stringify(configData));
// 复制一个到config.json中
await CopyFileOrFolder(configPath, path.join(book.bookFolderPath, 'scripts/config.json'));
} catch (error) {
throw error
}
}
//#endregion
//#region 添加剪映草稿
/**
* 稿
@ -159,7 +177,6 @@ export class BookVideo {
*/
async AddJianyingDraft(id: string | string[], operateBookType: OperateBookType): Promise<GeneralResponse.ErrorItem | GeneralResponse.SuccessItem> {
try {
console.log(id, operateBookType)
let book = undefined as Book.SelectBook
let bookTasks = [] as Book.SelectBookTask[]
if (operateBookType == OperateBookType.ASSIGNBOOKTASK) {
@ -231,4 +248,211 @@ export class BookVideo {
return errorMessage('添加剪映草稿失败,错误信息如下:' + error.toString(), "BookTask_AddJianyingDraft");
}
}
//#endregion
//#region 添加合成视频任务
async AddGenerateVideoTask(id: string | string[], operateBookType: OperateBookType): Promise<GeneralResponse.ErrorItem | GeneralResponse.SuccessItem> {
try {
let book = undefined as Book.SelectBook
let bookTasks = [] as Book.SelectBookTask[]
if (operateBookType == OperateBookType.ASSIGNBOOKTASK) {
if (id.length <= 0) {
throw new Error("没有找到小说批次任务,请检查");
}
for (let i = 0; i < id.length; i++) {
const element = id[i];
let tempBookTask = await this.bookServiceBasic.GetBookTaskDataById(element as string);
bookTasks.push(tempBookTask)
if (book == undefined) {
book = await this.bookServiceBasic.GetBookDataById(tempBookTask.bookId);
}
// 这边要将主小说的数据写入到选中批次中
let useRes = await this.UseBookVideoDataToBookTask(element, OperateBookType.BOOKTASK);
if (useRes.code == 0) {
throw new Error(useRes.message);
}
}
} else if (operateBookType == OperateBookType.BOOKTASK) {
// 直接获取对应的数据
let tempBookTask = await this.bookServiceBasic.GetBookTaskDataById(id as string);
if (tempBookTask == null) {
throw new Error("没有找到小说批次任务,请检查");
}
bookTasks = [tempBookTask]
book = await this.bookServiceBasic.GetBookDataById(tempBookTask.bookId);
if (book == null) {
throw new Error
}
} else {
throw new Error("未知的操作类型");
}
if (bookTasks.length <= 0) {
throw new Error("没有找到小说批次任务,请检查")
}
let result = []
for (let i = 0; i < bookTasks.length; i++) {
const element = bookTasks[i];
await this.GenerateConfigFile(book, element);
// 数据处理完毕,开始添加任务
await this.bookServiceBasic.AddBookBackTask(book.id, BookBackTaskType.COMPOSING, TaskExecuteType.AUTO, element.id, null, DEFINE_STRING.BOOK.GENERATE_VIDEO_RETURN);
// 修改状态
await this.bookServiceBasic.UpdetedBookTaskData(element.id, {
status: BookTaskStatus.COMPOSING,
});
result.push(element.name);
}
// 添加完毕
return successMessage(result, `${result.join('\n')} ${'\n'} 合成视频任务添加成功`, "BookTask_AddGenerateVideoTask")
} catch (error) {
return errorMessage('添加剪映草稿失败,错误信息如下:' + error.toString(), "BookTask_AddJianyingDraft");
}
}
//#endregion
//#region 合成视频任务执行
/**
*
* @param task
*/
async GenerateVideo(task: TaskModal.Task): Promise<GeneralResponse.ErrorItem | GeneralResponse.SuccessItem> {
try {
console.log(task);
this.bookSetting = new BookSetting();
let bookTask = await this.bookServiceBasic.GetBookTaskDataById(task.bookTaskId);
let book = await this.bookServiceBasic.GetBookDataById(bookTask.bookId);
if (isEmpty(bookTask.srtPath) || !await CheckFileOrDirExist(bookTask.srtPath)) {
throw new Error("没有找到对应的SRT文件请检查");
}
if (isEmpty(bookTask.audioPath) || !await CheckFileOrDirExist(bookTask.audioPath)) {
throw new Error("没有找到对应的音频文件,请检查");
}
if (isEmpty(bookTask.backgroundMusic)) {
throw new Error("没有找到对应的背景音乐文件夹,请检查");
}
let videoConfigString = await fs.promises.readFile(define.video_config, 'utf-8');
if (!ValidateJson(videoConfigString)) {
throw new Error("视频配置文件格式错误,请检查");
}
// 检查图片是不是出完了
let bookTaskDetails = await this.bookServiceBasic.GetBookTaskDetailData({
bookTaskId: bookTask.id
});
for (let i = 0; i < bookTaskDetails.length; i++) {
const element = bookTaskDetails[i];
if (isEmpty(element.outImagePath) || !await CheckFileOrDirExist(element.outImagePath)) {
throw new Error("存在没有出完的图片或者图片不存在,请检查");
}
}
// 检查分镜信息和出入的图片数量是不是一致
let outImages = await GetFilesWithExtensions(bookTask.imageFolder, ['.png']);
if (outImages.length != bookTaskDetails.length) {
throw new Error("分镜信息和出图文件夹中图片数量不一致,请检查");
}
let videoConfig = JSON.parse(videoConfigString);
let assRandomIndex = Math.floor(Math.random() * videoConfig.assConfig.length)
let outMp4 = path.join(
book.bookFolderPath,
bookTask.name + '.mp4'
)
// 将所有的数据写入到配置文件中
let obj = {
srt_path: bookTask.srtPath,
// 字幕样式(需要随机)
srt_style: isEmpty(bookTask.draftSrtStyle) ? videoConfig.assConfig[assRandomIndex] : videoConfig.assConfig[assRandomIndex],
audio_path: bookTask.audioPath,
background_music_folder: await this.bookSetting.GetBackgroundMusicFolder(bookTask.backgroundMusic),
// 水印设置(需要随机)
friendly_reminder: await this.bookSetting.GetWatermarkSetting(),
video_resolution_x: videoConfig.video_resolution_x,
video_resolution_y: videoConfig.video_resolution_y,
outpue_file: outMp4,
image_folder: bookTask.imageFolder,
srt_config: path.join(book.bookFolderPath, `scripts/${bookTask.name}_config.json`),
mp4_file_txt: path.join(book.bookFolderPath, `scripts/${bookTask.name}.txt`),
status: 'no',
audio_sound_size: videoConfig.audioSoundSize,
background_music_sound_size: videoConfig.backgroundMusicSoundSize,
keyFrame: videoConfig.keyframe,
frameRate: videoConfig.frameRate,
bitRate: videoConfig.bitRate,
stdout: '',
stderr: ""
}
// 写出配置文件
let configPath = path.join(book.bookFolderPath, `scripts/${bookTask.name}_video_config.json`);
await fs.promises.writeFile(configPath, JSON.stringify(obj), 'utf-8');
let scriptPath = path.join(define.scripts_path, 'Lai.exe')
let gpu = global.gpu.type
if (videoConfig.libx264) {
gpu = 'OTHER'
}
// 执行生成图片的脚本
let script = `cd "${define.scripts_path}" && "${scriptPath}" -c "${configPath.replaceAll("\\", '/')}" "${gpu}"`
const output = await execAsync(script, { maxBuffer: 1024 * 1024 * 10, encoding: 'utf-8' })
if (output.stderr != '') {
obj.status = 'video_error'
obj.stdout = output.stdout
obj.stderr = output.stderr
throw new Error(output.stderr)
} else {
obj.status = 'video_ok'
obj.stdout = output.stdout
obj.stderr = output.stderr
}
// 检查输出文件是不是存在
if (!CheckFileOrDirExist(outMp4)) {
throw new Error("生成的视频文件不存在,请检查")
}
// 修改数据
await this.bookServiceBasic.UpdetedBookTaskData(task.bookTaskId, {
status: BookTaskStatus.COMPOSING_DONE,
generateVideoPath: path.relative(define.project_path, outMp4)
})
await this.bookServiceBasic.UpdateTaskStatus({
id: task.id,
status: BookBackTaskStatus.DONE
})
SendMessageToRenderer({
code: 1,
id: task.bookTaskDetailId,
data: BookTaskStatus.COMPOSING_DONE
}, task.messageName);
return successMessage(null, "合成视频任务执行成功", "BookTask_GenerateVideo");
} catch (error) {
let message = '合成视频任务执行失败,错误信息如下:' + error.toString()
// 失败
await this.bookServiceBasic.UpdetedBookTaskData(task.bookTaskId, {
status: BookTaskStatus.COMPOSING_FAIL,
errorMsg: error.message
})
await this.bookServiceBasic.UpdateTaskStatus({
id: task.id,
status: BookBackTaskStatus.FAIL,
errorMessage: message
})
SendMessageToRenderer({
code: 1,
id: task.bookTaskDetailId,
data: BookTaskStatus.COMPOSING_FAIL,
message: message
}, task.messageName);
return errorMessage(message, "BookTask_GenerateVideo");
}
}
//#endregion
}

View File

@ -588,7 +588,6 @@ export class MJOpt {
} else {
if (task_res.progress == 100) {
task_res.type == MJRespoonseType.FINISHED;
console.log(task.id, "22222")
await this.bookServiceBasic.UpdateTaskStatus({
id: task.id,
status: BookBackTaskStatus.DONE
@ -597,7 +596,6 @@ export class MJOpt {
let imagePath = path.join(book.bookFolderPath, `data\\MJOriginalImage\\${task_res.messageId}.png`);
await CheckFolderExistsOrCreate(path.dirname(imagePath))
await this.tools.downloadFileUrl(task_res.imageClick, imagePath)
debugger
// 进行图片裁剪
let imageRes = await ImageSplit(imagePath, bookTaskDetail.name, path.join(book.bookFolderPath, 'data\\MJOriginalImage'));
if (imageRes && imageRes.length < 4) {
@ -606,7 +604,7 @@ export class MJOpt {
// 修改数据库数据,将图片保存到对应的文件夹中
let firstImage = imageRes[0];
if (book.type == BookType.ORIGINAL) {
if (book.type == BookType.ORIGINAL && bookTask.name == "output_00001") {
await CopyFileOrFolder(firstImage, path.join(book.bookFolderPath, `tmp\\input\\${bookTaskDetail.name}.png`));
}
let out_file = path.join(bookTask.imageFolder, `${bookTaskDetail.name}.png`)

View File

@ -4,15 +4,20 @@ import { checkStringValueAddSuffix, errorMessage, successMessage } from "../../P
import { define } from '../../../define/define'
import fs from "fs";
import { ImageStyle } from "../Book/imageStyle";
import { BookBackTaskStatus, BookType, OperateBookType } from "../../../define/enum/bookEnum";
import { BookBackTaskStatus, BookTaskStatus, BookType, OperateBookType } from "../../../define/enum/bookEnum";
import { isEmpty } from "lodash";
import { BookServiceBasic } from "../ServiceBasic/bookServiceBasic";
import { PresetService } from "../presetService";
import path from "path";
import axios from "axios";
import { CheckFolderExistsOrCreate, CopyFileOrFolder, DeleteFileExifData } from "../../../define/Tools/file";
import { CheckFileOrDirExist, CheckFolderExistsOrCreate, CopyFileOrFolder, DeleteFileExifData } from "../../../define/Tools/file";
import { Base64ToFile } from "../../../define/Tools/image";
import { MJAction, MJImageType } from "../../../define/enum/mjEnum";
import { DEFINE_STRING } from "../../../define/define_string";
import { OtherData, ResponseMessageType } from "../../../define/enum/softwareEnum";
import util from 'util';
import { exec } from 'child_process';
const execAsync = util.promisify(exec)
const fspromise = fs.promises
export class SDOpt {
@ -35,6 +40,14 @@ export class SDOpt {
return sdSetting
}
private sendChangeMessage(data: GeneralResponse.MessageResponse, message_name: string = DEFINE_STRING.BOOK.MAIN_DATA_RETURN) {
if (!message_name) {
message_name = DEFINE_STRING.BOOK.MAIN_DATA_RETURN
}
global.newWindow[0].win.webContents.send(message_name, data)
}
//#region 合并提示词
/**
@ -300,7 +313,7 @@ export class SDOpt {
// 这边去图片信息
await DeleteFileExifData(path.join(define.package_path, 'exittool/exiftool.exe'), infoImgPath, imgPath);
// 写出去
if (bookTask.name == 'output_00001') {
if (bookTask.name == 'output_00001' && book.type == BookType.ORIGINAL) {
// 复制一个到input
let inputImgPath = path.join(inputFolder, `${bookTaskDetail.name}.png`)
await CopyFileOrFolder(imgPath, inputImgPath)
@ -359,6 +372,16 @@ export class SDOpt {
id: task.bookTaskDetailId
})
await this.bookServiceBasic.UpdateBookTaskDetail(task.bookTaskDetailId, {
status: BookTaskStatus.IMAGE_FAIL,
})
await this.bookServiceBasic.UpdateTaskStatus({
id: task.id,
status: BookBackTaskStatus.FAIL,
errorMessage: errorMsg
})
global.newWindow[0].win.webContents.send(task.messageName, {
code: 0,
message: errorMsg,
@ -371,4 +394,126 @@ export class SDOpt {
}
}
//#endregion
//#region 反推
/**
* SD
* @param task
* @returns
*/
async SDReversePrompt(task: TaskModal.Task): Promise<GeneralResponse.ErrorItem | GeneralResponse.SuccessItem> {
try {
console.log(task)
if (task.bookTaskDetailId == OtherData.DEFAULT) { // 反推整个任务的提示词
let bookTask = await this.bookServiceBasic.GetBookTaskDataById(task.bookTaskId);
let book = await this.bookServiceBasic.GetBookDataById(bookTask.bookId);
let bookTaskDetail = await this.bookServiceBasic.GetBookTaskDetailData({
bookTaskId: task.bookTaskId
});
let oldImages = []
for (let i = 0; i < bookTaskDetail.length; i++) {
const element = bookTaskDetail[i];
oldImages.push({
id: element.id,
name: element.name,
imagePath: element.oldImage
})
}
// 判断是不是有为空的
let imagePaths = [] as string[]
for (let i = 0; i < oldImages.length; i++) {
const element = oldImages[i];
if (!isEmpty(element.imagePath)) {
imagePaths.push(element.imagePath)
}
}
if (imagePaths.length == 0) {
throw new Error("没有找到需要反推的图片,请先生成图片")
}
let inputTxt = path.join(book.bookFolderPath, 'tmp/input/input.txt')
await fspromise.writeFile(inputTxt, imagePaths.join('\n'), 'utf-8')
let exe = path.join(define.scripts_path, 'Lai.exe')
let scripts = `cd "${define.scripts_path}" && ${exe} -pt "${inputTxt}"`;
// 完成了反推
// 开始写出数据
const output = await execAsync(scripts, { maxBuffer: 1024 * 1024 * 10, encoding: 'utf-8' })
for (let i = 0; i < oldImages.length; i++) {
const element = oldImages[i];
let tagTxt = path.join(path.dirname(element.imagePath), `${element.name}.txt`)
if (!await CheckFileOrDirExist(tagTxt)) {
throw new Error("反推失败,没有找到反推出来的提示词文件,请检查")
}
let content = await fspromise.readFile(tagTxt, 'utf-8');
content = content.replace(/\r?\n/g, '');
// 更新数据库
await this.bookServiceBasic.UpdateBookTaskDetail(element.id, {
gptPrompt: content
});
// 主动返回任务状态
this.sendChangeMessage({
code: 1,
type: ResponseMessageType.SD_REVERSE,
id: element.id,
data: content
}, task.messageName);
}
} else { // 反推单个分镜的提示词
let bookTaskDetail = await this.bookServiceBasic.GetBookTaskDetailDataById(task.bookTaskDetailId);
let imagePath = bookTaskDetail.oldImage;
if (isEmpty(imagePath)) {
throw new Error("当前任务没有生成图片,请先生成图片")
}
if (!await CheckFileOrDirExist(imagePath)) {
throw new Error("当前任务生成图片不存在,请先生成图片")
}
// 开始执行exe进行反推
let exe = path.join(define.scripts_path, 'Lai.exe')
let scripts = `cd "${define.scripts_path}" && ${exe} -ps "${imagePath}"`;
const output = await execAsync(scripts, { maxBuffer: 1024 * 1024 * 10, encoding: 'utf-8' })
// 反推成功
let imageName = path.basename(imagePath, path.extname(imagePath));
let txt = path.join(path.dirname(imagePath), `${imageName}.txt`)
if (!await CheckFileOrDirExist(txt)) {
throw new Error("反推失败,没有找到反推出来的提示词文件,请检查")
}
let content = await fspromise.readFile(txt, 'utf-8');
content = content.replace(/\r?\n/g, '');
// 更新数据库
await this.bookServiceBasic.UpdateBookTaskDetail(task.bookTaskDetailId, {
gptPrompt: content
});
await this.bookServiceBasic.UpdateTaskStatus({
id: task.id,
status: BookBackTaskStatus.DONE
})
// 主动返回任务状态
this.sendChangeMessage({
code: 1,
type: ResponseMessageType.SD_REVERSE,
id: task.bookTaskDetailId,
data: content
}, task.messageName);
}
} catch (error) {
let errorMsg = "SD反推失败错误信息如下" + error.toString()
await this.bookServiceBasic.UpdateTaskStatus({
id: task.id,
status: BookBackTaskStatus.FAIL,
errorMessage: errorMsg
})
return errorMessage(errorMsg, "SDOpt_SDReversePrompt");
}
}
//#endregion
}

View File

@ -32,7 +32,7 @@ class BookServiceBasic {
}
//#region 事务操作
transaction = async (realm : any) => await this.bookBasic.transaction(realm);
transaction = async (realm: any) => await this.bookBasic.transaction(realm);
// (callback: (realm: any) => void) {
// this.bookService.transaction(() => {
@ -64,16 +64,16 @@ class BookServiceBasic {
//#region 分镜任务
AddBookTaskDetail = async (bookTaskDetail: Book.SelectBookTaskDetail) => await this.bookTaskDetailServiceBasic.AddBookTaskDetail(bookTaskDetail);
GetBookTaskDetailDataById = async (bookTaskDetailId: string) => await this.bookTaskDetailServiceBasic.GetBookTaskDetailDataById(bookTaskDetailId);
GetBookTaskDetailData = async (condition: Book.QueryBookTaskDetailCondition, returnEmpty: boolean = false) => await this.bookTaskDetailServiceBasic.GetBookTaskDetailData(condition, returnEmpty);
UpdateBookTaskDetail = async (bookTaskDetailId: string, data: Book.SelectBookTaskDetail) => await this.bookTaskDetailServiceBasic.UpdateBookTaskDetail(bookTaskDetailId, data);
UpdateBookTaskStatus = async (bookTaskDetailId: string, status: BookTaskStatus,errorMsg? : string) => await this.bookTaskDetailServiceBasic.UpdateBookTaskStatus(bookTaskDetailId, status,errorMsg);
DeleteBookTaskDetailReversePromptById = async (bookTaskDetailId: string, reversePromptId: string) => await this.bookTaskDetailServiceBasic.DeleteBookTaskDetailReversePromptById(bookTaskDetailId);
UpdateBookTaskStatus = async (bookTaskDetailId: string, status: BookTaskStatus, errorMsg?: string) => await this.bookTaskDetailServiceBasic.UpdateBookTaskStatus(bookTaskDetailId, status, errorMsg);
DeleteBookTaskDetailReversePromptById = async (bookTaskDetailId: string) => await this.bookTaskDetailServiceBasic.DeleteBookTaskDetailReversePromptById(bookTaskDetailId);
DeleteBoookTaskDetailGenerateImage = async (bookTaskDetailId: string) => await this.bookTaskDetailServiceBasic.DeleteBoookTaskDetailGenerateImage(bookTaskDetailId);
UpdateBookTaskDetailReversePrompt = async (bookTaskDetailId: string, reversePromptId: string, data: Book.ReversePrompt) => await this.bookTaskDetailServiceBasic.UpdateBookTaskDetailReversePrompt(bookTaskDetailId, reversePromptId, data);
UpdateBookTaskDetailMjMessage = async (bookTaskDetailId: string, mjMessage: Book.MJMessage) => await this.bookTaskDetailServiceBasic.UpdateBookTaskDetailMjMessage(bookTaskDetailId, mjMessage);
DeleteBookTaskDetailData = async (condition: Book.DeleteBookTaskDetailCondition) => await this.bookTaskDetailServiceBasic.DeleteBookTaskDetail(condition);
//#endregion

View File

@ -89,6 +89,16 @@ export default class BookTaskDetailServiceBasic {
this.bookTaskDetailService.DeleteBookTaskDetailReversePromptById(bookTaskDetailId)
}
/**
*
* @param condition
*/
async DeleteBookTaskDetail(condition: Book.QueryBookTaskDetailCondition): Promise<void> {
await this.InitService();
this.bookTaskDetailService.DeleteBookTaskDetail(condition)
}
/**
*
* @param bookTaskDetailId

View File

@ -354,7 +354,6 @@ export class SubtitleService {
bookTaskId: bookTaskId
}, true)
debugger
// 获取SD设置
let sdConifg = JSON.parse(await fs.promises.readFile(define.sd_setting, 'utf-8'));
let adetailer = false;

View File

@ -0,0 +1,14 @@
import { DEFINE_STRING } from "@/define/define_string"
import { GeneralResponse } from "@/model/generalResponse"
/**
*
* @param data
* @param message_name
*/
export function SendMessageToRenderer(data: GeneralResponse.MessageResponse, message_name: string = DEFINE_STRING.BOOK.MAIN_DATA_RETURN) {
if (!message_name) {
message_name = DEFINE_STRING.BOOK.MAIN_DATA_RETURN
}
global.newWindow[0].win.webContents.send(message_name, data)
}

View File

@ -36,9 +36,8 @@ class JianyingService {
* 稿
* @param draftDir 稿
* @param projectDir
* @param packagePath
*/
public async GetDraftFrameAndText(draftDir: string, projectDir: string, packagePath: string) {
public async GetDraftFrameAndText(draftDir: string, projectDir: string, inputName: string = "input_crop"): Promise<void> {
try {
// 获取草稿文件路径
let draftJsonPath = path.resolve(draftDir, "draft_content.json");
@ -59,7 +58,7 @@ class JianyingService {
await DeleteFolderAllFile(projectTmp);
}
// 创建输出文件夹
let projectInput = path.resolve(projectTmp, "input_crop");
let projectInput = path.resolve(projectTmp, inputName);
console.log("projectInput", projectInput);
// 获取剪映的轨道并且判断是否包含一个video轨道和一个text轨道
@ -98,6 +97,8 @@ class JianyingService {
console.log("GetDraftFrameAndText", jsonPath);
} catch (error) {
throw error;
} finally {
this.draftTimeLine = [];
}
}

View File

@ -13,6 +13,7 @@ import { FluxOpt } from '../Flux/flux'
import { AsyncQueue } from '../../quene'
import { SoftWareServiceBasic } from '../ServiceBasic/softwareServiceBasic'
import { MJSetting } from '../../../model/Setting/mjSetting'
import { BookVideo } from '../Book/bookVideo'
export class TaskManager {
isExecuting: boolean = false;
@ -24,6 +25,7 @@ export class TaskManager {
bookBackTaskListService!: BookBackTaskListService;
eventListeners: Record<string | number, Function[]> = {};
softWareServiceBasic: SoftWareServiceBasic
bookVideo: BookVideo
mjSetting: MJSetting.MjSetting
spaceTime: number = 5000;
@ -252,6 +254,15 @@ export class TaskManager {
}, `${batch}_${task.id}`, batch, `${batch}_${task.id}_${new Date().getTime()}`)
}
/** 添加生成视频的后台任务 */
async AddGenerateVideo(task: TaskModal.Task) {
let batch = task.messageName;
global.requestQuene.enqueue(async () => {
this.bookVideo = new BookVideo();
await this.bookVideo.GenerateVideo(task);
}, `${batch}_${task.id}`, batch, `${batch}_${task.id}_${new Date().getTime()}`)
}
/**
* flux forge
@ -299,7 +310,8 @@ export class TaskManager {
case BookBackTaskType.RECOGNIZE:
this.AddExtractSubtitlesData(task);
break;
case BookBackTaskType.MJ_REVERSE || BookBackTaskType.SD_REVERSE:
case BookBackTaskType.MJ_REVERSE:
case BookBackTaskType.SD_REVERSE:
this.AddSingleReversePrompt(task);
break;
case BookBackTaskType.FLUX_FORGE_IMAGE:
@ -317,6 +329,10 @@ export class TaskManager {
case BookBackTaskType.D3_IMAGE:
this.AddD3Image(task);
break;
case BookBackTaskType.COMPOSING: // 合成视频
this.AddGenerateVideo(task);
break;
default:
throw new Error('未知的任务类型');
}

View File

@ -506,7 +506,7 @@ async function getFrame(value) {
let package_path = define.package_path.replaceAll('\\', '/')
let jianying = new JianyingService()
await jianying.GetDraftFrameAndText(draft_path, out_dir, package_path)
await jianying.GetDraftFrameAndText(draft_path, out_dir)
global.newWindow[0].win.webContents.send(DEFINE_STRING.SHOW_MESSAGE_DIALOG, {
code: 1,
@ -539,7 +539,7 @@ async function PushBackPrompt() {
let script = `cd "${define.scripts_path}" && "${py_path}" -p "${sd_config_path.replaceAll(
'\\',
'/'
)}" "input" "${global.config.project_path}"`
)}" "input_crop" "${global.config.project_path}"`
const output = await execAsync(script, { maxBuffer: 1024 * 1024 * 10, encoding: 'utf-8' })
global.newWindow[0].win.webContents.send(DEFINE_STRING.SHOW_MESSAGE_DIALOG, {
code: 1,

View File

@ -1,5 +0,0 @@
export class BookSetting {
constructor() {
}
}

View File

@ -0,0 +1,57 @@
import { define } from "@/define/define";
import { CheckFileOrDirExist } from "@/define/Tools/file";
import { ValidateJson } from "@/define/Tools/validate";
import fs from 'fs';
class BookSetting {
constructor() { }
/**
* ID获取设置的背景音乐文件夹
* @param id ID
*/
async GetBackgroundMusicFolder(id: string): Promise<string> {
let clipSettingString = await fs.promises.readFile(define.clip_setting, 'utf-8');
if (!ValidateJson(clipSettingString)) {
throw new Error('Clip设置文件格式错误');
}
let clipSetting = JSON.parse(clipSettingString);
let backgroundMusicSetting = clipSetting.background_music_setting;
if (!backgroundMusicSetting) {
throw new Error('没有找到对应的背景音乐文件夹地址设置,请检查');
}
let clip = backgroundMusicSetting.find((clip: any) => clip.id === id);
if (clip == undefined) {
throw new Error('没有找到对应的背景音乐文件夹地址,请检查');
}
if (!await CheckFileOrDirExist(clip.folder_path)) {
throw new Error('背景音乐文件夹不存在,请检查');
}
return clip.folder_path;
}
/**
*
* @param id ID
*/
async GetWatermarkSetting(id?: string) {
let videoSettingString = await fs.promises.readFile(define.video_config, 'utf-8');
if (!ValidateJson(videoSettingString)) {
throw new Error('Video设置文件格式错误');
}
let videoSetting = JSON.parse(videoSettingString);
let watermarkConfig = videoSetting.watermarkConfig;
if (!watermarkConfig) {
throw new Error('没有找到对应的水印设置,请检查');
}
if (id == undefined) {
return watermarkConfig[Math.floor(Math.random() * watermarkConfig.length)];
}
let watermark = watermarkConfig.find((watermark: any) => watermark.id === id);
if (watermark == undefined) {
throw new Error('没有找到对应的水印设置,请检查');
}
return watermark;
}
}
export default BookSetting;

7
src/model/book.d.ts vendored
View File

@ -272,6 +272,13 @@ declare namespace Book {
duration: number
}
type DeleteBookTaskDetailCondition = {
id?: string;
bookId?: string;
bookTaskId?: string;
name?: string;
}
//#region 替换小说数据
type BookReplaceData = {

View File

@ -54,7 +54,7 @@ const book = {
//#endregion
//#region 小说通用操作
//#region bookTaskDetail通用操作
// 替换文案数据
ReplaceBookData: async (bookTaskId: string, replaceData: Book.BookReplaceData) =>
@ -82,6 +82,9 @@ const book = {
bookTaskDetailId,
currentTime
),
/** 剪映抽帧,并判断是不是切割视频 */
JianyingFrame: async (bookTaskId: string, draftName: string) => await ipcRenderer.invoke(DEFINE_STRING.BOOK.JIANYING_FRAME, bookTaskId, draftName),
//#endregion
//#region 文案相关信息
@ -160,6 +163,10 @@ const book = {
// 导入提示词
ImportGPTPrompt: async (bookTaskId: string, txtPath: string) => await ipcRenderer.invoke(DEFINE_STRING.BOOK.IMPORT_GPT_PROMPT, bookTaskId, txtPath),
/** 移除不想要的提示词 */
RemoveBadPrompt: async (bookTaskId: string) => await ipcRenderer.invoke(DEFINE_STRING.BOOK.REMOVE_BAD_PROMPT, bookTaskId),
//#endregion
//#region 图片相关
@ -227,12 +234,12 @@ const book = {
await ipcRenderer.invoke(DEFINE_STRING.BOOK.ADD_NEW_BOOK_TASK, addBookTaskData),
// 重置小说批次数据
ReSetBookTask: async (bookTaskId) =>
await ipcRenderer.invoke(DEFINE_STRING.BOOK.RESET_BOOK_TASK, bookTaskId),
ReSetBookTask: async (bookTaskId: string | string[], operateBookType: OperateBookType) =>
await ipcRenderer.invoke(DEFINE_STRING.BOOK.RESET_BOOK_TASK, bookTaskId, operateBookType),
// 删除小说批次数据
DeleteBookTask: async (bookTaskId) =>
await ipcRenderer.invoke(DEFINE_STRING.BOOK.DELETE_BOOK_TASK, bookTaskId),
DeleteBookTask: async (bookTaskId: string | string[], operateBookType: OperateBookType) =>
await ipcRenderer.invoke(DEFINE_STRING.BOOK.DELETE_BOOK_TASK, bookTaskId, operateBookType),
// 一键生成所有图片
GenerateImageAll: async (bookTaskId, imageCategory) =>
@ -261,8 +268,11 @@ const book = {
// 添加数据到剪映草稿
AddJianyingDraft: async (id: string | string[], operateBookType: OperateBookType) =>
await ipcRenderer.invoke(DEFINE_STRING.BOOK.ADD_JIANYING_DRAFT, id, operateBookType)
await ipcRenderer.invoke(DEFINE_STRING.BOOK.ADD_JIANYING_DRAFT, id, operateBookType),
/** 合成视频相关 */
AddGenerateVideoTask: async (id: string | string[], operateBookType: OperateBookType) =>
await ipcRenderer.invoke(DEFINE_STRING.BOOK.ADD_GENERATE_VIDEO_TASK, id, operateBookType)
//#endregion
}
export { book }

View File

@ -1,300 +0,0 @@
<template>
<div>
<n-dropdown trigger="hover" :options="reverseOptions" @select="ReverseSelect">
<n-button size="tiny" :color="softwareStore.SoftColor.BROWN_YELLOW" @click="AddReversePrompt">
单句反推
</n-button>
</n-dropdown>
<n-dropdown trigger="hover" :options="tranlateOptions" @select="ReverseSelect">
<n-button
style="margin-left: 5px"
size="tiny"
:color="softwareStore.SoftColor.BROWN_YELLOW"
@click="TranslateOne('translate_chinese')"
>
英文 2 中文
</n-button>
</n-dropdown>
<n-button
style="margin-left: 5px"
size="tiny"
:color="softwareStore.SoftColor.BROWN_YELLOW"
@click="SelectReversePrompt"
>
重选反推提示词
</n-button>
</div>
</template>
<script>
import { ref, onMounted, defineComponent, onUnmounted, toRaw, watch, h } from 'vue'
import { useMessage, useDialog, NDropdown, NButton } from 'naive-ui'
import { useSoftwareStore } from '../../../../../stores/software'
import { useReverseManageStore } from '../../../../../stores/reverseManage'
import { TranslateType } from '../../../../../define/enum/translate'
import { ContainsChineseOrPunctuation } from '../../../../../define/Tools/common'
import ReSelectReversePrompt from '../MJReverse/ReSelectReversePrompt.vue'
import { OperateBookType } from '../../../../../define/enum/bookEnum'
import { isEmpty } from 'lodash'
import { DEFINE_STRING } from '../../../../../define/define_string'
export default defineComponent({
components: { NDropdown, NButton },
props: ['initData', 'index'],
setup(props) {
let message = useMessage()
let dialog = useDialog()
let data = ref(props.initData)
let index = ref(props.index)
let softwareStore = useSoftwareStore()
let reverseManageStore = useReverseManageStore()
onMounted(async () => {})
/**
* 单句反推提示词
*/
async function AddReversePrompt() {
let res = await window.book.AddReversePrompt(
data.value.id,
OperateBookType.BOOKTASKDETAIL,
null
)
if (res.code == 0) {
message.error(res.message)
} else {
message.success('添加反推任务成功')
}
}
async function AddNextReversePrompt() {
let res = await window.book.AddReversePrompt(
data.value.id,
OperateBookType.UNDERBOOKTASK,
reverseManageStore.selectBook.type
)
if (res.code == 0) {
message.error(res.message)
} else {
message.success('添加反推任务成功')
}
}
/**
* 提示词菜单下拉操作
*/
async function ReverseSelect(key) {
switch (key) {
case 'remove_reverse': //
let res = await window.book.ResetGptReverseData(
data.value.id,
OperateBookType.BOOKTASKDETAIL,
reverseManageStore.selectBook.type
)
if (res.code == 0) {
message.error(res.message)
} else {
reverseManageStore.selectBookTaskDetail[props.index].gptPrompt = undefined
reverseManageStore.selectBookTaskDetail[props.index].reversePrompt = undefined
message.success('删除反推数据成功')
}
break
case 'translate_english': // 2
await TranslateOne('translate_english')
break
case 'down_translate_chinese': //
await TranslateOne('down_translate_chinese')
break
case 'down_translate_english': //
await TranslateOne('down_translate_english')
break
case 'down_reverse':
await AddNextReversePrompt()
break
default:
message.error('未知的操作类型')
break
}
}
//
function TranslateOneParams(from, to) {
let translateData = []
if (!isEmpty(data.value.gptPrompt)) {
translateData.push({
text: data.value.gptPrompt,
from: from,
to: to,
type: TranslateType.GPT_PROMPT_TRANSLATE,
isSplit: false, // true
bookTaskDetailId: data.value.id,
responseMessgeName: DEFINE_STRING.BOOK.REVERSE_PROMPT_TRANSLATE_RETURN
})
} else {
let reversePrompt = data.value.reversePrompt
if (reversePrompt) {
reversePrompt.forEach((item) => {
let temp_obj = undefined
if (from == 'zh' && to == 'en') {
//
if (!ContainsChineseOrPunctuation(item.promptCN ? item.promptCN : item.prompt)) {
return
}
}
temp_obj = {
text: item.prompt,
from: from,
to: to,
type: TranslateType.REVERSE_PROMPT_TRANSLATE,
isSplit: false, // true
bookTaskDetailId: data.value.id,
reversePromptId: item.id,
responseMessgeName: DEFINE_STRING.BOOK.REVERSE_PROMPT_TRANSLATE_RETURN
}
translateData.push(temp_obj)
})
}
}
return translateData
}
//
function TranslateBatchParams(from, to) {
let translateData = []
for (let i = index.value; i < reverseManageStore.selectBookTaskDetail.length; i++) {
const element = reverseManageStore.selectBookTaskDetail[i]
// GPTGPT
if (!isEmpty(element.gptPrompt)) {
translateData.push({
text: element.gptPrompt,
from: from,
to: to,
type: TranslateType.GPT_PROMPT_TRANSLATE,
isSplit: false, // true
bookTaskDetailId: element.id,
responseMessgeName: DEFINE_STRING.BOOK.REVERSE_PROMPT_TRANSLATE_RETURN
})
} else {
let reversePrompt = element.reversePrompt
if (reversePrompt) {
reversePrompt.forEach((item) => {
if (from == 'zh' && to == 'en') {
//
if (!ContainsChineseOrPunctuation(item.promptCN ? item.promptCN : item.prompt)) {
return
}
}
let temp_obj = {
text: item.prompt,
from: from,
to: to,
type: TranslateType.REVERSE_PROMPT_TRANSLATE,
isSplit: false, // true
bookTaskDetailId: element.id,
reversePromptId: item.id,
responseMessgeName: DEFINE_STRING.BOOK.REVERSE_PROMPT_TRANSLATE_RETURN
}
translateData.push(temp_obj)
})
}
}
}
return translateData
}
//
async function TranslateOne(key) {
let translateData = []
switch (key) {
case 'translate_english':
translateData = TranslateOneParams('zh', 'en')
break
case 'translate_chinese':
translateData = TranslateOneParams('en', 'zh')
break
case 'down_translate_english':
translateData = TranslateBatchParams('zh', 'en')
break
case 'down_translate_chinese':
translateData = TranslateBatchParams('en', 'zh')
break
default:
message.error('未知的操作类型')
break
}
if (translateData.length <= 0) {
message.error('没有需要翻译的数据')
return
}
softwareStore.spin.spinning = true
softwareStore.spin.tip = `翻译中 ... 0 / ${translateData.length}`
//
let res = await window.translate.TranslateNowReturn(translateData)
softwareStore.spin.spinning = false
window.api.showGlobalMessageDialog(res)
}
/**
* 重选反推提示词
*/
async function SelectReversePrompt() {
//
if (!data.value.reversePrompt || data.value.reversePrompt.length <= 0) {
message.error('当前没有反推提示词')
return
}
// ImportWordAndSrt
dialog.create({
showIcon: false,
title: `重选MJ反推提示词 ${data.value.name}`,
closeOnEsc: false,
content: () =>
h(ReSelectReversePrompt, {
reversePrompts: data.value.reversePrompt,
index: index.value
}),
style: `width : 750px;`,
maskClosable: false
})
}
return {
index,
data,
softwareStore,
ReverseSelect,
TranslateOne,
reverseManageStore,
SelectReversePrompt,
AddReversePrompt,
reverseOptions: [
{
label: '下推理',
key: 'down_reverse'
},
{
label: '删除反推提示词',
key: 'remove_reverse'
}
],
tranlateOptions: [
{
label: '中文 2 英文',
key: 'translate_english'
},
{
label: '下翻译英文',
key: 'down_translate_english'
},
{
label: '下翻译中文',
key: 'down_translate_chinese'
}
]
}
}
})
</script>

View File

@ -1,165 +0,0 @@
<template>
<div style="display: flex; justify-content: space-between; white-space: nowrap">
<div style="display: flex">
<div>反推/GPT提示词</div>
<n-button
v-if="reverseManageStore.selectBook.type == bookType.MJ_REVERSE"
type="text"
:color="softwareStore.SoftColor.ZHUYANTUO"
size="tiny"
style="margin-left: 10px"
@click="SelectPromptIndex"
>选择提示词</n-button
><n-button
v-if="reverseManageStore.selectBook.type == bookType.MJ_REVERSE"
type="text"
:color="softwareStore.SoftColor.ZHUYANTUO"
size="tiny"
style="margin-left: 5px"
@click="SelectStyle"
>风格</n-button
>
</div>
<div>
<n-popover trigger="hover">
<template #trigger>
<n-button
color="#7c461e"
@click="SelectReversePromptAndReplace"
text
style="font-size: 24px"
>
<n-icon>
<FindReplaceRound />
</n-icon>
</n-button>
</template>
<span>批量替换反推提示词</span>
</n-popover>
</div>
</div>
</template>
<script>
import { ref, onMounted, defineComponent, onUnmounted, toRaw, h, watch } from 'vue'
import { useMessage, useDialog, NButton, NPopover, NIcon } from 'naive-ui'
import { useReverseManageStore } from '../../../../../stores/reverseManage'
import { BookRepalceDataType, BookType } from '../../../../../define/enum/bookEnum'
import { useSoftwareStore } from '../../../../../stores/software'
import SelectMJReversePrompt from '../MJReverse/SelectMJReversePrompt.vue'
import SelectImageStyle from '../../Components/SelectImageStyle.vue'
import FindReplaceRound from '../../Icon/FindReplaceRound.vue'
import DatatableHeaderAfterGptSelectAndReplace from './DatatableHeaderAfterGptSelectAndReplace.vue'
export default defineComponent({
components: { NButton, NPopover, FindReplaceRound, NIcon },
setup() {
let message = useMessage()
let reverseManageStore = useReverseManageStore()
let softwareStore = useSoftwareStore()
let bookType = ref(BookType)
let dialog = useDialog()
let selectStyleRef = ref(null)
let da = undefined
/**
* 选择MJ反推提示词用于生图
*/
async function SelectPromptIndex() {
// MJ
if (reverseManageStore.selectBook.type != BookType.MJ_REVERSE) {
message.error('只有MJ反推可以使用该按钮')
return
}
let hasReversePrompt = false
//
for (let index = 0; index < reverseManageStore.selectBookTaskDetail.length; index++) {
const element = reverseManageStore.selectBookTaskDetail[index]
if (element.reversePrompt && element.reversePrompt.length > 0) {
hasReversePrompt = true
break
}
}
if (!hasReversePrompt) {
message.error('请先进行MJ反推')
return
}
dialog.create({
title: '选择反推提示词',
content: () => h(SelectMJReversePrompt),
style: 'width : 500px',
showIcon: false
})
}
//
async function SelectStyle() {
//
//
//
let dialogWidth = window.innerWidth * 0.8
let dialogHeight = window.innerHeight * 0.9
// ImportWordAndSrt
dialog.create({
showIcon: false,
closeOnEsc: false,
content: () =>
h(SelectImageStyle, {
height: dialogHeight,
ref: selectStyleRef,
imageStyle: reverseManageStore.selectBookTask.imageStyle,
customizeImageStyle: reverseManageStore.selectBookTask.customizeImageStyle
}),
style: `width : ${dialogWidth}px; height : ${dialogHeight}px`,
maskClosable: false,
onClose: async () => {
console.log(selectStyleRef)
//
// reverseManageStore.selectBookTask.styleList = toRaw(selectStyleRef.value.selectStyle)
//
let res = await window.book.SaveImageStyle(
toRaw(selectStyleRef.value.selectStyle),
reverseManageStore.selectBookTask.id
)
if (res.code == 0) {
message.error(res.message)
return
}
reverseManageStore.selectBookTask.imageStyle = res.data.imageStyle
reverseManageStore.selectBookTask.customizeImageStyle = res.data.customizeImageStyle
message.success('保存成功')
}
})
}
async function SelectReversePromptAndReplace() {
if (da) {
return
}
da = dialog.create({
title: '批量查询替换反推提示词',
showIcon: false,
maskClosable: false,
content: () =>
h(DatatableHeaderAfterGptSelectAndReplace, { type: BookRepalceDataType.GPT_PROMPT }),
onClose: () => {
da = undefined
}
})
}
return {
reverseManageStore,
bookType,
softwareStore,
SelectPromptIndex,
SelectStyle,
selectStyleRef,
SelectReversePromptAndReplace
}
}
})
</script>

View File

@ -1,215 +0,0 @@
<template>
<div>
<n-dropdown trigger="hover" :options="mergeOptions" @select="MergePrompt">
<n-button
size="tiny"
:color="softwareStore.SoftColor.BROWN_YELLOW"
@click="MergePrompt(undefined)"
>
单句合并
</n-button>
</n-dropdown>
<n-button
style="margin-left: 5px"
size="tiny"
:color="softwareStore.SoftColor.BROWN_YELLOW"
@click="SingleGenerateImage"
>
单句生图
</n-button>
<n-button
style="margin-left: 5px"
size="tiny"
:color="softwareStore.SoftColor.BROWN_YELLOW"
@click="UnderGenerateImage"
>
下生图
</n-button>
<n-button
style="margin-left: 5px"
size="tiny"
:color="softwareStore.SoftColor.BROWN_YELLOW"
@click="ImportMJImageUrl"
>
导入图片
</n-button>
</div>
<n-input
type="textarea"
size="tiny"
@input="InputDebounced"
v-model:value="data.prompt"
:autosize="{ minRows: 6, maxRows: 6 }"
></n-input>
</template>
<script>
import { ref, onMounted, defineComponent, onUnmounted, toRaw, watch, h } from 'vue'
import { useMessage, useDialog, NInput, NButton, NDropdown } from 'naive-ui'
import { useSoftwareStore } from '../../../../../stores/software'
import { useReverseManageStore } from '../../../../../stores/reverseManage'
import { OperateBookType } from '../../../../../define/enum/bookEnum'
import { BookImageCategory } from '../../../../../define/enum/bookEnum'
import { debounce } from 'lodash'
import InputDialogContent from '../../Original/Components/InputDialogContent.vue'
import { DEFINE_STRING } from '../../../../../define/define_string'
export default defineComponent({
components: { NInput, NButton, NDropdown },
props: ['initData', 'index'],
setup(props) {
let message = useMessage()
let softwareStore = useSoftwareStore()
let reverseManageStore = useReverseManageStore()
let data = ref(props.initData)
let image_url_ref = ref(null)
let dialog = useDialog()
onMounted(async () => {})
//
async function MergePrompt(key) {
let type = key
if (!key) {
if (reverseManageStore.selectBookTask.imageCategory == BookImageCategory.MJ) {
type = 'mj_merge'
} else if (reverseManageStore.selectBookTask.imageCategory == BookImageCategory.SD) {
type = 'sd_merge'
} else {
type = 'mj_merge'
}
}
//
//
softwareStore.spin.spinning = true
softwareStore.spin.tip = '合并命令中。。。'
let res = await window.book.MergePrompt(data.value.id, type, OperateBookType.BOOKTASKDETAIL)
softwareStore.spin.spinning = false
//
if (res.code == 0) {
message.error(res.message)
return
}
for (let i = 0; i < res.data.length; i++) {
const element = res.data[i]
let selectBookDetailIndex = reverseManageStore.selectBookTaskDetail.findIndex(
(item) => item.id == element.id
)
reverseManageStore.selectBookTaskDetail[selectBookDetailIndex].prompt = element.prompt
}
message.success(res.message)
}
//
async function SingleGenerateImage() {
let res = undefined
if (reverseManageStore.selectBookTask.imageCategory == BookImageCategory.MJ) {
res = await window.mj.AddMJGenerateImageTask(
data.value.id,
OperateBookType.BOOKTASKDETAIL,
DEFINE_STRING.BOOK.MJ_IMAGE_GENERATE_RETURN
)
} else if (reverseManageStore.selectBookTask.imageCategory == BookImageCategory.SD) {
// res = await window.sd.SingleGenerateImage(data.value.id, OperateBookType.BOOKTASKDETAIL)
message.error('暂不支该出图模式')
} else {
message.error('未知的出图模式')
return
}
if (res.code == 0) {
message.error(res.message)
return
}
message.success(res.message)
}
//
async function UnderGenerateImage() {
let res = undefined
if (reverseManageStore.selectBookTask.imageCategory == BookImageCategory.MJ) {
res = await window.mj.AddMJGenerateImageTask(
data.value.id,
OperateBookType.UNDERBOOKTASK,
DEFINE_STRING.BOOK.MJ_IMAGE_GENERATE_RETURN
)
} else if (reverseManageStore.selectBookTask.imageCategory == BookImageCategory.SD) {
// res = await window.sd.SingleGenerateImage(data.value.id, OperateBookType.UNDERBOOKTASK)
message.error('暂不支该出图模式')
} else {
message.error('未知的出图模式')
return
}
if (res.code == 0) {
message.error(res.message)
return
}
message.success(res.message)
}
let InputDebounced = debounce(handleInput, 1000)
async function handleInput() {
let res = await window.db.UpdateBookTaskDetailData(data.value.id, {
prompt: data.value.prompt
})
if (res.code == 0) {
message.error(res.message)
}
}
/**
* 写入图片的链接将图片链接下载并且分割
*/
async function ImportMJImageUrl() {
//
//
let dialogWidth = 400
let dialogHeight = 150
let da = dialog.create({
title: '添加图片链接/本地地址',
showIcon: false,
closeOnEsc: false,
content: () =>
h(InputDialogContent, {
ref: image_url_ref,
initData: null,
placeholder: '请输入图片链接/本地地址'
}),
style: `width : ${dialogWidth}px; min-height : ${dialogHeight}px`,
maskClosable: false,
onClose: async () => {
da?.destroy()
let row_image_url = image_url_ref.value.data
softwareStore.spin.spinning = true
softwareStore.spin.tip = '正在下载图片/分割图片中。。。'
let res = await window.book.DownloadImageUrlAndSplit(data.value.id, row_image_url)
softwareStore.spin.spinning = false
if (res.code == 0) {
message.error(res.message)
return
}
data.value.outImagePath = res.data.outImagePath
data.value.subImagePath = res.data.subImagePath
message.success(res.message)
}
})
}
return {
softwareStore,
reverseManageStore,
InputDebounced,
SingleGenerateImage,
MergePrompt,
data,
image_url_ref,
ImportMJImageUrl,
UnderGenerateImage,
mergeOptions: [
{ label: 'MJ模式合并', key: 'mj_merge' },
{ label: 'SD模式合并', key: 'sd_merge' }
]
}
}
})
</script>

View File

@ -33,7 +33,7 @@
</n-button>
</template>
<script>
<script setup>
import { ref, onMounted, defineComponent, h } from 'vue'
import { useMessage, NButton, useDialog } from 'naive-ui'
import { useReverseManageStore } from '../../../../../../stores/reverseManage.ts'
@ -41,27 +41,24 @@ import { useSoftwareStore } from '../../../../../../stores/software.ts'
import { BookType, OperateBookType } from '../../../../../../define/enum/bookEnum.ts'
import ManageBookTaskGenerateInformation from './ManageBookTaskGenerateInformation.vue'
export default defineComponent({
components: {
NButton
},
let props = defineProps({
bookTask: undefined
})
props: ['bookTask'],
setup(props) {
let message = useMessage()
let dialog = useDialog()
let bookTask = ref(props.bookTask)
console.log('bookTask', bookTask.value)
let reverseManageStore = useReverseManageStore()
let softwareStore = useSoftwareStore()
let message = useMessage()
let dialog = useDialog()
let bookTask = ref(props.bookTask)
console.log('bookTask', bookTask.value)
let reverseManageStore = useReverseManageStore()
let softwareStore = useSoftwareStore()
onMounted(async () => {})
onMounted(async () => {})
/**
/**
* 重置小说数据
* @param e
*/
async function ReSetBookTaskDetail(e) {
async function ReSetBookTaskDetail(e) {
e.stopPropagation()
dialog.warning({
title: '重置小说任务',
@ -78,12 +75,12 @@ export default defineComponent({
}
}
})
}
}
/**
/**
* 删除小说任务
*/
async function DeleteBookTask(e) {
async function DeleteBookTask(e) {
e.stopPropagation()
dialog.warning({
title: '删除小说任务',
@ -110,9 +107,9 @@ export default defineComponent({
}
}
})
}
}
async function hdImageFunc(id, operateBookType) {
async function hdImageFunc(id, operateBookType) {
let da = dialog.warning({
title: '高清提示',
content: `是否确认高清,当前的高清倍数为 ${softwareStore.globalSetting.hdScale} 倍,是否继续?`,
@ -135,12 +132,12 @@ export default defineComponent({
}
}
})
}
}
/**
/**
* 高清图片
*/
async function HDImage(e) {
async function HDImage(e) {
e.stopPropagation()
softwareStore.spin.spinning = true
softwareStore.spin.tip = '正在进行高清检查。。。'
@ -175,12 +172,12 @@ export default defineComponent({
//
await hdImageFunc(bookTask.value.id, OperateBookType.BOOKTASK)
}
}
}
/**
/**
* 生成草稿
*/
async function ClipDraft(e) {
async function ClipDraft(e) {
e.stopPropagation()
dialog.info({
@ -188,28 +185,29 @@ export default defineComponent({
title: `生成草稿前检查 ${bookTask.value.name}`,
maskClosable: false,
content: () =>
h(ManageBookTaskGenerateInformation, { bookTask: bookTask.value, type: 'bookTask' })
h(ManageBookTaskGenerateInformation, {
bookTask: bookTask.value,
type: 'bookTask',
optionType: 'draft'
})
}
})
}
/**
/**
* 合成视频
*/
async function GenerateVideo(e) {
async function GenerateVideo(e) {
e.stopPropagation()
message.error('该功能暂不可用')
}
return {
bookTask,
reverseManageStore,
ClipDraft,
GenerateVideo,
softwareStore,
ReSetBookTaskDetail,
DeleteBookTask,
HDImage
}
}
})
dialog.info({
closeOnEsc: false,
title: `合成视频前检查 ${bookTask.value.name}`,
maskClosable: false,
content: () =>
h(ManageBookTaskGenerateInformation, {
bookTask: bookTask.value,
type: 'bookTask',
optionType: 'video'
})
})
}
</script>

View File

@ -46,7 +46,7 @@
clearable
/>
</n-form-item>
<n-form-item label="选择剪映草稿" path="backgroundMusic">
<n-form-item label="选择剪映草稿" path="backgroundMusic" v-if="optionType != 'video'">
<div>
<div style="color: red">
注意选择的草稿主轨道的图片数量要和当前的相同会生成新的草稿并且只会替换图片保留其余的数据
@ -67,12 +67,23 @@
>
<n-button type="info" style="margin-left: 10px" @click="SaveVideoData">保存数据</n-button>
</n-form-item>
<div v-if="type == 'bookTask'" style="color: red">注意在生成草稿前要先保存数据</div>
<div v-if="type == 'bookTask'" style="color: red">
注意 生成草稿/合成视频 前要先保存数据
</div>
<div v-else style="color: red">
注意在生成草稿前要先保存数据当前会生成选择的草稿全部会使用上面的参数
注意 生成草稿/合成视频 前要先保存数据当前会生成选择的 草稿/视频全部会使用上面的参数
</div>
<n-form-item style="display: flex; justify-content: flex-end">
<n-button type="info" style="margin-left: 10px" @click="AddJianyingDraft">生成草稿</n-button>
<n-button
v-if="optionType == 'video'"
type="info"
style="margin-left: 10px"
@click="AddGenerateVideoTask"
>添加合成视频任务</n-button
>
<n-button v-else type="info" style="margin-left: 10px" @click="AddJianyingDraft"
>生成草稿</n-button
>
</n-form-item>
</n-form>
</template>
@ -81,18 +92,21 @@
import { ref, onMounted } from 'vue'
import { useMessage, NForm, NFormItem, NButton, NIcon, NInput, NSelect } from 'naive-ui'
import { FolderOpen } from '@vicons/ionicons5'
import { OperateBookType } from '../../../../../../define/enum/bookEnum'
import { BookTaskStatus, OperateBookType } from '../../../../../../define/enum/bookEnum'
import { useReverseManageStore } from '../../../../../../stores/reverseManage'
let props = defineProps({
bookTask: undefined,
type: undefined,
selectBookTask: []
selectBookTask: [],
optionType: undefined
})
let bookTask = ref(props.bookTask)
let type = ref(props.type)
let optionType = ref(props.optionType)
let backgroundMusicOptions = ref([])
let message = useMessage()
let draftSelect = ref([])
@ -217,23 +231,78 @@ async function SaveVideoData() {
}
}
/**
* 添加草稿
*/
async function AddJianyingDraft() {
async function AddGenerateVideoTask() {
let res = undefined
if (props.type == 'book') {
let res = await window.book.AddJianyingDraft(
//
res = await window.book.AddGenerateVideoTask(
props.selectBookTask,
OperateBookType.ASSIGNBOOKTASK
)
window.api.showGlobalMessageDialog(res)
if (res.code == 1) {
reverseManageStore.UpdatedBookTaskData(props.selectBookTask, {
status: BookTaskStatus.COMPOSING
})
} else {
reverseManageStore.UpdatedBookTaskData(props.selectBookTask, {
status: BookTaskStatus.COMPOSING_FAIL,
errorMsg: res.message
})
}
} else if (props.type == 'bookTask') {
let res = await window.book.AddJianyingDraft(bookTask.value.id, OperateBookType.BOOKTASK)
window.api.showGlobalMessageDialog(res)
//
res = await window.book.AddGenerateVideoTask(bookTask.value.id, OperateBookType.BOOKTASK)
if (res.code == 1) {
reverseManageStore.UpdatedBookTaskData(bookTask.value.id, {
status: BookTaskStatus.COMPOSING
})
} else {
reverseManageStore.UpdatedBookTaskData(bookTask.value.id, {
status: BookTaskStatus.COMPOSING_FAIL,
errorMsg: res.message
})
}
} else {
message.error('未知的操作类型,请检查')
return
}
window.api.showGlobalMessageDialog(res)
}
/**
* 添加草稿
*/
async function AddJianyingDraft() {
let res = undefined
if (props.type == 'book') {
res = await window.book.AddJianyingDraft(props.selectBookTask, OperateBookType.ASSIGNBOOKTASK)
if (res.code == 1) {
reverseManageStore.UpdatedBookTaskData(props.selectBookTask, {
status: BookTaskStatus.DRAFT_DONE
})
} else {
reverseManageStore.UpdatedBookTaskData(props.selectBookTask, {
status: BookTaskStatus.DRAFT_FAIL,
errorMsg: res.message
})
}
} else if (props.type == 'bookTask') {
res = await window.book.AddJianyingDraft(bookTask.value.id, OperateBookType.BOOKTASK)
if (res.code == 1) {
reverseManageStore.UpdatedBookTaskData(bookTask.value.id, {
status: BookTaskStatus.DRAFT_DONE
})
} else {
reverseManageStore.UpdatedBookTaskData(bookTask.value.id, {
status: BookTaskStatus.DRAFT_FAIL,
errorMsg: res.message
})
}
} else {
message.error('未知的操作类型,请检查')
return
}
window.api.showGlobalMessageDialog(res)
}
let rules = ref({
srtPath: [{ required: true, message: '请选择背景音乐', trigger: ['input', 'blur', 'change'] }],

View File

@ -1,960 +0,0 @@
<template>
<div style="display: flex; justify-content: space-between; align-items: center">
<div style="display: flex; align-items: center">
<n-button strong secondary @click="RetuenMain" size="small"> 返回 </n-button>
<!-- <n-dropdown trigger="hover" :options="AutoOptions" @select="SelectAutoAction">
<n-button :color="softwareStore.SoftColor.ORANGE" @click="Start" style="margin-left: 5px">
自动开始
</n-button>
</n-dropdown> -->
<n-divider vertical style="height: 30px" />
<n-dropdown trigger="hover" :options="frameOptions" @select="handleSelect">
<n-button
:color="softwareStore.SoftColor.ORANGE"
class="top-button"
size="small"
@click="ActionFrame"
>
开始分镜
</n-button>
</n-dropdown>
<n-dropdown trigger="hover" :options="copywritingOptions" @select="handleSelect">
<n-button
:color="softwareStore.SoftColor.ORANGE"
style="margin-left: 5px"
size="small"
@click="GetCopywriting('all')"
>
开始提取全部文案
</n-button>
</n-dropdown>
<n-dropdown trigger="hover" :options="watermarkOptions" @select="handleSelect">
<n-button
:color="softwareStore.SoftColor.ORANGE"
style="margin-left: 5px"
size="small"
@click="RemoveWatermark"
>
开始去除水印
</n-button>
</n-dropdown>
<n-dropdown trigger="hover" :options="importWordOptions" @select="handleSelect">
<n-button
:color="softwareStore.SoftColor.ORANGE"
style="margin-left: 5px"
size="small"
@click="ImportWordAndSrtFunc"
>
导入文案/SRT
</n-button>
</n-dropdown>
<n-dropdown trigger="hover" :options="reverseOptions" @select="handleSelect">
<n-button
:color="softwareStore.SoftColor.ORANGE"
style="margin-left: 5px"
size="small"
@click="ImageReversePrompt(undefined)"
>
一键反推
</n-button>
</n-dropdown>
<n-dropdown trigger="hover" :options="translateOptions" @select="handleSelect">
<n-button
:color="softwareStore.SoftColor.ORANGE"
style="margin-left: 5px"
size="small"
@click="Translate(undefined)"
>
一键翻译
</n-button>
</n-dropdown>
<n-dropdown trigger="hover" :options="generateOptions" @select="handleSelect">
<n-button
:color="softwareStore.SoftColor.ORANGE"
style="margin-left: 5px"
size="small"
@click="GenerateImageAll"
>
一键生图
</n-button>
</n-dropdown>
<n-dropdown trigger="hover" :options="resetOptions" @select="handleSelect">
<n-button
:color="softwareStore.SoftColor.ORANGE"
style="margin-left: 5px"
size="small"
@click="ResetAllData"
>
重置
</n-button>
</n-dropdown>
<n-button
:color="softwareStore.SoftColor.ORANGE"
style="margin-left: 5px"
size="small"
@click="OpenSettingDialog"
>
设置
</n-button>
<div style="margin-left: 10px">
<MonitorStatus />
</div>
</div>
<div class="right-button">
<n-button
text
strong
:type="softwareStore.show_logger ? 'default' : 'info'"
@click="switchLogger"
>
{{ softwareStore.show_logger ? '隐藏日志' : '显示日志' }}
</n-button>
</div>
</div>
</template>
<script>
import { ref, onMounted, defineComponent, onUnmounted, toRaw, watch, h } from 'vue'
import { useMessage, NButton, useDialog, NCheckbox, NDropdown, NDivider, NRate } from 'naive-ui'
import { useRouter } from 'vue-router'
import { useSoftwareStore } from '../../../../../stores/software'
import { useReverseManageStore } from '../../../../../stores/reverseManage.ts'
import { isEmpty } from 'lodash'
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, 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'
import MonitorStatus from './MonitorStatus.vue'
export default defineComponent({
components: {
NButton,
NCheckbox,
NDropdown,
NDivider,
GetWaterMaskRectangle,
MonitorStatus
},
setup() {
let router = useRouter()
let message = useMessage()
let dialog = useDialog()
let reverseManageStore = useReverseManageStore()
let softwareStore = useSoftwareStore()
//
onMounted(async () => {})
//
function RetuenMain() {
router.push('/book_management')
}
//
async function AutoAction() {
//
let res_frame = await window.book.AutoAction(reverseManageStore.selectBook.id)
}
//
async function SelectAutoAction() {}
//
async function switchLogger() {
softwareStore.show_logger = !softwareStore.show_logger
}
/**
* 开始分镜
*/
async function ComputeStoryboard() {
softwareStore.spin.spinning = true
softwareStore.spin.tip = '正在计算分镜中'
if (isEmpty(reverseManageStore.selectBookTask.id)) {
window.api.showGlobalMessageDialog({
code: 0,
message: '没有找到要执行的任务'
})
return
}
let res_frame = await window.book.ComputeStoryboard(reverseManageStore.selectBook.id)
if (res_frame.code == 0) {
softwareStore.spin.spinning = false
window.api.showGlobalMessageDialog(res_frame)
return
}
//
let getRes = await reverseManageStore.GetBookTaskDataFromDB({
id: reverseManageStore.selectBookTask.id
})
if (getRes.code == 0) {
softwareStore.spin.spinning = false
message.error(getRes.message)
return
}
softwareStore.spin.spinning = false
message.success('重新获取数据成功')
}
//
async function Framing() {
softwareStore.spin.spinning = true
softwareStore.spin.tip = '正在分镜中'
if (isEmpty(reverseManageStore.selectBookTask.id)) {
window.api.showGlobalMessageDialog({
code: 0,
message: '没有找到要执行的任务'
})
return
}
let res_frame = await window.book.Framing(reverseManageStore.selectBook.id)
if (res_frame.code == 0) {
softwareStore.spin.spinning = false
window.api.showGlobalMessageDialog(res_frame)
return
}
//
let getRes = await reverseManageStore.GetBookTaskDataFromDB({
id: reverseManageStore.selectBookTask.id
})
if (getRes.code == 0) {
softwareStore.spin.spinning = false
message.error(getRes.message)
return
}
softwareStore.spin.spinning = false
message.success('重新获取数据成功')
}
//
async function GetCopywriting(type) {
if (isEmpty(reverseManageStore.selectBookTask.id)) {
window.api.showGlobalMessageDialog({
code: 0,
message: '没有找到要执行的任务'
})
return
}
let subtitleSettingRes = await window.write.GetSubtitleSetting()
if (subtitleSettingRes.code == 0) {
message.error(subtitleSettingRes.message)
return
}
let da = dialog.warning({
title: '开始提取文案提示',
content: `即将进行 ${type == 'blank' ? '空白' : '全部'} 文案提取${
type == 'blank' ? '' : ',会重新提取并覆盖旧的文案数据'
}当前的文案提取模式为 ${subtitleSettingRes.data.selectModel} 是否继续`,
positiveText: '继续',
negativeText: '取消',
onPositiveClick: async () => {
da?.destroy()
softwareStore.spin.spinning = true
softwareStore.spin.tip = `正在使用 ${subtitleSettingRes.data.selectModel} 提取文案,正在准备中。。。`
let copywriting_res = await window.book.GetCopywriting(
reverseManageStore.selectBook.id,
reverseManageStore.selectBookTask.id,
OperateBookType.BOOKTASK,
type == 'blank' ? false : true
)
softwareStore.spin.spinning = false
if (copywriting_res.code == 0) {
message.error(copywriting_res.message)
return
}
// dialog.success({})
message.success('获取全部文案成功')
}
})
}
//
async function RemoveWatermark() {
if (isEmpty(reverseManageStore.selectBookTask.id)) {
window.api.showGlobalMessageDialog({
code: 0,
message: '没有找到要执行的任务'
})
return
}
let da = dialog.warning({
title: '去除水印提示',
content: '即将去除全部水印,是否继续?',
positiveText: '继续',
negativeText: '取消',
onPositiveClick: async () => {
da?.destroy()
softwareStore.spin.spinning = true
softwareStore.spin.tip = '正在去除水印中。。。'
let res_frame = await window.book.RemoveWatermark(
reverseManageStore.selectBookTask.id,
OperateBookType.BOOKTASK
)
softwareStore.spin.spinning = false
window.api.showGlobalMessageDialog(res_frame)
}
})
}
//
async function GetCopywritingSetting() {
let dialogHeight = window.innerHeight * 0.9
dialog.create({
showIcon: false,
closeOnEsc: false,
content: () =>
h(VideoCanvas, {
videoWidth: 800,
videoSrc: reverseManageStore.selectBook.oldVideoPath,
height: dialogHeight - 50,
type: SubtitleSavePositionType.SETTING
}),
style: `width : 850px; height : ${dialogHeight}px`,
maskClosable: false,
onClose: () => {}
})
}
//
async function ExportCopywriting() {
softwareStore.spin.spinning = true
softwareStore.spin.tip = '正在导出文案中...'
let res = await window.book.ExportCopywriting(reverseManageStore.selectBookTask.id)
if (res.code == 0) {
message.error(res.message)
} else {
dialog.success({
title: '导出文案成功',
content: '导出文案成功,是否打开导出的字幕文件',
positiveText: '打开',
negativeText: '取消',
onPositiveClick: () => {
window.system.OpenFile(res.data)
}
})
}
softwareStore.spin.spinning = false
}
//
async function ImportCopywriting() {
window.api.SelectFile(['txt'], async (value) => {
if (value.code == 0) {
message.error(value.message)
return
}
let txtPath = value.value
//
let res = await window.book.ImportCopywriting(
reverseManageStore.selectBook.id,
reverseManageStore.selectBookTask.id,
txtPath
)
if (res.code == 1) {
for (let i = 0; i < res.data.length; i++) {
const element = res.data[i]
let index = reverseManageStore.selectBookTaskDetail.findIndex(
(x) => x.id == element.bookTaskDetailId
)
if (index != -1) {
reverseManageStore.selectBookTaskDetail[index].afterGpt = element.afterGpt
}
}
} else {
window.api.showGlobalMessageDialog(res)
}
})
}
/**
* 获取水印位置
*/
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': //
await ComputeStoryboard()
break
case 'framing': //
await Framing()
break
case 'recognizing_blank': //
await GetCopywriting('blank')
break
case 'recognizing_setting': //
await GetCopywritingSetting()
break
case 'export_recognizing': //
await ExportCopywriting()
break
case 'import_recognizing': //
await ImportCopywriting()
break
case 'watermark_position': //
await GetWatermarkPosition()
break
case 'clear_import_word': //
await ClearImportWord()
break
case 'mj_reverse': // MJ
await ImageReversePrompt(BookType.MJ_REVERSE)
break
case 'sd_reverse': // SD
await ImageReversePrompt(BookType.SD_REVERSE)
break
case 'translate_chinese': //
await Translate('translate_chinese')
break
case 'translate_english': //
await Translate('translate_english')
break
case 'translate_setting': //
await Translate('translate_setting')
break
case 'reset_gpt_reverse_data': // /GPT
await ResetGptReverseData()
break
case 'reset_merge_prompt': //
await ResetMergePromptData()
break
case 'reset_Generate_Image': //
await ResetGenerateImage()
break
default:
message.error('未知的选项')
}
}
//
async function ResetGenerateImage() {
dialog.warning({
title: '重置所有图片提示',
content:
'继续操作会重置所有的生成的图片,包括生成的主图和子图,但是并不会删除本地的图片文件,是否继续?',
positiveText: '继续',
negativeText: '取消',
onPositiveClick: async () => {
let res = await window.book.ResetGenerateImage(
reverseManageStore.selectBookTask.id,
OperateBookType.BOOKTASK
)
if (res.code == 0) {
message.error(res.message)
} else {
//
for (let i = 0; i < reverseManageStore.selectBookTaskDetail.length; i++) {
reverseManageStore.selectBookTaskDetail[i].mjMessage = undefined
reverseManageStore.selectBookTaskDetail[i].outImagePath = undefined
reverseManageStore.selectBookTaskDetail[i].subImagePath = undefined
}
message.success('重置所有生成图片成功')
}
}
})
}
//
async function ResetMergePromptData() {
dialog.warning({
title: '重置合并提示词数据提示',
content: '继续操作会重置所有的合并提示词数据,请确认是不是继续?',
positiveText: '继续',
negativeText: '取消',
onPositiveClick: async () => {
let res = await window.book.ResetMergePromptData(
reverseManageStore.selectBookTask.id,
OperateBookType.BOOKTASK
)
if (res.code == 0) {
message.error(res.message)
} else {
//
for (let i = 0; i < reverseManageStore.selectBookTaskDetail.length; i++) {
reverseManageStore.selectBookTaskDetail[i].prompt = undefined
}
message.success('重置所有合并提示词数据成功')
}
}
})
}
/**
* 重置反推/GPT提示词更具不同的小说类型进行不同的重置
*/
async function ResetGptReverseData() {
dialog.warning({
title: '删除反推提示词警告提示',
content: '继续操作会移除所有的反推提示词数据,注意,是所有,请确认是不是继续?',
positiveText: '继续',
negativeText: '取消',
onPositiveClick: async () => {
let res = await window.book.ResetGptReverseData(
reverseManageStore.selectBookTask.id,
OperateBookType.BOOKTASK,
reverseManageStore.selectBook.type
)
if (res.code == 0) {
message.error(res.message)
} else {
//
for (let i = 0; i < reverseManageStore.selectBookTaskDetail.length; i++) {
reverseManageStore.selectBookTaskDetail[i].reversePrompt = undefined
reverseManageStore.selectBookTaskDetail[i].gptPrompt = undefined
}
message.success('删除所有的反推/GPT提示词成功')
}
}
})
}
/**
* 打开设置对话框
*/
async function OpenSettingDialog() {
let dialogWidth = window.innerWidth * 0.8
let dialogHeight = window.innerHeight * 0.95
dialog.create({
showIcon: false,
title: '设置',
content: () => h(Setting, { height: dialogHeight }),
style: `width : ${dialogWidth}px; height : ${dialogHeight}px`,
maskClosable: false
})
}
/**
* MJ反推数据
* @param type 反推类型
*/
async function ImageReversePrompt(type = undefined) {
if (isEmpty(reverseManageStore.selectBookTask.id)) {
window.api.showGlobalMessageDialog({
code: 0,
message: '没有找到要执行的任务'
})
return
}
dialog.warning({
title: '反推提示',
content: `即将进行反推操作,反推方式为 ${
type ? type : reverseManageStore.selectBook.type
} 是否继续`,
positiveText: '继续',
negativeText: '取消',
onPositiveClick: async () => {
if (!type) {
let bookType = reverseManageStore.selectBook.type
type = bookType
}
if (type != BookType.MJ_REVERSE && type != BookType.SD_REVERSE) {
message.error(`该类型 ${bookType} 的小说不支持反推`)
return
}
//
let res = await window.book.AddReversePrompt(
reverseManageStore.selectBookTask.id,
OperateBookType.BOOKTASK,
type
)
if (res.code == 0) {
message.error(res.message)
} else {
message.success('添加所有反推任务成功')
}
}
})
}
/**
* 翻译提示词
* @param key 操作的值进行分类
*/
async function Translate(key) {
switch (key) {
case undefined:
case null:
message.error('请选择子菜单中的选项')
break
case 'translate_chinese': //
await TranslateService('en', 'zh')
break
case 'translate_english': //
await TranslateService('zh', 'en')
break
case 'translate_setting': //
await OpenTranslateSettingDialog()
break
default:
message.error('不支持的操作类型')
break
}
}
/**
* 打开翻译设置弹窗
*/
async function OpenTranslateSettingDialog() {
//
//
let dialogWidth = window.innerWidth * 0.8
let dialogHeight = window.innerHeight * 0.9
dialog.create({
showIcon: false,
title: '翻译设置',
content: () => h(TranslateSetting),
style: `width : ${dialogWidth}px; height : ${dialogHeight}px`,
maskClosable: false
})
}
/**
* 调用翻译接口
* @param from 源语言
* @param to 目标语言
*/
async function TranslateService(from, to) {
let translateData = []
for (let i = 0; i < reverseManageStore.selectBookTaskDetail.length; i++) {
const element = reverseManageStore.selectBookTaskDetail[i]
if (!isEmpty(element.gptPrompt)) {
translateData.push({
text: element.gptPrompt,
from: from,
to: to,
type: TranslateType.GPT_PROMPT_TRANSLATE,
isSplit: false, // true
bookTaskDetailId: element.id,
responseMessgeName: DEFINE_STRING.BOOK.REVERSE_PROMPT_TRANSLATE_RETURN
})
} else {
let reversePrompt = element.reversePrompt
if (reversePrompt) {
reversePrompt.forEach((item) => {
if (from == 'zh' && to == 'en') {
//
if (!ContainsChineseOrPunctuation(item.promptCN ? item.promptCN : item.prompt)) {
return
}
}
let temp_obj = {
text: item.promptCN ? item.promptCN : item.prompt,
from: from,
to: to,
type: TranslateType.REVERSE_PROMPT_TRANSLATE,
isSplit: false, // true
bookTaskDetailId: element.id,
reversePromptId: item.id,
responseMessgeName: DEFINE_STRING.BOOK.REVERSE_PROMPT_TRANSLATE_RETURN
}
translateData.push(temp_obj)
})
}
}
}
if (translateData.length == 0) {
message.error('没有需要翻译的数据')
return
}
softwareStore.spin.spinning = true
softwareStore.spin.tip = `翻译中 ... 0 / ${translateData.length}`
//
let res = await window.translate.TranslateNowReturn(translateData)
softwareStore.spin.spinning = false
window.api.showGlobalMessageDialog(res)
}
/**
* 生成所有图片
*/
async function GenerateImageAll() {
let res = await window.book.GenerateImageAll(
reverseManageStore.selectBookTask.id,
reverseManageStore.selectBookTask.imageCategory
)
if (res.code == 0) {
message.error(res.message)
return
}
message.success(res.message)
}
/**
* 导入文案和SRT
*/
async function ImportWordAndSrtFunc() {
let dialogWidth = window.innerWidth * 0.95
let dialogHeight = window.innerHeight * 0.95
//
if (
reverseManageStore.selectBook.type == BookType.MJ_REVERSE ||
reverseManageStore.selectBook.type == BookType.MJ_REVERSE
) {
if (reverseManageStore.selectBookTaskDetail.length <= 0) {
message.error('反推小说,没有找到对应的分镜数据')
}
}
let tempData = []
for (let i = 0; i < reverseManageStore.selectBookTaskDetail.length; i++) {
const element = reverseManageStore.selectBookTaskDetail[i]
tempData.push({
no: element.no,
id: element.id,
lastId: i == 0 ? '' : reverseManageStore.selectBookTaskDetail[i - 1].id,
word: element.word,
after_gpt: element.afterGpt,
subValue: element.subValue ? element.subValue : [],
name: element.name + '.png',
prompt: element.prompt,
timeLimit: element.timeLimit
})
}
// ImportWordAndSrt
dialog.create({
showIcon: false,
closeOnEsc: false,
content: () =>
h(ImportWordAndSrt, {
initData: tempData,
height: dialogHeight,
type: BookType.MJ_REVERSE
}),
style: `width : ${dialogWidth}px; min-height : ${dialogHeight}px`,
maskClosable: false,
onClose: () => {}
})
}
/**
* 清除导入文案
*/
async function ClearImportWord() {
let da = dialog.warning({
title: '清除导入文案提示',
content:
'继续执行该操作会将导入的对齐文案全部清除(包括文案的时间线),并且操作不可逆,是否继续?',
positiveText: '继续',
negativeText: '取消',
onPositiveClick: async () => {
da?.destroy()
softwareStore.spin.spinning = true
softwareStore.spin.tip = '正在清除导入数据。。。'
let res = await window.book.ClearImportWord(reverseManageStore.selectBookTask.id)
softwareStore.spin.spinning = false
if (res.code == 0) {
message.error(res.message)
} else {
//
for (let i = 0; i < res.data.length; i++) {
const element = res.data[i]
let index = reverseManageStore.selectBookTaskDetail.findIndex(
(item) => item.id == element.id
)
if (index != -1) {
reverseManageStore.selectBookTaskDetail[index].subValue = undefined
reverseManageStore.selectBookTaskDetail[index].startTime = element.startTime
reverseManageStore.selectBookTaskDetail[index].endTime = element.endTime
reverseManageStore.selectBookTaskDetail[index].timeLimit = element.timeLimit
}
}
message.success('清除导入文案成功')
}
}
})
}
async function ActionFrame() {
message.info('请使用下面菜单中的按钮进行对应的操作')
}
return {
RetuenMain,
AutoAction,
ImportWordAndSrtFunc,
ClearImportWord,
reverseManageStore,
softwareStore,
ActionFrame,
switchLogger,
ComputeStoryboard,
OpenSettingDialog,
GetCopywriting,
RemoveWatermark,
ImageReversePrompt,
ResetGptReverseData,
Translate,
TranslateService,
GenerateImageAll,
handleSelect,
AutoOptions: [
{
label: '停止任务',
value: 'auto_stop'
},
{
label: '继续任务',
value: 'auto_continue'
}
],
frameOptions: [
{
label: '计算分镜',
key: 'compute_frame'
},
{ label: '分镜', key: 'framing' }
],
copywritingOptions: [
{
label: '提取空白文案',
key: 'recognizing_blank'
},
{
label: '提取文案位置',
key: 'recognizing_setting'
},
{
label: '导出文案',
key: 'export_recognizing'
},
{
label: '导入文案',
key: 'import_recognizing'
},
{
label: '停止提取',
key: 'stop_recognizing'
}
],
watermarkOptions: [
{
label: '选择水印位置',
key: 'watermark_position'
},
{
label: '停止去除',
key: 'stop_watermark'
}
],
reverseOptions: [
{
label: 'MJ反推',
key: 'mj_reverse'
},
{
label: 'SD反推',
key: 'sd_reverse',
disabled: reverseManageStore.selectBook.type != BookType.SD_REVERSE
},
{
label: '停止反推',
key: 'stop_reverse'
}
],
translateOptions: [
{
label: '英文 -> 中文',
key: 'translate_chinese'
},
{
label: '中文 -> 英文',
key: 'translate_english'
},
{
label: '翻译设置',
key: 'translate_setting'
},
{
label: '停止翻译',
key: 'translate_stop'
}
],
generateOptions: [
{
label: '停止生图',
key: 'stop_image_generate'
}
],
importWordOptions: [
{
label: '清除导入文案',
key: 'clear_import_word'
}
],
resetOptions: [
{
label: '重置反推/GPT提示词',
key: 'reset_gpt_reverse_data'
},
{
label: '重置合并提示词',
key: 'reset_merge_prompt'
},
{
label: '重置图片',
key: 'reset_Generate_Image'
}
]
}
}
})
</script>
<style lang="css">
.right-button {
display: flex;
justify-content: flex-end;
}
.top-button {
margin-left: 5px;
}
</style>

View File

@ -35,7 +35,6 @@ export default defineComponent({
props: ['initData', 'index'],
setup(props) {
let data = ref(props.initData)
console.log('data', props.initData, props.index)
onMounted(async () => {})
let InputDebounced = debounce(handleInput, 1000)

View File

@ -0,0 +1,284 @@
<template>
<div>
<n-dropdown trigger="hover" :options="reverseOptions" @select="ReverseSelect">
<n-button size="tiny" :color="softwareStore.SoftColor.BROWN_YELLOW" @click="AddReversePrompt">
单句反推
</n-button>
</n-dropdown>
<n-dropdown trigger="hover" :options="tranlateOptions" @select="ReverseSelect">
<n-button
style="margin-left: 5px"
size="tiny"
:color="softwareStore.SoftColor.BROWN_YELLOW"
@click="TranslateOne('translate_chinese')"
>
英文 2 中文
</n-button>
</n-dropdown>
<n-button
v-if="reverseManageStore.selectBook.type == bookType.MJ_REVERSE"
style="margin-left: 5px"
size="tiny"
:color="softwareStore.SoftColor.BROWN_YELLOW"
@click="SelectReversePrompt"
>
重选反推提示词
</n-button>
</div>
</template>
<script setup>
import { ref, onMounted, defineComponent, onUnmounted, toRaw, watch, h } from 'vue'
import { useMessage, useDialog, NDropdown, NButton } from 'naive-ui'
import { useSoftwareStore } from '../../../../../stores/software'
import { useReverseManageStore } from '../../../../../stores/reverseManage'
import { TranslateType } from '../../../../../define/enum/translate'
import { ContainsChineseOrPunctuation } from '../../../../../define/Tools/common'
import ReSelectReversePrompt from '../MJReverse/ReSelectReversePrompt.vue'
import { BookType, OperateBookType } from '../../../../../define/enum/bookEnum'
import { isEmpty } from 'lodash'
import { DEFINE_STRING } from '../../../../../define/define_string'
let props = defineProps({
initData: undefined,
index: undefined
})
let message = useMessage()
let dialog = useDialog()
let data = ref(props.initData)
let index = ref(props.index)
let softwareStore = useSoftwareStore()
let reverseManageStore = useReverseManageStore()
let bookType = ref(BookType)
onMounted(async () => {})
/**
* 单句反推提示词
*/
async function AddReversePrompt() {
let res = await window.book.AddReversePrompt(data.value.id, OperateBookType.BOOKTASKDETAIL, null)
if (res.code == 0) {
message.error(res.message)
} else {
message.success('添加反推任务成功')
}
}
async function AddNextReversePrompt() {
let res = await window.book.AddReversePrompt(
data.value.id,
OperateBookType.UNDERBOOKTASK,
reverseManageStore.selectBook.type
)
if (res.code == 0) {
message.error(res.message)
} else {
message.success('添加反推任务成功')
}
}
/**
* 提示词菜单下拉操作
*/
async function ReverseSelect(key) {
switch (key) {
case 'remove_reverse': //
let res = await window.book.ResetGptReverseData(
data.value.id,
OperateBookType.BOOKTASKDETAIL,
reverseManageStore.selectBook.type
)
if (res.code == 0) {
message.error(res.message)
} else {
reverseManageStore.selectBookTaskDetail[props.index].gptPrompt = undefined
reverseManageStore.selectBookTaskDetail[props.index].reversePrompt = undefined
message.success('删除反推数据成功')
}
break
case 'translate_english': // 2
await TranslateOne('translate_english')
break
case 'down_translate_chinese': //
await TranslateOne('down_translate_chinese')
break
case 'down_translate_english': //
await TranslateOne('down_translate_english')
break
case 'down_reverse':
await AddNextReversePrompt()
break
default:
message.error('未知的操作类型')
break
}
}
//
function TranslateOneParams(from, to) {
let translateData = []
if (!isEmpty(data.value.gptPrompt)) {
translateData.push({
text: data.value.gptPrompt,
from: from,
to: to,
type: TranslateType.GPT_PROMPT_TRANSLATE,
isSplit: false, // true
bookTaskDetailId: data.value.id,
responseMessgeName: DEFINE_STRING.BOOK.REVERSE_PROMPT_TRANSLATE_RETURN
})
} else {
let reversePrompt = data.value.reversePrompt
if (reversePrompt) {
reversePrompt.forEach((item) => {
let temp_obj = undefined
if (from == 'zh' && to == 'en') {
//
if (!ContainsChineseOrPunctuation(item.promptCN ? item.promptCN : item.prompt)) {
return
}
}
temp_obj = {
text: item.prompt,
from: from,
to: to,
type: TranslateType.REVERSE_PROMPT_TRANSLATE,
isSplit: false, // true
bookTaskDetailId: data.value.id,
reversePromptId: item.id,
responseMessgeName: DEFINE_STRING.BOOK.REVERSE_PROMPT_TRANSLATE_RETURN
}
translateData.push(temp_obj)
})
}
}
return translateData
}
//
function TranslateBatchParams(from, to) {
let translateData = []
for (let i = index.value; i < reverseManageStore.selectBookTaskDetail.length; i++) {
const element = reverseManageStore.selectBookTaskDetail[i]
// GPTGPT
if (!isEmpty(element.gptPrompt)) {
translateData.push({
text: element.gptPrompt,
from: from,
to: to,
type: TranslateType.GPT_PROMPT_TRANSLATE,
isSplit: false, // true
bookTaskDetailId: element.id,
responseMessgeName: DEFINE_STRING.BOOK.REVERSE_PROMPT_TRANSLATE_RETURN
})
} else {
let reversePrompt = element.reversePrompt
if (reversePrompt) {
reversePrompt.forEach((item) => {
if (from == 'zh' && to == 'en') {
//
if (!ContainsChineseOrPunctuation(item.promptCN ? item.promptCN : item.prompt)) {
return
}
}
let temp_obj = {
text: item.prompt,
from: from,
to: to,
type: TranslateType.REVERSE_PROMPT_TRANSLATE,
isSplit: false, // true
bookTaskDetailId: element.id,
reversePromptId: item.id,
responseMessgeName: DEFINE_STRING.BOOK.REVERSE_PROMPT_TRANSLATE_RETURN
}
translateData.push(temp_obj)
})
}
}
}
return translateData
}
//
async function TranslateOne(key) {
let translateData = []
switch (key) {
case 'translate_english':
translateData = TranslateOneParams('zh', 'en')
break
case 'translate_chinese':
translateData = TranslateOneParams('en', 'zh')
break
case 'down_translate_english':
translateData = TranslateBatchParams('zh', 'en')
break
case 'down_translate_chinese':
translateData = TranslateBatchParams('en', 'zh')
break
default:
message.error('未知的操作类型')
break
}
if (translateData.length <= 0) {
message.error('没有需要翻译的数据')
return
}
softwareStore.spin.spinning = true
softwareStore.spin.tip = `翻译中 ... 0 / ${translateData.length}`
//
let res = await window.translate.TranslateNowReturn(translateData)
softwareStore.spin.spinning = false
window.api.showGlobalMessageDialog(res)
}
/**
* 重选反推提示词
*/
async function SelectReversePrompt() {
//
if (!data.value.reversePrompt || data.value.reversePrompt.length <= 0) {
message.error('当前没有反推提示词')
return
}
// ImportWordAndSrt
dialog.create({
showIcon: false,
title: `重选MJ反推提示词 ${data.value.name}`,
closeOnEsc: false,
content: () =>
h(ReSelectReversePrompt, {
reversePrompts: data.value.reversePrompt,
index: index.value
}),
style: `width : 750px;`,
maskClosable: false
})
}
let reverseOptions = [
{
label: '下推理',
key: 'down_reverse'
},
{
label: '删除反推提示词',
key: 'remove_reverse'
}
]
let tranlateOptions = [
{
label: '中文 2 英文',
key: 'translate_english'
},
{
label: '下翻译英文',
key: 'down_translate_english'
},
{
label: '下翻译中文',
key: 'down_translate_chinese'
}
]</script>

View File

@ -0,0 +1,235 @@
<template>
<div style="display: flex; justify-content: space-between; white-space: nowrap">
<div style="display: flex">
<div>反推/GPT提示词</div>
<n-button
v-if="reverseManageStore.selectBook.type == bookType.MJ_REVERSE"
type="text"
:color="softwareStore.SoftColor.ZHUYANTUO"
size="tiny"
style="margin-left: 10px"
@click="SelectPromptIndex"
>选择提示词</n-button
><n-button
type="text"
:color="softwareStore.SoftColor.ZHUYANTUO"
size="tiny"
style="margin-left: 5px"
@click="SelectStyle"
>风格</n-button
>
<n-button
type="text"
:color="softwareStore.SoftColor.ZHUYANTUO"
size="tiny"
style="margin-left: 5px"
@click="RemoveBadPrompt"
>去除提示词</n-button
>
<n-button
type="text"
:color="softwareStore.SoftColor.ZHUYANTUO"
size="tiny"
style="margin-left: 5px"
@click="ManagentBadPrompt"
>管理</n-button
>
</div>
<div>
<n-popover trigger="hover">
<template #trigger>
<n-button
color="#7c461e"
@click="SelectReversePromptAndReplace"
text
style="font-size: 24px"
>
<n-icon>
<FindReplaceRound />
</n-icon>
</n-button>
</template>
<span>批量替换反推提示词</span>
</n-popover>
</div>
</div>
</template>
<script setup>
import { ref, toRaw, h } from 'vue'
import { useMessage, useDialog, NButton, NPopover, NIcon } from 'naive-ui'
import { useReverseManageStore } from '../../../../../stores/reverseManage'
import { BookRepalceDataType, BookType } from '../../../../../define/enum/bookEnum'
import { useSoftwareStore } from '../../../../../stores/software'
import SelectMJReversePrompt from '../MJReverse/SelectMJReversePrompt.vue'
import SelectImageStyle from '../../Components/SelectImageStyle.vue'
import FindReplaceRound from '../../Icon/FindReplaceRound.vue'
import DatatableHeaderAfterGptSelectAndReplace from './DatatableHeaderAfterGptSelectAndReplace.vue'
import ManageBadPrompt from '../../Components/ManageBadPrompt.vue'
let message = useMessage()
let reverseManageStore = useReverseManageStore()
let softwareStore = useSoftwareStore()
let bookType = ref(BookType)
let dialog = useDialog()
let selectStyleRef = ref(null)
let da = undefined
let promprRef = ref(null)
/**
* 管理不想要的值
*/
async function ManagentBadPrompt() {
//
dialog.create({
showIcon: false,
title: '管理不想要的值',
content: () => h(ManageBadPrompt, { ref: promprRef }),
style: `width : 650px;`,
maskClosable: false,
positiveText: '确定',
negativeText: '取消',
onPositiveClick: async () => {
console.log(toRaw(promprRef.value.tags))
//
await window.api.SaveBadPrompt(toRaw(promprRef.value.tags), (value) => {
console.log(value)
if (value.code == 0) {
message.error(value.message)
return false
}
message.success('添加成功')
})
return true
}
})
}
/** 移除不想要的值 */
async function RemoveBadPrompt() {
softwareStore.spin.spinning = true
softwareStore.spin.tip = '正在移除不想要的值...'
let dia = dialog.warning({
title: '移除不想要的值提示',
content: '是否移除反推/GPT提示词里面不想要的值移除后不可恢复是否继续',
positiveText: '继续',
negativeText: '取消',
onPositiveClick: async () => {
try {
dia.destroy()
let res = await window.book.RemoveBadPrompt(reverseManageStore.selectBookTask.id)
if (res.code == 0) {
message.error(res.message)
return
}
console.log('RemoveBadPrompt', res)
if (res.data && res.data.length > 0) {
for (let i = 0; i < res.data.length; i++) {
const element = res.data[i]
let findIndex = reverseManageStore.selectBookTaskDetail.findIndex(
(item) => item.id == element.id
)
if (findIndex != -1) {
reverseManageStore.selectBookTaskDetail[findIndex].gptPrompt = element.gptPrompt
}
}
} else {
throw new Error('没有移除任何值')
}
message.success(res.message)
} catch (error) {
message.error(error.message)
} finally {
softwareStore.spin.spinning = false
}
}
})
}
/**
* 选择MJ反推提示词用于生图
*/
async function SelectPromptIndex() {
// MJ
if (reverseManageStore.selectBook.type != BookType.MJ_REVERSE) {
message.error('只有MJ反推可以使用该按钮')
return
}
let hasReversePrompt = false
//
for (let index = 0; index < reverseManageStore.selectBookTaskDetail.length; index++) {
const element = reverseManageStore.selectBookTaskDetail[index]
if (element.reversePrompt && element.reversePrompt.length > 0) {
hasReversePrompt = true
break
}
}
if (!hasReversePrompt) {
message.error('请先进行MJ反推')
return
}
dialog.create({
title: '选择反推提示词',
content: () => h(SelectMJReversePrompt),
style: 'width : 500px',
showIcon: false
})
}
//
async function SelectStyle() {
//
//
//
let dialogWidth = window.innerWidth * 0.8
let dialogHeight = window.innerHeight * 0.9
// ImportWordAndSrt
dialog.create({
showIcon: false,
closeOnEsc: false,
content: () =>
h(SelectImageStyle, {
height: dialogHeight,
ref: selectStyleRef,
imageStyle: reverseManageStore.selectBookTask.imageStyle,
customizeImageStyle: reverseManageStore.selectBookTask.customizeImageStyle
}),
style: `width : ${dialogWidth}px; height : ${dialogHeight}px`,
maskClosable: false,
onClose: async () => {
console.log(selectStyleRef)
//
// reverseManageStore.selectBookTask.styleList = toRaw(selectStyleRef.value.selectStyle)
//
let res = await window.book.SaveImageStyle(
toRaw(selectStyleRef.value.selectStyle),
reverseManageStore.selectBookTask.id
)
if (res.code == 0) {
message.error(res.message)
return
}
reverseManageStore.selectBookTask.imageStyle = res.data.imageStyle
reverseManageStore.selectBookTask.customizeImageStyle = res.data.customizeImageStyle
message.success('保存成功')
}
})
}
async function SelectReversePromptAndReplace() {
if (da) {
return
}
da = dialog.create({
title: '批量查询替换反推提示词',
showIcon: false,
maskClosable: false,
content: () =>
h(DatatableHeaderAfterGptSelectAndReplace, { type: BookRepalceDataType.GPT_PROMPT }),
onClose: () => {
da = undefined
}
})
}
</script>

View File

@ -2,7 +2,7 @@
<div style="display: flex; align-items: center">
<div>出图</div>
<n-select
style="width: 70px; margin-left: 5px"
style="width: 100px; margin-left: 5px"
size="tiny"
v-model:value="select"
@update:value="handleUpdateValue"
@ -47,13 +47,20 @@ export default defineComponent({
? reverseManageStore.selectBookTask.imageCategory
: BookImageCategory.MJ
)
onMounted(async () => {})
let options = ref([])
async function handleUpdateValue(value) {
if (value == BookImageCategory.D3) {
message.error('暂不支持该出图模式')
onMounted(async () => {
//
await window.api.GetImageGenerateCategory((value) => {
if (value.code == 0) {
message.error(value.message)
return
}
options.value = value.data
})
})
async function handleUpdateValue(value) {
//
let res = await window.db.UpdateBookTaskData(reverseManageStore.selectBookTask.id, {
imageCategory: value
@ -124,20 +131,7 @@ export default defineComponent({
key: 'unlock'
}
],
options: [
{
label: 'MJ',
value: BookImageCategory.MJ
},
{
label: 'SD',
value: BookImageCategory.SD
},
{
label: 'D3',
value: BookImageCategory.D3
}
]
options
}
}
})

View File

@ -0,0 +1,305 @@
<template>
<div>
<n-dropdown trigger="hover" :options="mergeOptions" @select="MergePrompt">
<n-button
size="tiny"
:color="softwareStore.SoftColor.BROWN_YELLOW"
@click="MergePrompt(undefined)"
>
单句合并
</n-button>
</n-dropdown>
<n-button
style="margin-left: 5px"
size="tiny"
:color="softwareStore.SoftColor.BROWN_YELLOW"
@click="SingleGenerateImage"
>
单句生图
</n-button>
<n-button
style="margin-left: 5px"
size="tiny"
:color="softwareStore.SoftColor.BROWN_YELLOW"
@click="UnderGenerateImage"
>
下生图
</n-button>
<n-button
style="margin-left: 5px"
size="tiny"
:color="softwareStore.SoftColor.BROWN_YELLOW"
@click="ImportMJImageUrl"
>
导入图片
</n-button>
</div>
<n-input
type="textarea"
size="tiny"
@input="InputDebounced"
v-model:value="data.prompt"
:autosize="{ minRows: 6, maxRows: 6 }"
></n-input>
</template>
<script setup>
import { ref, onMounted, defineComponent, onUnmounted, toRaw, watch, h } from 'vue'
import { useMessage, useDialog, NInput, NButton, NDropdown } from 'naive-ui'
import { useSoftwareStore } from '../../../../../stores/software'
import { useReverseManageStore } from '../../../../../stores/reverseManage'
import {
BookBackTaskType,
OperateBookType,
TaskExecuteType
} from '../../../../../define/enum/bookEnum'
import { BookImageCategory } from '../../../../../define/enum/bookEnum'
import { debounce } from 'lodash'
import InputDialogContent from '../../Original/Components/InputDialogContent.vue'
import { DEFINE_STRING } from '../../../../../define/define_string'
let props = defineProps({
initData: undefined,
index: undefined
})
let message = useMessage()
let softwareStore = useSoftwareStore()
let reverseManageStore = useReverseManageStore()
let data = ref(props.initData)
let image_url_ref = ref(null)
let dialog = useDialog()
onMounted(async () => {})
//
async function MergePrompt(key) {
let type = key
if (!key) {
if (reverseManageStore.selectBookTask.imageCategory == BookImageCategory.MJ) {
type = 'mj_merge'
} else if (reverseManageStore.selectBookTask.imageCategory == BookImageCategory.SD) {
type = 'sd_merge'
} else {
type = 'mj_merge'
}
}
//
//
softwareStore.spin.spinning = true
softwareStore.spin.tip = '合并命令中。。。'
let res = await window.book.MergePrompt(data.value.id, type, OperateBookType.BOOKTASKDETAIL)
softwareStore.spin.spinning = false
//
if (res.code == 0) {
message.error(res.message)
return
}
for (let i = 0; i < res.data.length; i++) {
const element = res.data[i]
let selectBookDetailIndex = reverseManageStore.selectBookTaskDetail.findIndex(
(item) => item.id == element.id
)
reverseManageStore.selectBookTaskDetail[selectBookDetailIndex].prompt = element.prompt
}
message.success(res.message)
}
//
async function SingleGenerateImage() {
let res = undefined
if (reverseManageStore.selectBookTask.imageCategory == BookImageCategory.MJ) {
res = await window.mj.AddMJGenerateImageTask(
data.value.id,
OperateBookType.BOOKTASKDETAIL,
DEFINE_STRING.BOOK.MJ_IMAGE_GENERATE_RETURN
)
} else if (reverseManageStore.selectBookTask.imageCategory == BookImageCategory.SD) {
// SD
res = await window.task.AddBookBackTask(
reverseManageStore.selectBook.id,
BookBackTaskType.SD_IMAGE,
TaskExecuteType.AUTO,
reverseManageStore.selectBookTask.id,
data.value.id,
DEFINE_STRING.BOOK.SD_IMAGE_GENERATE_RETURN
)
} else if (reverseManageStore.selectBookTask.imageCategory == BookImageCategory.D3) {
res = await window.task.AddBookBackTask(
reverseManageStore.selectBook.id,
BookBackTaskType.D3_IMAGE,
TaskExecuteType.AUTO,
reverseManageStore.selectBookTask.id,
data.value.id,
DEFINE_STRING.BOOK.D3_IMAGE_GENERATE_RETURN
)
} else if (reverseManageStore.selectBookTask.imageCategory == BookImageCategory.FLUX_API) {
res = await window.task.AddBookBackTask(
reverseManageStore.selectBook.id,
BookBackTaskType.FLUX_API_IMAGE,
TaskExecuteType.AUTO,
reverseManageStore.selectBookTask.id,
data.value.id,
DEFINE_STRING.BOOK.FLUX_API_IMAGE_GENERATE_RETURN
)
} else if (reverseManageStore.selectBookTask.imageCategory == BookImageCategory.FLUX_FORGE) {
res = await window.task.AddBookBackTask(
reverseManageStore.selectBook.id,
BookBackTaskType.FLUX_FORGE_IMAGE,
TaskExecuteType.AUTO,
reverseManageStore.selectBookTask.id,
data.value.id,
DEFINE_STRING.BOOK.FLUX_FORGE_IMAGE_GENERATE_RETURN
)
} else {
message.error('未知的出图模式')
return
}
if (res.code == 0) {
message.error(res.message)
return
}
message.success(res.message)
}
//
async function UnderGenerateImage() {
let res = undefined
let bookTaskDetails = reverseManageStore.selectBookTaskDetail.filter(
(item, index) => index >= props.index && !item.imageLock
)
if (bookTaskDetails.length <= 0) {
message.error('未找到可生成图片的分镜,或者分镜都被锁定')
return
}
if (reverseManageStore.selectBookTask.imageCategory == BookImageCategory.MJ) {
res = await window.mj.AddMJGenerateImageTask(
data.value.id,
OperateBookType.UNDERBOOKTASK,
DEFINE_STRING.BOOK.MJ_IMAGE_GENERATE_RETURN
)
} else if (reverseManageStore.selectBookTask.imageCategory == BookImageCategory.SD) {
// SD
let tasksList = []
for (let i = 0; i < bookTaskDetails.length; i++) {
const element = bookTaskDetails[i]
tasksList.push({
bookId: reverseManageStore.selectBook.id,
type: BookBackTaskType.SD_IMAGE,
executeType: TaskExecuteType.AUTO,
bookTaskId: reverseManageStore.selectBookTask.id,
bookTaskDetailId: element.id,
messageName: DEFINE_STRING.BOOK.SD_IMAGE_GENERATE_RETURN
})
}
res = await window.task.AddMultiBookBackTask(tasksList)
} else if (reverseManageStore.selectBookTask.imageCategory == BookImageCategory.FLUX_FORGE) {
// forge flux
let tasksList = []
for (let i = 0; i < bookTaskDetails.length; i++) {
const element = bookTaskDetails[i]
tasksList.push({
bookId: reverseManageStore.selectBook.id,
type: BookBackTaskType.FLUX_FORGE_IMAGE,
executeType: TaskExecuteType.AUTO,
bookTaskId: reverseManageStore.selectBookTask.id,
bookTaskDetailId: element.id,
messageName: DEFINE_STRING.BOOK.FLUX_FORGE_IMAGE_GENERATE_RETURN
})
}
res = await window.task.AddMultiBookBackTask(tasksList)
} else if (reverseManageStore.selectBookTask.imageCategory == BookImageCategory.FLUX_API) {
// forge flux
let tasksList = []
for (let i = 0; i < bookTaskDetails.length; i++) {
const element = bookTaskDetails[i]
tasksList.push({
bookId: reverseManageStore.selectBook.id,
type: BookBackTaskType.FLUX_API_IMAGE,
executeType: TaskExecuteType.AUTO,
bookTaskId: reverseManageStore.selectBookTask.id,
bookTaskDetailId: element.id,
messageName: DEFINE_STRING.BOOK.FLUX_API_IMAGE_GENERATE_RETURN
})
}
res = await window.task.AddMultiBookBackTask(tasksList)
} else if (reverseManageStore.selectBookTask.imageCategory == BookImageCategory.D3) {
let tasksList = []
for (let i = 0; i < bookTaskDetails.length; i++) {
const element = bookTaskDetails[i]
tasksList.push({
bookId: reverseManageStore.selectBook.id,
type: BookBackTaskType.D3_IMAGE,
executeType: TaskExecuteType.AUTO,
bookTaskId: reverseManageStore.selectBookTask.id,
bookTaskDetailId: element.id,
messageName: DEFINE_STRING.BOOK.D3_IMAGE_GENERATE_RETURN
})
}
res = await window.task.AddMultiBookBackTask(tasksList)
} else {
message.error('未知的出图模式')
return
}
if (res.code == 0) {
message.error(res.message)
return
}
message.success(res.message)
}
let InputDebounced = debounce(handleInput, 1000)
async function handleInput() {
let res = await window.db.UpdateBookTaskDetailData(data.value.id, {
prompt: data.value.prompt
})
if (res.code == 0) {
message.error(res.message)
}
}
/**
* 写入图片的链接将图片链接下载并且分割
*/
async function ImportMJImageUrl() {
//
//
let dialogWidth = 400
let dialogHeight = 150
let da = dialog.create({
title: '添加图片链接/本地地址',
showIcon: false,
closeOnEsc: false,
content: () =>
h(InputDialogContent, {
ref: image_url_ref,
initData: null,
placeholder: '请输入图片链接/本地地址'
}),
style: `width : ${dialogWidth}px; min-height : ${dialogHeight}px`,
maskClosable: false,
onClose: async () => {
da?.destroy()
let row_image_url = image_url_ref.value.data
softwareStore.spin.spinning = true
softwareStore.spin.tip = '正在下载图片/分割图片中。。。'
let res = await window.book.DownloadImageUrlAndSplit(data.value.id, row_image_url)
softwareStore.spin.spinning = false
if (res.code == 0) {
message.error(res.message)
return
}
data.value.outImagePath = res.data.outImagePath
data.value.subImagePath = res.data.subImagePath
message.success(res.message)
}
})
}
let mergeOptions = [
{ label: 'MJ模式合并', key: 'mj_merge' },
{ label: 'SD模式合并', key: 'sd_merge' }
]
</script>

View File

@ -0,0 +1,110 @@
<template>
<n-spin :show="show">
<n-form :label-width="80" :model="formValue">
<n-form-item label="选择剪映草稿" path="draftName">
<n-select
v-model:value="formValue.draftName"
filterable
placeholder="选择草稿"
:options="draftSelect"
clearable
/>
</n-form-item>
<n-form-item path="writeTimeLine">
<n-button type="primary" @click="GetJianyingFrame">开始抽帧</n-button>
</n-form-item>
</n-form>
<template #description> 正在剪映抽帧中... </template>
</n-spin>
</template>
<script setup>
import { onMounted, ref } from 'vue'
import {
NForm,
NFormItem,
NInput,
NSpin,
NButton,
NSelect,
NCheckbox,
useMessage,
useDialog
} from 'naive-ui'
import { isEmpty } from 'lodash'
import { useReverseManageStore } from '../../../../../stores/reverseManage.ts'
let draftSelect = ref([])
let formValue = ref({
draftName: undefined
})
let show = ref(false)
let message = useMessage()
let dialog = useDialog()
let reverseManageStore = useReverseManageStore()
onMounted(() => {
// 稿
window.api.getDraftFileList((value) => {
value.forEach((element) => {
let obj = {
label: element,
value: element
}
draftSelect.value.push(obj)
})
})
})
async function GetJianyingFrame() {
if (
reverseManageStore.selectBookTaskDetail &&
reverseManageStore.selectBookTaskDetail.length > 0
) {
let da = dialog.warning({
title: '剪映抽帧提示',
content:
'检测到到当前已经存在分镜数据,继续抽帧将会首先删除当前数据,哪怕是失败也不能恢复,是否继续?',
positiveText: '继续',
negativeText: '取消',
onPositiveClick: async () => {
da.destroy()
await JianyingFrame()
}
})
} else {
await JianyingFrame()
}
}
async function JianyingFrame() {
if (isEmpty(formValue.value.draftName)) {
message.error('请选择草稿文件')
return
}
show.value = true
try {
let res = await window.book.JianyingFrame(
reverseManageStore.selectBookTask.id,
formValue.value.draftName
)
if (res.code == 0) {
message.error(res.message)
return
}
message.success('剪映抽帧成功')
//
//
let getRes = await reverseManageStore.GetBookTaskDetail(reverseManageStore.selectBookTask.id)
if (getRes.code == 0) {
message.error(getRes.message)
return
}
} catch (error) {
message.error(error.toString())
} finally {
show.value = false
}
}
</script>

View File

@ -37,7 +37,7 @@
import { ref, onMounted, defineComponent, onUnmounted, toRaw, computed } from 'vue'
import { useMessage, NButton, NInput, NSpin, NDropdown } from 'naive-ui'
import { useReverseManageStore } from '../../../../../stores/reverseManage'
import DatatableGptPromptButton from '../Components/DatatableGptPromptButton.vue'
import DatatableGptPromptButton from './DatatableGptPromptButton.vue'
import { debounce } from 'lodash'
export default defineComponent({

File diff suppressed because it is too large Load Diff

View File

@ -10,34 +10,31 @@
</div>
</template>
<script>
import { ref, onMounted, defineComponent, watch, h } from 'vue'
<script setup>
import { ref, onMounted, defineComponent, computed, watch, h } from 'vue'
import { useMessage, NDataTable, NImage } from 'naive-ui'
import { useSoftwareStore } from '../../../../../stores/software'
import { useReverseManageStore } from '../../../../../stores/reverseManage'
import DataTableShowGenerateImage from '../../Original/Components/DataTableShowGenerateImage.vue'
import ManageBookOldImage from '../Components/ManageBookOldImage.vue'
import ManageBookOldImage from './ManageBookOldImage.vue'
import { BookType } from '../../../../../define/enum/bookEnum'
import MJReversePrompt from './MJReversePrompt.vue'
import DatatableHeaderGptPrompt from '../Components/DatatableHeaderGptPrompt.vue'
import DatatableHeaderAfterGpt from '../Components/DatatableHeaderAfterGpt.vue'
import DatatableHeaderPrompt from '../Components/DatatableHeaderPrompt.vue'
import DatatablePrompt from '../Components/DatatablePrompt.vue'
import DatatableHeaderImage from '../Components/DatatableHeaderImage.vue'
import DatatableAfterGpt from '../Components/DatatableAfterGpt.vue'
import DatatableGenerateImage from '../Components/DatatableGenerateImage.vue'
import DatatableHeaderGptPrompt from './DatatableHeaderGptPrompt.vue'
import DatatableHeaderAfterGpt from './DatatableHeaderAfterGpt.vue'
import DatatableHeaderPrompt from './DatatableHeaderPrompt.vue'
import DatatablePrompt from './DatatablePrompt.vue'
import DatatableHeaderImage from './DatatableHeaderImage.vue'
import DatatableAfterGpt from './DatatableAfterGpt.vue'
import DatatableGenerateImage from './DatatableGenerateImage.vue'
import ODataTableAction from '../Original/ODataTableAction.vue'
import SDReversePrompt from './SDReversePrompt.vue'
export default defineComponent({
components: { NDataTable, NImage },
let softwareStore = useSoftwareStore()
let reverseManageStore = useReverseManageStore()
let message = useMessage()
let maxHeight = ref(0)
let datatable = ref(null)
setup() {
let softwareStore = useSoftwareStore()
let reverseManageStore = useReverseManageStore()
let message = useMessage()
let maxHeight = ref(0)
let datatable = ref(null)
watch(
watch(
() => softwareStore.skip,
(newValue) => {
let doc = document.querySelectorAll('.n-data-table-tr')
@ -45,12 +42,12 @@ export default defineComponent({
datatable.value.scrollTo({ el: sk })
},
{ deep: true }
)
)
const createColumns = () => [
const columns = [
{
title: 'No.',
key: 'name',
key: 'no',
width: 50,
fixed: 'left'
},
@ -61,6 +58,7 @@ export default defineComponent({
key: 'afterGpt',
width: 200,
fixed: 'left',
className: 'empty-margin',
render(row, index) {
return h(DatatableAfterGpt, { initData: row, index: index })
}
@ -68,7 +66,7 @@ export default defineComponent({
{
title: '图片/视频',
key: 'oldImage',
className: 'space-row',
className: 'empty-margin',
width: 130,
render(row, index) {
return h(ManageBookOldImage, {
@ -82,7 +80,7 @@ export default defineComponent({
return h(DatatableHeaderGptPrompt)
},
key: 'gptPrompt',
className: 'space-row',
className: 'empty-margin',
resizable: true,
width: 400,
minWidth: 400,
@ -94,7 +92,10 @@ export default defineComponent({
index: index
})
} else if (reverseManageStore.selectBook.type == BookType.SD_REVERSE) {
return h('span', ['SD 反推这边不能用', index])
return h(SDReversePrompt, {
initData: row,
index: index
})
} else if (reverseManageStore.selectBook.type == BookType.ORIGINAL) {
return h('span', ['原创 这边暂时不能用', index])
}
@ -105,7 +106,7 @@ export default defineComponent({
title(row) {
return h(DatatableHeaderPrompt)
},
className: 'space-row',
className: 'empty-margin',
key: 'row1',
render(row, index) {
return h(DatatablePrompt, {
@ -114,12 +115,21 @@ export default defineComponent({
})
}
},
{
//
title: '参数',
key: 'parameter',
width: '110',
render(row, index) {
return h(ODataTableAction, { initData: row, index: index })
}
},
{
title() {
return h(DatatableHeaderImage)
},
width: 300,
className: 'space-row',
className: 'empty-margin',
render(row, index) {
return h(DatatableGenerateImage, {
image_generate_category: 'mj',
@ -130,17 +140,16 @@ export default defineComponent({
},
fixed: 'right'
}
]
onMounted(async () => {
calcHeight()
]
onMounted(async () => {
setTimeout(() => {
let div = document.getElementsByClassName('space-row')
let div = document.getElementsByClassName('empty-margin')
for (let i = 0; i < div.length; i++) {
div[i].style.padding = '0px 5px'
}
calcHeight()
// getMaxHeight()
}, 100)
//
@ -152,22 +161,11 @@ export default defineComponent({
}
softwareStore.spin.spinning = false
reverseManageStore.selectBookTaskDetail = detailRes.data
})
function calcHeight() {
})
function calcHeight() {
//
let height = window.innerHeight
maxHeight.value = height - 120
}
return {
softwareStore,
reverseManageStore,
maxHeight,
columns: createColumns(),
datatable
}
}
})
}
</script>

View File

@ -0,0 +1,44 @@
<template>
<div>
<DatatableGptPromptButton :initData="data" :index="index" />
</div>
<n-input
type="textarea"
size="tiny"
v-model:value="data.gptPrompt"
:autosize="{
minRows: 6,
maxRows: 6
}"
@input="InputDebounced"
>
</n-input>
</template>
<script setup>
import { ref, onMounted } from 'vue'
import { useMessage, NButton, NInput, NSpin, NDropdown } from 'naive-ui'
import { useReverseManageStore } from '../../../../../stores/reverseManage'
import DatatableGptPromptButton from './DatatableGptPromptButton.vue'
import { debounce } from 'lodash'
let props = defineProps({
initData: undefined,
index: undefined
})
let data = ref(props.initData)
let index = ref(props.index)
let message = useMessage()
let reverseManageStore = useReverseManageStore()
onMounted(async () => {})
let InputDebounced = debounce(handleInput, 1000)
async function handleInput(value) {
let res = await window.db.UpdateBookTaskDetailData(data.value.id, {
gptPrompt: value
})
if (res.code == 0) {
message.error(res.message)
}
}
</script>

View File

@ -1,7 +1,12 @@
<template>
<div style="height: 100%; display: flex; width: 100%; overflow: auto; padding: 0; margin: 0">
<div>
<div v-if="reverseManageStore.selectBook.type == bookType.MJ_REVERSE">
<div
v-if="
reverseManageStore.selectBook.type == bookType.MJ_REVERSE ||
reverseManageStore.selectBook.type == bookType.SD_REVERSE
"
>
<div v-if="true" class="left_container" style="flex: 10; min-width: 500px">
<ManageBookDetailButton />
<ManageBookReverseTable style="margin-top: 5px" />
@ -20,10 +25,10 @@
</div>
</template>
<script>
<script setup>
import { ref, onMounted, defineComponent, onUnmounted, toRaw, watch } from 'vue'
import { useMessage, NDivider } from 'naive-ui'
import ManageBookDetailButton from './Components/ManageBookDetailButton.vue'
import ManageBookDetailButton from './MJReverse/ManageBookDetailButton.vue'
import ManageBookReverseTable from './MJReverse/ManageBookReverseTable.vue'
import ManageBookShowLogger from './Components/ManageBook/ManageBookShowLogger.vue'
import { useSoftwareStore } from '../../../../stores/software'
@ -33,22 +38,51 @@ import { ResponseMessageType } from '../../../../define/enum/softwareEnum'
import { useReverseManageStore } from '../../../../stores/reverseManage'
import OriginalMainPage from './Original/OriginalMainPage.vue'
export default defineComponent({
components: {
ManageBookDetailButton,
ManageBookReverseTable,
ManageBookShowLogger,
NDivider,
OriginalMainPage
},
let message = useMessage()
let softwareStore = useSoftwareStore()
let reverseManageStore = useReverseManageStore()
let bookType = ref(BookType)
setup() {
// SD
function OriginalSDImageResponseReturn(value) {
console.log('OriginalSDImageResponseReturn', value)
//
if (value.data) {
let dataIndex = reverseManageStore.selectBookTaskDetail.findIndex(
(item) => item.id == value.data.id
)
if (dataIndex >= 0) {
reverseManageStore.selectBookTaskDetail[dataIndex].mjMessage = value.data
reverseManageStore.selectBookTaskDetail[dataIndex].outImagePath = value.data.outImagePath
reverseManageStore.selectBookTaskDetail[dataIndex].subImagePath = value.data.subImagePath
}
if (value.data.status == 'error') {
message.error(value.data.message)
}
}
}
onMounted(async () => {
// SD
window.api.setEventListen([DEFINE_STRING.BOOK.SD_IMAGE_GENERATE_RETURN], (value) =>
OriginalSDImageResponseReturn(value)
)
// D3
window.api.setEventListen([DEFINE_STRING.BOOK.D3_IMAGE_GENERATE_RETURN], (value) => {
OriginalSDImageResponseReturn(value)
})
// FLUX API
window.api.setEventListen([DEFINE_STRING.BOOK.FLUX_API_IMAGE_GENERATE_RETURN], (value) =>
OriginalSDImageResponseReturn(value)
)
// FLUX FORGE
window.api.setEventListen([DEFINE_STRING.BOOK.FLUX_FORGE_IMAGE_GENERATE_RETURN], (value) =>
OriginalSDImageResponseReturn(value)
)
let message = useMessage()
let softwareStore = useSoftwareStore()
let reverseManageStore = useReverseManageStore()
let bookType = ref(BookType)
onMounted(async () => {
//
window.api.setEventListen([DEFINE_STRING.BOOK.GET_COPYWRITING_RETURN], (value) => {
if (value.code == 0) {
@ -82,8 +116,15 @@ export default defineComponent({
window.api.setEventListen([DEFINE_STRING.BOOK.REVERSE_PROMPT_RETURN], (value) => {
if (value.code == 0) {
errorResponse(value)
} else {
return
}
if (value.type == ResponseMessageType.MJ_REVERSE) {
MJReverseResponse(value)
} else if (value.type == ResponseMessageType.SD_REVERSE) {
SDReverseResponse(value)
} else {
console.error('错误的返回数据类型')
return
}
})
@ -110,9 +151,9 @@ export default defineComponent({
}
}
})
})
})
function errorResponse(value) {
function errorResponse(value) {
switch (value.dialogType) {
case DialogType.DIALOG:
window.api.showGlobalMessageDialog(value)
@ -127,17 +168,21 @@ export default defineComponent({
window.api.showGlobalNotificationDialog(value)
break
}
}
}
onUnmounted(() => {
onUnmounted(() => {
window.api.removeEventListen(DEFINE_STRING.BOOK.GET_COPYWRITING_RETURN)
window.api.removeEventListen(DEFINE_STRING.BOOK.REMOVE_WATERMARK_RETURN)
window.api.removeEventListen(DEFINE_STRING.BOOK.REVERSE_PROMPT_RETURN)
window.api.removeEventListen(DEFINE_STRING.BOOK.MJ_IMAGE_GENERATE_RETURN)
window.api.removeEventListen(DEFINE_STRING.BOOK.REVERSE_PROMPT_TRANSLATE_RETURN)
})
window.api.removeEventListen(DEFINE_STRING.BOOK.SD_IMAGE_GENERATE_RETURN)
window.api.removeEventListen(DEFINE_STRING.BOOK.D3_IMAGE_GENERATE_RETURN)
window.api.removeEventListen(DEFINE_STRING.BOOK.FLUX_API_IMAGE_GENERATE_RETURN)
window.api.removeEventListen(DEFINE_STRING.BOOK.FLUX_FORGE_IMAGE_GENERATE_RETURN)
})
function MJImageResponse(value) {
function MJImageResponse(value) {
//
if (value.data) {
let dataIndex = reverseManageStore.selectBookTaskDetail.findIndex(
@ -149,9 +194,9 @@ export default defineComponent({
reverseManageStore.selectBookTaskDetail[dataIndex].subImagePath = value.data.subImagePath
}
}
}
}
/**
/**
* 处理翻译返回的数据主要是将翻译的数据放到对应的位置
* @param value 返回的数据数据结构如下
* bookTaskDetailId: string // ID
@ -161,7 +206,7 @@ export default defineComponent({
* prompt: string //
* promptCN: string //
*/
function ReversePromptTranslateResponse(value) {
function ReversePromptTranslateResponse(value) {
if (!value.data) {
return
}
@ -191,9 +236,9 @@ export default defineComponent({
} else {
message.error('未知的翻译目标语言')
}
}
}
function GptPromptTranslateResponse(value) {
function GptPromptTranslateResponse(value) {
console.log('GptPromptTranslateResponse', value)
if (!value.data) {
return
@ -207,12 +252,12 @@ export default defineComponent({
reverseManageStore.selectBookTaskDetail[index].gptPrompt = value.data.prompt
//
softwareStore.spin.tip = `翻译中 ... ${value.data.progress} / ${value.data.total}`
}
}
/**
/**
* MJ反推返回数据
*/
function MJReverseResponse(value) {
function MJReverseResponse(value) {
if (value.data && value.data.progress == 100) {
let dataIndex = reverseManageStore.selectBookTaskDetail.findIndex(
(item) => item.id == value.data.id
@ -224,11 +269,19 @@ export default defineComponent({
)
}
}
}
}
return { softwareStore, reverseManageStore, bookType }
/**
* SD反推返回数据
*/
function SDReverseResponse(value) {
if (value.data) {
let dataIndex = reverseManageStore.selectBookTaskDetail.findIndex((item) => item.id == value.id)
if (dataIndex >= 0) {
reverseManageStore.selectBookTaskDetail[dataIndex].gptPrompt = value.data
}
})
}
}
</script>
<style lang="css">

View File

@ -30,11 +30,29 @@
@update:checked-row-keys="handleCheck"
/>
</div>
<n-dropdown
placement="bottom-start"
trigger="manual"
:x="x"
:y="y"
:options="options"
:show="showDropdown"
:on-clickoutside="onClickoutside"
@select="handleSelect"
/>
</template>
<script setup>
import { ref, onMounted, defineComponent, onUnmounted, toRaw, watch, h } from 'vue'
import { useMessage, useDialog, NButton, NDataTable, NIcon, NWatermark } from 'naive-ui'
import { ref, onMounted, defineComponent, onUnmounted, toRaw, nextTick, h } from 'vue'
import {
useMessage,
useDialog,
NButton,
NDataTable,
NIcon,
NDropdown,
NPerformantEllipsis
} from 'naive-ui'
import { useReverseManageStore } from '../../../../stores/reverseManage'
import { useSoftwareStore } from '../../../../stores/software'
import { AddSharp } from '@vicons/ionicons5'
@ -53,6 +71,12 @@ const router = useRouter()
let message = useMessage()
let columns = createColumns()
const checkedRowKeysRef = ref([])
let showDropdown = ref(false)
let x = ref(0)
let y = ref(0)
let selectRow = ref(null)
let options = []
onMounted(async () => {
window.api.setEventListen([DEFINE_STRING.BOOK.MAIN_DATA_RETURN], (value) => {
if (value.code == 0) {
@ -107,39 +131,238 @@ function createColumns() {
width: 100
},
{
title: '操作',
key: 'action',
width: 215,
fixed: 'right',
title: '失败原因',
key: 'errorMsg',
width: 210,
render(row, index) {
return h(BookTaskListAction, {
bookTask: row
})
return h(
NPerformantEllipsis,
{
style: 'max-width: 200px'
},
row.errorMsg
)
}
}
]
}
/**
* 高清数据
*/
function createOptions(row) {
return [
{
label: `高清 ${row.name}`,
key: 'hd'
},
{
label: `生成草稿 ${row.name}`,
key: 'draft'
},
{
label: `合成视频 ${row.name}`,
key: 'generateVideo'
},
{
label: () => h('span', { style: { color: '#f4a534' } }, `重置 ${row.name}`),
key: 'reset'
},
{
label: () => h('span', { style: { color: 'red' } }, `删除 ${row.name}`),
key: 'delete'
}
]
}
async function HDImage(value) {
softwareStore.spin.spinning = true
softwareStore.spin.tip = '正在进行高清检查。。。'
let checkImage = await window.book.CheckImageFileSize(value.id, 10240, OperateBookType.BOOKTASK)
softwareStore.spin.spinning = false
if (checkImage.code == 0) {
message.error(checkImage.message)
return
}
//
if (checkImage.data && checkImage.data.length > 0) {
//
let msg = checkImage.data.map((item, index) => {
return item.outImagePath + '\n'
})
dialog.warning({
title: '高清警告',
content: '下面的图片大于10MB是否继续高清' + '\n' + msg,
positiveText: '确定',
negativeText: '取消',
maskClosable: false,
onPositiveClick: async () => {
alert('确认高清')
await hdImageFunc(value.id, OperateBookType.BOOKTASK)
}
})
} else {
//
await hdImageFunc(value.id, OperateBookType.BOOKTASK)
}
}
async function hdImageFunc(id, operateBookType) {
let da = dialog.warning({
title: '高清提示',
content: `是否确认高清,当前的高清倍数为 ${softwareStore.globalSetting.hdScale} 倍,是否继续?`,
positiveText: '继续',
negativeText: '取消',
onPositiveClick: async () => {
da.destroy()
softwareStore.spin.spinning = true
softwareStore.spin.tip = '正在高清中。。。'
let res = await window.book.HDImage(id, 4, operateBookType)
let res = await window.book.HDImage(
id,
softwareStore.globalSetting.hdScale ?? 2,
operateBookType
)
softwareStore.spin.spinning = false
if (res.code == 1) {
message.success('高清成功')
} else {
message.error(res.message)
}
}
})
}
/**
* 生成草稿
*/
async function ClipDraft(value) {
dialog.info({
closeOnEsc: false,
title: `生成草稿前检查 ${value.name}`,
maskClosable: false,
content: () =>
h(ManageBookTaskGenerateInformation, {
bookTask: value,
type: 'bookTask',
optionType: 'draft'
})
})
}
/**
* 合成视频
*/
async function GenerateVideo(value) {
dialog.info({
closeOnEsc: false,
title: `合成视频前检查 ${value.name}`,
maskClosable: false,
content: () =>
h(ManageBookTaskGenerateInformation, {
bookTask: value,
type: 'bookTask',
optionType: 'video'
})
})
}
/**
* 重置小说数据
* @param e
*/
async function ReSetBookTaskDetail(value) {
dialog.warning({
title: '重置小说任务',
positiveText: '确定',
negativeText: '取消',
content:
'确定要重置小说任务吗?该操作不可逆,会将对应的批次任务的数据清空,分镜数据全部删除,请谨慎操作!',
onPositiveClick: async () => {
let res = await window.book.ReSetBookTask(value.id, OperateBookType.BOOKTASK)
if (res.code == 1) {
message.success('重置小说任务成功')
} else {
message.error(res.message)
}
}
})
}
/**
* 删除小说任务
*/
async function DeleteBookTask(value) {
dialog.warning({
title: '删除小说任务',
positiveText: '确定',
negativeText: '取消',
content:
'确定要删除小说任务吗?该操作不可逆,会将对应的批次任务数据和分镜数据全部删除,请谨慎操作!',
onPositiveClick: async () => {
let res = await window.book.DeleteBookTask(value.id, OperateBookType.BOOKTASK)
if (res.code == 1) {
//
let bookTaskRes = await reverseManageStore.GetBookTaskDataFromDB({
bookId: reverseManageStore.selectBook.id
})
softwareStore.spin.spinning = false
if (bookTaskRes.code == 0) {
message.error(bookTaskRes.message)
return
}
//
message.success('删除小说任务并刷新数据成功')
} else {
message.error(res.message)
}
}
})
}
async function handleSelect(key) {
showDropdown.value = false
switch (key) {
case 'hd':
await HDImage(selectRow.value)
break
case 'draft':
await ClipDraft(selectRow.value)
break
case 'generateVideo':
await GenerateVideo(selectRow.value)
break
case 'reset':
await ReSetBookTaskDetail(selectRow.value)
break
case 'delete':
await DeleteBookTask(selectRow.value)
break
default:
message.error('未知的下拉值')
break
}
}
function onClickoutside() {
showDropdown.value = false
}
// /**
// *
// */
// async function hdImageFunc2(id, operateBookType) {
// softwareStore.spin.spinning = true
// softwareStore.spin.tip = ''
// let res = await window.book.HDImage(id, 4, operateBookType)
// softwareStore.spin.spinning = false
// if (res.code == 1) {
// message.success('')
// } else {
// message.error(res.message)
// }
// }
/**
* 一键高清全部
*/
async function HDImageAll() {
console.log('一键高清', checkedRowKeysRef.value)
if (checkedRowKeysRef.value.length == 0) {
message.error('请选择要高清的数据')
return
@ -214,21 +437,99 @@ async function DraftAll() {
* 一键生成视频
*/
async function VideoAll() {
alert('视频全部')
if (checkedRowKeysRef.value.length == 0) {
message.error('请选择要生成草稿的任务')
return
}
// 稿
dialog.info({
closeOnEsc: false,
title: `生成草稿前检查 ${reverseManageStore.selectBook.name}`,
maskClosable: false,
content: () =>
h(ManageBookTaskGenerateInformation, {
bookTask: reverseManageStore.selectBook,
type: 'book',
selectBookTask: [...checkedRowKeysRef.value],
optionType: 'video'
})
})
}
/**
* 一键重置
*/
async function ResetAll() {
alert('重置全部')
if (checkedRowKeysRef.value.length == 0) {
message.error('请选择要高清的数据')
return
}
dialog.warning({
title: '重置选中小说任务',
positiveText: '确定',
negativeText: '取消',
content:
'确定要重置选中的小说任务吗?该操作不可逆,会将对应的批次任务的数据清空,分镜数据全部删除,请谨慎操作!',
onPositiveClick: async () => {
let res = await window.book.ReSetBookTask(
[...checkedRowKeysRef.value],
OperateBookType.ASSIGNBOOKTASK
)
if (res.code == 1) {
//
let bookTaskRes = await reverseManageStore.GetBookTaskDataFromDB({
bookId: reverseManageStore.selectBook.id
})
softwareStore.spin.spinning = false
if (bookTaskRes.code == 0) {
message.error(bookTaskRes.message)
return
}
message.success('重置小说任务成功')
} else {
message.error(res.message)
}
}
})
}
/**
* 一键删除
*/
async function DeleteAll() {
alert('删除全部')
if (checkedRowKeysRef.value.length == 0) {
message.error('请选择要高清的数据')
return
}
dialog.warning({
title: '删除所选小说任务',
positiveText: '确定',
negativeText: '取消',
content:
'确定要删除选中的小说任务吗?该操作不可逆,会将对应的批次任务数据和分镜数据全部删除,请谨慎操作!',
onPositiveClick: async () => {
let res = await window.book.DeleteBookTask(
[...checkedRowKeysRef.value],
OperateBookType.ASSIGNBOOKTASK
)
if (res.code == 1) {
//
let bookTaskRes = await reverseManageStore.GetBookTaskDataFromDB({
bookId: reverseManageStore.selectBook.id
})
softwareStore.spin.spinning = false
if (bookTaskRes.code == 0) {
message.error(bookTaskRes.message)
return
}
//
message.success('删除小说任务并刷新数据成功')
} else {
message.error(res.message)
}
}
})
}
const renderIcon = () => {
return h(NIcon, null, {
@ -245,6 +546,17 @@ const rowProps = (row) => {
softwareStore.spin.tip = '正在加载数据'
//
router.push({ name: 'manage_book', params: { id: row.id } })
},
onContextmenu: (e) => {
e.preventDefault()
selectRow.value = row
showDropdown.value = false
options = createOptions(row)
nextTick().then(() => {
showDropdown.value = true
x.value = e.clientX
y.value = e.clientY
})
}
}
}

View File

@ -87,7 +87,7 @@ import { isEmpty } from 'lodash'
import { TranslateType } from '../../../../../define/enum/translate'
import { DEFINE_STRING } from '../../../../../define/define_string'
import FindReplaceRound from '../../Icon/FindReplaceRound.vue'
import DatatableHeaderAfterGptSelectAndReplace from '../Components/DatatableHeaderAfterGptSelectAndReplace.vue'
import DatatableHeaderAfterGptSelectAndReplace from '../MJReverse/DatatableHeaderAfterGptSelectAndReplace.vue'
import { BookRepalceDataType } from '../../../../../define/enum/bookEnum'
let softwareStore = useSoftwareStore()

View File

@ -60,7 +60,7 @@ import { useSoftwareStore } from '../../../../../stores/software'
import { useReverseManageStore } from '../../../../../stores/reverseManage'
import { BookRepalceDataType, OperateBookType } from '../../../../../define/enum/bookEnum'
import FindReplaceRound from '../../Icon/FindReplaceRound.vue'
import DatatableHeaderAfterGptSelectAndReplace from '../Components/DatatableHeaderAfterGptSelectAndReplace.vue'
import DatatableHeaderAfterGptSelectAndReplace from '../MJReverse/DatatableHeaderAfterGptSelectAndReplace.vue'
let message = useMessage()
let dialog = useDialog()
let softwareStore = useSoftwareStore()

View File

@ -73,7 +73,7 @@ import { useReverseManageStore } from '../../../../../stores/reverseManage'
import ImportWordAndSrt from '../Components/ImportWord/ImportWordAndSrt.vue'
import CharacterAnalyze from '../Components/PresetLibrary/CharacterAnalyze.vue'
import PromptSetting from '../../Original/Components/PromptSetting.vue'
import MonitorStatus from '../Components/MonitorStatus.vue'
import MonitorStatus from '../MJReverse/MonitorStatus.vue'
import {
BookBackTaskType,
BookImageCategory,

View File

@ -20,8 +20,8 @@
<script setup>
import { ref, h, onMounted, watch } from 'vue'
import { NDataTable, useMessage } from 'naive-ui'
import DatatableHeaderAfterGpt from '../Components/DatatableHeaderAfterGpt.vue'
import DatatableAfterGpt from '../Components/DatatableAfterGpt.vue'
import DatatableHeaderAfterGpt from '../MJReverse/DatatableHeaderAfterGpt.vue'
import DatatableAfterGpt from '../MJReverse/DatatableAfterGpt.vue'
import ODatatableHeaderCharacterAndScene from './ODataTableHeaderCharacterAndScene.vue'
import ODatatableCharacterAndScene from './ODataTableCharacterAndScene.vue'
import ODataTableGptPrompt from './ODataTableGptPrompt.vue'
@ -29,8 +29,8 @@ import ODataTableHeaderGptPrompt from './ODataTableHeaderGptPrompt.vue'
import ODataTableHeaderPrompt from './ODatatableHeaderPrompt.vue'
import ODatatablePrompt from './ODatatablePrompt.vue'
import ODataTableAction from './ODataTableAction.vue'
import DatatableHeaderGenerateImage from '../Components/DatatableHeaderGenerateImage.vue'
import DatatableGenerateImage from '../Components/DatatableGenerateImage.vue'
import DatatableHeaderGenerateImage from '../MJReverse/DatatableHeaderGenerateImage.vue'
import DatatableGenerateImage from '../MJReverse/DatatableGenerateImage.vue'
import { useReverseManageStore } from '../../../../../stores/reverseManage'
import { useSoftwareStore } from '../../../../../stores/software'
let reverseManageStore = useReverseManageStore()

View File

@ -171,7 +171,6 @@ async function ManageAccount(remote = null) {
settingStore.actionRemoteMJ = remote
}
//
let dW = 800
let dH = 600
dialog.create({

View File

@ -509,7 +509,6 @@ async function openGptBuyUrl() {
* 添加多个账号这样可以同时跑多个账号
*/
async function AddMultiMjAccount() {
//
let dW = window.innerWidth * 0.9
let dH = window.innerHeight * 0.9
dialog.create({

View File

@ -468,7 +468,6 @@ export default defineComponent({
}
//
//
dp = dialog.create({
name: 'progressDialog',
showIcon: false,

View File

@ -59,6 +59,31 @@ export const useReverseManageStore = defineStore('reverseManage', {
}
},
actions: {
//#region 更新小说批次任务数据
/** 更新小说批次任务数据 */
UpdatedBookTaskData(bookTaskId: string | string[], data: Book.SelectBookTask) {
if (Array.isArray(bookTaskId)) {
bookTaskId.forEach(item => {
let findIndex = this.bookTaskData.findIndex((t: Book.SelectBookTask) => t.id === item)
if (findIndex != -1) {
for (let key in data) {
this.bookTaskData[findIndex][key] = data[key]
}
}
})
} else {
let findIndex = this.bookTaskData.findIndex((t: Book.SelectBookTask) => t.id === bookTaskId)
if (findIndex != -1) {
for (let key in data) {
this.bookTaskData[findIndex][key] = data[key]
}
}
}
},
//#endregion
/**
*
* @param {*} condition
@ -238,7 +263,8 @@ export const useReverseManageStore = defineStore('reverseManage', {
return {
...item,
outImagePath: item.outImagePath ? item.outImagePath + '?t=' + new Date().getTime() : undefined,
subImagePath: item.subImagePath ? item.subImagePath.map(item => item + '?t=' + new Date().getTime()) : []
subImagePath: item.subImagePath ? item.subImagePath.map(item => item + '?t=' + new Date().getTime()) : [],
oldImage: item.oldImage ? item.oldImage + '?t=' + new Date().getTime() : undefined
}
})
// 这边开始修改数据,

View File

@ -6,7 +6,11 @@
"resolveJsonModule": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true,
"target": "es2021", //
"target": "es2021", //
"baseUrl": ".", //
"paths": {
"@/*": ["src/*"] // 使 @ src
}
},
"include": [
"src",