Universal Video Sharpener

Applies video sharpening filter to streaming videos across websites with smooth transitions

  1. // ==UserScript==
  2. // @name Universal Video Sharpener
  3. // @namespace http://tampermonkey.net/
  4. // @version 1.3
  5. // @description Applies video sharpening filter to streaming videos across websites with smooth transitions
  6. // @match *://*/*
  7. // @grant GM_setValue
  8. // @grant GM_getValue
  9. // @grant GM_registerMenuCommand
  10. // @grant GM_notification
  11. // @license MIT
  12. // ==/UserScript==
  13.  
  14. (function() {
  15. 'use strict';
  16.  
  17. // Configuration
  18. const CONFIG = {
  19. sharpnessValue: 0.0003,
  20. contrastBoost: 0.997,
  21. brightnessBoost: 1.02,
  22. transitionDuration: '0.5s',
  23. debugMode: false
  24. };
  25.  
  26. // Logging function
  27. function log(message) {
  28. if (CONFIG.debugMode) {
  29. console.log(`[Video Sharpener] ${message}`);
  30. }
  31. }
  32.  
  33. // Create SVG filter more efficiently
  34. function createSVGFilter() {
  35. if (document.getElementById('universal-sharpness-filter')) return;
  36.  
  37. const svgNamespace = 'http://www.w3.org/2000/svg';
  38. const svg = document.createElementNS(svgNamespace, 'svg');
  39. svg.setAttribute('width', '0');
  40. svg.setAttribute('height', '0');
  41. svg.setAttribute('id', 'universal-sharpness-filter');
  42.  
  43. const filter = document.createElementNS(svgNamespace, 'filter');
  44. filter.setAttribute('id', 'sharpness-filter');
  45.  
  46. const feConvolve = document.createElementNS(svgNamespace, 'feConvolveMatrix');
  47. feConvolve.setAttribute('order', '3');
  48. feConvolve.setAttribute('preserveAlpha', 'true');
  49. feConvolve.setAttribute('kernelMatrix', '0 -1 0 -1 5 -1 0 -1 0');
  50.  
  51. filter.appendChild(feConvolve);
  52. svg.appendChild(filter);
  53.  
  54. document.body.appendChild(svg);
  55. log('SVG Filter created');
  56. }
  57.  
  58. // Setup transition style
  59. function setupTransitionStyle() {
  60. if (document.getElementById('sharpener-transition-style')) return;
  61.  
  62. const style = document.createElement('style');
  63. style.id = 'sharpener-transition-style';
  64. style.textContent = `
  65. .video-sharpener-transition {
  66. transition: filter ${CONFIG.transitionDuration} ease-in-out !important;
  67. }
  68. `;
  69. document.head.appendChild(style);
  70. }
  71.  
  72. // Detect if an element is likely a video
  73. function isVideoElement(element) {
  74. return element instanceof HTMLVideoElement &&
  75. element.videoWidth > 0 &&
  76. element.videoHeight > 0;
  77. }
  78.  
  79. // Apply sharpness filter with transition
  80. function applySharpnessFilter(video, isEnabled) {
  81. if (!video) return;
  82.  
  83. try {
  84. // Add transition class if not already present
  85. if (!video.classList.contains('video-sharpener-transition')) {
  86. video.classList.add('video-sharpener-transition');
  87. }
  88.  
  89. if (isEnabled) {
  90. const { contrastBoost, brightnessBoost } = CONFIG;
  91. // Start with no filter and then apply gradually
  92. requestAnimationFrame(() => {
  93. video.style.filter = `
  94. url(#sharpness-filter)
  95. contrast(${contrastBoost})
  96. brightness(${brightnessBoost})
  97. `;
  98. });
  99. video.dataset.sharpened = 'true';
  100. log('Sharpness filter applied');
  101. } else {
  102. video.style.filter = 'none';
  103. delete video.dataset.sharpened;
  104. log('Sharpness filter removed');
  105. }
  106. } catch (error) {
  107. console.error('Error applying sharpness filter:', error);
  108. }
  109. }
  110.  
  111. // Update all videos on the page
  112. function updateAllVideos(isEnabled) {
  113. const videos = document.querySelectorAll('video');
  114. videos.forEach(video => {
  115. if (isVideoElement(video)) {
  116. applySharpnessFilter(video, isEnabled);
  117. }
  118. });
  119. }
  120.  
  121. // Main processing function
  122. function processVideos() {
  123. const isScriptEnabled = GM_getValue('universalSharpenerEnabled', false);
  124. const videos = document.querySelectorAll('video:not([data-sharpened])');
  125. videos.forEach(video => {
  126. if (isVideoElement(video)) {
  127. applySharpnessFilter(video, isScriptEnabled);
  128. }
  129. });
  130. }
  131.  
  132. // Toggle function with notification
  133. function toggleSharpener() {
  134. const currentState = GM_getValue('universalSharpenerEnabled', false);
  135. const newState = !currentState;
  136. GM_setValue('universalSharpenerEnabled', newState);
  137.  
  138. if (newState) {
  139. createSVGFilter();
  140. }
  141.  
  142. // Update all existing videos
  143. updateAllVideos(newState);
  144.  
  145. // Show notification
  146. GM_notification({
  147. text: `Video Sharpener: ${newState ? 'Enabled' : 'Disabled'}`,
  148. timeout: 2000,
  149. title: 'Video Sharpener'
  150. });
  151. }
  152.  
  153. // Initialize script
  154. function initScript() {
  155. // Get initial state
  156. const isEnabled = GM_getValue('universalSharpenerEnabled', false);
  157.  
  158. // Create SVG filter if enabled
  159. if (isEnabled) {
  160. createSVGFilter();
  161. }
  162.  
  163. // Setup transition styles
  164. setupTransitionStyle();
  165.  
  166. // Register menu command with state indicator
  167. GM_registerMenuCommand(
  168. `${isEnabled ? '✓' : '✗'} Toggle Video Sharpener`,
  169. toggleSharpener
  170. );
  171.  
  172. // Use IntersectionObserver for efficient video tracking
  173. const observer = new IntersectionObserver((entries) => {
  174. entries.forEach(entry => {
  175. if (entry.isIntersecting && isVideoElement(entry.target)) {
  176. processVideos();
  177. }
  178. });
  179. }, { threshold: 0.5 });
  180.  
  181. // Start observing new videos
  182. const videoObserver = new MutationObserver((mutations) => {
  183. mutations.forEach(mutation => {
  184. mutation.addedNodes.forEach(node => {
  185. if (node.nodeName === 'VIDEO') {
  186. observer.observe(node);
  187. }
  188. });
  189. });
  190. });
  191.  
  192. // Observe the entire document for new videos
  193. videoObserver.observe(document.body, {
  194. childList: true,
  195. subtree: true
  196. });
  197.  
  198. // Observe existing videos
  199. document.querySelectorAll('video').forEach(video => {
  200. observer.observe(video);
  201. });
  202.  
  203. // Initial processing and periodic check
  204. processVideos();
  205. setInterval(processVideos, 2000);
  206. }
  207.  
  208. // Start script
  209. initScript();
  210. })();