YouTube Thumbnails - Full Video Thumbnails for YouTube

Shows complete video thumbnails for YouTube videos. You can click a thumbnail image to jump to that point in the video.

  1. // ==UserScript==
  2. // @name YouTube Thumbnails - Full Video Thumbnails for YouTube
  3. // @namespace driver8.net
  4. // @description Shows complete video thumbnails for YouTube videos. You can click a thumbnail image to jump to that point in the video.
  5. // @match *://*.youtube.com/*
  6. // @version 0.4.10
  7. // @require https://ajax.googleapis.com/ajax/libs/jquery/2.2.4/jquery.min.js
  8. // @grant GM_addStyle
  9. // @grant unsafeWindow
  10. // @grant GM_registerMenuCommand
  11. // @grant GM_getValue
  12. // @grant GM_setValue
  13. // @grant GM_xmlhttpRequest
  14. // @run-at document-idle
  15. // ==/UserScript==
  16.  
  17. var AND_BUTTS = GM_getValue('ytAndButts', false);
  18. var BEFORE_TITLE = true;
  19. var TIMEOUT_DELAY = 50;
  20. var MAX_TRIES = 1;
  21. var DIV_PADDING = 10;
  22. var TD_PADDING = 2;
  23. var DISABLE_SPF = GM_getValue('ytDisableSPF', false);
  24. var MIN_WIDTH = 4;
  25. var MAX_IMAGES = 400;
  26. var LOGGING = true;
  27.  
  28. var $thumbDiv, $thumbHeader, $titleDiv, storyboard_spec, storyboard, best_size_idx, best_size, len_seconds = 0, video_id, current_id,
  29. tries, yp, mp, args, scriptStarted = false, scriptFinished = false;
  30.  
  31. function log(...msg) {
  32. LOGGING && console.log(...msg);
  33. }
  34.  
  35. console.log("Hi, yt thumbs");
  36.  
  37. function findStuff(event) {
  38. event && console.log(event);
  39. if (!window.location.href.match(/watch\?/) || (scriptStarted && !scriptFinished)) {
  40. console.log('scriptStarted', scriptStarted, 'scriptFinished', scriptFinished);
  41. console.log("don't run");
  42. return;
  43. }
  44. console.log("started...");
  45. scriptStarted = true;
  46. scriptFinished = false;
  47. storyboard_spec = null;
  48.  
  49. var m = document.location.href.match(/(?:watch\?.*v=|\/v\/)([\w\-=]+)/);
  50. current_id = m[1];
  51. if ($('#thumbDiv.yes-thumbs').size() > 0) {
  52. console.log('current_id', current_id, 'existing id', $('#thumbDiv.yes-thumbs').data('video_id'));
  53. if ($('#thumbDiv.yes-thumbs').data('video_id') === current_id) {
  54. scriptFinished = true;
  55. return;
  56. } else {
  57. $('#thumbDiv.yes-thumbs').remove();
  58. }
  59. } else if ($('#thumbDiv').size() > 0) { //no-thumbs
  60. $('#thumbDiv').remove();
  61. }
  62.  
  63. try {
  64. console.log('trying ytplayer');
  65. yp = (window && window.ytplayer) || (unsafeWindow && unsafeWindow.ytplayer);
  66. args = yp.config.args;
  67. video_id = args.video_id;
  68. if (video_id === current_id) {
  69. storyboard_spec = args.storyboard_spec;
  70. len_seconds = parseInt(args.length_seconds);
  71. if (!storyboard_spec) {
  72. var pr = JSON.parse(args.player_response);
  73. storyboard_spec = pr.storyboards.playerStoryboardSpecRenderer.spec;
  74. }
  75. }
  76. } catch (e) {
  77. console.log('problem loading storyboard_spec from ytplayer.config', e);
  78. }
  79. if (video_id !== current_id || !storyboard_spec) {
  80. try {
  81. console.log('try #movie_player');
  82. mp = document.querySelector('div#movie_player');
  83. if (mp === null) {
  84. var frame = document.querySelector('iframe#player-container');
  85. mp = frame.contentWindow.document.querySelector('div.html5-video-player');
  86. }
  87. //args = mp.getUpdatedConfigurationData().args;
  88. //video_id = args.video_id;
  89. video_id = mp.getVideoData().video_id;
  90. if (video_id === current_id) {
  91. //storyboard_spec = args.storyboard_spec;
  92. //len_seconds = parseInt(args.length_seconds);
  93. storyboard_spec = mp.getStoryboardFormat();
  94. len_seconds = parseInt(mp.getDuration());
  95. }
  96. } catch (e) {
  97. console.log('problem loading storyboard_spec from #movie_player', e);
  98. }
  99. }
  100. console.log('yp', yp, 'mp', mp, 'args', args, 'sb_spec', storyboard_spec);
  101.  
  102. if (video_id === current_id && storyboard_spec) {
  103. tries = 0;
  104. tryTitle();
  105. } else {
  106. tryXHR1();
  107. }
  108. }
  109.  
  110. function getInfoFromXHR(args) {
  111. console.log('args from xhr', args);
  112. storyboard_spec = args.storyboard_spec;
  113. len_seconds = parseInt(args.length_seconds);
  114. video_id = args.video_id;
  115. if (!storyboard_spec) {
  116. var pr = JSON.parse(args.player_response);
  117. storyboard_spec = pr.storyboards.playerStoryboardSpecRenderer.spec;
  118. }
  119. }
  120.  
  121. function tryXHR1() {
  122. console.log('try get_video_info xhr');
  123. storyboard_spec = null;
  124. var oReq = new XMLHttpRequest();
  125. oReq.addEventListener('load', function(evt) {
  126. console.log('evt', evt, 'resp', this.response);
  127. try {
  128. var params = getParameters(this.responseText);
  129. getInfoFromXHR(params);
  130. } catch (e) {
  131. console.log('problem loading storyboard_spec from get_video_info xhr', e);
  132. } finally {
  133. if (video_id === current_id && storyboard_spec) {
  134. tryTitle();
  135. } else {
  136. tryXHR2();
  137. }
  138. }
  139. });
  140. oReq.open('GET', "https://www.youtube.com/get_video_info?asv=3&video_id=" + current_id);
  141. oReq.send();
  142. }
  143.  
  144. function tryXHR2() {
  145. console.log('try pbj xhr');
  146. storyboard_spec = null;
  147. var y = window.ytcfg || unsafeWindow.ytcfg;
  148. console.log('ytcfg', y);
  149. var reqHeaders = {
  150. //"Referer": document.location.href,
  151. "X-SPF-Previous": document.location.href,
  152. "X-SPF-Referer": document.location.href,
  153. "X-YouTube-Client-Name": y.data_.INNERTUBE_CONTEXT_CLIENT_NAME,
  154. "X-YouTube-Client-Version": y.data_.INNERTUBE_CONTEXT_CLIENT_VERSION,
  155. //"X-Youtube-Identity-Token": y.data_.XSRF_TOKEN,
  156. "X-Youtube-Identity-Token": y.data_.ID_TOKEN,
  157. "X-YouTube-Page-CL": y.data_.PAGE_CL,
  158. "X-YouTube-Page-Label": y.data_.PAGE_BUILD_LABEL,
  159. "X-YouTube-STS": y.data_.FILLER_DATA.player.sts,
  160. "X-YouTube-Utc-Offset": 0,
  161. "X-YouTube-Variants-Checksum": y.data_.VARIANTS_CHECKSUM
  162. };
  163. console.log('req headers', reqHeaders);
  164.  
  165. var oReq = new XMLHttpRequest();
  166. oReq.addEventListener('load', function(evt) {
  167. console.log('evt', evt, 'resp', this.response);
  168. try {
  169. var args = this.response[2].player.args;
  170. getInfoFromXHR(args)
  171. } catch (e) {
  172. console.log('problem loading storyboard_spec from pbj xhr json', e);
  173. }
  174. tries = 0;
  175. tryTitle();
  176. });
  177. oReq.open('GET', document.location.href + '&pbj=1');
  178. oReq.responseType = 'json';
  179. for (var h in reqHeaders) {
  180. oReq.setRequestHeader(h, reqHeaders[h]);
  181. }
  182. oReq.send();
  183. }
  184.  
  185. function tryTitle() {
  186. console.log('tryTitle');
  187. //$titleDiv = $('div#container.ytd-video-primary-info-renderer, div#info.ytd-watch, #watch-header');
  188. //$titleDiv = $('ytd-video-primary-info-renderer.ytd-watch-flexy, div#info.ytd-watch, #watch-header');
  189. $titleDiv = $('div#info.ytd-watch-flexy, div#info.ytd-watch, #watch-header');
  190. if (!$titleDiv || $titleDiv.prop('nodeName') !== 'DIV') {
  191. console.log("no title div");
  192. if (tries++ < MAX_TRIES) {
  193. console.log("trying again for title div");
  194. window.setTimeout(tryTitle, TIMEOUT_DELAY);
  195. } else {
  196. console.log("done trying for title div");
  197. scriptFinished = true;
  198. }
  199. } else {
  200. console.log('found title div', $titleDiv);
  201. setUp();
  202. }
  203. }
  204.  
  205. function setUp() {
  206. if (storyboard_spec) {
  207. console.log('sb_spec', storyboard_spec);
  208. storyboard = parseStoryboardSpec(storyboard_spec);
  209. console.log('sb', storyboard);
  210. best_size_idx = chooseBestStoryboardSize(storyboard);
  211. best_size = storyboard.sizes[best_size_idx];
  212. console.log(best_size);
  213. if (! len_seconds > 0) {
  214. var len_str = $('div.ytp-progress-bar').attr('aria-valuemax') || yp.config.args.length_seconds;
  215. len_seconds = parseInt(len_str);
  216. }
  217. console.log("len: ", len_seconds);
  218.  
  219. $thumbDiv = $('<div id="thumbDiv" class="style-scope yt-card yes-thumbs thumb-inactive"></div>');
  220. $thumbDiv.data('video_id', video_id);
  221. $thumbHeader = $('<h1 width="100%" class="title ytd-video-primary-info-renderer">Thumbnails</h1>');
  222. $thumbDiv.append($thumbHeader);
  223. $thumbDiv.click(showThumbs);
  224. } else if (unsafeWindow.ytplayer) {
  225. $thumbDiv = $('<div id="thumbDiv" class="style-scope yt-card no-thumbs"></div>');
  226. $thumbHeader = $('<h1 width="100%" class="title ytd-video-primary-info-renderer">No Thumbnails Available</h1>');
  227. $thumbDiv.append($thumbHeader);
  228. } else {
  229. console.log("Reload for thumbs");
  230. $thumbDiv = $('<div id="thumbDiv" class="style-scope yt-card no-thumbs"></div>');
  231. $thumbHeader = $('<h1 width="100%" class="title ytd-video-primary-info-renderer">Reload for Thumbnails</h1>');
  232. $thumbDiv.append($thumbHeader);
  233. }
  234.  
  235. if (BEFORE_TITLE) {
  236. $titleDiv.before($thumbDiv);
  237. } else {
  238. $titleDiv.after($thumbDiv);
  239. }
  240. console.log($titleDiv);
  241.  
  242. console.log("Script done");
  243.  
  244. styleIt();
  245.  
  246. console.log("finished.");
  247. scriptFinished = true;
  248. }
  249.  
  250. function showThumbs(event) {
  251. var duration = best_size.duration / 1000;
  252. var num_thumbs = 1;
  253. if (duration > 0) {
  254. num_thumbs = Math.ceil(len_seconds / duration / best_size.cols / best_size.rows);
  255. } else {
  256. duration = len_seconds / best_size.cols / best_size.rows;
  257. }
  258. console.log("Thumb header clicked. Loading " + num_thumbs + " images.");
  259. var total_width = best_size.width * best_size.cols + DIV_PADDING * 2;
  260. var parent_diff = $thumbDiv.parent().innerWidth() - total_width;
  261. parent_diff < 0 && $thumbDiv.css({'left': + parent_diff + 'px'});
  262. // $thumbDiv.css({'position': 'relative', 'width': total_width + 'px'});
  263. // $thumbDiv.css({'position': 'relative'});
  264. $thumbDiv.removeClass('thumb-inactive');
  265. $thumbDiv.addClass('thumb-active');
  266.  
  267. // Grab the youtube sessionlink ID for time links
  268. var sessionlink = $('#logo-container').data("sessionlink");
  269.  
  270. var badImage = false;
  271. (function loadImage(imgNum) {
  272. if (badImage === false && imgNum < num_thumbs && imgNum < MAX_IMAGES) {
  273. // EX: https:\/\/i.ytimg.com\/sb\/2XY3AvVgDns\/storyboard3_L$L\/$N.jpg
  274. // EX: https://i.ytimg.com/sb/k4YRWT_Aldo/storyboard3_L2/M0.jpg?sigh=RVdv4fMsE-eDcsCUzIy-iCQNteI
  275. var link = storyboard.baseUrl.replace('\\', '');
  276. link = link.replace('$L', best_size_idx);
  277. link = link.replace('$N', best_size.img_name);
  278. link = link.replace('$M', imgNum);
  279. if (link.indexOf('?') > -1) {
  280. link += '&';
  281. } else {
  282. link += '?';
  283. }
  284. link += 'sigh=' + best_size.sigh;
  285.  
  286. console.log(link);
  287.  
  288. // Create a table for the timestamps over the image
  289. var $newTable = $('<table>');
  290. $newTable.addClass('imgTable');
  291. var x = imgNum * duration * best_size.rows * best_size.cols; // the starting time for this table
  292. var innerStr = '';
  293. var doclocation = document.location.href.replace(/\#.*/, '');
  294. for (var i = 0; i < best_size.rows; i++) {
  295. if (x <= len_seconds + 1) { // if we haven't reached the end of the video
  296. //console.log( 'x', x, 'len_seconds', len_seconds);
  297. innerStr += '<tr>';
  298. for (var j = 0; j < best_size.cols; j++) {
  299. innerStr += '<td><a href="' + doclocation + '#t=' + x + '" data-seek-to="' + x + '">';
  300. if (x <= len_seconds + 1) {
  301. var hrs = Math.floor(x / 3600);
  302. var mins = Math.floor((x % 3600) / 60);
  303. var secs = Math.round(x % 60);
  304. innerStr += hrs > 0 ? hrs + ':' : ''; // hrs
  305. innerStr += ( hrs > 0 && mins < 10 ? "0" + mins : mins ) + ':'; // mins
  306. innerStr += secs < 10 ? "0" + secs : secs; // secs
  307. }
  308. innerStr += '</a></td>';
  309. x += duration;
  310. }
  311. innerStr += '<tr>';
  312. }
  313. }
  314. $newTable.html(innerStr);
  315. $newTable.error(function() {
  316. badImage = true;
  317. $(this).remove();
  318. console.log("Hid bad image");
  319. });
  320. //$newTable.load(function() {
  321. // loadImage(imgNum + 1);
  322. //});
  323. $newTable.css({'background-image': 'url(' + link + ')', 'width': best_size.width * best_size.cols});
  324.  
  325. $thumbDiv.append($newTable);
  326.  
  327. //setTimeout(loadImage, Math.min(LOAD_DELAY_START + imgNum * LOAD_DELAY_FACTOR, LOAD_DELAY_MAX), imgNum + 1);
  328. setTimeout(loadImage, 1, imgNum + 1);
  329. }
  330.  
  331. $('.imgTable a').click(function(event) {
  332. event.preventDefault();
  333. var seekTime = parseInt($(this).data('seekTo'));
  334. console.log('seek time', seekTime);
  335. $('video.html5-main-video')[0].currentTime = seekTime;
  336. $('html, body').scrollTop(0);
  337. event.stopImmediatePropagation();
  338. });
  339. })(0);
  340.  
  341. console.log("Done making thumb div");
  342. $thumbDiv.off('click');
  343. $thumbDiv.click(hideThumbs);
  344. }
  345.  
  346. function hideThumbs(event) {
  347. if ($(event.target).is('#thumbDiv, #thumbDiv h1')) {
  348. $thumbDiv.children('table').hide();
  349. $thumbDiv.off('click');
  350. $thumbDiv.click(showThumbsAgain);
  351. $thumbDiv.removeClass('thumb-active');
  352. $thumbDiv.addClass('thumb-inactive');
  353. } else {
  354. $('html, body').scrollTop(0);
  355. }
  356. }
  357.  
  358. function showThumbsAgain(event) {
  359. if ($(event.target).is('#thumbDiv, #thumbDiv h1')) {
  360. $thumbDiv.children('table').show();
  361. $thumbDiv.off('click');
  362. $thumbDiv.click(hideThumbs);
  363. $thumbDiv.removeClass('thumb-inactive');
  364. $thumbDiv.addClass('thumb-active');
  365. }
  366. }
  367.  
  368. function parseStoryboardSpec(spec) {
  369. // 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
  370. var sections = spec.split('|');
  371. console.log(sections);
  372. var sb = {
  373. sizes: [],
  374. baseUrl: sections.shift()
  375. };
  376.  
  377. // EX: 80#45#90#10#10#2000#M$M#hCWDvBSbgeV52mPYmOHjgdLFI1o
  378. // EX: 160# 90# 90# 5# 5# 2000# M$M# ys1MKEnwYXA1QAcFiugAA_cZ81Q
  379. sections.forEach(function(value, idx) {
  380. var data = value.split('#');
  381. console.log(data);
  382. var new_size = {
  383. width : +data[0],
  384. height : +data[1],
  385. qual : +data[2], // image quality???
  386. cols : +data[3],
  387. rows : +data[4],
  388. duration : +data[5], // duration of each snapshot in milliseconds
  389. img_name : data[6],
  390. sigh : data[7]
  391. };
  392. sb.sizes[idx] = new_size;
  393. });
  394. console.log(sb);
  395. return sb;
  396. }
  397.  
  398. function chooseBestStoryboardSize(sb) {
  399. var sizes = sb.sizes;
  400. var widest = 0;
  401. var widest_idx = -1;
  402. for (var i = 0; i < sizes.length; i++) {
  403. if (widest < sizes[i].width || (widest == sizes[i].width && sizes[widest_idx].cols < sizes[i].cols)) {
  404. if (sizes[i].cols >= MIN_WIDTH) {
  405. widest = sizes[i].width;
  406. widest_idx = i;
  407. }
  408. }
  409. }
  410. return widest_idx;
  411. }
  412.  
  413.  
  414. function styleIt() {
  415. console.log("styling");
  416.  
  417. var td_width = best_size ? best_size.width - 2 * TD_PADDING : 10;
  418. var td_height = best_size ? best_size.height - 2 * TD_PADDING : 10;
  419. var userStyles0 =
  420. "table.imgTable { border-collapse: collapse; background-repeat: no-repeat; cursor: auto; background-color: rgba(0, 0, 0, 0) !important; } " +
  421. ".imgTable tbody { background-color: rgba(0, 0, 0, 0) !important; }" +
  422. ".imgTable td { width: " + td_width + "px;" +
  423. " height: " + td_height + "px;" +
  424. " padding: " + TD_PADDING + "px;" +
  425. " border-width: 0px;" +
  426. " background-color: rgba(0, 0, 0, 0) !important;" +
  427. " vertical-align: top;" +
  428. " color: white;" +
  429. " text-shadow:" +
  430. " -1px -1px 0 #000, " +
  431. " 1px -1px 0 #000, " +
  432. " -1px 1px 0 #000, " +
  433. " 1px 1px 0 #000; " +
  434. " !important}" +
  435. ".imgTable a { text-decoration: none; color: white; font-size: small; display: block; width: 100%; height: 100%; } " +
  436. "#thumbDiv { cursor: pointer; padding-right: var(--watch-sidebar-width); } " +
  437. //".thumb-inactive { background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAMAAAAoLQ9TAAAAD1BMVEXAwMABAQEBAQEAAAAAAADcECccAAAABHRSTlMAPEjK3fOUzQAAADFJREFUGNNjYGRmQQLMjAzMTAxIgImZgYUBBbDgFGBhIU0AZiluATIMxe9SDM+hex8AtbABVeH7XA4AAAAASUVORK5CYII=); }" +
  438. //".thumb-active { background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAMAAAAoLQ9TAAAAD1BMVEXAwMABAQEBAQEAAAAAAADcECccAAAABHRSTlMAPEjJRPrFdwAAACtJREFUGNNjYGRmQQLMjAzMTAxIgImZgYUBBbBQTQBmKW4BqluL4Tl07wMAnDABJd8eqrEAAAAASUVORK5CYII=); }" +
  439. ".thumb-active h1 { margin-bottom: 2px; }" +
  440. ".no-thumbs { cursor: default !important; }" +
  441. ".no-thumbs h1 { color: grey !important; }";
  442.  
  443. //.yt-simple-endpoint{display:inline-block;cursor:pointer;text-decoration:none;color:var(--yt-endpoint-color, hsl(0, 0%, 6.7%));}.yt-simple-endpoint:hover{color:var(--yt-endpoint-hover-color, var(--yt-endpoint-color));text-decoration: none;}
  444. var userStyles1 =
  445. "#video-title.old-endpoint.ytd-grid-video-renderer {" +
  446. "color:var(--yt-primary-color);" +
  447. "display:block;" +
  448. "max-height:3.2rem;" +
  449. "overflow:hidden;" +
  450. "font-size:1.4rem;" +
  451. "font-weight:500;" +
  452. "line-height:1.6rem;" +
  453. "}" +
  454. ".old-endpoint{display:inline-block;cursor:pointer;text-decoration:none;color:var(--yt-endpoint-color, hsl(0, 0%, 6.7%));}.old-endpoint:hover{color:var(--yt-endpoint-hover-color, var(--yt-endpoint-color));text-decoration: none;}";
  455. GM_addStyle(userStyles0);
  456. GM_addStyle(userStyles1);
  457. window.setTimeout(killSPF, 500);
  458.  
  459. console.log("DONE");
  460. }
  461.  
  462. function killSPF() {
  463. return;
  464. if (DISABLE_SPF) {
  465. console.log('trying to kill SPF');
  466. $('a.spf-link, a.yt-simple-endpoint').each(function () {
  467. var $link = $(this);
  468. //console.log('$link', $link);
  469. $link.removeClass('spf-link');
  470. $link.removeClass('yt-simple-endpoint');
  471. $link.addClass('old-endpoint');
  472. $link.off();
  473. });
  474. }
  475. //AND_BUTTS && $('#info-contents h1.title').text($('#info-contents h1.title').text() + " and Butts");
  476. }
  477.  
  478. function getParameters(str) {
  479. var r = {};
  480. str.split('&').forEach(str => {
  481. var split = str.split('=');
  482. r[split[0]] = decodeURIComponent(split[1]);
  483. });
  484. return r;
  485. }
  486.  
  487. //GM_registerMenuCommand("Toggle SPF", function toggleSpf() {
  488. // DISABLE_SPF = !DISABLE_SPF;
  489. // console.log("Diable SPF: " + DISABLE_SPF);
  490. // GM_setValue('ytDisableSPF', DISABLE_SPF);
  491. //}, 's');
  492. //GM_registerMenuCommand("and Butts", function toggleButts() {
  493. // AND_BUTTS = !AND_BUTTS;
  494. // console.log("Butts: " + AND_BUTTS);
  495. // GM_setValue('ytAndButts', AND_BUTTS);
  496. //}, 'b');
  497.  
  498. //<div>Icons made by <a href="http://www.flaticon.com/authors/dave-gandy" title="Dave Gandy">Dave Gandy</a> from <a href="http://www.flaticon.com" title="Flaticon">www.flaticon.com</a> is licensed by <a href="http://creativecommons.org/licenses/by/3.0/" title="Creative Commons BY 3.0" target="_blank">CC 3.0 BY</a></div>
  499. // http://www.flaticon.com/free-icon/add-square-button_25300
  500. //data:image/svg+xml;utf8;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iaXNvLTg4NTktMSI/Pgo8IS0tIEdlbmVyYXRvcjogQWRvYmUgSWxsdXN0cmF0b3IgMTYuMC4wLCBTVkcgRXhwb3J0IFBsdWctSW4gLiBTVkcgVmVyc2lvbjogNi4wMCBCdWlsZCAwKSAgLS0+CjwhRE9DVFlQRSBzdmcgUFVCTElDICItLy9XM0MvL0RURCBTVkcgMS4xLy9FTiIgImh0dHA6Ly93d3cudzMub3JnL0dyYXBoaWNzL1NWRy8xLjEvRFREL3N2ZzExLmR0ZCI+CjxzdmcgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB4bWxuczp4bGluaz0iaHR0cDovL3d3dy53My5vcmcvMTk5OS94bGluayIgdmVyc2lvbj0iMS4xIiBpZD0iQ2FwYV8xIiB4PSIwcHgiIHk9IjBweCIgd2lkdGg9IjE2cHgiIGhlaWdodD0iMTZweCIgdmlld0JveD0iMCAwIDMyOC45MTEgMzI4LjkxMSIgc3R5bGU9ImVuYWJsZS1iYWNrZ3JvdW5kOm5ldyAwIDAgMzI4LjkxMSAzMjguOTExOyIgeG1sOnNwYWNlPSJwcmVzZXJ2ZSI+CjxnPgoJPGc+CgkJPHBhdGggZD0iTTMxMC4xOTksMTguNzFDMjk3LjczNSw2LjI0MiwyODIuNjUsMC4wMDcsMjY0Ljk1MSwwLjAwN0g2My45NTRjLTE3LjcwMywwLTMyLjc5LDYuMjM1LTQ1LjI1MywxOC43MDQgICAgQzYuMjM1LDMxLjE3NywwLDQ2LjI2MSwwLDYzLjk2djIwMC45OTFjMCwxNy41MTUsNi4yMzIsMzIuNTUyLDE4LjcwMSw0NS4xMWMxMi40NjcsMTIuNTY2LDI3LjU1MywxOC44NDMsNDUuMjUzLDE4Ljg0M2gyMDEuMDA0ICAgIGMxNy42OTksMCwzMi43NzctNi4yNzYsNDUuMjQ4LTE4Ljg0M2MxMi40Ny0xMi41NTksMTguNzA1LTI3LjU5NiwxOC43MDUtNDUuMTFWNjMuOTYgICAgQzMyOC45MTEsNDYuMjYxLDMyMi42NjYsMzEuMTc3LDMxMC4xOTksMTguNzF6IE0yOTIuMzYyLDI2NC45NmMwLDcuNjE0LTIuNjczLDE0LjA4OS04LjAwMSwxOS40MTQgICAgYy01LjMyNCw1LjMzMi0xMS43OTksNy45OTQtMTkuNDEsNy45OTRINjMuOTU0Yy03LjYxNCwwLTE0LjA4Mi0yLjY2Mi0xOS40MTQtNy45OTRjLTUuMzMtNS4zMjUtNy45OTItMTEuOC03Ljk5Mi0xOS40MTRWNjMuOTY1ICAgIGMwLTcuNjEzLDIuNjYyLTE0LjA4Niw3Ljk5Mi0xOS40MTRjNS4zMjctNS4zMjcsMTEuOC03Ljk5NCwxOS40MTQtNy45OTRoMjAxLjAwNGM3LjYxLDAsMTQuMDg2LDIuNjYzLDE5LjQxLDcuOTk0ICAgIGM1LjMyNSw1LjMyOCw3Ljk5NCwxMS44MDEsNy45OTQsMTkuNDE0VjI2NC45NnoiIGZpbGw9IiMwMDAwMDAiLz4KCQk8cGF0aCBkPSJNMjQ2LjY4MywxNDYuMTg5SDE4Mi43M1Y4Mi4yMzZjMC0yLjY2Ny0wLjg1NS00Ljg1NC0yLjU3My02LjU2N2MtMS43MDQtMS43MTQtMy44OTUtMi41NjgtNi41NjQtMi41NjhoLTE4LjI3MSAgICBjLTIuNjY3LDAtNC44NTQsMC44NTQtNi41NjcsMi41NjhjLTEuNzE0LDEuNzEzLTIuNTY4LDMuOTAzLTIuNTY4LDYuNTY3djYzLjk1NEg4Mi4yMzNjLTIuNjY0LDAtNC44NTcsMC44NTUtNi41NjcsMi41NjggICAgYy0xLjcxMSwxLjcxMy0yLjU2OCwzLjkwMy0yLjU2OCw2LjU2N3YxOC4yNzFjMCwyLjY2NiwwLjg1NCw0Ljg1NSwyLjU2OCw2LjU2M2MxLjcxMiwxLjcwOCwzLjkwMywyLjU3LDYuNTY3LDIuNTdoNjMuOTU0djYzLjk1MyAgICBjMCwyLjY2NiwwLjg1NCw0Ljg1NSwyLjU2OCw2LjU2M2MxLjcxMywxLjcxMSwzLjkwMywyLjU2Niw2LjU2NywyLjU2NmgxOC4yNzFjMi42NywwLDQuODYtMC44NTUsNi41NjQtMi41NjYgICAgYzEuNzE4LTEuNzA4LDIuNTczLTMuODk3LDIuNTczLTYuNTYzVjE4Mi43M2g2My45NTNjMi42NjIsMCw0Ljg1My0wLjg2Miw2LjU2My0yLjU3YzEuNzEyLTEuNzA4LDIuNTYzLTMuODk3LDIuNTYzLTYuNTYzdi0xOC4yNzEgICAgYzAtMi42NjQtMC44NTItNC44NTctMi41NjMtNi41NjdDMjUxLjUzNiwxNDcuMDQ4LDI0OS4zNDUsMTQ2LjE4OSwyNDYuNjgzLDE0Ni4xODl6IiBmaWxsPSIjMDAwMDAwIi8+Cgk8L2c+CjwvZz4KPGc+CjwvZz4KPGc+CjwvZz4KPGc+CjwvZz4KPGc+CjwvZz4KPGc+CjwvZz4KPGc+CjwvZz4KPGc+CjwvZz4KPGc+CjwvZz4KPGc+CjwvZz4KPGc+CjwvZz4KPGc+CjwvZz4KPGc+CjwvZz4KPGc+CjwvZz4KPGc+CjwvZz4KPGc+CjwvZz4KPC9zdmc+Cg==
  501. // http://www.flaticon.com/free-icon/minus-button_25434
  502. //data:image/svg+xml;utf8;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iaXNvLTg4NTktMSI/Pgo8IS0tIEdlbmVyYXRvcjogQWRvYmUgSWxsdXN0cmF0b3IgMTYuMC4wLCBTVkcgRXhwb3J0IFBsdWctSW4gLiBTVkcgVmVyc2lvbjogNi4wMCBCdWlsZCAwKSAgLS0+CjwhRE9DVFlQRSBzdmcgUFVCTElDICItLy9XM0MvL0RURCBTVkcgMS4xLy9FTiIgImh0dHA6Ly93d3cudzMub3JnL0dyYXBoaWNzL1NWRy8xLjEvRFREL3N2ZzExLmR0ZCI+CjxzdmcgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB4bWxuczp4bGluaz0iaHR0cDovL3d3dy53My5vcmcvMTk5OS94bGluayIgdmVyc2lvbj0iMS4xIiBpZD0iQ2FwYV8xIiB4PSIwcHgiIHk9IjBweCIgd2lkdGg9IjE2cHgiIGhlaWdodD0iMTZweCIgdmlld0JveD0iMCAwIDQwMS45OTggNDAxLjk5OCIgc3R5bGU9ImVuYWJsZS1iYWNrZ3JvdW5kOm5ldyAwIDAgNDAxLjk5OCA0MDEuOTk4OyIgeG1sOnNwYWNlPSJwcmVzZXJ2ZSI+CjxnPgoJPGc+CgkJPHBhdGggZD0iTTM3Ny44NywyNC4xMjZDMzYxLjc4Niw4LjA0MiwzNDIuNDE3LDAsMzE5Ljc2OSwwSDgyLjIyN0M1OS41NzksMCw0MC4yMTEsOC4wNDIsMjQuMTI1LDI0LjEyNiAgICBDOC4wNDQsNDAuMjEyLDAuMDAyLDU5LjU3NiwwLjAwMiw4Mi4yMjh2MjM3LjU0M2MwLDIyLjY0Nyw4LjA0Miw0Mi4wMTQsMjQuMTIzLDU4LjEwMWMxNi4wODYsMTYuMDg1LDM1LjQ1NCwyNC4xMjcsNTguMTAyLDI0LjEyNyAgICBoMjM3LjU0MmMyMi42NDgsMCw0Mi4wMTEtOC4wNDIsNTguMTAyLTI0LjEyN2MxNi4wODUtMTYuMDg3LDI0LjEyNi0zNS40NTMsMjQuMTI2LTU4LjEwMVY4Mi4yMjggICAgQzQwMS45OTMsNTkuNTgsMzkzLjk1MSw0MC4yMTIsMzc3Ljg3LDI0LjEyNnogTTM2NS40NDgsMzE5Ljc3MWMwLDEyLjU2NS00LjQ3LDIzLjMxNC0xMy40MTUsMzIuMjY0ICAgIGMtOC45NDUsOC45NDUtMTkuNjk4LDEzLjQxOC0zMi4yNjUsMTMuNDE4SDgyLjIyN2MtMTIuNTYzLDAtMjMuMzE3LTQuNDczLTMyLjI2NC0xMy40MTggICAgYy04Ljk0NS04Ljk0OS0xMy40MTgtMTkuNjk4LTEzLjQxOC0zMi4yNjRWODIuMjMxYzAtMTIuNTYzLDQuNDczLTIzLjMxNywxMy40MTgtMzIuMjY1QzU4LjkxLDQxLjAyMSw2OS42NjQsMzYuNTUsODIuMjI3LDM2LjU1ICAgIGgyMzcuNTQyYzEyLjU2NiwwLDIzLjMxOSw0LjQ3MSwzMi4yNjUsMTMuNDE3YzguOTQ1LDguOTQ3LDEzLjQxNSwxOS43MDIsMTMuNDE1LDMyLjI2NVYzMTkuNzcxTDM2NS40NDgsMzE5Ljc3MXoiIGZpbGw9IiMwMDAwMDAiLz4KCQk8cGF0aCBkPSJNMzE5Ljc2OSwxODIuNzMxSDgyLjIyN2MtMi42NjMsMC00Ljg1MywwLjg1NS02LjU2NywyLjU2NWMtMS43MDksMS43MTMtMi41NjgsMy45MDMtMi41NjgsNi41Njd2MTguMjcxICAgIGMwLDIuNjY5LDAuODU2LDQuODU5LDIuNTY4LDYuNTdjMS43MTUsMS43MDQsMy45MDUsMi41Niw2LjU2NywyLjU2aDIzNy41NDJjMi42NjMsMCw0Ljg1My0wLjg1NSw2LjU3MS0yLjU2ICAgIGMxLjcxMS0xLjcxMSwyLjU2Ni0zLjkwMSwyLjU2Ni02LjU3di0xOC4yNzFjMC0yLjY2NC0wLjg1NS00Ljg1NC0yLjU2Ni02LjU2N0MzMjQuNjE4LDE4My41ODcsMzIyLjQyOCwxODIuNzMxLDMxOS43NjksMTgyLjczMXoiIGZpbGw9IiMwMDAwMDAiLz4KCTwvZz4KPC9nPgo8Zz4KPC9nPgo8Zz4KPC9nPgo8Zz4KPC9nPgo8Zz4KPC9nPgo8Zz4KPC9nPgo8Zz4KPC9nPgo8Zz4KPC9nPgo8Zz4KPC9nPgo8Zz4KPC9nPgo8Zz4KPC9nPgo8Zz4KPC9nPgo8Zz4KPC9nPgo8Zz4KPC9nPgo8Zz4KPC9nPgo8Zz4KPC9nPgo8L3N2Zz4K
  503.  
  504. //<div>Icons made by <a href="http://www.flaticon.com/authors/catalin-fertu" title="Catalin Fertu">Catalin Fertu</a> from <a href="http://www.flaticon.com" title="Flaticon">www.flaticon.com</a> is licensed by <a href="http://creativecommons.org/licenses/by/3.0/" title="Creative Commons BY 3.0" target="_blank">CC 3.0 BY</a></div>
  505. // http://www.flaticon.com/free-icon/minus-square-outlined-button_54373
  506. //data:image/svg+xml;utf8;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iaXNvLTg4NTktMSI/Pgo8IS0tIEdlbmVyYXRvcjogQWRvYmUgSWxsdXN0cmF0b3IgMTYuMC4wLCBTVkcgRXhwb3J0IFBsdWctSW4gLiBTVkcgVmVyc2lvbjogNi4wMCBCdWlsZCAwKSAgLS0+CjwhRE9DVFlQRSBzdmcgUFVCTElDICItLy9XM0MvL0RURCBTVkcgMS4xLy9FTiIgImh0dHA6Ly93d3cudzMub3JnL0dyYXBoaWNzL1NWRy8xLjEvRFREL3N2ZzExLmR0ZCI+CjxzdmcgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB4bWxuczp4bGluaz0iaHR0cDovL3d3dy53My5vcmcvMTk5OS94bGluayIgdmVyc2lvbj0iMS4xIiBpZD0iQ2FwYV8xIiB4PSIwcHgiIHk9IjBweCIgd2lkdGg9IjE2cHgiIGhlaWdodD0iMTZweCIgdmlld0JveD0iMCAwIDYxMiA2MTIiIHN0eWxlPSJlbmFibGUtYmFja2dyb3VuZDpuZXcgMCAwIDYxMiA2MTI7IiB4bWw6c3BhY2U9InByZXNlcnZlIj4KPGc+Cgk8ZyBpZD0iX3gzMl9fM18iPgoJCTxnPgoJCQk8cGF0aCBkPSJNNDIwLjc1LDI4Ni44NzVoLTIyOS41Yy0xMC41NTcsMC0xOS4xMjUsOC41NjgtMTkuMTI1LDE5LjEyNWMwLDEwLjU1Nyw4LjU2OCwxOS4xMjUsMTkuMTI1LDE5LjEyNWgyMjkuNSAgICAgYzEwLjU1NywwLDE5LjEyNS04LjU2OCwxOS4xMjUtMTkuMTI1QzQzOS44NzUsMjk1LjQ0Myw0MzEuMzA3LDI4Ni44NzUsNDIwLjc1LDI4Ni44NzV6IE01MzUuNSwwaC00NTlDMzQuMjUzLDAsMCwzNC4yNTMsMCw3Ni41ICAgICB2NDU5QzAsNTc3Ljc0NywzNC4yNTMsNjEyLDc2LjUsNjEyaDQ1OWM0Mi4yNDcsMCw3Ni41LTM0LjI1Myw3Ni41LTc2LjV2LTQ1OUM2MTIsMzQuMjUzLDU3Ny43NDcsMCw1MzUuNSwweiBNNTczLjc1LDUzNS41ICAgICBjMCwyMS4xMzMtMTcuMTE3LDM4LjI1LTM4LjI1LDM4LjI1aC00NTljLTIxLjEzMywwLTM4LjI1LTE3LjExNy0zOC4yNS0zOC4yNXYtNDU5YzAtMjEuMTMzLDE3LjExNy0zOC4yNSwzOC4yNS0zOC4yNWg0NTkgICAgIGMyMS4xMzMsMCwzOC4yNSwxNy4xMzYsMzguMjUsMzguMjVWNTM1LjV6IiBmaWxsPSIjMDAwMDAwIi8+CgkJPC9nPgoJPC9nPgo8L2c+CjxnPgo8L2c+CjxnPgo8L2c+CjxnPgo8L2c+CjxnPgo8L2c+CjxnPgo8L2c+CjxnPgo8L2c+CjxnPgo8L2c+CjxnPgo8L2c+CjxnPgo8L2c+CjxnPgo8L2c+CjxnPgo8L2c+CjxnPgo8L2c+CjxnPgo8L2c+CjxnPgo8L2c+CjxnPgo8L2c+Cjwvc3ZnPgo=
  507. //
  508. // http://www.flaticon.com/free-icon/add-square-outlined-interface-button_54731
  509. //data:image/svg+xml;utf8;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iaXNvLTg4NTktMSI/Pgo8IS0tIEdlbmVyYXRvcjogQWRvYmUgSWxsdXN0cmF0b3IgMTYuMC4wLCBTVkcgRXhwb3J0IFBsdWctSW4gLiBTVkcgVmVyc2lvbjogNi4wMCBCdWlsZCAwKSAgLS0+CjwhRE9DVFlQRSBzdmcgUFVCTElDICItLy9XM0MvL0RURCBTVkcgMS4xLy9FTiIgImh0dHA6Ly93d3cudzMub3JnL0dyYXBoaWNzL1NWRy8xLjEvRFREL3N2ZzExLmR0ZCI+CjxzdmcgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB4bWxuczp4bGluaz0iaHR0cDovL3d3dy53My5vcmcvMTk5OS94bGluayIgdmVyc2lvbj0iMS4xIiBpZD0iQ2FwYV8xIiB4PSIwcHgiIHk9IjBweCIgd2lkdGg9IjE2cHgiIGhlaWdodD0iMTZweCIgdmlld0JveD0iMCAwIDYxMiA2MTIiIHN0eWxlPSJlbmFibGUtYmFja2dyb3VuZDpuZXcgMCAwIDYxMiA2MTI7IiB4bWw6c3BhY2U9InByZXNlcnZlIj4KPGc+Cgk8ZyBpZD0iX3gzMV9fMjZfIj4KCQk8Zz4KCQkJPHBhdGggZD0iTTQyMC43NSwyODYuODc1aC05NS42MjVWMTkxLjI1YzAtMTAuNTU3LTguNTY4LTE5LjEyNS0xOS4xMjUtMTkuMTI1Yy0xMC41NTcsMC0xOS4xMjUsOC41NjgtMTkuMTI1LDE5LjEyNXY5NS42MjUgICAgIEgxOTEuMjVjLTEwLjU1NywwLTE5LjEyNSw4LjU2OC0xOS4xMjUsMTkuMTI1YzAsMTAuNTU3LDguNTY4LDE5LjEyNSwxOS4xMjUsMTkuMTI1aDk1LjYyNXY5NS42MjUgICAgIGMwLDEwLjU1Nyw4LjU2OCwxOS4xMjUsMTkuMTI1LDE5LjEyNWMxMC41NTcsMCwxOS4xMjUtOC41NjgsMTkuMTI1LTE5LjEyNXYtOTUuNjI1aDk1LjYyNWMxMC41NTcsMCwxOS4xMjUtOC41NjgsMTkuMTI1LTE5LjEyNSAgICAgQzQzOS44NzUsMjk1LjQ0Myw0MzEuMzA3LDI4Ni44NzUsNDIwLjc1LDI4Ni44NzV6IE01MzUuNSwwaC00NTlDMzQuMjUzLDAsMCwzNC4yNTMsMCw3Ni41djQ1OUMwLDU3Ny43NDcsMzQuMjUzLDYxMiw3Ni41LDYxMiAgICAgaDQ1OWM0Mi4yNDcsMCw3Ni41LTM0LjI1Myw3Ni41LTc2LjV2LTQ1OUM2MTIsMzQuMjUzLDU3Ny43NDcsMCw1MzUuNSwweiBNNTczLjc1LDUzNS41YzAsMjEuMTMzLTE3LjEzNiwzOC4yNS0zOC4yNSwzOC4yNWgtNDU5ICAgICBjLTIxLjEzMywwLTM4LjI1LTE3LjExNy0zOC4yNS0zOC4yNXYtNDU5YzAtMjEuMTMzLDE3LjExNy0zOC4yNSwzOC4yNS0zOC4yNWg0NTljMjEuMTE0LDAsMzguMjUsMTcuMTM2LDM4LjI1LDM4LjI1VjUzNS41eiIgZmlsbD0iIzAwMDAwMCIvPgoJCTwvZz4KCTwvZz4KPC9nPgo8Zz4KPC9nPgo8Zz4KPC9nPgo8Zz4KPC9nPgo8Zz4KPC9nPgo8Zz4KPC9nPgo8Zz4KPC9nPgo8Zz4KPC9nPgo8Zz4KPC9nPgo8Zz4KPC9nPgo8Zz4KPC9nPgo8Zz4KPC9nPgo8Zz4KPC9nPgo8Zz4KPC9nPgo8Zz4KPC9nPgo8Zz4KPC9nPgo8L3N2Zz4K
  510. //data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAMAAAAoLQ9TAAAAGFBMVEX///8AAAAAAAAAAAAAAAAAAAAAAAAAAABcYkG9AAAAB3RSTlMAIDxIgMDJoM584QAAADlJREFUGNOFjzkCACAIw4oU+P+PXREFM2bogeWR8AVXJNQROIhWiBRBjkJIM1J68c941Q5Lr3P1/gYInwHxRyFQjQAAAABJRU5ErkJggg==
  511.  
  512. //plus data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAMAAAAoLQ9TAAAAD1BMVEXAwMABAQEBAQEAAAAAAADcECccAAAABHRSTlMAPEjK3fOUzQAAADFJREFUGNNjYGRmQQLMjAzMTAxIgImZgYUBBbDgFGBhIU0AZiluATIMxe9SDM+hex8AtbABVeH7XA4AAAAASUVORK5CYII=
  513. //minus data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAMAAAAoLQ9TAAAAD1BMVEXAwMABAQEBAQEAAAAAAADcECccAAAABHRSTlMAPEjJRPrFdwAAACtJREFUGNNjYGRmQQLMjAzMTAxIgImZgYUBBbBQTQBmKW4BqluL4Tl07wMAnDABJd8eqrEAAAAASUVORK5CYII=
  514.  
  515.  
  516. //// CODE STOLEN FROM YoutubeCenter AND YouTubePlaylistAutoplayDisable TO DEAL WITH STUPID SPF.
  517. // Runs the function when the page is loading and has finished loading
  518. //window.addEventListener('readystatechange', findStuff);
  519. // Runs the function when the page changes via SPF method: https://github.com/youtube/spfjs/
  520. window.addEventListener('spfdone', findStuff);
  521. //window.addEventListener('yt-navigate', findStuff);
  522. //window.addEventListener('yt-page-type-changed', findStuff);
  523. window.addEventListener('yt-navigate-finish', findStuff);
  524. //window.addEventListener('yt-navigate-finish', killSPF);
  525. //findStuff();
  526. //killSPF();
  527. window.setTimeout(findStuff, 200);
  528.  
  529.  
  530. //VIMEO info
  531. // video at https://vimeo.com/160190376
  532. // source contains URL https://player.vimeo.com/video/160190376/config?byline=0&collections=0&context=Vimeo%5CController%5CClipController.main&default_to_hd=1&outro=nothing&portrait=0&share=1&title=0&watch_trailer=0&s=f32dd867feea75e8b71005f7793d4ba285958b4c_1472286552
  533. // which is JSON with json.request.thumb_preview containing url, frame_height, frame_width, height, width, frames, columns
  534. // json.request.thumb_preview.url = https://i.vimeocdn.com/1472189353-0x9b6eb1264f85669b1a37525cb5b4d18b9edc2b39/sprite/160190376/120?q=48&h=126&ver=1&w=224&n=4