added mpv scripts

This commit is contained in:
Alexander Bocken 2020-09-12 01:26:41 +02:00
parent 6d4a64ac01
commit de494b6c68
Signed by: Alexander
GPG Key ID: 1D237BE83F9B05E8
3 changed files with 587 additions and 0 deletions

View File

@ -0,0 +1,36 @@
-- mpvSockets, one socket per instance, removes socket on exit
local utils = require 'mp.utils'
local function get_temp_path()
local directory_seperator = package.config:match("([^\n]*)\n?")
local example_temp_file_path = os.tmpname()
-- remove generated temp file
pcall(os.remove, example_temp_file_path)
local seperator_idx = example_temp_file_path:reverse():find(directory_seperator)
local temp_path_length = #example_temp_file_path - seperator_idx
return example_temp_file_path:sub(1, temp_path_length)
end
tempDir = get_temp_path()
function join_paths(...)
local arg={...}
path = ""
for i,v in ipairs(arg) do
path = utils.join_path(path, tostring(v))
end
return path;
end
ppid = utils.getpid()
os.execute("mkdir " .. join_paths(tempDir, "mpvSockets") .. " 2>/dev/null")
mp.set_property("options/input-ipc-server", join_paths(tempDir, "mpvSockets", ppid))
function shutdown_handler()
os.remove(join_paths(tempDir, "mpvSockets", ppid))
end
mp.register_event("shutdown", shutdown_handler)

View File

