Shows all the available video formats at the top of the page on the blip.tv watch page so that they can be saved easily.
// ==UserScript== // @name Blip.tv Links // @namespace http://www.smallapple.net/ // @description Shows all the available video formats at the top of the page on the blip.tv watch page so that they can be saved easily. // @author Ng Hun Yang // @include http://*.blip.tv/* // @include http://blip.tv/* // @include https://*.blip.tv/* // @include https://blip.tv/* // @match *://*.blip.tv/* // @version 1.05 // ==/UserScript== /* This is based on Blip.tv Video Download 1.4 */ /* Tested on Firefox 6.0, Chrome 13 and Opera 11.50 */ (function() { // ============================================================================= var win = typeof(unsafeWindow) != "undefined" ? unsafeWindow : window; var doc = win.document; var loc = win.location; // ============================================================================= var SCRIPT_NAME = "BlipTv Links"; var SCRIPT_UPDATE_LINK = loc.protocol + "//greasyfork.org/scripts/5665-blip-tv-links-updater/code/Bliptv Links Updater.user.js"; var SCRIPT_LINK = loc.protocol + "//greasyfork.org/scripts/5666-blip-tv-links/code/Bliptv Links.user.js"; var relInfo = { ver: 10500, ts: 2014101200, desc: "Change link to Greasy Fork" }; // ============================================================================= var dom = {}; dom.gE = function(id) { return doc.getElementById(id); }; dom.gT = function(dom, tag) { if(arguments.length == 1) { tag = dom; dom = doc; } return dom.getElementsByTagName(tag); }; dom.cE = function(tag) { return doc.createElement(tag); }; dom.cT = function(s) { return doc.createTextNode(s); }; dom.attr = function(obj, k, v) { if(arguments.length == 2) return obj.getAttribute(k); obj.setAttribute(k, v); }; dom.append = function(obj, child) { obj.appendChild(child); }; dom.html = function(obj, s) { if(arguments.length == 1) return obj.innerHTML; obj.innerHTML = s; }; dom.emitHtml = function(tag, attrs, body) { if(arguments.length == 2) { if(typeof(attrs) == "string") { body = attrs; attrs = {}; } } var list = []; for(var k in attrs) { list.push(k + "='" + attrs[k].replace(/'/g, "\\'") + "'"); } var s = "<" + tag + " " + list.join(" ") + ">"; if(body != null) s += body + "</" + tag + ">"; return s; }; dom.emitCssStyles = function(styles) { var list = []; for(var k in styles) { list.push(k + ": " + styles[k] + ";"); } return " { " + list.join(" ") + " }"; }; dom.ajax = function(opts) { function newXhr() { if(window.ActiveXObject) { try { return new ActiveXObject("Msxml2.XMLHTTP"); } catch(e) { } try { return new ActiveXObject("Microsoft.XMLHTTP"); } catch(e) { return null; } } if(window.XMLHttpRequest) return new XMLHttpRequest(); return null; } function nop() { } // Entry point var xhr = newXhr(); opts = addProp({ type: "GET", async: true, success: nop, error: nop, complete: nop }, opts); xhr.open(opts.type, opts.url, opts.async); xhr.onreadystatechange = function() { if(xhr.readyState == 4) { var status = +xhr.status; if(status >= 200 && status < 300) { opts.success(xhr.responseText, "success"); } else { opts.error(xhr, "error"); } opts.complete(xhr); } }; xhr.send(""); }; dom.addEvent = function(e, type, fn) { function mouseEvent(event) { if(this != event.relatedTarget && !dom.isAChildOf(this, event.relatedTarget)) fn.call(this, event); } // Entry point if(e.addEventListener) { var effFn = fn; if(type == "mouseenter") { type = "mouseover"; effFn = mouseEvent; } else if(type == "mouseleave") { type = "mouseout"; effFn = mouseEvent; } e.addEventListener(type, effFn, /*capturePhase*/ false); } else e.attachEvent("on" + type, function() { fn(win.event); }); }; dom.insertCss = function (styles) { var ss = dom.cE("style"); dom.attr(ss, "type", "text/css"); var hh = dom.gT("head") [0]; dom.append(hh, ss); dom.append(ss, dom.cT(styles)); }; dom.isAChildOf = function(parent, child) { if(parent === child) return false; while(child && child !== parent) { child = child.parentNode; } return child === parent; }; // ----------------------------------------------------------------------------- function forLoop(opts, fn) { opts = addProp({ start: 0, inc: 1 }, opts); for(var idx = opts.start; idx < opts.num; idx += opts.inc) { if(fn.call(opts, idx, opts) === false) break; } } function forEach(list, fn) { forLoop({ num: list.length }, function(idx) { return fn.call(list[idx], idx, list[idx]); }); } function addProp(dest, src) { for(var k in src) { if(src[k] != null) dest[k] = src[k]; } return dest; } function unescHtmlEntities(s) { return s.replace(/</g, "<").replace(/>/g, ">").replace(/&/g, "&").replace(/"/g, '"').replace(/'/g, "'"); } function logMsg(s) { win.console.log(s); } function cnvSafeFname(s) { s = s.replace(/:/g, "-").replace(/"/g, "'").replace(/[\\/|*?]/g, "_"); return encodeURIComponent(s).replace(/'/g, "%27"); } function getVideoName(s) { var list = [ { name: "FLV", codec: "video/x-flv" }, { name: "M4V", codec: "video/x-m4v" }, { name: "MP3", codec: "audio/mpeg" }, { name: "MP4", codec: "video/mp4" }, { name: "QT", codec: "video/quicktime" }, { name: "WEBM", codec: "video/webm" }, { name: "WMV", codec: "video/ms-wmv" } ]; var name = "?"; forEach(list, function(idx, elm) { if(s.match("^" + elm.codec)) { name = elm.name; return false; } }); return name; } // ============================================================================= var CSS_PREFIX = "ujs-"; var HDR_LINKS_HTML_ID = CSS_PREFIX + "links-div"; var UPDATE_HTML_ID = CSS_PREFIX + "update-div"; var CSS_STYLES = "#" + UPDATE_HTML_ID + dom.emitCssStyles({ "background-color": "#f00", "border-radius": "2px", "color": "#fff", "padding": "5px", "text-align": "center", "text-decoration": "none", "position": "fixed", "top": "0.5em", "right": "0.5em", "z-index": "100" }) + "\n" + "#" + UPDATE_HTML_ID + ":hover" + dom.emitCssStyles({ "background-color": "#0d0" }) + "\n" + "#" + HDR_LINKS_HTML_ID + dom.emitCssStyles({ "background-color": "#eee", "border": "#ccc 1px solid", //"border-radius": "3px", "color": "#333", "font-size": "90%", "margin": "5px", "padding": "5px" }) + "\n" + "#" + HDR_LINKS_HTML_ID + " a" + dom.emitCssStyles({ "background-color": "#fff", "border": "#ccc 1px solid", "border-radius": "3px", "color": "#000 !important", "display": "inline-block", "margin": "3px", "padding": "5px", "text-decoration": "none" }) + "\n" + "#" + HDR_LINKS_HTML_ID + " a:hover" + dom.emitCssStyles({ "background-color": "#d1e1fa" }) + "\n" + "." + CSS_PREFIX + "video" + dom.emitCssStyles({ "color": "#fff !important", "padding": "1px 3px" }) + "\n" + "." + CSS_PREFIX + "quality" + dom.emitCssStyles({ "color": "#fff !important", "padding": "1px 3px" }) + "\n" + "." + CSS_PREFIX + "flv" + dom.emitCssStyles({ "background-color": "#0dd" }) + "\n" + "." + CSS_PREFIX + "m4v" + dom.emitCssStyles({ "background-color": "#777" }) + "\n" + "." + CSS_PREFIX + "mp3" + dom.emitCssStyles({ "background-color": "#7ba" }) + "\n" + "." + CSS_PREFIX + "mp4" + dom.emitCssStyles({ "background-color": "#777" }) + "\n" + "." + CSS_PREFIX + "qt" + dom.emitCssStyles({ "background-color": "#f08" }) + "\n" + "." + CSS_PREFIX + "webm" + dom.emitCssStyles({ "background-color": "#e0e" }) + "\n" + "." + CSS_PREFIX + "wmv" + dom.emitCssStyles({ "background-color": "#c75" }) + "\n" + "." + CSS_PREFIX + "small" + dom.emitCssStyles({ "color": "#000 !important" }) + "\n" + "." + CSS_PREFIX + "medium" + dom.emitCssStyles({ "background-color": "#0d0" }) + "\n" + "." + CSS_PREFIX + "large" + dom.emitCssStyles({ "background-color": "#00d" }) + "\n" + "." + CSS_PREFIX + "hd720" + dom.emitCssStyles({ "background-color": "#f90" }) + "\n" + "." + CSS_PREFIX + "hd1080" + dom.emitCssStyles({ "background-color": "#f00" }) + "\n" + ""; function condInsertHdr() { if(dom.gE(HDR_LINKS_HTML_ID)) return true; var div = dom.cE("div"); div.id = HDR_LINKS_HTML_ID; var node = dom.gE("Content"); if(node == null) return false; node.parentNode.insertBefore(div, node); return true; } function condInsertUpdateIcon() { if(dom.gE(UPDATE_HTML_ID)) return; var div = dom.cE("a"); div.id = UPDATE_HTML_ID; dom.append(doc.body, div); } // ----------------------------------------------------------------------------- var STORE_ID = "ujsBtLinks"; var JSONP_ID = "ujsBtLinks"; var userConfig = { }; // ----------------------------------------------------------------------------- function Links() { } Links.prototype.init = function() { }; Links.prototype.showLinks = function(files) { function getVideoQuality(wt, ht, videoBitRate) { if(ht >= 1080) { if(videoBitRate >= 2.5) return "hd1080"; else return "large"; } if(ht >= 720) { if(videoBitRate >= 2.0) return "hd720"; else return "large"; } if(ht >= 480) { if(videoBitRate >= 1.5) return "large"; else return "medium"; } if(ht >= 360) { if(videoBitRate >= 0.75) return "medium"; else return "small"; } return "small"; } // Entry point if(!condInsertHdr()) return; var s = []; files.sort(function(a, b) { var a = { wt: +a.media_width, ht: +a.media_height, sz: +a.filesize }; var b = { wt: +b.media_width, ht: +b.media_height, sz: +b.filesize }; if(a.ht < b.ht) return 1; else if(a.ht > b.ht) return -1; if(a.wt < b.wt) return 1; else if(a.wt > b.wt) return -1; if(a.sz < b.sz) return 1; else if(a.sz > b.sz) return -1; else return 0; }); forEach(files, function(idx, elm) { //logMsg(idx + ": " + JSON.stringify(elm)); //logMsg("fname: " + elm.media_src + " " + elm.filename); logMsg("url: " + elm.url); logMsg(" res " + elm.media_width + " x " + elm.media_height); logMsg(" len: " + elm.media_length + ", size: " + elm.filesize); logMsg(" role: " + elm.role + ", type: " + elm.archive_type + ", mime: " + elm.primary_mime_type); logMsg(" video: " + elm.video_codec + ", bitrate " + elm.video_bitrate + ", fps " + elm.fps); logMsg(" audio: " + elm.audio_codec + ", bitrate " + elm.audio_bitrate + ", samplerate " + elm.sample_rate); var videoName = getVideoName(elm.primary_mime_type); var wt = +elm.media_width; var ht = +elm.media_height; var videoLen = Math.round(+elm.media_length / 6) / 10; var fileSize = Math.round(+elm.filesize / 1000 / 100) / 10; var videoBitRate = +elm.video_bitrate / 1000; var samplingRate = +elm.sample_rate / 1000; //var calcBitRate = +elm.filesize / +elm.media_length / 1000; elm.quality = getVideoQuality(wt, ht, videoBitRate); var videoResStr = ""; if(wt > 0 && ht > 0) videoResStr = " (" + wt + "x" + ht + ")"; var ahref = dom.emitHtml("a", { href: elm.url, title: videoLen + "mins | " + fileSize + "MiB | " + elm.video_codec + " " + videoBitRate + "Mbps " + elm.fps + "fps | " + elm.audio_codec + " " + elm.audio_bitrate + "kbps " + samplingRate + "kHz" }, dom.emitHtml("span", { "class": CSS_PREFIX + "video " + CSS_PREFIX + videoName.toLowerCase() }, videoName) + dom.emitHtml("span", { "class": CSS_PREFIX + "quality " + CSS_PREFIX + elm.quality }, elm.role + videoResStr)); s.push(ahref); }); dom.html(dom.gE(HDR_LINKS_HTML_ID), s.join("")); }; Links.prototype.checkFmts = function() { function success(data) { //logMsg(data); try { var obj = JSON.parse(data); me.showLinks(obj.Post.additionalMedia); } catch(e) { logMsg("Error: unable to parse data"); } } // Entry point var me = this; if(document.getElementsByClassName("EpisodePlayer").length == 0) return; dom.ajax({ url: loc.href + "?skin=json&no_wrap=1", success: success }); }; // ----------------------------------------------------------------------------- Links.prototype.loadSettings = function() { var obj = localStorage[STORE_ID]; if(obj == null) return; obj = JSON.parse(obj); this.lastChkReqTs = +obj.lastChkReqTs; this.lastChkTs = +obj.lastChkTs; this.lastChkVer = +obj.lastChkVer; }; Links.prototype.storeSettings = function() { localStorage[STORE_ID] = JSON.stringify({ lastChkReqTs: this.lastChkReqTs, lastChkTs: this.lastChkTs, lastChkVer: this.lastChkVer }); }; // ----------------------------------------------------------------------------- var UPDATE_CHK_INTERVAL = 5 * 86400; var FAIL_TO_CHK_UPDATE_INTERVAL = 14 * 86400; Links.prototype.chkVer = function(forceFlag) { if(this.lastChkVer > relInfo.ver) { this.showNewVer({ ver: this.lastChkVer }); return; } var now = Math.round(+new Date() / 1000); //logMsg("lastChkReqTs " + this.lastChkReqTs + ", diff " + (now - this.lastChkReqTs)); //logMsg("lastChkTs " + this.lastChkTs); //logMsg("lastChkVer " + this.lastChkVer); if(this.lastChkReqTs == null || now < this.lastChkReqTs) { this.lastChkReqTs = now; this.storeSettings(); return; } if(now - this.lastChkReqTs < UPDATE_CHK_INTERVAL) return; if(this.lastChkReqTs - this.lastChkTs > FAIL_TO_CHK_UPDATE_INTERVAL) logMsg("Failed to check ver for " + ((this.lastChkReqTs - this.lastChkTs) / 86400) + " days"); this.lastChkReqTs = now; this.storeSettings(); win[JSONP_ID] = this; var script = dom.cE("script"); script.type = "text/javascript"; script.src = SCRIPT_UPDATE_LINK; dom.append(doc.body, script); }; Links.prototype.chkVerCallback = function(data) { delete win[JSONP_ID]; this.lastChkTs = Math.round(+new Date() / 1000); this.storeSettings(); //logMsg(JSON.stringify(data)); var latestElm = data[0]; if(latestElm.ver <= relInfo.ver) return; this.showNewVer(latestElm); }; Links.prototype.showNewVer = function(latestElm) { function getVerStr(ver) { var verStr = "" + ver; var majorV = verStr.substr(0, verStr.length - 4) || "0"; var minorV = verStr.substr(verStr.length - 4, 2); return majorV + "." + minorV; } // Entry point this.lastChkVer = latestElm.ver; this.storeSettings(); condInsertUpdateIcon(); var aNode = dom.gE(UPDATE_HTML_ID); aNode.href = SCRIPT_LINK; dom.html(aNode, dom.emitHtml("b", SCRIPT_NAME + " " + getVerStr(relInfo.ver)) + "<br>Click to update to " + getVerStr(latestElm.ver)); }; // ----------------------------------------------------------------------------- var inst = new Links(); inst.init(); inst.loadSettings(); dom.insertCss(CSS_STYLES); inst.checkFmts(); inst.chkVer(); }) ();