|
|
|
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") {
|
|
|
|
alert(`Edit submitted for video from ${start} to ${end}`);
|
|
|
|
} 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));
|
|
|
|
};
|