diff options
Diffstat (limited to 'TOOLS/lua')
-rw-r--r-- | TOOLS/lua/acompressor.lua | 9 | ||||
-rw-r--r-- | TOOLS/lua/ao-null-reload.lua | 6 | ||||
-rw-r--r-- | TOOLS/lua/audio-hotplug-test.lua | 6 | ||||
-rw-r--r-- | TOOLS/lua/autocrop.lua | 284 | ||||
-rw-r--r-- | TOOLS/lua/autodeint.lua | 77 | ||||
-rw-r--r-- | TOOLS/lua/autoload.lua | 411 | ||||
-rw-r--r-- | TOOLS/lua/command-test.lua | 27 | ||||
-rw-r--r-- | TOOLS/lua/cycle-deinterlace-pullup.lua | 12 | ||||
-rw-r--r-- | TOOLS/lua/nan-test.lua | 6 | ||||
-rw-r--r-- | TOOLS/lua/observe-all.lua | 10 | ||||
-rw-r--r-- | TOOLS/lua/ontop-playback.lua | 2 | ||||
-rw-r--r-- | TOOLS/lua/osd-test.lua | 2 | ||||
-rw-r--r-- | TOOLS/lua/pause-when-minimize.lua | 2 | ||||
-rw-r--r-- | TOOLS/lua/skip-logo.lua | 20 | ||||
-rw-r--r-- | TOOLS/lua/status-line.lua | 12 | ||||
-rw-r--r-- | TOOLS/lua/test-hooks.lua | 6 |
16 files changed, 527 insertions, 365 deletions
diff --git a/TOOLS/lua/acompressor.lua b/TOOLS/lua/acompressor.lua index 6a6914076a..e94dc3e0fb 100644 --- a/TOOLS/lua/acompressor.lua +++ b/TOOLS/lua/acompressor.lua @@ -53,8 +53,10 @@ local params = { } local function parse_value(value) - -- Using nil here because tonumber differs between lua 5.1 and 5.2 when parsing fractions in combination with explicit base argument set to 10. - -- And we can't omit it because gsub returns 2 values which would get unpacked and cause more problems. Gotta love scripting languages. + -- Using nil here because tonumber differs between lua 5.1 and 5.2 when + -- parsing fractions in combination with explicit base argument set to 10. + -- And we can't omit it because gsub returns 2 values which would get + -- unpacked and cause more problems. Gotta love scripting languages. return tonumber(value:gsub('dB$', ''), nil) end @@ -76,7 +78,8 @@ local function show_osd(filter) for _,param in ipairs(params) do local value = parse_value(filter.params[param.name]) if not (param.hide_default and value == o['default_' .. param.name]) then - pretty[#pretty+1] = string.format('%s: %g%s', param.name:gsub("^%l", string.upper), value, param.dB) + pretty[#pretty+1] = string.format('%s: %g%s', param.name:gsub("^%l", string.upper), + value, param.dB) end end diff --git a/TOOLS/lua/ao-null-reload.lua b/TOOLS/lua/ao-null-reload.lua index 5b2330b517..6809bb3a25 100644 --- a/TOOLS/lua/ao-null-reload.lua +++ b/TOOLS/lua/ao-null-reload.lua @@ -3,12 +3,14 @@ -- particular for ao=wasapi, since the internal IMMNotificationClient code that -- normally triggers ao-reload will not be running in this case. -function do_reload() +local reloading + +local function do_reload() mp.command("ao-reload") reloading = nil end -function on_audio_device_list_change() +local function on_audio_device_list_change() if mp.get_property("current-ao") == "null" and not reloading then mp.msg.verbose("audio-device-list changed: reloading audio") -- avoid calling ao-reload too often diff --git a/TOOLS/lua/audio-hotplug-test.lua b/TOOLS/lua/audio-hotplug-test.lua index 8dedc68cbe..e0ef223c0c 100644 --- a/TOOLS/lua/audio-hotplug-test.lua +++ b/TOOLS/lua/audio-hotplug-test.lua @@ -1,8 +1,6 @@ -local utils = require("mp.utils") - -mp.observe_property("audio-device-list", "native", function(name, val) +mp.observe_property("audio-device-list", "native", function(_, val) print("Audio device list changed:") - for index, e in ipairs(val) do + for _, e in ipairs(val) do print(" - '" .. e.name .. "' (" .. e.description .. ")") end end) diff --git a/TOOLS/lua/autocrop.lua b/TOOLS/lua/autocrop.lua index 3683b9f02a..ba38f487cc 100644 --- a/TOOLS/lua/autocrop.lua +++ b/TOOLS/lua/autocrop.lua @@ -1,105 +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. - -suppress_osd: bool - Whether the OSD shouldn't be used when filters - are applied and removed. ---]] +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, + -- 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 } -local command_prefix = options.suppress_osd and 'no-osd' or '' - -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") @@ -107,81 +73,82 @@ function is_enough_time(seconds) return playtime_remaining and time_needed < playtime_remaining end -function is_cropable() - for _, track in pairs(mp.get_property_native('track-list')) do - if track.type == 'video' and track.selected then - return not track.albumart - end +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 false + return true end -function remove_filter(label) - if is_filter_present(label) then - mp.command(string.format('%s vf remove @%s', command_prefix, 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( - '%s vf pre @%s:cropdetect=limit=%s:round=%d:reset=0', - command_prefix, 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 @@ -189,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 @@ -202,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 @@ -224,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("%s vf pre @%s:lavfi-crop=w=%s:h=%s:x=%s:y=%s", - command_prefix, 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() @@ -282,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 @@ -301,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 @@ -310,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 @@ -320,7 +286,7 @@ function on_toggle() return end - -- Neither => Do delectcrop. + -- Neither => Detect crop. detect_crop() end diff --git a/TOOLS/lua/autodeint.lua b/TOOLS/lua/autodeint.lua index b891c9a838..df938f8318 100644 --- a/TOOLS/lua/autodeint.lua +++ b/TOOLS/lua/autodeint.lua @@ -8,7 +8,7 @@ -- telecined and the interlacing field dominance. -- -- Based on this information, it may set mpv's ``deinterlace`` property (which --- usually inserts the yadif filter), or insert the ``pullup`` filter if the +-- usually inserts the bwdif filter), or insert the ``pullup`` filter if the -- content is telecined. It also sets field dominance with lavfi setfield. -- -- OPTIONS: @@ -26,19 +26,21 @@ require "mp.msg" -script_name = mp.get_script_name() -detect_label = string.format("%s-detect", script_name) -pullup_label = string.format("%s", script_name) -dominance_label = string.format("%s-dominance", script_name) -ivtc_detect_label = string.format("%s-ivtc-detect", script_name) +local script_name = mp.get_script_name() +local detect_label = string.format("%s-detect", script_name) +local pullup_label = string.format("%s", script_name) +local dominance_label = string.format("%s-dominance", script_name) +local ivtc_detect_label = string.format("%s-ivtc-detect", script_name) +local timer +local progressive, interlaced_tff, interlaced_bff, interlaced = 0, 1, 2, 3 -- number of seconds to gather cropdetect data -detect_seconds = tonumber(mp.get_opt(string.format("%s.detect_seconds", script_name))) +local detect_seconds = tonumber(mp.get_opt(string.format("%s.detect_seconds", script_name))) if not detect_seconds then detect_seconds = 4 end -function del_filter_if_present(label) +local 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") @@ -57,39 +59,13 @@ local function add_vf(label, filter) return mp.command(('vf add @%s:%s'):format(label, filter)) end -function start_detect() - -- exit if detection is already in progress - if timer then - mp.msg.warn("already detecting!") - return - end - - mp.set_property("deinterlace","no") - del_filter_if_present(pullup_label) - del_filter_if_present(dominance_label) - - -- insert the detection filters - if not (add_vf(detect_label, 'idet') and - add_vf(dominance_label, 'setfield=mode=auto') and - add_vf(pullup_label, 'lavfi-pullup') and - add_vf(ivtc_detect_label, 'idet')) then - mp.msg.error("failed to insert detection filters") - return - end - - -- wait to gather data - timer = mp.add_timeout(detect_seconds, select_filter) -end - -function stop_detect() +local function stop_detect() del_filter_if_present(detect_label) del_filter_if_present(ivtc_detect_label) timer = nil end -progressive, interlaced_tff, interlaced_bff, interlaced = 0, 1, 2, 3, 4 - -function judge(label) +local function judge(label) -- get the metadata local result = mp.get_property_native(string.format("vf-metadata/%s", label)) local num_tff = tonumber(result["lavfi.idet.multiple.tff"]) @@ -118,7 +94,7 @@ function judge(label) end end -function select_filter() +local function select_filter() -- handle the first detection filter results local verdict = judge(detect_label) local ivtc_verdict = judge(ivtc_detect_label) @@ -146,11 +122,36 @@ function select_filter() mp.msg.info(string.format("telecined with %s field dominance: using pullup", dominance)) stop_detect() else - mp.msg.info(string.format("interlaced with %s field dominance: setting deinterlace property", dominance)) + mp.msg.info("interlaced with " .. dominance .. + " field dominance: setting deinterlace property") del_filter_if_present(pullup_label) mp.set_property("deinterlace","yes") stop_detect() end end +local function start_detect() + -- exit if detection is already in progress + if timer then + mp.msg.warn("already detecting!") + return + end + + mp.set_property("deinterlace","no") + del_filter_if_present(pullup_label) + del_filter_if_present(dominance_label) + + -- insert the detection filters + if not (add_vf(detect_label, 'idet') and + add_vf(dominance_label, 'setfield=mode=auto') and + add_vf(pullup_label, 'lavfi-pullup') and + add_vf(ivtc_detect_label, 'idet')) then + mp.msg.error("failed to insert detection filters") + return + end + + -- wait to gather data + timer = mp.add_timeout(detect_seconds, select_filter) +end + mp.add_key_binding("ctrl+d", script_name, start_detect) diff --git a/TOOLS/lua/autoload.lua b/TOOLS/lua/autoload.lua index 048ecdedb0..dedfc64e85 100644 --- a/TOOLS/lua/autoload.lua +++ b/TOOLS/lua/autoload.lua @@ -1,5 +1,5 @@ -- This script automatically loads playlist entries before and after the --- the currently played file. It does so by scanning the directory a file is +-- currently played file. It does so by scanning the directory a file is -- located in when starting playback. It sorts the directory entries -- alphabetically, and adds entries before and after the current file to -- the internal playlist. (It stops if it would add an already existing @@ -10,153 +10,330 @@ To configure this script use file autoload.conf in directory script-opts (the "script-opts" directory must be in the mpv configuration directory, typically ~/.config/mpv/). +Option `ignore_patterns` is a comma-separated list of patterns (see lua.org/pil/20.2.html). +Additionally to the standard lua patterns, you can also escape commas with `%`, +for example, the option `bak%,x%,,another` will be resolved as patterns `bak,x,` and `another`. +But it does not mean you need to escape all lua patterns twice, +so the option `bak%%,%.mp4,` will be resolved as two patterns `bak%%` and `%.mp4`. + Example configuration would be: disabled=no images=no videos=yes audio=yes +additional_image_exts=list,of,ext +additional_video_exts=list,of,ext +additional_audio_exts=list,of,ext ignore_hidden=yes +same_type=yes +directory_mode=recursive +ignore_patterns=^~,^bak-,%.bak$ --]] -MAXENTRIES = 5000 +local MAX_ENTRIES = 5000 +local MAX_DIR_STACK = 20 local msg = require 'mp.msg' local options = require 'mp.options' local utils = require 'mp.utils' -o = { +local o = { disabled = false, images = true, videos = true, audio = true, - ignore_hidden = true + additional_image_exts = "", + additional_video_exts = "", + additional_audio_exts = "", + ignore_hidden = true, + same_type = false, + directory_mode = "auto", + ignore_patterns = "" } -options.read_options(o) -function Set (t) +local function Set(t) local set = {} for _, v in pairs(t) do set[v] = true end return set end -function SetUnion (a,b) - local res = {} - for k in pairs(a) do res[k] = true end - for k in pairs(b) do res[k] = true end - return res -end - -EXTENSIONS_VIDEO = Set { +local EXTENSIONS_VIDEO_DEFAULT = Set { '3g2', '3gp', 'avi', 'flv', 'm2ts', 'm4v', 'mj2', 'mkv', 'mov', 'mp4', 'mpeg', 'mpg', 'ogv', 'rmvb', 'webm', 'wmv', 'y4m' } -EXTENSIONS_AUDIO = Set { +local EXTENSIONS_AUDIO_DEFAULT = Set { 'aiff', 'ape', 'au', 'flac', 'm4a', 'mka', 'mp3', 'oga', 'ogg', 'ogm', 'opus', 'wav', 'wma' } -EXTENSIONS_IMAGES = Set { +local EXTENSIONS_IMAGES_DEFAULT = Set { 'avif', 'bmp', 'gif', 'j2k', 'jp2', 'jpeg', 'jpg', 'jxl', 'png', 'svg', 'tga', 'tif', 'tiff', 'webp' } -EXTENSIONS = Set {} -if o.videos then EXTENSIONS = SetUnion(EXTENSIONS, EXTENSIONS_VIDEO) end -if o.audio then EXTENSIONS = SetUnion(EXTENSIONS, EXTENSIONS_AUDIO) end -if o.images then EXTENSIONS = SetUnion(EXTENSIONS, EXTENSIONS_IMAGES) end +local EXTENSIONS, EXTENSIONS_VIDEO, EXTENSIONS_AUDIO, EXTENSIONS_IMAGES -function add_files_at(index, files) - index = index - 1 - local oldcount = mp.get_property_number("playlist-count", 1) - for i = 1, #files do - mp.commandv("loadfile", files[i], "append") - mp.commandv("playlist-move", oldcount + i - 1, index + i - 1) +local function SetUnion(a, b) + for k in pairs(b) do a[k] = true end + return a +end + +-- Returns first and last positions in string or past-to-end indices +local function FindOrPastTheEnd(string, pattern, start_at) + local pos1, pos2 = string:find(pattern, start_at) + return pos1 or #string + 1, + pos2 or #string + 1 +end + +local function Split(list) + local set = {} + + local item_pos = 1 + local item = "" + + while item_pos <= #list do + local pos1, pos2 = FindOrPastTheEnd(list, "%%*,", item_pos) + + local pattern_length = pos2 - pos1 + local is_comma_escaped = pattern_length % 2 + + local pos_before_escape = pos1 - 1 + local item_escape_count = pattern_length - is_comma_escaped + + item = item .. string.sub(list, item_pos, pos_before_escape + item_escape_count) + + if is_comma_escaped == 1 then + item = item .. "," + else + set[item] = true + item = "" + end + + item_pos = pos2 + 1 end + + set[item] = true + + -- exclude empty items + set[""] = nil + + return set end -function get_extension(path) - match = string.match(path, "%.([^%.]+)$" ) - if match == nil then - return "nomatch" - else - return match +local function split_option_exts(video, audio, image) + if video then o.additional_video_exts = Split(o.additional_video_exts) end + if audio then o.additional_audio_exts = Split(o.additional_audio_exts) end + if image then o.additional_image_exts = Split(o.additional_image_exts) end +end + +local function split_patterns() + o.ignore_patterns = Split(o.ignore_patterns) +end + +local function create_extensions() + EXTENSIONS = {} + EXTENSIONS_VIDEO = {} + EXTENSIONS_AUDIO = {} + EXTENSIONS_IMAGES = {} + if o.videos then + SetUnion(SetUnion(EXTENSIONS_VIDEO, EXTENSIONS_VIDEO_DEFAULT), o.additional_video_exts) + SetUnion(EXTENSIONS, EXTENSIONS_VIDEO) + end + if o.audio then + SetUnion(SetUnion(EXTENSIONS_AUDIO, EXTENSIONS_AUDIO_DEFAULT), o.additional_audio_exts) + SetUnion(EXTENSIONS, EXTENSIONS_AUDIO) + end + if o.images then + SetUnion(SetUnion(EXTENSIONS_IMAGES, EXTENSIONS_IMAGES_DEFAULT), o.additional_image_exts) + SetUnion(EXTENSIONS, EXTENSIONS_IMAGES) end end -table.filter = function(t, iter) - for i = #t, 1, -1 do - if not iter(t[i]) then - table.remove(t, i) - end +local function validate_directory_mode() + if o.directory_mode ~= "recursive" and o.directory_mode ~= "lazy" + and o.directory_mode ~= "ignore" then + o.directory_mode = nil end end --- splitbynum and alnumcomp from alphanum.lua (C) Andre Bogus --- Released under the MIT License --- http://www.davekoelle.com/files/alphanum.lua +options.read_options(o, nil, function(list) + split_option_exts(list.additional_video_exts, list.additional_audio_exts, + list.additional_image_exts) + if list.videos or list.additional_video_exts or + list.audio or list.additional_audio_exts or + list.images or list.additional_image_exts then + create_extensions() + end + if list.directory_mode then + validate_directory_mode() + end + if list.ignore_patterns then + split_patterns() + end +end) --- split a string into a table of number and string values -function splitbynum(s) - local result = {} - for x, y in (s or ""):gmatch("(%d*)(%D*)") do - if x ~= "" then table.insert(result, tonumber(x)) end - if y ~= "" then table.insert(result, y) end +split_option_exts(true, true, true) +split_patterns() +create_extensions() +validate_directory_mode() + +local function add_files(files) + local oldcount = mp.get_property_number("playlist-count", 1) + for i = 1, #files do + mp.commandv("loadfile", files[i][1], "append") + mp.commandv("playlist-move", oldcount + i - 1, files[i][2]) end - return result end -function clean_key(k) - k = (' '..k..' '):gsub("%s+", " "):sub(2, -2):lower() - return splitbynum(k) +local function get_extension(path) + return path:match("%.([^%.]+)$") or "nomatch" end --- compare two strings -function alnumcomp(x, y) - local xt, yt = clean_key(x), clean_key(y) - for i = 1, math.min(#xt, #yt) do - local xe, ye = xt[i], yt[i] - if type(xe) == "string" then ye = tostring(ye) - elseif type(ye) == "string" then xe = tostring(xe) end - if xe ~= ye then return xe < ye end +local function is_ignored(file) + for pattern in pairs(o.ignore_patterns) do + if file:match(pattern) then + return true + end end - return #xt < #yt + return false end -local autoloaded = nil +-- alphanum sorting for humans in Lua +-- http://notebook.kulchenko.com/algorithms/alphanumeric-natural-sorting-for-humans-in-lua + +local function alphanumsort(filenames) + local function padnum(n, d) + return #d > 0 and ("%03d%s%.12f"):format(#n, n, tonumber(d) / (10 ^ #d)) + or ("%03d%s"):format(#n, n) + end + + local tuples = {} + for i, f in ipairs(filenames) do + tuples[i] = {f:lower():gsub("0*(%d+)%.?(%d*)", padnum), f} + end + table.sort(tuples, function(a, b) + return a[1] == b[1] and #b[2] < #a[2] or a[1] < b[1] + end) + for i, tuple in ipairs(tuples) do filenames[i] = tuple[2] end + return filenames +end + +local autoloaded +local added_entries = {} +local autoloaded_dir + +local function scan_dir(path, current_file, dir_mode, separator, dir_depth, total_files, extensions) + if dir_depth == MAX_DIR_STACK then + return + end + msg.trace("scanning: " .. path) + local files = utils.readdir(path, "files") or {} + local dirs = dir_mode ~= "ignore" and utils.readdir(path, "dirs") or {} + local prefix = path == "." and "" or path + + local function filter(t, iter) + for i = #t, 1, -1 do + if not iter(t[i]) then + table.remove(t, i) + end + end + end + + filter(files, function(v) + -- Always accept current file + local current = prefix .. v == current_file + if current then + return true + end + if o.ignore_hidden and v:match("^%.") then + return false + end + if is_ignored(v) then + return false + end + |