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.

目前为 2015-09-05 提交的版本。查看 最新版本

// ==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);