elwm / yt_clipper

(function () {
function $parcel$interopDefault(a) {
  return a && a.__esModule ? {
    d: a.default
  } : {
    d: a
  };
}

var $parcel$global = this;
// ASSET: yt_clipper.ts
var $XbmT$exports = {};
// ASSET: ..\..\node_modules\file-saver\dist\FileSaver.min.js
var $oSxG$exports = {};
var $oSxG$var$define;

(function (a, b) {
  if ("function" == typeof $oSxG$var$define && $oSxG$var$define.amd) $oSxG$var$define([], b);else if ("undefined" != typeof $oSxG$exports) b();else {
    b(), a.FileSaver = {
      exports: {}
    }.exports;
  }
})($oSxG$exports, function () {
  function b(a, b) {
    return "undefined" == typeof b ? b = {
      autoBom: !1
    } : "object" != typeof b && (console.warn("Deprecated: Expected third argument to be a object"), b = {
      autoBom: !b
    }), b.autoBom && /^\s*(?:text\/\S*|application\/xml|\S*\/\S*\+xml)\s*;.*charset\s*=\s*utf-8/i.test(a.type) ? new Blob(["\uFEFF", a], {
      type: a.type
    }) : a;
  }

  function c(b, c, d) {
    var e = new XMLHttpRequest();
    e.open("GET", b), e.responseType = "blob", e.onload = function () {
      a(e.response, c, d);
    }, e.onerror = function () {
      console.error("could not download file");
    }, e.send();
  }

  function d(a) {
    var b = new XMLHttpRequest();
    b.open("HEAD", a, !1);

    try {
      b.send();
    } catch (a) {}

    return 200 <= b.status && 299 >= b.status;
  }

  function e(a) {
    try {
      a.dispatchEvent(new MouseEvent("click"));
    } catch (c) {
      var b = document.createEvent("MouseEvents");
      b.initMouseEvent("click", !0, !0, window, 0, 0, 0, 80, 20, !1, !1, !1, !1, 0, null), a.dispatchEvent(b);
    }
  }

  var f = "object" == typeof window && window.window === window ? window : "object" == typeof self && self.self === self ? self : "object" == typeof $parcel$global && $parcel$global.global === $parcel$global ? $parcel$global : void 0,
      a = f.saveAs || ("object" != typeof window || window !== f ? function () {} : "download" in HTMLAnchorElement.prototype ? function (b, g, h) {
    var i = f.URL || f.webkitURL,
        j = document.createElement("a");
    g = g || b.name || "download", j.download = g, j.rel = "noopener", "string" == typeof b ? (j.href = b, j.origin === location.origin ? e(j) : d(j.href) ? c(b, g, h) : e(j, j.target = "_blank")) : (j.href = i.createObjectURL(b), setTimeout(function () {
      i.revokeObjectURL(j.href);
    }, 4E4), setTimeout(function () {
      e(j);
    }, 0));
  } : "msSaveOrOpenBlob" in navigator ? function (f, g, h) {
    if (g = g || f.name || "download", "string" != typeof f) navigator.msSaveOrOpenBlob(b(f, h), g);else if (d(f)) c(f, g, h);else {
      var i = document.createElement("a");
      i.href = f, i.target = "_blank", setTimeout(function () {
        e(i);
      });
    }
  } : function (a, b, d, e) {
    if (e = e || open("", "_blank"), e && (e.document.title = e.document.body.innerText = "downloading..."), "string" == typeof a) return c(a, b, d);
    var g = "application/octet-stream" === a.type,
        h = /constructor/i.test(f.HTMLElement) || f.safari,
        i = /CriOS\/[\d]+/.test(navigator.userAgent);

    if ((i || g && h) && "object" == typeof FileReader) {
      var j = new FileReader();
      j.onloadend = function () {
        var a = j.result;
        a = i ? a : a.replace(/^data:[^;]*;/, "data:attachment/file;"), e ? e.location.href = a : location = a, e = null;
      }, j.readAsDataURL(a);
    } else {
      var k = f.URL || f.webkitURL,
          l = k.createObjectURL(a);
      e ? e.location = l : location.href = l, e = null, setTimeout(function () {
        k.revokeObjectURL(l);
      }, 4E4);
    }
  });
  f.saveAs = a.saveAs = a, "undefined" != "object" && ($oSxG$exports = a);
}); //# sourceMappingURL=FileSaver.min.js.map


// ASSET: ..\..\.parcel-globals\jszip.js
var $ZBU$exports = {};

if ("production" === 'production') {
  $ZBU$exports = JSZip;
} else {
  $ZBU$exports = JSZip;
}

// ASSET: ..\..\.parcel-globals\chart.js.js
var $JsLj$exports = {};

if ("production" === 'production') {
  $JsLj$exports = Chart;
} else {
  $JsLj$exports = Chart;
}

async function $BHXf$export$retryUntilTruthyResult(fn) {
  let wait = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 100;
  let result = fn();

  while (!result) {
    console.log(`Retrying function: ${fn.name} because result was ${result}`);
    result = fn();
    await $BHXf$export$sleep(wait);
  }

  return result;
}

function $BHXf$export$sleep(ms) {
  return new Promise(resolve => setTimeout(resolve, ms));
}

function $BHXf$export$htmlToElement(html) {
  const template = document.createElement('template');
  html = html.trim(); // Never return a text node of whitespace as the result

  template.innerHTML = html;
  return template.content.firstChild;
}

function $BHXf$export$htmlToSVGElement(html) {
  const template = document.createElementNS('http://www.w3.org/2000/svg', 'template');
  html = html.trim(); // Never return a text node of whitespace as the result

  template.innerHTML = html;
  return template.firstElementChild;
}

function $BHXf$export$once(fn, context) {
  var result;
  return function () {
    if (fn) {
      result = fn.apply(context || this, arguments);
      fn = null;
    }

    return result;
  };
}

function $BHXf$export$setAttributes(el, attrs) {
  Object.keys(attrs).forEach(key => el.setAttribute(key, attrs[key]));
}

function $BHXf$export$copyToClipboard(str) {
  const el = document.createElement('textarea');
  el.value = str;
  document.body.appendChild(el);
  el.select();
  document.execCommand('copy');
  document.body.removeChild(el);
}

function $BHXf$export$createRounder(multiple, precision) {
  return value => {
    const roundedValue = Math.round(value / multiple) * multiple;
    const roundedValueFixedPrecision = +roundedValue.toFixed(precision);
    return roundedValueFixedPrecision;
  };
}

function $BHXf$export$roundValue(value, multiple, precision) {
  return $BHXf$export$createRounder(multiple, precision)(value);
}

function $BHXf$export$clampNumber(number, min, max) {
  return Math.max(min, Math.min(number, max));
}

function $BHXf$export$toHHMMSS(seconds) {
  return new Date(seconds * 1000).toISOString().substr(11, 12);
}

function $BHXf$export$toHHMMSSTrimmed(seconds) {
  return $BHXf$export$toHHMMSS(seconds).replace(/(00:)+(.*)/, '$2');
}

const $ZMrW$var$sortX = (a, b) => {
  if (a.x < b.x) return -1;
  if (a.x > b.x) return 1;
  return 0;
};

const $ZMrW$var$lightgrey = opacity => `rgba(120, 120, 120, ${opacity})`;

const $ZMrW$var$medgrey = opacity => `rgba(90, 90, 90, ${opacity})`;

const $ZMrW$var$grey = opacity => `rgba(50, 50, 50, ${opacity})`;

const $ZMrW$export$speedPointRawSecondsFormatter = point => {
  return `T:${point.x.toFixed(2)}\nS:${+point.y.toFixed(2)}`;
};

const $ZMrW$export$cubicInOutTension = 0.6;
const $ZMrW$export$global = {
  defaultColor: 'rgba(255, 255, 255, 1)',
  defaultFontSize: 16,
  defaultFontStyle: 'bold',
  defaultFontColor: $ZMrW$var$lightgrey(1),
  maintainAspectRatio: false,
  hover: {
    mode: 'nearest'
  },
  animation: {
    duration: 0
  }
};
const $ZMrW$var$roundX = $BHXf$export$createRounder(0.1, 1);
const $ZMrW$var$roundY = $BHXf$export$createRounder(0.05, 2);

function $ZMrW$var$getSpeedPointColor(context) {
  var index = context.dataIndex;
  var value = context.dataset.data[index];
  return value.y <= 1 ? `rgba(255, ${100 * value.y}, 100, 0.9)` : `rgba(${130 - 90 * (value.y - 1)}, 100, 245, 0.9)`;
}

function $ZMrW$var$updateSpeedInput(newSpeed) {
  const speedInput = document.getElementById('speed-input');

  if (speedInput) {
    if (newSpeed) speedInput.value = newSpeed.toString();
    speedInput.dispatchEvent(new Event('change'));
  }
}

const $ZMrW$export$options = {
  type: 'scatter',
  data: {
    datasets: [{
      label: 'Speed',
      lineTension: 0,
      data: [],
      showLine: true,
      pointBackgroundColor: $ZMrW$var$getSpeedPointColor,
      pointBorderColor: $ZMrW$var$medgrey(0.7),
      pointRadius: 5,
      pointHoverRadius: 6,
      pointHoverBorderWidth: 1.5,
      pointHoverBorderColor: $ZMrW$var$lightgrey(0.8),
      pointHitRadius: 5
    }]
  },
  options: {
    elements: {
      line: {
        fill: true,
        backgroundColor: 'rgba(160,0, 255, 0.1)',
        borderColor: $ZMrW$var$lightgrey(0.8),
        borderWidth: 2,
        borderDash: [5, 2]
      }
    },
    legend: {
      display: false
    },
    layout: {
      padding: {
        left: 0,
        right: 0,
        top: 15,
        bottom: 0
      }
    },
    tooltips: {
      enabled: false
    },
    scales: {
      xAxes: [{
        scaleLabel: {
          display: true,
          labelString: 'Time (s)',
          fontSize: 12,
          padding: 0
        },
        position: 'bottom',
        gridLines: {
          color: $ZMrW$var$medgrey(0.6),
          lineWidth: 1
        },
        ticks: {
          min: 0,
          max: 10,
          maxTicksLimit: 100,
          autoSkip: false,
          maxRotation: 0,
          minRotation: 0,
          major: {
            fontColor: 'red'
          },
          minor: {}
        }
      }],
      yAxes: [{
        scaleLabel: {
          display: true,
          labelString: 'Speed',
          fontSize: 12,
          padding: 0
        },
        gridLines: {
          color: $ZMrW$var$medgrey(0.6),
          lineWidth: 1
        },
        ticks: {
          stepSize: 0.1,
          min: 0,
          max: 2
        }
      }]
    },
    plugins: {
      datalabels: {
        clip: false,
        clamp: true,
        font: {
          size: 14,
          weight: 'bold'
        },
        textStrokeWidth: 2,
        textStrokeColor: $ZMrW$var$grey(0.9),
        textAlign: 'center',
        formatter: $ZMrW$export$speedPointRawSecondsFormatter,
        display: function display(context) {
          return context.active ? true : 'auto';
        },
        align: function align(context) {
          const index = context.dataIndex; // const value = context.dataset.data[index];

          if (index === 0) {
            return 'right';
          } else if (index === context.dataset.data.length - 1) {
            return 'left';
          } else if (context.dataset.data[context.dataIndex].y > 1.85) {
            return 'start';
          } else {
            return 'end';
          }
        },
        color: $ZMrW$var$getSpeedPointColor
      },
      zoom: {
        pan: {
          enabled: true,
          mode: 'x',
          rangeMin: {
            x: 0,
            y: 0
          },
          rangeMax: {
            x: 10,
            y: 2
          }
        },
        zoom: {
          enabled: true,
          mode: 'x',
          drag: false,
          speed: 0.1,
          rangeMin: {
            x: 0,
            y: 0
          },
          rangeMax: {
            x: 10,
            y: 2
          }
        }
      }
    },
    annotation: {
      drawTime: 'afterDraw',
      annotations: [{
        label: 'time',
        type: 'line',
        mode: 'vertical',
        scaleID: 'x-axis-1',
        value: 2,
        borderColor: 'rgba(255, 0, 0, 0.9)',
        borderWidth: 1
      }, {
        label: 'start',
        type: 'line',
        display: true,
        mode: 'vertical',
        scaleID: 'x-axis-1',
        value: -1,
        borderColor: 'rgba(0, 255, 0, 0.9)',
        borderWidth: 1
      }, {
        label: 'end',
        type: 'line',
        display: true,
        mode: 'vertical',
        scaleID: 'x-axis-1',
        value: -1,
        borderColor: 'rgba(255, 215, 0, 0.9)',
        borderWidth: 1
      }]
    },
    onHover: (event, chartElement) => {
      event.target.style.cursor = chartElement[0] ? 'grab' : 'default';
    },
    dragData: true,
    dragY: true,
    dragX: true,
    dragDataRound: 0.5,
    dragDataRoundMultipleX: 0.05,
    dragDataRoundPrecisionX: 2,
    dragDataRoundMultipleY: 0.05,
    dragDataRoundPrecisionY: 2,
    dragDataSort: false,
    dragDataSortFunction: $ZMrW$var$sortX,
    onDragStart: function onDragStart(e, chartInstance, element) {
      // console.log(e, element);
      chartInstance.options.plugins.zoom.pan.enabled = false;
      event.target.style.cursor = 'grabbing';
      chartInstance.update();
    },
    onDrag: function onDrag(e, chartInstance, datasetIndex, index, fromValue, toValue) {
      // console.log(datasetIndex, index, fromValue, toValue);
      const shouldDrag = {
        dragX: true,
        dragY: true
      };
      let speedChartMinBound = chartInstance.options.scales.xAxes[0].ticks.min;
      let speedChartMaxBound = chartInstance.options.scales.xAxes[0].ticks.max;

      if (fromValue.x <= speedChartMinBound || fromValue.x >= speedChartMaxBound || toValue.x <= speedChartMinBound || toValue.x >= speedChartMaxBound) {
        shouldDrag.dragX = false;
      }

      if (toValue.y < 0.05 || toValue.y > 2) {
        shouldDrag.dragY = false;
      }

      return shouldDrag;
    },
    onDragEnd: function onDragEnd(e, chartInstance, datasetIndex, index, value) {
      // console.log(datasetIndex, index, value);
      if (index === 0) {
        $ZMrW$var$updateSpeedInput(value.y);
      } else {
        $ZMrW$var$updateSpeedInput();
      }

      chartInstance.data.datasets[datasetIndex].data.sort($ZMrW$var$sortX);
      chartInstance.options.plugins.zoom.pan.enabled = true;
      event.target.style.cursor = 'default';
      chartInstance.update({
        duration: 0
      });
    },
    onClick: function onClick(event, dataAtClick) {
      if (event.button === 0 && !event.ctrlKey && !event.altKey && event.shiftKey && dataAtClick.length === 0) {
        // console.log(element, dataAtClick);
        let valueX, valueY;
        valueX = this.scales['x-axis-1'].getValueForPixel(event.offsetX);
        valueY = this.scales['y-axis-1'].getValueForPixel(event.offsetY);

        if (valueX && valueY) {
          valueX = $ZMrW$var$roundX(valueX);
          valueY = $ZMrW$var$roundY(valueY);
          this.data.datasets[0].data.push({
            x: valueX,
            y: valueY
          });
          this.data.datasets[0].data.sort($ZMrW$var$sortX);
          $ZMrW$var$updateSpeedInput();
          this.update();
        }
      }

      if (event.button === 0 && !event.ctrlKey && event.altKey && event.shiftKey && dataAtClick.length === 1) {
        const datum = dataAtClick[0];

        if (datum) {
          const datasetIndex = datum['_datasetIndex'];
          const index = datum['_index'];
          let speedChartMinBound = this.options.scales.xAxes[0].ticks.min;
          let speedChartMaxBound = this.options.scales.xAxes[0].ticks.max;
          let dataRef = this.data.datasets[datasetIndex].data;

          if (dataRef[index].x !== speedChartMinBound && dataRef[index].x !== speedChartMaxBound) {
            dataRef.splice(index, 1);
            $ZMrW$var$updateSpeedInput();
            this.update();
          }
        }
      }

      if (event.ctrlKey && !event.altKey && !event.shiftKey) {
        this.resetZoom();
      }
    }
  }
};
var $SYAW$var$noop = {
  value: function () {}
};

function $SYAW$export$default() {
  for (var i = 0, n = arguments.length, _ = {}, t; i < n; ++i) {
    if (!(t = arguments[i] + "") || t in _) throw new Error("illegal type: " + t);
    _[t] = [];
  }

  return new $SYAW$var$Dispatch(_);
}

function $SYAW$var$Dispatch(_) {
  this._ = _;
}

function $SYAW$var$parseTypenames(typenames, types) {
  return typenames.trim().split(/^|\s+/).map(function (t) {
    var name = "",
        i = t.indexOf(".");
    if (i >= 0) name = t.slice(i + 1), t = t.slice(0, i);
    if (t && !types.hasOwnProperty(t)) throw new Error("unknown type: " + t);
    return {
      type: t,
      name: name
    };
  });
}

$SYAW$var$Dispatch.prototype = $SYAW$export$default.prototype = {
  constructor: $SYAW$var$Dispatch,
  on: function (typename, callback) {
    var _ = this._,
        T = $SYAW$var$parseTypenames(typename + "", _),
        t,
        i = -1,
        n = T.length; // If no callback was specified, return the callback of the given type and name.

    if (arguments.length < 2) {
      while (++i < n) if ((t = (typename = T[i]).type) && (t = $SYAW$var$get(_[t], typename.name))) return t;

      return;
    } // If a type was specified, set the callback for the given type and name.
    // Otherwise, if a null callback was specified, remove callbacks of the given name.


    if (callback != null && typeof callback !== "function") throw new Error("invalid callback: " + callback);

    while (++i < n) {
      if (t = (typename = T[i]).type) _[t] = $SYAW$var$set(_[t], typename.name, callback);else if (callback == null) for (t in _) _[t] = $SYAW$var$set(_[t], typename.name, null);
    }

    return this;
  },
  copy: function () {
    var copy = {},
        _ = this._;

    for (var t in _) copy[t] = _[t].slice();

    return new $SYAW$var$Dispatch(copy);
  },
  call: function (type, that) {
    if ((n = arguments.length - 2) > 0) for (var args = new Array(n), i = 0, n, t; i < n; ++i) args[i] = arguments[i + 2];
    if (!this._.hasOwnProperty(type)) throw new Error("unknown type: " + type);

    for (t = this._[type], i = 0, n = t.length; i < n; ++i) t[i].value.apply(that, args);
  },
  apply: function (type, that, args) {
    if (!this._.hasOwnProperty(type)) throw new Error("unknown type: " + type);

    for (var t = this._[type], i = 0, n = t.length; i < n; ++i) t[i].value.apply(that, args);
  }
};

function $SYAW$var$get(type, name) {
  for (var i = 0, n = type.length, c; i < n; ++i) {
    if ((c = type[i]).name === name) {
      return c.value;
    }
  }
}

function $SYAW$var$set(type, name, callback) {
  for (var i = 0, n = type.length; i < n; ++i) {
    if (type[i].name === name) {
      type[i] = $SYAW$var$noop, type = type.slice(0, i).concat(type.slice(i + 1));
      break;
    }
  }

  if (callback != null) type.push({
    name: name,
    value: callback
  });
  return type;
}

var $TZ6S$export$xhtml = "http://www.w3.org/1999/xhtml";
var $TZ6S$export$default = {
  svg: "http://www.w3.org/2000/svg",
  xhtml: $TZ6S$export$xhtml,
  xlink: "http://www.w3.org/1999/xlink",
  xml: "http://www.w3.org/XML/1998/namespace",
  xmlns: "http://www.w3.org/2000/xmlns/"
};

var $Sva1$export$default = function (name) {
  var prefix = name += "",
      i = prefix.indexOf(":");
  if (i >= 0 && (prefix = name.slice(0, i)) !== "xmlns") name = name.slice(i + 1);
  return $TZ6S$export$default.hasOwnProperty(prefix) ? {
    space: $TZ6S$export$default[prefix],
    local: name
  } : name;
};

function $vTHN$var$creatorInherit(name) {
  return function () {
    var document = this.ownerDocument,
        uri = this.namespaceURI;
    return uri === $TZ6S$export$xhtml && document.documentElement.namespaceURI === $TZ6S$export$xhtml ? document.createElement(name) : document.createElementNS(uri, name);
  };
}

function $vTHN$var$creatorFixed(fullname) {
  return function () {
    return this.ownerDocument.createElementNS(fullname.space, fullname.local);
  };
}

var $vTHN$export$default = function (name) {
  var fullname = $Sva1$export$default(name);
  return (fullname.local ? $vTHN$var$creatorFixed : $vTHN$var$creatorInherit)(fullname);
};

function $hX8m$var$none() {}

var $hX8m$export$default = function (selector) {
  return selector == null ? $hX8m$var$none : function () {
    return this.querySelector(selector);
  };
};

var $um0S$export$default = function (select) {
  if (typeof select !== "function") select = $hX8m$export$default(select);

  for (var groups = this._groups, m = groups.length, subgroups = new Array(m), j = 0; j < m; ++j) {
    for (var group = groups[j], n = group.length, subgroup = subgroups[j] = new Array(n), node, subnode, i = 0; i < n; ++i) {
      if ((node = group[i]) && (subnode = select.call(node, node.__data__, i, group))) {
        if ("__data__" in node) subnode.__data__ = node.__data__;
        subgroup[i] = subnode;
      }
    }
  }

  return new $Pd8D$export$Selection(subgroups, this._parents);
};

function $sRe$var$empty() {
  return [];
}

var $sRe$export$default = function (selector) {
  return selector == null ? $sRe$var$empty : function () {
    return this.querySelectorAll(selector);
  };
};

var $EUV$export$default = function (select) {
  if (typeof select !== "function") select = $sRe$export$default(select);

  for (var groups = this._groups, m = groups.length, subgroups = [], parents = [], j = 0; j < m; ++j) {
    for (var group = groups[j], n = group.length, node, i = 0; i < n; ++i) {
      if (node = group[i]) {
        subgroups.push(select.call(node, node.__data__, i, group));
        parents.push(node);
      }
    }
  }

  return new $Pd8D$export$Selection(subgroups, parents);
};

var $LbZ4$export$default = function (selector) {
  return function () {
    return this.matches(selector);
  };
};

var $hZf9$export$default = function (match) {
  if (typeof match !== "function") match = $LbZ4$export$default(match);

  for (var groups = this._groups, m = groups.length, subgroups = new Array(m), j = 0; j < m; ++j) {
    for (var group = groups[j], n = group.length, subgroup = subgroups[j] = [], node, i = 0; i < n; ++i) {
      if ((node = group[i]) && match.call(node, node.__data__, i, group)) {
        subgroup.push(node);
      }
    }
  }

  return new $Pd8D$export$Selection(subgroups, this._parents);
};

var $NxKh$export$default = function (update) {
  return new Array(update.length);
};

var $BlDg$export$default = function () {
  return new $Pd8D$export$Selection(this._enter || this._groups.map($NxKh$export$default), this._parents);
};

function $BlDg$export$EnterNode(parent, datum) {
  this.ownerDocument = parent.ownerDocument;
  this.namespaceURI = parent.namespaceURI;
  this._next = null;
  this._parent = parent;
  this.__data__ = datum;
}

$BlDg$export$EnterNode.prototype = {
  constructor: $BlDg$export$EnterNode,
  appendChild: function (child) {
    return this._parent.insertBefore(child, this._next);
  },
  insertBefore: function (child, next) {
    return this._parent.insertBefore(child, next);
  },
  querySelector: function (selector) {
    return this._parent.querySelector(selector);
  },
  querySelectorAll: function (selector) {
    return this._parent.querySelectorAll(selector);
  }
};

var $QcpV$export$default = function (x) {
  return function () {
    return x;
  };
};

var $U33$var$keyPrefix = "$"; // Protect against keys like “__proto__”.

function $U33$var$bindIndex(parent, group, enter, update, exit, data) {
  var i = 0,
      node,
      groupLength = group.length,
      dataLength = data.length; // Put any non-null nodes that fit into update.
  // Put any null nodes into enter.
  // Put any remaining data into enter.

  for (; i < dataLength; ++i) {
    if (node = group[i]) {
      node.__data__ = data[i];
      update[i] = node;
    } else {
      enter[i] = new $BlDg$export$EnterNode(parent, data[i]);
    }
  } // Put any non-null nodes that don’t fit into exit.


  for (; i < groupLength; ++i) {
    if (node = group[i]) {
      exit[i] = node;
    }
  }
}

function $U33$var$bindKey(parent, group, enter, update, exit, data, key) {
  var i,
      node,
      nodeByKeyValue = {},
      groupLength = group.length,
      dataLength = data.length,
      keyValues = new Array(groupLength),
      keyValue; // Compute the key for each node.
  // If multiple nodes have the same key, the duplicates are added to exit.

  for (i = 0; i < groupLength; ++i) {
    if (node = group[i]) {
      keyValues[i] = keyValue = $U33$var$keyPrefix + key.call(node, node.__data__, i, group);

      if (keyValue in nodeByKeyValue) {
        exit[i] = node;
      } else {
        nodeByKeyValue[keyValue] = node;
      }
    }
  } // Compute the key for each datum.
  // If there a node associated with this key, join and add it to update.
  // If there is not (or the key is a duplicate), add it to enter.


  for (i = 0; i < dataLength; ++i) {
    keyValue = $U33$var$keyPrefix + key.call(parent, data[i], i, data);

    if (node = nodeByKeyValue[keyValue]) {
      update[i] = node;
      node.__data__ = data[i];
      nodeByKeyValue[keyValue] = null;
    } else {
      enter[i] = new $BlDg$export$EnterNode(parent, data[i]);
    }
  } // Add any remaining nodes that were not bound to data to exit.


  for (i = 0; i < groupLength; ++i) {
    if ((node = group[i]) && nodeByKeyValue[keyValues[i]] === node) {
      exit[i] = node;
    }
  }
}

var $U33$export$default = function (value, key) {
  if (!value) {
    data = new Array(this.size()), j = -1;
    this.each(function (d) {
      data[++j] = d;
    });
    return data;
  }

  var bind = key ? $U33$var$bindKey : $U33$var$bindIndex,
      parents = this._parents,
      groups = this._groups;
  if (typeof value !== "function") value = $QcpV$export$default(value);

  for (var m = groups.length, update = new Array(m), enter = new Array(m), exit = new Array(m), j = 0; j < m; ++j) {
    var parent = parents[j],
        group = groups[j],
        groupLength = group.length,
        data = value.call(parent, parent && parent.__data__, j, parents),
        dataLength = data.length,
        enterGroup = enter[j] = new Array(dataLength),
        updateGroup = update[j] = new Array(dataLength),
        exitGroup = exit[j] = new Array(groupLength);
    bind(parent, group, enterGroup, updateGroup, exitGroup, data, key); // Now connect the enter nodes to their following update node, such that
    // appendChild can insert the materialized enter node before this node,
    // rather than at the end of the parent node.

    for (var i0 = 0, i1 = 0, previous, next; i0 < dataLength; ++i0) {
      if (previous = enterGroup[i0]) {
        if (i0 >= i1) i1 = i0 + 1;

        while (!(next = updateGroup[i1]) && ++i1 < dataLength);

        previous._next = next || null;
      }
    }
  }

  update = new $Pd8D$export$Selection(update, parents);
  update._enter = enter;
  update._exit = exit;
  return update;
};

var $uwqx$export$default = function () {
  return new $Pd8D$export$Selection(this._exit || this._groups.map($NxKh$export$default), this._parents);
};

var $tBqC$export$default = function (onenter, onupdate, onexit) {
  var enter = this.enter(),
      update = this,
      exit = this.exit();
  enter = typeof onenter === "function" ? onenter(enter) : enter.append(onenter + "");
  if (onupdate != null) update = onupdate(update);
  if (onexit == null) exit.remove();else onexit(exit);
  return enter && update ? enter.merge(update).order() : update;
};

var $oMRz$export$default = function (selection) {
  for (var groups0 = this._groups, groups1 = selection._groups, m0 = groups0.length, m1 = groups1.length, m = Math.min(m0, m1), merges = new Array(m0), j = 0; j < m; ++j) {
    for (var group0 = groups0[j], group1 = groups1[j], n = group0.length, merge = merges[j] = new Array(n), node, i = 0; i < n; ++i) {
      if (node = group0[i] || group1[i]) {
        merge[i] = node;
      }
    }
  }

  for (; j < m0; ++j) {
    merges[j] = groups0[j];
  }

  return new $Pd8D$export$Selection(merges, this._parents);
};

var $DTXT$export$default = function () {
  for (var groups = this._groups, j = -1, m = groups.length; ++j < m;) {
    for (var group = groups[j], i = group.length - 1, next = group[i], node; --i >= 0;) {
      if (node = group[i]) {
        if (next && node.compareDocumentPosition(next) ^ 4) next.parentNode.insertBefore(node, next);
        next = node;
      }
    }
  }

  return this;
};

var $Eo98$export$default = function (compare) {
  if (!compare) compare = $Eo98$var$ascending;

  function compareNode(a, b) {
    return a && b ? compare(a.__data__, b.__data__) : !a - !b;
  }

  for (var groups = this._groups, m = groups.length, sortgroups = new Array(m), j = 0; j < m; ++j) {
    for (var group = groups[j], n = group.length, sortgroup = sortgroups[j] = new Array(n), node, i = 0; i < n; ++i) {
      if (node = group[i]) {
        sortgroup[i] = node;
      }
    }

    sortgroup.sort(compareNode);
  }

  return new $Pd8D$export$Selection(sortgroups, this._parents).order();
};

function $Eo98$var$ascending(a, b) {
  return a < b ? -1 : a > b ? 1 : a >= b ? 0 : NaN;
}

var $del$export$default = function () {
  var callback = arguments[0];
  arguments[0] = this;
  callback.apply(null, arguments);
  return this;
};

var $RJvN$export$default = function () {
  var nodes = new Array(this.size()),
      i = -1;
  this.each(function () {
    nodes[++i] = this;
  });
  return nodes;
};

var $P0$export$default = function () {
  for (var groups = this._groups, j = 0, m = groups.length; j < m; ++j) {
    for (var group = groups[j], i = 0, n = group.length; i < n; ++i) {
      var node = group[i];
      if (node) return node;
    }
  }

  return null;
};

var $cLfp$export$default = function () {
  var size = 0;
  this.each(function () {
    ++size;
  });
  return size;
};

var $fLE2$export$default = function () {
  return !this.node();
};

var $Ex0s$export$default = function (callback) {
  for (var groups = this._groups, j = 0, m = groups.length; j < m; ++j) {
    for (var group = groups[j], i = 0, n = group.length, node; i < n; ++i) {
      if (node = group[i]) callback.call(node, node.__data__, i, group);
    }
  }

  return this;
};

function $kqGm$var$attrRemove(name) {
  return function () {
    this.removeAttribute(name);
  };
}

function $kqGm$var$attrRemoveNS(fullname) {
  return function () {
    this.removeAttributeNS(fullname.space, fullname.local);
  };
}

function $kqGm$var$attrConstant(name, value) {
  return function () {
    this.setAttribute(name, value);
  };
}

function $kqGm$var$attrConstantNS(fullname, value) {
  return function () {
    this.setAttributeNS(fullname.space, fullname.local, value);
  };
}

function $kqGm$var$attrFunction(name, value) {
  return function () {
    var v = value.apply(this, arguments);
    if (v == null) this.removeAttribute(name);else this.setAttribute(name, v);
  };
}

function $kqGm$var$attrFunctionNS(fullname, value) {
  return function () {
    var v = value.apply(this, arguments);
    if (v == null) this.removeAttributeNS(fullname.space, fullname.local);else this.setAttributeNS(fullname.space, fullname.local, v);
  };
}

var $kqGm$export$default = function (name, value) {
  var fullname = $Sva1$export$default(name);

  if (arguments.length < 2) {
    var node = this.node();
    return fullname.local ? node.getAttributeNS(fullname.space, fullname.local) : node.getAttribute(fullname);
  }

  return this.each((value == null ? fullname.local ? $kqGm$var$attrRemoveNS : $kqGm$var$attrRemove : typeof value === "function" ? fullname.local ? $kqGm$var$attrFunctionNS : $kqGm$var$attrFunction : fullname.local ? $kqGm$var$attrConstantNS : $kqGm$var$attrConstant)(fullname, value));
};

var $a7iO$export$default = function (node) {
  return node.ownerDocument && node.ownerDocument.defaultView || // node is a Node
  node.document && node // node is a Window
  || node.defaultView; // node is a Document
};

function $PNU$var$styleRemove(name) {
  return function () {
    this.style.removeProperty(name);
  };
}

function $PNU$var$styleConstant(name, value, priority) {
  return function () {
    this.style.setProperty(name, value, priority);
  };
}

function $PNU$var$styleFunction(name, value, priority) {
  return function () {
    var v = value.apply(this, arguments);
    if (v == null) this.style.removeProperty(name);else this.style.setProperty(name, v, priority);
  };
}

var $PNU$export$default = function (name, value, priority) {
  return arguments.length > 1 ? this.each((value == null ? $PNU$var$styleRemove : typeof value === "function" ? $PNU$var$styleFunction : $PNU$var$styleConstant)(name, value, priority == null ? "" : priority)) : $PNU$export$styleValue(this.node(), name);
};

function $PNU$export$styleValue(node, name) {
  return node.style.getPropertyValue(name) || $a7iO$export$default(node).getComputedStyle(node, null).getPropertyValue(name);
}

function $Qc7K$var$propertyRemove(name) {
  return function () {
    delete this[name];
  };
}

function $Qc7K$var$propertyConstant(name, value) {
  return function () {
    this[name] = value;
  };
}

function $Qc7K$var$propertyFunction(name, value) {
  return function () {
    var v = value.apply(this, arguments);
    if (v == null) delete this[name];else this[name] = v;
  };
}

var $Qc7K$export$default = function (name, value) {
  return arguments.length > 1 ? this.each((value == null ? $Qc7K$var$propertyRemove : typeof value === "function" ? $Qc7K$var$propertyFunction : $Qc7K$var$propertyConstant)(name, value)) : this.node()[name];
};

function $u8Lo$var$classArray(string) {
  return string.trim().split(/^|\s+/);
}

function $u8Lo$var$classList(node) {
  return node.classList || new $u8Lo$var$ClassList(node);
}

function $u8Lo$var$ClassList(node) {
  this._node = node;
  this._names = $u8Lo$var$classArray(node.getAttribute("class") || "");
}

$u8Lo$var$ClassList.prototype = {
  add: function (name) {
    var i = this._names.indexOf(name);

    if (i < 0) {
      this._names.push(name);

      this._node.setAttribute("class", this._names.join(" "));
    }
  },
  remove: function (name) {
    var i = this._names.indexOf(name);

    if (i >= 0) {
      this._names.splice(i, 1);

      this._node.setAttribute("class", this._names.join(" "));
    }
  },
  contains: function (name) {
    return this._names.indexOf(name) >= 0;
  }
};

function $u8Lo$var$classedAdd(node, names) {
  var list = $u8Lo$var$classList(node),
      i = -1,
      n = names.length;

  while (++i < n) list.add(names[i]);
}

function $u8Lo$var$classedRemove(node, names) {
  var list = $u8Lo$var$classList(node),
      i = -1,
      n = names.length;

  while (++i < n) list.remove(names[i]);
}

function $u8Lo$var$classedTrue(names) {
  return function () {
    $u8Lo$var$classedAdd(this, names);
  };
}

function $u8Lo$var$classedFalse(names) {
  return function () {
    $u8Lo$var$classedRemove(this, names);
  };
}

function $u8Lo$var$classedFunction(names, value) {
  return function () {
    (value.apply(this, arguments) ? $u8Lo$var$classedAdd : $u8Lo$var$classedRemove)(this, names);
  };
}

var $u8Lo$export$default = function (name, value) {
  var names = $u8Lo$var$classArray(name + "");

  if (arguments.length < 2) {
    var list = $u8Lo$var$classList(this.node()),
        i = -1,
        n = names.length;

    while (++i < n) if (!list.contains(names[i])) return false;

    return true;
  }

  return this.each((typeof value === "function" ? $u8Lo$var$classedFunction : value ? $u8Lo$var$classedTrue : $u8Lo$var$classedFalse)(names, value));
};

function $sv0a$var$textRemove() {
  this.textContent = "";
}

function $sv0a$var$textConstant(value) {
  return function () {
    this.textContent = value;
  };
}

function $sv0a$var$textFunction(value) {
  return function () {
    var v = value.apply(this, arguments);
    this.textContent = v == null ? "" : v;
  };
}

var $sv0a$export$default = function (value) {
  return arguments.length ? this.each(value == null ? $sv0a$var$textRemove : (typeof value === "function" ? $sv0a$var$textFunction : $sv0a$var$textConstant)(value)) : this.node().textContent;
};

function $IOHV$var$htmlRemove() {
  this.innerHTML = "";
}

function $IOHV$var$htmlConstant(value) {
  return function () {
    this.innerHTML = value;
  };
}

function $IOHV$var$htmlFunction(value) {
  return function () {
    var v = value.apply(this, arguments);
    this.innerHTML = v == null ? "" : v;
  };
}

var $IOHV$export$default = function (value) {
  return arguments.length ? this.each(value == null ? $IOHV$var$htmlRemove : (typeof value === "function" ? $IOHV$var$htmlFunction : $IOHV$var$htmlConstant)(value)) : this.node().innerHTML;
};

function $Bs6$var$raise() {
  if (this.nextSibling) this.parentNode.appendChild(this);
}

var $Bs6$export$default = function () {
  return this.each($Bs6$var$raise);
};

function $s7fh$var$lower() {
  if (this.previousSibling) this.parentNode.insertBefore(this, this.parentNode.firstChild);
}

var $s7fh$export$default = function () {
  return this.each($s7fh$var$lower);
};

var $XBOq$export$default = function (name) {
  var create = typeof name === "function" ? name : $vTHN$export$default(name);
  return this.select(function () {
    return this.appendChild(create.apply(this, arguments));
  });
};

function $DmT$var$constantNull() {
  return null;
}

var $DmT$export$default = function (name, before) {
  var create = typeof name === "function" ? name : $vTHN$export$default(name),
      select = before == null ? $DmT$var$constantNull : typeof before === "function" ? before : $hX8m$export$default(before);
  return this.select(function () {
    return this.insertBefore(create.apply(this, arguments), select.apply(this, arguments) || null);
  });
};

function $lzg5$var$remove() {
  var parent = this.parentNode;
  if (parent) parent.removeChild(this);
}

var $lzg5$export$default = function () {
  return this.each($lzg5$var$remove);
};

function $lcuA$var$selection_cloneShallow() {
  return this.parentNode.insertBefore(this.cloneNode(false), this.nextSibling);
}

function $lcuA$var$selection_cloneDeep() {
  return this.parentNode.insertBefore(this.cloneNode(true), this.nextSibling);
}

var $lcuA$export$default = function (deep) {
  return this.select(deep ? $lcuA$var$selection_cloneDeep : $lcuA$var$selection_cloneShallow);
};

var $rHST$export$default = function (value) {
  return arguments.length ? this.property("__data__", value) : this.node().__data__;
};

var $tgqN$var$filterEvents = {};
var $tgqN$export$event = null;

if (typeof document !== "undefined") {
  var $tgqN$var$element = document.documentElement;

  if (!("onmouseenter" in $tgqN$var$element)) {
    $tgqN$var$filterEvents = {
      mouseenter: "mouseover",
      mouseleave: "mouseout"
    };
  }
}

function $tgqN$var$filterContextListener(listener, index, group) {
  listener = $tgqN$var$contextListener(listener, index, group);
  return function (event) {
    var related = event.relatedTarget;

    if (!related || related !== this && !(related.compareDocumentPosition(this) & 8)) {
      listener.call(this, event);
    }
  };
}

function $tgqN$var$contextListener(listener, index, group) {
  return function (event1) {
    var event0 = $tgqN$export$event; // Events can be reentrant (e.g., focus).

    $tgqN$export$event = event1;

    try {
      listener.call(this, this.__data__, index, group);
    } finally {
      $tgqN$export$event = event0;
    }
  };
}

function $tgqN$var$parseTypenames(typenames) {
  return typenames.trim().split(/^|\s+/).map(function (t) {
    var name = "",
        i = t.indexOf(".");
    if (i >= 0) name = t.slice(i + 1), t = t.slice(0, i);
    return {
      type: t,
      name: name
    };
  });
}

function $tgqN$var$onRemove(typename) {
  return function () {
    var on = this.__on;
    if (!on) return;

    for (var j = 0, i = -1, m = on.length, o; j < m; ++j) {
      if (o = on[j], (!typename.type || o.type === typename.type) && o.name === typename.name) {
        this.removeEventListener(o.type, o.listener, o.capture);
      } else {
        on[++i] = o;
      }
    }

    if (++i) on.length = i;else delete this.__on;
  };
}

function $tgqN$var$onAdd(typename, value, capture) {
  var wrap = $tgqN$var$filterEvents.hasOwnProperty(typename.type) ? $tgqN$var$filterContextListener : $tgqN$var$contextListener;
  return function (d, i, group) {
    var on = this.__on,
        o,
        listener = wrap(value, i, group);
    if (on) for (var j = 0, m = on.length; j < m; ++j) {
      if ((o = on[j]).type === typename.type && o.name === typename.name) {
        this.removeEventListener(o.type, o.listener, o.capture);
        this.addEventListener(o.type, o.listener = listener, o.capture = capture);
        o.value = value;
        return;
      }
    }
    this.addEventListener(typename.type, listener, capture);
    o = {
      type: typename.type,
      name: typename.name,
      value: value,
      listener: listener,
      capture: capture
    };
    if (!on) this.__on = [o];else on.push(o);
  };
}

var $tgqN$export$default = function (typename, value, capture) {
  var typenames = $tgqN$var$parseTypenames(typename + ""),
      i,
      n = typenames.length,
      t;

  if (arguments.length < 2) {
    var on = this.node().__on;

    if (on) for (var j = 0, m = on.length, o; j < m; ++j) {
      for (i = 0, o = on[j]; i < n; ++i) {
        if ((t = typenames[i]).type === o.type && t.name === o.name) {
          return o.value;
        }
      }
    }
    return;
  }

  on = value ? $tgqN$var$onAdd : $tgqN$var$onRemove;
  if (capture == null) capture = false;

  for (i = 0; i < n; ++i) this.each(on(typenames[i], value, capture));

  return this;
};

function $tgqN$export$customEvent(event1, listener, that, args) {
  var event0 = $tgqN$export$event;
  event1.sourceEvent = $tgqN$export$event;
  $tgqN$export$event = event1;

  try {
    return listener.apply(that, args);
  } finally {
    $tgqN$export$event = event0;
  }
}

function $Sfd$var$dispatchEvent(node, type, params) {
  var window = $a7iO$export$default(node),
      event = window.CustomEvent;

  if (typeof event === "function") {
    event = new event(type, params);
  } else {
    event = window.document.createEvent("Event");
    if (params) event.initEvent(type, params.bubbles, params.cancelable), event.detail = params.detail;else event.initEvent(type, false, false);
  }

  node.dispatchEvent(event);
}

function $Sfd$var$dispatchConstant(type, params) {
  return function () {
    return $Sfd$var$dispatchEvent(this, type, params);
  };
}

function $Sfd$var$dispatchFunction(type, params) {
  return function () {
    return $Sfd$var$dispatchEvent(this, type, params.apply(this, arguments));
  };
}

var $Sfd$export$default = function (type, params) {
  return this.each((typeof params === "function" ? $Sfd$var$dispatchFunction : $Sfd$var$dispatchConstant)(type, params));
};

var $Pd8D$export$root = [null];

function $Pd8D$export$Selection(groups, parents) {
  this._groups = groups;
  this._parents = parents;
}

function $Pd8D$export$default() {
  return new $Pd8D$export$Selection([[document.documentElement]], $Pd8D$export$root);
}

$Pd8D$export$Selection.prototype = $Pd8D$export$default.prototype = {
  constructor: $Pd8D$export$Selection,
  select: $um0S$export$default,
  selectAll: $EUV$export$default,
  filter: $hZf9$export$default,
  data: $U33$export$default,
  enter: $BlDg$export$default,
  exit: $uwqx$export$default,
  join: $tBqC$export$default,
  merge: $oMRz$export$default,
  order: $DTXT$export$default,
  sort: $Eo98$export$default,
  call: $del$export$default,
  nodes: $RJvN$export$default,
  node: $P0$export$default,
  size: $cLfp$export$default,
  empty: $fLE2$export$default,
  each: $Ex0s$export$default,
  attr: $kqGm$export$default,
  style: $PNU$export$default,
  property: $Qc7K$export$default,
  classed: $u8Lo$export$default,
  text: $sv0a$export$default,
  html: $IOHV$export$default,
  raise: $Bs6$export$default,
  lower: $s7fh$export$default,
  append: $XBOq$export$default,
  insert: $DmT$export$default,
  remove: $lzg5$export$default,
  clone: $lcuA$export$default,
  datum: $rHST$export$default,
  on: $tgqN$export$default,
  dispatch: $Sfd$export$default
};

var $SawF$export$default = function (selector) {
  return typeof selector === "string" ? new $Pd8D$export$Selection([[document.querySelector(selector)]], [document.documentElement]) : new $Pd8D$export$Selection([[selector]], $Pd8D$export$root);
};

var $F0n$var$nextId = 0;

function $F0n$export$default() {
  return new $F0n$var$Local();
}

function $F0n$var$Local() {
  this._ = "@" + (++$F0n$var$nextId).toString(36);
}

$F0n$var$Local.prototype = $F0n$export$default.prototype = {
  constructor: $F0n$var$Local,
  get: function (node) {
    var id = this._;

    while (!(id in node)) if (!(node = node.parentNode)) return;

    return node[id];
  },
  set: function (node, value) {
    return node[this._] = value;
  },
  remove: function (node) {
    return this._ in node && delete node[this._];
  },
  toString: function () {
    return this._;
  }
};

var $fcL$export$default = function () {
  var current = $tgqN$export$event,
      source;

  while (source = current.sourceEvent) current = source;

  return current;
};

var $tjFE$export$default = function (node, event) {
  var svg = node.ownerSVGElement || node;

  if (svg.createSVGPoint) {
    var point = svg.createSVGPoint();
    point.x = event.clientX, point.y = event.clientY;
    point = point.matrixTransform(node.getScreenCTM().inverse());
    return [point.x, point.y];
  }

  var rect = node.getBoundingClientRect();
  return [event.clientX - rect.left - node.clientLeft, event.clientY - rect.top - node.clientTop];
};

var $dfC2$export$default = function (node) {
  var event = $fcL$export$default();
  if (event.changedTouches) event = event.changedTouches[0];
  return $tjFE$export$default(node, event);
};

var $XEgr$export$default = function (node, touches, identifier) {
  if (arguments.length < 3) identifier = touches, touches = $fcL$export$default().changedTouches;

  for (var i = 0, n = touches ? touches.length : 0, touch; i < n; ++i) {
    if ((touch = touches[i]).identifier === identifier) {
      return $tjFE$export$default(node, touch);
    }
  }

  return null;
};

function $CQsD$export$nopropagation() {
  $tgqN$export$event.stopImmediatePropagation();
}

var $CQsD$export$default = function () {
  $tgqN$export$event.preventDefault();
  $tgqN$export$event.stopImmediatePropagation();
};

var $JG2T$export$default = function (view) {
  var root = view.document.documentElement,
      selection = $SawF$export$default(view).on("dragstart.drag", $CQsD$export$default, true);

  if ("onselectstart" in root) {
    selection.on("selectstart.drag", $CQsD$export$default, true);
  } else {
    root.__noselect = root.style.MozUserSelect;
    root.style.MozUserSelect = "none";
  }
};

function $JG2T$export$yesdrag(view, noclick) {
  var root = view.document.documentElement,
      selection = $SawF$export$default(view).on("dragstart.drag", null);

  if (noclick) {
    selection.on("click.drag", $CQsD$export$default, true);
    setTimeout(function () {
      selection.on("click.drag", null);
    }, 0);
  }

  if ("onselectstart" in root) {
    selection.on("selectstart.drag", null);
  } else {
    root.style.MozUserSelect = root.__noselect;
    delete root.__noselect;
  }
}

var $l0wH$export$default = function (x) {
  return function () {
    return x;
  };
};

function $Qus$export$default(target, type, subject, id, active, x, y, dx, dy, dispatch) {
  this.target = target;
  this.type = type;
  this.subject = subject;
  this.identifier = id;
  this.active = active;
  this.x = x;
  this.y = y;
  this.dx = dx;
  this.dy = dy;
  this._ = dispatch;
}

$Qus$export$default.prototype.on = function () {
  var value = this._.on.apply(this._, arguments);

  return value === this._ ? this : value;
};

// Ignore right-click, since that should open the context menu.
function $U7NX$var$defaultFilter() {
  return !$tgqN$export$event.button;
}

function $U7NX$var$defaultContainer() {
  return this.parentNode;
}

function $U7NX$var$defaultSubject(d) {
  return d == null ? {
    x: $tgqN$export$event.x,
    y: $tgqN$export$event.y
  } : d;
}

function $U7NX$var$defaultTouchable() {
  return "ontouchstart" in this;
}

var $U7NX$export$default = function () {
  var filter = $U7NX$var$defaultFilter,
      container = $U7NX$var$defaultContainer,
      subject = $U7NX$var$defaultSubject,
      touchable = $U7NX$var$defaultTouchable,
      gestures = {},
      listeners = $SYAW$export$default("start", "drag", "end"),
      active = 0,
      mousedownx,
      mousedowny,
      mousemoving,
      touchending,
      clickDistance2 = 0;

  function drag(selection) {
    selection.on("mousedown.drag", mousedowned).filter(touchable).on("touchstart.drag", touchstarted).on("touchmove.drag", touchmoved).on("touchend.drag touchcancel.drag", touchended).style("touch-action", "none").style("-webkit-tap-highlight-color", "rgba(0,0,0,0)");
  }

  function mousedowned() {
    if (touchending || !filter.apply(this, arguments)) return;
    var gesture = beforestart("mouse", container.apply(this, arguments), $dfC2$export$default, this, arguments);
    if (!gesture) return;
    $SawF$export$default($tgqN$export$event.view).on("mousemove.drag", mousemoved, true).on("mouseup.drag", mouseupped, true);
    $JG2T$export$default($tgqN$export$event.view);
    $CQsD$export$nopropagation();
    mousemoving = false;
    mousedownx = $tgqN$export$event.clientX;
    mousedowny = $tgqN$export$event.clientY;
    gesture("start");
  }

  function mousemoved() {
    $CQsD$export$default();

    if (!mousemoving) {
      var dx = $tgqN$export$event.clientX - mousedownx,
          dy = $tgqN$export$event.clientY - mousedowny;
      mousemoving = dx * dx + dy * dy > clickDistance2;
    }

    gestures.mouse("drag");
  }

  function mouseupped() {
    $SawF$export$default($tgqN$export$event.view).on("mousemove.drag mouseup.drag", null);
    $JG2T$export$yesdrag($tgqN$export$event.view, mousemoving);
    $CQsD$export$default();
    gestures.mouse("end");
  }

  function touchstarted() {
    if (!filter.apply(this, arguments)) return;
    var touches = $tgqN$export$event.changedTouches,
        c = container.apply(this, arguments),
        n = touches.length,
        i,
        gesture;

    for (i = 0; i < n; ++i) {
      if (gesture = beforestart(touches[i].identifier, c, $XEgr$export$default, this, arguments)) {
        $CQsD$export$nopropagation();
        gesture("start");
      }
    }
  }

  function touchmoved() {
    var touches = $tgqN$export$event.changedTouches,
        n = touches.length,
        i,
        gesture;

    for (i = 0; i < n; ++i) {
      if (gesture = gestures[touches[i].identifier]) {
        $CQsD$export$default();
        gesture("drag");
      }
    }
  }

  function touchended() {
    var touches = $tgqN$export$event.changedTouches,
        n = touches.length,
        i,
        gesture;
    if (touchending) clearTimeout(touchending);
    touchending = setTimeout(function () {
      touchending = null;
    }, 500); // Ghost clicks are delayed!

    for (i = 0; i < n; ++i) {
      if (gesture = gestures[touches[i].identifier]) {
        $CQsD$export$nopropagation();
        gesture("end");
      }
    }
  }

  function beforestart(id, container, point, that, args) {
    var p = point(container, id),
        s,
        dx,
        dy,
        sublisteners = listeners.copy();
    if (!$tgqN$export$customEvent(new $Qus$export$default(drag, "beforestart", s, id, active, p[0], p[1], 0, 0, sublisteners), function () {
      if (($tgqN$export$event.subject = s = subject.apply(that, args)) == null) return false;
      dx = s.x - p[0] || 0;
      dy = s.y - p[1] || 0;
      return true;
    })) return;
    return function gesture(type) {
      var p0 = p,
          n;

      switch (type) {
        case "start":
          gestures[id] = gesture, n = active++;
          break;

        case "end":
          delete gestures[id], --active;
        // nobreak

        case "drag":
          p = point(container, id), n = active;
          break;
      }

      $tgqN$export$customEvent(new $Qus$export$default(drag, type, s, id, n, p[0] + dx, p[1] + dy, p[0] - p0[0], p[1] - p0[1], sublisteners), sublisteners.apply, sublisteners, [type, that, args]);
    };
  }

  drag.filter = function (_) {
    return arguments.length ? (filter = typeof _ === "function" ? _ : $l0wH$export$default(!!_), drag) : filter;
  };

  drag.container = function (_) {
    return arguments.length ? (container = typeof _ === "function" ? _ : $l0wH$export$default(_), drag) : container;
  };

  drag.subject = function (_) {
    return arguments.length ? (subject = typeof _ === "function" ? _ : $l0wH$export$default(_), drag) : subject;
  };

  drag.touchable = function (_) {
    return arguments.length ? (touchable = typeof _ === "function" ? _ : $l0wH$export$default(!!_), drag) : touchable;
  };

  drag.on = function () {
    var value = listeners.on.apply(listeners, arguments);
    return value === listeners ? drag : value;
  };

  drag.clickDistance = function (_) {
    return arguments.length ? (clickDistance2 = (_ = +_) * _, drag) : Math.sqrt(clickDistance2);
  };

  return drag;
};

let $ScEs$var$element, $ScEs$var$scale, $ScEs$var$scaleX, $ScEs$var$radar;

function $ScEs$var$getElement(chartInstance, callback) {
  return () => {
    if ($tgqN$export$event) {
      const e = $tgqN$export$event.sourceEvent;
      $ScEs$var$element = chartInstance.getElementAtEvent(e)[0];
      $ScEs$var$radar = chartInstance.config.type == 'radar';
      let scaleName = $ScEs$var$radar ? '_scale' : '_yScale';

      if ($ScEs$var$element) {
        if (chartInstance.data.datasets[$ScEs$var$element['_datasetIndex']].dragData === false || $ScEs$var$element[scaleName].options.dragData === false) {
          $ScEs$var$element = null;
          return;
        }

        $ScEs$var$scale = $ScEs$var$element[scaleName].id;

        if ($ScEs$var$element['_xScale']) {
          $ScEs$var$scaleX = $ScEs$var$element['_xScale'].id;
        }

        if (typeof callback === 'function' && $ScEs$var$element) {
          if (callback(e, chartInstance, $ScEs$var$element) === false) {
            $ScEs$var$element = null;
          }
        }
      }
    }
  };
}

function $ScEs$export$createRounder(multiple, precision) {
  return value => {
    const roundedValue = Math.round(value / multiple) * multiple;
    const roundedValueFixedPrecision = +roundedValue.toFixed(precision);
    return roundedValueFixedPrecision;
  };
}

function $ScEs$export$roundValue(value, multiple, precision) {
  return $ScEs$export$createRounder(multiple, precision)(value);
}

function $ScEs$var$updateData(chartInstance, callback) {
  return () => {
    if ($ScEs$var$element && $tgqN$export$event) {
      const e = $tgqN$export$event.sourceEvent;
      const datasetIndex = $ScEs$var$element['_datasetIndex'];
      const index = $ScEs$var$element['_index'];
      const roundMultipleX = chartInstance.options.dragDataRoundMultipleX;
      const roundPrecisionX = chartInstance.options.dragDataRoundPrecisionX;
      const roundMultipleY = chartInstance.options.dragDataRoundMultipleY;
      const roundPrecisionY = chartInstance.options.dragDataRoundPrecisionY;
      const roundX = $ScEs$export$createRounder(roundMultipleX, roundPrecisionX);
      const roundY = $ScEs$export$createRounder(roundMultipleY, roundPrecisionY);
      let x;
      let y;
      const dataRef = chartInstance.data.datasets[datasetIndex].data;
      let datumRef = dataRef[index];
      let proposedDatum = {
        x: datumRef.x,
        y: datumRef.y
      };

      if ($ScEs$var$radar) {
        let v;

        if (e.touches) {
          x = e.touches[0].clientX - chartInstance.canvas.getBoundingClientRect().left;
          y = e.touches[0].clientY - chartInstance.canvas.getBoundingClientRect().top;
        } else {
          x = e.clientX - chartInstance.canvas.getBoundingClientRect().left;
          y = e.clientY - chartInstance.canvas.getBoundingClientRect().top;
        }

        let rScale = chartInstance.scales[$ScEs$var$scale];
        let d = Math.sqrt(Math.pow(x - rScale.xCenter, 2) + Math.pow(y - rScale.yCenter, 2));
        let scalingFactor = rScale.drawingArea / (rScale.max - rScale.min);

        if (rScale.options.ticks.reverse) {
          v = rScale.max - d / scalingFactor;
        } else {
          v = rScale.min + d / scalingFactor;
        }

        v = $ScEs$export$roundValue(chartInstance.options.dragDataRound, 2)(v);
        v = Math.min(v, chartInstance.scale.max);
        v = Math.max(v, chartInstance.scale.min);
        proposedDatum = v;
      } else {
        if (e.touches) {
          x = chartInstance.scales[$ScEs$var$scaleX].getValueForPixel(e.touches[0].clientX - chartInstance.canvas.getBoundingClientRect().left);
          y = chartInstance.scales[$ScEs$var$scale].getValueForPixel(e.touches[0].clientY - chartInstance.canvas.getBoundingClientRect().top);
        } else {
          x = chartInstance.scales[$ScEs$var$scaleX].getValueForPixel(e.clientX - chartInstance.canvas.getBoundingClientRect().left);
          y = chartInstance.scales[$ScEs$var$scale].getValueForPixel(e.clientY - chartInstance.canvas.getBoundingClientRect().top);
        }

        x = roundX(x);
        y = roundY(y);
        x = Math.min(x, chartInstance.scales[$ScEs$var$scaleX].max);
        x = Math.max(x, chartInstance.scales[$ScEs$var$scaleX].min);
        y = Math.min(y, chartInstance.scales[$ScEs$var$scale].max);
        y = Math.max(y, chartInstance.scales[$ScEs$var$scale].min);
        proposedDatum.x = x;

        if (datumRef.y !== undefined) {
          proposedDatum.y = y;
        } else {
          proposedDatum = y;
        }
      }

      let shouldChartUpdateX = chartInstance.options.dragX && datumRef.x !== undefined;
      let shouldChartUpdateY = chartInstance.options.dragY;
      let shouldChartUpdate;

      if (typeof callback === 'function') {
        shouldChartUpdate = callback(e, chartInstance, datasetIndex, index, datumRef, proposedDatum);
        shouldChartUpdateX = shouldChartUpdateX && shouldChartUpdate.dragX;
        shouldChartUpdateY = shouldChartUpdateY && shouldChartUpdate.dragY;
      }

      if (shouldChartUpdateX !== false) {
        datumRef.x = proposedDatum.x;
      }

      if (shouldChartUpdateY !== false) {
        if (datumRef.y !== undefined) {
          datumRef.y = proposedDatum.y;
        } else {
          datumRef = proposedDatum;
        }
      }

      if (shouldChartUpdateX !== false || shouldChartUpdateY !== false) {
        chartInstance.update(0);
      }
    }
  };
}

function $ScEs$var$dragEndCallback(chartInstance, callback) {
  return () => {
    if (typeof callback === 'function' && $ScEs$var$element) {
      const e = $tgqN$export$event.sourceEvent;
      const datasetIndex = $ScEs$var$element['_datasetIndex'];
      const index = $ScEs$var$element['_index'];
      const value = chartInstance.data.datasets[datasetIndex].data[index];
      return callback(e, chartInstance, datasetIndex, index, value);
    }
  };
}

const $ScEs$export$default = {
  afterInit: function afterInit(chartInstance) {
    if (chartInstance.options.dragData) {
      $SawF$export$default(chartInstance.chart.canvas).call($U7NX$export$default().container(chartInstance.chart.canvas).on('start', $ScEs$var$getElement(chartInstance, chartInstance.options.onDragStart)).on('drag', $ScEs$var$updateData(chartInstance, chartInstance.options.onDrag)).on('end', $ScEs$var$dragEndCallback(chartInstance, chartInstance.options.onDragEnd)));
    }
  }
};
var $JsLj$$interop$default = $parcel$interopDefault($JsLj$exports);
$JsLj$$interop$default.d.pluginService.register($ScEs$export$default);

function $ATE0$export$cubicInOut(t) {
  return ((t *= 2) <= 1 ? t * t * t : (t -= 2) * t * t + 2) / 2;
}

var $xJOL$var$exponent = 3;

var $xJOL$export$polyIn = function custom(e) {
  e = +e;

  function polyIn(t) {
    return Math.pow(t, e);
  }

  polyIn.exponent = custom;
  return polyIn;
}($xJOL$var$exponent);

var $xJOL$export$polyOut = function custom(e) {
  e = +e;

  function polyOut(t) {
    return 1 - Math.pow(1 - t, e);
  }

  polyOut.exponent = custom;
  return polyOut;
}($xJOL$var$exponent);

var $xJOL$export$polyInOut = function custom(e) {
  e = +e;

  function polyInOut(t) {
    return ((t *= 2) <= 1 ? Math.pow(t, e) : 2 - Math.pow(2 - t, e)) / 2;
  }

  polyInOut.exponent = custom;
  return polyInOut;
}($xJOL$var$exponent);

var $aKKp$var$pi = Math.PI;
var $N$var$overshoot = 1.70158;

var $N$export$backIn = function custom(s) {
  s = +s;

  function backIn(t) {
    return t * t * ((s + 1) * t - s);
  }

  backIn.overshoot = custom;
  return backIn;
}($N$var$overshoot);

var $N$export$backOut = function custom(s) {
  s = +s;

  function backOut(t) {
    return --t * t * ((s + 1) * t + s) + 1;
  }

  backOut.overshoot = custom;
  return backOut;
}($N$var$overshoot);

var $N$export$backInOut = function custom(s) {
  s = +s;

  function backInOut(t) {
    return ((t *= 2) < 1 ? t * t * ((s + 1) * t - s) : (t -= 2) * t * ((s + 1) * t + s) + 2) / 2;
  }

  backInOut.overshoot = custom;
  return backInOut;
}($N$var$overshoot);

var $ZzFe$var$tau = 2 * Math.PI,
    $ZzFe$var$amplitude = 1,
    $ZzFe$var$period = 0.3;

var $ZzFe$export$elasticIn = function custom(a, p) {
  var s = Math.asin(1 / (a = Math.max(1, a))) * (p /= $ZzFe$var$tau);

  function elasticIn(t) {
    return a * Math.pow(2, 10 * --t) * Math.sin((s - t) / p);
  }

  elasticIn.amplitude = function (a) {
    return custom(a, p * $ZzFe$var$tau);
  };

  elasticIn.period = function (p) {
    return custom(a, p);
  };

  return elasticIn;
}($ZzFe$var$amplitude, $ZzFe$var$period);

var $ZzFe$export$elasticOut = function custom(a, p) {
  var s = Math.asin(1 / (a = Math.max(1, a))) * (p /= $ZzFe$var$tau);

  function elasticOut(t) {
    return 1 - a * Math.pow(2, -10 * (t = +t)) * Math.sin((t + s) / p);
  }

  elasticOut.amplitude = function (a) {
    return custom(a, p * $ZzFe$var$tau);
  };

  elasticOut.period = function (p) {
    return custom(a, p);
  };

  return elasticOut;
}($ZzFe$var$amplitude, $ZzFe$var$period);

var $ZzFe$export$elasticInOut = function custom(a, p) {
  var s = Math.asin(1 / (a = Math.max(1, a))) * (p /= $ZzFe$var$tau);

  function elasticInOut(t) {
    return ((t = t * 2 - 1) < 0 ? a * Math.pow(2, 10 * t) * Math.sin((s - t) / p) : 2 - a * Math.pow(2, -10 * t) * Math.sin((s + t) / p)) / 2;
  }

  elasticInOut.amplitude = function (a) {
    return custom(a, p * $ZzFe$var$tau);
  };

  elasticInOut.period = function (p) {
    return custom(a, p);
  };

  return elasticInOut;
}($ZzFe$var$amplitude, $ZzFe$var$period);

function $XbmT$var$_slicedToArray(arr, i) {
  return $XbmT$var$_arrayWithHoles(arr) || $XbmT$var$_iterableToArrayLimit(arr, i) || $XbmT$var$_nonIterableRest();
}

function $XbmT$var$_nonIterableRest() {
  throw new TypeError("Invalid attempt to destructure non-iterable instance");
}

function $XbmT$var$_iterableToArrayLimit(arr, i) {
  var _arr = [];
  var _n = true;
  var _d = false;
  var _e = undefined;

  try {
    for (var _i = arr[Symbol.iterator](), _s; !(_n = (_s = _i.next()).done); _n = true) {
      _arr.push(_s.value);

      if (i && _arr.length === i) break;
    }
  } catch (err) {
    _d = true;
    _e = err;
  } finally {
    try {
      if (!_n && _i["return"] != null) _i["return"]();
    } finally {
      if (_d) throw _e;
    }
  }

  return _arr;
}

function $XbmT$var$_arrayWithHoles(arr) {
  if (Array.isArray(arr)) return arr;
}
// ==UserScript==
// @locale       english
// @name         yt_clipper
// @version      0.0.86
// @description  Add markers to youtube videos and generate clipped webms online or offline.
// @author       elwm
// @namespace    https://github.com/exwm
// @homepage     https://github.com/exwm/yt_clipper
// @supportURL   https://github.com/exwm/yt_clipper/issues
// @downloadURL  https://openuserjs.org/src/scripts/elwm/yt_clipper.user.js
// @updateURL    https://openuserjs.org/meta/elwm/yt_clipper.meta.js
// @icon         https://raw.githubusercontent.com/exwm/yt_clipper/master/assets/image/pepe-clipper.gif
// @require      https://cdn.jsdelivr.net/npm/jszip@3.2.1/dist/jszip.min.js
// @require      https://cdn.jsdelivr.net/npm/chart.js@2.8.0/dist/Chart.min.js
// @require      https://cdn.jsdelivr.net/npm/chartjs-plugin-datalabels@0.6.0/dist/chartjs-plugin-datalabels.min.js
// @require      https://cdnjs.cloudflare.com/ajax/libs/hammer.js/2.0.8/hammer.min.js
// @require      https://gitcdn.xyz/repo/exwm/chartjs-plugin-zoom/master/dist/chartjs-plugin-zoom.min.js
// @require      https://cdn.jsdelivr.net/npm/chartjs-plugin-annotation@0.5.7/chartjs-plugin-annotation.min.js
// @run-at       document-end
// @license      MIT
// @match        *://*.youtube.com/*
// @noframes
// @grant        none
// ==/UserScript==


var $XbmT$var$__rest = $XbmT$exports && $XbmT$exports.__rest || function (s, e) {
  var t = {};

  for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0) t[p] = s[p];

  if (s != null && typeof Object.getOwnPropertySymbols === "function") for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) {
    if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i])) t[p[i]] = s[p[i]];
  }
  return t;
};

