| <!DOCTYPE html> |
| <html lang="en"> |
| <head> |
| <meta charset="UTF-8"> |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> |
| <title>Image Sequence Generator</title> |
| <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css"> |
| <style> |
| :root { |
| --primary-color: #6c5ce7; |
| --secondary-color: #a29bfe; |
| --accent-color: #fd79a8; |
| --dark-color: #2d3436; |
| --light-color: #f5f6fa; |
| --success-color: #00b894; |
| --warning-color: #fdcb6e; |
| --danger-color: #d63031; |
| } |
| |
| * { |
| margin: 0; |
| padding: 0; |
| box-sizing: border-box; |
| font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; |
| } |
| |
| body { |
| background: linear-gradient(135deg, #f5f7fa 0%, #c3cfe2 100%); |
| min-height: 100vh; |
| display: flex; |
| flex-direction: column; |
| align-items: center; |
| color: var(--dark-color); |
| line-height: 1.6; |
| } |
| |
| .header { |
| width: 100%; |
| background: white; |
| box-shadow: 0 2px 20px rgba(0, 0, 0, 0.1); |
| padding: 1rem 2rem; |
| display: flex; |
| justify-content: space-between; |
| align-items: center; |
| position: sticky; |
| top: 0; |
| z-index: 100; |
| } |
| |
| .logo { |
| display: flex; |
| align-items: center; |
| gap: 10px; |
| text-decoration: none; |
| color: var(--dark-color); |
| } |
| |
| .logo-icon { |
| font-size: 1.5rem; |
| color: var(--primary-color); |
| } |
| |
| .logo-text { |
| font-weight: 700; |
| font-size: 1.2rem; |
| } |
| |
| .built-with { |
| font-size: 0.8rem; |
| color: var(--dark-color); |
| } |
| |
| .built-with a { |
| color: var(--primary-color); |
| text-decoration: none; |
| font-weight: 600; |
| } |
| |
| .main-container { |
| width: 90%; |
| max-width: 1200px; |
| margin: 2rem auto; |
| display: grid; |
| grid-template-columns: 1fr 1fr; |
| gap: 2rem; |
| } |
| |
| .controls { |
| background: white; |
| border-radius: 15px; |
| padding: 2rem; |
| box-shadow: 0 5px 15px rgba(0, 0, 0, 0.1); |
| } |
| |
| .preview { |
| background: white; |
| border-radius: 15px; |
| padding: 2rem; |
| box-shadow: 0 5px 15px rgba(0, 0, 0, 0.1); |
| display: flex; |
| flex-direction: column; |
| gap: 1rem; |
| } |
| |
| .section-title { |
| font-size: 1.5rem; |
| font-weight: 700; |
| margin-bottom: 1.5rem; |
| color: var(--primary-color); |
| display: flex; |
| align-items: center; |
| gap: 10px; |
| } |
| |
| .form-group { |
| margin-bottom: 1.5rem; |
| } |
| |
| .form-group label { |
| display: block; |
| margin-bottom: 0.5rem; |
| font-weight: 600; |
| color: var(--dark-color); |
| } |
| |
| .form-control { |
| width: 100%; |
| padding: 0.8rem; |
| border: 2px solid #e0e0e0; |
| border-radius: 8px; |
| font-size: 1rem; |
| transition: all 0.3s ease; |
| } |
| |
| .form-control:focus { |
| outline: none; |
| border-color: var(--primary-color); |
| box-shadow: 0 0 0 3px rgba(108, 92, 231, 0.2); |
| } |
| |
| .btn { |
| display: inline-block; |
| padding: 0.8rem 1.5rem; |
| background: var(--primary-color); |
| color: white; |
| border: none; |
| border-radius: 8px; |
| font-size: 1rem; |
| font-weight: 600; |
| cursor: pointer; |
| transition: all 0.3s ease; |
| text-align: center; |
| } |
| |
| .btn:hover { |
| background: #5649c0; |
| transform: translateY(-2px); |
| } |
| |
| .btn:disabled { |
| background: #a29bfe; |
| cursor: not-allowed; |
| transform: none; |
| } |
| |
| .btn-secondary { |
| background: var(--secondary-color); |
| } |
| |
| .btn-secondary:hover { |
| background: #8a82e5; |
| } |
| |
| .btn-danger { |
| background: var(--danger-color); |
| } |
| |
| .btn-danger:hover { |
| background: #b72424; |
| } |
| |
| .image-buffer { |
| display: grid; |
| grid-template-columns: repeat(auto-fill, minmax(120px, 1fr)); |
| gap: 1rem; |
| margin-top: 1rem; |
| max-height: 400px; |
| overflow-y: auto; |
| padding: 0.5rem; |
| border: 2px dashed #e0e0e0; |
| border-radius: 8px; |
| } |
| |
| .buffer-item { |
| position: relative; |
| aspect-ratio: 1; |
| border-radius: 8px; |
| overflow: hidden; |
| box-shadow: 0 2px 5px rgba(0, 0, 0, 0.1); |
| transition: transform 0.3s ease; |
| } |
| |
| .buffer-item:hover { |
| transform: scale(1.05); |
| z-index: 10; |
| } |
| |
| .buffer-item img { |
| width: 100%; |
| height: 100%; |
| object-fit: cover; |
| } |
| |
| .buffer-item .remove-btn { |
| position: absolute; |
| top: 5px; |
| right: 5px; |
| background: rgba(214, 48, 49, 0.8); |
| color: white; |
| border: none; |
| border-radius: 50%; |
| width: 25px; |
| height: 25px; |
| display: flex; |
| align-items: center; |
| justify-content: center; |
| cursor: pointer; |
| opacity: 0; |
| transition: opacity 0.3s ease; |
| } |
| |
| .buffer-item:hover .remove-btn { |
| opacity: 1; |
| } |
| |
| .status { |
| display: flex; |
| justify-content: space-between; |
| align-items: center; |
| margin-top: 1rem; |
| padding: 1rem; |
| background: #f8f9fa; |
| border-radius: 8px; |
| } |
| |
| .status-item { |
| text-align: center; |
| } |
| |
| .status-label { |
| font-size: 0.9rem; |
| color: #666; |
| margin-bottom: 0.3rem; |
| } |
| |
| .status-value { |
| font-weight: 700; |
| color: var(--primary-color); |
| } |
| |
| .player-controls { |
| display: flex; |
| gap: 1rem; |
| margin-top: 1rem; |
| } |
| |
| .player-display { |
| width: 100%; |
| aspect-ratio: 16/9; |
| background: #f0f0f0; |
| border-radius: 8px; |
| overflow: hidden; |
| position: relative; |
| display: flex; |
| align-items: center; |
| justify-content: center; |
| margin-top: 1rem; |
| } |
| |
| .player-display img { |
| width: 100%; |
| height: 100%; |
| object-fit: contain; |
| display: none; |
| } |
| |
| .player-display img.active { |
| display: block; |
| } |
| |
| .empty-state { |
| text-align: center; |
| padding: 2rem; |
| color: #999; |
| } |
| |
| .empty-state i { |
| font-size: 3rem; |
| margin-bottom: 1rem; |
| color: #ddd; |
| } |
| |
| .progress-container { |
| margin-top: 1rem; |
| } |
| |
| .progress-bar { |
| height: 8px; |
| background: #e0e0e0; |
| border-radius: 4px; |
| overflow: hidden; |
| } |
| |
| .progress { |
| height: 100%; |
| background: var(--primary-color); |
| width: 0%; |
| transition: width 0.3s ease; |
| } |
| |
| .settings-panel { |
| background: white; |
| border-radius: 15px; |
| padding: 1.5rem; |
| box-shadow: 0 5px 15px rgba(0, 0, 0, 0.1); |
| margin-top: 2rem; |
| } |
| |
| .settings-grid { |
| display: grid; |
| grid-template-columns: 1fr 1fr; |
| gap: 1rem; |
| } |
| |
| @media (max-width: 768px) { |
| .main-container { |
| grid-template-columns: 1fr; |
| } |
| |
| .settings-grid { |
| grid-template-columns: 1fr; |
| } |
| |
| .header { |
| flex-direction: column; |
| gap: 1rem; |
| padding: 1rem; |
| } |
| } |
| |
| @media (max-width: 480px) { |
| .image-buffer { |
| grid-template-columns: repeat(auto-fill, minmax(80px, 1fr)); |
| } |
| |
| .form-control { |
| padding: 0.6rem; |
| } |
| |
| .btn { |
| padding: 0.6rem 1rem; |
| } |
| } |
| </style> |
| </head> |
| <body> |
| <header class="header"> |
| <a href="#" class="logo"> |
| <i class="fas fa-images logo-icon"></i> |
| <span class="logo-text">Image Sequence Generator</span> |
| </a> |
| <div class="built-with"> |
| Built with <a href="https://huggingface.co/spaces/akhaliq/anycoder" target="_blank">anycoder</a> |
| </div> |
| </header> |
|
|
| <div class="main-container"> |
| <div class="controls"> |
| <h2 class="section-title"> |
| <i class="fas fa-cog"></i> |
| Generation Controls |
| </h2> |
|
|
| <div class="form-group"> |
| <label for="prompt">Prompt</label> |
| <input type="text" id="prompt" class="form-control" placeholder="Describe the image you want to generate"> |
| </div> |
|
|
| <div class="form-group"> |
| <label for="negative-prompt">Negative Prompt</label> |
| <input type="text" id="negative-prompt" class="form-control" placeholder="What you don't want to see"> |
| </div> |
|
|
| <div class="settings-panel"> |
| <h3 class="section-title" style="font-size: 1.2rem; margin-bottom: 1rem;"> |
| <i class="fas fa-sliders-h"></i> |
| Advanced Settings |
| </h3> |
|
|
| <div class="settings-grid"> |
| <div class="form-group"> |
| <label for="steps">Steps</label> |
| <input type="number" id="steps" class="form-control" value="4" min="1" max="20"> |
| </div> |
|
|
| <div class="form-group"> |
| <label for="guidance-scale">Guidance Scale</label> |
| <input type="number" id="guidance-scale" class="form-control" value="0" min="0" max="20" step="0.1"> |
| </div> |
|
|
| <div class="form-group"> |
| <label for="seed">Seed</label> |
| <input type="number" id="seed" class="form-control" value="-1" min="-1"> |
| </div> |
|
|
| <div class="form-group"> |
| <label for="batch-size">Batch Size</label> |
| <input type="number" id="batch-size" class="form-control" value="1" min="1" max="4"> |
| </div> |
| </div> |
| </div> |
|
|
| <button id="generate-btn" class="btn"> |
| <i class="fas fa-magic"></i> Generate Image |
| </button> |
|
|
| <div class="progress-container"> |
| <div class="progress-bar"> |
| <div class="progress" id="progress-bar"></div> |
| </div> |
| </div> |
| </div> |
|
|
| <div class="preview"> |
| <h2 class="section-title"> |
| <i class="fas fa-film"></i> |
| Image Buffer & Player |
| </h2> |
|
|
| <div class="status"> |
| <div class="status-item"> |
| <div class="status-label">Images in Buffer</div> |
| <div class="status-value" id="buffer-count">0</div> |
| </div> |
| <div class="status-item"> |
| <div class="status-label">Required for Playback</div> |
| <div class="status-value" id="required-count">10</div> |
| </div> |
| <div class="status-item"> |
| <div class="status-label">Playback Speed</div> |
| <div class="status-value">30 FPS</div> |
| </div> |
| </div> |
|
|
| <div class="player-controls"> |
| <button id="clear-buffer" class="btn btn-secondary"> |
| <i class="fas fa-trash"></i> Clear Buffer |
| </button> |
| <button id="play-sequence" class="btn" disabled> |
| <i class="fas fa-play"></i> Play Sequence |
| </button> |
| <button id="stop-sequence" class="btn btn-danger" disabled> |
| <i class="fas fa-stop"></i> Stop |
| </button> |
| </div> |
|
|
| <div class="player-display" id="player-display"> |
| <div class="empty-state"> |
| <i class="fas fa-image"></i> |
| <p>No images in buffer yet</p> |
| </div> |
| </div> |
|
|
| <h3 style="margin-top: 1.5rem; font-size: 1.1rem; color: var(--dark-color);"> |
| <i class="fas fa-inbox"></i> Image Buffer |
| </h3> |
|
|
| <div class="image-buffer" id="image-buffer"> |
| |
| </div> |
| </div> |
| </div> |
|
|
| <script> |
| |
| const config = { |
| requiredImages: 10, |
| playbackFPS: 30, |
| apiUrl: "https://diffusers-unofficial-sdxl-turbo-i2i-t2i.hf.space" |
| }; |
| |
| |
| let state = { |
| images: [], |
| isPlaying: false, |
| currentFrame: 0, |
| playbackInterval: null, |
| isGenerating: false |
| }; |
| |
| |
| const elements = { |
| generateBtn: document.getElementById('generate-btn'), |
| playBtn: document.getElementById('play-sequence'), |
| stopBtn: document.getElementById('stop-sequence'), |
| clearBtn: document.getElementById('clear-buffer'), |
| imageBuffer: document.getElementById('image-buffer'), |
| playerDisplay: document.getElementById('player-display'), |
| bufferCount: document.getElementById('buffer-count'), |
| requiredCount: document.getElementById('required-count'), |
| progressBar: document.getElementById('progress-bar'), |
| promptInput: document.getElementById('prompt'), |
| negativePromptInput: document.getElementById('negative-prompt'), |
| stepsInput: document.getElementById('steps'), |
| guidanceScaleInput: document.getElementById('guidance-scale'), |
| seedInput: document.getElementById('seed'), |
| batchSizeInput: document.getElementById('batch-size') |
| }; |
| |
| |
| function init() { |
| |
| elements.requiredCount.textContent = config.requiredImages; |
| |
| |
| elements.generateBtn.addEventListener('click', generateImage); |
| elements.playBtn.addEventListener('click', playSequence); |
| elements.stopBtn.addEventListener('click', stopSequence); |
| elements.clearBtn.addEventListener('click', clearBuffer); |
| |
| |
| updateUI(); |
| } |
| |
| |
| async function generateImage() { |
| if (state.isGenerating) return; |
| |
| const prompt = elements.promptInput.value.trim(); |
| if (!prompt) { |
| alert('Please enter a prompt'); |
| return; |
| } |
| |
| state.isGenerating = true; |
| updateUI(); |
| |
| |
| try { |
| |
| let progress = 0; |
| const progressInterval = setInterval(() => { |
| progress += Math.random() * 10; |
| if (progress > 90) progress = 90; |
| elements.progressBar.style.width = `${progress}%`; |
| }, 300); |
| |
| |
| await new Promise(resolve => setTimeout(resolve, 2000 + Math.random() * 3000)); |
| |
| |
| const imageData = generateRandomImageData(); |
| |
| |
| addImageToBuffer(imageData); |
| |
| |
| elements.progressBar.style.width = '100%'; |
| clearInterval(progressInterval); |
| |
| |
| setTimeout(() => { |
| elements.progressBar.style.width = '0%'; |
| }, 500); |
| } catch (error) { |
| console.error('Error generating image:', error); |
| alert('Failed to generate image. Please try again.'); |
| } finally { |
| state.isGenerating = false; |
| updateUI(); |
| } |
| } |
| |
| |
| function generateRandomImageData() { |
| const canvas = document.createElement('canvas'); |
| canvas.width = 512; |
| canvas.height = 512; |
| const ctx = canvas.getContext('2d'); |
| |
| |
| const colors = []; |
| for (let i = 0; i < 5; i++) { |
| colors.push(`hsl(${Math.random() * 360}, 70%, 60%)`); |
| } |
| |
| |
| const gradient = ctx.createLinearGradient(0, 0, canvas.width, canvas.height); |
| colors.forEach((color, i) => { |
| gradient.addColorStop(i / (colors.length - 1), color); |
| }); |
| |
| |
| ctx.fillStyle = gradient; |
| ctx.fillRect(0, 0, canvas.width, canvas.height); |
| |
| |
| for (let i = 0; i < 10; i++) { |
| ctx.fillStyle = `rgba(${Math.random() * 255}, ${Math.random() * 255}, ${Math.random() * 255}, 0.7)`; |
| const size = 50 + Math.random() * 100; |
| const x = Math.random() * (canvas.width - size); |
| const y = Math.random() * (canvas.height - size); |
| |
| if (Math.random() > 0.5) { |
| ctx.beginPath(); |
| ctx.arc(x + size/2, y + size/2, size/2, 0, Math.PI * 2); |
| ctx.fill(); |
| } else { |
| ctx.fillRect(x, y, size, size); |
| } |
| } |
| |
| |
| const prompt = elements.promptInput.value.trim(); |
| if (prompt) { |
| ctx.font = 'bold 24px Arial'; |
| ctx.fillStyle = 'white'; |
| ctx.textAlign = 'center'; |
| ctx.textBaseline = 'middle'; |
| ctx.fillText(prompt.substring(0, 30) + (prompt.length > 30 ? '...' : ''), canvas.width/2, canvas.height/2); |
| } |
| |
| return canvas.toDataURL('image/png'); |
| } |
| |
| |
| function addImageToBuffer(imageData) { |
| state.images.push(imageData); |
| updateBufferDisplay(); |
| updateUI(); |
| } |
| |
| |
| function updateBufferDisplay() { |
| elements.imageBuffer.innerHTML = ''; |
| |
| if (state.images.length === 0) { |
| elements.imageBuffer.innerHTML = ` |
| <div class="empty-state" style="grid-column: 1 / -1;"> |
| <i class="fas fa-inbox"></i> |
| <p>No images in buffer</p> |
| </div> |
| `; |
| return; |
| } |
| |
| state.images.forEach((imageData, index) => { |
| const bufferItem = document.createElement('div'); |
| bufferItem.className = 'buffer-item'; |
| bufferItem.innerHTML = ` |
| <img src="${imageData}" alt="Generated image ${index + 1}"> |
| <button class="remove-btn" data-index="${index}"> |
| <i class="fas fa-times"></i> |
| </button> |
| `; |
| elements.imageBuffer.appendChild(bufferItem); |
| }); |
| |
| |
| document.querySelectorAll('.remove-btn').forEach(btn => { |
| btn.addEventListener('click', (e) => { |
| const index = parseInt(e.target.closest('.remove-btn').dataset.index); |
| removeImageFromBuffer(index); |
| }); |
| }); |
| } |
| |
| |
| function removeImageFromBuffer(index) { |
| state.images.splice(index, 1); |
| updateBufferDisplay(); |
| updateUI(); |
| } |
| |
| |
| function clearBuffer() { |
| state.images = []; |
| updateBufferDisplay(); |
| updateUI(); |
| stopSequence(); |
| } |
| |
| |
| function playSequence() { |
| if (state.isPlaying || state.images.length < config.requiredImages) return; |
| |
| state.isPlaying = true; |
| state.currentFrame = 0; |
| updateUI(); |
| |
| |
| elements.playerDisplay.innerHTML = ''; |
| state.images.forEach((imageData, index) => { |
| const img = document.createElement('img'); |
| img.src = imageData; |
| img.alt = `Frame ${index + 1}`; |
| if (index === 0) img.classList.add('active'); |
| elements.playerDisplay.appendChild(img); |
| }); |
| |
| |
| const frameDuration = 1000 / config.playbackFPS; |
| state.playbackInterval = setInterval(() => { |
| const images = elements.playerDisplay.querySelectorAll('img'); |
| images.forEach(img => img.classList.remove('active')); |
| |
| if (state.currentFrame >= state.images.length) { |
| state.currentFrame = 0; |
| } |
| |
| images[state.currentFrame].classList.add('active'); |
| state.currentFrame++; |
| }, frameDuration); |
| } |
| |
| |
| function stopSequence() { |
| if (!state.isPlaying) return; |
| |
| clearInterval(state.playbackInterval); |
| state.isPlaying = false; |
| state.currentFrame = 0; |
| updateUI(); |
| |
| |
| elements.playerDisplay.innerHTML = ` |
| <div class="empty-state"> |
| <i class="fas fa-image"></i> |
| <p>Playback stopped</p> |
| </div> |
| `; |
| } |
| |
| |
| function updateUI() { |
| |
| elements.bufferCount.textContent = state.images.length; |
| |
| |
| elements.playBtn.disabled = state.images.length < config.requiredImages || state.isPlaying; |
| elements.stopBtn.disabled = !state.isPlaying; |
| elements.generateBtn.disabled = state.isGenerating; |
| elements.generateBtn.textContent = state.isGenerating ? |
| '<i class="fas fa-spinner fa-spin"></i> Generating...' : |
| '<i class="fas fa-magic"></i> Generate Image'; |
| |
| |
| if (!state.isPlaying && state.images.length === 0) { |
| elements.playerDisplay.innerHTML = ` |
| <div class="empty-state"> |
| <i class="fas fa-image"></i> |
| <p>No images in buffer yet</p> |
| </div> |
| `; |
| } |
| } |
| |
| |
| document.addEventListener('DOMContentLoaded', init); |
| </script> |
| </body> |
| </html> |