diff --git a/client/src/components/thumbnail-generator.tsx b/client/src/components/thumbnail-generator.tsx index a324d0d..89efd52 100644 --- a/client/src/components/thumbnail-generator.tsx +++ b/client/src/components/thumbnail-generator.tsx @@ -1,5 +1,5 @@ import { useRef, useState, useEffect } from "react"; -import { X, Download, Upload, Check, RotateCcw } from "lucide-react"; +import { X, Download, Upload, Check, RotateCcw, Sparkles, Zap } from "lucide-react"; import { Button } from "@/components/ui/button"; import { Slider } from "@/components/ui/slider"; import Hls from "hls.js"; @@ -30,6 +30,8 @@ export default function ThumbnailGenerator({ const [selectedThumbnail, setSelectedThumbnail] = useState(null); const [customThumbnail, setCustomThumbnail] = useState(null); const [isGenerating, setIsGenerating] = useState(false); + const [aiSuggestions, setAiSuggestions] = useState([]); + const [isGeneratingAI, setIsGeneratingAI] = useState(false); // Initialize video when modal opens useEffect(() => { @@ -158,6 +160,69 @@ export default function ThumbnailGenerator({ } }; + const generateAIThumbnails = async () => { + const videoElement = videoRef.current; + const canvas = canvasRef.current; + + if (!videoElement || !canvas || duration === 0) return; + + setIsGeneratingAI(true); + + try { + const ctx = canvas.getContext('2d'); + if (!ctx) return; + + // Set canvas dimensions + canvas.width = videoElement.videoWidth || 1280; + canvas.height = videoElement.videoHeight || 720; + + const suggestions: string[] = []; + + // Generate thumbnails at strategic points (10%, 35%, 65% of video) + const timePoints = [ + duration * 0.1, // 10% - early content + duration * 0.35, // 35% - middle content + duration * 0.65 // 65% - later content + ]; + + for (const timePoint of timePoints) { + // Seek to specific time + videoElement.currentTime = timePoint; + + // Wait for video to seek to the correct time + await new Promise((resolve) => { + const onSeeked = () => { + videoElement.removeEventListener('seeked', onSeeked); + resolve(void 0); + }; + videoElement.addEventListener('seeked', onSeeked); + }); + + // Small delay to ensure frame is rendered + await new Promise(resolve => setTimeout(resolve, 100)); + + // Draw frame to canvas + ctx.drawImage(videoElement, 0, 0, canvas.width, canvas.height); + + // Convert to data URL + const thumbnailDataUrl = canvas.toDataURL('image/jpeg', 0.9); + suggestions.push(thumbnailDataUrl); + } + + setAiSuggestions(suggestions); + + } catch (error) { + console.error('Error generating AI thumbnails:', error); + } finally { + setIsGeneratingAI(false); + } + }; + + const selectAISuggestion = (suggestionUrl: string) => { + setSelectedThumbnail(suggestionUrl); + setCustomThumbnail(null); + }; + const formatTime = (seconds: number) => { const mins = Math.floor(seconds / 60); const secs = Math.floor(seconds % 60); @@ -229,25 +294,47 @@ export default function ThumbnailGenerator({ )} - {/* Generate Button */} - + {/* Generate Buttons */} +
+ + + +
{/* Thumbnail Preview Section */} @@ -280,6 +367,41 @@ export default function ThumbnailGenerator({ )} + {/* AI Suggestions */} + {aiSuggestions.length > 0 && ( +
+

+ + AI predlogi +

+
+ {aiSuggestions.map((suggestion, index) => ( + + ))} +
+
+ )} + {/* Custom Upload */}