Files
NianAIGC/lib/video-input.ts
T
2026-10-02 19:56:32 +08:00

89 lines
5.4 KiB
TypeScript

import { SEEDANCE_25_MODEL } from "@/lib/video-settings";
export type VideoInputEngine = "seedance" | "minimax" | "bailian";
export type VideoInputMode = "reference" | "frames";
export type VideoMaterialKind = "image" | "video" | "audio";
export const BAILIAN_27_VIDEO_MODEL = "wan2.7-i2v-2026-04-25";
export const BAILIAN_30_VIDEO_MODEL = "wan3.0-video";
export const BAILIAN_VIDEO_MODELS = [
{ id: BAILIAN_27_VIDEO_MODEL, label: "Wan 2.7 · 首尾帧" },
{ id: BAILIAN_30_VIDEO_MODEL, label: "Wan 3.0 · 多图参考" }
] as const;
export type VideoInputSelection = {
engine: VideoInputEngine;
model: string;
inputMode: VideoInputMode;
};
export function videoInputHint(selection: VideoInputSelection): string {
if (selection.engine === "seedance") {
return selection.model === SEEDANCE_25_MODEL
? "多素材参考:最多 30 张图片、10 段视频、10 段音频,总计 50 个。可在提示词中用 @图片1 等编号指定用途。"
: "多素材参考:最多 9 张图片、3 段视频、3 段音频;音频需要搭配图片或视频。可用 @图片1 等编号指定用途。";
}
if (selection.engine === "minimax") {
return selection.inputMode === "frames"
? "首帧模式:可不上传图片,或上传 1 张图片固定视频开头;有首帧时比例跟随图片。若想用多张图共同指导内容,请切换到「多图参考」。"
: "多图参考:最多 9 张图片。图片用于指导人物、服装或场景,不会被固定为视频首帧;可在提示词中引用 @图片1 等编号。";
}
if (selection.model === BAILIAN_27_VIDEO_MODEL) {
return "Wan 2.7 首尾帧:上传 1 张图片作为首帧,或按顺序上传 2 张作为首帧和尾帧。多图参考请切换到 Wan 3.0。";
}
return selection.inputMode === "frames"
? "首尾帧:上传 1 张首帧图,或按顺序上传 2 张首尾帧图。若想让更多图片共同指导内容,请切换到「多图参考」。"
: "多图参考:可只输入文字,或上传最多 10 张图片指导人物、服装和场景;参考图不会固定为首尾帧,可在提示词中引用 @图片1 等编号。";
}
export function videoMaterialError(selection: VideoInputSelection, materials: readonly { type: VideoMaterialKind }[]): string | null {
const imageCount = materials.filter((item) => item.type === "image").length;
const videoCount = materials.filter((item) => item.type === "video").length;
const audioCount = materials.filter((item) => item.type === "audio").length;
if (selection.engine === "seedance") {
const is25 = selection.model === SEEDANCE_25_MODEL;
const maxImages = is25 ? 30 : 9;
const maxVideo = is25 ? 10 : 3;
const maxAudio = is25 ? 10 : 3;
if (imageCount > maxImages || videoCount > maxVideo || audioCount > maxAudio || is25 && materials.length > 50) {
return `${is25 ? "Seedance 2.5" : "Seedance 2.0"} 最多支持 ${maxImages} 张图片、${maxVideo} 段视频、${maxAudio} 段音频${is25 ? ",总计 50 个素材" : ""};请移除超出的素材。`;
}
if (!is25 && audioCount > 0 && imageCount + videoCount === 0) {
return "Seedance 2.0 的音频不能单独生成视频;请添加至少 1 张图片或 1 段视频,或切换到 Seedance 2.5。";
}
return null;
}
if (selection.engine === "minimax") {
if (videoCount || audioCount) return "MiniMax H3 当前只接受图片素材;请移除视频和音频,或切换到 Seedance。";
if (selection.inputMode === "frames" && imageCount > 1) {
return "MiniMax H3 首帧模式最多只能上传 1 张图;请切换到「多图参考」或移除多余图片。";
}
if (selection.inputMode === "reference" && imageCount > 9) {
return "MiniMax H3 多图参考最多支持 9 张图片;请移除多余图片。";
}
return null;
}
if (videoCount || audioCount) return "百炼当前只接受图片素材;请移除视频和音频,或切换到 Seedance。";
if (selection.model === BAILIAN_27_VIDEO_MODEL || selection.inputMode === "frames") {
if (!imageCount) return "首尾帧模式需要至少 1 张图片作为首帧;请上传图片,或切换到 Wan 3.0 多图参考。";
if (imageCount > 2) return "首尾帧模式最多支持 2 张图片;请切换到 Wan 3.0 多图参考,或移除多余图片。";
return null;
}
if (imageCount > 10) return "Wan 3.0 多图参考最多支持 10 张图片;请移除多余图片。";
return null;
}
export function videoImageFileError(selection: VideoInputSelection, file: Pick<File, "name" | "size" | "type">): string | null {
if (selection.engine === "seedance" || selection.engine === "bailian" && selection.model === BAILIAN_27_VIDEO_MODEL) return null;
const format = file.name.split(".").pop()?.toLowerCase() || file.type.split("/").pop()?.toLowerCase() || "";
const isMinimax = selection.engine === "minimax";
const formats = isMinimax ? ["jpg", "jpeg", "png", "webp", "heic", "heif"] : ["jpg", "jpeg", "png", "bmp", "webp"];
const maxBytes = (isMinimax ? 30 : 20) * 1024 * 1024;
const modelLabel = isMinimax ? "MiniMax H3" : "Wan 3.0";
if (!formats.includes(format)) {
return `${modelLabel} 不支持「${file.name}」的格式;请转换为 ${isMinimax ? "JPG、PNG、WEBP、HEIC 或 HEIF" : "JPG、PNG、BMP 或 WEBP"} 后上传。`;
}
if (file.size > maxBytes) return `${modelLabel} 的单张图片不能超过 ${isMinimax ? 30 : 20} MB;请压缩「${file.name}」后重试。`;
return null;
}