add youtube search, scroll list, user input

This commit is contained in:
ksyasuda
2023-08-17 01:48:10 -07:00
parent 0acbc646b3
commit b42fdf40f1
4 changed files with 1541 additions and 0 deletions

View File

@@ -0,0 +1,293 @@
local mp = require 'mp'
local scroll_list = {
global_style = [[]],
header_style = [[{\q2\fs35\c&00ccff&}]],
list_style = [[{\q2\fs25\c&Hffffff&}]],
wrapper_style = [[{\c&00ccff&\fs16}]],
cursor_style = [[{\c&00ccff&}]],
selected_style = [[{\c&Hfce788&}]],
cursor = [[➤\h]],
indent = [[\h\h\h\h]],
num_entries = 16,
wrap = false,
empty_text = "no entries"
}
--formats strings for ass handling
--this function is based on a similar function from https://github.com/mpv-player/mpv/blob/master/player/lua/console.lua#L110
function scroll_list.ass_escape(str, replace_newline)
if replace_newline == true then replace_newline = "\\\239\187\191n" end
--escape the invalid single characters
str = str:gsub('[\\{}\n]', {
-- There is no escape for '\' in ASS (I think?) but '\' is used verbatim if
-- it isn't followed by a recognised character, so add a zero-width
-- non-breaking space
['\\'] = '\\\239\187\191',
['{'] = '\\{',
['}'] = '\\}',
-- Precede newlines with a ZWNBSP to prevent ASS's weird collapsing of
-- consecutive newlines
['\n'] = '\239\187\191\\N',
})
-- Turn leading spaces into hard spaces to prevent ASS from stripping them
str = str:gsub('\\N ', '\\N\\h')
str = str:gsub('^ ', '\\h')
if replace_newline then
str = str:gsub("\\N", replace_newline)
end
return str
end
--format and return the header string
function scroll_list:format_header_string(str)
return str
end
--appends the entered text to the overlay
function scroll_list:append(text)
if text == nil then return end
self.ass.data = self.ass.data .. text
end
--appends a newline character to the osd
function scroll_list:newline()
self.ass.data = self.ass.data .. '\\N'
end
--re-parses the list into an ass string
--if the list is closed then it flags an update on the next open
function scroll_list:update()
if self.hidden then self.flag_update = true
else self:update_ass() end
end
--prints the header to the overlay
function scroll_list:format_header()
self:append(self.header_style)
self:append(self:format_header_string(self.header))
self:newline()
end
--formats each line of the list and prints it to the overlay
function scroll_list:format_line(index, item)
self:append(self.list_style)
if index == self.selected then self:append(self.cursor_style..self.cursor..self.selected_style)
else self:append(self.indent) end
self:append(item.style)
self:append(item.ass)
self:newline()
end
--refreshes the ass text using the contents of the list
function scroll_list:update_ass()
self.ass.data = self.global_style
self:format_header()
if #self.list < 1 then
self:append(self.empty_text)
self.ass:update()
return
end
local start = 1
local finish = start+self.num_entries-1
--handling cursor positioning
local mid = math.ceil(self.num_entries/2)+1
if self.selected+mid > finish then
local offset = self.selected - finish + mid
--if we've overshot the end of the list then undo some of the offset
if finish + offset > #self.list then
offset = offset - ((finish+offset) - #self.list)
end
start = start + offset
finish = finish + offset
end
--making sure that we don't overstep the boundaries
if start < 1 then start = 1 end
local overflow = finish < #self.list
--this is necessary when the number of items in the dir is less than the max
if not overflow then finish = #self.list end
--adding a header to show there are items above in the list
if start > 1 then self:append(self.wrapper_style..(start-1)..' item(s) above\\N\\N') end
for i=start, finish do
self:format_line(i, self.list[i])
end
if overflow then self:append('\\N'..self.wrapper_style..#self.list-finish..' item(s) remaining') end
self.ass:update()
end
--moves the selector down the list
function scroll_list:scroll_down()
if self.selected < #self.list then
self.selected = self.selected + 1
self:update_ass()
elseif self.wrap then
self.selected = 1
self:update_ass()
end
end
--moves the selector up the list
function scroll_list:scroll_up()
if self.selected > 1 then
self.selected = self.selected - 1
self:update_ass()
elseif self.wrap then
self.selected = #self.list
self:update_ass()
end
end
--moves the selector to the list next page
function scroll_list:move_pagedown()
if #self.list > self.num_entries then
self.selected = self.selected + self.num_entries
if self.selected > #self.list then self.selected = #self.list end
self:update_ass()
end
end
--moves the selector to the list previous page
function scroll_list:move_pageup()
if #self.list > self.num_entries then
self.selected = self.selected - self.num_entries
if self.selected < 1 then self.selected = 1 end
self:update_ass()
end
end
--moves the selector to the list begin
function scroll_list:move_begin()
if #self.list > 1 then
self.selected = 1
self:update_ass()
end
end
--moves the selector to the list end
function scroll_list:move_end()
if #self.list > 1 then
self.selected = #self.list
self:update_ass()
end
end
--adds the forced keybinds
function scroll_list:add_keybinds()
for _,v in ipairs(self.keybinds) do
mp.add_forced_key_binding(v[1], 'dynamic/'..self.ass.id..'/'..v[2], v[3], v[4])
end
end
--removes the forced keybinds
function scroll_list:remove_keybinds()
for _,v in ipairs(self.keybinds) do
mp.remove_key_binding('dynamic/'..self.ass.id..'/'..v[2])
end
end
--opens the list and sets the hidden flag
function scroll_list:open_list()
self.hidden = false
if not self.flag_update then self.ass:update()
else self.flag_update = false ; self:update_ass() end
end
--closes the list and sets the hidden flag
function scroll_list:close_list()
self.hidden = true
self.ass:remove()
end
--modifiable function that opens the list
function scroll_list:open()
if self.hidden then self:add_keybinds() end
self:open_list()
end
--modifiable function that closes the list
function scroll_list:close()
self:remove_keybinds()
self:close_list()
end
--toggles the list
function scroll_list:toggle()
if self.hidden then self:open()
else self:close() end
end
--clears the list in-place
function scroll_list:clear()
local i = 1
while self.list[i] do
self.list[i] = nil
i = i + 1
end
end
--added alias for ipairs(list.list) for lua 5.1
function scroll_list:ipairs()
return ipairs(self.list)
end
--append item to the end of the list
function scroll_list:insert(item)
self.list[#self.list + 1] = item
end
local metatable = {
__index = function(t, key)
if scroll_list[key] ~= nil then return scroll_list[key]
elseif key == "__current" then return t.list[t.selected]
elseif type(key) == "number" then return t.list[key] end
end,
__newindex = function(t, key, value)
if type(key) == "number" then rawset(t.list, key, value)
else rawset(t, key, value) end
end,
__scroll_list = scroll_list,
__len = function(t) return #t.list end,
__ipairs = function(t) return ipairs(t.list) end
}
--creates a new list object
function scroll_list:new()
local vars
vars = {
ass = mp.create_osd_overlay('ass-events'),
hidden = true,
flag_update = true,
header = "header \\N ----------------------------------------------",
list = {},
selected = 1,
keybinds = {
{'DOWN', 'scroll_down', function() vars:scroll_down() end, {repeatable = true}},
{'UP', 'scroll_up', function() vars:scroll_up() end, {repeatable = true}},
{'PGDWN', 'move_pagedown', function() vars:move_pagedown() end, {}},
{'PGUP', 'move_pageup', function() vars:move_pageup() end, {}},
{'HOME', 'move_begin', function() vars:move_begin() end, {}},
{'END', 'move_end', function() vars:move_end() end, {}},
{'ESC', 'close_browser', function() vars:close() end, {}}
}
}
return setmetatable(vars, metatable)
end
return scroll_list:new()

View File

@@ -0,0 +1,126 @@
--[[
This is a module designed to interface with mpv-user-input
https://github.com/CogentRedTester/mpv-user-input
Loading this script as a module will return a table with two functions to format
requests to get and cancel user-input requests. See the README for details.
Alternatively, developers can just paste these functions directly into their script,
however this is not recommended as there is no guarantee that the formatting of
these requests will remain the same for future versions of user-input.
]]
local API_VERSION = "0.1.0"
local mp = require 'mp'
local msg = require "mp.msg"
local utils = require 'mp.utils'
local mod = {}
local name = mp.get_script_name()
local counter = 1
local function pack(...)
local t = {...}
t.n = select("#", ...)
return t
end
local request_mt = {}
-- ensures the option tables are correctly formatted based on the input
local function format_options(options, response_string)
return {
response = response_string,
version = API_VERSION,
id = name..'/'..(options.id or ""),
source = name,
request_text = ("[%s] %s"):format(options.source or name, options.request_text or options.text or "requesting user input:"),
default_input = options.default_input,
cursor_pos = tonumber(options.cursor_pos),
queueable = options.queueable and true,
replace = options.replace and true
}
end
-- cancels the request
function request_mt:cancel()
assert(self.uid, "request object missing UID")
mp.commandv("script-message-to", "user_input", "cancel-user-input/uid", self.uid)
end
-- updates the options for the request
function request_mt:update(options)
assert(self.uid, "request object missing UID")
options = utils.format_json( format_options(options) )
mp.commandv("script-message-to", "user_input", "update-user-input/uid", self.uid, options)
end
-- sends a request to ask the user for input using formatted options provided
-- creates a script message to recieve the response and call fn
function mod.get_user_input(fn, options, ...)
options = options or {}
local response_string = name.."/__user_input_request/"..counter
counter = counter + 1
local request = {
uid = response_string,
passthrough_args = pack(...),
callback = fn,
pending = true
}
-- create a callback for user-input to respond to
mp.register_script_message(response_string, function(response)
mp.unregister_script_message(response_string)
request.pending = false
response = utils.parse_json(response)
request.callback(response.line, response.err, unpack(request.passthrough_args, 1, request.passthrough_args.n))
end)
-- send the input command
options = utils.format_json( format_options(options, response_string) )
mp.commandv("script-message-to", "user_input", "request-user-input", options)
return setmetatable(request, { __index = request_mt })
end
-- runs the request synchronously using coroutines
-- takes the option table and an optional coroutine resume function
function mod.get_user_input_co(options, co_resume)
local co, main = coroutine.running()
assert(not main and co, "get_user_input_co must be run from within a coroutine")
local uid = {}
local request = mod.get_user_input(function(line, err)
if co_resume then
co_resume(uid, line, err)
else
local success, er = coroutine.resume(co, uid, line, err)
if not success then
msg.warn(debug.traceback(co))
msg.error(er)
end
end
end, options)
-- if the uid was not sent then the coroutine was resumed by the user.
-- we will treat this as a cancellation request
local success, line, err = coroutine.yield(request)
if success ~= uid then
request:cancel()
request.callback = function() end
return nil, "cancelled"
end
return line, err
end
-- sends a request to cancel all input requests with the given id
function mod.cancel_user_input(id)
id = name .. '/' .. (id or "")
mp.commandv("script-message-to", "user_input", "cancel-user-input/id", id)
end
return mod