[mpv] Add playlistmanager

This commit is contained in:
Marty Oehme 2020-05-13 23:35:28 +02:00
parent f0cabb9149
commit 2c11d14cbd
No known key found for this signature in database
GPG Key ID: 0CCB0526EFB9611A
4 changed files with 1005 additions and 1 deletions

View File

@ -32,7 +32,8 @@ f cycle fullscreen # toggle fullscreen
F5 async screenshot # take a screenshot
Shift+F5 async screenshot video # screenshot without subtitles
i show-text ${playlist} # diplay osd playlist
i script-message playlistmanager show playlist toggle # toggle advanced playlist
I script-message playlistmanager show filename
R cycle-values video-aspect "16:9" "4:3" "3.25:1" "no" "-1" # cycle aspect ratios
# uosc definitions and menu

View File

@ -0,0 +1,23 @@
#navigation keybindings force override only while playlist is visible
#if "no" then you can display the playlist by any of the navigation keys
dynamic_binds=yes
#dynamic keybind keys, they should not be re-bound in input.conf
#to bind multiple keys separate them by a space
key_moveup=UP
key_movedown=DOWN
key_selectfile=RIGHT LEFT
key_unselectfile=
key_playfile=ENTER
key_removefile=BS
key_closeplaylist=ESC
#Use ~ for home directory. Leave as empty to use mpv/playlists
playlist_savepath=~/.local/share/mpv/playlists
#2 shows playlist, 1 shows current file(filename strip applied), 0 shows nothing
show_playlist_on_fileload=1
#call youtube-dl to resolve the titles of urls in the playlist
resolve_titles=yes

View File