const $XbmT$var$ytClipperCSS = "@keyframes valid-input {\n  0% {\n    background-color: tomato;\n  }\n  100% {\n    background-color: lightgreen;\n  }\n}\n\n@keyframes invalid-input {\n  0% {\n    background-color: lightgreen;\n  }\n  100% {\n    background-color: tomato;\n  }\n}\n\n@keyframes flash {\n  0% {\n    opacity: 1;\n  }\n  100% {\n    opacity: 0;\n  }\n}\n\n.msg-div {\n  margin-top: 2px;\n  padding: 2px;\n  border: 2px outset grey;\n}\n\n.flash-div {\n  animation-name: flash;\n  animation-duration: 5s;\n  animation-fill-mode: forwards;\n}\n\n.flash-msg {\n  font-size: 10pt;\n  font-weight: bold;\n}\n\n.marker {\n  width: 1.5px;\n  height: 16px;\n}\n\n.start-marker {\n  fill: lime;\n  pointer-events: none;\n}\n\n.end-marker {\n  fill: gold;\n  pointer-events: visibleFill;\n}\n\n.selected-marker-overlay {\n  fill: black;\n  width: 1.5px;\n  height: 8.5px;\n  y: 3.5px;\n  pointer-events: none;\n}\n\n#markerInputsDiv {\n  display: flex;\n}\n.editor-input-div {\n  display: inline-block;\n  color: grey;\n  font-size: 12pt;\n  margin: 2px;\n  padding: 2px;\n  border: 2px solid grey;\n}\n\n.editor-input-label {\n  color: grey;\n  font-size: 12pt;\n}\n\n.yt_clipper-input {\n  font-weight: bold;\n}\n\n.yt_clipper-input:valid {\n  animation-name: valid-input;\n  animation-duration: 1s;\n  animation-fill-mode: forwards;\n}\n\n.yt_clipper-input:invalid {\n  animation-name: invalid-input;\n  animation-duration: 1s;\n  animation-fill-mode: forwards;\n}\n\n.marker-settings-display {\n  display: block;\n  color: grey;\n  font-size: 12pt;\n  font-style: italic;\n  margin: 2px;\n  padding: 2px;\n  border: 2px solid grey;\n}\n\n.yt_clipper-settings-editor {\n  display: inline;\n  color: grey;\n  font-size: 12pt;\n  margin: 10px;\n  padding: 4px;\n  border: 2px solid grey;\n  border-radius: 5px;\n}\n\n#markers-svg,\n#selected-marker-pair-overlay,\n#start-marker-numberings,\n#end-marker-numberings {\n  font-size: 6.5pt;\n  width: 100%;\n  height: 300%;\n  top: -4px;\n  position: absolute;\n  z-index: 99;\n  paint-order: stroke;\n  stroke: rgb(60, 60, 60);\n  stroke-width: 0.6pt;\n}\n\n#start-marker-numberings {\n  top: -20px;\n}\n\n#end-marker-numberings {\n  top: 10px;\n}\n\n.markerNumbering {\n  pointer-events: none;\n}\n\n.startMarkerNumbering {\n  fill: lime;\n}\n\n.endMarkerNumbering {\n  fill: gold;\n}\n\n#crop-div {\n  pointer-events: none;\n  z-index: 10;\n}\n\n#begin-crop-preview-div {\n  pointer-events: none;\n  z-index: 11;\n}\n\n#crop-svg,\n#begin-crop-preview-svg {\n  width: 100%;\n  height: 100%;\n  top: 0px;\n  position: absolute;\n}\n";
const $XbmT$var$shortcutsTable = "<table>\n  <thead>\n    <tr>\n      <th colspan=\"2\">Marker Shortcuts</th>\n    </tr>\n    <tr>\n      <th>Action</th>\n      <th>Shortcut</th>\n    </tr>\n  </thead>\n  <tbody>\n    <tr>\n      <td>Toggle shortcuts on/off</td>\n      <td><kbd>Alt</kbd> + <kbd>Shift</kbd> + <kbd>A</kbd></td>\n    </tr>\n    <tr>\n      <td>Add marker at current time</td>\n      <td><kbd>A</kbd></td>\n    </tr>\n    <tr>\n      <td>Undo last marker</td>\n      <td><kbd>Z</kbd></td>\n    </tr>\n    <tr>\n      <td>Toggle marker pair editor</td>\n      <td><kbd>Shift</kbd> + <kbd>Mouse-Over</kbd></td>\n    </tr>\n    <tr>\n      <td>Open overrides editor</td>\n      <td><kbd>Shift</kbd> + <kbd>W</kbd></td>\n    </tr>\n    <tr>\n      <td>Toggle marker pair selection</td>\n      <td><kbd>Ctrl</kbd> + <kbd>Up</kbd></td>\n    </tr>\n    <tr>\n      <td>Toggle auto-hiding unselected marker pairs</td>\n      <td><kbd>Ctrl</kbd> + <kbd>Down</kbd></td>\n    </tr>\n    <tr>\n      <td>Jump to nearest next/previous marker</td>\n      <td><kbd>Ctrl</kbd> + <kbd>Left</kbd> / <kbd>Right</kbd></td>\n    </tr>\n    <tr>\n      <td>Select next/previous marker pair</td>\n      <td><kbd>Alt</kbd> + <kbd>Left</kbd> / <kbd>Right</kbd></td>\n    </tr>\n    <tr>\n      <td>Select next/previous marker pair and jump to start marker</td>\n      <td><kbd>Ctrl</kbd> + <kbd>Alt</kbd> + <kbd>Left</kbd> / <kbd>Right</kbd></td>\n    </tr>\n  </tbody>\n</table>\n\n<table>\n  <thead>\n    <tr>\n      <th colspan=\"2\">Global Settings Editor Shortcuts</th>\n    </tr>\n    <tr>\n      <th>Action</th>\n      <th>Shortcut</th>\n    </tr>\n  </thead>\n  <tbody>\n    <tr>\n      <td>Toggle global settings editor</td>\n      <td><kbd>W</kbd></td>\n    </tr>\n    <tr>\n      <td>Toggle encoding settings editor (when global settings editor open)</td>\n      <td><kbd>Shift</kbd> + <kbd>W</kbd></td>\n    </tr>\n    <tr>\n      <td>Update all markers to default new marker <em>speed</em></td>\n      <td><kbd>Alt</kbd> + <kbd>Shift</kbd> + <kbd>Q</kbd></td>\n    </tr>\n    <tr>\n      <td>Update all markers to default new marker <em>crop</em></td>\n      <td><kbd>Alt</kbd> + <kbd>Shift</kbd> + <kbd>X</kbd></td>\n    </tr>\n  </tbody>\n</table>\n\n<table>\n  <thead>\n    <tr>\n      <th colspan=\"2\">Cropping Shortcuts</th>\n    </tr>\n    <tr>\n      <th>Action</th>\n      <th>Shortcut</th>\n    </tr>\n  </thead>\n  <tbody>\n    <tr>\n      <td>Begin drawing crop</td>\n      <td><kbd>X</kbd></td>\n    </tr>\n    <tr>\n      <td>Begin drawing vertical crop (sets only left and right)</td>\n      <td><kbd>Shift</kbd> + <kbd>X</kbd></td>\n    </tr>\n    <tr>\n      <td>Draw crop</td>\n      <td><kbd>Click</kbd> + <kbd>Drag</kbd> rectangular area</td>\n    </tr>\n    <tr>\n      <td>Drag or resize crop</td>\n      <td><kbd>Ctrl</kbd> + <kbd>Click</kbd> + <kbd>Drag</kbd> crop</td>\n    </tr>\n    <tr>\n      <td>Cycle crop dim opacity by +0.25</td>\n      <td><kbd>Ctrl</kbd> + <kbd>X</kbd></td>\n    </tr>\n    <tr>\n      <td>Toggle crop adjustment with arrow keys</td>\n      <td><kbd>Alt</kbd> + <kbd>X</kbd></td>\n    </tr>\n    <tr>\n      <td>Adjust crop input string with arrow keys</td>\n      <td><pre>Place cursor on target value</pre></td>\n    </tr>\n    <tr>\n      <td>Change crop change amount from 10 to 1/50/100</td>\n      <td><kbd>Alt</kbd> / <kbd>Shift</kbd> / <kbd>Alt</kbd> + <kbd>Shift</kbd></td>\n    </tr>\n    <tr>\n      <td>Modify crop width/height instead of x/y offset</td>\n      <td><kbd>Ctrl</kbd> + <kbd>ArrowKey</kbd></td>\n    </tr>\n  </tbody>\n</table>\n\n<table>\n  <thead>\n    <tr>\n      <th colspan=\"2\">Preview Shortcuts</th>\n    </tr>\n    <tr>\n      <th>Action</th>\n      <th>Shortcut</th>\n    </tr>\n  </thead>\n  <tbody>\n    <tr>\n      <td>Toggle previewing speed</td>\n      <td><kbd>C</kbd></td>\n    </tr>\n    <tr>\n      <td>Toggle auto marker pair looping</td>\n      <td><kbd>Shift</kbd> + <kbd>C</kbd></td>\n    </tr>\n    <tr>\n      <td>Toggle previewing gamma</td>\n      <td><kbd>Alt</kbd> + <kbd>C</kbd></td>\n    </tr>\n    <tr>\n      <td>Toggle previewing fade loop</td>\n      <td><kbd>Alt</kbd> + <kbd>Shift</kbd> + <kbd>C</kbd></td>\n    </tr>\n    <tr>\n      <td>Toggle all previews</td>\n      <td><kbd>Ctrl</kbd> + <kbd>Alt</kbd> + <kbd>Shift</kbd> + <kbd>C</kbd></td>\n    </tr>\n    <tr>\n      <td>Cycle playback speed down by 0.25</td>\n      <td><kbd>Q</kbd></td>\n    </tr>\n    <tr>\n      <td>Seek video frame by frame</td>\n      <td>\n        <kbd>&lt;</kbd> / <kbd>&gt;</kbd> or <kbd>Shift</kbd> + <kbd>Mouse-Wheel</kbd>\n      </td>\n    </tr>\n    <tr>\n      <td>Toggle previewing rotation 90&deg; clockwise/anti-clockwise</td>\n      <td><kbd>R</kbd> / <kbd>Alt</kbd> + <kbd>R</kbd></td>\n    </tr>\n    <tr>\n      <td>Toggle big video preview thumbnails</td>\n      <td><kbd>Shift</kbd> + <kbd>R</kbd></td>\n    </tr>\n  </tbody>\n</table>\n\n<table>\n  <thead>\n    <tr>\n      <th colspan=\"2\">Speed Chart Shortcuts</th>\n    </tr>\n    <tr>\n      <th>Action</th>\n      <th>Shortcut</th>\n    </tr>\n  </thead>\n  <tbody>\n    <tr>\n      <td>Toggle speed chart</td>\n      <td><kbd>D</kbd></td>\n    </tr>\n    <tr>\n      <td>Seek to time on time-axis</td>\n      <td><kbd>Right-Click</kbd></td>\n    </tr>\n    <tr>\n      <td>Set speed chart loop start/end marker</td>\n      <td>\n        <kbd>Alt</kbd> + <kbd>Right-click</kbd> / <kbd>Ctrl</kbd> + <kbd>Alt</kbd> +\n        <kbd>Right-click</kbd>\n      </td>\n    </tr>\n    <tr>\n      <td>Toggle speed chart looping</td>\n      <td><kbd>Shift</kbd> + <kbd>D</kbd></td>\n    </tr>\n    <tr>\n      <td>Reset speed chart looping markers</td>\n      <td><kbd>Alt</kbd> + <kbd>D</kbd></td>\n    </tr>\n    <tr>\n      <td>Move speed point or pan chart</td>\n      <td><kbd>Click</kbd> + <kbd>Drag</kbd></td>\n    </tr>\n    <tr>\n      <td>Add speed point</td>\n      <td><kbd>Shift</kbd> + <kbd>Click</kbd></td>\n    </tr>\n    <tr>\n      <td>Delete speed point</td>\n      <td><kbd>Alt</kbd> + <kbd>Shift</kbd> + <kbd>Click</kbd></td>\n    </tr>\n    <tr>\n      <td>Zoom in and out</td>\n      <td><kbd>Ctrl</kbd> + <kbd>Mouse-Wheel</kbd></td>\n    </tr>\n    <tr>\n      <td>Reset zoom</td>\n      <td><kbd>Ctrl</kbd> + <kbd>Click</kbd></td>\n    </tr>\n  </tbody>\n</table>\n\n<table>\n  <thead>\n    <tr>\n      <th colspan=\"2\">Frame Capturer Shortcuts</th>\n    </tr>\n    <tr>\n      <th>Action</th>\n      <th>Shortcut</th>\n    </tr>\n  </thead>\n  <tbody>\n    <tr>\n      <td>Capture frame</td>\n      <td><kbd>E</kbd></td>\n    </tr>\n    <tr>\n      <td>Zip and download captured frames</td>\n      <td><kbd>Alt</kbd> + <kbd>E</kbd></td>\n    </tr>\n  </tbody>\n</table>\n\n<table>\n  <thead>\n    <tr>\n      <th colspan=\"2\">Saving Shortcuts</th>\n    </tr>\n    <tr>\n      <th>Action</th>\n      <th>Shortcut</th>\n    </tr>\n  </thead>\n  <tbody>\n    <tr>\n      <td>Save markers data as json</td>\n      <td><kbd>S</kbd></td>\n    </tr>\n    <tr>\n      <td>Copy markers data to clipboard</td>\n      <td><kbd>Alt</kbd> + <kbd>S</kbd></td>\n    </tr>\n    <tr>\n      <td>Toggle markers data loading dialog</td>\n      <td><kbd>G</kbd></td>\n    </tr>\n  </tbody>\n</table>\n";
const $XbmT$var$shortcutsTableStyle = "#shortcutsTableContainer table {\n  text-align: left;\n  line-height: 32px;\n  border-collapse: separate;\n  border-spacing: 0;\n  border: 2px solid #ed1c40;\n  width: 630px;\n  margin: 8px auto;\n  border-radius: 0.25rem;\n  font-family: 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;\n  font-size: 13px;\n  font-weight: 500;\n  background-color: #222;\n  color: #ddd;\n}\n\n#shortcutsTableContainer thead tr:first-child {\n  background: #ed1c40;\n  color: #fff;\n  border: none;\n}\n\n#shortcutsTableContainer thead tr:nth-child(2) {\n  text-align: center;\n}\n\n#shortcutsTableContainer tr:nth-child(even) {\n  background-color: #333;\n}\n\n#shortcutsTableContainer th:first-child,\ntd:first-child {\n  padding: 0 5px;\n}\n\n#shortcutsTableContainer th {\n  font-size: 14px;\n  font-weight: 600;\n}\n\n#shortcutsTableContainer thead tr:last-child th {\n  border-bottom: 3px solid #777;\n}\n\n#shortcutsTableContainer tbody tr:hover {\n  background-color: #444;\n}\n\n#shortcutsTableContainer tbody tr:first-child {\n  border-bottom: 1px solid #666;\n  border-right: 2px solid #666;\n}\n\n#shortcutsTableContainer td {\n  border-bottom: 1px solid #666;\n}\n\n#shortcutsTableContainer td:first-child {\n  border-right: 2px solid #666;\n}\n\n#shortcutsTableContainer td:last-child {\n  text-align: right;\n  padding-right: 5px;\n}\n\n#shortcutsTableContainer kbd {\n  border: 1px solid #999;\n  border-radius: 2px;\n  font-weight: bold;\n  padding: 2px 4px;\n  margin: 1px;\n  background-color: #e0e0e0;\n  color: #333;\n}\n";
const $XbmT$var$shortcutsTableToggleButtonHTML = "<button\n  id=\"shortcutsTableToggleButton\"\n  class=\"ytp-button\"\n  title=\"Toggle yt_clipper Shortcuts Table\"\n  style=\"cursor: help\"\n>\n  <svg\n    version=\"1.1\"\n    width=\"100%\"\n    height=\"100%\"\n    viewbox=\"0 0 40 40\"\n    xmlns=\"http://www.w3.org/2000/svg\"\n  >\n    <path\n      d=\"M19.66 21.528l5.425 3.972c0.447 0.335 1.26 0.599 1.818 0.599h3.149l-13.319-9.773c0.073-0.27 0.116-0.579 0.116-0.899 0-1.964-1.592-3.556-3.556-3.556s-3.556 1.592-3.556 3.556c0 1.964 1.592 3.556 3.556 3.556 0.759 0 1.462-0.237 2.039-0.643l-0.011 0.008 2.265 1.656-2.265 1.656c-0.568-0.403-1.276-0.644-2.040-0.644-1.964 0-3.556 1.592-3.556 3.556s1.592 3.556 3.556 3.556c1.964 0 3.556-1.592 3.556-3.556 0-0.316-0.041-0.623-0.119-0.915l0.005 0.025 2.946-2.154zM13.29 16.957c-0.841 0-1.524-0.682-1.524-1.524s0.682-1.524 1.524-1.524v0c0.841 0 1.524 0.682 1.524 1.524s-0.682 1.524-1.524 1.524v0zM13.29 26.1c-0.841 0-1.524-0.682-1.524-1.524s0.682-1.524 1.524-1.524v0c0.841 0 1.524 0.682 1.524 1.524s-0.682 1.524-1.524 1.524v0zM25.075 14.508c0.515-0.348 1.143-0.567 1.82-0.599l0.008-0h3.149l-7.619 5.587-2.083-1.524 4.724-3.464z\"\n      fill=\"#fff\"\n    ></path>\n  </svg>\n</button>\n";
const $XbmT$var$__version__ = '0.0.86';
let $XbmT$export$player;
$XbmT$exports.player = $XbmT$export$player;

