Compare commits

..

No commits in common. "d41d85b0e3e9ba748878a6d387787e81bde7521b" and "894bc5b8e6fef95d9a9f2d7dfaf7dfae8b530ccc" have entirely different histories.

3 changed files with 58 additions and 112 deletions

View File

@ -8,20 +8,22 @@ navigate through the queue, and select a video to play.
## Features ## Features
- Add YouTube videos to a queue from the clipboard - Add YouTube videos to a queue from the clipboard
- Fetch and display the video and channel names of the videos in the queue - Select a video from the queue to play from an interactive menu,
- Select a video to play from the queue with an interactive menu, or navigate through the queue with keybinds
or navigate through the queue with keyboard shortcuts - Open the URL of the currently playing video in a new browser tab.
- Open the URL of the currently playing video in a new browser tab - Fetch and display the names of YouTube videos.
- Open the channel page of the currently playing video - Print the current contents of the queue
- Download the currently playing video
## Notes ## Notes
- This script uses the Linux `xclip` utility to read from the clipboard. - This script uses the Linux `xclip` utility to read from the clipboard.
If you're on macOS or Windows, you'll need to adjust the `clipboard_command` If you're on macOS or Windows, you'll need to adjust the setting in
config variable in [mpv-youtube-queue.conf](./mpv-youtube-queue.conf) `mpv-youtube-queue.conf` as described in the [install section](#installation).
- When adding videos to the queue, the script fetches the video name using - When adding videos to the queue, the script fetches the video name using
`yt-dlp`. Ensure you have `yt-dlp` installed and in your PATH. `yt-dlp`. Ensure you have `yt-dlp` installed and in your PATH.
- The script maintains its own queue separate from mpv's internal playlist.
This means that loading files manually or using the next/previous buttons on
the mpv OSC will not affect the queue.
## Requirements ## Requirements
@ -34,7 +36,7 @@ This script requires the following software to be installed on the system
- Copy the `mpv-youtube-queue.lua` script to your `~~/scripts` directory - Copy the `mpv-youtube-queue.lua` script to your `~~/scripts` directory
- Optionally copy the `mpv-youtube-queue.conf` to the `~~/script-opts` directory - Optionally copy the `mpv-youtube-queue.conf` to the `~~/script-opts` directory
to customize the script configuration to customize the keybindings
## License ## License

View File

@ -14,5 +14,3 @@ display_limit=6
cursor_icon=🠺 cursor_icon=🠺
font_size=24 font_size=24
font_name=JetBrains Mono font_name=JetBrains Mono
download_quality=720p
download_directory=~/Videos/YouTube

View File

@ -35,15 +35,12 @@ local options = {
open_video_in_browser = "ctrl+o", open_video_in_browser = "ctrl+o",
open_channel_in_browser = "ctrl+O", open_channel_in_browser = "ctrl+O",
print_current_video = "ctrl+P", print_current_video = "ctrl+P",
download_current_video = "ctrl+d",
browser = "firefox", browser = "firefox",
clipboard_command = "xclip -o", clipboard_command = "xclip -o",
display_limit = 6, display_limit = 6,
cursor_icon = "🠺", cursor_icon = "🠺",
font_size = 14, font_size = 14,
font_name = "JetBrains Mono", font_name = "JetBrains Mono"
download_quality = "720p",
download_directory = "~/Videos/YouTube"
} }
local colors = { local colors = {
@ -83,42 +80,17 @@ local display_offset = 0
-- run sleep shell command for n seconds -- run sleep shell command for n seconds
local function sleep(n) os.execute("sleep " .. tonumber(n)) end local function sleep(n) os.execute("sleep " .. tonumber(n)) end
local function print_osd_message(message, duration, s) local function print_osd_message(message, duration, color)
if not s then s = style.font .. "{" .. notransparent .. "}" end if not color then color = colors.text end
if not duration then duration = MSG_DURATION end mp.osd_message(styleOn .. "{\\c&" .. color .. "&}" .. message .. "{\\c&" ..
mp.osd_message(styleOn .. s .. message .. style.reset .. styleOff .. "\n", colors.text .. "&}" .. styleOff .. "\n", duration)
duration)
end end
local function print_current_video() -- print the name of the current video to the OSD
print_osd_message("Playing: " .. current_video.video_name .. ' by ' .. local function print_video_name(video, duration)
current_video.video_name, 3) if not video then return end
end if not duration then duration = 2 end
print_osd_message('Playing: ' .. video.video_name, duration)
local function expanduser(path)
if path:sub(1, 1) == "~" then
local home = os.getenv("HOME")
if home then
return home .. path:sub(2)
else
return path
end
else
return path
end
end
local function open_url_in_browser(url)
local command = options.browser .. " " .. url
os.execute(command)
end
local function open_video_in_browser()
open_url_in_browser(current_video.video_url)
end
local function open_channel_in_browser()
open_url_in_browser(current_video.channel_url)
end end
local function get_video_info(url) local function get_video_info(url)
@ -172,7 +144,7 @@ function YouTubeQueue.get_current_video() return current_video end
function YouTubeQueue.get_video_at(idx) function YouTubeQueue.get_video_at(idx)
if idx <= 0 or idx > #video_queue then if idx <= 0 or idx > #video_queue then
print_osd_message("Invalid video index", MSG_DURATION, style.error) print_osd_message("Invalid video index", MSG_DURATION, colors.error)
return nil return nil
end end
return video_queue[idx] return video_queue[idx]
@ -200,14 +172,14 @@ function YouTubeQueue.prev_in_queue()
index = index - 1 index = index - 1
selected_index = index selected_index = index
current_video = video_queue[index] current_video = video_queue[index]
return current_video else
current_video = video_queue[1]
end end
return current_video
end end
function YouTubeQueue.is_in_queue(url) function YouTubeQueue.is_in_queue(url)
for _, v in ipairs(video_queue) do for _, v in ipairs(video_queue) do if v.url == url then return true end end
if v.video_url == url then return true end
end
return false return false
end end
@ -216,7 +188,7 @@ function YouTubeQueue.update_current_index()
local current_url = mp.get_property("path") local current_url = mp.get_property("path")
if #video_queue == 0 then return end if #video_queue == 0 then return end
for i, v in ipairs(video_queue) do for i, v in ipairs(video_queue) do
if v.video_url == current_url then if v.url == current_url then
index = i index = i
return return
end end
@ -284,7 +256,7 @@ function YouTubeQueue.print_queue(duration)
mp.osd_message(message, duration) mp.osd_message(message, duration)
else else
print_osd_message("No videos in the queue or history.", duration, print_osd_message("No videos in the queue or history.", duration,
style.error) colors.error)
end end
end end
@ -297,7 +269,7 @@ local function get_clipboard_content()
local handle = io.popen(options.clipboard_command) local handle = io.popen(options.clipboard_command)
if not handle then if not handle then
print_osd_message("Error getting clipboard content", MSG_DURATION, print_osd_message("Error getting clipboard content", MSG_DURATION,
style.error) colors.error)
return nil return nil
end end
local result = handle:read("*a") local result = handle:read("*a")
@ -328,7 +300,7 @@ end
local function play_video_at(idx) local function play_video_at(idx)
local queue = YouTubeQueue.get_video_queue() local queue = YouTubeQueue.get_video_queue()
if idx <= 0 or idx > #queue then if idx <= 0 or idx > #queue then
print_osd_message("Invalid video index", MSG_DURATION, style.error) print_osd_message("Invalid video index", MSG_DURATION, colors.error)
return nil return nil
end end
YouTubeQueue.set_current_index(idx) YouTubeQueue.set_current_index(idx)
@ -339,28 +311,24 @@ end
local function play_selected_video() local function play_selected_video()
-- local current_index = YouTubeQueue.get_current_index() -- local current_index = YouTubeQueue.get_current_index()
play_video_at(selected_index) local video = play_video_at(selected_index)
YouTubeQueue.print_queue(MSG_DURATION - 0.5) YouTubeQueue.print_queue(MSG_DURATION - 0.5)
sleep(MSG_DURATION) sleep(MSG_DURATION)
print_current_video() print_video_name(video, MSG_DURATION)
end end
-- play the next video in the queue -- play the next video in the queue
local function play_next_in_queue() local function play_next_in_queue()
local next_video = YouTubeQueue.next_in_queue() local next_video = YouTubeQueue.next_in_queue()
if not next_video or next_video == "" then if not next_video then return end
print_osd_message("No more videos in the queue.", MSG_DURATION, local next_video_url = next_video.url
style.error)
return
end
local next_video_url = next_video.video_url
local current_index = YouTubeQueue.get_current_index() local current_index = YouTubeQueue.get_current_index()
if YouTubeQueue.size() > 1 then if YouTubeQueue.size() > 1 then
mp.set_property_number("playlist-pos", current_index - 1) mp.set_property_number("playlist-pos", current_index - 1)
else else
mp.commandv("loadfile", next_video_url, "replace") mp.commandv("loadfile", next_video_url, "replace")
end end
print_current_video() print_video_name(next_video, MSG_DURATION)
selected_index = current_index selected_index = current_index
sleep(MSG_DURATION) sleep(MSG_DURATION)
end end
@ -370,11 +338,11 @@ local function add_to_queue()
local url = get_clipboard_content() local url = get_clipboard_content()
if not url then if not url then
print_osd_message("Nothing found in the clipboard.", MSG_DURATION, print_osd_message("Nothing found in the clipboard.", MSG_DURATION,
style.error) colors.error)
return return
end end
if YouTubeQueue.is_in_queue(url) then if YouTubeQueue.is_in_queue(url) then
print_osd_message("Video already in queue.", MSG_DURATION, style.error) print_osd_message("Video already in queue.", MSG_DURATION, colors.error)
return return
-- elseif not is_valid_ytdlp_url(url) then -- elseif not is_valid_ytdlp_url(url) then
-- mp.osd_message("Invalid URL.") -- mp.osd_message("Invalid URL.")
@ -383,12 +351,13 @@ local function add_to_queue()
local channel_url, channel_name, video_name = get_video_info(url) local channel_url, channel_name, video_name = get_video_info(url)
if (not channel_url or not channel_name or not video_name) or if (not channel_url or not channel_name or not video_name) or
(channel_url == "" or channel_name == "" or video_name == "") then (channel_url == "" or channel_name == "" or video_name == "") then
print_osd_message("Error getting video info.", MSG_DURATION, style.error) print_osd_message("Error getting video info.", MSG_DURATION,
colors.error)
return return
end end
YouTubeQueue.add_to_queue({ YouTubeQueue.add_to_queue({
video_url = url, url = url,
video_name = video_name, video_name = video_name,
channel_url = channel_url, channel_url = channel_url,
channel_name = channel_name channel_name = channel_name
@ -404,55 +373,34 @@ end
-- play the previous video in the queue -- play the previous video in the queue
local function play_previous_video() local function play_previous_video()
local previous_video = YouTubeQueue.prev_in_queue() local previous_video = YouTubeQueue.prev_in_queue()
if not previous_video or previous_video == "" then local current_index = YouTubeQueue.get_current_index()
if not previous_video then
print_osd_message("No previous video available.", MSG_DURATION, print_osd_message("No previous video available.", MSG_DURATION,
style.error) colors.error)
return return
end end
local current_index = YouTubeQueue.get_current_index()
mp.set_property_number("playlist-pos", current_index - 1) mp.set_property_number("playlist-pos", current_index - 1)
selected_index = current_index selected_index = current_index
print_current_video() print_video_name(previous_video, MSG_DURATION)
sleep(MSG_DURATION) sleep(MSG_DURATION)
end end
local function download_current_video() local function open_url_in_browser(url)
if current_video and current_video ~= "" then local command = options.browser .. " " .. url
local o = options os.execute(command)
local v = current_video
local q = o.download_quality:sub(1, -2)
local command = 'yt-dlp -f \'bestvideo[height<=' .. q ..
']+bestaudio/best[height<=' .. q ..
']\' --newline -o "' ..
expanduser(o.download_directory) .. '/' ..
v.channel_name .. '/' .. v.video_name ..
'.%(ext)s" ' .. v.video_url
-- Run the download command
local handle = io.popen(command)
if not handle then
mp.osd_message("Error starting download.")
return
end
local result = handle:read("*a")
if not result then
mp.osd_message("Error starting download.")
return
end
handle:close()
mp.msg.log("info", "RESULTS: " .. result)
if result then
print_osd_message("Downloading " .. v.video_name, MSG_DURATION)
else
print_osd_message("Error starting download for " .. v.video_name,
MSG_DURATION, style.error)
end
else
print_osd_message("No video to download.", MSG_DURATION, style.error)
end
end end
local function open_video_in_browser() open_url_in_browser(current_video.url) end
local function open_channel_in_browser()
open_url_in_browser(current_video.channel_url)
end
local function print_current_video()
print_osd_message(
"Currently playing " .. current_video.video_name .. ' by ' ..
current_video.video_name, 3)
end
-- }}} -- }}}
-- KEY BINDINGS {{{ -- KEY BINDINGS {{{
@ -474,8 +422,6 @@ mp.add_key_binding(options.print_current_video, "print_current_video",
print_current_video) print_current_video)
mp.add_key_binding(options.open_channel_in_browser, "open_channel_in_browser", mp.add_key_binding(options.open_channel_in_browser, "open_channel_in_browser",
open_channel_in_browser) open_channel_in_browser)
mp.add_key_binding(options.download_current_video, "download_current_video",
download_current_video)
-- Listen for the file-loaded event -- Listen for the file-loaded event
mp.register_event("end-file", YouTubeQueue.on_end_file) mp.register_event("end-file", YouTubeQueue.on_end_file)