diff options
Diffstat (limited to 'TOOLS/lua/autocrop.lua')
-rw-r--r-- | TOOLS/lua/autocrop.lua | 283 |
1 files changed, 128 insertions, 155 deletions
diff --git a/TOOLS/lua/autocrop.lua b/TOOLS/lua/autocrop.lua index af6021b75a..ba38f487cc 100644 --- a/TOOLS/lua/autocrop.lua +++ b/TOOLS/lua/autocrop.lua @@ -1,99 +1,71 @@ --[[ -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 - -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. - -detect_seconds: seconds - How long to gather cropdetect data. - Increasing this may be desirable to allow cropdetect more - time to collect data. ---]] +This script uses the lavfi cropdetect filter and the video-crop property to +automatically crop the currently playing video with appropriate parameters. + +It automatically crops the video when playback starts. + +You can also manually crop the video by pressing the "C" (shift+c) key. +Pressing it again undoes the crop. + +The workflow is as follows: First, it inserts the cropdetect filter. After +<detect_seconds> (default is 1) seconds, it then sets video-crop based on the +vf-metadata values gathered by cropdetect. The cropdetect filter is removed +after video-crop is set as it is no longer needed. + +Since the crop parameters are determined from the 1 second of video between +inserting the cropdetect filter and setting video-crop, 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). -require "mp.msg" -require 'mp.options' +If non-copy-back hardware decoding is in use, hwdec is temporarily disabled for +the duration of cropdetect as the filter would fail otherwise. +These are the default options. They can be overridden by adding +script-opts-append=autocrop-<parameter>=<value> to mpv.conf. +--]] local options = { + -- Whether to automatically apply crop at the start of playback. If you + -- don't want to crop automatically, add + -- script-opts-append=autocrop-auto=no to mpv.conf. auto = true, + -- Delay before starting crop in auto mode. You can try to increase this + -- value to avoid dark scenes or fade ins at beginning. Automatic cropping + -- will not occur if the value is larger than the remaining playback time. auto_delay = 4, + -- Black threshold for cropdetect. Smaller values will generally result in + -- less cropping. See limit of + -- https://ffmpeg.org/ffmpeg-filters.html#cropdetect detect_limit = "24/255", + -- 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 detect_round = 2, + -- The ratio of the minimum clip size to the original. A number from 0 to + -- 1. If the picture is over cropped, try adjusting this value. detect_min_ratio = 0.5, - detect_seconds = 1 + -- How long to gather cropdetect data. Increasing this may be desirable to + -- allow cropdetect more time to collect data. + detect_seconds = 1, + -- Whether the OSD shouldn't be used when cropdetect and video-crop are + -- applied and removed. + suppress_osd = false, } -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) -} +require "mp.options".read_options(options) -timers = { +local cropdetect_label = mp.get_script_name() .. "-cropdetect" + +local timers = { auto_delay = nil, detect_crop = nil } -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 +local hwdec_backup -function is_enough_time(seconds) +local command_prefix = options.suppress_osd and 'no-osd' or '' +local function is_enough_time(seconds) -- Plus 1 second for deviation. local time_needed = seconds + 1 local playtime_remaining = mp.get_property_native("playtime-remaining") @@ -101,80 +73,82 @@ function is_enough_time(seconds) 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 +local function is_cropable(time_needed) + if mp.get_property_native('current-tracks/video/image') ~= false then + mp.msg.warn("autocrop only works for videos.") + return false + end + + if not is_enough_time(time_needed) then + mp.msg.warn("Not enough time to detect crop.") + return false + end - return vid and not is_album + return true end -function remove_filter(label) - if is_filter_present(label) then - mp.command(string.format('vf remove @%s', label)) - return true +local function remove_cropdetect() + for _, filter in pairs(mp.get_property_native("vf")) do + if filter.label == cropdetect_label then + mp.command( + string.format("%s vf remove @%s", command_prefix, filter.label)) + + return + end end - return false end -function cleanup() - - -- Remove all existing filters. - for key, value in pairs(labels) do - remove_filter(value) +local function restore_hwdec() + if hwdec_backup then + mp.set_property("hwdec", hwdec_backup) + hwdec_backup = nil end +end + +local function cleanup() + remove_cropdetect() -- Kill all timers. for index, timer in pairs(timers) do if timer then timer:kill() - timer = nil + timers[index] = nil end end + + restore_hwdec() end -function detect_crop() +local function apply_crop(meta) + -- Verify if it is necessary to crop. + local is_effective = meta.w and meta.h and meta.x and meta.y and + (meta.x > 0 or meta.y > 0 + or meta.w < meta.max_w or meta.h < meta.max_h) - -- If it's not cropable, exit. - if not is_cropable() then - mp.msg.warn("autocrop only works for videos.") - return + -- Verify it is not over cropped. + local is_excessive = false + if is_effective and (meta.w < meta.min_w or meta.h < meta.min_h) then + mp.msg.info("The area to be cropped is too large.") + mp.msg.info("You might need to decrease detect_min_ratio.") + is_excessive = true end - -- 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.") + if not is_effective or is_excessive then + -- Clear any existing crop. + mp.command(string.format("%s set file-local-options/video-crop ''", command_prefix)) return end - -- Insert the cropdetect filter. - local limit = options.detect_limit - local round = options.detect_round - - mp.command( - string.format( - 'vf pre @%s:cropdetect=limit=%s:round=%d:reset=0', - labels.cropdetect, limit, round - ) - ) - - -- Wait to gather data. - timers.detect_crop = mp.add_timeout(time_needed, detect_end) + -- Apply crop. + mp.command(string.format("%s set file-local-options/video-crop %sx%s+%s+%s", + command_prefix, meta.w, meta.h, meta.x, meta.y)) end -function detect_end() - +local 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) + local cropdetect_metadata = mp.get_property_native( + "vf-metadata/" .. cropdetect_label) + remove_cropdetect() -- Remove the timer of detect crop. if timers.detect_crop then @@ -182,7 +156,9 @@ function detect_end() timers.detect_crop = nil end - local meta = {} + restore_hwdec() + + local meta -- Verify the existence of metadata. if cropdetect_metadata then @@ -195,7 +171,7 @@ function detect_end() else 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?") + mp.msg.info("Does your version of FFmpeg support AVFrame metadata?") return end @@ -217,44 +193,41 @@ function detect_end() 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 +local function detect_crop() + local time_needed = options.detect_seconds - if not is_effective then - mp.msg.info("No area detected for cropping.") + if not is_cropable(time_needed) then 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 + local hwdec_current = mp.get_property("hwdec-current") + if hwdec_current:find("-copy$") == nil and hwdec_current ~= "no" and + hwdec_current ~= "crystalhd" and hwdec_current ~= "rkmpp" then + hwdec_backup = mp.get_property("hwdec") + mp.set_property("hwdec", "no") end - -- Remove existing crop. - remove_filter(labels.crop) + -- Insert the cropdetect filter. + local limit = options.detect_limit + local round = options.detect_round - -- 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 + string.format( + '%s vf pre @%s:cropdetect=limit=%s:round=%d:reset=0', + command_prefix, cropdetect_label, limit, round ) ) + + -- Wait to gather data. + timers.detect_crop = mp.add_timeout(time_needed, detect_end) end -function on_start() +local function on_start() -- Clean up at the beginning. cleanup() @@ -275,8 +248,7 @@ function on_start() -- 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.") + if not is_cropable(time_needed) then return end @@ -294,7 +266,7 @@ function on_start() end end -function on_toggle() +local function on_toggle() -- If it is during auto_delay, kill the timer. if timers.auto_delay then @@ -303,7 +275,8 @@ function on_toggle() end -- Cropped => Remove it. - if remove_filter(labels.crop) then + if mp.get_property("video-crop") ~= "" then + mp.command(string.format("%s set file-local-options/video-crop ''", command_prefix)) return end @@ -313,7 +286,7 @@ function on_toggle() return end - -- Neither => Do delectcrop. + -- Neither => Detect crop. detect_crop() end |