111 lines
3.6 KiB
TypeScript
111 lines
3.6 KiB
TypeScript
import { NextResponse } from "next/server";
|
|
import { auth } from "@/app/(auth)/auth";
|
|
import { executeDirectVideoGeneration } from "@/lib/ai/tools/generate-video";
|
|
import { probeRemoteUrl } from "@/lib/net/url-access";
|
|
import { validateVideoReferences } from "@/lib/video-canvas/reference-input";
|
|
import type { VideoGenerationMode, VideoRatio, VideoResolution } from "@/types/ai";
|
|
|
|
export const maxDuration = 300;
|
|
|
|
interface GenerateVideoRequest {
|
|
prompt?: string;
|
|
mode?: VideoGenerationMode;
|
|
images?: string[];
|
|
audioUrls?: string[];
|
|
generateAudio?: boolean;
|
|
resolution?: VideoResolution;
|
|
ratio?: VideoRatio;
|
|
duration?: number;
|
|
watermark?: boolean;
|
|
cameraFixed?: boolean;
|
|
seed?: number;
|
|
returnLastFrame?: boolean;
|
|
preferredModelRef?: string;
|
|
}
|
|
|
|
function inferMode(images: string[]): VideoGenerationMode {
|
|
if (images.length >= 3) return "reference-images";
|
|
if (images.length >= 2) return "first-last-frame";
|
|
if (images.length === 1) return "first-frame";
|
|
return "text-to-video";
|
|
}
|
|
|
|
export async function POST(request: Request) {
|
|
try {
|
|
const session = await auth();
|
|
if (!session?.user?.id) {
|
|
return NextResponse.json(
|
|
{ success: false, error: "未登录" },
|
|
{ status: 401 }
|
|
);
|
|
}
|
|
|
|
const body = (await request.json().catch(() => ({}))) as GenerateVideoRequest;
|
|
const prompt = String(body.prompt || "").trim();
|
|
if (!prompt) {
|
|
return NextResponse.json(
|
|
{ success: false, error: "请输入视频提示词" },
|
|
{ status: 400 }
|
|
);
|
|
}
|
|
|
|
const referenceCheck = await validateVideoReferences(body.images, url => probeRemoteUrl(url, { timeoutMs: 8000 }));
|
|
if (referenceCheck.error) {
|
|
return NextResponse.json({ success: false, error: referenceCheck.error }, { status: referenceCheck.status });
|
|
}
|
|
const reachableImages = referenceCheck.images;
|
|
|
|
const mode = body.mode || inferMode(reachableImages);
|
|
const result = await executeDirectVideoGeneration({
|
|
prompt,
|
|
mode,
|
|
audioUrls: Array.isArray(body.audioUrls) ? body.audioUrls.filter(url => typeof url === "string" && /^https?:\/\//i.test(url)).slice(0, 3) : undefined,
|
|
generateAudio: body.generateAudio,
|
|
images: reachableImages,
|
|
resolution: body.resolution,
|
|
ratio: body.ratio,
|
|
duration:
|
|
typeof body.duration === "number" && Number.isFinite(body.duration)
|
|
? body.duration
|
|
: undefined,
|
|
watermark: body.watermark,
|
|
cameraFixed: body.cameraFixed,
|
|
seed:
|
|
typeof body.seed === "number" && Number.isFinite(body.seed) ? body.seed : undefined,
|
|
returnLastFrame: body.returnLastFrame,
|
|
preferredModelRef:
|
|
typeof body.preferredModelRef === "string" ? body.preferredModelRef : undefined,
|
|
});
|
|
|
|
if (!result.success || !result.videoUrl) {
|
|
return NextResponse.json(
|
|
{ success: false, error: "视频生成失败,请稍后重试" },
|
|
{ status: 502 }
|
|
);
|
|
}
|
|
|
|
return NextResponse.json({
|
|
success: true,
|
|
data: {
|
|
url: result.videoUrl,
|
|
metadata: { ...result.metadata, temporaryVideoUrl: result.videoUrl, storageStatus: result.metadata?.permanentVideoUrl === result.videoUrl ? "archived" : "temporary" },
|
|
modelDisplayName: result.modelDisplayName,
|
|
modelId: result.modelId,
|
|
modelRef: result.modelRef,
|
|
mode,
|
|
images: reachableImages,
|
|
droppedImages: [],
|
|
},
|
|
});
|
|
} catch (error) {
|
|
console.error("[Canvas Generate Video] Error:", error);
|
|
return NextResponse.json(
|
|
{
|
|
success: false,
|
|
error: error instanceof Error ? error.message : "生成视频失败",
|
|
},
|
|
{ status: 500 }
|
|
);
|
|
}
|
|
}
|