mirror of https://github.com/mpv-player/mpv
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.
This commit is contained in:
parent
f457f3839a
commit
13d354e46d
|
@ -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
|
||||
|
|
131
DOCS/man/mpv.rst
131
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
|
||||
-------------------------
|
||||
|
||||
Some profiles are loaded automatically. The following example demonstrates this:
|
||||
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 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
|
||||
========================================
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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.
|
||||
*
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -78,6 +78,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}
|
||||
};
|
||||
|
|
|
@ -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
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in New Issue