11local opts = {
2- mode = " hard" , -- can be "hard" or "soft". If hard, apply a crop filter , if soft zoom + pan. Or a bonus "delogo" mode
2+ mode = " hard" , -- can be "hard" or "soft". If hard, use video- crop, if soft use zoom + pan. Or a bonus "delogo" mode
33 draw_shade = true ,
44 shade_opacity = " 77" ,
55 draw_frame = false ,
@@ -261,6 +261,11 @@ function draw_crop_zone()
261261 end
262262end
263263
264+ -- history tables
265+ local recursive_crop = {}
266+ local recursive_zoom_pan = {}
267+ local remove_last_filter = {}
268+
264269function crop_video (x1 , y1 , x2 , y2 )
265270 if active_mode == " soft" then
266271 local w = x2 - x1
@@ -271,34 +276,63 @@ function crop_video(x1, y1, x2, y2)
271276 local zoom = mp .get_property_number (" video-zoom" )
272277 local newZoom1 = math.log (dim .h * (2 ^ zoom ) / (dim .h - dim .mt - dim .mb ) / h ) / math.log (2 )
273278 local newZoom2 = math.log (dim .w * (2 ^ zoom ) / (dim .w - dim .ml - dim .mr ) / w ) / math.log (2 )
279+
280+ local newZoom = math.min (newZoom1 , newZoom2 )
281+ local newPanX = 0.5 - (x1 + w / 2 )
282+ local newPanY = 0.5 - (y1 + h / 2 )
283+
274284 mp .set_property (" video-zoom" , math.min (newZoom1 , newZoom2 ))
275285 mp .set_property (" video-pan-x" , 0.5 - (x1 + w / 2 ))
276286 mp .set_property (" video-pan-y" , 0.5 - (y1 + h / 2 ))
287+
288+ table.insert (recursive_zoom_pan , {zoom = newZoom , panX = newPanX , panY = newPanY })
289+
290+ mp .set_property (" video-zoom" , newZoom )
291+ mp .set_property (" video-pan-x" , newPanX )
292+ mp .set_property (" video-pan-y" , newPanY )
293+ table.insert (remove_last_filter , " soft" )
294+
277295 elseif active_mode == " hard" or active_mode == " delogo" then
278296 x1 = clamp (0 , x1 , 1 )
279297 y1 = clamp (0 , y1 , 1 )
280298 x2 = clamp (0 , x2 , 1 )
281299 y2 = clamp (0 , y2 , 1 )
282300 local vop = mp .get_property_native (" video-out-params" )
283- local vf_table = mp .get_property_native (" vf" )
284- local x = math.floor (x1 * vop .w + 0.5 )
285- local y = math.floor (y1 * vop .h + 0.5 )
286- local w = math.floor ((x2 - x1 ) * vop .w + 0.5 )
287- local h = math.floor ((y2 - y1 ) * vop .h + 0.5 )
288- if active_mode == " delogo" then
301+ if active_mode == " hard" then
302+ local w = x2 - x1
303+ local h = y2 - y1
304+
305+ table.insert (recursive_crop , {x = x1 , y = y1 , w = w , h = h })
306+ apply_video_crop ()
307+ table.insert (remove_last_filter , " hard" )
308+
309+ elseif active_mode == " delogo" then
310+ local vf_table = mp .get_property_native (" vf" )
311+
312+ local x , y , w , h = adjust_coordinates ()
313+
314+ local x = math.floor ((x + x1 * w ) * vop .w + 0.5 )
315+ local y = math.floor ((y + y1 * h ) * vop .h + 0.5 )
316+ local w = math.floor (w * (x2 - x1 ) * vop .w + 0.5 )
317+ local h = math.floor (h * (y2 - y1 ) * vop .h + 0.5 )
318+
289319 -- delogo is a little special and needs some padding to function
290320 w = math.min (vop .w - 1 , w )
291321 h = math.min (vop .h - 1 , h )
292322 x = math.max (1 , x )
293323 y = math.max (1 , y )
324+
294325 if x + w == vop .w then w = w - 1 end
295326 if y + h == vop .h then h = h - 1 end
327+
328+ vf_table [# vf_table + 1 ] = {
329+ name = " delogo" ,
330+ params = { x = tostring (x ), y = tostring (y ), w = tostring (w ), h = tostring (h ) }
331+ }
332+
333+ mp .set_property_native (" vf" , vf_table )
334+ table.insert (remove_last_filter , " delogo" )
296335 end
297- vf_table [# vf_table + 1 ] = {
298- name = (active_mode == " hard" ) and " crop" or " delogo" ,
299- params = { x = tostring (x ), y = tostring (y ), w = tostring (w ), h = tostring (h ) }
300- }
301- mp .set_property_native (" vf" , vf_table )
302336 end
303337end
304338
@@ -346,6 +380,136 @@ function cancel_crop()
346380 end
347381end
348382
383+ -- adjust coordinates based on previous values
384+ function adjust_coordinates ()
385+ local x , y , w , h = 0 , 0 , 1 , 1
386+ for _ , crop in ipairs (recursive_crop ) do
387+ x = x + w * crop .x
388+ y = y + h * crop .y
389+ w = w * crop .w
390+ h = h * crop .h
391+ end
392+ return x , y , w , h
393+ end
394+
395+ function apply_video_crop ()
396+ local x , y , w , h = adjust_coordinates ()
397+
398+ local vop = mp .get_property_native (" video-out-params" )
399+ local x = math.floor (x * vop .w + 0.5 )
400+ local y = math.floor (y * vop .h + 0.5 )
401+ local w = math.floor (w * vop .w + 0.5 )
402+ local h = math.floor (h * vop .h + 0.5 )
403+
404+ local video_crop = tostring (w ) .. " x" .. tostring (h ) .. " +" .. tostring (x ) .. " +" .. tostring (y )
405+ mp .set_property_native (" video-crop" , video_crop )
406+ end
407+
408+ function remove_filter (vf_table , filter_name , filter_number )
409+ local filter_count = 0
410+ local remove_last = 0
411+ for i = 1 , # vf_table do
412+ if vf_table [i ].name == filter_name then
413+ filter_count = filter_count + 1
414+ remove_last = i
415+ end
416+ end
417+ if filter_count > 0 then
418+ table.remove (vf_table , remove_last )
419+ mp .set_property_native (" vf" , vf_table )
420+ mp .osd_message (" Removed: #" .. tostring (filter_number or filter_count ) .. " " .. filter_name )
421+ return true
422+ end
423+ return false
424+ end
425+
426+ function remove_video_crop (filter_number )
427+ if # recursive_crop > 0 then
428+ table.remove (recursive_crop )
429+ -- reapply each crop in the table
430+ apply_video_crop ()
431+ if # recursive_crop == 0 then
432+ mp .set_property_native (" video-crop" , " " )
433+ end
434+ mp .osd_message (" Removed: #" .. tostring (filter_number or # recursive_crop + 1 ) .. " " .. " video-crop" )
435+ return true
436+ end
437+ return false
438+ end
439+
440+ function remove_zoom_pan (filter_number )
441+ if # recursive_zoom_pan > 0 then
442+ table.remove (recursive_zoom_pan )
443+ if # recursive_zoom_pan > 0 then
444+ local lastZoomPan = recursive_zoom_pan [# recursive_zoom_pan ]
445+ mp .set_property (" video-zoom" , lastZoomPan .zoom )
446+ mp .set_property (" video-pan-x" , lastZoomPan .panX )
447+ mp .set_property (" video-pan-y" , lastZoomPan .panY )
448+ else
449+ mp .set_property (" video-zoom" , 0 )
450+ mp .set_property (" video-pan-x" , 0 )
451+ mp .set_property (" video-pan-y" , 0 )
452+ end
453+ mp .osd_message (" Removed: #" .. tostring (filter_number or # recursive_zoom_pan + 1 ) .. " " .. " soft-crop" )
454+ return true
455+ end
456+ return false
457+ end
458+
459+ -- remove an entry in 'remove_last_filter' at correct position to keep it in sync when 'remove_crop' and 'toggle_crop' are used in the same session
460+ function remove_last_filter_entry (filter_type )
461+ for i = # remove_last_filter , 1 , - 1 do
462+ if remove_last_filter [i ] == filter_type then
463+ table.remove (remove_last_filter , i )
464+ break
465+ end
466+ end
467+ end
468+
469+ function remove_crop (mode , order )
470+ local vf_table = mp .get_property_native (" vf" )
471+ local total_filters = # remove_last_filter
472+
473+ -- 'remove-crop all order' removes all filters starting with most recently added
474+ if order == " order" then
475+ if total_filters == 0 then
476+ mp .osd_message (" Nothing to remove" )
477+ return
478+ end
479+ local last_filter = table.remove (remove_last_filter )
480+ if last_filter == " hard" then
481+ remove_video_crop (total_filters )
482+ elseif last_filter == " delogo" then
483+ remove_filter (vf_table , " delogo" , total_filters )
484+ elseif last_filter == " soft" then
485+ remove_zoom_pan (total_filters )
486+ end
487+ else
488+ local modes = {" delogo" , " hard" , " soft" }
489+ if order == " hard" then
490+ modes = {" hard" , " soft" , " delogo" }
491+ elseif order == " soft" then
492+ modes = {" soft" , " hard" , " delogo" }
493+ end
494+
495+ for _ , mode_name in ipairs (modes ) do
496+ if not mode or mode == " all" or mode == mode_name then
497+ if mode_name == " delogo" and remove_filter (vf_table , " delogo" ) then
498+ remove_last_filter_entry (" delogo" )
499+ return
500+ elseif mode_name == " hard" and remove_video_crop () then
501+ remove_last_filter_entry (" hard" )
502+ return
503+ elseif mode_name == " soft" and remove_zoom_pan () then
504+ remove_last_filter_entry (" soft" )
505+ return
506+ end
507+ end
508+ end
509+ mp .osd_message (" Nothing to remove" )
510+ end
511+ end
512+
349513function start_crop (mode )
350514 if active then return end
351515 if not mp .get_property_native (" osd-dimensions" ) then return end
@@ -354,7 +518,7 @@ function start_crop(mode)
354518 return
355519 end
356520 local mode_maybe = mode or opts .mode
357- if mode_maybe ~= ' soft ' then
521+ if mode_maybe == " delogo " then
358522 local hwdec = mp .get_property (" hwdec-current" )
359523 if hwdec and hwdec ~= " no" and not string.find (hwdec , " -copy$" ) then
360524 msg .error (" Cannot crop with hardware decoding active (see manual)" )
@@ -387,27 +551,24 @@ function toggle_crop(mode)
387551 msg .error (" Invalid mode value: " .. mode )
388552 end
389553 local toggle_mode = mode or opts .mode
390- if toggle_mode == " soft" then return end -- can't toggle soft mode
391-
392- local remove_filter = function ()
393- local to_remove = (toggle_mode == " hard" ) and " crop" or " delogo"
394- local vf_table = mp .get_property_native (" vf" )
395- if # vf_table > 0 then
396- for i = # vf_table , 1 , - 1 do
397- if vf_table [i ].name == to_remove then
398- for j = i , # vf_table - 1 do
399- vf_table [j ] = vf_table [j + 1 ]
400- end
401- vf_table [# vf_table ] = nil
402- mp .set_property_native (" vf" , vf_table )
403- return true
404- end
405- end
406- end
407- return false
554+
555+ if toggle_mode == " soft" and not remove_zoom_pan () then
556+ start_crop (mode )
557+ elseif toggle_mode == " soft" then
558+ remove_last_filter_entry (" soft" )
559+ end
560+
561+ local vf_table = mp .get_property_native (" vf" )
562+ if toggle_mode == " delogo" and not remove_filter (vf_table , " delogo" ) then
563+ start_crop (mode )
564+ elseif toggle_mode == " delogo" then
565+ remove_last_filter_entry (" delogo" )
408566 end
409- if not remove_filter () then
567+
568+ if toggle_mode == " hard" and not remove_video_crop () then
410569 start_crop (mode )
570+ elseif toggle_mode == " hard" then
571+ remove_last_filter_entry (" hard" )
411572 end
412573end
413574
@@ -443,5 +604,6 @@ bindings_repeat[opts.up_fine] = movement_func(0, -opts.fine_movement)
443604bindings_repeat [opts .down_fine ] = movement_func (0 , opts .fine_movement )
444605
445606
607+ mp .add_key_binding (nil , " remove-crop" , remove_crop )
446608mp .add_key_binding (nil , " start-crop" , start_crop )
447609mp .add_key_binding (nil , " toggle-crop" , toggle_crop )
0 commit comments