diff --git a/features/video-studio/adversary-view-bottom.png b/features/video-studio/adversary-view-bottom.png new file mode 100644 index 000000000..0eba65404 Binary files /dev/null and b/features/video-studio/adversary-view-bottom.png differ diff --git a/features/video-studio/adversary-view.png b/features/video-studio/adversary-view.png new file mode 100644 index 000000000..71e606964 Binary files /dev/null and b/features/video-studio/adversary-view.png differ diff --git a/features/video-studio/frontend-demo/src/components/InvisibleProtectionsDemo.tsx b/features/video-studio/frontend-demo/src/components/InvisibleProtectionsDemo.tsx index 5a2743011..18f89e740 100644 --- a/features/video-studio/frontend-demo/src/components/InvisibleProtectionsDemo.tsx +++ b/features/video-studio/frontend-demo/src/components/InvisibleProtectionsDemo.tsx @@ -38,7 +38,7 @@ export function InvisibleProtectionsDemo({ const [outputMode, setOutputMode] = useState<'default' | 'lossless'>('lossless'); const [jobs, setJobs] = useState([]); const [jobMeta, setJobMeta] = useState< - ReadonlyMap + ReadonlyMap >(new Map()); const [expandedJobId, setExpandedJobId] = useState(null); const [submitting, setSubmitting] = useState(false); @@ -177,6 +177,7 @@ export function InvisibleProtectionsDemo({ label: activeLabel ?? activeVideoPath, ops: [...selectedOps], submittedAt: Date.now(), + photoId: selectedPhoto?.id ?? null, }); return next; }); @@ -187,6 +188,16 @@ export function InvisibleProtectionsDemo({ } }, [activeVideoPath, selectedOps, outputMode]); + const protectedPhotoOps = new Map(); + for (const job of jobs) { + if (job.status !== 'done') continue; + const meta = jobMeta.get(job.job_id); + if (meta?.photoId) { + const existing = protectedPhotoOps.get(meta.photoId) ?? []; + protectedPhotoOps.set(meta.photoId, [...new Set([...existing, ...meta.ops])]); + } + } + return (
{/* Top section: gallery + operation picker */} @@ -254,6 +265,7 @@ export function InvisibleProtectionsDemo({ setSelectedRecording(null); onVideoSelect?.(photo.id); }} + protectedOps={protectedPhotoOps.get(photo.id) ?? null} /> ))}
@@ -352,11 +364,13 @@ interface VideoCardProps { photo: PhotoItem; selected: boolean; onClick: () => void; + protectedOps: string[] | null; } -function VideoCard({ photo, selected, onClick }: VideoCardProps): ReactElement { +function VideoCard({ photo, selected, onClick, protectedOps }: VideoCardProps): ReactElement { const [hovered, setHovered] = useState(false); const isDone = photo.processing_stage === 'done' || photo.processing_stage === 'failed'; + const isProtected = protectedOps !== null; return (