Spaces:
Runtime error
Runtime error
| <html lang="en"> | |
| <head> | |
| <meta charset="UTF-8"> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
| <title>VideoCoF - Unified Video Editing</title> | |
| <script src="https://cdn.jsdelivr.net/npm/react@18.0.0/umd/react.development.js"></script> | |
| <script src="https://cdn.jsdelivr.net/npm/react-dom@18.0.0/umd/react-dom.development.js"></script> | |
| <script src="https://cdn.jsdelivr.net/npm/@babel/standalone/babel.js"></script> | |
| <script src="https://cdn.tailwindcss.com"></script> | |
| <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.15.3/css/all.min.css"> | |
| <link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&display=swap" rel="stylesheet"> | |
| <style> | |
| body { | |
| font-family: 'Inter', sans-serif; | |
| } | |
| .gradient-bg { | |
| background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); | |
| } | |
| .timeline-track { | |
| background: linear-gradient(90deg, #e0e7ff 0%, #c7d2fe 50%, #a5b4fc 100%); | |
| height: 4px; | |
| border-radius: 2px; | |
| } | |
| .timeline-marker { | |
| width: 12px; | |
| height: 12px; | |
| background: #4f46e5; | |
| border-radius: 50%; | |
| position: absolute; | |
| top: -4px; | |
| transform: translateX(-50%); | |
| } | |
| .video-timeline { | |
| background: #f3f4f6; | |
| border-radius: 8px; | |
| padding: 1rem; | |
| } | |
| .tab-active { | |
| background-color: #667eea; | |
| color: white; | |
| } | |
| .tab-inactive { | |
| background-color: #e5e7eb; | |
| color: #374151; | |
| } | |
| .slider-thumb::-webkit-slider-thumb { | |
| appearance: none; | |
| width: 20px; | |
| height: 20px; | |
| background: #667eea; | |
| border-radius: 50%; | |
| cursor: pointer; | |
| } | |
| .slider-thumb::-moz-range-thumb { | |
| width: 20px; | |
| height: 20px; | |
| background: #667eea; | |
| border-radius: 50%; | |
| cursor: pointer; | |
| border: none; | |
| } | |
| </style> | |
| </head> | |
| <body> | |
| <div id="root"></div> | |
| <script type="text/babel"> | |
| const { useState, useEffect, useRef } = React; | |
| function VideoCoF() { | |
| const [videoFile, setVideoFile] = useState(null); | |
| const [videoInfo, setVideoInfo] = useState(null); | |
| const [timelineData, setTimelineData] = useState(null); | |
| const [isProcessing, setIsProcessing] = useState(false); | |
| const [activeTab, setActiveTab] = useState(0); | |
| const [startTrim, setStartTrim] = useState(0); | |
| const [endTrim, setEndTrim] = useState(100); | |
| const [selectedEffects, setSelectedEffects] = useState([]); | |
| const [selectedFilter, setSelectedFilter] = useState('None'); | |
| const [playbackSpeed, setPlaybackSpeed] = useState(1.0); | |
| const [editedVideo, setEditedVideo] = useState(null); | |
| const [videoDuration, setVideoDuration] = useState(0); | |
| const [videoResolution, setVideoResolution] = useState({ width: 0, height: 0 }); | |
| const [videoFps, setVideoFps] = useState(0); | |
| const [showSuccess, setShowSuccess] = useState(false); | |
| const videoRef = useRef(null); | |
| const fileInputRef = useRef(null); | |
| const extractVideoInfo = (file) => { | |
| // Simulate video info extraction | |
| return { | |
| duration: 120.5, | |
| width: 1920, | |
| height: 1080, | |
| fps: 30, | |
| frame_count: 3615 | |
| }; | |
| }; | |
| const createTimelineFigure = (videoInfo) => { | |
| // Simulate timeline data | |
| const timePoints = Array.from({ length: 100 }, (_, i) => i * videoInfo.duration / 100); | |
| const intensity = timePoints.map(t => Math.sin(t * 0.1) * 0.5 + 0.5); | |
| return { timePoints, intensity }; | |
| }; | |
| const handleFileUpload = (e) => { | |
| const file = e.target.files[0]; | |
| if (file && file.type.startsWith('video/')) { | |
| setVideoFile(file); | |
| setIsProcessing(true); | |
| setTimeout(() => { | |
| const info = extractVideoInfo(file); | |
| setVideoInfo(info); | |
| setTimelineData(createTimelineFigure(info)); | |
| setVideoDuration(info.duration); | |
| setVideoResolution({ width: info.width, height: info.height }); | |
| setVideoFps(info.fps); | |
| setIsProcessing(false); | |
| }, 1000); | |
| } | |
| }; | |
| const handleLoadVideo = () => { | |
| if (fileInputRef.current) { | |
| fileInputRef.current.click(); | |
| } | |
| }; | |
| const handleApplyEdits = () => { | |
| setIsProcessing(true); | |
| setTimeout(() => { | |
| setEditedVideo('edited_video.mp4'); | |
| setIsProcessing(false); | |
| setShowSuccess(true); | |
| setTimeout(() => setShowSuccess(false), 3000); | |
| }, 2000); | |
| }; | |
| const handleEffectToggle = (effect) => { | |
| if (selectedEffects.includes(effect)) { | |
| setSelectedEffects(selectedEffects.filter(e => e !== effect)); | |
| } else { | |
| setSelectedEffects([...selectedEffects, effect]); | |
| } | |
| }; | |
| const handleDownload = () => { | |
| const link = document.createElement('a'); | |
| link.href = editedVideo; | |
| link.download = 'edited_video.mp4'; | |
| link.click(); | |
| }; | |
| return ( | |
| <div className="min-h-screen bg-gray-50"> | |
| {/* Header */} | |
| <div className="gradient-bg text-white p-6 rounded-b-lg"> | |
| <div className="max-w-7xl mx-auto"> | |
| <h1 className="text-3xl font-bold mb-2">🎥 VideoCoF</h1> | |
| <p className="text-lg mb-4">Unified Video Editing with Temporal Reasoner</p> | |
| <a | |
| href="https://huggingface.co/spaces/akhaliq/anycoder" | |
| target="_blank" | |
| className="text-white hover:underline" | |
| > | |
| Built with anycoder | |
| </a> | |
| </div> | |
| </div> | |
| <div className="max-w-7xl mx-auto p-6"> | |
| {/* Sidebar */} | |
| <div className="bg-white rounded-lg shadow-md p-6 mb-6"> | |
| <h2 className="text-xl font-semibold mb-4">🎛️ Controls</h2> | |
| <div className="mb-6"> | |
| <h3 className="text-lg font-medium mb-3">Upload Video</h3> | |
| <input | |
| ref={fileInputRef} | |
| type="file" | |
| accept="video/*" | |
| onChange={handleFileUpload} | |
| className="hidden" | |
| /> | |
| <button | |
| onClick={handleLoadVideo} | |
| className="w-full bg-blue-600 text-white py-3 px-4 rounded-lg hover:bg-blue-700 transition-colors" | |
| > | |
| Choose Video File | |
| </button> | |
| <p className="text-sm text-gray-500 mt-2">Supported formats: MP4, AVI, MOV, MKV</p> | |
| </div> | |
| {isProcessing && ( | |
| <div className="flex items-center justify-center py-4"> | |
| <div className="animate-spin rounded-full h-8 w-8 border-b-2 border-blue-600"></div> | |
| <span className="ml-2">Processing video...</span> | |
| </div> | |
| )} | |
| </div> | |
| {/* Main Content */} | |
| {videoFile ? ( | |
| <div className="space-y-6"> | |
| {/* Video Player */} | |
| <div className="bg-white rounded-lg shadow-md p-6"> | |
| <h2 className="text-xl font-semibold mb-4">🎬 Video Player</h2> | |
| <div className="grid grid-cols-1 lg:grid-cols-4 gap-4"> | |
| <div className="lg:col-span-3"> | |
| <div className="relative bg-black rounded-lg overflow-hidden aspect-video"> | |
| <video | |
| ref={videoRef} | |
| className="w-full h-full object-contain" | |
| controls | |
| > | |
| <source src={URL.createObjectURL(videoFile)} type={videoFile.type} /> | |
| </video> | |
| </div> | |
| </div> | |
| <div className="space-y-4"> | |
| <div> | |
| <p className="text-sm text-gray-500">Duration</p> | |
| <p className="font-semibold">{videoDuration.toFixed(2)}s</p> | |
| </div> | |
| <div> | |
| <p className="text-sm text-gray-500">Resolution</p> | |
| <p className="font-semibold">{videoResolution.width}×{videoResolution.height}</p> | |
| </div> | |
| <div> | |
| <p className="text-sm text-gray-500">FPS</p> | |
| <p className="font-semibold">{videoFps}</p> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| {/* Timeline Editor */} | |
| <div className="bg-white rounded-lg shadow-md p-6"> | |
| <h2 className="text-xl font-semibold mb-4">⏱️ Timeline Editor</h2> | |
| <div className="video-timeline"> | |
| <div className="relative h-12"> | |
| <div className="timeline-track w-full"></div> | |
| <div className="timeline-marker" style={{ left: `${startTrim}%` }}></div> | |
| <div className="timeline-marker" style={{ left: `${endTrim}%` }}></div> | |
| </div> | |
| </div> | |
| </div> | |
| {/* Editing Tools */} | |
| <div className="bg-white rounded-lg shadow-md p-6"> | |
| <h2 className="text-xl font-semibold mb-4">✂️ Editing Tools</h2> | |
| <div className="flex space-x-2 mb-6"> | |
| {['Trim', 'Effects', 'Filters', 'Speed'].map((tab, index) => ( | |
| <button | |
| key={tab} | |
| onClick={() => setActiveTab(index)} | |
| className={`px-4 py-2 rounded-lg font-medium transition-colors ${ | |
| activeTab === index ? 'tab-active' : 'tab-inactive' | |
| }`} | |
| > | |
| {tab} | |
| </button> | |
| ))} | |
| </div> | |
| <div className="space-y-6"> | |
| {activeTab === 0 && ( | |
| <div className="grid grid-cols-1 md:grid-cols-2 gap-4"> | |
| <div> | |
| <label className="block text-sm font-medium text-gray-700 mb-2"> | |
| Start Time | |
| </label> | |
| <input | |
| type="range" | |
| min="0" | |
| max={videoDuration} | |
| step="0.1" | |
| value={startTrim} | |
| onChange={(e) => setStartTrim(parseFloat(e.target.value))} | |
| className="w-full slider-thumb" | |
| /> | |
| <span className="text-sm text-gray-500"> | |
| {startTrim.toFixed(1)}s | |
| </span> | |
| </div> | |
| <div> | |
| <label className="block text-sm font-medium text-gray-700 mb-2"> | |
| End Time | |
| </label> | |
| <input | |
| type="range" | |
| min="0" | |
| max={videoDuration} | |
| step="0.1" | |
| value={endTrim} | |
| onChange={(e) => setEndTrim(parseFloat(e.target.value))} | |
| className="w-full slider-thumb" | |
| /> | |
| <span className="text-sm text-gray-500"> | |
| {endTrim.toFixed(1)}s | |
| </span> | |
| </div> | |
| </div> | |
| )} | |
| {activeTab === 1 && ( | |
| <div> | |
| <label className="block text-sm font-medium text-gray-700 mb-2"> | |
| Apply Effects | |
| </label> | |
| <div className="grid grid-cols-2 md:grid-cols-3 gap-3"> | |
| {['Fade In', 'Fade Out', 'Crossfade', 'Blur', 'Sharpen'].map(effect => ( | |
| <button | |
| key={effect} | |
| onClick={() => handleEffectToggle(effect)} | |
| className={`px-3 py-2 rounded-lg text-sm font-medium transition-colors ${ | |
| selectedEffects.includes(effect) | |
| ? 'bg-blue-600 text-white' | |
| : 'bg-gray-200 text-gray-700 hover:bg-gray-300' | |
| }`} | |
| > | |
| {effect} | |
| </button> | |
| ))} | |
| </div> | |
| </div> | |
| )} | |
| {activeTab === 2 && ( | |
| <div> | |
| <label className="block text-sm font-medium text-gray-700 mb-2"> | |
| Video Filter | |
| </label> | |
| <select | |
| value={selectedFilter} | |
| onChange={(e) => setSelectedFilter(e.target.value)} | |
| className="w-full px-3 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500" | |
| > | |
| <option value="None">None</option> | |
| <option value="Grayscale">Grayscale</option> | |
| <option value="Sepia">Sepia</option> | |
| <option value="Vintage">Vintage</option> | |
| <option value="Cool">Cool</option> | |
| <option value="Warm">Warm</option> | |
| </select> | |
| </div> | |
| )} | |
| {activeTab === 3 && ( | |
| <div> | |
| <label className="block text-sm font-medium text-gray-700 mb-2"> | |
| Playback Speed | |
| </label> | |
| <input | |
| type="range" | |
| min="0.25" | |
| max="2.0" | |
| step="0.25" | |
| value={playbackSpeed} | |
| onChange={(e) => setPlaybackSpeed(parseFloat(e.target.value))} | |
| className="w-full slider-thumb" | |
| /> | |
| <span className="text-sm text-gray-500"> | |
| {playbackSpeed}x | |
| </span> | |
| </div> | |
| )} | |
| </div> | |
| </div> | |
| {/* Apply Edits Button */} | |
| <div className="text-center"> | |
| <button | |
| onClick={handleApplyEdits} | |
| disabled={isProcessing} | |
| className="bg-blue-600 text-white px-8 py-3 rounded-lg font-medium hover:bg-blue-700 transition-colors disabled:bg-gray-400" | |
| > | |
| {isProcessing ? 'Applying Edits...' : 'Apply Edits'} | |
| </button> | |
| {showSuccess && ( | |
| <div className="mt-4 p-4 bg-green-100 border border-green-400 text-green-700 rounded-lg"> | |
| Edits applied successfully! | |
| </div> | |
| )} | |
| </div> | |
| {/* Export Options */} | |
| {editedVideo && ( | |
| <div className="bg-white rounded-lg shadow-md p-6"> | |
| <h2 className="text-xl font-semibold mb-4">💾 Export Options</h2> | |
| <div className="grid grid-cols-1 md:grid-cols-2 gap-4"> | |
| <div> | |
| <label className="block text-sm font-medium text-gray-700 mb-2"> | |
| Export Format | |
| </label> | |
| <select className="w-full px-3 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500"> | |
| <option value="MP4">MP4</option> | |
| <option value="AVI">AVI</option> | |
| <option value="MOV">MOV</option> | |
| </select> | |
| </div> | |
| <div className="flex items-end space-x-4"> | |
| <button | |
| onClick={handleDownload} | |
| className="bg-green-600 text-white px-6 py-2 rounded-lg font-medium hover:bg-green-700 transition-colors" | |
| > | |
| Download Edited Video | |
| </button> | |
| <button className="bg-gray-600 text-white px-6 py-2 rounded-lg font-medium hover:bg-gray-700 transition-colors"> | |
| Export to Cloud | |
| </button> | |
| </div> | |
| </div> | |
| </div> | |
| )} | |
| </div> | |
| ) : ( | |
| <div className="bg-white rounded-lg shadow-md p-12 text-center"> | |
| <div className="text-gray-400 mb-4"> | |
| <i className="fas fa-video text-6xl"></i> | |
| </div> | |
| <p className="text-lg text-gray-600">📁 Please upload a video file to get started with VideoCoF!</p> | |
| </div> | |
| )} | |
| </div> | |
| </div> | |
| ); | |
| } | |
| ReactDOM.render(<VideoCoF />, document.getElementById('root')); | |
| </script> | |
| </body> | |
| </html> |