Compare commits

..

2 Commits

Author SHA1 Message Date
Kyle Yasuda
e35aeac422
cleanup/optimize code and squash some bugs (#12)
* cleanup/optimize code and squash some bugs

- change print_queue to use mp.assdraw
- change print_queue function to a toggle
- clean up duplicated code
- squash some bugs

* remove unused function

* make download async and update clipboard function to use mp command

- utilize mp.command_native and command_native_async
- download files in the background

* update default display_limit from 6 -> 10
2023-08-09 01:24:24 -07:00
ksyasuda
3a47cb0d0b
update luacheck workflow 2023-08-09 01:21:40 -07:00
4 changed files with 162 additions and 148 deletions

View File

@ -1,7 +1,7 @@
name: Luacheck
on: [push, pull_request]
on: push
jobs:
sile:
luacheck:
runs-on: ubuntu-latest
steps:
- name: Checkout

View File

@ -72,7 +72,7 @@ This script requires the following software to be installed on the system
- `browser - firefox`: The browser to use when opening a video or channel page
- `clipboard_command - xclip -o`: The command to use to get the contents of the clipboard
- `cursor_icon - ➤`: The icon to use for the cursor
- `display_limit - 6`: The maximum amount of videos to show on the OSD at once
- `display_limit - 10`: The maximum amount of videos to show on the OSD at once
- `download_directory - ~/videos/YouTube`: The directory to use when
downloading a video
- `download_quality 720p`: The maximum download quality

View File

@ -15,7 +15,7 @@ play_selected_video=ctrl+ENTER
browser=firefox
clipboard_command=xclip -o
cursor_icon=➤
display_limit=6
display_limit=10
download_directory=~/videos/YouTube
download_quality=720p
downloader=curl

View File

@ -16,6 +16,7 @@
local mp = require 'mp'
mp.options = require 'mp.options'
local utils = require 'mp.utils'
local assdraw = require 'mp.assdraw'
local styleOn = mp.get_property("osd-ass-cc/0")
local styleOff = mp.get_property("osd-ass-cc/1")
@ -37,7 +38,7 @@ local options = {
browser = "firefox",
clipboard_command = "xclip -o",
cursor_icon = "",
display_limit = 6,
display_limit = 10,
download_directory = "~/videos/YouTube",
download_quality = "720p",
downloader = "curl",
@ -51,6 +52,7 @@ local options = {
mp.options.read_options(options, "mpv-youtube-queue")
-- STYLE {{{
local colors = {
error = "676EFF",
selected = "F993BD",
@ -79,6 +81,7 @@ local style = {
font = "{\\fn" .. options.font_name .. "\\fs" .. options.font_size .. "{" ..
sortoftransparent .. "}"
}
-- }}}
local YouTubeQueue = {}
local video_queue = {}
@ -89,6 +92,16 @@ local selected_index = 1
local display_offset = 0
local marked_index = nil
local current_video = nil
local destroyer = nil
local timeout
local function destroy()
timeout:kill()
mp.set_osd_ass(0, 0, "")
destroyer = nil
end
timeout = mp.add_periodic_timer(5, destroy)
-- HELPERS {{{
@ -108,6 +121,7 @@ local function sleep(n) os.execute("sleep " .. tonumber(n)) end
local function print_osd_message(message, duration, s)
if s == style.error and not options.show_errors then return end
destroy()
if s == nil then s = style.font .. "{" .. notransparent .. "}" end
if duration == nil then duration = MSG_DURATION end
mp.osd_message(styleOn .. s .. message .. style.reset .. styleOff .. "\n",
@ -117,9 +131,7 @@ end
-- returns true if the provided path exists and is a file
local function is_file(filepath)
local result = utils.file_info(filepath)
if result == nil then
return false
end
if result == nil then return false end
return result.is_file
end
@ -128,16 +140,6 @@ local function split_path(filepath)
if is_file(filepath) then return utils.split_path(filepath) end
end
local function print_current_video()
local current = YouTubeQueue.get_current_video()
if is_file(current.video_url) then
print_osd_message("Playing: " .. current.video_name, 3)
else
print_osd_message("Playing: " .. current.video_name .. ' by ' ..
current.channel_name, 3)
end
end
local function expanduser(path)
-- remove trailing slash if it exists
if string.sub(path, -1) == "/" then path = string.sub(path, 1, -2) end
@ -166,16 +168,22 @@ local function open_channel_in_browser()
open_url_in_browser(YouTubeQueue.get_current_video().channel_url)
end
-- local function is_valid_ytdlp_url(url)
-- local command = 'yt-dlp --simulate \'' .. url .. '\' >/dev/null 2>&1'
-- local handle = io.popen(command .. "; echo $?")
-- if handle == nil then return false end
-- local result = handle:read("*a")
-- if result == nil then return false end
-- handle:close()
-- return result:gsub("%s+$", "") == "0"
-- local function _print_internal_playlist()
-- local count = mp.get_property_number("playlist-count")
-- print("Playlist contents:")
-- for i = 0, count - 1 do
-- local uri = mp.get_property(string.format("playlist/%d/filename", i))
-- print(string.format("%d: %s", i, uri))
-- end
-- end
local function toggle_print()
if destroyer ~= nil then
destroyer()
else
YouTubeQueue.print_queue()
end
end
-- }}}
-- QUEUE GETTERS AND SETTERS {{{
@ -203,15 +211,21 @@ end
-- returns the content of the clipboard
function YouTubeQueue.get_clipboard_content()
local handle = io.popen(options.clipboard_command)
if handle == nil then
print_osd_message("Error getting clipboard content", MSG_DURATION,
local command, args = options.clipboard_command:match("(%S+)%s+(%S+)")
local res = mp.command_native({
name = "subprocess",
playback_only = false,
capture_stdout = true,
args = { command, args }
})
if res.status ~= 0 then
print_osd_message("Failed to get clipboard content", MSG_DURATION,
style.error)
return nil
end
local result = handle:read("*a")
handle:close()
return result
return res.stdout
end
function YouTubeQueue.get_video_info(url)
@ -236,27 +250,41 @@ function YouTubeQueue.get_video_info(url)
return channel_url, channel_name, video_name
end
-- }}}
-- QUEUE FUNCTIONS {{{
-- Function to get the next video in the queue
-- Returns nil if there are no videos in the queue
function YouTubeQueue.next_in_queue()
if index < #video_queue then
index = index + 1
selected_index = index
current_video = video_queue[index]
return current_video
function YouTubeQueue.print_current_video()
destroy()
local current = current_video
if is_file(current.video_url) then
print_osd_message("Playing: " .. current.video_name, 3)
else
print_osd_message("Playing: " .. current.video_name .. ' by ' ..
current.channel_name, 3)
end
end
function YouTubeQueue.prev_in_queue()
if index > 1 then
index = index - 1
selected_index = index
current_video = video_queue[index]
return current_video
-- }}}
-- QUEUE FUNCTIONS {{{
-- Function to set the next or previous video in the queue as the current video
-- direction can be "NEXT" or "PREV". If nil, "next" is assumed
-- Returns nil if there are no more videos in the queue
function YouTubeQueue.set_video(direction)
local amt
direction = string.upper(direction)
if (direction == "NEXT" or direction == nil) then
amt = 1
elseif (direction == "PREV" or direction == "PREVIOUS") then
amt = -1
else
print_osd_message("Invalid direction: " .. direction, MSG_DURATION,
style.error)
return nil
end
if index + amt > #video_queue or index + amt == 0 then return nil end
index = index + amt
selected_index = index
current_video = video_queue[index]
return current_video
end
function YouTubeQueue.is_in_queue(url)
@ -309,11 +337,14 @@ function YouTubeQueue.reorder_queue(from_index, to_index)
table.remove(video_queue, from_index)
table.insert(video_queue, to_index, temp_video)
-- Swap the videos between the two provided indices in the MPV playlist
mp.commandv("playlist-move", from_index - 1, to_index - 1)
-- Redraw the queue after reordering
YouTubeQueue.print_queue()
-- swap the videos in the mpv playlist
-- playlist-move is 0-indexed and works the opposite of what is expected
-- ex: playlist-move 1 2 will move the video at index 2 to index 1
mp.commandv("loadfile", video_queue[to_index].video_url, "append")
mp.commandv("playlist-move", #video_queue, to_index - 1)
mp.commandv("playlist-move", to_index - 1, from_index - 1)
mp.commandv("playlist-move", from_index - 1, #video_queue)
mp.commandv("playlist-remove", #video_queue)
else
print_osd_message("Invalid indices for reordering. No changes made.",
MSG_DURATION, style.error)
@ -321,36 +352,38 @@ function YouTubeQueue.reorder_queue(from_index, to_index)
end
function YouTubeQueue.print_queue(duration)
timeout:kill()
timeout:resume()
local ass = assdraw.ass_new()
local current_index = index
if duration == nil then duration = 3 end
if #video_queue > 0 then
local start_index = math.max(1, selected_index - display_limit / 2)
local end_index =
math.min(#video_queue, start_index + display_limit - 1)
display_offset = start_index - 1
local message =
styleOn .. style.header .. "MPV-YOUTUBE-QUEUE{\\u0\\b0}" ..
style.reset .. style.font .. "\n"
ass:append(
style.header .. "MPV-YOUTUBE-QUEUE{\\u0\\b0}" .. style.reset ..
style.font .. "\n")
local message
for i = start_index, end_index do
local prefix = (i == selected_index) and style.cursor ..
options.cursor_icon .. " " .. style.reset or
" "
if i == current_index and i == selected_index then
message =
message .. prefix .. style.hover_selected .. i .. ". " ..
message = prefix .. style.hover_selected .. i .. ". " ..
video_queue[i].video_name .. " - (" ..
video_queue[i].channel_name .. ")" .. style.reset
elseif i == current_index then
message = message .. prefix .. style.selected .. i .. ". " ..
message = prefix .. style.selected .. i .. ". " ..
video_queue[i].video_name .. " - (" ..
video_queue[i].channel_name .. ")" .. style.reset
elseif i == selected_index then
message = message .. prefix .. style.hover .. i .. ". " ..
message = prefix .. style.hover .. i .. ". " ..
video_queue[i].video_name .. " - (" ..
video_queue[i].channel_name .. ")" .. style.reset
else
message = message .. prefix .. style.reset .. i .. ". " ..
message = prefix .. style.reset .. i .. ". " ..
video_queue[i].video_name .. " - (" ..
video_queue[i].channel_name .. ")" .. style.reset
end
@ -361,49 +394,44 @@ function YouTubeQueue.print_queue(duration)
else
message = message .. "\n"
end
ass:append(style.font .. message)
end
mp.set_osd_ass(0, 0, ass.text)
if duration ~= nil then
mp.add_timeout(duration, function() destroy() end)
end
message = message .. styleOff
mp.osd_message(message, duration)
else
print_osd_message("No videos in the queue or history.", duration,
style.error)
end
destroyer = destroy
end
function YouTubeQueue.move_cursor_up(amt)
function YouTubeQueue.move_cursor(amt)
timeout:kill()
timeout:resume()
selected_index = selected_index - amt
if selected_index < 1 then
selected_index = 1
elseif selected_index > #video_queue then
selected_index = #video_queue
end
if selected_index > 1 and selected_index < display_offset + 1 then
if amt == 1 and selected_index > 1 and selected_index < display_offset + 1 then
display_offset = display_offset - math.abs(selected_index - amt)
end
YouTubeQueue.print_queue(MSG_DURATION)
end
function YouTubeQueue.move_cursor_down(amt)
selected_index = selected_index + amt
if selected_index < 1 then
selected_index = 1
elseif selected_index > #video_queue then
selected_index = #video_queue
end
if selected_index < #video_queue and selected_index > display_offset + display_limit then
elseif amt == -1 and selected_index < #video_queue and selected_index >
display_offset + display_limit then
display_offset = display_offset + math.abs(selected_index - amt)
end
YouTubeQueue.print_queue(MSG_DURATION)
YouTubeQueue.print_queue()
end
function YouTubeQueue.play_video_at(idx)
local queue = YouTubeQueue.get_video_queue()
if idx <= 0 or idx > #queue then
if idx <= 0 or idx > #video_queue then
print_osd_message("Invalid video index", MSG_DURATION, style.error)
return nil
end
YouTubeQueue.set_current_index(idx)
selected_index = index
index = idx
selected_index = idx
mp.set_property_number("playlist-pos", index - 1) -- zero-based index
return current_video
end
@ -411,32 +439,33 @@ end
function YouTubeQueue.play_selected_video()
-- local current_index = YouTubeQueue.get_current_index()
YouTubeQueue.play_video_at(selected_index)
YouTubeQueue.print_queue(MSG_DURATION - 0.5)
sleep(MSG_DURATION)
print_current_video()
YouTubeQueue.print_current_video()
end
-- play the next video in the queue
function YouTubeQueue.play_next_in_queue()
local next_video = YouTubeQueue.next_in_queue()
if next_video == nil then
print_osd_message("No more videos in the queue.", MSG_DURATION,
style.error)
function YouTubeQueue.play_video(direction)
direction = string.upper(direction)
local video = YouTubeQueue.set_video(direction)
if video == nil then
print_osd_message("No video available.", MSG_DURATION, style.error)
return
end
local current_index = YouTubeQueue.get_current_index()
current_video = video
selected_index = index
-- if the current video is not the first in the queue, then play the video
-- else, check if the video is playing and if not play the video with replace
if YouTubeQueue.size() > 1 then
mp.set_property_number("playlist-pos", current_index - 1)
else
if direction == "NEXT" and #video_queue > 1 then
YouTubeQueue.play_video_at(index)
elseif direction == "NEXT" and #video_queue == 1 then
local state = mp.get_property("core-idle")
-- yes if the video is loaded but not currently playing
if state == "yes" then
mp.commandv("loadfile", next_video.video_url, "replace")
mp.commandv("loadfile", video.video_url, "replace")
end
elseif direction == "PREV" or direction == "PREVIOUS" then
mp.set_property_number("playlist-pos", index - 1)
end
print_current_video()
selected_index = current_index
YouTubeQueue.print_current_video()
sleep(MSG_DURATION)
end
@ -492,60 +521,43 @@ function YouTubeQueue.add_to_queue(url, update_internal_playlist)
-- if the queue was empty, start playing the video
-- otherwise, add the video to the playlist
if not YouTubeQueue.get_current_video() then
YouTubeQueue.play_next_in_queue()
YouTubeQueue.play_video("NEXT")
elseif update_internal_playlist == 0 then
mp.commandv("loadfile", url, "append-play")
end
print_osd_message("Added " .. video_name .. " to queue.", MSG_DURATION)
end
-- play the previous video in the queue
function YouTubeQueue.play_previous_video()
local previous_video = YouTubeQueue.prev_in_queue()
if previous_video == nil then
print_osd_message("No previous video available.", MSG_DURATION,
style.error)
return
end
local current_index = YouTubeQueue.get_current_index()
mp.set_property_number("playlist-pos", current_index - 1)
selected_index = current_index
print_current_video()
sleep(MSG_DURATION)
end
function YouTubeQueue.download_video_at(idx)
local o = options
local v = video_queue[idx]
local q = o.download_quality:sub(1, -2)
local dl_dir = expanduser(o.download_directory)
local command = 'yt-dlp -f \'bestvideo[height<=' .. q .. '][ext=' ..
options.ytdlp_file_format .. ']+bestaudio/best[height<=' ..
q .. ']/bestvideo[height<=' .. q ..
']+bestaudio/best[height<=' .. q .. ']\' -o "' .. dl_dir ..
"/" .. options.ytdlp_output_template ..
'" --downloader ' .. o.downloader .. ' ' .. v.video_url
print_osd_message("Downloading " .. v.video_name .. "...", MSG_DURATION)
-- Run the download command
local handle = io.popen(command)
if handle == nil then
print_osd_message("Error starting download.", MSG_DURATION, style.error)
return
end
print_osd_message("Starting download for " .. v.video_name, MSG_DURATION)
local result = handle:read("*a")
handle:close()
if result == nil then
print_osd_message("Error starting download.", MSG_DURATION, style.error)
return
end
if result then
print_osd_message("Finished downloading " .. v.video_name, MSG_DURATION)
else
print_osd_message("Error downloading " .. v.video_name, MSG_DURATION,
style.error)
end
-- local handle = io.popen(command)
mp.command_native_async({
name = "subprocess",
capture_stderr = true,
detach = true,
args = {
"yt-dlp", "-f",
"bestvideo[height<=" .. q .. "][ext=" .. options.ytdlp_file_format ..
"]+bestaudio/best[height<=" .. q .. "]/bestvideo[height<=" .. q ..
"]+bestaudio/best[height<=" .. q .. "]", "-o",
dl_dir .. "/" .. options.ytdlp_output_template, "--downloader",
o.downloader, "--", v.video_url
}
}, function(success, _, err)
if success then
print_osd_message("Finished downloading " .. v.video_name .. ".",
MSG_DURATION)
else
print_osd_message("Error downloading " .. v.video_name .. ": " ..
err, MSG_DURATION, style.error)
end
end)
end
function YouTubeQueue.download_current_video()
@ -566,7 +578,7 @@ function YouTubeQueue.download_selected_video()
print_osd_message("No video to download.", MSG_DURATION, style.error)
return
end
if is_file(YouTubeQueue.get_video_at(selected_index)) then
if is_file(YouTubeQueue.get_video_at(selected_index).video_name) then
print_osd_message("Current video is a local file... doing nothing.",
MSG_DURATION, style.error)
return
@ -619,20 +631,22 @@ end
mp.add_key_binding(options.add_to_queue, "add_to_queue",
YouTubeQueue.add_to_queue)
mp.add_key_binding(options.play_next_in_queue, "play_next_in_queue",
YouTubeQueue.play_next_in_queue)
mp.add_key_binding(options.play_previous_in_queue, "play_previous_video",
YouTubeQueue.play_previous_video)
mp.add_key_binding(options.print_queue, "print_queue", YouTubeQueue.print_queue)
function() YouTubeQueue.play_video("NEXT") end)
mp.add_key_binding(options.play_previous_in_queue, "play_prev_in_queue",
function() YouTubeQueue.play_video("PREV") end)
mp.add_key_binding(options.print_queue, "print_queue", toggle_print)
mp.add_key_binding(options.move_cursor_up, "move_cursor_up",
function() YouTubeQueue.move_cursor_up(1) end, { repeatable = true })
function() YouTubeQueue.move_cursor(1) end,
{ repeatable = true })
mp.add_key_binding(options.move_cursor_down, "move_cursor_down",
function() YouTubeQueue.move_cursor_down(1) end, { repeatable = true })
function() YouTubeQueue.move_cursor(-1) end,
{ repeatable = true })
mp.add_key_binding(options.play_selected_video, "play_selected_video",
YouTubeQueue.play_selected_video)
mp.add_key_binding(options.open_video_in_browser, "open_video_in_browser",
open_video_in_browser)
mp.add_key_binding(options.print_current_video, "print_current_video",
print_current_video)
YouTubeQueue.print_current_video)
mp.add_key_binding(options.open_channel_in_browser, "open_channel_in_browser",
open_channel_in_browser)
mp.add_key_binding(options.download_current_video, "download_current_video",