diff options
Diffstat (limited to 'mac/.config/mpv/scripts/fuzzydir.lua')
| -rw-r--r-- | mac/.config/mpv/scripts/fuzzydir.lua | 278 |
1 files changed, 278 insertions, 0 deletions
diff --git a/mac/.config/mpv/scripts/fuzzydir.lua b/mac/.config/mpv/scripts/fuzzydir.lua new file mode 100644 index 0000000..22119da --- /dev/null +++ b/mac/.config/mpv/scripts/fuzzydir.lua @@ -0,0 +1,278 @@ +--[[ + fuzzydir / by sibwaf / https://github.com/sibwaf/mpv-scripts + + Allows using "**" wildcards in sub-file-paths and audio-file-paths + so you don't have to specify all the possible directory names. + + Basically, allows you to do this and never have the need to edit any paths ever again: + audio-file-paths = ** + sub-file-paths = ** + + MIT license - do whatever you want, but I'm not responsible for any possible problems. + Please keep the URL to the original repository. Thanks! +]] + +--[[ + Configuration: + + # max_search_depth + + Determines the max depth of recursive search, should be >= 1 + + Examples for "sub-file-paths = **": + "max_search_depth = 1" => mpv will be able to find [xyz.ass, subs/xyz.ass] + "max_search_depth = 2" => mpv will be able to find [xyz.ass, subs/xyz.ass, subs/moresubs/xyz.ass] + + Please be careful when setting this value too high as it can result in awful performance or even stack overflow + + + # discovery_threshold + + fuzzydir will skip paths which contain more than discovery_threshold directories in them + + This is done to keep at least some garbage from getting into *-file-paths properties in case of big collections: + - dir1 <- will be ignored on opening video.mp4 as it's probably unrelated to the file + - ... + - dir999 <- will be ignored + - video.mp4 + + Use 0 to disable this behavior completely + + + # use_powershell + + fuzzydir will use PowerShell to traverse directories when it's available + + Can be faster in some cases, but can also be significantly slower +]] + +local max_search_depth = 3 +local discovery_threshold = 10 +local use_powershell = false + +---------- + +local utils = require("mp.utils") +local msg = require("mp.msg") + +local default_audio_paths = mp.get_property_native("options/audio-file-paths") +local default_sub_paths = mp.get_property_native("options/sub-file-paths") + +function foreach(list, action) + for _, item in pairs(list) do + action(item) + end +end + +function starts_with(str, prefix) + return string.sub(str, 1, string.len(prefix)) == prefix +end + +function ends_with(str, suffix) + return suffix == "" or string.sub(str, -string.len(suffix)) == suffix +end + +function add_all(to, from) + for index, element in pairs(from) do + table.insert(to, element) + end +end + +function contains(t, e) + for index, element in pairs(t) do + if element == e then + return true + end + end + return false +end + +function normalize(path) + if path == "." then + return "" + end + + if starts_with(path, "./") or starts_with(path, ".\\") then + path = string.sub(path, 3, -1) + end + if ends_with(path, "/") or ends_with(path, "\\") then + path = string.sub(path, 1, -2) + end + + return path +end + +function call_command(command) + local command_string = "" + for _, part in pairs(command) do + command_string = command_string .. part .. " " + end + + msg.trace("Calling external command:", command_string) + + local process = mp.command_native({ + name = "subprocess", + playback_only = false, + capture_stdout = true, + capture_stderr = true, + args = command, + }) + + if process.status ~= 0 then + msg.verbose("External command failed with status " .. process.status .. ": " .. command_string) + if process.stderr ~= "" then + msg.debug(process.stderr) + end + + return nil + end + + local result = {} + for line in string.gmatch(process.stdout, "([^\r\n]+)") do + table.insert(result, line) + end + return result +end + +-- Platform-dependent optimization + +local powershell_version = nil +if use_powershell then + powershell_version = call_command({ + "powershell", + "-NoProfile", + "-Command", + "$Host.Version.Major", + }) +end +if powershell_version ~= nil then + powershell_version = tonumber(powershell_version[1]) +end +if powershell_version == nil then + powershell_version = -1 +end +msg.debug("PowerShell version", powershell_version) + +function fast_readdir(path) + if powershell_version >= 3 then + msg.trace("Scanning", path, "with PowerShell") + result = call_command({ + "powershell", + "-NoProfile", + "-Command", + [[ + $dirs = Get-ChildItem -LiteralPath ]] .. string.format("%q", path) .. [[ -Directory + foreach($dir in $dirs) { + $u8clip = [System.Text.Encoding]::UTF8.GetBytes($dir.Name) + [Console]::OpenStandardOutput().Write($u8clip, 0, $u8clip.Length) + Write-Host "" + } ]], + }) + msg.trace("Finished scanning", path, "with PowerShell") + return result + end + + msg.trace("Scanning", path, "with default readdir") + result = utils.readdir(path, "dirs") + msg.trace("Finished scanning", path, "with default readdir") + return result +end + +-- Platform-dependent optimization end + +function traverse(search_path, current_path, level, cache) + local full_path = utils.join_path(search_path, current_path) + + if level > max_search_depth then + msg.trace("Traversed too deep, skipping scan for", full_path) + return {} + end + + if cache[full_path] ~= nil then + msg.trace("Returning from cache for", full_path) + return cache[full_path] + end + + local result = {} + + local discovered_paths = fast_readdir(full_path) + if discovered_paths == nil then + -- noop + msg.debug("Unable to scan " .. full_path .. ", skipping") + elseif discovery_threshold > 0 and #discovered_paths > discovery_threshold then + -- noop + msg.debug("Too many directories in " .. full_path .. ", skipping") + else + for _, discovered_path in pairs(discovered_paths) do + local new_path = utils.join_path(current_path, discovered_path) + + table.insert(result, new_path) + add_all(result, traverse(search_path, new_path, level + 1, cache)) + end + end + + cache[full_path] = result + + return result +end + +function explode(raw_paths, search_path, cache) + local result = {} + for _, raw_path in pairs(raw_paths) do + local parent, leftover = utils.split_path(raw_path) + if leftover == "**" then + msg.trace("Expanding wildcard for", raw_path) + table.insert(result, parent) + add_all(result, traverse(search_path, parent, 1, cache)) + else + msg.trace("Path", raw_path, "doesn't have a wildcard, keeping as-is") + table.insert(result, raw_path) + end + end + + local normalized = {} + for index, path in pairs(result) do + local normalized_path = normalize(path) + if not contains(normalized, normalized_path) and normalized_path ~= "" then + table.insert(normalized, normalized_path) + end + end + + return normalized +end + +function explode_all() + msg.debug("max_search_depth = " .. max_search_depth .. ", discovery_threshold = " .. discovery_threshold) + + local video_path = mp.get_property("path") + local search_path, _ = utils.split_path(video_path) + msg.debug("search_path = " .. search_path) + + local cache = {} + + foreach(default_audio_paths, function(it) + msg.debug("audio-file-paths:", it) + end) + local audio_paths = explode(default_audio_paths, search_path, cache) + foreach(audio_paths, function(it) + msg.debug("Adding to audio-file-paths:", it) + end) + mp.set_property_native("options/audio-file-paths", audio_paths) + + msg.verbose("Done expanding audio-file-paths") + + foreach(default_sub_paths, function(it) + msg.debug("sub-file-paths:", it) + end) + local sub_paths = explode(default_sub_paths, search_path, cache) + foreach(sub_paths, function(it) + msg.debug("Adding to sub-file-paths:", it) + end) + mp.set_property_native("options/sub-file-paths", sub_paths) + + msg.verbose("Done expanding sub-file-paths") + + msg.debug("Done expanding paths") +end + +mp.add_hook("on_load", 50, explode_all) |
