YouTube Thumbnails - Full Video Thumbnails for YouTube

Shows complete video thumbnails for YouTube videos

当前为 2015-07-25 提交的版本,查看 最新版本

  1. // ==UserScript==
  2. // @name YouTube Thumbnails - Full Video Thumbnails for YouTube
  3. // @namespace driver8.net
  4. // @description Shows complete video thumbnails for YouTube videos
  5. // @match *://*.youtube.com/watch?*
  6. // @version 0.3.2
  7. // @require https://ajax.googleapis.com/ajax/libs/jquery/2.1.4/jquery.min.js
  8. // @grant GM_addStyle
  9. // @grant unsafeWindow
  10. // @grant GM_registerMenuCommand
  11. // @grant GM_getValue
  12. // @grant GM_setValue
  13. // ==/UserScript==
  14.  
  15. var AND_BUTTS = false;
  16. var LOAD_DELAY_START = 200;
  17. var LOAD_DELAY_FACTOR = 50;
  18. var LOAD_DELAY_MAX = 500;
  19. var TIMEOUT_DELAY = 2000;
  20. var DIV_PADDING = 10;
  21. var TD_PADDING = 2;
  22. var DISABLE_SPF = GM_getValue('ytDisableSPF', false);
  23. var LOGGING = false;
  24. var MIN_WIDTH = 4;
  25. var MAX_IMAGES = 30;
  26.  
  27. var $thumbDiv, $thumbHeader, storyboard_spec, storyboard, best_size_idx, best_size, len_seconds, videoId, tries;
  28.  
  29. function log(msg) {
  30. LOGGING && console.log(msg);
  31. }
  32. log("Hi");
  33.  
  34. setUp();
  35. function setUp() {
  36.  
  37. AND_BUTTS && $('#eow-title').text($('#eow-title').text() + " and Butts");
  38.  
  39. // See if waiting will help w/spf bullshit (it won't)
  40. tries = 0;
  41. (function trySb() {
  42. try {
  43. storyboard_spec = unsafeWindow.ytplayer.config.args.storyboard_spec;
  44. } catch (e) {
  45. log("oops " + e);
  46. try {
  47. storyboard_spec = window.ytplayer.config.args.storyboard_spec;
  48. } catch (e2) {
  49. log("oops2 " + e2);
  50. if (tries++ < 2) {
  51. window.setTimeout(trySb, TIMEOUT_DELAY);
  52. }
  53. }
  54. }
  55. })();
  56.  
  57. storyboard = parseStoryboardSpec(storyboard_spec);
  58. best_size_idx = chooseBestStoryboardSize(storyboard);
  59. best_size = storyboard.sizes[best_size_idx];
  60. log(best_size);
  61. len_seconds = parseInt(unsafeWindow.ytplayer.config.args.length_seconds);
  62. log("len: " + len_seconds);
  63.  
  64. var $titleDiv = $('#watch-header');
  65. $thumbDiv = $('<div id="thumbDiv" class="yt-card"></div>');
  66. $thumbHeader = $('<h1 width="100%">Thumbs</h1>');
  67. $thumbDiv.append($thumbHeader);
  68. $titleDiv.after($thumbDiv);
  69.  
  70. $thumbDiv.click(showThumbs);
  71.  
  72. log("Script done");
  73.  
  74. styleIt();
  75. }
  76.  
  77. function showThumbs(event) {
  78. var duration = best_size.duration / 1000;
  79. var num_thumbs = 1;
  80. if (duration > 0) {
  81. num_thumbs = Math.ceil(len_seconds / duration / best_size.cols / best_size.rows);
  82. } else {
  83. duration = len_seconds / best_size.cols / best_size.rows;
  84. }
  85. log("Thumb header clicked. Loading " + num_thumbs + " images.");
  86. var total_width = best_size.width * best_size.cols + DIV_PADDING * 2;
  87. var parent_diff = $thumbDiv.parent().innerWidth() - total_width;
  88. parent_diff < 0 && $thumbDiv.css({'left': + parent_diff + 'px'});
  89. $thumbDiv.css({'position': 'relative', 'width': total_width + 'px'});
  90.  
  91. // Grab the youtube sessionlink ID for time links
  92. var sessionlink = $('#logo-container').data("sessionlink");
  93.  
  94. var badImage = false;
  95. (function loadImage(imgNum) {
  96. if (badImage === false && imgNum < num_thumbs && imgNum < MAX_IMAGES) {
  97. // EX: https:\/\/i.ytimg.com\/sb\/2XY3AvVgDns\/storyboard3_L$L\/$N.jpg
  98. // EX: https://i.ytimg.com/sb/k4YRWT_Aldo/storyboard3_L2/M0.jpg?sigh=RVdv4fMsE-eDcsCUzIy-iCQNteI
  99. var link = storyboard.baseUrl.replace('\\', '');
  100. link = link.replace('$L', best_size_idx);
  101. link = link.replace('$N', best_size.img_name);
  102. link = link.replace('$M', imgNum);
  103. link += '?sigh=' + best_size.sigh;
  104.  
  105. log(link);
  106.  
  107. // Create a table for the timestamps over the image
  108. var $newTable = $('<table>');
  109. $newTable.addClass('imgTable');
  110. var x = imgNum * duration * best_size.rows * best_size.cols; // the starting time for this table
  111. var innerStr = '';
  112. var doclocation = document.location.href.replace(/\#.*/, '');
  113. for (var i = 0; i < best_size.rows; i++) {
  114. if (x <= len_seconds + 1) { // if we haven't reached the end of the video
  115. innerStr += '<tr>';
  116. for (var j = 0; j < best_size.cols; j++) {
  117. innerStr += '<td><a class="yt-uix-sessionlink" href="' + doclocation + '#t=' + x + '">';
  118. if (x <= len_seconds + 1) {
  119. var hrs = Math.floor(x / 3600);
  120. var mins = Math.floor((x % 3600) / 60);
  121. var secs = Math.round(x % 60);
  122. innerStr += hrs > 0 ? hrs + ':' : ''; // hrs
  123. innerStr += ( hrs > 0 && mins < 10 ? "0" + mins : mins ) + ':'; // mins
  124. innerStr += secs < 10 ? "0" + secs : secs; // secs
  125. }
  126. innerStr += '</a></td>';
  127. x += duration;
  128. }
  129. innerStr += '<tr>';
  130. }
  131. }
  132. $newTable.html(innerStr);
  133. $newTable.error(function() {
  134. badImage = true;
  135. $(this).remove();
  136. log("Hid bad image");
  137. });
  138. //$newTable.load(function() {
  139. // loadImage(imgNum + 1);
  140. //});
  141. $newTable.css({'background-image': 'url(' + link + ')', 'width': best_size.width * best_size.cols});
  142.  
  143. $thumbDiv.append($newTable);
  144.  
  145. setTimeout(loadImage, Math.min(LOAD_DELAY_START + imgNum * LOAD_DELAY_FACTOR, LOAD_DELAY_MAX), imgNum + 1);
  146. }
  147. })(0);
  148.  
  149. log("Done making thumb div");
  150. $thumbDiv.off('click');
  151. $thumbDiv.click(hideThumbs);
  152. }
  153.  
  154. function hideThumbs(event) {
  155. if ($(event.target).is('#thumbDiv, #thumbDiv h1')) {
  156. $thumbDiv.children('table').hide();
  157. $thumbDiv.off('click');
  158. $thumbDiv.click(showThumbsAgain);
  159. } else {
  160. $('html, body').scrollTop(0);
  161. }
  162. }
  163.  
  164. function showThumbsAgain(event) {
  165. if ($(event.target).is('#thumbDiv, #thumbDiv h1')) {
  166. $thumbDiv.children('table').show();
  167. $thumbDiv.off('click');
  168. $thumbDiv.click(hideThumbs);
  169. }
  170. }
  171.  
  172. function parseStoryboardSpec(spec) {
  173. // EX: https:\/\/i.ytimg.com\/sb\/Pk2oW4SDDxY\/storyboard3_L$L\/$N.jpg|48#27#100#10#10#0#default#vpw4l5h3xmm2AkCT6nMZbvFIyJw|80#45#90#10#10#2000#M$M#hCWDvBSbgeV52mPYmOHjgdLFI1o|160#90#90#5#5#2000#M$M#ys1MKEnwYXA1QAcFiugAA_cZ81Q
  174. var sections = spec.split('|');
  175. log(sections);
  176. var sb = {
  177. sizes: [],
  178. baseUrl: sections.shift()
  179. };
  180.  
  181. // EX: 80#45#90#10#10#2000#M$M#hCWDvBSbgeV52mPYmOHjgdLFI1o
  182. // EX: 160# 90# 90# 5# 5# 2000# M$M# ys1MKEnwYXA1QAcFiugAA_cZ81Q
  183. sections.forEach(function(value, idx) {
  184. var data = value.split('#');
  185. log(data);
  186. var new_size = {
  187. width : +data[0],
  188. height : +data[1],
  189. qual : +data[2], // image quality???
  190. cols : +data[3],
  191. rows : +data[4],
  192. duration : +data[5], // duration of each snapshot in milliseconds
  193. img_name : data[6],
  194. sigh : data[7]
  195. };
  196. sb.sizes[idx] = new_size;
  197. });
  198. log(sb);
  199. return sb;
  200. }
  201.  
  202. function chooseBestStoryboardSize(sb) {
  203. var sizes = sb.sizes;
  204. var widest = 0;
  205. var widest_idx = -1;
  206. for (var i = 0; i < sizes.length; i++) {
  207. if (widest < sizes[i].width || (widest == sizes[i].width && sizes[widest_idx].cols < sizes[i].cols)) {
  208. if (sizes[i].cols >= MIN_WIDTH) {
  209. widest = sizes[i].width;
  210. widest_idx = i;
  211. }
  212. }
  213. }
  214. return widest_idx;
  215. }
  216.  
  217.  
  218. function styleIt() {
  219. var userStyles0 =
  220. "table.imgTable { border-collapse: collapse; background-repeat: no-repeat; cursor: auto; !important } " +
  221. ".imgTable td { width: " + (best_size.width - 2 * TD_PADDING) + "px;" +
  222. " height: " + (best_size.height - 2 * TD_PADDING) + "px;" +
  223. " padding: " + TD_PADDING + "px;" +
  224. " border-width: 0px;" +
  225. " vertical-align: top;" +
  226. " color: white;" +
  227. " text-shadow:" +
  228. " -1px -1px 0 #000, " +
  229. " 1px -1px 0 #000, " +
  230. " -1px 1px 0 #000, " +
  231. " 1px 1px 0 #000; " +
  232. " !important}" +
  233. ".imgTable a { text-decoration: none; color: white; display: block; width: 100%; height: 100% !important }" +
  234. "#thumbDiv {padding: " + DIV_PADDING + "px; cursor: pointer; }";
  235. GM_addStyle(userStyles0);
  236.  
  237. if (DISABLE_SPF) {
  238. $('a.spf-link').each(function () {
  239. var $link = $(this);
  240. $link.removeClass('spf-link');
  241. $link.off();
  242. });
  243. }
  244.  
  245. GM_registerMenuCommand( "Toggle SPF", function toggleSpf() {
  246. DISABLE_SPF = !DISABLE_SPF;
  247. GM_setValue('ytDisableSPF', DISABLE_SPF);
  248. }, 's' );
  249. log("DONE");
  250. }
  251.  
  252.  
  253. /// TRYING TO DEAL WITH YOUTUBE'S STUPID SPF SHIT WITHOUT DISABLING IT. BLAHH! ///
  254.  
  255. //window.addEventListener('spfrequest', function() { log("SPF WUT? spfrequest"); });
  256. //window.addEventListener('spfpartprocess', function() { log("SPF WUT? spfpartprocess"); });
  257. //window.addEventListener('spfpartdone', function() { log("SPF WUT? spfpartdone"); });
  258. //window.addEventListener('spfprocess', function() { log("SPF WUT? spfprocess"); });
  259. //window.addEventListener('spfdone', function() { log("SPF WUT? spfdone"); });
  260. //window.addEventListener('spferror', function() { log("SPF WUT? spferror"); });
  261. //window.addEventListener('spfreload', function() { log("SPF WUT? spfreload"); });
  262. //window.addEventListener('spfjsbeforeunload', function() { log("SPF WUT? spfjsbeforeunload"); });
  263.  
  264. //window.addEventListener('spfdone', spfWut);
  265.  
  266. //function spfWut(a) {
  267. // log(a);
  268. // var parts = a.detail.response.parts;
  269. // var good_part = null;
  270. // parts.forEach(function(val, idx) {
  271. // good_part = good_part || (val.data && val.data.swfcfg);
  272. // });
  273. // log(good_part);
  274. // // var storyboard_spec = a.data.swfcfg.args.storyboard_spec;
  275. // storyboard_spec = good_part.args.storyboard_spec;
  276. // log("sbSpec: " + sb_spec);
  277. // best_size_idx_idx = chooseBestStoryboardSize(storyboard);
  278. // best_size = storyboard.sizes[best_size_idx_idx];
  279. // log(best_size);
  280. // len_seconds = parseInt(good_part.args.length_seconds);
  281. // log("len: " + len_seconds);
  282. // setUp();
  283. // log("BOOM");
  284. //}
  285. //
  286. //(function transitionCheck() {
  287. // $('body').on('transitionend', function(event) {
  288. // log("transitioned prog");
  289. // if (event.target.id != 'progress') return false;
  290. // log("transitioned event");
  291. // setUp();
  292. // log("BOOM");
  293. // transitionCheck();
  294. // });
  295. //})();