import "server-only"; import type { MappedStoryboardShot, StoryboardTile, NormalizedStoryboardScript, } from "@/lib/video-agent/types"; function clampIndex(value: number, length: number) { if (length <= 0) return 0; const rounded = Math.max(1, Math.round(value)); return Math.min(length, rounded) - 1; } function buildShotPrompt(params: { style: string; action: string; camera: string; mood: string; language: "zh" | "en" | "mixed"; }) { const { style, action, camera, mood, language } = params; if (language === "en") { return [ "Animate this still image into a coherent short scene.", `Action: ${action}.`, `Camera: ${camera}.`, `Mood: ${mood}.`, `Style: ${style}.`, "Keep identity and key structure stable.", ].join(" "); } return [ "请基于当前输入图片生成连贯的视频镜头。", `动作:${action}。`, `镜头:${camera}。`, `氛围:${mood}。`, `风格:${style}。`, "保持主体身份、构图和关键细节稳定。", ].join(""); } export function runStoryboardMapperSkill(params: { script: NormalizedStoryboardScript; tiles: StoryboardTile[]; }): MappedStoryboardShot[] { const { script, tiles } = params; if (!tiles.length) return []; const usedTileIndexes = new Set(); const nextAvailableTile = (startIndex = 1) => { for (let index = Math.max(1, startIndex); index <= tiles.length; index += 1) { if (!usedTileIndexes.has(index)) return index; } for (let index = 1; index <= tiles.length; index += 1) { if (!usedTileIndexes.has(index)) return index; } return Math.max(1, Math.min(tiles.length, startIndex)); }; return script.shots.map((shot, index) => { const preferredTileIndex = shot.preferredTileIndex && shot.preferredTileIndex >= 1 && shot.preferredTileIndex <= tiles.length ? shot.preferredTileIndex : shot.index || index + 1; const pickedTileIndex = usedTileIndexes.has(preferredTileIndex) ? nextAvailableTile(index + 1) : preferredTileIndex; usedTileIndexes.add(pickedTileIndex); const tile = tiles[clampIndex(pickedTileIndex, tiles.length)] || tiles[index] || tiles[0]; const prompt = buildShotPrompt({ style: script.globalStyle, action: shot.action, camera: shot.camera, mood: shot.mood, language: script.language, }); return { shotIndex: shot.index, tileIndex: tile.index, tileImageUrl: tile.imageUrl, durationSec: shot.durationSec, prompt, negativePrompt: shot.negativePrompt, action: shot.action, camera: shot.camera, mood: shot.mood, }; }); }