diff --git a/README.md b/README.md index 25c88b1..6946b63 100644 --- a/README.md +++ b/README.md @@ -604,4 +604,8 @@ Toonflow 基于 Apache-2.0 协议开源发布,并附有补充商业协议。 完整的第三方依赖清单请查阅 `NOTICES.txt` -##### copyright © 淮北艾阿网络科技有限公司 \ No newline at end of file +<<<<<<< HEAD +##### copyright © 淮北艾阿网络科技有限公司 +======= +##### copyright © 淮北艾阿网络科技有限公司 +>>>>>>> 108 diff --git a/package.json b/package.json index ba1971c..211e532 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,11 @@ { +<<<<<<< HEAD + "name": "toonflow-app", + "version": "1.0.7", +======= "name": "toonflow", "version": "1.0.8", +>>>>>>> 108 "description": "Toonflow 是一款 AI 短剧漫剧工具,能够利用 AI 技术将小说自动转化为剧本,并结合 AI 生成的图片和视频,实现高效的短剧创作。", "author": "HBAI-Ltd ", "license": "Apache-2.0", diff --git a/src/utils/ai/video/owned/volcengine.ts b/src/utils/ai/video/owned/volcengine.ts new file mode 100644 index 0000000..c58a478 --- /dev/null +++ b/src/utils/ai/video/owned/volcengine.ts @@ -0,0 +1,88 @@ +import "../type"; +import axios from "axios"; +import { pollTask, validateVideoConfig } from "@/utils/ai/utils"; + +export default async (input: VideoConfig, config: AIConfig) => { + if (!config.apiKey) throw new Error("缺少API Key"); + + // const { owned, images, hasStartEndType } = validateVideoConfig(input, config); + const hasStartEndType = input.mode === "startEnd"; + const authorization = "Bearer " + config.apiKey.replace(/^Bearer\s*/i, "").trim(); + const baseUrl = config.baseURL ?? "https://ark.cn-beijing.volces.com/api/v3/contents/generations/tasks"; + const images = input.imageBase64 || []; + + // 构建图片内容 + const imageContent = images.map((base64, index) => { + const item: Record = { + type: "image_url", + image_url: { url: base64 }, + }; + if (hasStartEndType) { + item.role = index === 0 ? "first_frame" : "last_frame"; + } else { + item.role = "reference_image"; + } + return item; + }); + + // 构建请求体 + const requestBody: Record = { + model: config.model, + content: [{ type: "text", text: input.prompt }, ...imageContent], + duration: input.duration, + resolution: input.resolution, + watermark: false, + }; + + // 仅当模型支持音频时才添加 generate_audio 字段 + if (typeof input?.audio == "boolean") { + requestBody.generate_audio = input.audio ?? false; + } + + // 创建视频生成任务 + const createResponse = await axios.post(baseUrl, requestBody, { + headers: { + "Content-Type": "application/json", + Authorization: authorization, + }, + }); + console.log("%c Line:44 🍡 createResponse", "background:#2eafb0", createResponse.data); + + const taskId = createResponse.data.id; + + if (!taskId) throw new Error("视频任务创建失败"); + + // 轮询任务状态 + return await pollTask(async () => { + const data = await axios.get(`${baseUrl}/${taskId}`, { + headers: { Authorization: authorization }, + }); + console.log("%c Line:62 🥕 data.data", "background:#e41a6a", data.data); + + const { status, content, error } = data.data; + + switch (status) { + case "succeeded": + case "completed": + return { completed: true, url: content?.video_url }; + case "failed": + case "cancelled": + case "expired": + let errorMsg = ""; + try { + errorMsg = typeof error === "string" ? error : JSON.stringify(error); + } catch (e) { + errorMsg = error || ""; + } + return { completed: false, error: `任务${status}: ${errorMsg}` }; + case "queued": + case "running": + case "unknown": + case "submit": + case "in_progress": + return { completed: false }; + default: + return { completed: false, error: `未知状态: ${status}` }; + } + }); +};