import { useState, useRef, useCallback } from 'react'; import type { DetectionResult } from '@core/types.js'; import { autoDetectMultiFrame, type AutoDetectResult } from '@core/detector.js'; import { extractFrames, rgbaToY } from '../lib/video-io.js'; import ResultCard from './ResultCard.js'; export default function DetectPanel() { const [videoUrl, setVideoUrl] = useState(null); const [videoName, setVideoName] = useState(''); const [key, setKey] = useState(''); const [maxFrames, setMaxFrames] = useState(10); const [cropResilient, setCropResilient] = useState(false); const [processing, setProcessing] = useState(false); const [progress, setProgress] = useState({ phase: '', current: 0, total: 0 }); const [result, setResult] = useState(null); const fileRef = useRef(null); const handleFile = useCallback((file: File) => { const url = URL.createObjectURL(file); setVideoUrl(url); setVideoName(file.name); setResult(null); }, []); const handleDrop = useCallback( (e: React.DragEvent) => { e.preventDefault(); const file = e.dataTransfer.files[0]; if (file?.type.startsWith('video/')) handleFile(file); }, [handleFile] ); const handleDetect = async () => { if (!videoUrl || !key) return; setProcessing(true); setResult(null); try { setProgress({ phase: 'Extracting frames', current: 0, total: 0 }); const { frames, width, height } = await extractFrames(videoUrl, maxFrames, (c, t) => setProgress({ phase: 'Extracting frames', current: c, total: t }) ); setProgress({ phase: 'Converting frames', current: 0, total: frames.length }); const yPlanes = frames.map((frame, i) => { setProgress({ phase: 'Converting frames', current: i + 1, total: frames.length }); return rgbaToY(frame); }); setProgress({ phase: 'Trying all presets', current: 0, total: 0 }); const detection = autoDetectMultiFrame(yPlanes, width, height, key, { cropResilient }); setResult(detection); } catch (e) { console.error('Detection error:', e); alert(`Error: ${e}`); } finally { setProcessing(false); } }; return (
{/* Upload area */}
e.preventDefault()} onClick={() => fileRef.current?.click()} className={`group cursor-pointer rounded-xl border-2 border-dashed p-10 text-center transition-colors ${videoUrl ? 'border-zinc-700 bg-zinc-900/30' : 'border-zinc-800 bg-zinc-900/20 hover:border-zinc-600 hover:bg-zinc-900/40' }`} > { const file = e.target.files?.[0]; if (file) handleFile(file); }} /> {videoUrl ? (

{videoName}

Click or drop to replace

) : (

Drop a video file to analyze

Upload a potentially watermarked video

)}
{/* Configuration — just key and frame count */}
setKey(e.target.value)} placeholder="Enter the secret key used for embedding..." className="w-full rounded-lg border border-zinc-800 bg-zinc-900/50 px-3 py-2 text-sm text-zinc-100 placeholder:text-zinc-600 focus:border-blue-600 focus:outline-none focus:ring-1 focus:ring-blue-600" />
setMaxFrames(Math.max(1, Math.min(100, parseInt(e.target.value) || 10)))} min={1} max={100} className="w-full rounded-lg border border-zinc-800 bg-zinc-900/50 px-3 py-2 text-sm text-zinc-100 focus:border-blue-600 focus:outline-none focus:ring-1 focus:ring-blue-600" />

More frames = better detection, slower processing

); }