All pastes #150899 Raw Edit

Anonymous

public text v1 · immutable
#150899 ·published 2006-08-27 13:33 UTC
rendered paste body
// ==UserScript==
// @name          Homestar Subtitles Pre-Alpha
// @namespace     http://www.hrwiki.org/
// @description	  Subtitles for Homestar Runner
// @include       http://homestarrunner.com/*
// @include       http://www.homestarrunner.com/*
// @include       http://podstar.homestarrunner.com/*
// @include       http://videlectrix.com/*
// @include       http://www.videlectrix.com/*
// @include       http://hrwiki.org/mirror/*
// @include       http://www.hrwiki.org/mirror/*
// @include       https://secure.homestarrunner.com/heythanks.html*
// ==/UserScript==

var subtitleOptions = {
	"colors": true, // distinguish characters by colors
	"captions": true, // include sound effects
	"lang": "en",
	"debug": false
}

var characters = {
	"sfx": {
		"color": "#FFF",
		"name": {"en": ""}
	}
};
var transcript = new Array()
var flash = false;
var subtitleholder = false;
var currentsubtitles = false;
var nosubtitles = document.createComment("");
var currentsubtitles = nosubtitles;
var filename = location.pathname.toLowerCase();
var i = filename.lastIndexOf('/');
if (i >= 0)
	filename = filename.substr(i + 1);
i = filename.lastIndexOf('.');
if (i >= 0)
	filename = filename.substr(0,i);

GM_xmlhttpRequest({
	method: "GET",
	url: "http://www.hrwiki.org/index.php/HRWiki:Subtitles/Characters?action=raw",
	onload: charactersLoaded
});
function charactersLoaded(results)
{
	var parser = new unsafeWindow.DOMParser();
	var text = results.responseText;
	text = text.replace(/^\s+/g, "");
	var doc = parser.parseFromString(text, "application/xml");
	var speakers = doc.getElementsByTagName("speaker");
	for (var i = 0; i < speakers.length; i++)
	{
		var speakername = speakers[i].getAttribute("id");
		characters[speakername] = {"color": speakers[i].getAttribute("color"), "name": {"en": ""}};
		var names = speakers[i].getElementsByTagName("name");
		for (var j = 0; j < names.length; j++)
		{
			var lang = names[j].getAttribute("xml:lang");
			characters[speakername].name[lang] = names[j].firstChild.nodeValue;
		}
	}
	if (subtitleOptions["debug"])
	{
		for (i in characters)
		{
			var p = document.createElement('p');
			p.style.color = characters[i].color;
			p.appendChild(document.createTextNode(characters[i].name.en));
			document.body.appendChild(p);
		}
	}
	GM_xmlhttpRequest({
		method: "GET",
		url: "http://www.hrwiki.org/index.php?title=HRWiki:Subtitles/" + subtitleOptions["lang"] + "/" + escape(filename) + "&action=raw",
		onload: function(x) { transcriptLoaded(x, false); }
	});
}

