From 32ac82b08b024c4e5e99901d919e462e0a5e8fc8 Mon Sep 17 00:00:00 2001 From: zhishi <1951671751@qq.com> Date: Sat, 7 Feb 2026 23:32:47 +0800 Subject: [PATCH 1/2] =?UTF-8?q?=E4=BF=AE=E5=A4=8Dapi=E5=88=A0=E9=99=A4?= =?UTF-8?q?=EF=BC=8C=E5=AF=BC=E8=87=B4=E6=A8=A1=E5=9E=8B=E5=88=A0=E9=99=A4?= =?UTF-8?q?=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/lib/initDB.ts | 26 ++++++++++++-------------- src/routes/setting/delModel.ts | 2 +- src/routes/setting/getAiModelMap.ts | 2 +- src/utils/ai/text/index.ts | 2 +- src/utils/ai/text/modelList.ts | 20 ++++++++++---------- 5 files changed, 25 insertions(+), 27 deletions(-) diff --git a/src/lib/initDB.ts b/src/lib/initDB.ts index 8876fe0..71c2672 100644 --- a/src/lib/initDB.ts +++ b/src/lib/initDB.ts @@ -212,7 +212,7 @@ export default async (knex: Knex, forceInit: boolean = false): Promise => table.integer("id").notNullable(); table.integer("scriptId"); // 关联的脚本ID table.integer("projectId"); // 关联的项目ID - table.integer("aiConfigId");//ai配置ID + table.integer("aiConfigId"); //ai配置ID table.text("manufacturer"); // 厂商:volcengine/runninghub/openAi table.text("mode"); // 模式:startEnd/multi/single table.text("startFrame"); // 首帧图片信息 JSON @@ -249,41 +249,39 @@ export default async (knex: Knex, forceInit: boolean = false): Promise => { id: 2, configId: null, - - name: "大纲故事线Agent", - key: "outlineScriptAgent", + name: "分镜Agent图片生成", + key: "storyboardImage", }, { id: 3, configId: null, - + name: "大纲故事线Agent", + key: "outlineScriptAgent", + }, + { + id: 4, + configId: null, name: "资产提示词润色", key: "assetsPrompt", }, { - id: 4, + id: 5, configId: null, name: "资产图片生成", key: "assetsImage", }, { - id: 5, + id: 6, configId: null, name: "剧本生成", key: "generateScript", }, { - id: 6, + id: 7, configId: null, name: "视频提示词生成", key: "videoPrompt", }, - { - id: 7, - configId: null, - name: "分镜图片生成", - key: "storyboardImage", - }, { id: 8, configId: null, diff --git a/src/routes/setting/delModel.ts b/src/routes/setting/delModel.ts index 718eca7..802ae0c 100644 --- a/src/routes/setting/delModel.ts +++ b/src/routes/setting/delModel.ts @@ -13,7 +13,7 @@ export default router.post( async (req, res) => { const { id } = req.body; await u.db("t_config").where("id", id).delete(); - await u.db("t_aiModelMap").where("configId", id).delete(); + await u.db("t_aiModelMap").where("configId", id).update("configId",null); res.status(200).send(success("删除成功")); }, ); diff --git a/src/routes/setting/getAiModelMap.ts b/src/routes/setting/getAiModelMap.ts index ebd6d66..fa10f51 100644 --- a/src/routes/setting/getAiModelMap.ts +++ b/src/routes/setting/getAiModelMap.ts @@ -8,6 +8,6 @@ export default router.post("/", async (req, res) => { const configData = await u .db("t_aiModelMap") .leftJoin("t_config", "t_aiModelMap.configId", "t_config.id") - .select("t_aiModelMap.name", "t_config.model", "t_aiModelMap.id"); + .select("t_aiModelMap.name", "t_config.model", "t_aiModelMap.id", "t_aiModelMap.key"); res.status(200).send(success(configData)); }); diff --git a/src/utils/ai/text/index.ts b/src/utils/ai/text/index.ts index a353ab3..bfc45d7 100644 --- a/src/utils/ai/text/index.ts +++ b/src/utils/ai/text/index.ts @@ -23,7 +23,7 @@ interface AIConfig { } const buildOptions = async (input: AIInput, config: AIConfig = {}) => { - if (!config || !config?.model || !config?.apiKey || !config?.baseURL || !config?.manufacturer) throw new Error("请检查模型配置是否正确"); + if (!config || !config?.model || !config?.apiKey || !config?.manufacturer) throw new Error("请检查模型配置是否正确"); const { model, apiKey, baseURL, manufacturer } = { ...config }; let owned; if (manufacturer == "other") { diff --git a/src/utils/ai/text/modelList.ts b/src/utils/ai/text/modelList.ts index 4aca5ac..421885a 100644 --- a/src/utils/ai/text/modelList.ts +++ b/src/utils/ai/text/modelList.ts @@ -48,7 +48,7 @@ const modelList: Owned[] = [ // 豆包 { manufacturer: "doubao", - model: "doubao-seed-1-8", + model: "doubao-seed-1-8-251228", responseFormat: "schema", image: true, think: true, @@ -57,7 +57,7 @@ const modelList: Owned[] = [ }, { manufacturer: "doubao", - model: "doubao-seed-1-6", + model: "doubao-seed-1-6-251015", responseFormat: "schema", image: true, think: true, @@ -66,7 +66,7 @@ const modelList: Owned[] = [ }, { manufacturer: "doubao", - model: "doubao-seed-1-6-lite", + model: "doubao-seed-1-6-lite-251015", responseFormat: "schema", image: true, think: true, @@ -75,7 +75,7 @@ const modelList: Owned[] = [ }, { manufacturer: "doubao", - model: "doubao-seed-1-6-flash", + model: "doubao-seed-1-6-flash-250828", responseFormat: "schema", image: true, think: true, @@ -286,7 +286,7 @@ const modelList: Owned[] = [ // Gemini { - manufacturer: "google", + manufacturer: "gemini", model: "gemini-2.5-pro", responseFormat: "schema", image: true, @@ -295,7 +295,7 @@ const modelList: Owned[] = [ tool: true, }, { - manufacturer: "google", + manufacturer: "gemini", model: "gemini-2.5-flash", responseFormat: "schema", image: true, @@ -304,7 +304,7 @@ const modelList: Owned[] = [ tool: true, }, { - manufacturer: "google", + manufacturer: "gemini", model: "gemini-2.0-flash", responseFormat: "schema", image: true, @@ -313,7 +313,7 @@ const modelList: Owned[] = [ tool: true, }, { - manufacturer: "google", + manufacturer: "gemini", model: "gemini-2.0-flash-lite", responseFormat: "schema", image: true, @@ -322,7 +322,7 @@ const modelList: Owned[] = [ tool: true, }, { - manufacturer: "google", + manufacturer: "gemini", model: "gemini-1.5-pro", responseFormat: "schema", image: true, @@ -331,7 +331,7 @@ const modelList: Owned[] = [ tool: true, }, { - manufacturer: "google", + manufacturer: "gemini", model: "gemini-1.5-flash", responseFormat: "schema", image: true, From 12fdb3f0d1992644dcc3f7bae3f308166e66f967 Mon Sep 17 00:00:00 2001 From: zhishi <1951671751@qq.com> Date: Sun, 8 Feb 2026 18:07:03 +0800 Subject: [PATCH 2/2] =?UTF-8?q?=E8=A7=86=E9=A2=91=E7=94=9F=E6=88=90=20sora?= =?UTF-8?q?=E5=A4=9A=E5=9B=BE=E8=87=AA=E5=8A=A8=E8=BD=AC=E6=8D=A2=E5=AE=AB?= =?UTF-8?q?=E6=A0=BC=EF=BC=8C=E5=85=B6=E4=BB=96=E5=8E=82=E5=95=86=E6=8E=A5?= =?UTF-8?q?=E5=85=A5=E8=A7=86=E9=A2=91(=E5=BE=85=E6=B5=8B=E8=AF=95)?= =?UTF-8?q?=E3=80=82/(=E3=84=92o=E3=84=92)/~~?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/agents/outlineScript/index.ts | 1 - src/agents/storyboard/index.ts | 17 +- src/routes/video/generateVideo.ts | 208 ++++++++++++++++++++----- src/utils/ai/image/owned/other.ts | 1 - src/utils/ai/utils.ts | 12 +- src/utils/ai/video/index.ts | 3 +- src/utils/ai/video/owned/other.ts | 59 +++++++ src/utils/ai/video/owned/runninghub.ts | 16 +- 8 files changed, 267 insertions(+), 50 deletions(-) create mode 100644 src/utils/ai/video/owned/other.ts diff --git a/src/agents/outlineScript/index.ts b/src/agents/outlineScript/index.ts index 30348ed..2ca7d75 100644 --- a/src/agents/outlineScript/index.ts +++ b/src/agents/outlineScript/index.ts @@ -700,7 +700,6 @@ ${task} const envContext = await this.buildEnvironmentContext(); const prompts = await u.db("t_prompts").where("code", "outlineScript-main").first(); - console.log("%c Line:703 🍭 prompts", "background:#f5ce50", prompts); const promptConfig = await u.getPromptAi("outlineScriptAgent"); const mainPrompts = prompts?.customValue || prompts?.defaultValue || "不论用户说什么,请直接输出Agent配置异常"; diff --git a/src/agents/storyboard/index.ts b/src/agents/storyboard/index.ts index 4bf36e7..a555c7e 100644 --- a/src/agents/storyboard/index.ts +++ b/src/agents/storyboard/index.ts @@ -36,8 +36,13 @@ interface Shot { x: number; y: number; cells: Array<{ src?: string; prompt?: string; id?: string }>; // 镜头数组,每个cell是一个镜头 + fragmentContent: string; + assetsTags: AssetsType[]; +} +interface AssetsType { + type: "role" | "props" | "scene"; + text: string; } - // ==================== 主类 ==================== export default class Storyboard { @@ -220,11 +225,17 @@ ${sections.join("\n\n")} z.object({ segmentIndex: z.number().describe("对应的片段序号"), prompts: z.array(z.string()).describe("镜头提示词数组,每个提示词对应一个镜头(中文)"), + assetsTags: z.array( + z.object({ + type: z.enum(["role", "props", "scene"]).describe("资源类型"), + text: z.string().describe("资源名称"), + }), + ), }), ) .describe("要添加的分镜数组"), }), - execute: async ({ shots }: { shots: Array<{ segmentIndex: number; prompts: string[] }> }) => { + execute: async ({ shots }: { shots: Array<{ segmentIndex: number; prompts: string[]; assetsTags: AssetsType[] }> }) => { const added: { id: number; segmentIndex: number }[] = []; const skipped: number[] = []; @@ -244,6 +255,8 @@ ${sections.join("\n\n")} x: 0, y: 0, cells: item.prompts.map((prompt) => ({ id: u.uuid(), prompt })), + fragmentContent: this.segments[item.segmentIndex]?.description, + assetsTags: item.assetsTags, }); added.push({ id: shotId, segmentIndex: item.segmentIndex }); } diff --git a/src/routes/video/generateVideo.ts b/src/routes/video/generateVideo.ts index 14bb5d4..defafbe 100644 --- a/src/routes/video/generateVideo.ts +++ b/src/routes/video/generateVideo.ts @@ -5,6 +5,9 @@ import { v4 as uuidv4 } from "uuid"; import { error, success } from "@/lib/responseFormat"; import { validateFields } from "@/middleware/middleware"; import { t_config } from "@/types/database"; +import sharp from "sharp"; +import fs from "fs"; +import path from "path"; const router = express.Router(); @@ -21,32 +24,29 @@ export default router.post( filePath: z.array(z.string()), duration: z.number(), prompt: z.string(), + mode: z.enum(["startEnd", "multi", "single", "text"]), }), async (req, res) => { - const { type, scriptId, projectId, configId, aiConfigId, resolution, filePath, duration, prompt } = req.body; + const { type, mode, scriptId, projectId, configId, aiConfigId, resolution, filePath, duration, prompt } = req.body; - // // 参数校验 - // if (type === "volcengine") { - // if (duration < 4 || duration > 12) { - // return res.status(400).send(error("视频时长需在4-12秒之间")); - // } - // if (!["480p", "720p", "1080p"].includes(resolution)) { - // return res.status(400).send(error("视频分辨率不正确")); - // } - // } - - // if (type === "runninghub") { - // if (duration !== 10 && duration !== 15) { - // return res.status(400).send(error("视频时长只能是10秒或15秒")); - // } - // if (resolution !== "9:16" && resolution !== "16:9") { - // return res.status(400).send(error("视频分辨率不正确")); - // } - // } + if (mode == "text") filePath.length = 0; + else if (!filePath.length) { + return res.status(500).send(error("请先选择图片")); + } const configData = await u.db("t_videoConfig").where("id", configId).first(); + if (!configData) { return res.status(500).send(error("视频配置不存在")); } + if (configData.manufacturer == "runninghub") { + if (filePath.length > 1) { + const gridUrl = await sharpProcessingImage(filePath, projectId); + if (gridUrl) { + filePath.length = 0; + filePath.push(gridUrl); + } + } + } // 优先使用视频配置中的AI配置ID查询,查不到再使用传入的aiConfigId let aiConfigData = null; @@ -63,12 +63,8 @@ export default router.post( // 过滤掉空值 let fileUrl = filePath.filter((p: string) => p && p.trim() !== ""); - if (fileUrl.length === 0) { - return res.status(400).send(error("请至少选择一张图片")); - } - // 处理文件路径,如果是 base64 则上传到 OSS - if (fileUrl.length === 1) { + if (fileUrl.length) { const match = fileUrl[0].match(/base64,([A-Za-z0-9+/=]+)/); if (match && match.length >= 2) { const imagePath = `/${projectId}/assets/${uuidv4()}.jpg`; @@ -87,20 +83,21 @@ export default router.post( // 否则认为已经是路径 return url; }; + if (fileUrl.length) { + // 校验文件是否存在 + const fileExistsResults = await Promise.all( + fileUrl.map(async (url: string) => { + const path = getPathname(url); + return u.oss.fileExists(path); + }), + ); - // 校验文件是否存在 - const fileExistsResults = await Promise.all( - fileUrl.map(async (url: string) => { - const path = getPathname(url); - return u.oss.fileExists(path); - }), - ); - - if (!fileExistsResults.every(Boolean)) { - return res.status(400).send(error("选择分镜文件不存在")); + if (!fileExistsResults.every(Boolean)) { + return res.status(400).send(error("选择分镜文件不存在")); + } } - const firstFrame = getPathname(fileUrl[0]); + const firstFrame = fileUrl.length ? getPathname(fileUrl[0]) : ""; const storyboardImgs = fileUrl.map((path: string) => getPathname(path)); const savePath = `/${projectId}/video/${uuidv4()}.mp4`; @@ -137,7 +134,7 @@ async function generateVideoAsync( aiConfigData: t_config, ) { try { - const projectData = await u.db("t_project").where("id", projectId).select("artStyle").first(); + const projectData = await u.db("t_project").where("id", projectId).select("artStyle", "videoRatio").first(); // 提取路径名的辅助函数 const getPathname = (url: string): string => { @@ -173,7 +170,7 @@ ${prompt} savePath, prompt: inputPrompt, duration: duration as any, - aspectRatio: resolution as any, + aspectRatio: projectData?.videoRatio as any, resolution: resolution as any, }, { @@ -195,7 +192,142 @@ ${prompt} await u.db("t_video").where("id", videoId).update({ state: -1 }); } } catch (err) { - console.error(`视频生成失败 videoId=${videoId}:`, err); + console.error(`视频生成失败 videoId=${videoId}:`, u.error(err).message); await u.db("t_video").where("id", videoId).update({ state: -1 }); } } + +/** + * 使用sharp把图片拼接为宫格图,最多3x3,图片数量为1-9不等 + * @param imageList - 图片路径或base64数组 + * @returns 拼接后的图片Buffer + */ +async function sharpProcessingImage(imageList: string[], projectId: number): Promise { + if (!imageList || imageList.length === 0) { + throw new Error("图片列表不能为空"); + } + + if (imageList.length > 9) { + throw new Error("图片数量不能超过9张"); + } + + // 计算网格布局:根据图片数量确定行列数 + const count = imageList.length; + let cols: number, rows: number; + + if (count === 1) { + cols = rows = 1; + } else if (count === 2) { + cols = 2; + rows = 1; + } else if (count <= 4) { + cols = rows = 2; + } else if (count <= 6) { + cols = 3; + rows = 2; + } else { + cols = rows = 3; + } + + // 第一步:加载所有图片并获取原始尺寸 + const loadedImages = await Promise.all( + imageList.map(async (imagePath) => { + let imageBuffer: Buffer; + + // 判断是base64、URL还是文件路径 + if (imagePath.startsWith("data:image") || imagePath.match(/^[A-Za-z0-9+/=]+$/)) { + // Base64格式 + const base64Data = imagePath.replace(/^data:image\/\w+;base64,/, ""); + imageBuffer = Buffer.from(base64Data, "base64"); + } else if (imagePath.startsWith("http://") || imagePath.startsWith("https://")) { + // URL格式,提取pathname后从OSS读取 + const pathname = new URL(imagePath).pathname; + imageBuffer = await u.oss.getFile(pathname); + } else { + // 文件路径,直接从OSS读取 + imageBuffer = await u.oss.getFile(imagePath); + } + + const metadata = await sharp(imageBuffer).metadata(); + return { + buffer: imageBuffer, + width: metadata.width || 0, + height: metadata.height || 0, + }; + }), + ); + + // 第二步:找出所有图片中的最大宽度和高度 + const maxWidth = Math.max(...loadedImages.map((img) => img.width)); + const maxHeight = Math.max(...loadedImages.map((img) => img.height)); + + // 第三步:将所有图片调整为统一尺寸(使用contain模式保持比例,填充背景色) + const imageData = await Promise.all( + loadedImages.map(async (img) => { + const resizedBuffer = await sharp(img.buffer) + .resize(maxWidth, maxHeight, { + fit: "contain", + background: { r: 0, g: 0, b: 0, alpha: 1 }, // 黑色背景填充 + }) + .png() + .toBuffer(); + + return { + buffer: resizedBuffer, + width: maxWidth, + height: maxHeight, + }; + }), + ); + + // 所有图片都是相同尺寸,直接计算画布大小 + const cellWidth = maxWidth; + const cellHeight = maxHeight; + const canvasWidth = cols * cellWidth; + const canvasHeight = rows * cellHeight; + + // 创建空白画布 + const canvas = sharp({ + create: { + width: canvasWidth, + height: canvasHeight, + channels: 4, + background: { r: 255, g: 255, b: 255, alpha: 1 }, + }, + }); + + // 准备合成操作 + const compositeOperations = imageData.map((data, index) => { + const row = Math.floor(index / cols); + const col = index % cols; + + // 计算当前图片的位置(无偏移,紧密排列) + const left = col * cellWidth; + const top = row * cellHeight; + + return { + input: data.buffer, + top: top, + left: left, + }; + }); + + // 合成所有图片 + const result = await canvas.composite(compositeOperations).png().toBuffer(); + + // 保存图片到当前文件夹,方便查看测试效果 + const timestamp = new Date().getTime(); + const outputFileName = `merged_image_${timestamp}.png`; + const outputPath = path.join(__dirname, outputFileName); + + try { + await fs.promises.writeFile(outputPath, result); + } catch (err) { + console.error(`❌ 保存图片失败:`, err); + } + const imagePath = `/${projectId}/assets/${uuidv4()}.jpg`; + const buffer = Buffer.from(result, "base64"); + await u.oss.writeFile(imagePath, buffer); + + return await u.oss.getFileUrl(imagePath); +} diff --git a/src/utils/ai/image/owned/other.ts b/src/utils/ai/image/owned/other.ts index 5683da7..83e1df6 100644 --- a/src/utils/ai/image/owned/other.ts +++ b/src/utils/ai/image/owned/other.ts @@ -40,7 +40,6 @@ export default async (input: ImageConfig, config: AIConfig): Promise => } else { promptData = fullPrompt + `请直接输出图片`; } - console.log("%c Line:31 🍅 promptData", "background:#2eafb0", promptData); const result = await generateText({ model: otherProvider.languageModel(model), diff --git a/src/utils/ai/utils.ts b/src/utils/ai/utils.ts index 55aab17..d03aa2e 100644 --- a/src/utils/ai/utils.ts +++ b/src/utils/ai/utils.ts @@ -37,9 +37,15 @@ export const validateVideoConfig = (input: VideoConfig, config: AIConfig, custom throw new Error(`模型 ${config.model} 不支持多图模式`); } // 校验duration和resolution是否在支持范围内 - const validDurationResolution = owned.durationResolutionMap.some( - (map) => map.duration.includes(input.duration) && map.resolution.includes(input.resolution as typeof map.resolution[number]), - ); + const validDurationResolution = owned.durationResolutionMap.some((map) => { + const durationMatch = map.duration.includes(input.duration); + const resolutionMatch = + // 若 map.resolution 和 input.resolution 均为空,视为匹配 + (!input.resolution && map.resolution.length === 0) || + // 否则匹配 includes + map.resolution.includes(input.resolution as (typeof map.resolution)[number]); + return durationMatch && resolutionMatch; + }); if (!validDurationResolution) { const supportedDurations = [...new Set(owned.durationResolutionMap.flatMap((m) => m.duration))].sort((a, b) => a - b); const supportedResolutions = [...new Set(owned.durationResolutionMap.flatMap((m) => m.resolution))]; diff --git a/src/utils/ai/video/index.ts b/src/utils/ai/video/index.ts index f8f7183..ad39c9a 100644 --- a/src/utils/ai/video/index.ts +++ b/src/utils/ai/video/index.ts @@ -10,6 +10,7 @@ import wan from "./owned/wan"; import runninghub from "./owned/runninghub"; import gemini from "./owned/gemini"; import apimart from "./owned/apimart"; +import other from "./owned/other"; const modelInstance = { volcengine: volcengine, @@ -19,10 +20,10 @@ const modelInstance = { gemini: gemini, runninghub: runninghub, apimart: apimart, + // other: other, } as const; export default async (input: VideoConfig, config?: AIConfig) => { - console.log("%c Line:25 🥛 config", "background:#2eafb0", config); const { model, apiKey, baseURL, manufacturer } = { ...config }; if (!config || !config?.model || !config?.apiKey) throw new Error("请检查模型配置是否正确"); diff --git a/src/utils/ai/video/owned/other.ts b/src/utils/ai/video/owned/other.ts new file mode 100644 index 0000000..2843326 --- /dev/null +++ b/src/utils/ai/video/owned/other.ts @@ -0,0 +1,59 @@ +import "../type"; +import axios from "axios"; +import sharp from "sharp"; +import FormData from "form-data"; +import { pollTask, validateVideoConfig } from "@/utils/ai/utils"; +import { createOpenAI } from "@ai-sdk/openai"; +import { experimental_generateVideo as generateVideo } from "ai"; +export default async (input: VideoConfig, config: AIConfig) => { + console.log("%c Line:9 🌰 config", "background:#fca650", config); + console.log("%c Line:9 🍒 input", "background:#33a5ff", input); + if (!config.apiKey) throw new Error("缺少API Key"); + if (!config.baseURL) throw new Error("缺少baseURL"); + // const { owned, images, hasTextType } = validateVideoConfig(input, config); + const [requestUrl, queryUrl] = config.baseURL.split("|"); + + const authorization = `Bearer ${config.apiKey}`; + + const formData = new FormData(); + formData.append("model", config.model); + formData.append("prompt", input.prompt); + formData.append("seconds", String(input.duration)); + + // 根据 aspectRatio 设置 size + const sizeMap: Record = { + "16:9": "1280x720", + "9:16": "720x1280", + }; + formData.append("size", sizeMap[input.aspectRatio] || "1920x1080"); + console.log("%c Line:30 🍇 sizeMap[input.aspectRatio]", "background:#93c0a4", sizeMap[input.aspectRatio]); + if (input.imageBase64 && input.imageBase64.length) { + const base64Data = input.imageBase64[0]!.replace(/^data:image\/\w+;base64,/, ""); + const buffer = Buffer.from(base64Data, "base64"); + formData.append("input_reference", buffer, { filename: "image.jpg", contentType: "image/jpeg" }); + } + + const body = { + model: "sora-2-all", + messages: [ + { + role: "user", + content: [ + { + type: "text", + text: input.prompt, + }, + ], + }, + ], + }; + const { data } = await axios.post( + "https://api2.aigcbest.top/v1/chat/completions", + { ...body }, + { + headers: { "Content-Type": "application/json", Authorization: authorization }, + }, + ); + console.log("%c Line:62 🍩 data", "background:#465975", data); + if (data.status === "FAILED") throw new Error(`任务提交失败: ${data.errorMessage || "未知错误"}`); +}; diff --git a/src/utils/ai/video/owned/runninghub.ts b/src/utils/ai/video/owned/runninghub.ts index df756d2..1a4d6c2 100644 --- a/src/utils/ai/video/owned/runninghub.ts +++ b/src/utils/ai/video/owned/runninghub.ts @@ -14,7 +14,7 @@ export default async (input: VideoConfig, config: AIConfig) => { "https://www.runninghub.cn/openapi/v2/rhart-video-s/image-to-video-pro", "https://www.runninghub.cn/openapi/v2/rhart-video-s/text-to-video", "https://www.runninghub.cn/openapi/v2/rhart-video-s/text-to-video-pro", - "https://www.runninghub.cn/openapi/v2/rhart-video-s/{taskId}", + "https://www.runninghub.cn/openapi/v2/query", "https://www.runninghub.cn/openapi/v2/media/upload/binary", ].join("|"); @@ -78,9 +78,17 @@ export default async (input: VideoConfig, config: AIConfig) => { const { taskId } = await submitTask(submitUrl, requestBody); return await pollTask(async () => { - const { data } = await axios.get(queryUrl.replace("{taskId}", taskId), { - headers: { Authorization: authorization }, - }); + + const { data } = await axios.post( + queryUrl, + { + taskId, + }, + { + headers: { Authorization: authorization }, + }, + ); + if (data.status === "SUCCESS") { return data.results?.length ? { completed: true, url: data.results[0].url } : { completed: false, error: "任务成功但未返回视频链接" }; }