// ==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
// ==/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;
}
}
})();