YouTube Enhancer (Loop & Screenshot Button)

Integrating loop and screenshot buttons into the video and shorts player to enhance user functionality.

当前为 2024-11-03 提交的版本,查看 最新版本

  1. // ==UserScript==
  2. // @name YouTube Enhancer (Loop & Screenshot Button)
  3. // @description Integrating loop and screenshot buttons into the video and shorts player to enhance user functionality.
  4. // @icon https://raw.githubusercontent.com/exyezed/youtube-enhancer/refs/heads/main/extras/youtube-enhancer.png
  5. // @version 1.2
  6. // @author exyezed
  7. // @namespace https://github.com/exyezed/youtube-enhancer/
  8. // @supportURL https://github.com/exyezed/youtube-enhancer/issues
  9. // @license MIT
  10. // @match https://www.youtube.com/*
  11. // @grant GM_xmlhttpRequest
  12. // ==/UserScript==
  13.  
  14. (function() {
  15. 'use strict';
  16.  
  17. // Configuration
  18. const YouTubeEnhancerLoopScreenshotConfig = {
  19. screenshotFormat: "png",
  20. extension: 'png',
  21. screenshotFunctionality: 2, // 0: download, 1: clipboard, 2: both
  22. clickDuration: 200
  23. };
  24.  
  25. // CSS Styles
  26. const YouTubeEnhancerLoopScreenshotCSS = `
  27. a.YouTubeEnhancerLoopScreenshot-loop-button,
  28. a.YouTubeEnhancerLoopScreenshot-screenshot-button {
  29. text-align: center;
  30. position: relative;
  31. display: flex;
  32. align-items: center;
  33. justify-content: center;
  34. width: 48px;
  35. height: 48px;
  36. }
  37.  
  38. a.YouTubeEnhancerLoopScreenshot-loop-button svg,
  39. a.YouTubeEnhancerLoopScreenshot-screenshot-button svg {
  40. width: 24px;
  41. height: 24px;
  42. vertical-align: middle;
  43. }
  44.  
  45. a.YouTubeEnhancerLoopScreenshot-loop-button.active svg {
  46. fill: url(#buttonGradient);
  47. }
  48.  
  49. .YouTubeEnhancerLoopScreenshot-screenshot-button {
  50. position: relative;
  51. display: flex;
  52. align-items: center;
  53. justify-content: center;
  54. width: 48px;
  55. height: 48px;
  56. }
  57.  
  58. .YouTubeEnhancerLoopScreenshot-screenshot-button .icon-container {
  59. position: absolute;
  60. top: 50%;
  61. left: 50%;
  62. transform: translate(-50%, -50%);
  63. width: 24px;
  64. height: 24px;
  65. }
  66.  
  67. .YouTubeEnhancerLoopScreenshot-screenshot-button .default-icon,
  68. .YouTubeEnhancerLoopScreenshot-screenshot-button .hover-icon {
  69. position: absolute;
  70. top: 0;
  71. left: 0;
  72. width: 100%;
  73. height: 100%;
  74. transition: opacity 0.2s ease;
  75. }
  76.  
  77. .YouTubeEnhancerLoopScreenshot-screenshot-button .default-icon {
  78. opacity: 1;
  79. }
  80.  
  81. .YouTubeEnhancerLoopScreenshot-screenshot-button .hover-icon {
  82. opacity: 0;
  83. }
  84.  
  85. .YouTubeEnhancerLoopScreenshot-screenshot-button:hover .default-icon {
  86. opacity: 0;
  87. }
  88.  
  89. .YouTubeEnhancerLoopScreenshot-screenshot-button:hover .hover-icon {
  90. opacity: 1;
  91. }
  92.  
  93. .YouTubeEnhancerLoopScreenshot-screenshot-button.clicked .hover-icon {
  94. fill: url(#buttonGradient);
  95. }
  96.  
  97. .YouTubeEnhancerLoopScreenshot-shorts-screenshot-button {
  98. display: flex;
  99. align-items: center;
  100. justify-content: center;
  101. margin-top: 16px;
  102. width: 48px;
  103. height: 48px;
  104. border-radius: 50%;
  105. cursor: pointer;
  106. transition: background-color 0.3s;
  107. }
  108.  
  109. .YouTubeEnhancerLoopScreenshot-shorts-screenshot-button svg {
  110. width: 24px;
  111. height: 24px;
  112. transition: fill 0.1s ease;
  113. }
  114.  
  115. .YouTubeEnhancerLoopScreenshot-shorts-screenshot-button svg path {
  116. transition: fill 0.1s ease;
  117. }
  118.  
  119. .YouTubeEnhancerLoopScreenshot-shorts-screenshot-button.clicked svg path {
  120. fill: url(#shortsButtonGradient) !important;
  121. }
  122.  
  123. html[dark] .YouTubeEnhancerLoopScreenshot-shorts-screenshot-button {
  124. background-color: rgba(255, 255, 255, 0.1);
  125. }
  126.  
  127. html[dark] .YouTubeEnhancerLoopScreenshot-shorts-screenshot-button:hover {
  128. background-color: rgba(255, 255, 255, 0.2);
  129. }
  130.  
  131. html[dark] .YouTubeEnhancerLoopScreenshot-shorts-screenshot-button svg path {
  132. fill: white;
  133. }
  134.  
  135. html:not([dark]) .YouTubeEnhancerLoopScreenshot-shorts-screenshot-button {
  136. background-color: rgba(0, 0, 0, 0.05);
  137. }
  138.  
  139. html:not([dark]) .YouTubeEnhancerLoopScreenshot-shorts-screenshot-button:hover {
  140. background-color: rgba(0, 0, 0, 0.1);
  141. }
  142.  
  143. html:not([dark]) .YouTubeEnhancerLoopScreenshot-shorts-screenshot-button svg path {
  144. fill: #030303;
  145. }
  146. `;
  147. // Utility Functions
  148. const YouTubeEnhancerLoopScreenshotUtils = {
  149. addStyle(styleString) {
  150. const style = document.createElement('style');
  151. style.textContent = styleString;
  152. document.head.append(style);
  153. },
  154.  
  155. getYouTubeVideoLoopSVG() {
  156. const svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
  157. svg.setAttribute('xmlns', 'http://www.w3.org/2000/svg');
  158. svg.setAttribute('height', '24px');
  159. svg.setAttribute('viewBox', '0 -960 960 960');
  160. svg.setAttribute('width', '24px');
  161. svg.setAttribute('fill', '#e8eaed');
  162. const defs = document.createElementNS('http://www.w3.org/2000/svg', 'defs');
  163. const gradient = document.createElementNS('http://www.w3.org/2000/svg', 'linearGradient');
  164. gradient.setAttribute('id', 'buttonGradient');
  165. gradient.setAttribute('x1', '0%');
  166. gradient.setAttribute('y1', '0%');
  167. gradient.setAttribute('x2', '100%');
  168. gradient.setAttribute('y2', '100%');
  169. const stop1 = document.createElementNS('http://www.w3.org/2000/svg', 'stop');
  170. stop1.setAttribute('offset', '0%');
  171. stop1.setAttribute('style', 'stop-color:#f03');
  172. const stop2 = document.createElementNS('http://www.w3.org/2000/svg', 'stop');
  173. stop2.setAttribute('offset', '100%');
  174. stop2.setAttribute('style', 'stop-color:#ff2791');
  175. gradient.appendChild(stop1);
  176. gradient.appendChild(stop2);
  177. defs.appendChild(gradient);
  178. svg.appendChild(defs);
  179. const path = document.createElementNS('http://www.w3.org/2000/svg', 'path');
  180. 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');
  181. svg.appendChild(path);
  182. return svg;
  183. },
  184.  
  185. getYouTubeVideoScreenshotSVG() {
  186. const container = document.createElement('div');
  187. container.className = 'icon-container';
  188. const svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
  189. svg.setAttribute('xmlns', 'http://www.w3.org/2000/svg');
  190. svg.setAttribute('height', '24px');
  191. svg.setAttribute('viewBox', '0 -960 960 960');
  192. svg.setAttribute('width', '24px');
  193. svg.setAttribute('fill', '#e8eaed');
  194. svg.classList.add('default-icon');
  195. const defs = document.createElementNS('http://www.w3.org/2000/svg', 'defs');
  196. const gradient = document.createElementNS('http://www.w3.org/2000/svg', 'linearGradient');
  197. gradient.setAttribute('id', 'buttonGradient');
  198. gradient.setAttribute('x1', '0%');
  199. gradient.setAttribute('y1', '0%');
  200. gradient.setAttribute('x2', '100%');
  201. gradient.setAttribute('y2', '100%');
  202. const stop1 = document.createElementNS('http://www.w3.org/2000/svg', 'stop');
  203. stop1.setAttribute('offset', '0%');
  204. stop1.setAttribute('style', 'stop-color:#f03');
  205. const stop2 = document.createElementNS('http://www.w3.org/2000/svg', 'stop');
  206. stop2.setAttribute('offset', '100%');
  207. stop2.setAttribute('style', 'stop-color:#ff2791');
  208. gradient.appendChild(stop1);
  209. gradient.appendChild(stop2);
  210. defs.appendChild(gradient);
  211. svg.appendChild(defs);
  212. const path = document.createElementNS('http://www.w3.org/2000/svg', 'path');
  213. 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');
  214. svg.appendChild(path);
  215. const hoverSvg = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
  216. hoverSvg.setAttribute('xmlns', 'http://www.w3.org/2000/svg');
  217. hoverSvg.setAttribute('height', '24px');
  218. hoverSvg.setAttribute('viewBox', '0 0 24 24');
  219. hoverSvg.setAttribute('width', '24px');
  220. hoverSvg.setAttribute('fill', '#e8eaed');
  221. hoverSvg.classList.add('hover-icon');
  222. const hoverPath = document.createElementNS('http://www.w3.org/2000/svg', 'path');
  223. hoverPath.setAttribute('d', 'M20 5h-3.17l-1.24-1.35A2 2 0 0 0 14.12 3H9.88c-.56 0-1.1.24-1.47.65L7.17 5H4c-1.1 0-2 .9-2 2v12c0 1.1.9 2 2 2h16c1.1 0 2-.9 2-2V7c0-1.1-.9-2-2-2m-3 12H7a.5.5 0 0 1-.4-.8l2-2.67c.2-.27.6-.27.8 0L11.25 16l2.6-3.47c.2-.27.6-.27.8 0l2.75 3.67a.5.5 0 0 1-.4.8');
  224. hoverSvg.appendChild(defs.cloneNode(true));
  225. hoverSvg.appendChild(hoverPath);
  226. container.appendChild(svg);
  227. container.appendChild(hoverSvg);
  228. return container;
  229. },
  230.  
  231. getShortsScreenshotSVG() {
  232. const svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
  233. svg.setAttribute('xmlns', 'http://www.w3.org/2000/svg');
  234. svg.setAttribute('height', '24px');
  235. svg.setAttribute('viewBox', '0 0 24 24');
  236. svg.setAttribute('width', '24px');
  237. const defs = document.createElementNS('http://www.w3.org/2000/svg', 'defs');
  238. const gradient = document.createElementNS('http://www.w3.org/2000/svg', 'linearGradient');
  239. gradient.setAttribute('id', 'shortsButtonGradient');
  240. gradient.setAttribute('x1', '0%');
  241. gradient.setAttribute('y1', '0%');
  242. gradient.setAttribute('x2', '100%');
  243. gradient.setAttribute('y2', '100%');
  244. const stop1 = document.createElementNS('http://www.w3.org/2000/svg', 'stop');
  245. stop1.setAttribute('offset', '0%');
  246. stop1.setAttribute('style', 'stop-color:#f03');
  247. const stop2 = document.createElementNS('http://www.w3.org/2000/svg', 'stop');
  248. stop2.setAttribute('offset', '100%');
  249. stop2.setAttribute('style', 'stop-color:#ff2791');
  250. gradient.appendChild(stop1);
  251. gradient.appendChild(stop2);
  252. defs.appendChild(gradient);
  253. svg.appendChild(defs);
  254. const path = document.createElementNS('http://www.w3.org/2000/svg', 'path');
  255. path.setAttribute('d', 'M20 5h-3.17l-1.24-1.35A2 2 0 0 0 14.12 3H9.88c-.56 0-1.1.24-1.47.65L7.17 5H4c-1.1 0-2 .9-2 2v12c0 1.1.9 2 2 2h16c1.1 0 2-.9 2-2V7c0-1.1-.9-2-2-2m-3 12H7a.5.5 0 0 1-.4-.8l2-2.67c.2-.27.6-.27.8 0L11.25 16l2.6-3.47c.2-.27.6-.27.8 0l2.75 3.67a.5.5 0 0 1-.4.8');
  256. svg.appendChild(path);
  257. return svg;
  258. },
  259.  
  260. getVideoId() {
  261. const urlParams = new URLSearchParams(window.location.search);
  262. return urlParams.get('v') || window.location.pathname.split('/').pop();
  263. },
  264.  
  265. getVideoTitle(callback) {
  266. const videoId = this.getVideoId();
  267. GM_xmlhttpRequest({
  268. method: "GET",
  269. url: `https://exyezed.vercel.app/api/video/${videoId}`,
  270. onload: function(response) {
  271. if (response.status === 200) {
  272. const data = JSON.parse(response.responseText);
  273. callback(data.title);
  274. } else {
  275. callback('YouTube Video');
  276. }
  277. },
  278. onerror: function() {
  279. callback('YouTube Video');
  280. }
  281. });
  282. },
  283.  
  284. formatTime(time) {
  285. const date = new Date();
  286. const dateString = `${date.getFullYear()}-${String(date.getMonth() + 1).padStart(2, '0')}-${String(date.getDate()).padStart(2, '0')}`;
  287. const timeString = [
  288. Math.floor(time / 3600),
  289. Math.floor((time % 3600) / 60),
  290. Math.floor(time % 60)
  291. ].map(v => v.toString().padStart(2, '0')).join('-');
  292. return `${dateString} ${timeString}`;
  293. },
  294.  
  295. async copyToClipboard(blob) {
  296. const clipboardItem = new ClipboardItem({ "image/png": blob });
  297. await navigator.clipboard.write([clipboardItem]);
  298. },
  299.  
  300. downloadScreenshot(blob, filename) {
  301. const url = URL.createObjectURL(blob);
  302. const a = document.createElement('a');
  303. a.style.display = 'none';
  304. a.href = url;
  305. a.download = filename;
  306. document.body.appendChild(a);
  307. a.click();
  308. document.body.removeChild(a);
  309. URL.revokeObjectURL(url);
  310. },
  311.  
  312. captureScreenshot(player) {
  313. if (!player) return;
  314. const canvas = document.createElement("canvas");
  315. canvas.width = player.videoWidth;
  316. canvas.height = player.videoHeight;
  317. canvas.getContext('2d').drawImage(player, 0, 0, canvas.width, canvas.height);
  318. this.getVideoTitle((title) => {
  319. const time = player.currentTime;
  320. const filename = `${title} ${this.formatTime(time)}.${YouTubeEnhancerLoopScreenshotConfig.extension}`;
  321. canvas.toBlob(async (blob) => {
  322. const { screenshotFunctionality } = YouTubeEnhancerLoopScreenshotConfig;
  323. if (screenshotFunctionality >= 1) {
  324. await this.copyToClipboard(blob);
  325. }
  326. if (screenshotFunctionality !== 1) {
  327. this.downloadScreenshot(blob, filename);
  328. }
  329. }, `image/${YouTubeEnhancerLoopScreenshotConfig.screenshotFormat}`);
  330. });
  331. }
  332. };
  333.  
  334. // Regular YouTube Video Functions
  335. const YouTubeEnhancerLoopScreenshotRegularVideo = {
  336. init() {
  337. this.insertLoopElement();
  338. this.insertScreenshotElement();
  339. this.addObserver();
  340. this.addContextMenuListener();
  341. },
  342.  
  343. insertLoopElement() {
  344. const newButton = document.createElement('a');
  345. newButton.classList.add('ytp-button', 'YouTubeEnhancerLoopScreenshot-loop-button');
  346. newButton.title = 'Loop Video';
  347. newButton.appendChild(YouTubeEnhancerLoopScreenshotUtils.getYouTubeVideoLoopSVG());
  348. newButton.addEventListener('click', this.toggleLoopState);
  349.  
  350. document.querySelector('div.ytp-left-controls').appendChild(newButton);
  351. },
  352.  
  353. insertScreenshotElement() {
  354. const newButton = document.createElement('a');
  355. newButton.classList.add('ytp-button', 'YouTubeEnhancerLoopScreenshot-screenshot-button');
  356. newButton.title = 'Take Screenshot';
  357. newButton.appendChild(YouTubeEnhancerLoopScreenshotUtils.getYouTubeVideoScreenshotSVG());
  358. newButton.addEventListener('click', this.handleScreenshotClick);
  359.  
  360. const loopButton = document.querySelector('.YouTubeEnhancerLoopScreenshot-loop-button');
  361. loopButton.parentNode.insertBefore(newButton, loopButton.nextSibling);
  362. },
  363.  
  364. toggleLoopState() {
  365. const video = document.querySelector('video');
  366. video.loop = !video.loop;
  367. if (video.loop) video.play();
  368.  
  369. YouTubeEnhancerLoopScreenshotRegularVideo.updateToggleControls();
  370. },
  371.  
  372. updateToggleControls() {
  373. const youtubeVideoLoop = document.querySelector('.YouTubeEnhancerLoopScreenshot-loop-button');
  374. youtubeVideoLoop.classList.toggle('active');
  375. youtubeVideoLoop.setAttribute('title', this.isActive() ? 'Stop Looping' : 'Loop Video');
  376. },
  377.  
  378. isActive() {
  379. const youtubeVideoLoop = document.querySelector('.YouTubeEnhancerLoopScreenshot-loop-button');
  380. return youtubeVideoLoop.classList.contains('active');
  381. },
  382.  
  383. addObserver() {
  384. const video = document.querySelector('video');
  385. new MutationObserver((mutations) => {
  386. mutations.forEach(() => {
  387. if ((video.getAttribute('loop') === null && this.isActive()) ||
  388. (video.getAttribute('loop') !== null && !this.isActive())) this.updateToggleControls();
  389. });
  390. }).observe(video, { attributes: true, attributeFilter: ['loop'] });
  391. },
  392.  
  393. addContextMenuListener() {
  394. const video = document.querySelector('video');
  395. video.addEventListener('contextmenu', () => {
  396. setTimeout(() => {
  397. const checkbox = document.querySelector('[role=menuitemcheckbox]');
  398. checkbox.setAttribute('aria-checked', this.isActive());
  399. checkbox.addEventListener('click', this.toggleLoopState);
  400. }, 50);
  401. });
  402. },
  403.  
  404. handleScreenshotClick(event) {
  405. const button = event.currentTarget;
  406. button.classList.add('clicked');
  407. setTimeout(() => {
  408. button.classList.remove('clicked');
  409. }, YouTubeEnhancerLoopScreenshotConfig.clickDuration);
  410.  
  411. const player = document.querySelector('video');
  412. YouTubeEnhancerLoopScreenshotUtils.captureScreenshot(player);
  413. }
  414. };
  415.  
  416. // YouTube Shorts Functions
  417. const YouTubeEnhancerLoopScreenshotShorts = {
  418. init() {
  419. this.insertScreenshotElement();
  420. },
  421. insertScreenshotElement() {
  422. const shortsContainer = document.querySelector('ytd-reel-video-renderer[is-active] #actions');
  423. if (shortsContainer && !shortsContainer.querySelector('.YouTubeEnhancerLoopScreenshot-shorts-screenshot-button')) {
  424. const iconDiv = document.createElement('div');
  425. iconDiv.className = 'YouTubeEnhancerLoopScreenshot-shorts-screenshot-button';
  426. iconDiv.appendChild(YouTubeEnhancerLoopScreenshotUtils.getShortsScreenshotSVG());
  427. const customShortsIcon = shortsContainer.querySelector('#custom-shorts-icon');
  428. if (customShortsIcon) {
  429. customShortsIcon.parentNode.insertBefore(iconDiv, customShortsIcon);
  430. } else {
  431. shortsContainer.insertBefore(iconDiv, shortsContainer.firstChild);
  432. }
  433. iconDiv.addEventListener('click', (event) => {
  434. const button = event.currentTarget;
  435. button.classList.add('clicked');
  436. const path = button.querySelector('svg path');
  437. if (path) {
  438. path.style.fill = 'url(#shortsButtonGradient)';
  439. }
  440. setTimeout(() => {
  441. button.classList.remove('clicked');
  442. if (path) {
  443. path.style.fill = '';
  444. }
  445. }, YouTubeEnhancerLoopScreenshotConfig.clickDuration);
  446. this.captureScreenshot();
  447. });
  448. }
  449. },
  450.  
  451. captureScreenshot() {
  452. const player = document.querySelector('ytd-reel-video-renderer[is-active] video');
  453. YouTubeEnhancerLoopScreenshotUtils.captureScreenshot(player);
  454. }
  455. };
  456.  
  457. // Theme Functions
  458. const YouTubeEnhancerLoopScreenshotTheme = {
  459. init() {
  460. this.updateStyles();
  461. this.addObserver();
  462. },
  463.  
  464. updateStyles() {
  465. const isDarkTheme = document.documentElement.hasAttribute('dark');
  466. document.documentElement.classList.toggle('dark-theme', isDarkTheme);
  467. },
  468.  
  469. addObserver() {
  470. const observer = new MutationObserver(() => this.updateStyles());
  471. observer.observe(document.documentElement, {
  472. attributes: true,
  473. attributeFilter: ['dark']
  474. });
  475. }
  476. };
  477.  
  478. // Main Initialization
  479. function YouTubeEnhancerLoopScreenshotInit() {
  480. YouTubeEnhancerLoopScreenshotUtils.addStyle(YouTubeEnhancerLoopScreenshotCSS);
  481. initializeWhenReady();
  482. }
  483.  
  484. function initializeWhenReady() {
  485. if (document.querySelector('video')) {
  486. YouTubeEnhancerLoopScreenshotInitializeFeatures();
  487. } else {
  488. setTimeout(initializeWhenReady, 500);
  489. }
  490. }
  491.  
  492. function YouTubeEnhancerLoopScreenshotInitializeFeatures() {
  493. YouTubeEnhancerLoopScreenshotRegularVideo.init();
  494. YouTubeEnhancerLoopScreenshotTheme.init();
  495. YouTubeEnhancerLoopScreenshotInitializeShortsFeatures();
  496. }
  497.  
  498. function YouTubeEnhancerLoopScreenshotInitializeShortsFeatures() {
  499. if (window.location.pathname.includes('/shorts/')) {
  500. setTimeout(YouTubeEnhancerLoopScreenshotShorts.init.bind(YouTubeEnhancerLoopScreenshotShorts), 500);
  501. }
  502. }
  503.  
  504. // Observers and Event Listeners
  505. const YouTubeEnhancerLoopScreenshotShortsObserver = new MutationObserver((mutations) => {
  506. for (let mutation of mutations) {
  507. if (mutation.type === 'childList') {
  508. YouTubeEnhancerLoopScreenshotInitializeShortsFeatures();
  509. }
  510. }
  511. });
  512.  
  513. YouTubeEnhancerLoopScreenshotShortsObserver.observe(document.body, { childList: true, subtree: true });
  514.  
  515. window.addEventListener('yt-navigate-finish', YouTubeEnhancerLoopScreenshotInitializeShortsFeatures);
  516.  
  517. document.addEventListener('yt-action', function(event) {
  518. if (event.detail && event.detail.actionName === 'yt-reload-continuation-items-command') {
  519. YouTubeEnhancerLoopScreenshotInitializeShortsFeatures();
  520. }
  521. });
  522.  
  523. // Initialize
  524. YouTubeEnhancerLoopScreenshotInit();
  525. console.log('YouTube Enhancer (Loop & Screenshot Button) is running');
  526. })();