// utils/timeline-template-processor.js export const generateTimelineFromTemplate = (dimensions, template, mediaStoreData) => { const { selectedMeme, selectedBackground, currentCaption } = mediaStoreData; // If no selections, return empty timeline if (!selectedMeme && !selectedBackground) { return []; } // Calculate duration based on template config let maxDuration = 5; // default fallback if (template.max_duration_based_on === 'memes' && selectedMeme?.duration) { maxDuration = parseFloat(selectedMeme.duration); } // Process each timeline element const processedTimeline = template.timeline .map((element) => { let processedElement = { ...element }; // Update duration for all elements processedElement.duration = maxDuration; // Process by element ID/type switch (element.id) { case 'background': if (selectedBackground) { processedElement.source = selectedBackground.media_url; processedElement.name = selectedBackground.prompt || 'Background'; processedElement.media_width = selectedBackground.media_width; processedElement.media_height = selectedBackground.media_height; const canvasWidth = dimensions.width; const canvasHeight = dimensions.height; // Calculate scale factors to fit media within canvas while maintaining aspect ratio const scaleX = canvasWidth / selectedBackground.media_width; const scaleY = canvasHeight / selectedBackground.media_height; // Use the larger scale factor to ensure the media fills the entire canvas const scale = Math.max(scaleX, scaleY); // Calculate final dimensions const scaledWidth = selectedBackground.media_width * scale; const scaledHeight = selectedBackground.media_height * scale; // Center the scaled media within the canvas const offsetX = (canvasWidth - scaledWidth) / 2; const offsetY = (canvasHeight - scaledHeight) / 2; // Set the processed element properties processedElement.width = scaledWidth; processedElement.height = scaledHeight; processedElement.x = offsetX; processedElement.y = offsetY; } else { return null; // Skip if no background selected } break; case 'meme': if (selectedMeme) { processedElement.source_webm = selectedMeme.webm_url; processedElement.source_mov = selectedMeme.mov_url; processedElement.poster = selectedMeme.webp_url; processedElement.name = selectedMeme.name; processedElement.media_width = selectedMeme.media_width; processedElement.media_height = selectedMeme.media_height; processedElement.width = selectedMeme.media_width; processedElement.height = selectedMeme.media_height; } else { return null; // Skip if no meme selected } break; case 'caption': if (currentCaption) { processedElement.text = currentCaption; // Calculate text width properties for better rendering consistency const textWidth = Math.min(dimensions.width * 0.8, 600); // Max 80% of canvas width or 600px processedElement.fixedWidth = textWidth; processedElement.offsetX = textWidth / 2; // Center alignment offset // Ensure text is positioned properly (center horizontally) if (!processedElement.x || processedElement.x === 0) { processedElement.x = dimensions.width / 2; // Center horizontally } // Ensure text has proper vertical positioning if (!processedElement.y || processedElement.y === 0) { processedElement.y = dimensions.height * 0.1; // 10% from top } } else { return null; // Skip if no caption } break; default: // Keep element as-is for any other types break; } return processedElement; }) .filter(Boolean); // Remove null elements return processedTimeline; };