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) {