// ==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", // which language subtitles to download
"debug": false // spit heaps of crap to the bottom of the page
}
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;
}
text = text.replace(/gu?itar/ig, "GUITAR
GUITAR");
if (text == "")
// text = "No transcript found";
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 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);
}