281 lines
8 KiB
Lua
281 lines
8 KiB
Lua
--[[
|
|
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:
|
|
|
|
# enabled
|
|
|
|
Determines whether the script is enabled or not
|
|
|
|
# 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 msg = require 'mp.msg'
|
|
local utils = require 'mp.utils'
|
|
local options = require 'mp.options'
|
|
|
|
o = {
|
|
enabled = true,
|
|
max_search_depth = 3,
|
|
discovery_threshold = 10,
|
|
use_powershell = false,
|
|
}
|
|
options.read_options(o, _, function() end)
|
|
|
|
----------
|
|
|
|
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 o.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 > o.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 o.discovery_threshold > 0 and #discovered_paths > o.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()
|
|
if not o.enabled then return end
|
|
msg.debug("max_search_depth = ".. o.max_search_depth .. ", discovery_threshold = " .. o.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)
|