Spaces:
Sleeping
Sleeping
| <html lang="en"> | |
| <head> | |
| <meta charset="UTF-8" /> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0" /> | |
| <title>Video Downloader</title> | |
| <style> | |
| body { | |
| font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, | |
| Helvetica, Arial, sans-serif; | |
| display: flex; | |
| justify-content: center; | |
| align-items: center; | |
| height: 100vh; | |
| margin: 0; | |
| background-color: #f4f7f6; | |
| } | |
| .container { | |
| background: white; | |
| padding: 2rem; | |
| border-radius: 8px; | |
| box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1); | |
| width: 100%; | |
| max-width: 500px; | |
| text-align: center; | |
| } | |
| h1 { | |
| margin-bottom: 1.5rem; | |
| color: #333; | |
| } | |
| #download-form { | |
| display: flex; | |
| flex-direction: column; | |
| gap: 1rem; | |
| } | |
| #url { | |
| padding: 0.75rem; | |
| border: 1px solid #ccc; | |
| border-radius: 4px; | |
| font-size: 1rem; | |
| } | |
| button { | |
| padding: 0.75rem; | |
| border: none; | |
| border-radius: 4px; | |
| background-color: #007bff; | |
| color: white; | |
| font-size: 1rem; | |
| cursor: pointer; | |
| transition: background-color 0.2s; | |
| } | |
| button:hover { | |
| background-color: #0056b3; | |
| } | |
| button:disabled { | |
| background-color: #6c757d; | |
| cursor: not-allowed; | |
| } | |
| .message { | |
| margin-top: 1rem; | |
| padding: 0.75rem; | |
| border-radius: 4px; | |
| display: none; | |
| } | |
| .message.info { | |
| display: block; | |
| background-color: #e2f3ff; | |
| color: #005f9e; | |
| } | |
| .message.success { | |
| display: block; | |
| background-color: #d4edda; | |
| color: #155724; | |
| } | |
| .message.error { | |
| display: block; | |
| background-color: #f8d7da; | |
| color: #721c24; | |
| } | |
| #progress { | |
| display: none; | |
| margin-top: 1rem; | |
| } | |
| #progress-bar { | |
| width: 100%; | |
| height: 20px; | |
| } | |
| #percent { | |
| text-align: center; | |
| margin-top: 0.5rem; | |
| font-weight: bold; | |
| } | |
| </style> | |
| </head> | |
| <body> | |
| <div class="container"> | |
| <h1>Video Downloader</h1> | |
| <form id="download-form"> | |
| <input | |
| type="text" | |
| id="url" | |
| name="url" | |
| placeholder="Enter video URL" | |
| required | |
| /> | |
| <button type="submit" id="download-btn">Download</button> | |
| </form> | |
| <div id="progress"> | |
| <progress id="progress-bar" value="0" max="100"></progress> | |
| <div id="percent">0%</div> | |
| </div> | |
| <div id="message" class="message"></div> | |
| </div> | |
| <script> | |
| document | |
| .getElementById("download-form") | |
| .addEventListener("submit", function (event) { | |
| event.preventDefault(); | |
| const form = event.target; | |
| const url = form.elements.url.value; | |
| const messageDiv = document.getElementById("message"); | |
| const progressDiv = document.getElementById("progress"); | |
| const progressBar = document.getElementById("progress-bar"); | |
| const percentSpan = document.getElementById("percent"); | |
| const downloadBtn = document.getElementById("download-btn"); | |
| // Reset | |
| messageDiv.style.display = "none"; | |
| progressDiv.style.display = "none"; | |
| downloadBtn.disabled = true; | |
| // Start | |
| messageDiv.textContent = "Starting download..."; | |
| messageDiv.className = "message info"; | |
| messageDiv.style.display = "block"; | |
| progressDiv.style.display = "block"; | |
| progressBar.value = 0; | |
| percentSpan.textContent = "0%"; | |
| const formData = new FormData(); | |
| formData.append("url", url); | |
| const postXhr = new XMLHttpRequest(); | |
| postXhr.open("POST", "/download", true); | |
| postXhr.addEventListener("load", function () { | |
| if (postXhr.status === 200) { | |
| try { | |
| const data = JSON.parse(postXhr.responseText); | |
| const taskId = data.task_id; | |
| messageDiv.textContent = "Downloading video..."; | |
| messageDiv.className = "message info"; | |
| let polling = true; | |
| function poll() { | |
| if (!polling) return; | |
| const getXhr = new XMLHttpRequest(); | |
| getXhr.open("GET", `/progress/${taskId}`, true); | |
| getXhr.addEventListener("load", function () { | |
| if (getXhr.status === 200) { | |
| try { | |
| const task = JSON.parse(getXhr.responseText); | |
| const percent = task.progress || 0; | |
| progressBar.value = percent; | |
| percentSpan.textContent = Math.round(percent) + "%"; | |
| if (task.done) { | |
| polling = false; | |
| progressDiv.style.display = "none"; | |
| if (task.error) { | |
| messageDiv.textContent = task.error; | |
| messageDiv.className = "message error"; | |
| } else { | |
| messageDiv.textContent = | |
| "Download completed successfully!"; | |
| messageDiv.className = "message success"; | |
| const filename = | |
| task.filename || "downloaded_video.mp4"; | |
| const link = document.createElement("a"); | |
| link.href = `/file/${taskId}`; | |
| link.download = filename; | |
| document.body.appendChild(link); | |
| link.click(); | |
| document.body.removeChild(link); | |
| } | |
| downloadBtn.disabled = false; | |
| } else { | |
| setTimeout(poll, 500); | |
| } | |
| } catch (e) { | |
| // Parse error | |
| polling = false; | |
| messageDiv.textContent = | |
| "Error parsing progress update."; | |
| messageDiv.className = "message error"; | |
| downloadBtn.disabled = false; | |
| } | |
| } else { | |
| // Poll error, retry | |
| setTimeout(poll, 1000); | |
| } | |
| }); | |
| getXhr.addEventListener("error", function () { | |
| polling = false; | |
| messageDiv.textContent = | |
| "Network error while checking progress."; | |
| messageDiv.className = "message error"; | |
| downloadBtn.disabled = false; | |
| }); | |
| getXhr.send(); | |
| } | |
| poll(); | |
| } catch (e) { | |
| messageDiv.textContent = "Error starting download."; | |
| messageDiv.className = "message error"; | |
| downloadBtn.disabled = false; | |
| } | |
| } else { | |
| let errorMsg = `Error: ${postXhr.statusText}`; | |
| try { | |
| const errorData = JSON.parse(postXhr.responseText); | |
| errorMsg = `Error: ${errorData.error}`; | |
| } catch (e) { | |
| // Ignore | |
| } | |
| messageDiv.textContent = errorMsg; | |
| messageDiv.className = "message error"; | |
| progressDiv.style.display = "none"; | |
| downloadBtn.disabled = false; | |
| } | |
| }); | |
| postXhr.addEventListener("error", function () { | |
| messageDiv.textContent = | |
| "An unexpected error occurred: Network issue"; | |
| messageDiv.className = "message error"; | |
| progressDiv.style.display = "none"; | |
| downloadBtn.disabled = false; | |
| }); | |
| postXhr.send(formData); | |
| }); | |
| </script> | |
| </body> | |
| </html> | |