diff options
Diffstat (limited to 'home-config')
-rw-r--r-- | home-config/home-configuration.scm | 1 | ||||
-rw-r--r-- | home-config/mpv/scripts/easycrop.lua | 253 |
2 files changed, 254 insertions, 0 deletions
diff --git a/home-config/home-configuration.scm b/home-config/home-configuration.scm index 8dc46b9..f9fb2fd 100644 --- a/home-config/home-configuration.scm +++ b/home-config/home-configuration.scm | |||
@@ -115,6 +115,7 @@ | |||
115 | ("waybar" ,(local-file "waybar" #:recursive? #t)) | 115 | ("waybar" ,(local-file "waybar" #:recursive? #t)) |
116 | ("alacritty" ,(local-file "alacritty" #:recursive? #t)) | 116 | ("alacritty" ,(local-file "alacritty" #:recursive? #t)) |
117 | ("aerc" ,(local-file "aerc" #:recursive? #t)) | 117 | ("aerc" ,(local-file "aerc" #:recursive? #t)) |
118 | ("mpv" ,(local-file "mpv" #:recursive? #t)) | ||
118 | ("home-manager" ,(local-file "nix-home-manager" #:recursive? #t)) )) | 119 | ("home-manager" ,(local-file "nix-home-manager" #:recursive? #t)) )) |
119 | (service home-files-service-type | 120 | (service home-files-service-type |
120 | `((".local/share/nvim/site/autoload/plug.vim" ,(local-file "nvim/plugin-manager/plug.vim")) | 121 | `((".local/share/nvim/site/autoload/plug.vim" ,(local-file "nvim/plugin-manager/plug.vim")) |
diff --git a/home-config/mpv/scripts/easycrop.lua b/home-config/mpv/scripts/easycrop.lua new file mode 100644 index 0000000..b3a84a7 --- /dev/null +++ b/home-config/mpv/scripts/easycrop.lua | |||
@@ -0,0 +1,253 @@ | |||
1 | local msg = require('mp.msg') | ||
2 | local assdraw = require('mp.assdraw') | ||
3 | |||
4 | local script_name = "easycrop" | ||
5 | |||
6 | -- Number of crop points currently chosen (0 to 2) | ||
7 | local points = {} | ||
8 | -- True if in cropping selection mode | ||
9 | local cropping = false | ||
10 | -- Original value of osc property | ||
11 | local osc_prop = false | ||
12 | |||
13 | -- Helper that converts two points to top-left and bottom-right | ||
14 | local swizzle_points = function (p1, p2) | ||
15 | if p1.x > p2.x then p1.x, p2.x = p2.x, p1.x end | ||
16 | if p1.y > p2.y then p1.y, p2.y = p2.y, p1.y end | ||
17 | end | ||
18 | |||
19 | local clamp = function (val, min, max) | ||
20 | assert(min <= max) | ||
21 | if val < min then return min end | ||
22 | if val > max then return max end | ||
23 | return val | ||
24 | end | ||
25 | |||
26 | local video_space_from_screen_space = function (ssp) | ||
27 | -- Video native dimensions and screen size | ||
28 | local vid_w = mp.get_property("width") | ||
29 | local vid_h = mp.get_property("height") | ||
30 | local osd_w = mp.get_property("osd-width") | ||
31 | local osd_h = mp.get_property("osd-height") | ||
32 | |||
33 | -- Factor by which the video is scaled to fit the screen | ||
34 | local scale = math.min(osd_w/vid_w, osd_h/vid_h) | ||
35 | |||
36 | -- Size video takes up in screen | ||
37 | local vid_sw, vid_sh = scale*vid_w, scale*vid_h | ||
38 | |||
39 | -- Video offset within screen | ||
40 | local off_x = math.floor((osd_w - vid_sw)/2) | ||
41 | local off_y = math.floor((osd_h - vid_sh)/2) | ||
42 | |||
43 | local vsp = {} | ||
44 | |||
45 | -- Move the point to within the video | ||
46 | vsp.x = clamp(ssp.x, off_x, off_x + vid_sw) | ||
47 | vsp.y = clamp(ssp.y, off_y, off_y + vid_sh) | ||
48 | |||
49 | -- Convert screen-space to video-space | ||
50 | vsp.x = math.floor((vsp.x - off_x) / scale) | ||
51 | vsp.y = math.floor((vsp.y - off_y) / scale) | ||
52 | |||
53 | return vsp | ||
54 | end | ||
55 | |||
56 | local screen_space_from_video_space = function (vsp) | ||
57 | -- Video native dimensions and screen size | ||
58 | local vid_w = mp.get_property("width") | ||
59 | local vid_h = mp.get_property("height") | ||
60 | local osd_w = mp.get_property("osd-width") | ||
61 | local osd_h = mp.get_property("osd-height") | ||
62 | |||
63 | -- Factor by which the video is scaled to fit the screen | ||
64 | local scale = math.min(osd_w/vid_w, osd_h/vid_h) | ||
65 | |||
66 | -- Size video takes up in screen | ||
67 | local vid_sw, vid_sh = scale*vid_w, scale*vid_h | ||
68 | |||
69 | -- Video offset within screen | ||
70 | local off_x = math.floor((osd_w - vid_sw)/2) | ||
71 | local off_y = math.floor((osd_h - vid_sh)/2) | ||
72 | |||
73 | local ssp = {} | ||
74 | ssp.x = vsp.x * scale + off_x | ||
75 | ssp.y = vsp.y * scale + off_y | ||
76 | return ssp | ||
77 | end | ||
78 | |||
79 | -- Wrapper that converts RRGGBB / RRGGBBAA to ASS format | ||
80 | local ass_set_color = function (idx, color) | ||
81 | assert(color:len() == 8 or color:len() == 6) | ||
82 | local ass = "" | ||
83 | |||
84 | -- Set alpha value (if present) | ||
85 | if color:len() == 8 then | ||
86 | local alpha = 0xff - tonumber(color:sub(7, 8), 16) | ||
87 | ass = ass .. string.format("\\%da&H%X&", idx, alpha) | ||
88 | end | ||
89 | |||
90 | -- Swizzle RGB to BGR and build ASS string | ||
91 | color = color:sub(5, 6) .. color:sub(3, 4) .. color:sub(1, 2) | ||
92 | return "{" .. ass .. string.format("\\%dc&H%s&", idx, color) .. "}" | ||
93 | end | ||
94 | |||
95 | local draw_rect = function (p1, p2) | ||
96 | local osd_w, osd_h = mp.get_property("osd-width"), mp.get_property("osd-height") | ||
97 | |||
98 | ass = assdraw.ass_new() | ||
99 | |||
100 | -- Draw overlay over surrounding unselected region | ||
101 | |||
102 | ass:draw_start() | ||
103 | ass:pos(0, 0) | ||
104 | |||
105 | ass:append(ass_set_color(1, "000000aa")) | ||
106 | ass:append(ass_set_color(3, "00000000")) | ||
107 | |||
108 | local l = math.min(p1.x, p2.x) | ||
109 | local r = math.max(p1.x, p2.x) | ||
110 | local u = math.min(p1.y, p2.y) | ||
111 | local d = math.max(p1.y, p2.y) | ||
112 | |||
113 | ass:rect_cw(0, 0, l, osd_h) | ||
114 | ass:rect_cw(r, 0, osd_w, osd_h) | ||
115 | ass:rect_cw(l, 0, r, u) | ||
116 | ass:rect_cw(l, d, r, osd_h) | ||
117 | |||
118 | ass:draw_stop() | ||
119 | |||
120 | -- Draw border around selected region | ||
121 | |||
122 | ass:new_event() | ||
123 | ass:draw_start() | ||
124 | ass:pos(0, 0) | ||
125 | |||
126 | ass:append(ass_set_color(1, "00000000")) | ||
127 | ass:append(ass_set_color(3, "000000ff")) | ||
128 | ass:append("{\\bord2}") | ||
129 | |||
130 | ass:rect_cw(p1.x, p1.y, p2.x, p2.y) | ||
131 | |||
132 | ass:draw_stop() | ||
133 | |||
134 | mp.set_osd_ass(osd_w, osd_h, ass.text) | ||
135 | end | ||
136 | |||
137 | local draw_fill = function () | ||
138 | local osd_w, osd_h = mp.get_property("osd-width"), mp.get_property("osd-height") | ||
139 | |||
140 | ass = assdraw.ass_new() | ||
141 | ass:draw_start() | ||
142 | ass:pos(0, 0) | ||
143 | |||
144 | ass:append(ass_set_color(1, "000000aa")) | ||
145 | ass:append(ass_set_color(3, "00000000")) | ||
146 | ass:rect_cw(0, 0, osd_w, osd_h) | ||
147 | |||
148 | ass:draw_stop() | ||
149 | mp.set_osd_ass(osd_w, osd_h, ass.text) | ||
150 | end | ||
151 | |||
152 | local draw_clear = function () | ||
153 | local osd_w, osd_h = mp.get_property("osd-width"), mp.get_property("osd-height") | ||
154 | mp.set_osd_ass(osd_w, osd_h, "") | ||
155 | end | ||
156 | |||
157 | local draw_cropper = function () | ||
158 | if #points == 1 then | ||
159 | local p1 = screen_space_from_video_space(points[1]) | ||
160 | local p2 = {} | ||
161 | p2.x, p2.y = mp.get_mouse_pos() | ||
162 | draw_rect(p1, p2) | ||
163 | end | ||
164 | end | ||
165 | |||
166 | local uncrop = function () | ||
167 | mp.command("no-osd vf del @" .. script_name .. ":crop") | ||
168 | end | ||
169 | |||
170 | local crop = function(p1, p2) | ||
171 | swizzle_points(p1, p2) | ||
172 | |||
173 | local w = p2.x - p1.x | ||
174 | local h = p2.y - p1.y | ||
175 | local ok, err = mp.command(string.format( | ||
176 | "no-osd vf add @%s:crop=%s:%s:%s:%s", script_name, w, h, p1.x, p1.y)) | ||
177 | |||
178 | if not ok then | ||
179 | mp.osd_message("Cropping failed") | ||
180 | points = {} | ||
181 | end | ||
182 | end | ||
183 | |||
184 | local easycrop_stop = function () | ||
185 | mp.set_property("osc", osc_prop) | ||
186 | cropping = false | ||
187 | mp.remove_key_binding("easycrop_mouse_btn0") | ||
188 | draw_clear() | ||
189 | end | ||
190 | |||
191 | local mouse_btn0_cb = function () | ||
192 | if not cropping then | ||
193 | return | ||
194 | end | ||
195 | |||
196 | local mx, my = mp.get_mouse_pos() | ||
197 | table.insert(points, video_space_from_screen_space({ x = mx, y = my })) | ||
198 | |||
199 | if #points == 2 then | ||
200 | crop(points[1], points[2]) | ||
201 | easycrop_stop() | ||
202 | end | ||
203 | end | ||
204 | |||
205 | local easycrop_start = function () | ||
206 | -- Cropping requires swdec or hwdec with copy-back | ||
207 | local hwdec = mp.get_property("hwdec-current") | ||
208 | if hwdec == nil then | ||
209 | return mp.msg.error("Cannot determine current hardware decoder mode") | ||
210 | end | ||
211 | -- Check whitelist of ok values | ||
212 | local valid_hwdec = { | ||
213 | ["no"] = true, -- software decoding | ||
214 | -- Taken from mpv manual | ||
215 | ["videotoolbox-co"] = true, | ||
216 | ["vaapi-copy"] = true, | ||
217 | ["dxva2-copy"] = true, | ||
218 | ["d3d11va-copy"] = true, | ||
219 | ["mediacodec"] = true | ||
220 | } | ||
221 | if not valid_hwdec[hwdec] then | ||
222 | return mp.osd_message("Cropping requires swdec or hwdec with copy-back (see mpv manual)") | ||
223 | end | ||
224 | |||
225 | -- Just clear the current crop and return, if there is one | ||
226 | if #points ~= 0 then | ||
227 | uncrop() | ||
228 | points = {} | ||
229 | return | ||
230 | end | ||
231 | |||
232 | -- Hide OSC | ||
233 | osc_prop = mp.get_property("osc") | ||
234 | mp.set_property("osc", "no") | ||
235 | |||
236 | cropping = true | ||
237 | mp.add_forced_key_binding("mouse_btn0", "easycrop_mouse_btn0", mouse_btn0_cb) | ||
238 | draw_fill() | ||
239 | end | ||
240 | |||
241 | local easycrop_activate = function () | ||
242 | if cropping then | ||
243 | easycrop_stop() | ||
244 | else | ||
245 | easycrop_start() | ||
246 | end | ||
247 | end | ||
248 | |||
249 | mp.add_key_binding("mouse_move", draw_cropper) | ||
250 | mp.observe_property("osd-width", "native", draw_cropper) | ||
251 | mp.observe_property("osd-height", "native", draw_cropper) | ||
252 | |||
253 | mp.add_key_binding("c", "easy_crop", easycrop_activate) | ||