import React, { useState, useEffect, useRef } from 'react'; import { Camera, Plus, Trash2, Edit3, Download, Play, Pause, ChevronDown, Settings, Save, X, FileText, Upload, RefreshCw, Globe, Volume2, Image, Clock, Copy, FileVideo, Check, AlertTriangle, Zap, Layers, UploadCloud, Grid, CheckCircle, Move, ArrowLeft, ArrowRight, SkipBack, SkipForward, Music, Mic, Eye, EyeOff, Maximize, Minimize, Monitor, Wind } from 'lucide-react'; import { motion, AnimatePresence } from 'framer-motion'; import * as d3 from 'd3'; // Mock imports for shadcn UI components import { Alert, AlertDescription, AlertTitle } from '@/components/ui/alert'; import { Button } from '@/components/ui/button'; import { Input } from '@/components/ui/input'; import { Label } from '@/components/ui/label'; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select'; import { Slider } from '@/components/ui/slider'; import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs'; import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from '@/components/ui/tooltip'; import { Badge } from '@/components/ui/badge'; import { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle, DialogTrigger, DialogFooter } from '@/components/ui/dialog'; import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle } from '@/components/ui/card'; import { Switch } from '@/components/ui/switch'; import { Progress } from '@/components/ui/progress'; import { RadioGroup, RadioGroupItem } from '@/components/ui/radio-group'; import { Checkbox } from '@/components/ui/checkbox'; import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuLabel, DropdownMenuSeparator, DropdownMenuTrigger } from '@/components/ui/dropdown-menu'; import { Popover, PopoverContent, PopoverTrigger } from '@/components/ui/popover'; import { ScrollArea } from '@/components/ui/scroll-area'; import { Sheet, SheetContent, SheetDescription, SheetHeader, SheetTitle, SheetTrigger } from '@/components/ui/sheet'; import { Textarea } from '@/components/ui/textarea'; // Animation presets for elements const animationPresets = { fadeIn: { initial: { opacity: 0 }, animate: { opacity: 1 }, exit: { opacity: 0 }, transition: { duration: 0.5 } }, slideIn: { initial: { x: -100, opacity: 0 }, animate: { x: 0, opacity: 1 }, exit: { x: 100, opacity: 0 }, transition: { duration: 0.5 } }, zoomIn: { initial: { scale: 0, opacity: 0 }, animate: { scale: 1, opacity: 1 }, exit: { scale: 0, opacity: 0 }, transition: { duration: 0.5 } }, bounceIn: { initial: { scale: 0, opacity: 0 }, animate: { scale: 1, opacity: 1 }, exit: { scale: 0, opacity: 0 }, transition: { type: "spring", stiffness: 200, damping: 10 } }, flipIn: { initial: { rotateY: 90, opacity: 0 }, animate: { rotateY: 0, opacity: 1 }, exit: { rotateY: -90, opacity: 0 }, transition: { duration: 0.5 } } }; // Helper for getting element animation based on time const getElementAnimationState = (element, currentTime) => { if (currentTime < element.start) { return "before"; } else if (currentTime >= element.start && currentTime < element.start + element.duration) { return "active"; } else { return "after"; } }; const VideoGeneratorApp = () => { // References const canvasRef = useRef(null); const videoRef = useRef(null); const timelineRef = useRef(null); const previewContainerRef = useRef(null); // State management const [activeTab, setActiveTab] = useState("templates"); const [currentTemplate, setCurrentTemplate] = useState(null); const [isPlaying, setIsPlaying] = useState(false); const [progress, setProgress] = useState(0); const [showEditor, setShowEditor] = useState(false); const [targetLanguage, setTargetLanguage] = useState("es-ES"); const [nativeLanguage, setNativeLanguage] = useState("en-US"); const [currentTime, setCurrentTime] = useState(0); const [duration, setDuration] = useState(15); const [isDraggingTimeline, setIsDraggingTimeline] = useState(false); const [showWaveform, setShowWaveform] = useState(true); const [fullscreenPreview, setFullscreenPreview] = useState(false); const [exportProgress, setExportProgress] = useState(0); const [isExporting, setIsExporting] = useState(false); const [exportFormat, setExportFormat] = useState("mp4"); const [exportResolution, setExportResolution] = useState("1080p"); const [savedProjects, setSavedProjects] = useState([]); const [showLoadProjectDialog, setShowLoadProjectDialog] = useState(false); const [previewQuality, setPreviewQuality] = useState("medium"); const [draggedElement, setDraggedElement] = useState(null); const [content, setContent] = useState([ { id: 1, image: "medico.jpg", targetWord: "médico", nativeWord: "doctor", targetPhrase1: "quiere ser médico", nativePhrase1: "he wants to be a doctor", targetPhrase2: "su médico es bueno", nativePhrase2: "his doctor is good", audio: "medico-audio.mp3" }, { id: 2, image: "abogado.jpg", targetWord: "abogado", nativeWord: "lawyer", targetPhrase1: "estudia para abogado", nativePhrase1: "he studies to be a lawyer", targetPhrase2: "necesita un abogado", nativePhrase2: "he needs a lawyer", audio: "abogado-audio.mp3" } ]); const [currentContentIndex, setCurrentContentIndex] = useState(0); const [newRow, setNewRow] = useState({ image: "", targetWord: "", nativeWord: "", targetPhrase1: "", nativePhrase1: "", targetPhrase2: "", nativePhrase2: "" }); const [selectedAnimationStyle, setSelectedAnimationStyle] = useState("fadeIn"); const [audioSyncEnabled, setAudioSyncEnabled] = useState(true); const [generatingStatus, setGeneratingStatus] = useState(null); const [renderQueue, setRenderQueue] = useState([]); const [projectName, setProjectName] = useState("Vocabulary Project"); const [showNotification, setShowNotification] = useState(false); const [notificationMessage, setNotificationMessage] = useState(""); const [notificationType, setNotificationType] = useState("success"); const [editingElement, setEditingElement] = useState(null); const [audioData, setAudioData] = useState(null); const [audioWaveform, setAudioWaveform] = useState([]); const [templates, setTemplates] = useState([ { id: "vocab-template", name: "Vocabulary Template", description: "Standard vocabulary template with word and phrases", thumbnail: "template1.jpg", duration: 15, background: "#1a1a2e" }, { id: "grammar-template", name: "Grammar Template", description: "Template for grammar explanations with examples", thumbnail: "template2.jpg", duration: 20, background: "#16213e" }, { id: "conversation-template", name: "Conversation Template", description: "Dialog-based template for conversation practice", thumbnail: "template3.jpg", duration: 30, background: "#0f3460" } ]); const [timelineElements, setTimelineElements] = useState([ { id: "el-1", type: "text", content: "Vocabulary Word", start: 0, duration: 3, position: {x: 400, y: 150}, style: {color: "#ffffff", fontSize: 36, fontWeight: "bold"}, animation: "fadeIn", contentKey: "targetWord" }, { id: "el-2", type: "image", content: "medico.jpg", start: 0.5, duration: 12, position: {x: 200, y: 250}, style: {width: 300, height: 300, borderRadius: "8px"}, animation: "zoomIn", contentKey: "image" }, { id: "el-3", type: "text", content: "Example Phrase 1", start: 4, duration: 3, position: {x: 400, y: 350}, style: {color: "#ffffff", fontSize: 24}, animation: "slideIn", contentKey: "targetPhrase1" }, { id: "el-4", type: "text", content: "Example Phrase 2", start: 8, duration: 3, position: {x: 400, y: 450}, style: {color: "#ffffff", fontSize: 24}, animation: "slideIn", contentKey: "targetPhrase2" }, { id: "el-5", type: "audio", content: "narration.mp3", start: 0, duration: 15, contentKey: "audio" }, ]); // Initialize local storage and load saved projects useEffect(() => { const loadSavedProjects = () => { try { const projectsJson = localStorage.getItem('videogen-projects'); if (projectsJson) { const projects = JSON.parse(projectsJson); setSavedProjects(projects); } } catch (error) { console.error("Error loading saved projects:", error); showNotify("Error loading saved projects", "error"); } }; loadSavedProjects(); // Setup auto-save const autoSaveInterval = setInterval(() => { if (currentTemplate && content.length > 0) { saveCurrentProject(true); } }, 60000); // Auto-save every minute return () => clearInterval(autoSaveInterval); }, []); // Save current project const saveCurrentProject = (isAutoSave = false) => { try { const project = { id: Date.now().toString(), name: projectName, template: currentTemplate, content: content, timelineElements: timelineElements, lastSaved: new Date().toISOString() }; let projects = []; const projectsJson = localStorage.getItem('videogen-projects'); if (projectsJson) { projects = JSON.parse(projectsJson); // Check if project with same name exists const existingProjectIndex = projects.findIndex(p => p.name === projectName); if (existingProjectIndex >= 0) { projects[existingProjectIndex] = project; } else { projects.push(project); } } else { projects = [project]; } localStorage.setItem('videogen-projects', JSON.stringify(projects)); setSavedProjects(projects); if (!isAutoSave) { showNotify("Project saved successfully"); } } catch (error) { console.error("Error saving project:", error); showNotify("Error saving project", "error"); } }; // Load a saved project const loadProject = (project) => { setProjectName(project.name); setCurrentTemplate(project.template); setContent(project.content); setTimelineElements(project.timelineElements); setActiveTab("content"); setShowLoadProjectDialog(false); showNotify(`Project "${project.name}" loaded successfully`); }; // Template selection const selectTemplate = (templateId) => { setCurrentTemplate(templates.find(t => t.id === templateId)); showNotify("Template selected: " + templates.find(t => t.id === templateId).name); setActiveTab("content"); }; // Notification helper const showNotify = (message, type = "success") => { setNotificationMessage(message); setNotificationType(type); setShowNotification(true); setTimeout(() => setShowNotification(false), 3000); }; // Add new content row const addContentRow = () => { if (!newRow.targetWord || !newRow.nativeWord) { showNotify("Target and native words are required", "error"); return; } setContent([...content, { id: content.length + 1, ...newRow, audio: `audio-${content.length + 1}.mp3` // Placeholder for generated audio }]); setNewRow({ image: "", targetWord: "", nativeWord: "", targetPhrase1: "", nativePhrase1: "", targetPhrase2: "", nativePhrase2: "" }); showNotify("New content row added"); }; // Delete content row const deleteContentRow = (id) => { setContent(content.filter(row => row.id !== id)); showNotify("Content row deleted"); }; // Toggle play/pause const togglePlayback = () => { setIsPlaying(!isPlaying); }; // Add new element to timeline const addTimelineElement = (type) => { const newElement = { id: `el-${Date.now()}`, type, content: type === 'text' ? 'New Text' : type === 'image' ? 'image.jpg' : 'audio.mp3', start: currentTime, duration: type === 'audio' ? 5 : 3, position: type !== 'audio' ? {x: 400, y: 300} : undefined, style: type === 'text' ? {color: "#ffffff", fontSize: 24} : type === 'image' ? {width: 200, height: 200, borderRadius: "8px"} : undefined, animation: type !== 'audio' ? selectedAnimationStyle : undefined }; setTimelineElements([...timelineElements, newElement]); setEditingElement(newElement); setShowEditor(true); showNotify(`New ${type} element added`); }; // Animation preview useEffect(() => { let interval; if (isPlaying) { interval = setInterval(() => { setCurrentTime(time => { const newTime = time + 0.1; if (newTime >= duration) { setIsPlaying(false); return 0; } return newTime; }); setProgress(prog => { const newProg = prog + (100 / (duration * 10)); return newProg > 100 ? 0 : newProg; }); }, 100); } return () => clearInterval(interval); }, [isPlaying, duration]); // 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))); setProgress(percentClicked * 100); }; // Handle timeline drag const handleTimelineDragStart = () => { setIsDraggingTimeline(true); setIsPlaying(false); }; const handleTimelineDragMove = (e) => { if (!isDraggingTimeline || !timelineRef.current) return; const rect = timelineRef.current.getBoundingClientRect(); const dragPosition = e.clientX - rect.left; const percentDragged = Math.max(0, Math.min(dragPosition / rect.width, 1)); const newTime = percentDragged * duration; setCurrentTime(newTime); setProgress(percentDragged * 100); }; const handleTimelineDragEnd = () => { setIsDraggingTimeline(false); }; // Effect for handling window mouse events during timeline dragging useEffect(() => { const handleMouseMove = (e) => handleTimelineDragMove(e); const handleMouseUp = () => handleTimelineDragEnd(); if (isDraggingTimeline) { window.addEventListener('mousemove', handleMouseMove); window.addEventListener('mouseup', handleMouseUp); } return () => { window.removeEventListener('mousemove', handleMouseMove); window.removeEventListener('mouseup', handleMouseUp); }; }, [isDraggingTimeline]); // Generate videos for all content const generateAllVideos = () => { if (!currentTemplate) { showNotify("Please select a template first", "error"); return; } if (content.length === 0) { showNotify("No content to generate", "error"); return; } // Create render queue const queue = content.map(item => ({ id: `render-${item.id}`, content: item, template: currentTemplate, status: "pending", progress: 0 })); setRenderQueue(queue); setGeneratingStatus("preparing"); // Simulate processing setTimeout(() => { setGeneratingStatus("processing"); let currentIndex = 0; const processNext = () => { if (currentIndex >= queue.length) { setGeneratingStatus("complete"); showNotify(`Successfully generated ${queue.length} videos`); return; } const updatedQueue = [...queue]; updatedQueue[currentIndex].status = "processing"; setRenderQueue(updatedQueue); // Simulate processing time (in real app, this would be actual rendering) let progress = 0; const interval = setInterval(() => { progress += 5; const newQueue = [...updatedQueue]; newQueue[currentIndex].progress = progress; setRenderQueue(newQueue); if (progress >= 100) { clearInterval(interval); newQueue[currentIndex].status = "complete"; setRenderQueue(newQueue); currentIndex++; setTimeout(processNext, 500); } }, 300); }; processNext(); }, 1500); }; // Open element editor const openElementEditor = (element) => { setEditingElement(element); setShowEditor(true); }; // Update element const updateElement = (updatedElement) => { setTimelineElements(elements => elements.map(el => el.id === updatedElement.id ? updatedElement : el) ); setShowEditor(false); showNotify("Element updated"); }; // Handle element drag in preview const startElementDrag = (element) => { setDraggedElement(element); }; const handleElementDrag = (e, element) => { if (!previewContainerRef.current) return; const rect = previewContainerRef.current.getBoundingClientRect(); const newX = e.clientX - rect.left; const newY = e.clientY - rect.top; setTimelineElements(elements => elements.map(el => { if (el.id === element.id && el.position) { return { ...el, position: { x: Math.max(0, Math.min(newX, rect.width)), y: Math.max(0, Math.min(newY, rect.height)) } }; } return el; }) ); }; const endElementDrag = () => { setDraggedElement(null); }; // Effect for mouse events during element dragging useEffect(() => { const handleMouseMove = (e) => { if (draggedElement) { handleElementDrag(e, draggedElement); } }; const handleMouseUp = () => { if (draggedElement) { endElementDrag(); } }; if (draggedElement) { window.addEventListener('mousemove', handleMouseMove); window.addEventListener('mouseup', handleMouseUp); } return () => { window.removeEventListener('mousemove', handleMouseMove); window.removeEventListener('mouseup', handleMouseUp); }; }, [draggedElement]); // Generate audio for content const generateAudio = () => { showNotify("Generating audio for content..."); // Simulate audio generation let generatedCount = 0; const processNext = () => { if (generatedCount >= content.length) { showNotify("Audio generation complete!"); // Generate mock waveform data generateMockWaveform(); return; } setTimeout(() => { generatedCount++; processNext(); }, 500); }; processNext(); }; // Generate mock waveform for audio visualization const generateMockWaveform = () => { const waveformPoints = []; const numPoints = 100; for (let i = 0; i < numPoints; i++) { // Create a semi-random waveform height, with more activity in the middle const x = i / numPoints; let height = 0.2 + 0.6 * Math.sin(x * Math.PI) * Math.random(); waveformPoints.push(height); } setAudioWaveform(waveformPoints); }; // Export current video const exportCurrentVideo = () => { if (currentContentIndex >= content.length) { showNotify("No content selected for export", "error"); return; } setIsExporting(true); setExportProgress(0); // Simulate export process const interval = setInterval(() => { setExportProgress(prev => { const next = prev + 5; if (next >= 100) { clearInterval(interval); setTimeout(() => { setIsExporting(false); showNotify(`Video exported successfully as ${projectName}-${currentContentIndex + 1}.${exportFormat}`); }, 500); return 100; } return next; }); }, 200); }; // Reset progress for new render const startNewProject = () => { setCurrentTemplate(null); setActiveTab("templates"); setProjectName("New Project"); setContent([]); setRenderQueue([]); setGeneratingStatus(null); setCurrentTime(0); setProgress(0); showNotify("Started new project"); }; // Define placeholder thumbnails and assets const getImageUrl = (name) => { if (name === "medico.jpg") { return "/api/placeholder/300/300"; } else if (name === "abogado.jpg") { return "/api/placeholder/300/300"; } else if (name === "template1.jpg" || name === "template2.jpg" || name === "template3.jpg") { return "/api/placeholder/400/220"; } return "/api/placeholder/200/200"; }; // Get real content based on timeline element const getElementContent = (element) => { if (!element.contentKey || currentContentIndex >= content.length) { return element.content; } const contentItem = content[currentContentIndex]; return contentItem[element.contentKey] || element.content; }; // Timeline rendering const renderTimeline = () => { // Find the time range to display const visibleTimeStart = Math.max(0, currentTime - 5); const visibleTimeEnd = Math.min(duration, currentTime + 10); return (
{/* Time markers */}
{Array.from({ length: Math.ceil(duration) + 1 }).map((_, i) => (
{i}s
))}
{/* Waveform */} {showWaveform && audioWaveform.length > 0 && (
`L${(i / audioWaveform.length) * 100}%,${(1-h) * 10} `).join('')}`} stroke="url(#waveformGradient)" strokeWidth="1.5" fill="none" />
)} {/* Elements timeline */}
{timelineElements.map(element => (
= element.start && currentTime < element.start + element.duration ? 'ring-2 ring-white' : ''}`} style={{ left: `${(element.start / duration) * 100}%`, width: `${(element.duration / duration) * 100}%`, top: element.type === 'audio' ? 'auto' : element.type === 'text' ? '0px' : '8px', bottom: element.type === 'audio' ? '0px' : 'auto' }} onClick={() => openElementEditor(element)} >
{element.type === 'text' ? getElementContent(element).substring(0, 15) : element.type} {element.type === 'text' && getElementContent(element).length > 15 ? '...' : ''}
))}
{/* Current time indicator */}
{/* Click area */}
); }; // Render preview with elements const renderPreview = () => { const currentContent = content[currentContentIndex]; const backgroundColor = currentTemplate?.background || "#1a1a2e"; return (
{/* Background elements */} {currentTemplate && (
)} {/* Display timeline elements */} {timelineElements.map(element => { if (element.type === 'audio') return null; // Don't visually render audio const animationState = getElementAnimationState(element, currentTime); if (animationState === "before") return null; const content = getElementContent(element); const animationPreset = animationPresets[element.animation || "fadeIn"]; // Determine animation based on time const animationProgress = Math.min(1, (currentTime - element.start) / 0.5); // 0.5s animation duration const exitProgress = Math.min(1, (currentTime - (element.start + element.duration - 0.5)) / 0.5); const shouldExit = animationState === "after" || exitProgress > 0; const shouldAnimate = animationProgress < 1 && !shouldExit; // Calculate final animation values const opacity = shouldExit ? 1 - exitProgress : shouldAnimate ? animationProgress : 1; // Determine transform based on animation type let transform = ''; if (element.animation === 'slideIn') { const xOffset = shouldExit ? (exitProgress * 100) : shouldAnimate ? ((1 - animationProgress) * -100) : 0; transform = `translateX(${xOffset}px)`; } else if (element.animation === 'zoomIn') { const scale = shouldExit ? (1 - exitProgress * 0.5) : shouldAnimate ? animationProgress : 1; transform = `scale(${scale})`; } else if (element.animation === 'bounceIn') { const scale = shouldExit ? (1 - exitProgress * 0.5) : shouldAnimate ? (animationProgress < 0.5 ? 2 * animationProgress : 1 + (1 - animationProgress)) : 1; transform = `scale(${scale})`; } else if (element.animation === 'flipIn') { const rotateY = shouldExit ? (exitProgress * 90) : shouldAnimate ? ((1 - animationProgress) * 90) : 0; transform = `perspective(800px) rotateY(${rotateY}deg)`; } return (
startElementDrag(element) : undefined} > {element.type === 'text' ? (
{content}
) : element.type === 'image' ? ( {content} ) : null}
); })} {/* Playback controls */}
{/* Additional controls */}
{/* Export progress overlay */} {isExporting && (
Exporting video... {exportProgress}%
)} {/* Timeline progress */}
); }; return (
{/* Header */}

VideoGen Pro

setProjectName(e.target.value)} />

