From 51ecbeab02998cb017432ce5e9b20252d6d02d42 Mon Sep 17 00:00:00 2001 From: ksyasuda Date: Mon, 20 Jan 2025 16:01:00 -0800 Subject: [PATCH] remove youtube-search --- script-modules/user-input-module.lua | 126 ----- scripts/user-input.lua | 749 --------------------------- scripts/youtube-search.lua | 373 ------------- 3 files changed, 1248 deletions(-) delete mode 100644 script-modules/user-input-module.lua delete mode 100644 scripts/user-input.lua delete mode 100644 scripts/youtube-search.lua diff --git a/script-modules/user-input-module.lua b/script-modules/user-input-module.lua deleted file mode 100644 index f15d5c4..0000000 --- a/script-modules/user-input-module.lua +++ /dev/null @@ -1,126 +0,0 @@ ---[[ - 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 \ No newline at end of file diff --git a/scripts/user-input.lua b/scripts/user-input.lua deleted file mode 100644 index 1e66d75..0000000 --- a/scripts/user-input.lua +++ /dev/null @@ -1,749 +0,0 @@ -local mp = require 'mp' -local msg = require 'mp.msg' -local utils = require 'mp.utils' -local options = require 'mp.options' - --- Default options -local opts = { - -- All drawing is scaled by this value, including the text borders and the - -- cursor. Change it if you have a high-DPI display. - scale = 1, - -- Set the font used for the REPL and the console. This probably doesn't - -- have to be a monospaced font. - font = "", - -- Set the font size used for the REPL and the console. This will be - -- multiplied by "scale." - font_size = 16 -} - -options.read_options(opts, "user_input") - -local API_VERSION = "0.1.0" -local API_MAJOR_MINOR = API_VERSION:match("%d+%.%d+") - -local co = nil -local queue = {} -local active_ids = {} -local histories = {} -local request = nil - -local line = '' - ---[[ - The below code is a modified implementation of text input from mpv's console.lua: - https://github.com/mpv-player/mpv/blob/7ca14d646c7e405f3fb1e44600e2a67fc4607238/player/lua/console.lua - - Modifications: - removed support for log messages, sending commands, tab complete, help commands - removed update timer - Changed esc key to call handle_esc function - handle_esc and handle_enter now resume the main coroutine with a response table - made history specific to request ids - localised all functions - reordered some to fit - keybindings use new names -]] - -- - -------------------------------START ORIGINAL MPV CODE----------------------------------- ----------------------------------------------------------------------------------------- ----------------------------------------------------------------------------------------- ----------------------------------------------------------------------------------------- ----------------------------------------------------------------------------------------- - --- Copyright (C) 2019 the mpv developers --- --- Permission to use, copy, modify, and/or distribute this software for any --- purpose with or without fee is hereby granted, provided that the above --- copyright notice and this permission notice appear in all copies. --- --- THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES --- WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF --- MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY --- SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES --- WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION --- OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN --- CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. - -local assdraw = require 'mp.assdraw' - -local function detect_platform() - local o = {} - -- Kind of a dumb way of detecting the platform but whatever - if mp.get_property_native('options/vo-mmcss-profile', o) ~= o then - return 'windows' - elseif mp.get_property_native('options/macos-force-dedicated-gpu', o) ~= o then - return 'macos' - elseif os.getenv('WAYLAND_DISPLAY') then - return 'wayland' - end - return 'x11' -end - --- Pick a better default font for Windows and macOS -local platform = detect_platform() -if platform == 'windows' then - opts.font = 'Consolas' -elseif platform == 'macos' then - opts.font = 'Menlo' -else - opts.font = 'monospace' -end - -local repl_active = false -local insert_mode = false -local cursor = 1 -local key_bindings = {} -local global_margin_y = 0 - --- Escape a string for verbatim display on the OSD -local function ass_escape(str) - -- 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 - str = str:gsub('\\', '\\\239\187\191') - str = str:gsub('{', '\\{') - str = str:gsub('}', '\\}') - -- Precede newlines with a ZWNBSP to prevent ASS's weird collapsing of - -- consecutive newlines - str = str:gsub('\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') - return str -end - --- Render the REPL and console as an ASS OSD -local function update() - local dpi_scale = mp.get_property_native("display-hidpi-scale", 1.0) - - dpi_scale = dpi_scale * opts.scale - - local screenx, screeny, aspect = mp.get_osd_size() - screenx = screenx / dpi_scale - screeny = screeny / dpi_scale - - -- Clear the OSD if the REPL is not active - if not repl_active then - mp.set_osd_ass(screenx, screeny, '') - return - end - - local ass = assdraw.ass_new() - local style = '{\\r' .. '\\1a&H00&\\3a&H00&\\4a&H99&' .. - '\\1c&Heeeeee&\\3c&H111111&\\4c&H000000&' .. '\\fn' .. - opts.font .. '\\fs' .. opts.font_size .. - '\\bord1\\xshad0\\yshad1\\fsp0\\q1}' - - local queue_style = '{\\r' .. '\\1a&H00&\\3a&H00&\\4a&H99&' .. - '\\1c&Heeeeee&\\3c&H111111&\\4c&H000000&' .. '\\fn' .. - opts.font .. '\\fs' .. opts.font_size .. - '\\c&H66ccff&' .. - '\\bord1\\xshad0\\yshad1\\fsp0\\q1}' - - -- Create the cursor glyph as an ASS drawing. ASS will draw the cursor - -- inline with the surrounding text, but it sets the advance to the width - -- of the drawing. So the cursor doesn't affect layout too much, make it as - -- thin as possible and make it appear to be 1px wide by giving it 0.5px - -- horizontal borders. - local cheight = opts.font_size * 8 - local cglyph = '{\\r' .. '\\1a&H44&\\3a&H44&\\4a&H99&' .. - '\\1c&Heeeeee&\\3c&Heeeeee&\\4c&H000000&' .. - '\\xbord0.5\\ybord0\\xshad0\\yshad1\\p4\\pbo24}' .. - 'm 0 0 l 1 0 l 1 ' .. cheight .. ' l 0 ' .. cheight .. - '{\\p0}' - local before_cur = ass_escape(line:sub(1, cursor - 1)) - local after_cur = ass_escape(line:sub(cursor)) - - ass:new_event() - ass:an(1) - ass:pos(2, screeny - 2 - global_margin_y * screeny) - - if (#queue == 2) then - ass:append(queue_style .. - string.format("There is 1 more request queued\\N")) - elseif (#queue > 2) then - ass:append(queue_style .. - string.format("There are %d more requests queued\\N", - #queue - 1)) - end - ass:append(style .. request.text .. '\\N') - ass:append('> ' .. before_cur) - ass:append(cglyph) - ass:append(style .. after_cur) - - -- Redraw the cursor with the REPL text invisible. This will make the - -- cursor appear in front of the text. - ass:new_event() - ass:an(1) - ass:pos(2, screeny - 2) - ass:append(style .. '{\\alpha&HFF&}> ' .. before_cur) - ass:append(cglyph) - ass:append(style .. '{\\alpha&HFF&}' .. after_cur) - - mp.set_osd_ass(screenx, screeny, ass.text) -end - --- Naive helper function to find the next UTF-8 character in 'str' after 'pos' --- by skipping continuation bytes. Assumes 'str' contains valid UTF-8. -local function next_utf8(str, pos) - if pos > str:len() then return pos end - repeat - pos = pos + 1 - until pos > str:len() or str:byte(pos) < 0x80 or - str:byte(pos) > 0xbf - return pos -end - --- As above, but finds the previous UTF-8 charcter in 'str' before 'pos' -local function prev_utf8(str, pos) - if pos <= 1 then return pos end - repeat - pos = pos - 1 - until pos <= 1 or str:byte(pos) < 0x80 or str:byte(pos) > - 0xbf - return pos -end - --- Insert a character at the current cursor position (any_unicode) -local function handle_char_input(c) - if insert_mode then - line = line:sub(1, cursor - 1) .. c .. line:sub(next_utf8(line, cursor)) - else - line = line:sub(1, cursor - 1) .. c .. line:sub(cursor) - end - cursor = cursor + #c - update() -end - --- Remove the character behind the cursor (Backspace) -local function handle_backspace() - if cursor <= 1 then return end - local prev = prev_utf8(line, cursor) - line = line:sub(1, prev - 1) .. line:sub(cursor) - cursor = prev - update() -end - --- Remove the character in front of the cursor (Del) -local function handle_del() - if cursor > line:len() then return end - line = line:sub(1, cursor - 1) .. line:sub(next_utf8(line, cursor)) - update() -end - --- Toggle insert mode (Ins) -local function handle_ins() insert_mode = not insert_mode end - --- Move the cursor to the next character (Right) -local function next_char(amount) - cursor = next_utf8(line, cursor) - update() -end - --- Move the cursor to the previous character (Left) -local function prev_char(amount) - cursor = prev_utf8(line, cursor) - update() -end - --- Clear the current line (Ctrl+C) -local function clear() - line = '' - cursor = 1 - insert_mode = false - request.history.pos = #request.history.list + 1 - update() -end - --- Close the REPL if the current line is empty, otherwise do nothing (Ctrl+D) -local function maybe_exit() - if line == '' then - else - handle_del() - end -end - -local function handle_esc() coroutine.resume(co, { line = nil, err = "exited" }) end - --- Run the current command and clear the line (Enter) -local function handle_enter() - if request.history.list[#request.history.list] ~= line and line ~= "" then - request.history.list[#request.history.list + 1] = line - end - coroutine.resume(co, { line = line }) -end - --- Go to the specified position in the command history -local function go_history(new_pos) - local old_pos = request.history.pos - request.history.pos = new_pos - - -- Restrict the position to a legal value - if request.history.pos > #request.history.list + 1 then - request.history.pos = #request.history.list + 1 - elseif request.history.pos < 1 then - request.history.pos = 1 - end - - -- Do nothing if the history position didn't actually change - if request.history.pos == old_pos then return end - - -- If the user was editing a non-history line, save it as the last history - -- entry. This makes it much less frustrating to accidentally hit Up/Down - -- while editing a line. - if old_pos == #request.history.list + 1 and line ~= '' and - request.history.list[#request.history.list] ~= line then - request.history.list[#request.history.list + 1] = line - end - - -- Now show the history line (or a blank line for #history + 1) - if request.history.pos <= #request.history.list then - line = request.history.list[request.history.pos] - else - line = '' - end - cursor = line:len() + 1 - insert_mode = false - update() -end - --- Go to the specified relative position in the command history (Up, Down) -local function move_history(amount) go_history(request.history.pos + amount) end - --- Go to the first command in the command history (PgUp) -local function handle_pgup() go_history(1) end - --- Stop browsing history and start editing a blank line (PgDown) -local function handle_pgdown() go_history(#request.history.list + 1) end - --- Move to the start of the current word, or if already at the start, the start --- of the previous word. (Ctrl+Left) -local function prev_word() - -- This is basically the same as next_word() but backwards, so reverse the - -- string in order to do a "backwards" find. This wouldn't be as annoying - -- to do if Lua didn't insist on 1-based indexing. - cursor = line:len() - - select(2, line:reverse() - :find('%s*[^%s]*', line:len() - cursor + 2)) + 1 - update() -end - --- Move to the end of the current word, or if already at the end, the end of --- the next word. (Ctrl+Right) -local function next_word() - cursor = select(2, line:find('%s*[^%s]*', cursor)) + 1 - update() -end - --- Move the cursor to the beginning of the line (HOME) -local function go_home() - cursor = 1 - update() -end - --- Move the cursor to the end of the line (END) -local function go_end() - cursor = line:len() + 1 - update() -end - --- Delete from the cursor to the beginning of the word (Ctrl+Backspace) -local function del_word() - local before_cur = line:sub(1, cursor - 1) - local after_cur = line:sub(cursor) - - before_cur = before_cur:gsub('[^%s]+%s*$', '', 1) - line = before_cur .. after_cur - cursor = before_cur:len() + 1 - update() -end - --- Delete from the cursor to the end of the word (Ctrl+Del) -local function del_next_word() - if cursor > line:len() then return end - - local before_cur = line:sub(1, cursor - 1) - local after_cur = line:sub(cursor) - - after_cur = after_cur:gsub('^%s*[^%s]+', '', 1) - line = before_cur .. after_cur - update() -end - --- Delete from the cursor to the end of the line (Ctrl+K) -local function del_to_eol() - line = line:sub(1, cursor - 1) - update() -end - --- Delete from the cursor back to the start of the line (Ctrl+U) -local function del_to_start() - line = line:sub(cursor) - cursor = 1 - update() -end - --- Returns a string of UTF-8 text from the clipboard (or the primary selection) -local function get_clipboard(clip) - if platform == 'x11' then - local res = utils.subprocess({ - args = { - 'xclip', '-selection', clip and 'clipboard' or 'primary', '-out' - }, - playback_only = false - }) - if not res.error then return res.stdout end - elseif platform == 'wayland' then - local res = utils.subprocess({ - args = { 'wl-paste', clip and '-n' or '-np' }, - playback_only = false - }) - if not res.error then return res.stdout end - elseif platform == 'windows' then - local res = utils.subprocess({ - args = { - 'powershell', '-NoProfile', '-Command', [[& { - Trap { - Write-Error -ErrorRecord $_ - Exit 1 - } - - $clip = "" - if (Get-Command "Get-Clipboard" -errorAction SilentlyContinue) { - $clip = Get-Clipboard -Raw -Format Text -TextFormatType UnicodeText - } else { - Add-Type -AssemblyName PresentationCore - $clip = [Windows.Clipboard]::GetText() - } - - $clip = $clip -Replace "`r","" - $u8clip = [System.Text.Encoding]::UTF8.GetBytes($clip) - [Console]::OpenStandardOutput().Write($u8clip, 0, $u8clip.Length) - }]] - }, - playback_only = false - }) - if not res.error then return res.stdout end - elseif platform == 'macos' then - local res = - utils.subprocess({ args = { 'pbpaste' }, playback_only = false }) - if not res.error then return res.stdout end - end - return '' -end - --- Paste text from the window-system's clipboard. 'clip' determines whether the --- clipboard or the primary selection buffer is used (on X11 and Wayland only.) -local function paste(clip) - local text = get_clipboard(clip) - local before_cur = line:sub(1, cursor - 1) - local after_cur = line:sub(cursor) - line = before_cur .. text .. after_cur - cursor = cursor + text:len() - update() -end - --- List of input bindings. This is a weird mashup between common GUI text-input --- bindings and readline bindings. -local function get_bindings() - local bindings = { - { 'esc', handle_esc }, { 'enter', handle_enter }, - { 'kp_enter', handle_enter }, - { 'shift+enter', function() handle_char_input('\n') end }, - { 'ctrl+j', handle_enter }, { 'ctrl+m', handle_enter }, - { 'bs', handle_backspace }, { 'shift+bs', handle_backspace }, - { 'ctrl+h', handle_backspace }, { 'del', handle_del }, - { 'shift+del', handle_del }, { 'ins', handle_ins }, - { 'shift+ins', function() paste(false) end }, - { 'mbtn_mid', function() paste(false) end }, - { 'left', function() prev_char() end }, - { 'ctrl+b', function() prev_char() end }, - { 'right', function() next_char() end }, - { 'ctrl+f', function() next_char() end }, - { 'up', function() move_history(-1) end }, - { 'ctrl+p', function() move_history(-1) end }, - { 'wheel_up', function() move_history(-1) end }, - { 'down', function() move_history(1) end }, - { 'ctrl+n', function() move_history(1) end }, - { 'wheel_down', function() move_history(1) end }, - { 'wheel_left', function() end }, { 'wheel_right', function() end }, - { 'ctrl+left', prev_word }, { 'alt+b', prev_word }, - { 'ctrl+right', next_word }, { 'alt+f', next_word }, { 'ctrl+a', go_home }, - { 'home', go_home }, { 'ctrl+e', go_end }, { 'end', go_end }, - { 'pgup', handle_pgup }, { 'pgdwn', handle_pgdown }, { 'ctrl+c', clear }, - { 'ctrl+d', maybe_exit }, { 'ctrl+k', del_to_eol }, - { 'ctrl+u', del_to_start }, { 'ctrl+v', function() paste(true) end }, - { 'meta+v', function() paste(true) end }, { 'ctrl+bs', del_word }, - { 'ctrl+w', del_word }, { 'ctrl+del', del_next_word }, - { 'alt+d', del_next_word }, - { 'kp_dec', function() handle_char_input('.') end } - } - - for i = 0, 9 do - bindings[#bindings + 1] = { - 'kp' .. i, function() handle_char_input('' .. i) end - } - end - - return bindings -end - -local function text_input(info) - if info.key_text and - (info.event == "press" or info.event == "down" or info.event == "repeat") then - handle_char_input(info.key_text) - end -end - -local function define_key_bindings() - if #key_bindings > 0 then return end - for _, bind in ipairs(get_bindings()) do - -- Generate arbitrary name for removing the bindings later. - local name = "_userinput_" .. bind[1] - key_bindings[#key_bindings + 1] = name - mp.add_forced_key_binding(bind[1], name, bind[2], { repeatable = true }) - end - mp.add_forced_key_binding("any_unicode", "_userinput_text", text_input, - { repeatable = true, complex = true }) - key_bindings[#key_bindings + 1] = "_userinput_text" -end - -local function undefine_key_bindings() - for _, name in ipairs(key_bindings) do mp.remove_key_binding(name) end - key_bindings = {} -end - --- Set the REPL visibility ("enable", Esc) -local function set_active(active) - if active == repl_active then return end - if active then - repl_active = true - insert_mode = false - define_key_bindings() - else - clear() - repl_active = false - undefine_key_bindings() - collectgarbage() - end - update() -end - -utils.shared_script_property_observe("osc-margins", function(_, val) - if val then - -- formatted as "%f,%f,%f,%f" with left, right, top, bottom, each - -- value being the border size as ratio of the window size (0.0-1.0) - local vals = {} - for v in string.gmatch(val, "[^,]+") do - vals[#vals + 1] = tonumber(v) - end - global_margin_y = vals[4] -- bottom - else - global_margin_y = 0 - end - update() -end) - --- Redraw the REPL when the OSD size changes. This is needed because the --- PlayRes of the OSD will need to be adjusted. -mp.observe_property('osd-width', 'native', update) -mp.observe_property('osd-height', 'native', update) -mp.observe_property('display-hidpi-scale', 'native', update) - ----------------------------------------------------------------------------------------- ----------------------------------------------------------------------------------------- ----------------------------------------------------------------------------------------- --------------------------------END ORIGINAL MPV CODE------------------------------------ - ---[[ - sends a response to the original script in the form of a json string - it is expected that all requests get a response, if the input is nil then err should say why - current error codes are: - exited the user closed the input instead of pressing Enter - already_queued a request with the specified id was already in the queue - cancelled a script cancelled the request - replace replaced by another request -]] -local function send_response(res) - if res.source then - mp.commandv("script-message-to", res.source, res.response, - (utils.format_json(res))) - else - mp.commandv("script-message", res.response, (utils.format_json(res))) - end -end - --- push new request onto the queue --- if a request with the same id already exists and the queueable flag is not enabled then --- a nil result will be returned to the function -function push_request(req) - if active_ids[req.id] then - if req.replace then - for i, q_req in ipairs(queue) do - if q_req.id == req.id then - send_response { - err = "replaced", - response = q_req.response, - source = q_req.source - } - queue[i] = req - if i == 1 then request = req end - end - end - update() - return - end - - if not req.queueable then - send_response { - err = "already_queued", - response = req.response, - source = req.source - } - return - end - end - - table.insert(queue, req) - active_ids[req.id] = (active_ids[req.id] or 0) + 1 - if #queue == 1 then coroutine.resume(co) end - update() -end - --- safely removes an item from the queue and updates the set of active requests -function remove_request(index) - local req = table.remove(queue, index) - active_ids[req.id] = active_ids[req.id] - 1 - - if active_ids[req.id] == 0 then active_ids[req.id] = nil end - return req -end - --- an infinite loop that moves through the request queue --- uses a coroutine to handle asynchronous operations -local function driver() - while (true) do - while queue[1] do - request = queue[1] - line = request.default_input - cursor = request.cursor_pos - - if repl_active then - update() - else - set_active(true) - end - - res = coroutine.yield() - if res then - res.source, res.response = request.source, request.response - send_response(res) - remove_request(1) - end - end - - set_active(false) - coroutine.yield() - end -end - -co = coroutine.create(driver) - --- cancels any input request that returns true for the given predicate function -local function cancel_input_request(pred) - for i = #queue, 1, -1 do - if pred(i) then - req = remove_request(i) - send_response { - err = "cancelled", - response = req.response, - source = req.source - } - - -- if we're removing the first item then that means the coroutine is waiting for a response - -- we will need to tell the coroutine to resume, upon which it will move to the next request - -- if there is something in the buffer then save it to the history before erasing it - if i == 1 then - local old_line = line - if old_line ~= "" then - table.insert(histories[req.id].list, old_line) - end - clear() - coroutine.resume(co) - end - end - end -end - -mp.register_script_message("cancel-user-input/uid", function(uid) - cancel_input_request(function(i) return queue[i].response == uid end) -end) - --- removes all requests with the specified id from the queue -mp.register_script_message("cancel-user-input/id", function(id) - cancel_input_request(function(i) return queue[i].id == id end) -end) - --- ensures a request has the correct fields and is correctly formatted -local function format_request_fields(req) - assert(req.version, "input requests require an API version string") - if not string.find(req.version, API_MAJOR_MINOR, 1, true) then - error( - ("input request has invalid version: expected %s.x, got %s"):format( - API_MAJOR_MINOR, req.version)) - end - - assert(req.response, "input requests require a response string") - assert(req.id, "input requests require an id string") - - req.text = ass_escape(req.request_text or "") - req.default_input = req.default_input or "" - req.cursor_pos = tonumber(req.cursor_pos) or 1 - req.id = req.id or "mpv" - - if req.cursor_pos ~= 1 then - if req.cursor_pos < 1 then - req.cursor_pos = 1 - elseif req.cursor_pos > #req.default_input then - req.cursor_pos = #req.default_input + 1 - end - end - - if not histories[req.id] then histories[req.id] = { pos = 1, list = {} } end - req.history = histories[req.id] - return req -end - --- updates the fields of a specific request -mp.register_script_message("update-user-input/uid", function(uid, req_opts) - req_opts = utils.parse_json(req_opts) - req_opts.response = uid - for i, req in ipairs(queue) do - if req.response == uid then - local success, result = pcall(format_request_fields, req_opts) - if not success then return msg.error(result) end - - queue[i] = result - if i == 1 then request = queue[1] end - update() - return - end - end -end) - --- the function that parses the input requests -local function input_request(req) - req = format_request_fields(req) - push_request(req) -end - --- script message to recieve input requests, get-user-input.lua acts as an interface to call this script message -mp.register_script_message("request-user-input", function(req) - msg.debug(req) - req = utils.parse_json(req) - local success, err = pcall(input_request, req) - if not success then - send_response { err = err, response = req.response, source = req.source } - msg.error(err) - end -end) diff --git a/scripts/youtube-search.lua b/scripts/youtube-search.lua deleted file mode 100644 index af25998..0000000 --- a/scripts/youtube-search.lua +++ /dev/null @@ -1,373 +0,0 @@ ---[[ - This script allows users to search and open youtube results from within mpv. - Available at: https://github.com/CogentRedTester/mpv-scripts - - Users can open the search page with Y, and use Y again to open a search. - Alternatively, Ctrl+y can be used at any time to open a search. - Esc can be used to close the page. - Enter will open the selected item, Shift+Enter will append the item to the playlist. - - This script requires that my other scripts `scroll-list` and `user-input` be installed. - scroll-list.lua and user-input-module.lua must be in the ~~/script-modules/ directory, - while user-input.lua should be loaded by mpv normally. - - https://github.com/CogentRedTester/mpv-scroll-list - https://github.com/CogentRedTester/mpv-user-input - - This script also requires a youtube API key to be entered. - The API key must be passed to the `API_key` script-opt. - A personal API key is free and can be created from: - https://console.developers.google.com/apis/api/youtube.googleapis.com/ - - The script also requires that curl be in the system path. - - An alternative to using the official youtube API is to use Invidious. - This script has experimental support for Invidious searches using the 'invidious', - 'API_path', and 'frontend' options. API_path refers to the url of the API the - script uses, Invidious API paths are usually in the form: - https://domain.name/api/v1/ - The frontend option is the url to actualy try to load videos from. This - can probably be the same as the above url: - https://domain.name - Since the url syntax seems to be identical between Youtube and Invidious, - it should be possible to mix these options, a.k.a. using the Google - API to get videos from an Invidious frontend, or to use an Invidious - API to get videos from Youtube. - The 'invidious' option tells the script that the API_path is for an - Invidious path. This is to support other possible API options in the future. -]] - -- -local mp = require "mp" -local msg = require "mp.msg" -local utils = require "mp.utils" -local opts = require "mp.options" - -package.path = mp.command_native({ "expand-path", "~~/script-modules/?.lua;" }) .. - package.path -local ui = require "user-input-module" -local list = require "scroll-list" - -local o = { - API_key = "", - - -- number of search results to show in the list - num_results = 40, - - -- the url to send API calls to - API_path = "https://www.googleapis.com/youtube/v3/", - - -- attempt this API if the default fails - fallback_API_path = "", - - -- the url to load videos from - frontend = "https://www.youtube.com", - - -- use invidious API calls - invidious = false, - - -- whether the fallback uses invidious as well - fallback_invidious = false -} - -opts.read_options(o) - --- ensure the URL options are properly formatted -local function format_options() - if o.API_path:sub(-1) ~= "/" then o.API_path = o.API_path .. "/" end - if o.fallback_API_path:sub(-1) ~= "/" then - o.fallback_API_path = o.fallback_API_path .. "/" - end - if o.frontend:sub(-1) == "/" then o.frontend = o.frontend:sub(1, -2) end -end - -format_options() - -list.header = - ("%s Search: \\N-------------------------------------------------"):format( - o.invidious and "Invidious" or "Youtube") -list.num_entries = 17 -list.list_style = [[{\fs10}\N{\q2\fs25\c&Hffffff&}]] -list.empty_text = "enter search query" - -local ass_escape = list.ass_escape - --- encodes a string so that it uses url percent encoding --- this function is based on code taken from here: https://rosettacode.org/wiki/URL_encoding#Lua -local function encode_string(str) - if type(str) ~= "string" then return str end - local output, t = str:gsub("[^%w]", function(char) - return string.format("%%%X", string.byte(char)) - end) - return output -end - --- convert HTML character codes to the correct characters -local function html_decode(str) - if type(str) ~= "string" then return str end - - return str:gsub("&(#?)(%w-);", function(is_ascii, code) - if is_ascii == "#" then return string.char(tonumber(code)) end - if code == "amp" then return "&" end - if code == "quot" then return '"' end - if code == "apos" then return "'" end - if code == "lt" then return "<" end - if code == "gt" then return ">" end - return nil - end) -end - --- creates a formatted results table from an invidious API call -function format_invidious_results(response) - if not response then return nil end - local results = {} - - for i, item in ipairs(response) do - if i > o.num_results then break end - - local t = {} - table.insert(results, t) - - t.title = html_decode(item.title) - t.channelTitle = html_decode(item.author) - if item.type == "video" then - t.type = "video" - t.id = item.videoId - elseif item.type == "playlist" then - t.type = "playlist" - t.id = item.playlistId - elseif item.type == "channel" then - t.type = "channel" - t.id = item.authorId - t.title = t.channelTitle - end - end - - return results -end - --- creates a formatted results table from a youtube API call -function format_youtube_results(response) - if not response or not response.items then return nil end - local results = {} - - for _, item in ipairs(response.items) do - local t = {} - table.insert(results, t) - - t.title = html_decode(item.snippet.title) - t.channelTitle = html_decode(item.snippet.channelTitle) - - if item.id.kind == "youtube#video" then - t.type = "video" - t.id = item.id.videoId - elseif item.id.kind == "youtube#playlist" then - t.type = "playlist" - t.id = item.id.playlistId - elseif item.id.kind == "youtube#channel" then - t.type = "channel" - t.id = item.id.channelId - end - end - - return results -end - --- sends an API request -local function send_request(type, queries, API_path) - local url = (API_path or o.API_path) .. type - url = url .. "?" - - for key, value in pairs(queries) do - msg.verbose(key, value) - url = url .. "&" .. key .. "=" .. encode_string(value) - end - - msg.debug(url) - local request = mp.command_native({ - name = "subprocess", - capture_stdout = true, - capture_stderr = true, - playback_only = false, - args = { "curl", url } - }) - - local response = utils.parse_json(request.stdout) - msg.trace(utils.to_string(request)) - - if request.status ~= 0 then - msg.error(request.stderr) - return nil - end - if not response then - msg.error("Could not parse response:") - msg.error(request.stdout) - return nil - end - if response.error then - msg.error(request.stdout) - return nil - end - - return response -end - --- sends a search API request - handles Google/Invidious API differences -local function search_request(queries, API_path, invidious) - list.header = - ("%s Search: %s\\N-------------------------------------------------"):format( - invidious and "Invidious" or "Youtube", ass_escape(queries.q, true)) - list.list = {} - list.empty_text = "~" - list:update() - local results = {} - - -- we need to modify the returned results so that the rest of the script can read it - if invidious then - -- Invidious searches are done with pages rather than a max result number - local page = 1 - while #results < o.num_results do - queries.page = page - - local response = send_request("search", queries, API_path) - response = format_invidious_results(response) - if not response then - msg.warn("Search did not return a results list"); - return - end - if #response == 0 then break end - - for _, item in ipairs(response) do - table.insert(results, item) - end - - page = page + 1 - end - else - local response = send_request("search", queries, API_path) - results = format_youtube_results(response) - end - - -- print error messages to console if the API request fails - if not results then - msg.warn("Search did not return a results list") - return - end - - list.empty_text = "no results" - return results -end - -local function insert_video(item) - list:insert({ - ass = ("%s {\\c&aaaaaa&}%s"):format(ass_escape(item.title), - ass_escape(item.channelTitle)), - url = ("%s/watch?v=%s"):format(o.frontend, item.id) - }) -end - -local function insert_playlist(item) - list:insert({ - ass = ("🖿 %s {\\c&aaaaaa&}%s"):format(ass_escape(item.title), - ass_escape(item.channelTitle)), - url = ("%s/playlist?list=%s"):format(o.frontend, item.id) - }) -end - -local function insert_channel(item) - list:insert({ - ass = ("👤 %s"):format(ass_escape(item.title)), - url = ("%s/channel/%s"):format(o.frontend, item.id) - }) -end - -local function reset_list() - list.selected = 1 - list:clear() -end - --- creates the search request queries depending on what API we're using -local function get_search_queries(query, invidious) - if invidious then - return { q = query, type = "all", page = 1 } - else - return { - key = o.API_key, - q = query, - part = "id,snippet", - maxResults = o.num_results - } - end -end - -local function search(query) - local response = search_request(get_search_queries(query, o.invidious), - o.API_path, o.invidious) - if not response and o.fallback_API_path ~= "/" then - msg.info("search failed - attempting fallback") - response = search_request( - get_search_queries(query, o.fallback_invidious), - o.fallback_API_path, o.fallback_invidious) - end - - if not response then return end - reset_list() - - for _, item in ipairs(response) do - if item.type == "video" then - insert_video(item) - elseif item.type == "playlist" then - insert_playlist(item) - elseif item.type == "channel" then - insert_channel(item) - end - end - list:update() - list:open() -end - -local function play_result(flag) - if not list[list.selected] then return end - local url = list[list.selected].url - mp.msg.info("URL: " .. url) - mp.msg.info("Flag: " .. flag) - if flag == "new_window" then - mp.commandv("run", "mpv", url); - return - end - - mp.commandv("loadfile", url, flag) - if flag == "replace" then - list:close() - else - mp.commandv("script-message", "add_to_youtube_queue", - list[list.selected].url, 1) - end -end - -table.insert(list.keybinds, - { "ENTER", "play", function() play_result("replace") end, {} }) -table.insert(list.keybinds, { - "Ctrl+Shift", "play_append", function() play_result("append-play") end, {} -}) -table.insert(list.keybinds, { - "Ctrl+Shift+ENTER", "play_new_window", - function() play_result("new_window") end, {} -}) - -local function open_search_input() - ui.get_user_input(function(input) - if not input then return end - search(input) - end, { request_text = "Enter Query:" }) -end - -mp.add_key_binding("Ctrl+y", "yt", open_search_input) - -mp.add_key_binding("Y", "youtube-search", function() - if not list.hidden then - open_search_input() - else - list:open() - if #list.list == 0 then open_search_input() end - end -end)