446 lines
14 KiB
Lua
446 lines
14 KiB
Lua
|
|
||
|
-- https://github.com/stax76/mpv-scripts
|
||
|
|
||
|
----- options
|
||
|
|
||
|
local o = {
|
||
|
lines_to_show = 10,
|
||
|
pause_on_open = false, -- does not work on my system when enabled, menu won't show
|
||
|
resume_on_exit = "only-if-was-paused",
|
||
|
|
||
|
-- styles
|
||
|
font_size = 50,
|
||
|
line_bottom_margin = 1,
|
||
|
menu_x_padding = 5,
|
||
|
menu_y_padding = 2,
|
||
|
}
|
||
|
|
||
|
local opt = require "mp.options"
|
||
|
opt.read_options(o)
|
||
|
|
||
|
----- string
|
||
|
|
||
|
function is_empty(input)
|
||
|
if input == nil or input == "" then
|
||
|
return true
|
||
|
end
|
||
|
end
|
||
|
|
||
|
function contains(input, find)
|
||
|
if not is_empty(input) and not is_empty(find) then
|
||
|
return input:find(find, 1, true)
|
||
|
end
|
||
|
end
|
||
|
|
||
|
function starts_with(str, start)
|
||
|
return str:sub(1, #start) == start
|
||
|
end
|
||
|
|
||
|
function split(input, sep)
|
||
|
assert(#sep == 1) -- supports only single character separator
|
||
|
local tbl = {}
|
||
|
|
||
|
if input ~= nil then
|
||
|
for str in string.gmatch(input, "([^" .. sep .. "]+)") do
|
||
|
table.insert(tbl, str)
|
||
|
end
|
||
|
end
|
||
|
|
||
|
return tbl
|
||
|
end
|
||
|
|
||
|
function first_to_upper(str)
|
||
|
return (str:gsub("^%l", string.upper))
|
||
|
end
|
||
|
|
||
|
----- list
|
||
|
|
||
|
function list_contains(list, value)
|
||
|
for _, v in pairs(list) do
|
||
|
if v == value then
|
||
|
return true
|
||
|
end
|
||
|
end
|
||
|
end
|
||
|
|
||
|
----- path
|
||
|
|
||
|
function get_temp_dir()
|
||
|
local is_windows = package.config:sub(1,1) == "\\"
|
||
|
|
||
|
if is_windows then
|
||
|
return os.getenv("TEMP") .. "\\"
|
||
|
else
|
||
|
return "/tmp/"
|
||
|
end
|
||
|
end
|
||
|
|
||
|
---- file
|
||
|
|
||
|
function file_exists(path)
|
||
|
if is_empty(path) then return false end
|
||
|
local file = io.open(path, "r")
|
||
|
|
||
|
if file ~= nil then
|
||
|
io.close(file)
|
||
|
return true
|
||
|
end
|
||
|
end
|
||
|
|
||
|
function file_write(path, content)
|
||
|
local file = assert(io.open(path, "w"))
|
||
|
file:write(content)
|
||
|
file:close()
|
||
|
end
|
||
|
|
||
|
----- mpv
|
||
|
|
||
|
local utils = require "mp.utils"
|
||
|
local assdraw = require 'mp.assdraw'
|
||
|
local msg = require "mp.msg"
|
||
|
|
||
|
----- path mpv
|
||
|
|
||
|
function file_name(value)
|
||
|
local _, filename = utils.split_path(value)
|
||
|
return filename
|
||
|
end
|
||
|
|
||
|
----- main
|
||
|
|
||
|
package.path = mp.command_native({ "expand-path", "~~/script-modules/?.lua;" }) .. package.path
|
||
|
|
||
|
local em = require "extended-menu"
|
||
|
local menu = em:new(o)
|
||
|
local menu_content = { list = {}, current_i = nil }
|
||
|
local osc_visibility = nil
|
||
|
local media_info_cache = {}
|
||
|
local original_set_active_func = em.set_active
|
||
|
local original_get_line_func = em.get_line
|
||
|
|
||
|
function em:get_bindings()
|
||
|
local bindings = {
|
||
|
{ 'esc', function() self:set_active(false) end },
|
||
|
{ 'enter', function() self:handle_enter() end },
|
||
|
{ 'bs', function() self:handle_backspace() end },
|
||
|
{ 'del', function() self:handle_del() end },
|
||
|
{ 'ins', function() self:handle_ins() end },
|
||
|
{ 'left', function() self:prev_char() end },
|
||
|
{ 'right', function() self:next_char() end },
|
||
|
{ 'ctrl+f', function() self:next_char() end },
|
||
|
{ 'up', function() self:change_selected_index(-1) end },
|
||
|
{ 'down', function() self:change_selected_index(1) end },
|
||
|
{ 'ctrl+up', function() self:move_history(-1) end },
|
||
|
{ 'ctrl+down', function() self:move_history(1) end },
|
||
|
{ 'ctrl+left', function() self:prev_word() end },
|
||
|
{ 'ctrl+right', function() self:next_word() end },
|
||
|
{ 'home', function() self:go_home() end },
|
||
|
{ 'end', function() self:go_end() end },
|
||
|
{ 'pgup', function() self:change_selected_index(-o.lines_to_show) end },
|
||
|
{ 'pgdwn', function() self:change_selected_index(o.lines_to_show) end },
|
||
|
{ 'ctrl+u', function() self:del_to_start() end },
|
||
|
{ 'ctrl+v', function() self:paste(true) end },
|
||
|
{ 'ctrl+bs', function() self:del_word() end },
|
||
|
{ 'ctrl+del', function() self:del_next_word() end },
|
||
|
{ 'kp_dec', function() self:handle_char_input('.') end },
|
||
|
}
|
||
|
|
||
|
for i = 0, 9 do
|
||
|
bindings[#bindings + 1] =
|
||
|
{'kp' .. i, function() self:handle_char_input('' .. i) end}
|
||
|
end
|
||
|
|
||
|
return bindings
|
||
|
end
|
||
|
|
||
|
function em:set_active(active)
|
||
|
original_set_active_func(self, active)
|
||
|
|
||
|
if not active and osc_visibility then
|
||
|
mp.command("script-message osc-visibility " .. osc_visibility .. " no_osd")
|
||
|
osc_visibility = nil
|
||
|
end
|
||
|
end
|
||
|
|
||
|
menu.index_field = "index"
|
||
|
|
||
|
function get_media_info()
|
||
|
local path = mp.get_property("path")
|
||
|
|
||
|
if media_info_cache[path] then
|
||
|
return media_info_cache[path]
|
||
|
end
|
||
|
|
||
|
local format_file = get_temp_dir() .. mp.get_script_name() .. " media-info-format-v1.txt"
|
||
|
|
||
|
if not file_exists(format_file) then
|
||
|
media_info_format = [[General;N: %FileNameExtension%\\nG: %Format%, %FileSize/String%, %Duration/String%, %OverallBitRate/String%, %Recorded_Date%\\n
|
||
|
Video;V: %Format%, %Format_Profile%, %Width%x%Height%, %BitRate/String%, %FrameRate% FPS\\n
|
||
|
Audio;A: %Language/String%, %Format%, %Format_Profile%, %BitRate/String%, %Channel(s)% ch, %SamplingRate/String%, %Title%\\n
|
||
|
Text;S: %Language/String%, %Format%, %Format_Profile%, %Title%\\n]]
|
||
|
|
||
|
file_write(format_file, media_info_format)
|
||
|
end
|
||
|
|
||
|
if contains(path, "://") or not file_exists(path) then
|
||
|
return
|
||
|
end
|
||
|
|
||
|
local proc_result = mp.command_native({
|
||
|
name = "subprocess",
|
||
|
playback_only = false,
|
||
|
capture_stdout = true,
|
||
|
args = {"mediainfo", "--inform=file://" .. format_file, path},
|
||
|
})
|
||
|
|
||
|
if proc_result.status == 0 then
|
||
|
local output = proc_result.stdout
|
||
|
|
||
|
output = string.gsub(output, ", , ,", ",")
|
||
|
output = string.gsub(output, ", ,", ",")
|
||
|
output = string.gsub(output, ": , ", ": ")
|
||
|
output = string.gsub(output, ", \\n\r*\n", "\\n")
|
||
|
output = string.gsub(output, "\\n\r*\n", "\\n")
|
||
|
output = string.gsub(output, ", \\n", "\\n")
|
||
|
output = string.gsub(output, "\\n", "\n")
|
||
|
output = string.gsub(output, "%.000 FPS", " FPS")
|
||
|
output = string.gsub(output, "MPEG Audio, Layer 3", "MP3")
|
||
|
|
||
|
media_info_cache[path] = output
|
||
|
|
||
|
return output
|
||
|
end
|
||
|
end
|
||
|
|
||
|
function binding_get_line(self, _, v)
|
||
|
local a = assdraw.ass_new()
|
||
|
local cmd = self:ass_escape(v.cmd)
|
||
|
local key = self:ass_escape(v.key)
|
||
|
local comment = self:ass_escape(v.comment or '')
|
||
|
|
||
|
if v.priority == -1 or v.priority == -2 then
|
||
|
local why_inactive = (v.priority == -1) and 'Inactive' or 'Shadowed'
|
||
|
a:append(self:get_font_color('comment'))
|
||
|
|
||
|
if comment ~= "" then
|
||
|
a:append(comment .. '\\h')
|
||
|
end
|
||
|
|
||
|
a:append(key .. '\\h(' .. why_inactive .. ')' .. '\\h' .. cmd)
|
||
|
return a.text
|
||
|
end
|
||
|
|
||
|
if comment ~= "" then
|
||
|
a:append(self:get_font_color('default'))
|
||
|
a:append(comment .. '\\h')
|
||
|
end
|
||
|
|
||
|
a:append(self:get_font_color('accent'))
|
||
|
a:append(key)
|
||
|
a:append(self:get_font_color('comment'))
|
||
|
a:append(' ' .. cmd)
|
||
|
return a.text
|
||
|
end
|
||
|
|
||
|
mp.register_script_message("show-command-palette", function (name)
|
||
|
menu_content.list = {}
|
||
|
menu_content.current_i = 1
|
||
|
menu.search_heading = first_to_upper(name)
|
||
|
menu.filter_by_fields = { "content" }
|
||
|
em.get_line = original_get_line_func
|
||
|
|
||
|
if name == "bindings" then
|
||
|
local bindings = utils.parse_json(mp.get_property("input-bindings"))
|
||
|
|
||
|
for _, v in ipairs(bindings) do
|
||
|
v.key = "(" .. v.key .. ")"
|
||
|
|
||
|
if not is_empty(v.comment) then
|
||
|
v.comment = first_to_upper(v.comment)
|
||
|
end
|
||
|
end
|
||
|
|
||
|
for _, v in ipairs(bindings) do
|
||
|
for _, v2 in ipairs(bindings) do
|
||
|
if v.key == v2.key and v.priority < v2.priority then
|
||
|
v.priority = -2
|
||
|
break
|
||
|
end
|
||
|
end
|
||
|
end
|
||
|
|
||
|
table.sort(bindings, function(i, j)
|
||
|
return i.priority > j.priority
|
||
|
end)
|
||
|
|
||
|
menu_content.list = bindings
|
||
|
|
||
|
function menu:submit(val)
|
||
|
mp.command(val.cmd)
|
||
|
end
|
||
|
|
||
|
menu.filter_by_fields = {'cmd', 'key', 'comment'}
|
||
|
em.get_line = binding_get_line
|
||
|
elseif name == "chapters" then
|
||
|
local count = mp.get_property("chapter-list/count")
|
||
|
if count == 0 then return end
|
||
|
|
||
|
for i = 0, count do
|
||
|
local title = mp.get_property("chapter-list/" .. i .. "/title")
|
||
|
|
||
|
if title then
|
||
|
table.insert(menu_content.list, { index = i + 1, content = title })
|
||
|
end
|
||
|
end
|
||
|
|
||
|
menu_content.current_i = mp.get_property_number("chapter") + 1
|
||
|
|
||
|
function menu:submit(val)
|
||
|
mp.set_property_number("chapter", val.index - 1)
|
||
|
end
|
||
|
elseif name == "playlist" then
|
||
|
local count = mp.get_property_number("playlist-count")
|
||
|
if count == 0 then return end
|
||
|
|
||
|
for i = 0, (count - 1) do
|
||
|
local text = mp.get_property("playlist/" .. i .. "/title")
|
||
|
|
||
|
if text == nil then
|
||
|
text = file_name(mp.get_property("playlist/" .. i .. "/filename"))
|
||
|
end
|
||
|
|
||
|
table.insert(menu_content.list, { index = i + 1, content = text })
|
||
|
end
|
||
|
|
||
|
menu_content.current_i = mp.get_property_number("playlist-pos") + 1
|
||
|
|
||
|
function menu:submit(val)
|
||
|
mp.set_property_number("playlist-pos", val.index - 1)
|
||
|
end
|
||
|
elseif name == "commands" then
|
||
|
local commands = utils.parse_json(mp.get_property("command-list"))
|
||
|
|
||
|
for k, v in ipairs(commands) do
|
||
|
local text = v.name
|
||
|
|
||
|
for _, arg in ipairs(v.args) do
|
||
|
if arg.optional then
|
||
|
text = text .. " [<" .. arg.name .. ">]"
|
||
|
else
|
||
|
text = text .. " <" .. arg.name .. ">"
|
||
|
end
|
||
|
end
|
||
|
|
||
|
table.insert(menu_content.list, { index = k, content = text })
|
||
|
end
|
||
|
|
||
|
function menu:submit(val)
|
||
|
print(val.content)
|
||
|
local cmd = string.match(val.content, '%S+')
|
||
|
mp.commandv("script-message-to", "console", "type", cmd .. " ")
|
||
|
end
|
||
|
elseif name == "properties" then
|
||
|
local properties = split(mp.get_property("property-list"), ",")
|
||
|
|
||
|
for k, v in ipairs(properties) do
|
||
|
table.insert(menu_content.list, { index = k, content = v })
|
||
|
end
|
||
|
|
||
|
function menu:submit(val)
|
||
|
mp.commandv('script-message-to', 'console', 'type', "print-text ${" .. val.content .. "}")
|
||
|
end
|
||
|
elseif name == "options" then
|
||
|
local options = split(mp.get_property("options"), ",")
|
||
|
|
||
|
for k, v in ipairs(options) do
|
||
|
local type = mp.get_property_osd("option-info/" .. v .. "/type", "")
|
||
|
local default =mp.get_property_osd("option-info/" .. v .. "/default-value", "")
|
||
|
v = v .. " (type: " .. type .. ", default: " .. default .. ")"
|
||
|
table.insert(menu_content.list, { index = k, content = v })
|
||
|
end
|
||
|
|
||
|
function menu:submit(val)
|
||
|
print(val.content)
|
||
|
local prop = string.match(val.content, '%S+')
|
||
|
mp.commandv("script-message-to", "console", "type", "set " .. prop .. " ")
|
||
|
end
|
||
|
elseif name == "audio" then
|
||
|
local tracks = split(get_media_info() .. "\nA: None", "\n")
|
||
|
local id = 0
|
||
|
|
||
|
for _, v in ipairs(tracks) do
|
||
|
if starts_with(v, "A: ") then
|
||
|
id = id + 1
|
||
|
table.insert(menu_content.list, { index = id, content = string.sub(v, 4) })
|
||
|
end
|
||
|
end
|
||
|
|
||
|
menu_content.current_i = mp.get_property_number("aid") or id
|
||
|
|
||
|
function menu:submit(val)
|
||
|
mp.command("set aid " .. ((val.index == id) and 'no' or val.index))
|
||
|
end
|
||
|
elseif name == "subtitle" then
|
||
|
local tracks = split(get_media_info() .. "\nS: None", "\n")
|
||
|
local id = 0
|
||
|
|
||
|
for _, v in ipairs(tracks) do
|
||
|
if starts_with(v, "S: ") then
|
||
|
id = id + 1
|
||
|
table.insert(menu_content.list, { index = id, content = string.sub(v, 4) })
|
||
|
end
|
||
|
end
|
||
|
|
||
|
menu_content.current_i = mp.get_property_number("sid") or id
|
||
|
|
||
|
function menu:submit(val)
|
||
|
mp.command("set sid " .. ((val.index == id) and 'no' or val.index))
|
||
|
end
|
||
|
elseif name == "video" then
|
||
|
local tracks = split(get_media_info() .. "\nV: None", "\n")
|
||
|
local id = 0
|
||
|
|
||
|
for _, v in ipairs(tracks) do
|
||
|
if starts_with(v, "V: ") then
|
||
|
id = id + 1
|
||
|
table.insert(menu_content.list, { index = id, content = string.sub(v, 4) })
|
||
|
end
|
||
|
end
|
||
|
|
||
|
menu_content.current_i = mp.get_property_number("vid") or id
|
||
|
|
||
|
function menu:submit(val)
|
||
|
mp.command("set vid " .. ((val.index == id) and 'no' or val.index))
|
||
|
end
|
||
|
elseif name == "profiles" then
|
||
|
local profiles = utils.parse_json(mp.get_property("profile-list"))
|
||
|
local ignore_list = {"builtin-pseudo-gui", "encoding", "libmpv", "pseudo-gui", "default"}
|
||
|
|
||
|
for k, v in ipairs(profiles) do
|
||
|
if not list_contains(ignore_list, v.name) then
|
||
|
table.insert(menu_content.list, { index = k, content = v.name })
|
||
|
end
|
||
|
end
|
||
|
|
||
|
function menu:submit(val)
|
||
|
mp.command("show-text " .. val.content);
|
||
|
mp.command("apply-profile " .. val.content);
|
||
|
end
|
||
|
else
|
||
|
msg.error("Unknown mode " .. name)
|
||
|
return
|
||
|
end
|
||
|
|
||
|
if is_empty(mp.get_property("path")) then
|
||
|
osc_visibility = utils.shared_script_property_get("osc-visibility")
|
||
|
|
||
|
if osc_visibility then
|
||
|
mp.command("script-message osc-visibility never no_osd")
|
||
|
end
|
||
|
else
|
||
|
osc_visibility = nil
|
||
|
end
|
||
|
|
||
|
menu:init(menu_content)
|
||
|
end)
|