PageSourceSearch

https://uva-cv-lab-omnishotcut.hf.space/

html uva-cv-lab-omnishotcut.hf.space collected 2026-10-03 10:21:42 UTC 19,968 bytes, 568 lines download raw bytes

1<!DOCTYPE html>
2<html lang="en">
3<head>
4    <meta charset="UTF-8">
5    <meta name="viewport" content="width=device-width, initial-scale=1.0">
6    <title>OmniShotCut Pro | Shot Boundary Detection</title>
7    <link rel="preconnect" href="https://fonts.googleapis.com">
8    <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
9    <link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&family=JetBrains+Mono:wght@400;500&display=swap" rel="stylesheet">
10    
10<script src="https://unpkg.com/lucide@latest"></script>
10
11    <style>
12        :root {
13            --bg-base: #0c0c0e;
14            --bg-surface: #141417;
15            --bg-elevated: #1c1c21;
16            --border: #2a2a2f;
17            --primary: #8b5cf6;
18            --primary-muted: rgba(139, 92, 246, 0.2);
19            --text-main: #efeff1;
20            --text-dim: #94949e;
21            --accent-green: #10b981;
22            --header-height: 48px;
23            --sidebar-width: 280px;
24            --inspector-width: 320px;
25            --timeline-height: 240px;
26        }
27
28        * {
29            box-sizing: border-box;
30            margin: 0;
31            padding: 0;
32            scrollbar-width: thin;
33            scrollbar-color: var(--border) transparent;
34        }
35
36        ::-webkit-scrollbar { width: 6px; height: 6px; }
37        ::-webkit-scrollbar-track { background: transparent; }
38        ::-webkit-scrollbar-thumb { background: var(--border); border-radius: 10px; }
39        ::-webkit-scrollbar-thumb:hover { background: var(--text-dim); }
40
41        body {
42            font-family: 'Inter', sans-serif;
43            background-color: var(--bg-base);
44            color: var(--text-main);
45            height: 100vh;
46            display: grid;
47            grid-template-rows: var(--header-height) 1fr var(--timeline-height);
48            grid-template-columns: var(--sidebar-width) 1fr var(--inspector-width);
49            grid-template-areas: 
50                "header header header"
51                "media preview inspector"
52                "timeline timeline timeline";
53            overflow: hidden;
54        }
55
56        /* --- Header --- */
57        header {
58            grid-area: header;
59            background: var(--bg-surface);
60            border-bottom: 1px solid var(--border);
61            display: flex;
62            align-items: center;
63            justify-content: space-between;
64            padding: 0 1rem;
65            z-index: 100;
66        }
67
68        .brand {
69            display: flex;
70            align-items: center;
71            gap: 0.75rem;
72            font-weight: 700;
73            font-size: 0.9rem;
74            letter-spacing: -0.01em;
75        }
76
77        .brand svg { color: var(--primary); }
78
79        .header-actions {
80            display: flex;
81            align-items: center;
82            gap: 1rem;
83        }
84
85        /* --- Media Sidebar --- */
86        .sidebar {
87            grid-area: media;
88            background: var(--bg-surface);
89            border-right: 1px solid var(--border);
90            display: flex;
91            flex-direction: column;
92            overflow: hidden;
93        }
94
95        .panel-header {
96            padding: 0.75rem 1rem;
97            font-size: 0.75rem;
98            font-weight: 600;
99            text-transform: uppercase;
100            color: var(--text-dim);
101            border-bottom: 1px solid var(--border);
102            display: flex;
103            justify-content: space-between;
104            align-items: center;
105        }
106
107        .media-content {
108            flex: 1;
109            overflow-y: auto;
110            padding: 1rem;
111        }
112
113        .upload-card {
114            border: 2px dashed var(--border);
115            border-radius: 0.5rem;
116            padding: 1.5rem 1rem;
117            text-align: center;
118            cursor: pointer;
119            transition: all 0.2s;
120            margin-bottom: 1.5rem;
121        }
122
123        .upload-card:hover {
124            border-color: var(--primary);
125            background: var(--primary-muted);
126        }
127
128        .upload-card i { margin-bottom: 0.5rem; color: var(--text-dim); }
129        .upload-card p { font-size: 0.8rem; font-weight: 500; }
130
131        .examples-list {
132            display: flex;
133            flex-direction: column;
134            gap: 0.75rem;
135        }
136
137        .example-node {
138            background: var(--bg-elevated);
139            border: 1px solid var(--border);
140            border-radius: 0.4rem;
141            overflow: hidden;
142            cursor: pointer;
143            transition: border-color 0.2s;
144        }
145
146        .example-node:hover { border-color: var(--primary); }
147        .example-node video { width: 100%; display: block; aspect-ratio: 16/9; object-fit: cover; }
148        .example-node span { display: block; padding: 0.4rem 0.6rem; font-size: 0.7rem; color: var(--text-dim); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
149
150        /* --- Preview Panel --- */
151        .preview {
152            grid-area: preview;
153            background: #000;
154            display: flex;
155            align-items: center;
156            justify-content: center;
157            position: relative;
158            padding: 2rem;
159        }
160
161        .video-container {
162            width: 100%;
163            max-width: 1000px;
164            aspect-ratio: 16/9;
165            background: #050505;
166            box-shadow: 0 20px 50px rgba(0,0,0,0.5);
167            border-radius: 4px;
168            overflow: hidden;
169            position: relative;
170        }
171
172        #main-video { width: 100%; height: 100%; object-fit: contain; }
173
174        .analyze-overlay {
175            position: absolute;
176            bottom: 2rem;
177            right: 2rem;
178        }
179
180        .btn-analyze {
181            background: var(--primary);
182            color: white;
183            border: none;
184            padding: 0.6rem 1.25rem;
185            border-radius: 2rem;
186            font-weight: 600;
187            font-size: 0.85rem;
188            cursor: pointer;
189            box-shadow: 0 4px 12px rgba(139, 92, 246, 0.4);
190            display: flex;
191            align-items: center;
192            gap: 0.5rem;
193            transition: all 0.2s;
194        }
195
196        .btn-analyze:hover:not(:disabled) { transform: scale(1.05); background: #7c3aed; }
197        .btn-analyze:disabled { opacity: 0.5;
197 cursor: not-allowed; }
198
199        /* --- Inspector Panel --- */
200        .inspector {
201            grid-area: inspector;
202            background: var(--bg-surface);
203            border-left: 1px solid var(--border);
204            display: flex;
205            flex-direction: column;
206            overflow: hidden;
207        }
208
209        .inspector-content {
210            flex: 1;
211            overflow-y: auto;
212            padding: 0;
213        }
214
215        /* Result table override */
216        .result-table-wrap { width: 100%; }
217        .result-table { width: 100%; border-collapse: collapse; font-size: 11px; font-family: 'JetBrains Mono', monospace; }
218        .result-table th { 
219            background: var(--bg-elevated); 
220            padding: 0.5rem; 
221            text-align: left; 
222            color: var(--text-dim); 
223            font-weight: 500;
224            border-bottom: 1px solid var(--border);
225            position: sticky; top: 0;
226        }
227        .result-table td { padding: 0.5rem; border-bottom: 1px solid var(--border); color: var(--text-main); }
228        .result-table tr:hover { background: rgba(255,255,255,0.03); }
229
230        /* --- Timeline Panel --- */
231        .timeline {
232            grid-area: timeline;
233            background: var(--bg-surface);
234            border-top: 1px solid var(--border);
235            display: flex;
236            flex-direction: column;
237            overflow: hidden;
238        }
239
240        .timeline-track {
241            flex: 1;
242            overflow-x: auto;
243            overflow-y: hidden;
244            padding: 1rem;
245            display: flex;
246            gap: 4px;
247            align-items: center;
248            background: var(--bg-base);
249        }
250
251        .timeline-item {
252            flex: 0 0 280px;
253            aspect-ratio: 16/9;
254            background: var(--bg-elevated);
255            border: 1px solid var(--border);
256            border-radius: 2px;
257            overflow: hidden;
258            position: relative;
259            cursor: pointer;
260            transition: transform 0.2s, border-color 0.2s;
261        }
262
263        .timeline-item:hover {
264            border-color: var(--primary);
265            z-index: 10;
266        }
267
268        .timeline-item img { width: 100%; height: 100%; object-fit: cover; }
269        .timeline-item .timestamp {
270            position: absolute;
271            bottom: 4px;
272            left: 4px;
273            background: rgba(0,0,0,0.7);
274            padding: 2px 4px;
275            font-size: 10px;
276            font-family: 'JetBrains Mono', monospace;
277            border-radius: 2px;
278        }
279
280        /* --- Loader --- */
281        .loader-overlay {
282            position: fixed;
283            inset: 0;
284            background: rgba(0,0,0,0.8);
285            backdrop-filter: blur(8px);
286            z-index: 1000;
287            display: none;
288            flex-direction: column;
289            align-items: center;
290            justify-content: center;
291        }
292
293        .spinner {
294            width: 48px;
295            height: 48px;
296            border: 3px solid var(--border);
297            border-top-color: var(--primary);
298            border-radius: 50%;
299            animation: spin 1s linear infinite;
300        }
301
302        @keyframes spin { to { transform: rotate(360deg); } }
303
304        /* --- Modal --- */
305        .modal {
306            position: fixed;
307            inset: 0;
308            background: rgba(0,0,0,0.95);
309            z-index: 2000;
310            display: none;
311            align-items: center;
312            justify-content: center;
313            padding: 2rem;
314        }
315
316        .modal-content { max-width: 90%; max-height: 90%; object-fit: contain; }
317        .close-modal { position: absolute; top: 1rem; right: 1rem; cursor: pointer; color: var(--text-dim); }
318
319        .empty-state {
320            display: flex;
321            flex-direction: column;
322            align-items: center;
323            justify-content: center;
324            height: 100%;
325            color: var(--text-dim);
326            font-size: 0.8rem;
327            text-align: center;
328            padding: 2rem;
329        }
330    </style>
331</head>
332<body>
333
334    <header>
335        <div class="brand">
336            <i data-lucide="video"></i>
337        
338            <div style="display: flex; flex-direction: column; gap: 2px;">
339                <span>OmniShotCut: Holistic Relational Shot Boundary Detection with Shot-Query Transformer </span>
340        
341                <p style="white-space: nowrap; margin: 0; display: flex; gap: 6px; align-items: center;">
342                    <a href="https://arxiv.org/abs/2604.24762" target="_blank">
343                        <img src="https://img.shields.io/badge/arXiv-Paper-b31b1b?logo=arxiv&logoColor=white" style="height: 18px;">
344                    </a>
345                    <a href="https://uva-computer-vision-lab.github.io/OmniShotCut_website/" target="_blank">
346                        <img src="https://img.shields.io/badge/Project-Website-pink?logo=googlechrome&logoColor=white" style="height: 18px;">
347                    </a>
348                    <a href="https://github.com/UVA-Computer-Vision-Lab/OmniShotCut" target="_blank">
349                        <img src="https://img.shields.io/badge/GitHub-Code-black?logo=github&logoColor=white" style="height: 18px;">
350                    </a>
351                </p>
352            </div>
353        </div>
354        <div class="header-actions">
355            <span id="status-tag" style="font-size: 0.7rem; color: var(--accent-green); display: flex; align-items: center; gap: 0.4rem;">
356                <span style="width: 6px; height: 6px; background: var(--accent-green); border-radius: 50%;"></span>
357                Engine Ready
358            </span>
359        </div>
360    </header>
361
362    <aside class="sidebar">
363        <div class="panel-header">
364            <span>Media Library</span>
365            <i data-lucide="plus" size="14" style="cursor: pointer;"></i>
366        </div>
367        <div class="media-content">
368            <div class="upload-card" id="drop-zone">
369                <i data-lucide="upload-cloud"></i>
370                <p>Import Media</p>
371                <input type="file" id="video-input" accept="video/*" style="display: none;">
372            </div>
373            
374            <div class="panel-header" style="border: none; padding-left: 0;">Examples</div>
375            <div class="examples-list" id="examples-grid">
376                <!-- Examples load here -->
377            </div>
378        </div>
379    </aside>
380
381    <main class="preview">
382        <div class="video-container">
383            <video id="main-video" controls></video>
384            <div id="empty-preview" class="empty-state">
385                <i data-lucide="film" size="48" style="margin-bottom: 1rem; opacity: 0.2;"></i>
386                <p>No media loaded.<br>Import a file to begin analysis.</p>
387            </div>
388        </div>
389        
390        <div class="analyze-overlay">
391            <button id="run-btn" class="btn-analyze" disabled>
392                <i data-lucide="zap"></i>
393                Analyze Shots
394            </button>
395        </div>
396    </main>
397
398    <aside class="inspector">
399        <div class="panel-header">
400            <span>Shot Inspector</span>
401            <span id="shot-count" style="background: var(--primary-muted); padding: 1px 6px; border-radius: 4px; color: var(--primary); font-size: 10px;">0 SHOTS</span>
402        </div>
403        <div class="inspector-content" id="table-container">
404            <div class="empty-state">
405                <p>Results will appear here after analysis.</p>
406            </div>
407        </div>
408    </aside>
409
410    <footer class="timeline">
411        <div class="panel-header">
412            <span>Relational Visualization Track</span>
413            <div style="display: flex; gap: 1rem;">
414                <span style="font-size: 10px; color: var(--text-dim);">Frames: <span id="frame-info">--</span></span>
415            </div>
416        </div>
417        <div class="timeline-track" id="gallery">
418            <!-- Timeline items load here -->
419        </div>
420    </footer>
421
422    <div class="loader-overlay" id="loader">
423        <div class="spinner"></div>
424        <p style="margin-top: 1.5rem; font-weight: 500; letter-spacing: 0.05em; font-size: 0.9rem;">PROCESSING TEMPORAL RELATIONS...</p>
425    </div>
426
427    <div class="modal" id="modal">
428        <i data-lucide="x" class="close-modal"></i>
429        <img class="modal-content" id="modal-img">
430    </div>
431
432    
432<script type="module">
433        import { Client, handle_file } from "https://cdn.jsdelivr.net/npm/@gradio/client/dist/index.min.js";
434
435        // Initialize Icons
436        lucide.createIcons();
437
438        const dropZone = document.getElementById('drop-zone');
439        const videoInput = document.getElementById('video-input');
440        const mainVideo = document.getElementById('main-video');
441        const emptyPreview = document.getElementById('empty-preview');
442        const runBtn = document.getElementById('run-btn');
443        const loader = document.getElementById('loader');
444        const gallery = document.getElementById('gallery');
445        const tableContainer = document.getElementById('table-container');
446        const shotCountBadge = document.getElementById('shot-count');
447        const modal = document.getElementById('modal');
448        const modalImg = document.getElementById('modal-img');
449        const examplesGrid = document.getElementById('examples-grid');
450        const frameInfo = document.getElementById('frame-info');
451
452        let selectedFile = null;
453        let client = null;
454
455        async function initClient() {
456            client = await Client.connect(window.location.origin);
457            console.log("OmniShotCut Engine Online");
458            loadExamples();
459        }
460        initClient();
461
462        dropZone.onclick = () => videoInput.click();
463        videoInput.onchange = (e) => handleFile(e.target.files[0]);
464
465        function handleFile(file) {
466            if (!file) return;
467            selectedFile = file;
468            mainVideo.src = URL.createObjectURL(file);
469            mainVideo.style.display = 'block';
470            emptyPreview.style.display = 'none';
471            runBtn.disabled = false;
472        }
473
474        runBtn.onclick = async () => {
475            if (!selectedFile || !client) return;
476
477            loader.style.display = 'flex';
478            
479            try {
480                const result = await client.predict("/run_inference", { 
481                    video_file: handle_file(selectedFile) 
482                });
483                renderResults(result.data[0]);
484            } catch (err) {
485                console.error(err);
486                alert("Analysis engine error. Check console.");
487            } finally {
488                loader.style.display = 'none';
489            }
490        };
491
492        function renderResults(data) {
493            if (data.error) {
494                alert(data.error);
495                return;
496            }
497
498            shotCountBadge.innerText = `${data.shot_count} SHOTS`;
499            
500            // Timeline rendering
501            gallery.innerHTML = '';
502            data.gallery.forEach((file, idx) => {
503                const item = document.createElement('div');
504                item.className = 'timeline-item';
505                const img = document.createElement('img');
506                img.src = file.url;
507                
508                const ts = document.createElement('div');
509                ts.className = 'timestamp';
510                ts.innerText = `SHOT_${idx.toString().padStart(2, '0')}`;
511                
512                item.appendChild(img);
513                item.appendChild(ts);
514                
515                item.onclick = () => {
516                    modalImg.src = file.url;
517                    modal.style.display = 'flex';
518                };
519                gallery.appendChild(item);
520            });
521
522            // Table rendering
523            tableContainer.innerHTML = data.table;
524            frameInfo.innerText = data.shot_count > 0 ? "Analyzed" : "--";
525        }
526
527        async function loadExamples() {
528            try {
529                const result = await client.predict("/get_examples", {});
530                const examples = result.data[0];
531                examplesGrid.innerHTML = '';
532                
533                examples.forEach(ex => {
534                    const node = document.createElement('div');
535                    node.className = 'example-node';
536                    
537                    const video = document.createElement('video');
538                    video.src = ex.url;
539                    video.muted = true;
540                    video.preload = "metadata";
541                    video.onloadedmetadata = () => video.currentTime = 0.1;
542                    
543                    const label = document.createElement('span');
544                    label.innerText = ex.orig_name || 'Clip';
545                    
546                    node.appendChild(video);
547                    node.appendChild(label);
548                    
549                    node.onmouseenter = () => video.play();
550                    node.onmouseleave = () => { video.pause(); video.currentTime = 0.1; };
551                    
552                    node.onclick = async () => {
553                        const response = await fetch(ex.url);
554                        const blob = await response.blob();
555                        handleFile(new File([blob], ex.orig_name || 'clip.mp4', { type: 'video/mp4' }));
556                    };
557                    
558                    examplesGrid.appendChild(node);
559                });
560            } catch (e) { console.error("Examples load failed"); }
561        }
562
563        document.querySelector('.close-modal').onclick = () => modal.style.display = 'none';
564        modal.onclick = (e) => { if (e.target === modal) modal.style.display = 'none'; };
565
566    </script>
566
567</body>
568</html>

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.