Greasy Fork is available in English.

Netflix subtitle downloader

Allows you to download subtitles from Netflix

اعتبارا من 10-10-2017. شاهد أحدث إصدار.

// ==UserScript==
// @name        Netflix subtitle downloader
// @description Allows you to download subtitles from Netflix
// @namespace   tithen-firion
// @include     https://www.netflix.com/*
// @version     1.11
// @require     https://greasyfork.org/scripts/26651-xhrhijacker/code/xhrHijacker.js?version=171120
// @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), .button-nfplayerNextEpisode");
    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) {
  try {
    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;
    }
  }
  catch(e) {
    console.error(e);
    alert('Failed to convert to SRT format');
    return;
  }
  saveAsSrt(subs, filename);
}
function processResponse(responseText) {
  var el = document.querySelector(".player-status-main-title, .ellipsize-text>h4, .video-title>h4");
  if(el === null) {
    window.setTimeout(processResponse, 200, responseText);
    return;
  }
  var title = seriesTitle = el.innerText.replace(/[:*?"<>|\\\/]+/g, "_").replace(/ /g, ".") + ".";
  var nextEl = el.nextElementSibling;
  if(nextEl) {
    var m = nextEl.innerText.match(/^[^\d]*?(\d+)[^\d]*?(\d+)[^\d]*?$/);
    if(m && m.length === 3)
      title += "S" + pad(m[1]) + "E" + pad(m[2]) + ".WEBRip.Netflix";
  }
  var selectedSubs = document.querySelector(".player-timed-text-tracks > .player-track-selected");
  if(selectedSubs !== null)
    title += "." + selectedSubs.getAttribute("data-id").split(";")[2];
  try {
    var parser = new DOMParser();
    var xmlDoc = parser.parseFromString(responseText, "text/xml");
  }
  catch(e) {
    console.error(e);
    alert('Failed to parse XML subtitle file');
    return;
  }
  processXml(xmlDoc, title);
}

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);
      processResponse(xhr.response);
    }
  }
});

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