dotfiles/multimedia/.config/mpv/scripts/playlistmanager.lua

1012 lines
34 KiB
Lua

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)