From 9f149da688270493b93384b1144ae91ab7917f4a Mon Sep 17 00:00:00 2001 From: CogentRedTester Date: Sat, 10 Jan 2026 17:39:00 +1030 Subject: [PATCH 1/5] youtube-search: use yt-dlp to retrieve search results --- youtube-search.lua | 284 +++++++++++++-------------------------------- 1 file changed, 78 insertions(+), 206 deletions(-) diff --git a/youtube-search.lua b/youtube-search.lua index 00eb18a..9ac9b11 100644 --- a/youtube-search.lua +++ b/youtube-search.lua @@ -1,5 +1,5 @@ --[[ - This script allows users to search and open youtube results from within mpv. + This script allows users to search and open youtube results from within mpv using yt-dlp. Available at: https://github.com/CogentRedTester/mpv-scripts Users can open the search page with Y, and use Y again to open a search. @@ -11,30 +11,10 @@ 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. + yt-dlp must also be available in the system path + 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" @@ -47,33 +27,22 @@ 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/", + yt_dlp_path = "yt-dlp", - --attempt this API if the default fails - fallback_API_path = "", + --The search query to sent to yt-dlp. `%s` is substituted for the search query. + search_query = "https://www.youtube.com/search?q=%s", - --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 @@ -96,221 +65,124 @@ local function encode_string(str) return output end ---convert HTML character codes to the correct characters -local function html_decode(str) - if type(str) ~= "string" then return str end +---@param str string +---@return table[]|nil +local function json_parse_iterate(str) + local t = {} - 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 + local json, err, trail = utils.parse_json(str, true) + if not json then return nil 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 + repeat + table.insert(t, json) + json, err, trail = utils.parse_json(trail, true) + until not json - return results + return t 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", +local function search_ytdlp(query) + local req = mp.command_native({ + name = 'subprocess', + playback_only = false, capture_stdout = true, capture_stderr = true, - playback_only = false, - args = {"curl", url} + args = {o.yt_dlp_path, '-s', ('-I1:%d'):format(o.num_results), '--flat-playlist', '-j', o.search_query:format(encode_string(query))} }) - local response = utils.parse_json(request.stdout) - msg.trace(utils.to_string(request)) + msg.trace(utils.to_string(req)) + local results = json_parse_iterate(req.stdout) - if request.status ~= 0 then - msg.error(request.stderr) + if req.status ~= 0 then + msg.error(req.stderr) return nil end - if not response then + if not results or #results == 0 then msg.error("Could not parse response:") - msg.error(request.stdout) - return nil - end - if response.error then - msg.error(request.stdout) + msg.error(req.stdout) return nil end - return response + return results 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 +---@class SearchResult +---@field id string +---@field type 'video'|'playlist'|'channel' +---@field title string +---@field channelTitle string + +---@param results table|nil +---@return SearchResult[]|nil +local function process_ytdlp_results(results) + if not results then return nil end + + ---@type SearchResult[] + local t = {} + + for _, v in ipairs(results) do + local url_type = string.match(v.url, '^https://www.youtube.com/([^/?]+)') + + ---@type SearchResult + local result = { + id = v.id, + type = url_type == 'watch' and 'video' or url_type, + title = v.title or '', + channelTitle = v.channel or '', + } - page = page + 1 - end - else - local response = send_request("search", queries, API_path) - results = format_youtube_results(response) + table.insert(t, result) 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 + return t end +---@param item SearchResult local function insert_video(item) list:insert({ - ass = ("%s {\\c&aaaaaa&}%s"):format(ass_escape(item.title), ass_escape(item.channelTitle)), + 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 +---@param item SearchResult local function insert_playlist(item) list:insert({ - ass = ("🖿 %s {\\c&aaaaaa&}%s"):format(ass_escape(item.title), ass_escape(item.channelTitle)), + ass = ([[{\i1}[playlist]{\i0} %s {\\c&aaaaaa&}%s]]):format(ass_escape(item.title), ass_escape(item.channelTitle)), url = ("%s/playlist?list=%s"):format(o.frontend, item.id) }) end +---@param item SearchResult local function insert_channel(item) list:insert({ - ass = ("👤 %s"):format(ass_escape(item.title)), + ass = ([[[{\i1}channel]{\i2} %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 +local function search(query) + list.header = ("%s Search: %s\\N-------------------------------------------------"):format("Youtube", ass_escape(query, true)) + list.list = {} + list.empty_text = "~" + list:update() ---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 results = process_ytdlp_results(search_ytdlp(query)) -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) + --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 - 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 + for _, v in ipairs(results) do + print(utils.to_string(v)) + if v.type == 'video' then insert_video(v) + elseif v.type == 'playlist' then insert_playlist(v) + elseif v.type == 'channel' then insert_channel(v) end end + + list.empty_text = "no results" list:update() list:open() end From 70136189327ee0711b6d544b0c03934b813beddb Mon Sep 17 00:00:00 2001 From: CogentRedTester Date: Sat, 10 Jan 2026 17:58:05 +1030 Subject: [PATCH 2/5] youtube-search: use mp.input for user input --- youtube-search.lua | 21 ++++++++++++--------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/youtube-search.lua b/youtube-search.lua index 9ac9b11..fdbaf3c 100644 --- a/youtube-search.lua +++ b/youtube-search.lua @@ -7,36 +7,38 @@ 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. + This script requires that my other scripts `scroll-list` 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. yt-dlp must also be available in the system path https://github.com/CogentRedTester/mpv-scroll-list - https://github.com/CogentRedTester/mpv-user-input ]]-- local mp = require "mp" local msg = require "mp.msg" local utils = require "mp.utils" local opts = require "mp.options" +local input = require 'mp.input' 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 = { - --number of search results to show in the list + --Number of search results to show in the list. num_results = 40, - --the url to send API calls to + --The url to send API calls to. yt_dlp_path = "yt-dlp", --The search query to sent to yt-dlp. `%s` is substituted for the search query. search_query = "https://www.youtube.com/search?q=%s", frontend = "https://www.youtube.com", + + --Save search history between mpv sessions. + save_search_history = true, } opts.read_options(o) @@ -200,10 +202,11 @@ table.insert(list.keybinds, {"Shift+ENTER", "play_append", function() play_resul table.insert(list.keybinds, {"Ctrl+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:" }) + input.get({ + prompt = 'Youtube Search\n> ', + submit = search, + history_path = '~~state/youtube_search_history' + }) end mp.add_key_binding("Ctrl+y", "yt", open_search_input) From de1abacca17235f7ca54769e0c2b5887dbedc9f3 Mon Sep 17 00:00:00 2001 From: CogentRedTester Date: Sat, 10 Jan 2026 20:17:39 +1030 Subject: [PATCH 3/5] youtube-search: use mp.input.select for search page --- youtube-search.lua | 166 ++++++++++++++++++++++++--------------------- 1 file changed, 87 insertions(+), 79 deletions(-) diff --git a/youtube-search.lua b/youtube-search.lua index fdbaf3c..cb4ad34 100644 --- a/youtube-search.lua +++ b/youtube-search.lua @@ -2,18 +2,13 @@ This script allows users to search and open youtube results from within mpv using yt-dlp. Available at: https://github.com/CogentRedTester/mpv-scripts - Users can open the search page with Y, and use Y again to open a search. + The Y button opens the latest page of search results (or prompts for input + if nothing has yet been searched). + The search page has an entry to do a new 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` 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. yt-dlp must also be available in the system path - - https://github.com/CogentRedTester/mpv-scroll-list ]]-- local mp = require "mp" @@ -22,9 +17,6 @@ local utils = require "mp.utils" local opts = require "mp.options" local input = require 'mp.input' -package.path = mp.command_native({"expand-path", "~~/script-modules/?.lua;"}) .. package.path -local list = require "scroll-list" - local o = { --Number of search results to show in the list. num_results = 40, @@ -50,17 +42,27 @@ 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" +---@class SearchResult +---@field id string +---@field type 'video'|'playlist'|'channel' +---@field title string +---@field channelTitle string +---@field url string + +---@class LatestSearch +---@field latest_results SearchResult[] +---@field query string -local ass_escape = list.ass_escape +---@type LatestSearch|nil +local latest_search = nil ---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 selection_open = false + +---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 +---@param str string +---@return string 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) @@ -83,6 +85,8 @@ local function json_parse_iterate(str) return t end +---@param query string +---@return table[]|nil local function search_ytdlp(query) local req = mp.command_native({ name = 'subprocess', @@ -108,12 +112,6 @@ local function search_ytdlp(query) return results end ----@class SearchResult ----@field id string ----@field type 'video'|'playlist'|'channel' ----@field title string ----@field channelTitle string - ---@param results table|nil ---@return SearchResult[]|nil local function process_ytdlp_results(results) @@ -131,6 +129,10 @@ local function process_ytdlp_results(results) type = url_type == 'watch' and 'video' or url_type, title = v.title or '', channelTitle = v.channel or '', + url = url_type == 'watch' and ("%s/watch?v=%s"):format(o.frontend, v.id) + or url_type == 'playlist' and ("%s/playlist?list=%s"):format(o.frontend, v.id) + or url_type == 'channel' and ("%s/channel/%s"):format(o.frontend, v.id) + or '' } table.insert(t, result) @@ -139,35 +141,48 @@ local function process_ytdlp_results(results) return t end ----@param item SearchResult -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 +---@param index number +local function play(index) + if not index or not latest_search then return end ----@param item SearchResult -local function insert_playlist(item) - list:insert({ - ass = ([[{\i1}[playlist]{\i0} %s {\\c&aaaaaa&}%s]]):format(ass_escape(item.title), ass_escape(item.channelTitle)), - url = ("%s/playlist?list=%s"):format(o.frontend, item.id) - }) + mp.commandv("loadfile", latest_search.latest_results[index].url) end ----@param item SearchResult -local function insert_channel(item) - list:insert({ - ass = ([[[{\i1}channel]{\i2} %s]]):format(ass_escape(item.title)), - url = ("%s/channel/%s"):format(o.frontend, item.id) +local function show_results() + if not latest_search then return msg.error('no search results available to display') end + selection_open = true + + local t = {'~~ NEW SEARCH ~~'} + for _, v in ipairs(latest_search.latest_results) do + if v.type == 'video' then + table.insert(t, ('%s —\t%s'):format(v.channelTitle, v.title)) + elseif v.type == 'channel' then + table.insert(t, ('~Channel~\t%s'):format(v.title)) + else + table.insert(t, ('~Playlist~\t%s'):format(v.title)) + end + end + + input.select({ + id = mp.get_script_name()..'/select-results', + prompt = ('Results for: %s | Filter: '):format(latest_search.query), + items = t, + default_item = 1, + submit = function(i) + -- the first item is the option to do a new search + if i == 1 then + input.terminate() + mp.add_timeout(0.1, open_search_input) + else + play(i-1) + end + selection_open = false + end }) end +---@param query string local function search(query) - list.header = ("%s Search: %s\\N-------------------------------------------------"):format("Youtube", ass_escape(query, true)) - list.list = {} - list.empty_text = "~" - list:update() local results = process_ytdlp_results(search_ytdlp(query)) @@ -177,44 +192,37 @@ local function search(query) return end - for _, v in ipairs(results) do - print(utils.to_string(v)) - if v.type == 'video' then insert_video(v) - elseif v.type == 'playlist' then insert_playlist(v) - elseif v.type == 'channel' then insert_channel(v) end - end - - list.empty_text = "no results" - list:update() - list:open() -end - -local function play_result(flag) - if not list[list.selected] then return end - if flag == "new_window" then mp.commandv("run", "mpv", list[list.selected].url) ; return end - - mp.commandv("loadfile", list[list.selected].url, flag) - if flag == "replace" then list:close() end + latest_search = { + query = query, + latest_results = results, + } + show_results() end -table.insert(list.keybinds, {"ENTER", "play", function() play_result("replace") end, {}}) -table.insert(list.keybinds, {"Shift+ENTER", "play_append", function() play_result("append-play") end, {}}) -table.insert(list.keybinds, {"Ctrl+ENTER", "play_new_window", function() play_result("new_window") end, {}}) - -local function open_search_input() +---@diagnostic disable-next-line: lowercase-global +function open_search_input() input.get({ - prompt = 'Youtube Search\n> ', - submit = search, - history_path = '~~state/youtube_search_history' + id = mp.get_script_name()..'/enter-search-query', + prompt = 'Youtube Search:\n> ', + history_path = '~~state/youtube_search_history', + submit = function(line) + -- We must add this function to the event queue as the + -- 'closed' event (that removes the input handler) is sent at the + -- same time as submit. We must give it a chance to run before doing + -- the search so that the select prompt can receive input. + mp.add_timeout(0.1, function() search(line) end) + input.terminate() + end }) end -mp.add_key_binding("Ctrl+y", "yt", open_search_input) +mp.add_key_binding("Ctrl+y", "yt", function() + input.terminate() + mp.add_timeout(0.1, open_search_input) +end) 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 + if selection_open or latest_search == nil then open_search_input() + else show_results() end end) + From cff8fc2407236f2a1ebb41c7c5d347c6eafd1cd9 Mon Sep 17 00:00:00 2001 From: CogentRedTester Date: Sat, 10 Jan 2026 20:18:28 +1030 Subject: [PATCH 4/5] youtube-search: update README --- README.md | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 36d0b44..82bd1d8 100644 --- a/README.md +++ b/README.md @@ -162,7 +162,5 @@ This script allows users to set custom variables which can be used in commands a ## youtube-search -A script that allows users to search and open youtube results from within mpv. -Requires [scroll-list](#scroll-list), [user-input](#user-input), curl, and a youtube API key. -Alternatively, an Invidious frontend can be used for searches without requiring an API key. -See the file header for details. +A script that allows users to search and open youtube results from within mpv with the `Y` key. +Requires `yt-dlp`. From 1f4551ebed9a2c9d3bec6126b948eded91ab5a34 Mon Sep 17 00:00:00 2001 From: CogentRedTester Date: Sun, 11 Jan 2026 13:16:22 +1030 Subject: [PATCH 5/5] youtube-search: add extra select keybinds --- youtube-search.lua | 98 ++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 81 insertions(+), 17 deletions(-) diff --git a/youtube-search.lua b/youtube-search.lua index cb4ad34..93dbc14 100644 --- a/youtube-search.lua +++ b/youtube-search.lua @@ -52,6 +52,7 @@ format_options() ---@class LatestSearch ---@field latest_results SearchResult[] ---@field query string +---@field display_list string[] ---@type LatestSearch|nil local latest_search = nil @@ -141,49 +142,98 @@ local function process_ytdlp_results(results) return t end +---@alias PlayFlag 'play'|'append'|'new-window' + ---@param index number -local function play(index) +---@param flag PlayFlag +local function play(index, flag) if not index or not latest_search then return end - - mp.commandv("loadfile", latest_search.latest_results[index].url) + local item = latest_search.latest_results[index] + + if flag == 'new-window' then + mp.commandv('run', 'mpv', item.url) + elseif flag == 'append' then + mp.command_native({"loadfile", item.url, 'append-play'}) + else + mp.command_native({"loadfile", item.url}) + end end +---@type [string,string,PlayFlag][] +local custom_select_keybinds = { + {'Shift+Enter', 'select/append', 'append'}, + {'Shift+MBTN_LEFT', 'select/append/mbtn', 'append'}, + {'Ctrl+Enter', 'select/new-window', 'new-window'}, + {'Ctrl+MBTN_LEFT', 'select/new-window/mbtn', 'new-window'}, +} + local function show_results() if not latest_search then return msg.error('no search results available to display') end selection_open = true - local t = {'~~ NEW SEARCH ~~'} - for _, v in ipairs(latest_search.latest_results) do - if v.type == 'video' then - table.insert(t, ('%s —\t%s'):format(v.channelTitle, v.title)) - elseif v.type == 'channel' then - table.insert(t, ('~Channel~\t%s'):format(v.title)) - else - table.insert(t, ('~Playlist~\t%s'):format(v.title)) - end - end + local items = {'~~ NEW SEARCH ~~', unpack(latest_search.display_list)} + ---@type PlayFlag + local flag = 'play' input.select({ id = mp.get_script_name()..'/select-results', prompt = ('Results for: %s | Filter: '):format(latest_search.query), - items = t, + items = items, default_item = 1, + keep_open = true, + opened = function() + msg.debug('Select prompt opened - adding keybinds') + + for _, keybind in ipairs(custom_select_keybinds) do + mp.add_forced_key_binding(keybind[1], keybind[2], function() + flag = keybind[3] + mp.commandv('keypress', 'enter') + end) + + -- This is necessary because of a race condition that sometimes + -- causes the console keybinds to take precedence over ours + -- despite us declaring ours after. + if keybind[1]:find('Shift') then + mp.add_timeout(0.5, function() + mp.add_forced_key_binding(keybind[1], keybind[2], function() + flag = keybind[3] + mp.commandv('keypress', 'enter') + end) + end) + end + end + end, + closed = function() + msg.debug('selection closed - removing keybinds') + for _, keybind in ipairs(custom_select_keybinds) do + mp.remove_key_binding(keybind[2]) + end + mp.remove_key_binding('_console_text') + end, submit = function(i) -- the first item is the option to do a new search if i == 1 then input.terminate() mp.add_timeout(0.1, open_search_input) else - play(i-1) + play(i-1, flag) end - selection_open = false - end + + if flag == 'play' then + input.terminate() + selection_open = false + else + flag = 'play' + end + end, }) end ---@param query string local function search(query) + ---@type string[] + local display_list = {} local results = process_ytdlp_results(search_ytdlp(query)) --print error messages to console if the API request fails @@ -192,9 +242,20 @@ local function search(query) return end + for _, v in ipairs(results) do + if v.type == 'video' then + table.insert(display_list, ('%s —\t%s'):format(v.channelTitle, v.title)) + elseif v.type == 'channel' then + table.insert(display_list, ('~Channel~\t%s'):format(v.title)) + else + table.insert(display_list, ('~Playlist~\t%s'):format(v.title)) + end + end + latest_search = { query = query, latest_results = results, + display_list = display_list } show_results() end @@ -210,7 +271,10 @@ function open_search_input() -- 'closed' event (that removes the input handler) is sent at the -- same time as submit. We must give it a chance to run before doing -- the search so that the select prompt can receive input. + -- We are not using keep-open as there is still a delay + -- before the search completes and we don't want the input to remain open. mp.add_timeout(0.1, function() search(line) end) + mp.osd_message(('Searching Youtube for "%s"'):format(line)) input.terminate() end })