--[[ See script details on https://github.com/kevinlekiller/mpv_scripts Valid --script-opts are (they are all optional): autospeed-nircmd=false true/false - Use nircmd to change the refresh rate of your monitor. autospeed-speed=false true/false - Adjust speed of the video?. autospeed-nircmdc="nircmdc" String - Path to nircmdc executable file. If not set, nircmdc will be searched in Windows PATH variable. autospeed-monitor=0 Number - Which monitor (display) to set the refresh rate on. autospeed-dwidth=1920 Number - Display width. autospeed-dheight=1080 Number - Display height. autospeed-bdepth=32 Number - Display bit depth. autospeed-rates="60" String - String of refresh rates your monitor supports and you want to use, separated by semicolons. Nircmd seems to prefer rounded numbers, 72 instead of 71.92 for example. Examples: autospeed-rates="60" | autospeed-rates="50;60;72" Note if you want a rate to be prefered over another, for example the video is 24hz and your rates are "100;120;144", but you want 144hz instead of 120hz for 24fps videos, change the order to autospeed-rates="100;144;120", then 144hz will take precendence over 120hz. autospeed-exitrate=60 Number - Which refresh rate to set when exiting mpv. Set to 0 to ignore. autospeed-minspeed=0.9 Number - Minimum allowable speed to play video at. autospeed-maxspeed=1.1 Number - Maximum allowable speed to play video at. autospeed-osd=true true/false - Enable OSD. autospeed-osdtime=10 Number - How many seconds the OSD will be shown. autospeed-osdkey=y - Key to press to show the OSD. autospeed-estfps=false true/false - Calculate/change speed if a video has a variable fps at the cost of higher CPU usage (most videos have a fixed fps). autospeed-spause true/false - Pause video while switching display modes. Number - If you set this a number, it will pause for that amount of seconds. Example: mpv file.mkv --script-opts=autospeed-nircmd=true,autospeed-minspeed=0.8 --]] --[[ Copyright (C) 2016-2017 kevinlekiller This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. https://www.gnu.org/licenses/gpl-2.0.html --]] local _global = { osd_start = mp.get_property_osd("osd-ass-cc/0"), osd_end = mp.get_property_osd("osd-ass-cc/1"), utils = require 'mp.utils', rateCache = {}, lastDrr = 0, speedCache = {}, next = next, } function round(number) return math.floor(number + 0.5) end function osdEcho() if (_global.options["osd"] ~= true) then return end setOSD() if (_global.temp["output"] ~= nil) then mp.osd_message(_global.temp["output"], _global.options["osdtime"]) end end function getOptions() _global.options = { ["nircmd"] = false, ["speed"] = false, ["nircmdc"] = "nircmdc", ["monitor"] = 0, ["dwidth"] = 1920, ["dheight"] = 1080, ["bdepth"] = 32, ["rates"] = "", ["exitrate"] = 0, ["minspeed"] = 0.9, ["maxspeed"] = 1.1, ["osd"] = false, ["osdtime"] = 10, ["osdkey"] = "y", ["estfps"] = false, ["spause"] = 0, } for key, value in pairs(_global.options) do local opt = mp.get_opt("autospeed-" .. key) if (opt ~= nil) then if ((key == "nircmd" or key == "speed" or key == "osd" or key == "estfps") and opt == "true") then _global.options[key] = true elseif (key == "minspeed" or key == "maxspeed" or key == "osdtime" or key == "monitor" or key == "dwidth" or key == "dheight" or key == "bdepth" or key == "exitrate" or key == "spause") then local test = tonumber(opt) if (test ~= nil) then _global.options[key] = test end else _global.options[key] = opt end end end end getOptions() function main(name, fps) if (fps == nil) then return end _global.temp["fps"] = fps findRefreshRate() determineSpeed() if (_global.options["speed"] == true and _global.temp["speed"] >= _global.options["minspeed"] and _global.temp["speed"] <= _global.options["maxspeed"]) then mp.set_property_number("speed", _global.temp["speed"]) else _global.temp["speed"] = _global.confSpeed end end function setOSD() _global.temp["output"] = (_global.osd_start .. "{\\b1}Original monitor refresh rate{\\b0}\\h\\h" .. _global.temp["start_drr"] .. "Hz\\N" .. "{\\b1}Current monitor refresh rate{\\b0}\\h\\h" .. _global.temp["drr"] .. "Hz\\N" .. "{\\b1}Original video fps{\\b0}\\h\\h\\h\\h\\h\\h\\h\\h\\h\\h\\h\\h\\h\\h\\h\\h\\h\\h\\h\\h\\h" .. _global.temp["fps"] .. "fps\\N" .. "{\\b1}Current video fps{\\b0}\\h\\h\\h\\h\\h\\h\\h\\h\\h\\h\\h\\h\\h\\h\\h\\h\\h\\h\\h\\h\\h" .. (_global.temp["fps"] * _global.temp["speed"]) .. "fps\\N" .. "{\\b1}Original mpv speed setting{\\b0}\\h\\h\\h\\h\\h\\h" .. _global.confSpeed .. "x\\N" .. "{\\b1}Current mpv speed setting{\\b0}\\h\\h\\h\\h\\h\\h" .. _global.temp["speed"] .. "x" .. _global.osd_end ) end function determineSpeed() local id = _global.temp["drr"] .. _global.temp["fps"] if (_global.speedCache[id] ~= nil) then _global.temp["speed"] = _global.speedCache[id] return end if (_global.temp["drr"] > _global.temp["fps"]) then local difference = (_global.temp["drr"] / _global.temp["fps"]) if (difference >= 2) then -- fps = 24fps, drr = 60hz -- difference = 60hz/24fps = 3 rounded -- 24fps * 3 = 72fps -- 60hz / 72fps = 0.833333333333 speed -- 72fps * 0.833333333333 = 60fps _global.temp["speed"] = (_global.temp["drr"] / (_global.temp["fps"] * round(difference))) else -- fps = 50fps, drr = 60hz -- 60hz / 50fps = 1.2 speed -- 50fps * 1.2 speed = 60fps -- fps = 59.94fps, drr = 60hz -- 60hz / 59.94fps = 1.001001001001001 speed -- 59.94fps * 1.001001001001001 = 60fps _global.temp["speed"] = difference end elseif (_global.temp["drr"] < _global.temp["fps"]) then local difference = (_global.temp["fps"] / _global.temp["drr"]) if (difference >= 2) then -- fps = 120fps, drr = 25hz -- difference = 120fps/25hz = 5 rounded -- 120fps/5 = 24fps ; 25hz / 24fps = 1.04166666667 speed -- 24fps * 1.04166666667 speed = 25fps _global.temp["speed"] = (_global.temp["drr"] / (_global.temp["fps"] / round(difference))) else -- fps = 60fps, drr = 50hz -- difference = 50hz / 60fps = 0.833333333333 speed -- 60fps * 0.833333333333 speed = 50fps -- fps = 60fps, drr = 59.94hz -- difference = 59.94hz / 60fps = 0.999 speed -- 60fps * 0.999 speed = 59.94fps _global.temp["speed"] = (_global.temp["drr"] / _global.temp["fps"]) end elseif (_global.temp["drr"] == _global.temp["fps"]) then _global.temp["speed"] = 1 end _global.speedCache[id] = _global.temp["speed"] end function findRefreshRate() -- This is to prevent a system call if the screen refresh / video fps has not changed. if (_global.temp["drr"] == _global.lastDrr) then return elseif (_global.rateCache[_global.temp["drr"]] ~= nil) then setRate(_global.rateCache[_global.temp["drr"]]) return end if (_global.options["nircmd"] ~= true or _global.options["rates"] == "") then return end local floor_fps = math.floor(_global.temp["fps"]) if (_global.temp["maxrate"] == nil) then _global.temp["maxrate"] = 0 for rate in string.gmatch(_global.options["rates"], "[%w.]+") do rate = tonumber(rate) if (rate > _global.temp["maxrate"]) then _global.temp["maxrate"] = rate end end if (_global.temp["maxrate"] == 0) then _global.options["rates"] = "" return end end local iterator = 1 if (_global.temp["maxrate"] > floor_fps) then iterator = round(_global.temp["maxrate"] / floor_fps) elseif (_global.temp["maxrate"] < floor_fps) then iterator = round(floor_fps / _global.temp["maxrate"]) else setRate(_global.temp["maxrate"]) return end local smallest = 0 local foundRate = false for rate in string.gmatch(_global.options["rates"], "[%w.]+") do rate = tonumber(rate) local min = (rate * _global.options["minspeed"]) local max = (rate * _global.options["maxspeed"]) for multiplier = 1, iterator do local multiplied_fps = (multiplier * floor_fps) if (multiplied_fps >= min and multiplied_fps <= max) then if (multiplied_fps < rate) then local difference = (rate - multiplied_fps) if (smallest == 0 or difference < smallest) then smallest = difference foundRate = rate end elseif (multiplied_fps > rate) then local difference = (multiplied_fps - rate) if (smallest == 0 or difference < smallest) then smallest = difference foundRate = rate end else setRate(rate) return end end end end if (foundRate ~= false) then setRate(foundRate) end end function setRate(rate) local paused = mp.get_property("pause") if (_global.options["spause"] > 0 and paused ~= "yes") then mp.set_property("pause", "yes") end _global.utils.subprocess({ ["cancellable"] = false, ["args"] = { [1] = _global.options["nircmdc"], [2] = "setdisplay", [3] = "monitor:" .. _global.options["monitor"], [4] = _global.options["dwidth"], [5] = _global.options["dheight"], [6] = _global.options["bdepth"], [7] = rate } }) if (_global.options["spause"] > 0 and paused ~= "yes") then --os.execute("ping -n " .. _global.options["spause"] .. " localhost > NUL") _global.utils.subprocess({ ["cancellable"] = false, ["args"] = { [1] = "ping", [2] = "-n", [3] = _global.options["spause"], [4] = "localhost", [5] = ">", [6] = "NUL" } }) mp.set_property("pause", "no") end --os.execute("ping -n 2 localhost > NUL") _global.utils.subprocess({ ["cancellable"] = false, ["args"] = { [1] = "ping", [2] = "-n", [3] = "2", [4] = "localhost", [5] = ">", [6] = "NUL" } }) _global.temp["drr"] = mp.get_property_native("display-fps") _global.rateCache[_global.temp["drr"]] = rate _global.lastDrr = _global.temp["drr"] end function start() mp.unobserve_property(start) _global.temp = {} _global.temp["start_drr"] = mp.get_property_native("display-fps") if not (_global.temp["start_drr"]) then return end _global.temp["drr"] = _global.temp["start_drr"] if not (_global.confSpeed) then _global.confSpeed = mp.get_property_native("speed") end local test = mp.get_property("container-fps") if (test == nil or test == "nil property unavailable") then if (_global.options["estfps"] ~= true) then return end test = mp.get_property("estimated-vf-fps") if (test == nil or test == "nil property unavailable") then return end mp.observe_property("estimated-vf-fps", "number", main) else mp.observe_property("container-fps", "number", main) end mp.add_key_binding(_global.options["osdkey"], mp.get_script_name(), osdEcho, {repeatable=true}) if (_global.options["nircmd"] == true and _global.options["exitrate"] > 0) then function revertDrr() _global.utils.subprocess({ ["cancellable"] = false, ["args"] = { [1] = _global.options["nircmdc"], [2] = "setdisplay", [3] = "monitor:" .. _global.options["monitor"], [4] = _global.options["dwidth"], [5] = _global.options["dheight"], [6] = _global.options["bdepth"], [7] = _global.options["exitrate"] } }) end mp.register_event("shutdown", revertDrr) end end -- Wait until we get a video fps. function check() mp.observe_property("estimated-vf-fps", "string", start) end mp.register_event("file-loaded", check)