1075 lines
35 KiB
Lua
1075 lines
35 KiB
Lua
|
|
-- https://github.com/stax76/mpv-scripts
|
|
|
|
----- options
|
|
|
|
local o = {
|
|
font_size = 16,
|
|
scale_by_window = false,
|
|
lines_to_show = 12,
|
|
pause_on_open = false, -- does not work on my system when enabled, menu won't show
|
|
resume_on_exit = "only-if-was-paused",
|
|
|
|
-- styles
|
|
line_bottom_margin = 1,
|
|
menu_x_padding = 5,
|
|
menu_y_padding = 2,
|
|
|
|
use_mediainfo = false, -- # true requires the MediaInfo CLI app being installed
|
|
stream_quality_options = "2160,1440,1080,720,480",
|
|
aspect_ratios = "4:3,16:9,2.35:1,1.36,1.82,0,-1",
|
|
}
|
|
|
|
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 replace(str, what, with)
|
|
what = string.gsub(what, "[%(%)%.%+%-%*%?%[%]%^%$%%]", "%%%1")
|
|
with = string.gsub(with, "[%%]", "%%%%")
|
|
return string.gsub(str, what, with)
|
|
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
|
|
|
|
local command_palette_version = 1
|
|
mp.commandv('script-message', 'command-palette-version', command_palette_version)
|
|
|
|
local is_older_than_v0_36 = string.find(mp.get_property("mpv-version"), 'mpv v0%.[1-3][0-5]%.') == 1
|
|
|
|
if not is_older_than_v0_36 then
|
|
mp.set_property_native("user-data/command-palette/version", command_palette_version)
|
|
end
|
|
|
|
local BluRayTitles = {}
|
|
|
|
mp.enable_messages("info")
|
|
|
|
mp.register_event('log-message', function(e)
|
|
if e.prefix ~= "bd" then
|
|
return
|
|
end
|
|
|
|
if contains(e.text, " 0 duration: ") then
|
|
BluRayTitles = {}
|
|
end
|
|
|
|
if contains(e.text, " duration: ") then
|
|
local match = string.match(e.text, "%d%d:%d%d:%d%d")
|
|
|
|
if match then
|
|
table.insert(BluRayTitles, match)
|
|
end
|
|
end
|
|
end)
|
|
|
|
local uosc_available = false
|
|
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 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 },
|
|
{ 'mbtn_left', function() self:handle_enter() end },
|
|
{ 'mbtn_right', function() self:set_active(false) end },
|
|
{ 'wheel_up', function() self:change_selected_index(-1) end },
|
|
{ 'wheel_down', function() self:change_selected_index(1) end },
|
|
{ 'mbtn_forward',function() self:change_selected_index(-o.lines_to_show) end },
|
|
{ 'mbtn_back', function() self:change_selected_index(o.lines_to_show) 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 then
|
|
if osc_visibility == "auto" or osc_visibility == "always" then
|
|
mp.command("script-message osc-visibility " .. osc_visibility .. " no_osd")
|
|
osc_visibility = nil
|
|
elseif uosc_available then
|
|
mp.commandv('script-message-to', 'uosc', 'disable-elements', mp.get_script_name(), '')
|
|
end
|
|
end
|
|
end
|
|
|
|
menu.index_field = "index"
|
|
|
|
local function format_time(t, duration)
|
|
local h = math.floor(t / (60 * 60))
|
|
t = t - (h * 60 * 60)
|
|
local m = math.floor(t / 60)
|
|
local s = t - (m * 60)
|
|
|
|
if duration >= 60 * 60 or h > 0 then
|
|
return string.format("%.2d:%.2d:%.2d", h, m, s)
|
|
end
|
|
|
|
return string.format("%.2d:%.2d", m, s)
|
|
end
|
|
|
|
function get_media_info()
|
|
local path = mp.get_property("path")
|
|
|
|
if contains(path, "://") or not file_exists(path) then
|
|
return
|
|
end
|
|
|
|
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
|
|
|
|
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 ass = 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'
|
|
ass:append(self:get_font_color('comment'))
|
|
|
|
if comment ~= "" then
|
|
ass:append(comment .. '\\h')
|
|
end
|
|
|
|
ass:append(key .. '\\h(' .. why_inactive .. ')' .. '\\h' .. cmd)
|
|
return ass.text
|
|
end
|
|
|
|
if comment ~= "" then
|
|
ass:append(self:get_font_color('default'))
|
|
ass:append(comment .. '\\h')
|
|
end
|
|
|
|
ass:append(self:get_font_color('accent'))
|
|
ass:append(key)
|
|
ass:append(self:get_font_color('comment'))
|
|
ass:append(' ' .. cmd)
|
|
return ass.text
|
|
end
|
|
|
|
function command_palette_get_line(self, _, v)
|
|
local ass = assdraw.ass_new()
|
|
|
|
if v.key == "" then
|
|
ass:append(self:get_font_color('default'))
|
|
ass:append(self:ass_escape(v.name or ''))
|
|
else
|
|
ass:append(self:get_font_color('default'))
|
|
ass:append(self:ass_escape(v.name or '') .. '\\h')
|
|
|
|
ass:append(self:get_font_color('accent'))
|
|
ass:append(self:ass_escape("(" .. v.key .. ")"))
|
|
end
|
|
|
|
return ass.text
|
|
end
|
|
|
|
local function escape_codec(str)
|
|
if not str or str == '' then return '' end
|
|
|
|
local codec_map = {
|
|
mpeg2 = "mpeg2",
|
|
dvvideo = "dv",
|
|
pcm = "pcm",
|
|
pgs = "pgs",
|
|
subrip = "srt",
|
|
vtt = "vtt",
|
|
dvd_sub = "vob",
|
|
dvb_sub = "dvb",
|
|
dvb_tele = "teletext",
|
|
arib = "arib"
|
|
}
|
|
|
|
for key, value in pairs(codec_map) do
|
|
if str:find(key) then
|
|
return value
|
|
end
|
|
end
|
|
|
|
return str
|
|
end
|
|
|
|
local function format_flags(track)
|
|
local flags = ""
|
|
|
|
for _, flag in ipairs({
|
|
"default", "forced", "dependent", "visual-impaired", "hearing-impaired",
|
|
"image", "external"
|
|
}) do
|
|
if track[flag] then
|
|
flags = flags .. flag .. " "
|
|
end
|
|
end
|
|
|
|
if flags == "" then
|
|
return ""
|
|
end
|
|
|
|
return " [" .. flags:sub(1, -2) .. "]"
|
|
end
|
|
|
|
local function format_track(track, type)
|
|
local title = track.title or ''
|
|
local filename = mp.get_property('filename/no-ext', ''):gsub("[%(%)%.%%%+%-%*%?%[%]%^%$]", "%%%0")
|
|
local codec = escape_codec(track.codec)
|
|
|
|
if track.external and title ~= "" then
|
|
local extension = title:match("%.([^%.]+)$")
|
|
if filename ~= "" and extension then
|
|
title = title:gsub(filename .. "%.?", "")
|
|
end
|
|
if track.lang and extension
|
|
and title:lower() == track.lang:lower() .. "." .. extension:lower() then
|
|
title = extension
|
|
end
|
|
end
|
|
if title == '' then
|
|
local name = type:sub(1, 1):upper() .. type:sub(2, #type)
|
|
title = string.format('%s %02.f', name, track.id)
|
|
end
|
|
|
|
local hints = {}
|
|
local function h(value) hints[#hints + 1] = value end
|
|
if codec ~= '' then h(codec) end
|
|
if track['demux-h'] then
|
|
h(track['demux-w'] and (track['demux-w'] .. 'x' .. track['demux-h'] or track['demux-h'] .. 'p'))
|
|
end
|
|
if track['demux-fps'] then h(string.format('%.5gfps', track['demux-fps'])) end
|
|
if track['audio-channels'] then h(track['audio-channels'] .. 'ch') end
|
|
if track['demux-samplerate'] then h(string.format('%.3gkHz', track['demux-samplerate'] / 1000)) end
|
|
if track['demux-bitrate'] then h(string.format('%.0fkbps', track['demux-bitrate'] / 1000)) end
|
|
if track.selected then
|
|
title = "● " .. title
|
|
else
|
|
title = "○ " .. title
|
|
end
|
|
if track.lang then title = string.format('%s\t(%s)', title, track.lang) end
|
|
if #hints > 0 then title = string.format('%s\t[%s]', title, table.concat(hints, ' ')) end
|
|
title = title .. format_flags(track)
|
|
return title
|
|
end
|
|
|
|
local function select(conf)
|
|
for k, v in ipairs(conf.items) do
|
|
table.insert(menu_content.list, { index = k, content = v })
|
|
end
|
|
|
|
if conf.default_item then
|
|
menu_content.current_i = conf.default_item
|
|
end
|
|
|
|
function menu:submit(value)
|
|
conf.submit(value)
|
|
end
|
|
end
|
|
|
|
local function select_track(property, type, error)
|
|
local tracks = {}
|
|
local items = {}
|
|
local default_item
|
|
local track_id = mp.get_property_native(property)
|
|
|
|
for _, track in ipairs(mp.get_property_native("track-list")) do
|
|
if track.type == type then
|
|
tracks[#tracks + 1] = track
|
|
items[#items + 1] = format_track(track, type)
|
|
|
|
if track.id == track_id then
|
|
default_item = #items
|
|
end
|
|
end
|
|
end
|
|
|
|
if #items == 0 then
|
|
mp.commandv("show-text", error)
|
|
return
|
|
end
|
|
|
|
select({
|
|
items = items,
|
|
default_item = default_item,
|
|
submit = function (tbl)
|
|
mp.command("set " .. property .. " " ..
|
|
(tracks[tbl.index].selected and "no" or tracks[tbl.index].id))
|
|
end,
|
|
})
|
|
end
|
|
|
|
local function subtitle_line(data, codec)
|
|
local sub_lines = {}
|
|
local sub_times = {}
|
|
local default_item
|
|
local delay = mp.get_property_native("sub-delay")
|
|
local time_pos = mp.get_property_native("time-pos") - delay
|
|
local duration = mp.get_property_native("duration", math.huge)
|
|
local sub_content = {}
|
|
|
|
-- Strip HTML and ASS tags and process subtitles
|
|
for line in data:gmatch("[^\n]+") do
|
|
-- Clean up tags
|
|
local sub_line = line:gsub("<.->", "") -- Strip HTML tags
|
|
:gsub("\\h+", " ") -- Replace '\h' tag
|
|
:gsub("{[\\=].-}", "") -- Remove ASS formatting
|
|
:gsub(".-]", "", 1) -- Remove time info prefix
|
|
:gsub("^%s*(.-)%s*$", "%1") -- Strip whitespace
|
|
:gsub("^m%s[mbl%s%-%d%.]+$", "") -- Remove graphics code
|
|
|
|
if codec == "subrip" or (sub_line ~= "" and sub_line:match("^%s+$") == nil) then
|
|
local sub_time = line:match("%d+") * 60 + line:match(":([%d%.]*)")
|
|
local time_seconds = math.floor(sub_time)
|
|
sub_content[time_seconds] = sub_content[time_seconds] or {}
|
|
sub_content[time_seconds][sub_line] = true
|
|
end
|
|
end
|
|
|
|
-- Process all timestamps and content into selectable subtitle list
|
|
for time_seconds, contents in pairs(sub_content) do
|
|
for sub_line in pairs(contents) do
|
|
sub_times[#sub_times + 1] = time_seconds
|
|
sub_lines[#sub_lines + 1] = format_time(time_seconds, duration) .. " " .. sub_line
|
|
end
|
|
end
|
|
|
|
-- Generate time -> subtitle mapping
|
|
local time_to_lines = {}
|
|
for i = 1, #sub_times do
|
|
local time = sub_times[i]
|
|
local line = sub_lines[i]
|
|
|
|
if not time_to_lines[time] then
|
|
time_to_lines[time] = {}
|
|
end
|
|
table.insert(time_to_lines[time], line)
|
|
end
|
|
|
|
-- Sort by timestamp
|
|
local sorted_sub_times = {}
|
|
for i = 1, #sub_times do
|
|
sorted_sub_times[i] = sub_times[i]
|
|
end
|
|
table.sort(sorted_sub_times)
|
|
|
|
-- Use a helper table to avoid duplicates
|
|
local added_times = {}
|
|
|
|
-- Rebuild sub_lines and sub_times based on the sorted timestamps
|
|
local sorted_sub_lines = {}
|
|
for _, sub_time in ipairs(sorted_sub_times) do
|
|
-- Iterate over all subtitle content for this timestamp
|
|
if not added_times[sub_time] then
|
|
added_times[sub_time] = true
|
|
for _, line in ipairs(time_to_lines[sub_time]) do
|
|
table.insert(sorted_sub_lines, line)
|
|
end
|
|
end
|
|
end
|
|
|
|
-- Use the sorted subtitle list
|
|
sub_lines = sorted_sub_lines
|
|
sub_times = sorted_sub_times
|
|
|
|
-- Get the default item (last subtitle before current time position)
|
|
for i, sub_time in ipairs(sub_times) do
|
|
if sub_time <= time_pos then
|
|
default_item = i
|
|
end
|
|
end
|
|
|
|
return sub_lines, sub_times, default_item
|
|
end
|
|
|
|
function hide_osc()
|
|
if is_empty(mp.get_property("path")) and not is_older_than_v0_36 then
|
|
osc_visibility = mp.get_property_native("user-data/osc/visibility")
|
|
|
|
if osc_visibility == "auto" or osc_visibility == "always" then
|
|
mp.command("script-message osc-visibility never no_osd")
|
|
end
|
|
end
|
|
|
|
if uosc_available then
|
|
local disable_elements = "window_border, top_bar, timeline, controls, volume, idle_indicator, audio_indicator, buffering_indicator, pause_indicator"
|
|
mp.commandv('script-message-to', 'uosc', 'disable-elements', mp.get_script_name(), disable_elements)
|
|
end
|
|
end
|
|
|
|
mp.register_script_message("show-command-palette", function (name)
|
|
menu_content.list = {}
|
|
menu_content.current_i = 1
|
|
menu.search_heading = name
|
|
menu.filter_by_fields = { "content" }
|
|
em.get_line = original_get_line_func
|
|
|
|
if menu.is_active then
|
|
menu:set_active(false)
|
|
return
|
|
end
|
|
|
|
if name == "Command Palette" then
|
|
local menu_items = {}
|
|
local bindings = utils.parse_json(mp.get_property("input-bindings"))
|
|
|
|
local items = {
|
|
"Playlist",
|
|
"Tracks",
|
|
"Video Tracks",
|
|
"Audio Tracks",
|
|
"Subtitle Tracks",
|
|
"Secondary Subtitle",
|
|
"Subtitle Line",
|
|
"Chapters",
|
|
"Editions",
|
|
"Profiles",
|
|
"Bindings",
|
|
"Commands",
|
|
"Properties",
|
|
"Options",
|
|
"Audio Devices",
|
|
"Blu-ray Titles",
|
|
"Stream Quality",
|
|
"Aspect Ratio",
|
|
"Command Palette",
|
|
"Recent Files",
|
|
}
|
|
|
|
for _, item in ipairs(items) do
|
|
local found = false
|
|
|
|
for _, binding in ipairs(bindings) do
|
|
if contains(binding.cmd, "show-command-palette") and
|
|
(contains(binding.cmd, '"' .. item .. '"') or
|
|
contains(binding.cmd, "'" .. item .. "'")) then
|
|
|
|
table.insert(menu_items, { name = item, key = binding.key, cmd = binding.cmd })
|
|
found = true
|
|
break
|
|
end
|
|
end
|
|
|
|
if not found then
|
|
local cmd = "script-message-to command_palette show-command-palette '" .. item .. "'"
|
|
table.insert(menu_items, { name = item, key = "", cmd = cmd })
|
|
end
|
|
end
|
|
|
|
menu_content.list = menu_items
|
|
|
|
function menu:submit(tbl)
|
|
mp.command(tbl.cmd)
|
|
end
|
|
|
|
menu.filter_by_fields = {'name', 'key'}
|
|
em.get_line = command_palette_get_line
|
|
elseif 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
|
|
if contains(v.comment, "custom-menu: ") then
|
|
v.comment = replace(v.comment, "custom-menu: ", "")
|
|
end
|
|
|
|
if contains(v.comment, "menu: ") then
|
|
v.comment = replace(v.comment, "menu: ", "")
|
|
end
|
|
|
|
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(tbl)
|
|
mp.command(tbl.cmd)
|
|
end
|
|
|
|
menu.filter_by_fields = {'cmd', 'key', 'comment'}
|
|
em.get_line = binding_get_line
|
|
elseif name == "Chapters" then
|
|
local default_index = mp.get_property_native("chapter")
|
|
|
|
if not default_index then
|
|
mp.commandv("show-text", "Chapter: (unavailable)")
|
|
return
|
|
end
|
|
|
|
local duration = mp.get_property_native("duration", math.huge)
|
|
|
|
for i, chapter in ipairs(mp.get_property_native("chapter-list")) do
|
|
table.insert(menu_content.list, { index = i, content = format_time(chapter.time, duration) .. " "
|
|
.. chapter.title or ("Chapter " .. string.format("%02.f", i))})
|
|
end
|
|
|
|
menu_content.current_i = default_index + 1
|
|
|
|
function menu:submit(tbl)
|
|
mp.set_property_number("chapter", tbl.index - 1)
|
|
end
|
|
elseif name == "Editions" then
|
|
local default_index = mp.get_property_native("current-edition")
|
|
|
|
if not default_index then
|
|
mp.commandv("show-text", "Edition: (unavailable)")
|
|
return
|
|
end
|
|
|
|
for i, edition in ipairs(mp.get_property_native("edition-list")) do
|
|
table.insert(menu_content.list, { index = i, content = edition.title or
|
|
("Edition " .. string.format("%02.f", i))})
|
|
end
|
|
|
|
menu_content.current_i = default_index + 1
|
|
|
|
function menu:submit(tbl)
|
|
mp.set_property_number("edition", tbl.index - 1)
|
|
end
|
|
elseif name == "Playlist" then
|
|
local count = mp.get_property_number("playlist-count")
|
|
local show = mp.get_property_native("osd-playlist-entry")
|
|
if count == 0 then return end
|
|
|
|
for i = 0, (count - 1) do
|
|
local text = mp.get_property("playlist/" .. i .. "/title")
|
|
|
|
if not text or show ~= "title" 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(tbl)
|
|
mp.set_property_number("playlist-pos", tbl.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(tbl)
|
|
print(tbl.content)
|
|
local cmd = string.match(tbl.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(tbl)
|
|
mp.commandv('script-message-to', 'console', 'type', "print-text ${" .. tbl.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(tbl)
|
|
print(tbl.content)
|
|
local prop = string.match(tbl.content, '%S+')
|
|
mp.commandv("script-message-to", "console", "type", "set " .. prop .. " ")
|
|
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(tbl)
|
|
mp.command("show-text " .. tbl.content);
|
|
mp.command("apply-profile " .. tbl.content);
|
|
end
|
|
elseif name == "Audio Devices" then
|
|
local devices = utils.parse_json(mp.get_property("audio-device-list"))
|
|
local current_name = mp.get_property("audio-device")
|
|
|
|
for k, v in ipairs(devices) do
|
|
table.insert(menu_content.list, { index = k, name = v.name, content = v.description })
|
|
|
|
if v.name == current_name then
|
|
menu_content.current_i = k
|
|
end
|
|
end
|
|
|
|
function menu:submit(tbl)
|
|
mp.commandv("set", "audio-device", tbl.name)
|
|
mp.commandv("show-text", "audio-device: " .. tbl.content)
|
|
end
|
|
elseif name == "Aspect Ratio" then
|
|
local current_ar = mp.get_property_number("video-aspect-override")
|
|
|
|
for k, v in ipairs(split(o.aspect_ratios, ",")) do
|
|
local display_name = v
|
|
|
|
if display_name == "0" then display_name = "0 (square pixels)" end
|
|
if display_name == "-1" then display_name = "-1 (original)" end
|
|
|
|
table.insert(menu_content.list, { index = k, content = display_name, value = v })
|
|
|
|
local w, h = string.match(v, "^([0-9.]+):([0-9.]+)$")
|
|
|
|
if w and h then
|
|
local current_ar_truncated = tonumber(string.format("%.3f", current_ar))
|
|
local ar_truncated = tonumber(string.format("%.3f", w / h))
|
|
|
|
if current_ar_truncated == ar_truncated then
|
|
menu_content.current_i = k
|
|
end
|
|
elseif v == tostring(current_ar) then
|
|
menu_content.current_i = k
|
|
end
|
|
end
|
|
|
|
function menu:submit(tbl)
|
|
mp.command("set video-aspect-override " .. tbl.value)
|
|
end
|
|
elseif name == "Stream Quality" then
|
|
local ytdl_format = mp.get_property_native('ytdl-format')
|
|
|
|
for k, v in ipairs(split(o.stream_quality_options, ",")) do
|
|
local format = 'bestvideo[height<=?' .. v .. ']+bestaudio/best[height<=?' .. v .. ']'
|
|
table.insert(menu_content.list, { index = k, content = v .. 'p', value = format })
|
|
|
|
if format == ytdl_format then
|
|
menu_content.current_i = k
|
|
end
|
|
end
|
|
|
|
function menu:submit(tbl)
|
|
mp.set_property('ytdl-format', tbl.value)
|
|
mp.commandv("show-text", "Stream Quality: " .. tbl.content)
|
|
|
|
local duration = mp.get_property_native('duration')
|
|
local time_pos = mp.get_property('time-pos')
|
|
|
|
mp.command('playlist-play-index current')
|
|
|
|
if duration and duration > 0 then
|
|
local function seeker()
|
|
mp.commandv('seek', time_pos, 'absolute')
|
|
mp.unregister_event(seeker)
|
|
end
|
|
|
|
mp.register_event('file-loaded', seeker)
|
|
end
|
|
end
|
|
elseif name == "Tracks" then
|
|
local tracks = {}
|
|
|
|
for i, track in ipairs(mp.get_property_native("track-list")) do
|
|
local type = track.image and "I" or track.type
|
|
|
|
if type == "video" then track_type = "V" end
|
|
if type == "audio" then track_type = "A" end
|
|
if type == "sub" then track_type = "S" end
|
|
|
|
tracks[i] = track_type .. ": " .. format_track(track, type)
|
|
end
|
|
|
|
if #tracks == 0 then
|
|
mp.commandv("show-text", "No available tracks")
|
|
return
|
|
end
|
|
|
|
select({
|
|
items = tracks,
|
|
submit = function (tbl)
|
|
local track = mp.get_property_native("track-list/" .. tbl.index - 1)
|
|
|
|
if track then
|
|
mp.command("set " .. track.type .. " " .. (track.selected and "no" or track.id))
|
|
end
|
|
end,
|
|
})
|
|
elseif name == "Audio Tracks" then
|
|
if o.use_mediainfo then
|
|
local mi = get_media_info()
|
|
if mi == nil then return end
|
|
local tracks = split(mi .. "\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(tbl)
|
|
mp.command("set aid " .. ((tbl.index == id) and 'no' or tbl.index))
|
|
end
|
|
else
|
|
select_track("aid", "audio", "No available audio tracks")
|
|
end
|
|
elseif name == "Subtitle Tracks" then
|
|
if o.use_mediainfo then
|
|
local mi = get_media_info()
|
|
if mi == nil then return end
|
|
local tracks = split(mi .. "\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(tbl)
|
|
mp.command("set sid " .. ((tbl.index == id) and 'no' or tbl.index))
|
|
end
|
|
else
|
|
select_track("sid", "sub", "No available subtitle tracks")
|
|
end
|
|
elseif name == "Secondary Subtitle" then
|
|
select_track("secondary-sid", "sub", "No available subtitle tracks")
|
|
elseif name == "Recent Files" then
|
|
mp.command("script-message open-recent-menu command-palette")
|
|
return
|
|
elseif name == "Video Tracks" then
|
|
if o.use_mediainfo then
|
|
local mi = get_media_info()
|
|
if mi == nil then return end
|
|
local tracks = split(mi .. "\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(tbl)
|
|
mp.command("set vid " .. ((tbl.index == id) and 'no' or tbl.index))
|
|
end
|
|
else
|
|
select_track("vid", "video", "No available video tracks")
|
|
end
|
|
elseif name == "Blu-ray Titles" then
|
|
if #BluRayTitles == 0 then
|
|
return
|
|
end
|
|
|
|
local items = {}
|
|
|
|
for k, v in ipairs(BluRayTitles) do
|
|
table.insert(items, "Title " .. k .. " " .. v)
|
|
end
|
|
|
|
select({
|
|
items = items,
|
|
submit = function (tbl)
|
|
mp.commandv("loadfile", "bd://" .. (tbl.index - 1))
|
|
end,
|
|
})
|
|
elseif name == "Subtitle Line" then
|
|
local sub = mp.get_property_native("current-tracks/sub")
|
|
|
|
if sub == nil then
|
|
mp.commandv("show-text", "No subtitle is loaded")
|
|
return
|
|
end
|
|
|
|
if sub.external and sub["external-filename"]:find("^edl://") then
|
|
sub["external-filename"] = sub["external-filename"]:match('https?://.*')
|
|
or sub["external-filename"]
|
|
end
|
|
|
|
local r = mp.command_native({
|
|
name = "subprocess",
|
|
capture_stdout = true,
|
|
args = sub.external
|
|
and {"ffmpeg", "-loglevel", "error", "-i", sub["external-filename"],
|
|
"-f", "lrc", "-map_metadata", "-1", "-fflags", "+bitexact", "-"}
|
|
or {"ffmpeg", "-loglevel", "error", "-i", mp.get_property("path"),
|
|
"-map", "s:" .. sub["id"] - 1, "-f", "lrc", "-map_metadata",
|
|
"-1", "-fflags", "+bitexact", "-"}
|
|
})
|
|
|
|
if r.error_string == "init" then
|
|
mp.commandv("show-text", "Failed to extract subtitles: ffmpeg not found")
|
|
return
|
|
elseif r.status ~= 0 then
|
|
mp.commandv("show-text", "Failed to extract subtitles")
|
|
return
|
|
end
|
|
|
|
local delay = mp.get_property_native("sub-delay")
|
|
|
|
local sub_lines, sub_times, default_item = subtitle_line(r.stdout, sub.codec)
|
|
|
|
select({
|
|
items = sub_lines,
|
|
default_item = default_item,
|
|
submit = function (tbl)
|
|
-- Add an offset to seek to the correct line while paused without a video track.
|
|
if mp.get_property_native("current-tracks/video/image") ~= false then
|
|
delay = delay + 0.1
|
|
end
|
|
|
|
mp.commandv("seek", sub_times[tbl.index] + delay, "absolute")
|
|
end,
|
|
})
|
|
else
|
|
if name == nil then
|
|
msg.error("Unknown mode")
|
|
else
|
|
msg.error("Unknown mode: " .. name)
|
|
end
|
|
|
|
return
|
|
end
|
|
|
|
hide_osc()
|
|
menu:init(menu_content)
|
|
end)
|
|
|
|
mp.register_script_message('uosc-version', function(version)
|
|
local major, minor = version:match('^(%d+)%.(%d+)')
|
|
if major and minor and tonumber(major) >= 5 and tonumber(minor) >= 0 then
|
|
uosc_available = true
|
|
end
|
|
end)
|
|
|
|
mp.register_script_message("show-command-palette-json", function (json)
|
|
local menu_data = utils.parse_json(json)
|
|
menu_content.list = {}
|
|
menu_content.current_i = 1
|
|
menu.search_heading = menu_data.title
|
|
menu.filter_by_fields = { "content", "hint", "value_hint" }
|
|
em.get_line = original_get_line_func
|
|
|
|
for k, v in ipairs(menu_data.items) do
|
|
local values = v.value
|
|
|
|
if type(values) == "string" then
|
|
values = { values }
|
|
end
|
|
|
|
table.insert(menu_content.list, {
|
|
index = k,
|
|
content = v.title,
|
|
hint = v.hint,
|
|
values = values,
|
|
value_hint = table.concat(values, " "),
|
|
})
|
|
|
|
if menu_data.selected_index then
|
|
menu_content.current_i = menu_data.selected_index
|
|
end
|
|
end
|
|
|
|
function menu:submit(tbl)
|
|
mp.command_native(tbl.values)
|
|
end
|
|
|
|
hide_osc()
|
|
menu:init(menu_content)
|
|
end)
|