//BETA AUG 24 2020 //Safe Box now counts in one-line calculations //Quotes and ellipses are replaced automatically with their smart counterparts: «» for RU, “” and ‘’ for EN, and … for all languages. try {our_clq = document.location.href .split("=")[1].split(":")[2] } catch (e) { alert( "You must be in a started, saved Originator task!"); throw new Error } LANG = "EN" document.addEventListener("keypress", promptSettings, {once:true}) function promptSettings (){ LANG = prompt("Enter language code") console.log("Custom user-defined LANG is "+LANG) } (function (global, factory) { typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() : typeof define === 'function' && define.amd ? define( factory) : (global.runAutoQC = factory()); }(this, (function () { 'use strict'; function runAutoQC() { var our_clq = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : ''; document.removeEventListener("keypress",promptSettings) LANG = LANG.toUpperCase() console.log("Running with LANG: "+LANG) var line_limit = 0 var max_cps = 0 var normal_cps = 0 switch (LANG) { case "RU": line_limit = 39 normal_cps = 17 max_cps = 22.1 break; case "GR": case "FR": case "PT-BR": case "EN": default: line_limit = 42 normal_cps = 17 max_cps = 22.5 } 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!"); return null } if (!clq_pattern.test(our_clq)) { alert("The CLQ is invalid: " + our_clq + "\n" + "Please report me to the developer."); return null } var shotChanges = localStorage.getItem("shotChanges:"+our_clq) try { shotChanges = shotChanges.split(",");console.log("Shot change info found!") } catch(e) {var shotChanges = undefined; console.log("Proceeding without shot change info")} var hasSafeBox = document.getElementsByClassName("marketing-border")[0] ? true : false var reportHtml = '
DLFIXSRTTOKEN
'; var lsJson = localStorage["clq:origination:" + our_clq] if (!lsJson) { alert("Timed text events not found in localStorage\nfor CLQ: " + our_clq + "\n" + "If the CLQ is correct and" + "\n" + "\"Save to local storage\" is enabled in Settings," + "\n" + "save the task and try again."); return null } 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("Framerate set to: " + fps + " fps.\n\nEnter new value like 2997, 2500 or 23976023976023978:"); try { 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; } } catch (e) {} var frms = 1000 / fps function srtName(suffix = "", extension = ".srt") { var s = document .getElementsByClassName( "cpe-page-menu-label")[0] .innerText var srtName = (s.replace(/[ ]/g , '_') .replace(/[^a-z0-9_]/gi , '') + "_" + suffix + extension ) //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 + extension //Fallback measure. Useful for debugging later return srtName } function frames2tcf(framenumber, framerate = fps) { //frames to 00:01:02:24 format var seconds_float = framenumber / framerate var seconds_int = Math.floor(seconds_float) var seconds_frac = seconds_float - seconds_int var frames = Math.round(seconds_frac * framerate) + '' var date = new Date(0); date.setSeconds(seconds_int); try {var timeString = date.toISOString() .substr(11, 8) if (frames.length == 1) frames = "0" + frames var timecodef = timeString + ":" + frames } catch(e) {var timecodef = 'ERROR'} return timecodef } 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 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"] += ""; } } 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 parsed2srt(parsedSRT) { var srt_txt = '' for(let event of parsedSRT){ let text_event = event["id"] + "\n" + event["start"] + " --> " + event["end"] + "\n" + event["text"] + "\n\n" srt_txt += text_event } return srt_txt.trim() } 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) { var element = document .createElement('a'); element.setAttribute('href' , 'data:text/plain;charset=utf-8,' + 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 } function isBetween(distance,a,b){ var left_lim = a; var right_lim = b; if(a>b) {left_lim = b; right_lim = a} if(distance >= left_lim && distance <= right_lim) {return true} return false } function validateScE(distance, framesE, sc, distance2) { var violation = false var V = {} var css_cs1=''; var css_cs2=''; var oldTcf = frames2tcf(framesE) var scTcf = frames2tcf(sc) var distanceNext = distance2 if (isBetween(distance,5,11)) { var moveTo = 12 - distance var newFramesE = framesE+moveTo var newTcf = frames2tcf(newFramesE) var intTo = (moveTo > 0) ? "+"+moveTo : moveTo; moveTo=moveToSigned(moveTo) violation = '🎞 Move out-cue right '+moveTo+' to +12 frames after shot change
[OUT-CUE: ' + oldTcf + ' ' +intTo+ ' = ' + newTcf +']' } if ( isBetween(distance,-1,4) ) { var moveTo = -2 - distance var newFramesE = framesE+moveTo var newTcf = frames2tcf(newFramesE) var intTo = (moveTo > 0) ? "+"+moveTo : moveTo; moveTo=moveToSigned(moveTo) violation = '🎞 Move out-cue left ' + moveTo + ' to -2 frames before shot change
[OUT-CUE: ' + oldTcf + ' ' +intTo+ ' = ' + newTcf +']' } if ( isBetween(distance,-3,-7)) { var moveTo = -2 - distance var newFramesE = framesE+moveTo var newTcf = frames2tcf(newFramesE) var intTo = (moveTo > 0) ? "+"+moveTo : moveTo; moveTo=moveToSigned(moveTo) violation = '🎞 Move out-cue right ' +moveTo+ ' to -2 before shot change
[OUT-CUE: ' + oldTcf + ' ' +intTo+ ' = ' + newTcf +']' } if ( distanceNext > 2 && distanceNext < 12 && distanceNext <= Math.abs(distance)) //fixes nasty, nasty situation! { var moveTo = distanceNext - 2 var newFramesE = framesE+moveTo var newTcf = frames2tcf(newFramesE) var intTo = (moveTo > 0) ? "+"+moveTo : moveTo; moveTo=moveToSigned(moveTo) violation = '🎞 Move out-cue right '+moveTo+' to reduce frame gap from '+distanceNext+' to 2.
[OUT-CUE: ' + oldTcf + ' ' +intTo+ ' = ' + newTcf +']' } if(violation) { V = {violation:css_cs1+violation+css_cs2, start: undefined, end: frames2timecode(newFramesE) } return V; } return false } function moveToSigned(moveTo){ var frames = " frames" if(Math.abs(moveTo) == 1) {var frames = " frame"} if(moveTo <= 0) { return moveTo+frames } else {return '+'+moveTo+frames} } function validateScS(distance,framesE,sc) { var violation = false var V = {} var css_cs1=''; var css_cs2=''; var oldTcf = frames2tcf(framesE) var scTcf = frames2tcf(sc) if (isBetween(distance,1,10) ) { var moveTo = 0 - distance var newFramesE = framesE+moveTo var newTcf = frames2tcf(newFramesE) var intTo = (moveTo > 0) ? "+"+moveTo : moveTo; moveTo=moveToSigned(moveTo) violation = '🎞 Move in-cue left ' + moveTo + ' to snap it to shot change
[IN-CUE: ' + oldTcf + ' ' +intTo+ ' = ' + newTcf +']' } if (isBetween(distance,11,11) ) { var moveTo = 1 var newFramesE = framesE+moveTo var newTcf = frames2tcf(newFramesE) var intTo = (moveTo > 0) ? "+"+moveTo : moveTo; moveTo=moveToSigned(moveTo) violation = '🎞 Move in-cue right ' + moveTo + ' to put 12 frames after shot change
[IN-CUE: ' + oldTcf + ' ' +intTo+ ' = ' + newTcf +']' } if (isBetween(distance,10,10) ) { var moveTo = 2 var newFramesE = framesE+moveTo var newTcf = frames2tcf(newFramesE) var intTo = (moveTo > 0) ? "+"+moveTo : moveTo; moveTo=moveToSigned(moveTo) violation = '🎞 Move in-cue right ' + moveTo + ' to +12 frames after shot change
[IN-CUE: ' + oldTcf + ' ' +intTo+ ' = ' + newTcf +']' } if ( isBetween(distance,-11,-5) ) { var moveTo = -12 - distance var newFramesE = framesE+moveTo var newTcf = frames2tcf(newFramesE) var intTo = (moveTo > 0) ? "+"+moveTo : moveTo; moveTo=moveToSigned(moveTo) violation = '🎞 Move in-cue left ' + moveTo + ' to -12 frames before shot change
[IN-CUE: ' + oldTcf + ' ' +intTo+ ' = ' + newTcf +']' } if ( isBetween(distance,-4,-1)) { var moveTo = 0 - distance var newFramesE = framesE+moveTo var newTcf = frames2tcf(newFramesE) var intTo = (moveTo > 0) ? "+"+moveTo : moveTo; moveTo=moveToSigned(moveTo) violation = '🎞 Move in-cue right ' + moveTo + ' to snap it to shot change
[IN-CUE: ' + oldTcf + ' ' +intTo+ ' = ' + newTcf +']' } if(violation) { V = { violation:css_cs1+violation+css_cs2, start: frames2timecode(newFramesE), end: undefined } return V } return false } function curlyQuotes(text){ if( text.indexOf("'") !== -1) {if(LANG === "EN") { text = text.replace(/\'/ig, '’') } } if( text.indexOf('"') !== -1 ) { if(LANG === "EN") {text = text.replace(/(^|\s)\"/ig, "$1“").replace(/\"($|\s)/ig,"”$1") } else if(LANG === "RU") { text = text.replace(/(^|\s)\"/ig, "$1«").replace(/\"(\s|$)/ig,"»$1") } } return text } //returns distance in frames from a given frame to the nearest shot change function getNearestShotchange(frame) { if(!shotChanges) { return false } else { var smallestDist = Number.POSITIVE_INFINITY var bestCandidate = 0 for (let shotChange of shotChanges) { var distance = Math.abs(frame - shotChange) if (distance < smallestDist) { smallestDist = distance bestCandidate = shotChange } } return bestCandidate } } function leftFillNum(num, targetLength) { return num.toString() .padStart(targetLength, 0); } var SRT = parseSRT(array2srt(json_obj["events"])); var fSRT = SRT var issues = [] var events_detected = 0 for (var i=0;i") !== -1) { eventIsFn = true; _id += '🅵🅽' } eventTxt = eventTxt.replace(/(<([^>]+)>)/ig, ""); eventTxt = eventTxt.replace(/{.an.}/ig, ""); if (eventTxt.indexOf("\n") !== -1 && eventTxt.indexOf("\n-") == -1 && eventTxt.replace("\n", " ") .length <= line_limit && !eventIsFn && !hasSafeBox) { eventIssue.push("🚫 Can fit on one line"); eventFixedTxt = eventFixedTxt.replace("\n"," ").replace(" "," "); } if (eventTxt.indexOf("...") !== -1) { eventIssue.push( "⚠️ Legacy ellipsis must be one character (U+2026: …)" ); eventFixedTxt = eventFixedTxt.replace(/\.\.\./g,"…") } if (eventTxt.indexOf("!?") !== -1) { eventIssue.push( "⚠️ Unconventional punctuation detected. Did you mean to use '?!'" ) } if (eventTxt.indexOf(" - ") !== -1) { eventIssue.push( "⚠️ Instead of hyphen, consider en or em dash: – or —" ) } var eventSpecial = false var hasSpace = /\s$|^\s/; var eventTxt_lines = eventTxt.split("\n"); var nPos = eventTxt.lastIndexOf("\n"); var hasSpaceIssue = nPos && (typeof eventTxt[ nPos + 1] == "undefined") var overLineLimit = false var hasDoubleSpace = eventTxt.replace("\n", "") .split(/\s\s/) .length - 1 var hasThreeLines = typeof eventTxt_lines[ 2] !== "undefined" for (var line of eventTxt_lines) { hasSpaceIssue = (hasSpace.test(line) || hasSpaceIssue) if (line.length > line_limit) { eventSpecial = line overLineLimit = true } } if (hasDoubleSpace) { eventIssue.push( "\"⚠️ Double spaces detected, replace with single space" ); } if (hasThreeLines) { eventIssue.push( "🚫 Three lines detected in this event" ); } if (hasSpaceIssue) { if (eventTxt.length > 1) { eventIssue.push( "⚠️ One of the lines begins or ends with whitespace" ); try { eventFixedTxt = (eventFixedTxt.split("\n")[0].trim() + "\n" + eventFixedTxt.split("\n")[1].trim()).trim() } catch(e){ eventFixedTxt = eventFixedTxt.trim() } } else { eventIssue.push( "🚫 Empty event detected!"); eventSpecial = "<text cannot be empty>" } } if (overLineLimit) { eventIssue.push( "🚫 Line over max limit of " + line_limit) eventSpecial = [eventSpecial.slice(0, line_limit) , "" , eventSpecial.slice( line_limit) ].join('') } var totalLength = eventTxt.length if (delta * delta > 49) { eventIssue.push( "🚫 Maximum duration exceeded for current FPS!" ); } // var halfLength = totalLength - Contrary to official statement, // ( eventTxt.replace(/[ ]/g, '') Originator does not count // .replace(/[^a-z0-9]/gi Western punctuation and spaces // , '').length); as 0.5 characters for CPS.*/ // // totalLength += (halfLength/(-2)) var cps = (Math.round((totalLength / delta) * 100)) / 100 var round_int_cps = Math.round(cps + 0.005); var html_cps = '' if (cps > (normal_cps + 0.5) && cps < ( max_cps + 0.5)) { html_cps = '
   ' + round_int_cps + ' c/s
