summary refs log tree commit diff
path: root/dot_config/mpv/scripts/webm.lua
diff options
context:
space:
mode:
Diffstat (limited to 'dot_config/mpv/scripts/webm.lua')
-rw-r--r--dot_config/mpv/scripts/webm.lua2914
1 files changed, 2914 insertions, 0 deletions
diff --git a/dot_config/mpv/scripts/webm.lua b/dot_config/mpv/scripts/webm.lua
new file mode 100644
index 0000000..4397b9b
--- /dev/null
+++ b/dot_config/mpv/scripts/webm.lua
@@ -0,0 +1,2914 @@
+local mp = require("mp")
+local assdraw = require("mp.assdraw")
+local msg = require("mp.msg")
+local utils = require("mp.utils")
+local mpopts = require("mp.options")
+local options = {
+	-- Defaults to shift+w
+	keybind = "W",
+	-- If empty, saves on the same directory of the playing video.
+	-- A starting "~" will be replaced by the home dir.
+	-- This field is delimited by double-square-brackets - [[ and ]] - instead of
+	-- quotes, because Windows users might run into a issue when using
+	-- backslashes as a path separator. Examples of valid inputs for this field
+	-- would be: [[]] (the default, empty value), [[C:\Users\John]] (on Windows),
+	-- and [[/home/john]] (on Unix-like systems eg. Linux).
+	-- The [[]] delimiter is not needed when using from a configuration file
+	-- in the script-opts folder.
+	output_directory = [[]],
+	run_detached = false,
+	-- Template string for the output file
+	-- %f - Filename, with extension
+	-- %F - Filename, without extension
+	-- %T - Media title, if it exists, or filename, with extension (useful for some streams, such as YouTube).
+	-- %s, %e - Start and end time, with milliseconds
+	-- %S, %E - Start and end time, without milliseconds
+	-- %M - "-audio", if audio is enabled, empty otherwise
+	-- %R - "-(height)p", where height is the video's height, or scale_height, if it's enabled.
+	-- More specifiers are supported, see https://mpv.io/manual/master/#options-screenshot-template
+	-- Property expansion is supported (with %{} at top level, ${} when nested), see https://mpv.io/manual/master/#property-expansion
+	output_template = "%F-[%s-%e]%M",
+	-- Scale video to a certain height, keeping the aspect ratio. -1 disables it.
+	scale_height = -1,
+	-- Change the FPS of the output video, dropping or duplicating frames as needed.
+	-- -1 means the FPS will be unchanged from the source.
+	fps = -1,
+	-- Target filesize, in kB. This will be used to calculate the bitrate
+	-- used on the encode. If this is set to <= 0, the video bitrate will be set
+	-- to 0, which might enable constant quality modes, depending on the
+	-- video codec that's used (VP8 and VP9, for example).
+	target_filesize = 2500,
+	-- If true, will use stricter flags to ensure the resulting file doesn't
+	-- overshoot the target filesize. Not recommended, as constrained quality
+	-- mode should work well, unless you're really having trouble hitting
+	-- the target size.
+	strict_filesize_constraint = false,
+	strict_bitrate_multiplier = 0.95,
+	-- In kilobits.
+	strict_audio_bitrate = 64,
+	-- Sets the output format, from a few predefined ones.
+	-- Currently we have:
+	-- webm-vp8 (libvpx/libvorbis)
+	-- webm-vp9 (libvpx-vp9/libopus)
+	-- mp4 (h264/AAC)
+	-- mp4-nvenc (h264-NVENC/AAC)
+	-- raw (rawvideo/pcm_s16le).
+	-- mp3 (libmp3lame)
+	-- and gif
+	output_format = "webm-vp8",
+	twopass = true,
+	-- If set, applies the video filters currently used on the playback to the encode.
+	apply_current_filters = true,
+	-- If set, writes the video's filename to the "Title" field on the metadata.
+	write_filename_on_metadata = false,
+	-- Set the number of encoding threads, for codecs libvpx and libvpx-vp9
+	libvpx_threads = 4,
+	additional_flags = "",
+	-- Constant Rate Factor (CRF). The value meaning and limits may change,
+	-- from codec to codec. Set to -1 to disable.
+	crf = 10,
+	-- Useful for flags that may impact output filesize, such as qmin, qmax etc
+	-- Won't be applied when strict_filesize_constraint is on.
+	non_strict_additional_flags = "",
+	-- Display the encode progress, in %. Requires run_detached to be disabled.
+	-- On Windows, it shows a cmd popup. "auto" will display progress on non-Windows platforms.
+	display_progress = "auto",
+	-- The font size used in the menu. Isn't used for the notifications (started encode, finished encode etc)
+	font_size = 28,
+	margin = 10,
+	message_duration = 5,
+	-- gif dither mode, 0-5 for bayer w/ bayer_scale 0-5, 6 for paletteuse default (sierra2_4a)
+	gif_dither = 2,
+	-- Force square pixels on output video
+	-- Some players like recent Firefox versions display videos with non-square pixels with wrong aspect ratio
+	force_square_pixels = false,
+}
+
+mpopts.read_options(options)
+local base64_chars='ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'
+
+-- encoding
+function base64_encode(data)
+    return ((data:gsub('.', function(x) 
+        local r,b='',x:byte()
+        for i=8,1,-1 do r=r..(b%2^i-b%2^(i-1)>0 and '1' or '0') end
+        return r;
+    end)..'0000'):gsub('%d%d%d?%d?%d?%d?', function(x)
+        if (#x < 6) then return '' end
+        local c=0
+        for i=1,6 do c=c+(x:sub(i,i)=='1' and 2^(6-i) or 0) end
+        return base64_chars:sub(c+1,c+1)
+    end)..({ '', '==', '=' })[#data%3+1])
+end
+
+-- decoding
+function base64_decode(data)
+    data = string.gsub(data, '[^'..base64_chars..'=]', '')
+    return (data:gsub('.', function(x)
+        if (x == '=') then return '' end
+        local r,f='',(base64_chars:find(x)-1)
+        for i=6,1,-1 do r=r..(f%2^i-f%2^(i-1)>0 and '1' or '0') end
+        return r;
+    end):gsub('%d%d%d?%d?%d?%d?%d?%d?', function(x)
+        if (#x ~= 8) then return '' end
+        local c=0
+        for i=1,8 do c=c+(x:sub(i,i)=='1' and 2^(8-i) or 0) end
+        return string.char(c)
+    end))
+end
+local emit_event
+emit_event = function(event_name, ...)
+  return mp.commandv("script-message", "webm-" .. tostring(event_name), ...)
+end
+local test_set_options
+test_set_options = function(new_options_json)
+  local new_options = utils.parse_json(new_options_json)
+  for k, v in pairs(new_options) do
+    options[k] = v
+  end
+end
+mp.register_script_message("mpv-webm-set-options", test_set_options)
+local bold
+bold = function(text)
+  return "{\\b1}" .. tostring(text) .. "{\\b0}"
+end
+local message
+message = function(text, duration)
+  local ass = mp.get_property_osd("osd-ass-cc/0")
+  ass = ass .. text
+  return mp.osd_message(ass, duration or options.message_duration)
+end
+local append
+append = function(a, b)
+  for _, val in ipairs(b) do
+    a[#a + 1] = val
+  end
+  return a
+end
+local seconds_to_time_string
+seconds_to_time_string = function(seconds, no_ms, full)
+  if seconds < 0 then
+    return "unknown"
+  end
+  local ret = ""
+  if not (no_ms) then
+    ret = string.format(".%03d", seconds * 1000 % 1000)
+  end
+  ret = string.format("%02d:%02d%s", math.floor(seconds / 60) % 60, math.floor(seconds) % 60, ret)
+  if full or seconds > 3600 then
+    ret = string.format("%d:%s", math.floor(seconds / 3600), ret)
+  end
+  return ret
+end
+local seconds_to_path_element
+seconds_to_path_element = function(seconds, no_ms, full)
+  local time_string = seconds_to_time_string(seconds, no_ms, full)
+  local _
+  time_string, _ = time_string:gsub(":", ".")
+  return time_string
+end
+local file_exists
+file_exists = function(name)
+  local info, err = utils.file_info(name)
+  if info ~= nil then
+    return true
+  end
+  return false
+end
+local expand_properties
+expand_properties = function(text, magic)
+  if magic == nil then
+    magic = "$"
+  end
+  for prefix, raw, prop, colon, fallback, closing in text:gmatch("%" .. magic .. "{([?!]?)(=?)([^}:]*)(:?)([^}]*)(}*)}") do
+    local err
+    local prop_value
+    local compare_value
+    local original_prop = prop
+    local get_property = mp.get_property_osd
+    if raw == "=" then
+      get_property = mp.get_property
+    end
+    if prefix ~= "" then
+      for actual_prop, compare in prop:gmatch("(.-)==(.*)") do
+        prop = actual_prop
+        compare_value = compare
+      end
+    end
+    if colon == ":" then
+      prop_value, err = get_property(prop, fallback)
+    else
+      prop_value, err = get_property(prop, "(error)")
+    end
+    prop_value = tostring(prop_value)
+    if prefix == "?" then
+      if compare_value == nil then
+        prop_value = err == nil and fallback .. closing or ""
+      else
+        prop_value = prop_value == compare_value and fallback .. closing or ""
+      end
+      prefix = "%" .. prefix
+    elseif prefix == "!" then
+      if compare_value == nil then
+        prop_value = err ~= nil and fallback .. closing or ""
+      else
+        prop_value = prop_value ~= compare_value and fallback .. closing or ""
+      end
+    else
+      prop_value = prop_value .. closing
+    end
+    if colon == ":" then
+      local _
+      text, _ = text:gsub("%" .. magic .. "{" .. prefix .. raw .. original_prop:gsub("%W", "%%%1") .. ":" .. fallback:gsub("%W", "%%%1") .. closing .. "}", expand_properties(prop_value))
+    else
+      local _
+      text, _ = text:gsub("%" .. magic .. "{" .. prefix .. raw .. original_prop:gsub("%W", "%%%1") .. closing .. "}", prop_value)
+    end
+  end
+  return text
+end
+local format_filename
+format_filename = function(startTime, endTime, videoFormat)
+  local hasAudioCodec = videoFormat.audioCodec ~= ""
+  local replaceFirst = {
+    ["%%mp"] = "%%mH.%%mM.%%mS",
+    ["%%mP"] = "%%mH.%%mM.%%mS.%%mT",
+    ["%%p"] = "%%wH.%%wM.%%wS",
+    ["%%P"] = "%%wH.%%wM.%%wS.%%wT"
+  }
+  local replaceTable = {
+    ["%%wH"] = string.format("%02d", math.floor(startTime / (60 * 60))),
+    ["%%wh"] = string.format("%d", math.floor(startTime / (60 * 60))),
+    ["%%wM"] = string.format("%02d", math.floor(startTime / 60 % 60)),
+    ["%%wm"] = string.format("%d", math.floor(startTime / 60)),
+    ["%%wS"] = string.format("%02d", math.floor(startTime % 60)),
+    ["%%ws"] = string.format("%d", math.floor(startTime)),
+    ["%%wf"] = string.format("%s", startTime),
+    ["%%wT"] = string.sub(string.format("%.3f", startTime % 1), 3),
+    ["%%mH"] = string.format("%02d", math.floor(endTime / (60 * 60))),
+    ["%%mh"] = string.format("%d", math.floor(endTime / (60 * 60))),
+    ["%%mM"] = string.format("%02d", math.floor(endTime / 60 % 60)),
+    ["%%mm"] = string.format("%d", math.floor(endTime / 60)),
+    ["%%mS"] = string.format("%02d", math.floor(endTime % 60)),
+    ["%%ms"] = string.format("%d", math.floor(endTime)),
+    ["%%mf"] = string.format("%s", endTime),
+    ["%%mT"] = string.sub(string.format("%.3f", endTime % 1), 3),
+    ["%%f"] = mp.get_property("filename"),
+    ["%%F"] = mp.get_property("filename/no-ext"),
+    ["%%s"] = seconds_to_path_element(startTime),
+    ["%%S"] = seconds_to_path_element(startTime, true),
+    ["%%e"] = seconds_to_path_element(endTime),
+    ["%%E"] = seconds_to_path_element(endTime, true),
+    ["%%T"] = mp.get_property("media-title"),
+    ["%%M"] = (mp.get_property_native('aid') and not mp.get_property_native('mute') and hasAudioCodec) and '-audio' or '',
+    ["%%R"] = (options.scale_height ~= -1) and "-" .. tostring(options.scale_height) .. "p" or "-" .. tostring(mp.get_property_native('height')) .. "p",
+    ["%%t%%"] = "%%"
+  }
+  local filename = options.output_template
+  for format, value in pairs(replaceFirst) do
+    local _
+    filename, _ = filename:gsub(format, value)
+  end
+  for format, value in pairs(replaceTable) do
+    local _
+    filename, _ = filename:gsub(format, value)
+  end
+  if mp.get_property_bool("demuxer-via-network", false) then
+    local _
+    filename, _ = filename:gsub("%%X{([^}]*)}", "%1")
+    filename, _ = filename:gsub("%%x", "")
+  else
+    local x = string.gsub(mp.get_property("stream-open-filename", ""), string.gsub(mp.get_property("filename", ""), "%W", "%%%1") .. "$", "")
+    local _
+    filename, _ = filename:gsub("%%X{[^}]*}", x)
+    filename, _ = filename:gsub("%%x", x)
+  end
+  filename = expand_properties(filename, "%")
+  for format in filename:gmatch("%%t([aAbBcCdDeFgGhHIjmMnprRStTuUVwWxXyYzZ])") do
+    local _
+    filename, _ = filename:gsub("%%t" .. format, os.date("%" .. format))
+  end
+  local _
+  filename, _ = filename:gsub("[<>:\"/\\|?*]", "")
+  return tostring(filename) .. "." .. tostring(videoFormat.outputExtension)
+end
+local parse_directory
+parse_directory = function(dir)
+  local home_dir = os.getenv("HOME")
+  if not home_dir then
+    home_dir = os.getenv("USERPROFILE")
+  end
+  if not home_dir then
+    local drive = os.getenv("HOMEDRIVE")
+    local path = os.getenv("HOMEPATH")
+    if drive and path then
+      home_dir = utils.join_path(drive, path)
+    else
+      msg.warn("Couldn't find home dir.")
+      home_dir = ""
+    end
+  end
+  local _
+  dir, _ = dir:gsub("^~", home_dir)
+  return dir
+end
+local is_windows = type(package) == "table" and type(package.config) == "string" and package.config:sub(1, 1) == "\\"
+local trim
+trim = function(s)
+  return s:match("^%s*(.-)%s*$")
+end
+local get_null_path
+get_null_path = function()
+  if file_exists("/dev/null") then
+    return "/dev/null"
+  end
+  return "NUL"
+end
+local run_subprocess
+run_subprocess = function(params)
+  local res = utils.subprocess(params)
+  msg.verbose("Command stdout: ")
+  msg.verbose(res.stdout)
+  if res.status ~= 0 then
+    msg.verbose("Command failed! Reason: ", res.error, " Killed by us? ", res.killed_by_us and "yes" or "no")
+    return false
+  end
+  return true
+end
+local shell_escape
+shell_escape = function(args)
+  local ret = { }
+  for i, a in ipairs(args) do
+    local s = tostring(a)
+    if string.match(s, "[^A-Za-z0-9_/:=-]") then
+      if is_windows then
+        s = '"' .. string.gsub(s, '"', '"\\""') .. '"'
+      else
+        s = "'" .. string.gsub(s, "'", "'\\''") .. "'"
+      end
+    end
+    table.insert(ret, s)
+  end
+  local concat = table.concat(ret, " ")
+  if is_windows then
+    concat = '"' .. concat .. '"'
+  end
+  return concat
+end
+local run_subprocess_popen
+run_subprocess_popen = function(command_line)
+  local command_line_string = shell_escape(command_line)
+  command_line_string = command_line_string .. " 2>&1"
+  msg.verbose("run_subprocess_popen: running " .. tostring(command_line_string))
+  return io.popen(command_line_string)
+end
+local calculate_scale_factor
+calculate_scale_factor = function()
+  local baseResY = 720
+  local osd_w, osd_h = mp.get_osd_size()
+  return osd_h / baseResY
+end
+local should_display_progress
+should_display_progress = function()
+  if options.display_progress == "auto" then
+    return not is_windows
+  end
+  return options.display_progress
+end
+local reverse
+reverse = function(list)
+  local _accum_0 = { }
+  local _len_0 = 1
+  local _max_0 = 1
+  for _index_0 = #list, _max_0 < 0 and #list + _max_0 or _max_0, -1 do
+    local element = list[_index_0]
+    _accum_0[_len_0] = element
+    _len_0 = _len_0 + 1
+  end
+  return _accum_0
+end
+local get_pass_logfile_path
+get_pass_logfile_path = function(encode_out_path)
+  return tostring(encode_out_path) .. "-video-pass1.log"
+end
+local dimensions_changed = true
+local _video_dimensions = { }
+local get_video_dimensions
+get_video_dimensions = function()
+  if not (dimensions_changed) then
+    return _video_dimensions
+  end
+  local video_params = mp.get_property_native("video-out-params")
+  if not video_params then
+    return nil
+  end
+  dimensions_changed = false
+  local keep_aspect = mp.get_property_bool("keepaspect")
+  local w = video_params["w"]
+  local h = video_params["h"]
+  local dw = video_params["dw"]
+  local dh = video_params["dh"]
+  if mp.get_property_number("video-rotate") % 180 == 90 then
+    w, h = h, w
+    dw, dh = dh, dw
+  end
+  _video_dimensions = {
+    top_left = { },
+    bottom_right = { },
+    ratios = { }
+  }
+  local window_w, window_h = mp.get_osd_size()
+  if keep_aspect then
+    local unscaled = mp.get_property_native("video-unscaled")
+    local panscan = mp.get_property_number("panscan")
+    local fwidth = window_w
+    local fheight = math.floor(window_w / dw * dh)
+    if fheight > window_h or fheight < h then
+      local tmpw = math.floor(window_h / dh * dw)
+      if tmpw <= window_w then
+        fheight = window_h
+        fwidth = tmpw
+      end
+    end
+    local vo_panscan_area = window_h - fheight
+    local f_w = fwidth / fheight
+    local f_h = 1
+    if vo_panscan_area == 0 then
+      vo_panscan_area = window_h - fwidth
+      f_w = 1
+      f_h = fheight / fwidth
+    end
+    if unscaled or unscaled == "downscale-big" then
+      vo_panscan_area = 0
+      if unscaled or (dw <= window_w and dh <= window_h) then
+        fwidth = dw
+        fheight = dh
+      end
+    end
+    local scaled_width = fwidth + math.floor(vo_panscan_area * panscan * f_w)
+    local scaled_height = fheight + math.floor(vo_panscan_area * panscan * f_h)
+    local split_scaling
+    split_scaling = function(dst_size, scaled_src_size, zoom, align, pan)
+      scaled_src_size = math.floor(scaled_src_size * 2 ^ zoom)
+      align = (align + 1) / 2
+      local dst_start = math.floor((dst_size - scaled_src_size) * align + pan * scaled_src_size)
+      if dst_start < 0 then
+        dst_start = dst_start + 1
+      end
+      local dst_end = dst_start + scaled_src_size
+      if dst_start >= dst_end then
+        dst_start = 0
+        dst_end = 1
+      end
+      return dst_start, dst_end
+    end
+    local zoom = mp.get_property_number("video-zoom")
+    local align_x = mp.get_property_number("video-align-x")
+    local pan_x = mp.get_property_number("video-pan-x")
+    _video_dimensions.top_left.x, _video_dimensions.bottom_right.x = split_scaling(window_w, scaled_width, zoom, align_x, pan_x)
+    local align_y = mp.get_property_number("video-align-y")
+    local pan_y = mp.get_property_number("video-pan-y")
+    _video_dimensions.top_left.y, _video_dimensions.bottom_right.y = split_scaling(window_h, scaled_height, zoom, align_y, pan_y)
+  else
+    _video_dimensions.top_left.x = 0
+    _video_dimensions.bottom_right.x = window_w
+    _video_dimensions.top_left.y = 0
+    _video_dimensions.bottom_right.y = window_h
+  end
+  _video_dimensions.ratios.w = w / (_video_dimensions.bottom_right.x - _video_dimensions.top_left.x)
+  _video_dimensions.ratios.h = h / (_video_dimensions.bottom_right.y - _video_dimensions.top_left.y)
+  return _video_dimensions
+end
+local set_dimensions_changed
+set_dimensions_changed = function()
+  dimensions_changed = true
+end
+local monitor_dimensions
+monitor_dimensions = function()
+  local properties = {
+    "keepaspect",
+    "video-out-params",
+    "video-unscaled",
+    "panscan",
+    "video-zoom",
+    "video-align-x",
+    "video-pan-x",
+    "video-align-y",
+    "video-pan-y",
+    "osd-width",
+    "osd-height"
+  }
+  for _, p in ipairs(properties) do
+    mp.observe_property(p, "native", set_dimensions_changed)
+  end
+end
+local clamp
+clamp = function(min, val, max)
+  if val <= min then
+    return min
+  end
+  if val >= max then
+    return max
+  end
+  return val
+end
+local clamp_point
+clamp_point = function(top_left, point, bottom_right)
+  return {
+    x = clamp(top_left.x, point.x, bottom_right.x),
+    y = clamp(top_left.y, point.y, bottom_right.y)
+  }
+end
+local VideoPoint
+do
+  local _class_0
+  local _base_0 = {
+    set_from_screen = function(self, sx, sy)
+      local d = get_video_dimensions()
+      local point = clamp_point(d.top_left, {
+        x = sx,
+        y = sy
+      }, d.bottom_right)
+      self.x = math.floor(d.ratios.w * (point.x - d.top_left.x) + 0.5)
+      self.y = math.floor(d.ratios.h * (point.y - d.top_left.y) + 0.5)
+    end,
+    to_screen = function(self)
+      local d = get_video_dimensions()
+      return {
+        x = math.floor(self.x / d.ratios.w + d.top_left.x + 0.5),
+        y = math.floor(self.y / d.ratios.h + d.top_left.y + 0.5)
+      }
+    end
+  }
+  _base_0.__index = _base_0
+  _class_0 = setmetatable({
+    __init = function(self)
+      self.x = -1
+      self.y = -1
+    end,
+    __base = _base_0,
+    __name = "VideoPoint"
+  }, {
+    __index = _base_0,
+    __call = function(cls, ...)
+      local _self_0 = setmetatable({}, _base_0)
+      cls.__init(_self_0, ...)
+      return _self_0
+    end
+  })
+  _base_0.__class = _class_0
+  VideoPoint = _class_0
+end
+local Region
+do
+  local _class_0
+  local _base_0 = {
+    is_valid = function(self)
+      return self.x > -1 and self.y > -1 and self.w > -1 and self.h > -1
+    end,
+    set_from_points = function(self, p1, p2)
+      self.x = math.min(p1.x, p2.x)
+      self.y = math.min(p1.y, p2.y)
+      self.w = math.abs(p1.x - p2.x)
+      self.h = math.abs(p1.y - p2.y)
+    end
+  }
+  _base_0.__index = _base_0
+  _class_0 = setmetatable({
+    __init = function(self)
+      self.x = -1
+      self.y = -1
+      self.w = -1
+      self.h = -1
+    end,
+    __base = _base_0,
+    __name = "Region"
+  }, {
+    __index = _base_0,
+    __call = function(cls, ...)
+      local _self_0 = setmetatable({}, _base_0)
+      cls.__init(_self_0, ...)
+      return _self_0
+    end
+  })
+  _base_0.__class = _class_0
+  Region = _class_0
+end
+local make_fullscreen_region
+make_fullscreen_region = function()
+  local r = Region()
+  local d = get_video_dimensions()
+  local a = VideoPoint()
+  local b = VideoPoint()
+  local xa, ya
+  do
+    local _obj_0 = d.top_left
+    xa, ya = _obj_0.x, _obj_0.y
+  end
+  a:set_from_screen(xa, ya)
+  local xb, yb
+  do
+    local _obj_0 = d.bottom_right
+    xb, yb = _obj_0.x, _obj_0.y
+  end
+  b:set_from_screen(xb, yb)
+  r:set_from_points(a, b)
+  return r
+end
+local read_double
+read_double = function(bytes)
+  local sign = 1
+  local mantissa = bytes[2] % 2 ^ 4
+  for i = 3, 8 do
+    mantissa = mantissa * 256 + bytes[i]
+  end
+  if bytes[1] > 127 then
+    sign = -1
+  end
+  local exponent = (bytes[1] % 128) * 2 ^ 4 + math.floor(bytes[2] / 2 ^ 4)
+  if exponent == 0 then
+    return 0
+  end
+  mantissa = (math.ldexp(mantissa, -52) + 1) * sign
+  return math.ldexp(mantissa, exponent - 1023)
+end
+local write_double
+write_double = function(num)
+  local bytes = {
+    0,
+    0,
+    0,
+    0,
+    0,
+    0,
+    0,
+    0
+  }
+  if num == 0 then
+    return bytes
+  end
+  local anum = math.abs(num)
+  local mantissa, exponent = math.frexp(anum)
+  exponent = exponent - 1
+  mantissa = mantissa * 2 - 1
+  local sign = num ~= anum and 128 or 0
+  exponent = exponent + 1023
+  bytes[1] = sign + math.floor(exponent / 2 ^ 4)
+  mantissa = mantissa * 2 ^ 4
+  local currentmantissa = math.floor(mantissa)
+  mantissa = mantissa - currentmantissa
+  bytes[2] = (exponent % 2 ^ 4) * 2 ^ 4 + currentmantissa
+  for i = 3, 8 do
+    mantissa = mantissa * 2 ^ 8
+    currentmantissa = math.floor(mantissa)
+    mantissa = mantissa - currentmantissa
+    bytes[i] = currentmantissa
+  end
+  return bytes
+end
+local FirstpassStats
+do
+  local _class_0
+  local duration_multiplier, fields_before_duration, fields_after_duration
+  local _base_0 = {
+    get_duration = function(self)
+      local big_endian_binary_duration = reverse(self.binary_duration)
+      return read_double(reversed_binary_duration) / duration_multiplier
+    end,
+    set_duration = function(self, duration)
+      local big_endian_binary_duration = write_double(duration * duration_multiplier)
+      self.binary_duration = reverse(big_endian_binary_duration)
+    end,
+    _bytes_to_string = function(self, bytes)
+      return string.char(unpack(bytes))
+    end,
+    as_binary_string = function(self)
+      local before_duration_string = self:_bytes_to_string(self.binary_data_before_duration)
+      local duration_string = self:_bytes_to_string(self.binary_duration)
+      local after_duration_string = self:_bytes_to_string(self.binary_data_after_duration)
+      return before_duration_string .. duration_string .. after_duration_string
+    end
+  }
+  _base_0.__index = _base_0
+  _class_0 = setmetatable({
+    __init = function(self, before_duration, duration, after_duration)
+      self.binary_data_before_duration = before_duration
+      self.binary_duration = duration
+      self.binary_data_after_duration = after_duration
+    end,
+    __base = _base_0,
+    __name = "FirstpassStats"
+  }, {
+    __index = _base_0,
+    __call = function(cls, ...)
+      local _self_0 = setmetatable({}, _base_0)
+      cls.__init(_self_0, ...)
+      return _self_0
+    end
+  })
+  _base_0.__class = _class_0
+  local self = _class_0
+  duration_multiplier = 10000000.0
+  fields_before_duration = 16
+  fields_after_duration = 1
+  self.data_before_duration_size = function(self)
+    return fields_before_duration * 8
+  end
+  self.data_after_duration_size = function(self)
+    return fields_after_duration * 8
+  end
+  self.size = function(self)
+    return (fields_before_duration + 1 + fields_after_duration) * 8
+  end
+  self.from_bytes = function(self, bytes)
+    local before_duration
+    do
+      local _accum_0 = { }
+      local _len_0 = 1
+      local _max_0 = self:data_before_duration_size()
+      for _index_0 = 1, _max_0 < 0 and #bytes + _max_0 or _max_0 do
+        local b = bytes[_index_0]
+        _accum_0[_len_0] = b
+        _len_0 = _len_0 + 1
+      end
+      before_duration = _accum_0
+    end
+    local duration
+    do
+      local _accum_0 = { }
+      local _len_0 = 1
+      local _max_0 = self:data_before_duration_size() + 8
+      for _index_0 = self:data_before_duration_size() + 1, _max_0 < 0 and #bytes + _max_0 or _max_0 do
+        local b = bytes[_index_0]
+        _accum_0[_len_0] = b
+        _len_0 = _len_0 + 1
+      end
+      duration = _accum_0
+    end
+    local after_duration
+    do
+      local _accum_0 = { }
+      local _len_0 = 1
+      for _index_0 = self:data_before_duration_size() + 8 + 1, #bytes do
+        local b = bytes[_index_0]
+        _accum_0[_len_0] = b
+        _len_0 = _len_0 + 1
+      end
+      after_duration = _accum_0
+    end
+    return self(before_duration, duration, after_duration)
+  end
+  FirstpassStats = _class_0
+end
+local read_logfile_into_stats_array
+read_logfile_into_stats_array = function(logfile_path)
+  local file = assert(io.open(logfile_path, "rb"))
+  local logfile_string = base64_decode(file:read())
+  file:close()
+  local stats_size = FirstpassStats:size()
+  assert(logfile_string:len() % stats_size == 0)
+  local stats = { }
+  for offset = 1, #logfile_string, stats_size do
+    local bytes = {
+      logfile_string:byte(offset, offset + stats_size - 1)
+    }
+    assert(#bytes == stats_size)
+    stats[#stats + 1] = FirstpassStats:from_bytes(bytes)
+  end
+  return stats
+end
+local write_stats_array_to_logfile
+write_stats_array_to_logfile = function(stats_array, logfile_path)
+  local file = assert(io.open(logfile_path, "wb"))
+  local logfile_string = ""
+  for _index_0 = 1, #stats_array do
+    local stat = stats_array[_index_0]
+    logfile_string = logfile_string .. stat:as_binary_string()
+  end
+  file:write(base64_encode(logfile_string))
+  return file:close()
+end
+local vp8_patch_logfile
+vp8_patch_logfile = function(logfile_path, encode_total_duration)
+  local stats_array = read_logfile_into_stats_array(logfile_path)
+  local average_duration = encode_total_duration / (#stats_array - 1)
+  for i = 1, #stats_array - 1 do
+    stats_array[i]:set_duration(average_duration)
+  end
+  stats_array[#stats_array]:set_duration(encode_total_duration)
+  return write_stats_array_to_logfile(stats_array, logfile_path)
+end
+local formats = { }
+local Format
+do
+  local _class_0
+  local _base_0 = {
+    getPreFilters = function(self)
+      return { }
+    end,
+    getPostFilters = function(self)
+      return { }
+    end,
+    getFlags = function(self)
+      return { }
+    end,
+    getCodecFlags = function(self)
+      local codecs = { }
+      if self.videoCodec ~= "" then
+        codecs[#codecs + 1] = "--ovc=" .. tostring(self.videoCodec)
+      end
+      if self.audioCodec ~= "" then
+        codecs[#codecs + 1] = "--oac=" .. tostring(self.audioCodec)
+      end
+      return codecs
+    end,
+    postCommandModifier = function(self, command, region, startTime, endTime)
+      return command
+    end
+  }
+  _base_0.__index = _base_0
+  _class_0 = setmetatable({
+    __init = function(self)
+      self.displayName = "Basic"
+      self.supportsTwopass = true
+      self.videoCodec = ""
+      self.audioCodec = ""
+      self.outputExtension = ""
+      self.acceptsBitrate = true
+    end,
+    __base = _base_0,
+    __name = "Format"
+  }, {
+    __index = _base_0,
+    __call = function(cls, ...)
+      local _self_0 = setmetatable({}, _base_0)
+      cls.__init(_self_0, ...)
+      return _self_0
+    end
+  })
+  _base_0.__class = _class_0
+  Format = _class_0
+end
+local RawVideo
+do
+  local _class_0
+  local _parent_0 = Format
+  local _base_0 = {
+    getColorspace = function(self)
+      local csp = mp.get_property("colormatrix")
+      local _exp_0 = csp
+      if "bt.601" == _exp_0 then
+        return "bt601"
+      elseif "bt.709" == _exp_0 then
+        return "bt709"
+      elseif "bt.2020" == _exp_0 then
+        return "bt2020"
+      elseif "smpte-240m" == _exp_0 then
+        return "smpte240m"
+      else
+        msg.info("Warning, unknown colorspace " .. tostring(csp) .. " detected, using bt.601.")
+        return "bt601"
+      end
+    end,
+    getPostFilters = function(self)
+      return {
+        "format=yuv444p16",
+        "lavfi-scale=in_color_matrix=" .. self:getColorspace(),
+        "format=bgr24"
+      }
+    end
+  }
+  _base_0.__index = _base_0
+  setmetatable(_base_0, _parent_0.__base)
+  _class_0 = setmetatable({
+    __init = function(self)
+      self.displayName = "Raw"
+      self.supportsTwopass = false
+      self.videoCodec = "rawvideo"
+      self.audioCodec = "pcm_s16le"
+      self.outputExtension = "avi"
+      self.acceptsBitrate = false
+    end,
+    __base = _base_0,
+    __name = "RawVideo",
+    __parent = _parent_0
+  }, {
+    __index = function(cls, name)
+      local val = rawget(_base_0, name)
+      if val == nil then
+        local parent = rawget(cls, "__parent")
+        if parent then
+          return parent[name]
+        end
+      else
+        return val
+      end
+    end,
+    __call = function(cls, ...)
+      local _self_0 = setmetatable({}, _base_0)
+      cls.__init(_self_0, ...)
+      return _self_0
+    end
+  })
+  _base_0.__class = _class_0
+  if _parent_0.__inherited then
+    _parent_0.__inherited(_parent_0, _class_0)
+  end
+  RawVideo = _class_0
+end
+formats["raw"] = RawVideo()
+local WebmVP8
+do
+  local _class_0
+  local _parent_0 = Format
+  local _base_0 = {
+    getPreFilters = function(self)
+      local colormatrixFilter = {
+        ["bt.709"] = "bt709",
+        ["bt.2020"] = "bt2020",
+        ["smpte-240m"] = "smpte240m"
+      }
+      local ret = { }
+      local colormatrix = mp.get_property_native("video-params/colormatrix")
+      if colormatrixFilter[colormatrix] then
+        append(ret, {
+          "lavfi-colormatrix=" .. tostring(colormatrixFilter[colormatrix]) .. ":bt601"
+        })
+      end
+      return ret
+    end,
+    getFlags = function(self)
+      return {
+        "--ovcopts-add=threads=" .. tostring(options.libvpx_threads),
+        "--ovcopts-add=auto-alt-ref=1",
+        "--ovcopts-add=lag-in-frames=25",
+        "--ovcopts-add=quality=good",
+        "--ovcopts-add=cpu-used=0"
+      }
+    end
+  }
+  _base_0.__index = _base_0
+  setmetatable(_base_0, _parent_0.__base)
+  _class_0 = setmetatable({
+    __init = function(self)
+      self.displayName = "WebM"
+      self.supportsTwopass = true
+      self.videoCodec = "libvpx"
+      self.audioCodec = "libvorbis"
+      self.outputExtension = "webm"
+      self.acceptsBitrate = true
+    end,
+    __base = _base_0,
+    __name = "WebmVP8",
+    __parent = _parent_0
+  }, {
+    __index = function(cls, name)
+      local val = rawget(_base_0, name)
+      if val == nil then
+        local parent = rawget(cls, "__parent")
+        if parent then
+          return parent[name]
+        end
+      else
+        return val
+      end
+    end,
+    __call = function(cls, ...)
+      local _self_0 = setmetatable({}, _base_0)
+      cls.__init(_self_0, ...)
+      return _self_0
+    end
+  })
+  _base_0.__class = _class_0
+  if _parent_0.__inherited then
+    _parent_0.__inherited(_parent_0, _class_0)
+  end
+  WebmVP8 = _class_0
+end
+formats["webm-vp8"] = WebmVP8()
+local WebmVP9
+do
+  local _class_0
+  local _parent_0 = Format
+  local _base_0 = {
+    getFlags = function(self)
+      return {
+        "--ovcopts-add=threads=" .. tostring(options.libvpx_threads)
+      }
+    end
+  }
+  _base_0.__index = _base_0
+  setmetatable(_base_0, _parent_0.__base)
+  _class_0 = setmetatable({
+    __init = function(self)
+      self.displayName = "WebM (VP9)"
+      self.supportsTwopass = false
+      self.videoCodec = "libvpx-vp9"
+      self.audioCodec = "libopus"
+      self.outputExtension = "webm"
+      self.acceptsBitrate = true
+    end,
+    __base = _base_0,
+    __name = "WebmVP9",
+    __parent = _parent_0
+  }, {
+    __index = function(cls, name)
+      local val = rawget(_base_0, name)
+      if val == nil then
+        local parent = rawget(cls, "__parent")
+        if parent then
+          return parent[name]
+        end
+      else
+        return val
+      end
+    end,
+    __call = function(cls, ...)
+      local _self_0 = setmetatable({}, _base_0)
+      cls.__init(_self_0, ...)
+      return _self_0
+    end
+  })
+  _base_0.__class = _class_0
+  if _parent_0.__inherited then
+    _parent_0.__inherited(_parent_0, _class_0)
+  end
+  WebmVP9 = _class_0
+end
+formats["webm-vp9"] = WebmVP9()
+local MP4
+do
+  local _class_0
+  local _parent_0 = Format
+  local _base_0 = { }
+  _base_0.__index = _base_0
+  setmetatable(_base_0, _parent_0.__base)
+  _class_0 = setmetatable({
+    __init = function(self)
+      self.displayName = "MP4 (h264/AAC)"
+      self.supportsTwopass = true
+      self.videoCodec = "libx264"
+      self.audioCodec = "aac"
+      self.outputExtension = "mp4"
+      self.acceptsBitrate = true
+    end,
+    __base = _base_0,
+    __name = "MP4",
+    __parent = _parent_0
+  }, {
+    __index = function(cls, name)
+      local val = rawget(_base_0, name)
+      if val == nil then
+        local parent = rawget(cls, "__parent")
+        if parent then
+          return parent[name]
+        end
+      else
+        return val
+      end
+    end,
+    __call = function(cls, ...)
+      local _self_0 = setmetatable({}, _base_0)
+      cls.__init(_self_0, ...)
+      return _self_0
+    end
+  })
+  _base_0.__class = _class_0
+  if _parent_0.__inherited then
+    _parent_0.__inherited(_parent_0, _class_0)
+  end
+  MP4 = _class_0
+end
+formats["mp4"] = MP4()
+local MP4NVENC
+do
+  local _class_0
+  local _parent_0 = Format
+  local _base_0 = { }
+  _base_0.__index = _base_0
+  setmetatable(_base_0, _parent_0.__base)
+  _class_0 = setmetatable({
+    __init = function(self)
+      self.displayName = "MP4 (h264-NVENC/AAC)"
+      self.supportsTwopass = true
+      self.videoCodec = "h264_nvenc"
+      self.audioCodec = "aac"
+      self.outputExtension = "mp4"
+      self.acceptsBitrate = true
+    end,
+    __base = _base_0,
+    __name = "MP4NVENC",
+    __parent = _parent_0
+  }, {
+    __index = function(cls, name)
+      local val = rawget(_base_0, name)
+      if val == nil then
+        local parent = rawget(cls, "__parent")
+        if parent then
+          return parent[name]
+        end
+      else
+        return val
+      end
+    end,
+    __call = function(cls, ...)
+      local _self_0 = setmetatable({}, _base_0)
+      cls.__init(_self_0, ...)
+      return _self_0
+    end
+  })
+  _base_0.__class = _class_0
+  if _parent_0.__inherited then
+    _parent_0.__inherited(_parent_0, _class_0)
+  end
+  MP4NVENC = _class_0
+end
+formats["mp4-nvenc"] = MP4NVENC()
+local MP3
+do
+  local _class_0
+  local _parent_0 = Format
+  local _base_0 = { }
+  _base_0.__index = _base_0
+  setmetatable(_base_0, _parent_0.__base)
+  _class_0 = setmetatable({
+    __init = function(self)
+      self.displayName = "MP3 (libmp3lame)"
+      self.supportsTwopass = false
+      self.videoCodec = ""
+      self.audioCodec = "libmp3lame"
+      self.outputExtension = "mp3"
+      self.acceptsBitrate = true
+    end,
+    __base = _base_0,
+    __name = "MP3",
+    __parent = _parent_0
+  }, {
+    __index = function(cls, name)
+      local val = rawget(_base_0, name)
+      if val == nil then
+        local parent = rawget(cls, "__parent")
+        if parent then
+          return parent[name]
+        end
+      else
+        return val
+      end
+    end,
+    __call = function(cls, ...)
+      local _self_0 = setmetatable({}, _base_0)
+      cls.__init(_self_0, ...)
+      return _self_0
+    end
+  })
+  _base_0.__class = _class_0
+  if _parent_0.__inherited then
+    _parent_0.__inherited(_parent_0, _class_0)
+  end
+  MP3 = _class_0
+end
+formats["mp3"] = MP3()
+local GIF
+do
+  local _class_0
+  local _parent_0 = Format
+  local _base_0 = {
+    postCommandModifier = function(self, command, region, startTime, endTime)
+      local new_command = { }
+      local start_ts = seconds_to_time_string(startTime, false, true)
+      local end_ts = seconds_to_time_string(endTime, false, true)
+      start_ts = start_ts:gsub(":", "\\\\:")
+      end_ts = end_ts:gsub(":", "\\\\:")
+      local cfilter = "[vid1]trim=start=" .. tostring(start_ts) .. ":end=" .. tostring(end_ts) .. "[vidtmp];"
+      if mp.get_property("deinterlace") == "yes" then
+        cfilter = cfilter .. "[vidtmp]yadif=mode=1[vidtmp];"
+      end
+      for _, v in ipairs(command) do
+        local _continue_0 = false
+        repeat
+          if v:match("^%-%-vf%-add=lavfi%-scale") or v:match("^%-%-vf%-add=lavfi%-crop") or v:match("^%-%-vf%-add=fps") or v:match("^%-%-vf%-add=lavfi%-eq") then
+            local n = v:gsub("^%-%-vf%-add=", ""):gsub("^lavfi%-", "")
+            cfilter = cfilter .. "[vidtmp]" .. tostring(n) .. "[vidtmp];"
+          else
+            if v:match("^%-%-video%-rotate=90") then
+              cfilter = cfilter .. "[vidtmp]transpose=1[vidtmp];"
+            else
+              if v:match("^%-%-video%-rotate=270") then
+                cfilter = cfilter .. "[vidtmp]transpose=2[vidtmp];"
+              else
+                if v:match("^%-%-video%-rotate=180") then
+                  cfilter = cfilter .. "[vidtmp]transpose=1[vidtmp];[vidtmp]transpose=1[vidtmp];"
+                else
+                  if v:match("^%-%-deinterlace=") then
+                    _continue_0 = true
+                    break
+                  else
+                    append(new_command, {
+                      v
+                    })
+                    _continue_0 = true
+                    break
+                  end
+                end
+              end
+            end
+          end
+          _continue_0 = true
+        until true
+        if not _continue_0 then
+          break
+        end
+      end
+      cfilter = cfilter .. "[vidtmp]split[topal][vidf];"
+      cfilter = cfilter .. "[topal]palettegen[pal];"
+      cfilter = cfilter .. "[vidf]fifo[vidf];"
+      if options.gif_dither == 6 then
+        cfilter = cfilter .. "[vidf][pal]paletteuse[vo]"
+      else
+        cfilter = cfilter .. "[vidf][pal]paletteuse=dither=bayer:bayer_scale=" .. tostring(options.gif_dither) .. ":diff_mode=rectangle[vo]"
+      end
+      append(new_command, {
+        "--lavfi-complex=" .. tostring(cfilter)
+      })
+      return new_command
+    end
+  }
+  _base_0.__index = _base_0
+  setmetatable(_base_0, _parent_0.__base)
+  _class_0 = setmetatable({
+    __init = function(self)
+      self.displayName = "GIF"
+      self.supportsTwopass = false
+      self.videoCodec = "gif"
+      self.audioCodec = ""
+      self.outputExtension = "gif"
+      self.acceptsBitrate = false
+    end,
+    __base = _base_0,
+    __name = "GIF",
+    __parent = _parent_0
+  }, {
+    __index = function(cls, name)
+      local val = rawget(_base_0, name)
+      if val == nil then
+        local parent = rawget(cls, "__parent")
+        if parent then
+          return parent[name]
+        end
+      else
+        return val
+      end
+    end,
+    __call = function(cls, ...)
+      local _self_0 = setmetatable({}, _base_0)
+      cls.__init(_self_0, ...)
+      return _self_0
+    end
+  })
+  _base_0.__class = _class_0
+  if _parent_0.__inherited then
+    _parent_0.__inherited(_parent_0, _class_0)
+  end
+  GIF = _class_0
+end
+formats["gif"] = GIF()
+local Page
+do
+  local _class_0
+  local _base_0 = {
+    add_keybinds = function(self)
+      if not self.keybinds then
+        return 
+      end
+      for key, func in pairs(self.keybinds) do
+        mp.add_forced_key_binding(key, key, func, {
+          repeatable = true
+        })
+      end
+    end,
+    remove_keybinds = function(self)
+      if not self.keybinds then
+        return 
+      end
+      for key, _ in pairs(self.keybinds) do
+        mp.remove_key_binding(key)
+      end
+    end,
+    observe_properties = function(self)
+      self.sizeCallback = function()
+        return self:draw()
+      end
+      local properties = {
+        "keepaspect",
+        "video-out-params",
+        "video-unscaled",
+        "panscan",
+        "video-zoom",
+        "video-align-x",
+        "video-pan-x",
+        "video-align-y",
+        "video-pan-y",
+        "osd-width",
+        "osd-height"
+      }
+      for _index_0 = 1, #properties do
+        local p = properties[_index_0]
+        mp.observe_property(p, "native", self.sizeCallback)
+      end
+    end,
+    unobserve_properties = function(self)
+      if self.sizeCallback then
+        mp.unobserve_property(self.sizeCallback)
+        self.sizeCallback = nil
+      end
+    end,
+    clear = function(self)
+      local window_w, window_h = mp.get_osd_size()
+      mp.set_osd_ass(window_w, window_h, "")
+      return mp.osd_message("", 0)
+    end,
+    prepare = function(self)
+      return nil
+    end,
+    dispose = function(self)
+      return nil
+    end,
+    show = function(self)
+      if self.visible then
+        return 
+      end
+      self.visible = true
+      self:observe_properties()
+      self:add_keybinds()
+      self:prepare()
+      self:clear()
+      return self:draw()
+    end,
+    hide = function(self)
+      if not self.visible then
+        return 
+      end
+      self.visible = false
+      self:unobserve_properties()
+      self:remove_keybinds()
+      self:clear()
+      return self:dispose()
+    end,
+    setup_text = function(self, ass)
+      local scale = calculate_scale_factor()
+      local margin = options.margin * scale
+      ass:append("{\\an7}")
+      ass:pos(margin, margin)
+      return ass:append("{\\fs" .. tostring(options.font_size * scale) .. "}")
+    end
+  }
+  _base_0.__index = _base_0
+  _class_0 = setmetatable({
+    __init = function() end,
+    __base = _base_0,
+    __name = "Page"
+  }, {
+    __index = _base_0,
+    __call = function(cls, ...)
+      local _self_0 = setmetatable({}, _base_0)
+      cls.__init(_self_0, ...)
+      return _self_0
+    end
+  })
+  _base_0.__class = _class_0
+  Page = _class_0
+end
+local EncodeWithProgress
+do
+  local _class_0
+  local _parent_0 = Page
+  local _base_0 = {
+    draw = function(self)
+      local progress = 100 * ((self.currentTime - self.startTime) / self.duration)
+      local progressText = string.format("%d%%", progress)
+      local window_w, window_h = mp.get_osd_size()
+      local ass = assdraw.ass_new()
+      ass:new_event()
+      self:setup_text(ass)
+      ass:append("Encoding (" .. tostring(bold(progressText)) .. ")\\N")
+      return mp.set_osd_ass(window_w, window_h, ass.text)
+    end,
+    parseLine = function(self, line)
+      local matchTime = string.match(line, "Encode time[-]pos: ([0-9.]+)")
+      local matchExit = string.match(line, "Exiting... [(]([%a ]+)[)]")
+      if matchTime == nil and matchExit == nil then
+        return 
+      end
+      if matchTime ~= nil and tonumber(matchTime) > self.currentTime then
+        self.currentTime = tonumber(matchTime)
+      end
+      if matchExit ~= nil then
+        self.finished = true
+        self.finishedReason = matchExit
+      end
+    end,
+    startEncode = function(self, command_line)
+      local copy_command_line
+      do
+        local _accum_0 = { }
+        local _len_0 = 1
+        for _index_0 = 1, #command_line do
+          local arg = command_line[_index_0]
+          _accum_0[_len_0] = arg
+          _len_0 = _len_0 + 1
+        end
+        copy_command_line = _accum_0
+      end
+      append(copy_command_line, {
+        '--term-status-msg=Encode time-pos: ${=time-pos}\\n'
+      })
+      self:show()
+      local processFd = run_subprocess_popen(copy_command_line)
+      for line in processFd:lines() do
+        msg.verbose(string.format('%q', line))
+        self:parseLine(line)
+        self:draw()
+      end
+      processFd:close()
+      self:hide()
+      if self.finishedReason == "End of file" then
+        return true
+      end
+      return false
+    end
+  }
+  _base_0.__index = _base_0
+  setmetatable(_base_0, _parent_0.__base)
+  _class_0 = setmetatable({
+    __init = function(self, startTime, endTime)
+      self.startTime = startTime
+      self.endTime = endTime
+      self.duration = endTime - startTime
+      self.currentTime = startTime
+    end,
+    __base = _base_0,
+    __name = "EncodeWithProgress",
+    __parent = _parent_0
+  }, {
+    __index = function(cls, name)
+      local val = rawget(_base_0, name)
+      if val == nil then
+        local parent = rawget(cls, "__parent")
+        if parent then
+          return parent[name]
+        end
+      else
+        return val
+      end
+    end,
+    __call = function(cls, ...)
+      local _self_0 = setmetatable({}, _base_0)
+      cls.__init(_self_0, ...)
+      return _self_0
+    end
+  })
+  _base_0.__class = _class_0
+  if _parent_0.__inherited then
+    _parent_0.__inherited(_parent_0, _class_0)
+  end
+  EncodeWithProgress = _class_0
+end
+local get_active_tracks
+get_active_tracks = function()
+  local accepted = {
+    video = true,
+    audio = not mp.get_property_bool("mute"),
+    sub = mp.get_property_bool("sub-visibility")
+  }
+  local active = {
+    video = { },
+    audio = { },
+    sub = { }
+  }
+  for _, track in ipairs(mp.get_property_native("track-list")) do
+    if track["selected"] and accepted[track["type"]] then
+      local count = #active[track["type"]]
+      active[track["type"]][count + 1] = track
+    end
+  end
+  return active
+end
+local filter_tracks_supported_by_format
+filter_tracks_supported_by_format = function(active_tracks, format)
+  local has_video_codec = format.videoCodec ~= ""
+  local has_audio_codec = format.audioCodec ~= ""
+  local supported = {
+    video = has_video_codec and active_tracks["video"] or { },
+    audio = has_audio_codec and active_tracks["audio"] or { },
+    sub = has_video_codec and active_tracks["sub"] or { }
+  }
+  return supported
+end
+local append_track
+append_track = function(out, track)
+  local external_flag = {
+    ["audio"] = "audio-file",
+    ["sub"] = "sub-file"
+  }
+  local internal_flag = {
+    ["video"] = "vid",
+    ["audio"] = "aid",
+    ["sub"] = "sid"
+  }
+  if track['external'] and string.len(track['external-filename']) <= 2048 then
+    return append(out, {
+      "--" .. tostring(external_flag[track['type']]) .. "=" .. tostring(track['external-filename'])
+    })
+  else
+    return append(out, {
+      "--" .. tostring(internal_flag[track['type']]) .. "=" .. tostring(track['id'])
+    })
+  end
+end
+local append_audio_tracks
+append_audio_tracks = function(out, tracks)
+  local internal_tracks = { }
+  for _index_0 = 1, #tracks do
+    local track = tracks[_index_0]
+    if track['external'] then
+      append_track(out, track)
+    else
+      append(internal_tracks, {
+        track
+      })
+    end
+  end
+  if #internal_tracks > 1 then
+    local filter_string = ""
+    for _index_0 = 1, #internal_tracks do
+      local track = internal_tracks[_index_0]
+      filter_string = filter_string .. "[aid" .. tostring(track['id']) .. "]"
+    end
+    filter_string = filter_string .. "amix[ao]"
+    return append(out, {
+      "--lavfi-complex=" .. tostring(filter_string)
+    })
+  else
+    if #internal_tracks == 1 then
+      return append_track(out, internal_tracks[1])
+    end
+  end
+end
+local get_scale_filters
+get_scale_filters = function()
+  local filters = { }
+  if options.force_square_pixels then
+    append(filters, {
+      "lavfi-scale=iw*sar:ih"
+    })
+  end
+  if options.scale_height > 0 then
+    append(filters, {
+      "lavfi-scale=-2:" .. tostring(options.scale_height)
+    })
+  end
+  return filters
+end
+local get_fps_filters
+get_fps_filters = function()
+  if options.fps > 0 then
+    return {
+      "fps=" .. tostring(options.fps)
+    }
+  end
+  return { }
+end
+local get_contrast_brightness_and_saturation_filters
+get_contrast_brightness_and_saturation_filters = function()
+  local mpv_brightness = mp.get_property("brightness")
+  local mpv_contrast = mp.get_property("contrast")
+  local mpv_saturation = mp.get_property("saturation")
+  if mpv_brightness == 0 and mpv_contrast == 0 and mpv_saturation == 0 then
+    return { }
+  end
+  local eq_saturation = (mpv_saturation + 100) / 100.0
+  local eq_contrast = (mpv_contrast + 100) / 100.0
+  local eq_brightness = (mpv_brightness / 50.0 + eq_contrast - 1) / 2.0
+  return {
+    "lavfi-eq=contrast=" .. tostring(eq_contrast) .. ":saturation=" .. tostring(eq_saturation) .. ":brightness=" .. tostring(eq_brightness)
+  }
+end
+local append_property
+append_property = function(out, property_name, option_name)
+  option_name = option_name or property_name
+  local prop = mp.get_property(property_name)
+  if prop and prop ~= "" then
+    return append(out, {
+      "--" .. tostring(option_name) .. "=" .. tostring(prop)
+    })
+  end
+end
+local append_list_options
+append_list_options = function(out, property_name, option_prefix)
+  option_prefix = option_prefix or property_name
+  local prop = mp.get_property_native(property_name)
+  if prop then
+    for _index_0 = 1, #prop do
+      local value = prop[_index_0]
+      append(out, {
+        "--" .. tostring(option_prefix) .. "-append=" .. tostring(value)
+      })
+    end
+  end
+end
+local get_playback_options
+get_playback_options = function()
+  local ret = { }
+  append_property(ret, "sub-ass-override")
+  append_property(ret, "sub-ass-force-style")
+  append_property(ret, "sub-ass-vsfilter-aspect-compat")
+  append_property(ret, "sub-auto")
+  append_property(ret, "sub-pos")
+  append_property(ret, "sub-delay")
+  append_property(ret, "video-rotate")
+  append_property(ret, "ytdl-format")
+  append_property(ret, "deinterlace")
+  return ret
+end
+local get_speed_flags
+get_speed_flags = function()
+  local ret = { }
+  local speed = mp.get_property_native("speed")
+  if speed ~= 1 then
+    append(ret, {
+      "--vf-add=setpts=PTS/" .. tostring(speed),
+      "--af-add=atempo=" .. tostring(speed),
+      "--sub-speed=1/" .. tostring(speed)
+    })
+  end
+  return ret
+end
+local get_metadata_flags
+get_metadata_flags = function()
+  local title = mp.get_property("filename/no-ext")
+  return {
+    "--oset-metadata=title=%" .. tostring(string.len(title)) .. "%" .. tostring(title)
+  }
+end
+local apply_current_filters
+apply_current_filters = function(filters)
+  local vf = mp.get_property_native("vf")
+  msg.verbose("apply_current_filters: got " .. tostring(#vf) .. " currently applied.")
+  for _index_0 = 1, #vf do
+    local _continue_0 = false
+    repeat
+      local filter = vf[_index_0]
+      msg.verbose("apply_current_filters: filter name: " .. tostring(filter['name']))
+      if filter["enabled"] == false then
+        _continue_0 = true
+        break
+      end
+      local str = filter["name"]
+      local params = filter["params"] or { }
+      for k, v in pairs(params) do
+        str = str .. ":" .. tostring(k) .. "=%" .. tostring(string.len(v)) .. "%" .. tostring(v)
+      end
+      append(filters, {
+        str
+      })
+      _continue_0 = true
+    until true
+    if not _continue_0 then
+      break
+    end
+  end
+end
+local get_video_filters
+get_video_filters = function(format, region)
+  local filters = { }
+  append(filters, format:getPreFilters())
+  if options.apply_current_filters then
+    apply_current_filters(filters)
+  end
+  if region and region:is_valid() then
+    append(filters, {
+      "lavfi-crop=" .. tostring(region.w) .. ":" .. tostring(region.h) .. ":" .. tostring(region.x) .. ":" .. tostring(region.y)
+    })
+  end
+  append(filters, get_scale_filters())
+  append(filters, get_fps_filters())
+  append(filters, get_contrast_brightness_and_saturation_filters())
+  append(filters, format:getPostFilters())
+  return filters
+end
+local get_video_encode_flags
+get_video_encode_flags = function(format, region)
+  local flags = { }
+  append(flags, get_playback_options())
+  local filters = get_video_filters(format, region)
+  for _index_0 = 1, #filters do
+    local f = filters[_index_0]
+    append(flags, {
+      "--vf-add=" .. tostring(f)
+    })
+  end
+  append(flags, get_speed_flags())
+  return flags
+end
+local calculate_bitrate
+calculate_bitrate = function(active_tracks, format, length)
+  if format.videoCodec == "" then
+    return nil, options.target_filesize * 8 / length
+  end
+  local video_kilobits = options.target_filesize * 8
+  local audio_kilobits = nil
+  local has_audio_track = #active_tracks["audio"] > 0
+  if options.strict_filesize_constraint and has_audio_track then
+    audio_kilobits = length * options.strict_audio_bitrate
+    video_kilobits = video_kilobits - audio_kilobits
+  end
+  local video_bitrate = math.floor(video_kilobits / length)
+  local audio_bitrate = audio_kilobits and math.floor(audio_kilobits / length) or nil
+  return video_bitrate, audio_bitrate
+end
+local find_path
+find_path = function(startTime, endTime)
+  local path = mp.get_property('path')
+  if not path then
+    return nil, nil, nil, nil, nil
+  end
+  local is_stream = not file_exists(path)
+  local is_temporary = false
+  if is_stream then
+    if mp.get_property('file-format') == 'hls' then
+      path = utils.join_path(parse_directory('~'), 'cache_dump.ts')
+      mp.command_native({
+        'dump_cache',
+        seconds_to_time_string(startTime, false, true),
+        seconds_to_time_string(endTime + 5, false, true),
+        path
+      })
+      endTime = endTime - startTime
+      startTime = 0
+      is_temporary = true
+    end
+  end
+  return path, is_stream, is_temporary, startTime, endTime
+end
+local encode
+encode = function(region, startTime, endTime)
+  local format = formats[options.output_format]
+  local originalStartTime = startTime
+  local originalEndTime = endTime
+  local path, is_stream, is_temporary
+  path, is_stream, is_temporary, startTime, endTime = find_path(startTime, endTime)
+  if not path then
+    message("No file is being played")
+    return 
+  end
+  local command = {
+    "mpv",
+    path,
+    "--start=" .. seconds_to_time_string(startTime, false, true),
+    "--end=" .. seconds_to_time_string(endTime, false, true),
+    "--loop-file=no",
+    "--no-pause"
+  }
+  append(command, format:getCodecFlags())
+  local active_tracks = get_active_tracks()
+  local supported_active_tracks = filter_tracks_supported_by_format(active_tracks, format)
+  for track_type, tracks in pairs(supported_active_tracks) do
+    if track_type == "audio" then
+      append_audio_tracks(command, tracks)
+    else
+      for _index_0 = 1, #tracks do
+        local track = tracks[_index_0]
+        append_track(command, track)
+      end
+    end
+  end
+  for track_type, tracks in pairs(supported_active_tracks) do
+    local _continue_0 = false
+    repeat
+      if #tracks > 0 then
+        _continue_0 = true
+        break
+      end
+      local _exp_0 = track_type
+      if "video" == _exp_0 then
+        append(command, {
+          "--vid=no"
+        })
+      elseif "audio" == _exp_0 then
+        append(command, {
+          "--aid=no"
+        })
+      elseif "sub" == _exp_0 then
+        append(command, {
+          "--sid=no"
+        })
+      end
+      _continue_0 = true
+    until true
+    if not _continue_0 then
+      break
+    end
+  end
+  if format.videoCodec ~= "" then
+    append(command, get_video_encode_flags(format, region))
+  end
+  append(command, format:getFlags())
+  if options.write_filename_on_metadata then
+    append(command, get_metadata_flags())
+  end
+  if format.acceptsBitrate then
+    if options.target_filesize > 0 then
+      local length = endTime - startTime
+      local video_bitrate, audio_bitrate = calculate_bitrate(supported_active_tracks, format, length)
+      if video_bitrate then
+        append(command, {
+          "--ovcopts-add=b=" .. tostring(video_bitrate) .. "k"
+        })
+      end
+      if audio_bitrate then
+        append(command, {
+          "--oacopts-add=b=" .. tostring(audio_bitrate) .. "k"
+        })
+      end
+      if options.strict_filesize_constraint then
+        local type = format.videoCodec ~= "" and "ovc" or "oac"
+        append(command, {
+          "--" .. tostring(type) .. "opts-add=minrate=" .. tostring(bitrate) .. "k",
+          "--" .. tostring(type) .. "opts-add=maxrate=" .. tostring(bitrate) .. "k"
+        })
+      end
+    else
+      local type = format.videoCodec ~= "" and "ovc" or "oac"
+      append(command, {
+        "--" .. tostring(type) .. "opts-add=b=0"
+      })
+    end
+  end
+  for token in string.gmatch(options.additional_flags, "[^%s]+") do
+    command[#command + 1] = token
+  end
+  if not options.strict_filesize_constraint then
+    for token in string.gmatch(options.non_strict_additional_flags, "[^%s]+") do
+      command[#command + 1] = token
+    end
+    if options.crf >= 0 then
+      append(command, {
+        "--ovcopts-add=crf=" .. tostring(options.crf)
+      })
+    end
+  end
+  local dir = ""
+  if is_stream then
+    dir = parse_directory("~")
+  else
+    local _
+    dir, _ = utils.split_path(path)
+  end
+  if options.output_directory ~= "" then
+    dir = parse_directory(options.output_directory)
+  end
+  local formatted_filename = format_filename(originalStartTime, originalEndTime, format)
+  local out_path = utils.join_path(dir, formatted_filename)
+  append(command, {
+    "--o=" .. tostring(out_path)
+  })
+  emit_event("encode-started")
+  if options.twopass and format.supportsTwopass and not is_stream then
+    local first_pass_cmdline
+    do
+      local _accum_0 = { }
+      local _len_0 = 1
+      for _index_0 = 1, #command do
+        local arg = command[_index_0]
+        _accum_0[_len_0] = arg
+        _len_0 = _len_0 + 1
+      end
+      first_pass_cmdline = _accum_0
+    end
+    append(first_pass_cmdline, {
+      "--ovcopts-add=flags=+pass1"
+    })
+    message("Starting first pass...")
+    msg.verbose("First-pass command line: ", table.concat(first_pass_cmdline, " "))
+    local res = run_subprocess({
+      args = first_pass_cmdline,
+      cancellable = false
+    })
+    if not res then
+      message("First pass failed! Check the logs for details.")
+      emit_event("encode-finished", "fail")
+      return 
+    end
+    append(command, {
+      "--ovcopts-add=flags=+pass2"
+    })
+    if format.videoCodec == "libvpx" then
+      msg.verbose("Patching libvpx pass log file...")
+      vp8_patch_logfile(get_pass_logfile_path(out_path), endTime - startTime)
+    end
+  end
+  command = format:postCommandModifier(command, region, startTime, endTime)
+  msg.info("Encoding to", out_path)
+  msg.verbose("Command line:", table.concat(command, " "))
+  if options.run_detached then
+    message("Started encode, process was detached.")
+    return utils.subprocess_detached({
+      args = command
+    })
+  else
+    local res = false
+    if not should_display_progress() then
+      message("Started encode...")
+      res = run_subprocess({
+        args = command,
+        cancellable = false
+      })
+    else
+      local ewp = EncodeWithProgress(startTime, endTime)
+      res = ewp:startEncode(command)
+    end
+    if res then
+      message("Encoded successfully! Saved to\\N" .. tostring(bold(out_path)))
+      emit_event("encode-finished", "success")
+    else
+      message("Encode failed! Check the logs for details.")
+      emit_event("encode-finished", "fail")
+    end
+    os.remove(get_pass_logfile_path(out_path))
+    if is_temporary then
+      return os.remove(path)
+    end
+  end
+end
+local CropPage
+do
+  local _class_0
+  local _parent_0 = Page
+  local _base_0 = {
+    reset = function(self)
+      local dimensions = get_video_dimensions()
+      local xa, ya
+      do
+        local _obj_0 = dimensions.top_left
+        xa, ya = _obj_0.x, _obj_0.y
+      end
+      self.pointA:set_from_screen(xa, ya)
+      local xb, yb
+      do
+        local _obj_0 = dimensions.bottom_right
+        xb, yb = _obj_0.x, _obj_0.y
+      end
+      self.pointB:set_from_screen(xb, yb)
+      if self.visible then
+        return self:draw()
+      end
+    end,
+    setPointA = function(self)
+      local posX, posY = mp.get_mouse_pos()
+      self.pointA:set_from_screen(posX, posY)
+      if self.visible then
+        return self:draw()
+      end
+    end,
+    setPointB = function(self)
+      local posX, posY = mp.get_mouse_pos()
+      self.pointB:set_from_screen(posX, posY)
+      if self.visible then
+        return self:draw()
+      end
+    end,
+    cancel = function(self)
+      self:hide()
+      return self.callback(false, nil)
+    end,
+    finish = function(self)
+      local region = Region()
+      region:set_from_points(self.pointA, self.pointB)
+      self:hide()
+      return self.callback(true, region)
+    end,
+    draw_box = function(self, ass)
+      local region = Region()
+      region:set_from_points(self.pointA:to_screen(), self.pointB:to_screen())
+      local d = get_video_dimensions()
+      ass:new_event()
+      ass:append("{\\an7}")
+      ass:pos(0, 0)
+      ass:append('{\\bord0}')
+      ass:append('{\\shad0}')
+      ass:append('{\\c&H000000&}')
+      ass:append('{\\alpha&H77}')
+      ass:draw_start()
+      ass:rect_cw(d.top_left.x, d.top_left.y, region.x, region.y + region.h)
+      ass:rect_cw(region.x, d.top_left.y, d.bottom_right.x, region.y)
+      ass:rect_cw(d.top_left.x, region.y + region.h, region.x + region.w, d.bottom_right.y)
+      ass:rect_cw(region.x + region.w, region.y, d.bottom_right.x, d.bottom_right.y)
+      return ass:draw_stop()
+    end,
+    draw = function(self)
+      local window = { }
+      window.w, window.h = mp.get_osd_size()
+      local ass = assdraw.ass_new()
+      self:draw_box(ass)
+      ass:new_event()
+      self:setup_text(ass)
+      ass:append(tostring(bold('Crop:')) .. "\\N")
+      ass:append(tostring(bold('1:')) .. " change point A (" .. tostring(self.pointA.x) .. ", " .. tostring(self.pointA.y) .. ")\\N")
+      ass:append(tostring(bold('2:')) .. " change point B (" .. tostring(self.pointB.x) .. ", " .. tostring(self.pointB.y) .. ")\\N")
+      ass:append(tostring(bold('r:')) .. " reset to whole screen\\N")
+      ass:append(tostring(bold('ESC:')) .. " cancel crop\\N")
+      local width, height = math.abs(self.pointA.x - self.pointB.x), math.abs(self.pointA.y - self.pointB.y)
+      ass:append(tostring(bold('ENTER:')) .. " confirm crop (" .. tostring(width) .. "x" .. tostring(height) .. ")\\N")
+      return mp.set_osd_ass(window.w, window.h, ass.text)
+    end
+  }
+  _base_0.__index = _base_0
+  setmetatable(_base_0, _parent_0.__base)
+  _class_0 = setmetatable({
+    __init = function(self, callback, region)
+      self.pointA = VideoPoint()
+      self.pointB = VideoPoint()
+      self.keybinds = {
+        ["1"] = (function()
+          local _base_1 = self
+          local _fn_0 = _base_1.setPointA
+          return function(...)
+            return _fn_0(_base_1, ...)
+          end
+        end)(),
+        ["2"] = (function()
+          local _base_1 = self
+          local _fn_0 = _base_1.setPointB
+          return function(...)
+            return _fn_0(_base_1, ...)
+          end
+        end)(),
+        ["r"] = (function()
+          local _base_1 = self
+          local _fn_0 = _base_1.reset
+          return function(...)
+            return _fn_0(_base_1, ...)
+          end
+        end)(),
+        ["ESC"] = (function()
+          local _base_1 = self
+          local _fn_0 = _base_1.cancel
+          return function(...)
+            return _fn_0(_base_1, ...)
+          end
+        end)(),
+        ["ENTER"] = (function()
+          local _base_1 = self
+          local _fn_0 = _base_1.finish
+          return function(...)
+            return _fn_0(_base_1, ...)
+          end
+        end)()
+      }
+      self:reset()
+      self.callback = callback
+      if region and region:is_valid() then
+        self.pointA.x = region.x
+        self.pointA.y = region.y
+        self.pointB.x = region.x + region.w
+        self.pointB.y = region.y + region.h
+      end
+    end,
+    __base = _base_0,
+    __name = "CropPage",
+    __parent = _parent_0
+  }, {
+    __index = function(cls, name)
+      local val = rawget(_base_0, name)
+      if val == nil then
+        local parent = rawget(cls, "__parent")
+        if parent then
+          return parent[name]
+        end
+      else
+        return val
+      end
+    end,
+    __call = function(cls, ...)
+      local _self_0 = setmetatable({}, _base_0)
+      cls.__init(_self_0, ...)
+      return _self_0
+    end
+  })
+  _base_0.__class = _class_0
+  if _parent_0.__inherited then
+    _parent_0.__inherited(_parent_0, _class_0)
+  end
+  CropPage = _class_0
+end
+local Option
+do
+  local _class_0
+  local _base_0 = {
+    hasPrevious = function(self)
+      local _exp_0 = self.optType
+      if "bool" == _exp_0 then
+        return true
+      elseif "int" == _exp_0 then
+        if self.opts.min then
+          return self.value > self.opts.min
+        else
+          return true
+        end
+      elseif "list" == _exp_0 then
+        return self.value > 1
+      end
+    end,
+    hasNext = function(self)
+      local _exp_0 = self.optType
+      if "bool" == _exp_0 then
+        return true
+      elseif "int" == _exp_0 then
+        if self.opts.max then
+          return self.value < self.opts.max
+        else
+          return true
+        end
+      elseif "list" == _exp_0 then
+        return self.value < #self.opts.possibleValues
+      end
+    end,
+    leftKey = function(self)
+      local _exp_0 = self.optType
+      if "bool" == _exp_0 then
+        self.value = not self.value
+      elseif "int" == _exp_0 then
+        self.value = self.value - self.opts.step
+        if self.opts.min and self.opts.min > self.value then
+          self.value = self.opts.min
+        end
+      elseif "list" == _exp_0 then
+        if self.value > 1 then
+          self.value = self.value - 1
+        end
+      end
+    end,
+    rightKey = function(self)
+      local _exp_0 = self.optType
+      if "bool" == _exp_0 then
+        self.value = not self.value
+      elseif "int" == _exp_0 then
+        self.value = self.value + self.opts.step
+        if self.opts.max and self.opts.max < self.value then
+          self.value = self.opts.max
+        end
+      elseif "list" == _exp_0 then
+        if self.value < #self.opts.possibleValues then
+          self.value = self.value + 1
+        end
+      end
+    end,
+    getValue = function(self)
+      local _exp_0 = self.optType
+      if "bool" == _exp_0 then
+        return self.value
+      elseif "int" == _exp_0 then
+        return self.value
+      elseif "list" == _exp_0 then
+        local value, _
+        do
+          local _obj_0 = self.opts.possibleValues[self.value]
+          value, _ = _obj_0[1], _obj_0[2]
+        end
+        return value
+      end
+    end,
+    setValue = function(self, value)
+      local _exp_0 = self.optType
+      if "bool" == _exp_0 then
+        self.value = value
+      elseif "int" == _exp_0 then
+        self.value = value
+      elseif "list" == _exp_0 then
+        local set = false
+        for i, possiblePair in ipairs(self.opts.possibleValues) do
+          local possibleValue, _
+          possibleValue, _ = possiblePair[1], possiblePair[2]
+          if possibleValue == value then
+            set = true
+            self.value = i
+            break
+          end
+        end
+        if not set then
+          return msg.warn("Tried to set invalid value " .. tostring(value) .. " to " .. tostring(self.displayText) .. " option.")
+        end
+      end
+    end,
+    getDisplayValue = function(self)
+      local _exp_0 = self.optType
+      if "bool" == _exp_0 then
+        return self.value and "yes" or "no"
+      elseif "int" == _exp_0 then
+        if self.opts.altDisplayNames and self.opts.altDisplayNames[self.value] then
+          return self.opts.altDisplayNames[self.value]
+        else
+          return tostring(self.value)
+        end
+      elseif "list" == _exp_0 then
+        local value, displayValue
+        do
+          local _obj_0 = self.opts.possibleValues[self.value]
+          value, displayValue = _obj_0[1], _obj_0[2]
+        end
+        return displayValue or value
+      end
+    end,
+    draw = function(self, ass, selected)
+      if selected then
+        ass:append(tostring(bold(self.displayText)) .. ": ")
+      else
+        ass:append(tostring(self.displayText) .. ": ")
+      end
+      if self:hasPrevious() then
+        ass:append("◀ ")
+      end
+      ass:append(self:getDisplayValue())
+      if self:hasNext() then
+        ass:append(" ▶")
+      end
+      return ass:append("\\N")
+    end,
+    optVisible = function(self)
+      if self.visibleCheckFn == nil then
+        return true
+      else
+        return self.visibleCheckFn()
+      end
+    end
+  }
+  _base_0.__index = _base_0
+  _class_0 = setmetatable({
+    __init = function(self, optType, displayText, value, opts, visibleCheckFn)
+      self.optType = optType
+      self.displayText = displayText
+      self.opts = opts
+      self.value = 1
+      self.visibleCheckFn = visibleCheckFn
+      return self:setValue(value)
+    end,
+    __base = _base_0,
+    __name = "Option"
+  }, {
+    __index = _base_0,
+    __call = function(cls, ...)
+      local _self_0 = setmetatable({}, _base_0)
+      cls.__init(_self_0, ...)
+      return _self_0
+    end
+  })
+  _base_0.__class = _class_0
+  Option = _class_0
+end
+local EncodeOptionsPage
+do
+  local _class_0
+  local _parent_0 = Page
+  local _base_0 = {
+    getCurrentOption = function(self)
+      return self.options[self.currentOption][2]
+    end,
+    leftKey = function(self)
+      (self:getCurrentOption()):leftKey()
+      return self:draw()
+    end,
+    rightKey = function(self)
+      (self:getCurrentOption()):rightKey()
+      return self:draw()
+    end,
+    prevOpt = function(self)
+      for i = self.currentOption - 1, 1, -1 do
+        if self.options[i][2]:optVisible() then
+          self.currentOption = i
+          break
+        end
+      end
+      return self:draw()
+    end,
+    nextOpt = function(self)
+      for i = self.currentOption + 1, #self.options do
+        if self.options[i][2]:optVisible() then
+          self.currentOption = i
+          break
+        end
+      end
+      return self:draw()
+    end,
+    confirmOpts = function(self)
+      for _, optPair in ipairs(self.options) do
+        local optName, opt
+        optName, opt = optPair[1], optPair[2]
+        options[optName] = opt:getValue()
+      end
+      self:hide()
+      return self.callback(true)
+    end,
+    cancelOpts = function(self)
+      self:hide()
+      return self.callback(false)
+    end,
+    draw = function(self)
+      local window_w, window_h = mp.get_osd_size()
+      local ass = assdraw.ass_new()
+      ass:new_event()
+      self:setup_text(ass)
+      ass:append(tostring(bold('Options:')) .. "\\N\\N")
+      for i, optPair in ipairs(self.options) do
+        local opt = optPair[2]
+        if opt:optVisible() then
+          opt:draw(ass, self.currentOption == i)
+        end
+      end
+      ass:append("\\N▲ / ▼: navigate\\N")
+      ass:append(tostring(bold('ENTER:')) .. " confirm options\\N")
+      ass:append(tostring(bold('ESC:')) .. " cancel\\N")
+      return mp.set_osd_ass(window_w, window_h, ass.text)
+    end
+  }
+  _base_0.__index = _base_0
+  setmetatable(_base_0, _parent_0.__base)
+  _class_0 = setmetatable({
+    __init = function(self, callback)
+      self.callback = callback
+      self.currentOption = 1
+      local scaleHeightOpts = {
+        possibleValues = {
+          {
+            -1,
+            "no"
+          },
+          {
+            144
+          },
+          {
+            240
+          },
+          {
+            360
+          },
+          {
+            480
+          },
+          {
+            540
+          },
+          {
+            720
+          },
+          {
+            1080
+          },
+          {
+            1440
+          },
+          {
+            2160
+          }
+        }
+      }
+      local filesizeOpts = {
+        step = 250,
+        min = 0,
+        altDisplayNames = {
+          [0] = "0 (constant quality)"
+        }
+      }
+      local crfOpts = {
+        step = 1,
+        min = -1,
+        altDisplayNames = {
+          [-1] = "disabled"
+        }
+      }
+      local fpsOpts = {
+        possibleValues = {
+          {
+            -1,
+            "source"
+          },
+          {
+            15
+          },
+          {
+            24
+          },
+          {
+            30
+          },
+          {
+            48
+          },
+          {
+            50
+          },
+          {
+            60
+          },
+          {
+            120
+          },
+          {
+            240
+          }
+        }
+      }
+      local formatIds = {
+        "webm-vp8",
+        "webm-vp9",
+        "mp4",
+        "mp4-nvenc",
+        "raw",
+        "mp3",
+        "gif"
+      }
+      local formatOpts = {
+        possibleValues = (function()
+          local _accum_0 = { }
+          local _len_0 = 1
+          for _index_0 = 1, #formatIds do
+            local fId = formatIds[_index_0]
+            _accum_0[_len_0] = {
+              fId,
+              formats[fId].displayName
+            }
+            _len_0 = _len_0 + 1
+          end
+          return _accum_0
+        end)()
+      }
+      local gifDitherOpts = {
+        possibleValues = {
+          {
+            0,
+            "bayer_scale 0"
+          },
+          {
+            1,
+            "bayer_scale 1"
+          },
+          {
+            2,
+            "bayer_scale 2"
+          },
+          {
+            3,
+            "bayer_scale 3"
+          },
+          {
+            4,
+            "bayer_scale 4"
+          },
+          {
+            5,
+            "bayer_scale 5"
+          },
+          {
+            6,
+            "sierra2_4a"
+          }
+        }
+      }
+      self.options = {
+        {
+          "output_format",
+          Option("list", "Output Format", options.output_format, formatOpts)
+        },
+        {
+          "twopass",
+          Option("bool", "Two Pass", options.twopass)
+        },
+        {
+          "apply_current_filters",
+          Option("bool", "Apply Current Video Filters", options.apply_current_filters)
+        },
+        {
+          "scale_height",
+          Option("list", "Scale Height", options.scale_height, scaleHeightOpts)
+        },
+        {
+          "strict_filesize_constraint",
+          Option("bool", "Strict Filesize Constraint", options.strict_filesize_constraint)
+        },
+        {
+          "write_filename_on_metadata",
+          Option("bool", "Write Filename on Metadata", options.write_filename_on_metadata)
+        },
+        {
+          "target_filesize",
+          Option("int", "Target Filesize", options.target_filesize, filesizeOpts)
+        },
+        {
+          "crf",
+          Option("int", "CRF", options.crf, crfOpts)
+        },
+        {
+          "fps",
+          Option("list", "FPS", options.fps, fpsOpts)
+        },
+        {
+          "gif_dither",
+          Option("list", "GIF Dither Type", options.gif_dither, gifDitherOpts, function()
+            return self.options[1][2]:getValue() == "gif"
+          end)
+        },
+        {
+          "force_square_pixels",
+          Option("bool", "Force Square Pixels", options.force_square_pixels)
+        }
+      }
+      self.keybinds = {
+        ["LEFT"] = (function()
+          local _base_1 = self
+          local _fn_0 = _base_1.leftKey
+          return function(...)
+            return _fn_0(_base_1, ...)
+          end
+        end)(),
+        ["RIGHT"] = (function()
+          local _base_1 = self
+          local _fn_0 = _base_1.rightKey
+          return function(...)
+            return _fn_0(_base_1, ...)
+          end
+        end)(),
+        ["UP"] = (function()
+          local _base_1 = self
+          local _fn_0 = _base_1.prevOpt
+          return function(...)
+            return _fn_0(_base_1, ...)
+          end
+        end)(),
+        ["DOWN"] = (function()
+          local _base_1 = self
+          local _fn_0 = _base_1.nextOpt
+          return function(...)
+            return _fn_0(_base_1, ...)
+          end
+        end)(),
+        ["ENTER"] = (function()
+          local _base_1 = self
+          local _fn_0 = _base_1.confirmOpts
+          return function(...)
+            return _fn_0(_base_1, ...)
+          end
+        end)(),
+        ["ESC"] = (function()
+          local _base_1 = self
+          local _fn_0 = _base_1.cancelOpts
+          return function(...)
+            return _fn_0(_base_1, ...)
+          end
+        end)()
+      }
+    end,
+    __base = _base_0,
+    __name = "EncodeOptionsPage",
+    __parent = _parent_0
+  }, {
+    __index = function(cls, name)
+      local val = rawget(_base_0, name)
+      if val == nil then
+        local parent = rawget(cls, "__parent")
+        if parent then
+          return parent[name]
+        end
+      else
+        return val
+      end
+    end,
+    __call = function(cls, ...)
+      local _self_0 = setmetatable({}, _base_0)
+      cls.__init(_self_0, ...)
+      return _self_0
+    end
+  })
+  _base_0.__class = _class_0
+  if _parent_0.__inherited then
+    _parent_0.__inherited(_parent_0, _class_0)
+  end
+  EncodeOptionsPage = _class_0
+end
+local PreviewPage
+do
+  local _class_0
+  local _parent_0 = Page
+  local _base_0 = {
+    prepare = function(self)
+      local vf = mp.get_property_native("vf")
+      vf[#vf + 1] = {
+        name = "sub"
+      }
+      if self.region:is_valid() then
+        vf[#vf + 1] = {
+          name = "crop",
+          params = {
+            w = tostring(self.region.w),
+            h = tostring(self.region.h),
+            x = tostring(self.region.x),
+            y = tostring(self.region.y)
+          }
+        }
+      end
+      mp.set_property_native("vf", vf)
+      if self.startTime > -1 and self.endTime > -1 then
+        mp.set_property_native("ab-loop-a", self.startTime)
+        mp.set_property_native("ab-loop-b", self.endTime)
+        mp.set_property_native("time-pos", self.startTime)
+      end
+      return mp.set_property_native("pause", false)
+    end,
+    dispose = function(self)
+      mp.set_property("ab-loop-a", "no")
+      mp.set_property("ab-loop-b", "no")
+      for prop, value in pairs(self.originalProperties) do
+        mp.set_property_native(prop, value)
+      end
+    end,
+    draw = function(self)
+      local window_w, window_h = mp.get_osd_size()
+      local ass = assdraw.ass_new()
+      ass:new_event()
+      self:setup_text(ass)
+      ass:append("Press " .. tostring(bold('ESC')) .. " to exit preview.\\N")
+      return mp.set_osd_ass(window_w, window_h, ass.text)
+    end,
+    cancel = function(self)
+      self:hide()
+      return self.callback()
+    end
+  }
+  _base_0.__index = _base_0
+  setmetatable(_base_0, _parent_0.__base)
+  _class_0 = setmetatable({
+    __init = function(self, callback, region, startTime, endTime)
+      self.callback = callback
+      self.originalProperties = {
+        ["vf"] = mp.get_property_native("vf"),
+        ["time-pos"] = mp.get_property_native("time-pos"),
+        ["pause"] = mp.get_property_native("pause")
+      }
+      self.keybinds = {
+        ["ESC"] = (function()
+          local _base_1 = self
+          local _fn_0 = _base_1.cancel
+          return function(...)
+            return _fn_0(_base_1, ...)
+          end
+        end)()
+      }
+      self.region = region
+      self.startTime = startTime
+      self.endTime = endTime
+      self.isLoop = false
+    end,
+    __base = _base_0,
+    __name = "PreviewPage",
+    __parent = _parent_0
+  }, {
+    __index = function(cls, name)
+      local val = rawget(_base_0, name)
+      if val == nil then
+        local parent = rawget(cls, "__parent")
+        if parent then
+          return parent[name]
+        end
+      else
+        return val
+      end
+    end,
+    __call = function(cls, ...)
+      local _self_0 = setmetatable({}, _base_0)
+      cls.__init(_self_0, ...)
+      return _self_0
+    end
+  })
+  _base_0.__class = _class_0
+  if _parent_0.__inherited then
+    _parent_0.__inherited(_parent_0, _class_0)
+  end
+  PreviewPage = _class_0
+end
+local MainPage
+do
+  local _class_0
+  local _parent_0 = Page
+  local _base_0 = {
+    setStartTime = function(self)
+      self.startTime = mp.get_property_number("time-pos")
+      if self.visible then
+        self:clear()
+        return self:draw()
+      end
+    end,
+    setEndTime = function(self)
+      self.endTime = mp.get_property_number("time-pos")
+      if self.visible then
+        self:clear()
+        return self:draw()
+      end
+    end,
+    setupStartAndEndTimes = function(self)
+      if mp.get_property_native("duration") then
+        self.startTime = 0
+        self.endTime = mp.get_property_native("duration")
+      else
+        self.startTime = -1
+        self.endTime = -1
+      end
+      if self.visible then
+        self:clear()
+        return self:draw()
+      end
+    end,
+    draw = function(self)
+      local window_w, window_h = mp.get_osd_size()
+      local ass = assdraw.ass_new()
+      ass:new_event()
+      self:setup_text(ass)
+      ass:append(tostring(bold('WebM maker')) .. "\\N\\N")
+      ass:append(tostring(bold('c:')) .. " crop\\N")
+      ass:append(tostring(bold('1:')) .. " set start time (current is " .. tostring(seconds_to_time_string(self.startTime)) .. ")\\N")
+      ass:append(tostring(bold('2:')) .. " set end time (current is " .. tostring(seconds_to_time_string(self.endTime)) .. ")\\N")
+      ass:append(tostring(bold('o:')) .. " change encode options\\N")
+      ass:append(tostring(bold('p:')) .. " preview\\N")
+      ass:append(tostring(bold('e:')) .. " encode\\N\\N")
+      ass:append(tostring(bold('ESC:')) .. " close\\N")
+      return mp.set_osd_ass(window_w, window_h, ass.text)
+    end,
+    show = function(self)
+      _class_0.__parent.show(self)
+      return emit_event("show-main-page")
+    end,
+    onUpdateCropRegion = function(self, updated, newRegion)
+      if updated then
+        self.region = newRegion
+      end
+      return self:show()
+    end,
+    crop = function(self)
+      self:hide()
+      local cropPage = CropPage((function()
+        local _base_1 = self
+        local _fn_0 = _base_1.onUpdateCropRegion
+        return function(...)
+          return _fn_0(_base_1, ...)
+        end
+      end)(), self.region)
+      return cropPage:show()
+    end,
+    onOptionsChanged = function(self, updated)
+      return self:show()
+    end,
+    changeOptions = function(self)
+      self:hide()
+      local encodeOptsPage = EncodeOptionsPage((function()
+        local _base_1 = self
+        local _fn_0 = _base_1.onOptionsChanged
+        return function(...)
+          return _fn_0(_base_1, ...)
+        end
+      end)())
+      return encodeOptsPage:show()
+    end,
+    onPreviewEnded = function(self)
+      return self:show()
+    end,
+    preview = function(self)
+      self:hide()
+      local previewPage = PreviewPage((function()
+        local _base_1 = self
+        local _fn_0 = _base_1.onPreviewEnded
+        return function(...)
+          return _fn_0(_base_1, ...)
+        end
+      end)(), self.region, self.startTime, self.endTime)
+      return previewPage:show()
+    end,
+    encode = function(self)
+      self:hide()
+      if self.startTime < 0 then
+        message("No start time, aborting")
+        return 
+      end
+      if self.endTime < 0 then
+        message("No end time, aborting")
+        return 
+      end
+      if self.startTime >= self.endTime then
+        message("Start time is ahead of end time, aborting")
+        return 
+      end
+      return encode(self.region, self.startTime, self.endTime)
+    end
+  }
+  _base_0.__index = _base_0
+  setmetatable(_base_0, _parent_0.__base)
+  _class_0 = setmetatable({
+    __init = function(self)
+      self.keybinds = {
+        ["c"] = (function()
+          local _base_1 = self
+          local _fn_0 = _base_1.crop
+          return function(...)
+            return _fn_0(_base_1, ...)
+          end
+        end)(),
+        ["1"] = (function()
+          local _base_1 = self
+          local _fn_0 = _base_1.setStartTime
+          return function(...)
+            return _fn_0(_base_1, ...)
+          end
+        end)(),
+        ["2"] = (function()
+          local _base_1 = self
+          local _fn_0 = _base_1.setEndTime
+          return function(...)
+            return _fn_0(_base_1, ...)
+          end
+        end)(),
+        ["o"] = (function()
+          local _base_1 = self
+          local _fn_0 = _base_1.changeOptions
+          return function(...)
+            return _fn_0(_base_1, ...)
+          end
+        end)(),
+        ["p"] = (function()
+          local _base_1 = self
+          local _fn_0 = _base_1.preview
+          return function(...)
+            return _fn_0(_base_1, ...)
+          end
+        end)(),
+        ["e"] = (function()
+          local _base_1 = self
+          local _fn_0 = _base_1.encode
+          return function(...)
+            return _fn_0(_base_1, ...)
+          end
+        end)(),
+        ["ESC"] = (function()
+          local _base_1 = self
+          local _fn_0 = _base_1.hide
+          return function(...)
+            return _fn_0(_base_1, ...)
+          end
+        end)()
+      }
+      self.startTime = -1
+      self.endTime = -1
+      self.region = Region()
+    end,
+    __base = _base_0,
+    __name = "MainPage",
+    __parent = _parent_0
+  }, {
+    __index = function(cls, name)
+      local val = rawget(_base_0, name)
+      if val == nil then
+        local parent = rawget(cls, "__parent")
+        if parent then
+          return parent[name]
+        end
+      else
+        return val
+      end
+    end,
+    __call = function(cls, ...)
+      local _self_0 = setmetatable({}, _base_0)
+      cls.__init(_self_0, ...)
+      return _self_0
+    end
+  })
+  _base_0.__class = _class_0
+  if _parent_0.__inherited then
+    _parent_0.__inherited(_parent_0, _class_0)
+  end
+  MainPage = _class_0
+end
+monitor_dimensions()
+local mainPage = MainPage()
+mp.add_key_binding(options.keybind, "display-webm-encoder", (function()
+  local _base_0 = mainPage
+  local _fn_0 = _base_0.show
+  return function(...)
+    return _fn_0(_base_0, ...)
+  end
+end)(), {
+  repeatable = false
+})
+mp.register_event("file-loaded", (function()
+  local _base_0 = mainPage
+  local _fn_0 = _base_0.setupStartAndEndTimes
+  return function(...)
+    return _fn_0(_base_0, ...)
+  end
+end)())
+msg.verbose("Loaded mpv-webm script!")
+return emit_event("script-loaded")