@@ -267,6 +267,11 @@ function draw_crop_zone()
267267 end
268268end
269269
270+ -- history tables
271+ local recursive_crop = {}
272+ local recursive_zoom_pan = {}
273+ local remove_last_filter = {}
274+
270275function crop_video (x1 , y1 , x2 , y2 )
271276 if active_mode == " soft" then
272277 local w = x2 - x1
@@ -277,36 +282,57 @@ function crop_video(x1, y1, x2, y2)
277282 local zoom = mp .get_property_number (" video-zoom" )
278283 local newZoom1 = math.log (dim .h * (2 ^ zoom ) / (dim .h - dim .mt - dim .mb ) / h ) / math.log (2 )
279284 local newZoom2 = math.log (dim .w * (2 ^ zoom ) / (dim .w - dim .ml - dim .mr ) / w ) / math.log (2 )
280- mp .set_property (" video-zoom" , math.min (newZoom1 , newZoom2 ))
281- mp .set_property (" video-pan-x" , 0.5 - (x1 + w / 2 ))
282- mp .set_property (" video-pan-y" , 0.5 - (y1 + h / 2 ))
285+
286+ local newZoom = math.min (newZoom1 , newZoom2 )
287+ local newPanX = 0.5 - (x1 + w / 2 )
288+ local newPanY = 0.5 - (y1 + h / 2 )
289+
290+ table.insert (recursive_zoom_pan , {zoom = newZoom , panX = newPanX , panY = newPanY })
291+ mp .set_property (" video-zoom" , newZoom )
292+ mp .set_property (" video-pan-x" , newPanX )
293+ mp .set_property (" video-pan-y" , newPanY )
294+ table.insert (remove_last_filter , " soft" )
295+
283296 elseif active_mode == " hard" or active_mode == " delogo" then
284297 x1 = clamp (0 , x1 , 1 )
285298 y1 = clamp (0 , y1 , 1 )
286299 x2 = clamp (0 , x2 , 1 )
287300 y2 = clamp (0 , y2 , 1 )
288301 local vop = mp .get_property_native (" video-out-params" )
289- local x = math.floor (x1 * vop .w + 0.5 )
290- local y = math.floor (y1 * vop .h + 0.5 )
291- local w = math.floor ((x2 - x1 ) * vop .w + 0.5 )
292- local h = math.floor ((y2 - y1 ) * vop .h + 0.5 )
293302 if active_mode == " hard" then
294- local video_crop = tostring (w ) .. " x" .. tostring (h ) .. " +" .. tostring (x ) .. " +" .. tostring (y )
295- mp .set_property_native (" video-crop" , video_crop )
303+ local w = x2 - x1
304+ local h = y2 - y1
305+
306+ table.insert (recursive_crop , {x = x1 , y = y1 , w = w , h = h })
307+ apply_video_crop ()
308+ table.insert (remove_last_filter , " hard" )
309+
296310 elseif active_mode == " delogo" then
297311 local vf_table = mp .get_property_native (" vf" )
312+
313+ local x , y , w , h = adjust_coordinates ()
314+
315+ local x = math.floor ((x + x1 * w ) * vop .w + 0.5 )
316+ local y = math.floor ((y + y1 * h ) * vop .h + 0.5 )
317+ local w = math.floor (w * (x2 - x1 ) * vop .w + 0.5 )
318+ local h = math.floor (h * (y2 - y1 ) * vop .h + 0.5 )
319+
298320 -- delogo is a little special and needs some padding to function
299321 w = math.min (vop .w - 1 , w )
300322 h = math.min (vop .h - 1 , h )
301323 x = math.max (1 , x )
302324 y = math.max (1 , y )
325+
303326 if x + w == vop .w then w = w - 1 end
304327 if y + h == vop .h then h = h - 1 end
328+
305329 vf_table [# vf_table + 1 ] = {
306330 name = " delogo" ,
307331 params = { x = tostring (x ), y = tostring (y ), w = tostring (w ), h = tostring (h ) }
308332 }
333+
309334 mp .set_property_native (" vf" , vf_table )
335+ table.insert (remove_last_filter , " delogo" )
310336 end
311337 end
312338end
@@ -351,6 +377,136 @@ function cancel_crop()
351377 active = false
352378end
353379
380+ -- adjust coordinates based on previous values
381+ function adjust_coordinates ()
382+ local x , y , w , h = 0 , 0 , 1 , 1
383+ for _ , crop in ipairs (recursive_crop ) do
384+ x = x + w * crop .x
385+ y = y + h * crop .y
386+ w = w * crop .w
387+ h = h * crop .h
388+ end
389+ return x , y , w , h
390+ end
391+
392+ function apply_video_crop ()
393+ local x , y , w , h = adjust_coordinates ()
394+
395+ local vop = mp .get_property_native (" video-out-params" )
396+ local x = math.floor (x * vop .w + 0.5 )
397+ local y = math.floor (y * vop .h + 0.5 )
398+ local w = math.floor (w * vop .w + 0.5 )
399+ local h = math.floor (h * vop .h + 0.5 )
400+
401+ local video_crop = tostring (w ) .. " x" .. tostring (h ) .. " +" .. tostring (x ) .. " +" .. tostring (y )
402+ mp .set_property_native (" video-crop" , video_crop )
403+ end
404+
405+ function remove_filter (vf_table , filter_name , filter_number )
406+ local filter_count = 0
407+ local remove_last = 0
408+ for i = 1 , # vf_table do
409+ if vf_table [i ].name == filter_name then
410+ filter_count = filter_count + 1
411+ remove_last = i
412+ end
413+ end
414+ if filter_count > 0 then
415+ table.remove (vf_table , remove_last )
416+ mp .set_property_native (" vf" , vf_table )
417+ mp .osd_message (" Removed: #" .. tostring (filter_number or filter_count ) .. " " .. filter_name )
418+ return true
419+ end
420+ return false
421+ end
422+
423+ function remove_video_crop (filter_number )
424+ if # recursive_crop > 0 then
425+ table.remove (recursive_crop )
426+ -- reapply each crop in the table
427+ apply_video_crop ()
428+ if # recursive_crop == 0 then
429+ mp .set_property_native (" video-crop" , " " )
430+ end
431+ mp .osd_message (" Removed: #" .. tostring (filter_number or # recursive_crop + 1 ) .. " " .. " video-crop" )
432+ return true
433+ end
434+ return false
435+ end
436+
437+ function remove_zoom_pan (filter_number )
438+ if # recursive_zoom_pan > 0 then
439+ table.remove (recursive_zoom_pan )
440+ if # recursive_zoom_pan > 0 then
441+ local lastZoomPan = recursive_zoom_pan [# recursive_zoom_pan ]
442+ mp .set_property (" video-zoom" , lastZoomPan .zoom )
443+ mp .set_property (" video-pan-x" , lastZoomPan .panX )
444+ mp .set_property (" video-pan-y" , lastZoomPan .panY )
445+ else
446+ mp .set_property (" video-zoom" , 0 )
447+ mp .set_property (" video-pan-x" , 0 )
448+ mp .set_property (" video-pan-y" , 0 )
449+ end
450+ mp .osd_message (" Removed: #" .. tostring (filter_number or # recursive_zoom_pan + 1 ) .. " " .. " soft-crop" )
451+ return true
452+ end
453+ return false
454+ end
455+
456+ -- 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
457+ function remove_last_filter_entry (filter_type )
458+ for i = # remove_last_filter , 1 , - 1 do
459+ if remove_last_filter [i ] == filter_type then
460+ table.remove (remove_last_filter , i )
461+ break
462+ end
463+ end
464+ end
465+
466+ function remove_crop (mode , order )
467+ local vf_table = mp .get_property_native (" vf" )
468+ local total_filters = # remove_last_filter
469+
470+ -- 'remove-crop all order' removes all filters starting with most recently added
471+ if order == " order" then
472+ if total_filters == 0 then
473+ mp .osd_message (" Nothing to remove" )
474+ return
475+ end
476+ local last_filter = table.remove (remove_last_filter )
477+ if last_filter == " hard" then
478+ remove_video_crop (total_filters )
479+ elseif last_filter == " delogo" then
480+ remove_filter (vf_table , " delogo" , total_filters )
481+ elseif last_filter == " soft" then
482+ remove_zoom_pan (total_filters )
483+ end
484+ else
485+ local modes = {" delogo" , " hard" , " soft" }
486+ if order == " hard" then
487+ modes = {" hard" , " soft" , " delogo" }
488+ elseif order == " soft" then
489+ modes = {" soft" , " hard" , " delogo" }
490+ end
491+
492+ for _ , mode_name in ipairs (modes ) do
493+ if not mode or mode == " all" or mode == mode_name then
494+ if mode_name == " delogo" and remove_filter (vf_table , " delogo" ) then
495+ remove_last_filter_entry (" delogo" )
496+ return
497+ elseif mode_name == " hard" and remove_video_crop () then
498+ remove_last_filter_entry (" hard" )
499+ return
500+ elseif mode_name == " soft" and remove_zoom_pan () then
501+ remove_last_filter_entry (" soft" )
502+ return
503+ end
504+ end
505+ end
506+ mp .osd_message (" Nothing to remove" )
507+ end
508+ end
509+
354510function start_crop (mode )
355511 if active then return end
356512 if not mp .get_property_native (" osd-dimensions" ) then return end
@@ -388,37 +544,24 @@ function toggle_crop(mode)
388544 msg .error (" Invalid mode value: " .. mode )
389545 end
390546 local toggle_mode = mode or opts .mode
391- if toggle_mode == " soft" then return end -- can't toggle soft mode
392-
393- local remove_delogo = function ()
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 == " delogo" 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
408- end
409- if toggle_mode == " delogo" and not remove_delogo () then
547+
548+ if toggle_mode == " soft" and not remove_zoom_pan () then
410549 start_crop (mode )
550+ elseif toggle_mode == " soft" then
551+ remove_last_filter_entry (" soft" )
411552 end
412- local remove_hard = function ()
413- video_crop = mp .get_property_native (" video-crop" )
414- if video_crop == " " then
415- return false
416- end
417- mp .set_property_native (" video-crop" , " " )
418- return true
553+
554+ local vf_table = mp .get_property_native (" vf" )
555+ if toggle_mode == " delogo" and not remove_filter (vf_table , " delogo" ) then
556+ start_crop (mode )
557+ elseif toggle_mode == " delogo" then
558+ remove_last_filter_entry (" delogo" )
419559 end
420- if toggle_mode == " hard" and not remove_hard () then
560+
561+ if toggle_mode == " hard" and not remove_video_crop () then
421562 start_crop (mode )
563+ elseif toggle_mode == " hard" then
564+ remove_last_filter_entry (" hard" )
422565 end
423566end
424567
@@ -449,5 +592,6 @@ bindings_repeat[opts.up_fine] = movement_func(0, -opts.fine_movement)
449592bindings_repeat [opts .down_fine ] = movement_func (0 , opts .fine_movement )
450593
451594
595+ mp .add_key_binding (nil , " remove-crop" , remove_crop )
452596mp .add_key_binding (nil , " start-crop" , start_crop )
453597mp .add_key_binding (nil , " toggle-crop" , toggle_crop )
0 commit comments