import React, { useState, useRef, useEffect } from 'react'; import { Play, Pause, Plus, Trash2, ChevronRight, ChevronLeft, Save, Download, Settings, Clock, Volume2, Edit3, Copy, Scissors, Music, Image, FileText, RotateCcw, RotateCw, CheckCircle, UploadCloud, Maximize, Layers, ZoomIn, ZoomOut } from 'lucide-react'; // Shadcn UI components import { Button } from '@/components/ui/button'; import { Slider } from '@/components/ui/slider'; import { Input } from '@/components/ui/input'; import { Tabs, TabsList, TabsTrigger } from '@/components/ui/tabs'; import { Card, CardContent } from '@/components/ui/card'; import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogFooter } from '@/components/ui/dialog'; import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger } from '@/components/ui/dropdown-menu'; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select'; import { ScrollArea } from '@/components/ui/scroll-area'; import { Textarea } from '@/components/ui/textarea'; import { Label } from '@/components/ui/label'; import { Switch } from '@/components/ui/switch'; import { Progress } from '@/components/ui/progress'; import { Badge } from '@/components/ui/badge'; const VideoEditor = () => { // Editor state const [projectName, setProjectName] = useState("My Video Project"); const [isPlaying, setIsPlaying] = useState(false); const [currentTime, setCurrentTime] = useState(0); const [duration, setDuration] = useState(60); // Total duration in seconds const [zoom, setZoom] = useState(1); const [selectedSceneIndex, setSelectedSceneIndex] = useState(0); const [showSceneEditor, setShowSceneEditor] = useState(false); const [notification, setNotification] = useState(null); const [showExportDialog, setShowExportDialog] = useState(false); const [exportProgress, setExportProgress] = useState(0); const [isExporting, setIsExporting] = useState(false); // References const timelineRef = useRef(null); const previewRef = useRef(null); // Scenes data structure const [scenes, setScenes] = useState([ { id: "scene-1", name: "Introduction", duration: 5, background: "linear-gradient(120deg, #1e3c72, #2a5298)", type: "title", content: { title: "Learning Spanish", subtitle: "Basic Vocabulary" } }, { id: "scene-2", name: "Word 1", duration: 8, background: "linear-gradient(120deg, #2a5298, #4776E6)", type: "word", content: { word: "médico", translation: "doctor", example: "Quiero ser médico" } }, { id: "scene-3", name: "Word 2", duration: 8, background: "linear-gradient(120deg, #4776E6, #8E54E9)", type: "word", content: { word: "abogado", translation: "lawyer", example: "Necesito un abogado" } }, { id: "scene-4", name: "Conclusion", duration: 5, background: "linear-gradient(120deg, #8E54E9, #1e3c72)", type: "title", content: { title: "Keep Practicing!", subtitle: "Learn a new word every day" } } ]); // Scene templates const sceneTemplates = [ { id: "template-title", name: "Title Scene", type: "title", thumbnail: "linear-gradient(120deg, #1e3c72, #2a5298)", duration: 5 }, { id: "template-word", name: "Vocabulary Word", type: "word", thumbnail: "linear-gradient(120deg, #4776E6, #8E54E9)", duration: 8 }, { id: "template-split", name: "Split Screen", type: "split", thumbnail: "linear-gradient(120deg, #11998e, #38ef7d)", duration: 6 }, { id: "template-blank", name: "Blank Scene", type: "blank", thumbnail: "linear-gradient(120deg, #333, #666)", duration: 5 } ]; // Get current scene const currentScene = scenes[selectedSceneIndex] || scenes[0]; // Update the total duration when scenes change useEffect(() => { const total = scenes.reduce((sum, scene) => sum + scene.duration, 0); setDuration(total); }, [scenes]); // When scene selection changes, update current time useEffect(() => { if (selectedSceneIndex >= 0 && selectedSceneIndex < scenes.length) { let startTime = 0; for (let i = 0; i < selectedSceneIndex; i++) { startTime += scenes[i].duration; } setCurrentTime(startTime); } }, [selectedSceneIndex]); // Animation playback useEffect(() => { let interval; if (isPlaying) { interval = setInterval(() => { setCurrentTime(time => { const newTime = time + 0.1; if (newTime >= duration) { setIsPlaying(false); return 0; } // Update selected scene based on current time let timeSum = 0; for (let i = 0; i < scenes.length; i++) { timeSum += scenes[i].duration; if (newTime < timeSum) { if (selectedSceneIndex !== i) { setSelectedSceneIndex(i); } break; } } return newTime; }); }, 100); } return () => clearInterval(interval); }, [isPlaying, duration, scenes, selectedSceneIndex]); // Handle timeline scrubbing const handleTimelineClick = (e) => { if (!timelineRef.current) return; const rect = timelineRef.current.getBoundingClientRect(); const clickPosition = e.clientX - rect.left; const percentClicked = clickPosition / rect.width; const newTime = percentClicked * duration; setCurrentTime(Math.max(0, Math.min(newTime, duration))); // Update selected scene let timeSum = 0; for (let i = 0; i < scenes.length; i++) { timeSum += scenes[i].duration; if (newTime < timeSum) { setSelectedSceneIndex(i); break; } } }; // Add a new scene const addScene = (templateId) => { const template = sceneTemplates.find(t => t.id === templateId); if (!template) return; const newScene = { id: `scene-${Date.now()}`, name: `New ${template.name}`, duration: template.duration, background: template.thumbnail, type: template.type, content: template.type === 'title' ? { title: "New Title", subtitle: "Add a subtitle" } : template.type === 'word' ? { word: "palabra", translation: "word", example: "Una palabra nueva" } : template.type === 'split' ? { leftText: "Left side", rightText: "Right side" } : {} }; // Insert after currently selected scene const newScenes = [...scenes]; newScenes.splice(selectedSceneIndex + 1, 0, newScene); setScenes(newScenes); setSelectedSceneIndex(selectedSceneIndex + 1); showNotification("Scene added"); }; // Delete scene const deleteScene = (index) => { if (scenes.length <= 1) { showNotification("Cannot delete the only scene", "error"); return; } const newScenes = [...scenes]; newScenes.splice(index, 1); setScenes(newScenes); if (selectedSceneIndex >= newScenes.length) { setSelectedSceneIndex(newScenes.length - 1); } showNotification("Scene deleted"); }; // Duplicate scene const duplicateScene = (index) => { const sceneToClone = scenes[index]; const clonedScene = { ...sceneToClone, id: `scene-${Date.now()}`, name: `${sceneToClone.name} (Copy)` }; const newScenes = [...scenes]; newScenes.splice(index + 1, 0, clonedScene); setScenes(newScenes); setSelectedSceneIndex(index + 1); showNotification("Scene duplicated"); }; // Update scene const updateScene = (sceneData) => { const newScenes = [...scenes]; newScenes[selectedSceneIndex] = { ...newScenes[selectedSceneIndex], ...sceneData }; setScenes(newScenes); setShowSceneEditor(false); showNotification("Scene updated"); }; // Show notification const showNotification = (message, type = "success") => { setNotification({ message, type }); setTimeout(() => setNotification(null), 3000); }; // Export video const exportVideo = (settings) => { setIsExporting(true); setExportProgress(0); // Simulate export process const interval = setInterval(() => { setExportProgress(prev => { const newValue = prev + 5; if (newValue >= 100) { clearInterval(interval); setTimeout(() => { setIsExporting(false); setShowExportDialog(false); showNotification("Video exported successfully!"); }, 500); return 100; } return newValue; }); }, 200); }; // Get time at scene start const getSceneStartTime = (index) => { let startTime = 0; for (let i = 0; i < index; i++) { startTime += scenes[i].duration; } return startTime; }; // Format time as MM:SS const formatTime = (seconds) => { const mins = Math.floor(seconds / 60); const secs = Math.floor(seconds % 60); return `${mins.toString().padStart(2, '0')}:${secs.toString().padStart(2, '0')}`; }; // Jump to previous scene const goToPreviousScene = () => { if (selectedSceneIndex > 0) { setSelectedSceneIndex(selectedSceneIndex - 1); } }; // Jump to next scene const goToNextScene = () => { if (selectedSceneIndex < scenes.length - 1) { setSelectedSceneIndex(selectedSceneIndex + 1); } }; // Render scene preview based on type const renderScenePreview = (scene) => { switch (scene.type) { case 'title': return (

