const normalizeText = (value: unknown) => String(value || "").trim(); function normalizeStoryShot(value: unknown, index: number) { const text = normalizeText(value); if (/^\d+$/.test(text)) return text.padStart(2, "0"); return text || String(index + 1).padStart(2, "0"); } type NormalizedStoryScriptRow = { shot: string; duration: string; scene: string; sceneDescription: string; shotSize: string; lighting: string; dialogue: string; backgroundMusic: string; voiceAndSfx: string; cameraMovement: string; imagePrompt: string; motionPrompt: string; keyframeUrl: string; }; export function normalizeStoryScriptRows(value: unknown): NormalizedStoryScriptRow[] { if (!Array.isArray(value)) return []; return value .map((item, index) => { if (Array.isArray(item)) { const cells = item.map((cell) => normalizeText(cell)); if (!cells.some(Boolean)) return null; const shot = normalizeStoryShot(cells[0], index); const secondCellLooksLikeDuration = /^\d+(\.\d+)?\s*(s|秒)?$/i.test(cells[1] || ""); if (!(secondCellLooksLikeDuration ? cells.slice(2) : cells.slice(1)).some(Boolean)) return null; const duration = secondCellLooksLikeDuration ? cells[1] : "5s"; const scene = secondCellLooksLikeDuration ? cells[2] || `镜头 ${shot}` : cells[1] || `镜头 ${shot}`; const sceneDescription = secondCellLooksLikeDuration ? cells[2] || scene : cells[2] || cells[3] || scene; return { shot, duration, scene, sceneDescription, shotSize: (secondCellLooksLikeDuration ? cells[3] : "") || "中景", lighting: (secondCellLooksLikeDuration ? cells[4] : cells[4]) || "电影感光影", dialogue: (secondCellLooksLikeDuration ? cells[5] : "") || "+", backgroundMusic: "无", voiceAndSfx: (secondCellLooksLikeDuration ? cells[6] : cells[3]) || "无", cameraMovement: (secondCellLooksLikeDuration ? cells[7] : "") || "缓慢推镜头", imagePrompt: (secondCellLooksLikeDuration ? cells[8] : cells[5]) || sceneDescription, motionPrompt: (secondCellLooksLikeDuration ? cells[9] || cells[7] : cells[6]) || "保持镜头运动自然、连贯,围绕主体动作推进。", keyframeUrl: "", }; } const raw = item && typeof item === "object" && !Array.isArray(item) ? (item as Record) : {}; const shot = normalizeStoryShot(raw.shot || raw.shotNumber || raw.index || raw.id, index); const duration = normalizeText(raw.duration || raw.length || raw.time || raw.seconds); const scene = normalizeText(raw.scene || raw.location || raw.title); const sceneDescription = normalizeText( raw.sceneDescription || raw.description || raw.visual || raw.picture || raw.screen || raw.summary, ); const shotSize = normalizeText( raw.shotSize || raw.frameSize || raw.shot_type || raw.sceneScale || raw.cameraShot, ); const lighting = normalizeText( raw.lighting || raw.light || raw.atmosphere || raw.mood || raw.lightMood, ); const dialogue = normalizeText( raw.dialogue || raw.dialog || raw.narration || raw.voiceover || raw.monologue || raw.line, ); const cameraMovement = normalizeText( raw.cameraMovement || raw.cameraMove || raw.movement || raw.lensMovement || raw.motion, ); const imagePrompt = normalizeText( raw.imagePrompt || raw.image_generation_prompt || raw.image || raw.keyframePrompt || raw.keyFramePrompt, ); const motionPrompt = normalizeText( raw.motionPrompt || raw.videoPrompt || raw.video_motion_prompt || raw.motion || raw.cameraMovement, ); const backgroundMusic = normalizeText( raw.backgroundMusic || raw.music || raw.bgm || raw.soundtrack, ); const voiceAndSfx = normalizeText( raw.voiceAndSfx || raw.voice || raw.sound || raw.audio || raw.sfx, ); if ( !scene && !sceneDescription && !imagePrompt && !motionPrompt && !backgroundMusic && !voiceAndSfx ) { return null; } return { shot, duration: duration || "5s", scene, sceneDescription: sceneDescription || imagePrompt || scene || `镜头 ${shot}`, shotSize: shotSize || "中景", lighting: lighting || "电影感光影", dialogue: dialogue || "+", backgroundMusic: backgroundMusic || "无", voiceAndSfx: voiceAndSfx || "无", cameraMovement: cameraMovement || "缓慢推镜头", imagePrompt: imagePrompt || sceneDescription || scene || `镜头 ${shot}关键帧`, motionPrompt: motionPrompt || cameraMovement || "保持镜头运动自然、连贯,围绕主体动作推进。", keyframeUrl: "", }; }) .filter( ( item, ): item is NormalizedStoryScriptRow => Boolean(item), ) .slice(0, 18); }