summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorヒカリ <50797982+Natural-Harmonia-Gropius@users.noreply.github.com>2020-05-25 12:06:24 +0800
committerKevin Mitchell <kevmitch@gmail.com>2020-06-01 23:07:26 -0700
commit376aea36ebffeb29a8c397e667acdcd83f9f659f (patch)
treedb6b43af1f67e61f3d33e5a22c2492b129bf7140
parentc5158b057c8d0936ea12c5bc3153903263f03947 (diff)
downloadmpv-376aea36ebffeb29a8c397e667acdcd83f9f659f.tar.bz2
mpv-376aea36ebffeb29a8c397e667acdcd83f9f659f.tar.xz
TOOLS/autocrop.lua: automatically crop at startup
-rw-r--r--TOOLS/lua/autocrop.lua376
1 files changed, 292 insertions, 84 deletions
diff --git a/TOOLS/lua/autocrop.lua b/TOOLS/lua/autocrop.lua
index 2316a46d00..af6021b75a 100644
--- a/TOOLS/lua/autocrop.lua
+++ b/TOOLS/lua/autocrop.lua
@@ -1,114 +1,322 @@
--- This script uses the lavfi cropdetect filter to automatically
--- insert a crop filter with appropriate parameters for the currently
--- playing video.
---
--- It registers the key-binding "C" (shift+c), which when pressed,
--- inserts the filter vf=lavfi=cropdetect. After 1 second, it then
--- inserts the filter vf=crop=w:h:x:y, where w,h,x,y are determined
--- from the vf-metadata gathered by cropdetect. The cropdetect filter
--- is removed immediately after the crop filter is inserted as it is
--- no longer needed.
---
--- If the "C" key is pressed again, the crop filter is removed
--- restoring playback to its original state.
---
--- Since the crop parameters are determined from the 1 second of video
--- between inserting the cropdetect and crop filters, the "C" key
--- should be pressed at a position in the video where the crop region
--- is unambiguous (i.e., not a black frame, black background title
--- card, or dark scene).
---
--- The default delay between insertion of the cropdetect and
--- crop filters may be overridden by adding
---
--- --script-opts=autocrop.detect_seconds=<number of seconds>
---
--- to mpv's arguments. This may be desirable to allow cropdetect more
--- time to collect data.
-require "mp.msg"
+--[[
+This script uses the lavfi cropdetect filter to automatically
+insert a crop filter with appropriate parameters for the
+currently playing video.
+
+It will automatically crop the video, when playback starts.
+
+Also It registers the key-binding "C" (shift+c). You can manually
+crop the video by pressing the "C" (shift+c) key.
+
+If the "C" key is pressed again, the crop filter is removed
+restoring playback to its original state.
+
+The workflow is as follows: First, it inserts the filter
+vf=lavfi=cropdetect. After <detect_seconds> (default is 1)
+seconds, it then inserts the filter vf=crop=w:h:x:y, where
+w,h,x,y are determined from the vf-metadata gathered by
+cropdetect. The cropdetect filter is removed immediately after
+the crop filter is inserted as it is no longer needed.
+
+Since the crop parameters are determined from the 1 second of
+video between inserting the cropdetect and crop filters, the "C"
+key should be pressed at a position in the video where the crop
+region is unambiguous (i.e., not a black frame, black background
+title card, or dark scene).
+
+The default options can be overridden by adding
+script-opts-append=autocrop-<parameter>=<value> into mpv.conf
+
+List of available parameters (For default values, see <options>):
+
+auto: bool - Whether to automatically apply crop at the start of
+ playback. If you don't want to crop automatically, set it to
+ false or add "script-opts-append=autocrop-auto=no" into
+ mpv.conf.
+
+auto_delay: seconds - Delay before starting crop in auto mode.
+ You can try to increase this value to avoid dark scene or
+ fade in at beginning. Automatic cropping will not occur if
+ the value is larger than the remaining playback time.
+
+detect_limit: number[0-255] - Black threshold for cropdetect.
+ Smaller values will generally result in less cropping.
+ See limit of https://ffmpeg.org/ffmpeg-filters.html#cropdetect
+
+detect_round: number[2^n] - The value which the width/height
+ should be divisible by. Smaller values ​​have better detection
+ accuracy. If you have problems with other filters,
+ you can try to set it to 4 or 16.
+ See round of https://ffmpeg.org/ffmpeg-filters.html#cropdetect
-script_name = mp.get_script_name()
-cropdetect_label = string.format("%s-cropdetect", script_name)
-crop_label = string.format("%s-crop", script_name)
+detect_min_ratio: number[0.0-1.0] - The ratio of the minimum clip
+ size to the original. If the picture is over cropped or under
+ cropped, try adjusting this value.
--- number of seconds to gather cropdetect data
-detect_seconds = tonumber(mp.get_opt(string.format("%s.detect_seconds", script_name)))
-if not detect_seconds then
+detect_seconds: seconds - How long to gather cropdetect data.
+ Increasing this may be desirable to allow cropdetect more
+ time to collect data.
+--]]
+
+require "mp.msg"
+require 'mp.options'
+
+local options = {
+ auto = true,
+ auto_delay = 4,
+ detect_limit = "24/255",
+ detect_round = 2,
+ detect_min_ratio = 0.5,
detect_seconds = 1
-end
+}
+read_options(options)
+
+local label_prefix = mp.get_script_name()
+local labels = {
+ crop = string.format("%s-crop", label_prefix),
+ cropdetect = string.format("%s-cropdetect", label_prefix)
+}
+
+timers = {
+ auto_delay = nil,
+ detect_crop = nil
+}
-function del_filter_if_present(label)
- -- necessary because mp.command('vf del @label:filter') raises an
- -- error if the filter doesn't exist
- local vfs = mp.get_property_native("vf")
- for i,vf in pairs(vfs) do
- if vf["label"] == label then
- table.remove(vfs, i)
- mp.set_property_native("vf", vfs)
+function is_filter_present(label)
+ local filters = mp.get_property_native("vf")
+ for index, filter in pairs(filters) do
+ if filter["label"] == label then
return true
end
end
return false
end
-function autocrop_start()
- -- exit if cropdetection is already in progress
- if timer then
- mp.msg.warn("already cropdetecting!")
+function is_enough_time(seconds)
+
+ -- Plus 1 second for deviation.
+ local time_needed = seconds + 1
+ local playtime_remaining = mp.get_property_native("playtime-remaining")
+
+ return playtime_remaining and time_needed < playtime_remaining
+end
+
+function is_cropable()
+ local vid = mp.get_property_native("vid")
+ local is_album = vid and mp.get_property_native(
+ string.format("track-list/%d/albumart", vid)
+ ) or false
+
+ return vid and not is_album
+end
+
+function remove_filter(label)
+ if is_filter_present(label) then
+ mp.command(string.format('vf remove @%s', label))
+ return true
+ end
+ return false
+end
+
+function cleanup()
+
+ -- Remove all existing filters.
+ for key, value in pairs(labels) do
+ remove_filter(value)
+ end
+
+ -- Kill all timers.
+ for index, timer in pairs(timers) do
+ if timer then
+ timer:kill()
+ timer = nil
+ end
+ end
+end
+
+function detect_crop()
+
+ -- If it's not cropable, exit.
+ if not is_cropable() then
+ mp.msg.warn("autocrop only works for videos.")
return
end
- -- if there's a crop filter, remove it and exit
- if del_filter_if_present(crop_label) then
+ -- Verify if there is enough time to detect crop.
+ local time_needed = options.detect_seconds
+
+ if not is_enough_time(time_needed) then
+ mp.msg.warn("Not enough time to detect crop.")
return
end
- -- insert the cropdetect filter
- ret=mp.command(
+ -- Insert the cropdetect filter.
+ local limit = options.detect_limit
+ local round = options.detect_round
+
+ mp.command(
string.format(
- 'vf add @%s:cropdetect=limit=%f:round=2:reset=0',
- cropdetect_label, 24/255
+ 'vf pre @%s:cropdetect=limit=%s:round=%d:reset=0',
+ labels.cropdetect, limit, round
)
)
- -- wait to gather data
- timer=mp.add_timeout(detect_seconds, do_crop)
+
+ -- Wait to gather data.
+ timers.detect_crop = mp.add_timeout(time_needed, detect_end)
end
-function do_crop()
- -- get the metadata
- local cropdetect_metadata = mp.get_property_native(
- string.format("vf-metadata/%s", cropdetect_label)
+function detect_end()
+
+ -- Get the metadata and remove the cropdetect filter.
+ local cropdetect_metadata =
+ mp.get_property_native(
+ string.format("vf-metadata/%s",
+ labels.cropdetect
+ )
)
+ remove_filter(labels.cropdetect)
+
+ -- Remove the timer of detect crop.
+ if timers.detect_crop then
+ timers.detect_crop:kill()
+ timers.detect_crop = nil
+ end
- -- use it to crop if its valid
+ local meta = {}
+
+ -- Verify the existence of metadata.
if cropdetect_metadata then
- if cropdetect_metadata["lavfi.cropdetect.w"]
- and cropdetect_metadata["lavfi.cropdetect.h"]
- and cropdetect_metadata["lavfi.cropdetect.x"]
- and cropdetect_metadata["lavfi.cropdetect.y"]
- then
- mp.command(string.format("vf add @%s:lavfi-crop=w=%s:h=%s:x=%s:y=%s",
- crop_label,
- cropdetect_metadata["lavfi.cropdetect.w"],
- cropdetect_metadata["lavfi.cropdetect.h"],
- cropdetect_metadata["lavfi.cropdetect.x"],
- cropdetect_metadata["lavfi.cropdetect.y"]))
- else
- mp.msg.error(
- "Got empty crop data. You might need to increase detect_seconds."
- )
- end
+ meta = {
+ w = cropdetect_metadata["lavfi.cropdetect.w"],
+ h = cropdetect_metadata["lavfi.cropdetect.h"],
+ x = cropdetect_metadata["lavfi.cropdetect.x"],
+ y = cropdetect_metadata["lavfi.cropdetect.y"],
+ }
else
- mp.msg.error(
- "No crop data. Was the cropdetect filter successfully inserted?"
+ mp.msg.error("No crop data.")
+ mp.msg.info("Was the cropdetect filter successfully inserted?")
+ mp.msg.info("Does your version of ffmpeg/libav support AVFrame metadata?")
+ return
+ end
+
+ -- Verify that the metadata meets the requirements and convert it.
+ if meta.w and meta.h and meta.x and meta.y then
+ local width = mp.get_property_native("width")
+ local height = mp.get_property_native("height")
+
+ meta = {
+ w = tonumber(meta.w),
+ h = tonumber(meta.h),
+ x = tonumber(meta.x),
+ y = tonumber(meta.y),
+ min_w = width * options.detect_min_ratio,
+ min_h = height * options.detect_min_ratio,
+ max_w = width,
+ max_h = height
+ }
+ else
+ mp.msg.error("Got empty crop data.")
+ mp.msg.info("You might need to increase detect_seconds.")
+ return
+ end
+
+ apply_crop(meta)
+end
+
+function apply_crop(meta)
+
+ -- Verify if it is necessary to crop.
+ local is_effective = meta.x > 0 or meta.y > 0
+ or meta.w < meta.max_w or meta.h < meta.max_h
+
+ if not is_effective then
+ mp.msg.info("No area detected for cropping.")
+ return
+ end
+
+ -- Verify it is not over cropped.
+ local is_excessive = meta.w < meta.min_w and meta.h < meta.min_h
+
+ if is_excessive then
+ mp.msg.info("The area to be cropped is too large.")
+ mp.msg.info("You might need to decrease detect_min_ratio.")
+ return
+ end
+
+ -- Remove existing crop.
+ remove_filter(labels.crop)
+
+ -- Apply crop.
+ mp.command(
+ string.format("vf pre @%s:lavfi-crop=w=%s:h=%s:x=%s:y=%s",
+ labels.crop, meta.w, meta.h, meta.x, meta.y
)
- mp.msg.error(
- "Does your version of ffmpeg/libav support AVFrame metadata?"
+ )
+end
+
+function on_start()
+
+ -- Clean up at the beginning.
+ cleanup()
+
+ -- If auto is not true, exit.
+ if not options.auto then
+ return
+ end
+
+ -- If it is the beginning, wait for detect_crop
+ -- after auto_delay seconds, otherwise immediately.
+ local playback_time = mp.get_property_native("playback-time")
+ local is_delay_needed = playback_time
+ and options.auto_delay > playback_time
+
+ if is_delay_needed then
+
+ -- Verify if there is enough time for autocrop.
+ local time_needed = options.auto_delay + options.detect_seconds
+
+ if not is_enough_time(time_needed) then
+ mp.msg.warn("Not enough time for autocrop.")
+ return
+ end
+
+ timers.auto_delay = mp.add_timeout(time_needed,
+ function()
+ detect_crop()
+
+ -- Remove the timer of auto delay.
+ timers.auto_delay:kill()
+ timers.auto_delay = nil
+ end
)
+ else
+ detect_crop()
end
- -- remove the cropdetect filter
- del_filter_if_present(cropdetect_label)
- timer=nil
end
-mp.add_key_binding("C", "auto_crop", autocrop_start)
+function on_toggle()
+
+ -- If it is during auto_delay, kill the timer.
+ if timers.auto_delay then
+ timers.auto_delay:kill()
+ timers.auto_delay = nil
+ end
+
+ -- Cropped => Remove it.
+ if remove_filter(labels.crop) then
+ return
+ end
+
+ -- Detecting => Leave it.
+ if timers.detect_crop then
+ mp.msg.warn("Already cropdetecting!")
+ return
+ end
+
+ -- Neither => Do delectcrop.
+ detect_crop()
+end
+
+mp.add_key_binding("C", "toggle_crop", on_toggle)
+mp.register_event("end-file", cleanup)
+mp.register_event("file-loaded", on_start)