Download templates/audio.html from Opera8/runflare: direct link, hf CLI and curl.
- Browser
- Download file 24.2 kB
-
https://huggingface.co/spaces/Opera8/runflare/resolve/main/templates/audio.html
- Command line
-
hf download hf://spaces/Opera8/runflare/templates/audio.html
-
curl -L -o audio.html https://huggingface.co/spaces/Opera8/runflare/resolve/main/templates/audio.html
24.2 kB
| <html lang="fa" dir="rtl"> | |
| <head> | |
| <meta charset="UTF-8"> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
| <title>مولد صدای هوشمند MMAudio</title> | |
| <link rel="stylesheet" href="https://cdn.jsdelivr.net/gh/rastikerdar/vazirmatn@v33.003/Vazirmatn-font-face.css"> | |
| <style> | |
| :root { | |
| --app-font: 'Vazirmatn', sans-serif; | |
| --app-bg: #F8F9FC; | |
| --panel-bg: #FFFFFF; | |
| --panel-border: #EAEFF7; | |
| --input-bg: #F6F8FB; | |
| --input-border: #E1E7EF; | |
| --text-primary: #1A202C; | |
| --text-secondary: #626F86; | |
| --text-tertiary: #8A94A6; | |
| --accent-primary: #4A6CFA; | |
| --accent-primary-hover: #3553D6; | |
| --accent-primary-glow: rgba(74, 108, 250, 0.25); | |
| --accent-secondary: #0FD4A8; | |
| --accent-secondary-hover: #0DA986; | |
| --accent-secondary-glow: rgba(15, 212, 168, 0.2); | |
| --shadow-sm: 0 1px 2px 0 rgba(26, 32, 44, 0.03); | |
| --shadow-md: 0 4px 6px -1px rgba(26, 32, 44, 0.05), 0 2px 4px -2px rgba(26, 32, 44, 0.04); | |
| --shadow-lg: 0 10px 15px -3px rgba(26, 32, 44, 0.06), 0 4px 6px -4px rgba(26, 32, 44, 0.05); | |
| --shadow-xl: 0 20px 25px -5px rgba(26, 32, 44, 0.07), 0 8px 10px -6px rgba(26, 32, 44, 0.05); | |
| --radius-card: 24px; | |
| --radius-btn: 14px; | |
| --radius-input: 12px; | |
| --transition-smooth: all 0.35s cubic-bezier(0.4, 0, 0.2, 1); | |
| } | |
| * { margin: 0; padding: 0; box-sizing: border-box; } | |
| body { | |
| font-family: var(--app-font); | |
| background-color: var(--app-bg); | |
| color: var(--text-primary); | |
| min-height: 100vh; | |
| overflow-x: hidden; | |
| display: flex; | |
| justify-content: center; | |
| align-items: flex-start; | |
| padding: 2.5rem 1rem; | |
| background-image: radial-gradient(var(--text-tertiary) 0.5px, transparent 0.5px); | |
| background-size: 20px 20px; | |
| background-position: -10px -10px; | |
| } | |
| .container { max-width: 600px; width: 100%; margin: 0 auto; display: flex; flex-direction: column; } | |
| .header { text-align: center; padding: 1rem 0 2rem; } | |
| .logo { font-size: 3rem; margin-bottom: 10px; filter: drop-shadow(0 4px 8px rgba(0,0,0,0.1)); } | |
| .title { font-size: 2.2rem; font-weight: 900; margin-bottom: 0.8rem; background: linear-gradient(90deg, var(--accent-primary), var(--accent-secondary)); -webkit-background-clip: text; -webkit-text-fill-color: transparent; letter-spacing: -1px; } | |
| .subtitle { font-size: 1rem; color: var(--text-secondary); opacity: 0.9; } | |
| .tabs { display: flex; background: var(--input-bg); border-radius: var(--radius-btn); padding: 6px; margin-bottom: 20px; border: 1px solid var(--panel-border); } | |
| .tab { flex: 1; padding: 12px; text-align: center; border-radius: 10px; cursor: pointer; transition: var(--transition-smooth); font-weight: 600; font-size: 0.9rem; color: var(--text-secondary); } | |
| .tab.active { background: var(--panel-bg); color: var(--text-primary); transform: translateY(-2px); box-shadow: var(--shadow-lg); } | |
| .card { background: var(--panel-bg); border-radius: var(--radius-card); padding: 30px; border: 1px solid var(--panel-border); box-shadow: var(--shadow-xl); display: none; flex-direction: column; } | |
| .tab-content.active { display: flex; } | |
| .form-group { margin-bottom: 20px; } | |
| .label { display: block; margin-bottom: 10px; font-size: 1rem; font-weight: 700; color: var(--text-primary); } | |
| .input { width: 100%; padding: 15px; border: 1px solid var(--input-border); border-radius: var(--radius-input); background: var(--input-bg); color: var(--text-primary); font-size: 1rem; font-family: var(--app-font); outline: none; transition: var(--transition-smooth); box-shadow: var(--shadow-sm) inset; } | |
| .input:focus { background: var(--panel-bg); border-color: var(--accent-primary); box-shadow: 0 0 0 3px var(--accent-primary-glow), var(--shadow-sm) inset; } | |
| .input-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 15px; } | |
| .file-upload { position: relative; background: var(--input-bg); border: 2px dashed var(--input-border); border-radius: var(--radius-input); padding: 20px; text-align: center; cursor: pointer; transition: var(--transition-smooth); min-height: 160px; display: flex; flex-direction: column; align-items: center; justify-content: center; overflow: hidden; } | |
| .file-upload:hover, .file-upload.drag-over { background: white; border-color: var(--accent-primary); box-shadow: 0 0 15px var(--accent-primary-glow); transform: translateY(-2px); } | |
| .file-upload input { position: absolute; left: -9999px; } | |
| .upload-icon { font-size: 2.5rem; margin-bottom: 10px; color: var(--accent-primary); } | |
| .upload-text { color: var(--text-secondary); font-size: 0.9rem; font-weight: 500; } | |
| .file-upload.has-preview { border-style: solid; border-color: var(--accent-primary); padding: 10px; cursor: default; } | |
| .preview-container { position: relative; width: 100%; height: 100%; } | |
| .preview-container video { width: 100%; max-height: 250px; border-radius: var(--radius-input); display: block; } | |
| .remove-btn { position: absolute; top: 10px; left: 10px; width: 32px; height: 32px; background-color: rgba(0, 0, 0, 0.6); color: white; border: none; border-radius: 50%; font-size: 20px; font-weight: bold; cursor: pointer; display: flex; align-items: center; justify-content: center; line-height: 1; transition: var(--transition-smooth); z-index: 10; } | |
| .remove-btn:hover { background-color: #e53e3e; transform: scale(1.1); } | |
| .btn { width: 100%; padding: 16px; border: none; border-radius: var(--radius-btn); font-size: 1.1rem; font-weight: 700; cursor: pointer; margin-top: 10px; transition: var(--transition-smooth); background: linear-gradient(95deg, var(--accent-secondary) 0%, var(--accent-primary) 100%); color: white; box-shadow: 0 6px 12px -3px var(--accent-primary-glow), 0 6px 12px -3px var(--accent-secondary-glow); } | |
| .btn:hover:not(:disabled) { transform: translateY(-5px) scale(1.02); box-shadow: 0 8px 20px -4px var(--accent-primary-glow), 0 8px 20px -4px var(--accent-secondary-glow); } | |
| .btn:disabled { background: var(--text-tertiary); color: var(--text-secondary); cursor: not-allowed; transform: none; box-shadow: none; opacity: 0.7; } | |
| .btn:active:not(:disabled) { transform: translateY(0); } | |
| .download-btn { display: inline-flex; align-items: center; justify-content: center; gap: 10px; padding: 12px 25px; margin-top: 20px; width: auto; border: none; border-radius: var(--radius-btn); font-size: 1rem; font-weight: 700; cursor: pointer; transition: var(--transition-smooth); background: var(--accent-primary); color: white; box-shadow: 0 6px 12px -3px var(--accent-primary-glow); } | |
| .download-btn:hover { background: var(--accent-primary-hover); transform: translateY(-3px); box-shadow: 0 8px 15px -4px var(--accent-primary-glow); } | |
| .result { margin-top: 20px; text-align: center; } | |
| .status-text { color: var(--text-secondary); font-weight: 500; margin: 20px 0; line-height: 1.6; } | |
| .status-text.error { color: #e53e3e; font-weight: 600; } | |
| .status-text.success { color: #38a169; font-weight: 600; margin-bottom: 15px; } | |
| audio, video { width: 100%; margin-top: 10px; border-radius: var(--radius-input); box-shadow: var(--shadow-md); outline: none; } | |
| .loader { width: 40px; height: 40px; border: 4px solid var(--input-border); border-top: 4px solid var(--accent-primary); border-radius: 50%; animation: spin 1s linear infinite; margin: 20px auto; } | |
| @keyframes spin { 0% { transform: rotate(0deg); } 100% { transform: rotate(360deg); } } | |
| .icon { display: inline-block; margin-left: 8px; vertical-align: middle; } | |
| /* ===== TOAST NOTIFICATION STYLES ===== */ | |
| #toast-container { | |
| position: fixed; | |
| top: 20px; | |
| left: 50%; | |
| transform: translateX(-50%); | |
| z-index: 9999; | |
| display: flex; | |
| flex-direction: column; | |
| gap: 10px; | |
| } | |
| .toast { | |
| background: linear-gradient(135deg, #ff0844 0%, #ffb199 100%); | |
| color: white; | |
| padding: 15px 25px; | |
| border-radius: 50px; | |
| font-family: var(--app-font); | |
| font-size: 1rem; | |
| font-weight: bold; | |
| box-shadow: 0 10px 25px rgba(255, 8, 68, 0.4); | |
| display: flex; | |
| align-items: center; | |
| gap: 10px; | |
| animation: slideDownBounce 0.6s cubic-bezier(0.68, -0.55, 0.265, 1.55) forwards; | |
| opacity: 0; | |
| } | |
| .toast.fade-out { | |
| animation: fadeOutUp 0.5s forwards; | |
| } | |
| @keyframes slideDownBounce { | |
| 0% { transform: translateY(-50px) scale(0.8); opacity: 0; } | |
| 100% { transform: translateY(0) scale(1); opacity: 1; } | |
| } | |
| @keyframes fadeOutUp { | |
| to { opacity: 0; transform: translateY(-50px) scale(0.9); } | |
| } | |
| </style> | |
| </head> | |
| <body> | |
| <div id="toast-container"></div> | |
| <div class="container"> | |
| <div class="header"> | |
| <div class="logo">🎧</div> | |
| <h1 class="title">مولد صدای هوشمند</h1> | |
| <p class="subtitle">با کمک هوش مصنوعی از متن یا ویدیو، صدا بسازید</p> | |
| </div> | |
| <div class="tabs"> | |
| <div class="tab active" data-tab="video-to-audio"><span class="icon">🎬</span>صدا برای ویدیو</div> | |
| <div class="tab" data-tab="text-to-audio"><span class="icon">✏️</span>متن به صدا</div> | |
| </div> | |
| <!-- بخش ساخت صدا برای ویدیو --> | |
| <div class="card tab-content active" id="video-to-audio-content"> | |
| <div class="form-group"> | |
| <label class="label">فایل ویدیو را انتخاب کنید:</label> | |
| <div class="file-upload" id="vta-file-upload-area"> | |
| <input type="file" id="vta-video" accept="video/*"> | |
| <div class="upload-content"> | |
| <div class="upload-icon">📹</div> | |
| <div class="upload-text">برای انتخاب کلیک کنید یا فایل را اینجا بکشید</div> | |
| </div> | |
| </div> | |
| </div> | |
| <div class="form-group"> | |
| <label class="label" for="vta-prompt">متن اصلی (اختیاری):</label> | |
| <input type="text" id="vta-prompt" class="input" placeholder="میتوانید برای هدایت بهتر، صدا را توصیف کنید"> | |
| </div> | |
| <div class="form-group"> | |
| <label class="label" for="vta-negative-prompt">متن منفی (مواردی که نمیخواهید):</label> | |
| <input type="text" id="vta-negative-prompt" class="input" placeholder="مثلا: موسیقی، صدای انسان"> | |
| </div> | |
| <div class="input-grid"> | |
| <div class="form-group"> | |
| <label class="label" for="vta-seed">Seed:</label> | |
| <input type="number" id="vta-seed" class="input" value="-1"> | |
| </div> | |
| <div class="form-group"> | |
| <label class="label" for="vta-duration">(مدت (ثانیه:</label> | |
| <input type="number" id="vta-duration" class="input" value="8" max="60" min="1"> | |
| </div> | |
| </div> | |
| <div id="vta-result" class="result"></div> | |
| <button id="generate-video-audio" class="btn"><span class="icon">🪄</span>ایجاد صدا برای ویدیو</button> | |
| </div> | |
| <!-- بخش تبدیل متن به صدا --> | |
| <div class="card tab-content" id="text-to-audio-content"> | |
| <div class="form-group"> | |
| <label class="label" for="tta-prompt">متن اصلی (توضیح صدا):</label> | |
| <input type="text" id="tta-prompt" class="input" placeholder="مثلا: صدای امواج دریا و مرغان دریایی"> | |
| </div> | |
| <div class="form-group"> | |
| <label class="label" for="tta-negative-prompt">متن منفی (مواردی که نمیخواهید):</label> | |
| <input type="text" id="tta-negative-prompt" class="input" placeholder="مثلا: موسیقی، نویز زیاد"> | |
| </div> | |
| <div class="input-grid"> | |
| <div class="form-group"> | |
| <label class="label" for="tta-seed">Seed:</label> | |
| <input type="number" id="tta-seed" class="input" value="-1"> | |
| </div> | |
| <div class="form-group"> | |
| <label class="label" for="tta-duration">(مدت (ثانیه:</label> | |
| <input type="number" id="tta-duration" class="input" value="8" max="60" min="1"> | |
| </div> | |
| </div> | |
| <div id="tta-result" class="result"></div> | |
| <button id="generate-text-audio" class="btn"><span class="icon">✨</span>ایجاد صدا</button> | |
| </div> | |
| </div> | |
| <script> | |
| // --- تابع انیمیشن خطای اختصاصی زیبا --- | |
| function showToast(message) { | |
| const container = document.getElementById('toast-container'); | |
| const toast = document.createElement('div'); | |
| toast.className = 'toast'; | |
| toast.innerHTML = `<span>⚠️</span> ${message}`; | |
| container.appendChild(toast); | |
| setTimeout(() => { | |
| toast.classList.add('fade-out'); | |
| setTimeout(() => toast.remove(), 500); | |
| }, 4000); | |
| } | |
| const tabs = document.querySelectorAll('.tab'); | |
| const tabContents = document.querySelectorAll('.tab-content'); | |
| tabs.forEach(tab => { | |
| tab.addEventListener('click', () => { | |
| tabs.forEach(t => t.classList.remove('active')); | |
| tabContents.forEach(tc => tc.classList.remove('active')); | |
| tab.classList.add('active'); | |
| document.getElementById(tab.getAttribute('data-tab') + '-content').classList.add('active'); | |
| }); | |
| }); | |
| const vtaFileInput = document.getElementById('vta-video'); | |
| const vtaFileUploadArea = document.getElementById('vta-file-upload-area'); | |
| const vtaUploadContent = vtaFileUploadArea.querySelector('.upload-content'); | |
| vtaFileUploadArea.addEventListener('click', (e) => { | |
| if (!vtaFileUploadArea.classList.contains('has-preview') || e.target === vtaFileUploadArea) { | |
| vtaFileInput.click(); | |
| } | |
| }); | |
| const handleFileSelect = () => { | |
| const file = vtaFileInput.files[0]; | |
| const existingPreview = vtaFileUploadArea.querySelector('.preview-container'); | |
| if (existingPreview) existingPreview.remove(); | |
| if (file) { | |
| // بررسی مدت زمان فایل آپلودی | |
| const videoElement = document.createElement('video'); | |
| videoElement.preload = 'metadata'; | |
| videoElement.onloadedmetadata = function() { | |
| window.URL.revokeObjectURL(videoElement.src); | |
| if (videoElement.duration > 60) { | |
| showToast('ویدیوی انتخابی طولانی است! حداکثر زمان مجاز ۱ دقیقه است.'); | |
| vtaFileInput.value = ''; | |
| vtaUploadContent.style.display = 'flex'; | |
| vtaFileUploadArea.classList.remove('has-preview'); | |
| return; | |
| } | |
| // در صورت مجاز بودن ایجاد پیشنمایش | |
| const fileURL = URL.createObjectURL(file); | |
| const previewContainer = document.createElement('div'); | |
| previewContainer.className = 'preview-container'; | |
| const videoPreview = document.createElement('video'); | |
| videoPreview.src = fileURL; | |
| videoPreview.controls = true; | |
| videoPreview.muted = true; | |
| const removeBtn = document.createElement('button'); | |
| removeBtn.className = 'remove-btn'; | |
| removeBtn.innerHTML = '×'; | |
| removeBtn.onclick = (e) => { | |
| e.stopPropagation(); | |
| vtaFileInput.value = ''; | |
| previewContainer.remove(); | |
| vtaUploadContent.style.display = 'flex'; | |
| vtaFileUploadArea.classList.remove('has-preview'); | |
| }; | |
| previewContainer.appendChild(videoPreview); | |
| previewContainer.appendChild(removeBtn); | |
| vtaUploadContent.style.display = 'none'; | |
| vtaFileUploadArea.appendChild(previewContainer); | |
| vtaFileUploadArea.classList.add('has-preview'); | |
| }; | |
| videoElement.src = URL.createObjectURL(file); | |
| } else { | |
| vtaUploadContent.style.display = 'flex'; | |
| vtaFileUploadArea.classList.remove('has-preview'); | |
| } | |
| }; | |
| vtaFileInput.addEventListener('change', handleFileSelect); | |
| function createDownloadButton(fileUrl) { | |
| const button = document.createElement('button'); | |
| button.className = 'download-btn'; | |
| button.innerHTML = `<span class="icon">⬇️</span> دانلود`; | |
| button.onclick = () => { | |
| parent.postMessage({ type: 'DOWNLOAD_REQUEST', url: fileUrl }, '*'); | |
| }; | |
| return button; | |
| } | |
| async function pollStatus(runId, resultContainer) { | |
| const interval = setInterval(async () => { | |
| try { | |
| const res = await fetch(`/api/status/${runId}`); | |
| const data = await res.json(); | |
| if (data.status === 'ready') { | |
| clearInterval(interval); | |
| resultContainer.innerHTML = ''; | |
| const successText = document.createElement('p'); | |
| successText.className = 'status-text success'; | |
| successText.textContent = 'صدا با موفقیت ایجاد شد!'; | |
| const media = document.createElement('video'); | |
| media.controls = true; | |
| media.src = data.url; | |
| media.style.maxHeight = '250px'; | |
| const downloadBtn = createDownloadButton(data.url); | |
| resultContainer.appendChild(successText); | |
| resultContainer.appendChild(media); | |
| resultContainer.appendChild(downloadBtn); | |
| } | |
| } catch (e) { | |
| console.error('Polling error:', e); | |
| } | |
| }, 3000); | |
| } | |
| const ttaButton = document.getElementById('generate-text-audio'); | |
| const ttaResult = document.getElementById('tta-result'); | |
| ttaButton.addEventListener('click', async () => { | |
| const prompt = document.getElementById('tta-prompt').value; | |
| const durationVal = parseInt(document.getElementById('tta-duration').value); | |
| if (!prompt) { alert('لطفا متن اصلی را وارد کنید.'); return; } | |
| if (durationVal > 60) { | |
| showToast('زمان تولید نمیتواند بیشتر از ۶۰ ثانیه باشد!'); | |
| return; | |
| } | |
| ttaButton.disabled = true; | |
| ttaResult.innerHTML = '<div class="loader"></div><p class="status-text">۱. در حال ارسال دستور پردازش...</p>'; | |
| const formData = new FormData(); | |
| formData.append('type', 'text'); | |
| formData.append('prompt', prompt); | |
| formData.append('negative_prompt', document.getElementById('tta-negative-prompt').value); | |
| formData.append('seed', document.getElementById('tta-seed').value); | |
| formData.append('duration', durationVal); | |
| try { | |
| const response = await fetch('/api/generate-audio', { method: 'POST', body: formData }); | |
| const data = await response.json(); | |
| if (data.status === 'success') { | |
| ttaResult.innerHTML = `<div class="loader"></div><p class="status-text">۲. دستور با موفقیت ارسال شد.<br>در حال تولید صدای درخواستی، تولید صدا ممکنه زمان بر باشه لطفاً صبور باشید...</p>`; | |
| pollStatus(data.run_id, ttaResult); | |
| } else { | |
| ttaResult.innerHTML = `<p class="status-text error">خطا: ${data.message}</p>`; | |
| } | |
| } catch (error) { | |
| ttaResult.innerHTML = `<p class="status-text error">خطای ارتباط: ${error.message}</p>`; | |
| } finally { | |
| ttaButton.disabled = false; | |
| } | |
| }); | |
| const vtaButton = document.getElementById('generate-video-audio'); | |
| const vtaResult = document.getElementById('vta-result'); | |
| vtaButton.addEventListener('click', async () => { | |
| const videoFile = document.getElementById('vta-video').files[0]; | |
| const durationVal = parseInt(document.getElementById('vta-duration').value); | |
| if (!videoFile) { alert('لطفا یک فایل ویدیویی انتخاب کنید.'); return; } | |
| if (durationVal > 60) { | |
| showToast('زمان تولید نمیتواند بیشتر از ۶۰ ثانیه باشد!'); | |
| return; | |
| } | |
| vtaButton.disabled = true; | |
| vtaResult.innerHTML = '<div class="loader"></div><p class="status-text">۱. در حال ارسال ویدیو و دستورات پردازش...</p>'; | |
| const formData = new FormData(); | |
| formData.append('type', 'video'); | |
| formData.append('file', videoFile); | |
| formData.append('prompt', document.getElementById('vta-prompt').value); | |
| formData.append('negative_prompt', document.getElementById('vta-negative-prompt').value); | |
| formData.append('seed', document.getElementById('vta-seed').value); | |
| formData.append('duration', durationVal); | |
| try { | |
| const response = await fetch('/api/generate-audio', { method: 'POST', body: formData }); | |
| const data = await response.json(); | |
| if (data.status === 'success') { | |
| vtaResult.innerHTML = `<div class="loader"></div><p class="status-text">۲. فایل ویدیویی ارسال شد.<br>در حال ساخت صدای سینمایی منطبق بر تصویر... (ساخت صدا ممکنه زمان بر باشه لطفاً صبور باشید)</p>`; | |
| pollStatus(data.run_id, vtaResult); | |
| } else { | |
| vtaResult.innerHTML = `<p class="status-text error">خطا: ${data.message}</p>`; | |
| } | |
| } catch (error) { | |
| vtaResult.innerHTML = `<p class="status-text error">خطای ارتباط: ${error.message}</p>`; | |
| } finally { | |
| vtaButton.disabled = false; | |
| } | |
| }); | |
| </script> | |
| </body> | |
| </html> |