Netflix subtitle downloader

Allows you to download subtitles from Netflix

23.01.2017 itibariyledir. En son verisyonu görün.

Bu betiği kurabilmeniz için Tampermonkey, Greasemonkey ya da Violentmonkey gibi bir kullanıcı betiği eklentisini kurmanız gerekmektedir.

Bu betiği kurabilmeniz için Tampermonkey ya da Violentmonkey gibi bir kullanıcı betiği eklentisini kurmanız gerekmektedir.

Bu betiği kurabilmeniz için Tampermonkey ya da Violentmonkey gibi bir kullanıcı betiği eklentisini kurmanız gerekmektedir.

Bu betiği kurabilmeniz için Tampermonkey ya da Userscripts gibi bir kullanıcı betiği eklentisini kurmanız gerekmektedir.

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

Bu komut dosyasını yüklemek için bir kullanıcı komut dosyası yöneticisi uzantısı yüklemeniz gerekecek.

(Zaten bir kullanıcı komut dosyası yöneticim var, kurmama izin verin!)

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

(Zateb bir user-style yöneticim var, yükleyeyim!)

// ==UserScript==
// @name        Netflix subtitle downloader
// @description Allows you to download subtitles from Netflix
// @namespace   tithen-firion
// @include     https://www.netflix.com/*
// @version     1.2
// @require     https://greatest.deepsurf.us/scripts/26651-xhrhijacker/code/xhrHijacker.js?version=171055
// @require     https://cdnjs.cloudflare.com/ajax/libs/jszip/3.1.3/jszip.js
// @require     https://cdnjs.cloudflare.com/ajax/libs/FileSaver.js/1.3.3/FileSaver.js
// @grant       GM_registerMenuCommand
// ==/UserScript==

function pad(n, w) {
  n = n + '';
  w = w || 2;
  return n.length >= w ? n : new Array(w - n.length + 1).join(0) + n;
}

function downloadThis() {
  if(typeof subFile === "undefined")
    window.setTimeout(downloadThis, 100);
  else {
    var blob = new Blob([subFile.content], {type: "text/plain;charset=utf-8"});
    saveAs(blob, subFile.name, true);
  }
}
function downloadAll() {
  batch = true;
  if(typeof subFile === "undefined")
    window.setTimeout(downloadThis, 100);
  else {
    zip = zip || new JSZip();
    zip.file(subFile.name, subFile.content);
    var el = document.querySelector(".player-next-episode:not(.player-hidden)");
    if(el)
      el.click();
    else
      zip.generateAsync({type:"blob"})
        .then(function(content) {
          saveAs(content, seriesTitle + ".zip");
          zip = undefined;
          batch = false;
        });
  }
}

function formatTime(time) {
  var tmp = time;
  var ms = pad(time%1000, 3);
  time = Math.floor(time/1000);
  var s = pad(time%60);
  time = Math.floor(time/60);
  var m = pad(time%60);
  var h = pad(Math.floor(time/60));
  return h + ":" + m + ":" + s + "," + ms;
}
function saveAsSrt(subs, filename) {
  txt = "";
  subs.forEach(function(sub, i) {
    txt += (i+1) + "\n" + formatTime(sub.s) + " --> " + formatTime(sub.e) + "\n" + sub.t + "\n\n";
  });
  subFile = {
    name: filename + ".srt",
    content: txt
  };
  if(batch)
    downloadAll();
}

function toText(node, styles) {
  var txt = "";
  var children = node.childNodes;
  for(let i = 0; i < children.length; ++i) {
    if(children[i].nodeType === 3)
      txt += children[i].textContent;
    else if(children[i].nodeType === 1) {
      if(children[i].nodeName.toUpperCase() === "BR")
        txt += "\n";
      else
        txt += toText(children[i], styles);
    }
  }
  if(node.hasAttribute("style")) {
    var s = node.getAttribute("style");
    if(s in styles)
      txt = styles[s].s + txt + styles[s].e;
  }
  return txt;
}

function styleParserHelper(style, styleElem, attribute, expectedValue, tag, colour) {
  var closeTag = false;
  if(styleElem.hasAttribute(attribute)) {
    let value = styleElem.getAttribute(attribute).trim();
    let equal = value === expectedValue;
    if(colour) {
      if(!equal) {
        style.s = "<" + tag + ' color="' + value + '">' + style.s;
        closeTag = true;
      }
    } else if(equal) {
      style.s = "<" + tag + ">" + style.s;
      closeTag = true;
    }
    if(closeTag)
      style.e += "</" + tag + ">";
  }
}
function processXml(xml, filename) {
  var styles = {}, prevStart = -1, subs = [{s: 0, e: 500, t: "Subtitles downloaded with 'Netflix subtitle downloader' UserScript by Tithen-Firion."}];
  var styleElems = xml.querySelectorAll("styling style");
  for(let i = 0; i < styleElems.length; ++i) {
    let id = styleElems[i].getAttribute("xml:id");
    styles[id] = {s: "", e: ""};
    styleParserHelper(styles[id], styleElems[i], "tts:fontWeight", "bold", "b");
    styleParserHelper(styles[id], styleElems[i], "tts:fontStyle", "italic", "i");
    styleParserHelper(styles[id], styleElems[i], "tts:textDecoration", "underline", "u");
    styleParserHelper(styles[id], styleElems[i], "tts:color", "white", "font", true);
    if(styles[id].s === "")
      delete styles[id];
  }
  var subElems = xml.querySelectorAll("div p");
  for(let i = 0; i < subElems.length; ++i) {
    let el = subElems[i];
    let start = Math.round(parseInt(el.getAttribute("begin"))/10000);
    let end = Math.round(parseInt(el.getAttribute("end"))/10000);
    let txt = toText(el, styles);
    if(start === prevStart)
      subs[subs.length-1].t += "\n" + txt;
    else
      subs.push({s: start, e: end, t: txt});
    prevStart = start;
  }
  saveAsSrt(subs, filename);
}

var IDs = [], batch = false, seriesTitle, zip, subFile;
xhrHijacker(function(xhr, id, origin, args) {
  if(origin === "open") {
    if(args[1].indexOf("/?o=") > -1)
      IDs.push(id);
  } else if(origin === "load") {
    var index = IDs.indexOf(id);
    if(index > -1) {
      IDs.splice(index, 1);
      var el = document.querySelector(".player-status-main-title");
      var title = seriesTitle = el.innerText.replace(/[:*?"<>|\\\/]+/g, "_").replace(/ /g, ".");
      var m = el.nextElementSibling.innerText.match(/^[^\d]*?(\d+)[^\d]*?(\d+)[^\d]*?$/);
      title += ".S" + pad(m[1]) + "E" + pad(m[2]) + ".WEBRip.Netflix.";
      title += document.querySelector(".player-timed-text-tracks > .player-track-selected").getAttribute("data-id").split(";")[2];
      var parser = new DOMParser();
      var xmlDoc = parser.parseFromString(xhr.response, "text/xml");
      processXml(xmlDoc, title);
    }
  }
});

GM_registerMenuCommand("Download subs for this episode", downloadThis);
GM_registerMenuCommand("Download subs from this ep till last available", downloadAll);