From d37175f914f33a4f3593b45e820b9a76929a7ba4 Mon Sep 17 00:00:00 2001 From: ElementalAlchemist Date: Wed, 27 Oct 2021 02:04:58 -0500 Subject: [PATCH] Switch date/time handling from JS Date objects to a datetime library to fix padding bug with DST changeover --- thrimbletrimmer/.prettierignore | 4 +- thrimbletrimmer/edit.html | 1 + thrimbletrimmer/index.html | 1 + thrimbletrimmer/scripts/common.js | 58 ++++++--- thrimbletrimmer/scripts/edit.js | 178 ++++++++++----------------- thrimbletrimmer/scripts/luxon.min.js | 1 + thrimbletrimmer/scripts/stream.js | 37 ++---- 7 files changed, 124 insertions(+), 156 deletions(-) create mode 100644 thrimbletrimmer/scripts/luxon.min.js diff --git a/thrimbletrimmer/.prettierignore b/thrimbletrimmer/.prettierignore index 3588f1c..0a15642 100644 --- a/thrimbletrimmer/.prettierignore +++ b/thrimbletrimmer/.prettierignore @@ -1,2 +1,4 @@ -videojs/ +styles/video-js.min.css +scripts/video.min.js +scripts/luxon.min.js dashboard.html \ No newline at end of file diff --git a/thrimbletrimmer/edit.html b/thrimbletrimmer/edit.html index b244077..2ddda2f 100644 --- a/thrimbletrimmer/edit.html +++ b/thrimbletrimmer/edit.html @@ -8,6 +8,7 @@ + diff --git a/thrimbletrimmer/index.html b/thrimbletrimmer/index.html index c213eeb..354c585 100644 --- a/thrimbletrimmer/index.html +++ b/thrimbletrimmer/index.html @@ -8,6 +8,7 @@ + diff --git a/thrimbletrimmer/scripts/common.js b/thrimbletrimmer/scripts/common.js index c93086b..71f0d30 100644 --- a/thrimbletrimmer/scripts/common.js +++ b/thrimbletrimmer/scripts/common.js @@ -1,4 +1,7 @@ -var globalBusStartTime = new Date("1970-01-01T00:00:00Z"); +var DateTime = luxon.DateTime; +var Interval = luxon.Interval; + +var globalBusStartTime = DateTime.fromISO("1970-01-01T00:00:00", { zone: "utc" }); var globalStreamName = ""; var globalStartTimeString = ""; var globalEndTimeString = ""; @@ -87,35 +90,54 @@ function updateVideoPlayer(newPlaylistURL) { player.src({ src: rangedPlaylistURL }); } -function dateObjFromBusTime(busTime) { +function parseHumanTimeStringAsDateTimeMathObject(inputTime) { // We need to handle inputs like "-0:10:15" in a way that consistently makes the time negative. // Since we can't assign the negative sign to any particular part, we'll check for the whole thing here. let direction = 1; - if (busTime.startsWith("-")) { - busTime = busTime.slice(1); + if (inputTime.startsWith("-")) { + inputTime = inputTime.slice(1); direction = -1; } - const parts = busTime.split(":", 3); - const hours = (parts[0] || 0) * direction; + const parts = inputTime.split(":", 3); + const hours = parseInt(parts[0]) * direction; const minutes = (parts[1] || 0) * direction; const seconds = (parts[2] || 0) * direction; - const time = new Date(globalBusStartTime); - time.setHours(time.getHours() + hours); - time.setMinutes(time.getMinutes() + minutes); - time.setSeconds(time.getSeconds() + seconds); - return time; + return { hours: hours, minutes: minutes, seconds: seconds }; +} + +function dateTimeFromBusTime(busTime) { + return globalBusStartTime.plus(parseHumanTimeStringAsDateTimeMathObject(busTime)); +} + +function busTimeFromDateTime(dateTime) { + const diff = dateTime.diff(globalBusStartTime); + return formatIntervalForDisplay(diff); } -function dateObjFromWubloaderTime(wubloaderTime) { - return new Date(`${wubloaderTime}Z`); +function formatIntervalForDisplay(interval) { + if (interval.milliseconds < 0) { + const negativeInterval = interval.negate(); + return `-${negativeInterval.toFormat("hh:mm:ss.SSS")}`; + } + return interval.toFormat("hh:mm:ss.SSS"); +} + +function dateTimeFromWubloaderTime(wubloaderTime) { + return DateTime.fromISO(wubloaderTime, { zone: "utc" }); } -function wubloaderTimeFromDateObj(date) { - if (!date) { +function wubloaderTimeFromDateTime(dateTime) { + if (!dateTime) { return null; } - return date.toISOString().substring(0, 23); // Trim "Z" marker and smaller than milliseconds + // Not using ISO here because Luxon doesn't give us a quick way to print an ISO8601 string with no offset. + return dateTime.toFormat("yyyy-LL-dd'T'HH:mm:ss.SSS"); +} + +function busTimeFromWubloaderTime(wubloaderTime) { + const dt = dateTimeFromWubloaderTime(wubloaderTime); + return busTimeFromDateTime(dt); } function assembleVideoPlaylistURL(basePlaylistURL) { @@ -134,10 +156,10 @@ function startAndEndTimeQueryStringParts() { let queryStringParts = []; if (startTime) { - queryStringParts.push(`start=${wubloaderTimeFromDateObj(startTime)}`); + queryStringParts.push(`start=${wubloaderTimeFromDateTime(startTime)}`); } if (endTime) { - queryStringParts.push(`end=${wubloaderTimeFromDateObj(endTime)}`); + queryStringParts.push(`end=${wubloaderTimeFromDateTime(endTime)}`); } return queryStringParts; } diff --git a/thrimbletrimmer/scripts/edit.js b/thrimbletrimmer/scripts/edit.js index 4942c2e..4cf8085 100644 --- a/thrimbletrimmer/scripts/edit.js +++ b/thrimbletrimmer/scripts/edit.js @@ -17,7 +17,7 @@ window.addEventListener("DOMContentLoaded", async (event) => { } const newStartField = document.getElementById("stream-time-setting-start"); - const newStart = dateObjFromBusTime(newStartField.value); + const newStart = dateTimeFromBusTime(newStartField.value); if (!newStart) { addError("Failed to parse start time"); return; @@ -26,7 +26,7 @@ window.addEventListener("DOMContentLoaded", async (event) => { const newEndField = document.getElementById("stream-time-setting-end"); let newEnd = null; if (newEndField.value !== "") { - newEnd = dateObjFromBusTime(newEndField.value); + newEnd = dateTimeFromBusTime(newEndField.value); if (!newEnd) { addError("Failed to parse end time"); return; @@ -34,8 +34,14 @@ window.addEventListener("DOMContentLoaded", async (event) => { } const oldStart = getStartTime(); - const startAdjustment = newStart - oldStart; - const newDuration = newEnd === null ? Infinity : newEnd - newStart; + const startAdjustment = newStart.diff(oldStart, "seconds").seconds; + let newDuration = newEnd === null ? Infinity : newEnd.diff(newStart, "seconds").seconds; + + // The video duration isn't precisely the video times, but can be padded by up to the + // segment length on either side. + // This makes the assumption that all segments have the same length. + const segmentLength = getPlaylistData().segments[0].duration; + newDuration += segmentLength * 2; // Abort for ranges that exceed new times for (const rangeContainer of document.getElementById("range-definitions").children) { @@ -54,8 +60,8 @@ window.addEventListener("DOMContentLoaded", async (event) => { } } - globalStartTimeString = wubloaderTimeFromDateObj(newStart); - globalEndTimeString = wubloaderTimeFromDateObj(newEnd); + globalStartTimeString = wubloaderTimeFromDateTime(newStart); + globalEndTimeString = wubloaderTimeFromDateTime(newEnd); updateSegmentPlaylist(); @@ -98,25 +104,17 @@ window.addEventListener("DOMContentLoaded", async (event) => { document.getElementById("stream-time-setting-start-pad").addEventListener("click", (_event) => { const startTimeField = document.getElementById("stream-time-setting-start"); let startTime = startTimeField.value; - startTime = dateObjFromBusTime(startTime); - if (isNaN(startTime)) { - addError("Couldn't parse entered start time for padding"); - return; - } - startTime.setMinutes(startTime.getMinutes() - 1); - startTimeField.value = busTimeFromDateObj(startTime); + startTime = dateTimeFromBusTime(startTime); + startTime = startTime.minus({ minutes: 1 }); + startTimeField.value = busTimeFromDateTime(startTime); }); document.getElementById("stream-time-setting-end-pad").addEventListener("click", (_event) => { const endTimeField = document.getElementById("stream-time-setting-end"); let endTime = endTimeField.value; - endTime = dateObjFromBusTime(endTime); - if (isNaN(endTime)) { - addError("Couldn't parse entered end time for padding"); - return; - } - endTime.setMinutes(endTime.getMinutes() + 1); - endTimeField.value = busTimeFromDateObj(endTime); + endTime = dateTimeFromBusTime(endTime); + endTime = endTime.plus({ minutes: 1 }); + endTimeField.value = busTimeFromDateTime(endTime); }); const addRangeIcon = document.getElementById("add-range-definition"); @@ -225,23 +223,23 @@ async function loadVideoInfo() { async function initializeVideoInfo() { globalStreamName = videoInfo.video_channel; - globalBusStartTime = new Date(videoInfo.bustime_start); + globalBusStartTime = DateTime.fromISO(videoInfo.bustime_start, { zone: "utc" }); - const eventStartTime = dateObjFromWubloaderTime(videoInfo.event_start); - const eventEndTime = videoInfo.event_end ? dateObjFromWubloaderTime(videoInfo.event_end) : null; + let eventStartTime = dateTimeFromWubloaderTime(videoInfo.event_start); + let eventEndTime = videoInfo.event_end ? dateTimeFromWubloaderTime(videoInfo.event_end) : null; // To account for various things (stream delay, just slightly off logging, etc.), we pad the start time by one minute - eventStartTime.setMinutes(eventStartTime.getMinutes() - 1); + eventStartTime = eventStartTime.minus({ minutes: 1 }); // To account for various things (stream delay, just slightly off logging, etc.), we pad the end time by one minute. // To account for the fact that we don't record seconds, but the event could've ended any time in the recorded minute, we pad by an additional minute. if (eventEndTime) { - eventEndTime.setMinutes(eventEndTime.getMinutes() + 2); + eventEndTime = eventEndTime.plus({ minutes: 2 }); } - globalStartTimeString = wubloaderTimeFromDateObj(eventStartTime); + globalStartTimeString = wubloaderTimeFromDateTime(eventStartTime); if (eventEndTime) { - globalEndTimeString = wubloaderTimeFromDateObj(eventEndTime); + globalEndTimeString = wubloaderTimeFromDateTime(eventEndTime); } else { document.getElementById("waveform").classList.add("hidden"); } @@ -255,42 +253,42 @@ async function initializeVideoInfo() { let endTime = range[1]; if (startTime) { - startTime = dateObjFromWubloaderTime(startTime); + startTime = dateTimeFromWubloaderTime(startTime); } else { startTime = null; } if (endTime) { - endTime = dateObjFromWubloaderTime(endTime); + endTime = dateTimeFromWubloaderTime(endTime); } else { endTime = null; } - if (!earliestStartTime || (startTime && startTime < earliestStartTime)) { + if (!earliestStartTime || (startTime && startTime.diff(earliestStartTime).milliseconds < 0)) { earliestStartTime = startTime; } - if (!latestEndTime || (endTime && endTime > latestEndTime)) { + if (!latestEndTime || (endTime && endTime.diff(latestEndTime).milliseconds > 0)) { latestEndTime = endTime; } } - if (earliestStartTime && earliestStartTime < eventStartTime) { - earliestStartTime.setMinutes(earliestStartTime.getMinutes() - 1); - globalStartTimeString = wubloaderTimeFromDateObj(earliestStartTime); + if (earliestStartTime && earliestStartTime.diff(eventStartTime).milliseconds < 0) { + earliestStartTime = earliestStartTime.minus({ minutes: 1 }); + globalStartTimeString = wubloaderTimeFromDateTime(earliestStartTime); } - if (latestEndTime && latestEndTime > eventEndTime) { + if (latestEndTime && latestEndTime.diff(eventEndTime).milliseconds > 0) { // If we're getting the time from a previous draft edit, we have seconds, so one minute is enough - latestEndTime.setMinutes(latestEndTime.getMinutes() + 1); - globalEndTimeString = wubloaderTimeFromDateObj(latestEndTime); + latestEndTime = latestEndTime.plus({ minutes: 1 }); + globalEndTimeString = wubloaderTimeFromDateTime(latestEndTime); } } document.getElementById("stream-time-setting-stream").innerText = globalStreamName; document.getElementById("stream-time-setting-start").value = - getBusTimeFromTimeString(globalStartTimeString); + busTimeFromWubloaderTime(globalStartTimeString); document.getElementById("stream-time-setting-end").value = - getBusTimeFromTimeString(globalEndTimeString); + busTimeFromWubloaderTime(globalEndTimeString); updateWaveform(); @@ -392,13 +390,11 @@ async function initializeVideoInfo() { } else { const rangeStartField = rangeDefinitionsContainer.getElementsByClassName("range-definition-start")[0]; - rangeStartField.value = videoHumanTimeFromVideoPlayerTime(0); - const player = getVideoJS(); - const videoDuration = player.duration(); - if (isFinite(videoDuration)) { + rangeStartField.value = videoHumanTimeFromWubloaderTime(globalStartTimeString); + if (globalEndTimeString) { const rangeEndField = rangeDefinitionsContainer.getElementsByClassName("range-definition-end")[0]; - rangeEndField.value = videoHumanTimeFromVideoPlayerTime(videoDuration); + rangeEndField.value = videoHumanTimeFromWubloaderTime(globalEndTimeString); } } rangeDataUpdated(); @@ -445,56 +441,14 @@ function getStartTime() { if (!globalStartTimeString) { return null; } - return dateObjFromWubloaderTime(globalStartTimeString); + return dateTimeFromWubloaderTime(globalStartTimeString); } function getEndTime() { if (!globalEndTimeString) { return null; } - return dateObjFromWubloaderTime(globalEndTimeString); -} - -function getBusTimeFromTimeString(timeString) { - if (timeString === "") { - return ""; - } - const time = dateObjFromWubloaderTime(timeString); - return busTimeFromDateObj(time); -} - -function busTimeFromDateObj(time) { - const busTimeMilliseconds = time - globalBusStartTime; - let remainingBusTimeSeconds = busTimeMilliseconds / 1000; - - let sign = ""; - if (remainingBusTimeSeconds < 0) { - sign = "-"; - remainingBusTimeSeconds = Math.abs(remainingBusTimeSeconds); - } - - const hours = Math.floor(remainingBusTimeSeconds / 3600); - remainingBusTimeSeconds %= 3600; - let minutes = Math.floor(remainingBusTimeSeconds / 60); - let seconds = remainingBusTimeSeconds % 60; - let milliseconds = Math.round((seconds % 1) * 1000); - seconds = Math.trunc(seconds); - - while (minutes.toString().length < 2) { - minutes = `0${minutes}`; - } - while (seconds.toString().length < 2) { - seconds = `0${seconds}`; - } - - if (milliseconds > 0) { - while (milliseconds.toString().length < 3) { - milliseconds = `0${milliseconds}`; - } - return `${sign}${hours}:${minutes}:${seconds}.${milliseconds}`; - } - - return `${sign}${hours}:${minutes}:${seconds}`; + return dateTimeFromWubloaderTime(globalEndTimeString); } async function submitVideo() { @@ -632,11 +586,11 @@ function generateDownloadURL(timeRanges, downloadType, allowHoles, quality) { for (const range of timeRanges) { let timeRangeString = ""; if (range.hasOwnProperty("start")) { - timeRangeString += wubloaderTimeFromDateObj(range.start); + timeRangeString += range.start; } timeRangeString += ","; if (range.hasOwnProperty("end")) { - timeRangeString += wubloaderTimeFromDateObj(range.end); + timeRangeString += range.end; } queryParts.push(`range=${timeRangeString}`); } @@ -954,14 +908,21 @@ function setCurrentRangeEndToVideoTime() { function videoPlayerTimeFromWubloaderTime(wubloaderTime) { const videoPlaylist = getPlaylistData(); - const wubloaderDateObj = dateObjFromWubloaderTime(wubloaderTime); + const wubloaderDateTime = dateTimeFromWubloaderTime(wubloaderTime); let highestDiscontinuitySegmentBefore = 0; for (start of videoPlaylist.discontinuityStarts) { const discontinuityStartSegment = videoPlaylist.segments[start]; + const discontinuityStartDateTime = DateTime.fromJSDate( + discontinuityStartSegment.dateTimeObject, + { zone: "utc" } + ); + const highestDiscontinuitySegmentDateTime = DateTime.fromJSDate( + videoPlaylist.segments[highestDiscontinuitySegmentBefore].dateTimeObject, + { zone: "utc" } + ); if ( - discontinuityStartSegment.dateTimeObject < wubloaderDateObj && - discontinuityStartSegment.dateTimeObject > - videoPlaylist.segments[highestDiscontinuitySegmentBefore].dateTimeObject + discontinuityStartDateTime.diff(wubloaderDateTime).milliseconds < 0 && // Discontinuity starts before the provided time + discontinuityStartDateTime.diff(highestDiscontinuitySegmentDateTime).milliseconds > 0 // Discontinuity starts after the latest found discontinuity ) { highestDiscontinuitySegmentBefore = start; } @@ -971,16 +932,17 @@ function videoPlayerTimeFromWubloaderTime(wubloaderTime) { for (let segment = 0; segment < highestDiscontinuitySegmentBefore; segment++) { highestDiscontinuitySegmentStart += videoPlaylist.segments[segment].duration; } + const highestDiscontinuitySegmentDateTime = DateTime.fromJSDate( + videoPlaylist.segments[highestDiscontinuitySegmentBefore].dateTimeObject, + { zone: "utc" } + ); return ( highestDiscontinuitySegmentStart + - secondsDifference( - videoPlaylist.segments[highestDiscontinuitySegmentBefore].dateTimeObject, - wubloaderDateObj - ) + wubloaderDateTime.diff(highestDiscontinuitySegmentDateTime, "seconds").seconds ); } -function wubloaderTimeFromVideoPlayerTime(videoPlayerTime) { +function dateTimeFromVideoPlayerTime(videoPlayerTime) { const videoPlaylist = getPlaylistData(); let segmentStartTime = 0; let segmentDateObj; @@ -997,11 +959,14 @@ function wubloaderTimeFromVideoPlayerTime(videoPlayerTime) { if (segmentDateObj === undefined) { return null; } - let wubloaderDateObj = new Date(segmentDateObj); + let wubloaderDateTime = DateTime.fromJSDate(segmentDateObj, { zone: "utc" }); const offset = videoPlayerTime - segmentStartTime; - const offsetMilliseconds = offset * 1000; - wubloaderDateObj.setMilliseconds(wubloaderDateObj.getMilliseconds() + offsetMilliseconds); - return wubloaderDateObj; + return wubloaderDateTime.plus({ seconds: offset }); +} + +function wubloaderTimeFromVideoPlayerTime(videoPlayerTime) { + const dt = dateTimeFromVideoPlayerTime(videoPlayerTime); + return wubloaderTimeFromDateTime(dt); } function videoHumanTimeFromVideoPlayerTime(videoPlayerTime) { @@ -1058,10 +1023,3 @@ function getPlaylistData() { // etc.), this and all callers will need to be updated. return player.tech("OK").vhs.playlists.master.playlists[0]; } - -function secondsDifference(date1, date2) { - if (date2 > date1) { - return (date2 - date1) / 1000; - } - return (date1 - date2) / 1000; -} diff --git a/thrimbletrimmer/scripts/luxon.min.js b/thrimbletrimmer/scripts/luxon.min.js new file mode 100644 index 0000000..550c963 --- /dev/null +++ b/thrimbletrimmer/scripts/luxon.min.js @@ -0,0 +1 @@ +var luxon=function(e){"use strict";function r(e,t){for(var n=0;ne.length)&&(t=e.length);for(var n=0,r=new Array(t);n=e.length?{done:!0}:{done:!1,value:e[r++]}}}throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.")}var n=function(e){function t(){return e.apply(this,arguments)||this}return i(t,e),t}(t(Error)),f=function(t){function e(e){return t.call(this,"Invalid DateTime: "+e.toMessage())||this}return i(e,t),e}(n),d=function(t){function e(e){return t.call(this,"Invalid Interval: "+e.toMessage())||this}return i(e,t),e}(n),h=function(t){function e(e){return t.call(this,"Invalid Duration: "+e.toMessage())||this}return i(e,t),e}(n),S=function(e){function t(){return e.apply(this,arguments)||this}return i(t,e),t}(n),y=function(t){function e(e){return t.call(this,"Invalid unit "+e)||this}return i(e,t),e}(n),v=function(e){function t(){return e.apply(this,arguments)||this}return i(t,e),t}(n),m=function(e){function t(){return e.call(this,"Zone is an abstract class")||this}return i(t,e),t}(n),p="numeric",g="short",w="long",T={year:p,month:p,day:p},b={year:p,month:g,day:p},O={year:p,month:g,day:p,weekday:g},M={year:p,month:w,day:p},N={year:p,month:w,day:p,weekday:w},D={hour:p,minute:p},E={hour:p,minute:p,second:p},I={hour:p,minute:p,second:p,timeZoneName:g},V={hour:p,minute:p,second:p,timeZoneName:w},x={hour:p,minute:p,hourCycle:"h23"},C={hour:p,minute:p,second:p,hourCycle:"h23"},F={hour:p,minute:p,second:p,hourCycle:"h23",timeZoneName:g},Z={hour:p,minute:p,second:p,hourCycle:"h23",timeZoneName:w},L={year:p,month:p,day:p,hour:p,minute:p},A={year:p,month:p,day:p,hour:p,minute:p,second:p},z={year:p,month:g,day:p,hour:p,minute:p},j={year:p,month:g,day:p,hour:p,minute:p,second:p},q={year:p,month:g,day:p,weekday:g,hour:p,minute:p},_={year:p,month:w,day:p,hour:p,minute:p,timeZoneName:g},U={year:p,month:w,day:p,hour:p,minute:p,second:p,timeZoneName:g},R={year:p,month:w,day:p,weekday:w,hour:p,minute:p,timeZoneName:w},H={year:p,month:w,day:p,weekday:w,hour:p,minute:p,second:p,timeZoneName:w};function P(e){return void 0===e}function W(e){return"number"==typeof e}function J(e){return"number"==typeof e&&e%1==0}function Y(){try{return"undefined"!=typeof Intl&&!!Intl.RelativeTimeFormat}catch(e){return!1}}function G(e,n,r){if(0!==e.length)return e.reduce(function(e,t){t=[n(t),t];return e&&r(e[0],t[0])===e[0]?e:t},null)[1]}function $(e,t){return Object.prototype.hasOwnProperty.call(e,t)}function B(e,t,n){return J(e)&&t<=e&&e<=n}function Q(e,t){void 0===t&&(t=2);var n=e<0?"-":"",e=n?-1*e:e,e=e.toString().lengthJt.indexOf(s)&&$t(this.matrix,a,d,i,s)}else W(a[s])&&(o[s]=a[s])}for(r in o)0!==o[r]&&(i[l]+=r===l?o[r]:o[r]/this.matrix[l][r]);return Gt(this,{values:i},!0).normalize()},e.negate=function(){if(!this.isValid)return this;for(var e={},t=0,n=Object.keys(this.values);te},e.isBefore=function(e){return!!this.isValid&&this.e<=e},e.contains=function(e){return!!this.isValid&&(this.s<=e&&this.e>e)},e.set=function(e){var t=void 0===e?{}:e,e=t.start,t=t.end;return this.isValid?c.fromDateTimes(e||this.s,t||this.e):this},e.splitAt=function(){var t=this;if(!this.isValid)return[];for(var e=arguments.length,n=new Array(e),r=0;r+this.e?this.e:s;o.push(c.fromDateTimes(a,s)),a=s,u+=1}return o},e.splitBy=function(e){var t=Qt(e);if(!this.isValid||!t.isValid||0===t.as("milliseconds"))return[];for(var n=this.s,r=1,i=[];n+this.e?this.e:o;i.push(c.fromDateTimes(n,o)),n=o,r+=1}return i},e.divideEqually=function(e){return this.isValid?this.splitBy(this.length()/e).slice(0,e):[]},e.overlaps=function(e){return this.e>e.s&&this.s=e.e)},e.equals=function(e){return!(!this.isValid||!e.isValid)&&(this.s.equals(e.s)&&this.e.equals(e.e))},e.intersection=function(e){if(!this.isValid)return this;var t=(this.s>e.s?this:e).s,e=(this.ee.e?this:e).e;return c.fromDateTimes(t,e)},c.merge=function(e){var t=e.sort(function(e,t){return e.s-t.s}).reduce(function(e,t){var n=e[0],e=e[1];return e?e.overlaps(t)||e.abutsStart(t)?[n,e.union(t)]:[n.concat([e]),t]:[n,t]},[[],null]),e=t[0],t=t[1];return t&&e.push(t),e},c.xor=function(e){for(var t=null,n=0,r=[],i=e.map(function(e){return[{time:e.s,type:"s"},{time:e.e,type:"e"}]}),o=k((e=Array.prototype).concat.apply(e,i).sort(function(e,t){return e.time-t.time}));!(a=o()).done;)var a=a.value,t=1===(n+="s"===a.type?1:-1)?a.time:(t&&+t!=+a.time&&r.push(c.fromDateTimes(t,a.time)),null);return c.merge(r)},e.difference=function(){for(var t=this,e=arguments.length,n=new Array(e),r=0;roe(n)?(t=n+1,o=1):t=n,u({weekYear:t,weekNumber:o,weekday:i},de(e))}function In(e){var t,n=e.weekYear,r=e.weekNumber,i=e.weekday,o=Mn(n,1,4),a=ne(n),o=7*r+i-o-3;o<1?o+=ne(t=n-1):athis.valueOf(),r=nn(n?this:e,n?e:this,t,r);return n?r.negate():r},e.diffNow=function(e,t){return void 0===e&&(e="milliseconds"),void 0===t&&(t={}),this.diff(w.now(),e,t)},e.until=function(e){return this.isValid?Xt.fromDateTimes(this,e):this},e.hasSame=function(e,t){if(!this.isValid)return!1;var n=e.valueOf(),e=this.setZone(e.zone,{keepLocalTime:!0});return e.startOf(t)<=n&&n<=e.endOf(t)},e.equals=function(e){return this.isValid&&e.isValid&&this.valueOf()===e.valueOf()&&this.zone.equals(e.zone)&&this.loc.equals(e.loc)},e.toRelative=function(e){if(!this.isValid)return null;var t=(e=void 0===e?{}:e).base||w.fromObject({},{zone:this.zone}),n=e.padding?thisthis.set({month:1}).offset||this.offset>this.set({month:5}).offset)}},{key:"isInLeapYear",get:function(){return te(this.year)}},{key:"daysInMonth",get:function(){return re(this.year,this.month)}},{key:"daysInYear",get:function(){return this.isValid?ne(this.year):NaN}},{key:"weeksInWeekYear",get:function(){return this.isValid?oe(this.weekYear):NaN}}],[{key:"DATE_SHORT",get:function(){return T}},{key:"DATE_MED",get:function(){return b}},{key:"DATE_MED_WITH_WEEKDAY",get:function(){return O}},{key:"DATE_FULL",get:function(){return M}},{key:"DATE_HUGE",get:function(){return N}},{key:"TIME_SIMPLE",get:function(){return D}},{key:"TIME_WITH_SECONDS",get:function(){return E}},{key:"TIME_WITH_SHORT_OFFSET",get:function(){return I}},{key:"TIME_WITH_LONG_OFFSET",get:function(){return V}},{key:"TIME_24_SIMPLE",get:function(){return x}},{key:"TIME_24_WITH_SECONDS",get:function(){return C}},{key:"TIME_24_WITH_SHORT_OFFSET",get:function(){return F}},{key:"TIME_24_WITH_LONG_OFFSET",get:function(){return Z}},{key:"DATETIME_SHORT",get:function(){return L}},{key:"DATETIME_SHORT_WITH_SECONDS",get:function(){return A}},{key:"DATETIME_MED",get:function(){return z}},{key:"DATETIME_MED_WITH_SECONDS",get:function(){return j}},{key:"DATETIME_MED_WITH_WEEKDAY",get:function(){return q}},{key:"DATETIME_FULL",get:function(){return _}},{key:"DATETIME_FULL_WITH_SECONDS",get:function(){return U}},{key:"DATETIME_HUGE",get:function(){return R}},{key:"DATETIME_HUGE_WITH_SECONDS",get:function(){return H}}]),w}();function nr(e){if(tr.isDateTime(e))return e;if(e&&e.valueOf&&W(e.valueOf()))return tr.fromJSDate(e);if(e&&"object"==typeof e)return tr.fromObject(e);throw new v("Unknown datetime argument: "+e+", of type "+typeof e)}return e.DateTime=tr,e.Duration=Bt,e.FixedOffsetZone=_e,e.IANAZone=je,e.Info=en,e.Interval=Xt,e.InvalidZone=Ue,e.Settings=$e,e.SystemZone=Fe,e.VERSION="2.0.2",e.Zone=xe,Object.defineProperty(e,"__esModule",{value:!0}),e}({}); \ No newline at end of file diff --git a/thrimbletrimmer/scripts/stream.js b/thrimbletrimmer/scripts/stream.js index 40fdda6..38308e8 100644 --- a/thrimbletrimmer/scripts/stream.js +++ b/thrimbletrimmer/scripts/stream.js @@ -29,20 +29,18 @@ async function loadDefaults() { const streamNameField = document.getElementById("stream-time-setting-stream"); streamNameField.value = defaultData.video_channel; - globalBusStartTime = new Date(defaultData.bustime_start); + globalBusStartTime = DateTime.fromISO(defaultData.bustime_start, { zone: "utc" }); } // Gets the start time of the video from settings. Returns an invalid date object if the user entered bad data. function getStartTime() { switch (globalVideoTimeReference) { case 1: - return dateObjFromWubloaderTime(globalStartTimeString); + return dateTimeFromWubloaderTime(globalStartTimeString); case 2: - return dateObjFromBusTime(globalStartTimeString); + return dateTimeFromBusTime(globalStartTimeString); case 3: - return new Date( - new Date().getTime() - 1000 * parseInputTimeAsNumberOfSeconds(globalStartTimeString) - ); + return DateTime.now().minus(parseHumanTimeStringAsDateTimeMathObject(globalStartTimeString)); } } @@ -53,29 +51,14 @@ function getEndTime() { } switch (globalVideoTimeReference) { case 1: - return dateObjFromWubloaderTime(globalEndTimeString); + return dateTimeFromWubloaderTime(globalEndTimeString); case 2: - return dateObjFromBusTime(globalEndTimeString); + return dateTimeFromBusTime(globalEndTimeString); case 3: - return new Date( - new Date().getTime() - 1000 * parseInputTimeAsNumberOfSeconds(globalEndTimeString) - ); + return DateTime.now().minus(parseHumanTimeStringAsDateTimeMathObject(globalEndTimeString)); } } -function parseInputTimeAsNumberOfSeconds(inputTime) { - // We need to handle inputs like "-0:10:15" in a way that consistently makes the time negative. - // Since we can't assign the negative sign to any particular part, we'll check for the whole thing here. - let direction = 1; - if (inputTime.startsWith("-")) { - inputTime = inputTime.slice(1); - direction = -1; - } - - const parts = inputTime.split(":", 3); - return (parseInt(parts[0]) + (parts[1] || 0) / 60 + (parts[2] || 0) / 3600) * 60 * 60 * direction; -} - function updateTimeSettings() { updateStoredTimeSettings(); if (globalLoadedVideoPlayer) { @@ -89,7 +72,7 @@ function updateTimeSettings() { const startTime = getStartTime(); const endTime = getEndTime(); - if (endTime && endTime < startTime) { + if (endTime && endTime.diff(startTime) < 0) { addError( "End time is before the start time. This will prevent video loading and cause other problems." ); @@ -97,8 +80,8 @@ function updateTimeSettings() { } function generateDownloadURL(startTime, endTime, downloadType, allowHoles, quality) { - const startURLTime = wubloaderTimeFromDateObj(startTime); - const endURLTime = wubloaderTimeFromDateObj(endTime); + const startURLTime = wubloaderTimeFromDateTime(startTime); + const endURLTime = wubloaderTimeFromDateTime(endTime); const queryParts = [`type=${downloadType}`, `allow_holes=${allowHoles}`]; if (startURLTime) {