@ -0,0 +1,973 @@
local settings = {
-- #### FUNCTIONALITY SETTINGS
--navigation keybindings force override only while playlist is visible
--if "no" then you can display the playlist by any of the navigation keys
dynamic_binds = true,
-- to bind multiple keys separate them by a space
key_moveup = "UP",
key_movedown = "DOWN",
key_selectfile = "RIGHT LEFT",
key_unselectfile = "",
key_playfile = "ENTER",
key_removefile = "BS",
key_closeplaylist = "ESC",
--replaces matches on filenames based on extension, put as empty string to not replace anything
--replace rules are executed in provided order
--replace rule key is the pattern and value is the replace value
--uses :gsub('pattern', 'replace'), read more http://lua-users.org/wiki/StringLibraryTutorial
--'all' will match any extension or protocol if it has one
--uses json and parses it into a lua table to be able to support .conf file
filename_replace = "",
--[=====[ START OF SAMPLE REPLACE, to use remove start and end line
--Sample replace: replaces underscore to space on all files
--for mp4 and webm; remove extension, remove brackets and surrounding whitespace, change dot between alphanumeric to space
filename_replace = [[
[
{
"ext": { "all": true},
"rules": [
{ "_" : " " }
]
},{
"ext": { "mp4": true, "mkv": true },
"rules": [
{ "^(.+)%..+$": "%1" },
{ "%s*[%[%(].-[%]%)]%s*": "" },
{ "(%w)%.(%w)": "%1 %2" }
]
},{
"protocol": { "http": true, "https": true },
"rules": [
{ "^%a+://w*%.?": "" }
]
}
]
]],
--END OF SAMPLE REPLACE ]=====]
--json array of filetypes to search from directory
loadfiles_filetypes = [[
[
"jpg", "jpeg", "png", "tif", "tiff", "gif", "webp", "svg", "bmp",
"mp3", "wav", "ogm", "flac", "m4a", "wma", "ogg", "opus",
"mkv", "avi", "mp4", "ogv", "webm", "rmvb", "flv", "wmv", "mpeg", "mpg", "m4v", "3gp"
]
]],
--loadfiles at startup if there is 0 or 1 items in playlist, if 0 uses worḱing dir for files
loadfiles_on_start = false,
--sort playlist on mpv start
sortplaylist_on_start = false,
--sort playlist when files are added to playlist
sortplaylist_on_file_add = false,
--use alphanumerical sort
alphanumsort = true,
--"linux | windows | auto"
system = "auto",
--Use ~ for home directory. Leave as empty to use mpv/playlists
playlist_savepath = "",
--show playlist or filename every time a new file is loaded
--2 shows playlist, 1 shows current file(filename strip applied) as osd text, 0 shows nothing
--instead of using this you can also call script-message playlistmanager show playlist/filename
--ex. KEY playlist-next ; script-message playlistmanager show playlist
show_playlist_on_fileload = 0,
--sync cursor when file is loaded from outside reasons(file-ending, playlist-next shortcut etc.)
--has the sideeffect of moving cursor if file happens to change when navigating
--good side is cursor always following current file when going back and forth files with playlist-next/prev
sync_cursor_on_load = true,
--playlist open key will toggle visibility instead of refresh, best used with long timeout
open_toggles = true,
--allow the playlist cursor to loop from end to start and vice versa
loop_cursor = true,
--#### VISUAL SETTINGS
--prefer to display titles for following files: "all", "url", "none". Sorting still uses filename.
prefer_titles = "url",
--call youtube-dl to resolve the titles of urls in the playlist
resolve_titles = false,
--osd timeout on inactivity, with high value on this open_toggles is good to be true
playlist_display_timeout = 5,
--amount of entries to show before slicing. Optimal value depends on font/video size etc.
showamount = 16,
--font size scales by window, if false requires larger font and padding sizes
scale_playlist_by_window=true,
--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
style_ass_tags = "{}",
--paddings from top left corner
text_padding_x = 10,
text_padding_y = 30,
--set title of window with stripped name
set_title_stripped = false,
title_prefix = "",
title_suffix = " - mpv",
--slice long filenames, and how many chars to show
slice_longfilenames = false,
slice_longfilenames_amount = 70,
--Playlist header template
--%mediatitle or %filename = title or name of playing file
--%pos = position of playing file
--%cursor = position of navigation
--%plen = playlist length
--%N = newline
playlist_header = "[%cursor/%plen]",
--Playlist file templates
--%pos = position of file with leading zeros
--%name = title or name of file
--%N = newline
--you can also use the ass tags mentioned above. For example:
-- selected_file="{\\c&HFF00FF&}➔ %name" | to add a color for selected file. However, if you
-- use ass tags you need to reset them for every line (see https://github.com/jonniek/mpv-playlistmanager/issues/20)
normal_file = "○ %name",
hovered_file = "● %name",
selected_file = "➔ %name",
playing_file = "▷ %name",
playing_hovered_file = "▶ %name",
playing_selected_file = "➤ %name",
-- what to show when playlist is truncated
playlist_sliced_prefix = "...",
playlist_sliced_suffix = "..."
}
local opts = require("mp.options")
opts.read_options(settings, "playlistmanager", function(list) update_opts(list) end)
local utils = require("mp.utils")
local msg = require("mp.msg")
local assdraw = require("mp.assdraw")
--check os
if settings.system=="auto" then
local o = {}
if mp.get_property_native('options/vo-mmcss-profile', o) ~= o then
settings.system = "windows"
else
settings.system = "linux"
end
end
--global variables
local playlist_visible = false
local strippedname = nil
local path = nil
local directory = nil
local filename = nil
local pos = 0
local plen = 0
local cursor = 0
--table for saved media titles for later if we prefer them
local url_table = {}
-- table for urls that we have request to be resolved to titles
local requested_urls = {}
--state for if we sort on playlist size change
local sort_watching = false
local filetype_lookup = {}
function update_opts(changelog)
msg.verbose('updating options')
--parse filename json
if changelog.filename_replace then
if(settings.filename_replace~="") then
settings.filename_replace = utils.parse_json(settings.filename_replace)
else
settings.filename_replace = false
end
end
--parse loadfiles json
if changelog.loadfiles_filetypes then
settings.loadfiles_filetypes = utils.parse_json(settings.loadfiles_filetypes)
filetype_lookup = {}
--create loadfiles set
for _, ext in ipairs(settings.loadfiles_filetypes) do
filetype_lookup[ext] = true
end
end
if changelog.resolve_titles then
resolve_titles()
end
if changelog.playlist_display_timeout then
keybindstimer = mp.add_periodic_timer(settings.playlist_display_timeout, remove_keybinds)
keybindstimer:kill()
end
if playlist_visible then showplaylist() end
end
update_opts({filename_replace = true, loadfiles_filetypes = true})
function on_loaded()
filename = mp.get_property("filename")
path = mp.get_property('path')
--if not a url then join path with working directory
if not path:match("^%a%a+:%/%/") then
path = utils.join_path(mp.get_property('working-directory'), path)
directory = utils.split_path(path)
else
directory = nil
end
refresh_globals()
if settings.sync_cursor_on_load then
cursor=pos
--refresh playlist if cursor moved
if playlist_visible then draw_playlist() end
end
local media_title = mp.get_property("media-title")
if path:match('^https?://') and not url_table[path] and path ~= media_title then
url_table[path] = media_title
end
strippedname = stripfilename(mp.get_property('media-title'))
if settings.show_playlist_on_fileload == 2 then
showplaylist()
elseif settings.show_playlist_on_fileload == 1 then
mp.commandv('show-text', strippedname)
end
if settings.set_title_stripped then
mp.set_property("title", settings.title_prefix..strippedname..settings.title_suffix)
end
local didload = false
if settings.loadfiles_on_start and plen == 1 then
didload = true --save reference for sorting
msg.info("Loading files from playing files directory")
playlist()
end
--if we promised to sort files on launch do it
if promised_sort then
promised_sort = false
msg.info("Your playlist is sorted before starting playback")
if didload then sortplaylist() else sortplaylist(true) end
end
--if we promised to listen and sort on playlist size increase do it
if promised_sort_watch then
promised_sort_watch = false
sort_watching = true
msg.info("Added files will be automatically sorted")
mp.observe_property('playlist-count', "number", autosort)
end
end
function on_closed()
strippedname = nil
path = nil
directory = nil
filename = nil
if playlist_visible then showplaylist() end
end
function refresh_globals()
pos = mp.get_property_number('playlist-pos', 0)
plen = mp.get_property_number('playlist-count', 0)
end
function escapepath(dir, escapechar)
return string.gsub(dir, escapechar, '\\'..escapechar)
end
--strip a filename based on its extension or protocol according to rules in settings
function stripfilename(pathfile, media_title)
if pathfile == nil then return '' end
local ext = pathfile:match("^.+%.(.+)$")
local protocol = pathfile:match("^(%a%a+)://")
if not ext then ext = "" end
local tmp = pathfile
if settings.filename_replace and not media_title then
for k,v in ipairs(settings.filename_replace) do
if ( v['ext'] and (v['ext'][ext] or (ext and not protocol and v['ext']['all'])) )
or ( v['protocol'] and (v['protocol'][protocol] or (protocol and not ext and v['protocol']['all'])) ) then
for ruleindex, indexrules in ipairs(v['rules']) do
for rule, override in pairs(indexrules) do
tmp = tmp:gsub(rule, override)
end
end
end
end
end
if settings.slice_longfilenames and tmp:len()>settings.slice_longfilenames_amount+5 then
tmp = tmp:sub(1, settings.slice_longfilenames_amount).." ..."
end
return tmp
end
--gets a nicename of playlist entry at 0-based position i
function get_name_from_index(i, notitle)
refresh_globals()
if plen <= i then msg.error("no index in playlist", i, "length", plen); return nil end
local _, name = nil
local title = mp.get_property('playlist/'..i..'/title')
local name = mp.get_property('playlist/'..i..'/filename')
local should_use_title = settings.prefer_titles == 'all' or name:match('^https?://') and settings.prefer_titles == 'url'
--check if file has a media title stored or as property
if not title and should_use_title then
local mtitle = mp.get_property('media-title')
if i == pos and mp.get_property('filename') ~= mtitle then
if not url_table[name] then
url_table[name] = mtitle
end
title = mtitle
elseif url_table[name] then
title = url_table[name]
end
end
--if we have media title use a more conservative strip
if title and not notitle and should_use_title then return stripfilename(title, true) end
--remove paths if they exist, keeping protocols for stripping
if string.sub(name, 1, 1) == '/' or name:match("^%a:[/\\]") then
_, name = utils.split_path(name)
end
return stripfilename(name)
end
function parse_header(string)
local esc_title = stripfilename(mp.get_property("media-title"), true):gsub("%%", "%%%%")
local esc_file = stripfilename(mp.get_property("filename")):gsub("%%", "%%%%")
return string:gsub("%%N", "\\N")
:gsub("%%pos", mp.get_property_number("playlist-pos",0)+1)
:gsub("%%plen", mp.get_property("playlist-count"))
:gsub("%%cursor", cursor+1)
:gsub("%%mediatitle", esc_title)
:gsub("%%filename", esc_file)
-- undo name escape
:gsub("%%%%", "%%")
end
function parse_filename(string, name, index)
local base = tostring(plen):len()
local esc_name = stripfilename(name):gsub("%%", "%%%%")
return string:gsub("%%N", "\\N")
:gsub("%%pos", string.format("%0"..base.."d", index+1))
:gsub("%%name", esc_name)
-- undo name escape
:gsub("%%%%", "%%")
end
function parse_filename_by_index(index)
local template = settings.normal_file
local is_idle = mp.get_property_native('idle-active')
local position = is_idle and -1 or pos
if index == position then
if index == cursor then
if selection then
template = settings.playing_selected_file
else
template = settings.playing_hovered_file
end
else
template = settings.playing_file
end
elseif index == cursor then
if selection then
template = settings.selected_file
else
template = settings.hovered_file
end
end
return parse_filename(template, get_name_from_index(index), index)
end
function draw_playlist()
refresh_globals()
local ass = assdraw.ass_new()
ass:new_event()
ass:pos(settings.text_padding_x, settings.text_padding_y)
ass:append(settings.style_ass_tags)
if settings.playlist_header ~= "" then
ass:append(parse_header(settings.playlist_header).."\\N")
end
local start = cursor - math.floor(settings.showamount/2)
local showall = false
local showrest = false
if start<0 then start=0 end
if plen <= settings.showamount then
start=0
showall=true
end
if start > math.max(plen-settings.showamount-1, 0) then
start=plen-settings.showamount
showrest=true
end
if start > 0 and not showall then ass:append(settings.playlist_sliced_prefix.."\\N") end
for index=start,start+settings.showamount-1,1 do
if index == plen then break end
ass:append(parse_filename_by_index(index).."\\N")
if index == start+settings.showamount-1 and not showall and not showrest then
ass:append(settings.playlist_sliced_suffix)
end
end
local w, h = mp.get_osd_size()
if settings.scale_playlist_by_window then w,h = 0, 0 end
mp.set_osd_ass(w, h, ass.text)
end
function toggle_playlist()
if settings.open_toggles then
if playlist_visible then
remove_keybinds()
return
end
end
showplaylist()
end
function showplaylist(duration)
refresh_globals()
if plen == 0 then return end
playlist_visible = true
add_keybinds()
draw_playlist()
keybindstimer:kill()
if duration then
keybindstimer = mp.add_periodic_timer(duration, remove_keybinds)
else
keybindstimer:resume()
end
end
selection=nil
function selectfile()
refresh_globals()
if plen == 0 then return end
if not selection then
selection=cursor
else
selection=nil
end
showplaylist()
end
function unselectfile()
selection=nil
showplaylist()
end
function removefile()
refresh_globals()
if plen == 0 then return end
selection = nil
if cursor==pos then mp.command("script-message unseenplaylist mark true \"playlistmanager avoid conflict when removing file\"") end
mp.commandv("playlist-remove", cursor)
if cursor==plen-1 then cursor = cursor - 1 end
showplaylist()
end
function moveup()
refresh_globals()
if plen == 0 then return end
if cursor~=0 then
if selection then mp.commandv("playlist-move", cursor,cursor-1) end
cursor = cursor-1
elseif settings.loop_cursor then
if selection then mp.commandv("playlist-move", cursor,plen) end
cursor = plen-1
end
showplaylist()
end
function movedown()
refresh_globals()
if plen == 0 then return end
if cursor ~= plen-1 then
if selection then mp.commandv("playlist-move", cursor,cursor+2) end
cursor = cursor + 1
elseif settings.loop_cursor then
if selection then mp.commandv("playlist-move", cursor,0) end
cursor = 0
end
showplaylist()
end
function Watch_later()
if mp.get_property_bool("save-position-on-quit") then
mp.command("write-watch-later-config")
end
end
function playfile()
refresh_globals()
if plen == 0 then return end
selection = nil
local is_idle = mp.get_property_native('idle-active')
if cursor ~= pos or is_idle then
mp.set_property("playlist-pos", cursor)
else
if cursor~=plen-1 then
cursor = cursor + 1
end
Watch_later()
mp.commandv("playlist-next", "weak")
end
if settings.show_playlist_on_fileload ~= 2 then
remove_keybinds()
end
end
function get_files_windows(dir)
local args = {
'powershell', '-NoProfile', '-Command', [[& {
Trap {
Write-Error -ErrorRecord $_
Exit 1
}
$path = "]]..dir..[["
$escapedPath = [WildcardPattern]::Escape($path)
cd $escapedPath
$list = (Get-ChildItem -File | Sort-Object { [regex]::Replace($_.Name, '\d+', { $args[0].Value.PadLeft(20) }) }).Name
$string = ($list -join "/")
$u8list = [System.Text.Encoding]::UTF8.GetBytes($string)
[Console]::OpenStandardOutput().Write($u8list, 0, $u8list.Length)
}]]
}
local process = utils.subprocess({ args = args, cancellable = false })
return parse_files(process, '%/')
end
function get_files_linux(dir)
local args = { 'ls', '-1pv', dir }
local process = utils.subprocess({ args = args, cancellable = false })
return parse_files(process, '\n')
end
function parse_files(res, delimiter)
if not res.error and res.status == 0 then
local valid_files = {}
for line in res.stdout:gmatch("[^"..delimiter.."]+") do
local ext = line:match("^.+%.(.+)$")
if ext and filetype_lookup[ext:lower()] then
table.insert(valid_files, line)
end
end
return valid_files, nil
else
return nil, res.error
end
end
--Creates a playlist of all files in directory, will keep the order and position
--For exaple, Folder has 12 files, you open the 5th file and run this, the remaining 7 are added behind the 5th file and prior 4 files before it
function playlist(force_dir)
refresh_globals()
if not directory and plen > 0 then return end
local hasfile = true
if plen == 0 then
hasfile = false
dir = mp.get_property('working-directory')
else
dir = directory
end
if force_dir then dir = force_dir end
local files, error
if settings.system == "linux" then
files, error = get_files_linux(dir)
else
files, error = get_files_windows(dir)
end
local c, c2 = 0,0
if files then
local cur = false
local filename = mp.get_property("filename")
for _, file in ipairs(files) do
local appendstr = "append"
if not hasfile then
cur = true
appendstr = "append-play"
hasfile = true
end
if cur == true then
mp.commandv("loadfile", utils.join_path(dir, file), appendstr)
msg.info("Appended to playlist: " .. file)
c2 = c2 + 1
elseif file ~= filename then
mp.commandv("loadfile", utils.join_path(dir, file), appendstr)
msg.info("Prepended to playlist: " .. file)
mp.commandv("playlist-move", mp.get_property_number("playlist-count", 1)-1, c)
c = c + 1
else
cur = true
end
end
if c2 > 0 or c>0 then
mp.osd_message("Added "..c + c2.." files to playlist")
else
mp.osd_message("No additional files found")
end
cursor = mp.get_property_number('playlist-pos', 1)
else
msg.error("Could not scan for files: "..(error or ""))
end
if sort_watching then
msg.info("Ignoring directory structure and using playlist sort")
sortplaylist()
end
refresh_globals()
if playlist_visible then showplaylist() end
return c + c2
end
function parse_home(path)
if not path:find("^~") then
return path
end
local home_dir = os.getenv("HOME") or os.getenv("USERPROFILE")
if not home_dir then
local drive = os.getenv("HOMEDRIVE")
local path = os.getenv("HOMEPATH")
if drive and path then
home_dir = utils.join_path(drive, path)
else
msg.error("Couldn't find home dir.")
return nil
end
end
local result = path:gsub("^~", home_dir)
return result
end
--saves the current playlist into a m3u file
function save_playlist()
local length = mp.get_property_number('playlist-count', 0)
if length == 0 then return end
--get playlist save path
local savepath
if settings.playlist_savepath == nil or settings.playlist_savepath == "" then
savepath = mp.command_native({"expand-path", "~~home/"}).."/playlists"
else
savepath = parse_home(settings.playlist_savepath)
if savepath == nil then return end
end
--create savepath if it doesn't exist
if utils.readdir(savepath) == nil then
local windows_args = {'powershell', '-NoProfile', '-Command', 'mkdir', savepath}
local unix_args = { 'mkdir', savepath }
local args = settings.system == 'windows' and windows_args or unix_args
local res = utils.subprocess({ args = args, cancellable = false })
if res.status ~= 0 then
msg.error("Failed to create playlist save directory "..savepath..". Error: "..(res.error or "unknown"))
return
end
end
local date = os.date("*t")
local datestring = ("%02d-%02d-%02d_%02d-%02d-%02d"):format(date.year, date.month, date.day, date.hour, date.min, date.sec)
local savepath = utils.join_path(savepath, datestring.."_playlist-size_"..length..".m3u")
local file, err = io.open(savepath, "w")
if not file then
msg.error("Error in creating playlist file, check permissions. Error: "..(err or "unknown"))
else
local i=0
while i < length do
local pwd = mp.get_property("working-directory")
local filename = mp.get_property('playlist/'..i..'/filename')
local fullpath = filename
if not filename:match("^%a%a+:%/%/") then
fullpath = utils.join_path(pwd, filename)
end
local title = mp.get_property('playlist/'..i..'/title')
if title then file:write("#EXTINF:,"..title.."\n") end
file:write(fullpath, "\n")
i=i+1
end
msg.info("Playlist written to: "..savepath)
file:close()
end
end
function alphanumsort(a, b)
local function padnum(d)
local dec, n = string.match(d, "(%.?)0*(.+)")
return #dec > 0 and ("%.12f"):format(d) or ("%s%03d%s"):format(dec, #n, n)
end
return tostring(a):lower():gsub("%.?%d+",padnum)..("%3d"):format(#b)
< tostring(b):lower():gsub("%.?%d+",padnum)..("%3d"):format(#a)
end
function dosort(a,b)
if settings.alphanumsort then
return alphanumsort(a,b)
else
return a < b
end
end
function sortplaylist(startover)
local length = mp.get_property_number('playlist-count', 0)
if length < 2 then return end
--use insertion sort on playlist to make it easy to order files with playlist-move
for outer=1, length-1, 1 do
local outerfile = get_name_from_index(outer, true)
local inner = outer - 1
while inner >= 0 and dosort(outerfile, get_name_from_index(inner, true)) do
inner = inner - 1
end
inner = inner + 1
if outer ~= inner then
mp.commandv('playlist-move', outer, inner)
end
end
cursor = mp.get_property_number('playlist-pos', 0)
if startover then
mp.set_property('playlist-pos', 0)
end
if playlist_visible then showplaylist() end
end
function autosort(name, param)
if param == 0 then return end
if plen < param then
msg.info("Playlistmanager autosorting playlist")
refresh_globals()
sortplaylist()
end
end
function reverseplaylist()
local length = mp.get_property_number('playlist-count', 0)
if length < 2 then return end
for outer=1, length-1, 1 do
mp.commandv('playlist-move', outer, 0)
end
if playlist_visible then showplaylist() end
end
function shuffleplaylist()
refresh_globals()
if plen < 2 then return end
mp.command("playlist-shuffle")
math.randomseed(os.time())
mp.commandv("playlist-move", pos, math.random(0, plen-1))
mp.set_property('playlist-pos', 0)
refresh_globals()
if playlist_visible then showplaylist() end
end
function bind_keys(keys, name, func, opts)
if not keys then
mp.add_forced_key_binding(keys, name, func, opts)
return
end
local i = 1
for key in keys:gmatch("[^%s]+") do
local prefix = i == 1 and '' or i
mp.add_forced_key_binding(key, name..prefix, func, opts)
i = i + 1
end
end
function unbind_keys(keys, name)
if not keys then
mp.remove_key_binding(name)
return
end
local i = 1
for key in keys:gmatch("[^%s]+") do
local prefix = i == 1 and '' or i
mp.remove_key_binding(name..prefix)
i = i + 1
end
end
function add_keybinds()
bind_keys(settings.key_moveup, 'moveup', moveup, "repeatable")
bind_keys(settings.key_movedown, 'movedown', movedown, "repeatable")
bind_keys(settings.key_selectfile, 'selectfile', selectfile)
bind_keys(settings.key_unselectfile, 'unselectfile', unselectfile)
bind_keys(settings.key_playfile, 'playfile', playfile)
bind_keys(settings.key_removefile, 'removefile', removefile, "repeatable")
bind_keys(settings.key_closeplaylist, 'closeplaylist', remove_keybinds)
end
function remove_keybinds()
keybindstimer:kill()
keybindstimer = mp.add_periodic_timer(settings.playlist_display_timeout, remove_keybinds)
keybindstimer:kill()
mp.set_osd_ass(0, 0, "")
playlist_visible = false
if settings.dynamic_binds then
unbind_keys(settings.key_moveup, 'moveup')
unbind_keys(settings.key_movedown, 'movedown')
unbind_keys(settings.key_selectfile, 'selectfile')
unbind_keys(settings.key_unselectfile, 'unselectfile')
unbind_keys(settings.key_playfile, 'playfile')
unbind_keys(settings.key_removefile, 'removefile')
unbind_keys(settings.key_closeplaylist, 'closeplaylist')
end
end
keybindstimer = mp.add_periodic_timer(settings.playlist_display_timeout, remove_keybinds)
keybindstimer:kill()
if not settings.dynamic_binds then
add_keybinds()
end
if settings.loadfiles_on_start and mp.get_property_number('playlist-count', 0) == 0 then
playlist()
end
promised_sort_watch = false
if settings.sortplaylist_on_file_add then
promised_sort_watch = true
end
promised_sort = false
if settings.sortplaylist_on_start then
promised_sort = true
end
mp.observe_property('playlist-count', "number", function()
if playlist_visible then showplaylist() end
if settings.prefer_titles == 'none' then return end
-- resolve titles
resolve_titles()
end)
--resolves url titles by calling youtube-dl
function resolve_titles()
if not settings.resolve_titles then return end
local length = mp.get_property_number('playlist-count', 0)
if length < 2 then return end
local i=0
-- loop all items in playlist because we can't predict how it has changed
while i < length do
local filename = mp.get_property('playlist/'..i..'/filename')
local title = mp.get_property('playlist/'..i..'/title')
if i ~= pos
and filename
and filename:match('^https?://')
and not title
and not url_table[filename]
and not requested_urls[filename]
then
requested_urls[filename] = true
local args = { 'youtube-dl', '--no-playlist', '--flat-playlist', '-sJ', filename }
local req = mp.command_native_async(
{
name = "subprocess",
args = args,
playback_only = false,
capture_stdout = true
}, function (success, res)
if res.killed_by_us then
msg.verbose('Request to resolve url title ' .. filename .. ' timed out')
return
end
if res.status == 0 then
local json, err = utils.parse_json(res.stdout)
if not err then
local is_playlist = json['_type'] and json['_type'] == 'playlist'
local title = (is_playlist and '[playlist]: ' or '') .. json['title']
msg.verbose(filename .. " resolved to '" .. title .. "'")
url_table[filename] = title
refresh_globals()
if playlist_visible then showplaylist() end
return
else
msg.error("Failed parsing json, reason: "..(err or "unknown"))
end
else
msg.error("Failed to resolve url title "..filename.." Error: "..(res.error or "unknown"))
end
end)
mp.add_timeout(5, function()
mp.abort_async_command(req)
end)
end
i=i+1
end
end
--script message handler
function handlemessage(msg, value, value2)
if msg == "show" and value == "playlist" then
if value2 ~= "toggle" then
showplaylist(value2)
return
else
toggle_playlist()
return
end
end
if msg == "show" and value == "filename" and strippedname and value2 then
mp.commandv('show-text', strippedname, tonumber(value2)*1000 ) ; return
end
if msg == "show" and value == "filename" and strippedname then
mp.commandv('show-text', strippedname ) ; return
end
if msg == "sort" then sortplaylist(value) ; return end
if msg == "shuffle" then shuffleplaylist() ; return end
if msg == "reverse" then reverseplaylist() ; return end
if msg == "loadfiles" then playlist(value) ; return end
if msg == "save" then save_playlist() ; return end
end
mp.register_script_message("playlistmanager", handlemessage)
mp.add_key_binding("CTRL+p", "sortplaylist", sortplaylist)
mp.add_key_binding("CTRL+P", "shuffleplaylist", shuffleplaylist)
mp.add_key_binding("CTRL+R", "reverseplaylist", reverseplaylist)
mp.add_key_binding("P", "loadfiles", playlist)
mp.add_key_binding("p", "saveplaylist", save_playlist)
mp.add_key_binding("SHIFT+ENTER", "showplaylist", toggle_playlist)
mp.register_event("file-loaded", on_loaded)
mp.register_event("end-file", on_closed)

View File

@ -30,6 +30,13 @@ from which you can load other files, subtitles, chapters, switch audio tracks an
Most of this is (thanks to the hard work of the original script writers) easily customizable,
mostly from `input.conf` directly.
## playlist management
Uses the wonderful [playlistmanager](https://github.com/jonniek/mpv-playlistmanager) script to enable easy management of mpv playlists.
Enables manually or automatically sorting playlists, shuffling them, and saving them to files.
Additionally, it automatically populates streaming content with the video title instead of the url within the playlist.
## battery saving
When mpv ist started on a pc which it assumes to be running on battery power, it will automatically switch to a (slightly) lower quality mode,