// ==UserScript==
// @name YouTube Thumbnails -- Full Video Thumbnails for YouTube
// @namespace driver8.net
// @description Shows complete video thumbnails for YouTube videos
// @match *://*.youtube.com/watch?*
// @version 0.3
// @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 = 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 LOGGING = false;
var MIN_WIDTH = 4;
var MAX_IMAGES = 30;
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++ < 2) {
window.setTimeout(trySb, TIMEOUT_DELAY);
}
}
}
})();
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);
var $titleDiv = $('#watch-header');
$thumbDiv = $('<div id="thumbDiv" class="yt-card"></div>');
$thumbDiv.css('padding', DIV_PADDING +'px');
$thumbHeader = $('<h1 width="100%">Thumbs</h1>');
$thumbHeader.css({'cursor': 'pointer'});
$thumbDiv.append($thumbHeader);
$titleDiv.after($thumbDiv);
$thumbHeader.click(showThumbs);
log("Script done");
styleIt();
}
function showThumbs() {
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");
$thumbHeader.off('click');
$thumbHeader.click(hideThumbs);
}
function hideThumbs() {
$thumbDiv.children('table').hide();
$thumbHeader.off('click');
$thumbHeader.click(showThumbsAgain);
}
function showThumbsAgain() {
$thumbDiv.children('table').show();
$thumbHeader.off('click');
$thumbHeader.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() {
var userStyles0 =
"table.imgTable { border-collapse: collapse; background-repeat: no-repeat; !important } " +
".imgTable td { width: " + (best_size.width - 2 * TD_PADDING) + "px;" +
" height: " + (best_size.height - 2 * TD_PADDING) + "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; padding-right: 100%; padding-bottom: 100%; !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' );
log("DONE");
}
/// TRYING TO DEAL WITH YOUTUBE'S STUPID SPF SHIT WITHOUT DISABLING IT. BLAHH! ///
//window.addEventListener('spfrequest', function() { log("SPF WUT? spfrequest"); });
//window.addEventListener('spfpartprocess', function() { log("SPF WUT? spfpartprocess"); });
//window.addEventListener('spfpartdone', function() { log("SPF WUT? spfpartdone"); });
//window.addEventListener('spfprocess', function() { log("SPF WUT? spfprocess"); });
//window.addEventListener('spfdone', function() { log("SPF WUT? spfdone"); });
//window.addEventListener('spferror', function() { log("SPF WUT? spferror"); });
//window.addEventListener('spfreload', function() { log("SPF WUT? spfreload"); });
//window.addEventListener('spfjsbeforeunload', function() { log("SPF WUT? spfjsbeforeunload"); });
//window.addEventListener('spfdone', spfWut);
//function spfWut(a) {
// log(a);
// var parts = a.detail.response.parts;
// var good_part = null;
// parts.forEach(function(val, idx) {
// good_part = good_part || (val.data && val.data.swfcfg);
// });
// log(good_part);
// // var storyboard_spec = a.data.swfcfg.args.storyboard_spec;
// storyboard_spec = good_part.args.storyboard_spec;
// log("sbSpec: " + sb_spec);
// best_size_idx_idx = chooseBestStoryboardSize(storyboard);
// best_size = storyboard.sizes[best_size_idx_idx];
// log(best_size);
// len_seconds = parseInt(good_part.args.length_seconds);
// log("len: " + len_seconds);
// setUp();
// log("BOOM");
//}
//
//(function transitionCheck() {
// $('body').on('transitionend', function(event) {
// log("transitioned prog");
// if (event.target.id != 'progress') return false;
// log("transitioned event");
// setUp();
// log("BOOM");
// transitionCheck();
// });
//})();