1770 lines · 63.9 KB · Lua
RawDownload
-- 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'local input = require 'mp.input' -- Optionslocal o = {    -- Default key bindings    key_page_1 = "1",    key_page_2 = "2",    key_page_3 = "3",    key_page_4 = "4",    key_page_5 = "5",    key_page_0 = "0",    -- For pages which support scrolling    key_scroll_up = "UP",    key_scroll_down = "DOWN",    key_search = "/",    key_exit = "ESC",    scroll_lines = 1,     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    filter_params_max_length = 100,  -- show one filter per line if list exceeds this length    file_tag_max_length = 128,       -- only show file tags shorter than this length in bytes    file_tag_max_count = 16,         -- only show the first x file tags    show_frame_info = false,         -- whether to show the current frame info    term_clip = true,    debug = false,     -- Graph options and style    plot_perfdata = true,    plot_vsync_ratio = true,    plot_vsync_jitter = true,    plot_tonemapping_lut = false,    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",    plot_bg_border_width = 1.25,     -- Text style    font = "",    font_mono = "monospace",   -- monospaced digits are sufficient    font_size = 20,    font_color = "",    border_size = 2,    border_color = "",    shadow_x_offset = 0.0,    shadow_y_offset = 0.0,    shadow_color = "",    alpha = "11",    vidscale = "auto",     -- 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 = "    ",    no_ass_prefix_sep = " ",    no_ass_b1 = "\027[1m",    no_ass_b0 = "\027[0m",    no_ass_it1 = "\027[3m",    no_ass_it0 = "\027[0m",     bindlist = "no",  -- print page 4 to the terminal on startup and quit mpv}options.read_options(o) local format = string.formatlocal max = math.maxlocal min = math.min -- Scaled metricslocal font_size = o.font_sizelocal border_size = o.border_sizelocal shadow_x_offset = o.shadow_x_offsetlocal shadow_y_offset = o.shadow_y_offsetlocal plot_bg_border_width = o.plot_bg_border_width-- Function used to record performance datalocal recorder = nil-- Timer used for redrawing (toggling) and clearing the screen (oneshot)local display_timer = nil-- Timer used to update cache stats.local cache_recorder_timer-- Current page and <page key>:<page function> mappingslocal curr_page = o.key_page_1local pages = {}local scroll_bound = falselocal searched_textlocal tm_viz_prev = nil-- Save these sequences locally as we'll need them a lotlocal 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 bufferlocal vsratio_buf, vsjitter_buflocal function init_buffers()    vsratio_buf = {0, pos = 1, len = 50, max = 0}    vsjitter_buf = {0, pos = 1, len = 50, max = 0}endlocal cache_ahead_buf, cache_speed_buflocal perf_buffers = {}local process_key_binding local function graph_add_value(graph, value)    graph.pos = (graph.pos % graph.len) + 1    graph[graph.pos] = value    graph.max = max(graph.max, value)end local function no_ASS(t)    if not o.use_ass then        return t    elseif not o.persistent_overlay then        -- mp.osd_message supports ass-escape using osd-ass-cc/{0|1}        return ass_stop .. t .. ass_start    else        return mp.command_native({"escape-ass", tostring(t)})    endend  local function bold(t)    return o.b1 .. t .. o.b0end  local function it(t)    return o.it1 .. t .. o.it0end  local function text_style()    if not o.use_ass then        return ""    end    if o.custom_header and o.custom_header ~= "" then        return o.custom_header    else        local style = "{\\r\\an7\\fs" .. font_size .. "\\bord" .. border_size         if o.font ~= "" then            style = style .. "\\fn" .. o.font        end         if o.font_color ~= "" then            style = style .. "\\1c&H" .. o.font_color .. "&\\1a&H" .. o.alpha .. "&"        end         if o.border_color ~= "" then            style = style .. "\\3c&H" .. o.border_color .. "&\\3a&H" .. o.alpha .. "&"        end         if o.shadow_color ~= "" then            style = style .. "\\4c&H" .. o.shadow_color .. "&\\4a&H" .. o.alpha .. "&"        end         return style .. "\\xshad" .. shadow_x_offset ..               "\\yshad" .. shadow_y_offset .. "}"    endend  local function has_vo_window()    return mp.get_property_native("vo-configured") and mp.get_property_native("video-osd")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 stepslocal 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 = border_size    local y_max = font_size * 0.66    local x = 0     if v_max > 0 then        -- try and center the graph if possible, but avoid going above `scale`        if v_avg and v_avg > 0 then            scale = min(scale, v_max / (2 * v_avg))        end        scale = scale * y_max / v_max    end  -- else if v_max==0 then all values are 0 and scale doesn't matter     local s = {format("m 0 0 n %f %f l ", x, y_max - scale * values[i])}    i = ((i - 2) % len) + 1     for _ = 1, len - 1 do        if values[i] then            x = x - x_tics            s[#s+1] = format("%f %f ", x, y_max - scale * values[i])        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("{\\bord%f}{\\3c&H%s&}{\\1c&H%s&}m 0 %f l %f %f %f 0 0 0",                          plot_bg_border_width, o.plot_bg_border_color, o.plot_bg_color,                          y_max, x_max, y_max, x_max)    return format("%s{\\rDefault}{\\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 bold(attr.prefix)     local index = #s + (attr.nl == "" and 0 or 1)    s[index] = s[index] or ""    s[index] = s[index] .. format("%s%s%s%s%s%s", attr.nl, attr.indent,                     attr.prefix, attr.prefix_sep, no_ASS(str), attr.suffix)    return trueend  -- 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 sorted_keys(t, comp_fn)    local keys = {}    for k,_ in pairs(t) do        keys[#keys+1] = k    end    table.sort(keys, comp_fn)    return keysend local function scroll_hint(search)    local hint = format("(hint: scroll with %s/%s", o.key_scroll_up, o.key_scroll_down)    if search then        hint = hint .. " and search with " .. o.key_search    end    hint = hint .. ")"    if not o.use_ass then return " " .. hint end    return format(" {\\fs%s}%s{\\fs%s}", font_size * 0.66, hint, font_size)end local function append_perfdata(header, s, dedicated_page)    local vo_p = mp.get_property_native("vo-passes")    if not vo_p then        return    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("%5d", 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        if not o.use_ass then            local str = format("%3d%%", i * 100)            return w >= 700 and bold(str) or str        end        return format("{\\b%d}%3d%%{\\b0}", w, i * 100)    end     local font_small = o.use_ass and format("{\\fs%s}", font_size * 0.66) or ""    local font_normal = o.use_ass and format("{\\fs%s}", font_size) or ""    local font = o.use_ass and format("{\\fn%s}", o.font) or ""    local font_mono = o.use_ass and format("{\\fn%s}", o.font_mono) or ""    local indent = o.use_ass and "\\h" or " "     -- ensure that the fixed title is one element and every scrollable line is    -- also one single element.    local h = dedicated_page and header or s    h[#h+1] = format("%s%s%s%s%s%s%s%s",                     dedicated_page and "" or o.nl, dedicated_page and "" or o.indent,                     bold("Frame Timings:"), o.prefix_sep, font_small,                     "(last/average/peak μs)", font_normal,                     dedicated_page and scroll_hint() or "")     for _,frame in ipairs(sorted_keys(vo_p)) do  -- ensure fixed display order        local data = vo_p[frame]        local f = "%s%s%s%s%s / %s / %s %s%s%s%s%s%s"         if dedicated_page then            s[#s+1] = format("%s%s%s:", o.nl, o.indent,                             bold(frame:gsub("^%l", string.upper)))             for _, pass in ipairs(data) do                s[#s+1] = format(f, o.nl, o.indent, o.indent,                                 font_mono, pp(pass["last"]),                                 pp(pass["avg"]), pp(pass["peak"]),                                 o.prefix_sep .. indent, p(pass["last"], last_s[frame]),                                 font, o.prefix_sep, o.prefix_sep, pass["desc"])                 if o.plot_perfdata and o.use_ass then                    -- use the same line that was already started for this iteration                    s[#s] = s[#s] ..                              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,                             font_mono, pp(last_s[frame]),                             pp(avg_s[frame]), pp(peak_s[frame]),                             o.prefix_sep, bold("Total"), font, "", "", "")        else            -- for the simplified view, we just print the sum of each pass            s[#s+1] = format(f, o.nl, o.indent, o.indent, font_mono,                            pp(last_s[frame]), pp(avg_s[frame]), pp(peak_s[frame]),                            "", "", font, o.prefix_sep, o.prefix_sep,                            frame:gsub("^%l", string.upper))        end    endend -- command prefix tokens to strip - includes generic property commandslocal cmd_prefixes = {    osd_auto=1, no_osd=1, osd_bar=1, osd_msg=1, osd_msg_bar=1, raw=1, sync=1,    async=1, expand_properties=1, repeatable=1, nonrepeatable=1, nonscalable=1,    set=1, add=1, multiply=1, toggle=1, cycle=1, cycle_values=1, ["!reverse"]=1,    change_list=1,}-- commands/writable-properties prefix sub-words (followed by -) to striplocal name_prefixes = {    define=1, delete=1, enable=1, disable=1, dump=1, write=1, drop=1, revert=1,    ab=1, hr=1, secondary=1, current=1,}-- extract a command "subject" from a command string, by removing all-- generic prefix tokens and then returning the first interesting sub-word-- of the next token. For target-script name we also check another token.-- The tokenizer works fine for things we care about - valid mpv commands,-- properties and script names, possibly quoted, white-space[s]-separated.-- It's decent in practice, and worst case is "incorrect" subject.local function cmd_subject(cmd)    cmd = cmd:gsub(";.*", ""):gsub("%-", "_")  -- only first cmd, s/-/_/    local TOKEN = '^%s*["\']?([%w_!]*)'  -- captures+ends before (maybe) final "    local tok, sname, subw     repeat tok, cmd = cmd:match(TOKEN .. '["\']?(.*)')    until not cmd_prefixes[tok]    -- tok is the 1st non-generic command/property name token, cmd is the rest     sname = tok == "script_message_to" and cmd:match(TOKEN)         or tok == "script_binding" and cmd:match(TOKEN .. "/")    if sname and sname ~= "" then        return "script: " .. sname    end     -- return the first sub-word of tok which is not a useless prefix    repeat subw, tok = tok:match("([^_]*)_?(.*)")    until tok == "" or not name_prefixes[subw]    return subw:len() > 1 and subw or "[unknown]"end -- key names are valid UTF-8, ascii7 except maybe the last/only codepoint.-- we count codepoints and ignore wcwidth. no need for grapheme clusters.-- our error for alignment is at most one cell (if last CP is double-width).-- (if k was valid but arbitrary: we'd count all bytes <0x80 or >=0xc0)local function keyname_cells(k)    local klen = k:len()    if klen > 1 and k:byte(klen) >= 0x80 then  -- last/only CP is not ascii7        repeat klen = klen-1        until klen == 1 or k:byte(klen) >= 0xc0  -- last CP begins at klen    end    return klenend local function get_kbinfo_lines()    -- active keys: only highest priority of each key, and not our (stats) keys    local bindings = mp.get_property_native("input-bindings", {})    local active = {}  -- map: key-name -> bind-info    for _, bind in pairs(bindings) do        if bind.priority >= 0 and (               not active[bind.key] or               (active[bind.key].is_weak and not bind.is_weak) or               (bind.is_weak == active[bind.key].is_weak and                bind.priority > active[bind.key].priority)           ) and not bind.cmd:find("script-binding stats/__forced_", 1, true)           and bind.section ~= "input_forced_console"           and (               searched_text == nil or               (bind.key .. bind.cmd .. (bind.comment or "")):lower():find(searched_text, 1, true)           )        then            active[bind.key] = bind        end    end     -- make an array, find max key len, add sort keys (.subject/.mods[_count])    local ordered = {}    local kspaces = ""  -- as many spaces as the longest key name    for _, bind in pairs(active) do        bind.subject = cmd_subject(bind.cmd)        if bind.subject ~= "ignore" then            ordered[#ordered+1] = bind            _,_, bind.mods = bind.key:find("(.*)%+.")            _, bind.mods_count = bind.key:gsub("%+.", "")            if bind.key:len() > kspaces:len() then                kspaces = string.rep(" ", bind.key:len())            end        end    end     local function align_right(key)        return kspaces:sub(keyname_cells(key)) .. key    end     -- sort by: subject, mod(ifier)s count, mods, key-len, lowercase-key, key    table.sort(ordered, function(a, b)        if a.subject ~= b.subject then            return a.subject < b.subject        elseif a.mods_count ~= b.mods_count then

Lines 1–500 of 1770.

Keyboard shortcuts

General

Show keyboard shortcuts
?
Focus the current search
/

Issues and pull requests

Open filters
f

Issues, pull requests and Inbox

Select next item
j↓
Select previous item
k↑
Open selected item
Enter
Dismiss filters or clear selection
Esc

Repository code

Go to file
t