94 lines
4.5 KiB
TypeScript
94 lines
4.5 KiB
TypeScript
import {
|
||
ProviderTaskFailedError,
|
||
type ProviderTaskLifecycle,
|
||
} from "@/lib/video-canvas/video-task-lifecycle";
|
||
import type { ExecuteDirectVideoGenerationParams } from "@/lib/ai/tools/generate-video";
|
||
|
||
// This adapter deliberately exposes only the official model whose contract we support.
|
||
export const ALIYUN_CANVAS_VIDEO_MODEL = "wan2.6-i2v-flash";
|
||
|
||
export function buildAliyunCanvasVideoRequest(params: ExecuteDirectVideoGenerationParams, modelId: string) {
|
||
if (modelId !== ALIYUN_CANVAS_VIDEO_MODEL) throw new Error("当前阿里云视频模型尚未接入画布");
|
||
if (params.mode !== "first-frame" || params.images?.length !== 1) throw new Error("万相首帧生视频需要一张参考图");
|
||
if (params.audioUrls?.length) throw new Error("此画布接入暂不支持参考音频");
|
||
const duration = params.duration ?? 5;
|
||
const resolution = params.resolution ?? "720p";
|
||
if (!Number.isInteger(duration) || duration < 2 || duration > 15) throw new Error("万相视频时长需为 2–15 秒整数");
|
||
if (!["720p", "1080p"].includes(resolution)) throw new Error("万相视频仅支持 720P / 1080P");
|
||
if (params.cameraFixed || params.returnLastFrame) throw new Error("此万相接入不支持固定镜头参数或返回尾帧参数");
|
||
return {
|
||
model: modelId,
|
||
input: { prompt: params.prompt, img_url: params.images[0] },
|
||
parameters: {
|
||
resolution: resolution.toUpperCase(), duration,
|
||
// The upstream derives aspect ratio from the input image, not a ratio parameter.
|
||
audio: params.generateAudio ?? false,
|
||
prompt_extend: false, watermark: params.watermark ?? false,
|
||
...(params.seed != null ? { seed: params.seed } : {}),
|
||
},
|
||
};
|
||
}
|
||
|
||
export async function executeAliyunCanvasVideo(
|
||
params: ExecuteDirectVideoGenerationParams & {
|
||
modelId: string;
|
||
apiKey: string;
|
||
baseURL: string;
|
||
lifecycle?: ProviderTaskLifecycle;
|
||
},
|
||
runtime = {
|
||
fetch: globalThis.fetch,
|
||
sleep: (ms: number) => new Promise<void>((resolve) => setTimeout(resolve, ms)),
|
||
},
|
||
) {
|
||
const body = buildAliyunCanvasVideoRequest(params, params.modelId);
|
||
const base = new URL(params.baseURL);
|
||
base.pathname = "/api/v1";
|
||
base.search = "";
|
||
base.hash = "";
|
||
const headers = { Authorization: `Bearer ${params.apiKey}`, "Content-Type": "application/json", "X-DashScope-Async": "enable" };
|
||
const deadline = Date.now() + 275_000;
|
||
let taskId = params.lifecycle?.resume?.taskId || "";
|
||
const request = async (path: string, init: RequestInit = {}) => {
|
||
const remaining = deadline - Date.now();
|
||
if (remaining <= 0) throw new Error(`阿里云视频查询超时,任务 ${taskId},请勿重复生成`);
|
||
const response = await runtime.fetch(`${base.toString().replace(/\/$/, "")}${path}`, {
|
||
...init, headers, signal: AbortSignal.timeout(Math.min(30_000, remaining)),
|
||
});
|
||
const data = await response.json();
|
||
if (!response.ok) throw new Error(`阿里云视频请求失败(${response.status}):${data.code || data.error?.code || "UnknownError"}${taskId ? `,任务 ${taskId}` : ""}`);
|
||
return data;
|
||
};
|
||
await params.lifecycle?.assertActive();
|
||
if (!taskId) {
|
||
const submitted = await request("/services/aigc/video-generation/video-synthesis", {
|
||
method: "POST",
|
||
body: JSON.stringify(body),
|
||
});
|
||
taskId = submitted.output?.task_id;
|
||
if (!taskId) throw new Error("阿里云未返回视频任务 ID");
|
||
await params.lifecycle?.accepted({ taskId });
|
||
}
|
||
await params.onProgress?.(`阿里云视频任务已提交:${taskId}`);
|
||
while (Date.now() < deadline) {
|
||
await runtime.sleep(Math.min(5_000, Math.max(0, deadline - Date.now())));
|
||
await params.lifecycle?.assertActive();
|
||
const result = await request(`/tasks/${encodeURIComponent(taskId)}`);
|
||
const output = result.output || {};
|
||
if (output.task_status === "SUCCEEDED") {
|
||
if (!/^https?:\/\//i.test(output.video_url || "")) throw new Error("阿里云任务完成但没有返回视频");
|
||
return { url: output.video_url as string, metadata: {
|
||
provider: "aliyun", taskId, requestId: result.request_id,
|
||
duration: body.parameters.duration, resolution: params.resolution ?? "720p",
|
||
generateAudio: body.parameters.audio,
|
||
} };
|
||
}
|
||
if (["FAILED", "CANCELED", "UNKNOWN"].includes(output.task_status)) {
|
||
throw new ProviderTaskFailedError(
|
||
`阿里云视频生成失败:${output.code || result.code || output.task_status},任务 ${taskId}`,
|
||
);
|
||
}
|
||
}
|
||
throw new Error(`阿里云视频查询超时,任务 ${taskId},请勿重复生成`);
|
||
}
|