// ==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_log("Loading character list...");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; GM_log(text); 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_log("Loading transcript..."); 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){ GM_log("All loaded."); var parser = new unsafeWindow.DOMParser(); var text = results.responseText; GM_log(text); 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"; GM_log("Setting up subtitles..."); 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" || name == "meta" || name == "link" || name == "form" || name == "input") 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") { if (name == "style") { // regex taken from MediaWiki Sanitizer.php if (!node.attributes[i].nodeValue.match(/(expression|tps*:\/\/|url\\s*\().*/i)) newNode.setAttribute("style", node.attributes[i].nodeValue); } else 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; var oldopen = unsafeWindow.open; unsafeWindow.open = function(a,b,c) { c = c.split(/,/); var opts = {}; for (var i = 0; i < c.length; i++) { var d = c[i].split(/=/); opts[d[0]] = d[1]; } opts["width"] = window.screen.width * 0.8; opts["height"] = window.screen.height * 0.8; opts["resizeable"] = "yes"; c = ""; for (var i in opts) { c += i + "=" + opts[i] + ","; } return oldopen(a,b,c); } GM_log("Starting refresh loop..."); 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);}