Spaces:
Running
Running
File size: 7,183 Bytes
0a8cce5 40ff32c 0a8cce5 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 | import { useState, useRef, useCallback } from "react";
import GlassContainer from "./GlassContainer";
import GlassButton from "./GlassButton";
import { GLASS_EFFECTS } from "../constants";
import type { VideoUploadState } from "../types";
interface VideoUploadScreenProps {
onVideoReady: (videoState: VideoUploadState) => void;
onBack: () => void;
}
export default function VideoUploadScreen({ onVideoReady, onBack }: VideoUploadScreenProps) {
const [dragActive, setDragActive] = useState(false);
const [uploadError, setUploadError] = useState<string | null>(null);
const [isProcessing, setIsProcessing] = useState(false);
const fileInputRef = useRef<HTMLInputElement>(null);
const handleFiles = useCallback(async (files: FileList | null) => {
if (!files || files.length === 0) return;
const file = files[0];
// Validate file type
if (!file.type.startsWith('video/')) {
setUploadError('Please select a valid video file.');
return;
}
// Validate file size (100MB limit)
if (file.size > 1024 * 1024 * 1024) {
setUploadError('Video file is too large. Please select a file under 1GB.');
return;
}
setIsProcessing(true);
setUploadError(null);
try {
// Create video element
const videoElement = document.createElement('video');
videoElement.muted = true;
videoElement.controls = false;
videoElement.preload = 'metadata';
// Create object URL for the video
const videoUrl = URL.createObjectURL(file);
videoElement.src = videoUrl;
// Wait for video metadata to load
await new Promise<void>((resolve, reject) => {
videoElement.onloadedmetadata = () => resolve();
videoElement.onerror = () => reject(new Error('Failed to load video'));
videoElement.load();
});
// Validate video duration (max 10 minutes)
if (videoElement.duration > 600) {
setUploadError('Video is too long. Please select a video under 10 minutes.');
URL.revokeObjectURL(videoUrl);
return;
}
const videoState: VideoUploadState = {
file,
videoElement,
isReady: true
};
onVideoReady(videoState);
} catch (error) {
console.error('Error processing video:', error);
setUploadError('Failed to process video file. Please try a different file.');
} finally {
setIsProcessing(false);
}
}, [onVideoReady]);
const handleDrag = useCallback((e: React.DragEvent) => {
e.preventDefault();
e.stopPropagation();
}, []);
const handleDragIn = useCallback((e: React.DragEvent) => {
e.preventDefault();
e.stopPropagation();
if (e.dataTransfer.items && e.dataTransfer.items.length > 0) {
setDragActive(true);
}
}, []);
const handleDragOut = useCallback((e: React.DragEvent) => {
e.preventDefault();
e.stopPropagation();
setDragActive(false);
}, []);
const handleDrop = useCallback((e: React.DragEvent) => {
e.preventDefault();
e.stopPropagation();
setDragActive(false);
if (e.dataTransfer.files && e.dataTransfer.files.length > 0) {
handleFiles(e.dataTransfer.files);
}
}, [handleFiles]);
const handleFileSelect = useCallback(() => {
fileInputRef.current?.click();
}, []);
const handleFileChange = useCallback((e: React.ChangeEvent<HTMLInputElement>) => {
handleFiles(e.target.files);
}, [handleFiles]);
return (
<div className="absolute inset-0 text-white flex items-center justify-center p-8">
<div className="max-w-2xl w-full space-y-8">
{/* Header */}
<GlassContainer
className="rounded-3xl shadow-2xl hover:scale-105 transition-transform duration-200"
role="banner"
>
<div className="p-8 text-center">
<h1 className="text-4xl font-bold text-gray-100 mb-4">Upload Video</h1>
<p className="text-lg text-gray-300 leading-relaxed">
Select a video file to analyze with FastVLM
</p>
</div>
</GlassContainer>
{/* Upload Area */}
<GlassContainer
className={`rounded-2xl shadow-2xl transition-all duration-200 border-2 border-dashed ${
dragActive
? 'border-blue-400 bg-blue-500/10 scale-105'
: uploadError
? 'border-red-400 bg-red-500/10'
: 'border-gray-500 hover:border-gray-400 hover:scale-105'
}`}
bgColor={
dragActive
? GLASS_EFFECTS.COLORS.BUTTON_BG
: uploadError
? GLASS_EFFECTS.COLORS.ERROR_BG
: GLASS_EFFECTS.COLORS.DEFAULT_BG
}
onDrag={handleDrag}
onDragStart={handleDrag}
onDragEnd={handleDrag}
onDragOver={handleDrag}
onDragEnter={handleDragIn}
onDragLeave={handleDragOut}
onDrop={handleDrop}
>
<div className="p-12 text-center">
{isProcessing ? (
<div className="space-y-4">
<div className="text-6xl">⏳</div>
<h3 className="text-xl font-semibold text-gray-200">Processing video...</h3>
<p className="text-gray-400">Please wait while we prepare your video</p>
</div>
) : (
<div className="space-y-6">
<div className="text-6xl">{dragActive ? '📂' : '📁'}</div>
<div>
<h3 className="text-xl font-semibold text-gray-200 mb-2">
{dragActive ? 'Drop your video here' : 'Upload Video File'}
</h3>
<p className="text-gray-400 mb-4">
Drag and drop a video file or click to browse
</p>
<div className="text-sm text-gray-500 space-y-1">
<p>Supported formats: MP4, WebM, AVI, MOV</p>
<p>Maximum size: 100MB | Maximum duration: 10 minutes</p>
</div>
</div>
<GlassButton
onClick={handleFileSelect}
className="px-6 py-3 rounded-xl"
disabled={isProcessing}
>
Choose File
</GlassButton>
</div>
)}
{uploadError && (
<div className="mt-6 p-4 rounded-xl bg-red-500/20 border border-red-500/30">
<p className="text-red-300 font-medium">{uploadError}</p>
</div>
)}
</div>
</GlassContainer>
{/* Back Button */}
<div className="flex justify-center">
<GlassButton
onClick={onBack}
className="px-6 py-3 rounded-xl"
disabled={isProcessing}
>
← Back to Options
</GlassButton>
</div>
{/* Hidden file input */}
<input
ref={fileInputRef}
type="file"
accept="video/*"
onChange={handleFileChange}
className="hidden"
/>
</div>
</div>
);
}
|