first commit
This commit is contained in:
12
mpv/scripts/file-browser/addons/README.md
Normal file
12
mpv/scripts/file-browser/addons/README.md
Normal file
@@ -0,0 +1,12 @@
|
||||
# addons
|
||||
|
||||
Add-ons are ways to add extra features to file-browser, for example adding support for network file servers like ftp, or implementing virtual directories in the root like recently opened files.
|
||||
They can be enabled by setting `addon` script-opt to yes, and placing the addon file into the `~~/script-modules/file-browser-addons/` directory.
|
||||
|
||||
Browsing filesystems provided by add-ons should feel identical to the normal handling of the script,
|
||||
but they may require extra commandline tools be installed.
|
||||
|
||||
Since addons are loaded programatically from the addon directory it is possible for anyone to write their own addon.
|
||||
Instructions on how to do this are available [here](../docs/addons.md).
|
||||
|
||||
For a list of available addons see the [wiki](https://github.com/CogentRedTester/mpv-file-browser/wiki/Addon-List).
|
||||
89
mpv/scripts/file-browser/addons/apache-browser.lua
Normal file
89
mpv/scripts/file-browser/addons/apache-browser.lua
Normal file
@@ -0,0 +1,89 @@
|
||||
--[[
|
||||
An addon for mpv-file-browser which adds support for apache http directory indexes
|
||||
]]--
|
||||
|
||||
local mp = require 'mp'
|
||||
local msg = require 'mp.msg'
|
||||
local utils = require 'mp.utils'
|
||||
local fb = require "file-browser"
|
||||
|
||||
--decodes a URL address
|
||||
--this piece of code was taken from: https://stackoverflow.com/questions/20405985/lua-decodeuri-luvit/20406960#20406960
|
||||
local decodeURI
|
||||
do
|
||||
local char, gsub, tonumber = string.char, string.gsub, tonumber
|
||||
local function _(hex) return char(tonumber(hex, 16)) end
|
||||
|
||||
function decodeURI(s)
|
||||
s = gsub(s, '%%(%x%x)', _)
|
||||
return s
|
||||
end
|
||||
end
|
||||
|
||||
local apache = {
|
||||
priority = 80,
|
||||
api_version = "1.1.0"
|
||||
}
|
||||
|
||||
function apache:can_parse(name)
|
||||
return name:find("^https?://")
|
||||
end
|
||||
|
||||
--send curl errors through the browser empty_text
|
||||
function apache:send_error(str)
|
||||
return {}, {empty_text = "curl error: "..str}
|
||||
end
|
||||
|
||||
local function execute(args)
|
||||
msg.trace(utils.to_string(args))
|
||||
local _, cmd = fb.get_parse_state():yield(
|
||||
mp.command_native_async({
|
||||
name = "subprocess",
|
||||
playback_only = false,
|
||||
capture_stdout = true,
|
||||
capture_stderr = true,
|
||||
args = args
|
||||
}, fb.coroutine.callback())
|
||||
)
|
||||
return cmd
|
||||
end
|
||||
|
||||
function apache:parse(directory)
|
||||
msg.verbose(directory)
|
||||
|
||||
local test = execute({"curl", "-k", "-l", "-I", directory})
|
||||
local response = test.stdout:match("(%d%d%d [^\n\r]+)")
|
||||
if test.stdout:match("Content%-Type: ([^\r\n/]+)") ~= "text" then return nil end
|
||||
if response ~= "200 OK" then return self:send_error(response) end
|
||||
|
||||
local html = execute({"curl", "-k", "-l", directory})
|
||||
if html.status ~= 0 then return self:send_error(tostring(html.status))
|
||||
elseif not html.stdout:find("%[PARENTDIR%]") then return nil end
|
||||
|
||||
html = html.stdout
|
||||
local list = {}
|
||||
for str in string.gmatch(html, "[^\r\n]+") do
|
||||
local valid = true
|
||||
if str:sub(1,4) ~= "<tr>" then valid = false end
|
||||
|
||||
local link = str:match('href="(.-)"')
|
||||
local alt = str:match('alt="%[(.-)%]"')
|
||||
|
||||
if valid and not alt or not link then valid = false end
|
||||
if valid and alt == "PARENTDIR" or alt == "ICO" then valid = false end
|
||||
if valid and link:find("[:?<>|]") then valid = false end
|
||||
|
||||
local is_dir = (alt == "DIR")
|
||||
if valid and is_dir and not self.valid_dir(link) then valid = false end
|
||||
if valid and not is_dir and not self.valid_file(link) then valid = false end
|
||||
|
||||
if valid then
|
||||
msg.trace(alt..": "..link)
|
||||
table.insert(list, { name = link, type = (is_dir and "dir" or "file"), label = decodeURI(link) })
|
||||
end
|
||||
end
|
||||
|
||||
return list, {filtered = true, directory_label = decodeURI(directory)}
|
||||
end
|
||||
|
||||
return apache
|
||||
191
mpv/scripts/file-browser/addons/favourites.lua
Normal file
191
mpv/scripts/file-browser/addons/favourites.lua
Normal file
@@ -0,0 +1,191 @@
|
||||
--[[
|
||||
An addon for mpv-file-browser which adds a Favourites path that can be loaded from the ROOT
|
||||
]]--
|
||||
|
||||
local mp = require "mp"
|
||||
local msg = require "mp.msg"
|
||||
local utils = require "mp.utils"
|
||||
local save_path = mp.command_native({"expand-path", "~~/script-opts/file_browser_favourites.txt"})
|
||||
do
|
||||
local file = io.open(save_path, "a+")
|
||||
if not file then
|
||||
msg.error("cannot access file", ("%q"):format(save_path), "make sure that the directory exists")
|
||||
return {}
|
||||
end
|
||||
file:close()
|
||||
end
|
||||
|
||||
local favourites = nil
|
||||
local favs = {
|
||||
api_version = "1.4.0",
|
||||
priority = 30,
|
||||
cursor = 1
|
||||
}
|
||||
|
||||
local use_virtual_directory = true
|
||||
local full_paths = {}
|
||||
|
||||
local function create_favourite_object(str)
|
||||
local item = {
|
||||
type = str:sub(-1) == "/" and "dir" or "file",
|
||||
path = str,
|
||||
redirect = not use_virtual_directory,
|
||||
name = str:match("([^/]+/?)$")
|
||||
}
|
||||
full_paths[str:match("([^/]+)/?$")] = str
|
||||
return item
|
||||
end
|
||||
|
||||
function favs:setup()
|
||||
self:register_root_item('Favourites/')
|
||||
end
|
||||
|
||||
local function update_favourites()
|
||||
favourites = {}
|
||||
|
||||
local file = io.open(save_path, "r")
|
||||
if not file then return end
|
||||
|
||||
for str in file:lines() do
|
||||
table.insert(favourites, create_favourite_object(str))
|
||||
end
|
||||
file:close()
|
||||
end
|
||||
|
||||
function favs:can_parse(directory)
|
||||
return directory:find("Favourites/") == 1
|
||||
end
|
||||
|
||||
function favs:parse(directory)
|
||||
if not favourites then update_favourites() end
|
||||
if directory == "Favourites/" then
|
||||
local opts = {
|
||||
filtered = true,
|
||||
sorted = true
|
||||
}
|
||||
if self.cursor ~= 1 then opts.selected_index = self.cursor ; self.cursor = 1 end
|
||||
return favourites, opts
|
||||
end
|
||||
|
||||
if use_virtual_directory then
|
||||
-- converts the relative favourite path into a full path
|
||||
local name = directory:match("Favourites/([^/]+)/?")
|
||||
|
||||
local _, finish = directory:find("Favourites/([^/]+/?)")
|
||||
local full_path = (full_paths[name] or "")..directory:sub(finish+1)
|
||||
local list, opts = self:defer(full_path or "")
|
||||
|
||||
if not list then return nil end
|
||||
opts.id = self:get_id()
|
||||
if opts.directory_label then
|
||||
opts.directory_label = opts.directory_label:gsub(full_paths[name], "Favourites/"..name..'/')
|
||||
if opts.directory_label:find("Favourites/") ~= 1 then opts.directory_label = nil end
|
||||
end
|
||||
|
||||
for _, item in ipairs(list) do
|
||||
if not item.path then item.redirect = false end
|
||||
item.path = item.path or full_path..item.name
|
||||
end
|
||||
|
||||
return list, opts
|
||||
end
|
||||
|
||||
local path = full_paths[ directory:match("([^/]+/?)$") or "" ]
|
||||
|
||||
local list, opts = self:defer(path)
|
||||
if not list then return nil end
|
||||
opts.directory = opts.directory or path
|
||||
return list, opts
|
||||
end
|
||||
|
||||
local function get_favourite(path)
|
||||
for index, value in ipairs(favourites) do
|
||||
if value.path == path then return index, value end
|
||||
end
|
||||
end
|
||||
|
||||
--update the browser with new contents of the file
|
||||
local function update_browser()
|
||||
if favs.get_directory():find("[fF]avourites/") then
|
||||
if favs.get_directory():find("[fF]avourites/$") then
|
||||
local cursor = favs.get_selected_index()
|
||||
favs.rescan()
|
||||
favs.set_selected_index(cursor)
|
||||
favs.redraw()
|
||||
else
|
||||
favs.clear_cache()
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
--write the contents of favourites to the file
|
||||
local function write_to_file()
|
||||
local file = io.open(save_path, "w+")
|
||||
if not file then return msg.error(file, "could not open favourites file") end
|
||||
for _, item in ipairs(favourites) do
|
||||
file:write(string.format("%s\n", item.path))
|
||||
end
|
||||
file:close()
|
||||
end
|
||||
|
||||
local function add_favourite(path)
|
||||
if get_favourite(path) then return end
|
||||
update_favourites()
|
||||
table.insert(favourites, create_favourite_object(path))
|
||||
write_to_file()
|
||||
end
|
||||
|
||||
local function remove_favourite(path)
|
||||
update_favourites()
|
||||
local index = get_favourite(path)
|
||||
if not index then return end
|
||||
table.remove(favourites, index)
|
||||
write_to_file()
|
||||
end
|
||||
|
||||
local function move_favourite(path, direction)
|
||||
update_favourites()
|
||||
local index, item = get_favourite(path)
|
||||
if not index or not favourites[index + direction] then return end
|
||||
|
||||
favourites[index] = favourites[index + direction]
|
||||
favourites[index + direction] = item
|
||||
write_to_file()
|
||||
end
|
||||
|
||||
local function toggle_favourite(cmd, state, co)
|
||||
local path = favs.get_full_path(state.list[state.selected], state.directory)
|
||||
|
||||
if state.directory:find("[fF]avourites/$") then remove_favourite(path)
|
||||
else add_favourite(path) end
|
||||
update_browser()
|
||||
end
|
||||
|
||||
local function move_key(cmd, state, co)
|
||||
if not state.directory:find("[fF]avourites/") then return false end
|
||||
local path = favs.get_full_path(state.list[state.selected], state.directory)
|
||||
|
||||
local cursor = favs.get_selected_index()
|
||||
if cmd.name == favs:get_id().."/move_up" then
|
||||
move_favourite(path, -1)
|
||||
favs.set_selected_index(cursor-1)
|
||||
else
|
||||
move_favourite(path, 1)
|
||||
favs.set_selected_index(cursor+1)
|
||||
end
|
||||
update_browser()
|
||||
end
|
||||
|
||||
update_favourites()
|
||||
mp.register_script_message("favourites/add_favourite", add_favourite)
|
||||
mp.register_script_message("favourites/remove_favourite", remove_favourite)
|
||||
mp.register_script_message("favourites/move_up", function(path) move_favourite(path, -1) end)
|
||||
mp.register_script_message("favourites/move_down", function(path) move_favourite(path, 1) end)
|
||||
|
||||
favs.keybinds = {
|
||||
{ "F", "toggle_favourite", toggle_favourite, {}, },
|
||||
{ "Ctrl+UP", "move_up", move_key, {repeatable = true} },
|
||||
{ "Ctrl+DOWN", "move_down", move_key, {repeatable = true} },
|
||||
}
|
||||
|
||||
return favs
|
||||
104
mpv/scripts/file-browser/addons/find.lua
Normal file
104
mpv/scripts/file-browser/addons/find.lua
Normal file
@@ -0,0 +1,104 @@
|
||||
--[[
|
||||
An addon for mpv-file-browser for searching the current directory
|
||||
Available at: https://github.com/CogentRedTester/mpv-file-browser/tree/master/addons
|
||||
|
||||
Requires mpv-user-input: https://github.com/CogentRedTester/mpv-user-input
|
||||
|
||||
Keybinds:
|
||||
Ctrl+f open search box
|
||||
Ctrl+F open advanced search box (supports Lua patterns)
|
||||
n cycle to next valid item
|
||||
]]--
|
||||
|
||||
local msg = require "mp.msg"
|
||||
local fb = require "file-browser"
|
||||
local input_loaded, input = pcall(require, "mp.input")
|
||||
local user_input_loaded, user_input = pcall(require, "user-input-module")
|
||||
|
||||
local find = {
|
||||
api_version = "1.3.0"
|
||||
}
|
||||
local latest_coroutine = nil
|
||||
local global_fb_state = getmetatable(fb.get_state()).__original
|
||||
|
||||
local function compare(name, query)
|
||||
if name:find(query) then return true end
|
||||
if name:lower():find(query) then return true end
|
||||
if name:upper():find(query) then return true end
|
||||
|
||||
return false
|
||||
end
|
||||
|
||||
local function main(key, state, co)
|
||||
if not state.list then return false end
|
||||
|
||||
local text
|
||||
if key.name == "find/find" then text = "Find: enter search string"
|
||||
else text = "Find: enter advanced search string" end
|
||||
|
||||
if input_loaded then
|
||||
input.get({
|
||||
prompt = text .. "\n>",
|
||||
id = "file-browser/find",
|
||||
submit = fb.coroutine.callback(),
|
||||
})
|
||||
elseif user_input_loaded then
|
||||
user_input.get_user_input( fb.coroutine.callback(), { text = text, id = "find", replace = true } )
|
||||
end
|
||||
|
||||
local query, error = coroutine.yield()
|
||||
if input_loaded then input.terminate() end
|
||||
if not query then return msg.debug(error) end
|
||||
|
||||
-- allow the directory to be changed before this point
|
||||
local list = fb.get_list()
|
||||
local parse_id = global_fb_state.co
|
||||
|
||||
if key.name == "find/find" then
|
||||
query = fb.pattern_escape(query)
|
||||
end
|
||||
|
||||
local results = {}
|
||||
|
||||
for index, item in ipairs(list) do
|
||||
if compare(item.label or item.name, query) then
|
||||
table.insert(results, index)
|
||||
end
|
||||
end
|
||||
|
||||
if (#results < 1) then
|
||||
msg.warn("No matching items for '"..query.."'")
|
||||
return
|
||||
end
|
||||
|
||||
--keep cycling through the search results if any are found
|
||||
--putting this into a separate coroutine removes any passthrough ambiguity
|
||||
--the final return statement should return to `step_find` not any other function
|
||||
fb.coroutine.run(function()
|
||||
latest_coroutine = coroutine.running()
|
||||
while (true) do
|
||||
for _, index in ipairs(results) do
|
||||
fb.set_selected_index(index)
|
||||
coroutine.yield(true)
|
||||
|
||||
if parse_id ~= global_fb_state.co then
|
||||
latest_coroutine = nil
|
||||
return false
|
||||
end
|
||||
end
|
||||
end
|
||||
end)
|
||||
end
|
||||
|
||||
local function step_find()
|
||||
if not latest_coroutine then return false end
|
||||
return fb.coroutine.resume_err(latest_coroutine)
|
||||
end
|
||||
|
||||
find.keybinds = {
|
||||
{"Ctrl+f", "find", main, {}},
|
||||
{"Ctrl+F", "find_advanced", main, {}},
|
||||
{"n", "next", step_find, {}},
|
||||
}
|
||||
|
||||
return find
|
||||
86
mpv/scripts/file-browser/addons/ftp-browser.lua
Normal file
86
mpv/scripts/file-browser/addons/ftp-browser.lua
Normal file
@@ -0,0 +1,86 @@
|
||||
--[[
|
||||
An addon for mpv-file-browser which adds support for ftp servers
|
||||
]]--
|
||||
|
||||
local mp = require 'mp'
|
||||
local msg = require 'mp.msg'
|
||||
local utils = require 'mp.utils'
|
||||
local fb = require 'file-browser'
|
||||
|
||||
local ftp = {
|
||||
priority = 100,
|
||||
api_version = "1.1.0"
|
||||
}
|
||||
|
||||
function ftp:can_parse(directory)
|
||||
return directory:sub(1, 6) == "ftp://"
|
||||
end
|
||||
|
||||
--in my experience curl has been somewhat unreliable when it comes to ftp requests
|
||||
--this fuction retries the request a few times just in case
|
||||
local function execute(args)
|
||||
msg.debug(utils.to_string(args))
|
||||
local _, cmd = fb.get_parse_state():yield(
|
||||
mp.command_native_async({
|
||||
name = "subprocess",
|
||||
playback_only = false,
|
||||
capture_stdout = true,
|
||||
capture_stderr = true,
|
||||
args = args
|
||||
}, fb.coroutine.callback())
|
||||
)
|
||||
return cmd
|
||||
end
|
||||
|
||||
-- encodes special characters using the URL percent encoding format
|
||||
function urlEncode(url)
|
||||
local domain, path = string.match(url, '(ftp://[^/]-/)(.*)')
|
||||
if not path then return url end
|
||||
|
||||
-- these are the unreserved URI characters according to RFC 3986
|
||||
-- https://www.rfc-editor.org/rfc/rfc3986#section-2.3
|
||||
path = string.gsub(path, '[^%w.~_%-]', function(c)
|
||||
return ('%%%x'):format(string.byte(c))
|
||||
end)
|
||||
return domain..path
|
||||
end
|
||||
|
||||
function ftp:parse(directory)
|
||||
msg.verbose(directory)
|
||||
|
||||
local ftp = execute({"curl", "-k", "-g", "--retry", "4", urlEncode(directory)})
|
||||
|
||||
local entries = execute({"curl", "-k", "-g", "-l", "--retry", "4", urlEncode(directory)})
|
||||
|
||||
if entries.status == 28 then
|
||||
msg.error(entries.stderr)
|
||||
elseif entries.status ~= 0 or ftp.status ~= 0 then
|
||||
msg.error(entries.stderr)
|
||||
return
|
||||
end
|
||||
|
||||
local response = {}
|
||||
for str in string.gmatch(ftp.stdout, "[^\r\n]+") do
|
||||
table.insert(response, str)
|
||||
end
|
||||
|
||||
local list = {}
|
||||
local i = 1
|
||||
for str in string.gmatch(entries.stdout, "[^\r\n]+") do
|
||||
if str and response[i] then
|
||||
msg.trace(str .. ' | ' .. response[i])
|
||||
|
||||
if response[i]:sub(1,1) == "d" then
|
||||
table.insert(list, { name = str..'/', type = "dir" })
|
||||
else
|
||||
table.insert(list, { name = str, type = "file" })
|
||||
end
|
||||
|
||||
i = i+1
|
||||
end
|
||||
end
|
||||
|
||||
return list
|
||||
end
|
||||
|
||||
return ftp
|
||||
27
mpv/scripts/file-browser/addons/home-label.lua
Normal file
27
mpv/scripts/file-browser/addons/home-label.lua
Normal file
@@ -0,0 +1,27 @@
|
||||
--[[
|
||||
An addon for mpv-file-browser which displays ~/ for the home directory instead of the full path
|
||||
]]--
|
||||
|
||||
local mp = require "mp"
|
||||
local fb = require "file-browser"
|
||||
|
||||
local home = fb.fix_path(mp.command_native({"expand-path", "~/"}), true)
|
||||
|
||||
local home_label = {
|
||||
priority = 100,
|
||||
api_version = "1.0.0"
|
||||
}
|
||||
|
||||
function home_label:can_parse(directory)
|
||||
return directory:sub(1, home:len()) == home
|
||||
end
|
||||
|
||||
function home_label:parse(directory)
|
||||
local list, opts = self:defer(directory)
|
||||
if (not opts.directory or opts.directory == directory) and not opts.directory_label then
|
||||
opts.directory_label = "~/"..(directory:sub(home:len()+1) or "")
|
||||
end
|
||||
return list, opts
|
||||
end
|
||||
|
||||
return home_label
|
||||
56
mpv/scripts/file-browser/addons/last-open-directory.lua
Normal file
56
mpv/scripts/file-browser/addons/last-open-directory.lua
Normal file
@@ -0,0 +1,56 @@
|
||||
--[[
|
||||
An addon for mpv-file-browser which stores the last opened directory and
|
||||
sets it as the opened directory the next time mpv is opened.
|
||||
|
||||
Available at: https://github.com/CogentRedTester/mpv-file-browser/tree/master/addons
|
||||
]]--
|
||||
|
||||
local mp = require 'mp'
|
||||
local msg = require 'mp.msg'
|
||||
|
||||
local fb = require 'file-browser'
|
||||
|
||||
local state_file = mp.command_native({'expand-path', '~~state/last_opened_directory'})
|
||||
msg.verbose('using', state_file)
|
||||
|
||||
local function write_directory(directory)
|
||||
local file = io.open(state_file, 'w+')
|
||||
|
||||
if not file then return msg.error('could not open', state_file, 'for writing') end
|
||||
|
||||
directory = directory or fb.get_directory()
|
||||
msg.verbose('writing', directory, 'to', state_file)
|
||||
file:write(directory)
|
||||
file:close()
|
||||
end
|
||||
|
||||
|
||||
local addon = {
|
||||
api_version = '1.7.0',
|
||||
priority = 0,
|
||||
}
|
||||
|
||||
function addon:setup()
|
||||
local file = io.open(state_file, "r")
|
||||
if not file then
|
||||
return msg.error('failed to open', state_file, 'for reading')
|
||||
end
|
||||
|
||||
local dir = file:read("*a")
|
||||
msg.verbose('setting default directory to', dir)
|
||||
fb.browse_directory(dir, false)
|
||||
file:close()
|
||||
end
|
||||
|
||||
function addon:can_parse(dir, parse_state)
|
||||
if parse_state.source == 'browser' then write_directory(dir) end
|
||||
return false
|
||||
end
|
||||
|
||||
function addon:parse()
|
||||
return nil
|
||||
end
|
||||
|
||||
mp.register_event('shutdown', function() write_directory() end)
|
||||
|
||||
return addon
|
||||
57
mpv/scripts/file-browser/addons/ls.lua
Normal file
57
mpv/scripts/file-browser/addons/ls.lua
Normal file
@@ -0,0 +1,57 @@
|
||||
--[[
|
||||
An addon for mpv-file-browser which uses the Linux ls command to parse native directories
|
||||
This behaves near identically to the native parser, but IO is done asynchronously.
|
||||
|
||||
Available at: https://github.com/CogentRedTester/mpv-file-browser/tree/master/addons
|
||||
]]--
|
||||
|
||||
local mp = require "mp"
|
||||
local msg = require "mp.msg"
|
||||
local fb = require "file-browser"
|
||||
|
||||
local ls = {
|
||||
priority = 109,
|
||||
api_version = "1.1.0",
|
||||
name = "ls",
|
||||
keybind_name = "file"
|
||||
}
|
||||
|
||||
local function command(args, parse_state)
|
||||
local _, cmd = parse_state:yield(
|
||||
mp.command_native_async({
|
||||
name = "subprocess",
|
||||
playback_only = false,
|
||||
capture_stdout = true,
|
||||
capture_stderr = true,
|
||||
args = args
|
||||
}, fb.coroutine.callback())
|
||||
)
|
||||
|
||||
return cmd.status == 0 and cmd.stdout or nil
|
||||
end
|
||||
|
||||
function ls:can_parse(directory)
|
||||
return directory ~= '' and not fb.get_protocol(directory)
|
||||
end
|
||||
|
||||
function ls:parse(directory, parse_state)
|
||||
local list = {}
|
||||
local files = command({"ls", "-1", "-p", "-A", "-N", "--zero", directory}, parse_state)
|
||||
|
||||
if not files then return nil end
|
||||
|
||||
for str in files:gmatch("%Z+") do
|
||||
local is_dir = str:sub(-1) == "/"
|
||||
msg.trace(str)
|
||||
|
||||
if is_dir and fb.valid_dir(str) then
|
||||
table.insert(list, {name = str, type = "dir"})
|
||||
elseif fb.valid_file(str) then
|
||||
table.insert(list, {name = str, type = "file"})
|
||||
end
|
||||
end
|
||||
|
||||
return list, {filtered = true}
|
||||
end
|
||||
|
||||
return ls
|
||||
54
mpv/scripts/file-browser/addons/m3u-browser.lua
Normal file
54
mpv/scripts/file-browser/addons/m3u-browser.lua
Normal file
@@ -0,0 +1,54 @@
|
||||
--[[
|
||||
An addon for mpv-file-browser which adds support for m3u playlists
|
||||
|
||||
If the first entry of a playlist isn't working it is because some playlists are created with random invisible unicode in the first line
|
||||
Vim makes it easy to detect these
|
||||
|
||||
This addon requires that my API mpv-read-file be available in ~~/script-modules/
|
||||
https://github.com/CogentRedTester/mpv-read-file
|
||||
]]--
|
||||
|
||||
local rf = require "read-file"
|
||||
|
||||
local m3u = {
|
||||
priority = 100,
|
||||
api_version = "1.0.0",
|
||||
name = "m3u"
|
||||
}
|
||||
|
||||
local full_paths = {}
|
||||
|
||||
function m3u:setup()
|
||||
self.register_parseable_extension("m3u")
|
||||
self.register_parseable_extension("m3u8")
|
||||
end
|
||||
|
||||
function m3u:can_parse(directory)
|
||||
return directory:find("m3u8?/?$")
|
||||
end
|
||||
|
||||
function m3u:parse(directory)
|
||||
directory = directory:gsub("/$", "")
|
||||
local list = {}
|
||||
|
||||
local path = full_paths[ directory ] or directory
|
||||
local playlist = rf.get_file_handler( path )
|
||||
|
||||
--if we can't read the path then stop here
|
||||
if not playlist then return {}, {sorted = true, filtered = true, empty_text = "Could not read filepath"} end
|
||||
|
||||
local parent = self.fix_path(path:match("^(.+/[^/]+)/"), true)
|
||||
|
||||
local lines = playlist:read("*a")
|
||||
|
||||
for item in lines:gmatch("[^%c]+") do
|
||||
item = self.fix_path(item)
|
||||
local fullpath = self.join_path(parent, item)
|
||||
|
||||
local name = ( self.get_protocol(item) and item or fullpath:match("([^/]+)/?$") )
|
||||
table.insert(list, {name = name, path = fullpath, type = "file"})
|
||||
end
|
||||
return list, {filtered = true, sorted = true}
|
||||
end
|
||||
|
||||
return m3u
|
||||
81
mpv/scripts/file-browser/addons/powershell.lua
Normal file
81
mpv/scripts/file-browser/addons/powershell.lua
Normal file
@@ -0,0 +1,81 @@
|
||||
--[[
|
||||
An addon for mpv-file-browser which uses powershell commands to parse native directories
|
||||
|
||||
This is slower than the default parser for local drives, but faster for network drives
|
||||
The drive_letters array below is used to list the drives to use this parser for
|
||||
]]--
|
||||
|
||||
--list the drive letters to use here (case sensitive)
|
||||
local drive_letters = {
|
||||
"Y", "Z"
|
||||
}
|
||||
|
||||
local mp = require "mp"
|
||||
local msg = require "mp.msg"
|
||||
local fb = require "file-browser"
|
||||
|
||||
local wn = {
|
||||
priority = 109,
|
||||
api_version = "1.1.0",
|
||||
name = "powershell",
|
||||
keybind_name = "file"
|
||||
}
|
||||
|
||||
local drives = {}
|
||||
for _, letter in ipairs(drive_letters) do
|
||||
drives[letter] = true
|
||||
end
|
||||
|
||||
local function command(args, parse_state)
|
||||
local _, cmd = parse_state:yield(
|
||||
mp.command_native_async({
|
||||
name = "subprocess",
|
||||
playback_only = false,
|
||||
capture_stdout = true,
|
||||
capture_stderr = true,
|
||||
args = args
|
||||
}, fb.coroutine.callback())
|
||||
)
|
||||
|
||||
return cmd.status == 0 and cmd.stdout or nil, cmd.stderr
|
||||
end
|
||||
|
||||
function wn:can_parse(directory)
|
||||
return directory ~= '' and not self.get_protocol(directory) and drives[ directory:sub(1,1) ]
|
||||
end
|
||||
|
||||
function wn:parse(directory, parse_state)
|
||||
local list = {}
|
||||
local files, err = command({"powershell", "-noprofile", "-command", [[
|
||||
$dirs = Get-ChildItem -LiteralPath ]]..string.format("%q", directory)..[[ -Directory
|
||||
$files = Get-ChildItem -LiteralPath ]]..string.format("%q", directory)..[[ -File
|
||||
|
||||
foreach ($n in $dirs.Name) {
|
||||
$n += "/"
|
||||
$u8clip = [System.Text.Encoding]::UTF8.GetBytes($n)
|
||||
[Console]::OpenStandardOutput().Write($u8clip, 0, $u8clip.Length)
|
||||
Write-Host ""
|
||||
}
|
||||
|
||||
foreach ($n in $files.Name) {
|
||||
$u8clip = [System.Text.Encoding]::UTF8.GetBytes($n)
|
||||
[Console]::OpenStandardOutput().Write($u8clip, 0, $u8clip.Length)
|
||||
Write-Host ""
|
||||
}
|
||||
]]}, parse_state)
|
||||
|
||||
if not files then msg.debug(err) ; return nil end
|
||||
|
||||
for str in files:gmatch("[^\n\r]+") do
|
||||
local is_dir = str:sub(-1) == "/"
|
||||
if is_dir and self.valid_dir(str) then
|
||||
table.insert(list, {name = str, type = "dir"})
|
||||
elseif self.valid_file(str) then
|
||||
table.insert(list, {name = str, type = "file"})
|
||||
end
|
||||
end
|
||||
|
||||
return self.sort(list), {filtered = true, sorted = true}
|
||||
end
|
||||
|
||||
return wn
|
||||
54
mpv/scripts/file-browser/addons/root.lua
Normal file
54
mpv/scripts/file-browser/addons/root.lua
Normal file
@@ -0,0 +1,54 @@
|
||||
--[[
|
||||
An addon that loads root items from a `~~/script-opts/file-browser-root.json` file.
|
||||
The contents of this file will override the root script-opt.
|
||||
|
||||
The json file takes the form of a list array as defined by the addon API:
|
||||
https://github.com/CogentRedTester/mpv-file-browser/blob/master/addons/addons.md#the-list-array
|
||||
|
||||
The main purpose of this addon is to allow for users to customise the appearance of their root items
|
||||
using the label or ass fields:
|
||||
|
||||
[
|
||||
{ "name": "Favourites/" },
|
||||
{ "label": "~/", "name": "C:/Users/User/" },
|
||||
{ "label": "1TB HDD", "name": "D:/" },
|
||||
{ "ass": "{\\c&H007700&}Green Text", "name": "E:/" },
|
||||
{ "label": "FTP Server", name: "ftp://user:password@server.com/" }
|
||||
]
|
||||
|
||||
Make sure local directories always end with `/`.
|
||||
`path` and `name` behave the same in the root but either name or label should have a value.
|
||||
ASS styling codes: https://aegi.vmoe.info/docs/3.0/ASS_Tags/
|
||||
]]
|
||||
|
||||
local mp = require 'mp'
|
||||
local msg = require 'mp.msg'
|
||||
local utils = require 'mp.utils'
|
||||
local fb = require 'file-browser'
|
||||
|
||||
-- loads the root json file
|
||||
local config_path = mp.command_native({'expand-path', '~~/script-opts/file-browser-root.json'})
|
||||
|
||||
local file = io.open(config_path, 'r')
|
||||
if not file then
|
||||
msg.error('failed to read file', config_path)
|
||||
return
|
||||
end
|
||||
|
||||
local root_config = utils.parse_json(file:read("*a"))
|
||||
if not root_config then
|
||||
msg.error('failed to parse contents of', config_path, '- Check the syntax is correct.')
|
||||
return
|
||||
end
|
||||
|
||||
local function setup()
|
||||
for i, item in ipairs(root_config) do
|
||||
fb.register_root_item(item, item.priority)
|
||||
end
|
||||
end
|
||||
|
||||
return {
|
||||
api_version = '1.4.0',
|
||||
setup = setup,
|
||||
priority = -1000,
|
||||
}
|
||||
35
mpv/scripts/file-browser/addons/url-decode.lua
Normal file
35
mpv/scripts/file-browser/addons/url-decode.lua
Normal file
@@ -0,0 +1,35 @@
|
||||
--[[
|
||||
An addon for file-browser which decodes URLs so that they are more readable
|
||||
]]
|
||||
|
||||
local urldecode = {
|
||||
priority = 5,
|
||||
api_version = "1.0.0"
|
||||
}
|
||||
|
||||
--decodes a URL address
|
||||
--this piece of code was taken from: https://stackoverflow.com/questions/20405985/lua-decodeuri-luvit/20406960#20406960
|
||||
local decodeURI
|
||||
do
|
||||
local char, gsub, tonumber = string.char, string.gsub, tonumber
|
||||
local function _(hex) return char(tonumber(hex, 16)) end
|
||||
|
||||
function decodeURI(s)
|
||||
s = gsub(s, '%%(%x%x)', _)
|
||||
return s
|
||||
end
|
||||
end
|
||||
|
||||
function urldecode:can_parse(directory)
|
||||
return self.get_protocol(directory)
|
||||
end
|
||||
|
||||
function urldecode:parse(directory)
|
||||
local list, opts = self:defer(directory)
|
||||
if opts.directory and not self.get_protocol(opts.directory) then return list, opts end
|
||||
|
||||
opts.directory_label = decodeURI(opts.directory_label or (opts.directory or directory))
|
||||
return list, opts
|
||||
end
|
||||
|
||||
return urldecode
|
||||
97
mpv/scripts/file-browser/addons/windir.lua
Normal file
97
mpv/scripts/file-browser/addons/windir.lua
Normal file
@@ -0,0 +1,97 @@
|
||||
--[[
|
||||
An addon for mpv-file-browser which uses the Windows dir command to parse native directories
|
||||
This behaves near identically to the native parser, but IO is done asynchronously.
|
||||
|
||||
Available at: https://github.com/CogentRedTester/mpv-file-browser/tree/master/addons
|
||||
]]--
|
||||
|
||||
local mp = require "mp"
|
||||
local msg = require "mp.msg"
|
||||
local fb = require "file-browser"
|
||||
|
||||
--this is a LuaJit module this addon will not load if not using LuaJit
|
||||
local ffi = require 'ffi'
|
||||
ffi.cdef([[
|
||||
int __stdcall WideCharToMultiByte(unsigned int CodePage, unsigned int dwFlags, const wchar_t *lpWideCharStr, int cchWideChar, char *lpMultiByteStr, int cbMultiByte, const char *lpDefaultChar, bool *lpUsedDefaultChar);
|
||||
]])
|
||||
|
||||
--converts a UTF16 string to a UTF8 string
|
||||
--this function was adapted from https://github.com/mpv-player/mpv/issues/10139#issuecomment-1117954648
|
||||
local function utf8(WideCharStr)
|
||||
WideCharStr = ffi.cast("wchar_t*", WideCharStr)
|
||||
if not WideCharStr then return nil end
|
||||
|
||||
local utf8_size = ffi.C.WideCharToMultiByte(65001, 0, WideCharStr, -1, nil, 0, nil, nil) --CP_UTF8
|
||||
if utf8_size > 0 then
|
||||
local utf8_path = ffi.new("char[?]", utf8_size)
|
||||
local utf8_size = ffi.C.WideCharToMultiByte(65001, 0, WideCharStr, -1, utf8_path, utf8_size, nil, nil)
|
||||
if utf8_size > 0 then
|
||||
--removes the trailing `\0` character which can break things
|
||||
return ffi.string(utf8_path, utf8_size):sub(1, -2)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local dir = {
|
||||
priority = 109,
|
||||
api_version = "1.1.0",
|
||||
name = "cmd-dir",
|
||||
keybind_name = "file"
|
||||
}
|
||||
|
||||
local function command(args, parse_state)
|
||||
local _, cmd = parse_state:yield(
|
||||
mp.command_native_async({
|
||||
name = "subprocess",
|
||||
playback_only = false,
|
||||
capture_stdout = true,
|
||||
capture_stderr = true,
|
||||
args = args,
|
||||
}, fb.coroutine.callback() )
|
||||
)
|
||||
cmd.stdout = utf8(cmd.stdout)
|
||||
cmd.stderr = utf8(cmd.stderr)
|
||||
|
||||
--dir returns this exact error message if the directory is empty
|
||||
if cmd.status == 1 and cmd.stderr == "File Not Found\r\n" then cmd.status = 0 end
|
||||
|
||||
return cmd.status == 0 and cmd.stdout or nil, cmd.stderr
|
||||
end
|
||||
|
||||
function dir:can_parse(directory)
|
||||
if directory == "" then return end
|
||||
return not fb.get_protocol(directory)
|
||||
end
|
||||
|
||||
function dir:parse(directory, parse_state)
|
||||
local list = {}
|
||||
local files, dirs, err
|
||||
|
||||
-- the dir command expects backslashes for our paths
|
||||
directory = directory:gsub("/", "\\")
|
||||
|
||||
dirs, err = command({ "cmd", "/U", "/c", "dir", "/b", "/ad", directory }, parse_state)
|
||||
if not dirs then return msg.error(err) end
|
||||
|
||||
files, err = command({ "cmd", "/U", "/c", "dir", "/b", "/a-d", directory }, parse_state)
|
||||
if not files then return msg.error(err) end
|
||||
|
||||
for name in dirs:gmatch("[^\n\r]+") do
|
||||
name = name.."/"
|
||||
if fb.valid_dir(name) then
|
||||
table.insert(list, { name = name, type = "dir" })
|
||||
msg.trace(name)
|
||||
end
|
||||
end
|
||||
|
||||
for name in files:gmatch("[^\n\r]+") do
|
||||
if fb.valid_file(name) then
|
||||
table.insert(list, { name = name, type = "file" })
|
||||
msg.trace(name)
|
||||
end
|
||||
end
|
||||
|
||||
return list, { filtered = true }
|
||||
end
|
||||
|
||||
return dir
|
||||
52
mpv/scripts/file-browser/addons/winroot.lua
Normal file
52
mpv/scripts/file-browser/addons/winroot.lua
Normal file
@@ -0,0 +1,52 @@
|
||||
--[[
|
||||
Automatically populates the root with windows drives on startup.
|
||||
Ctrl+r will add new drives mounted since startup.
|
||||
|
||||
Drives will only be added if they are not already present in the root.
|
||||
|
||||
Available at: https://github.com/CogentRedTester/mpv-file-browser/tree/master/addons
|
||||
]]
|
||||
|
||||
local mp = require 'mp'
|
||||
local msg = require 'mp.msg'
|
||||
local fb = require 'file-browser'
|
||||
|
||||
-- returns a list of windows drives
|
||||
local function get_drives()
|
||||
local result = mp.command_native({
|
||||
name = 'subprocess',
|
||||
playback_only = false,
|
||||
capture_stdout = true,
|
||||
args = {'fsutil', 'fsinfo', 'drives'}
|
||||
})
|
||||
if result.status ~= 0 then return msg.error('could not read windows root') end
|
||||
|
||||
local root = {}
|
||||
for drive in result.stdout:gmatch("(%a:)\\") do
|
||||
table.insert(root, drive..'/')
|
||||
end
|
||||
return root
|
||||
end
|
||||
|
||||
-- adds windows drives to the root if they are not already present
|
||||
local function import_drives()
|
||||
local drives = get_drives()
|
||||
|
||||
for _, drive in ipairs(drives) do
|
||||
fb.register_root_item(drive)
|
||||
end
|
||||
end
|
||||
|
||||
local keybind = {
|
||||
key = 'Ctrl+r',
|
||||
name = 'import_root_drives',
|
||||
command = import_drives,
|
||||
parser = 'root',
|
||||
passthrough = true
|
||||
}
|
||||
|
||||
return {
|
||||
api_version = '1.4.0',
|
||||
setup = import_drives,
|
||||
keybinds = { keybind }
|
||||
}
|
||||
Reference in New Issue
Block a user