diff --git a/.gitignore b/.gitignore index 368e5d6..6521761 100644 --- a/.gitignore +++ b/.gitignore @@ -2,3 +2,4 @@ home/.vim/bundle/* !home/.vim/bundle/Vundle.vim home/.vim/undo/%* .uuid +home/.config/mpv/watch_later diff --git a/home/.config/mpv/input.conf b/home/.config/mpv/input.conf new file mode 100644 index 0000000..9fa73e9 --- /dev/null +++ b/home/.config/mpv/input.conf @@ -0,0 +1,17 @@ +#q quit-watch-later +y frame-step +Y frame-back-step + +# af filter bs2b (for headphones) +b af toggle "bs2b" + +## control pulse app volume instead of mpv in-app volume +#0 add ao-volume 2 +#9 add ao-volume -2 +#m cycle ao-mute +## but with Shift+(0|9|m) the internal volume can still be controlled +#= add volume 2 # Shift+0 +#) add volume -2 # Shift+9 +#Shift+m cycle mute + +k cycle-values audio-pitch-correction "no" "yes" diff --git a/home/.config/mpv/mpv.conf b/home/.config/mpv/mpv.conf new file mode 100644 index 0000000..5322c8d --- /dev/null +++ b/home/.config/mpv/mpv.conf @@ -0,0 +1,28 @@ +# Configure fonts +osd-font = 'DINW07-Medium' +sub-font = 'Roboto Slab' +# also use custom fonts for ass subtitles +sub-ass-override=force + +# don’t correct pitch of audio +#audio-pitch-correction = no + +# if surround sound: mixdown to stereo +audio-channels = stereo + +# max volume +volume-max = 150 + +# i18n +#alang = ger,de,deu,eng,en +#slang = ger,de,deu,eng,en + +# prefer vp9/opus with youtube-dl +ytdl-format=bestvideo[ext=webm]+bestaudio[ext=webm]/bestvideo+bestaudio/best + +# allow seeking in streamed media +force-seekable + +# enable hardware decoding when avaliable +#hwdec = vaapi # does not work for > 1920 x 1080; software decoding is + # efficient enough in most cases diff --git a/home/.config/mpv/script-opts/stats.conf b/home/.config/mpv/script-opts/stats.conf new file mode 100644 index 0000000..63129ae --- /dev/null +++ b/home/.config/mpv/script-opts/stats.conf @@ -0,0 +1,4 @@ +font=DINW07-Regular +font_mono=DINW07-Regular +font_size=10 +redraw_delay=0.25 diff --git a/home/.config/mpv/scripts/stats.lua b/home/.config/mpv/scripts/stats.lua new file mode 100644 index 0000000..6ee491b --- /dev/null +++ b/home/.config/mpv/scripts/stats.lua @@ -0,0 +1,754 @@ +-- Display some stats. +-- +-- Please consult the readme for information about usage and configuration: +-- https://github.com/Argon-/mpv-stats +-- +-- Please note: not every property is always available and therefore not always +-- visible. + +local mp = require 'mp' +local options = require 'mp.options' +local utils = require 'mp.utils' + +-- Options +local o = { + -- Default key bindings + key_oneshot = "i", + key_toggle = "I", + key_page_1 = "1", + key_page_2 = "2", + key_page_3 = "3", + + duration = 4, + redraw_delay = 1, -- acts as duration in the toggling case + ass_formatting = true, + persistent_overlay = false, -- whether the stats can be overwritten by other output + print_perfdata_passes = false, -- when true, print the full information about all passes + filter_params_max_length = 100, -- a filter list longer than this many characters will be shown one filter per line instead + debug = false, + + -- Graph options and style + plot_perfdata = true, + plot_vsync_ratio = true, + plot_vsync_jitter = true, + skip_frames = 5, + global_max = true, + flush_graph_data = true, -- clear data buffers when toggling + plot_bg_border_color = "0000FF", + plot_bg_color = "262626", + plot_color = "FFFFFF", + + -- Text style + font = "Source Sans Pro", + font_mono = "Source Sans Pro", -- monospaced digits are sufficient + font_size = 8, + font_color = "FFFFFF", + border_size = 0.8, + border_color = "262626", + shadow_x_offset = 0.0, + shadow_y_offset = 0.0, + shadow_color = "000000", + alpha = "11", + + -- Custom header for ASS tags to style the text output. + -- Specifying this will ignore the text style values above and just + -- use this string instead. + custom_header = "", + + -- Text formatting + -- With ASS + ass_nl = "\\N", + ass_indent = "\\h\\h\\h\\h\\h", + ass_prefix_sep = "\\h\\h", + ass_b1 = "{\\b1}", + ass_b0 = "{\\b0}", + ass_it1 = "{\\i1}", + ass_it0 = "{\\i0}", + -- Without ASS + no_ass_nl = "\n", + no_ass_indent = "\t", + no_ass_prefix_sep = " ", + no_ass_b1 = "\027[1m", + no_ass_b0 = "\027[0m", + no_ass_it1 = "\027[3m", + no_ass_it0 = "\027[0m", +} +options.read_options(o) + +local format = string.format +local max = math.max +local min = math.min + +-- Function used to record performance data +local recorder = nil +-- Timer used for redrawing (toggling) and clearing the screen (oneshot) +local display_timer = nil +-- Current page and : mappings +local curr_page = o.key_page_1 +local pages = {} +-- Save these sequences locally as we'll need them a lot +local ass_start = mp.get_property_osd("osd-ass-cc/0") +local ass_stop = mp.get_property_osd("osd-ass-cc/1") +-- Ring buffers for the values used to construct a graph. +-- .pos denotes the current position, .len the buffer length +-- .max is the max value in the corresponding buffer +local vsratio_buf, vsjitter_buf +local function init_buffers() + vsratio_buf = {0, pos = 1, len = 50, max = 0} + vsjitter_buf = {0, pos = 1, len = 50, max = 0} +end +-- Save all properties known to this version of mpv +local property_list = {} +for p in string.gmatch(mp.get_property("property-list"), "([^,]+)") do property_list[p] = true end +-- Mapping of properties to their deprecated names +local property_aliases = { + ["decoder-frame-drop-count"] = "drop-frame-count", + ["frame-drop-count"] = "vo-drop-frame-count", + ["container-fps"] = "fps", +} + + +-- Return deprecated name for the given property +local function compat(p) + while not property_list[p] and property_aliases[p] do + p = property_aliases[p] + end + return p +end + + +local function set_ASS(b) + if not o.use_ass or o.persistent_overlay then + return "" + end + return b and ass_start or ass_stop +end + + +local function no_ASS(t) + return set_ASS(false) .. t .. set_ASS(true) +end + + +local function b(t) + return o.b1 .. t .. o.b0 +end + + +local function it(t) + return o.it1 .. t .. o.it0 +end + + +local function text_style() + if not o.use_ass then + return "" + end + if o.custom_header and o.custom_header ~= "" then + return set_ASS(true) .. o.custom_header + else + return format("%s{\\r}{\\an7}{\\fs%d}{\\fn%s}{\\bord%f}{\\3c&H%s&}" .. + "{\\1c&H%s&}{\\alpha&H%s&}{\\xshad%f}{\\yshad%f}{\\4c&H%s&}", + set_ASS(true), o.font_size, o.font, o.border_size, + o.border_color, o.font_color, o.alpha, o.shadow_x_offset, + o.shadow_y_offset, o.shadow_color) + end +end + + +local function has_vo_window() + return mp.get_property("vo-configured") == "yes" +end + + +local function has_ansi() + local is_windows = type(package) == 'table' + and type(package.config) == 'string' + and package.config:sub(1, 1) == '\\' + if is_windows then + return os.getenv("ANSICON") + end + return true +end + + +-- Generate a graph from the given values. +-- Returns an ASS formatted vector drawing as string. +-- +-- values: Array/table of numbers representing the data. Used like a ring buffer +-- it will get iterated backwards `len` times starting at position `i`. +-- i : Index of the latest data value in `values`. +-- len : The length/amount of numbers in `values`. +-- v_max : The maximum number in `values`. It is used to scale all data +-- values to a range of 0 to `v_max`. +-- v_avg : The average number in `values`. It is used to try and center graphs +-- if possible. May be left as nil +-- scale : A value that will be multiplied with all data values. +-- x_tics: Horizontal width multiplier for the steps +local function generate_graph(values, i, len, v_max, v_avg, scale, x_tics) + -- Check if at least one value exists + if not values[i] then + return "" + end + + local x_max = (len - 1) * x_tics + local y_offset = o.border_size + local y_max = o.font_size * 0.66 + local x = 0 + + -- try and center the graph if possible, but avoid going above `scale` + if v_avg then + scale = min(scale, v_max / (2 * v_avg)) + end + + local s = {format("m 0 0 n %f %f l ", x, y_max - (y_max * values[i] / v_max * scale))} + i = ((i - 2) % len) + 1 + + for p = 1, len - 1 do + if values[i] then + x = x - x_tics + s[#s+1] = format("%f %f ", x, y_max - (y_max * values[i] / v_max * scale)) + end + i = ((i - 2) % len) + 1 + end + + s[#s+1] = format("%f %f %f %f", x, y_max, 0, y_max) + + local bg_box = format("{\\bord0.5}{\\3c&H%s&}{\\1c&H%s&}m 0 %f l %f %f %f 0 0 0", + o.plot_bg_border_color, o.plot_bg_color, y_max, x_max, y_max, x_max) + return format("%s{\\r}{\\pbo%f}{\\shad0}{\\alpha&H00}{\\p1}%s{\\p0}{\\bord0}{\\1c&H%s}{\\p1}%s{\\p0}%s", + o.prefix_sep, y_offset, bg_box, o.plot_color, table.concat(s), text_style()) +end + + +local function append(s, str, attr) + if not str then + return false + end + attr.prefix_sep = attr.prefix_sep or o.prefix_sep + attr.indent = attr.indent or o.indent + attr.nl = attr.nl or o.nl + attr.suffix = attr.suffix or "" + attr.prefix = attr.prefix or "" + attr.no_prefix_markup = attr.no_prefix_markup or false + attr.prefix = attr.no_prefix_markup and attr.prefix or b(attr.prefix) + s[#s+1] = format("%s%s%s%s%s%s", attr.nl, attr.indent, + attr.prefix, attr.prefix_sep, no_ASS(str), attr.suffix) + return true +end + + +-- Format and append a property. +-- A property whose value is either `nil` or empty (hereafter called "invalid") +-- is skipped and not appended. +-- Returns `false` in case nothing was appended, otherwise `true`. +-- +-- s : Table containing strings. +-- prop : The property to query and format (based on its OSD representation). +-- attr : Optional table to overwrite certain (formatting) attributes for +-- this property. +-- exclude: Optional table containing keys which are considered invalid values +-- for this property. Specifying this will replace empty string as +-- default invalid value (nil is always invalid). +local function append_property(s, prop, attr, excluded) + excluded = excluded or {[""] = true} + local ret = mp.get_property_osd(prop) + if not ret or excluded[ret] then + if o.debug then + print("No value for property: " .. prop) + end + return false + end + return append(s, ret, attr) +end + + +local function append_perfdata(s, dedicated_page) + local vo_p = mp.get_property_native("vo-passes") + if not vo_p then + return + end + + local ds = mp.get_property_bool("display-sync-active", false) + local target_fps = ds and mp.get_property_number("display-fps", 0) + or mp.get_property_number(compat("container-fps"), 0) + if target_fps > 0 then target_fps = 1 / target_fps * 1e9 end + + -- Sums of all last/avg/peak values + local last_s, avg_s, peak_s = {}, {}, {} + for frame, data in pairs(vo_p) do + last_s[frame], avg_s[frame], peak_s[frame] = 0, 0, 0 + for _, pass in ipairs(data) do + last_s[frame] = last_s[frame] + pass["last"] + avg_s[frame] = avg_s[frame] + pass["avg"] + peak_s[frame] = peak_s[frame] + pass["peak"] + end + end + + -- Pretty print measured time + local function pp(i) + -- rescale to microseconds for a saner display + return format("%05d", i / 1000) + end + + -- Format n/m with a font weight based on the ratio + local function p(n, m) + local i = 0 + if m > 0 then + i = tonumber(n) / m + end + -- Calculate font weight. 100 is minimum, 400 is normal, 700 bold, 900 is max + local w = (700 * math.sqrt(i)) + 200 + return format("{\\b%d}%02d%%{\\b0}", w, i * 100) + end + + s[#s+1] = format("%s%s%s%s{\\fs%s}%s{\\fs%s}", + dedicated_page and "" or o.nl, dedicated_page and "" or o.indent, + b("Frame Timings:"), o.prefix_sep, o.font_size * 0.66, + "(last/average/peak μs)", o.font_size) + + for frame, data in pairs(vo_p) do + local f = "%s%s%s{\\fn%s}%s / %s / %s %s%s{\\fn%s}%s%s%s" + + if dedicated_page then + s[#s+1] = format("%s%s%s:", o.nl, o.indent, + b(frame:gsub("^%l", string.upper))) + + for _, pass in ipairs(data) do + s[#s+1] = format(f, o.nl, o.indent, o.indent, + o.font_mono, pp(pass["last"]), + pp(pass["avg"]), pp(pass["peak"]), + o.prefix_sep .. o.prefix_sep, p(pass["last"], last_s[frame]), + o.font, o.prefix_sep, o.prefix_sep, pass["desc"]) + + if o.plot_perfdata and o.use_ass then + s[#s+1] = generate_graph(pass["samples"], pass["count"], + pass["count"], pass["peak"], + pass["avg"], 0.9, 0.25) + end + end + + -- Print sum of timing values as "Total" + s[#s+1] = format(f, o.nl, o.indent, o.indent, + o.font_mono, pp(last_s[frame]), + pp(avg_s[frame]), pp(peak_s[frame]), "", "", o.font, + o.prefix_sep, o.prefix_sep, b("Total")) + else + -- for the simplified view, we just print the sum of each pass + s[#s+1] = format(f, o.nl, o.indent, o.indent, o.font_mono, + pp(last_s[frame]), pp(avg_s[frame]), pp(peak_s[frame]), + "", "", o.font, o.prefix_sep, o.prefix_sep, + frame:gsub("^%l", string.upper)) + end + end +end + + +local function append_display_sync(s) + if not mp.get_property_bool("display-sync-active", false) then + return + end + + local vspeed = append_property(s, "video-speed-correction", {prefix="DS:"}) + if vspeed then + append_property(s, "audio-speed-correction", + {prefix="/", nl="", indent=" ", prefix_sep=" ", no_prefix_markup=true}) + else + append_property(s, "audio-speed-correction", + {prefix="DS:" .. o.prefix_sep .. " - / ", prefix_sep=""}) + end + + append_property(s, "mistimed-frame-count", {prefix="Mistimed:", nl=""}) + append_property(s, "vo-delayed-frame-count", {prefix="Delayed:", nl=""}) + + -- As we need to plot some graphs we print jitter and ratio on their own lines + if not display_timer.oneshot and (o.plot_vsync_ratio or o.plot_vsync_jitter) and o.use_ass then + local ratio_graph = "" + local jitter_graph = "" + if o.plot_vsync_ratio then + ratio_graph = generate_graph(vsratio_buf, vsratio_buf.pos, vsratio_buf.len, vsratio_buf.max, nil, 0.8, 1) + end + if o.plot_vsync_jitter then + jitter_graph = generate_graph(vsjitter_buf, vsjitter_buf.pos, vsjitter_buf.len, vsjitter_buf.max, nil, 0.8, 1) + end + append_property(s, "vsync-ratio", {prefix="VSync Ratio:", suffix=o.prefix_sep .. ratio_graph}) + append_property(s, "vsync-jitter", {prefix="VSync Jitter:", suffix=o.prefix_sep .. jitter_graph}) + else + -- Since no graph is needed we can print ratio/jitter on the same line and save some space + local vratio = append_property(s, "vsync-ratio", {prefix="VSync Ratio:"}) + append_property(s, "vsync-jitter", {prefix="VSync Jitter:", nl="" or o.nl}) + end +end + + +local function append_filters(s, prop, prefix) + local length = 0 + local filters = {} + + for _,f in ipairs(mp.get_property_native(prop, {})) do + local n = f.name + if f.enabled ~= nil and not f.enabled then + n = n .. " (disabled)" + end + + local p = {} + for key,value in pairs(f.params) do + p[#p+1] = key .. "=" .. value + end + if #p > 0 then + p = " [" .. table.concat(p, " ") .. "]" + else + p = "" + end + + length = length + n:len() + p:len() + filters[#filters+1] = no_ASS(n) .. it(no_ASS(p)) + end + + if #filters > 0 then + local ret + if length < o.filter_params_max_length then + ret = table.concat(filters, ", ") + else + local sep = o.nl .. o.indent .. o.indent + ret = sep .. table.concat(filters, sep) + end + s[#s+1] = o.nl .. o.indent .. b(prefix) .. o.prefix_sep .. ret + end +end + + +local function add_header(s) + s[#s+1] = text_style() +end + + +local function add_file(s) + append(s, "", {prefix="File:", nl="", indent=""}) + append_property(s, "filename", {prefix_sep="", nl="", indent=""}) + if not (mp.get_property_osd("filename") == mp.get_property_osd("media-title")) then + append_property(s, "media-title", {prefix="Title:"}) + end + + local ch_index = mp.get_property_number("chapter") + if ch_index and ch_index >= 0 then + append_property(s, "chapter-list/" .. tostring(ch_index) .. "/title", {prefix="Chapter:"}) + append_property(s, "chapter-list/count", + {prefix="(" .. tostring(ch_index + 1) .. "/", suffix=")", nl="", + indent=" ", prefix_sep=" ", no_prefix_markup=true}) + end + + local demuxer_cache = mp.get_property_native("demuxer-cache-state", {}) + if demuxer_cache["fw-bytes"] then + demuxer_cache = demuxer_cache["fw-bytes"] -- returns bytes + else + demuxer_cache = 0 + end + local demuxer_secs = mp.get_property_number("demuxer-cache-duration", 0) + local stream_cache = mp.get_property_number("cache-used", 0) * 1024 -- returns KiB + if stream_cache + demuxer_cache + demuxer_secs > 0 then + append(s, utils.format_bytes_humanized(stream_cache + demuxer_cache), {prefix="Total Cache:"}) + append(s, utils.format_bytes_humanized(demuxer_cache), {prefix="(Demuxer:", + suffix=",", nl="", no_prefix_markup=true, indent=o.prefix_sep}) + append(s, format("%.1f", demuxer_secs), {suffix=" sec)", nl="", indent="", + no_prefix_markup=true}) + local speed = mp.get_property_number("cache-speed", 0) + if speed > 0 then + append(s, utils.format_bytes_humanized(speed) .. "/s", {prefix="Speed:", nl="", + indent=o.prefix_sep, no_prefix_markup=true}) + end + end + append_property(s, "file-size", {prefix="Size:"}) +end + + +local function add_video(s) + local r = mp.get_property_native("video-params") + -- in case of e.g. lavi-complex there can be no input video, only output + if not r then + r = mp.get_property_native("video-out-params") + end + if not r then + return + end + + append(s, "", {prefix=o.nl .. o.nl .. "Video:", nl="", indent=""}) + if append_property(s, "video-codec", {prefix_sep="", nl="", indent=""}) then + append_property(s, "hwdec-current", {prefix="(hwdec:", nl="", indent=" ", + no_prefix_markup=true, suffix=")"}, {no=true, [""]=true}) + end + append_property(s, "avsync", {prefix="A-V:"}) + if append_property(s, compat("decoder-frame-drop-count"), + {prefix="Dropped Frames:", suffix=" (decoder)"}) then + append_property(s, compat("frame-drop-count"), {suffix=" (output)", nl="", indent=""}) + end + if append_property(s, "display-fps", {prefix="Display FPS:", suffix=" (specified)"}) then + append_property(s, "estimated-display-fps", + {suffix=" (estimated)", nl="", indent=""}) + else + append_property(s, "estimated-display-fps", + {prefix="Display FPS:", suffix=" (estimated)"}) + end + if append_property(s, compat("container-fps"), {prefix="FPS:", suffix=" (specified)"}) then + append_property(s, "estimated-vf-fps", + {suffix=" (estimated)", nl="", indent=""}) + else + append_property(s, "estimated-vf-fps", + {prefix="FPS:", suffix=" (estimated)"}) + end + + append_display_sync(s) + append_perfdata(s, o.print_perfdata_passes) + + if append(s, r["w"], {prefix="Native Resolution:"}) then + append(s, r["h"], {prefix="x", nl="", indent=" ", prefix_sep=" ", no_prefix_markup=true}) + end + append_property(s, "window-scale", {prefix="Window Scale:"}) + append(s, format("%.2f", r["aspect"]), {prefix="Aspect Ratio:"}) + append(s, r["pixelformat"], {prefix="Pixel Format:"}) + + -- Group these together to save vertical space + local prim = append(s, r["primaries"], {prefix="Primaries:"}) + local cmat = append(s, r["colormatrix"], {prefix="Colormatrix:", nl=prim and "" or o.nl}) + append(s, r["colorlevels"], {prefix="Levels:", nl=cmat and "" or o.nl}) + + -- Append HDR metadata conditionally (only when present and interesting) + local hdrpeak = r["sig-peak"] or 0 + local hdrinfo = "" + if hdrpeak > 1 then + hdrinfo = " (HDR peak: " .. format("%.2f", hdrpeak) .. ")" + end + + append(s, r["gamma"], {prefix="Gamma:", suffix=hdrinfo}) + append_property(s, "packet-video-bitrate", {prefix="Bitrate:", suffix=" kbps"}) + append_filters(s, "vf", "Filters:") +end + + +local function add_audio(s) + local r = mp.get_property_native("audio-params") + -- in case of e.g. lavi-complex there can be no input audio, only output + if not r then + r = mp.get_property_native("audio-out-params") + end + if not r then + return + end + + append(s, "", {prefix=o.nl .. o.nl .. "Audio:", nl="", indent=""}) + append_property(s, "audio-codec", {prefix_sep="", nl="", indent=""}) + append(s, r["format"], {prefix="Format:"}) + append(s, r["samplerate"], {prefix="Sample Rate:", suffix=" Hz"}) + append(s, r["channel-count"], {prefix="Channels:"}) + append_property(s, "packet-audio-bitrate", {prefix="Bitrate:", suffix=" kbps"}) + append_filters(s, "af", "Filters:") +end + + +-- Determine whether ASS formatting shall/can be used and set formatting sequences +local function eval_ass_formatting() + o.use_ass = o.ass_formatting and has_vo_window() + if o.use_ass then + o.nl = o.ass_nl + o.indent = o.ass_indent + o.prefix_sep = o.ass_prefix_sep + o.b1 = o.ass_b1 + o.b0 = o.ass_b0 + o.it1 = o.ass_it1 + o.it0 = o.ass_it0 + else + o.nl = o.no_ass_nl + o.indent = o.no_ass_indent + o.prefix_sep = o.no_ass_prefix_sep + if not has_ansi() then + o.b1 = "" + o.b0 = "" + o.it1 = "" + o.it0 = "" + else + o.b1 = o.no_ass_b1 + o.b0 = o.no_ass_b0 + o.it1 = o.no_ass_it1 + o.it0 = o.no_ass_it0 + end + end +end + + +-- Returns an ASS string with "normal" stats +local function default_stats() + local stats = {} + eval_ass_formatting() + add_header(stats) + add_file(stats) + add_video(stats) + add_audio(stats) + return table.concat(stats) +end + + +-- Returns an ASS string with extended VO stats +local function vo_stats() + local stats = {} + eval_ass_formatting() + add_header(stats) + append_perfdata(stats, true) + return table.concat(stats) +end + + +-- Returns an ASS string with stats about filters/profiles/shaders +local function filter_stats() + return "coming soon" +end + + +-- Current page and : mapping +curr_page = o.key_page_1 +pages = { + [o.key_page_1] = { f = default_stats, desc = "Default" }, + [o.key_page_2] = { f = vo_stats, desc = "Extended Frame Timings" }, + --[o.key_page_3] = { f = filter_stats, desc = "Dummy" }, +} + + +-- Returns a function to record vsratio/jitter with the specified `skip` value +local function record_data(skip) + init_buffers() + skip = max(skip, 0) + local i = skip + return function() + if i < skip then + i = i + 1 + return + else + i = 0 + end + + if o.plot_vsync_jitter then + local r = mp.get_property_number("vsync-jitter", nil) + if r then + vsjitter_buf.pos = (vsjitter_buf.pos % vsjitter_buf.len) + 1 + vsjitter_buf[vsjitter_buf.pos] = r + vsjitter_buf.max = max(vsjitter_buf.max, r) + end + end + + if o.plot_vsync_ratio then + local r = mp.get_property_number("vsync-ratio", nil) + if r then + vsratio_buf.pos = (vsratio_buf.pos % vsratio_buf.len) + 1 + vsratio_buf[vsratio_buf.pos] = r + vsratio_buf.max = max(vsratio_buf.max, r) + end + end + end +end + + +-- Call the function for `page` and print it to OSD +local function print_page(page) + if o.persistent_overlay then + mp.set_osd_ass(0, 0, pages[page].f()) + else + mp.osd_message(pages[page].f(), display_timer.oneshot and o.duration or o.redraw_delay + 1) + end +end + + +local function clear_screen() + if o.persistent_overlay then mp.set_osd_ass(0, 0, "") else mp.osd_message("", 0) end +end + + +-- Add keybindings for every page +local function add_page_bindings() + local function a(k) + return function() + curr_page = k + print_page(k) + if display_timer.oneshot then display_timer:kill() ; display_timer:resume() end + end + end + for k, _ in pairs(pages) do + mp.add_forced_key_binding(k, k, a(k), {repeatable=true}) + end +end + + +-- Remove keybindings for every page +local function remove_page_bindings() + for k, _ in pairs(pages) do + mp.remove_key_binding(k) + end +end + + +local function process_key_binding(oneshot) + -- Stats are already being displayed + if display_timer:is_enabled() then + -- Previous and current keys were oneshot -> restart timer + if display_timer.oneshot and oneshot then + display_timer:kill() + print_page(curr_page) + display_timer:resume() + -- Previous and current keys were toggling -> end toggling + elseif not display_timer.oneshot and not oneshot then + display_timer:kill() + clear_screen() + remove_page_bindings() + if recorder then + mp.unregister_event(recorder) + recorder = nil + end + end + -- No stats are being displayed yet + else + if not oneshot and (o.plot_vsync_jitter or o.plot_vsync_ratio) then + recorder = record_data(o.skip_frames) + mp.register_event("tick", recorder) + end + display_timer:kill() + display_timer.oneshot = oneshot + display_timer.timeout = oneshot and o.duration or o.redraw_delay + add_page_bindings() + print_page(curr_page) + display_timer:resume() + end +end + + +-- Create the timer used for redrawing (toggling) or clearing the screen (oneshot) +-- The duration here is not important and always set in process_key_binding() +display_timer = mp.add_periodic_timer(o.duration, + function() + if display_timer.oneshot then + display_timer:kill() ; clear_screen() ; remove_page_bindings() + else + print_page(curr_page) + end + end) +display_timer:kill() + +-- Single invocation key binding +mp.add_key_binding(o.key_oneshot, "display-stats", function() process_key_binding(true) end, + {repeatable=true}) + +-- Toggling key binding +mp.add_key_binding(o.key_toggle, "display-stats-toggle", function() process_key_binding(false) end, + {repeatable=false}) + +-- Single invocation bindings without key, can be used in input.conf to create +-- bindings for a specific page: "e script-binding stats/display-page-2" +for k, _ in pairs(pages) do + mp.add_key_binding(nil, "display-page-" .. k, function() process_key_binding(true) end, + {repeatable=true}) +end + +-- Reprint stats immediately when VO was reconfigured, only when toggled +mp.register_event("video-reconfig", + function() + if display_timer:is_enabled() then + print_page(curr_page) + end + end) diff --git a/home/.config/mpv/scripts/webm.lua b/home/.config/mpv/scripts/webm.lua new file mode 100644 index 0000000..f7b7588 --- /dev/null +++ b/home/.config/mpv/scripts/webm.lua @@ -0,0 +1,1457 @@ +-- based on https://github.com/ElegantMonkey/mpv-webm but changed to h264 +local mp = require("mp") +local assdraw = require("mp.assdraw") +local msg = require("mp.msg") +local utils = require("mp.utils") +local mpopts = require("mp.options") +local options = { + keybind = "W", + output_directory = "", + run_detached = false, + output_template = "%F-[%s-%e]%M", + scale_height = -1, + output_format = "h264", + additional_flags = "", + font_size = 28, + margin = 10, + message_duration = 5 +} +mpopts.read_options(options) +local bold +bold = function(text) + return "{\\b1}" .. tostring(text) .. "{\\b0}" +end +local message +message = function(text, duration) + local ass = mp.get_property_osd("osd-ass-cc/0") + ass = ass .. text + return mp.osd_message(ass, duration or options.message_duration) +end +local append +append = function(a, b) + for _, val in ipairs(b) do + a[#a + 1] = val + end + return a +end +local seconds_to_time_string +seconds_to_time_string = function(seconds, no_ms, full) + if seconds < 0 then + return "unknown" + end + local ret = "" + if not (no_ms) then + ret = string.format(".%03d", seconds * 1000 % 1000) + end + ret = string.format("%02d:%02d%s", math.floor(seconds / 60) % 60, math.floor(seconds) % 60, ret) + if full or seconds > 3600 then + ret = string.format("%d:%s", math.floor(seconds / 3600), ret) + end + return ret +end +local seconds_to_path_element +seconds_to_path_element = function(seconds, no_ms, full) + local time_string = seconds_to_time_string(seconds, no_ms, full) + local _ + time_string, _ = time_string:gsub(":", ".") + return time_string +end +local file_exists +file_exists = function(name) + local f = io.open(name, "r") + if f ~= nil then + io.close(f) + return true + end + return false +end +local format_filename +format_filename = function(startTime, endTime, videoFormat) + local replaceTable = { + ["%%f"] = mp.get_property("filename"), + ["%%F"] = mp.get_property("filename/no-ext"), + ["%%s"] = seconds_to_path_element(startTime), + ["%%S"] = seconds_to_path_element(startTime, true), + ["%%e"] = seconds_to_path_element(endTime), + ["%%E"] = seconds_to_path_element(endTime, true), + ["%%T"] = mp.get_property("media-title"), + ["%%M"] = (mp.get_property_native('aid') and not mp.get_property_native('mute')) and '-audio' or '' + } + local filename = options.output_template + for format, value in pairs(replaceTable) do + local _ + filename, _ = filename:gsub(format, value) + end + local _ + filename, _ = filename:gsub("[<>:\"/\\|?*]", "") + return tostring(filename) .. "." .. tostring(videoFormat.outputExtension) +end +local parse_directory +parse_directory = function(dir) + local home_dir = os.getenv("HOME") + if not home_dir then + home_dir = os.getenv("USERPROFILE") + end + if not home_dir then + local drive = os.getenv("HOMEDRIVE") + local path = os.getenv("HOMEPATH") + if drive and path then + home_dir = utils.join_path(drive, path) + else + msg.warn("Couldn't find home dir.") + home_dir = "" + end + end + local _ + dir, _ = dir:gsub("^~", home_dir) + return dir +end +local get_null_path +get_null_path = function() + if file_exists("/dev/null") then + return "/dev/null" + end + return "NUL" +end +local run_subprocess +run_subprocess = function(params) + local res = utils.subprocess(params) + if res.status ~= 0 then + msg.verbose("Command failed! Reason: ", res.error, " Killed by us? ", res.killed_by_us and "yes" or "no") + msg.verbose("Command stdout: ") + msg.verbose(res.stdout) + return false + end + return true +end +local calculate_scale_factor +calculate_scale_factor = function() + local baseResY = 720 + local osd_w, osd_h = mp.get_osd_size() + return osd_h / baseResY +end +local dimensions_changed = true +local _video_dimensions = { } +local get_video_dimensions +get_video_dimensions = function() + if not (dimensions_changed) then + return _video_dimensions + end + local video_params = mp.get_property_native("video-out-params") + if not video_params then + return nil + end + dimensions_changed = false + local keep_aspect = mp.get_property_bool("keepaspect") + local w = video_params["w"] + local h = video_params["h"] + local dw = video_params["dw"] + local dh = video_params["dh"] + if mp.get_property_number("video-rotate") % 180 == 90 then + w, h = h, w + dw, dh = dh, dw + end + _video_dimensions = { + top_left = { }, + bottom_right = { }, + ratios = { } + } + local window_w, window_h = mp.get_osd_size() + if keep_aspect then + local unscaled = mp.get_property_native("video-unscaled") + local panscan = mp.get_property_number("panscan") + local fwidth = window_w + local fheight = math.floor(window_w / dw * dh) + if fheight > window_h or fheight < h then + local tmpw = math.floor(window_h / dh * dw) + if tmpw <= window_w then + fheight = window_h + fwidth = tmpw + end + end + local vo_panscan_area = window_h - fheight + local f_w = fwidth / fheight + local f_h = 1 + if vo_panscan_area == 0 then + vo_panscan_area = window_h - fwidth + f_w = 1 + f_h = fheight / fwidth + end + if unscaled or unscaled == "downscale-big" then + vo_panscan_area = 0 + if unscaled or (dw <= window_w and dh <= window_h) then + fwidth = dw + fheight = dh + end + end + local scaled_width = fwidth + math.floor(vo_panscan_area * panscan * f_w) + local scaled_height = fheight + math.floor(vo_panscan_area * panscan * f_h) + local split_scaling + split_scaling = function(dst_size, scaled_src_size, zoom, align, pan) + scaled_src_size = math.floor(scaled_src_size * 2 ^ zoom) + align = (align + 1) / 2 + local dst_start = math.floor((dst_size - scaled_src_size) * align + pan * scaled_src_size) + if dst_start < 0 then + dst_start = dst_start + 1 + end + local dst_end = dst_start + scaled_src_size + if dst_start >= dst_end then + dst_start = 0 + dst_end = 1 + end + return dst_start, dst_end + end + local zoom = mp.get_property_number("video-zoom") + local align_x = mp.get_property_number("video-align-x") + local pan_x = mp.get_property_number("video-pan-x") + _video_dimensions.top_left.x, _video_dimensions.bottom_right.x = split_scaling(window_w, scaled_width, zoom, align_x, pan_x) + local align_y = mp.get_property_number("video-align-y") + local pan_y = mp.get_property_number("video-pan-y") + _video_dimensions.top_left.y, _video_dimensions.bottom_right.y = split_scaling(window_h, scaled_height, zoom, align_y, pan_y) + else + _video_dimensions.top_left.x = 0 + _video_dimensions.bottom_right.x = window_w + _video_dimensions.top_left.y = 0 + _video_dimensions.bottom_right.y = window_h + end + _video_dimensions.ratios.w = w / (_video_dimensions.bottom_right.x - _video_dimensions.top_left.x) + _video_dimensions.ratios.h = h / (_video_dimensions.bottom_right.y - _video_dimensions.top_left.y) + return _video_dimensions +end +local set_dimensions_changed +set_dimensions_changed = function() + dimensions_changed = true +end +local clamp +clamp = function(min, val, max) + if val <= min then + return min + end + if val >= max then + return max + end + return val +end +local clamp_point +clamp_point = function(top_left, point, bottom_right) + return { + x = clamp(top_left.x, point.x, bottom_right.x), + y = clamp(top_left.y, point.y, bottom_right.y) + } +end +local VideoPoint +do + local _class_0 + local _base_0 = { + set_from_screen = function(self, sx, sy) + local d = get_video_dimensions() + local point = clamp_point(d.top_left, { + x = sx, + y = sy + }, d.bottom_right) + self.x = math.floor(d.ratios.w * (point.x - d.top_left.x) + 0.5) + self.y = math.floor(d.ratios.h * (point.y - d.top_left.y) + 0.5) + end, + to_screen = function(self) + local d = get_video_dimensions() + return { + x = math.floor(self.x / d.ratios.w + d.top_left.x + 0.5), + y = math.floor(self.y / d.ratios.h + d.top_left.y + 0.5) + } + end + } + _base_0.__index = _base_0 + _class_0 = setmetatable({ + __init = function(self) + self.x = -1 + self.y = -1 + end, + __base = _base_0, + __name = "VideoPoint" + }, { + __index = _base_0, + __call = function(cls, ...) + local _self_0 = setmetatable({}, _base_0) + cls.__init(_self_0, ...) + return _self_0 + end + }) + _base_0.__class = _class_0 + VideoPoint = _class_0 +end +local Region +do + local _class_0 + local _base_0 = { + is_valid = function(self) + return self.x > -1 and self.y > -1 and self.w > -1 and self.h > -1 + end, + set_from_points = function(self, p1, p2) + self.x = math.min(p1.x, p2.x) + self.y = math.min(p1.y, p2.y) + self.w = math.abs(p1.x - p2.x) + self.h = math.abs(p1.y - p2.y) + end + } + _base_0.__index = _base_0 + _class_0 = setmetatable({ + __init = function(self) + self.x = -1 + self.y = -1 + self.w = -1 + self.h = -1 + end, + __base = _base_0, + __name = "Region" + }, { + __index = _base_0, + __call = function(cls, ...) + local _self_0 = setmetatable({}, _base_0) + cls.__init(_self_0, ...) + return _self_0 + end + }) + _base_0.__class = _class_0 + Region = _class_0 +end +local formats = { } +local Format +do + local _class_0 + local _base_0 = { + getPreFilters = function(self) + return { } + end, + getPostFilters = function(self) + return { } + end, + getFlags = function(self) + return { } + end + } + _base_0.__index = _base_0 + _class_0 = setmetatable({ + __init = function(self) + self.displayName = "Basic" + self.supportsTwopass = true + self.videoCodec = "" + self.audioCodec = "" + self.outputExtension = "" + self.acceptsBitrate = true + end, + __base = _base_0, + __name = "Format" + }, { + __index = _base_0, + __call = function(cls, ...) + local _self_0 = setmetatable({}, _base_0) + cls.__init(_self_0, ...) + return _self_0 + end + }) + _base_0.__class = _class_0 + Format = _class_0 +end +local H264 +do + local _class_0 + local _parent_0 = Format + local _base_0 = { } + _base_0.__index = _base_0 + setmetatable(_base_0, _parent_0.__base) + _class_0 = setmetatable({ + __init = function(self) + self.displayName = "H.264" + self.supportsTwopass = false + self.videoCodec = "libx264" + self.audioCodec = "aac" + self.outputExtension = "mp4" + self.acceptsBitrate = false + end, + __base = _base_0, + __name = "H264", + __parent = _parent_0 + }, { + __index = function(cls, name) + local val = rawget(_base_0, name) + if val == nil then + local parent = rawget(cls, "__parent") + if parent then + return parent[name] + end + else + return val + end + end, + __call = function(cls, ...) + local _self_0 = setmetatable({}, _base_0) + cls.__init(_self_0, ...) + return _self_0 + end + }) + _base_0.__class = _class_0 + if _parent_0.__inherited then + _parent_0.__inherited(_parent_0, _class_0) + end + H264 = _class_0 +end +formats["h264"] = H264() +local get_active_tracks +get_active_tracks = function() + local accepted = { + video = true, + audio = not mp.get_property_bool("mute"), + sub = mp.get_property_bool("sub-visibility") + } + local active = { } + for _, track in ipairs(mp.get_property_native("track-list")) do + if track["selected"] and accepted[track["type"]] then + active[#active + 1] = track + end + end + return active +end +local get_scale_filters +get_scale_filters = function() + if options.scale_height > 0 then + return { + "scale=-1:" .. tostring(options.scale_height) + } + end + return { } +end +local append_property +append_property = function(out, property_name, option_name) + option_name = option_name or property_name + local prop = mp.get_property(property_name) + if prop and prop ~= "" then + return append(out, { + "--" .. tostring(option_name) .. "=" .. tostring(prop) + }) + end +end +local append_list_options +append_list_options = function(out, property_name, option_prefix) + option_prefix = option_prefix or property_name + local prop = mp.get_property_native(property_name) + if prop then + for _index_0 = 1, #prop do + local value = prop[_index_0] + append(out, { + "--" .. tostring(option_prefix) .. "-append=" .. tostring(value) + }) + end + end +end +local get_playback_options +get_playback_options = function() + local ret = { } + append_property(ret, "sub-ass-override") + append_property(ret, "sub-ass-force-style") + append_property(ret, "sub-auto") + for _, track in ipairs(mp.get_property_native("track-list")) do + if track["type"] == "sub" and track["external"] then + append(ret, { + "--sub-files-append=" .. tostring(track['external-filename']) + }) + end + end + return ret +end +local encode +encode = function(region, startTime, endTime) + local format = formats[options.output_format] + local path = mp.get_property("path") + if not path then + message("No file is being played") + return + end + local is_stream = not file_exists(path) + local command = { + "mpv", + path, + "--start=" .. seconds_to_time_string(startTime, false, true), + "--end=" .. seconds_to_time_string(endTime, false, true), + "--ovc=" .. tostring(format.videoCodec), + "--oac=" .. tostring(format.audioCodec), + "--loop-file=no" + } + local vid = -1 + local aid = -1 + local sid = -1 + for _, track in ipairs(get_active_tracks()) do + local _exp_0 = track["type"] + if "video" == _exp_0 then + vid = track['id'] + elseif "audio" == _exp_0 then + aid = track['id'] + elseif "sub" == _exp_0 then + sid = track['id'] + end + end + append(command, { + "--vid=" .. (vid >= 0 and tostring(vid) or "no"), + "--aid=" .. (aid >= 0 and tostring(aid) or "no"), + "--sid=" .. (sid >= 0 and tostring(sid) or "no") + }) + append(command, get_playback_options()) + local filters = { } + append(filters, format:getPreFilters()) + if region and region:is_valid() then + append(filters, { + "crop=" .. tostring(region.w) .. ":" .. tostring(region.h) .. ":" .. tostring(region.x) .. ":" .. tostring(region.y) + }) + end + append(filters, get_scale_filters()) + append(filters, format:getPostFilters()) + if #filters > 0 then + append(command, { + "--vf", + "lavfi=[" .. tostring(table.concat(filters, ',')) .. "]" + }) + end + append(command, format:getFlags()) + local dir = "" + if is_stream then + dir = parse_directory("~") + else + local _ + dir, _ = utils.split_path(path) + end + if options.output_directory ~= "" then + dir = parse_directory(options.output_directory) + end + local formatted_filename = format_filename(startTime, endTime, format) + local out_path = utils.join_path(dir, formatted_filename) + append(command, { + "-o=" .. tostring(out_path) + }) + msg.info("Encoding to", out_path) + msg.verbose("Command line:", table.concat(command, " ")) + if options.run_detached then + message("Started encode, process was detached.") + return utils.subprocess_detached({ + args = command + }) + else + message("Started encode...") + local res = run_subprocess({ + args = command, + cancellable = false + }) + if res then + return message("Encoded successfully! Saved to\\N" .. tostring(bold(out_path))) + else + return message("Encode failed! Check the logs for details.") + end + end +end +local Page +do + local _class_0 + local _base_0 = { + add_keybinds = function(self) + for key, func in pairs(self.keybinds) do + mp.add_forced_key_binding(key, key, func, { + repeatable = true + }) + end + end, + remove_keybinds = function(self) + for key, _ in pairs(self.keybinds) do + mp.remove_key_binding(key) + end + end, + observe_properties = function(self) + self.sizeCallback = function() + return self:draw() + end + local properties = { + "keepaspect", + "video-out-params", + "video-unscaled", + "panscan", + "video-zoom", + "video-align-x", + "video-pan-x", + "video-align-y", + "video-pan-y", + "osd-width", + "osd-height" + } + for _index_0 = 1, #properties do + local p = properties[_index_0] + mp.observe_property(p, "native", self.sizeCallback) + end + end, + unobserve_properties = function(self) + if self.sizeCallback then + mp.unobserve_property(self.sizeCallback) + self.sizeCallback = nil + end + end, + clear = function(self) + local window_w, window_h = mp.get_osd_size() + mp.set_osd_ass(window_w, window_h, "") + return mp.osd_message("", 0) + end, + prepare = function(self) + return nil + end, + dispose = function(self) + return nil + end, + show = function(self) + self.visible = true + self:observe_properties() + self:add_keybinds() + self:prepare() + self:clear() + return self:draw() + end, + hide = function(self) + self.visible = false + self:unobserve_properties() + self:remove_keybinds() + self:clear() + return self:dispose() + end, + setup_text = function(self, ass) + local scale = calculate_scale_factor() + local margin = options.margin * scale + ass:pos(margin, margin) + return ass:append("{\\fs" .. tostring(options.font_size * scale)) + end + } + _base_0.__index = _base_0 + _class_0 = setmetatable({ + __init = function() end, + __base = _base_0, + __name = "Page" + }, { + __index = _base_0, + __call = function(cls, ...) + local _self_0 = setmetatable({}, _base_0) + cls.__init(_self_0, ...) + return _self_0 + end + }) + _base_0.__class = _class_0 + Page = _class_0 +end +local CropPage +do + local _class_0 + local _parent_0 = Page + local _base_0 = { + reset = function(self) + local dimensions = get_video_dimensions() + local xa, ya + do + local _obj_0 = dimensions.top_left + xa, ya = _obj_0.x, _obj_0.y + end + self.pointA:set_from_screen(xa, ya) + local xb, yb + do + local _obj_0 = dimensions.bottom_right + xb, yb = _obj_0.x, _obj_0.y + end + self.pointB:set_from_screen(xb, yb) + if self.visible then + return self:draw() + end + end, + setPointA = function(self) + local posX, posY = mp.get_mouse_pos() + self.pointA:set_from_screen(posX, posY) + if self.visible then + return self:draw() + end + end, + setPointB = function(self) + local posX, posY = mp.get_mouse_pos() + self.pointB:set_from_screen(posX, posY) + if self.visible then + return self:draw() + end + end, + cancel = function(self) + self:hide() + return self.callback(false, nil) + end, + finish = function(self) + local region = Region() + region:set_from_points(self.pointA, self.pointB) + self:hide() + return self.callback(true, region) + end, + prepare = function(self) + local properties = { + "keepaspect", + "video-out-params", + "video-unscaled", + "panscan", + "video-zoom", + "video-align-x", + "video-pan-x", + "video-align-y", + "video-pan-y", + "osd-width", + "osd-height" + } + for _, p in ipairs(properties) do + mp.observe_property(p, "native", set_dimensions_changed) + end + end, + dispose = function(self) + return mp.unobserve_property(set_dimensions_changed) + end, + draw_box = function(self, ass) + local region = Region() + region:set_from_points(self.pointA:to_screen(), self.pointB:to_screen()) + local d = get_video_dimensions() + ass:new_event() + ass:pos(0, 0) + ass:append('{\\bord0}') + ass:append('{\\shad0}') + ass:append('{\\c&H000000&}') + ass:append('{\\alpha&H77}') + ass:draw_start() + ass:rect_cw(d.top_left.x, d.top_left.y, region.x, region.y + region.h) + ass:rect_cw(region.x, d.top_left.y, d.bottom_right.x, region.y) + ass:rect_cw(d.top_left.x, region.y + region.h, region.x + region.w, d.bottom_right.y) + ass:rect_cw(region.x + region.w, region.y, d.bottom_right.x, d.bottom_right.y) + return ass:draw_stop() + end, + draw = function(self) + local window = { } + window.w, window.h = mp.get_osd_size() + local ass = assdraw.ass_new() + self:draw_box(ass) + ass:new_event() + self:setup_text(ass) + ass:append(tostring(bold('Crop:')) .. "\\N") + ass:append(tostring(bold('1:')) .. " change point A (" .. tostring(self.pointA.x) .. ", " .. tostring(self.pointA.y) .. ")\\N") + ass:append(tostring(bold('2:')) .. " change point B (" .. tostring(self.pointB.x) .. ", " .. tostring(self.pointB.y) .. ")\\N") + ass:append(tostring(bold('r:')) .. " reset to whole screen\\N") + ass:append(tostring(bold('ESC:')) .. " cancel crop\\N") + ass:append(tostring(bold('ENTER:')) .. " confirm crop\\N") + return mp.set_osd_ass(window.w, window.h, ass.text) + end + } + _base_0.__index = _base_0 + setmetatable(_base_0, _parent_0.__base) + _class_0 = setmetatable({ + __init = function(self, callback, region) + self.pointA = VideoPoint() + self.pointB = VideoPoint() + self.keybinds = { + ["1"] = (function() + local _base_1 = self + local _fn_0 = _base_1.setPointA + return function(...) + return _fn_0(_base_1, ...) + end + end)(), + ["2"] = (function() + local _base_1 = self + local _fn_0 = _base_1.setPointB + return function(...) + return _fn_0(_base_1, ...) + end + end)(), + ["r"] = (function() + local _base_1 = self + local _fn_0 = _base_1.reset + return function(...) + return _fn_0(_base_1, ...) + end + end)(), + ["ESC"] = (function() + local _base_1 = self + local _fn_0 = _base_1.cancel + return function(...) + return _fn_0(_base_1, ...) + end + end)(), + ["ENTER"] = (function() + local _base_1 = self + local _fn_0 = _base_1.finish + return function(...) + return _fn_0(_base_1, ...) + end + end)() + } + self:reset() + self.callback = callback + if region and region:is_valid() then + self.pointA.x = region.x + self.pointA.y = region.y + self.pointB.x = region.x + region.w + self.pointB.y = region.y + region.h + end + end, + __base = _base_0, + __name = "CropPage", + __parent = _parent_0 + }, { + __index = function(cls, name) + local val = rawget(_base_0, name) + if val == nil then + local parent = rawget(cls, "__parent") + if parent then + return parent[name] + end + else + return val + end + end, + __call = function(cls, ...) + local _self_0 = setmetatable({}, _base_0) + cls.__init(_self_0, ...) + return _self_0 + end + }) + _base_0.__class = _class_0 + if _parent_0.__inherited then + _parent_0.__inherited(_parent_0, _class_0) + end + CropPage = _class_0 +end +local Option +do + local _class_0 + local _base_0 = { + hasPrevious = function(self) + local _exp_0 = self.optType + if "bool" == _exp_0 then + return true + elseif "int" == _exp_0 then + if self.opts.min then + return self.value > self.opts.min + else + return true + end + elseif "list" == _exp_0 then + return self.value > 1 + end + end, + hasNext = function(self) + local _exp_0 = self.optType + if "bool" == _exp_0 then + return true + elseif "int" == _exp_0 then + if self.opts.max then + return self.value < self.opts.max + else + return true + end + elseif "list" == _exp_0 then + return self.value < #self.opts.possibleValues + end + end, + leftKey = function(self) + local _exp_0 = self.optType + if "bool" == _exp_0 then + self.value = not self.value + elseif "int" == _exp_0 then + self.value = self.value - self.opts.step + if self.opts.min and self.opts.min > self.value then + self.value = self.opts.min + end + elseif "list" == _exp_0 then + if self.value > 1 then + self.value = self.value - 1 + end + end + end, + rightKey = function(self) + local _exp_0 = self.optType + if "bool" == _exp_0 then + self.value = not self.value + elseif "int" == _exp_0 then + self.value = self.value + self.opts.step + if self.opts.max and self.opts.max < self.value then + self.value = self.opts.max + end + elseif "list" == _exp_0 then + if self.value < #self.opts.possibleValues then + self.value = self.value + 1 + end + end + end, + getValue = function(self) + local _exp_0 = self.optType + if "bool" == _exp_0 then + return self.value + elseif "int" == _exp_0 then + return self.value + elseif "list" == _exp_0 then + local value, _ + do + local _obj_0 = self.opts.possibleValues[self.value] + value, _ = _obj_0[1], _obj_0[2] + end + return value + end + end, + setValue = function(self, value) + local _exp_0 = self.optType + if "bool" == _exp_0 then + self.value = value + elseif "int" == _exp_0 then + self.value = value + elseif "list" == _exp_0 then + local set = false + for i, possiblePair in ipairs(self.opts.possibleValues) do + local possibleValue, _ + possibleValue, _ = possiblePair[1], possiblePair[2] + if possibleValue == value then + set = true + self.value = i + break + end + end + if not set then + return msg.warn("Tried to set invalid value " .. tostring(value) .. " to " .. tostring(self.displayText) .. " option.") + end + end + end, + getDisplayValue = function(self) + local _exp_0 = self.optType + if "bool" == _exp_0 then + return self.value and "yes" or "no" + elseif "int" == _exp_0 then + if self.opts.altDisplayNames and self.opts.altDisplayNames[self.value] then + return self.opts.altDisplayNames[self.value] + else + return tostring(self.value) + end + elseif "list" == _exp_0 then + local value, displayValue + do + local _obj_0 = self.opts.possibleValues[self.value] + value, displayValue = _obj_0[1], _obj_0[2] + end + return displayValue or value + end + end, + draw = function(self, ass, selected) + if selected then + ass:append(tostring(bold(self.displayText)) .. ": ") + else + ass:append(tostring(self.displayText) .. ": ") + end + if self:hasPrevious() then + ass:append("◀ ") + end + ass:append(self:getDisplayValue()) + if self:hasNext() then + ass:append(" ▶") + end + return ass:append("\\N") + end + } + _base_0.__index = _base_0 + _class_0 = setmetatable({ + __init = function(self, optType, displayText, value, opts) + self.optType = optType + self.displayText = displayText + self.opts = opts + self.value = 1 + return self:setValue(value) + end, + __base = _base_0, + __name = "Option" + }, { + __index = _base_0, + __call = function(cls, ...) + local _self_0 = setmetatable({}, _base_0) + cls.__init(_self_0, ...) + return _self_0 + end + }) + _base_0.__class = _class_0 + Option = _class_0 +end +local EncodeOptionsPage +do + local _class_0 + local _parent_0 = Page + local _base_0 = { + getCurrentOption = function(self) + return self.options[self.currentOption][2] + end, + leftKey = function(self) + (self:getCurrentOption()):leftKey() + return self:draw() + end, + rightKey = function(self) + (self:getCurrentOption()):rightKey() + return self:draw() + end, + prevOpt = function(self) + self.currentOption = math.max(1, self.currentOption - 1) + return self:draw() + end, + nextOpt = function(self) + self.currentOption = math.min(#self.options, self.currentOption + 1) + return self:draw() + end, + confirmOpts = function(self) + for _, optPair in ipairs(self.options) do + local optName, opt + optName, opt = optPair[1], optPair[2] + options[optName] = opt:getValue() + end + self:hide() + return self.callback(true) + end, + cancelOpts = function(self) + self:hide() + return self.callback(false) + end, + draw = function(self) + local window_w, window_h = mp.get_osd_size() + local ass = assdraw.ass_new() + ass:new_event() + self:setup_text(ass) + ass:append(tostring(bold('Options:')) .. "\\N\\N") + for i, optPair in ipairs(self.options) do + local opt = optPair[2] + opt:draw(ass, self.currentOption == i) + end + ass:append("\\N▲ / ▼: navigate\\N") + ass:append(tostring(bold('ENTER:')) .. " confirm options\\N") + ass:append(tostring(bold('ESC:')) .. " cancel\\N") + return mp.set_osd_ass(window_w, window_h, ass.text) + end + } + _base_0.__index = _base_0 + setmetatable(_base_0, _parent_0.__base) + _class_0 = setmetatable({ + __init = function(self, callback) + self.callback = callback + self.currentOption = 1 + local scaleHeightOpts = { + possibleValues = { + { + -1, + "no" + }, + { + 240 + }, + { + 360 + }, + { + 480 + }, + { + 720 + }, + { + 1080 + }, + { + 1440 + }, + { + 2160 + } + } + } + local formatIds = { + "h264" + } + local formatOpts = { + possibleValues = (function() + local _accum_0 = { } + local _len_0 = 1 + for _index_0 = 1, #formatIds do + local fId = formatIds[_index_0] + _accum_0[_len_0] = { + fId, + formats[fId].displayName + } + _len_0 = _len_0 + 1 + end + return _accum_0 + end)() + } + self.options = { + { + "output_format", + Option("list", "Output Format", options.output_format, formatOpts) + }, + { + "scale_height", + Option("list", "Scale Height", options.scale_height, scaleHeightOpts) + } + } + self.keybinds = { + ["LEFT"] = (function() + local _base_1 = self + local _fn_0 = _base_1.leftKey + return function(...) + return _fn_0(_base_1, ...) + end + end)(), + ["RIGHT"] = (function() + local _base_1 = self + local _fn_0 = _base_1.rightKey + return function(...) + return _fn_0(_base_1, ...) + end + end)(), + ["UP"] = (function() + local _base_1 = self + local _fn_0 = _base_1.prevOpt + return function(...) + return _fn_0(_base_1, ...) + end + end)(), + ["DOWN"] = (function() + local _base_1 = self + local _fn_0 = _base_1.nextOpt + return function(...) + return _fn_0(_base_1, ...) + end + end)(), + ["ENTER"] = (function() + local _base_1 = self + local _fn_0 = _base_1.confirmOpts + return function(...) + return _fn_0(_base_1, ...) + end + end)(), + ["ESC"] = (function() + local _base_1 = self + local _fn_0 = _base_1.cancelOpts + return function(...) + return _fn_0(_base_1, ...) + end + end)() + } + end, + __base = _base_0, + __name = "EncodeOptionsPage", + __parent = _parent_0 + }, { + __index = function(cls, name) + local val = rawget(_base_0, name) + if val == nil then + local parent = rawget(cls, "__parent") + if parent then + return parent[name] + end + else + return val + end + end, + __call = function(cls, ...) + local _self_0 = setmetatable({}, _base_0) + cls.__init(_self_0, ...) + return _self_0 + end + }) + _base_0.__class = _class_0 + if _parent_0.__inherited then + _parent_0.__inherited(_parent_0, _class_0) + end + EncodeOptionsPage = _class_0 +end +local PreviewPage +do + local _class_0 + local _parent_0 = Page + local _base_0 = { + prepare = function(self) + local vf = mp.get_property_native("vf") + vf[#vf + 1] = { + name = "sub" + } + if self.region:is_valid() then + vf[#vf + 1] = { + name = "crop", + params = { + w = tostring(self.region.w), + h = tostring(self.region.h), + x = tostring(self.region.x), + y = tostring(self.region.y) + } + } + end + mp.set_property_native("vf", vf) + if self.startTime > -1 and self.endTime > -1 then + mp.set_property_native("ab-loop-a", self.startTime) + mp.set_property_native("ab-loop-b", self.endTime) + mp.set_property_native("time-pos", self.startTime) + end + return mp.set_property_native("pause", false) + end, + dispose = function(self) + mp.set_property("ab-loop-a", "no") + mp.set_property("ab-loop-b", "no") + for prop, value in pairs(self.originalProperties) do + mp.set_property_native(prop, value) + end + end, + draw = function(self) + local window_w, window_h = mp.get_osd_size() + local ass = assdraw.ass_new() + ass:new_event() + self:setup_text(ass) + ass:append("Press " .. tostring(bold('ESC')) .. " to exit preview.\\N") + return mp.set_osd_ass(window_w, window_h, ass.text) + end, + cancel = function(self) + self:hide() + return self.callback() + end + } + _base_0.__index = _base_0 + setmetatable(_base_0, _parent_0.__base) + _class_0 = setmetatable({ + __init = function(self, callback, region, startTime, endTime) + self.callback = callback + self.originalProperties = { + ["vf"] = mp.get_property_native("vf"), + ["time-pos"] = mp.get_property_native("time-pos"), + ["pause"] = mp.get_property_native("pause") + } + self.keybinds = { + ["ESC"] = (function() + local _base_1 = self + local _fn_0 = _base_1.cancel + return function(...) + return _fn_0(_base_1, ...) + end + end)() + } + self.region = region + self.startTime = startTime + self.endTime = endTime + self.isLoop = false + end, + __base = _base_0, + __name = "PreviewPage", + __parent = _parent_0 + }, { + __index = function(cls, name) + local val = rawget(_base_0, name) + if val == nil then + local parent = rawget(cls, "__parent") + if parent then + return parent[name] + end + else + return val + end + end, + __call = function(cls, ...) + local _self_0 = setmetatable({}, _base_0) + cls.__init(_self_0, ...) + return _self_0 + end + }) + _base_0.__class = _class_0 + if _parent_0.__inherited then + _parent_0.__inherited(_parent_0, _class_0) + end + PreviewPage = _class_0 +end +local MainPage +do + local _class_0 + local _parent_0 = Page + local _base_0 = { + setStartTime = function(self) + self.startTime = mp.get_property_number("time-pos") + if self.visible then + self:clear() + return self:draw() + end + end, + setEndTime = function(self) + self.endTime = mp.get_property_number("time-pos") + if self.visible then + self:clear() + return self:draw() + end + end, + draw = function(self) + local window_w, window_h = mp.get_osd_size() + local ass = assdraw.ass_new() + ass:new_event() + self:setup_text(ass) + ass:append(tostring(bold('Share Video')) .. "\\N\\N") + ass:append(tostring(bold('c:')) .. " crop\\N") + ass:append(tostring(bold('1:')) .. " set start time (current is " .. tostring(seconds_to_time_string(self.startTime)) .. ")\\N") + ass:append(tostring(bold('2:')) .. " set end time (current is " .. tostring(seconds_to_time_string(self.endTime)) .. ")\\N") + ass:append(tostring(bold('o:')) .. " change encode options\\N") + ass:append(tostring(bold('p:')) .. " preview\\N") + ass:append(tostring(bold('e:')) .. " encode\\N\\N") + ass:append(tostring(bold('ESC:')) .. " close\\N") + return mp.set_osd_ass(window_w, window_h, ass.text) + end, + onUpdateCropRegion = function(self, updated, newRegion) + if updated then + self.region = newRegion + end + return self:show() + end, + crop = function(self) + self:hide() + local cropPage = CropPage((function() + local _base_1 = self + local _fn_0 = _base_1.onUpdateCropRegion + return function(...) + return _fn_0(_base_1, ...) + end + end)(), self.region) + return cropPage:show() + end, + onOptionsChanged = function(self, updated) + return self:show() + end, + changeOptions = function(self) + self:hide() + local encodeOptsPage = EncodeOptionsPage((function() + local _base_1 = self + local _fn_0 = _base_1.onOptionsChanged + return function(...) + return _fn_0(_base_1, ...) + end + end)()) + return encodeOptsPage:show() + end, + onPreviewEnded = function(self) + return self:show() + end, + preview = function(self) + self:hide() + local previewPage = PreviewPage((function() + local _base_1 = self + local _fn_0 = _base_1.onPreviewEnded + return function(...) + return _fn_0(_base_1, ...) + end + end)(), self.region, self.startTime, self.endTime) + return previewPage:show() + end, + encode = function(self) + self:hide() + if self.startTime < 0 then + message("No start time, aborting") + return + end + if self.endTime < 0 then + message("No end time, aborting") + return + end + if self.startTime >= self.endTime then + message("Start time is ahead of end time, aborting") + return + end + return encode(self.region, self.startTime, self.endTime) + end + } + _base_0.__index = _base_0 + setmetatable(_base_0, _parent_0.__base) + _class_0 = setmetatable({ + __init = function(self) + self.keybinds = { + ["c"] = (function() + local _base_1 = self + local _fn_0 = _base_1.crop + return function(...) + return _fn_0(_base_1, ...) + end + end)(), + ["1"] = (function() + local _base_1 = self + local _fn_0 = _base_1.setStartTime + return function(...) + return _fn_0(_base_1, ...) + end + end)(), + ["2"] = (function() + local _base_1 = self + local _fn_0 = _base_1.setEndTime + return function(...) + return _fn_0(_base_1, ...) + end + end)(), + ["o"] = (function() + local _base_1 = self + local _fn_0 = _base_1.changeOptions + return function(...) + return _fn_0(_base_1, ...) + end + end)(), + ["p"] = (function() + local _base_1 = self + local _fn_0 = _base_1.preview + return function(...) + return _fn_0(_base_1, ...) + end + end)(), + ["e"] = (function() + local _base_1 = self + local _fn_0 = _base_1.encode + return function(...) + return _fn_0(_base_1, ...) + end + end)(), + ["ESC"] = (function() + local _base_1 = self + local _fn_0 = _base_1.hide + return function(...) + return _fn_0(_base_1, ...) + end + end)() + } + self.startTime = -1 + self.endTime = -1 + self.region = Region() + end, + __base = _base_0, + __name = "MainPage", + __parent = _parent_0 + }, { + __index = function(cls, name) + local val = rawget(_base_0, name) + if val == nil then + local parent = rawget(cls, "__parent") + if parent then + return parent[name] + end + else + return val + end + end, + __call = function(cls, ...) + local _self_0 = setmetatable({}, _base_0) + cls.__init(_self_0, ...) + return _self_0 + end + }) + _base_0.__class = _class_0 + if _parent_0.__inherited then + _parent_0.__inherited(_parent_0, _class_0) + end + MainPage = _class_0 +end +local mainPage = MainPage() +return mp.add_key_binding(options.keybind, "display-webm-encoder", (function() + local _base_0 = mainPage + local _fn_0 = _base_0.show + return function(...) + return _fn_0(_base_0, ...) + end +end)(), { + repeatable = false +})