CapTube

"S"キーを押すだけでYouTubeのスクリーンショットを保存

目前为 2016-11-09 提交的版本。查看 最新版本

  1. // ==UserScript==
  2. // @name CapTube
  3. // @namespace https://github.com/segabito/
  4. // @description "S"キーを押すだけでYouTubeのスクリーンショットを保存
  5. // @include https://www.youtube.com/*
  6. // @include https://youtube.com/*
  7. // @version 0.0.1
  8. // @grant none
  9. // @license public domain
  10. // @noframes
  11. // ==/UserScript==
  12.  
  13. (function() {
  14.  
  15. let previewContainer = null;
  16. const addStyle = function(styles, id) {
  17. var elm = document.createElement('style');
  18. elm.type = 'text/css';
  19. if (id) { elm.id = id; }
  20.  
  21. var text = styles.toString();
  22. text = document.createTextNode(text);
  23. elm.appendChild(text);
  24. var head = document.getElementsByTagName('head');
  25. head = head[0];
  26. head.appendChild(elm);
  27. return elm;
  28. };
  29.  
  30. const __css__ = (`
  31. #CapTubePreviewContainer {
  32. position: fixed;
  33. padding: 8px;
  34. width: 90%;
  35. bottom: 100px;
  36. left: 5%;
  37. z-index: 10000;
  38. pointer-events: none;
  39. transform: translateZ(0);
  40. background: rgba(192, 192, 192, 0.4);
  41. /*border: 2px solid #ccc;*/
  42. -webkit-user-select: none;
  43. user-select: none;
  44. }
  45.  
  46. #CapTubePreviewContainer:empty {
  47. display: none;
  48. }
  49. #CapTubePreviewContainer canvas {
  50. display: inline-block;
  51. width: 128px;
  52. margin-right: 16px;
  53. margin-bottom: 16px;
  54. box-shadow: 4px 4px 4px #000;
  55. transition:
  56. 0.4s opacity ease,
  57. 0.4s width ease 0.5s,
  58. 0.4s margin-right ease 0.5s;
  59. }
  60.  
  61. #CapTubePreviewContainer canvas.is-removing {
  62. opacity: 0;
  63. margin-right: 0;
  64. width: 0;
  65. }
  66.  
  67. `).trim();
  68.  
  69. addStyle(__css__);
  70.  
  71. const getVideoId = function() {
  72. var id = '';
  73. location.search.substring(1).split('&').forEach(function(item){
  74. if (item.split('=')[0] === 'v') { id = item.split('=')[1]; }
  75. });
  76. return id;
  77. };
  78.  
  79. const toSafeName = function(text) {
  80. text = text.trim()
  81. .replace(/</g, '<')
  82. .replace(/>/g, '>')
  83. .replace(/\?/g, '?')
  84. .replace(/:/g, ':')
  85. .replace(/\|/g, '|')
  86. .replace(/\//g, '/')
  87. .replace(/\\/g, '¥')
  88. .replace(/"/g, '”')
  89. .replace(/\./g, '.')
  90. ;
  91. return text;
  92. };
  93.  
  94. const getVideoTitle = function() {
  95. var videoId = getVideoId();
  96. var title = document.querySelector('.watch-title');
  97. var authorName = toSafeName(document.querySelector('.yt-user-info a').text);
  98. var titleText = toSafeName(title.textContent);
  99. titleText = titleText + ' - by ' + authorName + ' (v=' + videoId + ')';
  100.  
  101. return titleText;
  102. };
  103.  
  104. const createCanvasFromVideo = function(video) {
  105. const width = video.videoWidth;
  106. const height = video.videoHeight;
  107. const canvas = document.createElement('canvas');
  108. canvas.width = width;
  109. canvas.height = height;
  110. const context = canvas.getContext('2d');
  111. context.drawImage(video, 0, 0);
  112. return canvas;
  113. };
  114.  
  115. const getFileName = function(video) {
  116. const title = getVideoTitle();
  117. const currentTime = video.currentTime;
  118. const min = Math.floor(currentTime / 60);
  119. const sec = (currentTime % 60 + 100).toString().substr(1, 6);
  120. const time = `${min}_${sec}`;
  121.  
  122. return `${title}@${time}.png`;
  123. };
  124.  
  125. const createBlobLinkElement = function(canvas, fileName) {
  126. const dataUrl = canvas.toDataURL('image/png');
  127. const bin = atob(dataUrl.split(',')[1]);
  128. const buf = new Uint8Array(bin.length);
  129. for (let i = 0, len = buf.length; i < len; i++) { buf[i] = bin.charCodeAt(i); }
  130. const blob = new Blob([buf.buffer], {type: 'image/png'});
  131. const url = window.URL.createObjectURL(blob);
  132.  
  133. const link = document.createElement('a');
  134. link.setAttribute('download', fileName);
  135. link.setAttribute('target', '_blank');
  136. link.setAttribute('href', url);
  137.  
  138. return link;
  139. };
  140.  
  141. const saveScreenShot = function() {
  142. const video = document.querySelector('.html5-main-video');
  143. if (!video) { return; }
  144.  
  145. const canvas = createCanvasFromVideo(video);
  146. const fileName = getFileName(video);
  147.  
  148. const link = createBlobLinkElement(canvas, fileName);
  149.  
  150. if (previewContainer) {
  151. previewContainer.appendChild(canvas);
  152. setTimeout(function() {
  153. canvas.classList.add('is-removing');
  154. setTimeout(function() { canvas.remove(); }, 1000);
  155. }, 1500);
  156. }
  157.  
  158. document.body.appendChild(link);
  159. link.click();
  160. setTimeout(function() { link.remove(); }, 1000);
  161. };
  162.  
  163. const setPlaybackRate = function(v) {
  164. const video = document.querySelector('.html5-main-video');
  165. if (!video) { return; }
  166. video.playbackRate = v;
  167. };
  168.  
  169. const togglePlay = function() {
  170. const video = document.querySelector('.html5-main-video');
  171. if (!video) { return; }
  172.  
  173. if (video.paused) {
  174. video.play();
  175. } else {
  176. video.pause();
  177. }
  178. };
  179.  
  180. const seekBy = function(v) {
  181. const video = document.querySelector('.html5-main-video');
  182. if (!video) { return; }
  183.  
  184. const ct = Math.max(video.currentTime + v, 0);
  185. video.currentTime = ct;
  186. };
  187.  
  188. let isVerySlow = false;
  189. const onKeyDown = (e) => {
  190. const key = e.key.toLowerCase();
  191. switch (key) {
  192. case 'd':
  193. setPlaybackRate(0.1);
  194. isVerySlow = true;
  195. break;
  196. case 's':
  197. saveScreenShot();
  198. break;
  199. }
  200. };
  201.  
  202. const onKeyUp = (e) => {
  203. console.log('onKeyUp', e);
  204. const key = e.key.toLowerCase();
  205. switch (key) {
  206. case 'd':
  207. setPlaybackRate(1);
  208. isVerySlow = false;
  209. break;
  210. }
  211. };
  212.  
  213. const onKeyPress = (e) => {
  214. const key = e.key.toLowerCase();
  215. switch (key) {
  216. case 'w':
  217. togglePlay();
  218. break;
  219. case 'a':
  220. seekBy(isVerySlow ? -0.5 : -5);
  221. break;
  222. }
  223. };
  224.  
  225.  
  226. const initDom = function() {
  227. const div = document.createElement('div');
  228. div.id = 'CapTubePreviewContainer';
  229. document.body.appendChild(div);
  230. previewContainer = div;
  231. };
  232.  
  233. const initialize = function() {
  234. initDom();
  235. window.addEventListener('keydown', onKeyDown);
  236. window.addEventListener('keyup', onKeyUp);
  237. window.addEventListener('keypress', onKeyPress);
  238. };
  239.  
  240. initialize();
  241. })();