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; } } } }