Project Name

New Project setShowLoadProjectDialog(true)}> Load Project saveCurrentProject()}> Save Project Settings setPreviewQuality("low")}>
Low Quality Preview {previewQuality === "low" && }
setPreviewQuality("medium")}>
Medium Quality Preview {previewQuality === "medium" && }
setPreviewQuality("high")}>
High Quality Preview {previewQuality === "high" && }
Project Settings Export Settings User Preferences
{/* Main Content */}
{/* Left Sidebar */}
Templates Content
{activeTab === "editor" && (

Add Elements

Animation Style

)}
{/* Main Panel */}
{activeTab === "templates" && (

Video Templates

{templates.map(template => (
{template.name}

{template.name}

{template.description}

{template.duration}s
))}
)} {activeTab === "content" && (

Content

Image
Target Word
Native Word
Target Phrase 1
Native Phrase 1
Target Phrase 2
Actions
{content.map(row => (
{row.targetWord}
{row.targetWord}
{row.nativeWord}
{row.targetPhrase1}
{row.nativePhrase1}
{row.targetPhrase2}
))}
setNewRow({...newRow, targetWord: e.target.value})} />
setNewRow({...newRow, nativeWord: e.target.value})} />
setNewRow({...newRow, targetPhrase1: e.target.value})} />
setNewRow({...newRow, nativePhrase1: e.target.value})} />
setNewRow({...newRow, targetPhrase2: e.target.value})} />
)} {(activeTab === "editor" || activeTab === "generate") && (

{activeTab === "editor" ? "Template Editor" : "Generate Videos"}

{activeTab === "generate" && (
)}
{/* Preview and Timeline Area */}
{/* Preview Area */} {renderPreview()} {/* Timeline */} {renderTimeline()}
Item {currentContentIndex + 1} of {content.length}
{activeTab === "editor" && ( )} {activeTab === "generate" && ( )}
{activeTab === "generate" && (
{generatingStatus === "complete" ? "Generation Complete!" : generatingStatus === "processing" ? "Processing Videos..." : generatingStatus === "preparing" ? "Preparing..." : "Ready to Generate"} {generatingStatus === "complete" ? `Successfully generated ${renderQueue.length} videos.` : generatingStatus === "processing" ? `Processing ${renderQueue.filter(item => item.status === "complete").length} of ${renderQueue.length} videos...` : generatingStatus === "preparing" ? "Setting up render queue..." : "Click the button below to generate videos for all content."} Generation Queue {content.length} videos to generate using {currentTemplate?.name || "selected template"} {renderQueue.length > 0 ? ( renderQueue.map(item => (
{item.status === "complete" ? : item.status === "processing" ? : }
{item.content.targetWord} - {item.content.nativeWord}
{item.status === "complete" ? "Complete" : item.status === "processing" ? `${item.progress}%` : "Pending"}
)) ) : (

No videos in queue yet

Click the "Generate All Videos" button to start generation

)}
)}
)}
{/* Element Editor Dialog */} Edit Element Adjust properties for this timeline element {editingElement && (
setEditingElement({...editingElement, start: parseFloat(e.target.value)})} />
setEditingElement({...editingElement, duration: parseFloat(e.target.value)})} />
{editingElement.type === "text" && ( <>