mirror of
https://github.com/ksyasuda/mpv-youtube-queue.git
synced 2024-11-22 03:19:54 -08:00
Compare commits
3 Commits
894bc5b8e6
...
d41d85b0e3
Author | SHA1 | Date | |
---|---|---|---|
|
d41d85b0e3 | ||
|
90c255b60e | ||
|
0ad2880098 |
20
README.md
20
README.md
@ -8,22 +8,20 @@ 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
|
||||||
- Select a video from the queue to play from an interactive menu,
|
- Fetch and display the video and channel names of the videos in the queue
|
||||||
or navigate through the queue with keybinds
|
- Select a video to play from the queue with an interactive menu,
|
||||||
- Open the URL of the currently playing video in a new browser tab.
|
or navigate through the queue with keyboard shortcuts
|
||||||
- Fetch and display the names of YouTube videos.
|
- Open the URL of the currently playing video in a new browser tab
|
||||||
- Print the current contents of the queue
|
- Open the channel page of the currently playing video
|
||||||
|
- 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 setting in
|
If you're on macOS or Windows, you'll need to adjust the `clipboard_command`
|
||||||
`mpv-youtube-queue.conf` as described in the [install section](#installation).
|
config variable in [mpv-youtube-queue.conf](./mpv-youtube-queue.conf)
|
||||||
- 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
|
||||||
|
|
||||||
@ -36,7 +34,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 keybindings
|
to customize the script configuration
|
||||||
|
|
||||||
## License
|
## License
|
||||||
|
|
||||||
|
@ -14,3 +14,5 @@ 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
|
||||||
|
@ -35,12 +35,15 @@ 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 = {
|
||||||
@ -80,17 +83,42 @@ 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, color)
|
local function print_osd_message(message, duration, s)
|
||||||
if not color then color = colors.text end
|
if not s then s = style.font .. "{" .. notransparent .. "}" end
|
||||||
mp.osd_message(styleOn .. "{\\c&" .. color .. "&}" .. message .. "{\\c&" ..
|
if not duration then duration = MSG_DURATION end
|
||||||
colors.text .. "&}" .. styleOff .. "\n", duration)
|
mp.osd_message(styleOn .. s .. message .. style.reset .. styleOff .. "\n",
|
||||||
|
duration)
|
||||||
end
|
end
|
||||||
|
|
||||||
-- print the name of the current video to the OSD
|
local function print_current_video()
|
||||||
local function print_video_name(video, duration)
|
print_osd_message("Playing: " .. current_video.video_name .. ' by ' ..
|
||||||
if not video then return end
|
current_video.video_name, 3)
|
||||||
if not duration then duration = 2 end
|
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)
|
||||||
@ -144,7 +172,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, colors.error)
|
print_osd_message("Invalid video index", MSG_DURATION, style.error)
|
||||||
return nil
|
return nil
|
||||||
end
|
end
|
||||||
return video_queue[idx]
|
return video_queue[idx]
|
||||||
@ -172,14 +200,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]
|
||||||
else
|
return current_video
|
||||||
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 if v.url == url then return true end end
|
for _, v in ipairs(video_queue) do
|
||||||
|
if v.video_url == url then return true end
|
||||||
|
end
|
||||||
return false
|
return false
|
||||||
end
|
end
|
||||||
|
|
||||||
@ -188,7 +216,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.url == current_url then
|
if v.video_url == current_url then
|
||||||
index = i
|
index = i
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
@ -256,7 +284,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,
|
||||||
colors.error)
|
style.error)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@ -269,7 +297,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,
|
||||||
colors.error)
|
style.error)
|
||||||
return nil
|
return nil
|
||||||
end
|
end
|
||||||
local result = handle:read("*a")
|
local result = handle:read("*a")
|
||||||
@ -300,7 +328,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, colors.error)
|
print_osd_message("Invalid video index", MSG_DURATION, style.error)
|
||||||
return nil
|
return nil
|
||||||
end
|
end
|
||||||
YouTubeQueue.set_current_index(idx)
|
YouTubeQueue.set_current_index(idx)
|
||||||
@ -311,24 +339,28 @@ 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()
|
||||||
local video = play_video_at(selected_index)
|
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_video_name(video, MSG_DURATION)
|
print_current_video()
|
||||||
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 then return end
|
if not next_video or next_video == "" then
|
||||||
local next_video_url = next_video.url
|
print_osd_message("No more videos in the queue.", MSG_DURATION,
|
||||||
|
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_video_name(next_video, MSG_DURATION)
|
print_current_video()
|
||||||
selected_index = current_index
|
selected_index = current_index
|
||||||
sleep(MSG_DURATION)
|
sleep(MSG_DURATION)
|
||||||
end
|
end
|
||||||
@ -338,11 +370,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,
|
||||||
colors.error)
|
style.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, colors.error)
|
print_osd_message("Video already in queue.", MSG_DURATION, style.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.")
|
||||||
@ -351,13 +383,12 @@ 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,
|
print_osd_message("Error getting video info.", MSG_DURATION, style.error)
|
||||||
colors.error)
|
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
|
|
||||||
YouTubeQueue.add_to_queue({
|
YouTubeQueue.add_to_queue({
|
||||||
url = url,
|
video_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
|
||||||
@ -373,34 +404,55 @@ 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()
|
||||||
local current_index = YouTubeQueue.get_current_index()
|
if not previous_video or previous_video == "" then
|
||||||
if not previous_video then
|
|
||||||
print_osd_message("No previous video available.", MSG_DURATION,
|
print_osd_message("No previous video available.", MSG_DURATION,
|
||||||
colors.error)
|
style.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_video_name(previous_video, MSG_DURATION)
|
print_current_video()
|
||||||
sleep(MSG_DURATION)
|
sleep(MSG_DURATION)
|
||||||
end
|
end
|
||||||
|
|
||||||
local function open_url_in_browser(url)
|
local function download_current_video()
|
||||||
local command = options.browser .. " " .. url
|
if current_video and current_video ~= "" then
|
||||||
os.execute(command)
|
local o = options
|
||||||
|
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 {{{
|
||||||
@ -422,6 +474,8 @@ 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)
|
||||||
|
Loading…
Reference in New Issue
Block a user