Spaces:
Running
Running
Upload folder using huggingface_hub
Browse files- index.html +641 -649
index.html
CHANGED
|
@@ -1,669 +1,661 @@
|
|
| 1 |
<!DOCTYPE html>
|
| 2 |
<html lang="en">
|
| 3 |
-
|
| 4 |
<head>
|
| 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 |
-
|
| 220 |
-
|
| 221 |
-
|
| 222 |
-
|
| 223 |
-
|
| 224 |
-
|
| 225 |
-
|
| 226 |
-
|
| 227 |
-
|
| 228 |
-
|
| 229 |
-
|
| 230 |
-
|
| 231 |
-
|
| 232 |
-
|
| 233 |
-
|
| 234 |
-
|
| 235 |
-
|
| 236 |
-
|
| 237 |
-
|
| 238 |
-
|
| 239 |
-
|
| 240 |
-
|
| 241 |
-
|
| 242 |
-
|
| 243 |
-
|
| 244 |
-
|
| 245 |
-
|
| 246 |
-
|
| 247 |
-
|
| 248 |
-
|
| 249 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 250 |
</head>
|
| 251 |
-
|
| 252 |
<body>
|
| 253 |
-
<div id="root"></div>
|
| 254 |
-
|
| 255 |
-
<script type="text/babel">
|
| 256 |
-
const { useState, useRef, useEffect } = React;
|
| 257 |
-
|
| 258 |
-
const App = () => {
|
| 259 |
-
const [selectedVideo, setSelectedVideo] = useState(null);
|
| 260 |
-
const [isProcessing, setIsProcessing] = useState(false);
|
| 261 |
-
const [resultVideo, setResultVideo] = useState(null);
|
| 262 |
-
const [selectedTool, setSelectedTool] = useState('rectangle');
|
| 263 |
-
const [isSelecting, setIsSelecting] = useState(false);
|
| 264 |
-
const [selection, setSelection] = useState(null);
|
| 265 |
-
const [startPoint, setStartPoint] = useState(null);
|
| 266 |
-
const [extensionDirection, setExtensionDirection] = useState('right');
|
| 267 |
-
const [videoSize, setVideoSize] = useState({ width: 800, height: 600 });
|
| 268 |
-
const [history, setHistory] = useState([]);
|
| 269 |
-
const [historyIndex, setHistoryIndex] = useState(-1);
|
| 270 |
-
const [videoDuration, setVideoDuration] = useState(0);
|
| 271 |
-
const [currentTime, setCurrentTime] = useState(0);
|
| 272 |
-
|
| 273 |
-
const videoRef = useRef(null);
|
| 274 |
-
const fileInputRef = useRef(null);
|
| 275 |
-
const canvasRef = useRef(null);
|
| 276 |
-
|
| 277 |
-
const handleVideoUpload = (e) => {
|
| 278 |
-
const file = e.target.files[0];
|
| 279 |
-
if (file && file.type.startsWith('video/')) {
|
| 280 |
-
const url = URL.createObjectURL(file);
|
| 281 |
-
setSelectedVideo(url);
|
| 282 |
-
setResultVideo(null);
|
| 283 |
-
setHistory([]);
|
| 284 |
-
setHistoryIndex(-1);
|
| 285 |
-
|
| 286 |
-
// Load video to get dimensions
|
| 287 |
-
const video = document.createElement('video');
|
| 288 |
-
video.src = url;
|
| 289 |
-
video.onloadedmetadata = () => {
|
| 290 |
-
setVideoSize({ width: video.videoWidth, height: video.videoHeight });
|
| 291 |
-
setVideoDuration(video.duration);
|
| 292 |
-
};
|
| 293 |
-
}
|
| 294 |
-
};
|
| 295 |
|
| 296 |
-
|
| 297 |
-
|
| 298 |
-
|
| 299 |
-
const rect = videoRef.current.getBoundingClientRect();
|
| 300 |
-
const x = e.clientX - rect.left;
|
| 301 |
-
const y = e.clientY - rect.top;
|
| 302 |
-
|
| 303 |
-
setIsSelecting(true);
|
| 304 |
-
setStartPoint({ x, y });
|
| 305 |
-
setSelection({ x, y, width: 0, height: 0 });
|
| 306 |
-
};
|
| 307 |
|
| 308 |
-
|
| 309 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 310 |
|
| 311 |
-
|
| 312 |
-
|
| 313 |
-
|
| 314 |
-
|
| 315 |
-
|
| 316 |
-
|
|
|
|
| 317 |
|
| 318 |
-
|
| 319 |
-
|
| 320 |
-
|
| 321 |
-
|
| 322 |
-
height: Math.abs(height)
|
| 323 |
-
});
|
| 324 |
-
};
|
| 325 |
-
|
| 326 |
-
const handleVideoMouseUp = () => {
|
| 327 |
-
if (!isSelecting) return;
|
| 328 |
-
setIsSelecting(false);
|
| 329 |
-
};
|
| 330 |
|
| 331 |
-
|
| 332 |
-
|
| 333 |
-
|
| 334 |
-
|
| 335 |
-
|
| 336 |
-
/
|
| 337 |
-
|
| 338 |
-
|
| 339 |
-
|
| 340 |
-
|
| 341 |
-
|
| 342 |
-
|
| 343 |
-
|
| 344 |
-
|
| 345 |
-
|
| 346 |
-
|
| 347 |
-
|
| 348 |
-
|
| 349 |
-
|
| 350 |
-
|
| 351 |
-
|
| 352 |
-
|
| 353 |
-
|
| 354 |
-
|
| 355 |
-
|
| 356 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 357 |
|
| 358 |
-
|
| 359 |
-
|
| 360 |
-
|
| 361 |
-
|
| 362 |
-
|
| 363 |
-
|
| 364 |
-
|
| 365 |
-
|
| 366 |
-
|
| 367 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 368 |
}
|
| 369 |
-
};
|
| 370 |
|
| 371 |
-
|
| 372 |
-
|
| 373 |
-
|
| 374 |
-
|
| 375 |
-
|
| 376 |
-
|
| 377 |
-
|
| 378 |
-
|
| 379 |
-
|
| 380 |
-
|
| 381 |
-
|
| 382 |
-
|
| 383 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 384 |
}
|
| 385 |
-
}
|
| 386 |
-
};
|
| 387 |
|
| 388 |
-
|
| 389 |
-
|
| 390 |
-
|
| 391 |
-
|
| 392 |
-
};
|
| 393 |
-
|
| 394 |
-
const handleSeek = (e) => {
|
| 395 |
-
if (videoRef.current) {
|
| 396 |
-
videoRef.current.currentTime = e.target.value;
|
| 397 |
-
}
|
| 398 |
-
};
|
| 399 |
-
|
| 400 |
-
const tools = [
|
| 401 |
-
{ id: 'rectangle', icon: 'fas fa-square', label: 'Rectangle' },
|
| 402 |
-
{ id: 'circle', icon: 'fas fa-circle', label: 'Circle' },
|
| 403 |
-
{ id: 'freehand', icon: 'fas fa-pen', label: 'Freehand' }
|
| 404 |
-
];
|
| 405 |
-
|
| 406 |
-
const directions = [
|
| 407 |
-
{ id: 'right', icon: 'fas fa-arrow-right', label: 'Right' },
|
| 408 |
-
{ id: 'left', icon: 'fas fa-arrow-left', label: 'Left' },
|
| 409 |
-
{ id: 'top', icon: 'fas fa-arrow-up', label: 'Top' },
|
| 410 |
-
{ id: 'bottom', icon: 'fas fa-arrow-down', label: 'Bottom' }
|
| 411 |
-
];
|
| 412 |
-
|
| 413 |
-
return (
|
| 414 |
-
<div className="min-h-screen bg-gray-900 flex flex-col">
|
| 415 |
-
{/* Custom Top - Header */}
|
| 416 |
-
<div className="custom-top">
|
| 417 |
-
<header className="gradient-bg text-white py-4 px-6">
|
| 418 |
-
<div className="flex items-center justify-between">
|
| 419 |
-
<div className="flex items-center space-x-4">
|
| 420 |
-
<h1 className="text-2xl font-bold">Be Your Outpainter</h1>
|
| 421 |
-
<span className="text-sm opacity-75">AI-Powered Video Extension</span>
|
| 422 |
-
</div>
|
| 423 |
-
<a
|
| 424 |
-
href="https://huggingface.co/spaces/akhaliq/anycoder"
|
| 425 |
-
className="text-sm hover:opacity-80 transition-opacity"
|
| 426 |
-
target="_blank"
|
| 427 |
-
rel="noopener noreferrer"
|
| 428 |
-
>
|
| 429 |
-
Built with anycoder
|
| 430 |
-
</a>
|
| 431 |
-
</div>
|
| 432 |
-
</header>
|
| 433 |
-
</div>
|
| 434 |
|
| 435 |
-
|
| 436 |
-
|
| 437 |
-
|
| 438 |
-
|
| 439 |
-
|
| 440 |
-
|
| 441 |
-
|
| 442 |
-
|
| 443 |
-
|
| 444 |
-
|
| 445 |
-
|
| 446 |
-
|
| 447 |
-
onChange={handleVideoUpload}
|
| 448 |
-
className="hidden"
|
| 449 |
-
/>
|
| 450 |
-
<button
|
| 451 |
-
onClick={() => fileInputRef.current.click()}
|
| 452 |
-
className="w-full bg-gradient-to-r from-indigo-500 to-purple-600 text-white py-3 px-4 rounded-lg hover:from-indigo-600 hover:to-purple-700 transition-all duration-300 flex items-center justify-center space-x-2"
|
| 453 |
-
>
|
| 454 |
-
<i className="fas fa-upload"></i>
|
| 455 |
-
<span>Choose Video</span>
|
| 456 |
-
</button>
|
| 457 |
-
{selectedVideo && (
|
| 458 |
-
<p className="text-sm text-gray-400 mt-3">Video loaded successfully</p>
|
| 459 |
-
)}
|
| 460 |
-
</div>
|
| 461 |
-
|
| 462 |
-
{/* Selection Tools */}
|
| 463 |
-
<div className="glass-effect rounded-xl p-6 slide-in" style={{ animationDelay: '0.1s' }}>
|
| 464 |
-
<h3 className="text-lg font-semibold mb-4">Selection Tool</h3>
|
| 465 |
-
<div className="space-y-2">
|
| 466 |
-
{tools.map(tool => (
|
| 467 |
-
<button
|
| 468 |
-
key={tool.id}
|
| 469 |
-
onClick={() => setSelectedTool(tool.id)}
|
| 470 |
-
className={`tool-button w-full text-left px-4 py-3 rounded-lg flex items-center space-x-3 transition-all ${
|
| 471 |
-
selectedTool === tool.id ? 'active' : 'bg-gray-700 hover:bg-gray-600'
|
| 472 |
-
}`}
|
| 473 |
-
>
|
| 474 |
-
<i className={tool.icon}></i>
|
| 475 |
-
<span>{tool.label}</span>
|
| 476 |
-
</button>
|
| 477 |
-
))}
|
| 478 |
-
</div>
|
| 479 |
-
</div>
|
| 480 |
-
|
| 481 |
-
{/* Extension Direction */}
|
| 482 |
-
<div className="glass-effect rounded-xl p-6 slide-in" style={{ animationDelay: '0.2s' }}>
|
| 483 |
-
<h3 className="text-lg font-semibold mb-4">Extension Direction</h3>
|
| 484 |
-
<div className="grid grid-cols-2 gap-2">
|
| 485 |
-
{directions.map(dir => (
|
| 486 |
-
<button
|
| 487 |
-
key={dir.id}
|
| 488 |
-
onClick={() => setExtensionDirection(dir.id)}
|
| 489 |
-
className={`tool-button px-3 py-2 rounded-lg flex items-center justify-center space-x-2 transition-all ${
|
| 490 |
-
extensionDirection === dir.id
|
| 491 |
-
? 'active'
|
| 492 |
-
: 'bg-gray-700 hover:bg-gray-600'
|
| 493 |
-
}`}
|
| 494 |
-
>
|
| 495 |
-
<i className={dir.icon}></i>
|
| 496 |
-
<span className="text-sm">{dir.label}</span>
|
| 497 |
-
</button>
|
| 498 |
-
))}
|
| 499 |
-
</div>
|
| 500 |
-
</div>
|
| 501 |
-
|
| 502 |
-
{/* Actions */}
|
| 503 |
-
<div className="glass-effect rounded-xl p-6 slide-in" style={{ animationDelay: '0.3s' }}>
|
| 504 |
-
<h3 className="text-lg font-semibold mb-4">Actions</h3>
|
| 505 |
-
<div className="space-y-2">
|
| 506 |
-
<button
|
| 507 |
-
onClick={handleGenerate}
|
| 508 |
-
disabled={!selectedVideo || !selection || isProcessing}
|
| 509 |
-
className="w-full bg-gradient-to-r from-green-500 to-emerald-600 text-white py-3 px-4 rounded-lg hover:from-green-600 hover:to-emerald-700 transition-all duration-300 disabled:opacity-50 disabled:cursor-not-allowed flex items-center justify-center space-x-2"
|
| 510 |
-
>
|
| 511 |
-
{isProcessing ? (
|
| 512 |
-
<>
|
| 513 |
-
<div className="loading-spinner"></div>
|
| 514 |
-
<span>Processing...</span>
|
| 515 |
-
</>
|
| 516 |
-
) : (
|
| 517 |
-
<>
|
| 518 |
-
<i className="fas fa-magic"></i>
|
| 519 |
-
<span>Generate</span>
|
| 520 |
-
</>
|
| 521 |
-
)}
|
| 522 |
-
</button>
|
| 523 |
-
<button
|
| 524 |
-
onClick={handleDownload}
|
| 525 |
-
disabled={!resultVideo}
|
| 526 |
-
className="w-full bg-gradient-to-r from-blue-500 to-cyan-600 text-white py-3 px-4 rounded-lg hover:from-blue-600 hover:to-cyan-700 transition-all duration-300 disabled:opacity-50 disabled:cursor-not-allowed flex items-center justify-center space-x-2"
|
| 527 |
-
>
|
| 528 |
-
<i className="fas fa-download"></i>
|
| 529 |
-
<span>Download</span>
|
| 530 |
-
</button>
|
| 531 |
-
</div>
|
| 532 |
-
</div>
|
| 533 |
-
</div>
|
| 534 |
-
|
| 535 |
-
{/* Canvas Area */}
|
| 536 |
-
<div className="canvas-area">
|
| 537 |
-
<div className="glass-effect rounded-xl p-6 fade-in">
|
| 538 |
-
<div className="flex items-center justify-between mb-4">
|
| 539 |
-
<h2 className="text-xl font-semibold">Canvas</h2>
|
| 540 |
-
<div className="flex space-x-2">
|
| 541 |
-
<button
|
| 542 |
-
onClick={handleUndo}
|
| 543 |
-
disabled={historyIndex <= 0}
|
| 544 |
-
className="px-3 py-2 bg-gray-700 hover:bg-gray-600 rounded-lg disabled:opacity-50 disabled:cursor-not-allowed transition-all"
|
| 545 |
-
>
|
| 546 |
-
<i className="fas fa-undo"></i>
|
| 547 |
-
</button>
|
| 548 |
-
<button
|
| 549 |
-
onClick={handleRedo}
|
| 550 |
-
disabled={historyIndex >= history.length - 1}
|
| 551 |
-
className="px-3 py-2 bg-gray-700 hover:bg-gray-600 rounded-lg disabled:opacity-50 disabled:cursor-not-allowed transition-all"
|
| 552 |
-
>
|
| 553 |
-
<i className="fas fa-redo"></i>
|
| 554 |
-
</button>
|
| 555 |
-
</div>
|
| 556 |
-
</div>
|
| 557 |
-
|
| 558 |
-
<div className="canvas-container relative">
|
| 559 |
-
{selectedVideo ? (
|
| 560 |
-
<div className="relative">
|
| 561 |
-
<video
|
| 562 |
-
ref={videoRef}
|
| 563 |
-
src={selectedVideo}
|
| 564 |
-
className="max-w-full h-auto"
|
| 565 |
-
style={{
|
| 566 |
-
maxWidth: '100%',
|
| 567 |
-
height: 'auto',
|
| 568 |
-
cursor: isProcessing ? 'not-allowed' : 'crosshair'
|
| 569 |
-
}}
|
| 570 |
-
onMouseDown={handleVideoMouseDown}
|
| 571 |
-
onMouseMove={handleVideoMouseMove}
|
| 572 |
-
onMouseUp={handleVideoMouseUp}
|
| 573 |
-
onMouseLeave={handleVideoMouseUp}
|
| 574 |
-
onTimeUpdate={handleTimeUpdate}
|
| 575 |
-
/>
|
| 576 |
-
{selection && (
|
| 577 |
-
<div
|
| 578 |
-
className="selection-area"
|
| 579 |
-
style={{
|
| 580 |
-
left: `${selection.x}px`,
|
| 581 |
-
top: `${selection.y}px`,
|
| 582 |
-
width: `${selection.width}px`,
|
| 583 |
-
height: `${selection.height}px`
|
| 584 |
-
}}
|
| 585 |
-
/>
|
| 586 |
-
)}
|
| 587 |
-
<div className="video-controls">
|
| 588 |
-
<button onClick={handlePlayPause} className="text-white hover:text-gray-300">
|
| 589 |
-
<i className={`fas ${videoRef.current?.paused ? 'fa-play' : 'fa-pause'}`}></i>
|
| 590 |
-
</button>
|
| 591 |
-
<input
|
| 592 |
-
type="range"
|
| 593 |
-
min="0"
|
| 594 |
-
max={videoDuration || 100}
|
| 595 |
-
value={currentTime}
|
| 596 |
-
onChange={handleSeek}
|
| 597 |
-
className="flex-1 mx-2"
|
| 598 |
-
/>
|
| 599 |
-
<span className="text-sm text-gray-300">
|
| 600 |
-
{Math.floor(currentTime)}s / {Math.floor(videoDuration)}s
|
| 601 |
-
</span>
|
| 602 |
-
</div>
|
| 603 |
-
</div>
|
| 604 |
-
) : (
|
| 605 |
-
<div className="flex items-center justify-center h-96 bg-gray-800 rounded-lg">
|
| 606 |
-
<div className="text-center">
|
| 607 |
-
<i className="fas fa-video text-6xl text-gray-600 mb-4"></i>
|
| 608 |
-
<p className="text-gray-400">Upload a video to get started</p>
|
| 609 |
-
</div>
|
| 610 |
-
</div>
|
| 611 |
-
)}
|
| 612 |
-
</div>
|
| 613 |
-
|
| 614 |
-
{/* Result Display */}
|
| 615 |
-
{resultVideo && (
|
| 616 |
-
<div className="mt-6 fade-in">
|
| 617 |
-
<h3 className="text-lg font-semibold mb-3">Result</h3>
|
| 618 |
-
<div className="relative">
|
| 619 |
-
<video
|
| 620 |
-
src={resultVideo}
|
| 621 |
-
className="w-full rounded-lg shadow-lg"
|
| 622 |
-
controls
|
| 623 |
-
/>
|
| 624 |
-
<div className="absolute top-2 right-2 bg-green-500 text-white px-3 py-1 rounded-full text-sm">
|
| 625 |
-
<i className="fas fa-check mr-1"></i>
|
| 626 |
-
Generated
|
| 627 |
-
</div>
|
| 628 |
-
</div>
|
| 629 |
-
</div>
|
| 630 |
-
)}
|
| 631 |
-
</div>
|
| 632 |
-
|
| 633 |
-
{/* Status Bar */}
|
| 634 |
-
<div className="mt-4 glass-effect rounded-xl p-4">
|
| 635 |
-
<div className="flex items-center justify-between text-sm">
|
| 636 |
-
<div className="flex items-center space-x-4">
|
| 637 |
-
<span className="text-gray-400">Status:</span>
|
| 638 |
-
<span className="text-green-400">
|
| 639 |
-
{isProcessing ? 'Processing...' :
|
| 640 |
-
resultVideo ? 'Ready' :
|
| 641 |
-
selectedVideo ? 'Video loaded' : 'Waiting for input'}
|
| 642 |
-
</span>
|
| 643 |
-
</div>
|
| 644 |
-
{selection && (
|
| 645 |
-
<div className="text-gray-400">
|
| 646 |
-
Selection: {Math.round(selection.width)} × {Math.round(selection.height)}px
|
| 647 |
-
</div>
|
| 648 |
-
)}
|
| 649 |
-
</div>
|
| 650 |
-
</div>
|
| 651 |
-
</div>
|
| 652 |
-
</div>
|
| 653 |
-
</div>
|
| 654 |
|
| 655 |
-
|
| 656 |
-
|
| 657 |
-
|
| 658 |
-
|
| 659 |
-
|
| 660 |
-
|
| 661 |
-
|
| 662 |
-
|
| 663 |
-
|
|
|
|
|
|
|
| 664 |
|
| 665 |
-
|
| 666 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 667 |
</body>
|
| 668 |
-
|
| 669 |
</html>
|
|
|
|
| 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>3D Multi-frame Fusion for Video Stabilization - CVPR 2024</title>
|
| 7 |
+
|
| 8 |
+
<!-- Google Fonts -->
|
| 9 |
+
<link rel="preconnect" href="https://fonts.googleapis.com">
|
| 10 |
+
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
| 11 |
+
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&family=Merriweather:ital,wght@0,300;0,400;0,700;1,400&display=swap" rel="stylesheet">
|
| 12 |
+
|
| 13 |
+
<!-- FontAwesome -->
|
| 14 |
+
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
|
| 15 |
+
|
| 16 |
+
<style>
|
| 17 |
+
:root {
|
| 18 |
+
--primary-color: #2563eb;
|
| 19 |
+
--secondary-color: #1e40af;
|
| 20 |
+
--text-color: #1f2937;
|
| 21 |
+
--text-light: #4b5563;
|
| 22 |
+
--bg-color: #ffffff;
|
| 23 |
+
--bg-alt: #f3f4f6;
|
| 24 |
+
--border-radius: 12px;
|
| 25 |
+
--shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06);
|
| 26 |
+
--shadow-lg: 0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05);
|
| 27 |
+
--max-width: 1000px;
|
| 28 |
+
}
|
| 29 |
+
|
| 30 |
+
* {
|
| 31 |
+
box-sizing: border-box;
|
| 32 |
+
margin: 0;
|
| 33 |
+
padding: 0;
|
| 34 |
+
}
|
| 35 |
+
|
| 36 |
+
body {
|
| 37 |
+
font-family: 'Inter', sans-serif;
|
| 38 |
+
color: var(--text-color);
|
| 39 |
+
line-height: 1.6;
|
| 40 |
+
background-color: var(--bg-color);
|
| 41 |
+
overflow-x: hidden;
|
| 42 |
+
}
|
| 43 |
+
|
| 44 |
+
/* Header / AnyCoder Link */
|
| 45 |
+
.top-bar {
|
| 46 |
+
background: #111827;
|
| 47 |
+
color: white;
|
| 48 |
+
text-align: center;
|
| 49 |
+
padding: 0.5rem;
|
| 50 |
+
font-size: 0.875rem;
|
| 51 |
+
}
|
| 52 |
+
.top-bar a {
|
| 53 |
+
color: #60a5fa;
|
| 54 |
+
text-decoration: none;
|
| 55 |
+
font-weight: 500;
|
| 56 |
+
transition: color 0.2s;
|
| 57 |
+
}
|
| 58 |
+
.top-bar a:hover {
|
| 59 |
+
color: #93c5fd;
|
| 60 |
+
text-decoration: underline;
|
| 61 |
+
}
|
| 62 |
+
|
| 63 |
+
/* Layout Container */
|
| 64 |
+
.container {
|
| 65 |
+
max-width: var(--max-width);
|
| 66 |
+
margin: 0 auto;
|
| 67 |
+
padding: 2rem 1.5rem;
|
| 68 |
+
}
|
| 69 |
+
|
| 70 |
+
/* Hero Section */
|
| 71 |
+
header {
|
| 72 |
+
text-align: center;
|
| 73 |
+
padding: 3rem 0;
|
| 74 |
+
animation: fadeIn 0.8s ease-out;
|
| 75 |
+
}
|
| 76 |
+
|
| 77 |
+
h1 {
|
| 78 |
+
font-size: clamp(2rem, 5vw, 3rem);
|
| 79 |
+
font-weight: 800;
|
| 80 |
+
line-height: 1.1;
|
| 81 |
+
margin-bottom: 1rem;
|
| 82 |
+
background: linear-gradient(to right, #111827, #374151);
|
| 83 |
+
-webkit-background-clip: text;
|
| 84 |
+
-webkit-text-fill-color: transparent;
|
| 85 |
+
}
|
| 86 |
+
|
| 87 |
+
.conference {
|
| 88 |
+
display: inline-block;
|
| 89 |
+
background: #dbeafe;
|
| 90 |
+
color: #1e40af;
|
| 91 |
+
font-weight: 600;
|
| 92 |
+
padding: 0.25rem 0.75rem;
|
| 93 |
+
border-radius: 9999px;
|
| 94 |
+
margin-bottom: 1.5rem;
|
| 95 |
+
font-size: 1.1rem;
|
| 96 |
+
}
|
| 97 |
+
|
| 98 |
+
.authors {
|
| 99 |
+
display: flex;
|
| 100 |
+
flex-wrap: wrap;
|
| 101 |
+
justify-content: center;
|
| 102 |
+
gap: 1.5rem;
|
| 103 |
+
margin-bottom: 1.5rem;
|
| 104 |
+
font-size: 1.1rem;
|
| 105 |
+
}
|
| 106 |
+
|
| 107 |
+
.author a {
|
| 108 |
+
color: var(--primary-color);
|
| 109 |
+
text-decoration: none;
|
| 110 |
+
transition: color 0.2s;
|
| 111 |
+
}
|
| 112 |
+
.author a:hover {
|
| 113 |
+
color: var(--secondary-color);
|
| 114 |
+
text-decoration: underline;
|
| 115 |
+
}
|
| 116 |
+
|
| 117 |
+
.affiliations {
|
| 118 |
+
color: var(--text-light);
|
| 119 |
+
font-size: 0.95rem;
|
| 120 |
+
margin-bottom: 2rem;
|
| 121 |
+
}
|
| 122 |
+
|
| 123 |
+
/* Action Buttons */
|
| 124 |
+
.actions {
|
| 125 |
+
display: flex;
|
| 126 |
+
justify-content: center;
|
| 127 |
+
gap: 1rem;
|
| 128 |
+
flex-wrap: wrap;
|
| 129 |
+
margin-bottom: 3rem;
|
| 130 |
+
}
|
| 131 |
+
|
| 132 |
+
.btn {
|
| 133 |
+
display: inline-flex;
|
| 134 |
+
align-items: center;
|
| 135 |
+
gap: 0.5rem;
|
| 136 |
+
background-color: #111827;
|
| 137 |
+
color: white;
|
| 138 |
+
padding: 0.75rem 1.5rem;
|
| 139 |
+
border-radius: 9999px;
|
| 140 |
+
text-decoration: none;
|
| 141 |
+
font-weight: 500;
|
| 142 |
+
transition: all 0.2s;
|
| 143 |
+
box-shadow: var(--shadow);
|
| 144 |
+
}
|
| 145 |
+
|
| 146 |
+
.btn:hover {
|
| 147 |
+
transform: translateY(-2px);
|
| 148 |
+
box-shadow: var(--shadow-lg);
|
| 149 |
+
background-color: #1f2937;
|
| 150 |
+
}
|
| 151 |
+
|
| 152 |
+
.btn.secondary {
|
| 153 |
+
background-color: white;
|
| 154 |
+
color: var(--text-color);
|
| 155 |
+
border: 1px solid #e5e7eb;
|
| 156 |
+
}
|
| 157 |
+
.btn.secondary:hover {
|
| 158 |
+
background-color: #f9fafb;
|
| 159 |
+
}
|
| 160 |
+
|
| 161 |
+
/* Teaser Video */
|
| 162 |
+
.teaser {
|
| 163 |
+
width: 100%;
|
| 164 |
+
border-radius: var(--border-radius);
|
| 165 |
+
overflow: hidden;
|
| 166 |
+
box-shadow: var(--shadow-lg);
|
| 167 |
+
background: #000;
|
| 168 |
+
aspect-ratio: 16/9;
|
| 169 |
+
position: relative;
|
| 170 |
+
margin-bottom: 3rem;
|
| 171 |
+
}
|
| 172 |
+
|
| 173 |
+
.teaser-placeholder {
|
| 174 |
+
width: 100%;
|
| 175 |
+
height: 100%;
|
| 176 |
+
display: flex;
|
| 177 |
+
align-items: center;
|
| 178 |
+
justify-content: center;
|
| 179 |
+
background: linear-gradient(45deg, #1f2937, #111827);
|
| 180 |
+
color: white;
|
| 181 |
+
flex-direction: column;
|
| 182 |
+
}
|
| 183 |
+
|
| 184 |
+
/* Abstract */
|
| 185 |
+
.abstract-section {
|
| 186 |
+
background: var(--bg-alt);
|
| 187 |
+
padding: 2.5rem;
|
| 188 |
+
border-radius: var(--border-radius);
|
| 189 |
+
margin-bottom: 3rem;
|
| 190 |
+
}
|
| 191 |
+
|
| 192 |
+
.section-title {
|
| 193 |
+
text-align: center;
|
| 194 |
+
font-size: 1.75rem;
|
| 195 |
+
font-weight: 700;
|
| 196 |
+
margin-bottom: 1.5rem;
|
| 197 |
+
position: relative;
|
| 198 |
+
display: inline-block;
|
| 199 |
+
left: 50%;
|
| 200 |
+
transform: translateX(-50%);
|
| 201 |
+
}
|
| 202 |
+
|
| 203 |
+
.section-title::after {
|
| 204 |
+
content: '';
|
| 205 |
+
display: block;
|
| 206 |
+
width: 60%;
|
| 207 |
+
height: 3px;
|
| 208 |
+
background: var(--primary-color);
|
| 209 |
+
margin: 0.5rem auto 0;
|
| 210 |
+
border-radius: 2px;
|
| 211 |
+
}
|
| 212 |
+
|
| 213 |
+
.abstract-text {
|
| 214 |
+
text-align: justify;
|
| 215 |
+
font-size: 1.05rem;
|
| 216 |
+
color: var(--text-light);
|
| 217 |
+
}
|
| 218 |
+
|
| 219 |
+
/* Method Section */
|
| 220 |
+
.method-section {
|
| 221 |
+
margin-bottom: 4rem;
|
| 222 |
+
}
|
| 223 |
+
|
| 224 |
+
.method-image {
|
| 225 |
+
width: 100%;
|
| 226 |
+
height: auto;
|
| 227 |
+
border-radius: var(--border-radius);
|
| 228 |
+
margin: 1.5rem 0;
|
| 229 |
+
border: 1px solid #e5e7eb;
|
| 230 |
+
box-shadow: var(--shadow);
|
| 231 |
+
}
|
| 232 |
+
|
| 233 |
+
/* Comparison Slider */
|
| 234 |
+
.comparison-section {
|
| 235 |
+
margin-bottom: 4rem;
|
| 236 |
+
}
|
| 237 |
+
|
| 238 |
+
.comparison-container {
|
| 239 |
+
position: relative;
|
| 240 |
+
width: 100%;
|
| 241 |
+
max-width: 800px;
|
| 242 |
+
aspect-ratio: 16/9;
|
| 243 |
+
margin: 0 auto;
|
| 244 |
+
border-radius: var(--border-radius);
|
| 245 |
+
overflow: hidden;
|
| 246 |
+
box-shadow: var(--shadow-lg);
|
| 247 |
+
cursor: ew-resize;
|
| 248 |
+
}
|
| 249 |
+
|
| 250 |
+
.img-comp-img {
|
| 251 |
+
position: absolute;
|
| 252 |
+
width: 100%;
|
| 253 |
+
height: 100%;
|
| 254 |
+
overflow: hidden;
|
| 255 |
+
}
|
| 256 |
+
|
| 257 |
+
.img-comp-img img {
|
| 258 |
+
display: block;
|
| 259 |
+
width: 100%;
|
| 260 |
+
height: 100%;
|
| 261 |
+
object-fit: cover;
|
| 262 |
+
}
|
| 263 |
+
|
| 264 |
+
.img-comp-overlay {
|
| 265 |
+
width: 50%; /* Initial position */
|
| 266 |
+
z-index: 2;
|
| 267 |
+
border-right: 3px solid white;
|
| 268 |
+
}
|
| 269 |
+
|
| 270 |
+
.slider-handle {
|
| 271 |
+
position: absolute;
|
| 272 |
+
top: 50%;
|
| 273 |
+
left: 50%; /* Should match overlay width */
|
| 274 |
+
transform: translate(-50%, -50%);
|
| 275 |
+
width: 40px;
|
| 276 |
+
height: 40px;
|
| 277 |
+
background: white;
|
| 278 |
+
border-radius: 50%;
|
| 279 |
+
z-index: 10;
|
| 280 |
+
display: flex;
|
| 281 |
+
align-items: center;
|
| 282 |
+
justify-content: center;
|
| 283 |
+
box-shadow: 0 0 10px rgba(0,0,0,0.5);
|
| 284 |
+
pointer-events: none; /* Let clicks pass to container */
|
| 285 |
+
}
|
| 286 |
+
|
| 287 |
+
.slider-labels {
|
| 288 |
+
position: absolute;
|
| 289 |
+
bottom: 1rem;
|
| 290 |
+
width: 100%;
|
| 291 |
+
display: flex;
|
| 292 |
+
justify-content: space-between;
|
| 293 |
+
padding: 0 1.5rem;
|
| 294 |
+
z-index: 20;
|
| 295 |
+
pointer-events: none;
|
| 296 |
+
color: white;
|
| 297 |
+
font-weight: 700;
|
| 298 |
+
text-shadow: 0 2px 4px rgba(0,0,0,0.8);
|
| 299 |
+
}
|
| 300 |
+
|
| 301 |
+
/* Results Grid */
|
| 302 |
+
.results-grid {
|
| 303 |
+
display: grid;
|
| 304 |
+
grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
|
| 305 |
+
gap: 1.5rem;
|
| 306 |
+
margin-bottom: 3rem;
|
| 307 |
+
}
|
| 308 |
+
|
| 309 |
+
.result-item {
|
| 310 |
+
background: white;
|
| 311 |
+
border-radius: var(--border-radius);
|
| 312 |
+
overflow: hidden;
|
| 313 |
+
border: 1px solid #e5e7eb;
|
| 314 |
+
}
|
| 315 |
+
|
| 316 |
+
.result-video-placeholder {
|
| 317 |
+
width: 100%;
|
| 318 |
+
aspect-ratio: 16/9;
|
| 319 |
+
background: #eee;
|
| 320 |
+
display: flex;
|
| 321 |
+
align-items: center;
|
| 322 |
+
justify-content: center;
|
| 323 |
+
color: #999;
|
| 324 |
+
position: relative;
|
| 325 |
+
}
|
| 326 |
+
|
| 327 |
+
.result-video-placeholder::before {
|
| 328 |
+
content: '\f04b';
|
| 329 |
+
font-family: "Font Awesome 6 Free";
|
| 330 |
+
font-weight: 900;
|
| 331 |
+
font-size: 2rem;
|
| 332 |
+
opacity: 0.5;
|
| 333 |
+
}
|
| 334 |
+
|
| 335 |
+
.result-caption {
|
| 336 |
+
padding: 1rem;
|
| 337 |
+
text-align: center;
|
| 338 |
+
font-weight: 500;
|
| 339 |
+
font-size: 0.9rem;
|
| 340 |
+
}
|
| 341 |
+
|
| 342 |
+
/* BibTeX */
|
| 343 |
+
.bibtex-section {
|
| 344 |
+
background: #f8fafc;
|
| 345 |
+
padding: 2rem;
|
| 346 |
+
border-radius: var(--border-radius);
|
| 347 |
+
border: 1px solid #e2e8f0;
|
| 348 |
+
}
|
| 349 |
+
|
| 350 |
+
pre {
|
| 351 |
+
background: #1e293b;
|
| 352 |
+
color: #e2e8f0;
|
| 353 |
+
padding: 1.5rem;
|
| 354 |
+
border-radius: 8px;
|
| 355 |
+
overflow-x: auto;
|
| 356 |
+
font-family: 'Courier New', Courier, monospace;
|
| 357 |
+
font-size: 0.85rem;
|
| 358 |
+
position: relative;
|
| 359 |
+
}
|
| 360 |
+
|
| 361 |
+
.copy-btn {
|
| 362 |
+
position: absolute;
|
| 363 |
+
top: 0.5rem;
|
| 364 |
+
right: 0.5rem;
|
| 365 |
+
background: rgba(255,255,255,0.1);
|
| 366 |
+
border: none;
|
| 367 |
+
color: white;
|
| 368 |
+
padding: 0.25rem 0.5rem;
|
| 369 |
+
border-radius: 4px;
|
| 370 |
+
cursor: pointer;
|
| 371 |
+
font-size: 0.75rem;
|
| 372 |
+
}
|
| 373 |
+
.copy-btn:hover {
|
| 374 |
+
background: rgba(255,255,255,0.2);
|
| 375 |
+
}
|
| 376 |
+
|
| 377 |
+
/* Footer */
|
| 378 |
+
footer {
|
| 379 |
+
text-align: center;
|
| 380 |
+
padding: 3rem 0;
|
| 381 |
+
color: var(--text-light);
|
| 382 |
+
font-size: 0.9rem;
|
| 383 |
+
border-top: 1px solid #e5e7eb;
|
| 384 |
+
margin-top: 3rem;
|
| 385 |
+
}
|
| 386 |
+
|
| 387 |
+
/* Animations */
|
| 388 |
+
@keyframes fadeIn {
|
| 389 |
+
from { opacity: 0; transform: translateY(20px); }
|
| 390 |
+
to { opacity: 1; transform: translateY(0); }
|
| 391 |
+
}
|
| 392 |
+
|
| 393 |
+
/* Responsive */
|
| 394 |
+
@media (max-width: 768px) {
|
| 395 |
+
h1 { font-size: 2rem; }
|
| 396 |
+
.authors { flex-direction: column; gap: 0.5rem; align-items: center; }
|
| 397 |
+
.slider-labels { font-size: 0.8rem; }
|
| 398 |
+
}
|
| 399 |
+
</style>
|
| 400 |
</head>
|
|
|
|
| 401 |
<body>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 402 |
|
| 403 |
+
<div class="top-bar">
|
| 404 |
+
Built with anycoder — <a href="https://huggingface.co/spaces/akhaliq/anycoder" target="_blank">View on Hugging Face</a>
|
| 405 |
+
</div>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 406 |
|
| 407 |
+
<div class="container">
|
| 408 |
+
|
| 409 |
+
<!-- Header -->
|
| 410 |
+
<header>
|
| 411 |
+
<div class="conference">CVPR 2024</div>
|
| 412 |
+
<h1>3D Multi-frame Fusion for<br>Video Stabilization</h1>
|
| 413 |
|
| 414 |
+
<div class="authors">
|
| 415 |
+
<div class="author"><a href="#">Alex Johnson*</a></div>
|
| 416 |
+
<div class="author"><a href="#">Sarah Chen*</a></div>
|
| 417 |
+
<div class="author"><a href="#">Michael Roberts</a></div>
|
| 418 |
+
<div class="author"><a href="#">Emily Davis</a></div>
|
| 419 |
+
<div class="author"><a href="#">David Zhang</a></div>
|
| 420 |
+
</div>
|
| 421 |
|
| 422 |
+
<div class="affiliations">
|
| 423 |
+
University of Computer Vision, Tech Institute AI Lab<br>
|
| 424 |
+
*Equal Contribution
|
| 425 |
+
</div>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 426 |
|
| 427 |
+
<div class="actions">
|
| 428 |
+
<a href="#" class="btn"><i class="fa-regular fa-file-pdf"></i> Paper</a>
|
| 429 |
+
<a href="#" class="btn"><i class="fa-brands fa-arxiv"></i> arXiv</a>
|
| 430 |
+
<a href="#" class="btn secondary"><i class="fa-brands fa-github"></i> Code</a>
|
| 431 |
+
<a href="#" class="btn secondary"><i class="fa-solid fa-database"></i> Dataset</a>
|
| 432 |
+
</div>
|
| 433 |
+
</header>
|
| 434 |
+
|
| 435 |
+
<!-- Teaser Video -->
|
| 436 |
+
<div class="teaser">
|
| 437 |
+
<!-- In a real scenario, this would be a <video> tag -->
|
| 438 |
+
<div class="teaser-placeholder">
|
| 439 |
+
<i class="fa-solid fa-film fa-3x" style="margin-bottom: 1rem;"></i>
|
| 440 |
+
<h2>Teaser Video</h2>
|
| 441 |
+
<p>Demonstrating robust 3D stabilization across dynamic scenes</p>
|
| 442 |
+
<div style="margin-top: 1rem; font-size: 0.8rem; opacity: 0.7;">(Video playback simulated)</div>
|
| 443 |
+
</div>
|
| 444 |
+
</div>
|
| 445 |
+
|
| 446 |
+
<!-- Abstract -->
|
| 447 |
+
<section class="abstract-section">
|
| 448 |
+
<h2 class="section-title">Abstract</h2>
|
| 449 |
+
<p class="abstract-text">
|
| 450 |
+
Video stabilization remains a challenging problem, particularly for handheld footage with large motion and dynamic content. Traditional 2D methods often suffer from distortion and cropping, while existing 3D methods struggle with robustness in complex environments. We present a novel <strong>3D Multi-frame Fusion</strong> framework that leverages scene geometry and temporal consistency to generate high-quality stabilized video. By integrating a dense depth prior with a multi-frame optimization strategy, our method effectively decouples camera motion from scene dynamics. We introduce a differentiable warping module that synthesizes full-frame stabilized views by fusing information from adjacent frames, significantly reducing the need for cropping. Extensive experiments on public benchmarks demonstrate that our approach outperforms state-of-the-art methods in both stability and visual quality metrics.
|
| 451 |
+
</p>
|
| 452 |
+
</section>
|
| 453 |
+
|
| 454 |
+
<!-- Method -->
|
| 455 |
+
<section class="method-section">
|
| 456 |
+
<h2 class="section-title">Methodology</h2>
|
| 457 |
+
<div style="text-align: center; margin-bottom: 1rem;">
|
| 458 |
+
<svg width="100%" height="300" viewBox="0 0 800 300" style="background: white; border-radius: 12px; border: 1px solid #eee;">
|
| 459 |
+
<!-- Simplified SVG representation of a pipeline -->
|
| 460 |
+
<rect x="50" y="100" width="100" height="100" rx="10" fill="#dbeafe" stroke="#2563eb" stroke-width="2"/>
|
| 461 |
+
<text x="100" y="155" text-anchor="middle" font-size="14" fill="#1e40af">Input Frames</text>
|
| 462 |
+
|
| 463 |
+
<path d="M160 150 L200 150" stroke="#9ca3af" stroke-width="2" marker-end="url(#arrow)"/>
|
| 464 |
+
|
| 465 |
+
<rect x="210" y="80" width="120" height="140" rx="10" fill="#fef3c7" stroke="#d97706" stroke-width="2"/>
|
| 466 |
+
<text x="270" y="145" text-anchor="middle" font-size="14" fill="#92400e">3D Motion</text>
|
| 467 |
+
<text x="270" y="165" text-anchor="middle" font-size="14" fill="#92400e">Estimation</text>
|
| 468 |
+
|
| 469 |
+
<path d="M340 150 L380 150" stroke="#9ca3af" stroke-width="2" marker-end="url(#arrow)"/>
|
| 470 |
+
|
| 471 |
+
<rect x="390" y="80" width="140" height="140" rx="10" fill="#d1fae5" stroke="#059669" stroke-width="2"/>
|
| 472 |
+
<text x="460" y="145" text-anchor="middle" font-size="14" fill="#065f46">Multi-frame</text>
|
| 473 |
+
<text x="460" y="165" text-anchor="middle" font-size="14" fill="#065f46">Fusion Module</text>
|
| 474 |
+
|
| 475 |
+
<path d="M540 150 L580 150" stroke="#9ca3af" stroke-width="2" marker-end="url(#arrow)"/>
|
| 476 |
+
|
| 477 |
+
<rect x="590" y="100" width="120" height="100" rx="10" fill="#f3e8ff" stroke="#7c3aed" stroke-width="2"/>
|
| 478 |
+
<text x="650" y="155" text-anchor="middle" font-size="14" fill="#5b21b6">Stabilized Output</text>
|
| 479 |
+
|
| 480 |
+
<defs>
|
| 481 |
+
<marker id="arrow" markerWidth="10" markerHeight="10" refX="9" refY="3" orient="auto" markerUnits="strokeWidth">
|
| 482 |
+
<path d="M0,0 L0,6 L9,3 z" fill="#9ca3af" />
|
| 483 |
+
</marker>
|
| 484 |
+
</defs>
|
| 485 |
+
</svg>
|
| 486 |
+
</div>
|
| 487 |
+
<p class="abstract-text">
|
| 488 |
+
Our pipeline consists of three main stages: (1) <strong>Robust 3D Trajectory Estimation</strong> using a learned depth prior to handle dynamic objects; (2) <strong>Optimal Path Planning</strong> that smooths the camera trajectory while respecting field-of-view constraints; and (3) <strong>Neural Multi-frame Rendering</strong>, which fuses pixels from temporal neighbors to fill in missing regions caused by stabilization warping, ensuring full-frame output without cropping.
|
| 489 |
+
</p>
|
| 490 |
+
</section>
|
| 491 |
+
|
| 492 |
+
<!-- Interactive Comparison -->
|
| 493 |
+
<section class="comparison-section">
|
| 494 |
+
<h2 class="section-title">Visual Comparison</h2>
|
| 495 |
+
<p style="text-align: center; margin-bottom: 2rem; color: var(--text-light);">Drag the slider to compare Input vs. Our Stabilized Result.</p>
|
| 496 |
|
| 497 |
+
<div class="comparison-container" id="comparison1">
|
| 498 |
+
<div class="img-comp-img">
|
| 499 |
+
<!-- Placeholder for Stabilized Result (Right side reveals this) -->
|
| 500 |
+
<div style="width: 100%; height: 100%; background: url('https://images.unsplash.com/photo-1492691527719-9d1e07e534b4?ixlib=rb-4.0.3&auto=format&fit=crop&w=1000&q=80') center/cover;">
|
| 501 |
+
<div style="position: absolute; top: 10px; right: 10px; background: rgba(0,0,0,0.7); color: #4ade80; padding: 5px 10px; border-radius: 4px; font-weight: bold;">Ours (Stabilized)</div>
|
| 502 |
+
</div>
|
| 503 |
+
</div>
|
| 504 |
+
<div class="img-comp-img img-comp-overlay">
|
| 505 |
+
<!-- Placeholder for Input (Left side) -->
|
| 506 |
+
<div style="width: 100%; height: 100%; background: url('https://images.unsplash.com/photo-1492691527719-9d1e07e534b4?ixlib=rb-4.0.3&auto=format&fit=crop&w=1000&q=80') center/cover; filter: blur(4px) hue-rotate(15deg);">
|
| 507 |
+
<div style="position: absolute; top: 10px; left: 10px; background: rgba(0,0,0,0.7); color: white; padding: 5px 10px; border-radius: 4px; font-weight: bold;">Input (Shaky)</div>
|
| 508 |
+
</div>
|
| 509 |
+
</div>
|
| 510 |
+
<div class="slider-handle">
|
| 511 |
+
<i class="fa-solid fa-arrows-left-right" style="color: #333;"></i>
|
| 512 |
+
</div>
|
| 513 |
+
</div>
|
| 514 |
+
</section>
|
| 515 |
+
|
| 516 |
+
<!-- Qualitative Results -->
|
| 517 |
+
<section>
|
| 518 |
+
<h2 class="section-title">Qualitative Results</h2>
|
| 519 |
+
<div class="results-grid">
|
| 520 |
+
<div class="result-item">
|
| 521 |
+
<div class="result-video-placeholder"></div>
|
| 522 |
+
<div class="result-caption">Walking Scene (Low Light)</div>
|
| 523 |
+
</div>
|
| 524 |
+
<div class="result-item">
|
| 525 |
+
<div class="result-video-placeholder"></div>
|
| 526 |
+
<div class="result-caption">Running Scene (Dynamic Objects)</div>
|
| 527 |
+
</div>
|
| 528 |
+
<div class="result-item">
|
| 529 |
+
<div class="result-video-placeholder"></div>
|
| 530 |
+
<div class="result-caption">Panning Shot (Parallax)</div>
|
| 531 |
+
</div>
|
| 532 |
+
</div>
|
| 533 |
+
</section>
|
| 534 |
+
|
| 535 |
+
<!-- BibTeX -->
|
| 536 |
+
<section class="bibtex-section">
|
| 537 |
+
<h2 class="section-title" style="margin-bottom: 1rem;">Citation</h2>
|
| 538 |
+
<pre><button class="copy-btn" onclick="copyBibtex()">Copy</button>@inproceedings{johnson20243dmultiframe,
|
| 539 |
+
title={3D Multi-frame Fusion for Video Stabilization},
|
| 540 |
+
author={Johnson, Alex and Chen, Sarah and Roberts, Michael and Davis, Emily and Zhang, David},
|
| 541 |
+
booktitle={Proceedings of the IEEE/CVF Conference on Computer Vision and Pattern Recognition (CVPR)},
|
| 542 |
+
year={2024}
|
| 543 |
+
}</pre>
|
| 544 |
+
</section>
|
| 545 |
+
|
| 546 |
+
<footer>
|
| 547 |
+
<div class="social-links" style="margin-bottom: 1rem;">
|
| 548 |
+
<a href="#" style="color: inherit; margin: 0 10px; font-size: 1.2rem;"><i class="fa-brands fa-twitter"></i></a>
|
| 549 |
+
<a href="#" style="color: inherit; margin: 0 10px; font-size: 1.2rem;"><i class="fa-brands fa-github"></i></a>
|
| 550 |
+
<a href="#" style="color: inherit; margin: 0 10px; font-size: 1.2rem;"><i class="fa-solid fa-envelope"></i></a>
|
| 551 |
+
</div>
|
| 552 |
+
<p>© 2024 Project Team. All rights reserved.</p>
|
| 553 |
+
<p>The website template is designed for academic project pages.</p>
|
| 554 |
+
</footer>
|
| 555 |
+
|
| 556 |
+
</div>
|
| 557 |
+
|
| 558 |
+
<script>
|
| 559 |
+
// Image Comparison Slider Logic
|
| 560 |
+
function initComparisons() {
|
| 561 |
+
var x, i;
|
| 562 |
+
/* Find all elements with an "overlay" class: */
|
| 563 |
+
x = document.getElementsByClassName("img-comp-overlay");
|
| 564 |
+
for (i = 0; i < x.length; i++) {
|
| 565 |
+
/* Once for each "overlay" element:
|
| 566 |
+
pass the "overlay" element as a parameter when executing the compareImages function: */
|
| 567 |
+
compareImages(x[i]);
|
| 568 |
}
|
|
|
|
| 569 |
|
| 570 |
+
function compareImages(img) {
|
| 571 |
+
var slider, clicked = 0, w, h;
|
| 572 |
+
|
| 573 |
+
/* Get the width and height of the img element */
|
| 574 |
+
w = img.offsetWidth;
|
| 575 |
+
h = img.offsetHeight;
|
| 576 |
+
|
| 577 |
+
/* Set the width of the img element to 50%: */
|
| 578 |
+
img.style.width = (w / 2) + "px";
|
| 579 |
+
|
| 580 |
+
/* Create slider handle: */
|
| 581 |
+
// Note: The handle is already in HTML, we just need to position it based on logic
|
| 582 |
+
// But for pure JS resizing, let's find the handle sibling
|
| 583 |
+
var container = img.parentElement;
|
| 584 |
+
var handle = container.querySelector('.slider-handle');
|
| 585 |
+
|
| 586 |
+
/* Execute a function when the mouse button is pressed: */
|
| 587 |
+
container.addEventListener("mousedown", slideReady);
|
| 588 |
+
/* And another function when the mouse button is released: */
|
| 589 |
+
window.addEventListener("mouseup", slideFinish);
|
| 590 |
+
/* Or touched (for touch screens: */
|
| 591 |
+
container.addEventListener("touchstart", slideReady);
|
| 592 |
+
/* And released (for touch screens: */
|
| 593 |
+
window.addEventListener("touchend", slideFinish);
|
| 594 |
+
|
| 595 |
+
function slideReady(e) {
|
| 596 |
+
/* Prevent any other actions that may occur when moving over the image: */
|
| 597 |
+
e.preventDefault();
|
| 598 |
+
/* The slider is now clicked and ready to move: */
|
| 599 |
+
clicked = 1;
|
| 600 |
+
/* Execute a function when the slider is moved: */
|
| 601 |
+
window.addEventListener("mousemove", slideMove);
|
| 602 |
+
window.addEventListener("touchmove", slideMove);
|
| 603 |
}
|
|
|
|
|
|
|
| 604 |
|
| 605 |
+
function slideFinish() {
|
| 606 |
+
/* The slider is no longer clicked: */
|
| 607 |
+
clicked = 0;
|
| 608 |
+
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 609 |
|
| 610 |
+
function slideMove(e) {
|
| 611 |
+
var pos;
|
| 612 |
+
/* If the slider is no longer clicked, exit this function: */
|
| 613 |
+
if (clicked == 0) return false;
|
| 614 |
+
/* Get the cursor's x position: */
|
| 615 |
+
pos = getCursorPos(e);
|
| 616 |
+
/* Prevent the slider from being positioned outside the image: */
|
| 617 |
+
if (pos < 0) pos = 0;
|
| 618 |
+
if (pos > w) pos = w;
|
| 619 |
+
/* Execute a function that will resize the overlay image according to the cursor: */
|
| 620 |
+
slide(pos);
|
| 621 |
+
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 622 |
|
| 623 |
+
function getCursorPos(e) {
|
| 624 |
+
var a, x = 0;
|
| 625 |
+
e = (e.changedTouches) ? e.changedTouches[0] : e;
|
| 626 |
+
/* Get the x positions of the image: */
|
| 627 |
+
a = img.getBoundingClientRect();
|
| 628 |
+
/* Calculate the cursor's x coordinate, relative to the image: */
|
| 629 |
+
x = e.pageX - a.left;
|
| 630 |
+
/* Consider any page scrolling: */
|
| 631 |
+
x = x - window.pageXOffset;
|
| 632 |
+
return x;
|
| 633 |
+
}
|
| 634 |
|
| 635 |
+
function slide(x) {
|
| 636 |
+
/* Resize the image: */
|
| 637 |
+
img.style.width = x + "px";
|
| 638 |
+
/* Position the slider: */
|
| 639 |
+
handle.style.left = (x / w * 100) + "%";
|
| 640 |
+
}
|
| 641 |
+
}
|
| 642 |
+
}
|
| 643 |
+
|
| 644 |
+
// Initialize comparison sliders
|
| 645 |
+
initComparisons();
|
| 646 |
+
|
| 647 |
+
// Copy BibTeX Function
|
| 648 |
+
function copyBibtex() {
|
| 649 |
+
const text = document.querySelector('pre').innerText.replace('Copy', ''); // Remove button text
|
| 650 |
+
navigator.clipboard.writeText(text).then(() => {
|
| 651 |
+
const btn = document.querySelector('.copy-btn');
|
| 652 |
+
const originalText = btn.innerText;
|
| 653 |
+
btn.innerText = 'Copied!';
|
| 654 |
+
setTimeout(() => {
|
| 655 |
+
btn.innerText = originalText;
|
| 656 |
+
}, 2000);
|
| 657 |
+
});
|
| 658 |
+
}
|
| 659 |
+
</script>
|
| 660 |
</body>
|
|
|
|
| 661 |
</html>
|