YouTube Thumbnails - Full Video Thumbnails for YouTube

Shows complete video thumbnails for YouTube videos

Fra og med 25.07.2015. Se den nyeste version.

You will need to install an extension such as Tampermonkey, Greasemonkey or Violentmonkey to install this script.

You will need to install an extension such as Tampermonkey to install this script.

You will need to install an extension such as Tampermonkey or Violentmonkey to install this script.

You will need to install an extension such as Tampermonkey or Userscripts to install this script.

You will need to install an extension such as Tampermonkey to install this script.

You will need to install a user script manager extension to install this script.

(I already have a user script manager, let me install it!)

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

(I already have a user style manager, let me install it!)

// ==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.2
// @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>');
	$thumbHeader = $('<h1 width="100%">Thumbs</h1>');
	$thumbDiv.append($thumbHeader);
	$titleDiv.after($thumbDiv);

	$thumbDiv.click(showThumbs);

	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() {
	var userStyles0 =
		"table.imgTable { border-collapse: collapse; background-repeat: no-repeat; cursor: auto; !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; display: block; width: 100%; height: 100%; !important }" +
		"#thumbDiv {padding: " + DIV_PADDING + "px; cursor: pointer; }";
	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();
//	});
//})();