@ -0,0 +1,275 @@
-- youtube-quality.lua
--
-- Change youtube video quality on the fly.
--
-- Diplays a menu that lets you switch to different ytdl-format settings while
-- you're in the middle of a video (just like you were using the web player).
--
-- Bound to ctrl-f by default.
local mp = require 'mp'
local utils = require 'mp.utils'
local msg = require 'mp.msg'
local assdraw = require 'mp.assdraw'
local opts = {
--key bindings
toggle_menu_binding = "ctrl+f",
up_binding = "UP",
down_binding = "DOWN",
select_binding = "ENTER",
--formatting / cursors
selected_and_active = "▶ - ",
selected_and_inactive = "● - ",
unselected_and_active = "▷ - ",
unselected_and_inactive = "○ - ",
--font size scales by window, if false requires larger font and padding sizes
scale_playlist_by_window=false,
--playlist ass style overrides inside curly brackets, \keyvalue is one field, extra \ for escape in lua
--example {\\fnUbuntu\\fs10\\b0\\bord1} equals: font=Ubuntu, size=10, bold=no, border=1
--read http://docs.aegisub.org/3.2/ASS_Tags/ for reference of tags
--undeclared tags will use default osd settings
--these styles will be used for the whole playlist. More specific styling will need to be hacked in
--
--(a monospaced font is recommended but not required)
style_ass_tags = "{\\fnmonospace}",
--paddings for top left corner
text_padding_x = 5,
text_padding_y = 5,
--other
menu_timeout = 10,
--use youtube-dl to fetch a list of available formats (overrides quality_strings)
fetch_formats = true,
--default menu entries
quality_strings=[[
[
{"4320p" : "bestvideo[height<=?4320p]+bestaudio/best"},
{"2160p" : "bestvideo[height<=?2160]+bestaudio/best"},
{"1440p" : "bestvideo[height<=?1440]+bestaudio/best"},
{"1080p" : "bestvideo[height<=?1080]+bestaudio/best"},
{"720p" : "bestvideo[height<=?720]+bestaudio/best"},
{"480p" : "bestvideo[height<=?480]+bestaudio/best"},
{"360p" : "bestvideo[height<=?360]+bestaudio/best"},
{"240p" : "bestvideo[height<=?240]+bestaudio/best"},
{"144p" : "bestvideo[height<=?144]+bestaudio/best"}
]
]],
}
(require 'mp.options').read_options(opts, "youtube-quality")
opts.quality_strings = utils.parse_json(opts.quality_strings)
local destroyer = nil
function show_menu()
local selected = 1
local active = 0
local current_ytdl_format = mp.get_property("ytdl-format")
msg.verbose("current ytdl-format: "..current_ytdl_format)
local num_options = 0
local options = {}
if opts.fetch_formats then
options, num_options = download_formats()
end
if next(options) == nil then
for i,v in ipairs(opts.quality_strings) do
num_options = num_options + 1
for k,v2 in pairs(v) do
options[i] = {label = k, format=v2}
if v2 == current_ytdl_format then
active = i
selected = active
end
end
end
end
--set the cursor to the currently format
for i,v in ipairs(options) do
if v.format == current_ytdl_format then
active = i
selected = active
break
end
end
function selected_move(amt)
selected = selected + amt
if selected < 1 then selected = num_options
elseif selected > num_options then selected = 1 end
timeout:kill()
timeout:resume()
draw_menu()
end
function choose_prefix(i)
if i == selected and i == active then return opts.selected_and_active
elseif i == selected then return opts.selected_and_inactive end
if i ~= selected and i == active then return opts.unselected_and_active
elseif i ~= selected then return opts.unselected_and_inactive end
return "> " --shouldn't get here.
end
function draw_menu()
local ass = assdraw.ass_new()
ass:pos(opts.text_padding_x, opts.text_padding_y)
ass:append(opts.style_ass_tags)
for i,v in ipairs(options) do
ass:append(choose_prefix(i)..v.label.."\\N")
end
local w, h = mp.get_osd_size()
if opts.scale_playlist_by_window then w,h = 0, 0 end
mp.set_osd_ass(w, h, ass.text)
end
function destroy()
timeout:kill()
mp.set_osd_ass(0,0,"")
mp.remove_key_binding("move_up")
mp.remove_key_binding("move_down")
mp.remove_key_binding("select")
mp.remove_key_binding("escape")
destroyer = nil
end
timeout = mp.add_periodic_timer(opts.menu_timeout, destroy)
destroyer = destroy
mp.add_forced_key_binding(opts.up_binding, "move_up", function() selected_move(-1) end, {repeatable=true})
mp.add_forced_key_binding(opts.down_binding, "move_down", function() selected_move(1) end, {repeatable=true})
mp.add_forced_key_binding(opts.select_binding, "select", function()
destroy()
mp.set_property("ytdl-format", options[selected].format)
reload_resume()
end)
mp.add_forced_key_binding(opts.toggle_menu_binding, "escape", destroy)
draw_menu()
return
end
local ytdl = {
path = "youtube-dl",
searched = false,
blacklisted = {}
}
format_cache={}
function download_formats()
local function exec(args)
local ret = utils.subprocess({args = args})
return ret.status, ret.stdout, ret
end
local function table_size(t)
s = 0
for i,v in ipairs(t) do
s = s+1
end
return s
end
local url = mp.get_property("path")
url = string.gsub(url, "ytdl://", "") -- Strip possible ytdl:// prefix.
-- don't fetch the format list if we already have it
if format_cache[url] ~= nil then
local res = format_cache[url]
return res, table_size(res)
end
mp.osd_message("fetching available formats with youtube-dl...", 60)
if not (ytdl.searched) then
local ytdl_mcd = mp.find_config_file("youtube-dl")
if not (ytdl_mcd == nil) then
msg.verbose("found youtube-dl at: " .. ytdl_mcd)
ytdl.path = ytdl_mcd
end
ytdl.searched = true
end
local command = {ytdl.path, "--no-warnings", "--no-playlist", "-J"}
table.insert(command, url)
local es, json, result = exec(command)
if (es < 0) or (json == nil) or (json == "") then
mp.osd_message("fetching formats failed...", 1)
msg.error("failed to get format list: " .. err)
return {}, 0
end
local json, err = utils.parse_json(json)
if (json == nil) then
mp.osd_message("fetching formats failed...", 1)
msg.error("failed to parse JSON data: " .. err)
return {}, 0
end
res = {}
msg.verbose("youtube-dl succeeded!")
for i,v in ipairs(json.formats) do
if v.vcodec ~= "none" then
local fps = v.fps and v.fps.."fps" or ""
local resolution = string.format("%sx%s", v.width, v.height)
local l = string.format("%-9s %-5s (%-4s / %s)", resolution, fps, v.ext, v.vcodec)
local f = string.format("%s+bestaudio/best", v.format_id)
table.insert(res, {label=l, format=f, width=v.width })
end
end
table.sort(res, function(a, b) return a.width > b.width end)
mp.osd_message("", 0)
format_cache[url] = res
return res, table_size(res)
end
-- register script message to show menu
mp.register_script_message("toggle-quality-menu",
function()
if destroyer ~= nil then
destroyer()
else
show_menu()
end
end)
-- keybind to launch menu
mp.add_key_binding(opts.toggle_menu_binding, "quality-menu", show_menu)
-- special thanks to reload.lua (https://github.com/4e6/mpv-reload/)
function reload_resume()
local playlist_pos = mp.get_property_number("playlist-pos")
local reload_duration = mp.get_property_native("duration")
local time_pos = mp.get_property("time-pos")
mp.set_property_number("playlist-pos", playlist_pos)
-- Tries to determine live stream vs. pre-recordered VOD. VOD has non-zero
-- duration property. When reloading VOD, to keep the current time position
-- we should provide offset from the start. Stream doesn't have fixed start.
-- Decent choice would be to reload stream from it's current 'live' positon.
-- That's the reason we don't pass the offset when reloading streams.
if reload_duration and reload_duration > 0 then
local function seeker()
mp.commandv("seek", time_pos, "absolute")
mp.unregister_event(seeker)
end
mp.register_event("file-loaded", seeker)
end
end

View File

@ -0,0 +1,276 @@
-- youtube-upnext.lua
--
-- Fetch upnext/recommended videos from youtube
-- This is forked/based on https://github.com/jgreco/mpv-youtube-quality
--
-- Diplays a menu that lets you load the upnext/recommended video from youtube
-- that appear on the right side on the youtube website.
-- If auto_add is set to true (default), the 'up next' video is automatically
-- appended to the current playlist
--
-- Bound to ctrl-u by default.
--
-- Requires wget/wget.exe in PATH. On Windows you may need to set check_certificate
-- to false, otherwise wget.exe might not be able to download the youtube website.
local mp = require 'mp'
local utils = require 'mp.utils'
local msg = require 'mp.msg'
local assdraw = require 'mp.assdraw'
local opts = {
--key bindings
toggle_menu_binding = "ctrl+u",
up_binding = "UP",
down_binding = "DOWN",
select_binding = "ENTER",
--auto load and add the "upnext" video to the playlist
auto_add = true,
--formatting / cursors
cursor_selected = "",
cursor_unselected = "",
--font size scales by window, if false requires larger font and padding sizes
scale_playlist_by_window=false,
--playlist ass style overrides inside curly brackets, \keyvalue is one field, extra \ for escape in lua
--example {\\fnUbuntu\\fs10\\b0\\bord1} equals: font=Ubuntu, size=10, bold=no, border=1
--read http://docs.aegisub.org/3.2/ASS_Tags/ for reference of tags
--undeclared tags will use default osd settings
--these styles will be used for the whole playlist. More specific styling will need to be hacked in
--
--(a monospaced font is recommended but not required)
style_ass_tags = "{\\fnmonospace}",
--paddings for top left corner
text_padding_x = 5,
text_padding_y = 5,
--other
menu_timeout = 10,
youtube_url = "https://www.youtube.com/watch?v=%s",
check_certificate = true,
}
(require 'mp.options').read_options(opts, "youtube-upnext")
local destroyer = nil
upnext_cache={}
function on_file_loaded(event)
local url = mp.get_property("path")
url = string.gsub(url, "ytdl://", "") -- Strip possible ytdl:// prefix.
if string.find(url, "youtu") ~= nil then
local upnext, num_upnext = load_upnext()
if num_upnext > 0 then
mp.commandv("loadfile", upnext[1].file, "append")
end
end
end
function show_menu()
mp.osd_message("fetching 'up next' with wget...", 60)
local upnext, num_upnext = load_upnext()
mp.osd_message("", 1)
if num_upnext == 0 then
return
end
local selected = 1
function selected_move(amt)
selected = selected + amt
if selected < 1 then
selected = num_upnext
elseif selected > num_upnext then
selected = 1
end
timeout:kill()
timeout:resume()
draw_menu()
end
function choose_prefix(i)
if i == selected then
return opts.cursor_selected
else
return opts.cursor_unselected
end
end
function draw_menu()
local ass = assdraw.ass_new()
ass:pos(opts.text_padding_x, opts.text_padding_y)
ass:append(opts.style_ass_tags)
for i,v in ipairs(upnext) do
ass:append(choose_prefix(i)..v.label.."\\N")
end
local w, h = mp.get_osd_size()
if opts.scale_playlist_by_window then w,h = 0, 0 end
mp.set_osd_ass(w, h, ass.text)
end
function destroy()
timeout:kill()
mp.set_osd_ass(0,0,"")
mp.remove_key_binding("move_up")
mp.remove_key_binding("move_down")
mp.remove_key_binding("select")
mp.remove_key_binding("escape")
destroyer = nil
end
timeout = mp.add_periodic_timer(opts.menu_timeout, destroy)
destroyer = destroy
mp.add_forced_key_binding(opts.up_binding, "move_up", function() selected_move(-1) end, {repeatable=true})
mp.add_forced_key_binding(opts.down_binding, "move_down", function() selected_move(1) end, {repeatable=true})
mp.add_forced_key_binding(opts.select_binding, "select", function()
destroy()
mp.commandv("loadfile", upnext[selected].file, "replace")
reload_resume()
end)
mp.add_forced_key_binding(opts.toggle_menu_binding, "escape", destroy)
draw_menu()
return
end
function table_size(t)
local s = 0
for i,v in ipairs(t) do
s = s+1
end
return s
end
function load_upnext()
local url = mp.get_property("path")
url = string.gsub(url, "ytdl://", "") -- Strip possible ytdl:// prefix.
if string.find(url, "//youtu.be/") == nil
and string.find(url, "//ww.youtu.be/") == nil
and string.find(url, "//youtube.com/") == nil
and string.find(url, "//www.youtube.com/") == nil
then
return {}, 0
end
-- don't fetch the website if it's already cached
if upnext_cache[url] ~= nil then
local res = upnext_cache[url]
return res, table_size(res)
end
local res, n = parse_upnext(download_upnext(url), url)
return res, n
end
function download_upnext(url)
local function exec(args)
local ret = utils.subprocess({args = args})
return ret.status, ret.stdout, ret
end
local command = {"wget", "-q", "-O", "-"}
if not opts.check_certificate then
table.insert(command, "--no-check-certificate")
end
table.insert(command, url)
local es, s, result = exec(command)
if (es ~= 0) or (s == nil) or (s == "") then
if es == 5 then
mp.osd_message("upnext failed: wget does not support HTTPS", 10)
msg.error("wget is missing certificates, disable check-certificate in userscript options")
elseif es == -1 or es == 127 or es == 9009 then
mp.osd_message("upnext failed: wget not found", 10)
msg.error("wget/ wget.exe is missing. Please install it or put an executable in your PATH")
else
mp.osd_message("upnext failed: error=" .. tostring(es), 10)
msg.error("failed to get upnext list: error=%s" .. tostring(es))
end
return "{}"
end
local pos1 = string.find(s, "watchNextEndScreenRenderer", 1, true)
if pos1 == nil then
mp.osd_message("upnext failed, no upnext data found err01", 10)
msg.error("failed to find json position 01: pos1=nil")
return "{}"
end
local pos2 = string.find(s, "}}}],\\\"", pos1 + 1, true)
if pos2 ~= nil then
s = string.sub(s, pos1, pos2)
return "{\"" .. string.gsub(s, "\\\"", "\"") .. "}}]}}"
end
msg.verbose("failed to find json position 2: Trying alternative")
pos2 = string.find(s, "}}}]}}", pos1 + 1, true)
if pos2 ~= nil then
msg.verbose("Alternative found!")
s = string.sub(s, pos1, pos2)
return "{\"" .. string.gsub(s, "\\\"", "\"") .. "}}]}}]}}"
end
mp.osd_message("upnext failed, no upnext data found err03", 10)
msg.error("failed to get upnext data: pos1=" .. tostring(pos1) .. " pos2=" ..tostring(pos2))
return "{}"
end
function parse_upnext(json_str, url)
if json_str == "{}" then
return {}, 0
end
local data, err = utils.parse_json(json_str)
if data == nil then
mp.osd_message("upnext failed: JSON decode failed", 10)
msg.error("parse_json failed: " .. err)
return {}, 0
end
local res = {}
msg.verbose("wget and json decode succeeded!")
for i, v in ipairs(data.watchNextEndScreenRenderer.results) do
if v.endScreenVideoRenderer ~= nil and v.endScreenVideoRenderer.title ~= nil and v.endScreenVideoRenderer.title.simpleText ~= nil then
local title = v.endScreenVideoRenderer.title.simpleText
local video_id = v.endScreenVideoRenderer.videoId
table.insert(res, {
index=i,
label=title,
file=string.format(opts.youtube_url, video_id)
})
end
end
table.sort(res, function(a, b) return a.index < b.index end)
upnext_cache[url] = res
return res, table_size(res)
end
-- register script message to show menu
mp.register_script_message("toggle-upnext-menu",
function()
if destroyer ~= nil then
destroyer()
else
show_menu()
end
end)
-- keybind to launch menu
mp.add_key_binding(opts.toggle_menu_binding, "upnext-menu", show_menu)
if opts.auto_add then
mp.register_event("file-loaded", on_file_loaded)
end