(function () {
  async function onLoadVideoPage(callback) {
    const ytdapp = await $BHXf$export$retryUntilTruthyResult(() => document.getElementsByTagName('ytd-app')[0]);

    if (ytdapp.hasAttribute('is-watch-page')) {
      console.log('watch page loaded');
      callback();
      return;
    }

    const observer = new MutationObserver(mutationList => {
      mutationList.forEach(mutation => {
        if (mutation.type === 'attributes' && mutation.attributeName === 'is-watch-page' && ytdapp.hasAttribute('is-watch-page')) {
          console.log('watch page loaded');
          observer.disconnect();
          callback();
        }
      });
    });
    const config = {
      attributeFilter: ['is-watch-page']
    };
    console.log(`Waiting for video page load before calling ${callback.name}`);
    observer.observe(ytdapp, config);
  }

  onLoadVideoPage(loadytClipper);

  async function loadytClipper() {
    console.log('Loading yt clipper markup script');
    document.addEventListener('keydown', hotkeys, true);

    function hotkeys(e) {
      if (toggleKeys) {
        switch (e.code) {
          case 'KeyA':
            if (!e.shiftKey) {
              e.preventDefault();
              e.stopImmediatePropagation();
              addMarker();
            } else if (e.shiftKey && markerHotkeysEnabled && enableMarkerHotkeys.moveMarker) {
              e.preventDefault();
              e.stopImmediatePropagation();
              enableMarkerHotkeys.moveMarker(enableMarkerHotkeys.endMarker);
            }

            break;

          case 'KeyS':
            if (!e.ctrlKey && !e.shiftKey && !e.altKey) {
              e.preventDefault();
              e.stopImmediatePropagation();
              saveMarkersAndSettings();
            } else if (!e.ctrlKey && e.altKey && !e.shiftKey) {
              e.preventDefault();
              e.stopImmediatePropagation();
              $BHXf$export$copyToClipboard(getSettingsJSON());
            } else if (!e.ctrlKey && e.altKey && e.shiftKey) {
              e.preventDefault();
              e.stopImmediatePropagation();
              saveAuthServerScript();
            }

            break;

          case 'KeyQ':
            if (!e.ctrlKey && !e.altKey && e.shiftKey && markerHotkeysEnabled && enableMarkerHotkeys.moveMarker) {
              e.preventDefault();
              e.stopImmediatePropagation();
              enableMarkerHotkeys.moveMarker(enableMarkerHotkeys.startMarker);
            } else if (!e.ctrlKey && !e.altKey && !e.shiftKey) {
              e.preventDefault();
              e.stopImmediatePropagation();
              cyclePlayerSpeedDown();
            } else if (!e.ctrlKey && e.altKey && e.shiftKey) {
              e.preventDefault();
              e.stopImmediatePropagation();
              updateAllMarkerPairSpeeds(settings.newMarkerSpeed);
            }

            break;

          case 'KeyE':
            if (!e.ctrlKey && !e.altKey && !e.shiftKey) {
              e.preventDefault();
              e.stopImmediatePropagation();
              captureFrame();
            } else if (!e.ctrlKey && e.altKey && !e.shiftKey) {
              e.preventDefault();
              e.stopImmediatePropagation();
              saveCapturedFrames();
            }

            break;

          case 'KeyW':
            if (!e.ctrlKey && !e.shiftKey && !e.altKey) {
              e.preventDefault();
              e.stopImmediatePropagation();
              toggleGlobalSettingsEditor();
            } else if (!e.ctrlKey && e.shiftKey && !e.altKey) {
              e.preventDefault();
              e.stopImmediatePropagation();
              toggleMarkerPairOverridesEditor();
            }

            break;

          case 'KeyC':
            if (!e.ctrlKey && !e.shiftKey && !e.altKey) {
              e.preventDefault();
              e.stopImmediatePropagation();
              toggleSpeedDucking();
            } else if (!e.ctrlKey && e.shiftKey && !e.altKey) {
              e.preventDefault();
              e.stopImmediatePropagation();
              toggleMarkerLooping();
            } else if (!e.ctrlKey && !e.shiftKey && e.altKey) {
              e.preventDefault();
              e.stopImmediatePropagation();
              toggleGammaPreview();
            } else if (!e.ctrlKey && e.shiftKey && e.altKey) {
              e.preventDefault();
              e.stopImmediatePropagation();
              toggleFadeLoopPreview();
            } else if (e.ctrlKey && e.shiftKey && e.altKey) {
              e.preventDefault();
              e.stopImmediatePropagation();
              toggleAllPreviews();
            }

            break;

          case 'KeyG':
            if (!e.ctrlKey && !e.shiftKey && !e.altKey) {
              e.preventDefault();
              e.stopImmediatePropagation();
              loadMarkers();
            }

            break;

          case 'KeyD':
            // alt+shift+D do not work in chrome 75.0.3770.100
            if (!e.ctrlKey && !e.shiftKey && !e.altKey) {
              e.preventDefault();
              e.stopImmediatePropagation();
              toggleSpeedChart();
            } else if (!e.ctrlKey && e.shiftKey && !e.altKey) {
              e.preventDefault();
              e.stopImmediatePropagation();
              toggleSpeedMapLoop();
            } else if (!e.ctrlKey && !e.shiftKey && e.altKey) {
              e.preventDefault();
              e.stopImmediatePropagation();
              resetSpeedMapLoop();
            }

            break;

          case 'KeyZ':
            if (!e.ctrlKey && !e.shiftKey && !e.altKey) {
              e.preventDefault();
              e.stopImmediatePropagation();
              undoMarker();
            } else if (!e.ctrlKey && e.shiftKey && !e.altKey) {
              e.preventDefault();
              e.stopImmediatePropagation();
              redoMarker();
            } else if (!e.ctrlKey && !e.shiftKey && e.altKey && markerHotkeysEnabled && enableMarkerHotkeys.deleteMarkerPair) {
              e.preventDefault();
              e.stopImmediatePropagation();
              enableMarkerHotkeys.deleteMarkerPair();
            }

            break;

          case 'KeyX':
            if (!e.ctrlKey && !e.altKey && !e.shiftKey) {
              e.preventDefault();
              e.stopImmediatePropagation();
              drawCropOverlay(false);
            } else if (!e.ctrlKey && !e.altKey && e.shiftKey) {
              e.preventDefault();
              e.stopImmediatePropagation();
              drawCropOverlay(true);
            } else if (!e.ctrlKey && e.altKey && !e.shiftKey) {
              e.preventDefault();
              e.stopImmediatePropagation();
              toggleArrowKeyCropAdjustment();
            } else if (!e.ctrlKey && e.altKey && e.shiftKey) {
              e.preventDefault();
              e.stopImmediatePropagation();
              updateAllMarkerPairCrops(settings.newMarkerCrop);
            } else if (e.ctrlKey && !e.altKey && !e.shiftKey) {
              e.preventDefault();
              e.stopImmediatePropagation();
              cycleCropDimOpacity();
            }

            break;

          case 'KeyV':
            if (!e.ctrlKey && !e.shiftKey && e.altKey) {
              e.preventDefault();
              e.stopImmediatePropagation();
              sendGfyRequests(playerInfo.url);
            } else if (!e.ctrlKey && e.shiftKey && e.altKey) {
              e.preventDefault();
              e.stopImmediatePropagation();
              requestGfycatAuth();
            }

            break;

          case 'KeyR':
            if (!e.ctrlKey && !e.shiftKey && !e.altKey && playerInfo.watchFlexy.theater) {
              e.preventDefault();
              e.stopImmediatePropagation();
              rotateVideo('clock');
            } else if (!e.ctrlKey && !e.shiftKey && e.altKey && playerInfo.watchFlexy.theater) {
              e.preventDefault();
              e.stopImmediatePropagation();
              rotateVideo('cclock');
            } else if (!e.ctrlKey && e.shiftKey && !e.altKey) {
              e.preventDefault();
              e.stopImmediatePropagation();
              toggleBigVideoPreviews();
            } else if (!e.ctrlKey && !e.shiftKey && !playerInfo.watchFlexy.theater) {
              e.preventDefault();
              e.stopImmediatePropagation();
              flashMessage('Please switch to theater mode to rotate video.', 'red');
            }

            break;

          case 'ArrowLeft':
          case 'ArrowRight':
            jumpToNearestMarkerOrPair(e, e.code);
            break;

          case 'ArrowUp':
            toggleSelectedMarkerPair(e);
            break;

          case 'ArrowDown':
            toggleAutoHideUnselectedMarkerPairs(e);
            break;
        }
      }

      if (!e.ctrlKey && e.shiftKey && e.altKey && e.code === 'KeyA') {
        toggleKeys = !toggleKeys;
        initOnce();

        if (toggleKeys) {
          showShortcutsTableToggleButton();
          flashMessage('Enabled Hotkeys', 'green');
        } else {
          hideShortcutsTableToggleButton();
          flashMessage('Disabled Hotkeys', 'red');
        }
      }
    }

    window.addEventListener('keydown', addCropOverlayHoverListener, true);
    window.addEventListener('keyup', removeCropOverlayHoverListener, true);

    function addCropOverlayHoverListener(e) {
      if (e.key === 'Control' && hotkeys && !e.repeat && isCropOverlayVisible && !isDrawingCrop && !isSpeedChartVisible) {
        window.addEventListener('mousemove', cropOverlayHoverHandler, true);
      }
    }

    function removeCropOverlayHoverListener(e) {
      if (e.key === 'Control') {
        window.removeEventListener('mousemove', cropOverlayHoverHandler, true);
        showPlayerControls();
        video.style.removeProperty('cursor');
      }
    }

    function cropOverlayHoverHandler(e) {
      if (isMarkerEditorOpen && isCropOverlayVisible && !isDrawingCrop) {
        updateCropHoverCursor(e);
      }
    }

    function updateCropHoverCursor(e) {
      const cursor = getMouseCropHoverRegion(e);

      if (cursor) {
        hidePlayerControls();
        video.style.cursor = cursor;
      } else {
        showPlayerControls();
        video.style.removeProperty('cursor');
      }
    }

    function toggleSelectedMarkerPair(e) {
      if (e.ctrlKey && !arrowKeyCropAdjustmentEnabled) {
        e.preventDefault();
        e.stopImmediatePropagation();

        if (enableMarkerHotkeys.endMarker) {
          toggleMarkerPairEditor(enableMarkerHotkeys.endMarker);
        } else if (prevSelectedEndMarker) {
          toggleMarkerPairEditor(prevSelectedEndMarker);
        }
      }
    }

    const CLIENT_ID = 'XXXX';
    const REDIRECT_URI = 'https://127.0.0.1:4443/yt_clipper';
    const BROWSER_BASED_AUTH_ENDPOINT = `https://gfycat.com/oauth/authorize?client_id=${CLIENT_ID}&scope=all&state=yt_clipper&response_type=token&redirect_uri=${REDIRECT_URI}`;
    let start = true;
    let markerHotkeysEnabled = false;
    let isMarkerEditorOpen = false;
    let wasDefaultsEditorOpen = false;
    let isCropOverlayVisible = false;
    let isSpeedChartVisible = false;
    let checkGfysCompletedId;
    let markerPairs = [];
    let markerPairsHistory = [];
    let links = [];
    let startTime = 0.0;
    let toggleKeys = false;
    let prevSelectedEndMarker = null;
    let prevSelectedMarkerPairIndex = null;

    function init() {
      injectCSS($XbmT$var$ytClipperCSS, 'yt-clipper-css');
      initPlayerInfo();
      initMarkersContainer();
      addForeignEventListeners();
      injectToggleShortcutsTableButton();
      addCropOverlayDragListener();
    }

    function getMinWH() {
      const minWHMultiplier = Math.min(settings.cropResWidth, settings.cropResHeight) / 1080;
      const minW = Math.round(25 * minWHMultiplier);
      const minH = Math.round(25 * minWHMultiplier);
      return {
        minW,
        minH
      };
    }

    let isDraggingCrop = false;

    function addCropOverlayDragListener() {
      video.addEventListener('pointerdown', cropOverlayDragHandler, {
        capture: true
      });

      function cropOverlayDragHandler(e) {
        if (e.ctrlKey && isMarkerEditorOpen && isCropOverlayVisible && !isDrawingCrop && !isSpeedChartVisible) {
          const _getMinWH = getMinWH(),
                minW = _getMinWH.minW,
                minH = _getMinWH.minH;

          const _extractCropComponent = extractCropComponents(),
                _extractCropComponent2 = $XbmT$var$_slicedToArray(_extractCropComponent, 4),
                ix = _extractCropComponent2[0],
                iy = _extractCropComponent2[1],
                iw = _extractCropComponent2[2],
                ih = _extractCropComponent2[3];

          const videoRect = $XbmT$export$player.getVideoContentRect();
          const playerRect = $XbmT$export$player.getBoundingClientRect();
          const clickPosX = e.pageX - videoRect.left - playerRect.left;
          const clickPosY = e.pageY - videoRect.top - playerRect.top;
          const cursor = getMouseCropHoverRegion(e);
          let resizeHandler;

          if (!cursor) {
            return;
          } else {
            document.addEventListener('click', blockVideoPause, {
              once: true,
              capture: true
            });
            window.removeEventListener('mousemove', cropOverlayHoverHandler, true);
            window.removeEventListener('keydown', addCropOverlayHoverListener, true);
            window.removeEventListener('keyup', removeCropOverlayHoverListener, true);
            e.preventDefault();
            video.setPointerCapture(e.pointerId);
            document.addEventListener('pointerup', endCropOverlayDrag, {
              once: true,
              capture: true
            });
            isDraggingCrop = true;
            hidePlayerControls();

            if (cursor === 'grab') {
              video.style.cursor = 'grabbing';
              document.addEventListener('pointermove', dragCropHandler);
            } else {
              resizeHandler = e => getResizeHandler(e, cursor);

              document.addEventListener('pointermove', resizeHandler);
            }
          }

          function getResizeHandler(e, cursor) {
            const dragPosX = e.pageX - videoRect.left - playerRect.left;
            const changeX = dragPosX - clickPosX;
            const changeXScaled = changeX / videoRect.width * settings.cropResWidth;
            const dragPosY = e.pageY - videoRect.top - playerRect.top;
            const changeY = dragPosY - clickPosY;
            const changeYScaled = changeY / videoRect.height * settings.cropResHeight;
            let resizedDimensions;

            switch (cursor) {
              case 'n-resize':
                resizedDimensions = getResizeN(changeYScaled);
                break;

              case 'ne-resize':
                resizedDimensions = getResizeNE(changeXScaled, changeYScaled);
                break;

              case 'e-resize':
                resizedDimensions = getResizeE(changeXScaled);
                break;

              case 'se-resize':
                resizedDimensions = getResizeSE(changeXScaled, changeYScaled);
                break;

              case 's-resize':
                resizedDimensions = getResizeS(changeYScaled);
                break;

              case 'sw-resize':
                resizedDimensions = getResizeSW(changeXScaled, changeYScaled);
                break;

              case 'w-resize':
                resizedDimensions = getResizeW(changeXScaled);
                break;

              case 'nw-resize':
                resizedDimensions = getResizeNW(changeXScaled, changeYScaled);
                break;
            }

            const _resizedDimensions = resizedDimensions,
                  resizedX = _resizedDimensions.resizedX,
                  resizedY = _resizedDimensions.resizedY,
                  resizedW = _resizedDimensions.resizedW,
                  resizedH = _resizedDimensions.resizedH;
            updateCrop(resizedX, resizedY, resizedW, resizedH);
          }

          function dragCropHandler(e) {
            const dragPosX = e.pageX - videoRect.left - playerRect.left;
            const dragPosY = e.pageY - videoRect.top - playerRect.top;
            const changeX = dragPosX - clickPosX;
            const changeY = dragPosY - clickPosY;
            let X = Math.round(changeX / videoRect.width * settings.cropResWidth + ix);
            let Y = Math.round(changeY / videoRect.height * settings.cropResHeight + iy);
            X = $BHXf$export$clampNumber(X, 0, settings.cropResWidth - iw);
            Y = $BHXf$export$clampNumber(Y, 0, settings.cropResHeight - ih);
            updateCrop(X, Y, iw, ih);
          }

          function getResizeN(changeYScaled) {
            let Y = Math.round(iy + changeYScaled);
            let H = Math.round(ih - changeYScaled);
            Y = $BHXf$export$clampNumber(Y, 0, iy + ih - minH);
            H = $BHXf$export$clampNumber(H, minH, iy + ih);
            return {
              resizedX: ix,
              resizedY: Y,
              resizedW: iw,
              resizedH: H
            };
          }

          function getResizeE(changeXScaled) {
            let W = Math.round(iw + changeXScaled);
            W = $BHXf$export$clampNumber(W, minW, settings.cropResWidth - ix);
            return {
              resizedX: ix,
              resizedY: iy,
              resizedW: W,
              resizedH: ih
            };
          }

          function getResizeS(changeYScaled) {
            let H = Math.round(ih + changeYScaled);
            H = $BHXf$export$clampNumber(H, minH, settings.cropResHeight - iy);
            return {
              resizedX: ix,
              resizedY: iy,
              resizedW: iw,
              resizedH: H
            };
          }

          function getResizeW(changeXScaled) {
            let X = Math.round(ix + changeXScaled);
            let W = Math.round(iw - changeXScaled);
            X = $BHXf$export$clampNumber(X, 0, ix + iw - minW);
            W = $BHXf$export$clampNumber(W, minW, ix + iw);
            return {
              resizedX: X,
              resizedY: iy,
              resizedW: W,
              resizedH: ih
            };
          }

          function getResizeNE(changeXScaled, changeYScaled) {
            let Y = Math.round(iy + changeYScaled);
            let W = Math.round(iw + changeXScaled);
            let H = Math.round(ih - changeYScaled);
            Y = $BHXf$export$clampNumber(Y, 0, iy + ih - minH);
            W = $BHXf$export$clampNumber(W, minW, settings.cropResWidth - ix);
            H = $BHXf$export$clampNumber(H, minH, iy + ih);
            return {
              resizedX: ix,
              resizedY: Y,
              resizedW: W,
              resizedH: H
            };
          }

          function getResizeSE(changeXScaled, changeYScaled) {
            let W = Math.round(iw + changeXScaled);
            let H = Math.round(ih + changeYScaled);
            W = $BHXf$export$clampNumber(W, minW, settings.cropResWidth - ix);
            H = $BHXf$export$clampNumber(H, minH, settings.cropResHeight - iy);
            return {
              resizedX: ix,
              resizedY: iy,
              resizedW: W,
              resizedH: H
            };
          }

          function getResizeSW(changeXScaled, changeYScaled) {
            let X = Math.round(ix + changeXScaled);
            let W = Math.round(iw - changeXScaled);
            let H = Math.round(ih + changeYScaled);
            X = $BHXf$export$clampNumber(X, 0, ix + iw - minW);
            W = $BHXf$export$clampNumber(W, minW, ix + iw);
            H = $BHXf$export$clampNumber(H, minH, settings.cropResHeight - iy);
            return {
              resizedX: X,
              resizedY: iy,
              resizedW: W,
              resizedH: H
            };
          }

          function getResizeNW(changeXScaled, changeYScaled) {
            let X = Math.round(ix + changeXScaled);
            let W = Math.round(iw - changeXScaled);
            let Y = Math.round(iy + changeYScaled);
            let H = Math.round(ih - changeYScaled);
            X = $BHXf$export$clampNumber(X, 0, ix + iw - minW);
            W = $BHXf$export$clampNumber(W, minW, ix + iw);
            Y = $BHXf$export$clampNumber(Y, 0, iy + ih - minH);
            H = $BHXf$export$clampNumber(H, minH, iy + ih);
            return {
              resizedX: X,
              resizedY: Y,
              resizedW: W,
              resizedH: H
            };
          }

          function endCropOverlayDrag(e) {
            isDraggingCrop = false;
            video.releasePointerCapture(e.pointerId);
            cursor === 'grab' ? document.removeEventListener('pointermove', dragCropHandler) : document.removeEventListener('pointermove', resizeHandler);
            showPlayerControls();

            if (e.ctrlKey) {
              if (cursor) video.style.cursor = cursor;
              updateCropHoverCursor(e);
              window.addEventListener('mousemove', cropOverlayHoverHandler, true);
              window.addEventListener('keyup', removeCropOverlayHoverListener, true);
              window.addEventListener('keydown', addCropOverlayHoverListener, true);
            } else {
              video.style.removeProperty('cursor');
              window.addEventListener('keydown', addCropOverlayHoverListener, true);
            }
          }
        }
      }
    }

    function getMouseCropHoverRegion(e) {
      const _extractCropComponent3 = extractCropComponents(),
            _extractCropComponent4 = $XbmT$var$_slicedToArray(_extractCropComponent3, 4),
            x = _extractCropComponent4[0],
            y = _extractCropComponent4[1],
            w = _extractCropComponent4[2],
            h = _extractCropComponent4[3];

      const videoRect = $XbmT$export$player.getVideoContentRect();
      const playerRect = $XbmT$export$player.getBoundingClientRect();
      const clickPosX = e.pageX - videoRect.left - playerRect.left;
      const clickPosY = e.pageY - videoRect.top - playerRect.top;
      const clickPosXScaled = clickPosX / videoRect.width * settings.cropResWidth;
      const clickPosYScaled = clickPosY / videoRect.height * settings.cropResHeight;
      const slMultiplier = Math.min(settings.cropResWidth, settings.cropResHeight) / 1080;
      const sl = Math.ceil(Math.min(w, h) * slMultiplier * 0.1);
      const edgeOffset = 30 * slMultiplier;
      let cursor;
      let mouseCropColumn;

      if (x - edgeOffset < clickPosXScaled && clickPosXScaled < x + sl) {
        mouseCropColumn = 1;
      } else if (x + sl < clickPosXScaled && clickPosXScaled < x + w - sl) {
        mouseCropColumn = 2;
      } else if (x + w - sl < clickPosXScaled && clickPosXScaled < x + w + edgeOffset) {
        mouseCropColumn = 3;
      }

      let mouseCropRow;

      if (y - edgeOffset < clickPosYScaled && clickPosYScaled < y + sl) {
        mouseCropRow = 1;
      } else if (y + sl < clickPosYScaled && clickPosYScaled < y + h - sl) {
        mouseCropRow = 2;
      } else if (y + h - sl < clickPosYScaled && clickPosYScaled < y + h + edgeOffset) {
        mouseCropRow = 3;
      }

      const isMouseInCropCenter = mouseCropColumn === 2 && mouseCropRow === 2;
      const isMouseInCropN = mouseCropColumn === 2 && mouseCropRow === 1;
      const isMouseInCropNE = mouseCropColumn === 3 && mouseCropRow === 1;
      const isMouseInCropE = mouseCropColumn === 3 && mouseCropRow === 2;
      const isMouseInCropSE = mouseCropColumn === 3 && mouseCropRow === 3;
      const isMouseInCropS = mouseCropColumn === 2 && mouseCropRow === 3;
      const isMouseInCropSW = mouseCropColumn === 1 && mouseCropRow === 3;
      const isMouseInCropW = mouseCropColumn === 1 && mouseCropRow === 2;
      const isMouseInCropNW = mouseCropColumn === 1 && mouseCropRow === 1;
      if (isMouseInCropCenter) cursor = 'grab';
      if (isMouseInCropN) cursor = 'n-resize';
      if (isMouseInCropNE) cursor = 'ne-resize';
      if (isMouseInCropE) cursor = 'e-resize';
      if (isMouseInCropSE) cursor = 'se-resize';
      if (isMouseInCropS) cursor = 's-resize';
      if (isMouseInCropSW) cursor = 'sw-resize';
      if (isMouseInCropW) cursor = 'w-resize';
      if (isMouseInCropNW) cursor = 'nw-resize';
      return cursor;
    }

    const initOnce = $BHXf$export$once(init, this);
    $XbmT$export$player = await $BHXf$export$retryUntilTruthyResult(() => document.getElementById('movie_player'));
    $XbmT$exports.player = $XbmT$export$player;
    const playerInfo = {};
    const video = await $BHXf$export$retryUntilTruthyResult(() => document.getElementsByTagName('video')[0]);
    let settingsEditorHook;
    let flashMessageHook;
    let overlayHook;

    function initPlayerInfo() {
      playerInfo.url = $XbmT$export$player.getVideoUrl();
      playerInfo.playerData = $XbmT$export$player.getVideoData();
      playerInfo.duration = $XbmT$export$player.getDuration();
      playerInfo.video = document.getElementsByTagName('video')[0];
      playerInfo.video.setAttribute('id', 'yt-clipper-video');
      playerInfo.aspectRatio = $XbmT$export$player.getVideoAspectRatio();
      playerInfo.isVerticalVideo = playerInfo.aspectRatio <= 1;
      playerInfo.progress_bar = document.getElementsByClassName('ytp-progress-bar')[0];
      playerInfo.watchFlexy = document.getElementsByTagName('ytd-watch-flexy')[0];
      playerInfo.infoContents = document.getElementById('info-contents');
      playerInfo.container = document.querySelector('#ytd-player #container');
      flashMessageHook = playerInfo.infoContents;
      playerInfo.columns = document.getElementById('columns');
      updateSettingsEditorHook();
      playerInfo.annotations = document.getElementsByClassName('ytp-iv-video-content')[0];
      overlayHook = document.getElementsByClassName('html5-video-container')[0];
      playerInfo.controls = document.getElementsByClassName('ytp-chrome-bottom')[0];
    }

    function updateSettingsEditorHook() {
      if (playerInfo.watchFlexy.theater) {
        settingsEditorHook = playerInfo.columns;
      } else {
        settingsEditorHook = playerInfo.infoContents;
      }
    }

    document.body.addEventListener('wheel', mouseWheelFrameSkipHandler);

    function mouseWheelFrameSkipHandler(event) {
      if (toggleKeys && !event.ctrlKey && !event.altKey && event.shiftKey && Math.abs(event.deltaY) > 0) {
        let fps = getFPS();

        if (event.deltaY < 0) {
          $XbmT$export$player.seekBy(1 / fps);
        } else if (event.deltaY > 0) {
          $XbmT$export$player.seekBy(-1 / fps);
        }
      }
    }

    let settings;
    let markersSvg;
    let selectedMarkerPairOverlay;
    let startMarkerNumberings;
    let endMarkerNumberings;

    function initMarkersContainer() {
      settings = {
        videoID: playerInfo.playerData.video_id,
        videoTitle: playerInfo.playerData.title,
        newMarkerSpeed: 1.0,
        newMarkerCrop: '0:0:iw:ih',
        titleSuffix: `[${playerInfo.playerData.video_id}]`,
        isVerticalVideo: playerInfo.isVerticalVideo,
        cropRes: playerInfo.isVerticalVideo ? '1080x1920' : '1920x1080',
        cropResWidth: playerInfo.isVerticalVideo ? 1080 : 1920,
        cropResHeight: playerInfo.isVerticalVideo ? 1920 : 1080,
        markerPairMergeList: ''
      };
      const markersDiv = document.createElement('div');
      markersDiv.setAttribute('id', 'markers-div');
      markersDiv.innerHTML = `\
    <svg id="markers-svg"></svg>
    <svg id="selected-marker-pair-overlay" style="display:none">
      <rect id="selected-start-marker-overlay" width="1.5px" height="8.5px" y="3.5px" class="selected-marker-overlay"></rect>
      <rect id="selected-end-marker-overlay" width="1.5px" height="8.5px" y="3.5px" class="selected-marker-overlay"></rect>
    </svg>
    <svg id="start-marker-numberings"></svg>
    <svg id="end-marker-numberings"></svg>
    `;
      playerInfo.progress_bar.appendChild(markersDiv);
      markersSvg = markersDiv.children[0];
      selectedMarkerPairOverlay = markersDiv.children[1];
      startMarkerNumberings = markersDiv.children[2];
      endMarkerNumberings = markersDiv.children[3];
    }

    function injectCSS(css, id) {
      const style = document.createElement('style');
      style.setAttribute('id', id);
      style.innerHTML = css;
      document.body.appendChild(style);
      return style;
    }

    const adjustRotatedVideoPositionCSS = `\
    @media (min-aspect-ratio: 29/18) {
      #yt-clipper-video {
        margin-left: 36%;
      }
    }
    @media (min-aspect-ratio: 40/18) {
      #yt-clipper-video {
        margin-left: 25%;
      }
    }
    @media (max-aspect-ratio: 29/18) {
      #yt-clipper-video {
        margin-left: 34%;
      }
    }
    @media (max-aspect-ratio: 13/9) {
      #yt-clipper-video {
        margin-left: 32%;
      }
    }
    @media (max-aspect-ratio: 23/18) {
      #yt-clipper-video {
        margin-left: 30%;
      }
    }
    @media (max-aspect-ratio: 10/9) {
      #yt-clipper-video {
        margin-left: 26%;
      }
    }
    @media (max-aspect-ratio: 17/18) {
      #yt-clipper-video {
        margin-left: 22%;
      }
    }
    @media (max-aspect-ratio: 7/9) {
      #yt-clipper-video {
        margin-left: 16%;
      }
    }
    @media (max-aspect-ratio: 6/9) {
      #yt-clipper-video {
        margin-left: 14%;
      }
    }
    @media (max-aspect-ratio: 11/18) {
      #yt-clipper-video {
        margin-left: 10%;
      }
    }
    @media (max-aspect-ratio: 5/9) {
      #yt-clipper-video {
        margin-left: 0%;
      }
    }
    `;
    let rotatedVideoCSS;
    let fullscreenRotatedVideoCSS;
    let rotatedVideoPreviewsCSS;
    let rotatedVideoStyle;
    let adjustRotatedVideoPositionStyle;
    let fullscreenRotatedVideoStyle;
    let rotatedVideoPreviewsStyle;
    let rotation = 0;

    function rotateVideo(direction) {
      if (direction === 'clock') {
        rotation = rotation === 0 ? 90 : 0;
      } else if (direction === 'cclock') {
        rotation = rotation === 0 ? -90 : 0;
      }

      if (rotation === 90 || rotation === -90) {
        let scale = 1;
        scale = 1 / playerInfo.aspectRatio;
        rotatedVideoCSS = `
        #yt-clipper-video {
          transform: rotate(${rotation}deg) scale(2.2) !important;
          max-width: 45vh;
          max-height: 100vw;
        }
        #player-theater-container {
          height: 100vh !important;
          max-height: none !important;
        }
        #page-manager {
          margin-top: 0px !important;
        }
        #masthead #container {
          display: none !important;
        }
      `;
        rotatedVideoPreviewsCSS = `\
        .ytp-tooltip {
          transform: translateY(-20%) rotate(${rotation}deg) !important;
        }
        .ytp-tooltip-text-wrapper {
          transform: rotate(${-rotation}deg) !important;
          opacity: 0.6;
        }
      `;
        fullscreenRotatedVideoCSS = `
      #yt-clipper-video {
        transform: rotate(${rotation}deg) scale(${scale}) !important;
        margin-left: auto;
      }
      `;

        if (!document.fullscreen) {
          adjustRotatedVideoPositionStyle = injectCSS(adjustRotatedVideoPositionCSS, 'adjust-rotated-video-position-css');
          rotatedVideoStyle = injectCSS(rotatedVideoCSS, 'yt-clipper-rotate-video-css');
          window.dispatchEvent(new Event('resize'));
        } else {
          fullscreenRotatedVideoStyle = injectCSS(fullscreenRotatedVideoCSS, 'fullscreen-rotated-video-css');
        }

        rotatedVideoPreviewsStyle = injectCSS(rotatedVideoPreviewsCSS, 'yt-clipper-rotated-video-previews-css');
        deleteElement(bigVideoPreviewsStyle);
        bigVideoPreviewsStyle = null;
        window.dispatchEvent(new Event('resize'));
        document.addEventListener('fullscreenchange', fullscreenRotateVideoHandler);
      } else {
        deleteElement(rotatedVideoStyle);
        deleteElement(adjustRotatedVideoPositionStyle);
        deleteElement(fullscreenRotatedVideoStyle);
        deleteElement(rotatedVideoPreviewsStyle);
        deleteElement(bigVideoPreviewsStyle);
        bigVideoPreviewsStyle = null;
        window.dispatchEvent(new Event('resize'));
        document.removeEventListener('fullscreenchange', fullscreenRotateVideoHandler);
      }
    }

    function fullscreenRotateVideoHandler() {
      if (document.fullscreen) {
        deleteElement(rotatedVideoStyle);
        deleteElement(adjustRotatedVideoPositionStyle);
        fullscreenRotatedVideoStyle = injectCSS(fullscreenRotatedVideoCSS, 'fullscreen-rotated-video-css');
      } else {
        deleteElement(fullscreenRotatedVideoStyle);
        adjustRotatedVideoPositionStyle = injectCSS(adjustRotatedVideoPositionCSS, 'adjust-rotated-video-position-css');
        rotatedVideoStyle = injectCSS(rotatedVideoCSS, 'yt-clipper-rotate-video-css');
        document.removeEventListener('fullscreenchange', fullscreenRotateVideoHandler);
        window.dispatchEvent(new Event('resize'));
      }
    }

    let bigVideoPreviewsStyle;

    function toggleBigVideoPreviews() {
      const bigVideoPreviewsCSS = `\
    .ytp-tooltip {
      left: 45% !important;
      transform: ${rotation ? `translateY(-285%) rotate(${rotation}deg)` : 'translateY(-160%) '} scale(4) !important;
      padding: 1px !important;
      border-radius: 1px !important;
    }
    .ytp-tooltip-text-wrapper {
      transform: scale(0.5) ${rotation ? `rotate(${-rotation}deg)` : ''}!important;
      opacity: 0.6;
    }
    `;

      if (bigVideoPreviewsStyle) {
        deleteElement(bigVideoPreviewsStyle);
        bigVideoPreviewsStyle = null;
      } else {
        bigVideoPreviewsStyle = injectCSS(bigVideoPreviewsCSS, 'yt-clipper-big-video-previews-css');
      }
    }

    function addForeignEventListeners() {
      const ids = ['search'];
      ids.forEach(id => {
        const input = document.getElementById(id);

        if (toggleKeys) {
          input.addEventListener('focus', () => toggleKeys = false, {
            capture: true
          });
          input.addEventListener('blur', () => toggleKeys = true, {
            capture: true
          });
        }
      });
    }

    function flashMessage(msg, color) {
      let lifetime = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : 2500;
      const flashDiv = document.createElement('div');
      flashDiv.setAttribute('class', 'msg-div flash-div');
      flashDiv.innerHTML = `<span class="flash-msg" style="color:${color}">${msg}</span>`;
      flashMessageHook.insertAdjacentElement('beforebegin', flashDiv);
      setTimeout(() => deleteElement(flashDiv), lifetime);
    }

    function deleteElement(elem) {
      if (elem && elem.parentElement) {
        elem.parentElement.removeChild(elem);
      }
    }

    function getShortestActiveMarkerPair() {
      let currentTime = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : video.currentTime;
      const activeMarkerPairs = markerPairs.filter(markerPair => {
        if (currentTime >= Math.floor(markerPair.start * 1e6) / 1e6 && currentTime <= Math.ceil(markerPair.end * 1e6) / 1e6) {
          return true;
        }

        return false;
      });

      if (activeMarkerPairs.length === 0) {
        return null;
      }

      const shortestActiveMarkerPair = activeMarkerPairs.reduce((prev, cur) => {
        if (cur.end - cur.start < prev.end - prev.start) {
          return cur;
        }

        return prev;
      });
      return shortestActiveMarkerPair;
    }

    let isSpeedPreviewOn = false;

    const toggleSpeedDucking = () => {
      if (isSpeedPreviewOn) {
        isSpeedPreviewOn = false;
        flashMessage('Auto speed ducking disabled', 'red');
      } else {
        isSpeedPreviewOn = true;
        requestAnimationFrame(updateSpeed);
        flashMessage('Auto speed ducking enabled', 'green');
      }
    };

    let prevSpeed = 1;
    const defaultRoundSpeedMapEasing = 0.05;

    function updateSpeed() {
      const shortestActiveMarkerPair = getShortestActiveMarkerPair();

      if (shortestActiveMarkerPair) {
        let markerPairSpeed;
        const enableSpeedMaps = shortestActiveMarkerPair.overrides.enableSpeedMaps !== undefined ? shortestActiveMarkerPair.overrides.enableSpeedMaps : settings.enableSpeedMaps !== false;

        if (enableSpeedMaps) {
          markerPairSpeed = getSpeedMapping(shortestActiveMarkerPair.speedMap, video.currentTime, defaultRoundSpeedMapEasing, 2);
        } else {
          markerPairSpeed = shortestActiveMarkerPair.speed;
        } // console.log(markerPairSpeed);


        if (prevSpeed !== markerPairSpeed) {
          $XbmT$export$player.setPlaybackRate(markerPairSpeed);
          prevSpeed = markerPairSpeed;
        }
      } else if (prevSpeed !== 1) {
        $XbmT$export$player.setPlaybackRate(1);
        prevSpeed = 1;
      }

      if (isSpeedPreviewOn) {
        requestAnimationFrame(updateSpeed);
      } else {
        $XbmT$export$player.setPlaybackRate(1);
        prevSpeed = 1;
      }
    }

    function getSpeedMapping(speedMap, time) {
      let roundMultiple = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : defaultRoundSpeedMapEasing;
      let roundPrecision = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : 2;
      let len = speedMap.length;

      if (len === 2 && speedMap[0].y === speedMap[1].y) {
        return speedMap[0].y;
      }

      len--;
      let left;
      let right;

      for (let i = 0; i < len; ++i) {
        if (speedMap[i].x <= time && time <= speedMap[i + 1].x) {
          left = speedMap[i];
          right = speedMap[i + 1];
          break;
        }
      }

      if (left && right) {
        if (left.y === right.y) {
          return left.y;
        }

        const elapsed = video.currentTime - left.x;
        const duration = right.x - left.x;
        let easedTimePercentage;

        if (easingMode === 'cubicInOut') {
          easedTimePercentage = $ATE0$export$cubicInOut(elapsed / duration);
        } else if (easingMode === 'linear') {
          easedTimePercentage = elapsed / duration;
        }

        const change = right.y - left.y;
        const rawSpeed = left.y + change * easedTimePercentage || right.y;
        const roundedSpeed = roundMultiple > 0 ? $BHXf$export$roundValue(rawSpeed, roundMultiple, roundPrecision) : rawSpeed; // console.log(roundedSpeed);

        return roundedSpeed;
      } else {
        return 1;
      }
    }

    let isMarkerLoopPreviewOn = false;

    function toggleMarkerLooping() {
      if (isMarkerLoopPreviewOn) {
        isMarkerLoopPreviewOn = false;
        flashMessage('Auto marker looping disabled', 'red');
      } else {
        isMarkerLoopPreviewOn = true;
        requestAnimationFrame(loopMarkerPair);
        flashMessage('Auto marker looping enabled', 'green');
      }
    }

    function loopMarkerPair() {
      if (isMarkerEditorOpen && !wasDefaultsEditorOpen) {
        if (prevSelectedMarkerPairIndex != null) {
          const markerPair = markerPairs[prevSelectedMarkerPairIndex];

          if (markerPair.speedMapLoop.enabled && markerPair.speedMapLoop.start > markerPair.start && markerPair.speedMapLoop.end < markerPair.end && markerPair.speedMapLoop.start < markerPair.speedMapLoop.end) {
            const isTimeBetweenSpeedMapLoop = markerPair.speedMapLoop.start <= video.currentTime && video.currentTime <= markerPair.speedMapLoop.end;

            if (!isTimeBetweenSpeedMapLoop) {
              $XbmT$export$player.seekTo(markerPair.speedMapLoop.start);
            }
          } else {
            const isTimeBetweenMarkerPair = markerPair.start <= video.currentTime && video.currentTime <= markerPair.end;

            if (!isTimeBetweenMarkerPair) {
              $XbmT$export$player.seekTo(markerPair.start);
            }
          }
        }
      }

      if (isMarkerLoopPreviewOn) {
        requestAnimationFrame(loopMarkerPair);
      }
    }

    let gammaFilterDiv;
    let isGammaPreviewOn = false;
    let gammaR;
    let gammaG;
    let gammaB;
    let gammaFilterSvg;

    function toggleGammaPreview() {
      if (!gammaFilterDiv) {
        gammaFilterDiv = document.createElement('div');
        gammaFilterDiv.setAttribute('id', 'gamma-filter-div');
        gammaFilterDiv.innerHTML = `\
      <svg id="gamma-filter-svg" xmlns="http://www.w3.org/2000/svg" width="0" height="0">
        <defs>
          <filter id="gamma-filter">
            <feComponentTransfer id="gamma-filter-comp-transfer">
              <feFuncR id="gamma-r" type="gamma" offset="0" amplitude="1"></feFuncR>
              <feFuncG id="gamma-g" type="gamma" offset="0" amplitude="1"></feFuncG>
              <feFuncB id="gamma-b" type="gamma" offset="0" amplitude="1"></feFuncB>
            </feComponentTransfer>
          </filter>
        </defs>
      </svg>
      `;
        document.body.appendChild(gammaFilterDiv);
        gammaFilterSvg = gammaFilterDiv.firstElementChild;
        gammaR = document.getElementById('gamma-r');
        gammaG = document.getElementById('gamma-g');
        gammaB = document.getElementById('gamma-b');
      }

      if (!isGammaPreviewOn) {
        video.style.filter = 'url(#gamma-filter)';
        isGammaPreviewOn = true;
        requestAnimationFrame(gammaPreviewHandler);
        flashMessage('Gamma preview enabled', 'green');
      } else {
        video.style.filter = null;
        isGammaPreviewOn = false;
        flashMessage('Gamma preview disabled', 'red');
      }
    }

    let prevGammaVal = 1;

    function gammaPreviewHandler() {
      const shortestActiveMarkerPair = getShortestActiveMarkerPair();

      if (shortestActiveMarkerPair) {
        const markerPairGamma = shortestActiveMarkerPair.overrides.gamma || settings.gamma || 1;

        if (prevGammaVal !== markerPairGamma) {
          // console.log(`Updating gamma from ${prevGammaVal} to ${markerPairGamma}`);
          gammaR.exponent.baseVal = markerPairGamma;
          gammaG.exponent.baseVal = markerPairGamma;
          gammaB.exponent.baseVal = markerPairGamma; // force re-render of filter (possible bug with chrome and other browsers?)

          gammaFilterSvg.setAttribute('width', '0');
          prevGammaVal = markerPairGamma;
        }
      } else {
        if (prevGammaVal !== 1) {
          // console.log(`Updating gamma from ${prevGammaVal} to 1`);
          gammaR.exponent.baseVal = 1;
          gammaG.exponent.baseVal = 1;
          gammaB.exponent.baseVal = 1;
          gammaFilterSvg.setAttribute('width', '0');
          prevGammaVal = 1;
        }
      }

      if (isGammaPreviewOn) {
        requestAnimationFrame(gammaPreviewHandler);
      }
    }

    let isFadeLoopPreviewOn = false;

    function toggleFadeLoopPreview() {
      if (!isFadeLoopPreviewOn) {
        isFadeLoopPreviewOn = true;
        requestAnimationFrame(fadeLoopPreviewHandler);
        flashMessage('Fade loop preview enabled', 'green');
      } else {
        isFadeLoopPreviewOn = false;
        video.style.opacity = '1';
        flashMessage('Fade loop preview disabled', 'red');
      }
    }

    function fadeLoopPreviewHandler() {
      const currentTime = video.currentTime;
      const shortestActiveMarkerPair = getShortestActiveMarkerPair();

      if (shortestActiveMarkerPair && shortestActiveMarkerPair.overrides.loop === 'fade') {
        const currentTimeP = getFadeBounds(shortestActiveMarkerPair, currentTime);

        if (currentTimeP == null) {
          video.style.opacity = '1';
        } else {
          let currentTimeEased = $ATE0$export$cubicInOut(currentTimeP);
          video.style.opacity = currentTimeEased.toString(); // console.log(video.style.opacity);
        }
      } else {
        video.style.opacity = '1';
      }

      if (isFadeLoopPreviewOn) {
        requestAnimationFrame(fadeLoopPreviewHandler);
      }
    }

    function getFadeBounds(markerPair, currentTime) {
      const start = Math.floor(markerPair.start * 1e6) / 1e6;
      const end = Math.ceil(markerPair.end * 1e6) / 1e6;
      const inputDuration = end - start;
      const outputDuration = markerPair.outputDuration;
      let fadeDuration = markerPair.overrides.fadeDuration || settings.fadeDuration || 0.5;
      fadeDuration = Math.min(fadeDuration, 0.4 * outputDuration);
      const fadeInStartP = 0;
      const fadeInEndP = fadeDuration / outputDuration;
      const fadeOutStartP = (outputDuration - fadeDuration) / outputDuration;
      const fadeOutEndP = outputDuration / outputDuration;
      let currentTimeP = (currentTime - start) / inputDuration;

      if (currentTimeP >= fadeInStartP && currentTimeP <= fadeInEndP) {
        currentTimeP = (currentTime - start) / fadeDuration;
        return currentTimeP;
      } else if (currentTimeP >= fadeOutStartP && currentTimeP <= fadeOutEndP) {
        currentTimeP = 1 - (currentTime - start - (inputDuration - fadeDuration)) / fadeDuration;
        return currentTimeP;
      } else {
        return null;
      }
    }

    let isAllPreviewsOn = false;

    function toggleAllPreviews() {
      isAllPreviewsOn = isSpeedPreviewOn && isMarkerLoopPreviewOn && isGammaPreviewOn && isFadeLoopPreviewOn;

      if (!isAllPreviewsOn) {
        !isSpeedPreviewOn && toggleSpeedDucking();
        !isMarkerLoopPreviewOn && toggleMarkerLooping();
        !isGammaPreviewOn && toggleGammaPreview();
        !isFadeLoopPreviewOn && toggleFadeLoopPreview();
        isAllPreviewsOn = true;
      } else {
        isSpeedPreviewOn && toggleSpeedDucking();
        isMarkerLoopPreviewOn && toggleMarkerLooping();
        isGammaPreviewOn && toggleGammaPreview();
        isFadeLoopPreviewOn && toggleFadeLoopPreview();
        isAllPreviewsOn = false;
      }
    }

    function jumpToNearestMarkerOrPair(e, keyCode) {
      if (!arrowKeyCropAdjustmentEnabled) {
        const currentEndMarker = enableMarkerHotkeys.endMarker;

        if (e.ctrlKey && !e.altKey && !e.shiftKey) {
          jumpToNearestMarker(e, video.currentTime, keyCode);
        } else if (isMarkerEditorOpen && currentEndMarker && e.altKey && !e.shiftKey) {
          jumpToNearestMarkerPair(e, currentEndMarker, keyCode);
        }
      }
    }

    function jumpToNearestMarkerPair(e, currentEndMarker, keyCode) {
      e.preventDefault();
      e.stopImmediatePropagation();
      let index = parseInt(currentEndMarker.getAttribute('idx')) - 1;
      let targetMarker;

      if (keyCode === 'ArrowLeft' && index > 0) {
        targetMarker = enableMarkerHotkeys.endMarker.previousSibling.previousSibling;
        targetMarker && toggleMarkerPairEditor(targetMarker);

        if (e.ctrlKey) {
          index--;
          $XbmT$export$player.seekTo(markerPairs[index].start);
        }
      } else if (keyCode === 'ArrowRight' && index < markerPairs.length - 1) {
        targetMarker = enableMarkerHotkeys.endMarker.nextSibling.nextSibling;
        targetMarker && toggleMarkerPairEditor(targetMarker);

        if (e.ctrlKey) {
          index++;
          $XbmT$export$player.seekTo(markerPairs[index].start);
        }
      }

      return;
    }

    function jumpToNearestMarker(e, currentTime, keyCode) {
      e.preventDefault();
      e.stopImmediatePropagation();
      let minDist = 0; // Choose marker time to jump to based on low precision time distance
      // Avoids being unable to jump away from a marker that the current time is very close to

      let times = markerPairs.map(markerPair => {
        const distToStartMarker = markerPair.start - currentTime;
        const distToStartMarkerFixed = parseFloat(distToStartMarker.toFixed(1));
        const distToEndMarker = markerPair.end - currentTime;
        const distToEndMarkerFixed = parseFloat(distToEndMarker.toFixed(1));
        return [{
          distToMarker: distToStartMarker,
          distToMarkerFixed: distToStartMarkerFixed
        }, {
          distToMarker: distToEndMarker,
          distToMarkerFixed: distToEndMarkerFixed
        }];
      });
      times = times.flat();

      if (keyCode === 'ArrowLeft') {
        minDist = times.reduce((prevDistToMarker, dist) => {
          dist.distToMarkerFixed = dist.distToMarkerFixed >= 0 ? -Infinity : dist.distToMarkerFixed;

          if (dist.distToMarkerFixed > prevDistToMarker) {
            return dist.distToMarker;
          } else {
            return prevDistToMarker;
          }
        }, -Infinity);
      } else if (keyCode === 'ArrowRight') {
        minDist = times.reduce((prevDistToMarker, dist) => {
          dist.distToMarkerFixed = dist.distToMarkerFixed <= 0 ? Infinity : dist.distToMarkerFixed;

          if (dist.distToMarkerFixed < prevDistToMarker) {
            return dist.distToMarker;
          } else {
            return prevDistToMarker;
          }
        }, Infinity);
      }

      if (minDist != Infinity && minDist != -Infinity && minDist != 0) {
        $XbmT$export$player.seekTo(minDist + currentTime);
      }
    }

    function saveMarkersAndSettings() {
      const settingsJSON = getSettingsJSON();
      const blob = new Blob([settingsJSON], {
        type: 'text/plain;charset=utf-8'
      });
      $oSxG$exports.saveAs(blob, `${settings.titleSuffix}.json`);
    }

    function getSettingsJSON() {
      markerPairs.forEach((markerPair, index) => {
        const speed = markerPair.speed;

        if (typeof speed === 'string') {
          markerPair.speed = Number(speed);
          console.log(`Converted marker pair ${index}'s speed from String to Number`);
        }
      });
      const markerPairsNumbered = markerPairs.map((markerPair, idx) => {
        const markerPairNumbered = Object.assign({
          number: idx + 1
        }, markerPair, {
          speedMapLoop: undefined,
          speedMap: isVariableSpeed(markerPair.speedMap) ? markerPair.speedMap : undefined,
          startNumbering: undefined,
          endNumbering: undefined
        });
        return markerPairNumbered;
      });
      const settingsJSON = JSON.stringify(Object.assign({}, settings, {
        version: $XbmT$var$__version__,
        markerPairs: markerPairsNumbered
      }), undefined, 2);
      return settingsJSON;
    }

    function isVariableSpeed(speedMap) {
      if (speedMap.length < 2) return false;
      let isVariableSpeed = speedMap.some((speedPoint, i) => {
        if (i === speedMap.length - 1) return false;
        return speedPoint.y !== speedMap[i + 1].y;
      });
      return isVariableSpeed;
    }

    function loadMarkers() {
      const markersUploadDiv = document.getElementById('markers-upload-div');

      if (markersUploadDiv) {
        deleteElement(markersUploadDiv);
      } else {
        const markersUploadDiv = document.createElement('div');
        markersUploadDiv.setAttribute('id', 'markers-upload-div');
        markersUploadDiv.setAttribute('style', 'color:grey;margin-top:2px;padding:2px;border:2px outset grey');
        markersUploadDiv.innerHTML = `
      <fieldset>\
        <h2>Upload a markers .json file.</h2>\
        <input type="file" id="markers-json-input">\
        <input type="button" id="upload-markers-json" style="color:grey" value="Load">\
      </fieldset>`;
        updateSettingsEditorHook();
        settingsEditorHook.insertAdjacentElement('beforebegin', markersUploadDiv);
        const fileUploadButton = document.getElementById('upload-markers-json');
        fileUploadButton.onclick = loadMarkersJson;
      }
    }

    function loadMarkersJson() {
      const input = document.getElementById('markers-json-input');
      console.log(input.files);
      const file = input.files[0];
      const fr = new FileReader();
      fr.onload = receivedJson;
      fr.readAsText(file);
      const markersUploadDiv = document.getElementById('markers-upload-div');
      deleteElement(markersUploadDiv);
    }

    function receivedJson(e) {
      const lines = e.target.result;
      const markersJson = JSON.parse(lines);
      console.log(markersJson);
      flashMessage('Loading markers...', 'green');

      if (markersJson) {
        // move markers field to marker Pairs for backwards compat)
        if (markersJson.markers && !markersJson.markerPairs) {
          markersJson.markerPairs = markersJson.markers;
          delete markersJson.markers;
        }

        if (!markersJson.markerPairs) {
          flashMessage('Could not find markers or markerPairs field. Could not load marker data.', 'red');
        } // copy markersJson to settings object less markerPairs field


        const _markerPairs = markersJson.markerPairs,
              loadedSettings = $XbmT$var$__rest(markersJson, ["markerPairs"]);
        settings = Object.assign({}, settings, loadedSettings);
        markersJson.markerPairs.forEach(markerPair => {
          const startMarkerConfig = {
            time: markerPair.start,
            type: 'start'
          };
          const endMarkerConfig = {
            time: markerPair.end,
            type: 'end',
            crop: markerPair.crop,
            speed: markerPair.speed,
            speedMap: markerPair.speedMap,
            speedMapLoop: markerPair.speedMapLoop,
            overrides: markerPair.overrides
          };
          addMarker(startMarkerConfig);
          addMarker(endMarkerConfig);
        });
      }
    }

    const marker_attrs = {
      class: 'marker',
      markerPairOverridesEditorDisplay: 'none'
    };

    function addMarker() {
      let markerConfig = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
      const roughCurrentTime = markerConfig.time || $XbmT$export$player.getCurrentTime();
      const currentFrameTime = getCurrentFrameTime(roughCurrentTime);
      const progressPos = currentFrameTime / playerInfo.duration * 100;

      if (!start && currentFrameTime <= startTime) {
        flashMessage('Cannot add end marker before start marker.', 'red');
        return;
      }

      const marker = document.createElementNS('http://www.w3.org/2000/svg', 'rect');
      markersSvg.appendChild(marker);
      $BHXf$export$setAttributes(marker, marker_attrs);
      marker.setAttribute('x', `${progressPos}%`); // set width and height attributes for browsers not supporting svg 2

      marker.setAttribute('width', '1.5px');
      marker.setAttribute('height', '16px');
      const rectIdx = markerPairs.length + 1;
      marker.setAttribute('idx', rectIdx.toString());

      if (start === true) {
        marker.classList.add('start-marker');
        marker.setAttribute('type', 'start');
        marker.setAttribute('z-index', '1');
        startTime = currentFrameTime;
      } else {
        marker.addEventListener('mouseover', toggleMarkerPairEditorHandler, false);
        marker.classList.add('end-marker');
        marker.setAttribute('type', 'end');
        marker.setAttribute('z-index', '2');
        const startProgressPos = startTime / playerInfo.duration * 100;

        const _addMarkerPairNumberi = addMarkerPairNumberings(rectIdx, startProgressPos, progressPos),
              _addMarkerPairNumberi2 = $XbmT$var$_slicedToArray(_addMarkerPairNumberi, 2),
              startNumbering = _addMarkerPairNumberi2[0],
              endNumbering = _addMarkerPairNumberi2[1];

        updateMarkerPairsArray(currentFrameTime, Object.assign({}, markerConfig, {
          startNumbering,
          endNumbering
        }));
        updateMarkerPairEditor();
      }

      start = !start;
      console.log(markerPairs);
    }

    function getFPS() {
      let defaultFPS = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : 60;

      try {
        return parseFloat($XbmT$export$player.getStatsForNerds().resolution.match(/@(\d+)/)[1]);
      } catch (e) {
        console.log('Could not detect fps', e);
        return defaultFPS; // by default parameter value assume high fps to avoid skipping frames
      }
    }

    function getCurrentFrameTime(roughCurrentTime) {
      let currentFrameTime;
      let fps = getFPS();
      fps ? currentFrameTime = Math.floor(roughCurrentTime * fps) / fps : currentFrameTime = roughCurrentTime;
      return currentFrameTime;
    }

    function updateMarkerPairsArray(currentTime, markerPairConfig) {
      const speed = markerPairConfig.speed || settings.newMarkerSpeed;
      const newMarkerPair = {
        start: startTime,
        end: currentTime,
        crop: markerPairConfig.crop || settings.newMarkerCrop,
        speed: speed,
        outputDuration: markerPairConfig.outputDuration || currentTime - startTime,
        overrides: markerPairConfig.overrides || {},
        speedMapLoop: markerPairConfig.speedMapLoop || {
          enabled: true
        },
        speedMap: markerPairConfig.speedMap || [{
          x: startTime,
          y: speed
        }, {
          x: currentTime,
          y: speed
        }],
        startNumbering: markerPairConfig.startNumbering,
        endNumbering: markerPairConfig.endNumbering
      };
      markerPairs.push(newMarkerPair);
    }

    function updateMarkerPairEditor() {
      if (isMarkerEditorOpen) {
        const markerPairCountLabel = document.getElementById('marker-pair-count-label');

        if (markerPairCountLabel) {
          markerPairCountLabel.textContent = markerPairs.length.toString();
        }
      }
    }

    function addMarkerPairNumberings(idx, startProgressPos, endProgressPos) {
      const startNumbering = $BHXf$export$htmlToSVGElement(`\
        <text class="markerNumbering startMarkerNumbering" idx="${idx}"\
        x="${startProgressPos}%" y="11.5px"
        text-anchor="middle">\
        ${idx}\
        </text>\
        `);
      const endNumbering = $BHXf$export$htmlToSVGElement(`\
        <text class="markerNumbering endMarkerNumbering" idx="${idx}"\
          x="${endProgressPos}%" y="11.5px"
          text-anchor="middle"
        >\
        ${idx}\
        </text>\
        `);
      const startNumberingText = startMarkerNumberings.appendChild(startNumbering);
      const endNumberingText = endMarkerNumberings.appendChild(endNumbering);
      return [startNumberingText, endNumberingText];
    }

    function undoMarker() {
      const targetMarker = markersSvg.lastElementChild;
      const targetMarkerType = targetMarker.getAttribute('type'); // do not undo markers part of or before a currently select marker pair

      if (targetMarkerType === 'end' && isMarkerEditorOpen && enableMarkerHotkeys.markerPairIndex >= markerPairs.length) {
        toggleMarkerPairEditor(enableMarkerHotkeys.endMarker);
      }

      if (targetMarker) {
        deleteElement(targetMarker);

        if (targetMarkerType === 'end') {
          const markerPair = markerPairs[markerPairs.length - 1];
          deleteElement(markerPair.startNumbering);
          deleteElement(markerPair.endNumbering);
          startTime = markerPair.start;
          markerPairsHistory.push(markerPairs.pop());
          console.log(markerPairs);
          updateMarkerPairEditor();
        }

        start = !start;
      }
    }

    function redoMarker() {
      if (markerPairsHistory.length > 0) {
        const markerPairToRestore = markerPairsHistory[markerPairsHistory.length - 1];

        if (start) {
          addMarker({
            time: markerPairToRestore.start
          });
        } else {
          markerPairsHistory.pop();
          addMarker(Object.assign({}, markerPairToRestore, {
            time: markerPairToRestore.end
          }));
        }
      }
    }

    function cyclePlayerSpeedDown() {
      let newSpeed = $XbmT$export$player.getPlaybackRate() - 0.25;
      newSpeed = newSpeed <= 0 ? 1 : newSpeed;
      $XbmT$export$player.setPlaybackRate(newSpeed);
      flashMessage(`Video playback speed set to ${newSpeed}`, 'green');
    }

    let globalEncodeSettingsEditorDisplay = 'none';
    let cropInput;

    function toggleGlobalSettingsEditor() {
      if (isMarkerEditorOpen) {
        toggleOffMarkerPairEditor();
      }

      if (wasDefaultsEditorOpen) {
        wasDefaultsEditorOpen = false;
      } else {
        hideSelectedMarkerPairCropOverlay();
        createCropOverlay(settings.newMarkerCrop);
        const markerInputs = document.createElement('div');
        const cropInputValidation = `\\d+:\\d+:(\\d+|iw):(\\d+|ih)`;
        const csvRange = `(\\d{1,2})([,-]\\d{1,2})*`;
        const mergeListInputValidation = `(${csvRange})+(;${csvRange})*`;
        const gte100 = `([1-9]\\d{3}|[1-9]\\d{2})`;
        const cropResInputValidation = `${gte100}x${gte100}`;
        const resList = playerInfo.isVerticalVideo ? `<option value="1080x1920"><option value="2160x3840">` : `<option value="1920x1080"><option value="3840x2160">`;
        const denoise = settings.denoise;
        const denoiseDesc = denoise ? denoise.desc : null;
        const vidstab = settings.videoStabilization;
        const vidstabDesc = vidstab ? vidstab.desc : null;
        const vidstabDynamicZoomEnabled = vidstab ? vidstab.enabled && vidstab.optzoom == 2 ? true : false : null;
        const markerPairMergelistDurations = getMarkerPairMergeListDurations();
        markerInputs.setAttribute('id', 'markerInputsDiv');
        markerInputs.innerHTML = `\
      <div id="new-marker-defaults-inputs" class="yt_clipper-settings-editor">
        <span style="font-weight:bold">New Marker Settings: </span>
        <div class="editor-input-div">
          <span class="editor-input-label">Speed: </span>
          <input id="speed-input" class="yt_clipper-input"  type="number" placeholder="speed" value="${settings.newMarkerSpeed}" step="0.05" min="0.05" max="2" style="width:4em">
        </div>
        <div class="editor-input-div">
          <span class="editor-input-label">Crop: </span>
          <input id="crop-input" class="yt_clipper-input" value="${settings.newMarkerCrop}" pattern="${cropInputValidation}" style="width:10em" required>
        </div>
      </div>
      <div id="global-marker-settings" class="yt_clipper-settings-editor">
        <span style="font-weight:bold">Global Settings: </span>
        <div class="editor-input-div">
          <span class="editor-input-label"> Title Suffix: </span>
          <input id="title-suffix-input" class="yt_clipper-input" value="${settings.titleSuffix}" style="background-color:lightgreen;width:20em;text-align:right">
        </div>
        <div class="editor-input-div">
          <span class="editor-input-label"> Crop Resolution: </span>
          <input id="crop-res-input" class="yt_clipper-input" list="resolutions" pattern="${cropResInputValidation}" value="${settings.cropRes}" style="width:7em" required>
          <datalist id="resolutions" autocomplete="off">${resList}</datalist>
        </div>
        <div class="editor-input-div">
          <span class="editor-input-label"> Merge List: </span>
          <input id="merge-list-input" class="yt_clipper-input" pattern="${mergeListInputValidation}" value="${settings.markerPairMergeList != null ? settings.markerPairMergeList : ''}" placeholder="None" style="width:15em">
        </div>
        <div style="display:inline-block">
            <span style="font-weight:bold">Merge Durations: </span>
            <span id="merge-list-durations">${markerPairMergelistDurations}</span>
        </div>
        <div class="editor-input-div">
          <span>Rotate: </span>
          <input id="rotate-0" class="yt_clipper-input" type="radio" name="rotate" value="0" ${settings.rotate == null || settings.rotate === '0' ? 'checked' : ''}></input>
          <label for="rotate-0">0&#x00B0; </label>
          <input id="rotate-90-clock" class="yt_clipper-input" type="radio" value="clock" name="rotate" ${settings.rotate === 'clock' ? 'checked' : ''}></input>
          <label for="rotate-90-clock">90&#x00B0; &#x27F3;</label>
          <input id="rotate-90-counterclock" class="yt_clipper-input" type="radio" value="cclock" name="rotate" ${settings.rotate === 'cclock' ? 'checked' : ''}></input>
          <label for="rotate-90-counterclock">90&#x00B0; &#x27F2;</label>
        </div>
      </div>
      <div id="global-encode-settings" class="yt_clipper-settings-editor" style="display:${globalEncodeSettingsEditorDisplay}">
        <span style="font-weight:bold">Encode Settings: </span>
        <div class="editor-input-div">
          <span>Audio: </span>
          <select id="audio-input"> 
            <option ${settings.audio ? 'selected' : ''}>Enabled</option>
            <option ${settings.audio === false ? 'selected' : ''}>Disabled</option>
            <option value="Default" ${settings.audio == null ? 'selected' : ''}>Inherit (Disabled)</option>
          </select>
        </div>
        <div class="editor-input-div">
          <span>Encode Speed (0-5): </span>
          <input id="encode-speed-input" class="yt_clipper-input" type="number" min="0" max="5" step="1" value="${settings.encodeSpeed != null ? settings.encodeSpeed : ''}" placeholder="Auto" style="width:4em"></input>
        </div>
        <div class="editor-input-div">
          <span>CRF (0-63): </span>
          <input id="crf-input" class="yt_clipper-input" type="number" min="0" max="63" step="1" value="${settings.crf != null ? settings.crf : ''}" placeholder="Auto" style="width:4em"></input>
        </div>
        <div class="editor-input-div">
          <span>Target Bitrate (kb/s) (0 = &#x221E;): </span>
          <input id="target-max-bitrate-input" class="yt_clipper-input" type="number" min="0" max="1e5"step="100" value="${settings.targetMaxBitrate != null ? settings.targetMaxBitrate : ''}" placeholder="Auto" "style="width:4em"></input>
        </div>
        <div class="editor-input-div">
          <span>Two-Pass: </span>
          <select id="two-pass-input"> 
            <option ${settings.twoPass ? 'selected' : ''}>Enabled</option>
            <option ${settings.twoPass === false ? 'selected' : ''}>Disabled</option>
            <option value="Default" ${settings.twoPass == null ? 'selected' : ''}>Inherit (Disabled)</option>
          </select>
        </div>
        <div class="editor-input-div">
          <span>Gamma (0-4): </span>
          <input id="gamma-input" class="yt_clipper-input" type="number" min="0.01" max="4.00" step="0.01" value="${settings.gamma != null ? settings.gamma : ''}" placeholder="1" style="width:4em"></input>
        </div>
        <div class="editor-input-div">
          <span>Expand Colors: </span>
          <select id="expand-color-range-input"> 
            <option ${settings.expandColorRange ? 'selected' : ''}>Enabled</option>
            <option ${settings.expandColorRange === false ? 'selected' : ''}>Disabled</option>
            <option value="Default" ${settings.expandColorRange == null ? 'selected' : ''}>Inherit (Disabled)</option>
          </select>
        </div>
        <div class="editor-input-div">
          <span>Denoise: </span>
          <select id="denoise-input">
            <option ${denoiseDesc === 'Very Strong' ? 'selected' : ''}>Very Strong</option>
            <option ${denoiseDesc === 'Strong' ? 'selected' : ''}>Strong</option>
            <option ${denoiseDesc === 'Medium' ? 'selected' : ''}>Medium</option>
            <option ${denoiseDesc === 'Weak' ? 'selected' : ''}>Weak</option>
            <option ${denoiseDesc === 'Very Weak' ? 'selected' : ''}>Very Weak</option>
            <option value="Inherit" ${denoiseDesc == null ? 'selected' : ''}>Inherit (Disabled)</option>
          </select>
        </div>
        <div class="editor-input-div">
          <span>Stabilization: </span>
          <select id="video-stabilization-input">
            <option ${vidstabDesc === 'Strongest' ? 'selected' : ''}>Strongest</option>
            <option ${vidstabDesc === 'Very Strong' ? 'selected' : ''}>Very Strong</option>
            <option ${vidstabDesc === 'Strong' ? 'selected' : ''}>Strong</option>
            <option ${vidstabDesc === 'Medium' ? 'selected' : ''}>Medium</option>
            <option ${vidstabDesc === 'Weak' ? 'selected' : ''}>Weak</option>
            <option ${vidstabDesc === 'Very Weak' ? 'selected' : ''}>Very Weak</option>
            <option value="Inherit" ${vidstabDesc == null ? 'selected' : ''}>Inherit (Disabled)</option>
          </select>
          <div class="editor-input-div">
            <span>Dynamic Zoom: </span>
            <select id="video-stabilization-dynamic-zoom-input"> 
              <option ${vidstabDynamicZoomEnabled ? 'selected' : ''}>Enabled</option>
              <option ${vidstabDynamicZoomEnabled === false ? 'selected' : ''}>Disabled</option>
              <option value="Default" ${vidstabDynamicZoomEnabled == null ? 'selected' : ''}>Inherit (Disabled)</option>
          </select>
          </div>
        </div>
        <div class="editor-input-div">
          <span>Speed Maps: </span>
            <select id="enable-speed-maps-input">
              <option ${settings.enableSpeedMaps ? 'selected' : ''}>Enabled</option>
              <option ${settings.enableSpeedMaps === false ? 'selected' : ''}>Disabled</option>
              <option value="Default" ${settings.enableSpeedMaps == null ? 'selected' : ''}>Inherit (Enabled)</option>
            </select>
        </div>
        <div class="editor-input-div">
          <span>Loop: </span>
            <select id="loop-input">
              <option ${settings.loop === 'fwrev' ? 'selected' : ''}>fwrev</option>
              <option ${settings.loop === 'fade' ? 'selected' : ''}>fade</option>
              <option ${settings.loop === 'none' ? 'selected' : ''}>none</option>
              <option value="Default" ${settings.loop == null ? 'selected' : ''}>Inherit (none)</option>
            </select>
          <div class="editor-input-div">
            <span>Fade Duration: </span>
            <input id="fade-duration-input" class="yt_clipper-input" type="number" min="0.1" step="0.1" value="${settings.fadeDuration != null ? settings.fadeDuration : ''}" placeholder="0.5" style="width:4em"></input>
          </div>
        </div>
      </div>
      `;
        updateSettingsEditorHook();
        settingsEditorHook.insertAdjacentElement('beforebegin', markerInputs);
        addSettingsInputListeners([['speed-input', 'newMarkerSpeed', 'number'], ['crop-input', 'newMarkerCrop', 'string'], ['crop-res-input', 'cropRes', 'string'], ['merge-list-input', 'markerPairMergeList', 'string'], ['title-suffix-input', 'titleSuffix', 'string'], ['gamma-input', 'gamma', 'number'], ['encode-speed-input', 'encodeSpeed', 'number'], ['crf-input', 'crf', 'number'], ['target-max-bitrate-input', 'targetMaxBitrate', 'number'], ['rotate-0', 'rotate', 'string'], ['rotate-90-clock', 'rotate', 'string'], ['rotate-90-counterclock', 'rotate', 'string'], ['two-pass-input', 'twoPass', 'ternary'], ['audio-input', 'audio', 'ternary'], ['expand-color-range-input', 'expandColorRange', 'ternary'], ['denoise-input', 'denoise', 'preset'], ['video-stabilization-input', 'videoStabilization', 'preset'], ['video-stabilization-dynamic-zoom-input', 'videoStabilizationDynamicZoom', 'ternary'], ['enable-speed-maps-input', 'enableSpeedMaps', 'ternary'], ['loop-input', 'loop', 'inheritableString'], ['fade-duration-input', 'fadeDuration', 'number']], settings);
        cropInput = document.getElementById('crop-input');
        wasDefaultsEditorOpen = true;
        isMarkerEditorOpen = true;
        addMarkerPairMergeListDurationsListener();
        addCropInputHotkeys();
      }
    }

    function addSettingsInputListeners(inputs, target) {
      inputs.forEach(input => {
        const id = input[0];
        const targetProperty = input[1];
        const valueType = input[2] || 'string';
        const inputElem = document.getElementById(id);
        inputElem.addEventListener('focus', () => toggleKeys = false, false);
        inputElem.addEventListener('blur', () => toggleKeys = true, false);
        inputElem.addEventListener('change', e => updateSettingsValue(e, target, targetProperty, valueType), false);
      });
    }

    const presetsMap = {
      videoStabilization: {
        Disabled: {
          desc: 'Disabled',
          enabled: false
        },
        'Very Weak': {
          desc: 'Very Weak',
          enabled: true,
          shakiness: 2,
          smoothing: 2,
          zoomspeed: 0.05
        },
        Weak: {
          desc: 'Weak',
          enabled: true,
          shakiness: 4,
          smoothing: 4,
          zoomspeed: 0.1
        },
        Medium: {
          desc: 'Medium',
          enabled: true,
          shakiness: 6,
          smoothing: 6,
          zoomspeed: 0.2
        },
        Strong: {
          desc: 'Strong',
          enabled: true,
          shakiness: 8,
          smoothing: 10,
          zoomspeed: 0.3
        },
        'Very Strong': {
          desc: 'Very Strong',
          enabled: true,
          shakiness: 10,
          smoothing: 16,
          zoomspeed: 0.4
        },
        Strongest: {
          desc: 'Strongest',
          enabled: true,
          shakiness: 10,
          smoothing: 0,
          zoomspeed: 0.5
        }
      },
      denoise: {
        Disabled: {
          enabled: false,
          desc: 'Disabled'
        },
        'Very Weak': {
          enabled: true,
          shakiness: 1,
          desc: 'Very Weak'
        },
        Weak: {
          enabled: true,
          lumaSpatial: 2,
          desc: 'Weak'
        },
        Medium: {
          enabled: true,
          lumaSpatial: 4,
          desc: 'Medium'
        },
        Strong: {
          enabled: true,
          lumaSpatial: 6,
          desc: 'Strong'
        },
        'Very Strong': {
          enabled: true,
          lumaSpatial: 8,
          desc: 'Very Strong'
        }
      }
    };

    function updateSettingsValue(e, target, targetProperty, valueType) {
      if (e.target.reportValidity()) {
        let newValue = e.target.value;

        if (newValue != null) {
          if (targetProperty !== 'markerPairMergeList' && newValue === '') {
            delete target[targetProperty];
            return;
          } else if (valueType === 'number') {
            newValue = parseFloat(newValue);
          } else if (valueType === 'boolean') {
            newValue = e.target.checked;
          } else if (valueType === 'ternary' || valueType === 'inheritableString') {
            if (newValue === 'Default' || newValue === 'Inherit') {
              delete target[targetProperty];
              return;
            } else if (newValue === 'Enabled') {
              newValue = true;
            } else if (newValue === 'Disabled') {
              newValue = false;
            } else {
              newValue = newValue.toLowerCase();
            }
          } else if (valueType === 'preset') {
            if (newValue === 'Inherit') {
              delete target[targetProperty];
              return;
            }

            newValue = presetsMap[targetProperty][newValue];
          }
        }

        target[targetProperty] = newValue;

        if (targetProperty === 'newMarkerCrop') {
          createCropOverlay(target.newMarkerCrop);
        }

        if (targetProperty === 'cropRes') {
          const prevWidth = target.cropResWidth;
          const prevHeight = target.cropResHeight;

          const _target$cropRes$split = target.cropRes.split('x').map(str => parseInt(str), 10),
                _target$cropRes$split2 = $XbmT$var$_slicedToArray(_target$cropRes$split, 2),
                newWidth = _target$cropRes$split2[0],
                newHeight = _target$cropRes$split2[1];

          const cropMultipleX = newWidth / prevWidth;
          const cropMultipleY = newHeight / prevHeight;
          target.cropResWidth = newWidth;
          target.cropResHeight = newHeight;
          multiplyAllCrops(cropMultipleX, cropMultipleY);
        }

        if (targetProperty === 'crop') {
          const _extractCropComponent5 = extractCropComponents(newValue),
                _extractCropComponent6 = $XbmT$var$_slicedToArray(_extractCropComponent5, 4),
                x = _extractCropComponent6[0],
                y = _extractCropComponent6[1],
                w = _extractCropComponent6[2],
                h = _extractCropComponent6[3];

          setCropOverlayDimensions(x, y, w, h);
        }

        if (targetProperty === 'speed') {
          const speedMap = target.speedMap;

          if (speedMap.length === 2 && speedMap[0].y === speedMap[1].y) {
            target.speedMap[1].y = newValue;
          }

          target.speedMap[0].y = newValue;
          speedChart && speedChart.update();
          updateMarkerPairDuration(target);
        }
      }
    }

    function multiplyAllCrops(cropMultipleX, cropMultipleY) {
      const cropString = settings.newMarkerCrop;
      const multipliedCropString = multiplyCropString(cropMultipleX, cropMultipleY, cropString);
      settings.newMarkerCrop = multipliedCropString;
      cropInput.value = multipliedCropString;

      if (markerPairs) {
        markerPairs.forEach(markerPair => {
          const multipliedCropString = multiplyCropString(cropMultipleX, cropMultipleY, markerPair.crop);
          markerPair.crop = multipliedCropString;
        });
      }
    }

    function multiplyCropString(cropMultipleX, cropMultipleY, cropString) {
      let _cropString$split = cropString.split(':'),
          _cropString$split2 = $XbmT$var$_slicedToArray(_cropString$split, 4),
          x = _cropString$split2[0],
          y = _cropString$split2[1],
          width = _cropString$split2[2],
          height = _cropString$split2[3];

      x = Math.round(x * cropMultipleX);
      y = Math.round(y * cropMultipleY);
      width = width !== 'iw' ? Math.round(width * cropMultipleX) : width;
      height = height !== 'ih' ? Math.round(height * cropMultipleY) : height;
      const multipliedCropString = [x, y, width, height].join(':');
      return multipliedCropString;
    }

    function getMarkerPairMergeListDurations() {
      let markerPairMergeList = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : settings.markerPairMergeList;
      const durations = [];

      for (let merge of markerPairMergeList.split(';')) {
        let duration = 0;

        for (let mergeRange of merge.split(',')) {
          if (mergeRange.includes('-')) {
            let _mergeRange$split$map = mergeRange.split('-').map(str => parseInt(str, 10) - 1),
                _mergeRange$split$map2 = $XbmT$var$_slicedToArray(_mergeRange$split$map, 2),
                mergeRangeStart = _mergeRange$split$map2[0],
                mergeRangeEnd = _mergeRange$split$map2[1];

            if (mergeRangeStart > mergeRangeEnd) {
              var _ref = [mergeRangeEnd, mergeRangeStart];
              mergeRangeStart = _ref[0];
              mergeRangeEnd = _ref[1];
            }

            for (let idx = mergeRangeStart; idx <= mergeRangeEnd; idx++) {
              if (!isNaN(idx) && idx >= 0 && idx < markerPairs.length) {
                const marker = markerPairs[idx];
                duration += (marker.end - marker.start) / marker.speed;
              }
            }
          } else {
            const idx = parseInt(mergeRange, 10) - 1;

            if (!isNaN(idx) && idx >= 0 && idx < markerPairs.length) {
              const marker = markerPairs[idx];
              duration += (marker.end - marker.start) / marker.speed;
            }
          }
        }

        durations.push(duration);
      }

      const markerPairMergelistDurations = durations.map($BHXf$export$toHHMMSSTrimmed).join(' ; ');
      return markerPairMergelistDurations;
    }

    function addCropInputHotkeys() {
      cropInput.addEventListener('keydown', ke => {
        if (ke.code === 'ArrowUp' || ke.code === 'ArrowDown') {
          let cropString = cropInput.value;
          let cropStringArray = cropString.split(':');
          let cropArray = extractCropComponents(cropString); // let [x, y, w, ] = cropArray;

          const cropStringCursorPos = ke.target.selectionStart;
          let cropComponentCursorPos = cropStringCursorPos;
          let cropTarget = 0;

          while (cropComponentCursorPos - (cropStringArray[cropTarget].length + 1) >= 0) {
            cropComponentCursorPos -= cropStringArray[cropTarget].length + 1;
            cropTarget++;
          }

          if (cropTarget >= 0 && cropTarget <= cropArray.length - 1 && typeof cropArray[cropTarget] === 'number') {
            let changeAmount;

            if (!ke.altKey && !ke.shiftKey) {
              changeAmount = 10;
            } else if (ke.altKey && !ke.shiftKey) {
              changeAmount = 1;
            } else if (!ke.altKey && ke.shiftKey) {
              changeAmount = 50;
            } else if (ke.altKey && ke.shiftKey) {
              changeAmount = 100;
            }

            if (ke.code === 'ArrowUp') {
              cropArray[cropTarget] += changeAmount;
            } else if (ke.code === 'ArrowDown') {
              cropArray[cropTarget] -= changeAmount;
            }

            ke.preventDefault();
            ke.stopImmediatePropagation();
            cropArray = clampCropArray(cropArray, cropTarget);
            const updatedCropString = cropArray.join(':');
            cropInput.value = updatedCropString;
            let newCursorPos = cropStringCursorPos - cropComponentCursorPos;

            if (cropTarget === 3 && cropStringArray[3] === 'ih') {
              const cropStringLengthDelta = updatedCropString.length - cropString.length;
              const cursorPosAdjustment = cropStringLengthDelta - cropComponentCursorPos;
              newCursorPos += cursorPosAdjustment;
            }

            cropInput.selectionStart = newCursorPos;
            cropInput.selectionEnd = newCursorPos;
            cropInput.dispatchEvent(new Event('change'));
          }
        }
      });
    }

    function clampCropArray(cropArray, target) {
      let _cropArray = $XbmT$var$_slicedToArray(cropArray, 4),
          x = _cropArray[0],
          y = _cropArray[1],
          w = _cropArray[2],
          h = _cropArray[3];

      switch (target) {
        case 'x':
        case 0:
          x = $BHXf$export$clampNumber(x, 0, settings.cropResWidth - w);
          break;

        case 'y':
        case 1:
          y = $BHXf$export$clampNumber(y, 0, settings.cropResHeight - h);
          break;

        case 'w':
        case 2:
          w = $BHXf$export$clampNumber(w, 0, settings.cropResWidth - x);
          break;

        case 'h':
        case 3:
          h = $BHXf$export$clampNumber(h, 0, settings.cropResHeight - y);
          break;
      }

      return [x, y, w, h];
    }

    function addMarkerPairMergeListDurationsListener() {
      const markerPairMergeListInput = document.getElementById('merge-list-input');
      const markerPairMergeListDurationsSpan = document.getElementById('merge-list-durations');
      markerPairMergeListInput.addEventListener('change', () => {
        const markerPairMergelistDurations = getMarkerPairMergeListDurations();
        markerPairMergeListDurationsSpan.textContent = markerPairMergelistDurations;
      });
    }

    let shortcutsTableToggleButton;

    function injectToggleShortcutsTableButton() {
      const ytpRightControls = document.getElementsByClassName('ytp-right-controls')[0];
      shortcutsTableToggleButton = $BHXf$export$htmlToElement($XbmT$var$shortcutsTableToggleButtonHTML);
      shortcutsTableToggleButton.onclick = toggleShortcutsTable;
      ytpRightControls.insertAdjacentElement('afterbegin', shortcutsTableToggleButton);
    }

    function showShortcutsTableToggleButton() {
      if (shortcutsTableToggleButton) {
        shortcutsTableToggleButton.style.display = 'inline-block';
      }
    }

    function hideShortcutsTableToggleButton() {
      if (shortcutsTableToggleButton) {
        shortcutsTableToggleButton.style.display = 'none';
      }
    }

    let shortcutsTableContainer;

    function toggleShortcutsTable() {
      if (!shortcutsTableContainer) {
        injectCSS($XbmT$var$shortcutsTableStyle, 'shortcutsTableStyle');
        shortcutsTableContainer = document.createElement('div');
        shortcutsTableContainer.setAttribute('id', 'shortcutsTableContainer');
        shortcutsTableContainer.innerHTML = $XbmT$var$shortcutsTable;
        flashMessageHook.insertAdjacentElement('afterend', shortcutsTableContainer);
      } else if (shortcutsTableContainer.style.display !== 'none') {
        shortcutsTableContainer.style.display = 'none';
      } else {
        shortcutsTableContainer.style.display = 'block';
      }
    }

    const frameCaptureViewerHeadHTML = `\
      <title>yt_clipper Frame Capture Viewer</title>
      <style>
        body {
          margin: 0px;
          text-align: center;
        }
        #frames-div {
          font-family: Helvetica;
          background-color: rgb(160,50,20);
          margin: 0 auto;
          padding: 2px;
          width: 99%;
          text-align: center;
        }
        .frame-div {
          margin: 2px;
          padding: 2px;
          border: 2px black solid;
          font-weight: bold;
          color: black;
          text-align: center;
        }
        figcaption {
          display: inline-block;
          margin: 2px;
        }
        button {
          display: inline-block;
          font-weight: bold;
          margin-bottom: 2px;
          cursor: pointer;
          border: 2px solid black;
          border-radius: 4px;
        }
        button.download {
          background-color: rgb(66, 134, 244);
        }
        button.delete {
          background-color: red;
        }
        button:hover {
          box-shadow: 2px 4px 4px 0 rgba(0,0,0,0.2);
        }
        canvas {
          display: block;
          margin: 0 auto;
          ${$XbmT$export$player.getVideoAspectRatio() > 1 ? 'width: 98%;' : 'height: 96vh;'}
        }
        @keyframes flash {
          0% {
            opacity: 1;
          }
          100% {
            opacity: 0.5;
          }
        }
        .flash-div {
          animation-name: flash;
          animation-duration: 0.5s;
          animation-fill-mode: forwards;
        }
        </style>
      `;
    const frameCaptureViewerBodyHTML = `\
        <div id="frames-div"><strong></strong></div>
        `;
    let frameCaptureViewer;
    let frameCaptureViewerDoc;

    async function captureFrame() {
      const currentTime = video.currentTime;

      for (let i = 0; i < video.buffered.length; i++) {
        console.log(video.buffered.start(i), video.buffered.end(i));

        if (video.buffered.start(i) <= currentTime && currentTime <= video.buffered.end(i)) {
          break;
        }

        if (i === video.buffered.length - 1) {
          flashMessage('Frame not captured. Video has not yet buffered the frame.', 'red');
          return;
        }
      }

      const canvas = document.createElement('canvas');
      const context = canvas.getContext('2d');
      let resString;

      if (isMarkerEditorOpen && !wasDefaultsEditorOpen) {
        const idx = parseInt(prevSelectedEndMarker.getAttribute('idx'), 10) - 1;
        const markerPair = markerPairs[idx];
        const cropMultipleX = video.videoWidth / settings.cropResWidth;
        const cropMultipleY = video.videoHeight / settings.cropResHeight;
        resString = multiplyCropString(cropMultipleX, cropMultipleY, markerPair.crop);

        const _extractCropComponent7 = extractCropComponents(resString),
              _extractCropComponent8 = $XbmT$var$_slicedToArray(_extractCropComponent7, 4),
              x = _extractCropComponent8[0],
              y = _extractCropComponent8[1],
              w = _extractCropComponent8[2],
              h = _extractCropComponent8[3];

        canvas.width = w;
        canvas.height = h;

        if (h > w) {
          canvas.style.height = '96vh';
          canvas.style.width = 'auto';
        }

        context.drawImage(video, x, y, w, h, 0, 0, w, h);
        resString = `x${x}y${y}w${w}h${h}`;
      } else {
        resString = `x0y0w${video.videoWidth}h${video.videoHeight}`;
        canvas.width = video.videoWidth;
        canvas.height = video.videoHeight;
        context.drawImage(video, 0, 0, video.videoWidth, video.videoHeight);
      }

      if (!frameCaptureViewer || !frameCaptureViewerDoc || frameCaptureViewer.closed) {
        frameCaptureViewer = window.open('', 'window', `height=${window.innerHeight}, width=${window.innerWidth}`);

        frameCaptureViewer.downloadFrame = btn => {
          const frameCanvas = btn.parentElement.querySelector('canvas');
          frameCanvas.toBlob(blob => $oSxG$exports.saveAs(blob, frameCanvas.fileName));
        };

        frameCaptureViewer.deleteFrame = btn => {
          const frameDiv = btn.parentElement;
          frameDiv.setAttribute('class', 'frame-div flash-div');
          setTimeout(() => deleteElement(frameDiv), 300);
        };

        frameCaptureViewerDoc = frameCaptureViewer.document;
        frameCaptureViewerDoc.head.innerHTML = frameCaptureViewerHeadHTML;
        frameCaptureViewerDoc.body.innerHTML = frameCaptureViewerBodyHTML;
      }

      const frameDiv = document.createElement('div');
      frameDiv.setAttribute('class', 'frame-div');
      const frameCount = getFrameCount(currentTime);
      const frameFileName = `${settings.titleSuffix}-${resString}-@${currentTime}s(${$BHXf$export$toHHMMSSTrimmed(currentTime).replace(':', ';')})-f${frameCount.frameNumber}(${frameCount.totalFrames})`;
      frameDiv.innerHTML = `\
      <figcaption>Resolution: ${canvas.width}x${canvas.height} Name: ${frameFileName}</figcaption>
      <button class="download" onclick="downloadFrame(this)">Download Frame</button>
      <button class="delete" onclick="deleteFrame(this)">Delete Frame</button>
      `;
      canvas.fileName = `${frameFileName}.png`;
      const framesDiv = frameCaptureViewerDoc.getElementById('frames-div');
      frameDiv.appendChild(canvas);
      framesDiv.appendChild(frameDiv);
      flashMessage(`Captured frame: ${frameFileName}`, 'green');
    }

    function getFrameCount(seconds) {
      let fps = getFPS(null);
      let frameNumber;
      let totalFrames;

      if (fps) {
        frameNumber = Math.floor(seconds * fps);
        totalFrames = Math.floor(video.duration * fps);
      } else {
        frameNumber = 'Unknown';
        totalFrames = 'Unknown';
      }

      return {
        frameNumber,
        totalFrames
      };
    }

    function canvasBlobToPromise(canvas) {
      return new Promise(resolve => {
        canvas.toBlob(blob => resolve(blob));
      });
    }

    let isFrameCapturerZippingInProgress = false;

    function saveCapturedFrames() {
      if (isFrameCapturerZippingInProgress) {
        flashMessage('Frame Capturer zipping already in progress. Please wait before trying to zip again.', 'red');
        return;
      }

      if (!frameCaptureViewer || frameCaptureViewer.closed || !frameCaptureViewerDoc) {
        flashMessage('Frame capturer not open. Please capture a frame before zipping.', 'olive');
        return;
      }

      var $ZBU$$interop$default = $parcel$interopDefault($ZBU$exports);
      const zip = new $ZBU$$interop$default.d();
      const framesZip = zip.folder(settings.titleSuffix).folder('frames');
      const frames = frameCaptureViewerDoc.getElementsByTagName('canvas');

      if (frames.length === 0) {
        flashMessage('No frames to zip.', 'olive');
        return;
      }

      isFrameCapturerZippingInProgress = true;
      Array.from(frames).forEach(frame => {
        framesZip.file(frame.fileName, canvasBlobToPromise(frame), {
          binary: true
        });
      });
      const progressDiv = injectProgressBar('green');
      const progressSpan = progressDiv.firstElementChild;
      zip.generateAsync({
        type: 'blob'
      }, metadata => {
        const percent = metadata.percent.toFixed(2) + '%';
        progressSpan.textContent = `Frame Capturer Zipping Progress: ${percent}`;
      }).then(blob => {
        $oSxG$exports.saveAs(blob, `${settings.titleSuffix}-frames.zip`);
        progressDiv.dispatchEvent(new Event('done'));
        isFrameCapturerZippingInProgress = false;
      });
    }

    function injectProgressBar(color) {
      const progressDiv = document.createElement('div');
      progressDiv.setAttribute('class', 'msg-div');
      progressDiv.addEventListener('done', () => {
        progressDiv.setAttribute('class', 'msg-div flash-div');
        setTimeout(() => deleteElement(progressDiv), 2500);
      });
      progressDiv.innerHTML = `<span class="flash-msg" style="color:${color}"> Frame Capturer Zipping Progress: 0%</span>`;
      flashMessageHook.insertAdjacentElement('beforebegin', progressDiv);
      return progressDiv;
    }

    let cropSvg;
    let cropDim;
    let cropRect;
    let cropRectBorderBlack;
    let cropRectBorderWhite;

    function createCropOverlay(cropString) {
      deleteCropOverlay();
      const cropDiv = document.createElement('div');
      cropDiv.setAttribute('id', 'crop-div');
      cropDiv.innerHTML = `\
      <svg id="crop-svg">
        <defs>
          <mask id="cropMask">
            <rect x="0" y="0" width="100%" height="100%" fill="white"/>
            <rect id="cropRect" x="0" y="0" width="100%" height="100%" fill="black"/>
          </mask>
        </defs>
        <rect id="cropDim" mask="url(#cropMask)" x="0" y="0" width="100%" height="100%" fill="black" fill-opacity="${cropDimOpacity}"/>
        <rect id="cropRectBorderBlack" x="0" y="0" width="100%" height="100%" fill="none" stroke="black" shape-rendering="geometricPrecision" stroke-width="1px" stroke-opacity="0.9" />
        <rect id="cropRectBorderWhite" x="0" y="0" width="100%" height="100%" fill="none" stroke="white" shape-rendering="geometricPrecision" stroke-width="1px" stroke-dasharray="5 5" stroke-opacity="0.9"/>
      </svg>`;
      resizeCropOverlay(cropDiv);
      overlayHook.insertAdjacentElement('afterend', cropDiv);
      window.addEventListener('resize', () => resizeCropOverlay(cropDiv));
      cropSvg = cropDiv.firstElementChild;
      cropDim = document.getElementById('cropDim');
      cropRect = document.getElementById('cropRect');
      cropRectBorderBlack = document.getElementById('cropRectBorderBlack');
      cropRectBorderWhite = document.getElementById('cropRectBorderWhite');

      const _extractCropComponent9 = extractCropComponents(cropString),
            _extractCropComponent10 = $XbmT$var$_slicedToArray(_extractCropComponent9, 4),
            x = _extractCropComponent10[0],
            y = _extractCropComponent10[1],
            w = _extractCropComponent10[2],
            h = _extractCropComponent10[3];

      setCropOverlayDimensions(x, y, w, h);
      isCropOverlayVisible = true;
    }

    function resizeCropOverlay(cropDiv) {
      requestAnimationFrame(() => forceRerenderCrop(cropDiv));
    }

    function forceRerenderCrop(cropDiv) {
      cropDiv.setAttribute('style', video.getAttribute('style') + 'position:absolute');

      if (cropSvg) {
        cropSvg.setAttribute('width', '0');
      }
    }

    function setCropOverlayDimensions(x, y, w, h) {
      if (cropRect && cropRectBorderBlack && cropRectBorderWhite) {
        const cropRectAttrs = {
          x: `${x / settings.cropResWidth * 100}%`,
          y: `${y / settings.cropResHeight * 100}%`,
          width: `${w / settings.cropResWidth * 100}%`,
          height: `${h / settings.cropResHeight * 100}%`
        };
        $BHXf$export$setAttributes(cropRect, cropRectAttrs);
        $BHXf$export$setAttributes(cropRectBorderBlack, cropRectAttrs);
        $BHXf$export$setAttributes(cropRectBorderWhite, cropRectAttrs);
      }
    }

    let cropDimOpacity = 0.5;

    function cycleCropDimOpacity() {
      if (cropDim) {
        cropDimOpacity += 0.25;
        cropDimOpacity = cropDimOpacity > 1 ? 0 : cropDimOpacity;
        cropDim.setAttribute('fill-opacity', cropDimOpacity.toString());
      }
    }

    function showCropOverlay() {
      if (cropSvg) {
        cropSvg.style.display = 'block';
        isCropOverlayVisible = true;
      }
    }

    function hideCropOverlay() {
      if (isDrawingCrop) {
        finishDrawingCrop();
      }

      if (cropSvg) {
        cropSvg.style.display = 'none';
        isCropOverlayVisible = false;
      }
    }

    function deleteCropOverlay() {
      const cropDiv = document.getElementById('crop-div');
      deleteElement(cropDiv);
      isCropOverlayVisible = false;
    }

    let isDrawingCrop = false;
    let prevCropString = '0:0:iw:ih';
    let beginDrawHandler;

    function drawCropOverlay(verticalFill) {
      if (isDrawingCrop) {
        finishDrawingCrop(prevCropString);
      } else if (isSpeedChartVisible) {
        flashMessage('Please toggle off the time-variable speed chart before drawing crop', 'olive');
      } else if (isDraggingCrop) {
        flashMessage('Please finish dragging or resizing before drawing crop', 'olive');
      } else if (isMarkerEditorOpen && isCropOverlayVisible) {
        isDrawingCrop = true;

        if (!wasDefaultsEditorOpen) {
          prevCropString = markerPairs[prevSelectedMarkerPairIndex].crop;
        } else {
          prevCropString = settings.newMarkerCrop;
        }

        window.removeEventListener('keydown', addCropOverlayHoverListener, true);
        window.removeEventListener('mousemove', cropOverlayHoverHandler, true);
        hidePlayerControls();
        video.style.removeProperty('cursor');
        playerInfo.container.style.cursor = 'crosshair';

        beginDrawHandler = e => beginDraw(e, verticalFill);

        playerInfo.container.addEventListener('pointerdown', beginDrawHandler, {
          once: true,
          capture: true
        });
        flashMessage('Begin drawing crop', 'green');
      } else {
        flashMessage('Please open the global settings or a marker pair editor before drawing crop', 'olive');
      }
    }

    function hidePlayerControls() {
      playerInfo.controls.style.display = 'none';
    }

    function showPlayerControls() {
      playerInfo.controls.style.display = 'block';
    }

    let dragCropPreviewHandler;

    function beginDraw(e, verticalFill) {
      if (e.button == 0 && !dragCropPreviewHandler) {
        e.preventDefault();
        video.setPointerCapture(e.pointerId);
        const videoRect = $XbmT$export$player.getVideoContentRect();
        const playerRect = $XbmT$export$player.getBoundingClientRect();
        const ix = e.pageX - videoRect.left - playerRect.left;
        let ixScaled = Math.round(ix / videoRect.width * settings.cropResWidth);
        let iyScaled = 0;

        if (!verticalFill) {
          const iy = e.pageY - videoRect.top - playerRect.top;
          iyScaled = Math.round(iy / videoRect.height * settings.cropResHeight);
        }

        ixScaled = $BHXf$export$clampNumber(ixScaled, 0, settings.cropResWidth);
        iyScaled = $BHXf$export$clampNumber(iyScaled, 0, settings.cropResHeight);
        updateCrop(ixScaled, iyScaled, 0, 0);

        dragCropPreviewHandler = function dragCropPreviewHandler(e) {
          let endX = Math.round((e.pageX - videoRect.left - playerRect.left) / videoRect.width * settings.cropResWidth);
          let endY = settings.cropResHeight;

          if (!verticalFill) {
            endY = Math.round((e.pageY - videoRect.top - playerRect.top) / videoRect.height * settings.cropResHeight);
          }

          endX = $BHXf$export$clampNumber(endX, 0, settings.cropResWidth);
          endY = $BHXf$export$clampNumber(endY, 0, settings.cropResHeight);
          let x, y, w, h;

          if (endX > ixScaled) {
            x = ixScaled;
            w = endX - x;
          } else {
            x = endX;
            w = ixScaled - x;
          }

          if (endY > iyScaled) {
            y = iyScaled;
            h = endY - y;
          } else {
            y = endY;
            h = iyScaled - y;
          }

          w = $BHXf$export$clampNumber(w, 0, settings.cropResWidth);
          h = $BHXf$export$clampNumber(h, 0, settings.cropResHeight);
          updateCrop(x, y, w, h);
        };

        window.addEventListener('pointermove', dragCropPreviewHandler);
        window.addEventListener('pointerup', endDraw, true); // exact event listener reference only added once so remove not required

        document.addEventListener('click', blockVideoPause, {
          once: true,
          capture: true
        });
      } else {
        finishDrawingCrop(prevCropString);
      }
    }

    function blockVideoPause(e) {
      e.stopImmediatePropagation();
    }

    function endDraw(e) {
      if (e.button === 0) {
        finishDrawingCrop(null, e.pointerId);
      } else {
        finishDrawingCrop(prevCropString, e.pointerId);
      }

      if (e.ctrlKey) {
        window.addEventListener('mousemove', cropOverlayHoverHandler, true);
      }
    }

    function finishDrawingCrop(prevCropString, pointerId) {
      if (pointerId) video.releasePointerCapture(pointerId);
      playerInfo.container.style.removeProperty('cursor');
      playerInfo.container.removeEventListener('pointerdown', beginDrawHandler, true);
      window.removeEventListener('pointermove', dragCropPreviewHandler);
      window.removeEventListener('pointerup', endDraw, true);
      dragCropPreviewHandler = null;
      isDrawingCrop = false;
      showPlayerControls();
      window.addEventListener('keydown', addCropOverlayHoverListener, true);

      if (prevCropString) {
        updateCropString(prevCropString);
        flashMessage('Drawing crop canceled', 'red');
      } else {
        flashMessage('Finished drawing crop', 'green');
      }
    }

    function updateCropString(cropString) {
      const _extractCropComponent11 = extractCropComponents(cropString),
            _extractCropComponent12 = $XbmT$var$_slicedToArray(_extractCropComponent11, 4),
            x = _extractCropComponent12[0],
            y = _extractCropComponent12[1],
            w = _extractCropComponent12[2],
            h = _extractCropComponent12[3];

      updateCrop(x, y, w, h, cropString);
    }

    function updateCrop(x, y, w, h, cropString) {
      if (!cropString) {
        cropString = `${x}:${y}:${w}:${h}`;
      }

      if (isMarkerEditorOpen) {
        cropInput.value = cropString;

        if (!wasDefaultsEditorOpen) {
          markerPairs[prevSelectedMarkerPairIndex].crop = cropString;
        } else {
          settings.newMarkerCrop = cropString;
        }

        setCropOverlayDimensions(x, y, w, h);
      } else {
        throw new Error('No editor was open when trying to update crop.');
      }
    }

    let arrowKeyCropAdjustmentEnabled = false;

    function toggleArrowKeyCropAdjustment() {
      if (arrowKeyCropAdjustmentEnabled) {
        document.removeEventListener('keydown', arrowKeyCropAdjustmentHandler, true);
        flashMessage('Disabled crop adjustment with arrow keys', 'red');
        arrowKeyCropAdjustmentEnabled = false;
      } else {
        document.addEventListener('keydown', arrowKeyCropAdjustmentHandler, true);
        flashMessage('Enabled crop adjustment with arrow keys', 'green');
        arrowKeyCropAdjustmentEnabled = true;
      }
    }

    function arrowKeyCropAdjustmentHandler(ke) {
      if (isMarkerEditorOpen) {
        if (cropInput !== document.activeElement && ['ArrowUp', 'ArrowDown', 'ArrowLeft', 'ArrowRight'].indexOf(ke.code) > -1) {
          ke.preventDefault();
          ke.stopImmediatePropagation();

          let _extractCropComponent13 = extractCropComponents(cropInput.value),
              _extractCropComponent14 = $XbmT$var$_slicedToArray(_extractCropComponent13, 4),
              x = _extractCropComponent14[0],
              y = _extractCropComponent14[1],
              w = _extractCropComponent14[2],
              h = _extractCropComponent14[3];

          let changeAmount;

          if (!ke.altKey && !ke.shiftKey) {
            changeAmount = 10;
          } else if (ke.altKey && !ke.shiftKey) {
            changeAmount = 1;
          } else if (!ke.altKey && ke.shiftKey) {
            changeAmount = 50;
          } else if (ke.altKey && ke.shiftKey) {
            changeAmount = 100;
          } // without modifiers move crop x/y offset
          // with ctrl key modifier expand/shrink crop width/height


          let cropTarget;

          if (!ke.ctrlKey) {
            switch (ke.code) {
              case 'ArrowUp':
                y -= changeAmount;
                cropTarget = 'y';
                break;

              case 'ArrowDown':
                y += changeAmount;
                cropTarget = 'y';
                break;

              case 'ArrowLeft':
                x -= changeAmount;
                cropTarget = 'x';
                break;

              case 'ArrowRight':
                x += changeAmount;
                cropTarget = 'x';
                break;
            }
          } else {
            switch (ke.code) {
              case 'ArrowUp':
                h -= changeAmount;
                cropTarget = 'h';
                break;

              case 'ArrowDown':
                h += changeAmount;
                cropTarget = 'h';
                break;

              case 'ArrowLeft':
                w -= changeAmount;
                cropTarget = 'w';
                break;

              case 'ArrowRight':
                w += changeAmount;
                cropTarget = 'w';
                break;
            }
          }

          const cropArray = clampCropArray([x, y, w, h], cropTarget);
          cropInput.value = cropArray.join(':');
          cropInput.dispatchEvent(new Event('change'));
        }
      }
    }

    function extractCropComponents(cropString) {
      if (!cropString && isMarkerEditorOpen) {
        if (!wasDefaultsEditorOpen) {
          cropString = markerPairs[prevSelectedMarkerPairIndex].crop;
        } else {
          cropString = settings.newMarkerCrop;
        }
      }

      if (!cropString) {
        console.error('No valid crop string to extract components from.');
        cropString = '0:0:iw:ih';
      }

      const cropArray = cropString.split(':').map(cropStringComponent => {
        let cropComponent;

        if (cropStringComponent === 'iw') {
          cropComponent = settings.cropResWidth;
        } else if (cropStringComponent === 'ih') {
          cropComponent = settings.cropResHeight;
        } else {
          cropComponent = parseInt(cropStringComponent, 10);
        }

        return cropComponent;
      });
      return cropArray;
    }

    let speedChartContainer;
    let speedChartCanvas = `<canvas id="speedChartCanvas" width="1600" height="900"></canvas>`;
    let speedChart;
    $JsLj$exports.Chart.helpers.merge($JsLj$exports.Chart.defaults.global, $ZMrW$export$global);

    function toggleSpeedChart() {
      if (isMarkerEditorOpen && !wasDefaultsEditorOpen && prevSelectedMarkerPairIndex != null) {
        if (!speedChart) {
          isSpeedChartVisible = true;
          isSpeedChartEnabled = true;
          loadSpeedMap($ZMrW$export$options);
          speedChartContainer = document.createElement('div');
          speedChartContainer.setAttribute('id', 'speedChartContainer');
          speedChartContainer.setAttribute('style', 'width: 100%; height: calc(100% - 20px); position: relative; z-index: 12');
          speedChartContainer.innerHTML = speedChartCanvas;
          const videoContainer = document.getElementsByClassName('html5-video-container')[0];
          videoContainer.insertAdjacentElement('afterend', speedChartContainer);
          speedChart = new $JsLj$exports.Chart('speedChartCanvas', $ZMrW$export$options);
          speedChart.ctx.canvas.removeEventListener('wheel', speedChart.$zoom._wheelHandler);
          const wheelHandler = speedChart.$zoom._wheelHandler;

          speedChart.$zoom._wheelHandler = e => {
            if (e.ctrlKey && !e.altKey && !e.shiftKey) {
              wheelHandler(e);
            }
          };

          speedChart.ctx.canvas.addEventListener('wheel', speedChart.$zoom._wheelHandler);
          speedChart.ctx.canvas.addEventListener('contextmenu', e => {
            e.preventDefault();
            e.stopImmediatePropagation();
          }, true);
          speedChart.ctx.canvas.addEventListener('mouseup', speedChartContextMenuHandler, true);
          updateSpeedChartTimeAnnotation();
        } else {
          toggleSpeedChartVisibility();
          isSpeedChartEnabled = !isSpeedChartEnabled;
        }
      } else {
        flashMessage('Please open a marker pair editor before toggling the time-variable speed chart', 'olive');
      }
    }

    function speedChartContextMenuHandler(e) {
      e.preventDefault();
      e.stopImmediatePropagation(); // shift+right-click context menu opens screenshot tool in firefox 67.0.2

      if (e.button === 2 && !e.ctrlKey && !e.altKey && !e.shiftKey) {
        $XbmT$export$player.seekTo(speedChart.scales['x-axis-1'].getValueForPixel(e.offsetX));
      } else if (e.button === 2 && !e.ctrlKey && e.altKey && !e.shiftKey) {
        const start = speedChart.scales['x-axis-1'].getValueForPixel(e.offsetX);
        speedChart.config.options.annotation.annotations[1].value = start;
        markerPairs[prevSelectedMarkerPairIndex].speedMapLoop.start = start;
        speedChart.update();
      } else if (e.button === 2 && e.ctrlKey && e.altKey && !e.shiftKey) {
        const end = speedChart.scales['x-axis-1'].getValueForPixel(e.offsetX);
        speedChart.config.options.annotation.annotations[2].value = end;
        markerPairs[prevSelectedMarkerPairIndex].speedMapLoop.end = end;
        speedChart.update();
      }
    }

    let easingMode = 'linear';

    function toggleSpeedChartEasing() {
      if (speedChart) {
        if (easingMode === 'linear') {
          speedChart.data.datasets[0].lineTension = $ZMrW$export$cubicInOutTension;
          easingMode = 'cubicInOut';
        } else {
          speedChart.data.datasets[0].lineTension = 0;
          easingMode = 'linear';
        }

        speedChart.update();
      }
    }

    function toggleSpeedMapLoop() {
      if (isSpeedChartVisible && prevSelectedMarkerPairIndex != null) {
        if (markerPairs[prevSelectedMarkerPairIndex].speedMapLoop.enabled) {
          markerPairs[prevSelectedMarkerPairIndex].speedMapLoop.enabled = false;
          speedChart.config.options.annotation.annotations[1].borderColor = 'rgba(0, 255, 0, 0.4)';
          speedChart.config.options.annotation.annotations[2].borderColor = 'rgba(255, 215, 0, 0.4)';
          flashMessage('Speed chart looping disabled', 'red');
        } else {
          markerPairs[prevSelectedMarkerPairIndex].speedMapLoop.enabled = true;
          speedChart.config.options.annotation.annotations[1].borderColor = 'rgba(0, 255, 0, 0.9)';
          speedChart.config.options.annotation.annotations[2].borderColor = 'rgba(255, 215, 0, 0.9)';
          flashMessage('Speed chart looping enabled', 'green');
        }

        speedChart.update();
      }
    }

    function resetSpeedMapLoop() {
      if (isSpeedChartVisible && prevSelectedMarkerPairIndex != null) {
        markerPairs[prevSelectedMarkerPairIndex].speedMapLoop.start = undefined;
        markerPairs[prevSelectedMarkerPairIndex].speedMapLoop.end = undefined;
        speedChart.config.options.annotation.annotations[1].value = -1;
        speedChart.config.options.annotation.annotations[2].value = -1;
        speedChart.update();
      }
    }

    function loadSpeedMap(chartOrChartConfig) {
      if (chartOrChartConfig) {
        if (isMarkerEditorOpen && !wasDefaultsEditorOpen && prevSelectedMarkerPairIndex != null) {
          const markerPair = markerPairs[prevSelectedMarkerPairIndex];
          let speedMap = markerPair.speedMap;
          chartOrChartConfig.data.datasets[0].data = speedMap;

          if (chartOrChartConfig instanceof $JsLj$exports.Chart) {
            updateSpeedChartBounds(chartOrChartConfig.config, markerPair.start, markerPair.end);
            speedChart.update();
          } else {
            updateSpeedChartBounds(chartOrChartConfig, markerPair.start, markerPair.end);
          }
        }
      }
    }

    let speedChartMinBound;
    let speedChartMaxBound;

    function updateSpeedChartBounds(chartConfig, start, end) {
      speedChartMinBound = start;
      speedChartMaxBound = end;
      chartConfig.options.scales.xAxes[0].ticks.min = start;
      chartConfig.options.scales.xAxes[0].ticks.max = end;
      chartConfig.options.plugins.zoom.pan.rangeMin.x = start;
      chartConfig.options.plugins.zoom.pan.rangeMax.x = end;
      chartConfig.options.plugins.zoom.zoom.rangeMin.x = start;
      chartConfig.options.plugins.zoom.zoom.rangeMax.x = end;
    }

    function updateSpeedChartTimeAnnotation() {
      const time = video.currentTime;

      if (speedChartMinBound <= time && time <= speedChartMaxBound) {
        speedChart.config.options.annotation.annotations[0].value = video.currentTime;
        speedChart.update();
      }

      if (isSpeedChartVisible) {
        requestAnimationFrame(updateSpeedChartTimeAnnotation);
      }
    }

    function updateAllMarkerPairSpeeds(newSpeed) {
      markerPairs.forEach(markerPair => {
        markerPair.speed = newSpeed;
        const speedMap = markerPair.speedMap;

        if (speedMap.length === 2 && speedMap[0].y === speedMap[1].y) {
          markerPair.speedMap[1].y = newSpeed;
        }

        markerPair.speedMap[0].y = newSpeed;
      });

      if (isMarkerEditorOpen) {
        if (wasDefaultsEditorOpen) {
          const markerPairMergeListInput = document.getElementById('merge-list-input');
          markerPairMergeListInput.dispatchEvent(new Event('change'));
        } else {
          const speedInput = document.getElementById('speed-input');
          speedInput.value = newSpeed.toString();
          speedInput.dispatchEvent(new Event('change'));
        }
      }

      flashMessage(`All marker speeds updated to ${newSpeed}`, 'olive');
    }

    function updateAllMarkerPairCrops(newCrop) {
      markerPairs.forEach(markerPair => {
        markerPair.crop = newCrop;
      });

      if (isMarkerEditorOpen && !wasDefaultsEditorOpen) {
        const cropInput = document.getElementById('crop-input');
        cropInput.value = newCrop;
        cropInput.dispatchEvent(new Event('change'));
      }

      flashMessage(`All marker crops updated to ${newCrop}`, 'olive');
    }

    function toggleMarkerPairEditorHandler(e) {
      const targetMarker = e.target;

      if (targetMarker && e.shiftKey) {
        toggleMarkerPairEditor(targetMarker);
      }
    }

    let isSpeedChartEnabled = false;

    function toggleMarkerPairEditor(targetMarker) {
      // toggling on off current pair editor
      if (prevSelectedEndMarker === targetMarker && !wasDefaultsEditorOpen) {
        if (isMarkerEditorOpen) {
          toggleOffMarkerPairEditor();
        } else {
          toggleOnMarkerPairEditor(targetMarker);
        } // switching to different marker pair
        // delete current editor and create new editor

      } else {
        if (isMarkerEditorOpen) {
          toggleOffMarkerPairEditor();
        }

        toggleOnMarkerPairEditor(targetMarker);
      }
    }

    function toggleOffMarkerPairEditor() {
      deleteMarkerPairEditor();
      hideSelectedMarkerPairCropOverlay();
      hideCropOverlay();
      hideSpeedChart();
      prevSelectedEndMarker.classList.remove('selected-marker');
      prevSelectedEndMarker.previousElementSibling.classList.remove('selected-marker');
      const markerPair = markerPairs[prevSelectedMarkerPairIndex];
      markerPair.startNumbering.classList.remove('selectedMarkerNumbering');
      markerPair.endNumbering.classList.remove('selectedMarkerNumbering');

      if (isAutoHideUnselectedMarkerPairsOn) {
        deleteElement(autoHideUnselectedMarkerPairsStyle);
      }
    }

    function toggleOnMarkerPairEditor(targetMarker) {
      prevSelectedEndMarker = targetMarker;
      prevSelectedMarkerPairIndex = parseInt(prevSelectedEndMarker.getAttribute('idx')) - 1;
      highlightSelectedMarkerPair(targetMarker);
      enableMarkerHotkeys(targetMarker);
      createMarkerPairEditor(targetMarker);
      addCropInputHotkeys();
      loadSpeedMap(speedChart);
      showCropOverlay();

      if (isSpeedChartEnabled) {
        showSpeedChart();
      }

      targetMarker.classList.add('selected-marker');
      targetMarker.previousElementSibling.classList.add('selected-marker');
      const markerPair = markerPairs[prevSelectedMarkerPairIndex];
      markerPair.startNumbering.classList.add('selectedMarkerNumbering');
      markerPair.endNumbering.classList.add('selectedMarkerNumbering');

      if (isAutoHideUnselectedMarkerPairsOn) {
        autoHideUnselectedMarkerPairsStyle = injectCSS(autoHideUnselectedMarkerPairsCSS, 'auto-hide-unselected-marker-pairs-css');
      }
    }

    const autoHideUnselectedMarkerPairsCSS = `
    rect.marker {
      opacity: 0.25;
    }
    text.markerNumbering {
      opacity: 0.25;
    }

    rect.selected-marker {
      opacity: 1;
    }
    text.selectedMarkerNumbering {
      opacity: 1;
    }

    rect.marker.end-marker {
      pointer-events: none;
    }
    rect.selected-marker.end-marker {
      pointer-events: fill;
    }
    `;
    let autoHideUnselectedMarkerPairsStyle;
    let isAutoHideUnselectedMarkerPairsOn = false;

    function toggleAutoHideUnselectedMarkerPairs(e) {
      if (e.ctrlKey && !arrowKeyCropAdjustmentEnabled) {
        e.preventDefault();
        e.stopImmediatePropagation();

        if (!isAutoHideUnselectedMarkerPairsOn) {
          autoHideUnselectedMarkerPairsStyle = injectCSS(autoHideUnselectedMarkerPairsCSS, 'auto-hide-unselected-marker-pairs-css');
          isAutoHideUnselectedMarkerPairsOn = true;
          flashMessage('Auto-hiding of unselected marker pairs enabled', 'green');
        } else {
          deleteElement(autoHideUnselectedMarkerPairsStyle);
          isAutoHideUnselectedMarkerPairsOn = false;
          flashMessage('Auto-hiding of unselected marker pairs disabled', 'red');
        }
      }
    }

    function createMarkerPairEditor(targetMarker) {
      const markerPairIndex = parseInt(targetMarker.getAttribute('idx'), 10) - 1;
      const markerPair = markerPairs[markerPairIndex];
      const startTime = $BHXf$export$toHHMMSSTrimmed(markerPair.start);
      const endTime = $BHXf$export$toHHMMSSTrimmed(markerPair.end);
      const speed = markerPair.speed;
      const duration = $BHXf$export$toHHMMSSTrimmed(markerPair.end - markerPair.start);
      const speedAdjustedDuration = $BHXf$export$toHHMMSSTrimmed((markerPair.end - markerPair.start) / speed);
      const crop = markerPair.crop;
      const cropInputValidation = `\\d+:\\d+:(\\d+|iw):(\\d+|ih)`;
      const markerInputsDiv = document.createElement('div');
      const overrides = markerPair.overrides;
      const vidstab = overrides.videoStabilization;
      const vidstabDesc = vidstab ? vidstab.desc : null;
      const vidstabDescGlobal = settings.videoStabilization ? `(${settings.videoStabilization.desc})` : '';
      const vidstabDynamicZoomEnabled = vidstab ? vidstab.enabled && vidstab.optzoom == 2 ? true : false : null;
      const denoise = overrides.denoise;
      const denoiseDesc = denoise ? denoise.desc : null;
      const denoiseDescGlobal = settings.denoise ? `(${settings.denoise.desc})` : '';
      const markerPairOverridesEditorDisplay = targetMarker.getAttribute('markerPairOverridesEditorDisplay');
      createCropOverlay(crop);
      markerInputsDiv.setAttribute('id', 'markerInputsDiv');
      markerInputsDiv.innerHTML = `\
      <div class="yt_clipper-settings-editor">
        <span style="font-weight:bold;font-style:none">Marker Pair \
          <span id="marker-pair-number-label">${markerPairIndex + 1}</span>\
          /\
          <span id="marker-pair-count-label">${markerPairs.length}</span>\
        Settings: </span>
        <div class="editor-input-div">
          <span>Speed: </span>
          <input id="speed-input" class="yt_clipper-input" type="number" placeholder="speed" value="${speed}" 
            step="0.05" min="0.05" max="2" style="width:4em" required></input>
        </div>
        <div class="editor-input-div">
          <span>Crop: </span>
          <input id="crop-input" class="yt_clipper-input" value="${crop}" pattern="${cropInputValidation}" 
          style="width:10em" required></input>
        </div>
        <div class="editor-input-div">
          <span>Title Prefix: </span>
          <input id="title-prefix-input" class="yt_clipper-input" value="${overrides.titlePrefix != null ? overrides.titlePrefix : ''}" placeholder="None" style="width:10em;text-align:right"></input>
        </div>
        <div class="editor-input-div">
          <span style="font-weight:bold;font-style:none">Time:</span>
          <span id="start-time">${startTime}</span>
          <span> - </span>
          <span id="end-time">${endTime}</span>
          <span> - </span>
          <span style="font-weight:bold;font-style:none">Duration: </span>
          <span id="duration">${duration} / ${markerPair.speed} = ${speedAdjustedDuration}</span>
        </div>
      </div>
      <div id="marker-pair-overrides" class="yt_clipper-settings-editor" style="display:${markerPairOverridesEditorDisplay}">
        <span style="font-weight:bold">Overrides: </span>
        <div class="editor-input-div">
          <span>Audio: </span>
          <select id="audio-input">
            <option ${overrides.audio ? 'selected' : ''}>Enabled</option>
            <option ${overrides.audio === false ? 'selected' : ''}>Disabled</option>
            <option value="Default" ${overrides.audio == null ? 'selected' : ''}>Inherit Global ${ternaryToString(settings.audio)}</option>
          </select>
        </div>
        <div class="editor-input-div">
          <span>Encode Speed (0-5): </span>
          <input id="encode-speed-input" class="yt_clipper-input" type="number" min="0" max="5" step="1" value="${overrides.encodeSpeed != null ? overrides.encodeSpeed : ''}" placeholder="${settings.encodeSpeed || 'Auto'}"  style="width:4em"></input>
        </div>
        <div class="editor-input-div">
          <span>CRF (0-63): </span>
          <input id="crf-input" class="yt_clipper-input" type="number" min="0" max="63" step="1" value="${overrides.crf != null ? overrides.crf : ''}" placeholder="${settings.crf || 'Auto'}" "style="width:4em"></input>
        </div>
        <div class="editor-input-div">
          <span>Target Bitrate (kb/s) (0 = &#x221E;): </span>
          <input id="target-max-bitrate-input" class="yt_clipper-input" type="number" min="0" max="10e5" step="100" value="${overrides.targetMaxBitrate != null ? overrides.targetMaxBitrate : ''}" placeholder="${settings.targetMaxBitrate || 'Auto'}" "style="width:4em"></input>
        </div>
        <div class="editor-input-div">
          <span>Two-Pass: </span>
          <select id="two-pass-input"> 
            <option ${overrides.twoPass ? 'selected' : ''}>Enabled</option>
            <option ${overrides.twoPass === false ? 'selected' : ''}>Disabled</option>
            <option value="Default" ${overrides.twoPass == null ? 'selected' : ''}>Inherit Global ${ternaryToString(settings.twoPass)}</option>
          </select>
        </div>
        <div class="editor-input-div">
          <span>Gamma (0-4): </span>
          <input id="gamma-input" class="yt_clipper-input" type="number" min="0.01" max="4.00" step="0.01" value="${overrides.gamma != null ? overrides.gamma : ''}" placeholder="${settings.gamma || '1'}" style="width:4em"></input>
        </div>
        <div class="editor-input-div">
          <span>Expand Colors: </span>
          <select id="expand-color-range-input"> 
            <option ${overrides.expandColorRange ? 'selected' : ''}>Enabled</option>
            <option ${overrides.expandColorRange === false ? 'selected' : ''}>Disabled</option>
            <option value="Default" ${overrides.expandColorRange == null ? 'selected' : ''}>Inherit Global ${ternaryToString(settings.expandColorRange)}</option>
          </select>
        </div>
        <div class="editor-input-div">
          <span>Denoise: </span>
          <select id="denoise-input">
            <option ${denoiseDesc === 'Very Strong' ? 'selected' : ''}>Very Strong</option>
            <option ${denoiseDesc === 'Strong' ? 'selected' : ''}>Strong</option>
            <option ${denoiseDesc === 'Medium' ? 'selected' : ''}>Medium</option>
            <option ${denoiseDesc === 'Weak' ? 'selected' : ''}>Weak</option>
            <option ${denoiseDesc === 'Very Weak' ? 'selected' : ''}>Very Weak</option>
            <option value="Disabled" ${denoiseDesc == 'Disabled' ? 'selected' : ''}>Disabled</option>
            <option value="Inherit" ${denoiseDesc == null ? 'selected' : ''}>Inherit Global ${denoiseDescGlobal}</option>
          </select>
        </div>
        <div class="editor-input-div">
          <span>Stabilization: </span>
          <select id="video-stabilization-input">
            <option ${vidstabDesc === 'Strongest' ? 'selected' : ''}>Strongest</option>
            <option ${vidstabDesc === 'Very Strong' ? 'selected' : ''}>Very Strong</option>
            <option ${vidstabDesc === 'Strong' ? 'selected' : ''}>Strong</option>
            <option ${vidstabDesc === 'Medium' ? 'selected' : ''}>Medium</option>
            <option ${vidstabDesc === 'Weak' ? 'selected' : ''}>Weak</option>
            <option ${vidstabDesc === 'Very Weak' ? 'selected' : ''}>Very Weak</option>
            <option value="Disabled" ${vidstabDesc == 'Disabled' ? 'selected' : ''}>Disabled</option>
            <option value="Inherit" ${vidstabDesc == null ? 'selected' : ''}>Inherit Global ${vidstabDescGlobal}</option>
          </select>
          <div class="editor-input-div">
            <span>Dynamic Zoom: </span>
            <select id="video-stabilization-dynamic-zoom-input"> 
              <option ${vidstabDynamicZoomEnabled ? 'selected' : ''}>Enabled</option>
              <option ${vidstabDynamicZoomEnabled === false ? 'selected' : ''}>Disabled</option>
              <option value="Default" ${vidstabDynamicZoomEnabled == null ? 'selected' : ''}>Inherit (Disabled)</option>
            </select>
          </div>
        </div>
        <div class="editor-input-div">
        <span>Speed Map: </span>
            <select id="enable-speed-maps-input">
              <option ${overrides.enableSpeedMaps ? 'selected' : ''}>Enabled</option>
              <option ${overrides.enableSpeedMaps === false ? 'selected' : ''}>Disabled</option>
              <option value="Default" ${overrides.enableSpeedMaps == null ? 'selected' : ''}>Inherit Global ${ternaryToString(settings.enableSpeedMaps)}</option>
            </select>
        </div>
        <div class="editor-input-div">
          <span>Loop: </span>
            <select id="loop-input">
              <option ${overrides.loop === 'fwrev' ? 'selected' : ''}>fwrev</option>
              <option ${overrides.loop === 'fade' ? 'selected' : ''}>fade</option>
              <option ${overrides.loop === 'none' ? 'selected' : ''}>none</option>
              <option value="Default" ${overrides.loop == null ? 'selected' : ''}>Inherit Global ${settings.loop ? `(${settings.loop})` : ''}</option>
            </select>
          <div class="editor-input-div">
            <span>Fade Duration: </span>
            <input id="fade-duration-input" class="yt_clipper-input" type="number" min="0.1" step="0.1" value="${overrides.fadeDuration != null ? overrides.fadeDuration : ''}" placeholder="0.5" style="width:4em"></input>
          </div>
        </div>
      </div>
      `;
      updateSettingsEditorHook();
      settingsEditorHook.insertAdjacentElement('beforebegin', markerInputsDiv);
      addSettingsInputListeners([['speed-input', 'speed', 'number'], ['crop-input', 'crop', 'string']], markerPairs[markerPairIndex]);
      addSettingsInputListeners([['title-prefix-input', 'titlePrefix', 'string'], ['gamma-input', 'gamma', 'number'], ['encode-speed-input', 'encodeSpeed', 'number'], ['crf-input', 'crf', 'number'], ['target-max-bitrate-input', 'targetMaxBitrate', 'number'], ['two-pass-input', 'twoPass', 'ternary'], ['audio-input', 'audio', 'ternary'], ['expand-color-range-input', 'expandColorRange', 'ternary'], ['enable-speed-maps-input', 'enableSpeedMaps', 'ternary'], ['denoise-input', 'denoise', 'preset'], ['video-stabilization-input', 'videoStabilization', 'preset'], ['video-stabilization-dynamic-zoom-input', 'videoStabilizationDynamicZoom', 'ternary'], ['loop-input', 'loop', 'inheritableString'], ['fade-duration-input', 'fadeDuration', 'number']], markerPairs[markerPairIndex].overrides);
      cropInput = document.getElementById('crop-input');
      isMarkerEditorOpen = true;
      wasDefaultsEditorOpen = false;
    }

    function ternaryToString(ternary) {
      if (ternary == null) {
        return '';
      } else if (ternary === true) {
        return '(Enabled)';
      } else if (ternary === false) {
        return '(Disabled)';
      } else {
        return null;
      }
    }

    function enableMarkerHotkeys(endMarker) {
      markerHotkeysEnabled = true;
      enableMarkerHotkeys.endMarker = endMarker;
      enableMarkerHotkeys.markerPairIndex = endMarker.getAttribute('idx');
      enableMarkerHotkeys.startMarker = endMarker.previousSibling;

      enableMarkerHotkeys.moveMarker = marker => {
        const type = marker.getAttribute('type');
        const idx = parseInt(marker.getAttribute('idx')) - 1;
        const markerPair = markerPairs[idx];
        const currentTime = video.currentTime;
        const progress_pos = currentTime / playerInfo.duration * 100;
        const markerTimeSpan = document.getElementById(`${type}-time`);
        const speedMap = markerPair.speedMap;

        if (type === 'start' && currentTime >= markerPair.end) {
          flashMessage('Start marker cannot be placed after or at end marker', 'red');
          return;
        }

        if (type === 'end' && currentTime <= markerPair.start) {
          flashMessage('End marker cannot be placed before or at start marker', 'red');
          return;
        }

        marker.setAttribute('x', `${progress_pos}%`);
        markerPair[type] = currentTime;

        if (type === 'start') {
          selectedStartMarkerOverlay.setAttribute('x', `${progress_pos}%`);
          markerPair.startNumbering.setAttribute('x', `${progress_pos}%`);
          speedMap[0].x = currentTime;
          markerPair.speedMap = speedMap.filter(speedPoint => {
            return speedPoint.x >= currentTime;
          });
        } else if (type === 'end') {
          speedMap[speedMap.length - 1].x = currentTime;
          selectedEndMarkerOverlay.setAttribute('x', `${progress_pos}%`);
          markerPair.endNumbering.setAttribute('x', `${progress_pos}%`);
          markerPair.speedMap = speedMap.filter(speedPoint => {
            return speedPoint.x <= currentTime;
          });
        }

        markerTimeSpan.textContent = `${$BHXf$export$toHHMMSSTrimmed(currentTime)}`;

        if (speedChart) {
          speedChart.config.data.datasets[0].data = markerPair.speedMap;
          updateSpeedChartBounds(speedChart.config, markerPair.start, markerPair.end);
          speedChart.update();
        }

        updateMarkerPairDuration(markerPair);
      };

      enableMarkerHotkeys.deleteMarkerPair = () => {
        const idx = parseInt(enableMarkerHotkeys.endMarker.getAttribute('idx')) - 1;
        markerPairs.splice(idx, 1);
        const me = new MouseEvent('mouseover', {
          shiftKey: true
        });
        enableMarkerHotkeys.endMarker.dispatchEvent(me);
        deleteElement(enableMarkerHotkeys.endMarker);
        deleteElement(enableMarkerHotkeys.startMarker);
        const markersSvg = document.getElementById('markers-svg');
        markersSvg.childNodes.forEach((markerRect, idx) => {
          // renumber markers by pair starting with index 1
          const newIdx = Math.floor((idx + 2) / 2);
          markerRect.setAttribute('idx', newIdx);
        });
        enableMarkerHotkeys.moveMarker = null;
        enableMarkerHotkeys.deleteMarkerPair = null;
        markerHotkeysEnabled = false;
      };
    }

    let selectedStartMarkerOverlay;
    let selectedEndMarkerOverlay;

    function highlightSelectedMarkerPair(currentMarker) {
      if (!selectedStartMarkerOverlay) {
        selectedStartMarkerOverlay = document.getElementById('selected-start-marker-overlay');
      }

      if (!selectedEndMarkerOverlay) {
        selectedEndMarkerOverlay = document.getElementById('selected-end-marker-overlay');
      }

      const startMarker = currentMarker.previousSibling;
      selectedStartMarkerOverlay.setAttribute('x', startMarker.getAttribute('x'));
      selectedEndMarkerOverlay.setAttribute('x', currentMarker.getAttribute('x'));
      selectedMarkerPairOverlay.style.display = 'block';
    }

    function updateMarkerPairDuration(markerPair) {
      const speedAdjustedDurationSpan = document.getElementById('duration');
      const duration = markerPair.end - markerPair.start;
      const durationHHMMSS = $BHXf$export$toHHMMSSTrimmed(duration);
      const outputDuration = getOutputDuration(markerPair.speedMap);
      const outputDurationHHMMSS = $BHXf$export$toHHMMSSTrimmed(outputDuration);
      speedAdjustedDurationSpan.textContent = `${durationHHMMSS} (${outputDurationHHMMSS})`;
      markerPair.outputDuration = outputDuration;
    }

    function getOutputDuration(speedMap) {
      let outputDuration = 0;
      const fps = getFPS();
      const frameDur = 1 / fps;
      const nSects = speedMap.length - 1; // Account for marker pair start time as trim filter sets start time to ~0

      const speedMapStartTime = speedMap[0].x; // Account for first input frame delay due to potentially imprecise trim

      const startt = Math.ceil(speedMapStartTime / frameDur) * frameDur - speedMapStartTime;

      for (let sect = 0; sect < nSects; ++sect) {
        const left = speedMap[sect];
        const right = speedMap[sect + 1];
        const startSpeed = left.y;
        const endSpeed = right.y;
        const speedChange = endSpeed - startSpeed;
        const sectStart = left.x - speedMapStartTime - startt;
        let sectEnd = right.x - speedMapStartTime - startt; // Account for last input frame delay due to potentially imprecise trim

        if (sect === nSects - 1) {
          sectEnd = Math.floor(right['x'] / frameDur) * frameDur; // When trim is frame-precise, the frame that begins at the marker pair end time is not included

          if (right.x - sectEnd < 1e-10) sectEnd = sectEnd - frameDur;
          sectEnd = sectEnd - speedMapStartTime - startt;
          sectEnd = Math.floor(sectEnd * 1000000) / 1000000;
        }

        const sectDuration = sectEnd - sectStart;
        if (sectDuration === 0) continue;
        const m = speedChange / sectDuration;
        const b = startSpeed - m * sectStart;

        if (speedChange === 0) {
          outputDuration += sectDuration / endSpeed;
        } else {
          // Integrate the reciprocal of the linear time vs speed function for the current section
          outputDuration += 1 / m * (Math.log(Math.abs(m * sectEnd + b)) - Math.log(Math.abs(m * sectStart + b)));
        }
      } // Each output frame time is rounded to the nearest multiple of a frame's duration at the given fps


      outputDuration = Math.round(outputDuration / frameDur) * frameDur; // The last included frame is held for a single frame's duration

      outputDuration += frameDur;
      outputDuration = Math.round(outputDuration * 1000) / 1000;
      return outputDuration;
    }

    function hideSelectedMarkerPairCropOverlay() {
      if (selectedMarkerPairOverlay) {
        selectedMarkerPairOverlay.style.display = 'none';
      }
    }

    function showSpeedChart() {
      if (speedChartContainer) {
        if (isDrawingCrop) {
          finishDrawingCrop();
        }

        speedChartContainer.style.display = 'block';
        isSpeedChartVisible = true;
        requestAnimationFrame(updateSpeedChartTimeAnnotation);
      }
    }

    function hideSpeedChart() {
      if (speedChartContainer) {
        speedChartContainer.style.display = 'none';
        isSpeedChartVisible = false;
      }
    }

    function toggleSpeedChartVisibility() {
      if (!isSpeedChartVisible) {
        showSpeedChart();
      } else {
        hideSpeedChart();
      }
    }

    function deleteMarkerPairEditor() {
      const markerInputsDiv = document.getElementById('markerInputsDiv');
      hideCropOverlay();
      deleteElement(markerInputsDiv);
      isMarkerEditorOpen = false;
      markerHotkeysEnabled = false;
    }

    function toggleMarkerPairOverridesEditor() {
      if (isMarkerEditorOpen) {
        const markerPairOverridesEditor = document.getElementById('marker-pair-overrides');
        const globalEncodeSettingsEditor = document.getElementById('global-encode-settings');

        if (markerPairOverridesEditor) {
          if (markerPairOverridesEditor.style.display === 'none') {
            markerPairOverridesEditor.style.display = 'block';
            enableMarkerHotkeys.endMarker.setAttribute('markerPairOverridesEditorDisplay', 'block');
          } else {
            markerPairOverridesEditor.style.display = 'none';
            enableMarkerHotkeys.endMarker.setAttribute('markerPairOverridesEditorDisplay', 'none');
          }
        } else if (globalEncodeSettingsEditor) {
          if (globalEncodeSettingsEditor.style.display === 'none') {
            globalEncodeSettingsEditor.style.display = 'block';
            globalEncodeSettingsEditorDisplay = 'block';
          } else if (globalEncodeSettingsEditor.style.display === 'block') {
            globalEncodeSettingsEditor.style.display = 'none';
            globalEncodeSettingsEditorDisplay = 'none';
          }
        }
      }
    }

    function saveAuthServerScript() {
      const authScript = `\
import json
import re
from urllib.parse import urlencode, urlparse, parse_qs
from http.server import HTTPServer, BaseHTTPRequestHandler

CLIENT_ID = 'XXXX'
REDIRECT_URI = 'http://127.0.0.1:4443/yt_clipper?'

BROWSER_BASED_AUTH_ENDPOINT = f'https://gfycat.com/oauth/authorize?client_id={CLIENT_ID}&scope=all&state=yt_clipper&response_type=token&redirect_uri={REDIRECT_URI}'

REDIRECT_PAGE_BODY = b'''
<body>
    <script>
        let url = window.location.href;
        url = url.replace('?','&');
        url = url.replace('#','?access-token=');
        window.open(url,'_self');
    </script>
</body>
'''

COMPLETE_AUTH_PAGE_BODY = b'''
<body>
    <span>
        Please close this window and return to yt_clipper.
    </span>
</body>
'''


class getServer(BaseHTTPRequestHandler):
    redirected = -1

    def do_GET(self):
        print(self.path)
        if re.match('/yt_clipper*', self.path):
            if getServer.redirected == -1:
                self.send_response(200)
                self.end_headers()
                self.wfile.write(REDIRECT_PAGE_BODY)
                getServer.redirected = 0
            elif getServer.redirected == 0:
                self.send_response(200)
                self.end_headers()
                self.wfile.write(COMPLETE_AUTH_PAGE_BODY)
                getServer.query = parse_qs(urlparse(self.path).query)
                getServer.redirected = 1
            elif getServer.redirected == 1:
                self.send_response(200)
                self.send_header('Content-Type', 'application/json')
                self.send_header('Access-Control-Allow-Origin', '*')
                self.end_headers()
                self.wfile.write(json.dumps(getServer.query).encode())


httpd = HTTPServer(('localhost', 4443), getServer)
httpd.serve_forever()
`;
      const blob = new Blob([authScript], {
        type: 'text/plain;charset=utf-8'
      });
      $oSxG$exports.saveAs(blob, `yt_clipper_auth.py`);
    }

    function buildGfyRequests(markers, url) {
      return markers.map((marker, idx) => {
        const start = marker.start;
        const end = marker.end;
        const speed = marker.speed;

        let _marker$crop$split = marker.crop.split(':'),
            _marker$crop$split2 = $XbmT$var$_slicedToArray(_marker$crop$split, 4),
            x = _marker$crop$split2[0],
            y = _marker$crop$split2[1],
            w = _marker$crop$split2[2],
            h = _marker$crop$split2[3];

        w = w === 'iw' ? settings.cropResWidth.toString() : w;
        h = h === 'ih' ? settings.cropResHeight.toString() : h;
        let crop = [x, y, w, h].map(num => parseInt(num, 10));
        const startHHMMSS = $BHXf$export$toHHMMSS(start).split(':');
        const startHH = startHHMMSS[0];
        const startMM = startHHMMSS[1];
        const startSS = startHHMMSS[2];
        const duration = end - start;
        let req = {
          fetchUrl: url,
          title: `${settings.titleSuffix}-${idx + 1}`,
          fetchHours: startHH,
          fetchMinutes: startMM,
          fetchSeconds: startSS,
          noMd5: 'false',
          cut: {
            start,
            duration
          },
          speed,
          crop: {
            x: crop[0],
            y: crop[1],
            w: crop[2],
            h: crop[3]
          }
        };
        return req;
      });
    }

    function requestGfycatAuth() {
      const authPage = window.open(BROWSER_BASED_AUTH_ENDPOINT);
      const timer = setInterval(() => {
        if (authPage.closed) {
          clearInterval(timer);
          getAccessToken();
        }
      }, 2500);
    }

    function getAccessToken() {
      return new Promise(() => {
        fetch(REDIRECT_URI, {
          mode: 'cors'
        }).then(response => {
          return response.json();
        }).then(json => {
          const accessToken = json['access-token'][0];
          console.log(accessToken);
          sendGfyRequests(playerInfo.url, accessToken);
        }).catch(error => console.error(error));
      });
    }

    function sendGfyRequests(url, accessToken) {
      if (markerPairs.length > 0) {
        const markdown = toggleUploadStatus();
        const reqs = buildGfyRequests(markerPairs, url).map((req, idx) => {
          return buildGfyRequestPromise(req, idx, accessToken);
        });
        Promise.all(reqs).then(gfynames => {
          console.log(reqs);
          console.log(gfynames);
          checkGfysCompletedId = setInterval(checkGfysCompleted, 5000, gfynames, markdown);
        });
      }
    }

    function buildGfyRequestPromise(reqData, idx, accessToken) {
      return new Promise((resolve, reject) => {
        postData('https://api.gfycat.com/v1/gfycats', reqData, accessToken).then(resp => {
          links.push(`(${settings.titleSuffix}-${idx})[https://gfycat.com/${resp.gfyname}]`);
          resolve(resp.gfyname);
        }).catch(error => reject(error));
      });
    }

    function checkGfysCompleted(gfynames, markdown) {
      const gfyStatuses = gfynames.map(gfyname => {
        return checkGfyStatus(gfyname, markdown).then(isComplete => {
          return isComplete;
        });
      });
      Promise.all(gfyStatuses).then(gfyStatuses => {
        areGfysCompleted(gfyStatuses).then(() => insertMarkdown(markdown));
      }).catch(() => console.log('gfys not yet completed'));
    }

    function toggleUploadStatus() {
      const meta = document.getElementById('meta');
      const markdown = document.createElement('textarea');
      meta.insertAdjacentElement('beforebegin', markdown);
      $BHXf$export$setAttributes(markdown, {
        id: 'markdown',
        style: 'color:grey;width:600px;height:100px;',
        spellcheck: false
      });
      markdown.textContent = 'Upload initiated. Progress updates will begin shortly.\n';
      return markdown;
    }

    function updateUploadStatus(markdown, status, gfyname) {
      if (markdown) {
        markdown.textContent += `${gfyname} progress: ${status.progress}\n`;
        markdown.scrollTop = markdown.scrollHeight;
      }
    }

    function insertMarkdown(markdown) {
      if (markdown) {
        markdown.textContent = links.join('\n');
        window.clearInterval(checkGfysCompletedId);
      }
    }

    function areGfysCompleted(gfyStatuses) {
      return new Promise((resolve, reject) => {
        if (gfyStatuses.every(Boolean)) {
          resolve();
        } else {
          reject();
        }
      });
    }

    function checkGfyStatus(gfyname, markdown) {
      return new Promise((resolve, reject) => {
        fetch(`https://api.gfycat.com/v1/gfycats/fetch/status/${gfyname}`).then(response => {
          return response.json();
        }).then(myJson => {
          updateUploadStatus(markdown, myJson, gfyname);
          myJson.task === 'complete' ? resolve(true) : reject(false);
        }).catch(error => console.error(error));
      });
    }

    function postData(url, data, accessToken) {
      const auth = accessToken ? `Bearer ${accessToken}` : null;
      const req = {
        body: JSON.stringify(data),
        cache: 'no-cache',
        credentials: 'omit',
        headers: {
          'content-type': 'application/json'
        },
        method: 'POST',
        mode: 'cors',
        redirect: 'follow',
        referrer: 'no-referrer'
      };

      if (auth) {
        req.headers.Authorization = auth;
      }

      console.log(req);
      return fetch(url, req).then(response => response.json()); // parses response to JSON
    }
  }
})();

if (typeof exports === "object" && typeof module !== "undefined") {
  // CommonJS
  module.exports = $XbmT$exports;
} else if (typeof define === "function" && define.amd) {
  // RequireJS
  define(function () {
    return $XbmT$exports;
  });
}
})();