const normalizeText = (value: unknown) => typeof value === "string" || typeof value === "number" ? String(value).trim() : ""; export function normalizeVideoAnalysisRows( value: unknown, framePreviews: string[] = [], nodeId = "video-story", ) { if (!Array.isArray(value)) return []; return value .map((item, index) => { const raw = item && typeof item === "object" && !Array.isArray(item) ? (item as Record) : {}; const sceneDescription = normalizeText( raw.sceneDescription || raw.scene || raw.title || raw.description, ); const narrative = normalizeText( raw.narrative || raw.story || raw.storyContent || raw.narration || raw.summary, ); const shot = normalizeText(raw.shot || raw.shotNumber || raw.index || index + 1); const startTime = normalizeText(raw.startTime ?? raw.start ?? raw.start_time); const endTime = normalizeText(raw.endTime ?? raw.end ?? raw.end_time); const duration = normalizeText(raw.duration ?? raw.durationSeconds ?? raw.duration_seconds); const shotSize = normalizeText( raw.shotSize || raw.frameSize || raw.shot_type || raw.sceneScale, ); const cameraAngle = normalizeText(raw.cameraAngle || raw.angle || raw.camera_angle); const cameraMovement = normalizeText( raw.cameraMovement || raw.cameraMove || raw.camera_motion || raw.motionType, ); const focusDepth = normalizeText( raw.focusDepth || raw.depthOfField || raw.focalDepth || raw.focus_depth, ); const lighting = normalizeText(raw.lighting || raw.light || raw.lightDesign); const backgroundMusic = normalizeText(raw.backgroundMusic || raw.music || raw.bgm); const voiceAndSfx = normalizeText(raw.voiceAndSfx || raw.sound || raw.audio || raw.sfx); const imagePrompt = normalizeText( raw.imagePrompt || raw.image_generation_prompt || raw.image, ); const motionPrompt = normalizeText( raw.motionPrompt || raw.motion || raw.videoPrompt || raw.video_motion_prompt, ); const keyframeIndex = Number( raw.keyframeIndex || raw.frameIndex || raw.key_frame_index || index + 1, ); if (!sceneDescription && !narrative && !imagePrompt) return null; const normalizedFrameIndex = Number.isFinite(keyframeIndex) && keyframeIndex > 0 ? Math.max(0, Math.min(framePreviews.length - 1, Math.round(keyframeIndex) - 1)) : index % Math.max(framePreviews.length, 1); return { id: normalizeText(raw.id) || `${nodeId}-analysis-${index}`, shot: shot || String(index + 1), startTime, endTime, duration, sceneDescription, narrative, shotSize, cameraAngle, cameraMovement, focusDepth, lighting, backgroundMusic, voiceAndSfx, imagePrompt, motionPrompt, keyframeUrl: normalizeText(raw.keyframeUrl || raw.previewUrl || raw.imageUrl) || framePreviews[normalizedFrameIndex] || "", }; }) .filter((item): item is NonNullable => Boolean(item)) .slice(0, 120); }