From 60e8a3d404f0640fa5a3f834eae54b4f1fb9127d Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Sat, 4 May 2024 03:13:14 +0200 Subject: Adding upstream version 0.38.0. Signed-off-by: Daniel Baumann --- player/lua/stats.lua | 316 +++++++++++++++++++++++++++++++-------------------- 1 file changed, 191 insertions(+), 125 deletions(-) (limited to 'player/lua/stats.lua') diff --git a/player/lua/stats.lua b/player/lua/stats.lua index 16e8b68..3d093c7 100644 --- a/player/lua/stats.lua +++ b/player/lua/stats.lua @@ -30,6 +30,8 @@ local o = { 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 show_frame_info = false, -- whether to show the current frame info + term_width_limit = -1, -- overwrites the terminal width + term_height_limit = -1, -- overwrites the terminal height debug = false, -- Graph options and style @@ -83,6 +85,15 @@ local o = { } options.read_options(o) +o.term_width_limit = tonumber(o.term_width_limit) or -1 +o.term_height_limit = tonumber(o.term_height_limit) or -1 +if o.term_width_limit < 0 then + o.term_width_limit = nil +end +if o.term_height_limit < 0 then + o.term_height_limit = nil +end + local format = string.format local max = math.max local min = math.min @@ -118,9 +129,6 @@ local function graph_add_value(graph, value) graph.max = max(graph.max, value) end --- "\\" in UTF-8 (U+2060 is WORD-JOINER) -local ESC_BACKSLASH = "\\" .. string.char(0xE2, 0x81, 0xA0) - local function no_ASS(t) if not o.use_ass then return t @@ -128,16 +136,7 @@ local function no_ASS(t) -- mp.osd_message supports ass-escape using osd-ass-cc/{0|1} return ass_stop .. t .. ass_start else - -- mp.set_osd_ass doesn't support ass-escape. roll our own. - -- similar to mpv's sub/osd_libass.c:mangle_ass(...), excluding - -- space after newlines because no_ASS is not used with multi-line. - -- space at the beginning is replaced with "\\h" because it matters - -- at the beginning of a line, and we can't know where our output - -- ends up. no issue if it ends up at the middle of a line. - return tostring(t) - :gsub("\\", ESC_BACKSLASH) - :gsub("{", "\\{") - :gsub("^ ", "\\h") + return mp.command_native({"escape-ass", tostring(t)}) end end @@ -222,7 +221,7 @@ local function generate_graph(values, i, len, v_max, v_avg, scale, x_tics) 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", + return format("%s{\\r}{\\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 @@ -277,7 +276,13 @@ local function sorted_keys(t, comp_fn) return keys end -local function append_perfdata(s, dedicated_page, print_passes) +local function scroll_hint() + local hint = format("(hint: scroll with %s/%s)", o.key_scroll_up, o.key_scroll_down) + if not o.use_ass then return " " .. hint end + return format(" {\\fs%s}%s{\\fs%s}", o.font_size * 0.66, hint, o.font_size) +end + +local function append_perfdata(header, s, dedicated_page, print_passes) local vo_p = mp.get_property_native("vo-passes") if not vo_p then return @@ -318,11 +323,12 @@ local function append_perfdata(s, dedicated_page, print_passes) -- ensure that the fixed title is one element and every scrollable line is -- also one single element. - s[#s+1] = format("%s%s%s%s{\\fs%s}%s%s{\\fs%s}", + local h = dedicated_page and header or s + h[#h+1] = format("%s%s%s%s{\\fs%s}%s{\\fs%s}%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)", - dedicated_page and " (hint: scroll with ↑↓)" or "", o.font_size) + "(last/average/peak μs)", o.font_size, + dedicated_page and scroll_hint() or "") for _,frame in ipairs(sorted_keys(vo_p)) do -- ensure fixed display order local data = vo_p[frame] @@ -363,11 +369,6 @@ local function append_perfdata(s, dedicated_page, print_passes) end end -local function ellipsis(s, maxlen) - if not maxlen or s:len() <= maxlen then return s end - return s:sub(1, maxlen - 3) .. "..." -end - -- command prefix tokens to strip - includes generic property commands local cmd_prefixes = { osd_auto=1, no_osd=1, osd_bar=1, osd_msg=1, osd_msg_bar=1, raw=1, sync=1, @@ -419,7 +420,7 @@ local function keyname_cells(k) return klen end -local function get_kbinfo_lines(width) +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 @@ -482,8 +483,6 @@ local function get_kbinfo_lines(width) or format("{\\q2\\fn%s}%s {\\fn%s}{\\fs%d\\u1}", o.font_mono, kspaces, o.font, 1.3*o.font_size) local spost = term and "" or format("{\\u0\\fs%d}", o.font_size) - local _, itabs = o.indent:gsub("\t", "") - local cutoff = term and (width or 79) - o.indent:len() - itabs * 7 - spre:len() -- create the display lines local info_lines = {} @@ -497,38 +496,25 @@ local function get_kbinfo_lines(width) if bind.comment then bind.cmd = bind.cmd .. " # " .. bind.comment end - append(info_lines, ellipsis(bind.cmd, cutoff), - { prefix = kpre .. no_ASS(align_right(bind.key)) .. kpost }) + append(info_lines, bind.cmd, { prefix = kpre .. no_ASS(align_right(bind.key)) .. kpost }) end return info_lines end -local function append_general_perfdata(s, offset) - local perf_info = mp.get_property_native("perf-info") or {} - local count = 0 - for _, data in ipairs(perf_info) do - count = count + 1 - end - offset = max(1, min((offset or 1), count)) - - local i = 0 - for _, data in ipairs(perf_info) do - i = i + 1 - if i >= offset then - append(s, data.text or data.value, {prefix="["..tostring(i).."] "..data.name..":"}) - - if o.plot_perfdata and o.use_ass and data.value then - buf = perf_buffers[data.name] - if not buf then - buf = {0, pos = 1, len = 50, max = 0} - perf_buffers[data.name] = buf - end - graph_add_value(buf, data.value) - s[#s+1] = generate_graph(buf, buf.pos, buf.len, buf.max, nil, 0.8, 1) +local function append_general_perfdata(s) + for i, data in ipairs(mp.get_property_native("perf-info") or {}) do + append(s, data.text or data.value, {prefix="["..tostring(i).."] "..data.name..":"}) + + if o.plot_perfdata and o.use_ass and data.value then + buf = perf_buffers[data.name] + if not buf then + buf = {0, pos = 1, len = 50, max = 0} + perf_buffers[data.name] = buf end + graph_add_value(buf, data.value) + s[#s] = s[#s] .. generate_graph(buf, buf.pos, buf.len, buf.max, nil, 0.8, 1) end end - return offset end local function append_display_sync(s) @@ -806,6 +792,7 @@ local function append_img_params(s, r, ro) end local indent = o.prefix_sep .. o.prefix_sep + r = ro or r local pixel_format = r["hw-pixelformat"] or r["pixelformat"] append(s, pixel_format, {prefix="Format:"}) @@ -828,7 +815,7 @@ local function append_fps(s, prop, eprop) local unit = prop == "display-fps" and " Hz" or " fps" local suffix = single and "" or " (specified)" local esuffix = single and "" or " (estimated)" - local prefix = prop == "display-fps" and "Refresh Rate:" or "Frame rate:" + local prefix = prop == "display-fps" and "Refresh Rate:" or "Frame Rate:" local nl = o.nl local indent = o.indent @@ -855,6 +842,8 @@ local function add_video_out(s) append(s, vo, {prefix_sep="", nl="", indent=""}) append_property(s, "display-names", {prefix_sep="", prefix="(", suffix=")", no_prefix_markup=true, nl="", indent=" "}) + append(s, mp.get_property_native("current-gpu-context"), + {prefix="Context:", nl="", indent=o.prefix_sep .. o.prefix_sep}) append_property(s, "avsync", {prefix="A-V:"}) append_fps(s, "display-fps", "estimated-display-fps") if append_property(s, "decoder-frame-drop-count", @@ -862,9 +851,9 @@ local function add_video_out(s) append_property(s, "frame-drop-count", {suffix=" (output)", nl="", indent=""}) end append_display_sync(s) - append_perfdata(s, false, o.print_perfdata_passes) + append_perfdata(nil, s, false, o.print_perfdata_passes) - if mp.get_property_native("deinterlace") then + if mp.get_property_native("deinterlace-active") then append_property(s, "deinterlace", {prefix="Deinterlacing:"}) end @@ -902,12 +891,11 @@ local function add_video(s) return end - local osd_dims = mp.get_property_native("osd-dimensions") - local scaled_width = osd_dims["w"] - osd_dims["ml"] - osd_dims["mr"] - local scaled_height = osd_dims["h"] - osd_dims["mt"] - osd_dims["mb"] - append(s, "", {prefix=o.nl .. o.nl .. "Video:", nl="", indent=""}) - if append_property(s, "video-codec", {prefix_sep="", nl="", indent=""}) then + local track = mp.get_property_native("current-tracks/video") + if track and append(s, track["codec-desc"], {prefix_sep="", nl="", indent=""}) then + append(s, track["codec-profile"], {prefix="[", nl="", indent=" ", prefix_sep="", + no_prefix_markup=true, suffix="]"}) append_property(s, "hwdec-current", {prefix="HW:", nl="", indent=o.prefix_sep .. o.prefix_sep, no_prefix_markup=false, suffix=""}, {no=true, [""]=true}) @@ -947,19 +935,39 @@ end local function add_audio(s) local r = mp.get_property_native("audio-params") -- in case of e.g. lavfi-complex there can be no input audio, only output - if not r then - r = mp.get_property_native("audio-out-params") - end + local ro = mp.get_property_native("audio-out-params") or r + r = r or ro if not r then return end + local merge = function(r, ro, prop) + local a = r[prop] or ro[prop] + local b = ro[prop] or r[prop] + return (a == b or a == nil) and a or (a .. " ➜ " .. b) + end + append(s, "", {prefix=o.nl .. o.nl .. "Audio:", nl="", indent=""}) - append_property(s, "audio-codec", {prefix_sep="", nl="", indent=""}) - local cc = append(s, r["channel-count"], {prefix="Channels:"}) - append(s, r["format"], {prefix="Format:", nl=cc and "" or o.nl, + local track = mp.get_property_native("current-tracks/audio") + if track then + append(s, track["codec-desc"], {prefix_sep="", nl="", indent=""}) + append(s, track["codec-profile"], {prefix="[", nl="", indent=" ", prefix_sep="", + no_prefix_markup=true, suffix="]"}) + end + append_property(s, "current-ao", {prefix="AO:", nl="", + indent=o.prefix_sep .. o.prefix_sep}) + local dev = append_property(s, "audio-device", {prefix="Device:"}) + local ao_mute = mp.get_property_native("ao-mute") and " (Muted)" or "" + append_property(s, "ao-volume", {prefix="AO Volume:", suffix="%" .. ao_mute, + nl=dev and "" or o.nl, + indent=dev and o.prefix_sep .. o.prefix_sep}) + if math.abs(mp.get_property_native("audio-delay")) > 1e-6 then + append_property(s, "audio-delay", {prefix="A-V delay:"}) + end + local cc = append(s, merge(r, ro, "channel-count"), {prefix="Channels:"}) + append(s, merge(r, ro, "format"), {prefix="Format:", nl=cc and "" or o.nl, indent=cc and o.prefix_sep .. o.prefix_sep}) - append(s, r["samplerate"], {prefix="Sample Rate:", suffix=" Hz"}) + append(s, merge(r, ro, "samplerate"), {prefix="Sample Rate:", suffix=" Hz"}) append_property(s, "packet-audio-bitrate", {prefix="Bitrate:", suffix=" kbps"}) append_filters(s, "af", "Filters:") end @@ -987,6 +995,91 @@ local function eval_ass_formatting() end end +-- assumptions: +-- s is composed of SGR escape sequences and/or printable UTF8 sequences +-- printable codepoints occupy one terminal cell (we don't have wcwidth) +-- tabstops are 8, 16, 24..., and the output starts at 0 or a tabstop +-- note: if maxwidth <= 2 and s doesn't fit: the result is "..." (more than 2) +function term_ellipsis(s, maxwidth) + local TAB, ESC, SGR_END = 9, 27, ("m"):byte() + local width, ellipsis = 0, "..." + local fit_len, in_sgr + + for i = 1, #s do + local x = s:byte(i) + + if in_sgr then + in_sgr = x ~= SGR_END + elseif x == ESC then + in_sgr = true + ellipsis = "\27[0m..." -- ensure SGR reset + elseif x < 128 or x >= 192 then -- non UTF8-continuation + -- tab adds till the next stop, else add 1 + width = width + (x == TAB and 8 - width % 8 or 1) + + if fit_len == nil and width > maxwidth - 3 then + fit_len = i - 1 -- adding "..." still fits maxwidth + end + if width > maxwidth then + return s:sub(1, fit_len) .. ellipsis + end + end + end + + return s +end + +local function term_ellipsis_array(arr, from, to, max_width) + for i = from, to do + arr[i] = term_ellipsis(arr[i], max_width) + end + return arr +end + +-- split str into a table +-- example: local t = split(s, "\n") +-- plain: whether pat is a plain string (default false - pat is a pattern) +local function split(str, pat, plain) + local init = 1 + local r, i, find, sub = {}, 1, string.find, string.sub + repeat + local f0, f1 = find(str, pat, init, plain) + r[i], i = sub(str, init, f0 and f0 - 1), i+1 + init = f0 and f1 + 1 + until f0 == nil + return r +end + +-- Composes the output with header and scrollable content +-- Returns string of the finished page and the actually chosen offset +-- +-- header : table of the header where each entry is one line +-- content : table of the content where each entry is one line +-- apply_scroll: scroll the content +local function finalize_page(header, content, apply_scroll) + local term_size = mp.get_property_native("term-size", {}) + local term_width = o.term_width_limit or term_size.w or 80 + local term_height = o.term_height_limit or term_size.h or 24 + local from, to = 1, #content + if apply_scroll and term_height > 0 then + -- Up to 40 lines for libass because it can put a big performance toll on + -- libass to process many lines which end up outside (below) the screen. + -- In the terminal reduce height by 2 for the status line (can be more then one line) + local max_content_lines = (o.use_ass and 40 or term_height - 2) - #header + -- in the terminal the scrolling should stop once the last line is visible + local max_offset = o.use_ass and #content or #content - max_content_lines + 1 + from = max(1, min((pages[curr_page].offset or 1), max_offset)) + to = min(#content, from + max_content_lines - 1) + pages[curr_page].offset = from + end + local output = table.concat(header) .. table.concat(content, "", from, to) + if not o.use_ass and term_width > 0 then + local t = split(output, "\n", true) + -- limit width for the terminal + output = table.concat(term_ellipsis_array(t, 1, #t, term_width), "\n") + end + return output, from +end -- Returns an ASS string with "normal" stats local function default_stats() @@ -997,70 +1090,44 @@ local function default_stats() add_video_out(stats) add_video(stats) add_audio(stats) - return table.concat(stats) -end - -local function scroll_vo_stats(stats, fixed_items, offset) - local ret = {} - local count = #stats - fixed_items - offset = max(1, min((offset or 1), count)) - - for i, line in pairs(stats) do - if i <= fixed_items or i >= fixed_items + offset then - ret[#ret+1] = stats[i] - end - end - return ret, offset + return finalize_page({}, stats, false) end -- Returns an ASS string with extended VO stats local function vo_stats() - local stats = {} + local header, content = {}, {} eval_ass_formatting() - add_header(stats) - - -- first line (title) added next is considered fixed - local fixed_items = #stats + 1 - append_perfdata(stats, true, true) - - local page = pages[o.key_page_2] - stats, page.offset = scroll_vo_stats(stats, fixed_items, page.offset) - return table.concat(stats) + add_header(header) + append_perfdata(header, content, true, true) + header = {table.concat(header)} + return finalize_page(header, content, true) end local kbinfo_lines = nil -local function keybinding_info(after_scroll) +local function keybinding_info(after_scroll, bindlist) local header = {} local page = pages[o.key_page_4] eval_ass_formatting() add_header(header) - append(header, "", {prefix=format("%s: {\\fs%s}%s{\\fs%s}", page.desc, - o.font_size * 0.66, "(hint: scroll with ↑↓)", o.font_size), nl="", - indent=""}) + append(header, "", {prefix=format("%s:%s", page.desc, scroll_hint()), nl="", indent=""}) + header = {table.concat(header)} if not kbinfo_lines or not after_scroll then - kbinfo_lines = get_kbinfo_lines() + kbinfo_lines = get_kbinfo_lines(o.term_width_limit) end - -- up to 20 lines for the terminal - so that mpv can also print - -- the status line without scrolling, and up to 40 lines for libass - -- because it can put a big performance toll on libass to process - -- many lines which end up outside (below) the screen. - local term = not o.use_ass - local nlines = #kbinfo_lines - page.offset = max(1, min((page.offset or 1), term and nlines - 20 or nlines)) - local maxline = min(nlines, page.offset + (term and 20 or 40)) - return table.concat(header) .. - table.concat(kbinfo_lines, "", page.offset, maxline) + + return finalize_page(header, kbinfo_lines, not bindlist) end local function perf_stats() - local stats = {} + local header, content = {}, {} eval_ass_formatting() - add_header(stats) + add_header(header) local page = pages[o.key_page_0] - append(stats, "", {prefix=page.desc .. ":", nl="", indent=""}) - page.offset = append_general_perfdata(stats, page.offset) - return table.concat(stats) + append(header, "", {prefix=format("%s:%s", page.desc, scroll_hint()), nl="", indent=""}) + append_general_perfdata(content) + header = {table.concat(header)} + return finalize_page(header, content, true) end local function opt_time(t) @@ -1076,18 +1143,18 @@ local function cache_stats() eval_ass_formatting() add_header(stats) - append(stats, "", {prefix="Cache info:", nl="", indent=""}) + append(stats, "", {prefix="Cache Info:", nl="", indent=""}) local info = mp.get_property_native("demuxer-cache-state") if info == nil then append(stats, "Unavailable.", {}) - return table.concat(stats) + return finalize_page({}, stats, false) end local a = info["reader-pts"] local b = info["cache-end"] - append(stats, opt_time(a) .. " - " .. opt_time(b), {prefix = "Packet queue:"}) + append(stats, opt_time(a) .. " - " .. opt_time(b), {prefix = "Packet Queue:"}) local r = nil if a ~= nil and b ~= nil then @@ -1101,7 +1168,7 @@ local function cache_stats() nil, 0.8, 1) r_graph = o.prefix_sep .. r_graph end - append(stats, opt_time(r), {prefix = "Read-ahead:", suffix = r_graph}) + append(stats, opt_time(r), {prefix = "Readahead:", suffix = r_graph}) -- These states are not necessarily exclusive. They're about potentially -- separate mechanisms, whose states may be decoupled. @@ -1140,17 +1207,17 @@ local function cache_stats() else fc = "(disabled)" end - append(stats, fc, {prefix = "Disk cache:"}) + append(stats, fc, {prefix = "Disk Cache:"}) - append(stats, info["debug-low-level-seeks"], {prefix = "Media seeks:"}) - append(stats, info["debug-byte-level-seeks"], {prefix = "Stream seeks:"}) + append(stats, info["debug-low-level-seeks"], {prefix = "Media Seeks:"}) + append(stats, info["debug-byte-level-seeks"], {prefix = "Stream Seeks:"}) append(stats, "", {prefix=o.nl .. o.nl .. "Ranges:", nl="", indent=""}) append(stats, info["bof-cached"] and "yes" or "no", - {prefix = "Start cached:"}) + {prefix = "Start Cached:"}) append(stats, info["eof-cached"] and "yes" or "no", - {prefix = "End cached:"}) + {prefix = "End Cached:"}) local ranges = info["seekable-ranges"] or {} for n, r in ipairs(ranges) do @@ -1159,7 +1226,7 @@ local function cache_stats() {prefix = format("Range %s:", n)}) end - return table.concat(stats) + return finalize_page({}, stats, false) end -- Record 1 sample of cache statistics. @@ -1188,8 +1255,8 @@ pages = { [o.key_page_1] = { f = default_stats, desc = "Default" }, [o.key_page_2] = { f = vo_stats, desc = "Extended Frame Timings", scroll = true }, [o.key_page_3] = { f = cache_stats, desc = "Cache Statistics" }, - [o.key_page_4] = { f = keybinding_info, desc = "Active key bindings", scroll = true }, - [o.key_page_0] = { f = perf_stats, desc = "Internal performance info", scroll = true }, + [o.key_page_4] = { f = keybinding_info, desc = "Active Key Bindings", scroll = true }, + [o.key_page_0] = { f = perf_stats, desc = "Internal Performance Info", scroll = true }, } @@ -1409,9 +1476,8 @@ if o.bindlist ~= "no" then mp.add_timeout(0, function() -- wait for all other scripts to finish init o.ass_formatting = false o.no_ass_indent = " " - eval_ass_formatting() - io.write(pages[o.key_page_4].desc .. ":" .. - table.concat(get_kbinfo_lines(width)) .. "\n") + o.term_size = { w = width , h = 0} + io.write(keybinding_info(false, true) .. "\n") mp.command("quit") end) end -- cgit v1.2.3