Boost volume beyond 100% and apply advanced video filters (brightness, contrast, saturation) to YouTube videos
Size
14.0 KB
Version
1.0.1
Created
Feb 11, 2026
Updated
26 days ago
1// ==UserScript==
2// @name YouTube Advanced Video & Audio Enhancer
3// @description Boost volume beyond 100% and apply advanced video filters (brightness, contrast, saturation) to YouTube videos
4// @version 1.0.1
5// @match https://www.youtube.com/*
6// @icon https://robomonkey.io/favicon.ico
7// @grant GM.getValue
8// @grant GM.setValue
9// ==/UserScript==
10(function() {
11 'use strict';
12
13 // Default settings
14 const DEFAULT_SETTINGS = {
15 volume: 100,
16 brightness: 100,
17 contrast: 100,
18 saturation: 100,
19 hue: 0,
20 blur: 0,
21 grayscale: 0
22 };
23
24 let currentSettings = { ...DEFAULT_SETTINGS };
25 let audioContext = null;
26 let gainNode = null;
27 let sourceNode = null;
28 let videoElement = null;
29 let controlsPanel = null;
30
31 // Debounce function for performance
32 function debounce(func, wait) {
33 let timeout;
34 return function executedFunction(...args) {
35 const later = () => {
36 clearTimeout(timeout);
37 func(...args);
38 };
39 clearTimeout(timeout);
40 timeout = setTimeout(later, wait);
41 };
42 }
43
44 // Load saved settings
45 async function loadSettings() {
46 try {
47 const saved = await GM.getValue('youtube_enhancer_settings', null);
48 if (saved) {
49 currentSettings = { ...DEFAULT_SETTINGS, ...JSON.parse(saved) };
50 console.log('Loaded settings:', currentSettings);
51 }
52 } catch (error) {
53 console.error('Error loading settings:', error);
54 }
55 }
56
57 // Save settings
58 async function saveSettings() {
59 try {
60 await GM.setValue('youtube_enhancer_settings', JSON.stringify(currentSettings));
61 console.log('Settings saved:', currentSettings);
62 } catch (error) {
63 console.error('Error saving settings:', error);
64 }
65 }
66
67 // Apply video filters
68 function applyVideoFilters() {
69 if (!videoElement) return;
70
71 const filters = [];
72
73 if (currentSettings.brightness !== 100) {
74 filters.push(`brightness(${currentSettings.brightness}%)`);
75 }
76 if (currentSettings.contrast !== 100) {
77 filters.push(`contrast(${currentSettings.contrast}%)`);
78 }
79 if (currentSettings.saturation !== 100) {
80 filters.push(`saturate(${currentSettings.saturation}%)`);
81 }
82 if (currentSettings.hue !== 0) {
83 filters.push(`hue-rotate(${currentSettings.hue}deg)`);
84 }
85 if (currentSettings.blur !== 0) {
86 filters.push(`blur(${currentSettings.blur}px)`);
87 }
88 if (currentSettings.grayscale !== 0) {
89 filters.push(`grayscale(${currentSettings.grayscale}%)`);
90 }
91
92 videoElement.style.filter = filters.join(' ');
93 console.log('Applied filters:', filters.join(' '));
94 }
95
96 // Setup audio boost using Web Audio API
97 function setupAudioBoost() {
98 if (!videoElement || audioContext) return;
99
100 try {
101 audioContext = new (window.AudioContext || window.webkitAudioContext)();
102 sourceNode = audioContext.createMediaElementSource(videoElement);
103 gainNode = audioContext.createGain();
104
105 sourceNode.connect(gainNode);
106 gainNode.connect(audioContext.destination);
107
108 // Apply saved volume
109 applyVolumeBoost();
110
111 console.log('Audio boost initialized');
112 } catch (error) {
113 console.error('Error setting up audio boost:', error);
114 }
115 }
116
117 // Apply volume boost
118 function applyVolumeBoost() {
119 if (!gainNode) return;
120
121 // Convert percentage to gain (100% = 1.0, 200% = 2.0, etc.)
122 const gain = currentSettings.volume / 100;
123 gainNode.gain.value = gain;
124 console.log('Volume set to:', currentSettings.volume + '%');
125 }
126
127 // Create control slider
128 function createSlider(label, min, max, value, step, unit, onChange) {
129 const container = document.createElement('div');
130 container.style.cssText = 'margin: 10px 0; display: flex; flex-direction: column;';
131
132 const labelRow = document.createElement('div');
133 labelRow.style.cssText = 'display: flex; justify-content: space-between; margin-bottom: 5px; font-size: 12px;';
134
135 const labelText = document.createElement('span');
136 labelText.textContent = label;
137 labelText.style.cssText = 'color: #fff; font-weight: 500;';
138
139 const valueDisplay = document.createElement('span');
140 valueDisplay.textContent = value + unit;
141 valueDisplay.style.cssText = 'color: #aaa;';
142
143 labelRow.appendChild(labelText);
144 labelRow.appendChild(valueDisplay);
145
146 const slider = document.createElement('input');
147 slider.type = 'range';
148 slider.min = min;
149 slider.max = max;
150 slider.value = value;
151 slider.step = step;
152 slider.style.cssText = 'width: 100%; cursor: pointer;';
153
154 slider.addEventListener('input', (e) => {
155 const newValue = parseFloat(e.target.value);
156 valueDisplay.textContent = newValue + unit;
157 onChange(newValue);
158 });
159
160 container.appendChild(labelRow);
161 container.appendChild(slider);
162
163 return container;
164 }
165
166 // Create controls panel
167 function createControlsPanel() {
168 if (controlsPanel) return;
169
170 const panel = document.createElement('div');
171 panel.id = 'yt-enhancer-panel';
172 panel.style.cssText = `
173 position: fixed;
174 top: 80px;
175 right: 20px;
176 width: 300px;
177 background: rgba(0, 0, 0, 0.95);
178 border: 1px solid #333;
179 border-radius: 8px;
180 padding: 15px;
181 z-index: 9999;
182 box-shadow: 0 4px 20px rgba(0, 0, 0, 0.5);
183 font-family: 'Roboto', Arial, sans-serif;
184 display: none;
185 `;
186
187 // Header
188 const header = document.createElement('div');
189 header.style.cssText = 'display: flex; justify-content: space-between; align-items: center; margin-bottom: 15px; padding-bottom: 10px; border-bottom: 1px solid #333;';
190
191 const title = document.createElement('h3');
192 title.textContent = 'Video & Audio Enhancer';
193 title.style.cssText = 'margin: 0; color: #fff; font-size: 14px; font-weight: 600;';
194
195 const closeBtn = document.createElement('button');
196 closeBtn.textContent = '×';
197 closeBtn.style.cssText = 'background: none; border: none; color: #fff; font-size: 24px; cursor: pointer; padding: 0; width: 24px; height: 24px; line-height: 20px;';
198 closeBtn.addEventListener('click', () => {
199 panel.style.display = 'none';
200 });
201
202 header.appendChild(title);
203 header.appendChild(closeBtn);
204 panel.appendChild(header);
205
206 // Audio section
207 const audioSection = document.createElement('div');
208 audioSection.style.cssText = 'margin-bottom: 15px;';
209
210 const audioTitle = document.createElement('div');
211 audioTitle.textContent = '🔊 Audio';
212 audioTitle.style.cssText = 'color: #fff; font-weight: 600; margin-bottom: 10px; font-size: 13px;';
213 audioSection.appendChild(audioTitle);
214
215 audioSection.appendChild(createSlider('Volume Boost', 0, 300, currentSettings.volume, 5, '%',
216 debounce((value) => {
217 currentSettings.volume = value;
218 applyVolumeBoost();
219 saveSettings();
220 }, 100)
221 ));
222
223 panel.appendChild(audioSection);
224
225 // Video section
226 const videoSection = document.createElement('div');
227
228 const videoTitle = document.createElement('div');
229 videoTitle.textContent = '🎨 Video Filters';
230 videoTitle.style.cssText = 'color: #fff; font-weight: 600; margin-bottom: 10px; font-size: 13px;';
231 videoSection.appendChild(videoTitle);
232
233 videoSection.appendChild(createSlider('Brightness', 0, 200, currentSettings.brightness, 5, '%',
234 debounce((value) => {
235 currentSettings.brightness = value;
236 applyVideoFilters();
237 saveSettings();
238 }, 100)
239 ));
240
241 videoSection.appendChild(createSlider('Contrast', 0, 200, currentSettings.contrast, 5, '%',
242 debounce((value) => {
243 currentSettings.contrast = value;
244 applyVideoFilters();
245 saveSettings();
246 }, 100)
247 ));
248
249 videoSection.appendChild(createSlider('Saturation', 0, 200, currentSettings.saturation, 5, '%',
250 debounce((value) => {
251 currentSettings.saturation = value;
252 applyVideoFilters();
253 saveSettings();
254 }, 100)
255 ));
256
257 videoSection.appendChild(createSlider('Hue Rotate', 0, 360, currentSettings.hue, 5, '°',
258 debounce((value) => {
259 currentSettings.hue = value;
260 applyVideoFilters();
261 saveSettings();
262 }, 100)
263 ));
264
265 videoSection.appendChild(createSlider('Blur', 0, 10, currentSettings.blur, 0.5, 'px',
266 debounce((value) => {
267 currentSettings.blur = value;
268 applyVideoFilters();
269 saveSettings();
270 }, 100)
271 ));
272
273 videoSection.appendChild(createSlider('Grayscale', 0, 100, currentSettings.grayscale, 5, '%',
274 debounce((value) => {
275 currentSettings.grayscale = value;
276 applyVideoFilters();
277 saveSettings();
278 }, 100)
279 ));
280
281 panel.appendChild(videoSection);
282
283 // Reset button
284 const resetBtn = document.createElement('button');
285 resetBtn.textContent = 'Reset All';
286 resetBtn.style.cssText = `
287 width: 100%;
288 padding: 10px;
289 margin-top: 15px;
290 background: #cc0000;
291 color: #fff;
292 border: none;
293 border-radius: 4px;
294 cursor: pointer;
295 font-weight: 600;
296 font-size: 13px;
297 `;
298 resetBtn.addEventListener('click', async () => {
299 currentSettings = { ...DEFAULT_SETTINGS };
300 await saveSettings();
301 applyVideoFilters();
302 applyVolumeBoost();
303 // Recreate panel with default values
304 panel.remove();
305 controlsPanel = null;
306 createControlsPanel();
307 document.body.appendChild(controlsPanel);
308 controlsPanel.style.display = 'block';
309 });
310
311 panel.appendChild(resetBtn);
312
313 document.body.appendChild(panel);
314 controlsPanel = panel;
315 }
316
317 // Create toggle button
318 function createToggleButton() {
319 // Check if button already exists
320 if (document.getElementById('yt-enhancer-toggle')) return;
321
322 const button = document.createElement('button');
323 button.id = 'yt-enhancer-toggle';
324 button.innerHTML = '🎛️';
325 button.title = 'Video & Audio Enhancer';
326 button.style.cssText = `
327 position: fixed;
328 top: 80px;
329 right: 20px;
330 width: 48px;
331 height: 48px;
332 background: rgba(0, 0, 0, 0.8);
333 border: 2px solid #fff;
334 border-radius: 50%;
335 cursor: pointer;
336 z-index: 9998;
337 font-size: 24px;
338 display: flex;
339 align-items: center;
340 justify-content: center;
341 box-shadow: 0 2px 10px rgba(0, 0, 0, 0.5);
342 transition: all 0.3s ease;
343 `;
344
345 button.addEventListener('mouseenter', () => {
346 button.style.transform = 'scale(1.1)';
347 button.style.background = 'rgba(0, 0, 0, 0.95)';
348 });
349
350 button.addEventListener('mouseleave', () => {
351 button.style.transform = 'scale(1)';
352 button.style.background = 'rgba(0, 0, 0, 0.8)';
353 });
354
355 button.addEventListener('click', () => {
356 if (!controlsPanel) {
357 createControlsPanel();
358 }
359 controlsPanel.style.display = controlsPanel.style.display === 'none' ? 'block' : 'none';
360 });
361
362 document.body.appendChild(button);
363 }
364
365 // Initialize on video element
366 function initializeOnVideo() {
367 const video = document.querySelector('video.html5-main-video');
368
369 if (!video || video === videoElement) return;
370
371 console.log('Initializing enhancer on video element');
372 videoElement = video;
373
374 // Setup audio boost
375 setupAudioBoost();
376
377 // Apply saved filters
378 applyVideoFilters();
379
380 // Create UI if not exists
381 if (!document.getElementById('yt-enhancer-toggle')) {
382 createToggleButton();
383 }
384 }
385
386 // Watch for video element
387 function watchForVideo() {
388 const observer = new MutationObserver(debounce(() => {
389 initializeOnVideo();
390 }, 500));
391
392 observer.observe(document.body, {
393 childList: true,
394 subtree: true
395 });
396
397 // Initial check
398 initializeOnVideo();
399 }
400
401 // Initialize
402 async function init() {
403 console.log('YouTube Advanced Video & Audio Enhancer starting...');
404
405 // Load saved settings
406 await loadSettings();
407
408 // Wait for page to be ready
409 if (document.readyState === 'loading') {
410 document.addEventListener('DOMContentLoaded', watchForVideo);
411 } else {
412 watchForVideo();
413 }
414
415 // Also watch for navigation changes (YouTube is a SPA)
416 let lastUrl = location.href;
417 new MutationObserver(() => {
418 const url = location.href;
419 if (url !== lastUrl) {
420 lastUrl = url;
421 console.log('Navigation detected, reinitializing...');
422 setTimeout(() => {
423 initializeOnVideo();
424 }, 1000);
425 }
426 }).observe(document.body, { subtree: true, childList: true });
427 }
428
429 // Start the extension
430 init();
431})();