Greasy Fork is available in English.

YouTube ScreenShoter

Screenshots current YT frame in full quality

  1. // ==UserScript==
  2. // @name YouTube ScreenShoter
  3. // @namespace http://tampermonkey.net/
  4. // @version 1.0.4
  5. // @description Screenshots current YT frame in full quality
  6. // @author DoctorDeathDDracula & Sticky
  7. // @match *://*.youtube.com/*
  8. // @license MIT
  9. // @icon https://www.google.com/s2/favicons?sz=64&domain=youtube.com
  10. // @grant none
  11. // @run-at document-start
  12. // @noframes
  13. // ==/UserScript==
  14.  
  15.  
  16. (function() {
  17. 'use strict';
  18.  
  19. function YouTubeScreenShoter() {}
  20.  
  21. YouTubeScreenShoter.prototype.storage = {
  22. selectors: {
  23. reactionButton: 'ytd-toggle-button-renderer',
  24. screenshotButton: "#__my-screenshot-button",
  25. reactionPanel: "#top-level-buttons-computed"
  26. },
  27. ids: {
  28. screenshotButton: '__my-screenshot-button'
  29. },
  30. icons: {
  31. iconBlackSVG: '<svg xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:cc="http://creativecommons.org/ns#" xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns:svg="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg" xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" sodipodi:docname="iconB.svg" inkscape:version="1.0 (4035a4fb49, 2020-05-01)" id="svg8" version="1.1" viewBox="0 0 6.3499999 6.35" height="24" width="24"><defs id="defs2"/><sodipodi:namedview inkscape:window-maximized="1" inkscape:window-y="-8" inkscape:window-x="-8" inkscape:window-height="1017" inkscape:window-width="1920" units="px" showgrid="false" inkscape:document-rotation="0" inkscape:current-layer="layer1" inkscape:document-units="mm" inkscape:cy="9.5831076" inkscape:cx="11.629344" inkscape:zoom="22.4" inkscape:pageshadow="2" inkscape:pageopacity="0.0" borderopacity="1.0" bordercolor="#666666" pagecolor="#ffffff" id="base"/><metadata id="metadata5"><rdf:RDF><cc:Work rdf:about=""><dc:format>image/svg+xml</dc:format><dc:type rdf:resource="http://purl.org/dc/dcmitype/StillImage"/><dc:title/></cc:Work></rdf:RDF></metadata><g inkscape:label="Слой 2" id="layer2" inkscape:groupmode="layer"/><g id="layer1" inkscape:groupmode="layer" inkscape:label="Слой 1"><path id="path10" d="m 0.86839843,2.5970013 0.007815,-1.72490613 1.71861757,0.006879" style="fill:none;stroke:#000;stroke-width:.52916667;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"/><path style="fill:none;stroke:#000;stroke-width:.52916667;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" d="M 5.4816006,2.5933023 5.4737906,0.8684026 3.7551742,0.8752816" id="path10-5"/><path style="fill:none;stroke:#000;stroke-width:.52916667;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" d="m 0.86810411,3.7567748 0.007815,1.7214547 1.71884519,-0.00686" id="path10-55"/><path id="path10-5-2" d="m 5.4819176,3.7604659 -0.00781,1.7214488 -1.7188442,-0.00685" style="fill:none;stroke:#000;stroke-width:.52916667;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"/></g></svg>',
  32. iconWhiteSVG: '<svg xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:cc="http://creativecommons.org/ns#" xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns:svg="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg" xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" sodipodi:docname="iconW.svg" inkscape:version="1.0 (4035a4fb49, 2020-05-01)" id="svg8" version="1.1" viewBox="0 0 6.3499999 6.35" height="24" width="24"><defs id="defs2"/><sodipodi:namedview inkscape:window-maximized="1" inkscape:window-y="-8" inkscape:window-x="-8" inkscape:window-height="1017" inkscape:window-width="1920" units="px" showgrid="false" inkscape:document-rotation="0" inkscape:current-layer="layer1" inkscape:document-units="mm" inkscape:cy="9.5831076" inkscape:cx="11.629344" inkscape:zoom="22.4" inkscape:pageshadow="2" inkscape:pageopacity="0.0" borderopacity="1.0" bordercolor="#666666" pagecolor="#ffffff" id="base"/><metadata id="metadata5"><rdf:RDF><cc:Work rdf:about=""><dc:format>image/svg+xml</dc:format><dc:type rdf:resource="http://purl.org/dc/dcmitype/StillImage"/><dc:title></dc:title></cc:Work></rdf:RDF></metadata><g inkscape:label="Слой 2" id="layer2" inkscape:groupmode="layer"/><g id="layer1" inkscape:groupmode="layer" inkscape:label="Слой 1"><path id="path10" d="m 0.86839843,2.5970013 0.007815,-1.72490613 1.71861757,0.006879" style="fill:none;stroke:#fff;stroke-width:.52916667;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"/><path style="fill:none;stroke:#fff;stroke-width:.52916667;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" d="M 5.4816006,2.5933023 5.4737906,0.8684026 3.7551742,0.8752816" id="path10-5"/><path style="fill:none;stroke:#fff;stroke-width:.52916667;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" d="m 0.86810411,3.7567748 0.007815,1.7214547 1.71884519,-0.00686" id="path10-55"/><path id="path10-5-2" d="m 5.4819176,3.7604659 -0.00781,1.7214488 -1.7188442,-0.00685" style="fill:none;stroke:#fff;stroke-width:.52916667;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"/></g></svg>'
  33. },
  34. styles: '.__my-screenshot-animation {opacity: 0 !important}'
  35.  
  36. };
  37.  
  38. YouTubeScreenShoter.prototype.init = function() {
  39. document.addEventListener('DOMContentLoaded', this.onDOMContentLoaded.bind(this));
  40. this.setTriggerOnElement(this.storage.selectors.reactionPanel, 'addedNodes', this.onNewPanel.bind(this));
  41. };
  42.  
  43. YouTubeScreenShoter.prototype.cc = function(tag, options, parent, init) {
  44. options = options || {};
  45. var children = options.children || [];
  46. delete options.children;
  47. var element = Object.assign(document.createElement(tag), options);
  48. for (var i = 0; i < children.length; i++) element.appendChild(children[i]);
  49. if (typeof init == 'function') init.call(element);
  50. return parent && parent.nodeType === Node.ELEMENT_NODE ? parent.appendChild(element) : element;
  51. };
  52.  
  53. YouTubeScreenShoter.prototype.ss = function(selector, searchIn, all) {
  54. searchIn = searchIn || document;
  55. return all ? searchIn.querySelectorAll(selector) : searchIn.querySelector(selector);
  56. };
  57.  
  58. YouTubeScreenShoter.prototype.onDOMContentLoaded = function() {
  59. this.addStyles();
  60. };
  61.  
  62. YouTubeScreenShoter.prototype.addStyles = function() {
  63. this.cc('style', {
  64. textContent: this.storage.styles
  65. }, document.head);
  66. };
  67.  
  68. YouTubeScreenShoter.prototype.onNewPanel = function(panel) {
  69. this.initPanel(panel);
  70. this.setTriggerOnElement(this.storage.selectors.screenshotButton, 'removedNodes', (function() {
  71. this.initPanel(panel);
  72. }).bind(this), false, panel);
  73. };
  74.  
  75. YouTubeScreenShoter.prototype.initPanel = function(panel) {
  76. if (panel.children.length !== 0) {
  77. var myButton = this.ss(this.storage.selectors.screenshotButton, panel);
  78. if (!myButton) {
  79. var reactionButton = this.ss(this.storage.selectors.reactionButton, panel);
  80. if (reactionButton && reactionButton.nextElementSibling) {
  81. reactionButton.nextElementSibling.after(this.generateButton());
  82. }
  83. }
  84. }
  85. };
  86.  
  87. YouTubeScreenShoter.prototype.setTriggerOnElement = function(selector, action, callback, once, searchIn) {
  88. var observer = new MutationObserver(function(mutations) {
  89. for (var mi = 0; mi < mutations.length; mi++) {
  90. var mutation = mutations[mi];
  91. var addedNodes = mutation[action] || [];
  92. for (var ani = 0; ani < addedNodes.length; ani++) {
  93. var node = addedNodes[ani];
  94. var element = node.matches && node.matches(selector) ? node : (node.querySelector ? node.querySelector(selector) : null);
  95. if (element) {
  96. if (once) {
  97. observer.disconnect();
  98. return callback(element);
  99. } else {
  100. callback(element);
  101. }
  102. }
  103. }
  104. }
  105. });
  106.  
  107. observer.observe(searchIn || document, {
  108. attributes: false,
  109. childList: true,
  110. subtree: true
  111. });
  112.  
  113. return observer;
  114. };
  115.  
  116. YouTubeScreenShoter.prototype.generateButton = function() {
  117. return this.cc("button", {
  118. id: this.storage.ids.screenshotButton,
  119. style: "appearance:none;border:none;outline:none;background-color:#0000;padding:0;display:flex;cursor:pointer;",
  120. children: [
  121. this.cc("div", {
  122. style: "padding:6px;",
  123. children: [
  124. this.SVGPacker(document.firstElementChild.getAttribute("dark") == 'true' ? this.storage.icons.iconWhiteSVG : this.storage.icons.iconBlackSVG)
  125. ]
  126. }),
  127. this.cc("div", {
  128. children: [
  129. this.cc("div", {
  130. style: this.packInlineStyle(this.getNoneDefaultStyle(this.ss("#text", this.ss(this.storage.selectors.reactionButton)))),
  131. textContent: "PRTSC"
  132. }, false, function() {
  133. this.style.width = "";
  134. this.style.padding = "10px 10px 0px 0px";
  135. })
  136. ]
  137. })
  138. ],
  139. onclick: (function(event) {
  140. var video = this.ss("video");
  141. var canvas = this.cc("canvas", {
  142. width: video.videoWidth,
  143. height: video.videoHeight
  144. });
  145. var context = canvas.getContext('2d');
  146.  
  147. context.drawImage(video, 0, 0, canvas.width, canvas.height);
  148.  
  149. if (event.ctrlKey) {
  150. this.cc("a", {
  151. download: "download",
  152. href: canvas.toDataURL("image/png")
  153. }).click();
  154. } else {
  155. if ('ClipboardItem' in window) {
  156. canvas.toBlob(this.onCopyImage.bind(this));
  157. } else {
  158. alert('Sorry, your browser does not support coping, try to download it by pressing screenshot button with ctrl key pressed.')
  159. }
  160. }
  161. }).bind(this)
  162. });
  163. };
  164.  
  165. YouTubeScreenShoter.prototype.onCopyImage = function(blob) {
  166. navigator.clipboard.write([new window.ClipboardItem({
  167. "image/png": blob
  168. })]);
  169. };
  170.  
  171. YouTubeScreenShoter.prototype.getComputedStyle = window.getComputedStyle.bind(window);
  172.  
  173. YouTubeScreenShoter.prototype.getNoneDefaultStyle = function(node) {
  174. var styles = [];
  175. var supportElement = this.cc(node.tagName, {
  176. visible: false
  177. }, document.body);
  178. var elementStyles = this.getComputedStyle(node);
  179. var defaultStyles = this.getComputedStyle(supportElement);
  180. var keys = Object.keys(defaultStyles);
  181. for (var i = 0; i < keys.length; i++) {
  182. if (elementStyles[keys[i]] !== defaultStyles[keys[i]] && defaultStyles[keys[i]] !== '') styles.push([keys[i], elementStyles[keys[i]]]);
  183. }
  184. supportElement.remove();
  185. return styles;
  186. };
  187.  
  188. YouTubeScreenShoter.prototype.packInlineStyle = function(style) {
  189. return style.reduce(function(pre, cur) {
  190. return pre + ";" + cur[0] + ":" + cur[1];
  191. }, "") + ";";
  192. };
  193.  
  194. YouTubeScreenShoter.prototype.SVGPacker = function(SVGXML) {
  195. return this.cc("div", {
  196. innerHTML: SVGXML
  197. }).firstChild;
  198. };
  199.  
  200. var YTSS = new YouTubeScreenShoter();
  201. YTSS.init();
  202.  
  203. })();
  204.