// ==UserScript== // @name Youtube Video Downloader 2025 // @namespace http://tampermonkey.net/ // @author fb // @version 1.2.1 // @description Download videos from youtube.com easily in various formats (mp4, webm, mp3, etc.) in 2025. // @match https://www.youtube.com/* // @grant GM_xmlhttpRequest // @grant GM_setValue // @grant GM_getValue // @connect p.oceansaver.in // @license GPL-3.0-or-later // @run-at document-end // @downloadURL none // ==/UserScript== (function () { 'use strict'; let animInterval = null; let originalText = ''; const STORAGE_KEY = 'selectedFormat'; const UI_WRAPPER_ID = 'yt-downloader-wrapper'; function isYouTubeLiveStream() { const ypr = window.ytInitialPlayerResponse; if (ypr?.videoDetails?.isLiveContent === true) { return true; } if (ypr?.microformat?.playerMicroformatRenderer?.liveBroadcastDetails) { return true; } if (document.querySelector('meta[itemprop="isLiveBroadcast"][content="True"]')) { return true; } if (document.querySelector('.ytp-live')) { return true; } return false; } // check if we are on a video page and inject/remove UI function checkPageAndInjectUI() { const isVideoPage = (window.location.pathname.startsWith('/watch') || window.location.pathname.startsWith('/shorts/')) && !isYouTubeLiveStream(); const existingWrapper = document.getElementById(UI_WRAPPER_ID); if (isVideoPage) { setTimeout(() => { const actionsInner = document.querySelector('#end'); if (actionsInner && !existingWrapper) { console.log("Injecting Downloader UI"); injectUI(actionsInner); } else if (!actionsInner && !existingWrapper) { console.log("Target container #end not found yet, will retry on next navigation event."); } }, 500); } else { if (existingWrapper) { console.log("Removing Downloader UI"); removeUI(); } } } document.addEventListener('yt-navigate-finish', checkPageAndInjectUI); checkPageAndInjectUI(); function isDarkTheme() { return document.documentElement.hasAttribute('dark'); } function injectUI(container) { if (document.getElementById(UI_WRAPPER_ID)) return; const wrapper = document.createElement('div'); wrapper.id = UI_WRAPPER_ID; wrapper.style.display = 'flex'; wrapper.style.alignItems = 'center'; wrapper.style.marginRight = "10px"; const select = document.createElement('select'); select.id = 'format'; select.className = 'doc'; select.style.marginRight = '8px'; select.addEventListener('change', (event) => { GM_setValue(STORAGE_KEY, event.target.value); }); function makeOption(value, text, isSelected = false) { const opt = document.createElement('option'); opt.value = value; opt.textContent = text; if (isSelected) opt.selected = true; return opt; } // default mp4 1080p const savedFormat = GM_getValue(STORAGE_KEY, '1080'); const groups = [ { label: 'Audio', options: [ ['mp3', 'MP3'], ['m4a', 'M4A'], ['webm', 'WEBM'], ['aac', 'AAC'], ['flac', 'FLAC'], ['opus', 'OPUS'], ['ogg', 'OGG'], ['wav', 'WAV'] ] }, { label: 'Video', options: [ ['360', 'MP4 (360p)'], ['480', 'MP4 (480p)'], ['720', 'MP4 (720p)'], ['1080', 'MP4 (1080p)'], ['1440', 'MP4 (1440p)'], ['4k', 'WEBM (4K)'] ] } ]; select.textContent = ''; // build dropdown (trustedhtml fix) for (const { label, options } of groups) { const optgroup = document.createElement('optgroup'); optgroup.label = label; for (const [value, text] of options) { const isSelected = (value === savedFormat); const option = makeOption(value, text, isSelected); optgroup.appendChild(option); } select.appendChild(optgroup); } const style = document.createElement('style'); style.textContent = ` :root { --btn-bg: ${isDarkTheme() ? "#272727" : "#f2f2f2"}; --btn-hover-bg: ${isDarkTheme() ? "#3f3f3f" : "#e5e5e5"}; --btn-color: ${isDarkTheme() ? "#fff" : "#000"}; --btn-radius: 18px; --btn-padding: 0 20px; --btn-font: 500 14px/36px "Roboto", "Arial", sans-serif; --btn-cursor: pointer; --progress-bg: ${isDarkTheme() ? "#3f3f3f" : "#e5e5e5"}; --progress-fill-color: #2196F3; --progress-text-color: ${isDarkTheme() ? "#fff" : "#000"}; } #download-button, #format { display: inline-flex; align-items: center; color: var(--btn-color); background-color: var(--btn-bg); border: none; border-radius: var(--btn-radius); padding: var(--btn-padding); white-space: nowrap; text-transform: none; font: var(--btn-font); cursor: var(--btn-cursor); transition: background-color .2s ease; height: 36px; box-sizing: border-box; } #download-button:hover:not(:disabled), #format:hover:not(:disabled) { background-color: var(--btn-hover-bg); } #download-button:disabled, #format:disabled { cursor: not-allowed; opacity: 0.6; } #download-button { padding-left: 40px; background-image: url("data:image/svg+xml,%3Csvg viewBox='0 0 24 24' xmlns='http://www.w3.org/2000/svg'%3E%3Cg fill='none' stroke='${isDarkTheme() ? 'white' : 'black'}' stroke-width='0.8' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M12 4v12'/%3E%3Cpath d='M8 12l4 4 4-4'/%3E%3Cpath d='M4 18h16'/%3E%3C/g%3E%3C/svg%3E"); background-repeat: no-repeat; background-position: 8px center; background-size: 28px; } #format { appearance: none; -webkit-appearance: none; -moz-appearance: none; padding-right: 40px; background-image: url("data:image/svg+xml;charset=US-ASCII,%3Csvg%20width%3D'10'%20height%3D'7'%20xmlns%3D'http%3A//www.w3.org/2000/svg'%3E%3Cpath%20d%3D'M0%200l5%207%205-7z'%20fill%3D'%23${isDarkTheme() ? 'fff' : '000'}'/%3E%3C/svg%3E"); background-repeat: no-repeat; background-position: right 12px center; } #format::-ms-expand { display: none; } #download-progress-bar { display: inline-block; width: 150px; height: 36px; background-color: var(--progress-bg); border-radius: var(--btn-radius); overflow: hidden; position: relative; vertical-align: middle; } #download-progress-fill { height: 100%; width: 0%; background-color: var(--progress-fill-color); transition: width 0.3s ease-in-out; } #download-progress-text { position: absolute; top: 0; left: 0; right: 0; bottom: 0; display: flex; align-items: center; justify-content: center; color: var(--progress-text-color); font: var(--btn-font); white-space: nowrap; z-index: 1; } `; document.head.appendChild(style); const btn = document.createElement('button'); btn.id = 'download-button'; btn.textContent = 'Download'; btn.addEventListener('click', startDownload); wrapper.appendChild(select); wrapper.appendChild(btn); container.insertAdjacentElement('afterbegin', wrapper); } function removeUI() { const wrapper = document.getElementById(UI_WRAPPER_ID); if (wrapper) { wrapper.remove(); } } function startDownload() { const btn = document.getElementById('download-button'); const select = document.getElementById('format'); if (!btn || !select) return; const fmt = select.value; const videoUrl = encodeURIComponent(window.location.href); const initUrl = `https://p.oceansaver.in/ajax/download.php?format=${fmt}&url=${videoUrl}`; startButtonAnimation(btn, select); GM_xmlhttpRequest({ method: 'GET', url: initUrl, responseType: 'json', onload(res) { const data = res.response; if (!data || !data.success) { stopButtonAnimation(); alert('❌ Failed to initialize download'); return; } pollProgress(data.progress_url); }, onerror() { stopButtonAnimation(); alert('❌ Network error while starting download'); } }); } function pollProgress(progressUrl) { const progressBarFill = document.getElementById('download-progress-fill'); const progressText = document.getElementById('download-progress-text') if (!progressBarFill || !progressText) { console.error("Progress bar elements not found during polling."); stopButtonAnimation(); return; } const intervalId = setInterval(() => { GM_xmlhttpRequest({ method: 'GET', url: progressUrl, responseType: 'json', onload(res) { const p = res.response; if (!p) { console.warn('Polling received empty response.'); clearInterval(intervalId); stopButtonAnimation(); return; } if (p.success) { clearInterval(intervalId); progressBarFill.style.width = '100%'; progressText.textContent = '100%'; setTimeout(() => { triggerFileDownload(p.download_url); stopButtonAnimation(); }, 300); } else { const progressPercent = p.progress ? Math.min(Math.round(p.progress / 10), 100) : 0; console.log(`Download progress: ${progressPercent}%`); progressBarFill.style.width = `${progressPercent}%`; progressText.textContent = `${progressPercent}%`; } }, onerror() { console.error('Error polling download progress'); clearInterval(intervalId); stopButtonAnimation(); alert('❌ Error checking download status'); } }); }, 1500); } function triggerFileDownload(url) { const a = document.createElement('a'); a.href = url; a.download = ''; document.body.appendChild(a); a.click(); document.body.removeChild(a); } function startButtonAnimation(btn, select) { originalText = btn.textContent; btn.style.display = 'none'; select.disabled = true; const progressBar = document.createElement('div'); progressBar.id = 'download-progress-bar'; const progressFill = document.createElement('div'); progressFill.id = 'download-progress-fill'; const progressText = document.createElement('div'); progressText.id = 'download-progress-text'; progressText.textContent = '0%'; progressBar.appendChild(progressFill); progressBar.appendChild(progressText); select.parentNode.insertBefore(progressBar, select.nextSibling); if (animInterval) { clearInterval(animInterval); animInterval = null; } } function stopButtonAnimation() { const btn = document.getElementById('download-button'); const select = document.getElementById('format'); const progressBar = document.getElementById('download-progress-bar'); if (progressBar && progressBar.parentNode) { progressBar.parentNode.removeChild(progressBar); } const wrapper = document.getElementById(UI_WRAPPER_ID); if (wrapper && btn) { btn.style.display = 'inline-flex'; btn.disabled = false; btn.textContent = originalText || 'Download'; } if (wrapper && select) { select.disabled = false; } if (animInterval) { clearInterval(animInterval); animInterval = null; } } })();