summaryrefslogtreecommitdiffstats
path: root/TOOLS/lua/autoload.lua
diff options
context:
space:
mode:
Diffstat (limited to 'TOOLS/lua/autoload.lua')
-rw-r--r--TOOLS/lua/autoload.lua362
1 files changed, 276 insertions, 86 deletions
diff --git a/TOOLS/lua/autoload.lua b/TOOLS/lua/autoload.lua
index 7150abb95e..593d375431 100644
--- a/TOOLS/lua/autoload.lua
+++ b/TOOLS/lua/autoload.lua
@@ -10,16 +10,30 @@
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
+MAXDIRSTACK = 20
local msg = require 'mp.msg'
local options = require 'mp.options'
@@ -29,9 +43,26 @@ o = {
disabled = false,
images = true,
videos = true,
- audio = true
+ audio = 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)
+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
+end)
function Set (t)
local set = {}
@@ -40,35 +71,111 @@ function Set (t)
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
+ 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
+function FindOrPastTheEnd (string, pattern, start_at)
+ local pos1, pos2 = string.find(string, pattern, start_at)
+ return pos1 or #string + 1,
+ pos2 or #string + 1
+end
+
+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
-EXTENSIONS_VIDEO = Set {
- 'mkv', 'avi', 'mp4', 'ogv', 'webm', 'rmvb', 'flv', 'wmv', 'mpeg', 'mpg', 'm4v', '3gp'
+EXTENSIONS_VIDEO_DEFAULT = Set {
+ '3g2', '3gp', 'avi', 'flv', 'm2ts', 'm4v', 'mj2', 'mkv', 'mov',
+ 'mp4', 'mpeg', 'mpg', 'ogv', 'rmvb', 'webm', 'wmv', 'y4m'
}
-EXTENSIONS_AUDIO = Set {
- 'mp3', 'wav', 'ogm', 'flac', 'm4a', 'wma', 'ogg', 'opus'
+EXTENSIONS_AUDIO_DEFAULT = Set {
+ 'aiff', 'ape', 'au', 'flac', 'm4a', 'mka', 'mp3', 'oga', 'ogg',
+ 'ogm', 'opus', 'wav', 'wma'
}
-EXTENSIONS_IMAGES = Set {
- 'jpg', 'jpeg', 'png', 'tif', 'tiff', 'gif', 'webp', 'svg', 'bmp'
+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
+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
+split_option_exts(true, true, true)
-function add_files_at(index, files)
- index = index - 1
+function split_patterns()
+ o.ignore_patterns = Split(o.ignore_patterns)
+end
+split_patterns()
+
+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
+create_extensions()
+
+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
+validate_directory_mode()
+
+function add_files(files)
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)
+ mp.commandv("loadfile", files[i][1], "append")
+ mp.commandv("playlist-move", oldcount + i - 1, files[i][2])
end
end
@@ -81,6 +188,16 @@ function get_extension(path)
end
end
+function is_ignored(file)
+ for pattern, _ in pairs(o.ignore_patterns) do
+ if string.match(file, pattern) then
+ return true
+ end
+ end
+
+ return false
+end
+
table.filter = function(t, iter)
for i = #t, 1, -1 do
if not iter(t[i]) then
@@ -89,59 +206,130 @@ table.filter = function(t, iter)
end
end
--- splitbynum and alnumcomp from alphanum.lua (C) Andre Bogus
--- Released under the MIT License
--- http://www.davekoelle.com/files/alphanum.lua
-
--- 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
+table.append = function(t1, t2)
+ local t1_size = #t1
+ for i = 1, #t2 do
+ t1[t1_size + i] = t2[i]
end
- return result
end
-function clean_key(k)
- k = (' '..k..' '):gsub("%s+", " "):sub(2, -2):lower()
- return splitbynum(k)
-end
+-- alphanum sorting for humans in Lua
+-- http://notebook.kulchenko.com/algorithms/alphanumeric-natural-sorting-for-humans-in-lua
--- 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
+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
- return #xt < #yt
+
+ 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 = nil
+local added_entries = {}
+local autoloaded_dir = nil
+
+function scan_dir(path, current_file, dir_mode, separator, dir_depth, total_files, extensions)
+ if dir_depth == MAXDIRSTACK 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
+ table.filter(files, function (v)
+ -- The current file could be a hidden file, ignoring it doesn't load other
+ -- files from the current directory.
+ local current = prefix .. v == current_file
+ if o.ignore_hidden and not current and string.match(v, "^%.") then
+ return false
+ end
+ if not current and is_ignored(v) then
+ return false
+ end
+
+ local ext = get_extension(v)
+ if ext == nil then
+ return false
+ end
+ return extensions[string.lower(ext)]
+ end)
+ table.filter(dirs, function(d)
+ return not ((o.ignore_hidden and string.match(d, "^%.")))
+ end)
+ alphanumsort(files)
+ alphanumsort(dirs)
+
+ for i, file in ipairs(files) do
+ files[i] = prefix .. file
+ end
+
+ table.append(total_files, files)
+ if dir_mode == "recursive" then
+ for _, dir in ipairs(dirs) do
+ scan_dir(prefix .. dir .. separator, current_file, dir_mode,
+ separator, dir_depth + 1, total_files, extensions)
+ end
+ else
+ for i, dir in ipairs(dirs) do
+ dirs[i] = prefix .. dir
+ end
+ table.append(total_files, dirs)
+ end
+end
function find_and_add_entries()
+ local aborted = mp.get_property_native("playback-abort")
+ if aborted then
+ msg.debug("stopping: playback aborted")
+ return
+ end
+
local path = mp.get_property("path", "")
local dir, filename = utils.split_path(path)
msg.trace(("dir: %s, filename: %s"):format(dir, filename))
if o.disabled then
- msg.verbose("stopping: autoload disabled")
+ msg.debug("stopping: autoload disabled")
return
elseif #dir == 0 then
- msg.verbose("stopping: not a local path")
+ msg.debug("stopping: not a local path")
return
end
local pl_count = mp.get_property_number("playlist-count", 1)
+ this_ext = get_extension(filename)
-- check if this is a manually made playlist
if (pl_count > 1 and autoloaded == nil) or
- (pl_count == 1 and EXTENSIONS[string.lower(get_extension(filename))] == nil) then
- msg.verbose("stopping: manually made playlist")
+ (pl_count == 1 and EXTENSIONS[string.lower(this_ext)] == nil) then
+ msg.debug("stopping: manually made playlist")
return
else
- autoloaded = true
+ if pl_count == 1 then
+ autoloaded = true
+ autoloaded_dir = dir
+ added_entries = {}
+ end
+ end
+
+ local extensions = {}
+ if o.same_type then
+ if EXTENSIONS_VIDEO[string.lower(this_ext)] ~= nil then
+ extensions = EXTENSIONS_VIDEO
+ elseif EXTENSIONS_AUDIO[string.lower(this_ext)] ~= nil then
+ extensions = EXTENSIONS_AUDIO
+ else
+ extensions = EXTENSIONS_IMAGES
+ end
+ else
+ extensions = EXTENSIONS
end
local pl = mp.get_property_native("playlist", {})
@@ -149,31 +337,22 @@ function find_and_add_entries()
msg.trace(("playlist-pos-1: %s, playlist: %s"):format(pl_current,
utils.to_string(pl)))
- local files = utils.readdir(dir, "files")
- if files == nil then
- msg.verbose("no other files in directory")
- return
+ local files = {}
+ do
+ local dir_mode = o.directory_mode or mp.get_property("directory-mode", "lazy")
+ local separator = mp.get_property_native("platform") == "windows" and "\\" or "/"
+ scan_dir(autoloaded_dir, path, dir_mode, separator, 0, files, extensions)
end
- table.filter(files, function (v, k)
- if string.match(v, "^%.") then
- return false
- end
- local ext = get_extension(v)
- if ext == nil then
- return false
- end
- return EXTENSIONS[string.lower(ext)]
- end)
- table.sort(files, alnumcomp)
- if dir == "." then
- dir = ""
+ if next(files) == nil then
+ msg.debug("no other files or directories in directory")
+ return
end
-- Find the current pl entry (dir+"/"+filename) in the sorted dir list
local current
for i = 1, #files do
- if files[i] == filename then
+ if files[i] == path then
current = i
break
end
@@ -183,38 +362,49 @@ function find_and_add_entries()
end
msg.trace("current file position in files: "..current)
+ -- treat already existing playlist entries, independent of how they got added
+ -- as if they got added by autoload
+ for _, entry in ipairs(pl) do
+ added_entries[entry.filename] = true
+ end
+
local append = {[-1] = {}, [1] = {}}
for direction = -1, 1, 2 do -- 2 iterations, with direction = -1 and +1
for i = 1, MAXENTRIES do
- local file = files[current + i * direction]
- local pl_e = pl[pl_current + i * direction]
+ local pos = current + i * direction
+ local file = files[pos]
if file == nil or file[1] == "." then
break
end
- local filepath = dir .. file
- if pl_e then
- -- If there's a playlist entry, and it's the same file, stop.
- msg.trace(pl_e.filename.." == "..filepath.." ?")
- if pl_e.filename == filepath then
- break
+ -- skip files that are/were already in the playlist
+ if not added_entries[file] then
+ if direction == -1 then
+ msg.verbose("Prepending " .. file)
+ table.insert(append[-1], 1, {file, pl_current + i * direction + 1})
+ else
+ msg.verbose("Adding " .. file)
+ if pl_count > 1 then
+ table.insert(append[1], {file, pl_current + i * direction - 1})
+ else
+ mp.commandv("loadfile", file, "append")
+ end
end
end
-
- if direction == -1 then
- if pl_current == 1 then -- never add additional entries in the middle
- msg.info("Prepending " .. file)
- table.insert(append[-1], 1, filepath)
- end
- else
- msg.info("Adding " .. file)
- table.insert(append[1], filepath)
+ added_entries[file] = true
+ end
+ if pl_count == 1 and direction == -1 and #append[-1] > 0 then
+ for i = 1, #append[-1] do
+ mp.commandv("loadfile", append[-1][i][1], "append")
end
+ mp.commandv("playlist-move", 0, current)
end
end
- add_files_at(pl_current + 1, append[1])
- add_files_at(pl_current, append[-1])
+ if pl_count > 1 then
+ add_files(append[1])
+ add_files(append[-1])
+ end
end
mp.register_event("start-file", find_and_add_entries)