Advertisement
Guest User

webm.lua

a guest
Nov 19th, 2018
1,225
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Lua 58.41 KB | None | 0 0
  1. local mp = require("mp")
  2. local assdraw = require("mp.assdraw")
  3. local msg = require("mp.msg")
  4. local utils = require("mp.utils")
  5. local mpopts = require("mp.options")
  6. local mpopts = require("mp.options")
  7. local options = {
  8.     -- Defaults to shift+w
  9.     keybind = "W",
  10.     -- If empty, saves on the same directory of the playing video.
  11.     -- A starting "~" will be replaced by the home dir.
  12.     output_directory = "",
  13.     run_detached = false,
  14.     -- Template string for the output file
  15.     -- %f - Filename, with extension
  16.     -- %F - Filename, without extension
  17.     -- %T - Media title, if it exists, or filename, with extension (useful for some streams, such as YouTube).
  18.     -- %s, %e - Start and end time, with milliseconds
  19.     -- %S, %E - Start and time, without milliseconds
  20.     -- %M - "-audio", if audio is enabled, empty otherwise
  21.     output_template = "%T-[%s-%e]%M",
  22.     -- Scale video to a certain height, keeping the aspect ratio. -1 disables it.
  23.     scale_height = -1,
  24.     -- Target filesize, in kB. This will be used to calculate the bitrate
  25.     -- used on the encode. If this is set to <= 0, the video bitrate will be set
  26.     -- to 0, which might enable constant quality modes, depending on the
  27.     -- video codec that's used (VP8 and VP9, for example).
  28.     target_filesize = 0,
  29.     -- If true, will use stricter flags to ensure the resulting file doesn't
  30.     -- overshoot the target filesize. Not recommended, as constrained quality
  31.     -- mode should work well, unless you're really having trouble hitting
  32.     -- the target size.
  33.     strict_filesize_constraint = false,
  34.     strict_bitrate_multiplier = 0.95,
  35.     -- In kilobits.
  36.     strict_audio_bitrate = 128,
  37.     -- Sets the output format, from a few predefined ones.
  38.     -- Currently we have webm-vp8 (libvpx/libvorbis), webm-vp9 (libvpx-vp9/libopus)
  39.     -- and raw (rawvideo/pcm_s16le).
  40.     output_format = "webm-vp9",
  41.     twopass = false,
  42.     -- If set, applies the video filters currently used on the playback to the encode.
  43.     apply_current_filters = true,
  44.     -- If set, writes the video's filename to the "Title" field on the metadata.
  45.     write_filename_on_metadata = false,
  46.     -- Set the number of encoding threads, for codecs libvpx and libvpx-vp9
  47.     libvpx_threads = 4,
  48.     additional_flags = "",
  49.     -- Useful for flags that may impact output filesize, such as crf, qmin, qmax etc
  50.     -- Won't be applied when strict_filesize_constraint is on.
  51.     non_strict_additional_flags = "--ovcopts-add=crf=10,quality=best",
  52.     -- Display the encode progress, in %. Requires run_detached to be disabled.
  53.     -- On Windows, it shows a cmd popup. "auto" will display progress on non-Windows platforms.
  54.     display_progress = "auto",
  55.     -- The font size used in the menu. Isn't used for the notifications (started encode, finished encode etc)
  56.     font_size = 28,
  57.     margin = 10,
  58.     message_duration = 5
  59. }
  60.  
  61. mpopts.read_options(options)
  62. local bold
  63. bold = function(text)
  64.   return "{\\b1}" .. tostring(text) .. "{\\b0}"
  65. end
  66. local message
  67. message = function(text, duration)
  68.   local ass = mp.get_property_osd("osd-ass-cc/0")
  69.   ass = ass .. text
  70.   return mp.osd_message(ass, duration or options.message_duration)
  71. end
  72. local append
  73. append = function(a, b)
  74.   for _, val in ipairs(b) do
  75.     a[#a + 1] = val
  76.   end
  77.   return a
  78. end
  79. local seconds_to_time_string
  80. seconds_to_time_string = function(seconds, no_ms, full)
  81.   if seconds < 0 then
  82.     return "unknown"
  83.   end
  84.   local ret = ""
  85.   if not (no_ms) then
  86.     ret = string.format(".%03d", seconds * 1000 % 1000)
  87.   end
  88.   ret = string.format("%02d:%02d%s", math.floor(seconds / 60) % 60, math.floor(seconds) % 60, ret)
  89.   if full or seconds > 3600 then
  90.     ret = string.format("%d:%s", math.floor(seconds / 3600), ret)
  91.   end
  92.   return ret
  93. end
  94. local seconds_to_path_element
  95. seconds_to_path_element = function(seconds, no_ms, full)
  96.   local time_string = seconds_to_time_string(seconds, no_ms, full)
  97.   local _
  98.   time_string, _ = time_string:gsub(":", ".")
  99.   return time_string
  100. end
  101. local file_exists
  102. file_exists = function(name)
  103.   local f = io.open(name, "r")
  104.   if f ~= nil then
  105.     io.close(f)
  106.     return true
  107.   end
  108.   return false
  109. end
  110. local format_filename
  111. format_filename = function(startTime, endTime, videoFormat)
  112.   local replaceTable = {
  113.     ["%%f"] = mp.get_property("filename"),
  114.     ["%%F"] = mp.get_property("filename/no-ext"),
  115.     ["%%s"] = seconds_to_path_element(startTime),
  116.     ["%%S"] = seconds_to_path_element(startTime, true),
  117.     ["%%e"] = seconds_to_path_element(endTime),
  118.     ["%%E"] = seconds_to_path_element(endTime, true),
  119.     ["%%T"] = mp.get_property("media-title"),
  120.     ["%%M"] = (mp.get_property_native('aid') and not mp.get_property_native('mute')) and '-audio' or ''
  121.   }
  122.   local filename = options.output_template
  123.   for format, value in pairs(replaceTable) do
  124.     local _
  125.     filename, _ = filename:gsub(format, value)
  126.   end
  127.   local _
  128.   filename, _ = filename:gsub("[<>:\"/\\|?*]", "")
  129.   return tostring(filename) .. "." .. tostring(videoFormat.outputExtension)
  130. end
  131. local parse_directory
  132. parse_directory = function(dir)
  133.   local home_dir = os.getenv("HOME")
  134.   if not home_dir then
  135.     home_dir = os.getenv("USERPROFILE")
  136.   end
  137.   if not home_dir then
  138.     local drive = os.getenv("HOMEDRIVE")
  139.     local path = os.getenv("HOMEPATH")
  140.     if drive and path then
  141.       home_dir = utils.join_path(drive, path)
  142.     else
  143.       msg.warn("Couldn't find home dir.")
  144.       home_dir = ""
  145.     end
  146.   end
  147.   local _
  148.   dir, _ = dir:gsub("^~", home_dir)
  149.   return dir
  150. end
  151. local is_windows = type(package) == "table" and type(package.config) == "string" and package.config:sub(1, 1) == "\\"
  152. local trim
  153. trim = function(s)
  154.   return s:match("^%s*(.-)%s*$")
  155. end
  156. local get_mpv_path
  157. get_mpv_path = function()
  158.   if not is_windows then
  159.     return "mpv"
  160.   end
  161.   local pid = utils.getpid()
  162.   local res = utils.subprocess({
  163.     args = {
  164.       "wmic",
  165.       "process",
  166.       "where",
  167.       "processid=" .. tostring(pid),
  168.       "get",
  169.       "ExecutablePath",
  170.       "/VALUE"
  171.     }
  172.   })
  173.   local key_value = trim(res.stdout)
  174.   return key_value:sub(string.len("ExecutablePath=") + 1)
  175. end
  176. local get_null_path
  177. get_null_path = function()
  178.   if file_exists("/dev/null") then
  179.     return "/dev/null"
  180.   end
  181.   return "NUL"
  182. end
  183. local run_subprocess
  184. run_subprocess = function(params)
  185.   local res = utils.subprocess(params)
  186.   if res.status ~= 0 then
  187.     msg.verbose("Command failed! Reason: ", res.error, " Killed by us? ", res.killed_by_us and "yes" or "no")
  188.     msg.verbose("Command stdout: ")
  189.     msg.verbose(res.stdout)
  190.     return false
  191.   end
  192.   return true
  193. end
  194. local shell_escape
  195. shell_escape = function(args)
  196.   local ret = { }
  197.   for i, a in ipairs(args) do
  198.     local s = tostring(a)
  199.     if string.match(s, "[^A-Za-z0-9_/:=-]") then
  200.       if is_windows then
  201.         s = '"' .. string.gsub(s, '"', '"\\""') .. '"'
  202.       else
  203.         s = "'" .. string.gsub(s, "'", "'\\''") .. "'"
  204.       end
  205.     end
  206.     table.insert(ret, s)
  207.   end
  208.   local concat = table.concat(ret, " ")
  209.   if is_windows then
  210.     concat = '"' .. concat .. '"'
  211.   end
  212.   return concat
  213. end
  214. local run_subprocess_popen
  215. run_subprocess_popen = function(command_line)
  216.   local command_line_string = shell_escape(command_line)
  217.   command_line_string = command_line_string .. " 2>&1"
  218.   msg.verbose("run_subprocess_popen: running " .. tostring(command_line_string))
  219.   return io.popen(command_line_string)
  220. end
  221. local calculate_scale_factor
  222. calculate_scale_factor = function()
  223.   local baseResY = 720
  224.   local osd_w, osd_h = mp.get_osd_size()
  225.   return osd_h / baseResY
  226. end
  227. local should_display_progress
  228. should_display_progress = function()
  229.   if options.display_progress == "auto" then
  230.     return not is_windows
  231.   end
  232.   return options.display_progress
  233. end
  234. local dimensions_changed = true
  235. local _video_dimensions = { }
  236. local get_video_dimensions
  237. get_video_dimensions = function()
  238.   if not (dimensions_changed) then
  239.     return _video_dimensions
  240.   end
  241.   local video_params = mp.get_property_native("video-out-params")
  242.   if not video_params then
  243.     return nil
  244.   end
  245.   dimensions_changed = false
  246.   local keep_aspect = mp.get_property_bool("keepaspect")
  247.   local w = video_params["w"]
  248.   local h = video_params["h"]
  249.   local dw = video_params["dw"]
  250.   local dh = video_params["dh"]
  251.   if mp.get_property_number("video-rotate") % 180 == 90 then
  252.     w, h = h, w
  253.     dw, dh = dh, dw
  254.   end
  255.   _video_dimensions = {
  256.     top_left = { },
  257.     bottom_right = { },
  258.     ratios = { }
  259.   }
  260.   local window_w, window_h = mp.get_osd_size()
  261.   if keep_aspect then
  262.     local unscaled = mp.get_property_native("video-unscaled")
  263.     local panscan = mp.get_property_number("panscan")
  264.     local fwidth = window_w
  265.     local fheight = math.floor(window_w / dw * dh)
  266.     if fheight > window_h or fheight < h then
  267.       local tmpw = math.floor(window_h / dh * dw)
  268.       if tmpw <= window_w then
  269.         fheight = window_h
  270.         fwidth = tmpw
  271.       end
  272.     end
  273.     local vo_panscan_area = window_h - fheight
  274.     local f_w = fwidth / fheight
  275.     local f_h = 1
  276.     if vo_panscan_area == 0 then
  277.       vo_panscan_area = window_h - fwidth
  278.       f_w = 1
  279.       f_h = fheight / fwidth
  280.     end
  281.     if unscaled or unscaled == "downscale-big" then
  282.       vo_panscan_area = 0
  283.       if unscaled or (dw <= window_w and dh <= window_h) then
  284.         fwidth = dw
  285.         fheight = dh
  286.       end
  287.     end
  288.     local scaled_width = fwidth + math.floor(vo_panscan_area * panscan * f_w)
  289.     local scaled_height = fheight + math.floor(vo_panscan_area * panscan * f_h)
  290.     local split_scaling
  291.     split_scaling = function(dst_size, scaled_src_size, zoom, align, pan)
  292.       scaled_src_size = math.floor(scaled_src_size * 2 ^ zoom)
  293.       align = (align + 1) / 2
  294.       local dst_start = math.floor((dst_size - scaled_src_size) * align + pan * scaled_src_size)
  295.       if dst_start < 0 then
  296.         dst_start = dst_start + 1
  297.       end
  298.       local dst_end = dst_start + scaled_src_size
  299.       if dst_start >= dst_end then
  300.         dst_start = 0
  301.         dst_end = 1
  302.       end
  303.       return dst_start, dst_end
  304.     end
  305.     local zoom = mp.get_property_number("video-zoom")
  306.     local align_x = mp.get_property_number("video-align-x")
  307.     local pan_x = mp.get_property_number("video-pan-x")
  308.     _video_dimensions.top_left.x, _video_dimensions.bottom_right.x = split_scaling(window_w, scaled_width, zoom, align_x, pan_x)
  309.     local align_y = mp.get_property_number("video-align-y")
  310.     local pan_y = mp.get_property_number("video-pan-y")
  311.     _video_dimensions.top_left.y, _video_dimensions.bottom_right.y = split_scaling(window_h, scaled_height, zoom, align_y, pan_y)
  312.   else
  313.     _video_dimensions.top_left.x = 0
  314.     _video_dimensions.bottom_right.x = window_w
  315.     _video_dimensions.top_left.y = 0
  316.     _video_dimensions.bottom_right.y = window_h
  317.   end
  318.   _video_dimensions.ratios.w = w / (_video_dimensions.bottom_right.x - _video_dimensions.top_left.x)
  319.   _video_dimensions.ratios.h = h / (_video_dimensions.bottom_right.y - _video_dimensions.top_left.y)
  320.   return _video_dimensions
  321. end
  322. local set_dimensions_changed
  323. set_dimensions_changed = function()
  324.   dimensions_changed = true
  325. end
  326. local monitor_dimensions
  327. monitor_dimensions = function()
  328.   local properties = {
  329.     "keepaspect",
  330.     "video-out-params",
  331.     "video-unscaled",
  332.     "panscan",
  333.     "video-zoom",
  334.     "video-align-x",
  335.     "video-pan-x",
  336.     "video-align-y",
  337.     "video-pan-y",
  338.     "osd-width",
  339.     "osd-height"
  340.   }
  341.   for _, p in ipairs(properties) do
  342.     mp.observe_property(p, "native", set_dimensions_changed)
  343.   end
  344. end
  345. local clamp
  346. clamp = function(min, val, max)
  347.   if val <= min then
  348.     return min
  349.   end
  350.   if val >= max then
  351.     return max
  352.   end
  353.   return val
  354. end
  355. local clamp_point
  356. clamp_point = function(top_left, point, bottom_right)
  357.   return {
  358.     x = clamp(top_left.x, point.x, bottom_right.x),
  359.     y = clamp(top_left.y, point.y, bottom_right.y)
  360.   }
  361. end
  362. local VideoPoint
  363. do
  364.   local _class_0
  365.   local _base_0 = {
  366.     set_from_screen = function(self, sx, sy)
  367.       local d = get_video_dimensions()
  368.       local point = clamp_point(d.top_left, {
  369.         x = sx,
  370.         y = sy
  371.       }, d.bottom_right)
  372.       self.x = math.floor(d.ratios.w * (point.x - d.top_left.x) + 0.5)
  373.       self.y = math.floor(d.ratios.h * (point.y - d.top_left.y) + 0.5)
  374.     end,
  375.     to_screen = function(self)
  376.       local d = get_video_dimensions()
  377.       return {
  378.         x = math.floor(self.x / d.ratios.w + d.top_left.x + 0.5),
  379.         y = math.floor(self.y / d.ratios.h + d.top_left.y + 0.5)
  380.       }
  381.     end
  382.   }
  383.   _base_0.__index = _base_0
  384.   _class_0 = setmetatable({
  385.     __init = function(self)
  386.       self.x = -1
  387.       self.y = -1
  388.     end,
  389.     __base = _base_0,
  390.     __name = "VideoPoint"
  391.   }, {
  392.     __index = _base_0,
  393.     __call = function(cls, ...)
  394.       local _self_0 = setmetatable({}, _base_0)
  395.       cls.__init(_self_0, ...)
  396.       return _self_0
  397.     end
  398.   })
  399.   _base_0.__class = _class_0
  400.   VideoPoint = _class_0
  401. end
  402. local Region
  403. do
  404.   local _class_0
  405.   local _base_0 = {
  406.     is_valid = function(self)
  407.       return self.x > -1 and self.y > -1 and self.w > -1 and self.h > -1
  408.     end,
  409.     set_from_points = function(self, p1, p2)
  410.       self.x = math.min(p1.x, p2.x)
  411.       self.y = math.min(p1.y, p2.y)
  412.       self.w = math.abs(p1.x - p2.x)
  413.       self.h = math.abs(p1.y - p2.y)
  414.     end
  415.   }
  416.   _base_0.__index = _base_0
  417.   _class_0 = setmetatable({
  418.     __init = function(self)
  419.       self.x = -1
  420.       self.y = -1
  421.       self.w = -1
  422.       self.h = -1
  423.     end,
  424.     __base = _base_0,
  425.     __name = "Region"
  426.   }, {
  427.     __index = _base_0,
  428.     __call = function(cls, ...)
  429.       local _self_0 = setmetatable({}, _base_0)
  430.       cls.__init(_self_0, ...)
  431.       return _self_0
  432.     end
  433.   })
  434.   _base_0.__class = _class_0
  435.   Region = _class_0
  436. end
  437. local make_fullscreen_region
  438. make_fullscreen_region = function()
  439.   local r = Region()
  440.   local d = get_video_dimensions()
  441.   local a = VideoPoint()
  442.   local b = VideoPoint()
  443.   local xa, ya
  444.   do
  445.     local _obj_0 = d.top_left
  446.     xa, ya = _obj_0.x, _obj_0.y
  447.   end
  448.   a:set_from_screen(xa, ya)
  449.   local xb, yb
  450.   do
  451.     local _obj_0 = d.bottom_right
  452.     xb, yb = _obj_0.x, _obj_0.y
  453.   end
  454.   b:set_from_screen(xb, yb)
  455.   r:set_from_points(a, b)
  456.   return r
  457. end
  458. local formats = { }
  459. local Format
  460. do
  461.   local _class_0
  462.   local _base_0 = {
  463.     getPreFilters = function(self)
  464.       return { }
  465.     end,
  466.     getPostFilters = function(self)
  467.       return { }
  468.     end,
  469.     getFlags = function(self)
  470.       return { }
  471.     end
  472.   }
  473.   _base_0.__index = _base_0
  474.   _class_0 = setmetatable({
  475.     __init = function(self)
  476.       self.displayName = "Basic"
  477.       self.supportsTwopass = true
  478.       self.videoCodec = ""
  479.       self.audioCodec = ""
  480.       self.outputExtension = ""
  481.       self.acceptsBitrate = true
  482.     end,
  483.     __base = _base_0,
  484.     __name = "Format"
  485.   }, {
  486.     __index = _base_0,
  487.     __call = function(cls, ...)
  488.       local _self_0 = setmetatable({}, _base_0)
  489.       cls.__init(_self_0, ...)
  490.       return _self_0
  491.     end
  492.   })
  493.   _base_0.__class = _class_0
  494.   Format = _class_0
  495. end
  496. local RawVideo
  497. do
  498.   local _class_0
  499.   local _parent_0 = Format
  500.   local _base_0 = {
  501.     getColorspace = function(self)
  502.       local csp = mp.get_property("colormatrix")
  503.       local _exp_0 = csp
  504.       if "bt.601" == _exp_0 then
  505.         return "bt601"
  506.       elseif "bt.709" == _exp_0 then
  507.         return "bt709"
  508.       elseif "bt.2020" == _exp_0 then
  509.         return "bt2020"
  510.       elseif "smpte-240m" == _exp_0 then
  511.         return "smpte240m"
  512.       else
  513.         msg.info("Warning, unknown colorspace " .. tostring(csp) .. " detected, using bt.601.")
  514.         return "bt601"
  515.       end
  516.     end,
  517.     getPostFilters = function(self)
  518.       return {
  519.         "format=yuv444p16",
  520.         "lavfi-scale=in_color_matrix=" .. self:getColorspace(),
  521.         "format=bgr24"
  522.       }
  523.     end
  524.   }
  525.   _base_0.__index = _base_0
  526.   setmetatable(_base_0, _parent_0.__base)
  527.   _class_0 = setmetatable({
  528.     __init = function(self)
  529.       self.displayName = "Raw"
  530.       self.supportsTwopass = false
  531.       self.videoCodec = "rawvideo"
  532.       self.audioCodec = "pcm_s16le"
  533.       self.outputExtension = "avi"
  534.       self.acceptsBitrate = false
  535.     end,
  536.     __base = _base_0,
  537.     __name = "RawVideo",
  538.     __parent = _parent_0
  539.   }, {
  540.     __index = function(cls, name)
  541.       local val = rawget(_base_0, name)
  542.       if val == nil then
  543.         local parent = rawget(cls, "__parent")
  544.         if parent then
  545.           return parent[name]
  546.         end
  547.       else
  548.         return val
  549.       end
  550.     end,
  551.     __call = function(cls, ...)
  552.       local _self_0 = setmetatable({}, _base_0)
  553.       cls.__init(_self_0, ...)
  554.       return _self_0
  555.     end
  556.   })
  557.   _base_0.__class = _class_0
  558.   if _parent_0.__inherited then
  559.     _parent_0.__inherited(_parent_0, _class_0)
  560.   end
  561.   RawVideo = _class_0
  562. end
  563. formats["raw"] = RawVideo()
  564. local WebmVP8
  565. do
  566.   local _class_0
  567.   local _parent_0 = Format
  568.   local _base_0 = {
  569.     getPreFilters = function(self)
  570.       local colormatrixFilter = {
  571.         ["bt.709"] = "bt709",
  572.         ["bt.2020"] = "bt2020",
  573.         ["smpte-240m"] = "smpte240m"
  574.       }
  575.       local ret = { }
  576.       local colormatrix = mp.get_property_native("video-params/colormatrix")
  577.       if colormatrixFilter[colormatrix] then
  578.         append(ret, {
  579.           "lavfi-colormatrix=" .. tostring(colormatrixFilter[colormatrix]) .. ":bt601"
  580.         })
  581.       end
  582.       return ret
  583.     end,
  584.     getFlags = function(self)
  585.       return {
  586.         "--ovcopts-add=threads=" .. tostring(options.libvpx_threads)
  587.       }
  588.     end
  589.   }
  590.   _base_0.__index = _base_0
  591.   setmetatable(_base_0, _parent_0.__base)
  592.   _class_0 = setmetatable({
  593.     __init = function(self)
  594.       self.displayName = "WebM"
  595.       self.supportsTwopass = true
  596.       self.videoCodec = "libvpx"
  597.       self.audioCodec = "libvorbis"
  598.       self.outputExtension = "webm"
  599.       self.acceptsBitrate = true
  600.     end,
  601.     __base = _base_0,
  602.     __name = "WebmVP8",
  603.     __parent = _parent_0
  604.   }, {
  605.     __index = function(cls, name)
  606.       local val = rawget(_base_0, name)
  607.       if val == nil then
  608.         local parent = rawget(cls, "__parent")
  609.         if parent then
  610.           return parent[name]
  611.         end
  612.       else
  613.         return val
  614.       end
  615.     end,
  616.     __call = function(cls, ...)
  617.       local _self_0 = setmetatable({}, _base_0)
  618.       cls.__init(_self_0, ...)
  619.       return _self_0
  620.     end
  621.   })
  622.   _base_0.__class = _class_0
  623.   if _parent_0.__inherited then
  624.     _parent_0.__inherited(_parent_0, _class_0)
  625.   end
  626.   WebmVP8 = _class_0
  627. end
  628. formats["webm-vp8"] = WebmVP8()
  629. local WebmVP9
  630. do
  631.   local _class_0
  632.   local _parent_0 = Format
  633.   local _base_0 = {
  634.     getFlags = function(self)
  635.       return {
  636.         "--ovcopts-add=threads=" .. tostring(options.libvpx_threads)
  637.       }
  638.     end
  639.   }
  640.   _base_0.__index = _base_0
  641.   setmetatable(_base_0, _parent_0.__base)
  642.   _class_0 = setmetatable({
  643.     __init = function(self)
  644.       self.displayName = "WebM (VP9)"
  645.       self.supportsTwopass = true
  646.       self.videoCodec = "libvpx-vp9"
  647.       self.audioCodec = "libopus"
  648.       self.outputExtension = "webm"
  649.       self.acceptsBitrate = true
  650.     end,
  651.     __base = _base_0,
  652.     __name = "WebmVP9",
  653.     __parent = _parent_0
  654.   }, {
  655.     __index = function(cls, name)
  656.       local val = rawget(_base_0, name)
  657.       if val == nil then
  658.         local parent = rawget(cls, "__parent")
  659.         if parent then
  660.           return parent[name]
  661.         end
  662.       else
  663.         return val
  664.       end
  665.     end,
  666.     __call = function(cls, ...)
  667.       local _self_0 = setmetatable({}, _base_0)
  668.       cls.__init(_self_0, ...)
  669.       return _self_0
  670.     end
  671.   })
  672.   _base_0.__class = _class_0
  673.   if _parent_0.__inherited then
  674.     _parent_0.__inherited(_parent_0, _class_0)
  675.   end
  676.   WebmVP9 = _class_0
  677. end
  678. formats["webm-vp9"] = WebmVP9()
  679. local MP4
  680. do
  681.   local _class_0
  682.   local _parent_0 = Format
  683.   local _base_0 = { }
  684.   _base_0.__index = _base_0
  685.   setmetatable(_base_0, _parent_0.__base)
  686.   _class_0 = setmetatable({
  687.     __init = function(self)
  688.       self.displayName = "MP4 (h264/AAC)"
  689.       self.supportsTwopass = true
  690.       self.videoCodec = "libx264"
  691.       self.audioCodec = "aac"
  692.       self.outputExtension = "mp4"
  693.       self.acceptsBitrate = true
  694.     end,
  695.     __base = _base_0,
  696.     __name = "MP4",
  697.     __parent = _parent_0
  698.   }, {
  699.     __index = function(cls, name)
  700.       local val = rawget(_base_0, name)
  701.       if val == nil then
  702.         local parent = rawget(cls, "__parent")
  703.         if parent then
  704.           return parent[name]
  705.         end
  706.       else
  707.         return val
  708.       end
  709.     end,
  710.     __call = function(cls, ...)
  711.       local _self_0 = setmetatable({}, _base_0)
  712.       cls.__init(_self_0, ...)
  713.       return _self_0
  714.     end
  715.   })
  716.   _base_0.__class = _class_0
  717.   if _parent_0.__inherited then
  718.     _parent_0.__inherited(_parent_0, _class_0)
  719.   end
  720.   MP4 = _class_0
  721. end
  722. formats["mp4"] = MP4()
  723. local Page
  724. do
  725.   local _class_0
  726.   local _base_0 = {
  727.     add_keybinds = function(self)
  728.       if not self.keybinds then
  729.         return
  730.       end
  731.       for key, func in pairs(self.keybinds) do
  732.         mp.add_forced_key_binding(key, key, func, {
  733.           repeatable = true
  734.         })
  735.       end
  736.     end,
  737.     remove_keybinds = function(self)
  738.       if not self.keybinds then
  739.         return
  740.       end
  741.       for key, _ in pairs(self.keybinds) do
  742.         mp.remove_key_binding(key)
  743.       end
  744.     end,
  745.     observe_properties = function(self)
  746.       self.sizeCallback = function()
  747.         return self:draw()
  748.       end
  749.       local properties = {
  750.         "keepaspect",
  751.         "video-out-params",
  752.         "video-unscaled",
  753.         "panscan",
  754.         "video-zoom",
  755.         "video-align-x",
  756.         "video-pan-x",
  757.         "video-align-y",
  758.         "video-pan-y",
  759.         "osd-width",
  760.         "osd-height"
  761.       }
  762.       for _index_0 = 1, #properties do
  763.         local p = properties[_index_0]
  764.         mp.observe_property(p, "native", self.sizeCallback)
  765.       end
  766.     end,
  767.     unobserve_properties = function(self)
  768.       if self.sizeCallback then
  769.         mp.unobserve_property(self.sizeCallback)
  770.         self.sizeCallback = nil
  771.       end
  772.     end,
  773.     clear = function(self)
  774.       local window_w, window_h = mp.get_osd_size()
  775.       mp.set_osd_ass(window_w, window_h, "")
  776.       return mp.osd_message("", 0)
  777.     end,
  778.     prepare = function(self)
  779.       return nil
  780.     end,
  781.     dispose = function(self)
  782.       return nil
  783.     end,
  784.     show = function(self)
  785.       self.visible = true
  786.       self:observe_properties()
  787.       self:add_keybinds()
  788.       self:prepare()
  789.       self:clear()
  790.       return self:draw()
  791.     end,
  792.     hide = function(self)
  793.       self.visible = false
  794.       self:unobserve_properties()
  795.       self:remove_keybinds()
  796.       self:clear()
  797.       return self:dispose()
  798.     end,
  799.     setup_text = function(self, ass)
  800.       local scale = calculate_scale_factor()
  801.       local margin = options.margin * scale
  802.       ass:pos(margin, margin)
  803.       return ass:append("{\\fs" .. tostring(options.font_size * scale) .. "}")
  804.     end
  805.   }
  806.   _base_0.__index = _base_0
  807.   _class_0 = setmetatable({
  808.     __init = function() end,
  809.     __base = _base_0,
  810.     __name = "Page"
  811.   }, {
  812.     __index = _base_0,
  813.     __call = function(cls, ...)
  814.       local _self_0 = setmetatable({}, _base_0)
  815.       cls.__init(_self_0, ...)
  816.       return _self_0
  817.     end
  818.   })
  819.   _base_0.__class = _class_0
  820.   Page = _class_0
  821. end
  822. local EncodeWithProgress
  823. do
  824.   local _class_0
  825.   local _parent_0 = Page
  826.   local _base_0 = {
  827.     draw = function(self)
  828.       local progress = 100 * ((self.currentTime - self.startTime) / self.duration)
  829.       local progressText = string.format("%d%%", progress)
  830.       local window_w, window_h = mp.get_osd_size()
  831.       local ass = assdraw.ass_new()
  832.       ass:new_event()
  833.       self:setup_text(ass)
  834.       ass:append("Encoding (" .. tostring(bold(progressText)) .. ")\\N")
  835.       return mp.set_osd_ass(window_w, window_h, ass.text)
  836.     end,
  837.     parseLine = function(self, line)
  838.       local matchTime = string.match(line, "Encode time[-]pos: ([0-9.]+)")
  839.       local matchExit = string.match(line, "Exiting... [(]([%a ]+)[)]")
  840.       if matchTime == nil and matchExit == nil then
  841.         return
  842.       end
  843.       if matchTime ~= nil and tonumber(matchTime) > self.currentTime then
  844.         self.currentTime = tonumber(matchTime)
  845.       end
  846.       if matchExit ~= nil then
  847.         self.finished = true
  848.         self.finishedReason = matchExit
  849.       end
  850.     end,
  851.     startEncode = function(self, command_line)
  852.       local copy_command_line
  853.       do
  854.         local _accum_0 = { }
  855.         local _len_0 = 1
  856.         for _index_0 = 1, #command_line do
  857.           local arg = command_line[_index_0]
  858.           _accum_0[_len_0] = arg
  859.           _len_0 = _len_0 + 1
  860.         end
  861.         copy_command_line = _accum_0
  862.       end
  863.       append(copy_command_line, {
  864.         '--term-status-msg=Encode time-pos: ${=time-pos}'
  865.       })
  866.       self:show()
  867.       local processFd = run_subprocess_popen(copy_command_line)
  868.       for line in processFd:lines() do
  869.         msg.verbose(string.format('%q', line))
  870.         self:parseLine(line)
  871.         self:draw()
  872.       end
  873.       processFd:close()
  874.       self:hide()
  875.       if self.finishedReason == "End of file" then
  876.         return true
  877.       end
  878.       return false
  879.     end
  880.   }
  881.   _base_0.__index = _base_0
  882.   setmetatable(_base_0, _parent_0.__base)
  883.   _class_0 = setmetatable({
  884.     __init = function(self, startTime, endTime)
  885.       self.startTime = startTime
  886.       self.endTime = endTime
  887.       self.duration = endTime - startTime
  888.       self.currentTime = startTime
  889.     end,
  890.     __base = _base_0,
  891.     __name = "EncodeWithProgress",
  892.     __parent = _parent_0
  893.   }, {
  894.     __index = function(cls, name)
  895.       local val = rawget(_base_0, name)
  896.       if val == nil then
  897.         local parent = rawget(cls, "__parent")
  898.         if parent then
  899.           return parent[name]
  900.         end
  901.       else
  902.         return val
  903.       end
  904.     end,
  905.     __call = function(cls, ...)
  906.       local _self_0 = setmetatable({}, _base_0)
  907.       cls.__init(_self_0, ...)
  908.       return _self_0
  909.     end
  910.   })
  911.   _base_0.__class = _class_0
  912.   if _parent_0.__inherited then
  913.     _parent_0.__inherited(_parent_0, _class_0)
  914.   end
  915.   EncodeWithProgress = _class_0
  916. end
  917. local get_active_tracks
  918. get_active_tracks = function()
  919.   local accepted = {
  920.     video = true,
  921.     audio = not mp.get_property_bool("mute"),
  922.     sub = mp.get_property_bool("sub-visibility")
  923.   }
  924.   local active = { }
  925.   for _, track in ipairs(mp.get_property_native("track-list")) do
  926.     if track["selected"] and accepted[track["type"]] then
  927.       active[#active + 1] = track
  928.     end
  929.   end
  930.   return active
  931. end
  932. local get_color_conversion_filters
  933. get_color_conversion_filters = function()
  934.   local colormatrixFilter = {
  935.     ["bt.709"] = "bt709",
  936.     ["bt.2020"] = "bt2020"
  937.   }
  938.   local ret = { }
  939.   local colormatrix = mp.get_property_native("video-params/colormatrix")
  940.   if options.video_codec == "libvpx" and colormatrixFilter[colormatrix] then
  941.     append(ret, {
  942.       "colormatrix=" .. tostring(colormatrixFilter[colormatrix]) .. ":bt601"
  943.     })
  944.   end
  945.   return ret
  946. end
  947. local get_scale_filters
  948. get_scale_filters = function()
  949.   if options.scale_height > 0 then
  950.     return {
  951.       "lavfi-scale=-2:" .. tostring(options.scale_height)
  952.     }
  953.   end
  954.   return { }
  955. end
  956. local append_property
  957. append_property = function(out, property_name, option_name)
  958.   option_name = option_name or property_name
  959.   local prop = mp.get_property(property_name)
  960.   if prop and prop ~= "" then
  961.     return append(out, {
  962.       "--" .. tostring(option_name) .. "=" .. tostring(prop)
  963.     })
  964.   end
  965. end
  966. local append_list_options
  967. append_list_options = function(out, property_name, option_prefix)
  968.   option_prefix = option_prefix or property_name
  969.   local prop = mp.get_property_native(property_name)
  970.   if prop then
  971.     for _index_0 = 1, #prop do
  972.       local value = prop[_index_0]
  973.       append(out, {
  974.         "--" .. tostring(option_prefix) .. "-append=" .. tostring(value)
  975.       })
  976.     end
  977.   end
  978. end
  979. local get_playback_options
  980. get_playback_options = function()
  981.   local ret = { }
  982.   append_property(ret, "sub-ass-override")
  983.   append_property(ret, "sub-ass-force-style")
  984.   append_property(ret, "sub-auto")
  985.   append_property(ret, "video-rotate")
  986.   for _, track in ipairs(mp.get_property_native("track-list")) do
  987.     if track["type"] == "sub" and track["external"] then
  988.       append(ret, {
  989.         "--sub-files-append=" .. tostring(track['external-filename'])
  990.       })
  991.     end
  992.   end
  993.   return ret
  994. end
  995. local get_metadata_flags
  996. get_metadata_flags = function()
  997.   local title = mp.get_property("filename/no-ext")
  998.   return {
  999.     "--oset-metadata=title=%" .. tostring(string.len(title)) .. "%" .. tostring(title)
  1000.   }
  1001. end
  1002. local apply_current_filters
  1003. apply_current_filters = function(filters)
  1004.   local vf = mp.get_property_native("vf")
  1005.   msg.verbose("apply_current_filters: got " .. tostring(#vf) .. " currently applied.")
  1006.   for _index_0 = 1, #vf do
  1007.     local _continue_0 = false
  1008.     repeat
  1009.       local filter = vf[_index_0]
  1010.       msg.verbose("apply_current_filters: filter name: " .. tostring(filter['name']))
  1011.       if filter["enabled"] == false then
  1012.         _continue_0 = true
  1013.         break
  1014.       end
  1015.       local str = filter["name"]
  1016.       local params = filter["params"] or { }
  1017.       for k, v in pairs(params) do
  1018.         str = str .. ":" .. tostring(k) .. "=%" .. tostring(string.len(v)) .. "%" .. tostring(v)
  1019.       end
  1020.       append(filters, {
  1021.         str
  1022.       })
  1023.       _continue_0 = true
  1024.     until true
  1025.     if not _continue_0 then
  1026.       break
  1027.     end
  1028.   end
  1029. end
  1030. local encode
  1031. encode = function(region, startTime, endTime)
  1032.   local format = formats[options.output_format]
  1033.   local path = mp.get_property("path")
  1034.   if not path then
  1035.     message("No file is being played")
  1036.     return
  1037.   end
  1038.   local is_stream = not file_exists(path)
  1039.   local command = {
  1040.     get_mpv_path(),
  1041.     path,
  1042.     "--start=" .. seconds_to_time_string(startTime, false, true),
  1043.     "--end=" .. seconds_to_time_string(endTime, false, true),
  1044.     "--ovc=" .. tostring(format.videoCodec),
  1045.     "--oac=" .. tostring(format.audioCodec),
  1046.     "--loop-file=no"
  1047.   }
  1048.   local vid = -1
  1049.   local aid = -1
  1050.   local sid = -1
  1051.   for _, track in ipairs(get_active_tracks()) do
  1052.     local _exp_0 = track["type"]
  1053.     if "video" == _exp_0 then
  1054.       vid = track['id']
  1055.     elseif "audio" == _exp_0 then
  1056.       aid = track['id']
  1057.     elseif "sub" == _exp_0 then
  1058.       sid = track['id']
  1059.     end
  1060.   end
  1061.   append(command, {
  1062.     "--vid=" .. (vid >= 0 and tostring(vid) or "no"),
  1063.     "--aid=" .. (aid >= 0 and tostring(aid) or "no"),
  1064.     "--sid=" .. (sid >= 0 and tostring(sid) or "no")
  1065.   })
  1066.   append(command, get_playback_options())
  1067.   local filters = { }
  1068.   append(filters, format:getPreFilters())
  1069.   if options.apply_current_filters then
  1070.     apply_current_filters(filters)
  1071.   end
  1072.   if region and region:is_valid() then
  1073.     append(filters, {
  1074.       "lavfi-crop=" .. tostring(region.w) .. ":" .. tostring(region.h) .. ":" .. tostring(region.x) .. ":" .. tostring(region.y)
  1075.     })
  1076.   end
  1077.   append(filters, get_scale_filters())
  1078.   append(filters, format:getPostFilters())
  1079.   for _index_0 = 1, #filters do
  1080.     local f = filters[_index_0]
  1081.     append(command, {
  1082.       "--vf-add=" .. tostring(f)
  1083.     })
  1084.   end
  1085.   append(command, format:getFlags())
  1086.   if options.write_filename_on_metadata then
  1087.     append(command, get_metadata_flags())
  1088.   end
  1089.   if options.target_filesize > 0 and format.acceptsBitrate then
  1090.     local dT = endTime - startTime
  1091.     if options.strict_filesize_constraint then
  1092.       local video_kilobits = options.target_filesize * 8
  1093.       if aid >= 0 then
  1094.         video_kilobits = video_kilobits - dT * options.strict_audio_bitrate
  1095.         append(command, {
  1096.           "--oacopts-add=b=" .. tostring(options.strict_audio_bitrate) .. "k"
  1097.         })
  1098.       end
  1099.       video_kilobits = video_kilobits * options.strict_bitrate_multiplier
  1100.       local bitrate = math.floor(video_kilobits / dT)
  1101.       append(command, {
  1102.         "--ovcopts-add=b=" .. tostring(bitrate) .. "k",
  1103.         "--ovcopts-add=minrate=" .. tostring(bitrate) .. "k",
  1104.         "--ovcopts-add=maxrate=" .. tostring(bitrate) .. "k"
  1105.       })
  1106.     else
  1107.       local bitrate = math.floor(options.target_filesize * 8 / dT)
  1108.       append(command, {
  1109.         "--ovcopts-add=b=" .. tostring(bitrate) .. "k"
  1110.       })
  1111.     end
  1112.   elseif options.target_filesize <= 0 and format.acceptsBitrate then
  1113.     append(command, {
  1114.       "--ovcopts-add=b=0"
  1115.     })
  1116.   end
  1117.   for token in string.gmatch(options.additional_flags, "[^%s]+") do
  1118.     command[#command + 1] = token
  1119.   end
  1120.   if not options.strict_filesize_constraint then
  1121.     for token in string.gmatch(options.non_strict_additional_flags, "[^%s]+") do
  1122.       command[#command + 1] = token
  1123.     end
  1124.   end
  1125.   if options.twopass and format.supportsTwopass and not is_stream then
  1126.     local first_pass_cmdline
  1127.     do
  1128.       local _accum_0 = { }
  1129.       local _len_0 = 1
  1130.       for _index_0 = 1, #command do
  1131.         local arg = command[_index_0]
  1132.         _accum_0[_len_0] = arg
  1133.         _len_0 = _len_0 + 1
  1134.       end
  1135.       first_pass_cmdline = _accum_0
  1136.     end
  1137.     append(first_pass_cmdline, {
  1138.       "--ovcopts-add=flags=+pass1",
  1139.       "-of=" .. tostring(format.outputExtension),
  1140.       "-o=" .. tostring(get_null_path())
  1141.     })
  1142.     message("Starting first pass...")
  1143.     msg.verbose("First-pass command line: ", table.concat(first_pass_cmdline, " "))
  1144.     local res = run_subprocess({
  1145.       args = first_pass_cmdline,
  1146.       cancellable = false
  1147.     })
  1148.     if not res then
  1149.       message("First pass failed! Check the logs for details.")
  1150.       return
  1151.     end
  1152.     append(command, {
  1153.       "--ovcopts-add=flags=+pass2"
  1154.     })
  1155.   end
  1156.   local dir = ""
  1157.   if is_stream then
  1158.     dir = parse_directory("~")
  1159.   else
  1160.     local _
  1161.     dir, _ = utils.split_path(path)
  1162.   end
  1163.   if options.output_directory ~= "" then
  1164.     dir = parse_directory(options.output_directory)
  1165.   end
  1166.   local formatted_filename = format_filename(startTime, endTime, format)
  1167.   local out_path = utils.join_path(dir, formatted_filename)
  1168.   append(command, {
  1169.     "-o=" .. tostring(out_path)
  1170.   })
  1171.   msg.info("Encoding to", out_path)
  1172.   msg.verbose("Command line:", table.concat(command, " "))
  1173.   if options.run_detached then
  1174.     message("Started encode, process was detached.")
  1175.     return utils.subprocess_detached({
  1176.       args = command
  1177.     })
  1178.   else
  1179.     local res = false
  1180.     if not should_display_progress() then
  1181.       message("Started encode...")
  1182.       res = run_subprocess({
  1183.         args = command,
  1184.         cancellable = false
  1185.       })
  1186.     else
  1187.       local ewp = EncodeWithProgress(startTime, endTime)
  1188.       res = ewp:startEncode(command)
  1189.     end
  1190.     if res then
  1191.       return message("Encoded successfully! Saved to\\N" .. tostring(bold(out_path)))
  1192.     else
  1193.       return message("Encode failed! Check the logs for details.")
  1194.     end
  1195.   end
  1196. end
  1197. local CropPage
  1198. do
  1199.   local _class_0
  1200.   local _parent_0 = Page
  1201.   local _base_0 = {
  1202.     reset = function(self)
  1203.       local dimensions = get_video_dimensions()
  1204.       local xa, ya
  1205.       do
  1206.         local _obj_0 = dimensions.top_left
  1207.         xa, ya = _obj_0.x, _obj_0.y
  1208.       end
  1209.       self.pointA:set_from_screen(xa, ya)
  1210.       local xb, yb
  1211.       do
  1212.         local _obj_0 = dimensions.bottom_right
  1213.         xb, yb = _obj_0.x, _obj_0.y
  1214.       end
  1215.       self.pointB:set_from_screen(xb, yb)
  1216.       if self.visible then
  1217.         return self:draw()
  1218.       end
  1219.     end,
  1220.     setPointA = function(self)
  1221.       local posX, posY = mp.get_mouse_pos()
  1222.       self.pointA:set_from_screen(posX, posY)
  1223.       if self.visible then
  1224.         return self:draw()
  1225.       end
  1226.     end,
  1227.     setPointB = function(self)
  1228.       local posX, posY = mp.get_mouse_pos()
  1229.       self.pointB:set_from_screen(posX, posY)
  1230.       if self.visible then
  1231.         return self:draw()
  1232.       end
  1233.     end,
  1234.     cancel = function(self)
  1235.       self:hide()
  1236.       return self.callback(false, nil)
  1237.     end,
  1238.     finish = function(self)
  1239.       local region = Region()
  1240.       region:set_from_points(self.pointA, self.pointB)
  1241.       self:hide()
  1242.       return self.callback(true, region)
  1243.     end,
  1244.     draw_box = function(self, ass)
  1245.       local region = Region()
  1246.       region:set_from_points(self.pointA:to_screen(), self.pointB:to_screen())
  1247.       local d = get_video_dimensions()
  1248.       ass:new_event()
  1249.       ass:pos(0, 0)
  1250.       ass:append('{\\bord0}')
  1251.       ass:append('{\\shad0}')
  1252.       ass:append('{\\c&H000000&}')
  1253.       ass:append('{\\alpha&H77}')
  1254.       ass:draw_start()
  1255.       ass:rect_cw(d.top_left.x, d.top_left.y, region.x, region.y + region.h)
  1256.       ass:rect_cw(region.x, d.top_left.y, d.bottom_right.x, region.y)
  1257.       ass:rect_cw(d.top_left.x, region.y + region.h, region.x + region.w, d.bottom_right.y)
  1258.       ass:rect_cw(region.x + region.w, region.y, d.bottom_right.x, d.bottom_right.y)
  1259.       return ass:draw_stop()
  1260.     end,
  1261.     draw = function(self)
  1262.       local window = { }
  1263.       window.w, window.h = mp.get_osd_size()
  1264.       local ass = assdraw.ass_new()
  1265.       self:draw_box(ass)
  1266.       ass:new_event()
  1267.       self:setup_text(ass)
  1268.       ass:append(tostring(bold('Crop:')) .. "\\N")
  1269.       ass:append(tostring(bold('1:')) .. " change point A (" .. tostring(self.pointA.x) .. ", " .. tostring(self.pointA.y) .. ")\\N")
  1270.       ass:append(tostring(bold('2:')) .. " change point B (" .. tostring(self.pointB.x) .. ", " .. tostring(self.pointB.y) .. ")\\N")
  1271.       ass:append(tostring(bold('r:')) .. " reset to whole screen\\N")
  1272.       ass:append(tostring(bold('ESC:')) .. " cancel crop\\N")
  1273.       ass:append(tostring(bold('ENTER:')) .. " confirm crop\\N")
  1274.       return mp.set_osd_ass(window.w, window.h, ass.text)
  1275.     end
  1276.   }
  1277.   _base_0.__index = _base_0
  1278.   setmetatable(_base_0, _parent_0.__base)
  1279.   _class_0 = setmetatable({
  1280.     __init = function(self, callback, region)
  1281.       self.pointA = VideoPoint()
  1282.       self.pointB = VideoPoint()
  1283.       self.keybinds = {
  1284.         ["1"] = (function()
  1285.           local _base_1 = self
  1286.           local _fn_0 = _base_1.setPointA
  1287.           return function(...)
  1288.             return _fn_0(_base_1, ...)
  1289.           end
  1290.         end)(),
  1291.         ["2"] = (function()
  1292.           local _base_1 = self
  1293.           local _fn_0 = _base_1.setPointB
  1294.           return function(...)
  1295.             return _fn_0(_base_1, ...)
  1296.           end
  1297.         end)(),
  1298.         ["r"] = (function()
  1299.           local _base_1 = self
  1300.           local _fn_0 = _base_1.reset
  1301.           return function(...)
  1302.             return _fn_0(_base_1, ...)
  1303.           end
  1304.         end)(),
  1305.         ["ESC"] = (function()
  1306.           local _base_1 = self
  1307.           local _fn_0 = _base_1.cancel
  1308.           return function(...)
  1309.             return _fn_0(_base_1, ...)
  1310.           end
  1311.         end)(),
  1312.         ["ENTER"] = (function()
  1313.           local _base_1 = self
  1314.           local _fn_0 = _base_1.finish
  1315.           return function(...)
  1316.             return _fn_0(_base_1, ...)
  1317.           end
  1318.         end)()
  1319.       }
  1320.       self:reset()
  1321.       self.callback = callback
  1322.       if region and region:is_valid() then
  1323.         self.pointA.x = region.x
  1324.         self.pointA.y = region.y
  1325.         self.pointB.x = region.x + region.w
  1326.         self.pointB.y = region.y + region.h
  1327.       end
  1328.     end,
  1329.     __base = _base_0,
  1330.     __name = "CropPage",
  1331.     __parent = _parent_0
  1332.   }, {
  1333.     __index = function(cls, name)
  1334.       local val = rawget(_base_0, name)
  1335.       if val == nil then
  1336.         local parent = rawget(cls, "__parent")
  1337.         if parent then
  1338.           return parent[name]
  1339.         end
  1340.       else
  1341.         return val
  1342.       end
  1343.     end,
  1344.     __call = function(cls, ...)
  1345.       local _self_0 = setmetatable({}, _base_0)
  1346.       cls.__init(_self_0, ...)
  1347.       return _self_0
  1348.     end
  1349.   })
  1350.   _base_0.__class = _class_0
  1351.   if _parent_0.__inherited then
  1352.     _parent_0.__inherited(_parent_0, _class_0)
  1353.   end
  1354.   CropPage = _class_0
  1355. end
  1356. local Option
  1357. do
  1358.   local _class_0
  1359.   local _base_0 = {
  1360.     hasPrevious = function(self)
  1361.       local _exp_0 = self.optType
  1362.       if "bool" == _exp_0 then
  1363.         return true
  1364.       elseif "int" == _exp_0 then
  1365.         if self.opts.min then
  1366.           return self.value > self.opts.min
  1367.         else
  1368.           return true
  1369.         end
  1370.       elseif "list" == _exp_0 then
  1371.         return self.value > 1
  1372.       end
  1373.     end,
  1374.     hasNext = function(self)
  1375.       local _exp_0 = self.optType
  1376.       if "bool" == _exp_0 then
  1377.         return true
  1378.       elseif "int" == _exp_0 then
  1379.         if self.opts.max then
  1380.           return self.value < self.opts.max
  1381.         else
  1382.           return true
  1383.         end
  1384.       elseif "list" == _exp_0 then
  1385.         return self.value < #self.opts.possibleValues
  1386.       end
  1387.     end,
  1388.     leftKey = function(self)
  1389.       local _exp_0 = self.optType
  1390.       if "bool" == _exp_0 then
  1391.         self.value = not self.value
  1392.       elseif "int" == _exp_0 then
  1393.         self.value = self.value - self.opts.step
  1394.         if self.opts.min and self.opts.min > self.value then
  1395.           self.value = self.opts.min
  1396.         end
  1397.       elseif "list" == _exp_0 then
  1398.         if self.value > 1 then
  1399.           self.value = self.value - 1
  1400.         end
  1401.       end
  1402.     end,
  1403.     rightKey = function(self)
  1404.       local _exp_0 = self.optType
  1405.       if "bool" == _exp_0 then
  1406.         self.value = not self.value
  1407.       elseif "int" == _exp_0 then
  1408.         self.value = self.value + self.opts.step
  1409.         if self.opts.max and self.opts.max < self.value then
  1410.           self.value = self.opts.max
  1411.         end
  1412.       elseif "list" == _exp_0 then
  1413.         if self.value < #self.opts.possibleValues then
  1414.           self.value = self.value + 1
  1415.         end
  1416.       end
  1417.     end,
  1418.     getValue = function(self)
  1419.       local _exp_0 = self.optType
  1420.       if "bool" == _exp_0 then
  1421.         return self.value
  1422.       elseif "int" == _exp_0 then
  1423.         return self.value
  1424.       elseif "list" == _exp_0 then
  1425.         local value, _
  1426.         do
  1427.           local _obj_0 = self.opts.possibleValues[self.value]
  1428.           value, _ = _obj_0[1], _obj_0[2]
  1429.         end
  1430.         return value
  1431.       end
  1432.     end,
  1433.     setValue = function(self, value)
  1434.       local _exp_0 = self.optType
  1435.       if "bool" == _exp_0 then
  1436.         self.value = value
  1437.       elseif "int" == _exp_0 then
  1438.         self.value = value
  1439.       elseif "list" == _exp_0 then
  1440.         local set = false
  1441.         for i, possiblePair in ipairs(self.opts.possibleValues) do
  1442.           local possibleValue, _
  1443.           possibleValue, _ = possiblePair[1], possiblePair[2]
  1444.           if possibleValue == value then
  1445.             set = true
  1446.             self.value = i
  1447.             break
  1448.           end
  1449.         end
  1450.         if not set then
  1451.           return msg.warn("Tried to set invalid value " .. tostring(value) .. " to " .. tostring(self.displayText) .. " option.")
  1452.         end
  1453.       end
  1454.     end,
  1455.     getDisplayValue = function(self)
  1456.       local _exp_0 = self.optType
  1457.       if "bool" == _exp_0 then
  1458.         return self.value and "yes" or "no"
  1459.       elseif "int" == _exp_0 then
  1460.         if self.opts.altDisplayNames and self.opts.altDisplayNames[self.value] then
  1461.           return self.opts.altDisplayNames[self.value]
  1462.         else
  1463.           return tostring(self.value)
  1464.         end
  1465.       elseif "list" == _exp_0 then
  1466.         local value, displayValue
  1467.         do
  1468.           local _obj_0 = self.opts.possibleValues[self.value]
  1469.           value, displayValue = _obj_0[1], _obj_0[2]
  1470.         end
  1471.         return displayValue or value
  1472.       end
  1473.     end,
  1474.     draw = function(self, ass, selected)
  1475.       if selected then
  1476.         ass:append(tostring(bold(self.displayText)) .. ": ")
  1477.       else
  1478.         ass:append(tostring(self.displayText) .. ": ")
  1479.       end
  1480.       if self:hasPrevious() then
  1481.         ass:append("◀ ")
  1482.       end
  1483.       ass:append(self:getDisplayValue())
  1484.       if self:hasNext() then
  1485.         ass:append(" ▶")
  1486.       end
  1487.       return ass:append("\\N")
  1488.     end
  1489.   }
  1490.   _base_0.__index = _base_0
  1491.   _class_0 = setmetatable({
  1492.     __init = function(self, optType, displayText, value, opts)
  1493.       self.optType = optType
  1494.       self.displayText = displayText
  1495.       self.opts = opts
  1496.       self.value = 1
  1497.       return self:setValue(value)
  1498.     end,
  1499.     __base = _base_0,
  1500.     __name = "Option"
  1501.   }, {
  1502.     __index = _base_0,
  1503.     __call = function(cls, ...)
  1504.       local _self_0 = setmetatable({}, _base_0)
  1505.       cls.__init(_self_0, ...)
  1506.       return _self_0
  1507.     end
  1508.   })
  1509.   _base_0.__class = _class_0
  1510.   Option = _class_0
  1511. end
  1512. local EncodeOptionsPage
  1513. do
  1514.   local _class_0
  1515.   local _parent_0 = Page
  1516.   local _base_0 = {
  1517.     getCurrentOption = function(self)
  1518.       return self.options[self.currentOption][2]
  1519.     end,
  1520.     leftKey = function(self)
  1521.       (self:getCurrentOption()):leftKey()
  1522.       return self:draw()
  1523.     end,
  1524.     rightKey = function(self)
  1525.       (self:getCurrentOption()):rightKey()
  1526.       return self:draw()
  1527.     end,
  1528.     prevOpt = function(self)
  1529.       self.currentOption = math.max(1, self.currentOption - 1)
  1530.       return self:draw()
  1531.     end,
  1532.     nextOpt = function(self)
  1533.       self.currentOption = math.min(#self.options, self.currentOption + 1)
  1534.       return self:draw()
  1535.     end,
  1536.     confirmOpts = function(self)
  1537.       for _, optPair in ipairs(self.options) do
  1538.         local optName, opt
  1539.         optName, opt = optPair[1], optPair[2]
  1540.         options[optName] = opt:getValue()
  1541.       end
  1542.       self:hide()
  1543.       return self.callback(true)
  1544.     end,
  1545.     cancelOpts = function(self)
  1546.       self:hide()
  1547.       return self.callback(false)
  1548.     end,
  1549.     draw = function(self)
  1550.       local window_w, window_h = mp.get_osd_size()
  1551.       local ass = assdraw.ass_new()
  1552.       ass:new_event()
  1553.       self:setup_text(ass)
  1554.       ass:append(tostring(bold('Options:')) .. "\\N\\N")
  1555.       for i, optPair in ipairs(self.options) do
  1556.         local opt = optPair[2]
  1557.         opt:draw(ass, self.currentOption == i)
  1558.       end
  1559.       ass:append("\\N▲ / ▼: navigate\\N")
  1560.       ass:append(tostring(bold('ENTER:')) .. " confirm options\\N")
  1561.       ass:append(tostring(bold('ESC:')) .. " cancel\\N")
  1562.       return mp.set_osd_ass(window_w, window_h, ass.text)
  1563.     end
  1564.   }
  1565.   _base_0.__index = _base_0
  1566.   setmetatable(_base_0, _parent_0.__base)
  1567.   _class_0 = setmetatable({
  1568.     __init = function(self, callback)
  1569.       self.callback = callback
  1570.       self.currentOption = 1
  1571.       local scaleHeightOpts = {
  1572.         possibleValues = {
  1573.           {
  1574.             -1,
  1575.             "no"
  1576.           },
  1577.           {
  1578.             240
  1579.           },
  1580.           {
  1581.             360
  1582.           },
  1583.           {
  1584.             480
  1585.           },
  1586.           {
  1587.             720
  1588.           },
  1589.           {
  1590.             1080
  1591.           },
  1592.           {
  1593.             1440
  1594.           },
  1595.           {
  1596.             2160
  1597.           }
  1598.         }
  1599.       }
  1600.       local filesizeOpts = {
  1601.         step = 250,
  1602.         min = 0,
  1603.         altDisplayNames = {
  1604.           [0] = "0 (constant quality)"
  1605.         }
  1606.       }
  1607.       local formatIds = {
  1608.         "webm-vp8",
  1609.         "webm-vp9",
  1610.         "mp4",
  1611.         "raw"
  1612.       }
  1613.       local formatOpts = {
  1614.         possibleValues = (function()
  1615.           local _accum_0 = { }
  1616.           local _len_0 = 1
  1617.           for _index_0 = 1, #formatIds do
  1618.             local fId = formatIds[_index_0]
  1619.             _accum_0[_len_0] = {
  1620.               fId,
  1621.               formats[fId].displayName
  1622.             }
  1623.             _len_0 = _len_0 + 1
  1624.           end
  1625.           return _accum_0
  1626.         end)()
  1627.       }
  1628.       self.options = {
  1629.         {
  1630.           "output_format",
  1631.           Option("list", "Output Format", options.output_format, formatOpts)
  1632.         },
  1633.         {
  1634.           "twopass",
  1635.           Option("bool", "Two Pass", options.twopass)
  1636.         },
  1637.         {
  1638.           "apply_current_filters",
  1639.           Option("bool", "Apply Current Video Filters", options.apply_current_filters)
  1640.         },
  1641.         {
  1642.           "scale_height",
  1643.           Option("list", "Scale Height", options.scale_height, scaleHeightOpts)
  1644.         },
  1645.         {
  1646.           "strict_filesize_constraint",
  1647.           Option("bool", "Strict Filesize Constraint", options.strict_filesize_constraint)
  1648.         },
  1649.         {
  1650.           "write_filename_on_metadata",
  1651.           Option("bool", "Write Filename on Metadata", options.write_filename_on_metadata)
  1652.         },
  1653.         {
  1654.           "target_filesize",
  1655.           Option("int", "Target Filesize", options.target_filesize, filesizeOpts)
  1656.         }
  1657.       }
  1658.       self.keybinds = {
  1659.         ["LEFT"] = (function()
  1660.           local _base_1 = self
  1661.           local _fn_0 = _base_1.leftKey
  1662.           return function(...)
  1663.             return _fn_0(_base_1, ...)
  1664.           end
  1665.         end)(),
  1666.         ["RIGHT"] = (function()
  1667.           local _base_1 = self
  1668.           local _fn_0 = _base_1.rightKey
  1669.           return function(...)
  1670.             return _fn_0(_base_1, ...)
  1671.           end
  1672.         end)(),
  1673.         ["UP"] = (function()
  1674.           local _base_1 = self
  1675.           local _fn_0 = _base_1.prevOpt
  1676.           return function(...)
  1677.             return _fn_0(_base_1, ...)
  1678.           end
  1679.         end)(),
  1680.         ["DOWN"] = (function()
  1681.           local _base_1 = self
  1682.           local _fn_0 = _base_1.nextOpt
  1683.           return function(...)
  1684.             return _fn_0(_base_1, ...)
  1685.           end
  1686.         end)(),
  1687.         ["ENTER"] = (function()
  1688.           local _base_1 = self
  1689.           local _fn_0 = _base_1.confirmOpts
  1690.           return function(...)
  1691.             return _fn_0(_base_1, ...)
  1692.           end
  1693.         end)(),
  1694.         ["ESC"] = (function()
  1695.           local _base_1 = self
  1696.           local _fn_0 = _base_1.cancelOpts
  1697.           return function(...)
  1698.             return _fn_0(_base_1, ...)
  1699.           end
  1700.         end)()
  1701.       }
  1702.     end,
  1703.     __base = _base_0,
  1704.     __name = "EncodeOptionsPage",
  1705.     __parent = _parent_0
  1706.   }, {
  1707.     __index = function(cls, name)
  1708.       local val = rawget(_base_0, name)
  1709.       if val == nil then
  1710.         local parent = rawget(cls, "__parent")
  1711.         if parent then
  1712.           return parent[name]
  1713.         end
  1714.       else
  1715.         return val
  1716.       end
  1717.     end,
  1718.     __call = function(cls, ...)
  1719.       local _self_0 = setmetatable({}, _base_0)
  1720.       cls.__init(_self_0, ...)
  1721.       return _self_0
  1722.     end
  1723.   })
  1724.   _base_0.__class = _class_0
  1725.   if _parent_0.__inherited then
  1726.     _parent_0.__inherited(_parent_0, _class_0)
  1727.   end
  1728.   EncodeOptionsPage = _class_0
  1729. end
  1730. local PreviewPage
  1731. do
  1732.   local _class_0
  1733.   local _parent_0 = Page
  1734.   local _base_0 = {
  1735.     prepare = function(self)
  1736.       local vf = mp.get_property_native("vf")
  1737.       vf[#vf + 1] = {
  1738.         name = "sub"
  1739.       }
  1740.       if self.region:is_valid() then
  1741.         vf[#vf + 1] = {
  1742.           name = "crop",
  1743.           params = {
  1744.             w = tostring(self.region.w),
  1745.             h = tostring(self.region.h),
  1746.             x = tostring(self.region.x),
  1747.             y = tostring(self.region.y)
  1748.           }
  1749.         }
  1750.       end
  1751.       mp.set_property_native("vf", vf)
  1752.       if self.startTime > -1 and self.endTime > -1 then
  1753.         mp.set_property_native("ab-loop-a", self.startTime)
  1754.         mp.set_property_native("ab-loop-b", self.endTime)
  1755.         mp.set_property_native("time-pos", self.startTime)
  1756.       end
  1757.       return mp.set_property_native("pause", false)
  1758.     end,
  1759.     dispose = function(self)
  1760.       mp.set_property("ab-loop-a", "no")
  1761.       mp.set_property("ab-loop-b", "no")
  1762.       for prop, value in pairs(self.originalProperties) do
  1763.         mp.set_property_native(prop, value)
  1764.       end
  1765.     end,
  1766.     draw = function(self)
  1767.       local window_w, window_h = mp.get_osd_size()
  1768.       local ass = assdraw.ass_new()
  1769.       ass:new_event()
  1770.       self:setup_text(ass)
  1771.       ass:append("Press " .. tostring(bold('ESC')) .. " to exit preview.\\N")
  1772.       return mp.set_osd_ass(window_w, window_h, ass.text)
  1773.     end,
  1774.     cancel = function(self)
  1775.       self:hide()
  1776.       return self.callback()
  1777.     end
  1778.   }
  1779.   _base_0.__index = _base_0
  1780.   setmetatable(_base_0, _parent_0.__base)
  1781.   _class_0 = setmetatable({
  1782.     __init = function(self, callback, region, startTime, endTime)
  1783.       self.callback = callback
  1784.       self.originalProperties = {
  1785.         ["vf"] = mp.get_property_native("vf"),
  1786.         ["time-pos"] = mp.get_property_native("time-pos"),
  1787.         ["pause"] = mp.get_property_native("pause")
  1788.       }
  1789.       self.keybinds = {
  1790.         ["ESC"] = (function()
  1791.           local _base_1 = self
  1792.           local _fn_0 = _base_1.cancel
  1793.           return function(...)
  1794.             return _fn_0(_base_1, ...)
  1795.           end
  1796.         end)()
  1797.       }
  1798.       self.region = region
  1799.       self.startTime = startTime
  1800.       self.endTime = endTime
  1801.       self.isLoop = false
  1802.     end,
  1803.     __base = _base_0,
  1804.     __name = "PreviewPage",
  1805.     __parent = _parent_0
  1806.   }, {
  1807.     __index = function(cls, name)
  1808.       local val = rawget(_base_0, name)
  1809.       if val == nil then
  1810.         local parent = rawget(cls, "__parent")
  1811.         if parent then
  1812.           return parent[name]
  1813.         end
  1814.       else
  1815.         return val
  1816.       end
  1817.     end,
  1818.     __call = function(cls, ...)
  1819.       local _self_0 = setmetatable({}, _base_0)
  1820.       cls.__init(_self_0, ...)
  1821.       return _self_0
  1822.     end
  1823.   })
  1824.   _base_0.__class = _class_0
  1825.   if _parent_0.__inherited then
  1826.     _parent_0.__inherited(_parent_0, _class_0)
  1827.   end
  1828.   PreviewPage = _class_0
  1829. end
  1830. local MainPage
  1831. do
  1832.   local _class_0
  1833.   local _parent_0 = Page
  1834.   local _base_0 = {
  1835.     setStartTime = function(self)
  1836.       self.startTime = mp.get_property_number("time-pos")
  1837.       if self.visible then
  1838.         self:clear()
  1839.         return self:draw()
  1840.       end
  1841.     end,
  1842.     setEndTime = function(self)
  1843.       self.endTime = mp.get_property_number("time-pos")
  1844.       if self.visible then
  1845.         self:clear()
  1846.         return self:draw()
  1847.       end
  1848.     end,
  1849.     draw = function(self)
  1850.       local window_w, window_h = mp.get_osd_size()
  1851.       local ass = assdraw.ass_new()
  1852.       ass:new_event()
  1853.       self:setup_text(ass)
  1854.       ass:append(tostring(bold('WebM maker')) .. "\\N\\N")
  1855.       ass:append(tostring(bold('c:')) .. " crop\\N")
  1856.       ass:append(tostring(bold('1:')) .. " set start time (current is " .. tostring(seconds_to_time_string(self.startTime)) .. ")\\N")
  1857.       ass:append(tostring(bold('2:')) .. " set end time (current is " .. tostring(seconds_to_time_string(self.endTime)) .. ")\\N")
  1858.       ass:append(tostring(bold('o:')) .. " change encode options\\N")
  1859.       ass:append(tostring(bold('p:')) .. " preview\\N")
  1860.       ass:append(tostring(bold('e:')) .. " encode\\N\\N")
  1861.       ass:append(tostring(bold('ESC:')) .. " close\\N")
  1862.       return mp.set_osd_ass(window_w, window_h, ass.text)
  1863.     end,
  1864.     onUpdateCropRegion = function(self, updated, newRegion)
  1865.       if updated then
  1866.         self.region = newRegion
  1867.       end
  1868.       return self:show()
  1869.     end,
  1870.     crop = function(self)
  1871.       self:hide()
  1872.       local cropPage = CropPage((function()
  1873.         local _base_1 = self
  1874.         local _fn_0 = _base_1.onUpdateCropRegion
  1875.         return function(...)
  1876.           return _fn_0(_base_1, ...)
  1877.         end
  1878.       end)(), self.region)
  1879.       return cropPage:show()
  1880.     end,
  1881.     onOptionsChanged = function(self, updated)
  1882.       return self:show()
  1883.     end,
  1884.     changeOptions = function(self)
  1885.       self:hide()
  1886.       local encodeOptsPage = EncodeOptionsPage((function()
  1887.         local _base_1 = self
  1888.         local _fn_0 = _base_1.onOptionsChanged
  1889.         return function(...)
  1890.           return _fn_0(_base_1, ...)
  1891.         end
  1892.       end)())
  1893.       return encodeOptsPage:show()
  1894.     end,
  1895.     onPreviewEnded = function(self)
  1896.       return self:show()
  1897.     end,
  1898.     preview = function(self)
  1899.       self:hide()
  1900.       local previewPage = PreviewPage((function()
  1901.         local _base_1 = self
  1902.         local _fn_0 = _base_1.onPreviewEnded
  1903.         return function(...)
  1904.           return _fn_0(_base_1, ...)
  1905.         end
  1906.       end)(), self.region, self.startTime, self.endTime)
  1907.       return previewPage:show()
  1908.     end,
  1909.     encode = function(self)
  1910.       self:hide()
  1911.       if self.startTime < 0 then
  1912.         message("No start time, aborting")
  1913.         return
  1914.       end
  1915.       if self.endTime < 0 then
  1916.         message("No end time, aborting")
  1917.         return
  1918.       end
  1919.       if self.startTime >= self.endTime then
  1920.         message("Start time is ahead of end time, aborting")
  1921.         return
  1922.       end
  1923.       return encode(self.region, self.startTime, self.endTime)
  1924.     end
  1925.   }
  1926.   _base_0.__index = _base_0
  1927.   setmetatable(_base_0, _parent_0.__base)
  1928.   _class_0 = setmetatable({
  1929.     __init = function(self)
  1930.       self.keybinds = {
  1931.         ["c"] = (function()
  1932.           local _base_1 = self
  1933.           local _fn_0 = _base_1.crop
  1934.           return function(...)
  1935.             return _fn_0(_base_1, ...)
  1936.           end
  1937.         end)(),
  1938.         ["1"] = (function()
  1939.           local _base_1 = self
  1940.           local _fn_0 = _base_1.setStartTime
  1941.           return function(...)
  1942.             return _fn_0(_base_1, ...)
  1943.           end
  1944.         end)(),
  1945.         ["2"] = (function()
  1946.           local _base_1 = self
  1947.           local _fn_0 = _base_1.setEndTime
  1948.           return function(...)
  1949.             return _fn_0(_base_1, ...)
  1950.           end
  1951.         end)(),
  1952.         ["o"] = (function()
  1953.           local _base_1 = self
  1954.           local _fn_0 = _base_1.changeOptions
  1955.           return function(...)
  1956.             return _fn_0(_base_1, ...)
  1957.           end
  1958.         end)(),
  1959.         ["p"] = (function()
  1960.           local _base_1 = self
  1961.           local _fn_0 = _base_1.preview
  1962.           return function(...)
  1963.             return _fn_0(_base_1, ...)
  1964.           end
  1965.         end)(),
  1966.         ["e"] = (function()
  1967.           local _base_1 = self
  1968.           local _fn_0 = _base_1.encode
  1969.           return function(...)
  1970.             return _fn_0(_base_1, ...)
  1971.           end
  1972.         end)(),
  1973.         ["ESC"] = (function()
  1974.           local _base_1 = self
  1975.           local _fn_0 = _base_1.hide
  1976.           return function(...)
  1977.             return _fn_0(_base_1, ...)
  1978.           end
  1979.         end)()
  1980.       }
  1981.       self.startTime = -1
  1982.       self.endTime = -1
  1983.       self.region = Region()
  1984.     end,
  1985.     __base = _base_0,
  1986.     __name = "MainPage",
  1987.     __parent = _parent_0
  1988.   }, {
  1989.     __index = function(cls, name)
  1990.       local val = rawget(_base_0, name)
  1991.       if val == nil then
  1992.         local parent = rawget(cls, "__parent")
  1993.         if parent then
  1994.           return parent[name]
  1995.         end
  1996.       else
  1997.         return val
  1998.       end
  1999.     end,
  2000.     __call = function(cls, ...)
  2001.       local _self_0 = setmetatable({}, _base_0)
  2002.       cls.__init(_self_0, ...)
  2003.       return _self_0
  2004.     end
  2005.   })
  2006.   _base_0.__class = _class_0
  2007.   if _parent_0.__inherited then
  2008.     _parent_0.__inherited(_parent_0, _class_0)
  2009.   end
  2010.   MainPage = _class_0
  2011. end
  2012. monitor_dimensions()
  2013. local mainPage = MainPage()
  2014. return mp.add_key_binding(options.keybind, "display-webm-encoder", (function()
  2015.   local _base_0 = mainPage
  2016.   local _fn_0 = _base_0.show
  2017.   return function(...)
  2018.     return _fn_0(_base_0, ...)
  2019.   end
  2020. end)(), {
  2021.   repeatable = false
  2022. })
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement