diff --git a/.prettierrc.yaml b/.prettierrc.yaml new file mode 100644 index 0000000..2c4b8e9 --- /dev/null +++ b/.prettierrc.yaml @@ -0,0 +1,11 @@ +# Config for auto-formatting of our JS files using prettier +# https://prettier.io/docs/en/configuration.html + +# This is NOT a length limit but a hint to the auto-formatter for how long lines should try to be. +printWidth: 100 +# Use tabs for indentation, spaces for alignment +useTabs: true +# do {foo: bar} not { foo: bar }. personal preference. +bracketSpacing: false +# prefer "arg => body" over "(arg) => body" when possible +arrowParens: avoid diff --git a/thrimbletrimmer/scripts/IO.js b/thrimbletrimmer/scripts/IO.js index 3ce0eaa..7b08474 100644 --- a/thrimbletrimmer/scripts/IO.js +++ b/thrimbletrimmer/scripts/IO.js @@ -1,423 +1,515 @@ 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); - }); - - } +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:"); +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)); +fromBustime = function (bustime) { + return new Date(desertBusStart.getTime() + 1000 * parseDuration(bustime)); }; -toTimestamp = function(date) { - return date.toISOString().substring(0, 19); -} +toTimestamp = function (date) { + return date.toISOString().substring(0, 19); +}; -fromTimestamp = function(ts) { - return new Date(ts + "Z"); -} +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:"); -} +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)); -} +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) : ""; -} +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); -} +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 += ''; - }); -} - -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); +setOptions = function (element, options, selected) { + if (!selected && options.length > 0) { + selected = options[0]; + } + options.forEach(function (option) { + document.getElementById(element).innerHTML += + '"; + }); +}; + +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','');} + 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','');} + 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"; - } - }); + 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(); +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 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, + 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 - } + 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)); + 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;} + if (!wubData.video_start) { + alert("No start time set"); + return; + } + if (!wubData.video_end) { + alert("No end time set"); + return; + } - //Submit to thrimshim + //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; - })); + 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, +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 = ""; + 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); - } - })); +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); - } - })); +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) { +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); -} +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() { +round_trip_tag_string = function () { var element = document.getElementById("VideoTags"); - element.value = tags_list_to_string( - tags_string_to_list( - element.value - ) - ); -} + element.value = tags_list_to_string(tags_string_to_list(element.value)); +}; diff --git a/thrimbletrimmer/scripts/keyboardShortcuts.js b/thrimbletrimmer/scripts/keyboardShortcuts.js index 71191e8..c5fec9a 100644 --- a/thrimbletrimmer/scripts/keyboardShortcuts.js +++ b/thrimbletrimmer/scripts/keyboardShortcuts.js @@ -1,99 +1,102 @@ -function changeSpeed(direction) { - var speeds = [0.5, 1, 1.25, 1.5, 2]; - var currentIndex = speeds.indexOf(player.playbackRate()); - if (currentIndex < 0) { - // not present - return; - } - var newIndex = currentIndex + direction; - if (newIndex < 0 || newIndex >= speeds.length) { - // out of range - return - } - player.playbackRate(speeds[newIndex]); -} - -document.addEventListener('keypress', (event) => { - //if(event.target.nodeName == "BODY") { - if(event.target.nodeName !== "INPUT" && event.target.nodeName !== "TEXTAREA") { - switch(event.key) { - case "j": - player.currentTime(player.currentTime()-10); - break; - case "k": - case " ": // also pause on space - player.paused() ? player.play():player.pause(); - break; - case "l": - player.currentTime(player.currentTime()+10); - break; - case ",": - player.currentTime(player.currentTime()-0.1); - break; - case ".": - player.currentTime(player.currentTime()+0.1); - break; - case "i": - player.trimmingControls().updateTrimTimes(player.currentTime(), player.trimmingControls().options.endTrim); - break; - case "o": - player.trimmingControls().updateTrimTimes(player.trimmingControls().options.startTrim, player.currentTime()); - break; - case "=": - changeSpeed(1); - break - case "-": - changeSpeed(-1); - break - case "0": - player.currentTime(0); - break; - case "1": - player.currentTime(player.duration()*0.1); - break; - case "2": - player.currentTime(player.duration()*0.2); - break; - case "3": - player.currentTime(player.duration()*0.3); - break; - case "4": - player.currentTime(player.duration()*0.4); - break; - case "5": - player.currentTime(player.duration()*0.5); - break; - case "6": - player.currentTime(player.duration()*0.6); - break; - case "7": - player.currentTime(player.duration()*0.7); - break; - case "8": - player.currentTime(player.duration()*0.8); - break; - case "9": - player.currentTime(player.duration()*0.9); - break; - } - } - - // const keyName = event.key; - // console.log('keypress event\n\n' + 'key: ' + keyName); - // console.log(event.target.nodeName); -}); - -//Arrow keys only detected on keydown, keypress only works in "some" browsers -document.addEventListener('keydown', (event) => { - if(event.target.nodeName !== "INPUT" && event.target.nodeName !== "TEXTAREA") { - switch(event.keyCode) { - case 37: - player.currentTime(player.currentTime()-5); - break; - case 39: - player.currentTime(player.currentTime()+5); - break; - - } - } -}); +function changeSpeed(direction) { + var speeds = [0.5, 1, 1.25, 1.5, 2]; + var currentIndex = speeds.indexOf(player.playbackRate()); + if (currentIndex < 0) { + // not present + return; + } + var newIndex = currentIndex + direction; + if (newIndex < 0 || newIndex >= speeds.length) { + // out of range + return; + } + player.playbackRate(speeds[newIndex]); +} + +document.addEventListener("keypress", event => { + //if(event.target.nodeName == "BODY") { + if (event.target.nodeName !== "INPUT" && event.target.nodeName !== "TEXTAREA") { + switch (event.key) { + case "j": + player.currentTime(player.currentTime() - 10); + break; + case "k": + case " ": // also pause on space + player.paused() ? player.play() : player.pause(); + break; + case "l": + player.currentTime(player.currentTime() + 10); + break; + case ",": + player.currentTime(player.currentTime() - 0.1); + break; + case ".": + player.currentTime(player.currentTime() + 0.1); + break; + case "i": + player + .trimmingControls() + .updateTrimTimes(player.currentTime(), player.trimmingControls().options.endTrim); + break; + case "o": + player + .trimmingControls() + .updateTrimTimes(player.trimmingControls().options.startTrim, player.currentTime()); + break; + case "=": + changeSpeed(1); + break; + case "-": + changeSpeed(-1); + break; + case "0": + player.currentTime(0); + break; + case "1": + player.currentTime(player.duration() * 0.1); + break; + case "2": + player.currentTime(player.duration() * 0.2); + break; + case "3": + player.currentTime(player.duration() * 0.3); + break; + case "4": + player.currentTime(player.duration() * 0.4); + break; + case "5": + player.currentTime(player.duration() * 0.5); + break; + case "6": + player.currentTime(player.duration() * 0.6); + break; + case "7": + player.currentTime(player.duration() * 0.7); + break; + case "8": + player.currentTime(player.duration() * 0.8); + break; + case "9": + player.currentTime(player.duration() * 0.9); + break; + } + } + + // const keyName = event.key; + // console.log('keypress event\n\n' + 'key: ' + keyName); + // console.log(event.target.nodeName); +}); + +//Arrow keys only detected on keydown, keypress only works in "some" browsers +document.addEventListener("keydown", event => { + if (event.target.nodeName !== "INPUT" && event.target.nodeName !== "TEXTAREA") { + switch (event.keyCode) { + case 37: + player.currentTime(player.currentTime() - 5); + break; + case 39: + player.currentTime(player.currentTime() + 5); + break; + } + } +}); diff --git a/thrimbletrimmer/scripts/playerSetup.js b/thrimbletrimmer/scripts/playerSetup.js index 71b1f3c..77a1342 100644 --- a/thrimbletrimmer/scripts/playerSetup.js +++ b/thrimbletrimmer/scripts/playerSetup.js @@ -1,98 +1,115 @@ -var player = null; - -function setupPlayer(isEditor, source, startTrim, endTrim) { - document.getElementById("my-player").style.display = ""; - //Make poster of DB logo in correct aspect ratio, to control initial size of fluid container. - var options = { - sources: [{ src: source }], - liveui: true, - //fluid:true, - controls:true, - autoplay:false, - width:1280, - height:420, - playbackRates: [0.5, 1, 1.25, 1.5, 2], - inactivityTimeout: 0, - controlBar: { - fullscreenToggle: true, - volumePanel: { - inline: false - } - } - }; - if(player) { //Destroy and recreate the player if it already exists. - player.dispose(); - document.getElementById("EditorContainer").innerHTML = ` - - `; - } - player = videojs('my-player', options, function onPlayerReady() { - videojs.log('Your player is ready!'); - - // Set player volume to 50% by default - var defaultVolume = 0.5; - this.volume(defaultVolume); - - // In this context, `this` is the player that was created by Video.js. - this.on('ready', function() { - //this.play(); - }); - - this.vhs.playlists.on('loadedmetadata', function() { - // setTimeout(function() { player.play(); }, 1000); - player.hasStarted(true); //So it displays all the controls. - if (isEditor) { - var stream_start = player.vhs.playlists.master.playlists.filter(playlist => typeof playlist.discontinuityStarts !== "undefined")[0].dateTimeObject; - startTrim = startTrim ? (new Date(startTrim+"Z")-stream_start)/1000:0; - endTrim = endTrim ? (new Date(endTrim+"Z")-stream_start)/1000:player.duration(); - var trimmingControls = player.trimmingControls({ startTrim:startTrim, endTrim:endTrim }); - } - }); - - // How about an event listener? - this.on('ended', function() { - videojs.log('Awww...over so soon?!'); - }); - - this.on('error', function() { - videojs.log("Could not load video stream"); - alert("No video available for stream."); - }) - }); - var hlsQS = player.hlsQualitySelector(); -} - -mapDiscontinuities = function() { - var playlist = player.vhs.playlists.master.playlists.filter(playlist => typeof playlist.discontinuityStarts !== "undefined")[0]; //Only one of the playlists will have the discontinuity or stream start objects, and it's not necessarily the first one or the source one. - var discontinuities = playlist.discontinuityStarts.map(segmentIndex => { return {segmentIndex:segmentIndex, segmentTimestamp:playlist.segments[segmentIndex].dateTimeObject, playbackIndex:null}; }); - //var lastDiscontinuity = Math.max(...playlist.discontinuityStarts); - var lastDiscontinuity = playlist.discontinuityStarts.slice(-1).pop(); //Assumes discontinuities are sorted in ascending order. - - var durationMarker = 0; - for (var index = 0; index <= lastDiscontinuity; index++) { - let segment = playlist.segments[index]; - if(segment.discontinuity) { - discontinuities.find(discontinuity => discontinuity.segmentIndex == index).playbackIndex = durationMarker; - } - durationMarker += segment.duration; - } - - return discontinuities; -}; - -getRealTimeForPlayerTime = function(discontinuities, playbackIndex) { - var streamStart = player.vhs.playlists.master.playlists.filter(playlist => typeof playlist.dateTimeObject !== "undefined")[0].dateTimeObject; //Only one of the playlists will have the discontinuity or stream start objects, and it's not necessarily the first one or the source one. - - //Find last discontinuity before playbackIndex - var lastDiscontinuity = discontinuities.filter(discontinuity => discontinuity.playbackIndex < playbackIndex).slice(-1).pop(); - if(lastDiscontinuity) { - streamStart = lastDiscontinuity.segmentTimestamp; - playbackIndex -= lastDiscontinuity.playbackIndex; - } - - var realTime = streamStart.getTime()+playbackIndex*1000; - - return (isFinite(realTime)) ? new Date(realTime).toISOString() : null; -}; +var player = null; + +function setupPlayer(isEditor, source, startTrim, endTrim) { + document.getElementById("my-player").style.display = ""; + //Make poster of DB logo in correct aspect ratio, to control initial size of fluid container. + var options = { + sources: [{src: source}], + liveui: true, + //fluid:true, + controls: true, + autoplay: false, + width: 1280, + height: 420, + playbackRates: [0.5, 1, 1.25, 1.5, 2], + inactivityTimeout: 0, + controlBar: { + fullscreenToggle: true, + volumePanel: { + inline: false, + }, + }, + }; + if (player) { + //Destroy and recreate the player if it already exists. + player.dispose(); + document.getElementById("EditorContainer").innerHTML = ` + + `; + } + player = videojs("my-player", options, function onPlayerReady() { + videojs.log("Your player is ready!"); + + // Set player volume to 50% by default + var defaultVolume = 0.5; + this.volume(defaultVolume); + + // In this context, `this` is the player that was created by Video.js. + this.on("ready", function () { + //this.play(); + }); + + this.vhs.playlists.on("loadedmetadata", function () { + // setTimeout(function() { player.play(); }, 1000); + player.hasStarted(true); //So it displays all the controls. + if (isEditor) { + var stream_start = player.vhs.playlists.master.playlists.filter( + playlist => typeof playlist.discontinuityStarts !== "undefined" + )[0].dateTimeObject; + startTrim = startTrim ? (new Date(startTrim + "Z") - stream_start) / 1000 : 0; + endTrim = endTrim ? (new Date(endTrim + "Z") - stream_start) / 1000 : player.duration(); + var trimmingControls = player.trimmingControls({startTrim: startTrim, endTrim: endTrim}); + } + }); + + // How about an event listener? + this.on("ended", function () { + videojs.log("Awww...over so soon?!"); + }); + + this.on("error", function () { + videojs.log("Could not load video stream"); + alert("No video available for stream."); + }); + }); + var hlsQS = player.hlsQualitySelector(); +} + +mapDiscontinuities = function () { + var playlist = player.vhs.playlists.master.playlists.filter( + playlist => typeof playlist.discontinuityStarts !== "undefined" + )[0]; //Only one of the playlists will have the discontinuity or stream start objects, and it's not necessarily the first one or the source one. + var discontinuities = playlist.discontinuityStarts.map(segmentIndex => { + return { + segmentIndex: segmentIndex, + segmentTimestamp: playlist.segments[segmentIndex].dateTimeObject, + playbackIndex: null, + }; + }); + //var lastDiscontinuity = Math.max(...playlist.discontinuityStarts); + var lastDiscontinuity = playlist.discontinuityStarts.slice(-1).pop(); //Assumes discontinuities are sorted in ascending order. + + var durationMarker = 0; + for (var index = 0; index <= lastDiscontinuity; index++) { + let segment = playlist.segments[index]; + if (segment.discontinuity) { + discontinuities.find(discontinuity => discontinuity.segmentIndex == index).playbackIndex = + durationMarker; + } + durationMarker += segment.duration; + } + + return discontinuities; +}; + +getRealTimeForPlayerTime = function (discontinuities, playbackIndex) { + var streamStart = player.vhs.playlists.master.playlists.filter( + playlist => typeof playlist.dateTimeObject !== "undefined" + )[0].dateTimeObject; //Only one of the playlists will have the discontinuity or stream start objects, and it's not necessarily the first one or the source one. + + //Find last discontinuity before playbackIndex + var lastDiscontinuity = discontinuities + .filter(discontinuity => discontinuity.playbackIndex < playbackIndex) + .slice(-1) + .pop(); + if (lastDiscontinuity) { + streamStart = lastDiscontinuity.segmentTimestamp; + playbackIndex -= lastDiscontinuity.playbackIndex; + } + + var realTime = streamStart.getTime() + playbackIndex * 1000; + + return isFinite(realTime) ? new Date(realTime).toISOString() : null; +};