summaryrefslogtreecommitdiffstats
path: root/player
diff options
context:
space:
mode:
authorwm4 <wm4@nowhere>2020-08-05 22:37:47 +0200
committerwm4 <wm4@nowhere>2020-08-05 22:37:47 +0200
commit13d354e46d27fd0c433880839abcf9096dbcbc2f (patch)
tree8b410c67db9a1ee0573892386d442019b54122da /player
parentf457f3839a88447bdef2ecb3ea8b8e37864f9b93 (diff)
downloadmpv-13d354e46d27fd0c433880839abcf9096dbcbc2f.tar.bz2
mpv-13d354e46d27fd0c433880839abcf9096dbcbc2f.tar.xz
auto_profiles: add this script
This is taken from a somewhat older proof-of-concept script. The basic idea, and most of the implementation, is still the same. The way the profiles are actually defined changed. I still feel bad about this being a Lua script, and running user expressions as Lua code in a vaguely defined environment, but I guess as far as balance of effort/maintenance/results goes, this is fine. It's a bit bloated (the Lua scripting state is at least 150KB or so in total), so in order to enable this by default, I decided it should unload itself by default if no auto-profiles are used. (And currently, it does not actually rescan the profile list if a new config file is loaded some time later, so the script would do nothing anyway if no auto profiles were defined.) This still requires defining inverse profiles for "unapplying" a profile. Also this is still somewhat racy. Both will probably be alleviated to some degree in the future.
Diffstat (limited to 'player')
-rw-r--r--player/core.h2
-rw-r--r--player/lua.c3
-rw-r--r--player/lua/auto_profiles.lua158
-rw-r--r--player/scripting.c2
4 files changed, 164 insertions, 1 deletions
diff --git a/player/core.h b/player/core.h
index 8bafb707f4..9b468492bf 100644
--- a/player/core.h
+++ b/player/core.h
@@ -445,7 +445,7 @@ typedef struct MPContext {
struct mp_ipc_ctx *ipc_ctx;
- int64_t builtin_script_ids[4];
+ int64_t builtin_script_ids[5];
pthread_mutex_t abort_lock;
diff --git a/player/lua.c b/player/lua.c
index d1badac3b4..440e3c6ff8 100644
--- a/player/lua.c
+++ b/player/lua.c
@@ -79,6 +79,9 @@ static const char * const builtin_lua_scripts[][2] = {
{"@console.lua",
# include "generated/player/lua/console.lua.inc"
},
+ {"@auto_profiles.lua",
+# include "generated/player/lua/auto_profiles.lua.inc"
+ },
{0}
};
diff --git a/player/lua/auto_profiles.lua b/player/lua/auto_profiles.lua
new file mode 100644
index 0000000000..aebef28688
--- /dev/null
+++ b/player/lua/auto_profiles.lua
@@ -0,0 +1,158 @@
+-- Note: anything global is accessible by profile condition expressions.
+
+local utils = require 'mp.utils'
+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 value
+local properties_to_profiles = {} -- property name -> set of profiles using it
+local have_dirty_profiles = false -- at least one profile is marked dirty
+
+-- Used during evaluation of the profile condition, and should contain the
+-- profile the condition is evaluated for.
+local current_profile = nil
+
+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
+ elseif type(res) ~= "boolean" then
+ msg.verbose("Profile condition did not return a boolean, but "
+ .. type(res) .. ".")
+ res = false
+ end
+ if res ~= profile.status and res == true then
+ msg.info("Applying auto profile: " .. profile.name)
+ mp.commandv("apply-profile", profile.name)
+ end
+ profile.status = res
+ profile.dirty = false
+end
+
+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
+ end
+end
+
+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
+end
+
+function get(name, default)
+ -- Normally, we use the cached value only
+ if not watched_properties[name] then
+ watched_properties[name] = true
+ mp.observe_property(name, "native", on_property_change)
+ cached_properties[name] = mp.get_property_native(name)
+ 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 val
+end
+
+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(table, 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(table, key)
+ return magic_get(key)
+ end,
+})
+
+local function compile_cond(name, s)
+ -- (pre 5.2 ignores the extra arguments)
+ local chunk, err = load("return " .. s, "profile " .. name .. " condition",
+ "t", evil_magic)
+ if not chunk then
+ msg.error("Profile '" .. name .. "' condition: " .. err)
+ chunk = function() return false end
+ end
+ if setfenv then
+ setfenv(chunk, evil_magic)
+ end
+ return chunk
+end
+
+local function load_profiles()
+ for i, v in ipairs(mp.get_property_native("profile-list")) 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
+ }
+ profiles[#profiles + 1] = profile
+ have_dirty_profiles = true
+ end
+ end
+end
+
+load_profiles()
+
+if #profiles < 1 and mp.get_property("load-auto-profiles") == "auto" then
+ -- make it exist immediately
+ _G.mp_event_loop = function() end
+ return
+end
+
+mp.register_idle(on_idle)
+on_idle() -- re-evaluate all profiles immediately
diff --git a/player/scripting.c b/player/scripting.c
index 6b891d92aa..24e2931539 100644
--- a/player/scripting.c
+++ b/player/scripting.c
@@ -262,6 +262,8 @@ void mp_load_builtin_scripts(struct MPContext *mpctx)
load_builtin_script(mpctx, 1, mpctx->opts->lua_load_ytdl, "@ytdl_hook.lua");
load_builtin_script(mpctx, 2, mpctx->opts->lua_load_stats, "@stats.lua");
load_builtin_script(mpctx, 3, mpctx->opts->lua_load_console, "@console.lua");
+ load_builtin_script(mpctx, 4, mpctx->opts->lua_load_auto_profiles,
+ "@auto_profiles.lua");
}
bool mp_load_scripts(struct MPContext *mpctx)