import React, { useState, useRef } from 'react'; import { Image, Download, AlertCircle, CheckCircle, Loader2, Play } from 'lucide-react'; const DEFAULT_URLS = `https://static1.squarespace.com/static/67fe6263177965472e25bc12/t/6a1d7d5539722178e677a92a/1780317528116/OTS_shots_with_chatbot_mascots_202606011426.mp4 https://static1.squarespace.com/static/67fe6263177965472e25bc12/t/6a1d7eda39722178e6786899/1780317937978/Hero+video_v4.mp4 https://static1.squarespace.com/static/67fe6263177965472e25bc12/t/6a1eb2262df35c08ea748e05/1780396584936/2_jap+problem.mp4 https://static1.squarespace.com/static/67fe6263177965472e25bc12/t/6a1ef470b971ee3cd8d5ba21/1780413555189/2_solution_v2.mp4 https://static1.squarespace.com/static/67fe6263177965472e25bc12/t/6a1c38ad3aa0431ee4e4d005/1780234419680/Card_1_0_Hero.mp4 https://static1.squarespace.com/static/67fe6263177965472e25bc12/t/6a1cbbfd9c41d54b8f779225/1780268030454/Card+1_1_Fully+Animated+3D+Characters.mp4 https://static1.squarespace.com/static/67fe6263177965472e25bc12/t/6a1c383712c80708b0d2d04b/1780234301939/Card+1_2_Tone+of+voice.mp4 https://static1.squarespace.com/static/67fe6263177965472e25bc12/t/6a1c383733cd2d796d5cef59/1780234302285/Card+1_3+Quik+reskin.mp4 https://static1.squarespace.com/static/67fe6263177965472e25bc12/t/6a1c38ad780b2d06b8c373e6/1780234420843/Card_2_0_hero.mp4 https://static1.squarespace.com/static/67fe6263177965472e25bc12/t/6a1c38376af2ef7ddd6492ea/1780234303901/Card+2_1_Map+Every+Journey.mp4 https://static1.squarespace.com/static/67fe6263177965472e25bc12/t/6a1c3837bddf62635f2de1e9/1780234301476/Card+2_2_Powered+by+Your+Raw+Data.mp4 https://static1.squarespace.com/static/67fe6263177965472e25bc12/t/6a1c38374a7eaf7926d66b77/1780234304980/Card+2_3_Omnichannel+Deployment.mp4 https://static1.squarespace.com/static/67fe6263177965472e25bc12/t/6a1c3837a2b5ad6d4183e5d3/1780234304368/Card+3_0_hero.mp4 https://static1.squarespace.com/static/67fe6263177965472e25bc12/t/6a1c38ad3ce62272d156b791/1780234419716/Card_3_1+Real-Time+Insights.mp4 https://static1.squarespace.com/static/67fe6263177965472e25bc12/t/6a1c398a7e38353bd63a596b/1780234637751/Card+3_2+Emotional+Intelligence.mp4 https://static1.squarespace.com/static/67fe6263177965472e25bc12/t/6a1c38ad12c80708b0d2f520/1780234418972/Card_3_3_Track+Your+ROI.mp4 https://static1.squarespace.com/static/67fe6263177965472e25bc12/t/6a1c398a0bd25a1db0a69660/1780234638209/Card+4_0_Hero.mp4 https://static1.squarespace.com/static/67fe6263177965472e25bc12/t/6a1c398a3aa0431ee4e4f888/1780234638077/Card+4_1.mp4 https://static1.squarespace.com/static/67fe6263177965472e25bc12/t/6a1c38ad1eaa0b5d789061a5/1780234420750/Card+4_2.mp4 https://static1.squarespace.com/static/67fe6263177965472e25bc12/t/6a1c38adac56b001da72d9a0/1780234420005/Card+4_3.mp4 https://static1.squarespace.com/static/67fe6263177965472e25bc12/t/69d47b4aab856a27e86bd40b/1775532907808/Willy.mp4 https://static1.squarespace.com/static/67fe6263177965472e25bc12/t/69d47b4a5ec75b0f31bbec33/1775532907808/Biek_1.mp4 https://static1.squarespace.com/static/67fe6263177965472e25bc12/t/69d47b4a4a1bc73ee1ec1cd9/1775532895446/Liza.mp4 https://static1.squarespace.com/static/67fe6263177965472e25bc12/t/69d47b4a960144044bf3569e/1775532902828/Fin.mp4 https://static1.squarespace.com/static/67fe6263177965472e25bc12/t/69d47b4a0465de355013688f/1775532907904/Woof.mp4`; export default function App() { const [urlsInput, setUrlsInput] = useState(DEFAULT_URLS); const [results, setResults] = useState([]); const [isProcessing, setIsProcessing] = useState(false); const [progress, setProgress] = useState({ current: 0, total: 0 }); const getFilenameFromUrl = (url) => { try { const parts = url.split('?')[0].split('/'); let filename = parts[parts.length - 1] || 'frame'; // Decode URI components (e.g., %20 to space, + to space) filename = decodeURIComponent(filename.replace(/\+/g, ' ')); // Replace .mp4 or .webm with .png if (filename.toLowerCase().endsWith('.mp4')) { filename = filename.slice(0, -4) + '.png'; } else if (filename.toLowerCase().endsWith('.webm')) { filename = filename.slice(0, -5) + '.png'; } else { filename += '.png'; } return filename; } catch (e) { return `frame_${Date.now()}.png`; } }; const extractFrame = (url) => { return new Promise((resolve, reject) => { const video = document.createElement('video'); video.crossOrigin = 'anonymous'; // Important to avoid Canvas Tainting video.muted = true; video.playsInline = true; video.src = url; let timeoutId = setTimeout(() => { reject(new Error("Timeout loading video")); }, 15000); // 15 second timeout const handleLoadedData = () => { // Seek to 0.1 seconds to guarantee a frame is drawn (frame 0 is sometimes blank on the web) video.currentTime = 0.1; }; const handleSeeked = () => { try { const canvas = document.createElement('canvas'); canvas.width = video.videoWidth || 1920; // Fallbacks just in case canvas.height = video.videoHeight || 1080; const ctx = canvas.getContext('2d'); ctx.drawImage(video, 0, 0, canvas.width, canvas.height); const dataUrl = canvas.toDataURL('image/png'); clearTimeout(timeoutId); resolve(dataUrl); } catch (err) { clearTimeout(timeoutId); reject(new Error("Canvas tainted by CORS or draw error.")); } }; const handleError = () => { clearTimeout(timeoutId); reject(new Error("Failed to load video or CORS error.")); }; video.addEventListener('loadeddata', handleLoadedData); video.addEventListener('seeked', handleSeeked); video.addEventListener('error', handleError); // Trigger load video.load(); }); }; const processVideos = async () => { const urls = urlsInput .split('\n') .map(u => u.trim()) .filter(u => u.length > 0); if (urls.length === 0) return; setIsProcessing(true); setResults([]); setProgress({ current: 0, total: urls.length }); const newResults = []; // Process sequentially to prevent browser overload/crashes for (let i = 0; i < urls.length; i++) { const url = urls[i]; const filename = getFilenameFromUrl(url); setProgress({ current: i + 1, total: urls.length }); try { const dataUrl = await extractFrame(url); newResults.push({ id: i, url, filename, dataUrl, status: 'success' }); } catch (error) { newResults.push({ id: i, url, filename, error: error.message, status: 'error' }); } // Update state incrementally so user sees progress setResults([...newResults]); } setIsProcessing(false); }; const downloadImage = (dataUrl, filename) => { const a = document.createElement('a'); a.href = dataUrl; a.download = filename; document.body.appendChild(a); a.click(); document.body.removeChild(a); }; const downloadAll = () => { // Triggers downloads with a slight delay to bypass popup blockers const successful = results.filter(r => r.status === 'success'); successful.forEach((result, idx) => { setTimeout(() => { downloadImage(result.dataUrl, result.filename); }, idx * 300); }); }; return (
{/* Header */}

Video Frame Extractor

Automatically capture the first frame of a list of videos and download them as PNGs.

{/* Controls Column */}