Drag maru·nettu from here to the Bookmark Bar in Chrome. Click the bookmarklet once in an open Originator task, then check out the More Actions menu. It should be populated with new entries for extra features. Using them should be self-explanatory with pop-ups where it needs information or issues a warning; of course, feel free to send me questions, concerns, and bug reports. Also beer.
All changes "made" to the Originator are local and will not persist; they will be gone as soon as you refresh or quit the page. All tools provided here can work offline. My code contains no trackers and keeps no usage logs. The latest production version unminified source code is found below and is functionally identical to the minified-code link above.
I do not recommend using Import SRT unless you know the limitations and dangers of importing SRT as raw Originator data. Please contact me if you have questions.
This is an early beta release, which means there may be annoying bugs.
This GPLv3-released tool contains no trackers, keeps no usage statistics, and does not need an Internet connection to work. There is no warranty and no guarantee of result. Technical support will be provided on a limited basis. Donations are welcome but not required or requested. No liability will be accepted from potential consequences of using the tool. By using the tool it is tacitly understood that any data, files, and other information obtained in the process is by its nature confidential and may not be shared. In all other aspects let common sense and the general spirit of GPLv3 prevail for the good of all.
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
//the property is the function name; the value is the button text
//Object.entries()[Object.keys().length-1] is the last button
var marunettuButtons = {
"marunettuCleanup": "Restore Default View",
"marunettuExportMP4": "Export Video as .MP4",
"marunettuDoubleExportSRT": "Export Source/Target .SRT",
"marunettuBilingualExportHTML": "Export Bilingual .HTML",
"marunettuImportBetterSrt": "Advanced Import SRT",
"marunettuRunAutoQC": "Run AutoQC"
}
var clq = document.location.href.split(":")[3]
//marunettu may only work in Originator, so clq is always there
if(!localStorage["lang:" + clq])
{
var LANG = marunettuGetLang()
}
//there are 3 ways to get language: from meta (best), from title, from previously saved to window.marunettuLang
//incidentally, since a window.attr does not survive reload, its presence this early indicates the bookmarklet
//has been run once already. So let's not risk it.
var marunettuClickDropdown = ''
//placing it here in scope to pass a reference to the eventListener when removing it
marunettuPopulateDropdown()
function marunettuPopulateDropdown()
{
var itemButtons = document.getElementsByClassName("item-button")
var dropdown = itemButtons[itemButtons.length -
1] //this should always yield the More Actions dropdown trigger
marunettuClickDropdown = function (event)
{
setTimeout(marunettuAddButtonsToMenu,
30) //it takes about 5ms for div.popup to initialize
}
dropdown.addEventListener('click', marunettuClickDropdown,
{
passive: false,
capture: true
})
}
function deferAddButtonUntil(condition, argument)
{ //fortunately I hope never to need this anymore ever again
if(eval(condition))
{
console.log("Condition: " + condition + " evalued TRUE after " + firedon +
"ms");
firedon = 0;
marunettuAddButtonToMenu(argument)
}
else
{
firedon++;
setTimeout(function ()
{
deferAddButtonUntil(condition, argument)
}, 1);
}
}
function marunettuExportMP4()
{
(function ()
{
function download(videolink)
{
var element = document.createElement('a');
element.setAttribute('href', videolink);
element.style.display = 'none';
document.body.appendChild(element);
element.click();
document.body.removeChild(element);
}
videolink = document.getElementsByClassName("video-holder")[0]
.children[0].getAttribute("src")
download(videolink)
})()
}
function marunettuRunAutoQC(language = window.marunettuLang)
{
//here the scopes get REALLY interesting
(function ()
{
language ? (LANG = language) : (LANG = marunettuGetLang());
var outOfTheBox
var qcMeta = {}
qcMeta.lang = LANG
if(document.location.href.includes("originator.backlot.netflix.com"))
{
var our_clq = document.location.href.split(":")[3]
if(our_clq)
{
qcMeta.clq = our_clq
}
outOfTheBox = false
console.log("DEBUG: state: inTheBox\nour_clq: from location " +
our_clq)
}
else if(typeof Window.autoQC_safe_meta !== "undefined")
{
var qcSavedMeta = JSON.parse(Window.autoQC_safe_meta)
var our_clq = qcSavedMeta.clq
console.log(
"DEBUG: state: outOfTheBox\nour_clq: from qcSavedMeta " +
our_clq)
}
else if(document.body.innerText.split("\n")[1].includes(" --> "))
{
var our_clq = document.body.innerText.split("\n")
console.log("DEBUG: state: in SRT file\nour_clq: split object " + (
typeof our_clq))
}
function setShotChanges()
{
if(typeof our_clq !== "object" && document.location.href.includes(
"originator.backlot.netflix.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("shotChanges:" + our_clq))
{
if(!outOfTheBox)
{
getJSON(
"https://originator.backlot.netflix.com/api/request/shotchanges/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
{
localStorage.setItem("shotChanges:" + our_clq,
data["frameNumbers"])
qcMeta.SC = data["frameNumbers"]
console.log("DEBUG: Shot changes for " +
our_clq + " saved in localStorage")
}
})
}
else
{
localStorage.setItem("shotChanges:" + qcSavedMeta.clq,
qcSavedMeta.SC)
}
}
else
{
console.log("DEBUG: Shot changes for " + our_clq +
" already present in localStorage")
qcMeta.SC = localStorage.getItem("shotChanges:" + our_clq)
}
}
}
function defer(method)
{
if(localStorage.getItem("shotChanges:" + our_clq) ||
typeof our_clq == "object")
{
setTimeout(function ()
{
runAutoQC(our_clq);
}, 2400);
}
else
{
setTimeout(function ()
{
defer(runAutoQC)
}, 350);
}
}
(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 uid()
{
return ([1e7] + -1e3 + -4e3 + -8e3 + -1e11).replace(
/[018]/g, c => (c ^ crypto.getRandomValues(
new Uint8Array(1))[0] & 15 >> c / 4).toString(
16))
}
function runAutoQC()
{
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(typeof our_clq !== "object")
{
our_clq = arguments.length > 0 && arguments[0] !==
undefined && clq_pattern.test(arguments[0]) ?
arguments[0] : '';
}
LANG = LANG.toUpperCase()
console.log("DEBUG: Starting AutoQC with LANG: " + LANG)
var line_limit = 0
var max_cps = 0
var normal_cps = 0
switch (LANG)
{
case "AR":
line_limit = 42
normal_cps = 20
max_cps = 26
break;
case "CN":
line_limit = 16
normal_cps = 9
max_cps = 11.7
break;
case "RU":
line_limit = 39
normal_cps = 17
max_cps = 22.1
break;
case "DE":
case "MS":
case "GR":
case "FR":
case "PT":
case "EN":
default:
line_limit = 42
normal_cps = 17
max_cps = 22.1
}
if(typeof our_clq !== "object")
{
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("DEBUG: Shot changes validated.")
}
catch (e)
{
var shotChanges = undefined;
console.log(
"DEBUG: No shot changes found, which is ok if our_clq is object: " +
(typeof our_clq))
}
var hasSafeBox = document.getElementsByClassName(
"marketing-border")[0] ? true : false
try
{
var userIp = document.getElementsByClassName(
"Watermark")[0].innerHTML;
var userName = document.getElementsByClassName(
"username")[0].textContent;
}
catch (e)
{
var userIp = "LOCAL ITERATION"
}
var reportHtml =
`<head><meta charset="utf-8">
<script>
Window.autoQC_safe_meta = METADATATOKEN
</script>
<script>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;
}</script>
<script>var z=function(q) {
var s=document.getElementsByClassName(q);
for(x of s) {
x.className===q ? (x.className=q+" of0"): (x.setAttribute("class", q))
}
}
;
</script><style>body {
background-color: #212121
}
#navbar {
background-color: #333;
position: fixed;
top: 0;
width: 100%;
transition: top 0.5s;
}
#navbar a {
float: left;
display: block;
color: white;
text-align: center;
padding: 5px;
text-decoration: none;
}
#navbar a:hover {
background-color: #ddd;
color: black;
}
#background{
position:fixed;
z-index:-999;
background:#212121;
display:block
height:100vh;
width:100vh;
color:grey;
}
.bg-text
{
font-family: Netflix-Sans;
pointer-events: none;
color:lightgrey;
text-align: center;
opacity: 0.06;
font-size:80px;
transform:rotate(320deg);
-webkit-transform:rotate(320deg);
position: relative;
overflow: visible;
user-select: none;
-webkit-user-select: none;
-khtml-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
}
.of0 {
visibility: hidden;
color: #000!important;
}
.username {
float:left;
}
.ipAddress {
float:right;
}
.cSpace {
color: #00fa00;
visibility: visible;
}
.StyledTextEditor {
font-family: Netflix-Sans;
line-height: 1.3;
margin: 2px;
white-space: nowrap;
overflow: hidden;
font-variant-ligatures: none;
position: relative;
pointer-events: none
}
.StyledTextEditor .ltr {
unicode-bidi: embed;
direction: ltr
}
.TimedTextEvent {
font-family: Netflix-Sans;
font-size: 1.3rem;
font-weight: 400;
line-height: 2rem;
margin-top: .5rem;
display: -webkit-box;
display: -moz-box;
display: -webkit-flex;
display: -ms-flexbox;
display: box;
display: flex;
-webkit-box-orient: vertical;
-moz-box-orient: vertical;
-o-box-orient: vertical;
-webkit-flex-direction: column;
-ms-flex-direction: column;
flex-direction: column;
border: 1px solid transparent;
-webkit-transition: height 250ms ease, border 150ms ease;
-moz-transition: height 250ms ease, border 150ms ease;
-o-transition: height 250ms ease, border 150ms ease;
-ms-transition: height 250ms ease, border 150ms ease;
transition: height 250ms ease, border 150ms ease
}
.TimedTextEvent .details {
padding-top: .5rem;
padding-left: 1rem;
padding-right: 1rem;
display: -webkit-box;
display: -moz-box;
display: -webkit-flex;
display: -ms-flexbox;
display: box;
display: flex;
-webkit-box-orient: horizontal;
-moz-box-orient: horizontal;
-o-box-orient: horizontal;
-webkit-flex-direction: row;
-ms-flex-direction: row;
flex-direction: row
}
.TimedTextEvent .details .timing {
-webkit-flex-shrink: 0;
flex-shrink: 0;
display: -webkit-box;
display: -moz-box;
display: -webkit-flex;
display: -ms-flexbox;
display: box;
display: flex;
-webkit-box-orient: vertical;
-moz-box-orient: vertical;
-o-box-orient: vertical;
-webkit-flex-direction: column;
-ms-flex-direction: column;
flex-direction: column;
width: 8rem
}
#titleinfo {
color: #fffff0;
font-weight: bolder;
font-style: normal;
font-family: Roboto, Netflix-Sans;
font-size: 150%;
margin-left: 30px;
margin-right: 20px;
position: relative; top: 7px; left: -5px;
width: 580px;
flex-shrink: 1;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
display: inline-block;
}
.issue {
font-size: 16;
vertical-align: middle;
font-family: Netflix-Sans;
position: relative;
top: -1px
}
.TimedTextEvent .details .timing .TimeCode {
color: rgba(255, 255, 255, .54);
padding: 0 .5rem;
border-radius: 1rem;
white-space: nowrap
}
.TimedTextEvent .details .content {
-webkit-box-flex: 1;
-moz-box-flex: 1;
-o-box-flex: 1;
-ms-box-flex: 1;
box-flex: 1;
-webkit-flex-grow: 1;
flex-grow: 1;
display: -webkit-box;
display: -moz-box;
display: -webkit-flex;
display: -ms-flexbox;
display: box;
display: flex;
-webkit-box-orient: vertical;
-moz-box-orient: vertical;
-o-box-orient: vertical;
-webkit-flex-direction: column;
-ms-flex-direction: column;
flex-direction: column;
padding-left: 2rem;
padding-bottom: .5rem;
width: 24rem
}
.TimedTextEvent .details .content .header {
display: -webkit-box;
display: -moz-box;
display: -webkit-flex;
display: -ms-flexbox;
display: box;
display: flex;
-webkit-box-orient: horizontal;
-moz-box-orient: horizontal;
-o-box-orient: horizontal;
-webkit-flex-direction: row;
-ms-flex-direction: row;
flex-direction: row
}
.TimedTextEvent .details .content .header .index {
color: rgba(255, 255, 255, .66)
}
.TimedTextEvent .details .content .header .duration {
color: rgba(255, 255, 255, .54);
margin-left: auto;
border-radius: 1rem
}
.TimedTextEvent .details .content .StyledTextEditor {
color: rgba(255, 255, 255, .87);
min-height: 3.2rem;
margin-top: .3rem
}
img {
display: table-cell;
vertical-align: middle
}
#logonetflix {
margin-left: 12px;
}
.TimedTextEvent .footer {
height: .5rem;
border-bottom: 2px solid rgba(0, 0, 0, .77);
margin-left: 1rem;
margin-right: 1rem
}
.outline {
color: #000;
position: relative;
left: 350%;
}
button {
background-color: black;
color: #39ff1a;
opacity: 0.5;
}
i {
font-style: italic!important;
font-family: serif!important
}
#feedback {
height: 0;
width: 65px;
position: fixed;
right: 0;
top: 50%;
z-index: 1000;
transform: rotate(-90deg);
-webkit-transform: rotate(-90deg);
-moz-transform: rotate(-90deg);
-o-transform: rotate(-90deg)
}
#feedback a {
display: block;
background: #06c;
height: 15px;
width: 165px;
padding: 8px 16px;
color: #fff;
font-family: Arial, sans-serif;
font-size: 17px;
font-weight: 700;
text-decoration: none;
border-bottom: solid 1px #333;
border-left: solid 1px #333;
border-right: solid 1px #fff
}
#feedback a:hover {
background: #ccc
}
</style><body style="width:70%;height:100%">
<div id="navbar">
<a href="https://partnerhelp.netflixstudios.com/hc/en-us/articles/215758617-Timed-Text-Style-Guide-General-Requirements"><img id="logonetflix" width="auto" height="32" src="https://upload.wikimedia.org/wikipedia/commons/0/08/Netflix_2015_logo.svg" title="Netflix TTSG Quality Compliance Initiative"></a>
<span id="titleinfo" title="TITLEINFOTOKEN">TITLEINFOTOKEN</span>
</div>
<div id="background">
<p class="bg-text" style="top: 0px; left: 100px;">WATERMARKPLACEHOLDER<br>NETFLIX CONFIDENTIAL</p>
<p class="bg-text" style="top: -100px; left: 540px;">NETFLIX CONFIDENTIAL<br>WATERMARKPLACEHOLDER</p>
</div>
<div id="stick" style="position:relative;left:750px;"><button type="button" class="f" onclick="alert(Window.autoQC_safe_meta)">🛈</button><button type="button" class="f" onclick="z(\'cSpace\')" title="Highlight whitespace">🟩</button><button title="Display minor issues" type="button" class="f" onclick="z(\'minor_issue\') ">⚠️</button><button title="Display major issues" type="button" class="f" onclick="z(\'major_issue\')">🚫</button><button title="Shot-change suggestions" type="button" onclick="z(\'shot_change_issue\') ">🎞</button>DLFIXSRTTOKEN<span style="position: relative; top: 3px; left: 165px; font-family: monospace; color: #fdfdf2; opacity: 0.33;">Linguist: LINGUISTNAME</div>`
if(document.location.href.includes(
"originator.backlot.netflix.com"))
{
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
qcMeta.fps = fps
qcMeta.fps_ = fps_
}
else if(outOfTheBox)
{
var fps = qcSavedMeta.fps
var fps_ = qcSavedMeta.fps_
}
else if(typeof our_clq == "object")
{
var srt_fps = prompt(
"What FPS does this SRT file likely have?\n2397, 2398, 2400, 2500, 2997 are the most likely"
)
if(srt_fps.length < 4)
{
var fps = 24.00;
var fps_ = "FPS_2400"
}
else
{
var fps_ = "FPS_" + (srt_fps.replace(/[^\d]/g, "")
.replace(/(^....)(.+$)/, "$1"))
var fps = fps_.split("_")[1] / 100
}
alert("Continuing with " + fps_ +
" and real fps set to " + fps)
}
var majTotal = 0
var minTotal = 0
var scTotal = 0
var evTotal = 0
var afTotal = 0
if(typeof our_clq !== "object")
{
var proposed_fps = prompt("Framerate set to: " +
fps +
" fps.\nEnter custom FPS or press Enter to keep current"
);
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;
alert("Continuing with " + fps_ +
" and real fps set to " + fps);
}
}
var frms = 1000 / fps
if(document.location.href.includes(
"originator.backlot.netflix.com"))
{
var titleInfo = document.getElementsByClassName(
"cpe-page-menu-label")[0].innerText.replace(
/ "/g, ' “').replace(/"/g, "”")
qcMeta.titleinfo = titleInfo
}
else if(typeof our_clq !== "object")
{
var titleInfo = qcSavedMeta.titleinfo
}
else
{
var titleInfo = document.location.href.replace(
/(.+?)([^/]+$)/g, "$2")
}
function srtName(suffix = "", extension = ".srt")
{
var s = titleInfo
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
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"] += "<b></b>";
}
}
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 += '<b></b>'
}
}
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 nbspRegex = new RegExp(String.fromCharCode(160),
"gi");
var srt_txt = ''
for(let event of parsedSRT)
{
var cleanTxt = event["text"].replace(nbspRegex,
" ").trim().replace(/[ ][ ]/gi, " ")
if(cleanTxt.length < 2)
{
cleanTxt = "\n"
}
let text_event = event["id"] + "\n" + event[
"start"] + " --> " + event["end"] + "\n" +
cleanTxt + "\n\n"
srt_txt += text_event
}
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), "<i>",
content.slice(position_from)
].join('')
var position_to = italic["to"] + position_offset;
position_offset += 4
content = [content.slice(0, position_to), "</i>",
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 = '<span class="shot_change_issue">';
var css_cs2 = '</span>';
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<br><font style="color: #7DFDFE; font-family: monospace">[OUT-CUE: ' +
oldTcf + ' ' + intTo + ' = ' + newTcf +
']</font>'
}
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<br><font style="color: #7DFDFE; font-family: monospace">[OUT-CUE: ' +
oldTcf + ' ' + intTo + ' = ' + newTcf +
']</font>'
}
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<br><font style="color: #7DFDFE; font-family: monospace">[OUT-CUE: ' +
oldTcf + ' ' + intTo + ' = ' + newTcf +
']</font>'
}
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.<br><font style="color: #7DFDFE; font-family: monospace">[OUT-CUE: ' +
oldTcf + ' ' + intTo + ' = ' + newTcf +
']</font>'
}
if(cpsFixable)
{
var moveTo = framesToFixCPS
var newFramesE = framesE + framesToFixCPS
var newTcf = frames2tcf(newFramesE)
var intTo = (moveTo > 0) ? "+" + moveTo : moveTo;
moveTo = moveToSigned(moveTo)
violation = '📖 Move out-cue right ' + moveTo +
' to reduce CPS to 17.<br><font style="color: #7DDDFF; font-family: monospace">[OUT-CUE: ' +
oldTcf + ' ' + intTo + ' = ' + newTcf +
']</font>'
cpsFixable = false
}
if(cpsAttemptable)
{
var moveTo = framesToFixCPS
var newFramesE = framesE + framesToFixCPS
var newTcf = frames2tcf(newFramesE)
var intTo = (moveTo > 0) ? "+" + moveTo : moveTo;
moveTo = moveToSigned(moveTo)
cpsAttemptable = false
violation = '📖 Move out-cue right ' + moveTo +
' to reduce CPS to ' + bestWorstCPS +
'.<br><font style="color: #7DDDFF; font-family: monospace">[OUT-CUE: ' +
oldTcf + ' ' + intTo + ' = ' + newTcf +
']</font>'
cpsAttemptable = false
}
if(Math.abs(distanceNext) < 2)
{
var moveTo = (2 + Math.abs(distanceNext)) * (-1)
var newFramesE = framesE + moveTo
var newTcf = frames2tcf(newFramesE)
var intTo = (moveTo > 0) ? "+" + moveTo : moveTo;
moveTo = moveToSigned(moveTo)
violation = '🎞 Move out-cue ' + moveTo +
' to -2 before next subtitle event<br><font style="color: #7DFDFE; font-family: monospace">[OUT-CUE: ' +
oldTcf + ' ' + intTo + ' = ' + newTcf +
']</font>'
}
if(violation)
{
scTotal++
afTotal++
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 = '<span class="shot_change_issue">';
var css_cs2 = '</span>';
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<br><font style="color: #7FFFD4; font-family: monospace">[IN-CUE: ' +
oldTcf + ' ' + intTo + ' = ' + newTcf +
']</font>'
}
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<br><font style="color: #7FFFD4; font-family: monospace">[IN-CUE: ' +
oldTcf + ' ' + intTo + ' = ' + newTcf +
']</font>'
}
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<br><font style="color: #7FFFD4; font-family: monospace">[IN-CUE: ' +
oldTcf + ' ' + intTo + ' = ' + newTcf +
']</font>'
}
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<br><font style="color: #7FFFD4; font-family: monospace">[IN-CUE: ' +
oldTcf + ' ' + intTo + ' = ' + newTcf +
']</font>'
}
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<br><font style="color: #7FFFD4; font-family: monospace">[IN-CUE: ' +
oldTcf + ' ' + intTo + ' = ' + newTcf +
']</font>'
}
if(violation)
{
scTotal++
afTotal++
V = {
violation: css_cs1 + violation + css_cs2,
start: frames2timecode(newFramesE),
end: undefined
}
return V
}
return false
}
function getNextShotchange(frame)
{
if(!shotChanges)
{
return false
}
else
{
var smallestDist = Number.POSITIVE_INFINITY
var bestCandidate = 0
for(let shotChange of shotChanges)
{
var distance = shotChange - frame
if(distance < smallestDist && shotChange >=
frame)
{
smallestDist = distance
bestCandidate = shotChange * 1
}
}
return bestCandidate
}
}
//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 * 1
}
}
return bestCandidate
}
}
function leftFillNum(num, targetLength)
{
return num.toString().padStart(targetLength, 0);
}
if(src && !outOfTheBox)
{
console.log(
"DEBUG: In Originator, working with json_src")
var SRT = array2srt(src)
SRT = parseSRT(SRT)
}
else if(typeof our_clq !== "object" && outOfTheBox)
{
console.log(
"DEBUG: In AutoQC, working with encoded fixed")
var SRT = decodeURIComponent(document.getElementById(
"FixedTaskSRT").href).replace(
"data:text/plain;charset=utf-8,", "")
SRT = parseSRT(SRT)
}
else if(typeof our_clq == "object")
{
console.log(
"DEBUG: In SRT file, working with document body"
)
var SRT = our_clq.join("\n").trim()
SRT = parseSRT(SRT)
}
var fSRT = SRT
var issues = []
var events_detected = 0
for(var i = 0; i < SRT.length; i++)
{
var end_current = SRT[i]["end"]
if(typeof (SRT[i + 1]) !== "undefined")
{
var start_next = SRT[i + 1]["start"]
}
else
{
var start_next = 23976023976
}
var distance = Math.round(
(start_next - end_current) * fps)
SRT[i]["frames_to_next"] = distance
}
for(event of SRT)
{
var start_time = event["start"]
var end_time = event["end"]
var delta = Math.round((end_time - start_time) *
1000) / 1000
var delta_fps = Math.floor(delta) + ":" + Math.round(
(delta - Math.floor(delta)) * fps)
var start_frames = Math.round(start_time * fps)
var end_frames = Math.round(end_time * fps)
var start_tcf = frames2tcf(start_frames)
var end_tcf = frames2tcf(end_frames)
var id = event["id"]
var srtId = id - 1;
var distanceNextEvent = event["frames_to_next"]
var _id = leftFillNum(id, 4)
var eventIssue = []
var eventTxt = event["text"]
var bareTxt = eventTxt.replace(/\n/g, "‣").replace(
/<(|\/)(i|b)>/g, "").replace(/\{.an.\}/g, "")
var totalLength = bareTxt.length - 1
var cps = (Math.round((totalLength / delta) *
1000)) / 1000
var duration_for_normal_cps = (Math.round((
totalLength / normal_cps) * 1000)) / 1000
var framesToFixCPS = Math.floor(((((
duration_for_normal_cps - delta) *
1000) * fps) / 1000) + 1)
var round_int_cps = Math.floor(cps + 1);
var html_cps = ''
var eventFixedTxt = event["text"]
var eventOriginalTxt = event["text"]
var eventIsFn = (eventTxt.indexOf("<b></b>") !== -1)
var eventIsFnInCaps = eventIsFn && (LANG !== 'CN') &&
(eventTxt.toUpperCase().trim() === eventTxt
.trim())
var scS = getNearestShotchange(start_frames)
var scE = getNearestShotchange(end_frames)
var scN = getNextShotchange(end_frames)
var scND = scN - end_frames
var scSD = start_frames - scS
var scED = end_frames - scE
var cpsFixable = false
var cpsAttemptable = false
var bestWorstCPS = false
if(cps > normal_cps && cps < max_cps && scND -
framesToFixCPS > 11 && distanceNextEvent -
framesToFixCPS > 11 && framesToFixCPS < 12 && !
eventIsFnInCaps)
{
cpsFixable = true
//var log = "EVENT: "+id+"\nCPS: "+cps+"\tDuration: "+delta_fps+"\nTAR: //"+normal_cps+".00\tWant_dur: "+duration_for_normal_cps+
//"\nEnd_frame: "+end_frames+"\nNex_ev in: "+distanceNextEvent+"\nNex_sc in: "
//+scND+"\nADD_FRAM: "+framesToFixCPS+"\nTotal length: "
//+totalLength+"\nEND_TIMECODE_1: "+frames2tcf(end_frames+framesToFixCPS)+"\nEND_TIMECODE_2: "+
//frames2tcf(Math.round((start_time + duration_for_normal_cps)*fps))
}
if(cps >= max_cps && scND - 12 > 11 &&
distanceNextEvent - 12 > 11 && !eventIsFnInCaps)
{
cpsAttemptable = true
framesToFixCPS = 12
var plusDelta = 12 / fps
bestWorstCPS = Math.round((totalLength / (delta +
plusDelta) * 1000)) / 1000
}
var scStcf = frames2tcf(scS)
var scEtcf = frames2tcf(scE)
var violS = validateScS(scSD, start_frames, scS)
var violE = validateScE(scED, end_frames, scE,
distanceNextEvent, cps)
if(violS)
{
eventIssue.push(violS["violation"])
fSRT[srtId]["start"] = violS["start"];
}
else
{
fSRT[srtId]["start"] = frames2timecode(Math.round(
fSRT[srtId]
["start"] * fps))
}
if(violE)
{
eventIssue.push(violE["violation"])
fSRT[srtId]["end"] = violE["end"];
}
else
{
fSRT[srtId]["end"] = frames2timecode(Math.round(
fSRT[srtId]
["end"] * fps))
}
if(eventTxt.indexOf("<b></b>") !== -1)
{
eventIsFn = true;
_id +=
'<span class="outline" style="color: purple; position: relative; top: 0px; left: 137px;">🅵🅽</span>'
}
eventTxt = eventTxt.replace(/(<([^>]+)>)/ig, "");
eventTxt = eventTxt.replace(/{.an.}/ig, "");
if(eventTxt.indexOf("\n") !== -1 && eventTxt.indexOf(
"\n-") == -1 && eventTxt.replace("\n", " ")
.length <= line_limit && !eventIsFnInCaps && !
hasSafeBox)
{
eventIssue.push(
"<span class=\"major_issue\">🚫 Can fit on one line (" +
line_limit + " for " + LANG + ")")
majTotal++
eventFixedTxt = eventFixedTxt.replace(/\n/g, " ")
eventFixedTxt = eventFixedTxt.replace(/ /gi, " ")
.replace(/[ ][ ]/g, ' ');
afTotal++
}
if(eventTxt.indexOf("...") !== -1)
{
eventIssue.push(
"<span class=\"minor_issue\">⚠️ Legacy ellipsis detected, replace with U+2026: …"
);
minTotal++
eventFixedTxt = eventFixedTxt.replace(/\.\.\./g,
"…")
afTotal++
}
if(eventTxt.indexOf("'") !== -1 || eventTxt.indexOf(
'"') !== -1)
{
switch (LANG)
{
case "EN":
eventIssue.push(
"<span class=\"minor_issue\">⚠️ Smart quotes recommended for EN: <b>“ ” ’</b>"
);
minTotal++
eventFixedTxt = eventFixedTxt.replace(
/"( |\n|$)/g, "”$1")
eventFixedTxt = eventFixedTxt.replace(/"/g,
"“")
eventFixedTxt = eventFixedTxt.replace(/'/g,
"’")
afTotal++
break;
case "RU":
eventIssue.push(
"<span class=\"minor_issue\">⚠️ Proper Russian uses chevrons: <b>« »</b>"
);
minTotal++
eventFixedTxt = eventFixedTxt.replace(
/"( |\n|$)/g, "»$1")
eventFixedTxt = eventFixedTxt.replace(/"/g,
"«")
afTotal++
break;
}
}
if(eventTxt.indexOf("!?") !== -1)
{
eventIssue.push(
"<span class=\"minor_issue\">⚠️ Unconventional punctuation detected. Did you mean to use '?!'"
)
minTotal++
eventFixedTxt = eventFixedTxt.replace(/[!][?]/g,
"?!")
afTotal++
}
if(eventTxt.replace(/\.\.\./g, "…").search(
/(([,.:])(!?([^\d\s\.])))/) !== -1)
{
eventIssue.push(
"<span class=\"minor_issue\">⚠️ No space after a punctuation mark. Is this deliberate?"
)
minTotal++
eventFixedTxt = eventFixedTxt.replace(
/(([,.:])(!?([^\d\s\.])))/gi, "$2 $4")
.replace(/\. \./gi, "..")
afTotal++
}
if(eventTxt.indexOf(" - ") !== -1)
{
eventIssue.push(
"<span class=\"minor_issue\">⚠️ Instead of hyphen, consider en or em dash: – or —"
)
minTotal++
}
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(
"<span class=\"minor_issue>\"<span class=\"minor_issue\">⚠️ Double spaces detected, replace with single space</span>"
);
minTotal++
}
if(hasThreeLines)
{
eventIssue.push(
"<span class=\"major_issue\">🚫 Three lines detected in this event"
);
majTotal++
}
if(hasSpaceIssue)
{
if(eventTxt.length > 1)
{
eventIssue.push(
"<span class=\"minor_issue\">⚠️ One of the lines begins or ends with whitespace"
);
try
{
eventFixedTxt = eventFixedTxt.replace(
/( |)\n( |)/, "‣").replace(
/[ ]{2,}/gi, " ").trim().replace("‣",
"\n")
eventFixedTxt = eventFixedTxt.replace(
' <b></b>', '<b></b>').replace(
' </i><b></b>', '</i><b></b>')
eventFixedTxt = eventFixedTxt.replace(
/( )(<(|\/)i>)(\n)/g, "$2$4")
eventFixedTxt = eventFixedTxt.replace(
'<i></i>', '')
afTotal++
}
catch (e)
{
eventFixedTxt = eventFixedTxt.replace(
/( |)\n( |)/, "×").replace(/ \s+/gi,
" ").trim().replace("×", "\n")
afTotal++
}
}
else
{
eventIssue.push(
"<span class=\"major_issue\">🚫 Empty event detected!"
);
majTotal++
eventSpecial =
"<span style='color:red;'><text cannot be empty></span>"
}
}
if(overLineLimit)
{
eventIssue.push(
"<span class=\"major_issue\">🚫 Line over max limit of " +
line_limit + " for " + LANG)
majTotal++
eventSpecial = [
eventSpecial.slice(0, line_limit),
"<span style='color: red; text-overflow: fade;'>",
eventSpecial.slice(line_limit)
].join('')
}
if(delta * delta > 49)
{
eventIssue.push(
"<span class=\"major_issue\">🚫 Maximum duration exceeded for current FPS!"
)
majTotal++
}
// 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))
if(cps > normal_cps && cps < max_cps)
{
html_cps =
'<div><span style="color: gold;">  ' +
round_int_cps + ' c/s</span></div>'
}
else if(cps > (max_cps))
{
eventIssue.push(
"<span class=\"major_issue\">🚫 Reading speed above maximum! Either truncate text or extend timings!"
)
majTotal++
html_cps =
'<div><span style="color:red;">  ' +
round_int_cps + ' c/s</span></div>'
}
else
{
html_cps =
'<div><span style="color:silver;">  ' +
round_int_cps + ' c/s</span></div>'
}
var dur_tcf = frames2tcf(delta * fps).substr(6)
var eventLines = eventTxt.trim().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('<b></b>',
'').replace(/{.an.}/g, '')
var lines = original
}
catch (e)
{
var lines = issue.content;
}
lines = lines.replace(/\n/g, '⏎');
lines = lines.replace(/\s/g,
"<span class='cSpace'>⎵</span>");
lines = lines.replace(/⏎/g,
"<span class='cSpace' style=\"color: #00fa00;\">⏎</span>\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,
"<span class='cSpace'>⎵</span>") +
save_l0
}
}
var timedTextEvent =
'<div class="TimedTextEvent" style="width: 700px; max-width: 100%;">' +
'<div class="details">' +
'<div class="timing"><span class="TimeCode">' +
issue.start_tcf + '</span>' +
'<span class="TimeCode">' + issue.end_tcf +
'</span></div><div class="content"><div class="header">' +
'<span class="index">' + issue.id + '</span>' +
issue.html_cps +
'<span class="duration"><span class="TimeCode">' +
issue.dur_tcf + '</span></span>' +
'</div><div class="StyledTextEditor" dir="ltr" style="position:relative; top:-20px;">' +
'<pre><span>' + lines[0];
if(typeof lines[1] == "undefined")
{
lines[1] = ""
}
timedTextEvent += '</span><div><span>' + lines[1] +
'</span></div>'
if(typeof lines[2] !== "undefined")
{
timedTextEvent +=
'<div><span style="color:red;">' + lines[2] +
'</span></div></pre>'
}
else
{
timedTextEvent += "</pre>"
}
for(var each_issue of issue.issues)
{
var issueWithImg =
'<div style="vertical-align:middle;float:left;">' +
"<span class='issue'>" + each_issue +
"</span></span></div>"
timedTextEvent += issueWithImg + "<br />"
}
reportHtml += timedTextEvent +
"</div></div></div></div></div>"
evTotal++
}
var withSC = ''
if(shotChanges)
{
withSC = "_withShotChanges"
}
var fixesFilename = srtName("Fixes_" + LANG + "_" +
fps_ + withSC)
var fixedSrtFull = parsed2srt(fSRT)
fixedSrtFull = fixedSrtFull.replace(/ <b><\/b>/g,
'<b></b>')
var suggestedFixesTxt =
'<a id="FixedTaskSRT" download="' + fixesFilename +
'" href="data:text/plain;charset=utf-8,' +
encodeURIComponent(fixedSrtFull) +
'"><button>✎</button></a>'
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]
}
localStorage.removeItem("shotChanges:" + qcMeta.clq)
var qcMetaStr = "`" + JSON.stringify(qcMeta, null, 2) +
"`"
if(typeof userName == "undefined")
{
userName = getUnnamed()
}
reportHtml = reportHtml.replace("DLFIXSRTTOKEN",
suggestedFixesTxt).replace(
/WATERMARKPLACEHOLDER/g, userIp).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)
}
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, '<').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;
})));
setShotChanges()
defer(runAutoQC)
})()
}
function marunettuDoubleExportSRT()
{
(function ()
{
var our_clq = document.location.href.toString().split("=")[1].split(
":")[2]
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 bilingualHtml = `<head>
<script>
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;
}
</script>
<style>#navbar {
background-color: #333;
top: 0;
position: fixed;
width: 80%;
left: 10%;
transition: top 0.5s;
margin-left: auto;
margin-right: auto;
opacity: 0.7;
}
#navbar a {
float: left;
display: block;
color: white;
text-align: center;
padding: 5px;
text-decoration: none;
}
#navbar a:hover {
background-color: #ddd;
color: black;
}
body {
background: #212121;
color: grey;
}
table {
width: 80%;
table-layout: fixed;
text-align: center;
margin-left: auto;
margin-right: auto;
margin-top: 60px;
border-collapse: collapse;
}
thead,
tbody,
td,
tr
{
display: block;
font-family: monospace;
}
tr {
background-image: linear-gradient(to right, darkgrey 30%, rgba(255,255,255,0) 0%);
background-position: bottom;
background-size: 3px 1px;
background-repeat: repeat-x;
}
tr:after {
content: ' ';
display: block;
visibility: hidden;
clear: both;
border-bottom: 1px solid #ccc;
}
#titleinfo {
color: #fffff0;
font-weight: bolder;
font-style: normal;
font-family: Roboto;
font-size: 150%;
margin-left: 55px;
margin-top: 8px;
flex-shrink: 1;
width: 800px;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
display: inline-block;
}
thead th {
height: 30px;
/*text-align: left;*/
}
thead {
/* fallback */
}
tbody td,
thead th {
width: 33%;
float: left;
}
tr:hover {
color: darkgrey;
}
</style>
<head><body>
<div id="navbar">
<a href="TITLELINK"><img id="logonetflix" width="auto" height="32" src="https://upload.wikimedia.org/wikipedia/commons/0/08/Netflix_2015_logo.svg" title="Backup made on TITLEDATE"></a>
<span id="titleinfo" title="TITLEINFO">TITLEINFO (TITLEDATE)</span></div>
<table>
<thead>
<tr>
<th><b><u>In-cue/Out-cue</b></u></th>
<th><b><u>Source</b></u></th>
<th><b><u>Translation</b></u></th>
</tr>
</thead>
<tbody>
`
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.");
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."
)
if(which_url == "")
{
var template_url =
"https://originator.backlot.netflix.com/api/request/timedText/" +
our_clq + '/' + pid + '/' + mid + '/en/TEMPLATE/PRIMARY/' +
fps_ + '?source=ORIGINATOR'
}
else
{
var template_url =
"https://originator.backlot.netflix.com/api/request/timedText/" +
our_clq + '/' + pid + '/' + mid + '/en/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))
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(i = 0; i < array.length - 1; i++)
{
if(!skip_next)
{
thisEvent = array[i]
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"] += "<b></b>";
type_fn = undefined;
delete(type_fn)
}
}
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 = '<tr><td><pre>' + startend + '</pre></td><td><pre>' +
source_content + '</pre></td><td><pre>' + event[2] +
'</pre></td></tr>' + "\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"] += "<b></b>";
type_fn = undefined;
delete(type_fn)
}
}
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 += '<b></b>'
}
}
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), "<i>", content.slice(
position_from)].join('')
var position_to = italic["to"] + position_offset;
position_offset += 4
content = [content.slice(0, position_to), "</i>", 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
}
})();
}
function marunettuBilingualExportHTML()
{
(function ()
{
var our_clq = document.location.href.toString().split("=")[1].split(
":")[2]
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 bilingualHtml = `<head>
<script>
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;
}
</script>
<style>#navbar {
background-color: #333;
top: 0;
position: fixed;
width: 80%;
left: 10%;
transition: top 0.5s;
margin-left: auto;
margin-right: auto;
opacity: 0.7;
}
#navbar a {
float: left;
display: block;
color: white;
text-align: center;
padding: 5px;
text-decoration: none;
}
#navbar a:hover {
background-color: #ddd;
color: black;
}
body {
background: #212121;
color: grey;
}
table {
width: 80%;
table-layout: fixed;
text-align: center;
margin-left: auto;
margin-right: auto;
margin-top: 60px;
border-collapse: collapse;
}
thead,
tbody,
td,
tr
{
display: block;
font-family: monospace;
}
tr {
background-image: linear-gradient(to right, darkgrey 30%, rgba(255,255,255,0) 0%);
background-position: bottom;
background-size: 3px 1px;
background-repeat: repeat-x;
}
tr:after {
content: ' ';
display: block;
visibility: hidden;
clear: both;
border-bottom: 1px solid #ccc;
}
#titleinfo {
color: #fffff0;
font-weight: bolder;
font-style: normal;
font-family: Roboto;
font-size: 150%;
margin-left: 55px;
margin-top: 8px;
flex-shrink: 1;
width: 800px;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
display: inline-block;
}
thead th {
height: 30px;
/*text-align: left;*/
}
thead {
}
tbody td,
thead th {
width: 33%;
float: left;
}
tr:hover {
color: darkgrey;
}
</style>
<head><body>
<div id="navbar">
<a href="TITLELINK"><img id="logonetflix" width="auto" height="32" src="https://upload.wikimedia.org/wikipedia/commons/0/08/Netflix_2015_logo.svg" title="Backup made on TITLEDATE"></a>
<span id="titleinfo" title="TITLEINFO">TITLEINFO (TITLEDATE)</span></div>
<table>
<thead>
<tr>
<th><b><u>In-cue/Out-cue</b></u></th>
<th><b><u>Source</b></u></th>
<th><b><u>Translation</b></u></th>
</tr>
</thead>
<tbody>
`
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.");
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.")
if(which_url == "")
{
var template_url =
"https://originator.backlot.netflix.com/api/request/timedText/" +
our_clq + '/' + pid + '/' + mid + '/en/TEMPLATE/PRIMARY/' +
fps_ + '?source=ORIGINATOR'
}
else
{
var template_url =
"https://originator.backlot.netflix.com/api/request/timedText/" +
our_clq + '/' + pid + '/' + mid + '/en/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))
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(i = 0; i < array.length - 1; i++)
{
if(!skip_next)
{
thisEvent = array[i]
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"] += "<b></b>";
type_fn = undefined;
delete(type_fn)
}
}
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 = '<tr><td><pre>' + startend + '</pre></td><td><pre>' +
source_content + '</pre></td><td><pre>' + event[2] +
'</pre></td></tr>' + "\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"] += "<b></b>";
type_fn = undefined;
delete(type_fn)
}
}
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 += '<b></b>'
}
}
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), "<i>", content.slice(
position_from)].join('')
var position_to = italic["to"] + position_offset;
position_offset += 4
content = [content.slice(0, position_to), "</i>", 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
}
})();
}
function marunettuImportBetterSrt()
{
alert(
"DISABLED FOR NOW.\nPossible fixes: Send everybody to Github?\nStore and open in any report?\nCompletely replace current window? hmm.");
(function () {})()
}
function marunettuAuxFade(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 marunettuCleanup()
{
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"))
{
marunettuAuxFade('out', 100, byeButton, true)
}
dropdown.removeEventListener("click", marunettuClickDropdown,
{
passive: false,
capture: true
})
}
function marunettuAddButtonsToMenu(btnid, disabled = "")
{
var gonnaBeLast = false
if(typeof btnid == 'undefined')
{
gonnaBeLast = true
for(btnid in marunettuButtons)
{
marunettuAddButtonsToMenu(btnid)
}
}
if(!gonnaBeLast && !document.getElementById(btnid))
{
var btntext = marunettuButtons[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 = '<i class="bhicons"></i>'
var span = document.createElement("span")
span.className = "label"
if(btnid == "marunettuRunAutoQC")
{
btntext = btntext + " for " + window.marunettuLang
}
span.innerText = btntext
button.onclick = function (event)
{
if(btnid == "marunettuCleanup")
{
event.stopImmediatePropagation();
marunettuCleanup();
}
else
{
var funktion = btnid + '();'
eval(btnid + "" +
"();") //Hmm, I'm gonna try storing the principal JS code of the bookmarklets as text strings properties of one big object
}
}
button.append(span)
menu.append(button)
}
}
function marunettuGetLang()
{
if(!window.marunettuLang)
{
var taskTitle = document.getElementsByClassName("cpe-page-menu-label")[0]
.innerText
taskTitle = taskTitle.split(",")
var lang = taskTitle[taskTitle.length - 2].trim().toUpperCase()
window.marunettuLang = lang
}
return window.marunettuLang
}