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,58 @@ 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 )
274- mp .set_property (" video-zoom" , math.min (newZoom1 , newZoom2 ))
275- mp .set_property (" video-pan-x" , 0.5 - (x1 + w / 2 ))
276- mp .set_property (" video-pan-y" , 0.5 - (y1 + h / 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+
284+ table.insert (recursive_zoom_pan , {zoom = newZoom , panX = newPanX , panY = newPanY })
285+ mp .set_property (" video-zoom" , newZoom )
286+ mp .set_property (" video-pan-x" , newPanX )
287+ mp .set_property (" video-pan-y" , newPanY )
288+ table.insert (remove_last_filter , " soft" )
289+
277290 elseif active_mode == " hard" or active_mode == " delogo" then
278291 x1 = clamp (0 , x1 , 1 )
279292 y1 = clamp (0 , y1 , 1 )
280293 x2 = clamp (0 , x2 , 1 )
281294 y2 = clamp (0 , y2 , 1 )
282295 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
296+ if active_mode == " hard" then
297+ local w = x2 - x1
298+ local h = y2 - y1
299+
300+ table.insert (recursive_crop , {x = x1 , y = y1 , w = w , h = h })
301+ apply_video_crop ()
302+ table.insert (remove_last_filter , " hard" )
303+
304+ elseif active_mode == " delogo" then
305+ local vf_table = mp .get_property_native (" vf" )
306+
307+ local x , y , w , h = adjust_coordinates ()
308+
309+ local x = math.floor ((x + x1 * w ) * vop .w + 0.5 )
310+ local y = math.floor ((y + y1 * h ) * vop .h + 0.5 )
311+ local w = math.floor (w * (x2 - x1 ) * vop .w + 0.5 )
312+ local h = math.floor (h * (y2 - y1 ) * vop .h + 0.5 )
313+
289314 -- delogo is a little special and needs some padding to function
290315 w = math.min (vop .w - 1 , w )
291316 h = math.min (vop .h - 1 , h )
292317 x = math.max (1 , x )
293318 y = math.max (1 , y )
319+
294320 if x + w == vop .w then w = w - 1 end
295321 if y + h == vop .h then h = h - 1 end
322+
323+ vf_table [# vf_table + 1 ] = {
324+ name = " delogo" ,
325+ params = { x = tostring (x ), y = tostring (y ), w = tostring (w ), h = tostring (h ) }
326+ }
327+
328+ mp .set_property_native (" vf" , vf_table )
329+ table.insert (remove_last_filter , " delogo" )
296330 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 )
302331 end
303332end
304333
@@ -346,6 +375,136 @@ function cancel_crop()
346375 end
347376end
348377
378+ -- adjust coordinates based on previous values
379+ function adjust_coordinates ()
380+ local x , y , w , h = 0 , 0 , 1 , 1
381+ for _ , crop in ipairs (recursive_crop ) do
382+ x = x + w * crop .x
383+ y = y + h * crop .y
384+ w = w * crop .w
385+ h = h * crop .h
386+ end
387+ return x , y , w , h
388+ end
389+
390+ function apply_video_crop ()
391+ local x , y , w , h = adjust_coordinates ()
392+
393+ local vop = mp .get_property_native (" video-out-params" )
394+ local x = math.floor (x * vop .w + 0.5 )
395+ local y = math.floor (y * vop .h + 0.5 )
396+ local w = math.floor (w * vop .w + 0.5 )
397+ local h = math.floor (h * vop .h + 0.5 )
398+
399+ local video_crop = tostring (w ) .. " x" .. tostring (h ) .. " +" .. tostring (x ) .. " +" .. tostring (y )
400+ mp .set_property_native (" video-crop" , video_crop )
401+ end
402+
403+ function remove_filter (vf_table , filter_name , filter_number )
404+ local filter_count = 0
405+ local remove_last = 0
406+ for i = 1 , # vf_table do
407+ if vf_table [i ].name == filter_name then
408+ filter_count = filter_count + 1
409+ remove_last = i
410+ end
411+ end
412+ if filter_count > 0 then
413+ table.remove (vf_table , remove_last )
414+ mp .set_property_native (" vf" , vf_table )
415+ mp .osd_message (" Removed: #" .. tostring (filter_number or filter_count ) .. " " .. filter_name )
416+ return true
417+ end
418+ return false
419+ end
420+
421+ function remove_video_crop (filter_number )
422+ if # recursive_crop > 0 then
423+ table.remove (recursive_crop )
424+ -- reapply each crop in the table
425+ apply_video_crop ()
426+ if # recursive_crop == 0 then
427+ mp .set_property_native (" video-crop" , " " )
428+ end
429+ mp .osd_message (" Removed: #" .. tostring (filter_number or # recursive_crop + 1 ) .. " " .. " video-crop" )
430+ return true
431+ end
432+ return false
433+ end
434+
435+ function remove_zoom_pan (filter_number )
436+ if # recursive_zoom_pan > 0 then
437+ table.remove (recursive_zoom_pan )
438+ if # recursive_zoom_pan > 0 then
439+ local lastZoomPan = recursive_zoom_pan [# recursive_zoom_pan ]
440+ mp .set_property (" video-zoom" , lastZoomPan .zoom )
441+ mp .set_property (" video-pan-x" , lastZoomPan .panX )
442+ mp .set_property (" video-pan-y" , lastZoomPan .panY )
443+ else
444+ mp .set_property (" video-zoom" , 0 )
445+ mp .set_property (" video-pan-x" , 0 )
446+ mp .set_property (" video-pan-y" , 0 )
447+ end
448+ mp .osd_message (" Removed: #" .. tostring (filter_number or # recursive_zoom_pan + 1 ) .. " " .. " soft-crop" )
449+ return true
450+ end
451+ return false
452+ end
453+
454+ -- 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
455+ function remove_last_filter_entry (filter_type )
456+ for i = # remove_last_filter , 1 , - 1 do
457+ if remove_last_filter [i ] == filter_type then
458+ table.remove (remove_last_filter , i )
459+ break
460+ end
461+ end
462+ end
463+
464+ function remove_crop (mode , order )
465+ local vf_table = mp .get_property_native (" vf" )
466+ local total_filters = # remove_last_filter
467+
468+ -- 'remove-crop all order' removes all filters starting with most recently added
469+ if order == " order" then
470+ if total_filters == 0 then
471+ mp .osd_message (" Nothing to remove" )
472+ return
473+ end
474+ local last_filter = table.remove (remove_last_filter )
475+ if last_filter == " hard" then
476+ remove_video_crop (total_filters )
477+ elseif last_filter == " delogo" then
478+ remove_filter (vf_table , " delogo" , total_filters )
479+ elseif last_filter == " soft" then
480+ remove_zoom_pan (total_filters )
481+ end
482+ else
483+ local modes = {" delogo" , " hard" , " soft" }
484+ if order == " hard" then
485+ modes = {" hard" , " soft" , " delogo" }
486+ elseif order == " soft" then
487+ modes = {" soft" , " hard" , " delogo" }
488+ end
489+
490+ for _ , mode_name in ipairs (modes ) do
491+ if not mode or mode == " all" or mode == mode_name then
492+ if mode_name == " delogo" and remove_filter (vf_table , " delogo" ) then
493+ remove_last_filter_entry (" delogo" )
494+ return
495+ elseif mode_name == " hard" and remove_video_crop () then
496+ remove_last_filter_entry (" hard" )
497+ return
498+ elseif mode_name == " soft" and remove_zoom_pan () then
499+ remove_last_filter_entry (" soft" )
500+ return
501+ end
502+ end
503+ end
504+ mp .osd_message (" Nothing to remove" )
505+ end
506+ end
507+
349508function start_crop (mode )
350509 if active then return end
351510 if not mp .get_property_native (" osd-dimensions" ) then return end
@@ -354,7 +513,7 @@ function start_crop(mode)
354513 return
355514 end
356515 local mode_maybe = mode or opts .mode
357- if mode_maybe ~= ' soft ' then
516+ if mode_maybe == " delogo " then
358517 local hwdec = mp .get_property (" hwdec-current" )
359518 if hwdec and hwdec ~= " no" and not string.find (hwdec , " -copy$" ) then
360519 msg .error (" Cannot crop with hardware decoding active (see manual)" )
@@ -387,27 +546,24 @@ function toggle_crop(mode)
387546 msg .error (" Invalid mode value: " .. mode )
388547 end
389548 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
549+
550+ if toggle_mode == " soft" and not remove_zoom_pan () then
551+ start_crop (mode )
552+ elseif toggle_mode == " soft" then
553+ remove_last_filter_entry (" soft" )
554+ end
555+
556+ local vf_table = mp .get_property_native (" vf" )
557+ if toggle_mode == " delogo" and not remove_filter (vf_table , " delogo" ) then
558+ start_crop (mode )
559+ elseif toggle_mode == " delogo" then
560+ remove_last_filter_entry (" delogo" )
408561 end
409- if not remove_filter () then
562+
563+ if toggle_mode == " hard" and not remove_video_crop () then
410564 start_crop (mode )
565+ elseif toggle_mode == " hard" then
566+ remove_last_filter_entry (" hard" )
411567 end
412568end
413569
@@ -443,5 +599,6 @@ bindings_repeat[opts.up_fine] = movement_func(0, -opts.fine_movement)
443599bindings_repeat [opts .down_fine ] = movement_func (0 , opts .fine_movement )
444600
445601
602+ mp .add_key_binding (nil , " remove-crop" , remove_crop )
446603mp .add_key_binding (nil , " start-crop" , start_crop )
447604mp .add_key_binding (nil , " toggle-crop" , toggle_crop )
0 commit comments