192 lines
7.2 KiB
TypeScript
192 lines
7.2 KiB
TypeScript
|
|
import { TaskModal } from "@/model/task";
|
|||
|
|
import { BookServiceBasic } from "../ServiceBasic/bookServiceBasic";
|
|||
|
|
import { ValidateJson } from "@/define/Tools/validate";
|
|||
|
|
import { cloneDeep, head, isEmpty } from "lodash";
|
|||
|
|
import { RunawayModel, RunwaySeconds, VideoStatus } from "@/define/enum/video";
|
|||
|
|
import { BookTaskDetail } from "@/model/book/bookTaskDetail";
|
|||
|
|
import { GetImageBase64 } from "@/define/Tools/image";
|
|||
|
|
import axios from "axios";
|
|||
|
|
import { GetBaseUrl } from "@/define/Tools/common";
|
|||
|
|
import { SendMessageToRenderer } from "../globalService";
|
|||
|
|
import { ResponseMessageType } from "@/define/enum/softwareEnum";
|
|||
|
|
import { Book } from "@/model/book/book";
|
|||
|
|
import { BookBackTaskStatus, BookTaskStatus } from "@/define/enum/bookEnum";
|
|||
|
|
|
|||
|
|
export class RunwayService {
|
|||
|
|
bookServiceBasic: BookServiceBasic
|
|||
|
|
constructor() {
|
|||
|
|
this.bookServiceBasic = new BookServiceBasic();
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* runway合成视频
|
|||
|
|
* @param task 任务
|
|||
|
|
* @param gptUrl 调用地址
|
|||
|
|
* @param gptApiKey Key
|
|||
|
|
*/
|
|||
|
|
async ImageToVideo(task: TaskModal.Task, gptUrl: string, gptApiKey: string) {
|
|||
|
|
try {
|
|||
|
|
let bookTaskDetail = await this.bookServiceBasic.GetBookTaskDetailDataById(task.bookTaskDetailId);
|
|||
|
|
|
|||
|
|
let runwayOptionsString = bookTaskDetail.videoMessage.runwayOptions;
|
|||
|
|
if (!ValidateJson(runwayOptionsString)) {
|
|||
|
|
throw new Error("runwayOptions参数错误,请检查");
|
|||
|
|
}
|
|||
|
|
let runwayOptions: BookTaskDetail.RunwayOption = JSON.parse(runwayOptionsString);
|
|||
|
|
// console.log("runwayOptions", runwayOptions, "gptUrl", gptUrl, "gptApiKey", gptApiKey);
|
|||
|
|
|
|||
|
|
// 开始调用runway,做请求体
|
|||
|
|
let data: BookTaskDetail.RunwayOption = {
|
|||
|
|
callback_url: runwayOptions.callback_url,
|
|||
|
|
image: runwayOptions.image,
|
|||
|
|
model: runwayOptions.model,
|
|||
|
|
prompt: runwayOptions.prompt,
|
|||
|
|
options: {
|
|||
|
|
seconds: runwayOptions.options.seconds ?? RunwaySeconds.TEN,
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if (!isEmpty(runwayOptions.style)) {
|
|||
|
|
data.style = runwayOptions.style;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if (runwayOptions.options.flip) {
|
|||
|
|
data.options.flip = runwayOptions.options.flip;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if (runwayOptions.options.motion_vector && runwayOptions.options.motion_vector.isUse) {
|
|||
|
|
data.options.motion_vector = {
|
|||
|
|
x: runwayOptions.options.motion_vector.x,
|
|||
|
|
y: runwayOptions.options.motion_vector.y,
|
|||
|
|
z: runwayOptions.options.motion_vector.z,
|
|||
|
|
r: runwayOptions.options.motion_vector.r,
|
|||
|
|
bg_x_pan: runwayOptions.options.motion_vector.bg_x_pan,
|
|||
|
|
bg_y_pan: runwayOptions.options.motion_vector.bg_y_pan,
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if (runwayOptions.options.image_as_end_frame) {
|
|||
|
|
data.options.image_as_end_frame = runwayOptions.options.image_as_end_frame;
|
|||
|
|
}
|
|||
|
|
// 单独处理image
|
|||
|
|
//判断是不是本地图片还是网络图片。不是网络图片需要转为base64
|
|||
|
|
let image = runwayOptions.image;
|
|||
|
|
if (!image.startsWith("http")) {
|
|||
|
|
let imageBase = await GetImageBase64(image);
|
|||
|
|
data.image = imageBase;
|
|||
|
|
}
|
|||
|
|
// 开始请求
|
|||
|
|
let baseUrl = GetBaseUrl(gptUrl);
|
|||
|
|
let res = await axios.post(baseUrl + "/runway/pro/generate", data, {
|
|||
|
|
headers: {
|
|||
|
|
Authorization: gptApiKey
|
|||
|
|
}
|
|||
|
|
});
|
|||
|
|
// console.log("runway合成视频结果", res.data);
|
|||
|
|
let resData = res.data;
|
|||
|
|
if (resData.code != 200) {
|
|||
|
|
throw new Error(res.data.msg);
|
|||
|
|
}
|
|||
|
|
// 修改数据
|
|||
|
|
let videoMessage = cloneDeep(bookTaskDetail.videoMessage);
|
|||
|
|
videoMessage.taskId = resData.data.task_id;
|
|||
|
|
videoMessage.status = VideoStatus.PROCESSING;
|
|||
|
|
videoMessage.messageData = JSON.stringify(resData.data);
|
|||
|
|
|
|||
|
|
delete videoMessage.id;
|
|||
|
|
await this.bookServiceBasic.UpdateBookTaskDetailVideoMessage(task.bookTaskDetailId, videoMessage);
|
|||
|
|
resData.data.status == "" ? "0" : resData.data.status;
|
|||
|
|
// 返回前端数据
|
|||
|
|
SendMessageToRenderer({
|
|||
|
|
code: 1,
|
|||
|
|
id: task.bookTaskDetailId,
|
|||
|
|
message: "runway合成任务提交成功,正在合成中",
|
|||
|
|
type: ResponseMessageType.RUNWAY_VIDEO,
|
|||
|
|
data: JSON.stringify(resData.data)
|
|||
|
|
}, task.messageName);
|
|||
|
|
await this.FetchRunwayVideoResult(bookTaskDetail, task, resData.data.task_id, baseUrl, gptApiKey);
|
|||
|
|
} catch (error) {
|
|||
|
|
throw new Error("runway合成视频失败,错误信息如下:" + error.toString());
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
async FetchRunwayVideoResult(bookTaskDetail: Book.SelectBookTaskDetail, task: TaskModal.Task, taskId: string, baseUrl: string, gptApiKey: string) {
|
|||
|
|
while (true) {
|
|||
|
|
try {
|
|||
|
|
let url = baseUrl + "/runway/feed";
|
|||
|
|
let res = await axios.post(url, {
|
|||
|
|
"task_id": taskId
|
|||
|
|
}, {
|
|||
|
|
headers: {
|
|||
|
|
Authorization: gptApiKey
|
|||
|
|
}
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
let data = res.data;
|
|||
|
|
if (data.code != 200) {
|
|||
|
|
throw new Error(data.msg);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if (data.data.status == '3') {
|
|||
|
|
// 完成
|
|||
|
|
let videoMessage = cloneDeep(bookTaskDetail.videoMessage);
|
|||
|
|
delete videoMessage.id;
|
|||
|
|
videoMessage.status = VideoStatus.SUCCESS;
|
|||
|
|
videoMessage.taskId = taskId;
|
|||
|
|
videoMessage.videoUrl = data.data.video_url;
|
|||
|
|
videoMessage.messageData = JSON.stringify(data.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: "runway合成视频完成",
|
|||
|
|
type: ResponseMessageType.RUNWAY_VIDEO,
|
|||
|
|
data: JSON.stringify(data.data)
|
|||
|
|
}, task.messageName);
|
|||
|
|
break;
|
|||
|
|
} else if (data.data.status == '1') {
|
|||
|
|
// 处理中
|
|||
|
|
SendMessageToRenderer({
|
|||
|
|
code: 1,
|
|||
|
|
id: bookTaskDetail.id,
|
|||
|
|
message: "runway合成视频处理中",
|
|||
|
|
type: ResponseMessageType.RUNWAY_VIDEO,
|
|||
|
|
data: JSON.stringify(data.data)
|
|||
|
|
}, task.messageName);
|
|||
|
|
} else {
|
|||
|
|
// 没有完成
|
|||
|
|
if (data.data.status == '2') {
|
|||
|
|
// 有报错信息直接返回错误
|
|||
|
|
let videoMessage = cloneDeep(bookTaskDetail.videoMessage);
|
|||
|
|
delete videoMessage.id;
|
|||
|
|
videoMessage.status = VideoStatus.FAIL;
|
|||
|
|
videoMessage.msg = data.data.msg;
|
|||
|
|
videoMessage.taskId = taskId;
|
|||
|
|
videoMessage.messageData = JSON.stringify(data.data);
|
|||
|
|
await this.bookServiceBasic.UpdateBookTaskDetailVideoMessage(bookTaskDetail.id, videoMessage);
|
|||
|
|
SendMessageToRenderer({
|
|||
|
|
code: 0,
|
|||
|
|
id: bookTaskDetail.id,
|
|||
|
|
message: "runway合成视频失败,错误信息如下:" + data.data.msg,
|
|||
|
|
type: ResponseMessageType.RUNWAY_VIDEO,
|
|||
|
|
data: JSON.stringify(data.data)
|
|||
|
|
}, task.messageName);
|
|||
|
|
throw new Error(data.data.msg);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
// 等待20秒后再次请求
|
|||
|
|
await new Promise(resolve => setTimeout(resolve, 20000));
|
|||
|
|
} catch (e) {
|
|||
|
|
throw e;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|