// ==UserScript==
// @name YouTube Thumbnails - Full Video Thumbnails for YouTube
// @namespace driver8.net
// @description Shows complete video thumbnails for YouTube videos. You can click a thumbnail image to jump to that point in the video.
// @match *://*.youtube.com/*
// @version 0.3.5
// @require https://ajax.googleapis.com/ajax/libs/jquery/2.1.4/jquery.min.js
// @grant GM_addStyle
// @grant unsafeWindow
// @grant GM_registerMenuCommand
// @grant GM_getValue
// @grant GM_setValue
// ==/UserScript==
var AND_BUTTS = GM_getValue('ytAndButts', false);
var LOAD_DELAY_START = 200;
var LOAD_DELAY_FACTOR = 50;
var LOAD_DELAY_MAX = 500;
var TIMEOUT_DELAY = 2000;
var DIV_PADDING = 10;
var TD_PADDING = 2;
var DISABLE_SPF = GM_getValue('ytDisableSPF', false);
var MIN_WIDTH = 4;
var MAX_IMAGES = 30;
var LOGGING = false;
var $thumbDiv, $thumbHeader, storyboard_spec, storyboard, best_size_idx, best_size, len_seconds, videoId, tries;
function log(msg) {
LOGGING && console.log(msg);
}
log("Hi");
setUp();
function setUp() {
AND_BUTTS && $('#eow-title').text($('#eow-title').text() + " and Butts");
// See if waiting will help w/spf bullshit (it won't)
tries = 0;
(function trySb() {
try {
storyboard_spec = unsafeWindow.ytplayer.config.args.storyboard_spec;
} catch (e) {
log("oops " + e);
try {
storyboard_spec = window.ytplayer.config.args.storyboard_spec;
} catch (e2) {
log("oops2 " + e2);
if (tries++ < 0) {
window.setTimeout(trySb, TIMEOUT_DELAY);
}
}
}
})();
var $titleDiv = $('#watch-header');
if ($titleDiv.prop('nodeName') !== 'DIV') {
log("no title div");
return;
}
if (storyboard_spec) {
storyboard = parseStoryboardSpec(storyboard_spec);
best_size_idx = chooseBestStoryboardSize(storyboard);
best_size = storyboard.sizes[best_size_idx];
log(best_size);
len_seconds = parseInt(unsafeWindow.ytplayer.config.args.length_seconds);
log("len: " + len_seconds);
$thumbDiv = $('<div id="thumbDiv" class="yt-card"></div>');
$thumbHeader = $('<h1 width="100%">Thumbnails</h1>');
$thumbDiv.append($thumbHeader);
$thumbDiv.click(showThumbs);
} else if (unsafeWindow.ytplayer) {
$thumbDiv = $('<div id="thumbDiv" class="yt-card no-thumbs"></div>');
$thumbHeader = $('<h1 width="100%">No Thumbnails Available</h1>');
$thumbDiv.append($thumbHeader);
} else {
log("Reload for thumbs");
$thumbDiv = $('<div id="thumbDiv" class="yt-card no-thumbs"></div>');
$thumbHeader = $('<h1 width="100%">Reload for Thumbnails</h1>');
$thumbDiv.append($thumbHeader);
}
$titleDiv.after($thumbDiv);
log($titleDiv);
log("Script done");
styleIt();
}
function showThumbs(event) {
var duration = best_size.duration / 1000;
var num_thumbs = 1;
if (duration > 0) {
num_thumbs = Math.ceil(len_seconds / duration / best_size.cols / best_size.rows);
} else {
duration = len_seconds / best_size.cols / best_size.rows;
}
log("Thumb header clicked. Loading " + num_thumbs + " images.");
var total_width = best_size.width * best_size.cols + DIV_PADDING * 2;
var parent_diff = $thumbDiv.parent().innerWidth() - total_width;
parent_diff < 0 && $thumbDiv.css({'left': + parent_diff + 'px'});
$thumbDiv.css({'position': 'relative', 'width': total_width + 'px'});
// Grab the youtube sessionlink ID for time links
var sessionlink = $('#logo-container').data("sessionlink");
var badImage = false;
(function loadImage(imgNum) {
if (badImage === false && imgNum < num_thumbs && imgNum < MAX_IMAGES) {
// EX: https:\/\/i.ytimg.com\/sb\/2XY3AvVgDns\/storyboard3_L$L\/$N.jpg
// EX: https://i.ytimg.com/sb/k4YRWT_Aldo/storyboard3_L2/M0.jpg?sigh=RVdv4fMsE-eDcsCUzIy-iCQNteI
var link = storyboard.baseUrl.replace('\\', '');
link = link.replace('$L', best_size_idx);
link = link.replace('$N', best_size.img_name);
link = link.replace('$M', imgNum);
link += '?sigh=' + best_size.sigh;
log(link);
// Create a table for the timestamps over the image
var $newTable = $('<table>');
$newTable.addClass('imgTable');
var x = imgNum * duration * best_size.rows * best_size.cols; // the starting time for this table
var innerStr = '';
var doclocation = document.location.href.replace(/\#.*/, '');
for (var i = 0; i < best_size.rows; i++) {
if (x <= len_seconds + 1) { // if we haven't reached the end of the video
innerStr += '<tr>';
for (var j = 0; j < best_size.cols; j++) {
innerStr += '<td><a class="yt-uix-sessionlink" href="' + doclocation + '#t=' + x + '">';
if (x <= len_seconds + 1) {
var hrs = Math.floor(x / 3600);
var mins = Math.floor((x % 3600) / 60);
var secs = Math.round(x % 60);
innerStr += hrs > 0 ? hrs + ':' : ''; // hrs
innerStr += ( hrs > 0 && mins < 10 ? "0" + mins : mins ) + ':'; // mins
innerStr += secs < 10 ? "0" + secs : secs; // secs
}
innerStr += '</a></td>';
x += duration;
}
innerStr += '<tr>';
}
}
$newTable.html(innerStr);
$newTable.error(function() {
badImage = true;
$(this).remove();
log("Hid bad image");
});
//$newTable.load(function() {
// loadImage(imgNum + 1);
//});
$newTable.css({'background-image': 'url(' + link + ')', 'width': best_size.width * best_size.cols});
$thumbDiv.append($newTable);
setTimeout(loadImage, Math.min(LOAD_DELAY_START + imgNum * LOAD_DELAY_FACTOR, LOAD_DELAY_MAX), imgNum + 1);
}
})(0);
log("Done making thumb div");
$thumbDiv.off('click');
$thumbDiv.click(hideThumbs);
}
function hideThumbs(event) {
if ($(event.target).is('#thumbDiv, #thumbDiv h1')) {
$thumbDiv.children('table').hide();
$thumbDiv.off('click');
$thumbDiv.click(showThumbsAgain);
} else {
$('html, body').scrollTop(0);
}
}
function showThumbsAgain(event) {
if ($(event.target).is('#thumbDiv, #thumbDiv h1')) {
$thumbDiv.children('table').show();
$thumbDiv.off('click');
$thumbDiv.click(hideThumbs);
}
}
function parseStoryboardSpec(spec) {
// 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
var sections = spec.split('|');
log(sections);
var sb = {
sizes: [],
baseUrl: sections.shift()
};
// EX: 80#45#90#10#10#2000#M$M#hCWDvBSbgeV52mPYmOHjgdLFI1o
// EX: 160# 90# 90# 5# 5# 2000# M$M# ys1MKEnwYXA1QAcFiugAA_cZ81Q
sections.forEach(function(value, idx) {
var data = value.split('#');
log(data);
var new_size = {
width : +data[0],
height : +data[1],
qual : +data[2], // image quality???
cols : +data[3],
rows : +data[4],
duration : +data[5], // duration of each snapshot in milliseconds
img_name : data[6],
sigh : data[7]
};
sb.sizes[idx] = new_size;
});
log(sb);
return sb;
}
function chooseBestStoryboardSize(sb) {
var sizes = sb.sizes;
var widest = 0;
var widest_idx = -1;
for (var i = 0; i < sizes.length; i++) {
if (widest < sizes[i].width || (widest == sizes[i].width && sizes[widest_idx].cols < sizes[i].cols)) {
if (sizes[i].cols >= MIN_WIDTH) {
widest = sizes[i].width;
widest_idx = i;
}
}
}
return widest_idx;
}
function styleIt() {
log("styling");
var td_width = best_size ? best_size.width - 2 * TD_PADDING : 10;
var td_height = best_size ? best_size.height - 2 * TD_PADDING : 10;
var userStyles0 =
"table.imgTable { border-collapse: collapse; background-repeat: no-repeat; cursor: auto; } " +
".imgTable td { width: " + td_width + "px;" +
" height: " + td_height + "px;" +
" padding: " + TD_PADDING + "px;" +
" border-width: 0px;" +
" vertical-align: top;" +
" color: white;" +
" text-shadow:" +
" -1px -1px 0 #000, " +
" 1px -1px 0 #000, " +
" -1px 1px 0 #000, " +
" 1px 1px 0 #000; " +
" !important}" +
".imgTable a { text-decoration: none; color: white; display: block; width: 100%; height: 100%; } " +
"#thumbDiv { padding: " + DIV_PADDING + "px; cursor: pointer; } " +
".no-thumbs { color: grey; cursor: default !important; }";
GM_addStyle(userStyles0);
if (DISABLE_SPF) {
$('a.spf-link').each(function () {
var $link = $(this);
$link.removeClass('spf-link');
$link.off();
});
}
GM_registerMenuCommand( "Toggle SPF", function toggleSpf() {
DISABLE_SPF = !DISABLE_SPF;
GM_setValue('ytDisableSPF', DISABLE_SPF);
}, 's' );
GM_registerMenuCommand( "and Butts", function toggleButts() {
AND_BUTTS = !AND_BUTTS;
GM_setValue('ytAndButts', AND_BUTTS);
}, 'b' );
log("DONE");
}
//// CODE STOLEN FROM YoutubeCenter AND YouTubePlaylistAutoplayDisable TO DEAL WITH STUPID SPF.
// Runs the function when the page is loading and has finished loading
//window.addEventListener('readystatechange',username,true);
// Runs the function when the page changes via SPF method: https://github.com/youtube/spfjs/
window.addEventListener('spfdone', setUp);