1. 新增 图/文转视频 菜单界面,专注实现图/文转视频(目前只集成了 MJ VIDEO) 1. 全新的界面排列,小说列表和批次任务更加分明 2. 添加转视频进度,在主界面即可看到转视频的比例 3. 单独的界面去处理图转视频,避免表格数据过多繁琐 4. 新增分页显示,界面加载更快,也可切换不分页,需要更多的事件等待加载 5. 单独操作面板,参数修改处理更加清晰,支持多种模式显示,右侧固定或抽屉模式 6. 批量设置转视频配置,可以批量修改分类 7. 友好的选择视频界面 2. 重写 软件导出剪映,修复若干草稿导出问题 1. 修复导出剪映文案和图片对齐会有些许对不上,时长越长越明显 2. 修复导出草稿关键帧部分问题 3. 导出的文案通过分镜自动导入,不再需要手动选择SRT 3. 美化 生成草稿界面 弹窗,优化部分逻辑 1. 删除选择SRT文件,SRT根据聚合推文中导入的SRT自动生成草稿 2. 只需选择配音文件即可,配音文件和导入的SRT请自行对应 3. 背景音乐不在内部设置,自行选择文件夹或者是MP3、WAV文件 4. 背景音乐选择文件夹则读取文件夹,随机获取一个 5. 背景音乐选择指定的音乐文件则使用选择的
248 lines
9.1 KiB
TypeScript
248 lines
9.1 KiB
TypeScript
import { TaskModal } from "@/model/task";
|
||
import { BookServiceBasic } from "../ServiceBasic/bookServiceBasic";
|
||
import { ValidateJson } from "@/define/Tools/validate";
|
||
import { BookTaskDetail } from "@/model/book/bookTaskDetail";
|
||
import { GetImageBase64 } from "@/define/Tools/image";
|
||
import { cloneDeep, isEmpty } from "lodash";
|
||
import { GetBaseUrl } from "@/define/Tools/common";
|
||
import axios from "axios";
|
||
import { Book } from "@/model/book/book";
|
||
import { VideoStatus } from "@/define/enum/video";
|
||
import { SendMessageToRenderer } from "../globalService";
|
||
import { ResponseMessageType } from "@/define/enum/softwareEnum";
|
||
import { BookBackTaskStatus, BookTaskStatus } from "@/define/enum/bookEnum";
|
||
import { define } from "@/define/define"
|
||
import ForwardResponse from "@/define/response/ForwardResponse";
|
||
|
||
export class KlingService {
|
||
bookServiceBasic: BookServiceBasic
|
||
constructor() {
|
||
this.bookServiceBasic = new BookServiceBasic();
|
||
}
|
||
|
||
/**
|
||
* 可灵图转视频
|
||
* @param task 任务
|
||
* @param gptUrl GPT地址
|
||
* @param gptApiKey GPTAPIKey
|
||
*/
|
||
async KlingImageToVideo(task: TaskModal.Task, gptUrl: string, gptApiKey: string, useTransfer: boolean = false): Promise<void> {
|
||
try {
|
||
let bookTaskDetail = await this.bookServiceBasic.GetBookTaskDetailDataById(task.bookTaskDetailId);
|
||
let klingOptionsString = bookTaskDetail.videoMessage.klingOptions;
|
||
if (!ValidateJson(klingOptionsString)) {
|
||
throw new Error("klingOptions参数错误,请检查");
|
||
}
|
||
let klingOptions: BookTaskDetail.klingOptions = JSON.parse(klingOptionsString);
|
||
// console.log("klingOptions", klingOptions, "gptUrl", gptUrl, "gptApiKey", gptApiKey);
|
||
|
||
let data: BookTaskDetail.klingOptions = {
|
||
image: klingOptions.image,
|
||
model: "kling-v1"
|
||
}
|
||
|
||
if (!data.image.startsWith("http")) {
|
||
data.image = await GetImageBase64(data.image);
|
||
}
|
||
|
||
// 尾帧控制
|
||
if (klingOptions.hasOwnProperty("style") && !isEmpty(klingOptions.image_tail)) {
|
||
if (klingOptions.image_tail.startsWith("http")) {
|
||
data.image_tail = klingOptions.image_tail;
|
||
} else {
|
||
data.image_tail = await GetImageBase64(klingOptions.image_tail);
|
||
}
|
||
}
|
||
|
||
if (klingOptions.hasOwnProperty("prompt") && !isEmpty(klingOptions.prompt)) {
|
||
data.prompt = klingOptions.prompt;
|
||
}
|
||
|
||
if (klingOptions.hasOwnProperty("negative_prompt") && !isEmpty(klingOptions.negative_prompt)) {
|
||
data.negative_prompt = klingOptions.negative_prompt;
|
||
}
|
||
|
||
if (klingOptions.hasOwnProperty("cfg_scale")) {
|
||
data.cfg_scale = klingOptions.cfg_scale;
|
||
}
|
||
|
||
if (klingOptions.hasOwnProperty("mode")) {
|
||
data.mode = klingOptions.mode;
|
||
}
|
||
|
||
if (klingOptions.hasOwnProperty("duration")) {
|
||
data.duration = klingOptions.duration;
|
||
}
|
||
|
||
if (klingOptions.hasOwnProperty("callback_url")) {
|
||
data.callback_url = klingOptions.callback_url;
|
||
}
|
||
// 开始请求
|
||
let baseUrl = GetBaseUrl(gptUrl);
|
||
let url = baseUrl + "/kling/v1/videos/image2video";
|
||
|
||
let resData: any = undefined;
|
||
if (useTransfer) {
|
||
let transferUrl = define.lms + "/lms/Forward/SimpleTransfer";
|
||
let transferConfig = {
|
||
method: 'post',
|
||
url: transferUrl,
|
||
maxBodyLength: Infinity,
|
||
timeout: 600000, // 600 seconds timeout
|
||
headers: {
|
||
'Content-Type': 'application/json'
|
||
},
|
||
data: JSON.stringify({
|
||
url: url,
|
||
apiKey: gptApiKey,
|
||
dataString: JSON.stringify(data)
|
||
})
|
||
}
|
||
let response = await axios(transferConfig)
|
||
resData = ForwardResponse.GetForwardResponseData(response)
|
||
|
||
} else {
|
||
let res = await axios.post(url, data, {
|
||
headers: {
|
||
"Authorization": gptApiKey
|
||
}
|
||
});
|
||
|
||
// console.log("kling合成视频结果", res);
|
||
resData = res.data;
|
||
}
|
||
|
||
let id = resData.data.task_id;
|
||
|
||
// 修改数据
|
||
let videoMessage = cloneDeep(bookTaskDetail.videoMessage);
|
||
videoMessage.taskId = id;
|
||
videoMessage.status = VideoStatus.WAIT;
|
||
videoMessage.messageData = JSON.stringify(resData);
|
||
delete videoMessage.id;
|
||
await this.bookServiceBasic.UpdateBookTaskDetailVideoMessage(task.bookTaskDetailId, videoMessage);
|
||
|
||
// 返回前端数据
|
||
SendMessageToRenderer({
|
||
code: 1,
|
||
id: task.bookTaskDetailId,
|
||
message: "可灵合成任务提交成功",
|
||
type: ResponseMessageType.KLING_VIDEO,
|
||
data: JSON.stringify(resData)
|
||
}, task.messageName);
|
||
|
||
await this.FetchKlingVideoResult(bookTaskDetail, task, resData.data.task_id, baseUrl, gptApiKey, useTransfer);
|
||
} catch (error) {
|
||
throw new Error("可灵合成视频失败,失败信息如下:" + error.toString());
|
||
}
|
||
|
||
}
|
||
|
||
|
||
async FetchKlingVideoResult(bookTaskDetail: Book.SelectBookTaskDetail, task: TaskModal.Task, taskId: string, baseUrl: string, gptApiKey: string, useTransfer: boolean = false) {
|
||
while (true) {
|
||
try {
|
||
let url = baseUrl + "/kling/v1/videos/image2video/" + taskId;
|
||
let resData: any = undefined;
|
||
if (useTransfer) {
|
||
let transferUrl = define.lms + "/lms/Forward/GetTransfer";
|
||
let transferConfig = {
|
||
method: 'post',
|
||
url: transferUrl,
|
||
maxBodyLength: Infinity,
|
||
headers: {
|
||
'Content-Type': 'application/json'
|
||
},
|
||
data: JSON.stringify({
|
||
url: url,
|
||
apiKey: gptApiKey,
|
||
})
|
||
}
|
||
let res = await axios.request(transferConfig);
|
||
resData = ForwardResponse.GetForwardResponseData(res)
|
||
} else {
|
||
let res = await axios.get(url, {
|
||
headers: {
|
||
Authorization: gptApiKey
|
||
}
|
||
});
|
||
resData = res.data;
|
||
}
|
||
if (!resData) {
|
||
throw new Error("获取可灵合成视频结果失败")
|
||
}
|
||
|
||
if (resData.data.task_status == "submitted") {
|
||
SendMessageToRenderer({
|
||
code: 1,
|
||
id: bookTaskDetail.id,
|
||
message: "可灵合成任务提交成功,正在合成中",
|
||
type: ResponseMessageType.KLING_VIDEO,
|
||
data: JSON.stringify(resData.data)
|
||
}, task.messageName);
|
||
} else if (resData.data.task_status == "processing") {
|
||
|
||
let videoMessage = cloneDeep(bookTaskDetail.videoMessage);
|
||
delete videoMessage.id;
|
||
videoMessage.status = VideoStatus.PROCESSING;
|
||
videoMessage.taskId = taskId;
|
||
videoMessage.messageData = JSON.stringify(resData.data);
|
||
await this.bookServiceBasic.UpdateBookTaskDetailVideoMessage(task.bookTaskDetailId, videoMessage);
|
||
|
||
SendMessageToRenderer({
|
||
code: 1,
|
||
id: bookTaskDetail.id,
|
||
message: "可灵合成任务正在合成中",
|
||
type: ResponseMessageType.KLING_VIDEO,
|
||
data: JSON.stringify(resData.data)
|
||
}, task.messageName);
|
||
} else if (resData.data.task_status == "succeed") {
|
||
// 完成
|
||
let videoMessage = cloneDeep(bookTaskDetail.videoMessage);
|
||
delete videoMessage.id;
|
||
videoMessage.status = VideoStatus.SUCCESS;
|
||
videoMessage.taskId = taskId;
|
||
videoMessage.videoUrl = resData.data.task_result.videos[0].url;
|
||
videoMessage.messageData = JSON.stringify(resData.data);
|
||
await this.bookServiceBasic.UpdateBookTaskDetailVideoMessage(task.bookTaskDetailId, videoMessage);
|
||
await this.bookServiceBasic.UpdetedBookTaskData(task.bookTaskId, {
|
||
status: BookTaskStatus.IMAGE_TO_VIDEO_SUCCESS,
|
||
})
|
||
await this.bookServiceBasic.UpdateTaskStatus({
|
||
id: task.id,
|
||
status: BookBackTaskStatus.DONE,
|
||
})
|
||
SendMessageToRenderer({
|
||
code: 1,
|
||
id: bookTaskDetail.id,
|
||
message: "可灵合成视频完成",
|
||
type: ResponseMessageType.KLING_VIDEO,
|
||
data: JSON.stringify(resData.data.data)
|
||
}, task.messageName);
|
||
break;
|
||
} else {
|
||
// 失败
|
||
let videoMessage = cloneDeep(bookTaskDetail.videoMessage);
|
||
delete videoMessage.id;
|
||
videoMessage.status = VideoStatus.FAIL;
|
||
videoMessage.msg = resData.message;
|
||
videoMessage.taskId = taskId;
|
||
videoMessage.messageData = JSON.stringify(resData.data);
|
||
await this.bookServiceBasic.UpdateBookTaskDetailVideoMessage(bookTaskDetail.id, videoMessage);
|
||
SendMessageToRenderer({
|
||
code: 0,
|
||
id: bookTaskDetail.id,
|
||
message: "runway合成视频失败,错误信息如下:" + resData.data.message,
|
||
type: ResponseMessageType.KLING_VIDEO,
|
||
data: JSON.stringify(resData.data)
|
||
}, task.messageName);
|
||
throw new Error("可灵合成视频失败,失败信息如下:" + resData.message);
|
||
}
|
||
// 等待20秒后再次请求
|
||
await new Promise(resolve => setTimeout(resolve, 20000));
|
||
} catch (error) {
|
||
throw error;
|
||
}
|
||
}
|
||
}
|
||
} |