fix(canvas): 视频参考协议 - 统一首尾帧与角色资产语义
This commit is contained in:
1 parent
b5f8b66622
commit
37eb2b7e48
15 files changed
+479
-68
No files matched your search
@@ -5,6 +5,7 @@ import type { ReferenceAudio, ReferenceVideo } from "@/types/media";
|
||||
|
||||
import type { ApiEnvelope, RequestOptions, ResolvedAiConfig, VideoGenerationTask, VideoGenerationTaskState } from "./video-contracts";
|
||||
import type { VideoProviderDeps } from "./video-provider-deps";
|
||||
import { resolveVideoImageReferences, type VideoImageRole } from "./video-reference-roles";
|
||||
|
||||
type AgnesTaskResponse = {
|
||||
id?: string;
|
||||
@@ -44,9 +45,10 @@ export async function createAgnesVideoTask(
|
||||
Promise.all(videoReferences.map((item) => resolveAgnesMediaUrl(item.url, item.storageKey))),
|
||||
Promise.all(audioReferences.map((item) => resolveAgnesMediaUrl(item.url, item.storageKey))),
|
||||
]);
|
||||
const imageRoles = resolveVideoImageReferences(references, options, { videoCount: videoUrls.length, audioCount: audioUrls.length }).map(({ role }) => role);
|
||||
const payload = modelName === "agnes-video-v2.0"
|
||||
? buildAgnesV20Payload(config, modelName, normalizedPrompt, imageUrls, videoUrls, audioUrls)
|
||||
: buildAgnesV25Payload(config, modelName, normalizedPrompt, imageUrls, videoUrls, audioUrls);
|
||||
? buildAgnesV20Payload(config, modelName, normalizedPrompt, imageUrls, imageRoles, videoUrls, audioUrls, options)
|
||||
: buildAgnesV25Payload(config, modelName, normalizedPrompt, imageUrls, imageRoles, videoUrls, audioUrls);
|
||||
|
||||
try {
|
||||
const raw = await deps.transport.post<ApiEnvelope<AgnesTaskResponse>>(deps.transport.apiUrl("/videos"), payload, options);
|
||||
@@ -92,7 +94,7 @@ export function agnesPollUrl(config: ResolvedAiConfig, videoId: string, modelNam
|
||||
return base.toString();
|
||||
}
|
||||
|
||||
function buildAgnesV25Payload(config: ResolvedAiConfig, model: string, prompt: string, images: string[], videos: string[], audios: string[]) {
|
||||
function buildAgnesV25Payload(config: ResolvedAiConfig, model: string, prompt: string, images: string[], imageRoles: VideoImageRole[], videos: string[], audios: string[]) {
|
||||
if (!AGNES_V25_MODELS.has(model)) throw new Error(`Agnes 视频协议不支持模型 ${model}`);
|
||||
const seconds = agnesSeconds(config.videoSeconds);
|
||||
const size = agnesResolution(config.vquality);
|
||||
@@ -101,7 +103,10 @@ function buildAgnesV25Payload(config: ResolvedAiConfig, model: string, prompt: s
|
||||
if (images.length > 5) throw new Error("Agnes Video 2.5 Flash 最多支持 5 张参考图");
|
||||
if (videos.length) throw new Error("Agnes Video 2.5 Flash 不支持参考视频");
|
||||
}
|
||||
const mode = videos.length || audios.length || images.length > 2 ? "reference" : images.length ? "keyframe" : "text";
|
||||
const hasReferenceImages = imageRoles.includes("reference_image");
|
||||
const hasFrameImages = imageRoles.includes("first_frame") || imageRoles.includes("last_frame");
|
||||
if (hasFrameImages && (hasReferenceImages || videos.length || audios.length)) throw new Error("Agnes Video 2.5 不能同时混用首尾帧和角色、视频或音频参考素材");
|
||||
const mode = videos.length || audios.length || hasReferenceImages ? "reference" : images.length ? "keyframe" : "text";
|
||||
const payload: Record<string, unknown> = {
|
||||
model,
|
||||
prompt,
|
||||
@@ -112,8 +117,10 @@ function buildAgnesV25Payload(config: ResolvedAiConfig, model: string, prompt: s
|
||||
n: 1,
|
||||
};
|
||||
if (mode === "keyframe") {
|
||||
payload.first_frame = images[0];
|
||||
if (images[1]) payload.last_frame = images[1];
|
||||
const firstFrame = imageForRole(images, imageRoles, "first_frame") || images[0];
|
||||
const lastFrame = imageForRole(images, imageRoles, "last_frame");
|
||||
payload.first_frame = firstFrame;
|
||||
if (lastFrame) payload.last_frame = lastFrame;
|
||||
} else if (mode === "reference") {
|
||||
if (images.length) payload.images = images;
|
||||
if (audios.length) payload.audios = audios;
|
||||
@@ -122,8 +129,9 @@ function buildAgnesV25Payload(config: ResolvedAiConfig, model: string, prompt: s
|
||||
return payload;
|
||||
}
|
||||
|
||||
function buildAgnesV20Payload(config: ResolvedAiConfig, model: string, prompt: string, images: string[], videos: string[], audios: string[]) {
|
||||
function buildAgnesV20Payload(config: ResolvedAiConfig, model: string, prompt: string, images: string[], imageRoles: VideoImageRole[], videos: string[], audios: string[], options?: RequestOptions) {
|
||||
if (videos.length || audios.length) throw new Error("Agnes Video V2.0 不支持参考视频或音频");
|
||||
if (options?.videoEditOperation === "reference_to_video" || imageRoles.includes("reference_image")) throw new Error("Agnes Video V2.0 不支持角色或风格参考图模式,请改用 Agnes Video 2.5");
|
||||
const seconds = Math.max(1, Math.floor(Number(config.videoSeconds) || 5));
|
||||
const frameRate = 24;
|
||||
const targetFrames = seconds * frameRate;
|
||||
@@ -133,11 +141,17 @@ function buildAgnesV20Payload(config: ResolvedAiConfig, model: string, prompt: s
|
||||
frame_rate: frameRate,
|
||||
num_frames: Math.min(441, Math.floor((targetFrames - 1 + 4) / 8) * 8 + 1),
|
||||
};
|
||||
if (images.length === 1) payload.image = images[0];
|
||||
if (images.length > 1) payload.extra_body = { image: images, mode: "keyframes" };
|
||||
const orderedImages = [imageForRole(images, imageRoles, "first_frame"), imageForRole(images, imageRoles, "last_frame")].filter((value): value is string => Boolean(value));
|
||||
if (orderedImages.length === 1) payload.image = orderedImages[0];
|
||||
if (orderedImages.length > 1) payload.extra_body = { image: orderedImages, mode: "keyframes" };
|
||||
return payload;
|
||||
}
|
||||
|
||||
function imageForRole(images: string[], roles: VideoImageRole[], role: VideoImageRole) {
|
||||
const index = roles.indexOf(role);
|
||||
return index >= 0 ? images[index] : undefined;
|
||||
}
|
||||
|
||||
function agnesSeconds(value: string) {
|
||||
const seconds = Math.floor(Number(value) || 5);
|
||||
if (seconds < 4 || seconds > 12) throw new Error("Agnes Video 2.5 时长必须在 4–12 秒之间");
|
||||
|
||||
Reference in new issue
Block a user