-
Notifications
You must be signed in to change notification settings - Fork 6
/
fuzzydir.lua
280 lines (218 loc) · 8.03 KB
/
fuzzydir.lua
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
--[[
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)