You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
wubloader/thrimbletrimmer/scripts/IO.js

426 lines
17 KiB
JavaScript

var desertBusStart = new Date("1970-01-01T00:00:00Z");
var timeFormat = 'AGO';
pageSetup = function(isEditor) {
//Get values from ThrimShim
if(isEditor && /id=/.test(document.location.search)) {
var rowId = /id=(.*)(?:&|$)/.exec(document.location.search)[1];
fetch("/thrimshim/"+rowId).then(data => data.json()).then(function (data) {
if (!data) {
alert("No video available for stream.");
return;
}
document.data = data
desertBusStart = new Date(data.bustime_start);
document.getElementById("VideoTitlePrefix").value = data.title_prefix;
document.getElementById("VideoTitle").setAttribute("maxlength", data.title_max_length);
document.getElementById("StreamName").value = data.video_channel;
document.getElementById("hiddenSubmissionID").value = data.id;
// for editor, switch to bustime since that's the default
timeFormat = 'BUSTIME';
// Apply padding - start 1min early, finish 2min late because these times are generally
// rounded down to the minute, so if something ends at "00:10" it might actually end
// at 00:10:59 so we should pad to 00:12:00.
var start = (data.event_start) ? new Date(fromTimestamp(data.event_start).getTime() - 60*1000) : null;
var end = (data.event_end) ? new Date(fromTimestamp(data.event_end).getTime() + 2*60*1000) : null;
setTimeRange(start, end);
// title and description both default to row description
document.getElementById("VideoTitle").value = data.video_title ? data.video_title : data.description;
document.getElementById("VideoDescription").value = data.video_description ? data.video_description : data.description;
// tags default to tags from sheet
document.getElementById("VideoTags").value = tags_list_to_string(data.video_tags ? data.video_tags : data.tags);
// If any edit notes, show them
if (data.notes.length > 0) {
document.getElementById("EditNotes").value = data.notes;
document.getElementById("EditNotesPane").style.display = "block";
}
// Restore advanced options. If any of these are non-default, automatically expand the advanced options pane.
setOptions('uploadLocation', data.upload_locations, data.upload_location);
document.getElementById("AllowHoles").checked = data.allow_holes;
document.getElementById("uploaderWhitelist").value = (!!data.uploader_whitelist) ? data.uploader_whitelist.join(",") : "";
if (
(
data.upload_locations.length > 0
&& data.upload_location != null
&& data.upload_location != data.upload_locations[0]
)
|| data.allow_holes
|| !!data.uploader_whitelist
) {
document.getElementById('wubloaderAdvancedInputTable').style.display = "block";
}
loadPlaylist(isEditor, data.video_start, data.video_end, data.video_quality);
});
}
else {
if (isEditor) { document.getElementById('SubmitButton').disabled = true; }
fetch("/thrimshim/defaults").then(data => data.json()).then(function (data) {
if (!data) {
alert("Editor results call failed, is thrimshim running?");
return;
}
desertBusStart = new Date(data.bustime_start);
document.getElementById("StreamName").value = data.video_channel;
if (isEditor) {
document.getElementById("VideoTitlePrefix").value = data.title_prefix;
document.getElementById("VideoTitle").setAttribute("maxlength", data.title_max_length);
setOptions('uploadLocation', data.upload_locations);
}
// Default time format changes depending on mode.
// But in both cases the default input value is 10min ago / "",
// it's just for editor we convert it before the user sees.
if (isEditor) {
toggleTimeInput('BUSTIME');
}
loadPlaylist(isEditor);
});
}
};
// Time-formatting functions
parseDuration = function(duration) {
var direction = 1;
if(duration.startsWith("-")) {
duration = duration.slice(1);
direction = -1;
}
var parts = duration.split(':');
return (parseInt(parts[0]) + (parts[1] || "0")/60 + (parts[2] || "0")/3600) * 60 * 60 * direction;
}
toBustime = function(date) {
return (date < desertBusStart ? "-":"") + videojs.formatTime(Math.abs((date - desertBusStart)/1000), 600.01).padStart(7, "0:");
};
fromBustime = function(bustime) {
return new Date(desertBusStart.getTime() + 1000 * parseDuration(bustime));
};
toTimestamp = function(date) {
return date.toISOString().substring(0, 19);
}
fromTimestamp = function(ts) {
return new Date(ts + "Z");
}
toAgo = function(date) {
now = new Date()
return (date < now ? "":"-") + videojs.formatTime(Math.abs((date - now)/1000), 600.01).padStart(7, "0:");
}
fromAgo = function(ago) {
return new Date(new Date().getTime() - 1000 * parseDuration(ago));
}
// Set the stream start/end range from a pair of Dates using the current format
// If given null, sets to blank.
setTimeRange = function(start, end) {
var toFunc = {
UTC: toTimestamp,
BUSTIME: toBustime,
AGO: toAgo,
}[timeFormat];
document.getElementById("StreamStart").value = (start) ? toFunc(start) : "";
document.getElementById("StreamEnd").value = (end) ? toFunc(end) : "";
}
// Get the current start/end range as Dates using the current format
// Returns an object containing 'start' and 'end' fields.
// If either is empty / invalid, returns null.
getTimeRange = function() {
var fromFunc = {
UTC: fromTimestamp,
BUSTIME: fromBustime,
AGO: fromAgo,
}[timeFormat];
var convert = function(value) {
if (!value) { return null; }
var date = fromFunc(value);
return (isNaN(date)) ? null : date;
};
return {
start: convert(document.getElementById("StreamStart").value),
end: convert(document.getElementById("StreamEnd").value),
};
}
getTimeRangeAsTimestamp = function() {
var range = getTimeRange();
return {
// if not null, format as timestamp
start: range.start && toTimestamp(range.start),
end: range.end && toTimestamp(range.end),
};
}
toggleHiddenPane = function(paneID) {
var pane = document.getElementById(paneID);
pane.style.display = (pane.style.display === "none") ? "block":"none";
}
toggleUltrawide = function() {
var body = document.getElementsByTagName("Body")[0];
body.classList.contains("ultrawide") ? body.classList.remove("ultrawide"):body.classList.add("ultrawide");
}
toggleTimeInput = function(toggleInput) {
// Get times using current format, then change format, then write them back
var range = getTimeRange();
timeFormat = toggleInput;
setTimeRange(range.start, range.end);
}
// For a given select input element id, add the given list of options.
// If selected is given, it should be the name of an option to select.
// Otherwise the first one is used.
setOptions = function(element, options, selected) {
if (!selected && options.length > 0) {
selected = options[0]
}
options.forEach(function(option) {
document.getElementById(element).innerHTML += '<option value="'+option+'" '+(option==selected ? 'selected':'')+'>'+option+'</option>';
});
}
buildQuery = function(params) {
return Object.keys(params).filter(key => params[key] !== null).map(key =>
encodeURIComponent(key) + '=' + encodeURIComponent(params[key])
).join('&');
}
loadPlaylist = function(isEditor, startTrim, endTrim, defaultQuality) {
var playlist = "/playlist/" + document.getElementById("StreamName").value + ".m3u8";
var range = getTimeRangeAsTimestamp();
var queryString = buildQuery(range);
// Preserve existing edit times
if (player && player.trimmingControls && player.vhs.playlists.master) {
var discontinuities = mapDiscontinuities();
if (!startTrim) {
startTrim = getRealTimeForPlayerTime(discontinuities, player.trimmingControls().options.startTrim);
if (startTrim) {startTrim = startTrim.replace('Z','');}
}
if (!endTrim) {
endTrim = getRealTimeForPlayerTime(discontinuities, player.trimmingControls().options.endTrim);
if (endTrim) {endTrim = endTrim.replace('Z','');}
}
}
setupPlayer(isEditor, playlist + '?' + queryString, startTrim, endTrim);
//Get quality levels for advanced properties / download
document.getElementById('qualityLevel').innerHTML = "";
fetch('/files/' + document.getElementById('StreamName').value).then(data => data.json()).then(function (data) {
if (!data.length) {
console.log("Could not retrieve quality levels");
return;
}
var qualityLevels = data.sort().reverse();
setOptions('qualityLevel', qualityLevels, defaultQuality);
if (!!defaultQuality && qualityLevels.length > 0 && defaultQuality != qualityLevels[0]) {
document.getElementById('wubloaderAdvancedInputTable').style.display = "block";
}
});
};
thrimbletrimmerSubmit = function(state, override_changes=false) {
document.getElementById('SubmitButton').disabled = true;
var discontinuities = mapDiscontinuities();
var start = getRealTimeForPlayerTime(discontinuities, player.trimmingControls().options.startTrim);
if (start) {start = start.replace('Z','');}
var end = getRealTimeForPlayerTime(discontinuities, player.trimmingControls().options.endTrim);
if (end) {end = end.replace('Z','');}
var wubData = {
video_start:start,
video_end:end,
video_title:document.getElementById("VideoTitle").value,
video_description:document.getElementById("VideoDescription").value,
video_tags:tags_string_to_list(document.getElementById("VideoTags").value),
allow_holes:document.getElementById('AllowHoles').checked,
upload_location:document.getElementById('uploadLocation').value,
video_channel:document.getElementById("StreamName").value,
video_quality:document.getElementById('qualityLevel').options[document.getElementById('qualityLevel').options.selectedIndex].value,
uploader_whitelist:(document.getElementById('uploaderWhitelist').value ? document.getElementById('uploaderWhitelist').value.split(','):null),
state:state,
//pass back the sheet columns to check if any have changed
sheet_name:document.data.sheet_name,
event_start:document.data.event_start,
event_end:document.data.event_end,
category:document.data.category,
description:document.data.description,
notes:document.data.notes,
tags:document.data.tags,
};
if (!!user) {
wubData.token = user.getAuthResponse().id_token
}
if (override_changes) {
wubData["override_changes"] = true;
}
console.log(wubData);
console.log(JSON.stringify(wubData));
if (!wubData.video_start) {alert("No start time set"); return;}
if (!wubData.video_end) {alert("No end time set"); return;}
//Submit to thrimshim
var rowId = /id=(.*)(?:&|$)/.exec(document.location.search)[1];
fetch("/thrimshim/" + rowId, {
method: 'POST',
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json'
},
body: JSON.stringify(wubData)
})
.then(response => response.text().then(text => {
if (!response.ok) {
var error = response.statusText + ": " + text;
if (response.status == 409) {
dialogue = text + "\nClick Ok to submit anyway; Click Cancel to return to editing"
if (confirm(dialogue)) {
thrimbletrimmerSubmit(state, true);
}
} else {
alert(error);
}
} else if (state == 'EDITED') {
// Only return to dashboard if submitted, not for save draft
setTimeout(() => { window.location.href = '/thrimbletrimmer/dashboard.html'; }, 500);
return
} else {
alert("Draft saved");
}
document.getElementById('SubmitButton').disabled = false;
}));
};
thrimbletrimmerDownload = function(isEditor) {
var range = getTimeRangeAsTimestamp();
if (isEditor) {
if(player.trimmingControls().options.startTrim >= player.trimmingControls().options.endTrim) {
alert("End Time must be greater than Start Time");
return;
}
var discontinuities = mapDiscontinuities();
range.start = getRealTimeForPlayerTime(discontinuities, player.trimmingControls().options.startTrim);
range.end = getRealTimeForPlayerTime(discontinuities, player.trimmingControls().options.endTrim);
}
var targetURL = "/cut/" + document.getElementById("StreamName").value +
"/"+document.getElementById('qualityLevel').options[document.getElementById('qualityLevel').options.selectedIndex].value+".ts" +
"?" + buildQuery({
start: range.start,
end: range.end,
// In non-editor, always use rough cut. They don't have the edit controls to do
// fine time selection anyway.
type: (isEditor) ? (
document.getElementById('DownloadType').options[document.getElementById('DownloadType').options.selectedIndex].value
) : "rough",
// Always allow holes in non-editor, accidentially including holes isn't important
allow_holes: (isEditor) ? String(document.getElementById('AllowHoles').checked) : "true",
});
console.log(targetURL);
document.getElementById('DownloadLink').href = targetURL;
document.getElementById('DownloadLink').style.display = "";
};
thrimbletrimmerManualLink = function() {
document.getElementById("ManualButton").disabled = true;
var rowId = /id=(.*)(?:&|$)/.exec(document.location.search)[1];
var upload_location = (document.getElementById("ManualYoutube").checked) ? "youtube-manual" : "manual";
var body = {link: document.getElementById("ManualLink").value, upload_location: upload_location};
if (!!user) {
body.token = user.getAuthResponse().id_token;
}
fetch("/thrimshim/manual-link/"+rowId, {
method: 'POST',
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json'
},
body: JSON.stringify(body)
})
.then(response => response.text().then(text => {
if (!response.ok) {
var error = response.statusText + ": " + text;
console.log(error);
alert(error);
document.getElementById("ManualButton").disabled = false;
} else {
alert("Manual link set to " + body.link);
setTimeout(() => { window.location.href = '/thrimbletrimmer/dashboard.html'; }, 500);
}
}));
};
thrimbletrimmerResetLink = function(force) {
var rowId = /id=(.*)(?:&|$)/.exec(document.location.search)[1];
if(force && !confirm(
'Are you sure you want to reset this event? ' +
'This will set the row back to UNEDITED and forget about any video that already may exist. ' +
'It is intended as a last-ditch command to clear a malfunctioning cutter, ' +
'or if a video needs to be re-edited and replaced. ' +
'IT IS YOUR RESPONSIBILITY TO DEAL WITH ANY VIDEO THAT MAY HAVE ALREADY BEEN UPLOADED. '
)) {
return;
}
document.getElementById("ResetButton").disabled = true;
document.getElementById("CancelButton").disabled = true;
var body = {}
if (!!user) {
body.token = user.getAuthResponse().id_token;
}
fetch("/thrimshim/reset/"+rowId + "?force=" + force, {
method: 'POST',
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json'
},
body: JSON.stringify(body)
})
.then(response => response.text().then(text => {
if (!response.ok) {
var error = response.statusText + ": " + text;
console.log(error);
alert(error);
document.getElementById("ResetButton").disabled = false;
document.getElementById("CancelButton").disabled = true;
} else {
alert("Row has been " + ((force) ? "reset" : "cancelled") +". Reloading...");
setTimeout(() => { window.location.reload(); }, 500);
}
}));
};
tags_list_to_string = function(tag_list) {
return tag_list.join(", ");
}
tags_string_to_list = function(tag_string) {
return tag_string.split(",").map(tag => tag.trim()).filter(tag => tag.length > 0);
}
round_trip_tag_string = function() {
var element = document.getElementById("VideoTags");
element.value = tags_list_to_string(
tags_string_to_list(
element.value
)
);
}