| <!DOCTYPE html> |
| <html lang="en"> |
| <head> |
| <meta charset="UTF-8" /> |
| <meta name="viewport" content="width=device-width, initial-scale=1.0" /> |
| <title>Facebook Video & Audio Downloader</title> |
| <style> |
| :root { |
| --primary-color: #1877f2; |
| --primary-hover: #166fe5; |
| --background-color: #f0f2f5; |
| --container-bg: #ffffff; |
| --text-color: #1c1e21; |
| --subtle-text: #606770; |
| --border-color: #dddfe2; |
| --error-bg: #fff0f0; |
| --error-text: #d8000c; |
| --progress-bg: #e4e6ea; |
| --progress-fill: var(--primary-color); |
| } |
| body { |
| font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, |
| Helvetica, Arial, sans-serif; |
| background-color: var(--background-color); |
| color: var(--text-color); |
| margin: 0; |
| padding: 20px; |
| display: flex; |
| justify-content: center; |
| align-items: flex-start; |
| min-height: 100vh; |
| } |
| .container { |
| background: var(--container-bg); |
| padding: 2rem; |
| border-radius: 8px; |
| box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1); |
| width: 100%; |
| max-width: 600px; |
| transition: all 0.3s ease-in-out; |
| } |
| h1 { |
| text-align: center; |
| color: var(--primary-color); |
| margin-bottom: 1.5rem; |
| font-size: 1.8rem; |
| } |
| .tabs { |
| display: flex; |
| border-bottom: 1px solid var(--border-color); |
| margin-bottom: 1.5rem; |
| } |
| .tab-button { |
| background: none; |
| color: var(--subtle-text); |
| padding: 1rem; |
| border: none; |
| cursor: pointer; |
| font-size: 1rem; |
| font-weight: 600; |
| transition: color 0.2s, border-bottom 0.2s; |
| border-bottom: 3px solid transparent; |
| flex-grow: 1; |
| } |
| .tab-button.active { |
| color: var(--primary-color); |
| border-bottom-color: var(--primary-color); |
| } |
| .tab-content { |
| display: none; |
| } |
| .tab-content.active { |
| display: block; |
| } |
| form { |
| display: flex; |
| flex-direction: column; |
| } |
| input[type="text"], |
| select { |
| padding: 1rem; |
| margin-bottom: 1rem; |
| border: 1px solid var(--border-color); |
| border-radius: 6px; |
| font-size: 1rem; |
| width: 100%; |
| box-sizing: border-box; |
| } |
| button { |
| background-color: var(--primary-color); |
| color: white; |
| padding: 1rem; |
| border: none; |
| border-radius: 6px; |
| cursor: pointer; |
| font-size: 1.1rem; |
| font-weight: 600; |
| transition: background-color 0.3s; |
| } |
| button:hover:not(:disabled) { |
| background-color: var(--primary-hover); |
| } |
| button:disabled { |
| background-color: #a0bdf5; |
| cursor: not-allowed; |
| } |
| #status-container { |
| margin-top: 1.5rem; |
| text-align: center; |
| font-weight: 500; |
| } |
| #status.error { |
| color: var(--error-text); |
| background-color: var(--error-bg); |
| padding: 0.8rem; |
| border-radius: 6px; |
| } |
| .spinner { |
| border: 4px solid rgba(0, 0, 0, 0.1); |
| width: 36px; |
| height: 36px; |
| border-radius: 50%; |
| border-left-color: var(--primary-color); |
| animation: spin 1s linear infinite; |
| display: none; |
| margin: 20px auto; |
| } |
| @keyframes spin { |
| to { |
| transform: rotate(360deg); |
| } |
| } |
| .note { |
| font-size: 0.9rem; |
| color: var(--subtle-text); |
| margin-top: 1rem; |
| background-color: #f7f8fa; |
| padding: 0.8rem; |
| border-radius: 6px; |
| text-align: center; |
| } |
| .important-note { |
| font-size: 0.9rem; |
| color: #946c00; |
| margin-bottom: 1.5rem; |
| background-color: #fffbe6; |
| padding: 0.8rem; |
| border-radius: 6px; |
| text-align: center; |
| } |
| #progress-container { |
| display: none; |
| margin-top: 1rem; |
| text-align: center; |
| } |
| #progress-bar { |
| width: 100%; |
| height: 20px; |
| background-color: var(--progress-bg); |
| border-radius: 10px; |
| overflow: hidden; |
| display: block; |
| margin-bottom: 0.5rem; |
| } |
| #progress-bar::-webkit-progress-bar { |
| background-color: var(--progress-bg); |
| border-radius: 10px; |
| } |
| #progress-bar::-webkit-progress-value { |
| background-color: var(--progress-fill); |
| border-radius: 10px; |
| } |
| #progress-bar::-moz-progress-bar { |
| background-color: var(--progress-fill); |
| border-radius: 10px; |
| } |
| #progress-text { |
| font-weight: 600; |
| color: var(--subtle-text); |
| } |
| #control-buttons { |
| display: none; |
| margin-top: 1rem; |
| } |
| #pause-btn, |
| #cancel-btn { |
| margin: 0 0.5rem; |
| padding: 0.5rem 1rem; |
| font-size: 1rem; |
| } |
| #pause-btn.paused { |
| background-color: #28a745; |
| } |
| #pause-btn.paused:hover { |
| background-color: #218838; |
| } |
| #stories-results { |
| display: none; |
| margin-top: 1.5rem; |
| } |
| .story { |
| margin-bottom: 1.5rem; |
| border: 1px solid var(--border-color); |
| padding: 1rem; |
| border-radius: 8px; |
| background: #fafbfc; |
| } |
| .story h3 { |
| margin: 0 0 0.5rem 0; |
| color: var(--primary-color); |
| font-size: 1.1rem; |
| } |
| .preview-container { |
| text-align: center; |
| margin-bottom: 1rem; |
| } |
| .preview-container video, |
| .preview-container img, |
| .preview-container audio { |
| width: 150px; |
| height: 150px; |
| object-fit: cover; |
| border-radius: 6px; |
| box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); |
| display: block; |
| margin: 0 auto; |
| } |
| .preview-container audio { |
| height: auto; |
| width: 150px; |
| } |
| .story ul { |
| list-style: none; |
| padding: 0; |
| display: flex; |
| flex-wrap: wrap; |
| gap: 0.5rem; |
| } |
| .story li { |
| flex: 1; |
| min-width: 120px; |
| } |
| .story a { |
| display: block; |
| padding: 0.5rem; |
| background: var(--primary-color); |
| color: white; |
| text-decoration: none; |
| border-radius: 4px; |
| text-align: center; |
| font-size: 0.9rem; |
| transition: background-color 0.2s; |
| } |
| .story a:hover { |
| background-color: var(--primary-hover); |
| } |
| #back-to-form { |
| background-color: var(--subtle-text); |
| margin-top: 1rem; |
| } |
| #back-to-form:hover { |
| background-color: #525b69; |
| } |
| #stories-loader { |
| display: none; |
| text-align: center; |
| margin: 1rem 0; |
| } |
| </style> |
| </head> |
| <body> |
| <div class="container"> |
| <h1>Facebook Video & Audio Downloader</h1> |
|
|
| <div class="tabs"> |
| <button class="tab-button active" onclick="openTab(event, 'video')"> |
| Video |
| </button> |
| <button class="tab-button" onclick="openTab(event, 'audio')"> |
| Audio |
| </button> |
| <button class="tab-button" onclick="openTab(event, 'stories')"> |
| Stories |
| </button> |
| </div> |
|
|
| <div id="video" class="tab-content active"> |
| <form id="videoForm"> |
| <input |
| type="text" |
| name="url" |
| placeholder="Enter Facebook Video URL" |
| required |
| /> |
| <select name="format"> |
| <option value="mp4">MP4</option> |
| <option value="webm">WebM</option> |
| </select> |
| <button type="submit">Download Video</button> |
| </form> |
| <p class="note"> |
| Download Facebook videos in MP4 or WebM format. The download will |
| appear in your browser's download manager. |
| </p> |
| </div> |
|
|
| <div id="audio" class="tab-content"> |
| <form id="audioForm"> |
| <input |
| type="text" |
| name="url" |
| placeholder="Enter Facebook Video URL" |
| required |
| /> |
| <select name="format"> |
| <option value="mp3">MP3</option> |
| <option value="m4a">M4A</option> |
| <option value="wav">WAV</option> |
| </select> |
| <button type="submit">Download Audio</button> |
| </form> |
| <p class="note"> |
| Extract audio from Facebook videos in various formats. The download |
| will appear in your browser's download manager. |
| </p> |
| </div> |
|
|
| <div id="stories" class="tab-content"> |
| <form id="storiesForm"> |
| <input |
| type="text" |
| name="url" |
| placeholder="Enter Facebook Stories URL" |
| required |
| /> |
| <button type="submit">Fetch Stories</button> |
| </form> |
| <div id="stories-loader" class="spinner"></div> |
| <p class="note"> |
| Fetch and download Facebook stories. Previews and multiple format |
| options will be displayed. |
| </p> |
| <div id="stories-results"> |
| <h2 style="text-align: center; margin-bottom: 1rem">Stories</h2> |
| <div id="stories-container"></div> |
| <button id="back-to-form" onclick="backToStoriesForm()"> |
| Back to Form |
| </button> |
| </div> |
| </div> |
|
|
| <div id="status-container"> |
| <div id="spinner" class="spinner"></div> |
| <div id="progress-container"> |
| <progress id="progress-bar" value="0" max="100"></progress> |
| <div id="progress-text">0%</div> |
| </div> |
| <div id="control-buttons"> |
| <button id="pause-btn">Pause</button> |
| <button id="cancel-btn" style="background-color: #dc3545"> |
| Cancel |
| </button> |
| </div> |
| <div id="status"></div> |
| </div> |
| </div> |
|
|
| <script> |
| let isDownloading = false; |
| let paused = false; |
| let currentReader = null; |
| let currentChunks = []; |
| let currentReceived = 0; |
| let currentTotal = 0; |
| let currentResponse = null; |
| const proxy = "https://corsproxy.io/?"; |
| const targetUrl = "https://getvidfb.com/"; |
| |
| function openTab(evt, tabName) { |
| document |
| .querySelectorAll(".tab-content") |
| .forEach((tc) => tc.classList.remove("active")); |
| document |
| .querySelectorAll(".tab-button") |
| .forEach((tb) => tb.classList.remove("active")); |
| document.getElementById(tabName).classList.add("active"); |
| evt.currentTarget.classList.add("active"); |
| if (tabName === "stories") { |
| document.getElementById("stories-results").style.display = "none"; |
| } |
| } |
| |
| async function handleSubmit(e) { |
| e.preventDefault(); |
| |
| if (isDownloading) { |
| document.getElementById("status").textContent = |
| "A download is already in progress. Please wait or pause/cancel the current one."; |
| document.getElementById("status").classList.add("error"); |
| return; |
| } |
| |
| const form = e.target; |
| const submitButton = form.querySelector('button[type="submit"]'); |
| const status = document.getElementById("status"); |
| const spinner = document.getElementById("spinner"); |
| const progressContainer = document.getElementById("progress-container"); |
| const progressBar = document.getElementById("progress-bar"); |
| const progressText = document.getElementById("progress-text"); |
| const controlButtons = document.getElementById("control-buttons"); |
| |
| |
| document |
| .querySelectorAll('button[type="submit"]') |
| .forEach((btn) => (btn.disabled = true)); |
| isDownloading = true; |
| paused = false; |
| |
| spinner.style.display = "block"; |
| submitButton.disabled = true; |
| status.textContent = |
| "Initializing... Please wait, this may take a moment."; |
| status.classList.remove("error"); |
| progressContainer.style.display = "none"; |
| progressBar.value = 0; |
| progressText.textContent = "0%"; |
| controlButtons.style.display = "none"; |
| |
| try { |
| const formData = new FormData(form); |
| const response = await fetch("/download", { |
| method: "POST", |
| body: formData, |
| }); |
| |
| if (!response.ok) { |
| const data = await response.json(); |
| throw new Error(data.error || "An unknown server error occurred."); |
| } |
| |
| status.textContent = "Streaming download..."; |
| spinner.style.display = "none"; |
| progressContainer.style.display = "block"; |
| controlButtons.style.display = "block"; |
| |
| currentTotal = parseInt( |
| response.headers.get("Content-Length") || "0" |
| ); |
| currentReceived = 0; |
| currentChunks = []; |
| currentReader = response.body.getReader(); |
| currentResponse = response; |
| |
| |
| const pauseBtn = document.getElementById("pause-btn"); |
| pauseBtn.onclick = togglePause; |
| pauseBtn.textContent = "Pause"; |
| pauseBtn.classList.remove("paused"); |
| |
| |
| const cancelBtn = document.getElementById("cancel-btn"); |
| cancelBtn.onclick = cancelDownload; |
| |
| readChunk(); |
| } catch (error) { |
| status.textContent = `Error: ${error.message}`; |
| status.classList.add("error"); |
| resetDownloadState(); |
| } |
| } |
| |
| async function handleStoriesSubmit(e) { |
| e.preventDefault(); |
| const form = e.target; |
| const urlInput = form.querySelector('input[name="url"]'); |
| const fbUrl = urlInput.value; |
| const loader = document.getElementById("stories-loader"); |
| const results = document.getElementById("stories-results"); |
| const container = document.getElementById("stories-container"); |
| const status = document.getElementById("status"); |
| |
| if (!fbUrl) return; |
| |
| form.querySelector('button[type="submit"]').disabled = true; |
| loader.style.display = "block"; |
| status.textContent = "Fetching stories..."; |
| status.classList.remove("error"); |
| results.style.display = "none"; |
| container.innerHTML = ""; |
| |
| try { |
| const proxiedUrl = proxy + encodeURIComponent(targetUrl); |
| const formData = new URLSearchParams(); |
| formData.append("url", fbUrl); |
| formData.append("lang", "en"); |
| formData.append("type", "redirect"); |
| |
| const response = await fetch(proxiedUrl, { |
| method: "POST", |
| headers: { "Content-Type": "application/x-www-form-urlencoded" }, |
| body: formData, |
| }); |
| |
| if (!response.ok) throw new Error("Failed to fetch stories"); |
| |
| const html = await response.text(); |
| const parser = new DOMParser(); |
| const doc = parser.parseFromString(html, "text/html"); |
| const storyBlocks = doc.querySelectorAll(".snaptikvid"); |
| |
| if (storyBlocks.length === 0) { |
| |
| const links = doc.querySelectorAll("#snaptik-video a.abutton"); |
| const storyDiv = document.createElement("div"); |
| storyDiv.className = "story"; |
| storyDiv.innerHTML = "<h3>Story #0</h3>"; |
| const previewContainer = document.createElement("div"); |
| previewContainer.className = "preview-container"; |
| let previewAdded = false; |
| const ul = document.createElement("ul"); |
| links.forEach((link) => { |
| const title = |
| link.querySelector("span")?.textContent.trim() || "Download"; |
| const href = link.getAttribute("href"); |
| const li = document.createElement("li"); |
| let preview; |
| if (!previewAdded && title.includes("Video/Mp4")) { |
| preview = document.createElement("video"); |
| preview.controls = true; |
| preview.src = href; |
| previewContainer.appendChild(preview); |
| previewAdded = true; |
| } else if (!previewAdded && title.includes("Photo/Jpg")) { |
| preview = document.createElement("img"); |
| preview.src = href; |
| previewContainer.appendChild(preview); |
| previewAdded = true; |
| } else if (!previewAdded && title.includes("Audio/Mp3")) { |
| preview = document.createElement("audio"); |
| preview.controls = true; |
| preview.src = href; |
| previewContainer.appendChild(preview); |
| previewAdded = true; |
| } |
| const a = document.createElement("a"); |
| a.href = href; |
| a.textContent = title; |
| a.download = ""; |
| li.appendChild(a); |
| ul.appendChild(li); |
| }); |
| if (!previewAdded && links.length > 0) { |
| const firstHref = links[0].getAttribute("href"); |
| if (firstHref) { |
| const preview = document.createElement("img"); |
| preview.src = firstHref; |
| previewContainer.appendChild(preview); |
| } |
| } |
| storyDiv.appendChild(previewContainer); |
| storyDiv.appendChild(ul); |
| container.appendChild(storyDiv); |
| } else { |
| storyBlocks.forEach((storyBlock, index) => { |
| const storyDiv = document.createElement("div"); |
| storyDiv.className = "story"; |
| storyDiv.innerHTML = `<h3>Story #${index}</h3>`; |
| const previewContainer = document.createElement("div"); |
| previewContainer.className = "preview-container"; |
| const links = storyBlock.querySelectorAll("a.abutton"); |
| let previewAdded = false; |
| const ul = document.createElement("ul"); |
| links.forEach((link) => { |
| const title = |
| link.querySelector("span")?.textContent.trim() || "Download"; |
| const href = link.getAttribute("href"); |
| const li = document.createElement("li"); |
| let preview; |
| if (!previewAdded && title.includes("Video/Mp4")) { |
| preview = document.createElement("video"); |
| preview.controls = true; |
| preview.src = href; |
| previewContainer.appendChild(preview); |
| previewAdded = true; |
| } else if (!previewAdded && title.includes("Photo/Jpg")) { |
| preview = document.createElement("img"); |
| preview.src = href; |
| previewContainer.appendChild(preview); |
| previewAdded = true; |
| } else if (!previewAdded && title.includes("Audio/Mp3")) { |
| preview = document.createElement("audio"); |
| preview.controls = true; |
| preview.src = href; |
| previewContainer.appendChild(preview); |
| previewAdded = true; |
| } |
| const a = document.createElement("a"); |
| a.href = href; |
| a.textContent = title; |
| a.download = ""; |
| li.appendChild(a); |
| ul.appendChild(li); |
| }); |
| if (!previewAdded) { |
| const thumbImg = storyBlock.querySelector(".snaptik-left img"); |
| if (thumbImg) { |
| const preview = document.createElement("img"); |
| preview.src = thumbImg.src; |
| previewContainer.appendChild(preview); |
| } |
| } |
| storyDiv.appendChild(previewContainer); |
| storyDiv.appendChild(ul); |
| container.appendChild(storyDiv); |
| }); |
| } |
| |
| results.style.display = "block"; |
| status.textContent = "Stories fetched successfully!"; |
| urlInput.value = ""; |
| } catch (error) { |
| status.textContent = `Error: ${error.message}`; |
| status.classList.add("error"); |
| } finally { |
| loader.style.display = "none"; |
| form.querySelector('button[type="submit"]').disabled = false; |
| } |
| } |
| |
| function backToStoriesForm() { |
| document.getElementById("stories-results").style.display = "none"; |
| document.getElementById("storiesForm").reset(); |
| document.getElementById("status").textContent = ""; |
| } |
| |
| async function readChunk() { |
| if (!currentReader || !isDownloading) return; |
| |
| try { |
| |
| while (paused && isDownloading) { |
| await new Promise((resolve) => setTimeout(resolve, 100)); |
| } |
| |
| if (!isDownloading) return; |
| |
| const { done, value } = await currentReader.read(); |
| if (done) { |
| |
| await completeDownload(); |
| return; |
| } |
| |
| currentChunks.push(value); |
| currentReceived += value.length; |
| |
| if (currentTotal > 0) { |
| const percent = Math.min( |
| (currentReceived / currentTotal) * 100, |
| 100 |
| ).toFixed(2); |
| document.getElementById("progress-bar").value = percent; |
| document.getElementById( |
| "progress-text" |
| ).textContent = `${percent}%`; |
| } else { |
| document.getElementById("progress-text").textContent = `${( |
| currentReceived / |
| 1024 / |
| 1024 |
| ).toFixed(1)} MB`; |
| } |
| |
| if (isDownloading) { |
| readChunk(); |
| } |
| } catch (err) { |
| if (isDownloading) { |
| throw err; |
| } |
| } |
| } |
| |
| function togglePause() { |
| paused = !paused; |
| const pauseBtn = document.getElementById("pause-btn"); |
| if (paused) { |
| pauseBtn.textContent = "Resume"; |
| pauseBtn.classList.add("paused"); |
| document.getElementById("status").textContent = "Download paused."; |
| } else { |
| pauseBtn.textContent = "Pause"; |
| pauseBtn.classList.remove("paused"); |
| document.getElementById("status").textContent = |
| "Streaming download..."; |
| readChunk(); |
| } |
| } |
| |
| async function cancelDownload() { |
| isDownloading = false; |
| paused = false; |
| if (currentReader) { |
| await currentReader.cancel(); |
| } |
| document.getElementById("status").textContent = "Download cancelled."; |
| document.getElementById("status").classList.add("error"); |
| resetDownloadState(); |
| } |
| |
| async function completeDownload() { |
| isDownloading = false; |
| paused = false; |
| |
| const blob = new Blob(currentChunks); |
| let downloadName = "facebook_content"; |
| const contentDisposition = currentResponse.headers.get( |
| "Content-Disposition" |
| ); |
| if (contentDisposition && contentDisposition.includes("attachment")) { |
| const filenameRegex = /filename[^;=\n]*="?([^";\n]*)"?/; |
| const matches = filenameRegex.exec(contentDisposition); |
| if (matches != null && matches[1]) { |
| downloadName = decodeURIComponent(matches[1]); |
| } |
| } |
| |
| const url = window.URL.createObjectURL(blob); |
| const a = document.createElement("a"); |
| a.style.display = "none"; |
| a.href = url; |
| a.download = downloadName; |
| document.body.appendChild(a); |
| a.click(); |
| window.URL.revokeObjectURL(url); |
| a.remove(); |
| |
| document.getElementById("status").textContent = |
| "Download complete! Check your browser's downloads."; |
| document.getElementById("progress-container").style.display = "none"; |
| document.getElementById("control-buttons").style.display = "none"; |
| resetDownloadState(); |
| } |
| |
| function resetDownloadState() { |
| document |
| .querySelectorAll('button[type="submit"]') |
| .forEach((btn) => (btn.disabled = false)); |
| document.getElementById("spinner").style.display = "none"; |
| document.getElementById("progress-container").style.display = "none"; |
| document.getElementById("control-buttons").style.display = "none"; |
| isDownloading = false; |
| paused = false; |
| currentReader = null; |
| currentChunks = []; |
| currentReceived = 0; |
| currentResponse = null; |
| } |
| |
| document |
| .getElementById("videoForm") |
| .addEventListener("submit", handleSubmit); |
| document |
| .getElementById("audioForm") |
| .addEventListener("submit", handleSubmit); |
| document |
| .getElementById("storiesForm") |
| .addEventListener("submit", handleStoriesSubmit); |
| </script> |
| </body> |
| </html> |
|
|