Blip.tv Links

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.

Tendrás que instalar una extensión para tu navegador como Tampermonkey, Greasemonkey o Violentmonkey si quieres utilizar este script.

You will need to install an extension such as Tampermonkey to install this script.

Tendrás que instalar una extensión como Tampermonkey o Violentmonkey para instalar este script.

Necesitarás instalar una extensión como Tampermonkey o Userscripts para instalar este script.

Tendrás que instalar una extensión como Tampermonkey antes de poder instalar este script.

Necesitarás instalar una extensión para administrar scripts de usuario si quieres instalar este script.

(Ya tengo un administrador de scripts de usuario, déjame instalarlo)

Tendrás que instalar una extensión como Stylus antes de poder instalar este script.

Tendrás que instalar una extensión como Stylus antes de poder instalar este script.

Tendrás que instalar una extensión como Stylus antes de poder instalar este script.

Para poder instalar esto tendrás que instalar primero una extensión de estilos de usuario.

Para poder instalar esto tendrás que instalar primero una extensión de estilos de usuario.

Para poder instalar esto tendrás que instalar primero una extensión de estilos de usuario.

(Ya tengo un administrador de estilos de usuario, déjame instalarlo)

// ==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(/&lt;/g, "<").replace(/&gt;/g, ">").replace(/&amp;/g, "&").replace(/&quot;/g, '"').replace(/&#39;/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();

}) ();