add youtube search, scroll list, user input
This commit is contained in:
293
script-modules/scroll-list.lua
Normal file
293
script-modules/scroll-list.lua
Normal 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()
|
||||
126
script-modules/user-input-module.lua
Normal file
126
script-modules/user-input-module.lua
Normal 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
|
||||
Reference in New Issue
Block a user