' } else if (cps > (max_cps + 0.5)) { eventIssue.push( "🚫 Reading speed above maximum! Either truncate text or extend timings!" ) html_cps = '
   ' + round_int_cps + ' c/s
' } else { html_cps = '
   ' + round_int_cps + ' c/s
' } var dur_tcf = frames2tcf(delta * fps) .substr(6) var eventLines = eventTxt.split("\n") if (typeof eventIssue !== "undefined" && eventIssue.length > 0) { events_detected++; var log_event = (start_tcf + " " + eventTxt_lines[0].slice(0 , line_limit) .padEnd(line_limit + 4, " ") + _id ) .padEnd(line_limit + 10, " ") if (typeof eventTxt_lines[1] !== "undefined") { log_event += "\n" + end_tcf + " " + eventTxt_lines[1] .padEnd(line_limit + 8, " ") } else { log_event += "\n" + end_tcf + " " + " ".padEnd( line_limit + 6 , " ") } // console.log('%c' + log_event // , 'background: #12343b; color: #66ff00') //console.log((+_id+"\n"+start_tcf+"\n"+end_tcf+"\n"+eventTxt).padEnd(50), 'background: #222; color: #bada55'); // console.log(('%c' + eventIssue.join("\n")) // .padEnd(50) // , 'background: #FFF; color: #FF0000; font-style: italic; border:solid 1px #000;' //) //console.log("┉".repeat(63)) var issue = { id: _id , issues: eventIssue , content: eventTxt , contentO: eventOriginalTxt , start_tcf: start_tcf , end_tcf: end_tcf , start_frames: start_frames , end_frames: end_frames , start_time: start_time , end_time: end_time , duration: delta , cps: cps , html_cps: html_cps , dur_tcf: dur_tcf , special: eventSpecial } htmlize(issue) if(eventFixedTxt !== eventOriginalTxt){ fSRT[srtId]["text"] = eventFixedTxt } } } if (!events_detected) { console.log('%c' + "No issues detected" , 'background: #12343b; color: #66ff00' ) } function htmlize(issue) { try { var original = issue.contentO.replace( '', '') .replace(/{.an.}/g, '') var lines = original } catch (e) { var lines = issue.content; } lines = lines.replace(/\n/g, '⏎'); lines = lines.replace(/\s/g, ""); lines = lines.replace(/⏎/g , "\n" ); lines = lines.split("\n"); if (issue.special) { var check_length = lines[0].length; lines[0] = issue.special if (check_length > line_limit) { var save_l0 = issue.special.substring( line_limit - 1) lines[0] = issue.special.substring(0, line_limit - 1) .replace(/\s/g, "" ) + save_l0 } } var timedTextEvent = '
' + '
' + '
' + issue .start_tcf + '' + '' + issue .end_tcf + '
' + '' + issue.id + '' + issue.html_cps + '' + issue.dur_tcf + '' + '
' + '
' + lines[0];
            if (typeof lines[1] == "undefined") {
                lines[1] = ""
            }

            timedTextEvent += '
' + lines[1] + '
' if (typeof lines[2] !== "undefined") { timedTextEvent += '
' + lines[ 2] + '
' } else { timedTextEvent += "" } for (var each_issue of issue.issues) { var issueWithImg = '
' + "" + each_issue + "
" timedTextEvent += issueWithImg + "
" } reportHtml += timedTextEvent + "
" } var withSC = '' if(shotChanges) {withSC = "_withSC"} var fixesFilename = srtName("Fixes_"+LANG+"_"+fps_+withSC) var suggestedFixesTxt = '' reportHtml = reportHtml.replace("DLFIXSRTTOKEN",suggestedFixesTxt) download(srtName("Auto_QC_Log_"+LANG+'_'+fps_+withSC, ".html"), reportHtml) } 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; }))); setTimeout(runAutoQC,2000,our_clq)