diff options
Diffstat (limited to 'mac/.config/mpv/unused_scipts/xrandr.lua')
| -rw-r--r-- | mac/.config/mpv/unused_scipts/xrandr.lua | 382 |
1 files changed, 382 insertions, 0 deletions
diff --git a/mac/.config/mpv/unused_scipts/xrandr.lua b/mac/.config/mpv/unused_scipts/xrandr.lua new file mode 100644 index 0000000..43ce59f --- /dev/null +++ b/mac/.config/mpv/unused_scipts/xrandr.lua @@ -0,0 +1,382 @@ +-- use xrandr command to set output to best fitting fps rate +-- when playing videos with mpv. + +utils = require 'mp.utils' + +-- if you want your display output switched to a certain mode during playback, +-- use e.g. "--script-opts=xrandr-output-mode=1920x1080" +xrandr_output_mode = mp.get_opt("xrandr-output-mode") + +xrandr_blacklist = {} +function xrandr_parse_blacklist() + -- use e.g. "--script-opts=xrandr-blacklist=25" to have xrand.lua not use 25Hz refresh rate + + -- Parse the optional "blacklist" from a string into an array for later use. + -- For now, we only support a list of rates, since the "mode" is not subject + -- to automatic change (mpv is better at scaling than most displays) and + -- this also makes the blacklist option more easy to specify: + local b = mp.get_opt("xrandr-blacklist") + if (b == nil) then + return + end + + local i = 1 + for s in string.gmatch(b, "([^, ]+)") do + xrandr_blacklist[i] = 0.0 + s + i = i+1 + end +end +xrandr_parse_blacklist() + + +function xrandr_check_blacklist(mode, rate) + -- check if (mode, rate) is black-listed - e.g. because the + -- computer display output is known to be incompatible with the + -- display at this specific mode/rate + + for i=1,#xrandr_blacklist do + r = xrandr_blacklist[i] + + if (r == rate) then + mp.msg.log("v", "will not use mode '" .. mode .. "' with rate " .. rate .. " because option --script-opts=xrandr-blacklist said so") + return true + end + end + + return false +end + +xrandr_detect_done = false +xrandr_modes = {} +xrandr_connected_outputs = {} +function xrandr_detect_available_rates() + if (xrandr_detect_done) then + return + end + xrandr_detect_done = true + + -- invoke xrandr to find out which fps rates are available on which outputs + + local p = {} + p["cancellable"] = false + p["args"] = {} + p["args"][1] = "xrandr" + p["args"][2] = "-q" + local res = utils.subprocess(p) + + if (res["error"] ~= nil) then + mp.msg.log("info", "failed to execute 'xrand -q', error message: " .. res["error"]) + return + end + + mp.msg.log("v","xrandr -q\n" .. res["stdout"]) + + local output_idx = 1 + for output in string.gmatch(res["stdout"], '\n([^ ]+) connected') do + + table.insert(xrandr_connected_outputs, output) + + -- the first line with a "*" after the match contains the rates associated with the current mode + local mls = string.match(res["stdout"], "\n" .. string.gsub(output, "%p", "%%%1") .. " connected.*") + local r + local mode = nil + local old_rate + local old_mode + + -- old_rate = 0 means "no old rate known to switch to after playback" + old_rate = 0 + + if (xrandr_output_mode ~= nil) then + -- special case: user specified a certain preferred mode to use for playback + mp.msg.log("v", "looking for refresh rates for user supplied output mode " .. xrandr_output_mode) + mode, r = string.match(mls, '\n (' .. xrandr_output_mode .. ') ([^\n]+)') + + if (mode == nil) then + mp.msg.log("info", "user preferred output mode " .. xrandr_output_mode .. " not found for output " .. output .. " - will use current mode") + else + mp.msg.log("info", "using user preferred xrandr_output_mode " .. xrandr_output_mode .. " for output " .. output) + -- try to find the "old rate" for the other, currently active mode + local oldr + old_mode, oldr = string.match(mls, '\n ([0-9x]+) ([^*\n]*%*[^\n]*)') + if (oldr ~= nil) then + for s in string.gmatch(oldr, "([^ ]+)%*") do + old_rate = s + end + end + mp.msg.log("v", "old_rate=" .. old_rate .. " found for old_mode=" .. tostring(old_mode)) + end + end + + if (mode == nil) then + -- normal case: use current mode + mode, r = string.match(mls, '\n ([0-9x]+) ([^*\n]*%*[^\n]*)') + old_mode = mode + end + + if (r == nil) then + -- if no refresh rate is reported active for an output by xrandr, + -- search for the mode that is "recommended" (marked by "+" in xrandr's output) + mode, r = string.match(mls, '\n ([0-9x]+) ([^+\n]*%+[^\n]*)') + old_mode = mode + if (r == nil) then + -- there is not even a "recommended" mode, so let's just use + -- whatever first mode line there is + mode, r = string.match(mls, '\n ([0-9x]+) ([^+\n]*[^\n]*)') + old_mode = mode + end + else + -- so "r" contains a hint to the current ("old") rate, let's remember + -- it for later switching back to it. + for s in string.gmatch(r, "([^ ]+)%*") do + old_rate = s + end + end + mp.msg.log("info", "output " .. output .. " mode=" .. mode .. " old rate=" .. old_rate .. " refresh rates = " .. r) + + xrandr_modes[output] = { mode = mode, old_mode = old_mode, rates_s = r, rates = {}, old_rate = old_rate } + local i = 1 + for s in string.gmatch(r, "([^ +*]+)") do + + -- check if rate "r" is black-listed - this is checked here because + if (not xrandr_check_blacklist(mode, 0.0 + s)) then + xrandr_modes[output].rates[i] = 0.0 + s + i = i+1 + end + end + + output_idx = output_idx + 1 + end + +end + +function xrandr_find_best_fitting_rate(fps, output) + + local xrandr_rates = xrandr_modes[output].rates + + local best_fitting_rate = nil + local best_fitting_ratio = math.huge + + -- try integer multipliers of 1 to 10 (given that high-fps displays exist these days) + for m=1,10 do + for i=1,#xrandr_rates do + local r = xrandr_rates[i] + local ratio = r / (m * fps) + if (ratio < 1.0) then + ratio = 1.0 / ratio + end + -- If the ratio is more than "very insignificantly off", + -- then add a tiny additional score that will prefer faster + -- over slower display frame rates, because those will cause + -- shorter "stutters" when the display needs to skip or + -- duplicate one source frame. + -- If the ratio is very close to 1.0, then we rather not + -- choose the higher of the existing display rates, because + -- displays performing frame interpolation work better when + -- presented the actual, non-repeated source material frames. + if (ratio > 1.0001) then + ratio = ratio + (0.00000001 * (1000.0 - r)) + end + -- mp.msg.log("info", "ratio " .. ratio .. " for r == " .. r) + if (ratio < best_fitting_ratio) then + best_fitting_ratio = ratio + -- the xrand -q output may print nearby frequencies as the same + -- rounded numbers - therefore, if our multiplier is == 1, + -- we better return the video's frame rate, which xrandr + -- is then likely to set the best rate for, even if the mode + -- has some "odd" rate + if (m == 1) then + r = fps + end + best_fitting_rate = r + end + end + end + + return best_fitting_rate +end + + +xrandr_active_outputs = {} +function xrandr_set_active_outputs() + local dn = mp.get_property("display-names") + + if (dn ~= nil) then + mp.msg.log("v","display-names=" .. dn) + xrandr_active_outputs = {} + for w in (dn .. ","):gmatch("([^,]*),") do + table.insert(xrandr_active_outputs, w) + end + end +end + +-- last detected non-nil video frame rate: +xrandr_cfps = nil + +-- for each output, we remember which refresh rate we set last, so +-- we do not unnecessarily set the same refresh rate again +xrandr_previously_set = {} + +function xrandr_set_rate() + + local f = mp.get_property_native("container-fps") + if (f == nil or f == xrandr_cfps) then + -- either no change or no frame rate information, so don't set anything + return + end + xrandr_cfps = f + + xrandr_detect_available_rates() + + xrandr_set_active_outputs() + + local vdpau_hack = false + local old_vid = nil + local old_position = nil + if (mp.get_property("options/vo") == "vdpau" or mp.get_property("options/hwdec") == "vdpau") then + -- enable wild hack: need to close and re-open video for vdpau, + -- because vdpau barfs if xrandr is run while it is in use + + vdpau_hack = true + old_position = mp.get_property("time-pos") + old_vid = mp.get_property("vid") + mp.set_property("vid", "no") + end + + -- unless "--script-opts=xrandr-ignore_unknown_oldrate=true" is set, + -- xrandr.lua will not touch display outputs for which it cannot + -- get information on the current refresh rate for - assuming that + -- such outputs are "disabled" somehow. + local ignore_unknown_oldrate = mp.get_opt("xrandr-ignore_unknown_oldrate") + if (ignore_unknown_oldrate == nil) then + ignore_unknown_oldrate = false + end + + + local outs = {} + if (#xrandr_active_outputs == 0) then + -- No active outputs - probably because vo (like with vdpau) does + -- not provide the information which outputs are covered. + -- As a fall-back, let's assume all connected outputs are relevant. + mp.msg.log("v","no output is known to be used by mpv, assuming all connected outputs are used.") + outs = xrandr_connected_outputs + else + outs = xrandr_active_outputs + end + + -- iterate over all relevant outputs used by mpv's output: + for n, output in ipairs(outs) do + + if (ignore_unknown_oldrate == false and xrandr_modes[output].old_rate == 0) then + mp.msg.log("info", "not touching output " .. output .. " because xrandr did not indicate a used refresh rate for it - use --script-opts=xrandr-ignore_unknown_oldrate=true if that is not what you want.") + else + local bfr = xrandr_find_best_fitting_rate(xrandr_cfps, output) + + if (bfr == 0.0) then + mp.msg.log("info", "no non-blacklisted rate available, not invoking xrandr") + else + mp.msg.log("info", "container fps is " .. xrandr_cfps .. "Hz, for output " .. output .. " mode " .. xrandr_modes[output].mode .. " the best fitting display rate we will pass to xrandr is " .. bfr .. "Hz") + + if (bfr == xrandr_previously_set[output]) then + mp.msg.log("v", "output " .. output .. " was already set to " .. bfr .. "Hz before - not changing") + else + -- invoke xrandr to set the best fitting refresh rate for output + local p = {} + p["cancellable"] = false + p["args"] = {} + p["args"][1] = "xrandr" + p["args"][2] = "--output" + p["args"][3] = output + p["args"][4] = "--mode" + p["args"][5] = xrandr_modes[output].mode + p["args"][6] = "--rate" + p["args"][7] = tostring(bfr) + + local cmd_as_string = "" + for k, v in pairs(p["args"]) do + cmd_as_string = cmd_as_string .. v .. " " + end + mp.msg.log("debug", "executing as subprocess: \"" .. cmd_as_string .. "\"") + local res = utils.subprocess(p) + + if (res["error"] ~= nil) then + mp.msg.log("error", "failed to set refresh rate for output " .. output .. " using xrandr, error message: " .. res["error"]) + else + xrandr_previously_set[output] = bfr + end + end + end + end + end + + if (vdpau_hack) then + mp.set_property("vid", old_vid) + if (old_position ~= nil) then + mp.commandv("seek", old_position, "absolute", "keyframes") + else + mp.msg.log("v", "old_position is 'nil' - not seeking after vdpau re-initialization") + end + end +end + + +function xrandr_set_old_rate() + + local outs = {} + if (#xrandr_active_outputs == 0) then + -- No active outputs - probably because vo (like with vdpau) does + -- not provide the information which outputs are covered. + -- As a fall-back, let's assume all connected outputs are relevant. + mp.msg.log("v","no output is known to be used by mpv, assuming all connected outputs are used.") + outs = xrandr_connected_outputs + else + outs = xrandr_active_outputs + end + + -- iterate over all relevant outputs used by mpv's output: + for n, output in ipairs(outs) do + + local old_rate = xrandr_modes[output].old_rate + + if (old_rate == 0 or xrandr_previously_set[output] == nil ) then + mp.msg.log("v", "no previous frame rate known for output " .. output .. " - so no switching back.") + else + + if (math.abs(old_rate-xrandr_previously_set[output]) < 0.001) then + mp.msg.log("v", "output " .. output .. " is already set to " .. old_rate .. "Hz - no switching back required") + else + + mp.msg.log("info", "switching output " .. output .. " that was set for replay to mode " .. xrandr_modes[output].mode .. " at " .. xrandr_previously_set[output] .. "Hz back to mode " .. xrandr_modes[output].old_mode .. " with refresh rate " .. old_rate .. "Hz") + + -- invoke xrandr to set the best fitting refresh rate for output + local p = {} + p["cancellable"] = false + p["args"] = {} + p["args"][1] = "xrandr" + p["args"][2] = "--output" + p["args"][3] = output + p["args"][4] = "--mode" + p["args"][5] = xrandr_modes[output].old_mode + p["args"][6] = "--rate" + p["args"][7] = old_rate + + local res = utils.subprocess(p) + + if (res["error"] ~= nil) then + mp.msg.log("error", "failed to set refresh rate for output " .. output .. " using xrandr, error message: " .. res["error"]) + else + xrandr_previously_set[output] = old_rate + end + end + end + + end + +end + +-- we'll consider setting refresh rates whenever the video fps or the active outputs change: +mp.observe_property("container-fps", "native", xrandr_set_rate) +mp.observe_property("display-names", "native", xrandr_set_rate) + +-- and we'll try to revert the refresh rate when mpv is shut down +mp.register_event("shutdown", xrandr_set_old_rate) + |