{scene.content.title}

{scene.content.subtitle}

); case 'word': return (

New Word

{scene.content.word}

{scene.content.translation}

"{scene.content.example}"
); case 'split': return (

{scene.content.leftText}

{scene.content.rightText}

); default: return (

Empty Scene

); } }; return (
{/* Top toolbar */}
New Project Open Project Save Project setProjectName(e.target.value)} />
{/* Main Content Area */}
{/* Left panel - Templates */}

Scene Templates

{sceneTemplates.map(template => ( addScene(template.id)} >
{template.name}
{template.duration}s
))}
{/* Middle Panel - Preview */}
{/* Scene Preview */}
{renderScenePreview(currentScene)} {/* Scene info overlay */}
{currentScene.name} ({formatTime(getSceneStartTime(selectedSceneIndex))} - {formatTime(getSceneStartTime(selectedSceneIndex) + currentScene.duration)})
{/* Playback Controls */}
{formatTime(currentTime)} / {formatTime(duration)}
setCurrentTime(val[0])} className="mt-0.5" />
{Math.round(zoom * 100)}%
{/* Timeline */}
{/* Time markers */}
{Array.from({ length: Math.ceil(duration) + 1 }).map((_, i) => (
{formatTime(i)}
))}
{/* Scene blocks */}
{scenes.map((scene, index) => { const startPercent = (getSceneStartTime(index) / duration) * 100; const widthPercent = (scene.duration / duration) * 100; return (
{ e.stopPropagation(); setSelectedSceneIndex(index); }} >
{scene.name}
{scene.duration}s
); })}
{/* Playhead */}
{/* Scene Editor Dialog */} Edit Scene {currentScene && (
updateScene({ name: e.target.value })} className="bg-gray-700 border-gray-600 text-white" />
updateScene({ duration: parseFloat(e.target.value) })} className="bg-gray-700 border-gray-600 text-white" min="1" step="0.5" />
{currentScene.type === 'title' && ( <>
updateScene({ content: { ...currentScene.content, title: e.target.value } })} className="bg-gray-700 border-gray-600 text-white" />
updateScene({ content: { ...currentScene.content, subtitle: e.target.value } })} className="bg-gray-700 border-gray-600 text-white" />
)} {currentScene.type === 'word' && ( <>
updateScene({ content: { ...currentScene.content, word: e.target.value } })} className="bg-gray-700 border-gray-600 text-white" />
updateScene({ content: { ...currentScene.content, translation: e.target.value } })} className="bg-gray-700 border-gray-600 text-white" />
updateScene({ content: { ...currentScene.content, example: e.target.value } })} className="bg-gray-700 border-gray-600 text-white" />
)} {currentScene.type === 'split' && ( <>