This script provides script bindings to pan videos and images, making mpv a better image viewer out of the box. Fixes #3038, fixes #15020.
Unified · Split
| @@ -1532,6 +1532,8 @@ works like in older mpv releases: · 1531 unmodified lines | |||
| 1532 | 1532 | ||
| 1533 | .. include:: select.rst | 1533 | .. include:: select.rst |
| 1534 | 1534 | ||
| 1535 | .. include:: positioning.rst | ||
| 1536 | |||
| 1535 | .. include:: lua.rst | 1537 | .. include:: lua.rst |
| 1536 | 1538 | ||
| 1537 | .. include:: javascript.rst | 1539 | .. include:: javascript.rst |
| @@ -1067,6 +1067,10 @@ Program Behavior · 1066 unmodified lines | |||
| 1067 | Enable the builtin script that lets you select from lists of items (default: | 1067 | Enable the builtin script that lets you select from lists of items (default: |
| 1068 | yes). By default, its keybindings start with the ``g`` key. | 1068 | yes). By default, its keybindings start with the ``g`` key. |
| 1069 | 1069 | ||
| 1070 | ``--load-positioning=<yes|no>`` | ||
| 1071 | Enable the builtin script that provides various keybindings to pan videos | ||
| 1072 | and images (default: yes). | ||
| 1073 | |||
| 1070 | ``--player-operation-mode=<cplayer|pseudo-gui>`` | 1074 | ``--player-operation-mode=<cplayer|pseudo-gui>`` |
| 1071 | For enabling "pseudo GUI mode", which means that the defaults for some | 1075 | For enabling "pseudo GUI mode", which means that the defaults for some |
| 1072 | options are changed. This option should not normally be used directly, but | 1076 | options are changed. This option should not normally be used directly, but |
| @@ -0,0 +1,60 @@ | |||
| 1 | POSITIONING | ||
| 2 | =========== | ||
| 3 | |||
| 4 | This script provides script bindings to pan videos and images. It can be | ||
| 5 | disabled using the ``--load-positioning=no`` option. | ||
| 6 | |||
| 7 | Script bindings | ||
| 8 | --------------- | ||
| 9 | |||
| 10 | ``pan-x <amount>`` | ||
| 11 | Adjust ``--video-align-x`` relatively to the OSD width, rather than | ||
| 12 | relatively to the video width like the option. This is useful to pan large | ||
| 13 | images consistently. | ||
| 14 | |||
| 15 | ``amount`` is a number such that an amount of 1 scrolls as much as the OSD | ||
| 16 | width. | ||
| 17 | |||
| 18 | ``pan-y <amount>`` | ||
| 19 | Adjust ``--video-align-y`` relatively to the OSD height, rather than | ||
| 20 | relatively to the video height like the option. | ||
| 21 | |||
| 22 | ``amount`` is a number such that an amount of 1 scrolls as much as the OSD | ||
| 23 | height. | ||
| 24 | |||
| 25 | ``drag-to-pan`` | ||
| 26 | Pan the video while holding a mouse button, keeping the clicked part of the | ||
| 27 | video under the cursor. | ||
| 28 | |||
| 29 | ``align-to-cursor`` | ||
| 30 | Pan through the whole video while holding a mouse button, or after clicking | ||
| 31 | once if ``toggle_align_to_cursor`` is ``yes``. | ||
| 32 | |||
| 33 | ``cursor-centric-zoom <amount>`` | ||
| 34 | Increase ``--video-zoom`` by ``amount`` while keeping the part of the video | ||
| 35 | hovered by the cursor under it, or the average position of touch points if | ||
| 36 | known. | ||
| 37 | |||
| 38 | Configuration | ||
| 39 | ------------- | ||
| 40 | |||
| 41 | This script can be customized through a config file | ||
| 42 | ``script-opts/positioning.conf`` placed in mpv's user directory and through the | ||
| 43 | ``--script-opts`` command-line option. The configuration syntax is described in | ||
| 44 | `mp.options functions`_. | ||
| 45 | |||
| 46 | Configurable Options | ||
| 47 | ~~~~~~~~~~~~~~~~~~~~ | ||
| 48 | |||
| 49 | ``toggle_align_to_cursor`` | ||
| 50 | Default: no | ||
| 51 | |||
| 52 | Whether ``align-to-cursor`` requires holding down a mouse button to pan. If | ||
| 53 | ``no``, dragging pans. If ``yes``, clicking the first time makes pan follow | ||
| 54 | the cursor, and clicking a second time disables this. | ||
| 55 | |||
| 56 | ``suppress_osd`` | ||
| 57 | Default: no | ||
| 58 | |||
| 59 | Whether to not print the new value of ``--video-zoom`` when using | ||
| 60 | ``cursor-centric-zoom``. | ||
| @@ -554,6 +554,7 @@ static const m_option_t mp_opts[] = { · 553 unmodified lines | |||
| 554 | OPT_CHOICE(lua_load_auto_profiles, {"no", 0}, {"yes", 1}, {"auto", -1}), | 554 | OPT_CHOICE(lua_load_auto_profiles, {"no", 0}, {"yes", 1}, {"auto", -1}), |
| 555 | .flags = UPDATE_BUILTIN_SCRIPTS}, | 555 | .flags = UPDATE_BUILTIN_SCRIPTS}, |
| 556 | {"load-select", OPT_BOOL(lua_load_select), .flags = UPDATE_BUILTIN_SCRIPTS}, | 556 | {"load-select", OPT_BOOL(lua_load_select), .flags = UPDATE_BUILTIN_SCRIPTS}, |
| 557 | {"load-positioning", OPT_BOOL(lua_load_positioning), .flags = UPDATE_BUILTIN_SCRIPTS}, | ||
| 557 | #endif | 558 | #endif |
| 558 | 559 | ||
| 559 | // ------------------------- stream options -------------------- | 560 | // ------------------------- stream options -------------------- |
| @@ -986,6 +987,7 @@ static const struct MPOpts mp_default_opts = { · 426 unmodified lines | |||
| 986 | .lua_load_console = true, | 987 | .lua_load_console = true, |
| 987 | .lua_load_auto_profiles = -1, | 988 | .lua_load_auto_profiles = -1, |
| 988 | .lua_load_select = true, | 989 | .lua_load_select = true, |
| 990 | .lua_load_positioning = true, | ||
| 989 | #endif | 991 | #endif |
| 990 | .auto_load_scripts = true, | 992 | .auto_load_scripts = true, |
| 991 | .loop_times = 1, | 993 | .loop_times = 1, |
| @@ -174,6 +174,7 @@ typedef struct MPOpts { · 173 unmodified lines | |||
| 174 | bool lua_load_console; | 174 | bool lua_load_console; |
| 175 | int lua_load_auto_profiles; | 175 | int lua_load_auto_profiles; |
| 176 | bool lua_load_select; | 176 | bool lua_load_select; |
| 177 | bool lua_load_positioning; | ||
| 177 | 178 | ||
| 178 | bool auto_load_scripts; | 179 | bool auto_load_scripts; |
| 179 | 180 | ||
| @@ -444,7 +444,7 @@ typedef struct MPContext { · 443 unmodified lines | |||
| 444 | 444 | ||
| 445 | struct mp_ipc_ctx *ipc_ctx; | 445 | struct mp_ipc_ctx *ipc_ctx; |
| 446 | 446 | ||
| 447 | int64_t builtin_script_ids[6]; | 447 | int64_t builtin_script_ids[7]; |
| 448 | 448 | ||
| 449 | mp_mutex abort_lock; | 449 | mp_mutex abort_lock; |
| 450 | 450 | ||
| @@ -84,6 +84,9 @@ static const char * const builtin_lua_scripts[][2] = { · 83 unmodified lines | |||
| 84 | }, | 84 | }, |
| 85 | {"@select.lua", | 85 | {"@select.lua", |
| 86 | # include "player/lua/select.lua.inc" | 86 | # include "player/lua/select.lua.inc" |
| 87 | }, | ||
| 88 | {"@positioning.lua", | ||
| 89 | # include "player/lua/positioning.lua.inc" | ||
| 87 | }, | 90 | }, |
| 88 | {0} | 91 | {0} |
| 89 | }; | 92 | }; |
| @@ -1,6 +1,6 @@ | |||
| 1 | lua_files = ['defaults.lua', 'assdraw.lua', 'options.lua', 'osc.lua', | 1 | lua_files = ['defaults.lua', 'assdraw.lua', 'options.lua', 'osc.lua', |
| 2 | 'ytdl_hook.lua', 'stats.lua', 'console.lua', 'auto_profiles.lua', | 2 | 'ytdl_hook.lua', 'stats.lua', 'console.lua', 'auto_profiles.lua', |
| 3 | 'input.lua', 'fzy.lua', 'select.lua'] | 3 | 'input.lua', 'fzy.lua', 'select.lua', 'positioning.lua'] |
| 4 | foreach file: lua_files | 4 | foreach file: lua_files |
| 5 | lua_file = custom_target(file, | 5 | lua_file = custom_target(file, |
| 6 | input: file, | 6 | input: file, |
| @@ -0,0 +1,155 @@ | |||
| 1 | --[[ | ||
| 2 | This file is part of mpv. | ||
| 3 | |||
| 4 | mpv is free software; you can redistribute it and/or | ||
| 5 | modify it under the terms of the GNU Lesser General Public | ||
| 6 | License as published by the Free Software Foundation; either | ||
| 7 | version 2.1 of the License, or (at your option) any later version. | ||
| 8 | |||
| 9 | mpv is distributed in the hope that it will be useful, | ||
| 10 | but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
| 11 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
| 12 | GNU Lesser General Public License for more details. | ||
| 13 | |||
| 14 | You should have received a copy of the GNU Lesser General Public | ||
| 15 | License along with mpv. If not, see <http://www.gnu.org/licenses/>. | ||
| 16 | ]] | ||
| 17 | |||
| 18 | local options = { | ||
| 19 | toggle_align_to_cursor = false, | ||
| 20 | suppress_osd = false, | ||
| 21 | } | ||
| 22 | |||
| 23 | require "mp.options".read_options(options, nil, function () end) | ||
| 24 | |||
| 25 | local function clamp(value, min, max) | ||
| 26 | return math.min(math.max(value, min), max) | ||
| 27 | end | ||
| 28 | |||
| 29 | mp.add_key_binding(nil, "pan-x", function (t) | ||
| 30 | if t.event == "up" then | ||
| 31 | return | ||
| 32 | end | ||
| 33 | |||
| 34 | local dims = mp.get_property_native("osd-dimensions") | ||
| 35 | local width = dims.w - dims.ml - dims.mr | ||
| 36 | -- 1 video-align shifts the OSD by (width - osd-width) / 2 pixels, so the | ||
| 37 | -- equation to find how much video-align to add to offset the OSD by | ||
| 38 | -- osd-width is: | ||
| 39 | -- x/1 = osd-width / ((width - osd-width) / 2) | ||
| 40 | local amount = t.arg * t.scale * 2 * dims.w / (width - dims.w) | ||
| 41 | mp.commandv("add", "video-align-x", clamp(amount, -2, 2)) | ||
| 42 | end, { complex = true, scalable = true }) | ||
| 43 | |||
| 44 | mp.add_key_binding(nil, "pan-y", function (t) | ||
| 45 | if t.event == "up" then | ||
| 46 | return | ||
| 47 | end | ||
| 48 | |||
| 49 | local dims = mp.get_property_native("osd-dimensions") | ||
| 50 | local height = dims.h - dims.mt - dims.mb | ||
| 51 | local amount = t.arg * t.scale * 2 * dims.h / (height - dims.h) | ||
| 52 | mp.commandv("add", "video-align-y", clamp(amount, -2, 2)) | ||
| 53 | end, { complex = true, scalable = true }) | ||
| 54 | |||
| 55 | mp.add_key_binding(nil, "drag-to-pan", function (t) | ||
| 56 | if t.event == "up" then | ||
| 57 | mp.remove_key_binding("drag-to-pan-mouse-move") | ||
| 58 | return | ||
| 59 | end | ||
| 60 | |||
| 61 | local dims = mp.get_property_native("osd-dimensions") | ||
| 62 | local old_mouse_pos = mp.get_property_native("mouse-pos") | ||
| 63 | local old_align_x = mp.get_property_native("video-align-x") | ||
| 64 | local old_align_y = mp.get_property_native("video-align-y") | ||
| 65 | |||
| 66 | mp.add_forced_key_binding("MOUSE_MOVE", "drag-to-pan-mouse-move", function () | ||
| 67 | local mouse_pos = mp.get_property_native("mouse-pos") | ||
| 68 | -- 1 video-align shifts the OSD by (dimension - osd_dimension) / 2 pixels, | ||
| 69 | -- so the equation to find how much video-align to add to offset the OSD | ||
| 70 | -- by the difference in mouse position is: | ||
| 71 | -- x/1 = (mouse_pos - old_mouse_pos) / ((dimension - osd_dimension) / 2) | ||
| 72 | local align = old_align_x + 2 * (mouse_pos.x - old_mouse_pos.x) | ||
| 73 | / (dims.ml + dims.mr) | ||
| 74 | mp.set_property("video-align-x", clamp(align, -1, 1)) | ||
| 75 | align = old_align_y + 2 * (mouse_pos.y - old_mouse_pos.y) | ||
| 76 | / (dims.mt + dims.mb) | ||
| 77 | mp.set_property("video-align-y", clamp(align, -1, 1)) | ||
| 78 | end) | ||
| 79 | end, { complex = true }) | ||
| 80 | |||
| 81 | |||
| 82 | local align_to_cursor_bound = false | ||
| 83 | |||
| 84 | local function align_to_cursor(_, mouse_pos) | ||
| 85 | local dims = mp.get_property_native("osd-dimensions") | ||
| 86 | mp.set_property("video-align-x", (mouse_pos.x * 2 - dims.w) / dims.w) | ||
| 87 | mp.set_property("video-align-y", (mouse_pos.y * 2 - dims.h) / dims.h) | ||
| 88 | end | ||
| 89 | |||
| 90 | mp.add_key_binding(nil, "align-to-cursor", function (t) | ||
| 91 | if options.toggle_align_to_cursor == false then | ||
| 92 | if t.event == "down" then | ||
| 93 | mp.observe_property("mouse-pos", "native", align_to_cursor) | ||
| 94 | else | ||
| 95 | mp.unobserve_property(align_to_cursor) | ||
| 96 | end | ||
| 97 | |||
| 98 | return | ||
| 99 | end | ||
| 100 | |||
| 101 | if t.event ~= "up" then | ||
| 102 | return | ||
| 103 | end | ||
| 104 | |||
| 105 | if align_to_cursor_bound then | ||
| 106 | mp.unobserve_property(align_to_cursor) | ||
| 107 | else | ||
| 108 | mp.observe_property("mouse-pos", "native", align_to_cursor) | ||
| 109 | end | ||
| 110 | align_to_cursor_bound = not align_to_cursor_bound | ||
| 111 | end, { complex = true }) | ||
| 112 | |||
| 113 | |||
| 114 | mp.add_key_binding(nil, "cursor-centric-zoom", function (t) | ||
| 115 | local amount = t.arg * t.scale | ||
| 116 | |||
| 117 | local command = (options.suppress_osd and "no-osd " or "") .. | ||
| 118 | "add video-zoom " .. amount .. ";" | ||
| 119 | |||
| 120 | local x, y | ||
| 121 | local touch_positions = mp.get_property_native("touch-pos") | ||
| 122 | if touch_positions[1] then | ||
| 123 | for _, position in pairs(touch_positions) do | ||
| 124 | x = x + position.x | ||
| 125 | y = y + position.y | ||
| 126 | end | ||
| 127 | x = x / #touch_positions | ||
| 128 | y = y / #touch_positions | ||
| 129 | else | ||
| 130 | local mouse_pos = mp.get_property_native("mouse-pos") | ||
| 131 | x = mouse_pos.x | ||
| 132 | y = mouse_pos.y | ||
| 133 | end | ||
| 134 | |||
| 135 | local dims = mp.get_property_native("osd-dimensions") | ||
| 136 | local width = (dims.w - dims.ml - dims.mr) * 2^amount | ||
| 137 | local height = (dims.h - dims.mt - dims.mb) * 2^amount | ||
| 138 | |||
| 139 | local old_cursor_ml = dims.ml - x | ||
| 140 | local cursor_ml = old_cursor_ml * 2^amount | ||
| 141 | local ml = cursor_ml + x | ||
| 142 | -- video/out/aspect.c:src_dst_split_scaling() defines ml as: | ||
| 143 | -- ml = (osd-width - width) * (video-align-x + 1) / 2 + pan-x * width | ||
| 144 | -- So video-align-x is: | ||
| 145 | local align = 2 * (ml - mp.get_property_native("video-pan-x") * width) | ||
| 146 | / (dims.w - width) - 1 | ||
| 147 | command = command .. "no-osd set video-align-x " .. clamp(align, -1, 1) .. ";" | ||
| 148 | |||
| 149 | local mt = (dims.mt - y) * 2^amount + y | ||
| 150 | align = 2 * (mt - mp.get_property_native("video-pan-y") * height) | ||
| 151 | / (dims.h - height) - 1 | ||
| 152 | command = command .. "no-osd set video-align-y " .. clamp(align, -1, 1) | ||
| 153 | |||
| 154 | mp.command(command) | ||
| 155 | end, { complex = true, scalable = true }) | ||
| @@ -263,6 +263,7 @@ void mp_load_builtin_scripts(struct MPContext *mpctx) · 262 unmodified lines | |||
| 263 | load_builtin_script(mpctx, 4, mpctx->opts->lua_load_auto_profiles, | 263 | load_builtin_script(mpctx, 4, mpctx->opts->lua_load_auto_profiles, |
| 264 | "@auto_profiles.lua"); | 264 | "@auto_profiles.lua"); |
| 265 | load_builtin_script(mpctx, 5, mpctx->opts->lua_load_select, "@select.lua"); | 265 | load_builtin_script(mpctx, 5, mpctx->opts->lua_load_select, "@select.lua"); |
| 266 | load_builtin_script(mpctx, 6, mpctx->opts->lua_load_positioning, "@positioning.lua"); | ||
| 266 | } | 267 | } |
| 267 | 268 | ||
| 268 | bool mp_load_scripts(struct MPContext *mpctx) | 269 | bool mp_load_scripts(struct MPContext *mpctx) |