summaryrefslogtreecommitdiff
path: root/mac/.config/mpv/unused_scipts/xrandr.lua
diff options
context:
space:
mode:
Diffstat (limited to 'mac/.config/mpv/unused_scipts/xrandr.lua')
-rw-r--r--mac/.config/mpv/unused_scipts/xrandr.lua382
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)
+