YouTube Screenshot Frame

Capture YouTube screenshots with a hotkey (default is key = 'b'). Filename includes title, timestamp, and capture time.

  1. // ==UserScript==
  2. // @name YouTube Screenshot Frame
  3. // @namespace https://github.com/dpi0/scripts/blob/main/greasyfork/youtube-screenshot-frame.js
  4. // @version 1.0
  5. // @description Capture YouTube screenshots with a hotkey (default is key = 'b'). Filename includes title, timestamp, and capture time.
  6. // @author dpi0
  7. // @match https://www.youtube.com/*
  8. // @grant GM_registerMenuCommand
  9. // @grant GM_getValue
  10. // @grant GM_setValue
  11. // @homepageURL https://github.com/dpi0/scripts/blob/main/greasyfork/youtube-screenshot-frame.js
  12. // @originalhomepageURL https://greasyfork.org/en/scripts/532893-youtube-screenshot-helper/code
  13. // @supportURL https://github.com/dpi0/scripts/issues
  14. // @license MIT
  15. // ==/UserScript==
  16.  
  17. (function () {
  18. 'use strict';
  19.  
  20. const defaultHotkey = 'b';
  21. let screenshotKey = GM_getValue('screenshotKey', defaultHotkey);
  22.  
  23. function getVideoElement() {
  24. return document.querySelector('video');
  25. }
  26.  
  27. function getVideoTitle() {
  28. const selectors = [
  29. 'h1.title.ytd-video-primary-info-renderer',
  30. 'h1.title.style-scope.ytd-video-primary-info-renderer',
  31. 'ytd-watch-metadata h1',
  32. 'meta[name="title"]'
  33. ];
  34. for (const sel of selectors) {
  35. const el = document.querySelector(sel);
  36. let rawTitle = el?.textContent || el?.content;
  37. if (rawTitle) {
  38. return rawTitle
  39. .trim()
  40. .replace(/[\\/:*?"<>|]/g, '_') // illegal chars
  41. .replace(/\s+/g, '-') // spaces to dashes
  42. .toLowerCase();
  43. }
  44. }
  45. return 'unknown-title';
  46. }
  47.  
  48. function formatTimestamp(date) {
  49. const pad = (n) => n.toString().padStart(2, '0');
  50. return `${pad(date.getDate())}-${date.toLocaleString('default', { month: 'short' })}-${date.getFullYear()}_${pad(date.getHours())}-${pad(date.getMinutes())}-${pad(date.getSeconds())}`;
  51. }
  52.  
  53. function formatFrameTime(seconds) {
  54. const h = String(Math.floor(seconds / 3600)).padStart(2, '0');
  55. const m = String(Math.floor((seconds % 3600) / 60)).padStart(2, '0');
  56. const s = String(Math.floor(seconds % 60)).padStart(2, '0');
  57. return `${h}-${m}-${s}`;
  58. }
  59.  
  60. function takeScreenshot() {
  61. const video = getVideoElement();
  62. if (!video) return;
  63.  
  64. const canvas = document.createElement('canvas');
  65. canvas.width = video.videoWidth;
  66. canvas.height = video.videoHeight;
  67. const ctx = canvas.getContext('2d');
  68. ctx.drawImage(video, 0, 0, canvas.width, canvas.height);
  69.  
  70. const now = new Date();
  71. const title = getVideoTitle();
  72. const timestamp = formatTimestamp(now);
  73. const frameTime = formatFrameTime(video.currentTime);
  74.  
  75. const filename = `YS_${title}_${frameTime}_${timestamp}.png`;
  76. const link = document.createElement('a');
  77. link.download = filename;
  78. link.href = canvas.toDataURL('image/png');
  79. link.click();
  80. }
  81.  
  82. document.addEventListener('keydown', (e) => {
  83. if (e.key.toLowerCase() === screenshotKey && e.target.tagName !== 'INPUT' && e.target.tagName !== 'TEXTAREA') {
  84. takeScreenshot();
  85. }
  86. });
  87.  
  88. GM_registerMenuCommand(`Set Screenshot Key (Current: ${screenshotKey.toUpperCase()})`, () => {
  89. const input = prompt('Enter new hotkey (a-z):', screenshotKey);
  90. if (input && /^[a-zA-Z]$/.test(input)) {
  91. GM_setValue('screenshotKey', input.toLowerCase());
  92. location.reload();
  93. }
  94. });
  95. })();