Advertisement
Guest User

Untitled

a guest
Feb 19th, 2017
2,860
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 5.36 KB | None | 0 0
  1. // ==UserScript==
  2. // @name Netflix subtitle downloader
  3. // @description Allows you to download subtitles from Netflix
  4. // @namespace tithen-firion
  5. // @include https://www.netflix.com/*
  6. // @version 1.4
  7. // @require https://greasyfork.org/scripts/26651-xhrhijacker/code/xhrHijacker.js?version=171120
  8. // @require https://cdnjs.cloudflare.com/ajax/libs/jszip/3.1.3/jszip.js
  9. // @require https://cdnjs.cloudflare.com/ajax/libs/FileSaver.js/1.3.3/FileSaver.js
  10. // @grant GM_registerMenuCommand
  11. // ==/UserScript==
  12.  
  13. function pad(n, w) {
  14. n = n + '';
  15. w = w || 2;
  16. return n.length >= w ? n : new Array(w - n.length + 1).join(0) + n;
  17. }
  18.  
  19. function downloadThis() {
  20. if(typeof subFile === "undefined")
  21. window.setTimeout(downloadThis, 100);
  22. else {
  23. var blob = new Blob([subFile.content], {type: "text/plain;charset=utf-8"});
  24. saveAs(blob, subFile.name, true);
  25. }
  26. }
  27. function downloadAll() {
  28. batch = true;
  29. if(typeof subFile === "undefined")
  30. window.setTimeout(downloadThis, 100);
  31. else {
  32. zip = zip || new JSZip();
  33. zip.file(subFile.name, subFile.content);
  34. var el = document.querySelector(".player-next-episode:not(.player-hidden)");
  35. if(el)
  36. el.click();
  37. else
  38. zip.generateAsync({type:"blob"})
  39. .then(function(content) {
  40. saveAs(content, seriesTitle + ".zip");
  41. zip = undefined;
  42. batch = false;
  43. });
  44. }
  45. }
  46.  
  47. function formatTime(time) {
  48. var tmp = time;
  49. var ms = pad(time%1000, 3);
  50. time = Math.floor(time/1000);
  51. var s = pad(time%60);
  52. time = Math.floor(time/60);
  53. var m = pad(time%60);
  54. var h = pad(Math.floor(time/60));
  55. return h + ":" + m + ":" + s + "," + ms;
  56. }
  57. function saveAsSrt(subs, filename) {
  58. txt = "";
  59. subs.forEach(function(sub, i) {
  60. txt += (i+1) + "\n" + formatTime(sub.s) + " --> " + formatTime(sub.e) + "\n" + sub.t + "\n\n";
  61. });
  62. subFile = {
  63. name: filename + ".srt",
  64. content: txt
  65. };
  66. if(batch)
  67. downloadAll();
  68. }
  69.  
  70. function toText(node, styles) {
  71. var txt = "";
  72. var children = node.childNodes;
  73. for(let i = 0; i < children.length; ++i) {
  74. if(children[i].nodeType === 3)
  75. txt += children[i].textContent;
  76. else if(children[i].nodeType === 1) {
  77. if(children[i].nodeName.toUpperCase() === "BR")
  78. txt += "\n";
  79. else
  80. txt += toText(children[i], styles);
  81. }
  82. }
  83. if(node.hasAttribute("style")) {
  84. var s = node.getAttribute("style");
  85. if(s in styles)
  86. txt = styles[s].s + txt + styles[s].e;
  87. }
  88. return txt;
  89. }
  90.  
  91. function styleParserHelper(style, styleElem, attribute, expectedValue, tag, colour) {
  92. var closeTag = false;
  93. if(styleElem.hasAttribute(attribute)) {
  94. let value = styleElem.getAttribute(attribute).trim();
  95. let equal = value === expectedValue;
  96. if(colour) {
  97. if(!equal) {
  98. style.s = "<" + tag + ' color="' + value + '">' + style.s;
  99. closeTag = true;
  100. }
  101. } else if(equal) {
  102. style.s = "<" + tag + ">" + style.s;
  103. closeTag = true;
  104. }
  105. if(closeTag)
  106. style.e += "</" + tag + ">";
  107. }
  108. }
  109. function processXml(xml, filename) {
  110. var styles = {}, prevStart = -1, subs = [];
  111. var styleElems = xml.querySelectorAll("styling style");
  112. for(let i = 0; i < styleElems.length; ++i) {
  113. let id = styleElems[i].getAttribute("xml:id");
  114. styles[id] = {s: "", e: ""};
  115. styleParserHelper(styles[id], styleElems[i], "tts:fontWeight", "bold", "b");
  116. styleParserHelper(styles[id], styleElems[i], "tts:fontStyle", "italic", "i");
  117. styleParserHelper(styles[id], styleElems[i], "tts:textDecoration", "underline", "u");
  118. styleParserHelper(styles[id], styleElems[i], "tts:color", "white", "font", true);
  119. if(styles[id].s === "")
  120. delete styles[id];
  121. }
  122. var subElems = xml.querySelectorAll("div p");
  123. for(let i = 0; i < subElems.length; ++i) {
  124. let el = subElems[i];
  125. let start = Math.round(parseInt(el.getAttribute("begin"))/10000);
  126. let end = Math.round(parseInt(el.getAttribute("end"))/10000);
  127. let txt = toText(el, styles);
  128. if(start === prevStart)
  129. subs[subs.length-1].t += "\n" + txt;
  130. else
  131. subs.push({s: start, e: end, t: txt});
  132. prevStart = start;
  133. }
  134. saveAsSrt(subs, filename);
  135. }
  136.  
  137. var IDs = [], batch = false, seriesTitle, zip, subFile;
  138. xhrHijacker(function(xhr, id, origin, args) {
  139. if(origin === "open") {
  140. if(args[1].indexOf("/?o=") > -1)
  141. IDs.push(id);
  142. } else if(origin === "load") {
  143. var index = IDs.indexOf(id);
  144. if(index > -1) {
  145. IDs.splice(index, 1);
  146. var el = document.querySelector(".player-status-main-title");
  147. var title = seriesTitle = el.innerText.replace(/[:*?"<>|\\\/]+/g, "_").replace(/ /g, ".");
  148. if(el.nextElementSibling !== null) {
  149. var m = el.nextElementSibling.innerText.match(/^[^\d]*?(\d+)[^\d]*?(\d+)[^\d]*?$/);
  150. title += ".S" + pad(m[1]) + "E" + pad(m[2]);
  151. }
  152.  
  153. title += ".WEBRip.Netflix." + document.querySelector(".player-timed-text-tracks > .player-track-selected").getAttribute("data-id").split(";")[2];
  154. var parser = new DOMParser();
  155. var xmlDoc = parser.parseFromString(xhr.response, "text/xml");
  156. processXml(xmlDoc, title);
  157. }
  158. }
  159. });
  160.  
  161. GM_registerMenuCommand("Download subs for this episode", downloadThis);
  162. GM_registerMenuCommand("Download subs from this ep till last available", downloadAll);
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement