"
evTotal++
}
var withSC = ''
if (shotChanges) {
withSC = "_withShotChanges"
}
var fixesFilename = srtName("Fixes_" + LANG + "_" +
fps_ + withSC)
var fixedSrtFull = parsed2srt(fSRT)
fixedSrtFull = fixedSrtFull.replace(/ <\/b>/g,
'')
var suggestedFixesTxt =
''
qcMeta.results = {
totalEventsAffected: evTotal,
majorIssuesFound: majTotal,
minorIssuesFound: minTotal,
shotChangeViolations_found: scTotal,
autoFixesDone: afTotal
}
function getUnnamed() {
function n(n) {
for (let e = n.length - 1; e > 0; e--) {
let i = Math.floor(Math.random() * (e + 1));
[n[e], n[i]] = [n[i], n[e]]
}
}
var e = ["Beaver", "Koala", "Chipmunk", "Goat",
"Duckling", "Cheetah", "Platypus", "Eagle",
"Mongoose", "Butterfly"
];
n(e);
var i = ["An Intrepid", "A Meek", "A Feisty",
"A Lazy", "A Sly", "An Unperturbed",
"An Amused", "A Rabid", "A Very", "A Really"
];
n(i);
var t = [" Little ", " Humongous ", " Tiny ",
" Vain ", " Finicky ", " Cunning ", " Exotic ",
" Weary ", " Opinionated ", " Conniving "
];
return n(t), i[7] + t[7] + e[7]
}
var qcMetaStr = "`" + JSON.stringify(qcMeta, null, 2) +
"`"
if (typeof userName == "undefined") {
userName = getUnnamed()
}
reportHtml = reportHtml.replace("DLFIXSRTTOKEN",
suggestedFixesTxt)
.replace(
/WATERMARKPLACEHOLDER/g, userName)
.replace(
"TITLEINFOTOKEN", titleInfo)
.replace(
"TITLEINFOTOKEN", titleInfo)
.replace(
"LINGUISTNAME", userName)
.replace("METADATATOKEN",
qcMetaStr)
console.log(qcMeta.results)
download(srtName("Auto_QC_Log_" + LANG + '_' + fps_ +
withSC, ".html"), reportHtml)
localStorage.removeItem("shotChanges:" + qcMeta.clq)
localStorage.removeItem("clq:origination:" + qcMeta.clq)
console.log(
"DEBUG: Flushing data from localStorage.")
}
return runAutoQC
})));
(function(global, factory) {
typeof exports === 'object' && typeof module !== 'undefined' ?
module.exports = factory() : typeof define === 'function' &&
define.amd ? define(factory) : (global.parseSRT = factory());
}(this, (function() {
'use strict';
function toSeconds(time) {
var t = time.split(':');
try {
var s = t[2].split(',');
if (s.length === 1) {
s = t[2].split('.');
}
return parseFloat(t[0], 10) * 3600 + parseFloat(t[1],
10) * 60 + parseFloat(s[0], 10) + parseFloat(s[
1], 10) / 1000;
} catch (e) {
return 0;
}
}
function nextNonEmptyLine(linesArray, position) {
var idx = position;
while (!linesArray[idx]) {
idx++;
}
return idx;
}
function lastNonEmptyLine(linesArray) {
var idx = linesArray.length - 1;
while (idx >= 0 && !linesArray[idx]) {
idx--;
}
return idx;
}
function parseSRT() {
var data = arguments.length > 0 && arguments[0] !==
undefined ? arguments[0] : '';
var subs = [];
var lines = data.split(/(?:\r\n|\r|\n)/gm);
var endIdx = lastNonEmptyLine(lines) + 1;
var idx = 0;
var time = void 0;
var text = void 0;
var sub = void 0;
for (var i = 0; i < endIdx; i++) {
sub = {};
text = [];
i = nextNonEmptyLine(lines, i);
sub.id = parseInt(lines[i++], 10);
time = lines[i++].split(/[\t ]*-->[\t ]*/);
sub.start = toSeconds(time[0]);
idx = time[1].indexOf(' ');
if (idx !== -1) {
time[1] = time[1].substr(0, idx);
}
sub.end = toSeconds(time[1]);
while (i < endIdx && lines[i]) {
text.push(lines[i++]);
}
sub.text = text.join('\\N')
.replace(/{.an8}/,
'POSToppyTAG')
.replace(
/\{(\\[\w]+\(?([\w\d]+,?)+\)?)+\}/gi, '');
sub.text = sub.text.replace(//g, '>');
sub.text = sub.text.replace(
/<(\/?(font|b|u|i|s))((\s+(\w|\w[\w\-]*\w)(\s*=\s*(?:".*?"|'.*?'|[^'">\s]+))?)+\s*|\s*)(\/?)>/gi,
'<$1$3$7>');
sub.text = sub.text.replace(/\\N/gi, "\n");
sub.text = sub.text.replace('POSToppyTAG', '{\\an8}')
subs.push(sub);
}
return subs;
}
return parseSRT;
})));
setShotChangesAndEvents()
defer(runAutoQC)
})()
}
function imarunettuDoubleExportSRT() {
(function() {
var our_clq = decodeURIComponent(document.location.href.toString())
.split("=")[1].split(
":")[2]
function setEvents() {
if (typeof our_clq !== "object" && document.location.href.includes(
"timedtext.netflixstudios.com")) {
var getJSON = function(url, callback) {
var xhr = new XMLHttpRequest();
xhr.open('GET', url, true);
xhr.responseType = 'json';
xhr.onload = function() {
var status = xhr.status;
if (status === 200) {
callback(null, xhr.response);
} else {
callback(status, xhr.response);
}
};
xhr.send();
}
if (!localStorage.getItem("clq:origination:" + our_clq)) {
getJSON(
"https://timedtext.netflixstudios.com/nqapi/request/getDetails/clq:origination:" +
our_clq,
function(err, data) {
if (err !== null) {
alert("Something went wrong with error status: " +
err +
"\nIf it's 500-something, please reload the page and try again."
);
} else {
var events = '{"meta":' + JSON.stringify(data["media"]["meta"]) + ',"events":' + JSON.stringify(data["events"]) + '}';
localStorage.setItem("clq:origination:" + our_clq, events)
console.log("DEBUG: Timed text events for " +
our_clq + " saved in localStorage")
}
})
} else {
console.log("DEBUG: Timed text events for " + our_clq +
" already present in localStorage")
alert("Timed text events found in Local Storage for " +
our_clq + "without requesting from Netflix servers.\n" +
"Report me to the developer with the good news: Netflix has finaly fixed issues with writing data to local storage (i.e. saving locally to disk). Hooray!\n" +
"Proceeding to use the events found in Local Storage.");
}
}
}
function defer(method) {
if (localStorage.getItem("clq:origination:" + our_clq) ||
typeof our_clq == "object") {
setTimeout(function() {
runExport(our_clq);
}, 2400);
} else {
setTimeout(function() {
defer(runExport)
}, 350);
}
}
(function(global, factory) {
typeof exports === 'object' && typeof module !== 'undefined' ?
module.exports = factory() : typeof define === 'function' &&
define.amd ? define(factory) : (global.runExport = factory());
}(this, (function() {
'use strict';
function uid() {
return ([1e7] + -1e3 + -4e3 + -8e3 + -1e11)
.replace(
/[018]/g, c => (c ^ crypto.getRandomValues(
new Uint8Array(1))[0] & 15 >> c / 4)
.toString(
16))
}
function runExport() {
var clq_pattern = new RegExp(
'^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$',
'i');
if (!our_clq || !clq_pattern.test(our_clq)) {
alert("You must be in a started, saved Originator task!");
throw new Error
}
if (!clq_pattern.test(our_clq)) {
alert("The CLQ is invalid: " + our_clq + "\n" +
"Please report me to the developer.");
throw new Error
}
var events_and_meta = JSON.parse(localStorage.getItem("clq:origination:" +
our_clq))
try {
var events = JSON.stringify(events_and_meta["events"]).split(",");
console.log("DEBUG: Events validated.")
} catch (e) {
var events = undefined;
console.log(
"DEBUG: No events found. ")
alert("No events found. Report me to the developer.");
return null
}
try {
var meta = JSON.stringify(events_and_meta["meta"]).split(",");
console.log("DEBUG: Metadata validated.")
} catch (e) {
var meta = undefined;
console.log(
"DEBUG: No metadata found. ")
alert("No metadata found. Report me to the developer.");
}
var bilingualHtml = `
var prevScrollpos=window.pageYOffset;
window.onscroll=function() {
var currentScrollPos=window.pageYOffset;
if (prevScrollpos > currentScrollPos) {
document.getElementById("navbar").style.top="0";
}
else {
document.getElementById("navbar").style.top="-50px";
}
prevScrollpos=currentScrollPos;
}
function loadScript(src){
var jScript = document.createElement('script')
jScript.type = "text/javascript";
jScript.src = src.trim();
document.body.appendChild(jScript)
}
document.addEventListener('DOMContentLoaded', function load() {
if (!window.jQuery) return setTimeout(load, 50);
console.log("jQuery and JsDiff are loaded! Executing main.");
main();
}, false);
function main(){
document.addEventListener('paste', function(e) {
var pasteStatus = document.getElementById("pasteReady");
pasteStatus.innerText = "got paste!";
pasteStatus.style.opacity = 0.01;
fade("in",500,pasteStatus);
var earlier_html = e.clipboardData.getData('text/html');
//console.log(earlier_html);
proceedWithCompare(earlier_html);
}, {once: true} ); console.log("Listening for paste!");
var oldRows = document.getElementsByTagName("tr")
function fade(type, ms, el, remove = false) {
var isIn = type === 'in',
opacity = isIn ? 0 : 1,
interval = 20,
duration = ms,
gap = interval / duration
if (isIn) {
el.style.display = 'inline'
el.style.opacity = opacity
}
function func() {
opacity = isIn ? opacity + gap : opacity - gap
el.style.opacity = opacity
if (opacity <= 0) { remove ? el.remove() : el.style.display = 'none' }
if (opacity <= 0 || opacity >= 1) window.clearInterval(fading)
}
var fading = window.setInterval(func, interval)
}
var b = document.querySelector("#the-button")
b.innerText = "DIFF ENGINE ON";
b.style.color = "gold";
b.style.fontWeight = "bold";
b.style.backgroundColor = "brightgreen";
fade("in",700,b,false);
var p = document.createElement('span');
p.innerText = "paste it!";
p.style = "position: relative; top: 1px; left: 13px; font-weight: bold; color: ivory; opacity: 0.5;"
p.id = "pasteReady";
b.insertAdjacentElement("afterEnd",p);
function proceedWithCompare(document_html){
function createTextSheetFromRows(rows,childNumber){
var text = ""
for(row of rows){
text += row.children[childNumber].innerText+"\\n\\r\\r\\r\\n\\r\\n"
}
return text
}
var document_new = new DOMParser().parseFromString(document_html, "text/html");
newRows = document_new.body.getElementsByTagName("tr")
var pasteStatus = document.getElementById("pasteReady");
if(newRows.length == 0)
{
pasteStatus.innerText = "paste error!";
pasteStatus.style.opacity = 0.01;
pasteStatus.style.color = "red";
fade("in",500,pasteStatus,false);
} else {
pasteStatus.innerText = "use Alt+Z";
pasteStatus.style.opacity = 0.01;
pasteStatus.style.color = "green";
b.innerText = "DIFF SUCCESS";
b.style.opacity = 0.3;
fade("in",500,pasteStatus,false);
}
function diffAndPopulateCell(oldRows,newRows,dmethod,cellNumber)
{
oldRowsText = createTextSheetFromRows(oldRows, cellNumber)
newRowsText = createTextSheetFromRows(newRows, cellNumber)
switch(dmethod) {
case "1" : diff = JsDiff.diffWords(oldRowsText, newRowsText); break;
case "2" : diff = JsDiff.diffLines(oldRowsText, newRowsText); break;
case "3" : diff = JsDiff.diffChars(oldRowsText, newRowsText); break;
case "4" : diff = JsDiff.diffWordsWithSpace(oldRowsText, newRowsText); break;
case "5" : diff = JsDiff.diffTrimmedLines(oldRowsText, newRowsText); break;
case "6" : diff = JsDiff.diffSentences(oldRowsText, newRowsText); break;
default: diff = JsDiff.diffWordsWithSpace(oldRowsText, newRowsText);}
textHolder = document.createElement("pre")
fragment = document.createDocumentFragment();
diff.forEach((part) => { const color = part.added ? "#26ff00" : part.removed ? 'red' : '#808080';
var class_name = part.added ? "goody" : part.removed ? 'bady' : 'neutry';
var font_weight = part.added ? "bold" : part.removed ? "lighter" : "normal";
span = document.createElement('span');
span.style.color = color;
span.className = class_name;
span.style.fontWeight = font_weight;
part.removed && (span.style.textDecoration = "line-through");
part.removed && (span.style.opacity=0.9);
span.appendChild(document.createTextNode(part.value));
fragment.appendChild(span);
});
textHolder.appendChild(fragment)
eval("c" + cellNumber + ".appendChild(textHolder)")
}
var table = document.querySelector("table")
var newTable = document.createElement("table")
var row = newTable.insertRow(0)
var c0 = row.insertCell(0)
var c1 = row.insertCell(1)
var c2 = row.insertCell(2)
newTable.style.width = "80%";
newTable.style.margin = "auto";
newTable.style.contenteditable =true;
var dMeth = prompt("Select comparison mode: 1. Words, 2. Lines, 3. Characters\\n4. Words-and-Whitespace, 5. Trimmed-Lines, 6. Sentences","1");
diffAndPopulateCell(oldRows,newRows,3,0)
var cues = [];
cues = c0.innerHTML.split("\\n\\r\\r\\r\\n\\r\\n")
diffAndPopulateCell(oldRows,newRows,dMeth,1)
var originals = [];
originals = c1.innerHTML.split("\\n\\r\\r\\r\\n\\r\\n")
diffAndPopulateCell(oldRows,newRows,dMeth,2)
var translations = [];
translations = c2.innerHTML.split("\\n\\r\\r\\r\\n\\r\\n")
if(originals.length > translations.length) {var lim=originals.length} else {var lim=translations.length}; var newHtml='
'; for(a=1; a
' + cues[a] + '
' + originals[a] + '
' + translations[a] +'
'; } newHtml += "
"
document.querySelector("table").outerHTML = newHtml
var visibility = "both";
document.onkeydown = function(event){
if (event.altKey && event.keyCode == 90) {
//Alt+Z, simultaneously.
switch (visibility) {
case "both": for(ea of document.getElementsByClassName("bady")) { fade('out',100,ea,false) }; visibility = "only_goodies";
break;
case "only_goodies": for(ea of document.getElementsByClassName("bady")) { fade('in',100,ea,false) }
for(ea of document.getElementsByClassName("goody")) { fade('out',100,ea,false) }
visibility = "only_mistakes"
break;
case "only_mistakes": for(ea of document.getElementsByClassName("goody")) { fade('in',100,ea,false) }
visibility = "both"
break;
default: for(ea of document.getElementsByClassName("bady")) { fade('in',100,ea,false) }
for(ea of document.getElementsByClassName("goody")) { fade('in',100,ea,false) }
visibility = "both"}}}}}
TITLEINFO (TITLEDATE)
In-cue/Out-cue
Source
Translation
`
var lsJson = localStorage["clq:origination:" + our_clq]
if (!lsJson) {
alert("Timed text events not found in Local Storage for CLQ: " +
our_clq + "\nReport me to the developer.");
throw new Error
}
var json_obj = JSON.parse(lsJson)
var src = json_obj["events"]
var fps_ = json_obj["meta"]["fps"]
var fps = fps_.split("_")[1] / 100
//From {"fps":"FPS_2500"}
var proposed_fps = prompt(
"DOUBLE EXPORT SRT reportsโฆ\n\nPress Enter to accept framerate of " +
fps + " or enter new as 2400 or 2997:");
if (proposed_fps !== "") {
var int_fps = proposed_fps.substring(0, 2);
var decimal_fps = proposed_fps.substring(2, proposed_fps.length);
fps_ = "FPS" + "_" + int_fps + decimal_fps
fps = (int_fps + "." + decimal_fps) * 1;
}
var mid = json_obj["meta"]["movieId"]
var pid = json_obj["meta"]["packageId"]
var which_url = prompt(
"DOUBLE EXPORT SRT reportsโฆ\n\nPress Enter to try the template. Enter anything to go for CC."
)
var which_lang = prompt(
"DOUBLE EXPORT SRT reportsโฆ\n\nPress Enter to go for English. Enter a language code to try that (es/fr/ru/etc).",
"en");
if (which_lang == "") {
which_lang = "en"
}
if (which_url == "") {
var template_url =
"https://timedtext.netflixstudios.com/nqapi/request/timedText/" +
our_clq + '/' + pid + '/' + mid + '/' + which_lang + '/TEMPLATE/PRIMARY/' +
fps_ + '?source=ORIGINATOR'
} else {
var template_url =
"https://timedtext.netflixstudios.com/nqapi/request/timedText/" +
our_clq + '/' + pid + '/' + mid + '/' + which_lang + '/CC/PRIMARY/' + fps_ +
'?source=ARCHIVE'
}
var targetFilename = srtName(fps_ + "_TRANSLATION")
var sourceFilename = srtName(fps_ + "_SOURCE")
var backupFilename = srtName(fps_ + "_BILINGUAL_TABLE")
.replace(".srt",
".html")
var frms = 1000 / fps
async function getSourceColumnEvents() {
var result = await (await fetch(template_url))
.json();
return result;
}
async function delayedDownload() {
var result = await getSourceColumnEvents();
download(sourceFilename, array2srt(result))
// download(backupFilename, arrays2html(result, src),"html" )
}
delayedDownload()
download(targetFilename, array2srt(src))
localStorage.removeItem("clq:origination:" + our_clq)
console.log(
"DEBUG: Flushing data from localStorage.")
function srtName(suffix = "") {
var s = document.getElementsByClassName("cpe-page-menu-label")[0]
.innerText
var srtName = (s.replace(/[ ]/g, '_')
.replace(/[^a-z0-9_]/gi, '') +
suffix + ".srt"
) //This gets rid of all punctuation, spaces and non-English letters
.trim() //resulting in a name like 14545_El_Burrito_A_Breaking_Fat_Movie_FPS_2500.srt
if (!srtName) srtName = our_clq + "_" + suffix +
".srt" //Fallback measure. Useful for debugging later
return srtName
}
function frames2timecode(frames) { //frames to 00:01:02,000 format
var milliseconds = Math.round(frames * frms)
var srt_timecode = TimeConversion(milliseconds)
return srt_timecode
}
function merge_same(array) {
var merged = []
var skip_next = false
for (var i = 0; i < array.length - 1; i++) {
if (!skip_next) {
var thisEvent = array[i]
var nextEvent = array[i + 1]
if (thisEvent[5] == nextEvent[5]) {
skip_next = true
thisEvent[2] = thisEvent[2] + "\n" + nextEvent[2]
thisEvent[1] = nextEvent[1]
}
merged.push(thisEvent)
} else {
skip_next = false
}
}
return merged
}
function arrays2html(events_object, events_object2) {
var ordered_events = []
var col = "SRC"
for (var id in events_object) {
events_object[id]["column"] = col
try {
var type_fn = events_object[id]["type"]
if (type_fn === "fn") {
events_object[id]["txt"] += "";
type_fn = undefined;
}
} catch (e) {}
ordered_events.push([
events_object[id]["start"], events_object[id]["end"],
events_object[id]
["txt"],
events_object[id]
["styles"],
events_object[id]
["rgn"],
events_object[id]
["column"],
])
}
var col = "TRG"
for (var id in events_object2) {
events_object2[id]["column"] = col
ordered_events.push([
events_object2[id]["start"], events_object2[id]["end"],
events_object2[id]
["txt"],
events_object2[id]
["styles"],
events_object2[id]
["rgn"],
events_object2[id]
["column"],
])
}
ordered_events.sort(function(a, b) {
return a[0] - b[0];
}); //Array sorted by in_cues, sequentially
ordered_events = merge_same(ordered_events)
// ordered_events.sort(function (a
// , b) {
// return a[1] - b[1];
// }); //Array sorted by out_cues this time, because merge_same changes some, sequentially
var index = 0
var srt_txt = bilingualHtml;
var eol = ""
var source_content = ''
for (event of ordered_events) {
var start = frames2timecode(event[0])
var end = frames2timecode(event[1])
var startend = start + "\n" + end
try {
if (typeof event[3][0]["type"] !== "undefined") {
if (event[3][0]["type"] == "italic") {
content = italicize(content, event[3])
}
}
} catch (e) {}
if (event[5] == "SRC") {
source_content += event[2]
} else {
tr = '
' + startend + '
' +
source_content + '
' + event[2] +
'
' + "\n";
srt_txt += tr;
tr = "";
source_content = ""
}
}
var titleinfo = document.getElementsByClassName(
"cpe-page-menu-label")[0].innerText.replace(/ "/, " โ")
.replace(/"/, "โ")
var titlelink = document.location.href
var titledate = new Date()
.toISOString()
.slice(0, 10)
srt_txt = srt_txt.replace(/TITLEINFO/g, titleinfo)
.replace(
/TITLELINK/, titlelink)
.replace(/TITLEDATE/g, titledate)
return srt_txt
}
function array2srt(events_object) {
var ordered_events = []
for (var id in events_object) {
try {
var type_fn = events_object[id]["type"]
if (type_fn === "fn") {
events_object[id]["txt"] += "";
type_fn = undefined;
}
} catch (e) {}
ordered_events.push([
events_object[id]["start"], events_object[id]["end"],
events_object[id]
["txt"],
events_object[id]
["styles"],
events_object[id]
["rgn"]
])
}
ordered_events.sort(function(a, b) {
return a[0] - b[0];
}); //Array sorted by in_cues, sequentially
var index = 0
var srt_txt = ''
for (event of ordered_events) {
index++
var start = frames2timecode(event[0])
var end = frames2timecode(event[1])
var content = event[2]
try {
if (typeof event[3][0]["type"] !== "undefined") {
if (event[3][0]["type"] == "italic") {
content = italicize(content, event[3])
}
}
} catch (e) {}
try {
if (typeof event[4] !== "undefined") {
if (event[4] == "top") {
content = "{\\an8}" + content
}
}
} catch (e) {}
try {
if (event["type"] == "fn") {
content += ''
}
} catch (e) {}
console.log(content)
var current_event = index + "\n" + start + " --> " + end + "\n" +
content + "\n"
srt_txt += current_event + "\n"
}
return srt_txt
}
function italicize(content, italics_array) {
var position_offset = 0
for (var italic of italics_array) {
var position_from = italic["from"] + position_offset;
position_offset += 3
content = [content.slice(0, position_from), "", content.slice(
position_from)].join('')
var position_to = italic["to"] + position_offset;
position_offset += 4
content = [content.slice(0, position_to), "", content.slice(
position_to)].join('')
}
return content
}
function download(filename, text, type = "plain") {
var element = document.createElement('a');
element.setAttribute('href', 'data:text/' + type +
';charset=utf-8,%EF%BB%BF' + encodeURIComponent(text));
element.setAttribute('download', filename);
element.style.display = 'none';
document.body.appendChild(element);
element.click();
document.body.removeChild(element);
}
function TimeConversion(duration) {
let time = parseDuration(duration)
return formatTimeHMSS(time)
}
function parseDuration(duration) {
let remain = duration
let hours = Math.floor(remain / (1000 * 60 * 60))
remain = remain % (1000 * 60 * 60)
let minutes = Math.floor(remain / (1000 * 60))
remain = remain % (1000 * 60)
let seconds = Math.floor(remain / (1000))
remain = remain % (1000)
let milliseconds = remain
return {
hours,
minutes,
seconds,
milliseconds
}
}
function formatTimeHMSS(o) {
let hours = o.hours.toString()
if (hours.length === 1) hours = '0' + hours
let minutes = o.minutes.toString()
if (minutes.length === 1) minutes = '0' + minutes
let seconds = o.seconds.toString()
if (seconds.length === 1) seconds = '0' + seconds
let milliseconds = o.milliseconds.toString()
if (milliseconds.length === 1) milliseconds = '00' + milliseconds
if (milliseconds.length === 2) milliseconds = '0' + milliseconds
return hours + ":" + minutes + ":" +
//Example: 00:01:02,999 -- note that the SRT spec calls for a comma, not a period!
seconds + "," + milliseconds
}
}
return runExport
})));
setEvents()
defer(runExport)
})();
}
function imarunettuBilingualExportHTML() {
(function() {
var our_clq = decodeURIComponent(document.location.href.toString())
.split("=")[1].split(
":")[2]
function setEvents() {
if (typeof our_clq !== "object" && document.location.href.includes(
"timedtext.netflixstudios.com")) {
var getJSON = function(url, callback) {
var xhr = new XMLHttpRequest();
xhr.open('GET', url, true);
xhr.responseType = 'json';
xhr.onload = function() {
var status = xhr.status;
if (status === 200) {
callback(null, xhr.response);
} else {
callback(status, xhr.response);
}
};
xhr.send();
}
if (!localStorage.getItem("clq:origination:" + our_clq)) {
getJSON(
"https://timedtext.netflixstudios.com/nqapi/request/getDetails/clq:origination:" +
our_clq,
function(err, data) {
if (err !== null) {
alert("Something went wrong with error status: " +
err +
"\nIf it's 500-something, please reload the page and try again."
);
} else {
var events = '{"meta":' + JSON.stringify(data["media"]["meta"]) + ',"events":' + JSON.stringify(data["events"]) + '}';
localStorage.setItem("clq:origination:" + our_clq, events)
console.log("DEBUG: Timed text events for " +
our_clq + " saved in localStorage")
}
})
} else {
console.log("DEBUG: Timed text events for " + our_clq +
" already present in localStorage")
alert("Timed text events found in Local Storage for " +
our_clq + "without requesting from Netflix servers.\n" +
"Report me to the developer with the good news: Netflix has finaly fixed issues with writing data to local storage (i.e. saving locally to disk). Hooray!\n" +
"Proceeding to use the events found in Local Storage.");
}
}
}
function defer(method) {
if (localStorage.getItem("clq:origination:" + our_clq) ||
typeof our_clq == "object") {
setTimeout(function() {
runBilingual(our_clq);
}, 2400);
} else {
setTimeout(function() {
defer(runBilingual)
}, 350);
}
}
(function(global, factory) {
typeof exports === 'object' && typeof module !== 'undefined' ?
module.exports = factory() : typeof define === 'function' &&
define.amd ? define(factory) : (global.runBilingual = factory());
}(this, (function() {
'use strict';
function uid() {
return ([1e7] + -1e3 + -4e3 + -8e3 + -1e11)
.replace(
/[018]/g, c => (c ^ crypto.getRandomValues(
new Uint8Array(1))[0] & 15 >> c / 4)
.toString(
16))
}
function runBilingual() {
var clq_pattern = new RegExp(
'^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$',
'i');
if (!our_clq || !clq_pattern.test(our_clq)) {
alert("You must be in a started, saved Originator task!");
throw new Error
}
if (!clq_pattern.test(our_clq)) {
alert("The CLQ is invalid: " + our_clq + "\n" +
"Please report me to the developer.");
throw new Error
}
var events_and_meta = JSON.parse(localStorage.getItem("clq:origination:" +
our_clq))
try {
var events = JSON.stringify(events_and_meta["events"]).split(",");
console.log("DEBUG: Events validated.")
} catch (e) {
var events = undefined;
console.log(
"DEBUG: No events found. ")
alert("No events found. Report me to the developer.");
return null
}
try {
var meta = JSON.stringify(events_and_meta["meta"]).split(",");
console.log("DEBUG: Metadata validated.")
} catch (e) {
var meta = undefined;
console.log(
"DEBUG: No metadata found. ")
alert("No metadata found. Report me to the developer.");
}
var bilingualHtml = `
TITLEINFO (TITLEDATE)
In-cue/Out-cue
Source
Translation
`
var lsJson = localStorage["clq:origination:" + our_clq]
if (!lsJson) {
alert("Timed text events not found in Local Storage for CLQ: " +
our_clq + "\nReport me to the developer.");
throw new Error
}
var json_obj = JSON.parse(lsJson)
var src = json_obj["events"]
var fps_ = json_obj["meta"]["fps"]
var fps = fps_.split("_")[1] / 100
//From {"fps":"FPS_2500"}
var proposed_fps = prompt("Press Enter to accept framerate of " + fps +
" or enter new as 2400 or 2997:");
if (proposed_fps !== "") {
var int_fps = proposed_fps.substring(0, 2);
var decimal_fps = proposed_fps.substring(2, proposed_fps.length);
fps_ = "FPS" + "_" + int_fps + decimal_fps
fps = (int_fps + "." + decimal_fps) * 1;
}
var mid = json_obj["meta"]["movieId"]
var pid = json_obj["meta"]["packageId"]
var which_url = prompt(
"Press Enter to try the template. Enter anything to go for CC.")
var which_lang = prompt(
"DOUBLE EXPORT SRT reportsโฆ\n\nPress Enter to go for English. Enter a language code to try that (es/fr/ru/etc).",
"en");
if (which_lang == "") {
which_lang = "en"
}
if (which_url == "") {
var template_url =
"https://timedtext.netflixstudios.com/nqapi/request/timedText/" +
our_clq + '/' + pid + '/' + mid + '/' + which_lang + '/TEMPLATE/PRIMARY/' +
fps_ + '?source=ORIGINATOR'
} else {
var template_url =
"https://timedtext.netflixstudios.com/nqapi/request/timedText/" +
our_clq + '/' + pid + '/' + mid + '/' + which_lang + '/CC/PRIMARY/' + fps_ +
'?source=ARCHIVE'
}
var targetFilename = srtName(fps_ + "_TRANSLATION")
var sourceFilename = srtName(fps_ + "_SOURCE")
var backupFilename = srtName(fps_ + "_BILINGUAL_TABLE")
.replace(".srt",
".html")
var frms = 1000 / fps
async function getSourceColumnEvents() {
var result = await (await fetch(template_url))
.json();
return result;
}
async function delayedDownload() {
var result = await getSourceColumnEvents();
//download(sourceFilename, array2srt(result))
download(backupFilename, arrays2html(result, src), "html")
}
delayedDownload()
//download(targetFilename,array2srt(src))
localStorage.removeItem("clq:origination:" + our_clq)
console.log(
"DEBUG: Flushing data from localStorage.")
function srtName(suffix = "") {
var s = document.getElementsByClassName("cpe-page-menu-label")[0]
.innerText
var srtName = (s.replace(/[ ]/g, '_')
.replace(/[^a-z0-9_]/gi, '') +
suffix + ".srt"
) //This gets rid of all punctuation, spaces and non-English letters
.trim() //resulting in a name like 14545_El_Burrito_A_Breaking_Fat_Movie_FPS_2500.srt
if (!srtName) srtName = our_clq + "_" + suffix +
".srt" //Fallback measure. Useful for debugging later
return srtName
}
function frames2timecode(frames) { //frames to 00:01:02,000 format
var milliseconds = Math.round(frames * frms)
var srt_timecode = TimeConversion(milliseconds)
return srt_timecode
}
function merge_same(array) {
var merged = []
var skip_next = false
for (var i = 0; i < array.length - 1; i++) {
if (!skip_next) {
var thisEvent = array[i]
var nextEvent = array[i + 1]
if (thisEvent[5] == nextEvent[5]) {
skip_next = true
thisEvent[2] = thisEvent[2] + "\n" + nextEvent[2]
thisEvent[1] = nextEvent[1]
}
merged.push(thisEvent)
} else {
skip_next = false
}
}
return merged
}
function arrays2html(events_object, events_object2) {
var ordered_events = []
var col = "SRC"
for (var id in events_object) {
events_object[id]["column"] = col
try {
var type_fn = events_object[id]["type"]
if (type_fn === "fn") {
events_object[id]["txt"] += "";
type_fn = undefined;
}
} catch (e) {}
ordered_events.push([
events_object[id]["start"], events_object[id]["end"],
events_object[id]
["txt"],
events_object[id]
["styles"],
events_object[id]
["rgn"],
events_object[id]
["column"],
])
}
var col = "TRG"
for (var id in events_object2) {
events_object2[id]["column"] = col
ordered_events.push([
events_object2[id]["start"], events_object2[id]["end"],
events_object2[id]
["txt"],
events_object2[id]
["styles"],
events_object2[id]
["rgn"],
events_object2[id]
["column"],
])
}
ordered_events.sort(function(a, b) {
return a[0] - b[0];
}); //Array sorted by in_cues, sequentially
ordered_events = merge_same(ordered_events)
// ordered_events.sort(function (a
// , b) {
// return a[1] - b[1];
// }); //Array sorted by out_cues this time, because merge_same changes some, sequentially
var index = 0
var srt_txt = bilingualHtml;
var eol = ""
var source_content = ''
for (event of ordered_events) {
var start = frames2timecode(event[0])
var end = frames2timecode(event[1])
var startend = start + "\n" + end
try {
if (typeof event[3][0]["type"] !== "undefined") {
if (event[3][0]["type"] == "italic") {
content = italicize(content, event[3])
}
}
} catch (e) {}
if (event[5] == "SRC") {
source_content += event[2]
} else {
var tr = '
' + startend + '
' +
source_content + '
' + event[2] +
'
' + "\n";
srt_txt += tr;
tr = "";
source_content = ""
}
}
var titleinfo = document.getElementsByClassName(
"cpe-page-menu-label")[0].innerText.replace(/ "/, " โ")
.replace(/"/, "โ")
var titlelink = document.location.href
var titledate = new Date()
.toISOString()
.slice(0, 10)
srt_txt = srt_txt.replace(/TITLEINFO/g, titleinfo)
.replace(
/TITLELINK/, titlelink)
.replace(/TITLEDATE/g, titledate)
return srt_txt
}
function array2srt(events_object) {
var ordered_events = []
for (var id in events_object) {
try {
var type_fn = events_object[id]["type"]
if (type_fn === "fn") {
events_object[id]["txt"] += "";
type_fn = undefined;
}
} catch (e) {}
ordered_events.push([
events_object[id]["start"], events_object[id]["end"],
events_object[id]
["txt"],
events_object[id]
["styles"],
events_object[id]
["rgn"]
])
}
ordered_events.sort(function(a, b) {
return a[0] - b[0];
}); //Array sorted by in_cues, sequentially
var index = 0
var srt_txt = ''
for (event of ordered_events) {
index++
var start = frames2timecode(event[0])
var end = frames2timecode(event[1])
var content = event[2]
try {
if (typeof event[3][0]["type"] !== "undefined") {
if (event[3][0]["type"] == "italic") {
content = italicize(content, event[3])
}
}
} catch (e) {}
try {
if (typeof event[4] !== "undefined") {
if (event[4] == "top") {
content = "{\\an8}" + content
}
}
} catch (e) {}
try {
if (event["type"] == "fn") {
content += ''
}
} catch (e) {}
console.log(content)
var current_event = index + "\n" + start + " --> " + end + "\n" +
content + "\n"
srt_txt += current_event + "\n"
}
return srt_txt
}
function italicize(content, italics_array) {
position_offset = 0
for (var italic of italics_array) {
var position_from = italic["from"] + position_offset;
position_offset += 3
content = [content.slice(0, position_from), "", content.slice(
position_from)].join('')
var position_to = italic["to"] + position_offset;
position_offset += 4
content = [content.slice(0, position_to), "", content.slice(
position_to)].join('')
}
return content
}
function download(filename, text, type = "plain") {
var element = document.createElement('a');
element.setAttribute('href', 'data:text/' + type +
';charset=utf-8,%EF%BB%BF' + encodeURIComponent(text));
element.setAttribute('download', filename);
element.style.display = 'none';
document.body.appendChild(element);
element.click();
document.body.removeChild(element);
}
function TimeConversion(duration) {
let time = parseDuration(duration)
return formatTimeHMSS(time)
}
function parseDuration(duration) {
let remain = duration
let hours = Math.floor(remain / (1000 * 60 * 60))
remain = remain % (1000 * 60 * 60)
let minutes = Math.floor(remain / (1000 * 60))
remain = remain % (1000 * 60)
let seconds = Math.floor(remain / (1000))
remain = remain % (1000)
let milliseconds = remain
return {
hours,
minutes,
seconds,
milliseconds
}
}
function formatTimeHMSS(o) {
let hours = o.hours.toString()
if (hours.length === 1) hours = '0' + hours
let minutes = o.minutes.toString()
if (minutes.length === 1) minutes = '0' + minutes
let seconds = o.seconds.toString()
if (seconds.length === 1) seconds = '0' + seconds
let milliseconds = o.milliseconds.toString()
if (milliseconds.length === 1) milliseconds = '00' + milliseconds
if (milliseconds.length === 2) milliseconds = '0' + milliseconds
return hours + ":" + minutes + ":" +
//Example: 00:01:02,999 -- note that the SRT spec calls for a comma, not a period!
seconds + "," + milliseconds
}
}
return runBilingual
})));
setEvents()
defer(runBilingual)
})();
}
function imarunettuImportBetterSrt() {
(function() {
document.querySelectorAll(
"#appView > div > div.origination-editor > div.cpe-page-menu > div.cpe-tool-bar > div.actions > button:nth-child(2)")[0]
.click();
var fps_ = document.querySelectorAll(".info-body")[0].children[9].innerText.replace(
/[^_0-9]{5}/g, '')
.trim()
document.querySelectorAll("#appView > div > div.origination-editor > div.InfoPopup > dialog > div.info-body")[0]
.nextSibling.lastElementChild.click()
window.open('https://katzurki.github.io/nettufurikusu/ImportBetterSRT.html#' + fps_)
})()
}
function imarunettuAuxFade(type, ms, el, remove = false) {
var isIn = type === 'in',
opacity = isIn ? 0 : 1,
interval = 50,
duration = ms,
gap = interval / duration
if (isIn) {
el.style.display = 'inline'
el.style.opacity = opacity
}
function func() {
opacity = isIn ? opacity + gap : opacity - gap
el.style.opacity = opacity
if (opacity <= 0) {
remove ? el.remove() : el.style.display = 'none'
}
if (opacity <= 0 || opacity >= 1) window.clearInterval(fading)
}
var fading = window.setInterval(func, interval)
}
function imarunettuCleanup() {
var menu = document.querySelector("div.popup")
var itemButtons = document.getElementsByClassName("item-button")
var dropdown = itemButtons[itemButtons.length -
1] //this should always yield the More Actions dropdown trigger
//if(!menu) {dropdown.click();}
for (byeButton of document.getElementsByClassName("mnfkbtn")) {
imarunettuAuxFade('out', 100, byeButton, true)
}
dropdown.removeEventListener("click", imarunettuClickDropdown, {
passive: false,
capture: true
})
}
function imarunettuAddButtonsToMenu(btnid, disabled = "") {
var gonnaBeLast = false
if (typeof btnid == 'undefined') {
gonnaBeLast = true
for (btnid in imarunettuButtons) {
imarunettuAddButtonsToMenu(btnid)
}
}
if (!gonnaBeLast && !document.getElementById(btnid)) {
var btntext = imarunettuButtons[btnid]
var menu = document.querySelector("div.popup")
var button = document.createElement("button");
button.className = "icon-button mnfkbtn" + disabled
button.id = btnid
button.setAttribute("title", btntext)
button.innerHTML = ''
var span = document.createElement("span")
span.className = "label"
var taskBeenSaved = document.getElementsByClassName("bh-check_circle")[0] ? true : false;
if (btnid == "imarunettuKNP2CSV" && window.imarunettuLang !== "EN") {
btntext = btntext.replace("KNP","KNP" + " for " + window.imarunettuLang).replace("ZH-HANS","CN") //
}
if (btnid == "imarunettuRunAutoQC") {
btntext = btntext + " for " + window.imarunettuLang.replace("ZH-HANS","CN")
}
if (btnid == "imarunettuRunAutoQC" || btnid == "imarunettuDoubleExportSRT" || btnid ==
"imarunettuBilingualExportHTML") {
if (!taskBeenSaved) {
// button.classList.add("disabled");
// button.setAttribute("disabled", "");
} else {
button.classList.remove("disabled");
button.removeAttribute("disabled");
}
}
span.innerText = btntext
button.onclick = function(event) {
switch (btnid) {
case "imarunettuCleanup":
event.stopImmediatePropagation();
imarunettuCleanup();
break;
case "imarunettuBilingualExportHTML":
imarunettuBilingualExportHTML();
break;
case "imarunettuExportMP4":
imarunettuExportMP4();
break;
case "imarunettuDoubleExportSRT":
imarunettuDoubleExportSRT();
break;
case "imarunettuKNP2CSV":
imarunettuKNP2CSV();
break;
case "imarunettuExportSettings":
imarunettuExportSettings();
break;
case "imarunettuImportBetterSrt":
imarunettuImportBetterSrt();
break;
case "imarunettuRemoveClutter":
imarunettuRemoveClutter();
break;
case "imarunettuRunAutoQC":
imarunettuRunAutoQC();
break;
default:
;
}
}
button.append(span)
menu.append(button)
}
}
function imarunettuGetLang() {
if (!window.imarunettuLang) {
var taskTitle = document.getElementsByClassName("cpe-page-menu-label")[0]
.innerText
taskTitle = taskTitle.split(",")
var lang = taskTitle[taskTitle.length - 2].trim()
.toUpperCase()
window.imarunettuLang = lang
}
return window.imarunettuLang
}
} else {
alert("The imaruโขnettu script works in Originator or Lucid only.")
}
function imarunettuKNP2CSV() {
(function() {
var host = window.location.hostname
var clq = decodeURIComponent(window.location.href)
.split(":")[3]
var clq_pattern = new RegExp(
'^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$',
'i'); //CLQ is a guid v4
var lang_prompt =
"Enter a language code (for Chinese, use either zh-Hans or zh-Hant):"
var getAllLanguages = false
var oKnp = {}
var finalCsvArray = []
var currentCsvRow = []
var universalBOM = "\uFEFF";
var mid = getMovieIDFromOriginator();
if (!clq || !clq_pattern.test(clq) || !mid) {
throw new Error(alert("Something went wrong. CLQ for debug: " + clq + "/" + mid))
}
if (host == "timedtext.netflixstudios.com") { //We can run both from Lucid and from Backlot
var knpJsonLink =
"https://timedtext.netflixstudios.com/nqapi/knp/clq:origination:" + clq + "/" + mid
var taskTitle = document.getElementsByClassName("cpe-page-menu-label")[0]
.innerText
var LANG = taskTitle.split(",")
LANG = LANG[LANG.length - 2].trim()
} else if (host == "localization-lucid.netflix.com") {
var movieid = window.location.pathname.split("/")[3]
var knpJsonLink =
"https://localization-lucid.netflix.com/REST/v1/knp/getknp?appName=originator&movieid=" +
movieid + "&requestReference=clq:origination:" + clq
var taskTitle = document.querySelector("div.movie-title")
.innerText.replace("Movie: ", "")
var LANG = prompt(
lang_prompt, "zh-Hans"
) //but in Lucid, we can't guess what target language the user wants
} else {
throw new Error(alert(
"You must be either in a started task,\nor in KNP Lucid for this task."
))
}
if (LANG == "zh-CN" || LANG == "cn" || LANG == "CN") LANG = "zh-Hans" //special case
if (LANG == "en") {
do {
LANG = prompt("Target language cannot be \"en\".\n" + lang_prompt);
} while (LANG == "en")
}
if (LANG == "") {
throw new Error(alert("You must enter a language code."))
}
var getJSON = async url => {
try {
const response = await fetch(url, {
credentials: "same-origin"
}); //cookie for authorization = no cross-site fetching!
if (!response.ok)
throw new Error(alert(response.statusText));
const data = await response.json();
return data;
} catch (error) {
return error;
}
}
function nameCsv(suffix = "") {
if (suffix.length > 0) suffix = "_" + suffix
var s = taskTitle
var csvName = "KNP_" + (s.replace(/[ ]/g, '_')
.replace(
/[^a-z0-9_]/gi, '') + "_" + suffix + ".csv")
.replace(/__/gi, "_")
.trim()
if (!csvName) csvName = "KNP_" + clq + "_" + suffix + ".csv"
return csvName
} //resulting in a name like KNP_18383223_El_Burrito_A_Breaking_Fat_Movie_ru.csv
function getMovieIDFromOriginator() { //in Originator, movieId can be obtained from the info page, but it isn't loaded until clicked
document.querySelectorAll(
"#appView > div > div.origination-editor > div.cpe-page-menu > div.cpe-tool-bar > div.actions > button:nth-child(2)"
)[0].click();
movieid = document.querySelectorAll(".info-body")[0].children[2].innerText
.replace(/[^0-9]/g, '')
.trim()
document.querySelectorAll(
"#appView > div > div.origination-editor > div.InfoPopup > dialog > div.info-body")[0].nextSibling
.lastElementChild.click() //don't forget to close the popup!
return movieid
}
var csvName = nameCsv(LANG.toUpperCase())
function denull(value) {
if (!value) {
return ""
} else {
return value.replace(/^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g, '');
}
} //horrible hack since Netflix's internal logging was driving me bonkers
function arrayToCsv(data) {
return data.map(row => row
.map(String) //all to string
.map(v => v.replaceAll('"', '""')) //escape present double quotes
.map(v => `"${v}"`) //enclose the cell in double quotes
.map(v => v.replaceAll('"null"',
'""')) //catchall measure if we somehow do end up with a "null"
.join(',') //maybe tabs...
)
.join('\r\n');
}
getJSON(knpJsonLink)
.then(data => {
oKnp = data.terminology
var srcLocale = JSON.parse(oKnp[0].id).languageCode
if (typeof srcLocale.error !== "undefined") {
alert(srcLocale.error.message + "\nNo source locale detected. Trying to continue with srcLocale set to EN.")
srcLocale = "en";
}
if (typeof oKnp.error !== "undefined") {
alert(oKnp.error.message +
"\nTry exporting from the Lucid KNP page.\nIf the KNP opens but doesn't export, report me to the developer."
);
var movieid = getMovieIDFromOriginator()
window.location.href =
"https://localization-lucid.netflix.com/knp/view/" + movieid +
"?appName=originator&requestReference=clq:origination:" + clq
}
function getTermByLanguage(translations, lang = LANG) {
for (translation of translations) {
if (translation.language === lang) {
return translation.text
}
}
return ""
}
function doSort(ascending) {
var ascending = typeof ascending == 'undefined' || ascending == true;
return function(a, b) {
var ret = a[0].localeCompare(b[0]) || a[1] - b[1];
return ascending ? ret : -ret;
};
}
if (srcLocale == "en") {
var header = ["TYPE", "SOURCE (EN)", "TARGET (" + LANG.toUpperCase() + ")"]
for (const term of oKnp) {
currentCsvRow.push(term.type, term.text, getTermByLanguage(term.translations, LANG))
finalCsvArray.push(currentCsvRow);
currentCsvRow = [];
}
finalCsvArray.sort(doSort(true));
finalCsvArray.unshift(header);
} else { //we make EN translation the "source," but also include the original for reference as a separate column --for non-EN-audio tasks
var header = ["TYPE", "SOURCE (EN)", "TARGET (" + LANG.toUpperCase() + ")", "ORIGINAL (" + srcLocale.toUpperCase() + ")"]
for (const term of oKnp) {
currentCsvRow.push(term.type, getTermByLanguage(term.translations, "en"), getTermByLanguage(term.translations, LANG), term.text)
finalCsvArray.push(currentCsvRow);
currentCsvRow = [];
}
finalCsvArray.sort(doSort(true));
finalCsvArray.unshift(header);
}
let csv = arrayToCsv(finalCsvArray)
var a = window.document.createElement('a');
a.setAttribute('href', 'data:text/csv; charset=utf-8,' +
encodeURIComponent(universalBOM + csv));
a.setAttribute('download', csvName);
window.document.body.appendChild(a);
a.click();
a.remove();
console.log("File " + csvName + " with " + finalCsvArray.length +
" rows successfully downloaded!\nThis page must be reloaded to avoid namespace conflicts."
);
})
.catch(error => {
console.error(error);
});
})()
}