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 | 1533 | .. include:: select.rst |
| 1534 | 1534 | |
| 1535 | +.. include:: positioning.rst | |
| 1536 | + | |
| 1535 | 1537 | .. include:: lua.rst |
| 1536 | 1538 | |
| 1537 | 1539 | .. include:: javascript.rst |
| @@ -1067,6 +1067,10 @@ Program Behavior · 1066 unmodified lines | ||
| 1067 | 1067 | Enable the builtin script that lets you select from lists of items (default: |
| 1068 | 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 | 1074 | ``--player-operation-mode=<cplayer|pseudo-gui>`` |
| 1071 | 1075 | For enabling "pseudo GUI mode", which means that the defaults for some |
| 1072 | 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 | 554 | OPT_CHOICE(lua_load_auto_profiles, {"no", 0}, {"yes", 1}, {"auto", -1}), |
| 555 | 555 | .flags = UPDATE_BUILTIN_SCRIPTS}, |
| 556 | 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 | 558 | #endif |
| 558 | 559 | |
| 559 | 560 | // ------------------------- stream options -------------------- |
| @@ -986,6 +987,7 @@ static const struct MPOpts mp_default_opts = { · 426 unmodified lines | ||
| 986 | 987 | .lua_load_console = true, |
| 987 | 988 | .lua_load_auto_profiles = -1, |
| 988 | 989 | .lua_load_select = true, |
| 990 | + .lua_load_positioning = true, | |
| 989 | 991 | #endif |
| 990 | 992 | .auto_load_scripts = true, |
| 991 | 993 | .loop_times = 1, |
| @@ -174,6 +174,7 @@ typedef struct MPOpts { · 173 unmodified lines | ||
| 174 | 174 | bool lua_load_console; |
| 175 | 175 | int lua_load_auto_profiles; |
| 176 | 176 | bool lua_load_select; |
| 177 | + bool lua_load_positioning; | |
| 177 | 178 | |
| 178 | 179 | bool auto_load_scripts; |
| 179 | 180 | |
| @@ -444,7 +444,7 @@ typedef struct MPContext { · 443 unmodified lines | ||
| 444 | 444 | |
| 445 | 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 | 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 | 85 | {"@select.lua", |
| 86 | 86 | # include "player/lua/select.lua.inc" |
| 87 | + }, | |
| 88 | + {"@positioning.lua", | |
| 89 | +# include "player/lua/positioning.lua.inc" | |
| 87 | 90 | }, |
| 88 | 91 | {0} |
| 89 | 92 | }; |
| @@ -1,6 +1,6 @@ | ||
| 1 | 1 | lua_files = ['defaults.lua', 'assdraw.lua', 'options.lua', 'osc.lua', |
| 2 | 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 | 4 | foreach file: lua_files |
| 5 | 5 | lua_file = custom_target(file, |
| 6 | 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 | 263 | load_builtin_script(mpctx, 4, mpctx->opts->lua_load_auto_profiles, |
| 264 | 264 | "@auto_profiles.lua"); |
| 265 | 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 | 269 | bool mp_load_scripts(struct MPContext *mpctx) |