YouTube Advanced Video & Audio Enhancer

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