From d36bd4b8bf07ac875f1e02c004818a3d7ff42631 Mon Sep 17 00:00:00 2001 From: ct Date: Tue, 17 Jun 2025 22:58:51 +0800 Subject: [PATCH] Update --- .../editor/partials/canvas/video-editor.jsx | 58 +++++++++++++---- .../single_caption_meme_background.json | 16 ++--- .../utils/timeline-template-processor.js | 65 +++++++++++++++++++ 3 files changed, 118 insertions(+), 21 deletions(-) create mode 100644 resources/js/modules/editor/utils/timeline-template-processor.js diff --git a/resources/js/modules/editor/partials/canvas/video-editor.jsx b/resources/js/modules/editor/partials/canvas/video-editor.jsx index 78a9955..0e2ca85 100644 --- a/resources/js/modules/editor/partials/canvas/video-editor.jsx +++ b/resources/js/modules/editor/partials/canvas/video-editor.jsx @@ -1,7 +1,9 @@ import { useMitt } from '@/plugins/MittContext'; +import useMediaStore from '@/stores/MediaStore'; import useVideoEditorStore from '@/stores/VideoEditorStore'; import { useCallback, useEffect, useRef, useState } from 'react'; -import sampleTimelineElements from './sample-timeline-data'; +import SINGLE_CAPTION_TEMPLATE from '../../templates/single_caption_meme_background.json'; +import { generateTimelineFromTemplate } from '../../utils/timeline-template-processor'; import useVideoExport from './video-export'; import VideoPreview from './video-preview'; @@ -32,6 +34,7 @@ const VideoEditor = ({ width, height }) => { const pausedTimeRef = useRef(0); const { setVideoIsPlaying } = useVideoEditorStore(); + const { selectedMeme, selectedBackground, currentCaption } = useMediaStore(); const FPS_INTERVAL = 1000 / 30; // 30 FPS @@ -40,11 +43,18 @@ const VideoEditor = ({ width, height }) => { timelineElementsRef.current = timelineElements; }, [timelineElements]); - // Initialize timeline + // Initialize timeline on mount useEffect(() => { initTimeline(); }, []); + // Watch MediaStore changes and regenerate timeline + useEffect(() => { + if (selectedMeme || selectedBackground) { + generateAndSetTimeline(); + } + }, [selectedMeme, selectedBackground, currentCaption]); + const timelineUpdateResolverRef = useRef(null); const setTimelineElementsAsync = useCallback((newElements) => { @@ -61,13 +71,34 @@ const VideoEditor = ({ width, height }) => { } }, [timelineElements]); + const generateAndSetTimeline = () => { + const mediaStoreData = { + selectedMeme, + selectedBackground, + currentCaption: currentCaption || 'Default caption text', + }; + + const generatedTimeline = generateTimelineFromTemplate(SINGLE_CAPTION_TEMPLATE, mediaStoreData); + + if (generatedTimeline.length > 0) { + cleanupVideos(videoElements); + setTimelineElementsAsync(generatedTimeline).then(() => { + showConsoleLogs && console.log('Generated timeline from template:', generatedTimeline); + setupVideos(); + setupImages(); + }); + } + }; + const initTimeline = () => { - cleanupVideos(videoElements); - setTimelineElementsAsync(sampleTimelineElements).then(() => { - showConsoleLogs && console.log('Loaded sample timeline'); - setupVideos(); - setupImages(); - }); + // Try to generate from current MediaStore state first + if (selectedMeme || selectedBackground) { + generateAndSetTimeline(); + } else { + // Set empty timeline and wait for media selections + setTimelineElements([]); + setStatus('Select meme and background to start editing'); + } }; // Handle element transformations (position, scale, rotation) and text properties @@ -134,8 +165,8 @@ const VideoEditor = ({ width, height }) => { scaledHeight = imgHeight * scale; } - const centeredX = element.x || (maxWidth - scaledWidth) / 2; - const centeredY = element.y || (maxHeight - scaledHeight) / 2; + const centeredX = element.x !== undefined ? element.x : (maxWidth - scaledWidth) / 2; + const centeredY = element.y !== undefined ? element.y : (maxHeight - scaledHeight) / 2; setTimelineElements((prev) => prev.map((el) => { @@ -247,8 +278,8 @@ const VideoEditor = ({ width, height }) => { scaledHeight = posterHeight * scale; } - const centeredX = (maxWidth - scaledWidth) / 2; - const centeredY = (maxHeight - scaledHeight) / 2; + const centeredX = element.x !== undefined ? element.x : (maxWidth - scaledWidth) / 2; + const centeredY = element.y !== undefined ? element.y : (maxHeight - scaledHeight) / 2; setTimelineElements((prev) => prev.map((el) => { @@ -331,10 +362,11 @@ const VideoEditor = ({ width, height }) => { } else if (mediaCount > 0) { setStatus(`Loading media... (${loadedVideos.size}/${mediaCount})`); } else { - setStatus('Ready to play'); + setStatus('Select meme and background to start editing'); } }; + // Rest of the component remains the same... const handlePause = useCallback(() => { if (isPlaying) { setIsPlaying(false); diff --git a/resources/js/modules/editor/templates/single_caption_meme_background.json b/resources/js/modules/editor/templates/single_caption_meme_background.json index 1296c5b..999aad5 100644 --- a/resources/js/modules/editor/templates/single_caption_meme_background.json +++ b/resources/js/modules/editor/templates/single_caption_meme_background.json @@ -17,10 +17,10 @@ "layer": 1, "inPoint": 0, "duration": 6, - "x": 200, - "y": 200, - "width": 280, - "height": 180, + "x": 0, + "y": 0, + "width": 720, + "height": 1280, "rotation": 0 }, { @@ -34,10 +34,10 @@ "layer": 2, "inPoint": 0, "duration": 6, - "x": 200, - "y": 200, - "width": 280, - "height": 180, + "x": 0, + "y": 0, + "width": 720, + "height": 1280, "rotation": 0 }, { diff --git a/resources/js/modules/editor/utils/timeline-template-processor.js b/resources/js/modules/editor/utils/timeline-template-processor.js new file mode 100644 index 0000000..a922771 --- /dev/null +++ b/resources/js/modules/editor/utils/timeline-template-processor.js @@ -0,0 +1,65 @@ +// utils/timeline-template-processor.js + +export const generateTimelineFromTemplate = (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'; + } 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; + } else { + return null; // Skip if no meme selected + } + break; + + case 'caption': + if (currentCaption) { + processedElement.text = currentCaption; + } 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; +};