function transcriptLoaded(results, redirected)
{
	var parser = new unsafeWindow.DOMParser();
	var text = results.responseText;
	text = text.replace(/^\s+/g, "");
	// check for redirects
	var matches = text.match(/^#REDIRECT\s*\[\[(.*)\]\]/i);
	if (matches)
	{
		if (redirected) // trap double-redirects
			return;
		text = matches[1];
		if (matches = text.match(/^(.*)\|/))
			text = matches[1];
		if (matches = text.match(/^(.*)\#/))
			text = matches[1];
		text = text.replace(/^\s+|\s+$/g, '');
		text = text.replace(/ /, '_');
		GM_xmlhttpRequest({
			method: "GET",
			url: "http://www.hrwiki.org/index.php?title=" + escape(text) + "&action=raw",
			onload: function(x) { transcriptLoaded(x, true); }
		});
		return;
	}
	if (text == "")
		// text = "<?xml version=\"1.0\" encoding=\"utf-8\"?><transcript><line start=\"0\" end=\"1000000000\">No transcript found</line></transcript>";
		return;
	var doc = parser.parseFromString(text, "application/xml");
	// set some defaults
	if (!doc.documentElement.getAttribute("xml:lang")) doc.documentElement.setAttribute("xml:lang", "en");
	if (!doc.documentElement.getAttribute("dir"))      doc.documentElement.setAttribute("dir",      "ltr");
	// inherit languages to all subnodes
	inheritLanguages(doc.documentElement);
	// now parse the lines into divs and get start and end frames
	var lines = doc.getElementsByTagName("line");
	for (var i = 0; i < lines.length; i++)
	{
		var line = new Object();
		line.start = lines[i].getAttribute("start");
		line.end = lines[i].getAttribute("end");
		line.text = importNodes(lines[i]);
		transcript.push(line);
		
		if (subtitleOptions["debug"])
		{
			document.body.appendChild(document.createTextNode("\xA0\xA0\xA0\xA0Start: " + line.start + " End: " + line.end));
			document.body.appendChild(line.text);
		}
	}
	if (subtitleOptions["debug"])
		document.body.style.color="white";
	
	setupSubtitles();
}
function inheritLanguages(node)
{
	for (var i = node.firstChild; i; i = i.nextSibling)
	{
		if (i.nodeType == i.ELEMENT_NODE)
		{
			if (!i.getAttribute("xml:lang")) i.setAttribute("xml:lang", node.getAttribute("xml:lang"));
			if (!i.getAttribute("dir"))      i.setAttribute("dir",      node.getAttribute("dir"));
			inheritLanguages(i);
		}
	}
}
// replacement for document.importNode() that actually works properly
// turning eg <span> into an HTML span element, rather than an unknown element
// that happens to be called "span".
function importNodes(node)
{
	var name = node.nodeName.toLowerCase();
	if (characters[name])
	{
		node.setAttribute("speaker", name);
		name = "speaker";
	}
	if (name == "line" || name == "speaker")
	{
		// format the speaker appropriately as a div
		var speaker = node.getAttribute("speaker");
		if (!subtitleOptions["captions"] && speaker == "sfx")
			return document.createComment(""); // return nothing
		newNode = document.createElement("div");
		var char = characters[speaker];
		if (!char)
			char = {"color": "#FFF", "name": {"en": ""}};
		if (subtitleOptions["colors"])
			newNode.style.color = char.color;
		if (node.getAttribute("voiceover"))
			newNode.style.fontStyle = "italic";
		if (node.getAttribute("volume"))
			newNode.style.fontSize = (node.getAttribute("volume") * 100) + "%";
		newNode.lang = node.getAttribute("xml:lang");
		newNode.dir = node.getAttribute("dir");
		var hasSpeakerChildren = false;
		for (var i = node.firstChild; i; i = i.nextSibling)
		{
			if (i.nodeType == i.ELEMENT_NODE)
			{
				newNode.appendChild(importNodes(i));
				var a = i.nodeName.toLowerCase();
				if (i == "line" || i == "speaker" || characters[i])
					hasSpeakerChildren = true;
			}
			else if (i.nodeType == i.TEXT_NODE || i.nodeType == i.CDATA_SECTION_NODE)
				newNode.appendChild(document.importNode(i, true));
		}
		if (!hasSpeakerChildren)
		{
			// this is a normal text node - do some extra text stuff
			if (speaker == 'sfx' || speaker == 'thecheat' || speaker == 'pompom')
			{
				newNode.insertBefore(document.createTextNode('('), newNode.firstChild);
				newNode.appendChild(document.createTextNode(')'))
				newNode.style.fontStyle = "italic";
			}
			if (!subtitleOptions["colors"])
			{
				// find the language with the longest prefix match
				// fall back to "en" if none found
				var bestmatch = "en";
				var langbits = node.getAttribute("xml:lang").split("-");
				for (i = langbits.length; i >= 1; i--)
				{
					var lang = langbits.slice(0, i).join("-");
					if (char.name[lang])
					{
						bestmatch = lang;
						break;
					}
				}
				if (char.name[bestmatch] != '')
					newNode.insertBefore(document.createTextNode(char.name[bestmatch] + ": "), newNode.firstChild);
			}
		}
		return newNode;
	}
	else
	{
		// check element blacklist
		if (name == "script" ||
		    name == "style"  ||
		    name == "object" ||
		    name == "param"  ||
		    name == "embed"  ||
		    name == "a"      ||
		    name == "img"    ||
		    name == "applet" ||
		    name == "map"    ||
		    name == "frame"  ||
		    name == "iframe")
			return document.createComment(""); // return nothing
		var newNode = document.createElement(name);
		// copy across attributes
		for (var i = 0; i < node.attributes.length; i++)
		{
			name = node.attributes[i].nodeName.toLowerCase();
			// check attribute blacklist
			// javascript, and anything that might load stuff from offsite
			if (name != "href" && name != "src" && name.substring(0, 2) != "on")
				newNode.setAttribute(node.attributes[i].nodeName, node.attributes[i].nodeValue);
		}
		// copy across children
		for (var i = node.firstChild; i; i = i.nextSibling)
		{
			if (i.nodeType == i.ELEMENT_NODE)
				newNode.appendChild(importNodes(i));
			else if (i.nodeType == i.TEXT_NODE || i.nodeType == i.CDATA_SECTION_NODE)
				newNode.appendChild(document.importNode(i, true));
		}
		return newNode;
	}
	document.createComment(""); // fallthrough
}

function setupSubtitles()
{
	subtitleholder = document.createElement('div');
	subtitleholder.style.fontSize = "25px";
	subtitleholder.style.lineHeight = "25px";
	subtitleholder.style.color = "white";
	subtitleholder.style.backgroundColor = "black";
	subtitleholder.style.height = "100px";
	subtitleholder.style.marginTop = "10px";
	subtitleholder.style.textAlign = "center";
	document.body.appendChild(subtitleholder);
	subtitleholder.appendChild(nosubtitles);
	flash = document.getElementsByTagName("embed")[0].wrappedJSObject;
	setTimeout(refreshSubtitles, 100);
}
function setSubtitles(node)
{
	if (!node)
		node = nosubtitles;
	if (currentsubtitles != node)
	{
		subtitleholder.replaceChild(node, subtitleholder.firstChild);
		currentsubtitles = node;
	}
}
function refreshSubtitles()
{
	var frame = false;
	if (flash)
		frame = flash.CurrentFrame;
	if (typeof(frame) == "function")
		frame = frame();
	if (typeof(frame) == "number" && frame >= 0)
	{
		// change from native 0-based to more friendly 1-based
		frame++;
		// binary search to find the right transcript line
		var first = 0;
		var last = transcript.length;
		while(first < (last - 1))
		{
			var mid = (first + last) >> 1;
			if (frame >= transcript[mid].start)
				first = mid;
			else
				last = mid;
		}
		// should we actually show the line?
		if(transcript[first].start <= frame && transcript[first].end >= frame)
			setSubtitles(transcript[first].text);
		else
			setSubtitles(false);
	}
	setTimeout(refreshSubtitles, 100);
}