NOTICE: By continued use of this site you understand and agree to the binding Terms of Service and Privacy Policy.
// ==UserScript== // @name YouTube Enhancer (Loop & Screenshot Button) // @description Integrating loop and screenshot buttons into the video and shorts player to enhance user functionality. // @icon https://raw.githubusercontent.com/exyezed/youtube-enhancer/refs/heads/main/extras/youtube-enhancer.png // @version 1.1 // @author exyezed // @namespace https://github.com/exyezed/youtube-enhancer/ // @supportURL https://github.com/exyezed/youtube-enhancer/issues // @license MIT // @match https://www.youtube.com/* // @grant GM_xmlhttpRequest // ==/UserScript== (function() { 'use strict'; // Configuration const YouTubeEnhancerLoopScreenshotConfig = { screenshotFormat: "png", extension: 'png', screenshotFunctionality: 2 // 0: download, 1: clipboard, 2: both }; // CSS Styles const YouTubeEnhancerLoopScreenshotCSS = ` @import url('https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:opsz,wght,FILL,GRAD@20..48,100..700,0..1,-50..200'); a.YouTubeEnhancerLoopScreenshot-loop-button, a.YouTubeEnhancerLoopScreenshot-screenshot-button { text-align: center; } a.YouTubeEnhancerLoopScreenshot-loop-button svg, a.YouTubeEnhancerLoopScreenshot-screenshot-button svg { margin-bottom: 2px; width: 52%; vertical-align: middle; } a.YouTubeEnhancerLoopScreenshot-loop-button.active svg { fill: url(#buttonGradient); } a.YouTubeEnhancerLoopScreenshot-screenshot-button.clicked svg { fill: url(#buttonGradient); } .YouTubeEnhancerLoopScreenshot-shorts-screenshot-button { display: flex; align-items: center; justify-content: center; margin-top: 16px; width: 48px; height: 48px; border-radius: 50%; cursor: pointer; transition: background-color 0.3s; } .YouTubeEnhancerLoopScreenshot-shorts-screenshot-button .material-symbols-outlined { font-size: 24px; font-variation-settings: 'FILL' 1; } /* Theme-specific styles */ html[dark] .YouTubeEnhancerLoopScreenshot-shorts-screenshot-button { background-color: rgba(255, 255, 255, 0.1); } html[dark] .YouTubeEnhancerLoopScreenshot-shorts-screenshot-button:hover { background-color: rgba(255, 255, 255, 0.2); } html[dark] .YouTubeEnhancerLoopScreenshot-shorts-screenshot-button .material-symbols-outlined { color: white; } html:not([dark]) .YouTubeEnhancerLoopScreenshot-shorts-screenshot-button { background-color: rgba(0, 0, 0, 0.05); } html:not([dark]) .YouTubeEnhancerLoopScreenshot-shorts-screenshot-button:hover { background-color: rgba(0, 0, 0, 0.1); } html:not([dark]) .YouTubeEnhancerLoopScreenshot-shorts-screenshot-button .material-symbols-outlined { color: #030303; } `; // Utility Functions const YouTubeEnhancerLoopScreenshotUtils = { addStyle(styleString) { const style = document.createElement('style'); style.textContent = styleString; document.head.append(style); }, getYouTubeVideoLoopSVG() { const svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg'); svg.setAttribute('xmlns', 'http://www.w3.org/2000/svg'); svg.setAttribute('height', '24px'); svg.setAttribute('viewBox', '0 -960 960 960'); svg.setAttribute('width', '24px'); svg.setAttribute('fill', '#e8eaed'); // Add gradient definition const defs = document.createElementNS('http://www.w3.org/2000/svg', 'defs'); const gradient = document.createElementNS('http://www.w3.org/2000/svg', 'linearGradient'); gradient.setAttribute('id', 'buttonGradient'); gradient.setAttribute('x1', '0%'); gradient.setAttribute('y1', '0%'); gradient.setAttribute('x2', '100%'); gradient.setAttribute('y2', '100%'); const stop1 = document.createElementNS('http://www.w3.org/2000/svg', 'stop'); stop1.setAttribute('offset', '0%'); stop1.setAttribute('style', 'stop-color:#f03'); const stop2 = document.createElementNS('http://www.w3.org/2000/svg', 'stop'); stop2.setAttribute('offset', '100%'); stop2.setAttribute('style', 'stop-color:#ff2791'); gradient.appendChild(stop1); gradient.appendChild(stop2); defs.appendChild(gradient); svg.appendChild(defs); const path = document.createElementNS('http://www.w3.org/2000/svg', 'path'); path.setAttribute('d', 'M220-260q-92 0-156-64T0-480q0-92 64-156t156-64q37 0 71 13t61 37l68 62-60 54-62-56q-16-14-36-22t-42-8q-58 0-99 41t-41 99q0 58 41 99t99 41q22 0 42-8t36-22l310-280q27-24 61-37t71-13q92 0 156 64t64 156q0 92-64 156t-156 64q-37 0-71-13t-61-37l-68-62 60-54 62 56q16 14 36 22t42 8q58 0 99-41t41-99q0-58-41-99t-99-41q-22 0-42 8t-36 22L352-310q-27 24-61 37t-71 13Z'); svg.appendChild(path); return svg; }, getYouTubeVideoScreenshotSVG() { const svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg'); svg.setAttribute('xmlns', 'http://www.w3.org/2000/svg'); svg.setAttribute('height', '24px'); svg.setAttribute('viewBox', '0 -960 960 960'); svg.setAttribute('width', '24px'); svg.setAttribute('fill', '#e8eaed'); // Add gradient definition const defs = document.createElementNS('http://www.w3.org/2000/svg', 'defs'); const gradient = document.createElementNS('http://www.w3.org/2000/svg', 'linearGradient'); gradient.setAttribute('id', 'buttonGradient'); gradient.setAttribute('x1', '0%'); gradient.setAttribute('y1', '0%'); gradient.setAttribute('x2', '100%'); gradient.setAttribute('y2', '100%'); const stop1 = document.createElementNS('http://www.w3.org/2000/svg', 'stop'); stop1.setAttribute('offset', '0%'); stop1.setAttribute('style', 'stop-color:#f03'); const stop2 = document.createElementNS('http://www.w3.org/2000/svg', 'stop'); stop2.setAttribute('offset', '100%'); stop2.setAttribute('style', 'stop-color:#ff2791'); gradient.appendChild(stop1); gradient.appendChild(stop2); defs.appendChild(gradient); svg.appendChild(defs); const path = document.createElementNS('http://www.w3.org/2000/svg', 'path'); path.setAttribute('d', 'M240-280h480L570-480 450-320l-90-120-120 160Zm-80 160q-33 0-56.5-23.5T80-200v-480q0-33 23.5-56.5T160-760h126l74-80h240l74 80h126q33 0 56.5 23.5T880-680v480q0 33-23.5 56.5T800-120H160Zm0-80h640v-480H638l-73-80H395l-73 80H160v480Zm320-240Z'); svg.appendChild(path); return svg; }, getVideoId() { const urlParams = new URLSearchParams(window.location.search); return urlParams.get('v') || window.location.pathname.split('/').pop(); }, getVideoTitle(callback) { const videoId = this.getVideoId(); GM_xmlhttpRequest({ method: "GET", url: `https://exyezed.vercel.app/api/video/${videoId}`, onload: function(response) { if (response.status === 200) { const data = JSON.parse(response.responseText); callback(data.title); } else { callback('YouTube Video'); } }, onerror: function() { callback('YouTube Video'); } }); }, formatTime(time) { const date = new Date(); const dateString = `${date.getFullYear()}-${String(date.getMonth() + 1).padStart(2, '0')}-${String(date.getDate()).padStart(2, '0')}`; const timeString = [ Math.floor(time / 3600), Math.floor((time % 3600) / 60), Math.floor(time % 60) ].map(v => v.toString().padStart(2, '0')).join('-'); return `${dateString} ${timeString}`; }, async copyToClipboard(blob) { const clipboardItem = new ClipboardItem({ "image/png": blob }); await navigator.clipboard.write([clipboardItem]); }, downloadScreenshot(blob, filename) { const url = URL.createObjectURL(blob); const a = document.createElement('a'); a.style.display = 'none'; a.href = url; a.download = filename; document.body.appendChild(a); a.click(); document.body.removeChild(a); URL.revokeObjectURL(url); } }; // Regular YouTube Video Functions const YouTubeEnhancerLoopScreenshotRegularVideo = { init() { this.insertLoopElement(); this.insertScreenshotElement(); this.addObserver(); this.addContextMenuListener(); }, insertLoopElement() { const newButton = document.createElement('a'); newButton.classList.add('ytp-button', 'YouTubeEnhancerLoopScreenshot-loop-button'); newButton.title = 'Loop Video'; newButton.appendChild(YouTubeEnhancerLoopScreenshotUtils.getYouTubeVideoLoopSVG()); newButton.addEventListener('click', this.toggleLoopState); document.querySelector('div.ytp-left-controls').appendChild(newButton); }, insertScreenshotElement() { const newButton = document.createElement('a'); newButton.classList.add('ytp-button', 'YouTubeEnhancerLoopScreenshot-screenshot-button'); newButton.title = 'Take Screenshot'; newButton.appendChild(YouTubeEnhancerLoopScreenshotUtils.getYouTubeVideoScreenshotSVG()); newButton.addEventListener('click', this.handleScreenshotClick); const loopButton = document.querySelector('.YouTubeEnhancerLoopScreenshot-loop-button'); loopButton.parentNode.insertBefore(newButton, loopButton.nextSibling); }, toggleLoopState() { const video = document.querySelector('video'); video.loop = !video.loop; if (video.loop) video.play(); YouTubeEnhancerLoopScreenshotRegularVideo.updateToggleControls(); }, updateToggleControls() { const youtubeVideoLoop = document.querySelector('.YouTubeEnhancerLoopScreenshot-loop-button'); youtubeVideoLoop.classList.toggle('active'); youtubeVideoLoop.setAttribute('title', this.isActive() ? 'Stop Looping' : 'Loop Video'); }, isActive() { const youtubeVideoLoop = document.querySelector('.YouTubeEnhancerLoopScreenshot-loop-button'); return youtubeVideoLoop.classList.contains('active'); }, addObserver() { const video = document.querySelector('video'); new MutationObserver((mutations) => { mutations.forEach(() => { if ((video.getAttribute('loop') === null && this.isActive()) || (video.getAttribute('loop') !== null && !this.isActive())) this.updateToggleControls(); }); }).observe(video, { attributes: true, attributeFilter: ['loop'] }); }, addContextMenuListener() { const video = document.querySelector('video'); video.addEventListener('contextmenu', () => { setTimeout(() => { const checkbox = document.querySelector('[role=menuitemcheckbox]'); checkbox.setAttribute('aria-checked', this.isActive()); checkbox.addEventListener('click', this.toggleLoopState); }, 50); }); }, handleScreenshotClick(event) { const button = event.currentTarget; button.classList.add('clicked'); setTimeout(() => { button.classList.remove('clicked'); }, 100); YouTubeEnhancerLoopScreenshotRegularVideo.captureScreenshot(); }, captureScreenshot() { const player = document.querySelector('video'); if (!player) return; const canvas = document.createElement("canvas"); canvas.width = player.videoWidth; canvas.height = player.videoHeight; canvas.getContext('2d').drawImage(player, 0, 0, canvas.width, canvas.height); YouTubeEnhancerLoopScreenshotUtils.getVideoTitle((title) => { const time = player.currentTime; const filename = `${title} ${YouTubeEnhancerLoopScreenshotUtils.formatTime(time)}.${YouTubeEnhancerLoopScreenshotConfig.extension}`; canvas.toBlob(async (blob) => { if (YouTubeEnhancerLoopScreenshotConfig.screenshotFunctionality === 1 || YouTubeEnhancerLoopScreenshotConfig.screenshotFunctionality === 2) { await YouTubeEnhancerLoopScreenshotUtils.copyToClipboard(blob); } if (YouTubeEnhancerLoopScreenshotConfig.screenshotFunctionality === 0 || YouTubeEnhancerLoopScreenshotConfig.screenshotFunctionality === 2) { YouTubeEnhancerLoopScreenshotUtils.downloadScreenshot(blob, filename); } }, `image/${YouTubeEnhancerLoopScreenshotConfig.screenshotFormat}`); }); } }; // YouTube Shorts Functions const YouTubeEnhancerLoopScreenshotShorts = { init() { this.insertScreenshotElement(); }, insertScreenshotElement() { const shortsContainer = document.querySelector('ytd-reel-video-renderer[is-active] #actions'); if (shortsContainer && !shortsContainer.querySelector('.YouTubeEnhancerLoopScreenshot-shorts-screenshot-button')) { const iconDiv = document.createElement('div'); iconDiv.className = 'YouTubeEnhancerLoopScreenshot-shorts-screenshot-button'; const iconSpan = document.createElement('span'); iconSpan.className = 'material-symbols-outlined'; iconSpan.textContent = 'photo_camera_back'; iconDiv.appendChild(iconSpan); const customShortsIcon = shortsContainer.querySelector('#custom-shorts-icon'); if (customShortsIcon) { customShortsIcon.parentNode.insertBefore(iconDiv, customShortsIcon); } else { shortsContainer.insertBefore(iconDiv, shortsContainer.firstChild); } iconDiv.addEventListener('click', () => this.captureScreenshot()); } }, captureScreenshot() { const player = document.querySelector('ytd-reel-video-renderer[is-active] video'); if (!player) return; const canvas = document.createElement("canvas"); canvas.width = player.videoWidth; canvas.height = player.videoHeight; canvas.getContext('2d').drawImage(player, 0, 0, canvas.width, canvas.height); YouTubeEnhancerLoopScreenshotUtils.getVideoTitle((title) => { const time = player.currentTime; const filename = `${title} ${YouTubeEnhancerLoopScreenshotUtils.formatTime(time)}.${YouTubeEnhancerLoopScreenshotConfig.extension}`; canvas.toBlob(async (blob) => { if (YouTubeEnhancerLoopScreenshotConfig.screenshotFunctionality === 1 || YouTubeEnhancerLoopScreenshotConfig.screenshotFunctionality === 2) { await YouTubeEnhancerLoopScreenshotUtils.copyToClipboard(blob); } if (YouTubeEnhancerLoopScreenshotConfig.screenshotFunctionality === 0 || YouTubeEnhancerLoopScreenshotConfig.screenshotFunctionality === 2) { YouTubeEnhancerLoopScreenshotUtils.downloadScreenshot(blob, filename); } }, `image/${YouTubeEnhancerLoopScreenshotConfig.screenshotFormat}`); }); } }; // Theme Functions const YouTubeEnhancerLoopScreenshotTheme = { init() { this.updateStyles(); this.addObserver(); }, updateStyles() { const isDarkTheme = document.documentElement.hasAttribute('dark'); document.documentElement.classList.toggle('dark-theme', isDarkTheme); }, addObserver() { const observer = new MutationObserver(this.updateStyles); observer.observe(document.documentElement, { attributes: true, attributeFilter: ['dark'] }); } }; // Main Initialization function YouTubeEnhancerLoopScreenshotInit() { YouTubeEnhancerLoopScreenshotUtils.addStyle(YouTubeEnhancerLoopScreenshotCSS); setTimeout(YouTubeEnhancerLoopScreenshotVideoElementPresent() ? YouTubeEnhancerLoopScreenshotInitializeFeatures : YouTubeEnhancerLoopScreenshotInit, 500); } function YouTubeEnhancerLoopScreenshotVideoElementPresent() { return document.querySelector('video') !== null; } function YouTubeEnhancerLoopScreenshotInitializeFeatures() { YouTubeEnhancerLoopScreenshotRegularVideo.init(); YouTubeEnhancerLoopScreenshotTheme.init(); YouTubeEnhancerLoopScreenshotInitializeShortsFeatures(); } function YouTubeEnhancerLoopScreenshotInitializeShortsFeatures() { if (window.location.pathname.includes('/shorts/')) { setTimeout(YouTubeEnhancerLoopScreenshotShorts.init.bind(YouTubeEnhancerLoopScreenshotShorts), 500); } } // Observers and Event Listeners const YouTubeEnhancerLoopScreenshotShortsObserver = new MutationObserver((mutations) => { for (let mutation of mutations) { if (mutation.type === 'childList') { YouTubeEnhancerLoopScreenshotInitializeShortsFeatures(); } } }); YouTubeEnhancerLoopScreenshotShortsObserver.observe(document.body, { childList: true, subtree: true }); window.addEventListener('yt-navigate-finish', YouTubeEnhancerLoopScreenshotInitializeShortsFeatures); document.addEventListener('yt-action', function(event) { if (event.detail && event.detail.actionName === 'yt-reload-continuation-items-command') { YouTubeEnhancerLoopScreenshotInitializeShortsFeatures(); } }); // Initialize YouTubeEnhancerLoopScreenshotInit(); console.log('YouTubeEnhancerLoopScreenshot: Enjoy the awesome music and capture your favorite moments!'); })();