diff options
| author | Robert Günzler <r@gnzler.io> | 2020-12-03 12:56:51 +0100 |
|---|---|---|
| committer | Robert Günzler <r@gnzler.io> | 2020-12-07 19:28:05 +0100 |
| commit | 0eb11ed2c3dc235349b87d20bf657f2fb9729994 (patch) | |
| tree | ceba665d591328bb47418ab82bb0de31abff98e5 /.config/mvi/scripts | |
| parent | e93cee56264c7e1d07b422d66430d996fd8b0c40 (diff) | |
Add bin/mvi and config; mpv-based image viewer
Diffstat (limited to '.config/mvi/scripts')
| -rw-r--r-- | .config/mvi/scripts/autoload.lua | 220 | ||||
| -rw-r--r-- | .config/mvi/scripts/detect-image.lua | 89 | ||||
| -rw-r--r-- | .config/mvi/scripts/freeze-window.lua | 14 | ||||
| l--------- | .config/mvi/scripts/gallery-thumbgen.lua | 1 | ||||
| -rw-r--r-- | .config/mvi/scripts/image-positioning.lua | 341 | ||||
| l--------- | .config/mvi/scripts/lib.disable | 1 | ||||
| -rw-r--r-- | .config/mvi/scripts/minimap.lua | 306 | ||||
| l--------- | .config/mvi/scripts/playlist-view.lua | 1 | ||||
| -rw-r--r-- | .config/mvi/scripts/ruler.lua | 388 | ||||
| -rw-r--r-- | .config/mvi/scripts/status-line.lua | 108 |
10 files changed, 1469 insertions, 0 deletions
diff --git a/.config/mvi/scripts/autoload.lua b/.config/mvi/scripts/autoload.lua new file mode 100644 index 0000000..7150abb --- /dev/null +++ b/.config/mvi/scripts/autoload.lua @@ -0,0 +1,220 @@ +-- This script automatically loads playlist entries before and after the +-- the currently played file. It does so by scanning the directory a file is +-- located in when starting playback. It sorts the directory entries +-- alphabetically, and adds entries before and after the current file to +-- the internal playlist. (It stops if it would add an already existing +-- playlist entry at the same position - this makes it "stable".) +-- Add at most 5000 * 2 files when starting a file (before + after). + +--[[ +To configure this script use file autoload.conf in directory script-opts (the "script-opts" +directory must be in the mpv configuration directory, typically ~/.config/mpv/). + +Example configuration would be: + +disabled=no +images=no +videos=yes +audio=yes + +--]] + +MAXENTRIES = 5000 + +local msg = require 'mp.msg' +local options = require 'mp.options' +local utils = require 'mp.utils' + +o = { + disabled = false, + images = true, + videos = true, + audio = true +} +options.read_options(o) + +function Set (t) + local set = {} + for _, v in pairs(t) do set[v] = true end + return set +end + +function SetUnion (a,b) + local res = {} + for k in pairs(a) do res[k] = true end + for k in pairs(b) do res[k] = true end + return res +end + +EXTENSIONS_VIDEO = Set { + 'mkv', 'avi', 'mp4', 'ogv', 'webm', 'rmvb', 'flv', 'wmv', 'mpeg', 'mpg', 'm4v', '3gp' +} + +EXTENSIONS_AUDIO = Set { + 'mp3', 'wav', 'ogm', 'flac', 'm4a', 'wma', 'ogg', 'opus' +} + +EXTENSIONS_IMAGES = Set { + 'jpg', 'jpeg', 'png', 'tif', 'tiff', 'gif', 'webp', 'svg', 'bmp' +} + +EXTENSIONS = Set {} +if o.videos then EXTENSIONS = SetUnion(EXTENSIONS, EXTENSIONS_VIDEO) end +if o.audio then EXTENSIONS = SetUnion(EXTENSIONS, EXTENSIONS_AUDIO) end +if o.images then EXTENSIONS = SetUnion(EXTENSIONS, EXTENSIONS_IMAGES) end + +function add_files_at(index, files) + index = index - 1 + local oldcount = mp.get_property_number("playlist-count", 1) + for i = 1, #files do + mp.commandv("loadfile", files[i], "append") + mp.commandv("playlist-move", oldcount + i - 1, index + i - 1) + end +end + +function get_extension(path) + match = string.match(path, "%.([^%.]+)$" ) + if match == nil then + return "nomatch" + else + return match + end +end + +table.filter = function(t, iter) + for i = #t, 1, -1 do + if not iter(t[i]) then + table.remove(t, i) + end + end +end + +-- splitbynum and alnumcomp from alphanum.lua (C) Andre Bogus +-- Released under the MIT License +-- http://www.davekoelle.com/files/alphanum.lua + +-- split a string into a table of number and string values +function splitbynum(s) + local result = {} + for x, y in (s or ""):gmatch("(%d*)(%D*)") do + if x ~= "" then table.insert(result, tonumber(x)) end + if y ~= "" then table.insert(result, y) end + end + return result +end + +function clean_key(k) + k = (' '..k..' '):gsub("%s+", " "):sub(2, -2):lower() + return splitbynum(k) +end + +-- compare two strings +function alnumcomp(x, y) + local xt, yt = clean_key(x), clean_key(y) + for i = 1, math.min(#xt, #yt) do + local xe, ye = xt[i], yt[i] + if type(xe) == "string" then ye = tostring(ye) + elseif type(ye) == "string" then xe = tostring(xe) end + if xe ~= ye then return xe < ye end + end + return #xt < #yt +end + +local autoloaded = nil + +function find_and_add_entries() + local path = mp.get_property("path", "") + local dir, filename = utils.split_path(path) + msg.trace(("dir: %s, filename: %s"):format(dir, filename)) + if o.disabled then + msg.verbose("stopping: autoload disabled") + return + elseif #dir == 0 then + msg.verbose("stopping: not a local path") + return + end + + local pl_count = mp.get_property_number("playlist-count", 1) + -- check if this is a manually made playlist + if (pl_count > 1 and autoloaded == nil) or + (pl_count == 1 and EXTENSIONS[string.lower(get_extension(filename))] == nil) then + msg.verbose("stopping: manually made playlist") + return + else + autoloaded = true + end + + local pl = mp.get_property_native("playlist", {}) + local pl_current = mp.get_property_number("playlist-pos-1", 1) + msg.trace(("playlist-pos-1: %s, playlist: %s"):format(pl_current, + utils.to_string(pl))) + + local files = utils.readdir(dir, "files") + if files == nil then + msg.verbose("no other files in directory") + return + end + table.filter(files, function (v, k) + if string.match(v, "^%.") then + return false + end + local ext = get_extension(v) + if ext == nil then + return false + end + return EXTENSIONS[string.lower(ext)] + end) + table.sort(files, alnumcomp) + + if dir == "." then + dir = "" + end + + -- Find the current pl entry (dir+"/"+filename) in the sorted dir list + local current + for i = 1, #files do + if files[i] == filename then + current = i + break + end + end + if current == nil then + return + end + msg.trace("current file position in files: "..current) + + local append = {[-1] = {}, [1] = {}} + for direction = -1, 1, 2 do -- 2 iterations, with direction = -1 and +1 + for i = 1, MAXENTRIES do + local file = files[current + i * direction] + local pl_e = pl[pl_current + i * direction] + if file == nil or file[1] == "." then + break + end + + local filepath = dir .. file + if pl_e then + -- If there's a playlist entry, and it's the same file, stop. + msg.trace(pl_e.filename.." == "..filepath.." ?") + if pl_e.filename == filepath then + break + end + end + + if direction == -1 then + if pl_current == 1 then -- never add additional entries in the middle + msg.info("Prepending " .. file) + table.insert(append[-1], 1, filepath) + end + else + msg.info("Adding " .. file) + table.insert(append[1], filepath) + end + end + end + + add_files_at(pl_current + 1, append[1]) + add_files_at(pl_current, append[-1]) +end + +mp.register_event("start-file", find_and_add_entries) diff --git a/.config/mvi/scripts/detect-image.lua b/.config/mvi/scripts/detect-image.lua new file mode 100644 index 0000000..1b3496c --- /dev/null +++ b/.config/mvi/scripts/detect-image.lua @@ -0,0 +1,89 @@ +local opts = { + command_on_first_image_loaded="", + command_on_image_loaded="", + command_on_non_image_loaded="", +} +(require 'mp.options').read_options(opts) + +if opts.command_on_first_image_loaded == "" + and opts.command_on_image_loaded == "" + and opts.command_on_non_image_loaded == "" +then + return +end + +local msg = require 'mp.msg' + +local was_image = false +local frame_count = nil +local audio_tracks = nil +local out_params_ready = nil +local path = nil + +function run_maybe(str) + if str ~= "" then + mp.command(str) + end +end + +function set_image(is_image) + if is_image and not was_image then + msg.verbose("First image detected") + run_maybe(opts.command_on_first_image_loaded) + end + if is_image then + msg.verbose("Image detected") + run_maybe(opts.command_on_image_loaded) + end + if not is_image and was_image then + msg.verbose("Non-image detected") + run_maybe(opts.command_on_non_image_loaded) + end + was_image = is_image +end + +function state_changed() + -- only do things when state is consistent + if path ~= nil and audio_tracks ~= nil then + if frame_count == nil and audio_tracks > 0 then + set_image(false) + elseif out_params_ready and frame_count ~= nil then + -- png have 0 frames, jpg 1 ¯\_(ツ)_/¯ + set_image((frame_count == 0 or frame_count == 1) and audio_tracks == 0) + end + end +end + +mp.observe_property("dwidth", "number", function(_, val) + out_params_ready = (val ~= nil and val > 0) + state_changed() +end) + +mp.observe_property("estimated-frame-count", "number", function(_, val) + frame_count = val + state_changed() +end) + +mp.observe_property("path", "string", function(_, val) + if not val or val == "" then + path = nil + else + path = val + end + state_changed() +end) + +mp.register_event("tracks-changed", function() + audio_tracks = 0 + local tracks = 0 + for _, track in ipairs(mp.get_property_native("track-list")) do + tracks = tracks + 1 + if track.type == "audio" then + audio_tracks = audio_tracks + 1 + end + end + if tracks == 0 then + audio_tracks = nil + end + state_changed() +end) diff --git a/.config/mvi/scripts/freeze-window.lua b/.config/mvi/scripts/freeze-window.lua new file mode 100644 index 0000000..693c717 --- /dev/null +++ b/.config/mvi/scripts/freeze-window.lua @@ -0,0 +1,14 @@ +-- credit to TheAMM + +local size_changed = false + +mp.register_idle(function() + if not size_changed then return end + local ww, wh = mp.get_osd_size() + if not ww or ww <= 0 or not wh or wh <= 0 then return end + mp.set_property("geometry", string.format("%dx%d", ww, wh)) + size_changed = false +end) + +mp.observe_property("osd-width", "native", function() size_changed = true end) +mp.observe_property("osd-height", "native", function() size_changed = true end) diff --git a/.config/mvi/scripts/gallery-thumbgen.lua b/.config/mvi/scripts/gallery-thumbgen.lua new file mode 120000 index 0000000..92816c8 --- /dev/null +++ b/.config/mvi/scripts/gallery-thumbgen.lua @@ -0,0 +1 @@ +../../mpv/scripts/mpv-gallery-view/scripts/gallery-thumbgen.lua \ No newline at end of file diff --git a/.config/mvi/scripts/image-positioning.lua b/.config/mvi/scripts/image-positioning.lua new file mode 100644 index 0000000..08fa1e7 --- /dev/null +++ b/.config/mvi/scripts/image-positioning.lua @@ -0,0 +1,341 @@ +local opts = { + pan_follows_cursor_margin = 50, + pan_follows_cursor_move_if_full_view = false, + + drag_to_pan_margin = 50, + drag_to_pan_move_if_full_view = false, +} +(require 'mp.options').read_options(opts) + +function clamp(value, low, high) + if value <= low then + return low + elseif value >= high then + return high + else + return value + end +end + +local msg = require 'mp.msg' +local assdraw = require 'mp.assdraw' + +function get_video_dimensions() + -- this function is very much ripped from video/out/aspect.c in mpv's source + local video_params = mp.get_property_native("video-out-params") + if not video_params then + _video_dimensions = nil + return nil + end + _video_dimensions = { + top_left = { 0, 0 }, + bottom_right = { 0, 0 }, + size = { 0, 0 }, + ratios = { 0, 0 }, -- by how much the original video got scaled + } + 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 + 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 = 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 + --account for C int cast truncating as opposed to flooring + 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[1], _video_dimensions.bottom_right[1] = 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[2], _video_dimensions.bottom_right[2] = split_scaling(window_h, scaled_height, zoom, align_y, pan_y) + else + _video_dimensions.top_left[1] = 0 + _video_dimensions.bottom_right[1] = window_w + _video_dimensions.top_left[2] = 0 + _video_dimensions.bottom_right[2] = window_h + end + _video_dimensions.size[1] = _video_dimensions.bottom_right[1] - _video_dimensions.top_left[1] + _video_dimensions.size[2] = _video_dimensions.bottom_right[2] - _video_dimensions.top_left[2] + _video_dimensions.ratios[1] = _video_dimensions.size[1] / w + _video_dimensions.ratios[2] = _video_dimensions.size[2] / h + return _video_dimensions +end + +local cleanup = nil -- function set up by drag-to-pan/pan-follows cursor and must be called to clean lingering state + +function drag_to_pan_handler(table) + if cleanup then + cleanup() + cleanup = nil + end + if table["event"] == "down" then + local video_dimensions = get_video_dimensions() + if not video_dimensions then return end + local window_w, window_h = mp.get_osd_size() + local mouse_pos_origin, video_pan_origin = {}, {} + local moved = false + mouse_pos_origin[1], mouse_pos_origin[2] = mp.get_mouse_pos() + video_pan_origin[1] = mp.get_property_number("video-pan-x") + video_pan_origin[2] = mp.get_property_number("video-pan-y") + local margin = opts.drag_to_pan_margin + local move_up = true + local move_lateral = true + if not opts.drag_to_pan_move_if_full_view then + if video_dimensions.size[1] <= window_w then + move_lateral = false + end + if video_dimensions.size[2] <= window_h then + move_up = false + end + end + if not move_up and not move_lateral then return end + local idle = function() + if moved then + local mX, mY = mp.get_mouse_pos() + local pX = video_pan_origin[1] + local pY = video_pan_origin[2] + if move_lateral then + pX = video_pan_origin[1] + (mX - mouse_pos_origin[1]) / video_dimensions.size[1] + if video_dimensions.size[1] + 2 * margin > window_w then + pX = clamp(pX, + (-margin + window_w / 2) / video_dimensions.size[1] - 0.5, + (margin - window_w / 2) / video_dimensions.size[1] + 0.5) + else + pX = clamp(pX, + (margin - window_w / 2) / video_dimensions.size[1] + 0.5, + (-margin + window_w / 2) / video_dimensions.size[1] - 0.5) + end + end + if move_up then + pY = video_pan_origin[2] + (mY - mouse_pos_origin[2]) / video_dimensions.size[2] + if video_dimensions.size[2] + 2 * margin > window_h then + pY = clamp(pY, + (-margin + window_h / 2) / video_dimensions.size[2] - 0.5, + (margin - window_h / 2) / video_dimensions.size[2] + 0.5) + else + pY = clamp(pY, + (margin - window_h / 2) / video_dimensions.size[2] + 0.5, + (-margin + window_h / 2) / video_dimensions.size[2] - 0.5) + end + end + mp.command("no-osd set video-pan-x " .. clamp(pX, -3, 3) .. "; no-osd set video-pan-y " .. clamp(pY, -3, 3)) + moved = false + end + end + mp.register_idle(idle) + mp.add_forced_key_binding("mouse_move", "image-viewer-mouse-move", function() moved = true end) + cleanup = function() + mp.remove_key_binding("image-viewer-mouse-move") + mp.unregister_idle(idle) + end + end +end + +function pan_follows_cursor_handler(table) + if cleanup then + cleanup() + cleanup = nil + end + if table["event"] == "down" then + local video_dimensions = get_video_dimensions() + if not video_dimensions then return end + local window_w, window_h = mp.get_osd_size() + local moved = true + local idle = function() + if moved then + local mX, mY = mp.get_mouse_pos() + local x = math.min(1, math.max(- 2 * mX / window_w + 1, -1)) + local y = math.min(1, math.max(- 2 * mY / window_h + 1, -1)) + local command = "" + local margin, move_full = opts.pan_follows_cursor_margin, opts.pan_follows_cursor_move_if_full_view + if (not move_full and window_w < video_dimensions.size[1]) then + command = command .. "no-osd set video-pan-x " .. clamp(x * (video_dimensions.size[1] - window_w + 2 * margin) / (2 * video_dimensions.size[1]), -3, 3) .. ";" + elseif mp.get_property_number("video-pan-x") ~= 0 then + command = command .. "no-osd set video-pan-x " .. "0;" + end + if (not move_full and window_h < video_dimensions.size[2]) then + command = command .. "no-osd set video-pan-y " .. clamp(y * (video_dimensions.size[2] - window_h + 2 * margin) / (2 * video_dimensions.size[2]), -3, 3) .. ";" + elseif mp.get_property_number("video-pan-y") ~= 0 then + command = command .. "no-osd set video-pan-y " .. "0;" + end + if command ~= "" then + mp.command(command) + end + moved = false + end + end + mp.register_idle(idle) + mp.add_forced_key_binding("mouse_move", "image-viewer-mouse-move", function() moved = true end) + cleanup = function() + mp.remove_key_binding("image-viewer-mouse-move") + mp.unregister_idle(idle) + end + end +end + +function cursor_centric_zoom_handler(amt) + local zoom_inc = tonumber(amt) + if not zoom_inc or zoom_inc == 0 then return end + local video_dimensions = get_video_dimensions() + if not video_dimensions then return end + local mouse_pos_origin, video_pan_origin = {}, {} + mouse_pos_origin[1], mouse_pos_origin[2] = mp.get_mouse_pos() + video_pan_origin[1] = mp.get_property("video-pan-x") + video_pan_origin[2] = mp.get_property("video-pan-y") + local zoom_origin = mp.get_property("video-zoom") + -- how far the cursor is form the middle of the video (in percentage) + local rx = (video_dimensions.top_left[1] + video_dimensions.size[1] / 2 - mouse_pos_origin[1]) / (video_dimensions.size[1] / 2) + local ry = (video_dimensions.top_left[2] + video_dimensions.size[2] / 2 - mouse_pos_origin[2]) / (video_dimensions.size[2] / 2) + + -- the size in pixels of the (in|de)crement + local diffHeight = (2 ^ zoom_inc - 1) * video_dimensions.size[2] + local diffWidth = (2 ^ zoom_inc - 1) * video_dimensions.size[1] + local newPanX = (video_pan_origin[1] * video_dimensions.size[1] + rx * diffWidth / 2) / (video_dimensions.size[1] + diffWidth) + local newPanY = (video_pan_origin[2] * video_dimensions.size[2] + ry * diffHeight / 2) / (video_dimensions.size[2] + diffHeight) + mp.command("no-osd set video-zoom " .. zoom_origin + zoom_inc .. "; no-osd set video-pan-x " .. clamp(newPanX, -3, 3) .. "; no-osd set video-pan-y " .. clamp(newPanY, -3, 3)) +end + +function align_border(x, y) + local video_dimensions = get_video_dimensions() + if not video_dimensions then return end + local window_w, window_h = mp.get_osd_size() + local x, y = tonumber(x), tonumber(y) + local command = "" + if x then + command = command .. "no-osd set video-pan-x " .. clamp(x * (video_dimensions.size[1] - window_w) / (2 * video_dimensions.size[1]), -3, 3) .. ";" + end + if y then + command = command .. "no-osd set video-pan-y " .. clamp(y * (video_dimensions.size[2] - window_h) / (2 * video_dimensions.size[2]), -3, 3) .. ";" + end + if command ~= "" then + mp.command(command) + end +end + +function pan_image(axis, amount, zoom_invariant, image_constrained) + amount = tonumber(amount) + if not amount or amount == 0 or axis ~= "x" and axis ~= "y" then return end + if zoom_invariant == "yes" then + amount = amount / 2 ^ mp.get_property_number("video-zoom") + end + local prop = "video-pan-" .. axis + local old_pan = mp.get_property_number(prop) + axis = (axis == "x") and 1 or 2 + if image_constrained == "yes" then + local video_dimensions = get_video_dimensions() + if not video_dimensions then return end + local window = {} + window[1], window[2] = mp.get_osd_size() + local pixels_moved = amount * video_dimensions.size[axis] + -- should somehow refactor this + if pixels_moved > 0 then + if window[axis] > video_dimensions.size[axis] then + if video_dimensions.bottom_right[axis] >= window[axis] then return end + if video_dimensions.bottom_right[axis] + pixels_moved > window[axis] then + amount = (window[axis] - video_dimensions.bottom_right[axis]) / video_dimensions.size[axis] + end + else + if video_dimensions.top_left[axis] >= 0 then return end + if video_dimensions.top_left[axis] + pixels_moved > 0 then + amount = (0 - video_dimensions.top_left[axis]) / video_dimensions.size[axis] + end + end + else + if window[axis] > video_dimensions.size[axis] then + if video_dimensions.top_left[axis] <= 0 then return end + if video_dimensions.top_left[axis] + pixels_moved < 0 then + amount = (0 - video_dimensions.top_left[axis]) / video_dimensions.size[axis] + end + else + if video_dimensions.bottom_right[axis] <= window[axis] then return end + if video_dimensions.bottom_right[axis] + pixels_moved < window[axis] then + amount = (window[axis] - video_dimensions.bottom_right[axis]) / video_dimensions.size[axis] + end + end + end + end + mp.set_property_number(prop, old_pan + amount) +end + +function rotate_video(amt) + local rot = mp.get_property_number("video-rotate") + rot = (rot + amt) % 360 + mp.set_property_number("video-rotate", rot) +end + +function reset_pan_if_visible() + local video_dimensions = get_video_dimensions() + if not video_dimensions then return end + local window_w, window_h = mp.get_osd_size() + local command = "" + if (window_w >= video_dimensions.size[1]) then + command = command .. "no-osd set video-pan-x 0" .. ";" + end + if (window_h >= video_dimensions.size[2]) then + command = command .. "no-osd set video-pan-y 0" .. ";" + end + if command ~= "" then + mp.command(command) + end +end + +mp.add_key_binding(nil, "drag-to-pan", drag_to_pan_handler, {complex = true}) +mp.add_key_binding(nil, "pan-follows-cursor", pan_follows_cursor_handler, {complex = true}) +mp.add_key_binding(nil, "cursor-centric-zoom", cursor_centric_zoom_handler) +mp.add_key_binding(nil, "align-border", align_border) +mp.add_key_binding(nil, "pan-image", pan_image) +mp.add_key_binding(nil, "rotate-video", rotate_video) +mp.add_key_binding(nil, "reset-pan-if-visible", reset_pan_if_visible) +mp.add_key_binding(nil, "force-print-filename", force_print_filename) diff --git a/.config/mvi/scripts/lib.disable b/.config/mvi/scripts/lib.disable new file mode 120000 index 0000000..bc6efec --- /dev/null +++ b/.config/mvi/scripts/lib.disable @@ -0,0 +1 @@ +../../mpv/scripts/mpv-gallery-view/scripts/lib.disable \ No newline at end of file diff --git a/.config/mvi/scripts/minimap.lua b/.config/mvi/scripts/minimap.lua new file mode 100644 index 0000000..faa7f62 --- /dev/null +++ b/.config/mvi/scripts/minimap.lua @@ -0,0 +1,306 @@ +local opts = { + enabled = true, + center = "92,92", + scale = 12, + max_size = "16,16", + image_opacity = "88", + image_color = "BBBBBB", + view_opacity = "BB", + view_color = "222222", + view_above_image = true, + hide_when_full_image_in_view = true, +} +(require 'mp.options').read_options(opts) + +function process(input) + local ret = {} + for str in string.gmatch(input, "([^,]+)") do + ret[#ret + 1] = tonumber(str) + end + return ret +end +opts.center=process(opts.center) +opts.max_size=process(opts.max_size) + +local msg = require 'mp.msg' +local assdraw = require 'mp.assdraw' + +local video_dimensions_stale = true + +function get_video_dimensions() + -- this function is very much ripped from video/out/aspect.c in mpv's source + if not video_dimensions_stale then return _video_dimensions end + local video_params = mp.get_property_native("video-out-params") + if not video_params then + _video_dimensions = nil + return nil + end + if not _timestamp then _timestamp = 0 end + _timestamp = _timestamp + 1 + _video_dimensions = { + timestamp = _timestamp, + top_left = { 0, 0 }, + bottom_right = { 0, 0 }, + size = { 0, 0 }, + ratios = { 0, 0 }, -- by how much the original video got scaled + } + 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 + 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 = 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 + --account for C int cast truncating as opposed to flooring + 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[1], _video_dimensions.bottom_right[1] = 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[2], _video_dimensions.bottom_right[2] = split_scaling(window_h, scaled_height, zoom, align_y, pan_y) + else + _video_dimensions.top_left[1] = 0 + _video_dimensions.bottom_right[1] = window_w + _video_dimensions.top_left[2] = 0 + _video_dimensions.bottom_right[2] = window_h + end + _video_dimensions.size[1] = _video_dimensions.bottom_right[1] - _video_dimensions.top_left[1] + _video_dimensions.size[2] = _video_dimensions.bottom_right[2] - _video_dimensions.top_left[2] + _video_dimensions.ratios[1] = _video_dimensions.size[1] / w + _video_dimensions.ratios[2] = _video_dimensions.size[2] / h + + if not (_video_dimensions.size[1] > 0 and _video_dimensions.size[2] > 0) then return nil end + video_dimensions_stale = false + return _video_dimensions +end + +for _, p in ipairs({ + "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", +}) do + mp.observe_property(p, nil, function() video_dimensions_stale = true end) +end +function draw_ass(ass) + local ww, wh = mp.get_osd_size() + mp.set_osd_ass(ww, wh, ass) +end + +local old_timestamp = -1 + +function refresh_minimap() + local dim = get_video_dimensions() + if not dim then + draw_ass("") + return + end + if dim.timestamp == old_timestamp then return end + old_timestamp = dim.timestamp + local ww, wh = mp.get_osd_size() + if not (ww > 0 and wh > 0) then return end + if opts.hide_when_full_image_in_view then + if dim.top_left[1] >= 0 and + dim.top_left[2] >= 0 and + dim.bottom_right[1] <= ww and + dim.bottom_right[2] <= wh + then + draw_ass("") + return + end + end + local center = { + opts.center[1] * 0.01 * ww, + opts.center[2] * 0.01 * wh + } + local cutoff = { + opts.max_size[1] * 0.01 * ww * 0.5, + opts.max_size[2] * 0.01 * wh * 0.5 + } + local a = assdraw.ass_new() + local draw = function(x, y, w, h, opacity, color) + a:new_event() + a:pos(center[1], center[2]) + a:append("{\\bord0}") + a:append("{\\shad0}") + a:append("{\\c&" .. color .. "&}") + a:append("{\\2a&HFF}") + a:append("{\\3a&HFF}") + a:append("{\\4a&HFF}") + a:append("{\\1a&H" .. opacity .. "}") + w = w * 0.5 + h = h * 0.5 + a:draw_start() + local rounded = {true,true,true,true} -- tl, tr, br, bl + local x0,y0,x1,y1 = x-w, y-h, x+w, y+h + if x0 < -cutoff[1] then + x0 = -cutoff[1] + rounded[4] = false + rounded[1] = false + end + if y0 < -cutoff[2] then + y0 = -cutoff[2] + rounded[1] = false + rounded[2] = false + end + if x1 > cutoff[1] then + x1 = cutoff[1] + rounded[2] = false + rounded[3] = false + end + if y1 > cutoff[2] then + y1 = cutoff[2] + rounded[3] = false + rounded[4] = false + end + + local r = 3 + local c = 0.551915024494 * r + if rounded[0] then + a:move_to(x0 + r, y0) + else + a:move_to(x0,y0) + end + if rounded[1] then + a:line_to(x1 - r, y0) + a:bezier_curve(x1 - r + c, y0, x1, y0 + r - c, x1, y0 + r) + else + a:line_to(x1, y0) + end + if rounded[2] then + a:line_to(x1, y1 - r) + a:bezier_curve(x1, y1 - r + c, x1 - r + c, y1, x1 - r, y1) + else + a:line_to(x1, y1) + end + if rounded[3] then + a:line_to(x0 + r, y1) + a:bezier_curve(x0 + r - c, y1, x0, y1 - r + c, x0, y1 - r) + else + a:line_to(x0, y1) + end + if rounded[4] then + a:line_to(x0, y0 + r) + a:bezier_curve(x0, y0 + r - c, x0 + r - c, y0, x0 + r, y0) + else + a:line_to(x0, y0) + end + a:draw_stop() + end + local image = function() + draw((dim.top_left[1] + dim.size[1]/2 - ww/2) / opts.scale, + (dim.top_left[2] + dim.size[2]/2 - wh/2) / opts.scale, + dim.size[1] / opts.scale, + dim.size[2] / opts.scale, + opts.image_opacity, + opts.image_color) + end + local view = function() + draw(0, + 0, + ww / opts.scale, + wh / opts.scale, + opts.view_opacity, + opts.view_color) + end + if opts.view_above_image then + image() + view() + else + view() + image() + end + draw_ass(a.text) +end + +local active = false + +function enable_minimap() + if active then return end + active = true + mp.register_idle(refresh_minimap) +end + +function disable_minimap() + if not active then return end + active = false + ass.minimap = a.text + draw_ass() + mp.unregister_idle(refresh_minimap) +end + +function toggle() + if active then + disable_minimap() + else + enable_minimap() + end +end + +if opts.enabled then + enable_minimap() +end + +mp.add_key_binding(nil, "minimap-enable", enable) +mp.add_key_binding(nil, "minimap-disable", disable) +mp.add_key_binding(nil, "minimap-toggle", toggle) diff --git a/.config/mvi/scripts/playlist-view.lua b/.config/mvi/scripts/playlist-view.lua new file mode 120000 index 0000000..ed4ac05 --- /dev/null +++ b/.config/mvi/scripts/playlist-view.lua @@ -0,0 +1 @@ +../../mpv/scripts/mpv-gallery-view/scripts/playlist-view.lua \ No newline at end of file diff --git a/.config/mvi/scripts/ruler.lua b/.config/mvi/scripts/ruler.lua new file mode 100644 index 0000000..60355ce --- /dev/null +++ b/.config/mvi/scripts/ruler.lua @@ -0,0 +1,388 @@ +local opts = { + show_distance=true, + show_coordinates=true, + coordinates_space="image", + show_angles="degrees", + line_width=2, + dots_radius=3, + font_size=36, + line_color="33", + confirm_bindings="MBTN_LEFT,ENTER", + exit_bindings="ESC", + set_first_point_on_begin=false, + clear_on_second_point_set=false, +} +(require 'mp.options').read_options(opts) + +function split(input) + local ret = {} + for str in string.gmatch(input, "([^,]+)") do + ret[#ret + 1] = str + end + return ret +end +opts.confirm_bindings=split(opts.confirm_bindings) +opts.exit_bindings=split(opts.exit_bindings) + +local msg = require 'mp.msg' +local assdraw = require 'mp.assdraw' + +local video_dimensions_stale = true + +function get_video_dimensions() + -- this function is very much ripped from video/out/aspect.c in mpv's source + if not video_dimensions_stale then return _video_dimensions end + local video_params = mp.get_property_native("video-out-params") + if not video_params then + _video_dimensions = nil + return nil + end + if not _timestamp then _timestamp = 0 end + _timestamp = _timestamp + 1 + _video_dimensions = { + timestamp = _timestamp, + top_left = { 0, 0 }, + bottom_right = { 0, 0 }, + size = { 0, 0 }, + ratios = { 0, 0 }, -- by how much the original video got scaled + } + 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 + 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 = 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 + --account for C int cast truncating as opposed to flooring + 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[1], _video_dimensions.bottom_right[1] = 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[2], _video_dimensions.bottom_right[2] = split_scaling(window_h, scaled_height, zoom, align_y, pan_y) + else + _video_dimensions.top_left[1] = 0 + _video_dimensions.bottom_right[1] = window_w + _video_dimensions.top_left[2] = 0 + _video_dimensions.bottom_right[2] = window_h + end + _video_dimensions.size[1] = _video_dimensions.bottom_right[1] - _video_dimensions.top_left[1] + _video_dimensions.size[2] = _video_dimensions.bottom_right[2] - _video_dimensions.top_left[2] + _video_dimensions.ratios[1] = _video_dimensions.size[1] / w + _video_dimensions.ratios[2] = _video_dimensions.size[2] / h + video_dimensions_stale = false + return _video_dimensions +end + +for _, p in ipairs({ + "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", +}) do + mp.observe_property(p, nil, function() video_dimensions_stale = true end) +end + +local state = 0 -- {0,1,2,3} = {inactive,setting first point,setting second point,done} +local first_point = nil -- in video space coordinates +local second_point = nil -- in video space coordinates + +function draw_ass(ass) + local ww, wh = mp.get_osd_size() + mp.set_osd_ass(ww, wh, ass) +end + +function cursor_video_space() + local dim = get_video_dimensions() + if not dim then return nil end + local mx, my = mp.get_mouse_pos() + local ret = {} + ret[1] = (mx - dim.top_left[1]) / dim.ratios[1] + ret[2] = (my - dim.top_left[2]) / dim.ratios[2] + return ret +end + +function video_space_to_screen(point) + local dim = get_video_dimensions() + if not dim then return nil end + local ret = {} + ret[1] = point[1] * dim.ratios[1] + dim.top_left[1] + ret[2] = point[2] * dim.ratios[2] + dim.top_left[2] + return ret +end + +function refresh() + local dim = get_video_dimensions() + if not dim then + draw_ass("") + return + end + + local line_start = {} + local line_end = {} + if second_point then + line_start.image = first_point + line_start.screen = video_space_to_screen(first_point) + line_end.image = second_point + line_end.screen = video_space_to_screen(second_point) + elseif first_point then + line_start.image = first_point + line_start.screen = video_space_to_screen(first_point) + line_end.image = cursor_video_space() + line_end.screen = {} + line_end.screen[1], line_end.screen[2] = mp.get_mouse_pos() + else + local mx, my = mp.get_mouse_pos() + line_start.image = cursor_video_space() + line_start.screen = {} + line_start.screen[1], line_start.screen[2] = mp.get_mouse_pos() + line_end = line_start + end + local distinct = (math.abs(line_start.screen[1] - line_end.screen[1]) >= 1 + or math.abs(line_start.screen[2] - line_end.screen[2]) >= 1) + + local a = assdraw:ass_new() + local draw_setup = function(bord) + a:new_event() + a:pos(0,0) + a:append("{\\bord" .. bord .. "}") + a:append("{\\shad0}") + local r = opts.line_color + a:append("{\\3c&H".. r .. r .. r .. "&}") + a:append("{\\1a&HFF}") + a:append("{\\2a&HFF}") + a:append("{\\3a&H00}") + a:append("{\\4a&HFF}") + a:draw_start() + end + local dot = function(pos, size) + draw_setup(size) + a:move_to(pos[1], pos[2]-0.5) + a:line_to(pos[1], pos[2]+0.5) + end + local line = function(from, to, size) + draw_setup(size) + a:move_to(from[1], from[2]) + a:line_to(to[1], to[2]) + end + if distinct then + dot(line_start.screen, opts.dots_radius) + line(line_start.screen, line_end.screen, opts.line_width) + dot(line_end.screen, opts.dots_radius) + else + dot(line_start.screen, opts.dots_radius) + end + + local line_info = function() + if not opts.show_distance then return end + a:new_event() + a:append("{\\fs36}{\\bord1}") + a:pos((line_start.screen[1] + line_end.screen[1]) / 2, (line_start.screen[2] + line_end.screen[2]) / 2) + local an = 1 + if line_start.image[1] < line_end.image[1] then an = an + 2 end + if line_start.image[2] < line_end.image[2] then an = an + 6 end + a:an(an) + local image = math.sqrt(math.pow(line_start.image[1] - line_end.image[1], 2) + math.pow(line_start.image[2] - line_end.image[2], 2)) + local screen = math.sqrt(math.pow(line_start.screen[1] - line_end.screen[1], 2) + math.pow(line_start.screen[2] - line_end.screen[2], 2)) + if opts.coordinates_space == "both" then + a:append(string.format("image: %.1f\\Nscreen: %.1f", image, screen)) + elseif opts.coordinates_space == "image" then + a:append(string.format("%.1f", image)) + elseif opts.coordinates_space == "window" then + a:append(string.format("%.1f", screen)) + end + end + local dot_info = function(pos, opposite) + if not opts.show_coordinates then return end + a:new_event() + a:append("{\\fs" .. opts.font_size .."}{\\bord1}") + a:pos(pos.screen[1], pos.screen[2]) + local an + if distinct then + an = 1 + if line_start.image[1] > line_end.image[1] then an = an + 2 end + if line_start.image[2] < line_end.image[2] then an = an + 6 end + else + an = 7 + end + if opposite then + an = 9 + 1 - an + end + a:an(an) + if opts.coordinates_space == "both" then + a:append(string.format("image: %.1f, %.1f\\Nscreen: %i, %i", + pos.image[1], pos.image[2], pos.screen[1], pos.screen[2])) + elseif opts.coordinates_space == "image" then + a:append(string.format("%.1f, %.1f", pos.image[1], pos.image[2])) + elseif opts.coordinates_space == "window" then + a:append(string.format("%i, %i", pos.screen[1], pos.screen[2])) + end + end + dot_info(line_start, true) + if distinct then + line_info() + dot_info(line_end, false) + end + if distinct and opts.show_angles ~= "no" then + local dist = 50 + local pos_from_angle = function(mult, angle) + return { + line_start.screen[1] + mult * dist * math.cos(angle), + line_start.screen[2] + mult * dist * math.sin(angle), + } + end + local extended = { line_start.screen[1], line_start.screen[2] } + if line_end.screen[1] > line_start.screen[1] then + extended[1] = extended[1] + dist + else + extended[1] = extended[1] - dist + end + line(line_start.screen, extended, math.max(0, opts.line_width-0.5)) + local angle = math.atan(math.abs(line_start.image[2] - line_end.image[2]) / math.abs(line_start.image[1] - line_end.image[1])) + local fix_angle + local an + if line_end.image[2] < line_start.image[2] and line_end.image[1] > line_start.image[1] then + -- upper-right + an = 4 + fix_angle = function(angle) return - angle end + elseif line_end.image[2] < line_start.image[2] then + -- upper-left + an = 6 + fix_angle = function(angle) return math.pi + angle end + elseif line_end.image[1] < line_start.image[1] then + -- bottom-left + an = 6 + fix_angle = function(angle) return math.pi - angle end + else + -- bottom-right + an = 4 + fix_angle = function(angle) return angle end + end + -- should implement this https://math.stackexchange.com/questions/873224/calculate-control-points-of-cubic-bezier-curve-approximating-a-part-of-a-circle + local cp1 = pos_from_angle(1, fix_angle(angle*1/4)) + local cp2 = pos_from_angle(1, fix_angle(angle*3/4)) + local p2 = pos_from_angle(1, fix_angle(angle)) + a:bezier_curve(cp1[1], cp1[2], cp2[1], cp2[2], p2[1], p2[2]) + + a:new_event() + a:append("{\\fs" .. opts.font_size .."}{\\bord1}") + local text_pos = pos_from_angle(1.1, fix_angle(angle*2/3)) -- you'd think /2 would make more sense, but *2/3 looks better + a:pos(text_pos[1], text_pos[2]) + a:an(an) + if opts.show_angles == "both" then + a:append(string.format("%.2f\\N%.1f°", angle, angle / math.pi * 180)) + elseif opts.show_angles == "degrees" then + a:append(string.format("%.1f°", angle / math.pi * 180)) + elseif opts.show_angles == "radians" then + a:append(string.format("%.2f", angle)) + end + end + + draw_ass(a.text) +end + +function next() + if state == 0 then + mp.register_idle(refresh) + mp.add_forced_key_binding("mouse_move", "ruler-mouse-move", function() end) -- only used to get an idle event on mouse move + for _,key in ipairs(opts.confirm_bindings) do + mp.add_forced_key_binding(key, "ruler-next-" .. key, next) + end + for _,key in ipairs(opts.exit_bindings) do + mp.add_forced_key_binding(key, "ruler-stop-" .. key, stop) + end + state = 1 + if opts.set_first_point_on_begin then + next() + end + elseif state == 1 then + first_point = cursor_video_space() + state = 2 + elseif state == 2 then + state = 3 + second_point = cursor_video_space() + if opts.clear_on_second_point_set then + next() + end + else + stop() + end +end + +function stop() + if state == 0 then return end + mp.unregister_idle(refresh) + for _,key in ipairs(opts.confirm_bindings) do + mp.remove_key_binding("ruler-next-" .. key) + end + for _,key in ipairs(opts.exit_bindings) do + mp.remove_key_binding("ruler-stop-" .. key) + end + mp.remove_key_binding("ruler-mouse-move") + state = 0 + first_point = nil + second_point = nil + draw_ass("") +end + +mp.add_key_binding(nil, "ruler", next) diff --git a/.config/mvi/scripts/status-line.lua b/.config/mvi/scripts/status-line.lua new file mode 100644 index 0000000..bc8c129 --- /dev/null +++ b/.config/mvi/scripts/status-line.lua @@ -0,0 +1,108 @@ +local opts = { + enabled = true, + position = "bottom-left", + size = 36, + text = "${filename} [${playlist-pos-1}/${playlist-count}]", +} +(require 'mp.options').read_options(opts) + +local msg = require 'mp.msg' +local assdraw = require 'mp.assdraw' + +local stale = true + +function draw_ass(ass) + local ww, wh = mp.get_osd_size() + mp.set_osd_ass(ww, wh, ass) +end + +function refresh() + if not stale then return end + stale = false + local expanded = mp.command_native({ "expand-text", opts.text}) + if not expanded then + msg.error("Error expanding status-line") + draw_ass("") + return + end + msg.verbose("Status-line changed to: " .. expanded) + local w,h = mp.get_osd_size() + local an, x, y + local margin = 10 + if opts.position == "top-left" then + x = margin + y = margin + an = 7 + elseif opts.position == "top-right" then + x = w-margin + y = margin + an = 9 + elseif opts.position == "bottom-right" then + x = w-margin + y = h-margin + an = 3 + elseif opts.position == "bottom-left" then + x = margin + y = h-margin + an = 1 + else + msg.error("Invalid position: " .. opts.position) + return + end + local a = assdraw:ass_new() + a:new_event() + a:an(an) + a:pos(x,y) + a:append("{\\fs".. opts.size.. "}{\\bord1.0}") + a:append(expanded) + draw_ass(a.text) +end + +function mark_stale() + stale = true +end + +local active = false + +function enable() + if active then return end + active = true + local start = 0 + while true do + local s, e, cap = string.find(opts.text, "%${[?!]?([%l%d-/]*)", start) + if not s then break end + msg.verbose("Observing property " .. cap) + mp.observe_property(cap, nil, mark_stale) + start = e + end + mp.observe_property("osd-width", nil, mark_stale) + mp.observe_property("osd-height", nil, mark_stale) + mp.register_idle(refresh) + mark_stale() +end + + +function disable() + if not active then return end + active = false + mp.unobserve_property(mark_stale) + mp.unregister_idle(refresh) + ass.status_line = "" + draw_ass() +end + +function toggle() + if active then + disable() + else + enable() + end +end + +if opts.enabled then + enable() +end + +mp.add_key_binding(nil, "enable-status-line", enable) +mp.add_key_binding(nil, "disable-status-line", disable) +mp.add_key_binding(nil, "toggle-status-line", toggle) |