Something went wrong. Try again.
🎥 Command line media player
Something went wrong. Try again.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208-- Note: anything global is accessible by profile condition expressions.
local msg = require 'mp.msg'
local profiles = {}local watched_properties = {} -- indexed by property name (used as a set)local cached_properties = {} -- property name -> last known raw valuelocal properties_to_profiles = {} -- property name -> set of profiles using itlocal have_dirty_profiles = false -- at least one profile is marked dirtylocal pending_hooks = {} -- as set (keys only, meaningless values)
-- Used during evaluation of the profile condition, and should contain the-- profile the condition is evaluated for.local current_profile = nil
-- Cached set of all top-level mpv properties. Only used for extra validation.local property_set = {}for _, property in pairs(mp.get_property_native("property-list")) do property_set[property] = trueend
local function evaluate(profile) msg.verbose("Re-evaluating auto profile " .. profile.name)
current_profile = profile local status, res = pcall(profile.cond) current_profile = nil
if not status then -- errors can be "normal", e.g. in case properties are unavailable msg.verbose("Profile condition error on evaluating: " .. res) res = false end res = not not res if res ~= profile.status then if res == true then msg.info("Applying auto profile: " .. profile.name) mp.commandv("apply-profile", profile.name) elseif profile.status == true and profile.has_restore_opt then msg.info("Restoring profile: " .. profile.name) mp.commandv("apply-profile", profile.name, "restore") end end profile.status = res profile.dirty = falseend
local function on_property_change(name, val) cached_properties[name] = val -- Mark all profiles reading this property as dirty, so they get re-evaluated -- the next time the script goes back to sleep. local dependent_profiles = properties_to_profiles[name] if dependent_profiles then for profile, _ in pairs(dependent_profiles) do assert(profile.cond) -- must be a profile table profile.dirty = true have_dirty_profiles = true end endend
local function on_idle() -- When events and property notifications stop, re-evaluate all dirty profiles. if have_dirty_profiles then for _, profile in ipairs(profiles) do if profile.dirty then evaluate(profile) end end end have_dirty_profiles = false -- Release all hooks (the point was to wait until an idle event) while true do local h = next(pending_hooks) if not h then break end pending_hooks[h] = nil h:cont() endend
local function on_hook(h) h:defer() pending_hooks[h] = trueend
function get(name, default) -- Normally, we use the cached value only if not watched_properties[name] then watched_properties[name] = true local res, err = mp.get_property_native(name) -- Property has to not exist and the toplevel of property in the name must also -- not have an existing match in the property set for this to be considered an error. -- This allows things like user-data/test to still work. if err == "property not found" and property_set[name:match("^([^/]+)")] == nil then msg.error("Property '" .. name .. "' was not found.") return default end cached_properties[name] = res mp.observe_property(name, "native", on_property_change) end -- The first time the property is read we need add it to the -- properties_to_profiles table, which will be used to mark the profile -- dirty if a property referenced by it changes. if current_profile then local map = properties_to_profiles[name] if not map then map = {} properties_to_profiles[name] = map end map[current_profile] = true end local val = cached_properties[name] if val == nil then val = default end return valend
local function magic_get(name) -- Lua identifiers can't contain "-", so in order to match with mpv -- property conventions, replace "_" to "-" name = string.gsub(name, "_", "-") return get(name, nil)end
local evil_magic = {}setmetatable(evil_magic, { __index = function(_, key) -- interpret everything as property, unless it already exists as -- a non-nil global value local v = _G[key] if type(v) ~= "nil" then return v end return magic_get(key) end,})
p = {}setmetatable(p, { __index = function(_, key) return magic_get(key) end,})
local function compile_cond(name, s) local code, chunkname = "return " .. s, "profile " .. name .. " condition" local chunk, err -- luacheck: push -- luacheck: ignore setfenv loadstring if setfenv then -- lua 5.1 chunk, err = loadstring(code, chunkname) if chunk then setfenv(chunk, evil_magic) end else -- lua 5.2 chunk, err = load(code, chunkname, "t", evil_magic) end -- luacheck: pop if not chunk then msg.error("Profile '" .. name .. "' condition: " .. err) chunk = function() return false end end return chunkend
local function load_profiles(profiles_property) for _, v in ipairs(profiles_property) do local cond = v["profile-cond"] if cond and #cond > 0 then local profile = { name = v.name, cond = compile_cond(v.name, cond), properties = {}, status = nil, dirty = true, -- need re-evaluate has_restore_opt = v["profile-restore"] and v["profile-restore"] ~= "default" } profiles[#profiles + 1] = profile have_dirty_profiles = true end endend
mp.observe_property("profile-list", "native", function (_, profiles_property) profiles = {} watched_properties = {} cached_properties = {} properties_to_profiles = {} mp.unobserve_property(on_property_change)
load_profiles(profiles_property)
if #profiles < 1 and mp.get_property("load-auto-profiles") == "auto" then exit() return end
on_idle() -- re-evaluate all profiles immediatelyend)
mp.register_idle(on_idle)for _, name in ipairs({"on_load", "on_preloaded", "on_loaded", "on_before_start_file"}) do mp.add_hook(name, 5, on_hook)end