summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--DOCS/interface-changes.rst2
-rw-r--r--DOCS/man/mpv.rst131
-rw-r--r--DOCS/man/options.rst5
-rw-r--r--options/m_config_frontend.c16
-rw-r--r--options/m_config_frontend.h3
-rw-r--r--options/options.c4
-rw-r--r--options/options.h1
-rw-r--r--options/parse_configfile.c3
-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
-rw-r--r--wscript_build.py3
13 files changed, 326 insertions, 7 deletions
diff --git a/DOCS/interface-changes.rst b/DOCS/interface-changes.rst
index 4cbeb98cce..e58c13216d 100644
--- a/DOCS/interface-changes.rst
+++ b/DOCS/interface-changes.rst
@@ -72,6 +72,8 @@ Interface changes
- remove --video-sync-adrop-size option (implementation was changed, no
replacement for what this option did)
- undeprecate --video-sync=display-adrop
+ - deprecate legacy auto profiles (profiles starting with "extension." and
+ "protocol."). Use conditional auto profiles instead.
--- mpv 0.32.0 ---
- change behavior when using legacy option syntax with options that start
with two dashes (``--`` instead of a ``-``). Now, using the recommended
diff --git a/DOCS/man/mpv.rst b/DOCS/man/mpv.rst
index dbd700cc9e..a7202864e4 100644
--- a/DOCS/man/mpv.rst
+++ b/DOCS/man/mpv.rst
@@ -694,10 +694,130 @@ or at runtime with the ``apply-profile <name>`` command.
profile=big-cache
-Auto profiles
--------------
+Conditional auto profiles
+-------------------------
+
+Profiles which have the ``profile-cond`` option set are applied automatically
+if the associated condition matches (unless auto profiles are disabled). The
+option takes a string, which is interpreted as Lua condition. If evaluating the
+expression returns true, the profile is applied, if it returns false, it is
+ignored. This Lua code execution is not sandboxed.
+
+Any variables in condition expressions can reference properties. If an
+identifier is not already by defined by Lua or mpv, it is interpreted as
+property. For example, ``pause`` would return the current pause status. If the
+variable name contains any ``_`` characters, they are turned into ``-``. For
+example, ``playback_time`` would return the property ``playback-time``.
+
+A more robust way to access properties is using ``p.property_name`` or
+``get("property-name", default_value)``. The automatic variable to property
+magic will break if a new identifier with the same name is introduced (for
+example, if a function named ``pause()`` were added, ``pause`` would return a
+function value instead of the value of the ``pause`` property).
+
+Note that if a property is not available, it will return ``nil``, which can
+cause errors if used in expressions. These are logged in verbose mode, and the
+expression is considered to be false.
+
+Whenever a property referenced by a profile condition changes, the condition
+is re-evaluated. If the return value of the condition changes from false or
+error to true, the profile is applied.
+
+Note that profiles cannot be "unapplied", so you may have to define inverse
+profiles with inverse conditions do undo a profile.
+
+.. admonition:: Example
+
+ Make only HD video look funny:
+
+ ::
+
+ [something]
+ profile-desc=HD video sucks
+ profile-cond=width >= 1280
+ hue=-50
+
+ If you want the profile to be reverted if the condition goes to false again,
+ you need to do this by manually creating an inverse profile:
+
+ ::
+
+ [something]
+ profile-desc=Flip video when entering fullscreen
+ profile-cond=fullscreen
+ vf=vflip
+
+ [something2]
+ profile-desc=Inverse of [something]
+ profile-cond=not fullscreen
+ vf=
+
+ This sets the video filter chain to ``vflip`` when entering fullscreen. The
+ first profile does not cause the filter to be removed when leaving
+ fullscreen. A second profile has to be defined, which is explicitly applied
+ on leaving fullscreen, and which explicitly clears the filter list. (This
+ would also clear the filter list at program start when starting the player
+ in windowed mode.)
+
+.. warning::
+
+ Every time an involved property changes, the condition is evaluated again.
+ If your condition uses ``p.playback_time`` for example, the condition is
+ re-evaluated approximately on every video frame. This is probably slow.
+
+This feature is managed by an internal Lua script. Conditions are executed as
+Lua code within this script. Its environment contains at least the following
+things:
+
+``(function environment table)``
+ Every Lua function has an environment table. This is used for identifier
+ access. There is no named Lua symbol for it; it is implicit.
+
+ The environment does "magic" accesses to mpv properties. If an identifier
+ is not already defined in ``_G``, it retrieves the mpv property of the same
+ name. Any occurrences of ``_`` in the name are replaced with ``-`` before
+ reading the property. The returned value is as retrieved by
+ ``mp.get_property_native(name)``. Internally, a cache of property values,
+ updated by observing the property is used instead, so properties that are
+ not observable will be stuck at the initial value forever.
+
+ If you want to access properties, that actually contain ``_`` in the name,
+ use ``get()`` (which does not perform transliteration).
+
+ Internally, the environment table has a ``__index`` meta method set, which
+ performs the access logic.
+
+``p``
+ A "magic" table similar to the environment table. Unlike the latter, this
+ does not prefer accessing variables defined in ``_G`` - it always accesses
+ properties.
+
+``get(name [, def])``
+ Read a property and return its value. If the property value is ``nil`` (e.g.
+ if the property does not exist), ``def`` is returned.
+
+ This is superficially similar to ``mp.get_property_native(name)``. An
+ important difference is that this accesses the property cache, and enables
+ the change detection logic (which is essential to the dynamic runtime
+ behavior of auto profiles). Also, it does not return an error value as
+ second return value.
+
+ The "magic" tables mentioned above use this function as backend. It does not
+ perform the ``_`` transliteration.
+
+In addition, the same environment as in a blank mpv Lua script is present. For
+example, ``math`` is defined and gives access to the Lua standard math library.
+
+.. warning::
+
+ This feature is subject to change indefinitely. You might be forced to
+ adjust your profiles on mpv updates.
+
+Legacy auto profiles
+--------------------
-Some profiles are loaded automatically. The following example demonstrates this:
+Some profiles are loaded automatically using a legacy mechanism. The following
+example demonstrates this:
.. admonition:: Auto profile loading
@@ -705,14 +825,15 @@ Some profiles are loaded automatically. The following example demonstrates this:
[extension.mkv]
profile-desc="profile for .mkv files"
- vf=flip
+ vf=vflip
The profile name follows the schema ``type.name``, where type can be
``protocol`` for the input/output protocol in use (see ``--list-protocols``),
and ``extension`` for the extension of the path of the currently played file
(*not* the file format).
-This feature is very limited, and there are no other auto profiles.
+This feature is very limited, and is considered soft-deprecated. Use conditional
+auto profiles.
Using mpv from other programs or scripts
========================================
diff --git a/DOCS/man/options.rst b/DOCS/man/options.rst
index 95bec1c3d3..ad7d449b33 100644
--- a/DOCS/man/options.rst
+++ b/DOCS/man/options.rst
@@ -999,6 +999,11 @@ Program Behavior
show the console, and ``ESC`` to hide it again. (This is based on a user
script called ``repl.lua``.)
+``--load-auto-profiles=<yes|no|auto>``
+ Enable the builtin script that does auto profiles (default: auto). See
+ `Conditional auto profiles`_ for details. ``auto`` will load the script,
+ but immediately unload it if there are no conditional profiles.
+
``--player-operation-mode=<cplayer|pseudo-gui>``
For enabling "pseudo GUI mode", which means that the defaults for some
options are changed. This option should not normally be used directly, but
diff --git a/options/m_config_frontend.c b/options/m_config_frontend.c
index 467c13eb8f..94cc9c015a 100644
--- a/options/m_config_frontend.c
+++ b/options/m_config_frontend.c
@@ -50,8 +50,10 @@ struct m_profile {
struct m_profile *next;
char *name;
char *desc;
+ char *cond;
int num_opts;
// Option/value pair array.
+ // name,value = opts[n*2+0],opts[n*2+1]
char **opts;
};
@@ -85,6 +87,10 @@ static int show_profile(struct m_config *config, bstr param)
MP_INFO(config, "Profile %s: %s\n", p->name,
p->desc ? p->desc : "");
config->profile_depth++;
+ if (p->cond) {
+ MP_INFO(config, "%*sprofile-cond=%s\n", config->profile_depth, "",
+ p->cond);
+ }
for (int i = 0; i < p->num_opts; i++) {
MP_INFO(config, "%*s%s=%s\n", config->profile_depth, "",
p->opts[2 * i], p->opts[2 * i + 1]);
@@ -884,6 +890,14 @@ void m_profile_set_desc(struct m_profile *p, bstr desc)
p->desc = bstrto0(p, desc);
}
+void m_profile_set_cond(struct m_profile *p, bstr cond)
+{
+ TA_FREEP(&p->cond);
+ cond = bstr_strip(cond);
+ if (cond.len)
+ p->cond = bstrto0(p, cond);
+}
+
int m_config_set_profile_option(struct m_config *config, struct m_profile *p,
bstr name, bstr val)
{
@@ -944,6 +958,8 @@ struct mpv_node m_config_get_profiles(struct m_config *config)
node_map_add_string(entry, "name", profile->name);
if (profile->desc)
node_map_add_string(entry, "profile-desc", profile->desc);
+ if (profile->cond)
+ node_map_add_string(entry, "profile-cond", profile->cond);
struct mpv_node *opts =
node_map_add(entry, "options", MPV_FORMAT_NODE_ARRAY);
diff --git a/options/m_config_frontend.h b/options/m_config_frontend.h
index 19fbcadf25..81bc78b6e1 100644
--- a/options/m_config_frontend.h
+++ b/options/m_config_frontend.h
@@ -232,6 +232,9 @@ struct m_profile *m_config_add_profile(struct m_config *config, char *name);
*/
void m_profile_set_desc(struct m_profile *p, bstr desc);
+// Set auto profile condition of a profile.
+void m_profile_set_cond(struct m_profile *p, bstr cond);
+
/* Add an option to a profile.
* Used by the config file parser when defining a profile.
*
diff --git a/options/options.c b/options/options.c
index 4467c106ba..63c99053a8 100644
--- a/options/options.c
+++ b/options/options.c
@@ -426,6 +426,9 @@ static const m_option_t mp_opts[] = {
.flags = UPDATE_BUILTIN_SCRIPTS},
{"load-osd-console", OPT_FLAG(lua_load_console),
.flags = UPDATE_BUILTIN_SCRIPTS},
+ {"load-auto-profiles",
+ OPT_CHOICE(lua_load_auto_profiles, {"no", 0}, {"yes", 1}, {"auto", -1}),
+ .flags = UPDATE_BUILTIN_SCRIPTS},
#endif
// ------------------------- stream options --------------------
@@ -944,6 +947,7 @@ static const struct MPOpts mp_default_opts = {
.lua_ytdl_raw_options = NULL,
.lua_load_stats = 1,
.lua_load_console = 1,
+ .lua_load_auto_profiles = -1,
#endif
.auto_load_scripts = 1,
.loop_times = 1,
diff --git a/options/options.h b/options/options.h
index 45d0747358..ea5ece65a1 100644
--- a/options/options.h
+++ b/options/options.h
@@ -144,6 +144,7 @@ typedef struct MPOpts {
char **lua_ytdl_raw_options;
int lua_load_stats;
int lua_load_console;
+ int lua_load_auto_profiles;
int auto_load_scripts;
diff --git a/options/parse_configfile.c b/options/parse_configfile.c
index 14b30e87b4..96b607d554 100644
--- a/options/parse_configfile.c
+++ b/options/parse_configfile.c
@@ -131,6 +131,9 @@ int m_config_parse(m_config_t *config, const char *location, bstr data,
if (bstr_equals0(option, "profile-desc")) {
m_profile_set_desc(profile, value);
res = 0;
+ } else if (bstr_equals0(option, "profile-cond")) {
+ m_profile_set_cond(profile, value);
+ res = 0;
} else {
res = m_config_set_profile_option(config, profile, option, value);
}
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)
diff --git a/wscript_build.py b/wscript_build.py
index 6e5838f771..c57619b353 100644
--- a/wscript_build.py
+++ b/wscript_build.py
@@ -85,7 +85,8 @@ def build(ctx):
)
lua_files = ["defaults.lua", "assdraw.lua", "options.lua", "osc.lua",
- "ytdl_hook.lua", "stats.lua", "console.lua"]
+ "ytdl_hook.lua", "stats.lua", "console.lua",
+ "auto_profiles.lua"]
for fn in lua_files:
fn = "player/lua/" + fn