Spaces:
Running
Running
| <html lang="en"> | |
| <head> | |
| <meta charset="UTF-8"> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
| <title>Cinematic AI Studio | Professional Video Pipeline</title> | |
| <!-- Fonts --> | |
| <link rel="preconnect" href="https://fonts.googleapis.com"> | |
| <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin> | |
| <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"> | |
| <!-- Icons (Remix Icon) --> | |
| <link href="https://cdn.jsdelivr.net/npm/remixicon@3.5.0/fonts/remixicon.css" rel="stylesheet"> | |
| <style> | |
| :root { | |
| /* Color Palette - Dark Cyberpunk/Professional */ | |
| --bg-body: #0f1115; | |
| --bg-panel: #161b22; | |
| --bg-input: #0d1117; | |
| --border-color: #30363d; | |
| --primary-accent: #58a6ff; | |
| --primary-hover: #79c0ff; | |
| --text-main: #c9d1d9; | |
| --text-muted: #8b949e; | |
| --success: #238636; | |
| --warning: #d29922; | |
| --danger: #f85149; | |
| /* Spacing & Radius */ | |
| --radius-md: 8px; | |
| --radius-lg: 12px; | |
| --space-xs: 4px; | |
| --space-sm: 8px; | |
| --space-md: 16px; | |
| --space-lg: 24px; | |
| /* Transitions */ | |
| --transition-fast: 0.2s ease; | |
| } | |
| * { | |
| box-sizing: border-box; | |
| margin: 0; | |
| padding: 0; | |
| } | |
| body { | |
| font-family: 'Inter', sans-serif; | |
| background-color: var(--bg-body); | |
| color: var(--text-main); | |
| height: 100vh; | |
| display: flex; | |
| flex-direction: column; | |
| overflow: hidden; | |
| } | |
| /* --- Header --- */ | |
| header { | |
| height: 60px; | |
| background-color: var(--bg-panel); | |
| border-bottom: 1px solid var(--border-color); | |
| display: flex; | |
| align-items: center; | |
| justify-content: space-between; | |
| padding: 0 var(--space-lg); | |
| flex-shrink: 0; | |
| } | |
| .brand { | |
| display: flex; | |
| align-items: center; | |
| gap: var(--space-sm); | |
| font-weight: 700; | |
| font-size: 1.1rem; | |
| color: #fff; | |
| } | |
| .brand i { | |
| color: var(--primary-accent); | |
| font-size: 1.4rem; | |
| } | |
| .header-links a { | |
| color: var(--text-muted); | |
| text-decoration: none; | |
| font-size: 0.9rem; | |
| transition: color var(--transition-fast); | |
| display: flex; | |
| align-items: center; | |
| gap: 6px; | |
| } | |
| .header-links a:hover { | |
| color: var(--primary-accent); | |
| } | |
| /* --- Main Layout --- */ | |
| main { | |
| display: grid; | |
| grid-template-columns: 320px 1fr; | |
| flex: 1; | |
| overflow: hidden; | |
| } | |
| /* --- Sidebar / Controls --- */ | |
| aside { | |
| background-color: var(--bg-panel); | |
| border-right: 1px solid var(--border-color); | |
| padding: var(--space-lg); | |
| display: flex; | |
| flex-direction: column; | |
| gap: var(--space-lg); | |
| overflow-y: auto; | |
| } | |
| h2 { | |
| font-size: 0.85rem; | |
| text-transform: uppercase; | |
| letter-spacing: 0.5px; | |
| color: var(--text-muted); | |
| margin-bottom: var(--space-md); | |
| font-weight: 600; | |
| } | |
| .control-group { | |
| display: flex; | |
| flex-direction: column; | |
| gap: var(--space-md); | |
| } | |
| .input-wrapper { | |
| display: flex; | |
| flex-direction: column; | |
| gap: var(--space-xs); | |
| } | |
| label { | |
| font-size: 0.85rem; | |
| color: var(--text-main); | |
| display: flex; | |
| justify-content: space-between; | |
| } | |
| .value-display { | |
| color: var(--primary-accent); | |
| font-family: 'JetBrains Mono', monospace; | |
| font-size: 0.8rem; | |
| } | |
| /* Custom Range Slider */ | |
| input[type="range"] { | |
| -webkit-appearance: none; | |
| width: 100%; | |
| height: 6px; | |
| background: var(--bg-input); | |
| border-radius: 3px; | |
| outline: none; | |
| } | |
| input[type="range"]::-webkit-slider-thumb { | |
| -webkit-appearance: none; | |
| width: 16px; | |
| height: 16px; | |
| background: var(--primary-accent); | |
| border-radius: 50%; | |
| cursor: pointer; | |
| transition: background var(--transition-fast); | |
| } | |
| input[type="range"]::-webkit-slider-thumb:hover { | |
| background: var(--primary-hover); | |
| } | |
| /* Buttons */ | |
| .btn { | |
| border: none; | |
| padding: 12px; | |
| border-radius: var(--radius-md); | |
| font-weight: 600; | |
| cursor: pointer; | |
| transition: all var(--transition-fast); | |
| display: flex; | |
| align-items: center; | |
| justify-content: center; | |
| gap: var(--space-sm); | |
| font-size: 0.95rem; | |
| } | |
| .btn-primary { | |
| background-color: var(--success); | |
| color: #fff; | |
| } | |
| .btn-primary:hover { | |
| background-color: #2ea043; | |
| } | |
| .btn-primary:disabled { | |
| background-color: var(--border-color); | |
| color: var(--text-muted); | |
| cursor: not-allowed; | |
| } | |
| .btn-secondary { | |
| background-color: var(--bg-input); | |
| border: 1px solid var(--border-color); | |
| color: var(--text-main); | |
| } | |
| .btn-secondary:hover { | |
| border-color: var(--text-muted); | |
| } | |
| /* --- Workspace / Preview --- */ | |
| .workspace { | |
| padding: var(--space-lg); | |
| background-color: var(--bg-body); | |
| display: flex; | |
| flex-direction: column; | |
| gap: var(--space-lg); | |
| overflow-y: auto; | |
| position: relative; | |
| } | |
| /* Upload Area */ | |
| .upload-zone { | |
| border: 2px dashed var(--border-color); | |
| border-radius: var(--radius-lg); | |
| background-color: rgba(22, 27, 34, 0.5); | |
| height: 300px; | |
| display: flex; | |
| flex-direction: column; | |
| align-items: center; | |
| justify-content: center; | |
| transition: all var(--transition-fast); | |
| cursor: pointer; | |
| text-align: center; | |
| padding: var(--space-lg); | |
| } | |
| .upload-zone:hover, .upload-zone.dragover { | |
| border-color: var(--primary-accent); | |
| background-color: rgba(88, 166, 255, 0.05); | |
| } | |
| .upload-icon { | |
| font-size: 3rem; | |
| color: var(--text-muted); | |
| margin-bottom: var(--space-md); | |
| } | |
| .upload-text h3 { | |
| font-size: 1.1rem; | |
| margin-bottom: var(--space-xs); | |
| } | |
| .upload-text p { | |
| color: var(--text-muted); | |
| font-size: 0.9rem; | |
| } | |
| input[type="file"] { | |
| display: none; | |
| } | |
| /* Preview Area (Grid) */ | |
| .preview-grid { | |
| display: grid; | |
| grid-template-columns: 1fr 1fr; | |
| gap: var(--space-lg); | |
| height: 100%; | |
| min-height: 400px; | |
| } | |
| .media-container { | |
| background-color: var(--bg-panel); | |
| border: 1px solid var(--border-color); | |
| border-radius: var(--radius-lg); | |
| display: flex; | |
| align-items: center; | |
| justify-content: center; | |
| overflow: hidden; | |
| position: relative; | |
| } | |
| .media-container img, .media-container video { | |
| max-width: 100%; | |
| max-height: 100%; | |
| object-fit: contain; | |
| } | |
| .media-label { | |
| position: absolute; | |
| top: var(--space-sm); | |
| left: var(--space-sm); | |
| background: rgba(0,0,0,0.7); | |
| padding: 4px 8px; | |
| border-radius: 4px; | |
| font-size: 0.75rem; | |
| color: #fff; | |
| backdrop-filter: blur(4px); | |
| } | |
| /* Terminal / Logs */ | |
| .terminal { | |
| background-color: #0d1117; | |
| border: 1px solid var(--border-color); | |
| border-radius: var(--radius-md); | |
| padding: var(--space-md); | |
| font-family: 'JetBrains Mono', monospace; | |
| font-size: 0.8rem; | |
| height: 200px; | |
| overflow-y: auto; | |
| color: var(--text-muted); | |
| display: flex; | |
| flex-direction: column; | |
| gap: 4px; | |
| } | |
| .log-line { | |
| display: flex; | |
| gap: var(--space-sm); | |
| } | |
| .log-time { | |
| color: var(--text-muted); | |
| opacity: 0.6; | |
| } | |
| .log-info { color: var(--text-main); } | |
| .log-success { color: var(--success); } | |
| .log-warn { color: var(--warning); } | |
| .log-error { color: var(--danger); } | |
| /* Progress Bar */ | |
| .progress-container { | |
| width: 100%; | |
| height: 6px; | |
| background-color: var(--bg-input); | |
| border-radius: 3px; | |
| overflow: hidden; | |
| margin-top: var(--space-xs); | |
| display: none; /* Hidden by default */ | |
| } | |
| .progress-bar { | |
| height: 100%; | |
| background-color: var(--primary-accent); | |
| width: 0%; | |
| transition: width 0.3s ease; | |
| } | |
| /* Responsive */ | |
| @media (max-width: 900px) { | |
| main { | |
| grid-template-columns: 1fr; | |
| overflow-y: auto; | |
| } | |
| aside { | |
| border-right: none; | |
| border-bottom: 1px solid var(--border-color); | |
| max-height: 300px; | |
| } | |
| .preview-grid { | |
| grid-template-columns: 1fr; | |
| } | |
| } | |
| </style> | |
| </head> | |
| <body> | |
| <header> | |
| <div class="brand"> | |
| <i class="ri-movie-2-line"></i> | |
| <span>Cinematic AI Studio</span> | |
| </div> | |
| <div class="header-links"> | |
| <a href="https://huggingface.co/spaces/akhaliq/anycoder" target="_blank"> | |
| Built with anycoder <i class="ri-external-link-line"></i> | |
| </a> | |
| </div> | |
| </header> | |
| <main> | |
| <!-- Sidebar Controls --> | |
| <aside> | |
| <div class="control-group"> | |
| <h2>Pipeline Configuration</h2> | |
| <div class="input-wrapper"> | |
| <label>Motion Intensity <span class="value-display" id="val-motion">1.2</span></label> | |
| <input type="range" id="motion" min="0.5" max="2.0" step="0.1" value="1.2"> | |
| </div> | |
| <div class="input-wrapper"> | |
| <label>Animation Pace <span class="value-display" id="val-pace">0.9</span></label> | |
| <input type="range" id="pace" min="0.5" max="1.5" step="0.05" value="0.9"> | |
| </div> | |
| <div class="input-wrapper"> | |
| <label>Upscale Factor <span class="value-display" id="val-upscale">4x</span></label> | |
| <input type="range" id="upscale" min="1" max="4" step="1" value="4"> | |
| </div> | |
| </div> | |
| <div class="control-group"> | |
| <h2>Output Settings</h2> | |
| <div class="input-wrapper"> | |
| <label>FPS <span class="value-display" id="val-fps">25</span></label> | |
| <input type="range" id="fps" min="15" max="60" step="5" value="25"> | |
| </div> | |
| <div class="input-wrapper"> | |
| <label>Guidance Scale <span class="value-display" id="val-cfg">9.5</span></label> | |
| <input type="range" id="cfg" min="5.0" max="15.0" step="0.5" value="9.5"> | |
| </div> | |
| </div> | |
| <div style="margin-top: auto;"> | |
| <button class="btn btn-primary" id="generate-btn" style="width: 100%" disabled> | |
| <i class="ri-magic-line"></i> Generate Video | |
| </button> | |
| <button class="btn btn-secondary" id="reset-btn" style="width: 100%; margin-top: var(--space-sm); display: none;"> | |
| <i class="ri-refresh-line"></i> Reset Pipeline | |
| </button> | |
| </div> | |
| </aside> | |
| <!-- Main Workspace --> | |
| <section class="workspace"> | |
| <!-- Upload State --> | |
| <div class="upload-zone" id="upload-zone"> | |
| <i class="ri-upload-cloud-2-line upload-icon"></i> | |
| <div class="upload-text"> | |
| <h3>Drop Reference Image Here</h3> | |
| <p>Supports PNG, JPG, WEBP (Max 10MB)</p> | |
| <p style="margin-top: 8px; font-size: 0.8rem; color: var(--primary-accent);">or click to browse</p> | |
| </div> | |
| <input type="file" id="file-input" accept="image/png, image/jpeg, image/webp"> | |
| </div> | |
| <!-- Processing & Result State (Hidden initially) --> | |
| <div id="processing-ui" style="display: none; flex-direction: column; gap: var(--space-lg); height: 100%;"> | |
| <div class="progress-container" id="progress-container"> | |
| <div class="progress-bar" id="progress-bar"></div> | |
| </div> | |
| <div class="preview-grid"> | |
| <!-- Source Image --> | |
| <div class="media-container"> | |
| <span class="media-label">Source Reference</span> | |
| <img id="source-preview" src="" alt="Source"> | |
| </div> | |
| <!-- Output Video --> | |
| <div class="media-container" style="background: #000;"> | |
| <span class="media-label">Generated Output</span> | |
| <div id="output-placeholder" style="display: flex; flex-direction: column; align-items: center; color: var(--text-muted);"> | |
| <i class="ri-loader-4-line ri-spin" style="font-size: 2rem; margin-bottom: 10px;"></i> | |
| <span>Initializing AI Models...</span> | |
| </div> | |
| <video id="output-video" loop muted autoplay playsinline style="display: none;"></video> | |
| </div> | |
| </div> | |
| <!-- Terminal Logs --> | |
| <div class="terminal" id="terminal"> | |
| <div class="log-line"><span class="log-time">[SYSTEM]</span> <span class="log-info">Waiting for input...</span></div> | |
| </div> | |
| </div> | |
| </section> | |
| </main> | |
| <script> | |
| // DOM Elements | |
| const uploadZone = document.getElementById('upload-zone'); | |
| const fileInput = document.getElementById('file-input'); | |
| const generateBtn = document.getElementById('generate-btn'); | |
| const resetBtn = document.getElementById('reset-btn'); | |
| const processingUi = document.getElementById('processing-ui'); | |
| const sourcePreview = document.getElementById('source-preview'); | |
| const outputVideo = document.getElementById('output-video'); | |
| const outputPlaceholder = document.getElementById('output-placeholder'); | |
| const progressBar = document.getElementById('progress-bar'); | |
| const progressContainer = document.getElementById('progress-container'); | |
| const terminal = document.getElementById('terminal'); | |
| // Sliders | |
| const sliders = { | |
| motion: { el: document.getElementById('motion'), display: document.getElementById('val-motion') }, | |
| pace: { el: document.getElementById('pace'), display: document.getElementById('val-pace') }, | |
| upscale: { el: document.getElementById('upscale'), display: document.getElementById('val-upscale'), suffix: 'x' }, | |
| fps: { el: document.getElementById('fps'), display: document.getElementById('val-fps') }, | |
| cfg: { el: document.getElementById('cfg'), display: document.getElementById('val-cfg') } | |
| }; | |
| // State | |
| let currentFile = null; | |
| let isProcessing = false; | |
| // --- Event Listeners --- | |
| // Slider Value Updates | |
| Object.keys(sliders).forEach(key => { | |
| const slider = sliders[key]; | |
| slider.el.addEventListener('input', (e) => { | |
| const val = e.target.value; | |
| slider.display.textContent = val + (slider.suffix || ''); | |
| }); | |
| }); | |
| // File Upload Handling | |
| uploadZone.addEventListener('click', () => fileInput.click()); | |
| uploadZone.addEventListener('dragover', (e) => { | |
| e.preventDefault(); | |
| uploadZone.classList.add('dragover'); | |
| }); | |
| uploadZone.addEventListener('dragleave', () => { | |
| uploadZone.classList.remove('dragover'); | |
| }); | |
| uploadZone.addEventListener('drop', (e) => { | |
| e.preventDefault(); | |
| uploadZone.classList.remove('dragover'); | |
| if (e.dataTransfer.files.length) { | |
| handleFile(e.dataTransfer.files[0]); | |
| } | |
| }); | |
| fileInput.addEventListener('change', (e) => { | |
| if (e.target.files.length) { | |
| handleFile(e.target.files[0]); | |
| } | |
| }); | |
| // Generate Button | |
| generateBtn.addEventListener('click', startPipeline); | |
| // Reset Button | |
| resetBtn.addEventListener('click', resetApp); | |
| // --- Functions --- | |
| function handleFile(file) { | |
| if (!file.type.startsWith('image/')) { | |
| alert('Please upload a valid image file.'); | |
| return; | |
| } | |
| currentFile = file; | |
| const reader = new FileReader(); | |
| reader.onload = (e) => { | |
| sourcePreview.src = e.target.result; | |
| uploadZone.style.display = 'none'; | |
| processingUi.style.display = 'flex'; | |
| generateBtn.disabled = false; | |
| resetBtn.style.display = 'none'; | |
| // Reset output state | |
| outputVideo.style.display = 'none'; | |
| outputPlaceholder.style.display = 'flex'; | |
| outputPlaceholder.innerHTML = `<i class="ri-image-line" style="font-size: 2rem; margin-bottom: 10px;"></i><span>Ready to Process</span>`; | |
| progressContainer.style.display = 'none'; | |
| terminal.innerHTML = ''; // Clear logs | |
| log('Image loaded successfully: ' + file.name, 'info'); | |
| }; | |
| reader.readAsDataURL(file); | |
| } | |
| function log(message, type = 'info') { | |
| const time = new Date().toLocaleTimeString('en-US', { hour12: false, hour: '2-digit', minute:'2-digit', second:'2-digit' }); | |
| const div = document.createElement('div'); | |
| div.className = 'log-line'; | |
| div.innerHTML = `<span class="log-time">[${time}]</span> <span class="log-${type}">${message}</span>`; | |
| terminal.appendChild(div); | |
| terminal.scrollTop = terminal.scrollHeight; | |
| } | |
| function resetApp() { | |
| currentFile = null; | |
| fileInput.value = ''; | |
| uploadZone.style.display = 'flex'; | |
| processingUi.style.display = 'none'; | |
| generateBtn.disabled = true; | |
| resetBtn.style.display = 'none'; | |
| progressBar.style.width = '0%'; | |
| } | |
| async function startPipeline() { | |
| if (isProcessing) return; | |
| isProcessing = true; | |
| generateBtn.disabled = true; | |
| generateBtn.innerHTML = '<i class="ri-loader-4-line ri-spin"></i> Processing...'; | |
| outputPlaceholder.innerHTML = `<i class="ri-loader-4-line ri-spin" style="font-size: 2rem; margin-bottom: 10px;"></i><span>Initializing Pipeline...</span>`; | |
| outputPlaceholder.style.display = 'flex'; | |
| outputVideo.style.display = 'none'; | |
| progressContainer.style.display = 'block'; | |
| progressBar.style.width = '0%'; | |
| const motion = sliders.motion.el.value; | |
| const pace = sliders.pace.el.value; | |
| const upscale = sliders.upscale.el.value; | |
| const fps = sliders.fps.el.value; | |
| // Simulation Steps | |
| try { | |
| log('===== Application Startup =====', 'info'); | |
| await wait(600); | |
| log(`Running on local URL: http://0.0.0.0:7860`, 'info'); | |
| log(`Loading Configuration: Motion=${motion}, Pace=${pace}, FPS=${fps}`, 'info'); | |
| // Step 1: Upscaling | |
| await wait(800); | |
| updateProgress(10); | |
| log('Stage 1: Hyper-real upscaling...', 'info'); | |
| log('Loading RealESRGAN model weights...', 'warn'); | |
| await wait(1500); | |
| log(`Upscaling reference by factor of ${upscale}x (4x UltraSharp)...`, 'info'); | |
| updateProgress(30); | |
| await wait(1000); | |
| log('Detail enhancement complete. Identity preserved.', 'success'); | |
| // Step 2: Sequence Generation | |
| await wait(500); | |
| updateProgress(40); | |
| log('Stage 2: Sequential frame generation...', 'info'); | |
| log('Initializing IP-Adapter for identity lock...', 'warn'); | |
| await wait(1200); | |
| log('Generating frame 1/6: Initial pose adjustment...', 'info'); | |
| updateProgress(50); | |
| await wait(800); | |
| log('Generating frame 3/6: Motion interpolation...', 'info'); | |
| updateProgress(60); | |
| await wait(800); | |
| log('Generating frame 6/6: Final render details...', 'info'); | |
| updateProgress(75); | |
| // Step 3: Choreography | |
| await wait(600); | |
| log('Stage 3: AnimateDiff choreography...', 'info'); | |
| log('Applying temporal consistency layers...', 'info'); | |
| updateProgress(85); | |
| await wait(1000); | |
| log('Smoothing motion vectors...', 'info'); | |
| updateProgress(95); | |
| await wait(800); | |
| // Finalize | |
| log('Pipeline complete. Exporting video stream...', 'success'); | |
| updateProgress(100); | |
| await wait(500); | |
| showResult(); | |
| } catch (err) { | |
| log('Error: ' + err.message, 'error'); | |
| } finally { | |
| isProcessing = false; | |
| generateBtn.innerHTML = '<i class="ri-magic-line"></i> Generate Video'; | |
| generateBtn.disabled = true; | |
| resetBtn.style.display = 'block'; | |
| resetBtn.style.width = '100%'; | |
| resetBtn.style.marginTop = '10px'; | |
| } | |
| } | |
| function updateProgress(percent) { | |
| progressBar.style.width = percent + '%'; | |
| } | |
| function showResult() { | |
| outputPlaceholder.style.display = 'none'; | |
| outputVideo.style.display = 'block'; | |
| // Using a reliable, safe, abstract video placeholder for demonstration | |
| // In a real app, this would be the blob URL returned by the backend | |
| outputVideo.src = "https://assets.mixkit.co/videos/preview/mixkit-waves-in-the-water-1164-large.mp4"; | |
| outputVideo.load(); | |
| outputVideo.play(); | |
| log('Output rendered successfully (1080p, 25fps)', 'success'); | |
| } | |
| function wait(ms) { | |
| return new Promise(resolve => setTimeout(resolve, ms)); | |
| } | |
| // Initialize | |
| log('System Ready. Waiting for user input.', 'info'); | |
| </script> | |
| </body> | |
| </html> |