1. 文案处理后端服务迁移(此版本之前的后端服务持续到12月1日) 2. 新增默认动图方式 3. (聚合推文)实现原创分类的图生视频(runway,luma,可灵) 4. 修复软件内配音 5. 删除旧的原创生图(原创,SD反推,MJ反推等操作在聚合推文中) 6. 导出剪映适配(视频) 7. 新增四个单句推理模式
230 lines
8.9 KiB
TypeScript
230 lines
8.9 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 { GetBaseUrl } from "@/define/Tools/common";
|
||
import axios from "axios";
|
||
import { cloneDeep, isEmpty } from "lodash";
|
||
import { VideoStatus } from "@/define/enum/video";
|
||
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 LumaService {
|
||
bookServiceBasic: BookServiceBasic
|
||
constructor() {
|
||
this.bookServiceBasic = new BookServiceBasic();
|
||
}
|
||
|
||
async LumaImageToVideo(task: TaskModal.Task, gptUrl: string, gptApiKey: string): Promise<void> {
|
||
try {
|
||
let bookTaskDetail = await this.bookServiceBasic.GetBookTaskDetailDataById(task.bookTaskDetailId);
|
||
let lumaOptionsString = bookTaskDetail.videoMessage.lumaOptions;
|
||
if (!ValidateJson(lumaOptionsString)) {
|
||
throw new Error("lumaOptions参数错误,请检查");
|
||
}
|
||
let lumaOptions: BookTaskDetail.lumaOptions = JSON.parse(lumaOptionsString);
|
||
// console.log("lumaOptions", lumaOptions, "gptUrl", gptUrl, "gptApiKey", gptApiKey);
|
||
|
||
let data: BookTaskDetail.lumaOptions = {
|
||
user_prompt: lumaOptions.user_prompt,
|
||
aspect_ratio: lumaOptions.aspect_ratio,
|
||
loop: lumaOptions.loop,
|
||
}
|
||
// 是不是需要拓展提示词
|
||
if (lumaOptions.hasOwnProperty("expand_prompt")) {
|
||
data.expand_prompt = lumaOptions.expand_prompt;
|
||
}
|
||
// 图片地址(可以不传)
|
||
if (lumaOptions.hasOwnProperty("image_url")) {
|
||
if (!lumaOptions.image_url.startsWith("http")) {
|
||
let imageBase = await GetImageBase64(lumaOptions.image_url);
|
||
data.image_url = imageBase;
|
||
} else {
|
||
data.image_url = lumaOptions.image_url;
|
||
}
|
||
}
|
||
// 尾帧图片地址(可以不传)
|
||
if (lumaOptions.hasOwnProperty("image_end_url") && !isEmpty(lumaOptions.image_end_url)) {
|
||
if (!lumaOptions.image_end_url.startsWith("http")) {
|
||
let imageBase = await GetImageBase64(lumaOptions.image_end_url);
|
||
data.image_end_url = imageBase;
|
||
} else {
|
||
data.image_end_url = lumaOptions.image_end_url;
|
||
}
|
||
}
|
||
|
||
// 开始请求
|
||
let baseUrl = GetBaseUrl(gptUrl);
|
||
let url = baseUrl + "/luma/generations";
|
||
let res = await axios.post(url, data, {
|
||
headers: {
|
||
"Authorization": gptApiKey
|
||
}
|
||
});
|
||
|
||
// console.log("luma合成视频结果", res);
|
||
|
||
let returnData = res.data;
|
||
// console.log("luma合成视频结果", returnData);
|
||
let id = returnData.id;
|
||
|
||
// 修改数据
|
||
let videoMessage = cloneDeep(bookTaskDetail.videoMessage);
|
||
videoMessage.taskId = id;
|
||
videoMessage.status = VideoStatus.PROCESSING;
|
||
videoMessage.messageData = JSON.stringify(returnData);
|
||
delete videoMessage.id;
|
||
await this.bookServiceBasic.UpdateBookTaskDetailVideoMessage(task.bookTaskDetailId, videoMessage);
|
||
|
||
// 返回前端数据
|
||
SendMessageToRenderer({
|
||
code: 1,
|
||
id: task.bookTaskDetailId,
|
||
message: "runway合成任务提交成功,正在合成中",
|
||
type: ResponseMessageType.LUMA_VIDEO,
|
||
data: JSON.stringify(returnData)
|
||
}, task.messageName);
|
||
|
||
await this.FetchLumaVideoResult(bookTaskDetail, task, id, baseUrl, gptApiKey);
|
||
} catch (error) {
|
||
throw new Error("Luma合成视频失败,错误信息如下:" + error.toString());
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 获取指定无水印的视频地址
|
||
* @param taskId 人任务ID
|
||
* @param baseUrl baseUrl
|
||
* @param gptApiKey Key
|
||
* @returns
|
||
*/
|
||
async GetVideoUri(taskId: string, baseUrl: string, gptApiKey: string): Promise<string> {
|
||
let url = baseUrl + "/luma/generations/" + taskId + "/download_video_url";
|
||
let res = await axios.get(url, {
|
||
headers: {
|
||
Authorization: gptApiKey
|
||
}
|
||
});
|
||
let resData = res.data;
|
||
// console.log("luma获取视频地址", resData);
|
||
return resData.url;
|
||
}
|
||
|
||
/**
|
||
* 循环获取数据
|
||
* @param bookTaskDetail 小说详细数据
|
||
* @param task 任务实体
|
||
* @param taskId 任务ID(合成视频的)
|
||
* @param baseUrl 请求的网址
|
||
* @param gptApiKey APIKey
|
||
*/
|
||
async FetchLumaVideoResult(bookTaskDetail: Book.SelectBookTaskDetail, task: TaskModal.Task, taskId: string, baseUrl: string, gptApiKey: string) {
|
||
while (true) {
|
||
try {
|
||
let url = baseUrl + "/luma/generations/" + taskId;
|
||
let res = await axios.get(url, {
|
||
headers: {
|
||
Authorization: gptApiKey
|
||
}
|
||
});
|
||
// console.log("luma合成视频结果", res.data);
|
||
let resData = res.data;
|
||
if (resData.state == "completed") {
|
||
|
||
let video_url = resData.video.download_url;
|
||
if (isEmpty(video_url)) {
|
||
// 完成
|
||
let vr = await this.GetVideoUri(taskId, baseUrl, gptApiKey);
|
||
// console.log("luma合成视频结果", vr);
|
||
video_url = vr;
|
||
}
|
||
// 保存数据
|
||
let videoMessage = cloneDeep(bookTaskDetail.videoMessage);
|
||
delete videoMessage.id;
|
||
videoMessage.status = VideoStatus.SUCCESS;
|
||
videoMessage.taskId = taskId;
|
||
videoMessage.videoUrl = video_url;
|
||
videoMessage.messageData = JSON.stringify(resData);
|
||
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: "Luma合成视频完成",
|
||
type: ResponseMessageType.LUMA_VIDEO,
|
||
data: JSON.stringify(resData)
|
||
}, task.messageName);
|
||
break;
|
||
} else {
|
||
if (resData.state == "pending") {
|
||
// 任务再等待
|
||
let videoMessage = cloneDeep(bookTaskDetail.videoMessage);
|
||
delete videoMessage.id;
|
||
videoMessage.status = VideoStatus.WAIT;
|
||
videoMessage.taskId = taskId;
|
||
videoMessage.messageData = JSON.stringify(resData);
|
||
await this.bookServiceBasic.UpdateBookTaskDetailVideoMessage(task.bookTaskDetailId, videoMessage);
|
||
SendMessageToRenderer({
|
||
code: 1,
|
||
id: bookTaskDetail.id,
|
||
message: "Luma合成视频等待中",
|
||
type: ResponseMessageType.LUMA_VIDEO,
|
||
data: JSON.stringify(resData)
|
||
}, task.messageName);
|
||
} else if (resData.state == "processing") {
|
||
// 任务进行中
|
||
let videoMessage = cloneDeep(bookTaskDetail.videoMessage);
|
||
delete videoMessage.id;
|
||
videoMessage.status = VideoStatus.PROCESSING;
|
||
videoMessage.taskId = taskId;
|
||
videoMessage.messageData = JSON.stringify(resData);
|
||
await this.bookServiceBasic.UpdateBookTaskDetailVideoMessage(task.bookTaskDetailId, videoMessage);
|
||
SendMessageToRenderer({
|
||
code: 1,
|
||
id: bookTaskDetail.id,
|
||
message: "Luma合成视频进行中",
|
||
type: ResponseMessageType.LUMA_VIDEO,
|
||
data: JSON.stringify(resData)
|
||
}, task.messageName);
|
||
} else {
|
||
// 任务失败
|
||
let videoMessage = cloneDeep(bookTaskDetail.videoMessage);
|
||
delete videoMessage.id;
|
||
videoMessage.status = VideoStatus.FAIL;
|
||
videoMessage.taskId = taskId;
|
||
videoMessage.messageData = JSON.stringify(resData);
|
||
await this.bookServiceBasic.UpdateBookTaskDetailVideoMessage(task.bookTaskDetailId, videoMessage);
|
||
await this.bookServiceBasic.UpdetedBookTaskData(task.bookTaskId, {
|
||
status: BookTaskStatus.IMAGE_TO_VIDEO_ERROR,
|
||
})
|
||
await this.bookServiceBasic.UpdateTaskStatus({
|
||
id: task.id,
|
||
status: BookBackTaskStatus.FAIL,
|
||
})
|
||
SendMessageToRenderer({
|
||
code: 1,
|
||
id: bookTaskDetail.id,
|
||
message: "Luma合成视频失败",
|
||
type: ResponseMessageType.LUMA_VIDEO,
|
||
data: JSON.stringify(resData)
|
||
}, task.messageName);
|
||
throw new Error("Luma合成视频失败,失败信息请查看API后台");
|
||
}
|
||
}
|
||
// 等待20秒后再次请求
|
||
await new Promise(resolve => setTimeout(resolve, 15000));
|
||
} catch (error) {
|
||
throw error;
|
||
}
|
||
}
|
||
}
|
||
} |