summaryrefslogtreecommitdiff
path: root/mac/.config/mpv/scripts/command_palette.lua
diff options
context:
space:
mode:
Diffstat (limited to 'mac/.config/mpv/scripts/command_palette.lua')
-rw-r--r--mac/.config/mpv/scripts/command_palette.lua1229
1 files changed, 1229 insertions, 0 deletions
diff --git a/mac/.config/mpv/scripts/command_palette.lua b/mac/.config/mpv/scripts/command_palette.lua
new file mode 100644
index 0000000..adbd330
--- /dev/null
+++ b/mac/.config/mpv/scripts/command_palette.lua
@@ -0,0 +1,1229 @@
+-- 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 = 2
+local BluRayTitles = {}
+local dpiScale = 0
+local originalFontSize = o.font_size
+
+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
+
+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 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 fix_codec(value)
+ if contains(value, "hdmv_pgs_subtitle") then
+ value = replace(value, "hdmv_pgs_subtitle", "pgs")
+ end
+
+ return value:upper()
+end
+
+local function get_language(lng)
+ if lng == nil or lng == "" then
+ return lng
+ end
+
+ if lng == "ara" then
+ lng = "Arabic"
+ end
+ if lng == "ben" then
+ lng = "Bangla"
+ end
+ if lng == "bng" then
+ lng = "Bangla"
+ end
+ if lng == "chi" then
+ lng = "Chinese"
+ end
+ if lng == "zho" then
+ lng = "Chinese"
+ end
+ if lng == "eng" then
+ lng = "English"
+ end
+ if lng == "fre" then
+ lng = "French"
+ end
+ if lng == "fra" then
+ lng = "French"
+ end
+ if lng == "ger" then
+ lng = "German"
+ end
+ if lng == "deu" then
+ lng = "German"
+ end
+ if lng == "hin" then
+ lng = "Hindi"
+ end
+ if lng == "ita" then
+ lng = "Italian"
+ end
+ if lng == "jpn" then
+ lng = "Japanese"
+ end
+ if lng == "kor" then
+ lng = "Korean"
+ end
+ if lng == "msa" then
+ lng = "Malay"
+ end
+ if lng == "por" then
+ lng = "Portuguese"
+ end
+ if lng == "pan" then
+ lng = "Punjabi"
+ end
+ if lng == "rus" then
+ lng = "Russian"
+ end
+ if lng == "spa" then
+ lng = "Spanish"
+ end
+ if lng == "und" then
+ lng = "Undetermined"
+ end
+
+ return lng
+end
+
+local function format_track(track)
+ local lng = get_language(track.lang)
+ return (track.selected and "●" or "○")
+ .. (
+ (lng and lng .. " " or "")
+ .. fix_codec(track.codec and track.codec .. " " or "")
+ .. (track["demux-w"] and track["demux-w"] .. "x" .. track["demux-h"] .. " " or "")
+ .. (track["demux-fps"] and not track.image and string.format("%.4f", track["demux-fps"]):gsub("%.?0*$", "") .. " fps " or "")
+ .. (track["demux-channel-count"] and track["demux-channel-count"] .. "ch " or "")
+ .. (track["codec-profile"] and track.type == "audio" and track["codec-profile"] .. " " or "")
+ .. (track["demux-samplerate"] and track["demux-samplerate"] / 1000 .. " kHz " or "")
+ .. (track["demux-bitrate"] and string.format("%.0f", track["demux-bitrate"] / 1000) .. " kbps " or "")
+ .. (track["hls-bitrate"] and string.format("%.0f", track["hls-bitrate"] / 1000) .. " HLS kbps " or "")
+ ):sub(1, -2)
+ .. format_flags(track)
+ .. (track.title and " " .. track.title or "")
+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)
+
+ 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
+
+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)
+ if dpiScale == 0 then
+ dpiScale = mp.get_property_native("display-hidpi-scale", 1)
+ end
+
+ o.font_size = originalFontSize * dpiScale
+
+ 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 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",
+ "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 }
+ )
+ end
+
+ menu_content.current_i = default_index + 1
+
+ function menu:submit(tbl)
+ mp.set_property_number("chapter", tbl.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(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
+ type = "V"
+ end
+ if type == "audio" then
+ type = "A"
+ end
+ if type == "sub" then
+ type = "S"
+ end
+
+ tracks[i] = type .. ": " .. format_track(track)
+ 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
+ local frontend = mp.get_property_native("user-data/frontend/name")
+
+ if frontend == "mpv.net" then
+ mp.command("script-message show-recent-in-command-palette")
+ else
+ mp.command("script-message open-recent-menu command-palette")
+ end
+
+ 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 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)
+
+ -- Strip HTML and ASS tags.
+ for line in r.stdout:gsub("<.->", ""):gsub("{\\.-}", ""):gmatch("[^\n]+") do
+ -- ffmpeg outputs LRCs with minutes > 60 instead of adding hours.
+ sub_times[#sub_times + 1] = line:match("%d+") * 60 + line:match(":([%d%.]*)")
+ sub_lines[#sub_lines + 1] = format_time(sub_times[#sub_times], duration) .. " " .. line:gsub(".*]", "", 1)
+
+ if sub_times[#sub_times] <= time_pos then
+ default_item = #sub_times
+ end
+ end
+
+ 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)
+ if dpiScale == 0 then
+ dpiScale = mp.get_property_native("display-hidpi-scale", 1)
+ end
+
+ o.font_size = originalFontSize * dpiScale
+
+ 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)