diff --git a/mail/.config/imapfilter/accounts.lua b/mail/.config/imapfilter/accounts.lua index faa2141..7589f85 100644 --- a/mail/.config/imapfilter/accounts.lua +++ b/mail/.config/imapfilter/accounts.lua @@ -1,13 +1,15 @@ local accounts = {} -local status, gmailuser = pipe_from('gpg2 --decrypt --no-tty --quiet --no-verbose --for-your-eyes-only --pinentry-mode ask ~/.local/share/pass/misc/aerc-gmail-app-password.gpg | grep username | cut -d: -f2') -local status, gmailpass = pipe_from('gpg2 --decrypt --no-tty --quiet --no-verbose --for-your-eyes-only --pinentry-mode ask ~/.local/share/pass/misc/aerc-gmail-app-password.gpg | head -n1') +local status, gmailuser = pipe_from( + 'gpg2 --decrypt --no-tty --quiet --no-verbose --for-your-eyes-only --pinentry-mode ask ~/.local/share/pass/misc/aerc-gmail-app-password.gpg | grep username | cut -d: -f2') +local status, gmailpass = pipe_from( + 'gpg2 --decrypt --no-tty --quiet --no-verbose --for-your-eyes-only --pinentry-mode ask ~/.local/share/pass/misc/aerc-gmail-app-password.gpg | head -n1') -- Setup an imap account called gmail accounts.gmail = IMAP { - server = "imap.gmail.com", - username = gmailuser, - password = gmailpass, - ssl = "auto" + server = "imap.gmail.com", + username = gmailuser, + password = gmailpass, + ssl = "auto" } return accounts diff --git a/mail/.config/imapfilter/config.lua b/mail/.config/imapfilter/config.lua index 410df25..c95aca5 100644 --- a/mail/.config/imapfilter/config.lua +++ b/mail/.config/imapfilter/config.lua @@ -14,70 +14,73 @@ options.timeout = 120 -- whether to enter IDLE mode and conintuously check -- for new incoming mail to filter -CONTINUOUS=false +CONTINUOUS = false -- time to wait for next synchronization -- only used in case server does not support IDLE mode -UPDATE_TIME=120 +UPDATE_TIME = 120 -- implement simple wait function in case server does not support IDLE mode -function sleep(n) - os.execute("sleep " .. tonumber(n)) -end +function sleep(n) os.execute("sleep " .. tonumber(n)) end -- will set filters to be grabbed from XDG-compliant filter directory -- can be overridden with env var IMAPFILTER_FILTERDIR function getConfigDir() - -- -- set directory for imapfilter files - local configdir - if os.getenv("IMAPFILTER_CONFIGDIR") then - configdir=os.getenv("IMAPFILTER_CONFIGDIR") - elseif os.getenv("XDG_CONFIG_HOME") then - configdir=os.getenv("XDG_CONFIG_HOME") .. "/imapfilter" - else - configdir=os.getenv("HOME") .. "/.config/imapfilter" - end - return configdir + -- -- set directory for imapfilter files + local configdir + if os.getenv("IMAPFILTER_CONFIGDIR") then + configdir = os.getenv("IMAPFILTER_CONFIGDIR") + elseif os.getenv("XDG_CONFIG_HOME") then + configdir = os.getenv("XDG_CONFIG_HOME") .. "/imapfilter" + else + configdir = os.getenv("HOME") .. "/.config/imapfilter" + end + return configdir end -- will set filters to be grabbed from XDG-compliant filter directory -- can be overridden with env var IMAPFILTER_FILTERDIR function getFilterDir() - -- -- set directory for imapfilter files - local imapfilterdir - if os.getenv("IMAPFILTER_FILTERDIR") then - imapfilterdir=os.getenv("IMAPFILTER_FILTERDIR") - else - imapfilterdir=configDir .. "/filters" - end - return imapfilterdir + -- -- set directory for imapfilter files + local imapfilterdir + if os.getenv("IMAPFILTER_FILTERDIR") then + imapfilterdir = os.getenv("IMAPFILTER_FILTERDIR") + else + imapfilterdir = configDir .. "/filters" + end + return imapfilterdir end -- dirlist, from https://stackoverflow.com/a/25266573 function applyFilters(dir) - local p = io.popen('find "'..dir..'" -type f -name "*.lua"') --Open directory look for files, save data in p. By giving '-type f' as parameter, it returns all files. - for file in p:lines() do --Loop through all files - loadfile(file)() - end + local p = io.popen('find "' .. dir .. '" -type f -name "*.lua"') -- Open directory look for files, save data in p. By giving '-type f' as parameter, it returns all files. + for file in p:lines() do -- Loop through all files + loadfile(file)() + end end -- create global variable containing the configuration files -configDir=getConfigDir() -assert(configDir, "No configuration directory found. Ensure " .. os.getenv("HOME") .. "/.config/imapfilter exists.") +configDir = getConfigDir() +assert(configDir, + "No configuration directory found. Ensure " .. os.getenv("HOME") .. + "/.config/imapfilter exists.") -- create global variable containing account access accounts = loadfile(configDir .. "/accounts.lua")() -assert(accounts, "No accounts configured. Ensure accounts.lua exists and returns a table of accounts.") +assert(accounts, + "No accounts configured. Ensure accounts.lua exists and returns a table of accounts.") -- immediately act on the filters once applyFilters(getFilterDir()) -- continuously watch for mail if needed -while CONTINUOUS==true do - local has_idle=accounts.gmail['Inbox']:enter_idle() - applyFilters(getFilterDir()) +while CONTINUOUS == true do + local has_idle = accounts.gmail['Inbox']:enter_idle() + applyFilters(getFilterDir()) - if has_idle == false then - print("Server does not support idle, application will be polling again in " .. UPDATE_TIME .. "minutes.") - sleep(UPDATE_TIME) - end + if has_idle == false then + print( + "Server does not support idle, application will be polling again in " .. + UPDATE_TIME .. "minutes.") + sleep(UPDATE_TIME) + end end diff --git a/mail/.config/imapfilter/filters/rollup-dump.lua b/mail/.config/imapfilter/filters/rollup-dump.lua index 4ebefe9..7b1ef95 100644 --- a/mail/.config/imapfilter/filters/rollup-dump.lua +++ b/mail/.config/imapfilter/filters/rollup-dump.lua @@ -1,43 +1,40 @@ function sendToFolder(folderFrom, folderTo, senders) - local messages = folderFrom:select_all() - for _, sender in pairs(senders) do - local filtered=messages:contain_from(sender) - filtered:mark_seen() - filtered:move_messages(folderTo) - end + local messages = folderFrom:select_all() + for _, sender in pairs(senders) do + local filtered = messages:contain_from(sender) + filtered:mark_seen() + filtered:move_messages(folderTo) + end end -- will set filters to be grabbed from XDG-compliant filter directory -- can be overridden with env var IMAPFILTER_ROLLUPFILE function getRollupFile(fname) - local f - local fname=fname or "rollup.txt" - if os.getenv("IMAPFILTER_ROLLUPFILE") then - f=os.getenv("IMAPFILTER_ROLLUPFILE") - elseif os.getenv("XDG_DATA_HOME") then - f=os.getenv("XDG_DATA_HOME") .. "/imapfilter/" .. fname - else - f=os.getenv("HOME") .. "/.local/share/imapfilter/" .. fname - end - return f + local f + local fname = fname or "rollup.txt" + if os.getenv("IMAPFILTER_ROLLUPFILE") then + f = os.getenv("IMAPFILTER_ROLLUPFILE") + elseif os.getenv("XDG_DATA_HOME") then + f = os.getenv("XDG_DATA_HOME") .. "/imapfilter/" .. fname + else + f = os.getenv("HOME") .. "/.local/share/imapfilter/" .. fname + end + return f end function getSenderList(rollupfile) - local rollupSenders={} + local rollupSenders = {} - local file = io.open(rollupfile) - if file then - for line in file:lines() do - table.insert(rollupSenders, line) + local file = io.open(rollupfile) + if file then + for line in file:lines() do table.insert(rollupSenders, line) end + else + print( + "ERROR: rollup did not find rollup.txt file containing mail addresses at " .. + rollupfile or "") end - else - print("ERROR: rollup did not find rollup.txt file containing mail addresses at " .. rollupfile or "" ) - end - return rollupSenders + return rollupSenders end -sendToFolder ( - accounts.gmail["Inbox"], - accounts.gmail["Dump"], - getSenderList(getRollupFile()) - ) +sendToFolder(accounts.gmail["Inbox"], accounts.gmail["Dump"], + getSenderList(getRollupFile())) diff --git a/mpv/.config/mpv/scripts/battery.lua b/mpv/.config/mpv/scripts/battery.lua index d880946..31d1c10 100644 --- a/mpv/.config/mpv/scripts/battery.lua +++ b/mpv/.config/mpv/scripts/battery.lua @@ -4,24 +4,22 @@ local lqprofile = "lowquality" local hqprofile = "highquality" local function powerstate() - local f =io.open("/sys/class/power_supply/AC/online") - if f == nil then - return - end - local t = f:read("*n") - f:close() - return t + local f = io.open("/sys/class/power_supply/AC/online") + if f == nil then return end + local t = f:read("*n") + f:close() + return t end local function adjust() - local state = powerstate() - -- this actually overrides automatically applied profiles - -- like 'protocol.http' - if state == 0 then - mp.msg.info("Running on battery, setting low-quality options.") - mp.set_property("profile", lqprofile) - else - mp.msg.info("Not running on battery, setting high-quality options.") - end + local state = powerstate() + -- this actually overrides automatically applied profiles + -- like 'protocol.http' + if state == 0 then + mp.msg.info("Running on battery, setting low-quality options.") + mp.set_property("profile", lqprofile) + else + mp.msg.info("Not running on battery, setting high-quality options.") + end end mp.add_hook("on_load", 1, adjust) diff --git a/mpv/.config/mpv/scripts/playlistmanager.lua b/mpv/.config/mpv/scripts/playlistmanager.lua index 7efbdb7..7a7a6d5 100644 --- a/mpv/.config/mpv/scripts/playlistmanager.lua +++ b/mpv/.config/mpv/scripts/playlistmanager.lua @@ -1,30 +1,30 @@ local settings = { - -- #### FUNCTIONALITY 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, + -- 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", + -- 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 + -- 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 = "", + filename_replace = "", ---[=====[ START OF SAMPLE REPLACE, to use remove start and end line + --[=====[ 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 = [[ @@ -51,8 +51,8 @@ local settings = { ]], --END OF SAMPLE REPLACE ]=====] - --json array of filetypes to search from directory - loadfiles_filetypes = [[ + -- 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", @@ -60,125 +60,122 @@ local settings = { ] ]], - --loadfiles at startup if there is 0 or 1 items in playlist, if 0 uses worḱing dir for files - loadfiles_on_start = false, + -- 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 on mpv start + sortplaylist_on_start = false, - --sort playlist when files are added to playlist - sortplaylist_on_file_add = false, + -- sort playlist when files are added to playlist + sortplaylist_on_file_add = false, - --use alphanumerical sort - alphanumsort = true, + -- use alphanumerical sort + alphanumsort = true, - --"linux | windows | auto" - system = "auto", + -- "linux | windows | auto" + system = "auto", - --Use ~ for home directory. Leave as empty to use mpv/playlists - playlist_savepath = "", + -- 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, - --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, - --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, - --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, - --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", - --#### VISUAL SETTINGS + -- call youtube-dl to resolve the titles of urls in the playlist + resolve_titles = false, - --prefer to display titles for following files: "all", "url", "none". Sorting still uses filename. - prefer_titles = "url", + -- osd timeout on inactivity, with high value on this open_toggles is good to be true + playlist_display_timeout = 5, - --call youtube-dl to resolve the titles of urls in the playlist - resolve_titles = false, + -- amount of entries to show before slicing. Optimal value depends on font/video size etc. + showamount = 16, - --osd timeout on inactivity, with high value on this open_toggles is good to be true - playlist_display_timeout = 5, + -- 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, - --amount of entries to show before slicing. Optimal value depends on font/video size etc. - showamount = 16, + -- set title of window with stripped name + set_title_stripped = false, + title_prefix = "", + title_suffix = " - mpv", - --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, + -- slice long filenames, and how many chars to show + slice_longfilenames = false, + slice_longfilenames_amount = 70, - --set title of window with stripped name - set_title_stripped = false, - title_prefix = "", - title_suffix = " - mpv", + -- 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]", - --slice long filenames, and how many chars to show - slice_longfilenames = false, - slice_longfilenames_amount = 70, + -- 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", - --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 = "..." + -- 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) +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 +-- 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 +-- global variables local playlist_visible = false local strippedname = nil local path = nil @@ -187,380 +184,397 @@ local filename = nil local pos = 0 local plen = 0 local cursor = 0 ---table for saved media titles for later if we prefer them +-- 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 +-- state for if we sort on playlist size change local sort_watching = false local filetype_lookup = {} function update_opts(changelog) - msg.verbose('updating options') + 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 + -- 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 - end - --parse loadfiles json - if changelog.loadfiles_filetypes then - settings.loadfiles_filetypes = utils.parse_json(settings.loadfiles_filetypes) + -- 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 + filetype_lookup = {} + -- create loadfiles set + for _, ext in ipairs(settings.loadfiles_filetypes) do + filetype_lookup[ext] = true + end end - end - if changelog.resolve_titles then - resolve_titles() - 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 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 + 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 + 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 + 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 + 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 + 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 + 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 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 + -- 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 + 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) + 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) + return string.gsub(dir, escapechar, '\\' .. escapechar) end ---strip a filename based on its extension or protocol according to rules in settings +-- 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 + 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 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 + 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 +-- 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] + refresh_globals() + if plen <= i then + msg.error("no index in playlist", i, "length", plen); + return nil end - end + local _, name = nil + local title = mp.get_property('playlist/' .. i .. '/title') + local name = mp.get_property('playlist/' .. i .. '/filename') - --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 + 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 - --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) + -- 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("%%%%", "%%") + 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("%%%%", "%%") + 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 template = settings.normal_file - local is_idle = mp.get_property_native('idle-active') - local position = is_idle and -1 or pos + 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 + 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 - 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) + 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) + 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) + if settings.playlist_header ~= "" then + ass:append(parse_header(settings.playlist_header) .. "\\N") 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) + 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 + if settings.open_toggles then + if playlist_visible then + remove_keybinds() + return + end end - end - showplaylist() + showplaylist() end function showplaylist(duration) - refresh_globals() - if plen == 0 then return end - playlist_visible = true - add_keybinds() + 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 + draw_playlist() + keybindstimer:kill() + if duration then + keybindstimer = mp.add_periodic_timer(duration, remove_keybinds) + else + keybindstimer:resume() + end end -selection=nil +selection = nil function selectfile() - refresh_globals() - if plen == 0 then return end - if not selection then - selection=cursor - else - selection=nil - end - showplaylist() + 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() + 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() + 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() + 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() + 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 + 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 + 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 - Watch_later() - mp.commandv("playlist-next", "weak") - end - if settings.show_playlist_on_fileload ~= 2 then - remove_keybinds() - end + if settings.show_playlist_on_fileload ~= 2 then remove_keybinds() end end function get_files_windows(dir) - local args = { - 'powershell', '-NoProfile', '-Command', [[& { + local args = { + 'powershell', '-NoProfile', '-Command', [[& { Trap { Write-Error -ErrorRecord $_ Exit 1 } - $path = "]]..dir..[[" + $path = "]] .. dir .. [[" $escapedPath = [WildcardPattern]::Escape($path) cd $escapedPath @@ -569,395 +583,419 @@ function get_files_windows(dir) $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, '%/') + } + 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') + 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 + 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 - 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 +-- 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") + 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 - mp.osd_message("No additional files found") + dir = directory 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 + 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 + 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 - end - local result = path:gsub("^~", home_dir) - return result + local result = path:gsub("^~", home_dir) + return result end ---saves the current playlist into a m3u file +-- 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 + 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 + -- 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 - 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 + -- 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 - 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) + 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 +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 + 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 - 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 + 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 + 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 + 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 + 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 + 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 + 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) + 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 + 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 = mp.add_periodic_timer(settings.playlist_display_timeout, + remove_keybinds) keybindstimer:kill() -if not settings.dynamic_binds then - add_keybinds() -end +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 +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 +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 +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() + 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 +-- 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 + 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) + 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) + mp.add_timeout(5, function() mp.abort_async_command(req) end) + end + i = i + 1 end - i=i+1 - end end ---script message handler +-- 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 + 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 - 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) diff --git a/mpv/.config/mpv/scripts/sponsorblock/main.lua b/mpv/.config/mpv/scripts/sponsorblock/main.lua index 2f37c40..f89c83a 100644 --- a/mpv/.config/mpv/scripts/sponsorblock/main.lua +++ b/mpv/.config/mpv/scripts/sponsorblock/main.lua @@ -2,8 +2,7 @@ -- -- This script skips sponsored segments of YouTube videos -- using data from https://github.com/ajayyy/SponsorBlock - -local ON_WINDOWS = package.config:sub(1,1) ~= '/' +local ON_WINDOWS = package.config:sub(1, 1) ~= '/' local options = { server_address = "https://sponsor.ajay.app", @@ -71,9 +70,7 @@ mp.options = require "mp.options" mp.options.read_options(options, "sponsorblock") local legacy = mp.command_native_async == nil -if legacy then - options.local_database = false -end +if legacy then options.local_database = false end local utils = require "mp.utils" if mp.get_script_directory == nil then @@ -83,7 +80,9 @@ else end local sponsorblock = utils.join_path(scripts_dir, "shared/sponsorblock.py") local uid_path = utils.join_path(scripts_dir, "shared/sponsorblock.txt") -local database_file = options.local_database and utils.join_path(scripts_dir, "shared/sponsorblock.db") or "" +local database_file = options.local_database and + utils.join_path(scripts_dir, "shared/sponsorblock.db") or + "" local youtube_id = nil local ranges = {} local init = false @@ -96,8 +95,13 @@ local fade_dir = nil local volume_before = mp.get_property_number("volume") function file_exists(name) - local f = io.open(name,"r") - if f ~= nil then io.close(f) return true else return false end + local f = io.open(name, "r") + if f ~= nil then + io.close(f) + return true + else + return false + end end function t_count(t) @@ -106,15 +110,14 @@ function t_count(t) return count end -function time_sort(a, b) - return a.time < b.time -end +function time_sort(a, b) return a.time < b.time end function clean_chapters() local chapters = mp.get_property_native("chapter-list") local new_chapters = {} for _, chapter in pairs(chapters) do - if chapter.title ~= "Preview segment start" and chapter.title ~= "Preview segment end" then + if chapter.title ~= "Preview segment start" and chapter.title ~= + "Preview segment end" then table.insert(new_chapters, chapter) end end @@ -124,7 +127,11 @@ end function create_chapter(chapter_title, chapter_time) local chapters = mp.get_property_native("chapter-list") local duration = mp.get_property_native("duration") - table.insert(chapters, {title=chapter_title, time=(duration == nil or duration > chapter_time) and chapter_time or duration - .001}) + table.insert(chapters, { + title = chapter_title, + time = (duration == nil or duration > chapter_time) and chapter_time or + duration - .001 + }) table.sort(chapters, time_sort) mp.set_property_native("chapter-list", chapters) end @@ -134,7 +141,8 @@ function getranges(_, exists, db, more) if options.server_fallback then mp.add_timeout(0, function() getranges(true, true, "") end) else - return mp.osd_message("[sponsorblock] database update failed, gave up") + return mp.osd_message( + "[sponsorblock] database update failed, gave up") end end if db ~= "" and db ~= database_file then db = database_file end @@ -151,20 +159,23 @@ function getranges(_, exists, db, more) end local sponsors local args = { - options.python_path, - sponsorblock, - "ranges", - db, - options.server_address, + options.python_path, sponsorblock, "ranges", db, options.server_address, youtube_id } if not legacy then - sponsors = mp.command_native({name = "subprocess", capture_stdout = true, playback_only = false, args = args}) + sponsors = mp.command_native({ + name = "subprocess", + capture_stdout = true, + playback_only = false, + args = args + }) else sponsors = utils.subprocess({args = args}) end if not string.match(sponsors.stdout, "^%s*(.*%S)") then return end - if string.match(sponsors.stdout, "error") then return getranges(true, true) end + if string.match(sponsors.stdout, "error") then + return getranges(true, true) + end local new_ranges = {} local r_count = 0 if more then r_count = -1 end @@ -176,7 +187,8 @@ function getranges(_, exists, db, more) start_time = tonumber(string.match(t, '[^,]+')) end_time = tonumber(string.sub(string.match(t, ',[^,]+'), 2)) for o_uuid, o_t in pairs(ranges) do - if (start_time >= o_t.start_time and start_time <= o_t.end_time) or (o_t.start_time >= start_time and o_t.start_time <= end_time) then + if (start_time >= o_t.start_time and start_time <= o_t.end_time) or + (o_t.start_time >= start_time and o_t.start_time <= end_time) then new_ranges[o_uuid] = o_t goto continue end @@ -189,29 +201,31 @@ function getranges(_, exists, db, more) } end if options.make_chapters then - create_chapter("Sponsor start (" .. string.sub(uuid, 1, 6) .. ")", start_time) - create_chapter("Sponsor end (" .. string.sub(uuid, 1, 6) .. ")", end_time) + create_chapter("Sponsor start (" .. string.sub(uuid, 1, 6) .. + ")", start_time) + create_chapter("Sponsor end (" .. string.sub(uuid, 1, 6) .. ")", + end_time) end end ::continue:: r_count = r_count + 1 end local c_count = t_count(ranges) - if c_count == 0 or r_count >= c_count then - ranges = new_ranges - end + if c_count == 0 or r_count >= c_count then ranges = new_ranges end end function fast_forward() local last_speed = mp.get_property_number("speed") - local new_speed = math.min(last_speed + options.fast_forward_increase, options.fast_forward_cap) + local new_speed = math.min(last_speed + options.fast_forward_increase, + options.fast_forward_cap) if new_speed <= last_speed then return end mp.set_property("speed", new_speed) end function fade_audio(step) local last_volume = mp.get_property_number("volume") - local new_volume = math.max(options.audio_fade_cap, math.min(last_volume + step, volume_before)) + local new_volume = math.max(options.audio_fade_cap, + math.min(last_volume + step, volume_before)) if new_volume == last_volume then if step >= 0 then fade_dir = nil end if fade_timer ~= nil then fade_timer:kill() end @@ -225,7 +239,8 @@ function skip_ads(name, pos) if pos == nil then return end local sponsor_ahead = false for uuid, t in pairs(ranges) do - if (options.fast_forward == uuid or not options.skip_once or not t.skipped) and t.start_time <= pos and t.end_time > pos then + if (options.fast_forward == uuid or not options.skip_once or + not t.skipped) and t.start_time <= pos and t.end_time > pos then if options.fast_forward == uuid then return end if options.fast_forward == false then mp.osd_message("[sponsorblock] sponsor skipped") @@ -237,20 +252,18 @@ function skip_ads(name, pos) last_skip = {uuid = uuid, dir = nil} if options.report_views or options.auto_upvote then local args = { - options.python_path, - sponsorblock, - "stats", - database_file, - options.server_address, - youtube_id, - uuid, - options.report_views and "1" or "", - uid_path, - options.user_id, - options.auto_upvote and "1" or "" + options.python_path, sponsorblock, "stats", database_file, + options.server_address, youtube_id, uuid, + options.report_views and "1" or "", uid_path, + options.user_id, options.auto_upvote and "1" or "" } if not legacy then - mp.command_native_async({name = "subprocess", playback_only = false, args = args}, function () end) + mp.command_native_async( + { + name = "subprocess", + playback_only = false, + args = args + }, function() end) else utils.subprocess_detached({args = args}) end @@ -260,22 +273,29 @@ function skip_ads(name, pos) speed_timer = mp.add_periodic_timer(1, fast_forward) end return - elseif (not options.skip_once or not t.skipped) and t.start_time <= pos + 1 and t.end_time > pos + 1 then + elseif (not options.skip_once or not t.skipped) and t.start_time <= pos + + 1 and t.end_time > pos + 1 then sponsor_ahead = true end end if options.audio_fade then if sponsor_ahead then if fade_dir ~= false then - if fade_dir == nil then volume_before = mp.get_property_number("volume") end + if fade_dir == nil then + volume_before = mp.get_property_number("volume") + end if fade_timer ~= nil then fade_timer:kill() end fade_dir = false - fade_timer = mp.add_periodic_timer(.1, function() fade_audio(-options.audio_fade_step) end) + fade_timer = mp.add_periodic_timer(.1, function() + fade_audio(-options.audio_fade_step) + end) end elseif fade_dir == false then fade_dir = true if fade_timer ~= nil then fade_timer:kill() end - fade_timer = mp.add_periodic_timer(.1, function() fade_audio(options.audio_fade_step) end) + fade_timer = mp.add_periodic_timer(.1, function() + fade_audio(options.audio_fade_step) + end) end end if options.fast_forward and options.fast_forward ~= true then @@ -286,25 +306,27 @@ function skip_ads(name, pos) end function vote(dir) - if last_skip.uuid == "" then return mp.osd_message("[sponsorblock] no sponsors skipped, can't submit vote") end + if last_skip.uuid == "" then + return mp.osd_message( + "[sponsorblock] no sponsors skipped, can't submit vote") + end local updown = dir == "1" and "up" or "down" - if last_skip.dir == dir then return mp.osd_message("[sponsorblock] " .. updown .. "vote already submitted") end + if last_skip.dir == dir then + return mp.osd_message("[sponsorblock] " .. updown .. + "vote already submitted") + end last_skip.dir = dir local args = { - options.python_path, - sponsorblock, - "stats", - database_file, - options.server_address, - youtube_id, - last_skip.uuid, - "", - uid_path, - options.user_id, - dir + options.python_path, sponsorblock, "stats", database_file, + options.server_address, youtube_id, last_skip.uuid, "", uid_path, + options.user_id, dir } if not legacy then - mp.command_native_async({name = "subprocess", playback_only = false, args = args}, function () end) + mp.command_native_async({ + name = "subprocess", + playback_only = false, + args = args + }, function() end) else utils.subprocess({args = args}) end @@ -312,13 +334,14 @@ function vote(dir) end function update() - mp.command_native_async({name = "subprocess", playback_only = false, args = { - options.python_path, - sponsorblock, - "update", - database_file, - options.server_address - }}, getranges) + mp.command_native_async({ + name = "subprocess", + playback_only = false, + args = { + options.python_path, sponsorblock, "update", database_file, + options.server_address + } + }, getranges) end function file_loaded() @@ -327,15 +350,18 @@ function file_loaded() segment = {a = 0, b = 0, progress = 0, first = true} last_skip = {uuid = "", dir = nil} local video_path = mp.get_property("path") - local youtube_id1 = string.match(video_path, "https?://youtu%.be/([%a%d%-_]+).*") - local youtube_id2 = string.match(video_path, "https?://w?w?w?%.?youtube%.com/v/([%a%d%-_]+).*") + local youtube_id1 = string.match(video_path, + "https?://youtu%.be/([%a%d%-_]+).*") + local youtube_id2 = string.match(video_path, + "https?://w?w?w?%.?youtube%.com/v/([%a%d%-_]+).*") local youtube_id3 = string.match(video_path, "/watch%?v=([%a%d%-_]+).*") local youtube_id4 = string.match(video_path, "/embed/([%a%d%-_]+).*") local local_pattern = nil if options.local_pattern ~= "" then local_pattern = string.match(video_path, options.local_pattern) end - youtube_id = youtube_id1 or youtube_id2 or youtube_id3 or youtube_id4 or local_pattern + youtube_id = youtube_id1 or youtube_id2 or youtube_id3 or youtube_id4 or + local_pattern if not youtube_id then return end init = true if not options.local_database then @@ -344,7 +370,9 @@ function file_loaded() local exists = file_exists(database_file) if exists and options.server_fallback then getranges(true, true) - mp.add_timeout(0, function() getranges(true, true, "", true) end) + mp.add_timeout(0, function() + getranges(true, true, "", true) + end) elseif exists then getranges(true, true) elseif options.server_fallback then @@ -352,30 +380,25 @@ function file_loaded() end end if initialized then return end - if options.skip then - mp.observe_property("time-pos", "native", skip_ads) - end + if options.skip then mp.observe_property("time-pos", "native", skip_ads) end if options.display_name ~= "" then local args = { - options.python_path, - sponsorblock, - "username", - database_file, - options.server_address, - youtube_id, - "", - "", - uid_path, - options.user_id, - options.display_name + options.python_path, sponsorblock, "username", database_file, + options.server_address, youtube_id, "", "", uid_path, + options.user_id, options.display_name } if not legacy then - mp.command_native_async({name = "subprocess", playback_only = false, args = args}, function () end) + mp.command_native_async({ + name = "subprocess", + playback_only = false, + args = args + }, function() end) else utils.subprocess_detached({args = args}) end end - if not options.local_database or (not options.auto_update and file_exists(database_file)) then return end + if not options.local_database or + (not options.auto_update and file_exists(database_file)) then return end update() end @@ -383,17 +406,19 @@ function set_segment() if not youtube_id then return end local pos = mp.get_property_number("time-pos") if pos == nil then return end - if segment.progress > 1 then - segment.progress = segment.progress - 2 - end + if segment.progress > 1 then segment.progress = segment.progress - 2 end if segment.progress == 1 then segment.progress = 0 segment.b = pos - mp.osd_message("[sponsorblock] segment boundary B set, press again for boundary A", 3) + mp.osd_message( + "[sponsorblock] segment boundary B set, press again for boundary A", + 3) else segment.progress = 1 segment.a = pos - mp.osd_message("[sponsorblock] segment boundary A set, press again for boundary B", 3) + mp.osd_message( + "[sponsorblock] segment boundary A set, press again for boundary B", + 3) end if options.make_chapters and not segment.first then local start_time = math.min(segment.a, segment.b) @@ -414,25 +439,30 @@ function submit_segment() if end_time - start_time == 0 or end_time == 0 then mp.osd_message("[sponsorblock] empty segment, not submitting") elseif segment.progress <= 1 then - mp.osd_message(string.format("[sponsorblock] press Shift+G again to confirm: %.2d:%.2d:%.2d to %.2d:%.2d:%.2d", math.floor(start_time/(60*60)), math.floor(start_time/60%60), math.floor(start_time%60), math.floor(end_time/(60*60)), math.floor(end_time/60%60), math.floor(end_time%60)), 5) + mp.osd_message(string.format( + "[sponsorblock] press Shift+G again to confirm: %.2d:%.2d:%.2d to %.2d:%.2d:%.2d", + math.floor(start_time / (60 * 60)), + math.floor(start_time / 60 % 60), + math.floor(start_time % 60), + math.floor(end_time / (60 * 60)), + math.floor(end_time / 60 % 60), + math.floor(end_time % 60)), 5) segment.progress = segment.progress + 2 else mp.osd_message("[sponsorblock] submitting segment...", 30) local submit local args = { - options.python_path, - sponsorblock, - "submit", - database_file, - options.server_address, - youtube_id, - tostring(start_time), - tostring(end_time), - uid_path, - options.user_id + options.python_path, sponsorblock, "submit", database_file, + options.server_address, youtube_id, tostring(start_time), + tostring(end_time), uid_path, options.user_id } if not legacy then - submit = mp.command_native({name = "subprocess", capture_stdout = true, playback_only = false, args = args}) + submit = mp.command_native({ + name = "subprocess", + capture_stdout = true, + playback_only = false, + args = args + }) else submit = utils.subprocess({args = args}) end @@ -445,14 +475,21 @@ function submit_segment() create_chapter("Submitted segment end", end_time) end elseif string.match(submit.stdout, "error") then - mp.osd_message("[sponsorblock] segment submission failed, server may be down. try again", 5) + mp.osd_message( + "[sponsorblock] segment submission failed, server may be down. try again", + 5) elseif string.match(submit.stdout, "502") then - mp.osd_message("[sponsorblock] segment submission failed, server is down. try again", 5) + mp.osd_message( + "[sponsorblock] segment submission failed, server is down. try again", + 5) elseif string.match(submit.stdout, "400") then - mp.osd_message("[sponsorblock] segment submission failed, impossible inputs", 5) + mp.osd_message( + "[sponsorblock] segment submission failed, impossible inputs", 5) segment = {a = 0, b = 0, progress = 0, first = true} elseif string.match(submit.stdout, "429") then - mp.osd_message("[sponsorblock] segment submission failed, rate limited. try again", 5) + mp.osd_message( + "[sponsorblock] segment submission failed, rate limited. try again", + 5) elseif string.match(submit.stdout, "409") then mp.osd_message("[sponsorblock] segment already submitted", 3) segment = {a = 0, b = 0, progress = 0, first = true} diff --git a/mpv/.config/mpv/scripts/sponsorblock_legacy.lua b/mpv/.config/mpv/scripts/sponsorblock_legacy.lua index f35bed8..6a980e5 100644 --- a/mpv/.config/mpv/scripts/sponsorblock_legacy.lua +++ b/mpv/.config/mpv/scripts/sponsorblock_legacy.lua @@ -1,3 +1,3 @@ if mp.get_script_directory == nil then - dofile(mp.find_config_file("scripts/sponsorblock/main.lua")) + dofile(mp.find_config_file("scripts/sponsorblock/main.lua")) end diff --git a/mpv/.config/mpv/scripts/uosc.lua b/mpv/.config/mpv/scripts/uosc.lua index 8ce1b69..cdc3919 100644 --- a/mpv/.config/mpv/scripts/uosc.lua +++ b/mpv/.config/mpv/scripts/uosc.lua @@ -178,11 +178,9 @@ Key script-binding uosc/delete-file-quit Key script-binding uosc/show-in-directory Key script-binding uosc/open-config-directory ``` -]] - -if mp.get_property('osc') == 'yes' then - mp.msg.info('Disabled because original osc is enabled!') - return +]] if mp.get_property('osc') == 'yes' then + mp.msg.info('Disabled because original osc is enabled!') + return end local assdraw = require('mp.assdraw') @@ -194,236 +192,273 @@ local infinity = 1e309 -- OPTIONS/CONFIG/STATE local options = { - timeline_size_min = 2, - timeline_size_max = 40, - timeline_size_min_fullscreen = 0, - timeline_size_max_fullscreen = 60, - timeline_start_hidden = false, - timeline_opacity = 0.8, - timeline_border = 1, - timeline_step = 5, - timeline_cached_ranges = '345433:0.5', - timeline_font_scale = 1, - timeline_flash = true, + timeline_size_min = 2, + timeline_size_max = 40, + timeline_size_min_fullscreen = 0, + timeline_size_max_fullscreen = 60, + timeline_start_hidden = false, + timeline_opacity = 0.8, + timeline_border = 1, + timeline_step = 5, + timeline_cached_ranges = '345433:0.5', + timeline_font_scale = 1, + timeline_flash = true, - chapters = 'dots', - chapters_opacity = 0.3, + chapters = 'dots', + chapters_opacity = 0.3, - volume = 'right', - volume_size = 40, - volume_size_fullscreen = 60, - volume_opacity = 0.8, - volume_border = 1, - volume_step = 1, - volume_font_scale = 1, - volume_flash = true, + volume = 'right', + volume_size = 40, + volume_size_fullscreen = 60, + volume_opacity = 0.8, + volume_border = 1, + volume_step = 1, + volume_font_scale = 1, + volume_flash = true, - speed = false, - speed_size = 46, - speed_size_fullscreen = 68, - speed_opacity = 1, - speed_step = 0.1, - speed_font_scale = 1, - speed_flash = true, + speed = false, + speed_size = 46, + speed_size_fullscreen = 68, + speed_opacity = 1, + speed_step = 0.1, + speed_font_scale = 1, + speed_flash = true, - menu_item_height = 36, - menu_item_height_fullscreen = 50, - menu_wasd_navigation = false, - menu_hjkl_navigation = false, - menu_opacity = 0.8, - menu_font_scale = 1, + menu_item_height = 36, + menu_item_height_fullscreen = 50, + menu_wasd_navigation = false, + menu_hjkl_navigation = false, + menu_opacity = 0.8, + menu_font_scale = 1, - top_bar_size = 40, - top_bar_size_fullscreen = 46, - top_bar_controls = true, - top_bar_title = true, + top_bar_size = 40, + top_bar_size_fullscreen = 46, + top_bar_controls = true, + top_bar_title = true, - pause_on_click_shorter_than = 0, - flash_duration = 400, - proximity_in = 40, - proximity_out = 120, - color_foreground = 'ffffff', - color_foreground_text = '000000', - color_background = '000000', - color_background_text = 'ffffff', - font_bold = false, - autohide = false, - pause_indicator = 'flash', - directory_navigation_loops = false, - media_types = '3gp,avi,bmp,flac,flv,gif,h264,h265,jpeg,jpg,m4a,m4v,mid,midi,mkv,mov,mp3,mp4,mp4a,mp4v,mpeg,mpg,oga,ogg,ogm,ogv,opus,png,rmvb,svg,tif,tiff,wav,weba,webm,webp,wma,wmv', - subtitle_types = 'aqt,gsub,jss,sub,ttxt,pjs,psb,rt,smi,slt,ssf,srt,ssa,ass,usf,idx,vt', - font_height_to_letter_width_ratio = 0.5, - chapter_ranges = '^op| op$|opening<968638:0.5>.*, ^ed| ed$|^end|ending$<968638:0.5>.*|{eof}, sponsor start<3535a5:.5>sponsor end', + pause_on_click_shorter_than = 0, + flash_duration = 400, + proximity_in = 40, + proximity_out = 120, + color_foreground = 'ffffff', + color_foreground_text = '000000', + color_background = '000000', + color_background_text = 'ffffff', + font_bold = false, + autohide = false, + pause_indicator = 'flash', + directory_navigation_loops = false, + media_types = '3gp,avi,bmp,flac,flv,gif,h264,h265,jpeg,jpg,m4a,m4v,mid,midi,mkv,mov,mp3,mp4,mp4a,mp4v,mpeg,mpg,oga,ogg,ogm,ogv,opus,png,rmvb,svg,tif,tiff,wav,weba,webm,webp,wma,wmv', + subtitle_types = 'aqt,gsub,jss,sub,ttxt,pjs,psb,rt,smi,slt,ssf,srt,ssa,ass,usf,idx,vt', + font_height_to_letter_width_ratio = 0.5, + chapter_ranges = '^op| op$|opening<968638:0.5>.*, ^ed| ed$|^end|ending$<968638:0.5>.*|{eof}, sponsor start<3535a5:.5>sponsor end' } opt.read_options(options, 'uosc') local config = { - render_delay = 0.03, -- sets max rendering frequency - font = mp.get_property('options/osd-font'), - menu_parent_opacity = 0.4, - menu_min_width = 260 + render_delay = 0.03, -- sets max rendering frequency + font = mp.get_property('options/osd-font'), + menu_parent_opacity = 0.4, + menu_min_width = 260 } local bold_tag = options.font_bold and '\\b1' or '' -local display = { - width = 1280, - height = 720, - aspect = 1.77778, -} +local display = {width = 1280, height = 720, aspect = 1.77778} local cursor = { - hidden = true, -- true when autohidden or outside of the player window - x = 0, - y = 0, + hidden = true, -- true when autohidden or outside of the player window + x = 0, + y = 0 } local state = { - os = (function() - if os.getenv('windir') ~= nil then return 'windows' end - local homedir = os.getenv('HOME') - if homedir ~= nil and string.sub(homedir,1,6) == '/Users' then return 'macos' end - return 'linux' - end)(), - cwd = mp.get_property('working-directory'), - media_title = '', - duration = nil, - position = nil, - pause = false, - chapters = nil, - chapter_ranges = nil, - fullscreen = mp.get_property_native('fullscreen'), - maximized = mp.get_property_native('window-maximized'), - render_timer = nil, - render_last_time = 0, - volume = nil, - volume_max = nil, - mute = nil, - cursor_autohide_timer = mp.add_timeout(mp.get_property_native('cursor-autohide') / 1000, function() - if not options.autohide then return end - handle_mouse_leave() - end), - mouse_bindings_enabled = false, - cached_ranges = nil, + os = (function() + if os.getenv('windir') ~= nil then return 'windows' end + local homedir = os.getenv('HOME') + if homedir ~= nil and string.sub(homedir, 1, 6) == '/Users' then + return 'macos' + end + return 'linux' + end)(), + cwd = mp.get_property('working-directory'), + media_title = '', + duration = nil, + position = nil, + pause = false, + chapters = nil, + chapter_ranges = nil, + fullscreen = mp.get_property_native('fullscreen'), + maximized = mp.get_property_native('window-maximized'), + render_timer = nil, + render_last_time = 0, + volume = nil, + volume_max = nil, + mute = nil, + cursor_autohide_timer = mp.add_timeout( + mp.get_property_native('cursor-autohide') / 1000, function() + if not options.autohide then return end + handle_mouse_leave() + end), + mouse_bindings_enabled = false, + cached_ranges = nil } local forced_key_bindings -- defined at the bottom next to events -- HELPERS function round(number) - local modulus = number % 1 - return modulus < 0.5 and math.floor(number) or math.ceil(number) + local modulus = number % 1 + return modulus < 0.5 and math.floor(number) or math.ceil(number) end function call_me_maybe(fn, value1, value2, value3) - if fn then fn(value1, value2, value3) end + if fn then fn(value1, value2, value3) end end function split(str, pattern) - local list = {} - local full_pattern = '(.-)' .. pattern - local last_end = 1 - local start_index, end_index, capture = str:find(full_pattern, 1) - while start_index do - list[#list +1] = capture - last_end = end_index + 1 - start_index, end_index, capture = str:find(full_pattern, last_end) - end - if last_end <= (#str + 1) then - capture = str:sub(last_end) - list[#list +1] = capture - end - return list + local list = {} + local full_pattern = '(.-)' .. pattern + local last_end = 1 + local start_index, end_index, capture = str:find(full_pattern, 1) + while start_index do + list[#list + 1] = capture + last_end = end_index + 1 + start_index, end_index, capture = str:find(full_pattern, last_end) + end + if last_end <= (#str + 1) then + capture = str:sub(last_end) + list[#list + 1] = capture + end + return list end function itable_find(haystack, needle) - local is_needle = type(needle) == 'function' and needle or function(index, value) - return value == needle - end - for index, value in ipairs(haystack) do - if is_needle(index, value) then return index, value end - end + local is_needle = type(needle) == 'function' and needle or + function(index, value) return value == needle end + for index, value in ipairs(haystack) do + if is_needle(index, value) then return index, value end + end end function itable_filter(haystack, needle) - local is_needle = type(needle) == 'function' and needle or function(index, value) - return value == needle - end - local filtered = {} - for index, value in ipairs(haystack) do - if is_needle(index, value) then filtered[#filtered + 1] = value end - end - return filtered + local is_needle = type(needle) == 'function' and needle or + function(index, value) return value == needle end + local filtered = {} + for index, value in ipairs(haystack) do + if is_needle(index, value) then filtered[#filtered + 1] = value end + end + return filtered end function itable_remove(haystack, needle) - local should_remove = type(needle) == 'function' and needle or function(value) - return value == needle - end - local new_table = {} - for _, value in ipairs(haystack) do - if not should_remove(value) then - new_table[#new_table + 1] = value - end - end - return new_table + local should_remove = type(needle) == 'function' and needle or + function(value) return value == needle end + local new_table = {} + for _, value in ipairs(haystack) do + if not should_remove(value) then + new_table[#new_table + 1] = value + end + end + return new_table end function itable_slice(haystack, start_pos, end_pos) - start_pos = start_pos and start_pos or 1 - end_pos = end_pos and end_pos or #haystack + start_pos = start_pos and start_pos or 1 + end_pos = end_pos and end_pos or #haystack - if end_pos < 0 then end_pos = #haystack + end_pos + 1 end - if start_pos < 0 then start_pos = #haystack + start_pos + 1 end + if end_pos < 0 then end_pos = #haystack + end_pos + 1 end + if start_pos < 0 then start_pos = #haystack + start_pos + 1 end - local new_table = {} - for index, value in ipairs(haystack) do - if index >= start_pos and index <= end_pos then - new_table[#new_table + 1] = value - end - end - return new_table + local new_table = {} + for index, value in ipairs(haystack) do + if index >= start_pos and index <= end_pos then + new_table[#new_table + 1] = value + end + end + return new_table end function table_copy(table) - local new_table = {} - for key, value in pairs(table) do new_table[key] = value end - return new_table + local new_table = {} + for key, value in pairs(table) do new_table[key] = value end + return new_table end -- Sorting comparator close to (but not exactly) how file explorers sort files local word_order_comparator = (function() - local symbol_order - local default_order + local symbol_order + local default_order - if state.os == 'win' then - symbol_order = { - ['!'] = 1, ['#'] = 2, ['$'] = 3, ['%'] = 4, ['&'] = 5, ['('] = 6, [')'] = 6, [','] = 7, - ['.'] = 8, ["'"] = 9, ['-'] = 10, [';'] = 11, ['@'] = 12, ['['] = 13, [']'] = 13, ['^'] = 14, - ['_'] = 15, ['`'] = 16, ['{'] = 17, ['}'] = 17, ['~'] = 18, ['+'] = 19, ['='] = 20, - } - default_order = 21 - else - symbol_order = { - ['`'] = 1, ['^'] = 2, ['~'] = 3, ['='] = 4, ['_'] = 5, ['-'] = 6, [','] = 7, [';'] = 8, - ['!'] = 9, ["'"] = 10, ['('] = 11, [')'] = 11, ['['] = 12, [']'] = 12, ['{'] = 13, ['}'] = 14, - ['@'] = 15, ['$'] = 16, ['*'] = 17, ['&'] = 18, ['%'] = 19, ['+'] = 20, ['.'] = 22, ['#'] = 23, - } - default_order = 21 - end + if state.os == 'win' then + symbol_order = { + ['!'] = 1, + ['#'] = 2, + ['$'] = 3, + ['%'] = 4, + ['&'] = 5, + ['('] = 6, + [')'] = 6, + [','] = 7, + ['.'] = 8, + ["'"] = 9, + ['-'] = 10, + [';'] = 11, + ['@'] = 12, + ['['] = 13, + [']'] = 13, + ['^'] = 14, + ['_'] = 15, + ['`'] = 16, + ['{'] = 17, + ['}'] = 17, + ['~'] = 18, + ['+'] = 19, + ['='] = 20 + } + default_order = 21 + else + symbol_order = { + ['`'] = 1, + ['^'] = 2, + ['~'] = 3, + ['='] = 4, + ['_'] = 5, + ['-'] = 6, + [','] = 7, + [';'] = 8, + ['!'] = 9, + ["'"] = 10, + ['('] = 11, + [')'] = 11, + ['['] = 12, + [']'] = 12, + ['{'] = 13, + ['}'] = 14, + ['@'] = 15, + ['$'] = 16, + ['*'] = 17, + ['&'] = 18, + ['%'] = 19, + ['+'] = 20, + ['.'] = 22, + ['#'] = 23 + } + default_order = 21 + end - return function (a, b) - a = a:lower() - b = b:lower() - for i = 1, math.max(#a, #b) do - local ai = a:sub(i, i) - local bi = b:sub(i, i) - if ai == nil and bi then return true end - if bi == nil and ai then return false end - local a_order = symbol_order[ai] or default_order - local b_order = symbol_order[bi] or default_order - if a_order == b_order then - return a < b - else - return a_order < b_order - end - end - end + return function(a, b) + a = a:lower() + b = b:lower() + for i = 1, math.max(#a, #b) do + local ai = a:sub(i, i) + local bi = b:sub(i, i) + if ai == nil and bi then return true end + if bi == nil and ai then return false end + local a_order = symbol_order[ai] or default_order + local b_order = symbol_order[bi] or default_order + if a_order == b_order then + return a < b + else + return a_order < b_order + end + end + end end)() -- Creates in-between frames to animate value from `from` to `to` numbers. @@ -432,199 +467,209 @@ end)() -- `speed` is an optional float between 1-instant and 0-infinite duration -- `callback` is called either on animation end, or when animation is canceled function tween(from, to, setter, speed, callback) - if type(speed) ~= 'number' then - callback = speed - speed = 0.3 - end - local timeout - local getTo = type(to) == 'function' and to or function() return to end - local cutoff = math.abs(getTo() - from) * 0.01 - function tick() - from = from + ((getTo() - from) * speed) - local is_end = math.abs(getTo() - from) <= cutoff - setter(is_end and getTo() or from) - request_render() - if is_end then - call_me_maybe(callback) - else - timeout:resume() - end - end - timeout = mp.add_timeout(0.016, tick) - tick() - return function() - timeout:kill() - call_me_maybe(callback) - end + if type(speed) ~= 'number' then + callback = speed + speed = 0.3 + end + local timeout + local getTo = type(to) == 'function' and to or function() return to end + local cutoff = math.abs(getTo() - from) * 0.01 + function tick() + from = from + ((getTo() - from) * speed) + local is_end = math.abs(getTo() - from) <= cutoff + setter(is_end and getTo() or from) + request_render() + if is_end then + call_me_maybe(callback) + else + timeout:resume() + end + end + timeout = mp.add_timeout(0.016, tick) + tick() + return function() + timeout:kill() + call_me_maybe(callback) + end end -- Kills ongoing animation if one is already running on this element. -- Killed animation will not get its `on_end` called. function tween_element(element, from, to, setter, speed, callback) - if type(speed) ~= 'number' then - callback = speed - speed = 0.3 - end + if type(speed) ~= 'number' then + callback = speed + speed = 0.3 + end - tween_element_stop(element) + tween_element_stop(element) - element.stop_current_animation = tween( - from, to, - function(value) setter(element, value) end, - speed, - function() - element.stop_current_animation = nil - call_me_maybe(callback, element) - end - ) + element.stop_current_animation = tween(from, to, function(value) + setter(element, value) + end, speed, function() + element.stop_current_animation = nil + call_me_maybe(callback, element) + end) end -- Stopped animation will not get its on_end called. function tween_element_is_tweening(element) - return element and element.stop_current_animation + return element and element.stop_current_animation end -- Stopped animation will not get its on_end called. function tween_element_stop(element) - call_me_maybe(element and element.stop_current_animation) + call_me_maybe(element and element.stop_current_animation) end -- Helper to automatically use an element property setter function tween_element_property(element, prop, from, to, speed, callback) - tween_element(element, from, to, function(_, value) element[prop] = value end, speed, callback) + tween_element(element, from, to, + function(_, value) element[prop] = value end, speed, callback) end function get_point_to_rectangle_proximity(point, rect) - local dx = math.max(rect.ax - point.x, 0, point.x - rect.bx + 1) - local dy = math.max(rect.ay - point.y, 0, point.y - rect.by + 1) - return math.sqrt(dx*dx + dy*dy); + local dx = math.max(rect.ax - point.x, 0, point.x - rect.bx + 1) + local dy = math.max(rect.ay - point.y, 0, point.y - rect.by + 1) + return math.sqrt(dx * dx + dy * dy); end function text_width_estimate(letters, font_size) - return letters and letters * font_size * options.font_height_to_letter_width_ratio or 0 + return letters and letters * font_size * + options.font_height_to_letter_width_ratio or 0 end -function opacity_to_alpha(opacity) - return 255 - math.ceil(255 * opacity) -end +function opacity_to_alpha(opacity) return 255 - math.ceil(255 * opacity) end function ass_opacity(opacity, fraction) - fraction = fraction ~= nil and fraction or 1 - if type(opacity) == 'number' then - return string.format('{\\alpha&H%X&}', opacity_to_alpha(opacity * fraction)) - else - return string.format( - '{\\1a&H%X&\\2a&H%X&\\3a&H%X&\\4a&H%X&}', - opacity_to_alpha((opacity[1] or 0) * fraction), - opacity_to_alpha((opacity[2] or 0) * fraction), - opacity_to_alpha((opacity[3] or 0) * fraction), - opacity_to_alpha((opacity[4] or 0) * fraction) - ) - end + fraction = fraction ~= nil and fraction or 1 + if type(opacity) == 'number' then + return string.format('{\\alpha&H%X&}', + opacity_to_alpha(opacity * fraction)) + else + return string.format('{\\1a&H%X&\\2a&H%X&\\3a&H%X&\\4a&H%X&}', + opacity_to_alpha((opacity[1] or 0) * fraction), + opacity_to_alpha((opacity[2] or 0) * fraction), + opacity_to_alpha((opacity[3] or 0) * fraction), + opacity_to_alpha((opacity[4] or 0) * fraction)) + end end -- Ensures path is absolute and normalizes slashes to the current platform function normalize_path(path) - if not path or is_protocol(path) then return path end + if not path or is_protocol(path) then return path end - -- Ensure path is absolute - if not (path:match('^/') or path:match('^%a+:') or path:match('^\\\\')) then - path = utils.join_path(state.cwd, path) - end + -- Ensure path is absolute + if not (path:match('^/') or path:match('^%a+:') or path:match('^\\\\')) then + path = utils.join_path(state.cwd, path) + end - -- Use proper slashes - if state.os == 'windows' then - return path:gsub('/', '\\') - else - return path:gsub('\\', '/') - end + -- Use proper slashes + if state.os == 'windows' then + return path:gsub('/', '\\') + else + return path:gsub('\\', '/') + end end -- Check if path is a protocol, such as `http://...` -function is_protocol(path) - return path:match('^%a[%a%d-_]+://') -end +function is_protocol(path) return path:match('^%a[%a%d-_]+://') end function get_extension(path) - local parts = split(path, '%.') - return parts and #parts > 1 and parts[#parts] or nil + local parts = split(path, '%.') + return parts and #parts > 1 and parts[#parts] or nil end -- Serializes path into its semantic parts function serialize_path(path) - if not path or is_protocol(path) then return end - path = normalize_path(path) - local parts = split(path, '[\\/]+') - if parts[#parts] == '' then table.remove(parts, #parts) end -- remove trailing separator - local basename = parts and parts[#parts] or path - local dirname = #parts > 1 and table.concat(itable_slice(parts, 1, #parts - 1), state.os == 'windows' and '\\' or '/') or nil - local dot_split = split(basename, '%.') - return { - path = path:sub(-1) == ':' and state.os == 'windows' and path..'\\' or path, - is_root = dirname == nil, - dirname = dirname, - basename = basename, - filename = #dot_split > 1 and table.concat(itable_slice(dot_split, 1, #dot_split - 1), '.') or basename, - extension = #dot_split > 1 and dot_split[#dot_split] or nil, - } + if not path or is_protocol(path) then return end + path = normalize_path(path) + local parts = split(path, '[\\/]+') + if parts[#parts] == '' then table.remove(parts, #parts) end -- remove trailing separator + local basename = parts and parts[#parts] or path + local dirname = #parts > 1 and + table.concat(itable_slice(parts, 1, #parts - 1), + state.os == 'windows' and '\\' or '/') or + nil + local dot_split = split(basename, '%.') + return { + path = path:sub(-1) == ':' and state.os == 'windows' and path .. '\\' or + path, + is_root = dirname == nil, + dirname = dirname, + basename = basename, + filename = #dot_split > 1 and + table.concat(itable_slice(dot_split, 1, #dot_split - 1), '.') or + basename, + extension = #dot_split > 1 and dot_split[#dot_split] or nil + } end function get_files_in_directory(directory, allowed_types) - local files, error = utils.readdir(directory, 'files') + local files, error = utils.readdir(directory, 'files') - if not files then - msg.error('Retrieving files failed: '..(error or '')) - return - end + if not files then + msg.error('Retrieving files failed: ' .. (error or '')) + return + end - -- Filter only requested file types - if allowed_types then - files = itable_filter(files, function(_, file) - local extension = get_extension(file) - return extension and itable_find(allowed_types, extension:lower()) - end) - end + -- Filter only requested file types + if allowed_types then + files = itable_filter(files, function(_, file) + local extension = get_extension(file) + return extension and itable_find(allowed_types, extension:lower()) + end) + end - table.sort(files, word_order_comparator) + table.sort(files, word_order_comparator) - return files + return files end function get_adjacent_file(file_path, direction, allowed_types) - local current_file = serialize_path(file_path) - local files = get_files_in_directory(current_file.dirname, allowed_types) + local current_file = serialize_path(file_path) + local files = get_files_in_directory(current_file.dirname, allowed_types) - if not files then return end + if not files then return end - for index, file in ipairs(files) do - if current_file.basename == file then - if direction == 'forward' then - if files[index + 1] then return utils.join_path(current_file.dirname, files[index + 1]) end - if options.directory_navigation_loops and files[1] then return utils.join_path(current_file.dirname, files[1]) end - else - if files[index - 1] then return utils.join_path(current_file.dirname, files[index - 1]) end - if options.directory_navigation_loops and files[#files] then return utils.join_path(current_file.dirname, files[#files]) end - end + for index, file in ipairs(files) do + if current_file.basename == file then + if direction == 'forward' then + if files[index + 1] then + return utils.join_path(current_file.dirname, + files[index + 1]) + end + if options.directory_navigation_loops and files[1] then + return utils.join_path(current_file.dirname, files[1]) + end + else + if files[index - 1] then + return utils.join_path(current_file.dirname, + files[index - 1]) + end + if options.directory_navigation_loops and files[#files] then + return utils.join_path(current_file.dirname, files[#files]) + end + end - -- This is the only file in directory - return nil - end - end + -- This is the only file in directory + return nil + end + end end -- Ensures chapters are in chronological order function get_normalized_chapters() - local chapters = mp.get_property_native('chapter-list') + local chapters = mp.get_property_native('chapter-list') - if not chapters then return end + if not chapters then return end - -- Copy table - chapters = itable_slice(chapters) + -- Copy table + chapters = itable_slice(chapters) - -- Ensure chronological order of chapters - table.sort(chapters, function(a, b) return a.time < b.time end) + -- Ensure chronological order of chapters + table.sort(chapters, function(a, b) return a.time < b.time end) - return chapters + return chapters end -- Element @@ -654,28 +699,32 @@ Signature: } ]] local Element = { - captures = nil, - ax = 0, ay = 0, bx = 0, by = 0, - proximity = 0, proximity_raw = infinity, + captures = nil, + ax = 0, + ay = 0, + bx = 0, + by = 0, + proximity = 0, + proximity_raw = infinity } Element.__index = Element function Element.new(props) - local element = setmetatable(props, Element) - element._eventListeners = {} + local element = setmetatable(props, Element) + element._eventListeners = {} - -- Flash timer - element._flash_out_timer = mp.add_timeout(options.flash_duration / 1000, function() - local getTo = function() return element.proximity end - element:tween_property('forced_proximity', 1, getTo, function() - element.forced_proximity = nil - end) - end) - element._flash_out_timer:kill() + -- Flash timer + element._flash_out_timer = mp.add_timeout(options.flash_duration / 1000, + function() + local getTo = function() return element.proximity end + element:tween_property('forced_proximity', 1, getTo, + function() element.forced_proximity = nil end) + end) + element._flash_out_timer:kill() - element:init() + element:init() - return element + return element end function Element:init() end @@ -683,7 +732,7 @@ function Element:destroy() end -- Call method if it exists function Element:maybe(name, ...) - if self[name] then return self[name](self, ...) end + if self[name] then return self[name](self, ...) end end -- Tween helpers @@ -694,35 +743,36 @@ function Element:is_tweening() tween_element_is_tweening(self) end -- Event listeners function Element:on(name, handler) - if self._eventListeners[name] == nil then self._eventListeners[name] = {} end - local preexistingIndex = itable_find(self._eventListeners[name], handler) - if preexistingIndex then - return - else - self._eventListeners[name][#self._eventListeners[name] + 1] = handler - end + if self._eventListeners[name] == nil then self._eventListeners[name] = {} end + local preexistingIndex = itable_find(self._eventListeners[name], handler) + if preexistingIndex then + return + else + self._eventListeners[name][#self._eventListeners[name] + 1] = handler + end end function Element:off(name, handler) - if self._eventListeners[name] == nil then return end - local index = itable_find(self._eventListeners, handler) - if index then table.remove(self._eventListeners, index) end + if self._eventListeners[name] == nil then return end + local index = itable_find(self._eventListeners, handler) + if index then table.remove(self._eventListeners, index) end end function Element:trigger(name, ...) - self:maybe('on_'..name, ...) - if self._eventListeners[name] == nil then return end - for _, handler in ipairs(self._eventListeners[name]) do handler(...) end + self:maybe('on_' .. name, ...) + if self._eventListeners[name] == nil then return end + for _, handler in ipairs(self._eventListeners[name]) do handler(...) end end -- Briefly flashes the element for `options.flash_duration` milliseconds. -- Useful to visualize changes of volume and timeline when changed via hotkeys. -- Implemented by briefly adding animated `forced_proximity` property to the element. function Element:flash() - if options.flash_duration > 0 and (self.proximity < 1 or self._flash_out_timer:is_enabled()) then - self:tween_stop() - self.forced_proximity = 1 - self._flash_out_timer:kill() - self._flash_out_timer:resume() - end + if options.flash_duration > 0 and + (self.proximity < 1 or self._flash_out_timer:is_enabled()) then + self:tween_stop() + self.forced_proximity = 1 + self._flash_out_timer:kill() + self._flash_out_timer:resume() + end end -- ELEMENTS @@ -732,26 +782,26 @@ Elements.__index = Elements local elements = setmetatable({}, Elements) function Elements:add(name, element) - local insert_index = #Elements.itable + 1 + local insert_index = #Elements.itable + 1 - -- Replace if element already exists - if self:has(name) then - insert_index = itable_find(Elements.itable, function(_, element) - return element.name == name - end) - end + -- Replace if element already exists + if self:has(name) then + insert_index = itable_find(Elements.itable, function(_, element) + return element.name == name + end) + end - element.name = name - Elements.itable[insert_index] = element - self[name] = element + element.name = name + Elements.itable[insert_index] = element + self[name] = element - request_render() + request_render() end function Elements:remove(name, props) - Elements.itable = itable_remove(Elements.itable, self[name]) - self[name] = nil - request_render() + Elements.itable = itable_remove(Elements.itable, self[name]) + self[name] = nil + request_render() end function Elements:has(name) return self[name] ~= nil end @@ -786,406 +836,465 @@ Menu.__index = Menu local menu = setmetatable({key_bindings = {}, is_closing = false}, Menu) function Menu:is_open(menu_type) - return elements.menu ~= nil and (not menu_type or elements.menu.type == menu_type) + return elements.menu ~= nil and + (not menu_type or elements.menu.type == menu_type) end function Menu:open(items, open_item, opts) - opts = opts or {} + opts = opts or {} - if menu:is_open() then - if not opts.parent_menu then - menu:close(true, function() - menu:open(items, open_item, opts) - end) - return - end - else - menu:enable_key_bindings() - elements.curtain:fadein() - end + if menu:is_open() then + if not opts.parent_menu then + menu:close(true, function() + menu:open(items, open_item, opts) + end) + return + end + else + menu:enable_key_bindings() + elements.curtain:fadein() + end - elements:add('menu', Element.new({ - captures = {mouse_buttons = true}, - type = nil, -- menu type such as `menu`, `chapters`, ... - title = nil, - width = nil, - height = nil, - offset_x = 0, -- used to animated from/to left when submenu - item_height = nil, - item_spacing = 1, - item_content_spacing = nil, - font_size = nil, - scroll_step = nil, - scroll_height = nil, - scroll_y = 0, - opacity = 0, - relative_parent_opacity = 0.4, - items = items, - active_item = nil, - selected_item = nil, - open_item = open_item, - parent_menu = nil, - init = function(this) - -- Already initialized - if this.width ~= nil then return end + elements:add('menu', Element.new({ + captures = {mouse_buttons = true}, + type = nil, -- menu type such as `menu`, `chapters`, ... + title = nil, + width = nil, + height = nil, + offset_x = 0, -- used to animated from/to left when submenu + item_height = nil, + item_spacing = 1, + item_content_spacing = nil, + font_size = nil, + scroll_step = nil, + scroll_height = nil, + scroll_y = 0, + opacity = 0, + relative_parent_opacity = 0.4, + items = items, + active_item = nil, + selected_item = nil, + open_item = open_item, + parent_menu = nil, + init = function(this) + -- Already initialized + if this.width ~= nil then return end - -- Apply options - for key, value in pairs(opts) do this[key] = value end - this.selected_item = this.active_item + -- Apply options + for key, value in pairs(opts) do this[key] = value end + this.selected_item = this.active_item - -- Set initial dimensions - this:on_display_resize() + -- Set initial dimensions + this:on_display_resize() - -- Scroll to active item - this:scroll_to_item(this.active_item) + -- Scroll to active item + this:scroll_to_item(this.active_item) - -- Transition in animation - menu.transition = {to = 'child', target = this} - local start_offset = this.parent_menu and (this.parent_menu.width + this.width) / 2 or 0 + -- Transition in animation + menu.transition = {to = 'child', target = this} + local start_offset = this.parent_menu and + (this.parent_menu.width + this.width) / 2 or + 0 - tween_element(menu.transition.target, 0, 1, function(_, pos) - this:set_offset_x(round(start_offset * (1 - pos))) - this.opacity = pos - this:set_parent_opacity(1 - ((1 - config.menu_parent_opacity) * pos)) - end, function() - menu.transition = nil - update_proximities() - end) - end, - destroy = function(this) - request_render() - end, - on_display_resize = function(this) - this.item_height = (state.fullscreen or state.maximized) and options.menu_item_height_fullscreen or options.menu_item_height - this.font_size = round(this.item_height * 0.48 * options.menu_font_scale) - this.item_content_spacing = round((this.item_height - this.font_size) * 0.6) - this.scroll_step = this.item_height + this.item_spacing + tween_element(menu.transition.target, 0, 1, function(_, pos) + this:set_offset_x(round(start_offset * (1 - pos))) + this.opacity = pos + this:set_parent_opacity(1 - + ((1 - config.menu_parent_opacity) * + pos)) + end, function() + menu.transition = nil + update_proximities() + end) + end, + destroy = function(this) request_render() end, + on_display_resize = function(this) + this.item_height = (state.fullscreen or state.maximized) and + options.menu_item_height_fullscreen or + options.menu_item_height + this.font_size = round(this.item_height * 0.48 * + options.menu_font_scale) + this.item_content_spacing = round( + (this.item_height - this.font_size) * + 0.6) + this.scroll_step = this.item_height + this.item_spacing - -- Estimate width of a widest item - local estimated_max_width = 0 - for _, item in ipairs(items) do - local item_text_length = ((item.title and item.title:len() or 0) + (item.hint and item.hint:len() or 0)) - local spacings_in_item = item.hint and 3 or 2 - local estimated_width = text_width_estimate(item_text_length, this.font_size) + (this.item_content_spacing * spacings_in_item) - if estimated_width > estimated_max_width then - estimated_max_width = estimated_width - end - end + -- Estimate width of a widest item + local estimated_max_width = 0 + for _, item in ipairs(items) do + local item_text_length = + ((item.title and item.title:len() or 0) + + (item.hint and item.hint:len() or 0)) + local spacings_in_item = item.hint and 3 or 2 + local estimated_width = text_width_estimate(item_text_length, + this.font_size) + + (this.item_content_spacing * + spacings_in_item) + if estimated_width > estimated_max_width then + estimated_max_width = estimated_width + end + end - -- Also check menu title - local menu_title_length = this.title and this.title:len() or 0 - local estimated_menu_title_width = text_width_estimate(menu_title_length, this.font_size) - if estimated_menu_title_width > estimated_max_width then - estimated_max_width = estimated_menu_title_width - end + -- Also check menu title + local menu_title_length = this.title and this.title:len() or 0 + local estimated_menu_title_width = + text_width_estimate(menu_title_length, this.font_size) + if estimated_menu_title_width > estimated_max_width then + estimated_max_width = estimated_menu_title_width + end - -- Coordinates and sizes are of the scrollable area to make - -- consuming values in rendering easier. Title drawn above this, so - -- we need to account for that in max_height and ay position. - this.width = round(math.min(math.max(estimated_max_width, config.menu_min_width), display.width * 0.9)) - local title_height = this.title and this.scroll_step or 0 - local max_height = round(display.height * 0.9) - title_height - this.height = math.min(round(this.scroll_step * #items) - this.item_spacing, max_height) - this.scroll_height = math.max((this.scroll_step * #this.items) - this.height - this.item_spacing, 0) - this.ax = round((display.width - this.width) / 2) + this.offset_x - this.ay = round((display.height - this.height) / 2 + (title_height / 2)) - this.bx = round(this.ax + this.width) - this.by = round(this.ay + this.height) + -- Coordinates and sizes are of the scrollable area to make + -- consuming values in rendering easier. Title drawn above this, so + -- we need to account for that in max_height and ay position. + this.width = round(math.min(math.max(estimated_max_width, + config.menu_min_width), + display.width * 0.9)) + local title_height = this.title and this.scroll_step or 0 + local max_height = round(display.height * 0.9) - title_height + this.height = math.min(round(this.scroll_step * #items) - + this.item_spacing, max_height) + this.scroll_height = math.max( + (this.scroll_step * #this.items) - + this.height - this.item_spacing, 0) + this.ax = round((display.width - this.width) / 2) + this.offset_x + this.ay = round((display.height - this.height) / 2 + + (title_height / 2)) + this.bx = round(this.ax + this.width) + this.by = round(this.ay + this.height) - if this.parent_menu then - this.parent_menu:on_display_resize() - end - end, - set_items = function(this, items, props) - this.items = items - this.selected_item = nil - this.active_item = nil - if props then - for key, value in pairs(props) do this[key] = value end - end - this:on_display_resize() - request_render() - end, - set_offset_x = function(this, offset) - local delta = offset - this.offset_x - this.offset_x = offset - this.ax = this.ax + delta - this.bx = this.bx + delta - if this.parent_menu then - this.parent_menu:set_offset_x(offset - ((this.width + this.parent_menu.width) / 2) - this.item_spacing) - else - update_proximities() - end - end, - fadeout = function(this, callback) - this:tween(1, 0, function(this, pos) - this.opacity = pos - this:set_parent_opacity(pos * config.menu_parent_opacity) - end, callback) - end, - set_parent_opacity = function(this, opacity) - if this.parent_menu then - this.parent_menu.opacity = opacity - this.parent_menu:set_parent_opacity(opacity * config.menu_parent_opacity) - end - end, - get_item_index_below_cursor = function(this) - return math.ceil((cursor.y - this.ay + this.scroll_y) / this.scroll_step) - end, - get_first_visible_index = function(this) - return round(this.scroll_y / this.scroll_step) + 1 - end, - get_last_visible_index = function(this) - return round((this.scroll_y + this.height) / this.scroll_step) - end, - get_centermost_visible_index = function(this) - return round((this.scroll_y + (this.height / 2)) / this.scroll_step) - end, - scroll_to = function(this, pos) - this.scroll_y = math.max(math.min(pos, this.scroll_height), 0) - request_render() - end, - scroll_to_item = function(this, index) - if (index and index >= 1 and index <= #this.items) then - this:scroll_to(round((this.scroll_step * (index - 1)) - ((this.height - this.scroll_step) / 2))) - end - end, - select_index = function(this, index) - this.selected_item = (index and index >= 1 and index <= #this.items) and index or nil - request_render() - end, - select_value = function(this, value) - this:select_index(itable_find(this.items, function(_, item) return item.value == value end)) - end, - activate_index = function(this, index) - this.active_item = (index and index >= 1 and index <= #this.items) and index or nil - request_render() - end, - activate_value = function(this, value) - this:activate_index(itable_find(this.items, function(_, item) return item.value == value end)) - end, - delete_index = function(this, index) - if (index and index >= 1 and index <= #this.items) then - local previous_active_value = this.active_index and this.items[this.active_index].value or nil - table.remove(this.items, index) - this:on_display_resize() - if previous_active_value then this:activate_value(previous_active_value) end - this:scroll_to_item(this.selected_item) - end - end, - delete_value = function(this, value) - this:delete_index(itable_find(this.items, function(_, item) return item.value == value end)) - end, - prev = function(this) - local default_anchor = this.scroll_height > this.scroll_step and this:get_centermost_visible_index() or this:get_last_visible_index() - local current_index = this.selected_item or default_anchor + 1 - this.selected_item = math.max(current_index - 1, 1) - this:scroll_to_item(this.selected_item) - end, - next = function(this) - local default_anchor = this.scroll_height > this.scroll_step and this:get_centermost_visible_index() or this:get_first_visible_index() - local current_index = this.selected_item or default_anchor - 1 - this.selected_item = math.min(current_index + 1, #this.items) - this:scroll_to_item(this.selected_item) - end, - back = function(this) - if menu.transition then - local transition_target = menu.transition.target - local transition_target_type = menu.transition.target - tween_element_stop(transition_target) - if transition_target_type == 'parent' then - elements:add('menu', transition_target) - end - menu.transition = nil - transition_target:back() - return - else - menu.transition = {to = 'parent', target = this.parent_menu} - end + if this.parent_menu then + this.parent_menu:on_display_resize() + end + end, + set_items = function(this, items, props) + this.items = items + this.selected_item = nil + this.active_item = nil + if props then + for key, value in pairs(props) do + this[key] = value + end + end + this:on_display_resize() + request_render() + end, + set_offset_x = function(this, offset) + local delta = offset - this.offset_x + this.offset_x = offset + this.ax = this.ax + delta + this.bx = this.bx + delta + if this.parent_menu then + this.parent_menu:set_offset_x( + offset - ((this.width + this.parent_menu.width) / 2) - + this.item_spacing) + else + update_proximities() + end + end, + fadeout = function(this, callback) + this:tween(1, 0, function(this, pos) + this.opacity = pos + this:set_parent_opacity(pos * config.menu_parent_opacity) + end, callback) + end, + set_parent_opacity = function(this, opacity) + if this.parent_menu then + this.parent_menu.opacity = opacity + this.parent_menu:set_parent_opacity( + opacity * config.menu_parent_opacity) + end + end, + get_item_index_below_cursor = function(this) + return math.ceil((cursor.y - this.ay + this.scroll_y) / + this.scroll_step) + end, + get_first_visible_index = function(this) + return round(this.scroll_y / this.scroll_step) + 1 + end, + get_last_visible_index = function(this) + return round((this.scroll_y + this.height) / this.scroll_step) + end, + get_centermost_visible_index = function(this) + return round((this.scroll_y + (this.height / 2)) / this.scroll_step) + end, + scroll_to = function(this, pos) + this.scroll_y = math.max(math.min(pos, this.scroll_height), 0) + request_render() + end, + scroll_to_item = function(this, index) + if (index and index >= 1 and index <= #this.items) then + this:scroll_to(round((this.scroll_step * (index - 1)) - + ((this.height - this.scroll_step) / 2))) + end + end, + select_index = function(this, index) + this.selected_item = + (index and index >= 1 and index <= #this.items) and index or nil + request_render() + end, + select_value = function(this, value) + this:select_index(itable_find(this.items, function(_, item) + return item.value == value + end)) + end, + activate_index = function(this, index) + this.active_item = + (index and index >= 1 and index <= #this.items) and index or nil + request_render() + end, + activate_value = function(this, value) + this:activate_index(itable_find(this.items, function(_, item) + return item.value == value + end)) + end, + delete_index = function(this, index) + if (index and index >= 1 and index <= #this.items) then + local previous_active_value = + this.active_index and this.items[this.active_index].value or + nil + table.remove(this.items, index) + this:on_display_resize() + if previous_active_value then + this:activate_value(previous_active_value) + end + this:scroll_to_item(this.selected_item) + end + end, + delete_value = function(this, value) + this:delete_index(itable_find(this.items, function(_, item) + return item.value == value + end)) + end, + prev = function(this) + local default_anchor = this.scroll_height > this.scroll_step and + this:get_centermost_visible_index() or + this:get_last_visible_index() + local current_index = this.selected_item or default_anchor + 1 + this.selected_item = math.max(current_index - 1, 1) + this:scroll_to_item(this.selected_item) + end, + next = function(this) + local default_anchor = this.scroll_height > this.scroll_step and + this:get_centermost_visible_index() or + this:get_first_visible_index() + local current_index = this.selected_item or default_anchor - 1 + this.selected_item = math.min(current_index + 1, #this.items) + this:scroll_to_item(this.selected_item) + end, + back = function(this) + if menu.transition then + local transition_target = menu.transition.target + local transition_target_type = menu.transition.target + tween_element_stop(transition_target) + if transition_target_type == 'parent' then + elements:add('menu', transition_target) + end + menu.transition = nil + transition_target:back() + return + else + menu.transition = {to = 'parent', target = this.parent_menu} + end - if menu.transition.target == nil then - menu:close() - return - end + if menu.transition.target == nil then + menu:close() + return + end - local target = menu.transition.target - local to_offset = -target.offset_x + this.offset_x + local target = menu.transition.target + local to_offset = -target.offset_x + this.offset_x - tween_element(target, 0, 1, function(_, pos) - this:set_offset_x(round(to_offset * pos)) - this.opacity = 1 - pos - this:set_parent_opacity(config.menu_parent_opacity + ((1 - config.menu_parent_opacity) * pos)) - end, function() - menu.transition = nil - elements:add('menu', target) - update_proximities() - end) - end, - open_selected_item = function(this) - -- If there is a transition active and this method got called, it - -- means we are animating from this menu to parent menu, and all - -- calls to this method should be relayed to the parent menu. - if menu.transition and menu.transition.to == 'parent' then - local target = menu.transition.target - tween_element_stop(target) - menu.transition = nil - target:open_selected_item() - return - end + tween_element(target, 0, 1, function(_, pos) + this:set_offset_x(round(to_offset * pos)) + this.opacity = 1 - pos + this:set_parent_opacity(config.menu_parent_opacity + + ((1 - config.menu_parent_opacity) * + pos)) + end, function() + menu.transition = nil + elements:add('menu', target) + update_proximities() + end) + end, + open_selected_item = function(this) + -- If there is a transition active and this method got called, it + -- means we are animating from this menu to parent menu, and all + -- calls to this method should be relayed to the parent menu. + if menu.transition and menu.transition.to == 'parent' then + local target = menu.transition.target + tween_element_stop(target) + menu.transition = nil + target:open_selected_item() + return + end - if this.selected_item then - local item = this.items[this.selected_item] - -- Is submenu - if item.items then - local opts = table_copy(opts) - opts.parent_menu = this - menu:open(item.items, this.open_item, opts) - else - menu:close(true) - this.open_item(item.value) - end - end - end, - close = function(this) - menu:close() - end, - on_global_mbtn_left_down = function(this) - if this.proximity_raw == 0 then - this.selected_item = this:get_item_index_below_cursor() - this:open_selected_item() - else - -- check if this is clicking on any parent menus - local parent_menu = this.parent_menu - repeat - if parent_menu then - if get_point_to_rectangle_proximity(cursor, parent_menu) == 0 then - this:back() - return - end - parent_menu = parent_menu.parent_menu - end - until parent_menu == nil + if this.selected_item then + local item = this.items[this.selected_item] + -- Is submenu + if item.items then + local opts = table_copy(opts) + opts.parent_menu = this + menu:open(item.items, this.open_item, opts) + else + menu:close(true) + this.open_item(item.value) + end + end + end, + close = function(this) menu:close() end, + on_global_mbtn_left_down = function(this) + if this.proximity_raw == 0 then + this.selected_item = this:get_item_index_below_cursor() + this:open_selected_item() + else + -- check if this is clicking on any parent menus + local parent_menu = this.parent_menu + repeat + if parent_menu then + if get_point_to_rectangle_proximity(cursor, parent_menu) == + 0 then + this:back() + return + end + parent_menu = parent_menu.parent_menu + end + until parent_menu == nil - menu:close() - end - end, - on_global_mouse_move = function(this) - if this.proximity_raw == 0 then - this.selected_item = this:get_item_index_below_cursor() - else - if this.selected_item then this.selected_item = nil end - end - request_render() - end, - on_wheel_up = function(this) - this.selected_item = nil - this:scroll_to(this.scroll_y - this.scroll_step) - -- Selects item below cursor - this:on_global_mouse_move() - request_render() - end, - on_wheel_down = function(this) - this.selected_item = nil - this:scroll_to(this.scroll_y + this.scroll_step) - -- Selects item below cursor - this:on_global_mouse_move() - request_render() - end, - on_pgup = function(this) - this.selected_item = nil - this:scroll_to(this.scroll_y - this.height) - end, - on_pgdwn = function(this) - this.selected_item = nil - this:scroll_to(this.scroll_y + this.height) - end, - on_home = function(this) - this.selected_item = nil - this:scroll_to(0) - end, - on_end = function(this) - this.selected_item = nil - this:scroll_to(this.scroll_height) - end, - render = render_menu, - })) + menu:close() + end + end, + on_global_mouse_move = function(this) + if this.proximity_raw == 0 then + this.selected_item = this:get_item_index_below_cursor() + else + if this.selected_item then + this.selected_item = nil + end + end + request_render() + end, + on_wheel_up = function(this) + this.selected_item = nil + this:scroll_to(this.scroll_y - this.scroll_step) + -- Selects item below cursor + this:on_global_mouse_move() + request_render() + end, + on_wheel_down = function(this) + this.selected_item = nil + this:scroll_to(this.scroll_y + this.scroll_step) + -- Selects item below cursor + this:on_global_mouse_move() + request_render() + end, + on_pgup = function(this) + this.selected_item = nil + this:scroll_to(this.scroll_y - this.height) + end, + on_pgdwn = function(this) + this.selected_item = nil + this:scroll_to(this.scroll_y + this.height) + end, + on_home = function(this) + this.selected_item = nil + this:scroll_to(0) + end, + on_end = function(this) + this.selected_item = nil + this:scroll_to(this.scroll_height) + end, + render = render_menu + })) - elements.menu:maybe('on_open') + elements.menu:maybe('on_open') end function Menu:add_key_binding(key, name, fn, flags) - menu.key_bindings[#menu.key_bindings + 1] = name - mp.add_forced_key_binding(key, name, fn, flags) + menu.key_bindings[#menu.key_bindings + 1] = name + mp.add_forced_key_binding(key, name, fn, flags) end function Menu:enable_key_bindings() - menu.key_bindings = {} - -- The `mp.set_key_bindings()` method would be easier here, but that - -- doesn't support 'repeatable' flag, so we are stuck with this monster. - menu:add_key_binding('up', 'menu-prev', self:create_action('prev'), 'repeatable') - menu:add_key_binding('down', 'menu-next', self:create_action('next'), 'repeatable') - menu:add_key_binding('left', 'menu-back', self:create_action('back')) - menu:add_key_binding('right', 'menu-select', self:create_action('open_selected_item')) + menu.key_bindings = {} + -- The `mp.set_key_bindings()` method would be easier here, but that + -- doesn't support 'repeatable' flag, so we are stuck with this monster. + menu:add_key_binding('up', 'menu-prev', self:create_action('prev'), + 'repeatable') + menu:add_key_binding('down', 'menu-next', self:create_action('next'), + 'repeatable') + menu:add_key_binding('left', 'menu-back', self:create_action('back')) + menu:add_key_binding('right', 'menu-select', + self:create_action('open_selected_item')) - if options.menu_wasd_navigation then - menu:add_key_binding('w', 'menu-prev-alt', self:create_action('prev'), 'repeatable') - menu:add_key_binding('a', 'menu-back-alt', self:create_action('back')) - menu:add_key_binding('s', 'menu-next-alt', self:create_action('next'), 'repeatable') - menu:add_key_binding('d', 'menu-select-alt', self:create_action('open_selected_item')) - end + if options.menu_wasd_navigation then + menu:add_key_binding('w', 'menu-prev-alt', self:create_action('prev'), + 'repeatable') + menu:add_key_binding('a', 'menu-back-alt', self:create_action('back')) + menu:add_key_binding('s', 'menu-next-alt', self:create_action('next'), + 'repeatable') + menu:add_key_binding('d', 'menu-select-alt', + self:create_action('open_selected_item')) + end - if options.menu_hjkl_navigation then - menu:add_key_binding('h', 'menu-back-alt2', self:create_action('back')) - menu:add_key_binding('j', 'menu-next-alt2', self:create_action('next'), 'repeatable') - menu:add_key_binding('k', 'menu-prev-alt2', self:create_action('prev'), 'repeatable') - menu:add_key_binding('l', 'menu-select-alt2', self:create_action('open_selected_item')) - end + if options.menu_hjkl_navigation then + menu:add_key_binding('h', 'menu-back-alt2', self:create_action('back')) + menu:add_key_binding('j', 'menu-next-alt2', self:create_action('next'), + 'repeatable') + menu:add_key_binding('k', 'menu-prev-alt2', self:create_action('prev'), + 'repeatable') + menu:add_key_binding('l', 'menu-select-alt2', + self:create_action('open_selected_item')) + end - menu:add_key_binding('mbtn_back', 'menu-back-alt3', self:create_action('back')) - menu:add_key_binding('bs', 'menu-back-alt4', self:create_action('back')) - menu:add_key_binding('enter', 'menu-select-alt3', self:create_action('open_selected_item')) - menu:add_key_binding('kp_enter', 'menu-select-alt4', self:create_action('open_selected_item')) - menu:add_key_binding('esc', 'menu-close', self:create_action('close')) - menu:add_key_binding('pgup', 'menu-page-up', self:create_action('on_pgup')) - menu:add_key_binding('pgdwn', 'menu-page-down', self:create_action('on_pgdwn')) - menu:add_key_binding('home', 'menu-home', self:create_action('on_home')) - menu:add_key_binding('end', 'menu-end', self:create_action('on_end')) + menu:add_key_binding('mbtn_back', 'menu-back-alt3', + self:create_action('back')) + menu:add_key_binding('bs', 'menu-back-alt4', self:create_action('back')) + menu:add_key_binding('enter', 'menu-select-alt3', + self:create_action('open_selected_item')) + menu:add_key_binding('kp_enter', 'menu-select-alt4', + self:create_action('open_selected_item')) + menu:add_key_binding('esc', 'menu-close', self:create_action('close')) + menu:add_key_binding('pgup', 'menu-page-up', self:create_action('on_pgup')) + menu:add_key_binding('pgdwn', 'menu-page-down', + self:create_action('on_pgdwn')) + menu:add_key_binding('home', 'menu-home', self:create_action('on_home')) + menu:add_key_binding('end', 'menu-end', self:create_action('on_end')) end function Menu:disable_key_bindings() - for _, name in ipairs(menu.key_bindings) do mp.remove_key_binding(name) end - menu.key_bindings = {} + for _, name in ipairs(menu.key_bindings) do mp.remove_key_binding(name) end + menu.key_bindings = {} end function Menu:create_action(name) - return function(...) - if elements.menu then elements.menu:maybe(name, ...) end - end + return function(...) + if elements.menu then elements.menu:maybe(name, ...) end + end end function Menu:close(immediate, callback) - if type(immediate) ~= 'boolean' then callback = immediate end + if type(immediate) ~= 'boolean' then callback = immediate end - if elements:has('menu') and not menu.is_closing then - function close() - elements.menu:maybe('on_close') - elements.menu:destroy() - elements:remove('menu') - menu.is_closing = false - update_proximities() - menu:disable_key_bindings() - call_me_maybe(callback) - end + if elements:has('menu') and not menu.is_closing then + function close() + elements.menu:maybe('on_close') + elements.menu:destroy() + elements:remove('menu') + menu.is_closing = false + update_proximities() + menu:disable_key_bindings() + call_me_maybe(callback) + end - menu.is_closing = true - elements.curtain:fadeout() + menu.is_closing = true + elements.curtain:fadeout() - if immediate then - close() - else - elements.menu:fadeout(close) - end - end + if immediate then + close() + else + elements.menu:fadeout(close) + end + end end -- ICONS @@ -1202,818 +1311,953 @@ Function has to return ass path coordinates to draw the icon centered at pox_x and pos_y of passed size. ]] local icons = {} -function icon(name, icon_x, icon_y, icon_size, shad_x, shad_y, shad_size, backdrop, opacity, clip) - local ass = assdraw.ass_new() - local icon_path = icons[name](icon_x, icon_y, icon_size) - local icon_color = options['color_'..backdrop..'_text'] - local shad_color = options['color_'..backdrop] - local use_border = (shad_x + shad_y) == 0 - local icon_border = use_border and shad_size or 0 +function icon(name, icon_x, icon_y, icon_size, shad_x, shad_y, shad_size, + backdrop, opacity, clip) + local ass = assdraw.ass_new() + local icon_path = icons[name](icon_x, icon_y, icon_size) + local icon_color = options['color_' .. backdrop .. '_text'] + local shad_color = options['color_' .. backdrop] + local use_border = (shad_x + shad_y) == 0 + local icon_border = use_border and shad_size or 0 - -- clip can't clip out shadows, a very annoying limitation I can't work - -- around without going back to ugly default ass shadows, but atm I actually - -- don't need clipping of icons with shadows, so I'm choosing to ignore this - if not clip then - clip = '' - end + -- clip can't clip out shadows, a very annoying limitation I can't work + -- around without going back to ugly default ass shadows, but atm I actually + -- don't need clipping of icons with shadows, so I'm choosing to ignore this + if not clip then clip = '' end - if not use_border then - ass:new_event() - ass:append('{\\blur0\\bord0\\shad0\\1c&H'..shad_color..'\\iclip('..ass.scale..', '..icon_path..')}') - ass:append(ass_opacity(opacity)) - ass:pos(shad_x + shad_size, shad_y + shad_size) - ass:draw_start() - ass:append(icon_path) - ass:draw_stop() - end + if not use_border then + ass:new_event() + ass:append('{\\blur0\\bord0\\shad0\\1c&H' .. shad_color .. '\\iclip(' .. + ass.scale .. ', ' .. icon_path .. ')}') + ass:append(ass_opacity(opacity)) + ass:pos(shad_x + shad_size, shad_y + shad_size) + ass:draw_start() + ass:append(icon_path) + ass:draw_stop() + end - ass:new_event() - ass:append('{\\blur0\\bord'..icon_border..'\\shad0\\1c&H'..icon_color..'\\3c&H'..shad_color..clip..'}') - ass:append(ass_opacity(opacity)) - ass:pos(0, 0) - ass:draw_start() - ass:append(icon_path) - ass:draw_stop() + ass:new_event() + ass:append( + '{\\blur0\\bord' .. icon_border .. '\\shad0\\1c&H' .. icon_color .. + '\\3c&H' .. shad_color .. clip .. '}') + ass:append(ass_opacity(opacity)) + ass:pos(0, 0) + ass:draw_start() + ass:append(icon_path) + ass:draw_stop() - return ass.text + return ass.text end function icons._volume(muted, pos_x, pos_y, size) - local ass = assdraw.ass_new() - local scale = size / 200 - function x(number) return pos_x + (number * scale) end - function y(number) return pos_y + (number * scale) end - ass:move_to(x(-85), y(-35)) - ass:line_to(x(-50), y(-35)) - ass:line_to(x(-5), y(-75)) - ass:line_to(x(-5), y(75)) - ass:line_to(x(-50), y(35)) - ass:line_to(x(-85), y(35)) - if muted then - ass:move_to(x(76), y(-35)) ass:line_to(x(50), y(-9)) ass:line_to(x(24), y(-35)) - ass:line_to(x(15), y(-26)) ass:line_to(x(41), y(0)) ass:line_to(x(15), y(26)) - ass:line_to(x(24), y(35)) ass:line_to(x(50), y(9)) ass:line_to(x(76), y(35)) - ass:line_to(x(85), y(26)) ass:line_to(x(59), y(0)) ass:line_to(x(85), y(-26)) - else - ass:move_to(x(20), y(-30)) ass:line_to(x(20), y(30)) - ass:line_to(x(35), y(30)) ass:line_to(x(35), y(-30)) + local ass = assdraw.ass_new() + local scale = size / 200 + function x(number) return pos_x + (number * scale) end + function y(number) return pos_y + (number * scale) end + ass:move_to(x(-85), y(-35)) + ass:line_to(x(-50), y(-35)) + ass:line_to(x(-5), y(-75)) + ass:line_to(x(-5), y(75)) + ass:line_to(x(-50), y(35)) + ass:line_to(x(-85), y(35)) + if muted then + ass:move_to(x(76), y(-35)) + ass:line_to(x(50), y(-9)) + ass:line_to(x(24), y(-35)) + ass:line_to(x(15), y(-26)) + ass:line_to(x(41), y(0)) + ass:line_to(x(15), y(26)) + ass:line_to(x(24), y(35)) + ass:line_to(x(50), y(9)) + ass:line_to(x(76), y(35)) + ass:line_to(x(85), y(26)) + ass:line_to(x(59), y(0)) + ass:line_to(x(85), y(-26)) + else + ass:move_to(x(20), y(-30)) + ass:line_to(x(20), y(30)) + ass:line_to(x(35), y(30)) + ass:line_to(x(35), y(-30)) - ass:move_to(x(55), y(-60)) ass:line_to(x(55), y(60)) - ass:line_to(x(70), y(60)) ass:line_to(x(70), y(-60)) - end - return ass.text + ass:move_to(x(55), y(-60)) + ass:line_to(x(55), y(60)) + ass:line_to(x(70), y(60)) + ass:line_to(x(70), y(-60)) + end + return ass.text +end +function icons.volume(pos_x, pos_y, size) + return icons._volume(false, pos_x, pos_y, size) +end +function icons.volume_muted(pos_x, pos_y, size) + return icons._volume(true, pos_x, pos_y, size) end -function icons.volume(pos_x, pos_y, size) return icons._volume(false, pos_x, pos_y, size) end -function icons.volume_muted(pos_x, pos_y, size) return icons._volume(true, pos_x, pos_y, size) end function icons.arrow_right(pos_x, pos_y, size) - local ass = assdraw.ass_new() - local scale = size / 200 - function x(number) return pos_x + (number * scale) end - function y(number) return pos_y + (number * scale) end - ass:move_to(x(-22), y(-80)) - ass:line_to(x(-45), y(-57)) - ass:line_to(x(12), y(0)) - ass:line_to(x(-45), y(57)) - ass:line_to(x(-22), y(80)) - ass:line_to(x(58), y(0)) - return ass.text + local ass = assdraw.ass_new() + local scale = size / 200 + function x(number) return pos_x + (number * scale) end + function y(number) return pos_y + (number * scale) end + ass:move_to(x(-22), y(-80)) + ass:line_to(x(-45), y(-57)) + ass:line_to(x(12), y(0)) + ass:line_to(x(-45), y(57)) + ass:line_to(x(-22), y(80)) + ass:line_to(x(58), y(0)) + return ass.text end -- STATE UPDATES function update_display_dimensions() - local o = mp.get_property_native('osd-dimensions') - display.width = o.w - display.height = o.h - display.aspect = o.aspect + local o = mp.get_property_native('osd-dimensions') + display.width = o.w + display.height = o.h + display.aspect = o.aspect - -- Tell elements about this - for _, element in elements:ipairs() do - if element.on_display_resize ~= nil then - element.on_display_resize(element) - end - end + -- Tell elements about this + for _, element in elements:ipairs() do + if element.on_display_resize ~= nil then + element.on_display_resize(element) + end + end end function update_element_cursor_proximity(element) - if cursor.hidden then - element.proximity_raw = infinity - element.proximity = 0 - else - local range = options.proximity_out - options.proximity_in - element.proximity_raw = get_point_to_rectangle_proximity(cursor, element) - element.proximity = menu:is_open() and 0 or 1 - (math.min(math.max(element.proximity_raw - options.proximity_in, 0), range) / range) - end + if cursor.hidden then + element.proximity_raw = infinity + element.proximity = 0 + else + local range = options.proximity_out - options.proximity_in + element.proximity_raw = + get_point_to_rectangle_proximity(cursor, element) + element.proximity = menu:is_open() and 0 or 1 - + (math.min( + math.max( + element.proximity_raw - + options.proximity_in, 0), range) / + range) + end end function update_proximities() - local capture_mouse_buttons = false - local capture_wheel = false - local menu_only = menu:is_open() - local mouse_left_elements = {} - local mouse_entered_elements = {} + local capture_mouse_buttons = false + local capture_wheel = false + local menu_only = menu:is_open() + local mouse_left_elements = {} + local mouse_entered_elements = {} - -- Calculates proximities and opacities for defined elements - for _, element in elements:ipairs() do - local previous_proximity_raw = element.proximity_raw + -- Calculates proximities and opacities for defined elements + for _, element in elements:ipairs() do + local previous_proximity_raw = element.proximity_raw - -- If menu is open, all other elements have to be disabled - if menu_only then - if element.name == 'menu' then - capture_mouse_buttons = true - capture_wheel = true - update_element_cursor_proximity(element) - else - element.proximity_raw = infinity - element.proximity = 0 - end - else - update_element_cursor_proximity(element) - end + -- If menu is open, all other elements have to be disabled + if menu_only then + if element.name == 'menu' then + capture_mouse_buttons = true + capture_wheel = true + update_element_cursor_proximity(element) + else + element.proximity_raw = infinity + element.proximity = 0 + end + else + update_element_cursor_proximity(element) + end - if element.proximity_raw == 0 then - -- Mouse is over element - if element.captures and element.captures.mouse_buttons then capture_mouse_buttons = true end - if element.captures and element.captures.wheel then capture_wheel = true end + if element.proximity_raw == 0 then + -- Mouse is over element + if element.captures and element.captures.mouse_buttons then + capture_mouse_buttons = true + end + if element.captures and element.captures.wheel then + capture_wheel = true + end - -- Mouse entered element area - if previous_proximity_raw ~= 0 then - mouse_entered_elements[#mouse_entered_elements + 1] = element - end - else - -- Mouse left element area - if previous_proximity_raw == 0 then - mouse_left_elements[#mouse_left_elements + 1] = element - end - end - end + -- Mouse entered element area + if previous_proximity_raw ~= 0 then + mouse_entered_elements[#mouse_entered_elements + 1] = element + end + else + -- Mouse left element area + if previous_proximity_raw == 0 then + mouse_left_elements[#mouse_left_elements + 1] = element + end + end + end - -- Enable key group captures elements request. - if capture_mouse_buttons then - forced_key_bindings.mouse_buttons:enable() - else - forced_key_bindings.mouse_buttons:disable() - end - if capture_wheel then - forced_key_bindings.wheel:enable() - else - forced_key_bindings.wheel:disable() - end + -- Enable key group captures elements request. + if capture_mouse_buttons then + forced_key_bindings.mouse_buttons:enable() + else + forced_key_bindings.mouse_buttons:disable() + end + if capture_wheel then + forced_key_bindings.wheel:enable() + else + forced_key_bindings.wheel:disable() + end - -- Trigger `mouse_leave` and `mouse_enter` events - for _, element in ipairs(mouse_left_elements) do element:trigger('mouse_leave') end - for _, element in ipairs(mouse_entered_elements) do element:trigger('mouse_enter') end + -- Trigger `mouse_leave` and `mouse_enter` events + for _, element in ipairs(mouse_left_elements) do + element:trigger('mouse_leave') + end + for _, element in ipairs(mouse_entered_elements) do + element:trigger('mouse_enter') + end end -- ELEMENT RENDERERS function render_timeline(this) - if this.size_max == 0 or state.duration == nil or state.position == nil then return end + if this.size_max == 0 or state.duration == nil or state.position == nil then + return + end - local size_min = this:get_effective_size_min() - local size = this:get_effective_size() + local size_min = this:get_effective_size_min() + local size = this:get_effective_size() - if size < 1 then return end + if size < 1 then return end - local ass = assdraw.ass_new() + local ass = assdraw.ass_new() - -- Text opacity rapidly drops to 0 just before it starts overflowing, or before it reaches timeline.size_min - local hide_text_below = math.max(this.font_size * 0.7, size_min * 2) - local hide_text_ramp = hide_text_below / 2 - local text_opacity = math.max(math.min(size - hide_text_below, hide_text_ramp), 0) / hide_text_ramp + -- Text opacity rapidly drops to 0 just before it starts overflowing, or before it reaches timeline.size_min + local hide_text_below = math.max(this.font_size * 0.7, size_min * 2) + local hide_text_ramp = hide_text_below / 2 + local text_opacity = math.max(math.min(size - hide_text_below, + hide_text_ramp), 0) / hide_text_ramp - local spacing = math.max(math.floor((this.size_max - this.font_size) / 2.5), 4) - local progress = state.position / state.duration + local spacing = math.max(math.floor((this.size_max - this.font_size) / 2.5), + 4) + local progress = state.position / state.duration - -- Background bar coordinates - local bax = 0 - local bay = display.height - size - this.bottom_border - this.top_border - local bbx = display.width - local bby = display.height + -- Background bar coordinates + local bax = 0 + local bay = display.height - size - this.bottom_border - this.top_border + local bbx = display.width + local bby = display.height - -- Foreground bar coordinates - local fax = bax - local fay = bay + this.top_border - local fbx = bbx * progress - local fby = bby - this.bottom_border - local foreground_size = bby - bay - local foreground_coordinates = fax..','..fay..','..fbx..','..fby -- for clipping + -- Foreground bar coordinates + local fax = bax + local fay = bay + this.top_border + local fbx = bbx * progress + local fby = bby - this.bottom_border + local foreground_size = bby - bay + local foreground_coordinates = fax .. ',' .. fay .. ',' .. fbx .. ',' .. fby -- for clipping - -- Background - ass:new_event() - ass:append('{\\blur0\\bord0\\1c&H'..options.color_background..'\\iclip('..foreground_coordinates..')}') - ass:append(ass_opacity(math.max(options.timeline_opacity - 0.1, 0))) - ass:pos(0, 0) - ass:draw_start() - ass:rect_cw(bax, bay, bbx, bby) - ass:draw_stop() + -- Background + ass:new_event() + ass:append( + '{\\blur0\\bord0\\1c&H' .. options.color_background .. '\\iclip(' .. + foreground_coordinates .. ')}') + ass:append(ass_opacity(math.max(options.timeline_opacity - 0.1, 0))) + ass:pos(0, 0) + ass:draw_start() + ass:rect_cw(bax, bay, bbx, bby) + ass:draw_stop() - -- Foreground - ass:new_event() - ass:append('{\\blur0\\bord0\\1c&H'..options.color_foreground..'}') - ass:append(ass_opacity(options.timeline_opacity)) - ass:pos(0, 0) - ass:draw_start() - ass:rect_cw(fax, fay, fbx, fby) - ass:draw_stop() + -- Foreground + ass:new_event() + ass:append('{\\blur0\\bord0\\1c&H' .. options.color_foreground .. '}') + ass:append(ass_opacity(options.timeline_opacity)) + ass:pos(0, 0) + ass:draw_start() + ass:rect_cw(fax, fay, fbx, fby) + ass:draw_stop() - -- Seekable ranges - if options.timeline_cached_ranges and state.cached_ranges then - local range_height = math.max(foreground_size / 8, size_min) - local range_ay = fby - range_height - for _, range in ipairs(state.cached_ranges) do - ass:new_event() - ass:append('{\\blur0\\bord0\\1c&H'..options.timeline_cached_ranges.color..'}') - ass:append(ass_opacity(options.timeline_cached_ranges.opacity)) - ass:pos(0, 0) - ass:draw_start() - ass:rect_cw( - bbx * (range['start'] / state.duration), range_ay, - bbx * (range['end'] / state.duration), range_ay + range_height - ) - ass:draw_stop() - end - end + -- Seekable ranges + if options.timeline_cached_ranges and state.cached_ranges then + local range_height = math.max(foreground_size / 8, size_min) + local range_ay = fby - range_height + for _, range in ipairs(state.cached_ranges) do + ass:new_event() + ass:append('{\\blur0\\bord0\\1c&H' .. + options.timeline_cached_ranges.color .. '}') + ass:append(ass_opacity(options.timeline_cached_ranges.opacity)) + ass:pos(0, 0) + ass:draw_start() + ass:rect_cw(bbx * (range['start'] / state.duration), range_ay, + bbx * (range['end'] / state.duration), + range_ay + range_height) + ass:draw_stop() + end + end - -- Custom ranges - if state.chapter_ranges ~= nil then - for i, chapter_range in ipairs(state.chapter_ranges) do - for i, range in ipairs(chapter_range.ranges) do - local rax = display.width * (range['start'].time / state.duration) - local rbx = display.width * (range['end'].time / state.duration) - ass:new_event() - ass:append('{\\blur0\\bord0\\1c&H'..chapter_range.color..'}') - ass:append(ass_opacity(chapter_range.opacity)) - ass:pos(0, 0) - ass:draw_start() - -- for 1px chapter size, use the whole size of the bar including padding - if size <= 1 then - ass:rect_cw(rax, bay, rbx, bby) - else - ass:rect_cw(rax, fay, rbx, fby) - end - ass:draw_stop() - end - end - end + -- Custom ranges + if state.chapter_ranges ~= nil then + for i, chapter_range in ipairs(state.chapter_ranges) do + for i, range in ipairs(chapter_range.ranges) do + local rax = display.width * + (range['start'].time / state.duration) + local rbx = display.width * (range['end'].time / state.duration) + ass:new_event() + ass:append('{\\blur0\\bord0\\1c&H' .. chapter_range.color .. '}') + ass:append(ass_opacity(chapter_range.opacity)) + ass:pos(0, 0) + ass:draw_start() + -- for 1px chapter size, use the whole size of the bar including padding + if size <= 1 then + ass:rect_cw(rax, bay, rbx, bby) + else + ass:rect_cw(rax, fay, rbx, fby) + end + ass:draw_stop() + end + end + end - -- Chapters - if options.chapters ~= 'none' and state.chapters ~= nil and #state.chapters > 0 then - local half_size = size / 2 - local dots = false - local chapter_size, chapter_y - if options.chapters == 'dots' then - dots = true - chapter_size = math.min(6, (foreground_size / 2) + 2) - chapter_y = math.min(fay + chapter_size, fay + half_size) - elseif options.chapters == 'lines' then - chapter_size = size - chapter_y = fay + (chapter_size / 2) - elseif options.chapters == 'lines-top' then - chapter_size = math.min(this.size_max / 3.5, size) - chapter_y = fay + (chapter_size / 2) - elseif options.chapters == 'lines-bottom' then - chapter_size = math.min(this.size_max / 3.5, size) - chapter_y = fay + size - (chapter_size / 2) - end + -- Chapters + if options.chapters ~= 'none' and state.chapters ~= nil and #state.chapters > + 0 then + local half_size = size / 2 + local dots = false + local chapter_size, chapter_y + if options.chapters == 'dots' then + dots = true + chapter_size = math.min(6, (foreground_size / 2) + 2) + chapter_y = math.min(fay + chapter_size, fay + half_size) + elseif options.chapters == 'lines' then + chapter_size = size + chapter_y = fay + (chapter_size / 2) + elseif options.chapters == 'lines-top' then + chapter_size = math.min(this.size_max / 3.5, size) + chapter_y = fay + (chapter_size / 2) + elseif options.chapters == 'lines-bottom' then + chapter_size = math.min(this.size_max / 3.5, size) + chapter_y = fay + size - (chapter_size / 2) + end - if chapter_size ~= nil then - -- for 1px chapter size, use the whole size of the bar including padding - chapter_size = size <= 1 and foreground_size or chapter_size - local chapter_half_size = chapter_size / 2 + if chapter_size ~= nil then + -- for 1px chapter size, use the whole size of the bar including padding + chapter_size = size <= 1 and foreground_size or chapter_size + local chapter_half_size = chapter_size / 2 - for i, chapter in ipairs(state.chapters) do - local chapter_x = display.width * (chapter.time / state.duration) - local color = chapter_x > fbx and options.color_foreground or options.color_background + for i, chapter in ipairs(state.chapters) do + local chapter_x = display.width * + (chapter.time / state.duration) + local color = chapter_x > fbx and options.color_foreground or + options.color_background - ass:new_event() - ass:append('{\\blur0\\bord0\\1c&H'..color..'}') - ass:append(ass_opacity(options.chapters_opacity)) - ass:pos(0, 0) - ass:draw_start() + ass:new_event() + ass:append('{\\blur0\\bord0\\1c&H' .. color .. '}') + ass:append(ass_opacity(options.chapters_opacity)) + ass:pos(0, 0) + ass:draw_start() - if dots then - local bezier_stretch = chapter_size * 0.67 - ass:move_to(chapter_x - chapter_half_size, chapter_y) - ass:bezier_curve( - chapter_x - chapter_half_size, chapter_y - bezier_stretch, - chapter_x + chapter_half_size, chapter_y - bezier_stretch, - chapter_x + chapter_half_size, chapter_y - ) - ass:bezier_curve( - chapter_x + chapter_half_size, chapter_y + bezier_stretch, - chapter_x - chapter_half_size, chapter_y + bezier_stretch, - chapter_x - chapter_half_size, chapter_y - ) - else - ass:rect_cw(chapter_x, chapter_y - chapter_half_size, chapter_x + 1, chapter_y + chapter_half_size) - end + if dots then + local bezier_stretch = chapter_size * 0.67 + ass:move_to(chapter_x - chapter_half_size, chapter_y) + ass:bezier_curve(chapter_x - chapter_half_size, + chapter_y - bezier_stretch, + chapter_x + chapter_half_size, + chapter_y - bezier_stretch, + chapter_x + chapter_half_size, chapter_y) + ass:bezier_curve(chapter_x + chapter_half_size, + chapter_y + bezier_stretch, + chapter_x - chapter_half_size, + chapter_y + bezier_stretch, + chapter_x - chapter_half_size, chapter_y) + else + ass:rect_cw(chapter_x, chapter_y - chapter_half_size, + chapter_x + 1, chapter_y + chapter_half_size) + end - ass:draw_stop() - end - end - end + ass:draw_stop() + end + end + end - if text_opacity > 0 then - -- Elapsed time - if state.elapsed_seconds then - ass:new_event() - ass:append('{\\blur0\\bord0\\shad0\\1c&H'..options.color_foreground_text..'\\fn'..config.font..'\\fs'..this.font_size..bold_tag..'\\clip('..foreground_coordinates..')') - ass:append(ass_opacity(math.min(options.timeline_opacity + 0.1, 1), text_opacity)) - ass:pos(spacing, fay + (size / 2)) - ass:an(4) - ass:append(state.elapsed_time) - ass:new_event() - ass:append('{\\blur0\\bord0\\shad1\\1c&H'..options.color_background_text..'\\4c&H'..options.color_background..'\\fn'..config.font..'\\fs'..this.font_size..bold_tag..'\\iclip('..foreground_coordinates..')') - ass:append(ass_opacity(math.min(options.timeline_opacity + 0.1, 1), text_opacity)) - ass:pos(spacing, fay + (size / 2)) - ass:an(4) - ass:append(state.elapsed_time) - end + if text_opacity > 0 then + -- Elapsed time + if state.elapsed_seconds then + ass:new_event() + ass:append('{\\blur0\\bord0\\shad0\\1c&H' .. + options.color_foreground_text .. '\\fn' .. + config.font .. '\\fs' .. this.font_size .. bold_tag .. + '\\clip(' .. foreground_coordinates .. ')') + ass:append(ass_opacity(math.min(options.timeline_opacity + 0.1, 1), + text_opacity)) + ass:pos(spacing, fay + (size / 2)) + ass:an(4) + ass:append(state.elapsed_time) + ass:new_event() + ass:append('{\\blur0\\bord0\\shad1\\1c&H' .. + options.color_background_text .. '\\4c&H' .. + options.color_background .. '\\fn' .. config.font .. + '\\fs' .. this.font_size .. bold_tag .. '\\iclip(' .. + foreground_coordinates .. ')') + ass:append(ass_opacity(math.min(options.timeline_opacity + 0.1, 1), + text_opacity)) + ass:pos(spacing, fay + (size / 2)) + ass:an(4) + ass:append(state.elapsed_time) + end - -- Remaining time - if state.remaining_seconds then - ass:new_event() - ass:append('{\\blur0\\bord0\\shad0\\1c&H'..options.color_foreground_text..'\\fn'..config.font..'\\fs'..this.font_size..bold_tag..'\\clip('..foreground_coordinates..')') - ass:append(ass_opacity(math.min(options.timeline_opacity + 0.1, 1), text_opacity)) - ass:pos(display.width - spacing, fay + (size / 2)) - ass:an(6) - ass:append('-'..state.remaining_time) - ass:new_event() - ass:append('{\\blur0\\bord0\\shad1\\1c&H'..options.color_background_text..'\\4c&H'..options.color_background..'\\fn'..config.font..'\\fs'..this.font_size..bold_tag..'\\iclip('..foreground_coordinates..')') - ass:append(ass_opacity(math.min(options.timeline_opacity + 0.1, 1), text_opacity)) - ass:pos(display.width - spacing, fay + (size / 2)) - ass:an(6) - ass:append('-'..state.remaining_time) - end - end + -- Remaining time + if state.remaining_seconds then + ass:new_event() + ass:append('{\\blur0\\bord0\\shad0\\1c&H' .. + options.color_foreground_text .. '\\fn' .. + config.font .. '\\fs' .. this.font_size .. bold_tag .. + '\\clip(' .. foreground_coordinates .. ')') + ass:append(ass_opacity(math.min(options.timeline_opacity + 0.1, 1), + text_opacity)) + ass:pos(display.width - spacing, fay + (size / 2)) + ass:an(6) + ass:append('-' .. state.remaining_time) + ass:new_event() + ass:append('{\\blur0\\bord0\\shad1\\1c&H' .. + options.color_background_text .. '\\4c&H' .. + options.color_background .. '\\fn' .. config.font .. + '\\fs' .. this.font_size .. bold_tag .. '\\iclip(' .. + foreground_coordinates .. ')') + ass:append(ass_opacity(math.min(options.timeline_opacity + 0.1, 1), + text_opacity)) + ass:pos(display.width - spacing, fay + (size / 2)) + ass:an(6) + ass:append('-' .. state.remaining_time) + end + end - if (this.proximity_raw == 0 or this.pressed) and not (elements.speed and elements.speed.dragging) then - -- Hovered time - local hovered_seconds = state.duration * (cursor.x / display.width) - local box_half_width_guesstimate = (this.font_size * 4.2) / 2 - ass:new_event() - ass:append('{\\blur0\\bord1\\shad0\\1c&H'..options.color_background_text..'\\3c&H'..options.color_background..'\\fn'..config.font..'\\fs'..this.font_size..bold_tag..'') - ass:append(ass_opacity(math.min(options.timeline_opacity + 0.1, 1))) - ass:pos(math.min(math.max(cursor.x, box_half_width_guesstimate), display.width - box_half_width_guesstimate), fay) - ass:an(2) - ass:append(mp.format_time(hovered_seconds)) + if (this.proximity_raw == 0 or this.pressed) and + not (elements.speed and elements.speed.dragging) then + -- Hovered time + local hovered_seconds = state.duration * (cursor.x / display.width) + local box_half_width_guesstimate = (this.font_size * 4.2) / 2 + ass:new_event() + ass:append('{\\blur0\\bord1\\shad0\\1c&H' .. + options.color_background_text .. '\\3c&H' .. + options.color_background .. '\\fn' .. config.font .. + '\\fs' .. this.font_size .. bold_tag .. '') + ass:append(ass_opacity(math.min(options.timeline_opacity + 0.1, 1))) + ass:pos(math.min(math.max(cursor.x, box_half_width_guesstimate), + display.width - box_half_width_guesstimate), fay) + ass:an(2) + ass:append(mp.format_time(hovered_seconds)) - -- Cursor line - ass:new_event() - ass:append('{\\blur0\\bord0\\xshad-1\\yshad0\\1c&H'..options.color_foreground..'\\4c&H'..options.color_background..'}') - ass:append(ass_opacity(0.2)) - ass:pos(0, 0) - ass:draw_start() - ass:rect_cw(cursor.x, fay, cursor.x + 1, fby) - ass:draw_stop() - end + -- Cursor line + ass:new_event() + ass:append('{\\blur0\\bord0\\xshad-1\\yshad0\\1c&H' .. + options.color_foreground .. '\\4c&H' .. + options.color_background .. '}') + ass:append(ass_opacity(0.2)) + ass:pos(0, 0) + ass:draw_start() + ass:rect_cw(cursor.x, fay, cursor.x + 1, fby) + ass:draw_stop() + end - return ass + return ass end function render_top_bar(this) - local opacity = this:get_effective_proximity() + local opacity = this:get_effective_proximity() - if not this.enabled or opacity == 0 then return end + if not this.enabled or opacity == 0 then return end - local ass = assdraw.ass_new() + local ass = assdraw.ass_new() - if options.top_bar_controls then - -- Close button - local close = elements.window_controls_close - if close.proximity_raw == 0 then - -- Background on hover - ass:new_event() - ass:append('{\\blur0\\bord0\\1c&H2311e8}') - ass:append(ass_opacity(this.button_opacity, opacity)) - ass:pos(0, 0) - ass:draw_start() - ass:rect_cw(close.ax, close.ay, close.bx, close.by) - ass:draw_stop() - end - ass:new_event() - ass:append('{\\blur0\\bord1\\shad1\\3c&HFFFFFF\\4c&H000000}') - ass:append(ass_opacity(this.button_opacity, opacity)) - ass:pos(close.ax + (this.button_width / 2), (this.size / 2)) - ass:draw_start() - ass:move_to(-this.icon_size, this.icon_size) - ass:line_to(this.icon_size, -this.icon_size) - ass:move_to(-this.icon_size, -this.icon_size) - ass:line_to(this.icon_size, this.icon_size) - ass:draw_stop() + if options.top_bar_controls then + -- Close button + local close = elements.window_controls_close + if close.proximity_raw == 0 then + -- Background on hover + ass:new_event() + ass:append('{\\blur0\\bord0\\1c&H2311e8}') + ass:append(ass_opacity(this.button_opacity, opacity)) + ass:pos(0, 0) + ass:draw_start() + ass:rect_cw(close.ax, close.ay, close.bx, close.by) + ass:draw_stop() + end + ass:new_event() + ass:append('{\\blur0\\bord1\\shad1\\3c&HFFFFFF\\4c&H000000}') + ass:append(ass_opacity(this.button_opacity, opacity)) + ass:pos(close.ax + (this.button_width / 2), (this.size / 2)) + ass:draw_start() + ass:move_to(-this.icon_size, this.icon_size) + ass:line_to(this.icon_size, -this.icon_size) + ass:move_to(-this.icon_size, -this.icon_size) + ass:line_to(this.icon_size, this.icon_size) + ass:draw_stop() - -- Maximize button - local maximize = elements.window_controls_maximize - if maximize.proximity_raw == 0 then - -- Background on hover - ass:new_event() - ass:append('{\\blur0\\bord0\\1c&H222222}') - ass:append(ass_opacity(this.button_opacity, opacity)) - ass:pos(0, 0) - ass:draw_start() - ass:rect_cw(maximize.ax, maximize.ay, maximize.bx, maximize.by) - ass:draw_stop() - end - ass:new_event() - ass:append('{\\blur0\\bord2\\shad0\\1c\\3c&H000000}') - ass:append(ass_opacity({[3] = this.button_opacity}, opacity)) - ass:pos(maximize.ax + (this.button_width / 2), (this.size / 2)) - ass:draw_start() - ass:rect_cw(-this.icon_size + 1, -this.icon_size + 1, this.icon_size + 1, this.icon_size + 1) - ass:draw_stop() - ass:new_event() - ass:append('{\\blur0\\bord2\\shad0\\1c\\3c&HFFFFFF}') - ass:append(ass_opacity({[3] = this.button_opacity}, opacity)) - ass:pos(maximize.ax + (this.button_width / 2), (this.size / 2)) - ass:draw_start() - ass:rect_cw(-this.icon_size, -this.icon_size, this.icon_size, this.icon_size) - ass:draw_stop() + -- Maximize button + local maximize = elements.window_controls_maximize + if maximize.proximity_raw == 0 then + -- Background on hover + ass:new_event() + ass:append('{\\blur0\\bord0\\1c&H222222}') + ass:append(ass_opacity(this.button_opacity, opacity)) + ass:pos(0, 0) + ass:draw_start() + ass:rect_cw(maximize.ax, maximize.ay, maximize.bx, maximize.by) + ass:draw_stop() + end + ass:new_event() + ass:append('{\\blur0\\bord2\\shad0\\1c\\3c&H000000}') + ass:append(ass_opacity({[3] = this.button_opacity}, opacity)) + ass:pos(maximize.ax + (this.button_width / 2), (this.size / 2)) + ass:draw_start() + ass:rect_cw(-this.icon_size + 1, -this.icon_size + 1, + this.icon_size + 1, this.icon_size + 1) + ass:draw_stop() + ass:new_event() + ass:append('{\\blur0\\bord2\\shad0\\1c\\3c&HFFFFFF}') + ass:append(ass_opacity({[3] = this.button_opacity}, opacity)) + ass:pos(maximize.ax + (this.button_width / 2), (this.size / 2)) + ass:draw_start() + ass:rect_cw(-this.icon_size, -this.icon_size, this.icon_size, + this.icon_size) + ass:draw_stop() - -- Minimize button - local minimize = elements.window_controls_minimize - if minimize.proximity_raw == 0 then - -- Background on hover - ass:new_event() - ass:append('{\\blur0\\bord0\\1c&H222222}') - ass:append(ass_opacity(this.button_opacity, opacity)) - ass:pos(0, 0) - ass:draw_start() - ass:rect_cw(minimize.ax, minimize.ay, minimize.bx, minimize.by) - ass:draw_stop() - end - ass:new_event() - ass:append('{\\blur0\\bord1\\shad1\\3c&HFFFFFF\\4c&H000000}') - ass:append(ass_opacity(this.button_opacity, opacity)) - ass:append('{\\1a&HFF&}') - ass:pos(minimize.ax + (this.button_width / 2), (this.size / 2)) - ass:draw_start() - ass:move_to(-this.icon_size, 0) - ass:line_to(this.icon_size, 0) - ass:draw_stop() - end + -- Minimize button + local minimize = elements.window_controls_minimize + if minimize.proximity_raw == 0 then + -- Background on hover + ass:new_event() + ass:append('{\\blur0\\bord0\\1c&H222222}') + ass:append(ass_opacity(this.button_opacity, opacity)) + ass:pos(0, 0) + ass:draw_start() + ass:rect_cw(minimize.ax, minimize.ay, minimize.bx, minimize.by) + ass:draw_stop() + end + ass:new_event() + ass:append('{\\blur0\\bord1\\shad1\\3c&HFFFFFF\\4c&H000000}') + ass:append(ass_opacity(this.button_opacity, opacity)) + ass:append('{\\1a&HFF&}') + ass:pos(minimize.ax + (this.button_width / 2), (this.size / 2)) + ass:draw_start() + ass:move_to(-this.icon_size, 0) + ass:line_to(this.icon_size, 0) + ass:draw_stop() + end - -- Window title - if options.top_bar_title and state.media_title then - local clip_coordinates = '0,0,'..(this.title_bx - this.spacing)..','..this.size + -- Window title + if options.top_bar_title and state.media_title then + local clip_coordinates = + '0,0,' .. (this.title_bx - this.spacing) .. ',' .. this.size - ass:new_event() - ass:append('{\\q2\\blur0\\bord1\\shad0\\1c&HFFFFFF\\3c&H000000\\fn'..config.font..'\\fs'..this.font_size..bold_tag..'\\clip('..clip_coordinates..')') - ass:append(ass_opacity(1, opacity)) - ass:pos(0 + this.spacing, this.size / 2) - ass:an(4) - ass:append(state.media_title) - end + ass:new_event() + ass:append('{\\q2\\blur0\\bord1\\shad0\\1c&HFFFFFF\\3c&H000000\\fn' .. + config.font .. '\\fs' .. this.font_size .. bold_tag .. + '\\clip(' .. clip_coordinates .. ')') + ass:append(ass_opacity(1, opacity)) + ass:pos(0 + this.spacing, this.size / 2) + ass:an(4) + ass:append(state.media_title) + end - return ass + return ass end function render_volume(this) - local slider = elements.volume_slider - local opacity = this:get_effective_proximity() + local slider = elements.volume_slider + local opacity = this:get_effective_proximity() - if this.width == 0 or opacity == 0 then return end + if this.width == 0 or opacity == 0 then return end - local ass = assdraw.ass_new() + local ass = assdraw.ass_new() - if slider.height > 0 then - -- Background bar coordinates - local bax = slider.ax - local bay = slider.ay - local bbx = slider.bx - local bby = slider.by + if slider.height > 0 then + -- Background bar coordinates + local bax = slider.ax + local bay = slider.ay + local bbx = slider.bx + local bby = slider.by - -- Foreground bar coordinates - local height_without_border = slider.height - (options.volume_border * 2) - local fax = slider.ax + options.volume_border - local fay = slider.ay + (height_without_border * (1 - math.min(state.volume / state.volume_max, 1))) + options.volume_border - local fbx = slider.bx - options.volume_border - local fby = slider.by - options.volume_border + -- Foreground bar coordinates + local height_without_border = slider.height - + (options.volume_border * 2) + local fax = slider.ax + options.volume_border + local fay = slider.ay + + (height_without_border * + (1 - math.min(state.volume / state.volume_max, 1))) + + options.volume_border + local fbx = slider.bx - options.volume_border + local fby = slider.by - options.volume_border - -- Path to draw a foreground bar with a 100% volume indicator, already - -- clipped by volume level. Can't just clip it with rectangle, as it itself - -- also needs to be used as a path to clip the background bar and volume - -- number. - local fpath = assdraw.ass_new() - fpath:move_to(fbx, fby) - fpath:line_to(fax, fby) - local nudge_bottom_y = slider.nudge_y + slider.nudge_size - if fay <= nudge_bottom_y and slider.draw_nudge then - fpath:line_to(fax, math.min(nudge_bottom_y)) - if fay <= slider.nudge_y then - fpath:line_to((fax + slider.nudge_size), slider.nudge_y) - local nudge_top_y = slider.nudge_y - slider.nudge_size - if fay <= nudge_top_y then - fpath:line_to(fax, nudge_top_y) - fpath:line_to(fax, fay) - fpath:line_to(fbx, fay) - fpath:line_to(fbx, nudge_top_y) - else - local triangle_side = fay - nudge_top_y - fpath:line_to((fax + triangle_side), fay) - fpath:line_to((fbx - triangle_side), fay) - end - fpath:line_to((fbx - slider.nudge_size), slider.nudge_y) - else - local triangle_side = nudge_bottom_y - fay - fpath:line_to((fax + triangle_side), fay) - fpath:line_to((fbx - triangle_side), fay) - end - fpath:line_to(fbx, nudge_bottom_y) - else - fpath:line_to(fax, fay) - fpath:line_to(fbx, fay) - end - fpath:line_to(fbx, fby) + -- Path to draw a foreground bar with a 100% volume indicator, already + -- clipped by volume level. Can't just clip it with rectangle, as it itself + -- also needs to be used as a path to clip the background bar and volume + -- number. + local fpath = assdraw.ass_new() + fpath:move_to(fbx, fby) + fpath:line_to(fax, fby) + local nudge_bottom_y = slider.nudge_y + slider.nudge_size + if fay <= nudge_bottom_y and slider.draw_nudge then + fpath:line_to(fax, math.min(nudge_bottom_y)) + if fay <= slider.nudge_y then + fpath:line_to((fax + slider.nudge_size), slider.nudge_y) + local nudge_top_y = slider.nudge_y - slider.nudge_size + if fay <= nudge_top_y then + fpath:line_to(fax, nudge_top_y) + fpath:line_to(fax, fay) + fpath:line_to(fbx, fay) + fpath:line_to(fbx, nudge_top_y) + else + local triangle_side = fay - nudge_top_y + fpath:line_to((fax + triangle_side), fay) + fpath:line_to((fbx - triangle_side), fay) + end + fpath:line_to((fbx - slider.nudge_size), slider.nudge_y) + else + local triangle_side = nudge_bottom_y - fay + fpath:line_to((fax + triangle_side), fay) + fpath:line_to((fbx - triangle_side), fay) + end + fpath:line_to(fbx, nudge_bottom_y) + else + fpath:line_to(fax, fay) + fpath:line_to(fbx, fay) + end + fpath:line_to(fbx, fby) - -- Background - ass:new_event() - ass:append('{\\blur0\\bord0\\1c&H'..options.color_background..'\\iclip('..fpath.scale..', '..fpath.text..')}') - ass:append(ass_opacity(math.max(options.volume_opacity - 0.1, 0), opacity)) - ass:pos(0, 0) - ass:draw_start() - ass:move_to(bax, bay) - ass:line_to(bbx, bay) - local half_border = options.volume_border / 2 - if slider.draw_nudge then - ass:line_to(bbx, math.max(slider.nudge_y - slider.nudge_size + half_border, bay)) - ass:line_to(bbx - slider.nudge_size + half_border, slider.nudge_y) - ass:line_to(bbx, slider.nudge_y + slider.nudge_size - half_border) - end - ass:line_to(bbx, bby) - ass:line_to(bax, bby) - if slider.draw_nudge then - ass:line_to(bax, slider.nudge_y + slider.nudge_size - half_border) - ass:line_to(bax + slider.nudge_size - half_border, slider.nudge_y) - ass:line_to(bax, math.max(slider.nudge_y - slider.nudge_size + half_border, bay)) - end - ass:line_to(bax, bay) - ass:draw_stop() + -- Background + ass:new_event() + ass:append('{\\blur0\\bord0\\1c&H' .. options.color_background .. + '\\iclip(' .. fpath.scale .. ', ' .. fpath.text .. ')}') + ass:append(ass_opacity(math.max(options.volume_opacity - 0.1, 0), + opacity)) + ass:pos(0, 0) + ass:draw_start() + ass:move_to(bax, bay) + ass:line_to(bbx, bay) + local half_border = options.volume_border / 2 + if slider.draw_nudge then + ass:line_to(bbx, math.max(slider.nudge_y - slider.nudge_size + + half_border, bay)) + ass:line_to(bbx - slider.nudge_size + half_border, slider.nudge_y) + ass:line_to(bbx, slider.nudge_y + slider.nudge_size - half_border) + end + ass:line_to(bbx, bby) + ass:line_to(bax, bby) + if slider.draw_nudge then + ass:line_to(bax, slider.nudge_y + slider.nudge_size - half_border) + ass:line_to(bax + slider.nudge_size - half_border, slider.nudge_y) + ass:line_to(bax, math.max(slider.nudge_y - slider.nudge_size + + half_border, bay)) + end + ass:line_to(bax, bay) + ass:draw_stop() - -- Foreground - ass:new_event() - ass:append('{\\blur0\\bord0\\1c&H'..options.color_foreground..'}') - ass:append(ass_opacity(options.volume_opacity, opacity)) - ass:pos(0, 0) - ass:draw_start() - ass:append(fpath.text) - ass:draw_stop() + -- Foreground + ass:new_event() + ass:append('{\\blur0\\bord0\\1c&H' .. options.color_foreground .. '}') + ass:append(ass_opacity(options.volume_opacity, opacity)) + ass:pos(0, 0) + ass:draw_start() + ass:append(fpath.text) + ass:draw_stop() - -- Current volume value - local volume_string = tostring(round(state.volume * 10) / 10) - local font_size = round(((this.width * 0.6) - (#volume_string * (this.width / 20))) * options.volume_font_scale) - if fay < slider.by - slider.spacing then - ass:new_event() - ass:append('{\\blur0\\bord0\\shad0\\1c&H'..options.color_foreground_text..'\\fn'..config.font..'\\fs'..font_size..bold_tag..'\\clip('..fpath.scale..', '..fpath.text..')}') - ass:append(ass_opacity(math.min(options.volume_opacity + 0.1, 1), opacity)) - ass:pos(slider.ax + (slider.width / 2), slider.by - slider.spacing) - ass:an(2) - ass:append(volume_string) - end - if fay > slider.by - slider.spacing - font_size then - ass:new_event() - ass:append('{\\blur0\\bord0\\shad1\\1c&H'..options.color_background_text..'\\4c&H'..options.color_background..'\\fn'..config.font..'\\fs'..font_size..bold_tag..'\\iclip('..fpath.scale..', '..fpath.text..')}') - ass:append(ass_opacity(math.min(options.volume_opacity + 0.1, 1), opacity)) - ass:pos(slider.ax + (slider.width / 2), slider.by - slider.spacing) - ass:an(2) - ass:append(volume_string) - end - end + -- Current volume value + local volume_string = tostring(round(state.volume * 10) / 10) + local font_size = round(((this.width * 0.6) - + (#volume_string * (this.width / 20))) * + options.volume_font_scale) + if fay < slider.by - slider.spacing then + ass:new_event() + ass:append('{\\blur0\\bord0\\shad0\\1c&H' .. + options.color_foreground_text .. '\\fn' .. + config.font .. '\\fs' .. font_size .. bold_tag .. + '\\clip(' .. fpath.scale .. ', ' .. fpath.text .. + ')}') + ass:append(ass_opacity(math.min(options.volume_opacity + 0.1, 1), + opacity)) + ass:pos(slider.ax + (slider.width / 2), slider.by - slider.spacing) + ass:an(2) + ass:append(volume_string) + end + if fay > slider.by - slider.spacing - font_size then + ass:new_event() + ass:append('{\\blur0\\bord0\\shad1\\1c&H' .. + options.color_background_text .. '\\4c&H' .. + options.color_background .. '\\fn' .. config.font .. + '\\fs' .. font_size .. bold_tag .. '\\iclip(' .. + fpath.scale .. ', ' .. fpath.text .. ')}') + ass:append(ass_opacity(math.min(options.volume_opacity + 0.1, 1), + opacity)) + ass:pos(slider.ax + (slider.width / 2), slider.by - slider.spacing) + ass:an(2) + ass:append(volume_string) + end + end - -- Mute button - local mute = elements.volume_mute - local icon_name = state.mute and 'volume_muted' or 'volume' - ass:new_event() - ass:append(icon( - icon_name, - mute.ax + (mute.width / 2), mute.ay + (mute.height / 2), mute.width * 0.7, -- x, y, size - 0, 0, options.volume_border, -- shadow_x, shadow_y, shadow_size - 'background', options.volume_opacity * opacity -- backdrop, opacity - )) - return ass + -- Mute button + local mute = elements.volume_mute + local icon_name = state.mute and 'volume_muted' or 'volume' + ass:new_event() + ass:append(icon(icon_name, mute.ax + (mute.width / 2), + mute.ay + (mute.height / 2), mute.width * 0.7, -- x, y, size + 0, 0, options.volume_border, -- shadow_x, shadow_y, shadow_size + 'background', options.volume_opacity * opacity -- backdrop, opacity + )) + return ass end function render_speed(this) - if not this.dragging and (elements.curtain.opacity > 0) then return end + if not this.dragging and (elements.curtain.opacity > 0) then return end - local timeline = elements.timeline - local proximity = timeline:get_effective_proximity() - local opacity = this.forced_proximity and this.forced_proximity or (this.dragging and 1 or proximity) + local timeline = elements.timeline + local proximity = timeline:get_effective_proximity() + local opacity = this.forced_proximity and this.forced_proximity or + (this.dragging and 1 or proximity) - if opacity == 0 then return end + if opacity == 0 then return end - local ass = assdraw.ass_new() + local ass = assdraw.ass_new() - -- Coordinates - local ax = this.ax - local ay = this.ay + timeline.size_max - timeline:get_effective_size() - timeline.top_border - timeline.bottom_border - local bx = this.bx - local by = ay + this.height - local half_width = (this.width / 2) - local half_x = ax + half_width + -- Coordinates + local ax = this.ax + local ay = this.ay + timeline.size_max - timeline:get_effective_size() - + timeline.top_border - timeline.bottom_border + local bx = this.bx + local by = ay + this.height + local half_width = (this.width / 2) + local half_x = ax + half_width - -- Notches - local speed_at_center = state.speed - if this.dragging then - speed_at_center = this.dragging.start_speed + ((-this.dragging.distance / this.step_distance) * options.speed_step) - speed_at_center = math.min(math.max(speed_at_center, 0.01), 100) - end - local nearest_notch_speed = round(speed_at_center / this.notch_every) * this.notch_every - local nearest_notch_x = half_x + (((nearest_notch_speed - speed_at_center) / this.notch_every) * this.notch_spacing) - local guide_size = math.floor(this.height / 7.5) - local notch_by = by - guide_size - local notch_ay_big = ay + round(this.font_size * 1.1) - local notch_ay_medium = notch_ay_big + ((notch_by - notch_ay_big) * 0.2) - local notch_ay_small = notch_ay_big + ((notch_by - notch_ay_big) * 0.4) - local from_to_index = math.floor(this.notches / 2) + -- Notches + local speed_at_center = state.speed + if this.dragging then + speed_at_center = this.dragging.start_speed + + ((-this.dragging.distance / this.step_distance) * + options.speed_step) + speed_at_center = math.min(math.max(speed_at_center, 0.01), 100) + end + local nearest_notch_speed = round(speed_at_center / this.notch_every) * + this.notch_every + local nearest_notch_x = half_x + + (((nearest_notch_speed - speed_at_center) / + this.notch_every) * this.notch_spacing) + local guide_size = math.floor(this.height / 7.5) + local notch_by = by - guide_size + local notch_ay_big = ay + round(this.font_size * 1.1) + local notch_ay_medium = notch_ay_big + ((notch_by - notch_ay_big) * 0.2) + local notch_ay_small = notch_ay_big + ((notch_by - notch_ay_big) * 0.4) + local from_to_index = math.floor(this.notches / 2) - for i = -from_to_index, from_to_index do - local notch_speed = nearest_notch_speed + (i * this.notch_every) + for i = -from_to_index, from_to_index do + local notch_speed = nearest_notch_speed + (i * this.notch_every) - if notch_speed < 0 or notch_speed > 100 then goto continue end + if notch_speed < 0 or notch_speed > 100 then goto continue end - local notch_x = nearest_notch_x + (i * this.notch_spacing) - local notch_thickness = 1 - local notch_ay = notch_ay_small - if (notch_speed % (this.notch_every * 10)) < 0.00000001 then - notch_ay = notch_ay_big - notch_thickness = 1 - elseif (notch_speed % (this.notch_every * 5)) < 0.00000001 then - notch_ay = notch_ay_medium - end + local notch_x = nearest_notch_x + (i * this.notch_spacing) + local notch_thickness = 1 + local notch_ay = notch_ay_small + if (notch_speed % (this.notch_every * 10)) < 0.00000001 then + notch_ay = notch_ay_big + notch_thickness = 1 + elseif (notch_speed % (this.notch_every * 5)) < 0.00000001 then + notch_ay = notch_ay_medium + end - ass:new_event() - ass:append('{\\blur0\\bord1\\shad0\\1c&HFFFFFF\\3c&H000000}') - ass:append(ass_opacity(math.min(1.2 - (math.abs((notch_x - ax - half_width) / half_width)), 1), opacity)) - ass:pos(0, 0) - ass:draw_start() - ass:move_to(notch_x - notch_thickness, notch_ay) - ass:line_to(notch_x + notch_thickness, notch_ay) - ass:line_to(notch_x + notch_thickness, notch_by) - ass:line_to(notch_x - notch_thickness, notch_by) - ass:draw_stop() + ass:new_event() + ass:append('{\\blur0\\bord1\\shad0\\1c&HFFFFFF\\3c&H000000}') + ass:append(ass_opacity(math.min(1.2 - + (math.abs( + (notch_x - ax - half_width) / + half_width)), 1), opacity)) + ass:pos(0, 0) + ass:draw_start() + ass:move_to(notch_x - notch_thickness, notch_ay) + ass:line_to(notch_x + notch_thickness, notch_ay) + ass:line_to(notch_x + notch_thickness, notch_by) + ass:line_to(notch_x - notch_thickness, notch_by) + ass:draw_stop() - ::continue:: - end + ::continue:: + end - -- Center guide - ass:new_event() - ass:append('{\\blur0\\bord1\\shad0\\1c&HFFFFFF\\3c&H000000}') - ass:append(ass_opacity(options.speed_opacity, opacity)) - ass:pos(0, 0) - ass:draw_start() - ass:move_to(half_x, by - 2 - guide_size) - ass:line_to(half_x + guide_size, by - 2) - ass:line_to(half_x - guide_size, by - 2) - ass:draw_stop() + -- Center guide + ass:new_event() + ass:append('{\\blur0\\bord1\\shad0\\1c&HFFFFFF\\3c&H000000}') + ass:append(ass_opacity(options.speed_opacity, opacity)) + ass:pos(0, 0) + ass:draw_start() + ass:move_to(half_x, by - 2 - guide_size) + ass:line_to(half_x + guide_size, by - 2) + ass:line_to(half_x - guide_size, by - 2) + ass:draw_stop() - -- Speed value - local speed_text = (round(state.speed * 100) / 100)..'x' - ass:new_event() - ass:append('{\\blur0\\bord1\\shad0\\1c&H'..options.color_background_text..'\\3c&H'..options.color_background..'\\fn'..config.font..'\\fs'..this.font_size..bold_tag..'}') - ass:append(ass_opacity(options.speed_opacity, opacity)) - ass:pos(half_x, ay) - ass:an(8) - ass:append(speed_text) + -- Speed value + local speed_text = (round(state.speed * 100) / 100) .. 'x' + ass:new_event() + ass:append( + '{\\blur0\\bord1\\shad0\\1c&H' .. options.color_background_text .. + '\\3c&H' .. options.color_background .. '\\fn' .. config.font .. + '\\fs' .. this.font_size .. bold_tag .. '}') + ass:append(ass_opacity(options.speed_opacity, opacity)) + ass:pos(half_x, ay) + ass:an(8) + ass:append(speed_text) - return ass + return ass end function render_menu(this) - local ass = assdraw.ass_new() + local ass = assdraw.ass_new() - if this.parent_menu then - ass:merge(this.parent_menu:render()) - end + if this.parent_menu then ass:merge(this.parent_menu:render()) end - -- Menu title - if this.title then - -- Background - ass:new_event() - ass:append('{\\blur0\\bord0\\1c&H'..options.color_background..'}') - ass:append(ass_opacity(options.menu_opacity, this.opacity)) - ass:pos(0, 0) - ass:draw_start() - ass:rect_cw(this.ax, this.ay - this.item_height, this.bx, this.ay - 1) - ass:draw_stop() + -- Menu title + if this.title then + -- Background + ass:new_event() + ass:append('{\\blur0\\bord0\\1c&H' .. options.color_background .. '}') + ass:append(ass_opacity(options.menu_opacity, this.opacity)) + ass:pos(0, 0) + ass:draw_start() + ass:rect_cw(this.ax, this.ay - this.item_height, this.bx, this.ay - 1) + ass:draw_stop() - -- Title - ass:new_event() - ass:append('{\\blur0\\bord0\\shad1\\b1\\1c&H'..options.color_background_text..'\\4c&H'..options.color_background..'\\fn'..config.font..'\\fs'..this.font_size..'\\q2\\clip('..this.ax..','..this.ay - this.item_height..','..this.bx..','..this.ay..')}') - ass:append(ass_opacity(options.menu_opacity, this.opacity)) - ass:pos(display.width / 2, this.ay - (this.item_height * 0.5)) - ass:an(5) - ass:append(this.title) - end + -- Title + ass:new_event() + ass:append('{\\blur0\\bord0\\shad1\\b1\\1c&H' .. + options.color_background_text .. '\\4c&H' .. + options.color_background .. '\\fn' .. config.font .. + '\\fs' .. this.font_size .. '\\q2\\clip(' .. this.ax .. + ',' .. this.ay - this.item_height .. ',' .. this.bx .. + ',' .. this.ay .. ')}') + ass:append(ass_opacity(options.menu_opacity, this.opacity)) + ass:pos(display.width / 2, this.ay - (this.item_height * 0.5)) + ass:an(5) + ass:append(this.title) + end - local scroll_area_clip = '\\clip('..this.ax..','..this.ay..','..this.bx..','..this.by..')' + local scroll_area_clip = '\\clip(' .. this.ax .. ',' .. this.ay .. ',' .. + this.bx .. ',' .. this.by .. ')' - for index, item in ipairs(this.items) do - local item_ay = this.ay - this.scroll_y + (this.item_height * (index - 1) + this.item_spacing * (index - 1)) - local item_by = item_ay + this.item_height - local item_clip = '' + for index, item in ipairs(this.items) do + local item_ay = this.ay - this.scroll_y + + (this.item_height * (index - 1) + this.item_spacing * + (index - 1)) + local item_by = item_ay + this.item_height + local item_clip = '' - -- Clip items overflowing scroll area - if item_ay <= this.ay or item_by >= this.by then - item_clip = scroll_area_clip - end + -- Clip items overflowing scroll area + if item_ay <= this.ay or item_by >= this.by then + item_clip = scroll_area_clip + end - if item_by < this.ay or item_ay > this.by then goto continue end + if item_by < this.ay or item_ay > this.by then goto continue end - local is_active = this.active_item == index - local font_color, background_color, ass_shadow, ass_shadow_color - local icon_size = this.font_size + local is_active = this.active_item == index + local font_color, background_color, ass_shadow, ass_shadow_color + local icon_size = this.font_size - if is_active then - font_color, background_color = options.color_foreground_text, options.color_foreground - ass_shadow, ass_shadow_color = '\\shad0', '' - else - font_color, background_color = options.color_background_text, options.color_background - ass_shadow, ass_shadow_color = '\\shad1', '\\4c&H'..background_color - end + if is_active then + font_color, background_color = options.color_foreground_text, + options.color_foreground + ass_shadow, ass_shadow_color = '\\shad0', '' + else + font_color, background_color = options.color_background_text, + options.color_background + ass_shadow, ass_shadow_color = '\\shad1', + '\\4c&H' .. background_color + end - local has_submenu = item.items ~= nil - local hint_width = 0 - if item.hint then - hint_width = text_width_estimate(item.hint:len(), this.font_size) + this.item_content_spacing - elseif has_submenu then - hint_width = icon_size + this.item_content_spacing - end + local has_submenu = item.items ~= nil + local hint_width = 0 + if item.hint then + hint_width = text_width_estimate(item.hint:len(), this.font_size) + + this.item_content_spacing + elseif has_submenu then + hint_width = icon_size + this.item_content_spacing + end - -- Background - ass:new_event() - ass:append('{\\blur0\\bord0\\1c&H'..background_color..item_clip..'}') - ass:append(ass_opacity(options.menu_opacity, this.opacity)) - ass:pos(0, 0) - ass:draw_start() - ass:rect_cw(this.ax, item_ay, this.bx, item_by) - ass:draw_stop() + -- Background + ass:new_event() + ass:append('{\\blur0\\bord0\\1c&H' .. background_color .. item_clip .. + '}') + ass:append(ass_opacity(options.menu_opacity, this.opacity)) + ass:pos(0, 0) + ass:draw_start() + ass:rect_cw(this.ax, item_ay, this.bx, item_by) + ass:draw_stop() - -- Selected highlight - if this.selected_item == index then - ass:new_event() - ass:append('{\\blur0\\bord0\\1c&H'..options.color_foreground..item_clip..'}') - ass:append(ass_opacity(0.1, this.opacity)) - ass:pos(0, 0) - ass:draw_start() - ass:rect_cw(this.ax, item_ay, this.bx, item_by) - ass:draw_stop() - end + -- Selected highlight + if this.selected_item == index then + ass:new_event() + ass:append('{\\blur0\\bord0\\1c&H' .. options.color_foreground .. + item_clip .. '}') + ass:append(ass_opacity(0.1, this.opacity)) + ass:pos(0, 0) + ass:draw_start() + ass:rect_cw(this.ax, item_ay, this.bx, item_by) + ass:draw_stop() + end - -- Title - if item.title then - item.ass_save_title = item.ass_save_title or item.title:gsub("([{}])","\\%1") - local title_clip_x = (this.bx - hint_width - this.item_content_spacing) - local title_clip = '\\clip('..this.ax..','..math.max(item_ay, this.ay)..','..title_clip_x..','..math.min(item_by, this.by)..')' - ass:new_event() - ass:append('{\\blur0\\bord0\\shad1\\1c&H'..font_color..'\\4c&H'..background_color..'\\fn'..config.font..'\\fs'..this.font_size..bold_tag..title_clip..'\\q2}') - ass:append(ass_opacity(options.menu_opacity, this.opacity)) - ass:pos(this.ax + this.item_content_spacing, item_ay + (this.item_height / 2)) - ass:an(4) - ass:append(item.ass_save_title) - end + -- Title + if item.title then + item.ass_save_title = item.ass_save_title or + item.title:gsub("([{}])", "\\%1") + local title_clip_x = (this.bx - hint_width - + this.item_content_spacing) + local title_clip = '\\clip(' .. this.ax .. ',' .. + math.max(item_ay, this.ay) .. ',' .. + title_clip_x .. ',' .. + math.min(item_by, this.by) .. ')' + ass:new_event() + ass:append( + '{\\blur0\\bord0\\shad1\\1c&H' .. font_color .. '\\4c&H' .. + background_color .. '\\fn' .. config.font .. '\\fs' .. + this.font_size .. bold_tag .. title_clip .. '\\q2}') + ass:append(ass_opacity(options.menu_opacity, this.opacity)) + ass:pos(this.ax + this.item_content_spacing, + item_ay + (this.item_height / 2)) + ass:an(4) + ass:append(item.ass_save_title) + end - -- Hint - if item.hint then - item.ass_save_hint = item.ass_save_hint or item.hint:gsub("([{}])","\\%1") - ass:new_event() - ass:append('{\\blur0\\bord0'..ass_shadow..'\\1c&H'..font_color..''..ass_shadow_color..'\\fn'..config.font..'\\fs'..(this.font_size - 1)..bold_tag..item_clip..'}') - ass:append(ass_opacity(options.menu_opacity * (has_submenu and 1 or 0.5), this.opacity)) - ass:pos(this.bx - this.item_content_spacing, item_ay + (this.item_height / 2)) - ass:an(6) - ass:append(item.ass_save_hint) - elseif has_submenu then - ass:new_event() - ass:append(icon( - 'arrow_right', - this.bx - this.item_content_spacing - (icon_size / 2), -- x - item_ay + (this.item_height / 2), -- y - icon_size, -- size - 0, 0, 1, -- shadow_x, shadow_y, shadow_size - is_active and 'foreground' or 'background', this.opacity, -- backdrop, opacity - item_clip - )) - end + -- Hint + if item.hint then + item.ass_save_hint = item.ass_save_hint or + item.hint:gsub("([{}])", "\\%1") + ass:new_event() + ass:append( + '{\\blur0\\bord0' .. ass_shadow .. '\\1c&H' .. font_color .. '' .. + ass_shadow_color .. '\\fn' .. config.font .. '\\fs' .. + (this.font_size - 1) .. bold_tag .. item_clip .. '}') + ass:append(ass_opacity(options.menu_opacity * + (has_submenu and 1 or 0.5), this.opacity)) + ass:pos(this.bx - this.item_content_spacing, + item_ay + (this.item_height / 2)) + ass:an(6) + ass:append(item.ass_save_hint) + elseif has_submenu then + ass:new_event() + ass:append(icon('arrow_right', + this.bx - this.item_content_spacing - + (icon_size / 2), -- x + item_ay + (this.item_height / 2), -- y + icon_size, -- size + 0, 0, 1, -- shadow_x, shadow_y, shadow_size + is_active and 'foreground' or 'background', this.opacity, -- backdrop, opacity + item_clip)) + end - ::continue:: - end + ::continue:: + end - -- Scrollbar - if this.scroll_height > 0 then - local groove_height = this.height - 2 - local thumb_height = math.max((this.height / (this.scroll_height + this.height)) * groove_height, 40) - local thumb_y = this.ay + 1 + ((this.scroll_y / this.scroll_height) * (groove_height - thumb_height)) - ass:new_event() - ass:append('{\\blur0\\bord0\\1c&H'..options.color_foreground..'}') - ass:append(ass_opacity(options.menu_opacity, this.opacity * 0.8)) - ass:pos(0, 0) - ass:draw_start() - ass:rect_cw(this.bx - 3, thumb_y, this.bx - 1, thumb_y + thumb_height) - ass:draw_stop() - end + -- Scrollbar + if this.scroll_height > 0 then + local groove_height = this.height - 2 + local thumb_height = math.max((this.height / + (this.scroll_height + this.height)) * + groove_height, 40) + local thumb_y = this.ay + 1 + + ((this.scroll_y / this.scroll_height) * + (groove_height - thumb_height)) + ass:new_event() + ass:append('{\\blur0\\bord0\\1c&H' .. options.color_foreground .. '}') + ass:append(ass_opacity(options.menu_opacity, this.opacity * 0.8)) + ass:pos(0, 0) + ass:draw_start() + ass:rect_cw(this.bx - 3, thumb_y, this.bx - 1, thumb_y + thumb_height) + ass:draw_stop() + end - return ass + return ass end -- MAIN RENDERING @@ -2022,791 +2266,894 @@ end -- The render is then either executed immediately, or rate-limited if it was -- called a small time ago. function request_render() - if state.render_timer == nil then - state.render_timer = mp.add_timeout(0, render) - end + if state.render_timer == nil then + state.render_timer = mp.add_timeout(0, render) + end - if not state.render_timer:is_enabled() then - local now = mp.get_time() - local timeout = config.render_delay - (now - state.render_last_time) - if timeout < 0 then - timeout = 0 - end - state.render_timer.timeout = timeout - state.render_timer:resume() - end + if not state.render_timer:is_enabled() then + local now = mp.get_time() + local timeout = config.render_delay - (now - state.render_last_time) + if timeout < 0 then timeout = 0 end + state.render_timer.timeout = timeout + state.render_timer:resume() + end end function render() - state.render_last_time = mp.get_time() + state.render_last_time = mp.get_time() - -- Actual rendering - local ass = assdraw.ass_new() + -- Actual rendering + local ass = assdraw.ass_new() - for _, element in elements.ipairs() do - local result = element:maybe('render') - if result then - ass:new_event() - ass:merge(result) - end - end + for _, element in elements.ipairs() do + local result = element:maybe('render') + if result then + ass:new_event() + ass:merge(result) + end + end - -- submit - if osd.res_x == display.width and osd.res_y == display.height and osd.data == ass.text then - return - end + -- submit + if osd.res_x == display.width and osd.res_y == display.height and osd.data == + ass.text then return end - osd.res_x = display.width - osd.res_y = display.height - osd.data = ass.text - osd.z = 2000 - osd:update() + osd.res_x = display.width + osd.res_y = display.height + osd.data = ass.text + osd.z = 2000 + osd:update() end -- STATIC ELEMENTS if itable_find({'flash', 'static'}, options.pause_indicator) then - elements:add('pause_indicator', Element.new({ - base_icon_opacity = options.pause_indicator == 'flash' and 1 or 0.8, - paused = false, - is_flash = options.pause_indicator == 'flash', - is_static = options.pause_indicator == 'static', - opacity = 0, - init = function(this) - local initial_call = true - mp.observe_property('pause', 'bool', function(_, paused) - if initial_call then - initial_call = false - return - end + elements:add('pause_indicator', Element.new( + { + base_icon_opacity = options.pause_indicator == 'flash' and 1 or 0.8, + paused = false, + is_flash = options.pause_indicator == 'flash', + is_static = options.pause_indicator == 'static', + opacity = 0, + init = function(this) + local initial_call = true + mp.observe_property('pause', 'bool', function(_, paused) + if initial_call then + initial_call = false + return + end - this.paused = paused + this.paused = paused - if options.pause_indicator == 'flash' then - this.opacity = 1 - this:tween_property('opacity', 1, 0, 0.15) - else - this.opacity = paused and 1 or 0 - request_render() - end + if options.pause_indicator == 'flash' then + this.opacity = 1 + this:tween_property('opacity', 1, 0, 0.15) + else + this.opacity = paused and 1 or 0 + request_render() + end - end) - end, - render = function(this) - if this.opacity == 0 then return end + end) + end, + render = function(this) + if this.opacity == 0 then return end - local ass = assdraw.ass_new() + local ass = assdraw.ass_new() - -- Background fadeout - if this.is_static then - ass:new_event() - ass:append('{\\blur0\\bord0\\1c&H'..options.color_background..'}') - ass:append(ass_opacity(0.3, this.opacity)) - ass:pos(0, 0) - ass:draw_start() - ass:rect_cw(0, 0, display.width, display.height) - ass:draw_stop() - end + -- Background fadeout + if this.is_static then + ass:new_event() + ass:append('{\\blur0\\bord0\\1c&H' .. + options.color_background .. '}') + ass:append(ass_opacity(0.3, this.opacity)) + ass:pos(0, 0) + ass:draw_start() + ass:rect_cw(0, 0, display.width, display.height) + ass:draw_stop() + end - -- Icon - local size = round((math.min(display.width, display.height) * (this.is_static and 0.20 or 0.15)) / 2) + -- Icon + local size = round((math.min(display.width, display.height) * + (this.is_static and 0.20 or 0.15)) / 2) - size = size + size * (1 - this.opacity) + size = size + size * (1 - this.opacity) - if this.paused then - ass:new_event() - ass:append('{\\blur0\\bord1\\1c&H'..options.color_foreground..'\\3c&H'..options.color_background..'}') - ass:append(ass_opacity(this.base_icon_opacity, this.opacity)) - ass:pos(display.width / 2, display.height / 2) - ass:draw_start() - ass:rect_cw(-size, -size, -size / 3, size) - ass:draw_stop() + if this.paused then + ass:new_event() + ass:append('{\\blur0\\bord1\\1c&H' .. + options.color_foreground .. '\\3c&H' .. + options.color_background .. '}') + ass:append(ass_opacity(this.base_icon_opacity, this.opacity)) + ass:pos(display.width / 2, display.height / 2) + ass:draw_start() + ass:rect_cw(-size, -size, -size / 3, size) + ass:draw_stop() - ass:new_event() - ass:append('{\\blur0\\bord1\\1c&H'..options.color_foreground..'\\3c&H'..options.color_background..'}') - ass:append(ass_opacity(this.base_icon_opacity, this.opacity)) - ass:pos(display.width / 2, display.height / 2) - ass:draw_start() - ass:rect_cw(size / 3, -size, size, size) - ass:draw_stop() - elseif this.is_flash then - ass:new_event() - ass:append('{\\blur0\\bord1\\1c&H'..options.color_foreground..'\\3c&H'..options.color_background..'}') - ass:append(ass_opacity(this.base_icon_opacity, this.opacity)) - ass:pos(display.width / 2, display.height / 2) - ass:draw_start() - ass:move_to(-size * 0.6, -size) - ass:line_to(size, 0) - ass:line_to(-size * 0.6, size) - ass:draw_stop() - end + ass:new_event() + ass:append('{\\blur0\\bord1\\1c&H' .. + options.color_foreground .. '\\3c&H' .. + options.color_background .. '}') + ass:append(ass_opacity(this.base_icon_opacity, this.opacity)) + ass:pos(display.width / 2, display.height / 2) + ass:draw_start() + ass:rect_cw(size / 3, -size, size, size) + ass:draw_stop() + elseif this.is_flash then + ass:new_event() + ass:append('{\\blur0\\bord1\\1c&H' .. + options.color_foreground .. '\\3c&H' .. + options.color_background .. '}') + ass:append(ass_opacity(this.base_icon_opacity, this.opacity)) + ass:pos(display.width / 2, display.height / 2) + ass:draw_start() + ass:move_to(-size * 0.6, -size) + ass:line_to(size, 0) + ass:line_to(-size * 0.6, size) + ass:draw_stop() + end - return ass - end - })) + return ass + end + })) end elements:add('timeline', Element.new({ - captures = {mouse_buttons = true, wheel = true}, - pressed = false, - size_max = 0, size_min = 0, -- set in `on_display_resize` handler based on `state.fullscreen` - size_min_override = options.timeline_start_hidden and 0 or nil, -- used for toggle-progress command - font_size = 0, -- calculated in on_display_resize - top_border = options.timeline_border, - bottom_border = 0, -- set dynamically in `border` property observer - init = function(this) - -- Toggle 1px bottom border for timeline in no-border mode - mp.observe_property('border', 'bool', function(_, border) - this.bottom_border = not border and options.timeline_border or 0 - request_render() - end) + captures = {mouse_buttons = true, wheel = true}, + pressed = false, + size_max = 0, + size_min = 0, -- set in `on_display_resize` handler based on `state.fullscreen` + size_min_override = options.timeline_start_hidden and 0 or nil, -- used for toggle-progress command + font_size = 0, -- calculated in on_display_resize + top_border = options.timeline_border, + bottom_border = 0, -- set dynamically in `border` property observer + init = function(this) + -- Toggle 1px bottom border for timeline in no-border mode + mp.observe_property('border', 'bool', function(_, border) + this.bottom_border = not border and options.timeline_border or 0 + request_render() + end) - -- Flash on external changes - if options.timeline_flash then - mp.register_event('seek', function() - local position = mp.get_property_native('playback-time') - if position and state.position then - local seek_length = math.abs(position - state.position) - -- Don't flash on video looping (seek to 0) or tiny seeks (frame-step) - if position > 0.5 and seek_length > 0.5 then this:flash() end - end - end) - end - end, - get_effective_proximity = function(this) - if (elements.volume_slider and elements.volume_slider.pressed) then return 0 end - if this.pressed then return 1 end - return this.forced_proximity and this.forced_proximity or this.proximity - end, - get_effective_size_min = function(this) - return this.size_min_override or this.size_min - end, - get_effective_size = function(this) - if elements.speed and elements.speed.dragging then return this.size_max end - local size_min = this:get_effective_size_min() - return size_min + math.ceil((this.size_max - size_min) * this:get_effective_proximity()) - end, - on_display_resize = function(this) - if state.fullscreen or state.maximized then - this.size_min = options.timeline_size_min_fullscreen - this.size_max = options.timeline_size_max_fullscreen - else - this.size_min = options.timeline_size_min - this.size_max = options.timeline_size_max - end - this.font_size = math.floor(math.min((this.size_max + 60) * 0.2, this.size_max * 0.96) * options.timeline_font_scale) - this.ax = 0 - this.ay = display.height - this.size_max - this.top_border - this.bottom_border - this.bx = display.width - this.by = display.height - end, - set_from_cursor = function(this) - mp.commandv('seek', ((cursor.x / display.width) * 100), 'absolute-percent+exact') - end, - on_mbtn_left_down = function(this) - this.pressed = true - this:set_from_cursor() - end, - on_global_mbtn_left_up = function(this) this.pressed = false end, - on_global_mouse_leave = function(this) this.pressed = false end, - on_global_mouse_move = function(this) - if this.pressed then this:set_from_cursor() end - end, - on_wheel_up = function(this) - if options.timeline_step > 0 then mp.commandv('seek', -options.timeline_step) end - end, - on_wheel_down = function(this) - if options.timeline_step > 0 then mp.commandv('seek', options.timeline_step) end - end, - render = render_timeline, + -- Flash on external changes + if options.timeline_flash then + mp.register_event('seek', function() + local position = mp.get_property_native('playback-time') + if position and state.position then + local seek_length = math.abs(position - state.position) + -- Don't flash on video looping (seek to 0) or tiny seeks (frame-step) + if position > 0.5 and seek_length > 0.5 then + this:flash() + end + end + end) + end + end, + get_effective_proximity = function(this) + if (elements.volume_slider and elements.volume_slider.pressed) then + return 0 + end + if this.pressed then return 1 end + return this.forced_proximity and this.forced_proximity or this.proximity + end, + get_effective_size_min = function(this) + return this.size_min_override or this.size_min + end, + get_effective_size = function(this) + if elements.speed and elements.speed.dragging then + return this.size_max + end + local size_min = this:get_effective_size_min() + return size_min + + math.ceil((this.size_max - size_min) * + this:get_effective_proximity()) + end, + on_display_resize = function(this) + if state.fullscreen or state.maximized then + this.size_min = options.timeline_size_min_fullscreen + this.size_max = options.timeline_size_max_fullscreen + else + this.size_min = options.timeline_size_min + this.size_max = options.timeline_size_max + end + this.font_size = math.floor(math.min((this.size_max + 60) * 0.2, + this.size_max * 0.96) * + options.timeline_font_scale) + this.ax = 0 + this.ay = display.height - this.size_max - this.top_border - + this.bottom_border + this.bx = display.width + this.by = display.height + end, + set_from_cursor = function(this) + mp.commandv('seek', ((cursor.x / display.width) * 100), + 'absolute-percent+exact') + end, + on_mbtn_left_down = function(this) + this.pressed = true + this:set_from_cursor() + end, + on_global_mbtn_left_up = function(this) this.pressed = false end, + on_global_mouse_leave = function(this) this.pressed = false end, + on_global_mouse_move = function(this) + if this.pressed then this:set_from_cursor() end + end, + on_wheel_up = function(this) + if options.timeline_step > 0 then + mp.commandv('seek', -options.timeline_step) + end + end, + on_wheel_down = function(this) + if options.timeline_step > 0 then + mp.commandv('seek', options.timeline_step) + end + end, + render = render_timeline })) if options.top_bar_controls or options.top_bar_title then - elements:add('top_bar', Element.new({ - button_opacity = 0.8, - enabled = false, - init = function(this) - mp.observe_property('border', 'bool', function(_, border) - this.enabled = not border - end) - end, - get_effective_proximity = function(this) - if (elements.volume_slider and elements.volume_slider.pressed) or elements.curtain.opacity > 0 then return 0 end - return this.forced_proximity and this.forced_proximity or this.proximity - end, - on_display_resize = function(this) - this.size = (state.fullscreen or state.maximized) and options.top_bar_size_fullscreen or options.top_bar_size - this.icon_size = round(this.size / 8) - this.spacing = math.ceil(this.size * 0.25) - this.font_size = math.floor(this.size - (this.spacing * 2)) - this.button_width = round(this.size * 1.15) - this.title_bx = display.width - (options.top_bar_controls and (this.button_width * 3) or 0) - this.ax = options.top_bar_title and 0 or this.title_bx - this.ay = 0 - this.bx = display.width - this.by = this.size - end, - render = render_top_bar, - })) + elements:add('top_bar', Element.new({ + button_opacity = 0.8, + enabled = false, + init = function(this) + mp.observe_property('border', 'bool', function(_, border) + this.enabled = not border + end) + end, + get_effective_proximity = function(this) + if (elements.volume_slider and elements.volume_slider.pressed) or + elements.curtain.opacity > 0 then return 0 end + return this.forced_proximity and this.forced_proximity or + this.proximity + end, + on_display_resize = function(this) + this.size = (state.fullscreen or state.maximized) and + options.top_bar_size_fullscreen or + options.top_bar_size + this.icon_size = round(this.size / 8) + this.spacing = math.ceil(this.size * 0.25) + this.font_size = math.floor(this.size - (this.spacing * 2)) + this.button_width = round(this.size * 1.15) + this.title_bx = display.width - + (options.top_bar_controls and + (this.button_width * 3) or 0) + this.ax = options.top_bar_title and 0 or this.title_bx + this.ay = 0 + this.bx = display.width + this.by = this.size + end, + render = render_top_bar + })) end if options.top_bar_controls then - elements:add('window_controls_minimize', Element.new({ - captures = {mouse_buttons = true}, - on_display_resize = function(this) - this.ax = display.width - (elements.top_bar.button_width * 3) - this.ay = 0 - this.bx = this.ax + elements.top_bar.button_width - this.by = elements.top_bar.size - end, - on_mbtn_left_down = function() mp.commandv('cycle', 'window-minimized') end - })) - elements:add('window_controls_maximize', Element.new({ - captures = {mouse_buttons = true}, - on_display_resize = function(this) - this.ax = display.width - (elements.top_bar.button_width * 2) - this.ay = 0 - this.bx = this.ax + elements.top_bar.button_width - this.by = elements.top_bar.size - end, - on_mbtn_left_down = function() mp.commandv('cycle', 'window-maximized') end - })) - elements:add('window_controls_close', Element.new({ - captures = {mouse_buttons = true}, - on_display_resize = function(this) - this.ax = display.width - elements.top_bar.button_width - this.ay = 0 - this.bx = this.ax + elements.top_bar.button_width - this.by = elements.top_bar.size - end, - on_mbtn_left_down = function() mp.commandv('quit') end - })) + elements:add('window_controls_minimize', Element.new( + { + captures = {mouse_buttons = true}, + on_display_resize = function(this) + this.ax = display.width - (elements.top_bar.button_width * 3) + this.ay = 0 + this.bx = this.ax + elements.top_bar.button_width + this.by = elements.top_bar.size + end, + on_mbtn_left_down = function() + mp.commandv('cycle', 'window-minimized') + end + })) + elements:add('window_controls_maximize', Element.new( + { + captures = {mouse_buttons = true}, + on_display_resize = function(this) + this.ax = display.width - (elements.top_bar.button_width * 2) + this.ay = 0 + this.bx = this.ax + elements.top_bar.button_width + this.by = elements.top_bar.size + end, + on_mbtn_left_down = function() + mp.commandv('cycle', 'window-maximized') + end + })) + elements:add('window_controls_close', Element.new( + { + captures = {mouse_buttons = true}, + on_display_resize = function(this) + this.ax = display.width - elements.top_bar.button_width + this.ay = 0 + this.bx = this.ax + elements.top_bar.button_width + this.by = elements.top_bar.size + end, + on_mbtn_left_down = function() mp.commandv('quit') end + })) end if itable_find({'left', 'right'}, options.volume) then - elements:add('volume', Element.new({ - width = nil, -- set in `on_display_resize` handler based on `state.fullscreen` - height = nil, -- set in `on_display_resize` handler based on `state.fullscreen` - margin = nil, -- set in `on_display_resize` handler based on `state.fullscreen` - init = function(this) - -- FLash on external changes - if options.volume_flash then - local is_initial_volume_call = true - mp.observe_property('volume', 'number', function(_, value) - if not is_initial_volume_call then this:flash() end - is_initial_volume_call = false - end) - local is_initial_mute_call = true - mp.observe_property('mute', 'bool', function(_, value) - if not is_initial_mute_call then this:flash() end - is_initial_mute_call = false - end) - end - end, - get_effective_proximity = function(this) - if elements.volume_slider.pressed then return 1 end - if elements.timeline.proximity_raw == 0 or elements.curtain.opacity > 0 then return 0 end - return this.forced_proximity and this.forced_proximity or this.proximity - end, - on_display_resize = function(this) - this.width = (state.fullscreen or state.maximized) and options.volume_size_fullscreen or options.volume_size - this.height = round(math.min(this.width * 8, (elements.timeline.ay - elements.top_bar.size) * 0.8)) - -- Don't bother rendering this if too small - if this.height < (this.width * 2) then - this.height = 0 - end - this.margin = this.width / 2 - this.ax = round(options.volume == 'left' and this.margin or display.width - this.margin - this.width) - this.ay = round((display.height - this.height) / 2) - this.bx = round(this.ax + this.width) - this.by = round(this.ay + this.height) - end, - render = render_volume, - })) - elements:add('volume_mute', Element.new({ - captures = {mouse_buttons = true}, - width = 0, - height = 0, - on_display_resize = function(this) - this.width = elements.volume.width - this.height = this.width - this.ax = elements.volume.ax - this.ay = elements.volume.by - this.height - this.bx = elements.volume.bx - this.by = elements.volume.by - end, - on_mbtn_left_down = function(this) mp.commandv('cycle', 'mute') end - })) - elements:add('volume_slider', Element.new({ - captures = {mouse_buttons = true, wheel = true}, - pressed = false, - width = 0, - height = 0, - nudge_y = 0, -- vertical position where volume overflows 100 - nudge_size = nil, -- set on resize - font_size = nil, - spacing = nil, - on_display_resize = function(this) - this.ax = elements.volume.ax - this.ay = elements.volume.ay - this.bx = elements.volume.bx - this.by = elements.volume_mute.ay - this.width = this.bx - this.ax - this.height = this.by - this.ay - this.nudge_y = this.by - round(this.height * (100 / state.volume_max)) - this.nudge_size = round(elements.volume.width * 0.18) - this.draw_nudge = this.ay < this.nudge_y - this.spacing = round(this.width * 0.2) - end, - set_from_cursor = function(this) - local volume_fraction = (this.by - cursor.y - options.volume_border) / (this.height - options.volume_border) - local new_volume = math.min(math.max(volume_fraction, 0), 1) * state.volume_max - new_volume = round(new_volume / options.volume_step) * options.volume_step - if state.volume ~= new_volume then mp.commandv('set', 'volume', math.min(new_volume, state.volume_max)) end - end, - on_mbtn_left_down = function(this) - this.pressed = true - this:set_from_cursor() - end, - on_global_mbtn_left_up = function(this) this.pressed = false end, - on_global_mouse_leave = function(this) this.pressed = false end, - on_global_mouse_move = function(this) - if this.pressed then this:set_from_cursor() end - end, - on_wheel_up = function(this) - local current_rounded_volume = round(state.volume / options.volume_step) * options.volume_step - mp.commandv('set', 'volume', math.min(current_rounded_volume + options.volume_step, state.volume_max)) - end, - on_wheel_down = function(this) - local current_rounded_volume = round(state.volume / options.volume_step) * options.volume_step - mp.commandv('set', 'volume', math.min(current_rounded_volume - options.volume_step, state.volume_max)) - end, - })) + elements:add('volume', Element.new({ + width = nil, -- set in `on_display_resize` handler based on `state.fullscreen` + height = nil, -- set in `on_display_resize` handler based on `state.fullscreen` + margin = nil, -- set in `on_display_resize` handler based on `state.fullscreen` + init = function(this) + -- FLash on external changes + if options.volume_flash then + local is_initial_volume_call = true + mp.observe_property('volume', 'number', function(_, value) + if not is_initial_volume_call then + this:flash() + end + is_initial_volume_call = false + end) + local is_initial_mute_call = true + mp.observe_property('mute', 'bool', function(_, value) + if not is_initial_mute_call then + this:flash() + end + is_initial_mute_call = false + end) + end + end, + get_effective_proximity = function(this) + if elements.volume_slider.pressed then return 1 end + if elements.timeline.proximity_raw == 0 or elements.curtain.opacity > + 0 then return 0 end + return this.forced_proximity and this.forced_proximity or + this.proximity + end, + on_display_resize = function(this) + this.width = (state.fullscreen or state.maximized) and + options.volume_size_fullscreen or + options.volume_size + this.height = round(math.min(this.width * 8, (elements.timeline.ay - + elements.top_bar.size) * 0.8)) + -- Don't bother rendering this if too small + if this.height < (this.width * 2) then this.height = 0 end + this.margin = this.width / 2 + this.ax = round(options.volume == 'left' and this.margin or + display.width - this.margin - this.width) + this.ay = round((display.height - this.height) / 2) + this.bx = round(this.ax + this.width) + this.by = round(this.ay + this.height) + end, + render = render_volume + })) + elements:add('volume_mute', Element.new( + { + captures = {mouse_buttons = true}, + width = 0, + height = 0, + on_display_resize = function(this) + this.width = elements.volume.width + this.height = this.width + this.ax = elements.volume.ax + this.ay = elements.volume.by - this.height + this.bx = elements.volume.bx + this.by = elements.volume.by + end, + on_mbtn_left_down = function(this) + mp.commandv('cycle', 'mute') + end + })) + elements:add('volume_slider', Element.new( + { + captures = {mouse_buttons = true, wheel = true}, + pressed = false, + width = 0, + height = 0, + nudge_y = 0, -- vertical position where volume overflows 100 + nudge_size = nil, -- set on resize + font_size = nil, + spacing = nil, + on_display_resize = function(this) + this.ax = elements.volume.ax + this.ay = elements.volume.ay + this.bx = elements.volume.bx + this.by = elements.volume_mute.ay + this.width = this.bx - this.ax + this.height = this.by - this.ay + this.nudge_y = this.by - + round(this.height * (100 / state.volume_max)) + this.nudge_size = round(elements.volume.width * 0.18) + this.draw_nudge = this.ay < this.nudge_y + this.spacing = round(this.width * 0.2) + end, + set_from_cursor = function(this) + local volume_fraction = (this.by - cursor.y - + options.volume_border) / + (this.height - options.volume_border) + local new_volume = math.min(math.max(volume_fraction, 0), 1) * + state.volume_max + new_volume = round(new_volume / options.volume_step) * + options.volume_step + if state.volume ~= new_volume then + mp.commandv('set', 'volume', + math.min(new_volume, state.volume_max)) + end + end, + on_mbtn_left_down = function(this) + this.pressed = true + this:set_from_cursor() + end, + on_global_mbtn_left_up = function(this) + this.pressed = false + end, + on_global_mouse_leave = function(this) + this.pressed = false + end, + on_global_mouse_move = function(this) + if this.pressed then this:set_from_cursor() end + end, + on_wheel_up = function(this) + local current_rounded_volume = + round(state.volume / options.volume_step) * + options.volume_step + mp.commandv('set', 'volume', math.min( + current_rounded_volume + options.volume_step, + state.volume_max)) + end, + on_wheel_down = function(this) + local current_rounded_volume = + round(state.volume / options.volume_step) * + options.volume_step + mp.commandv('set', 'volume', math.min( + current_rounded_volume - options.volume_step, + state.volume_max)) + end + })) end if options.speed then - elements:add('speed', Element.new({ - captures = {mouse_buttons = true, wheel = true}, - dragging = nil, - width = 0, - height = 0, - notches = 10, - notch_every = 0.1, - step_distance = nil, - font_size = nil, - init = function(this) - -- Fade out/in on timeline mouse enter/leave - elements.timeline:on('mouse_enter', function() - if not this.dragging then this:fadeout() end - end) - elements.timeline:on('mouse_leave', function() - if not this.dragging then this:fadein() end - end) + elements:add('speed', Element.new({ + captures = {mouse_buttons = true, wheel = true}, + dragging = nil, + width = 0, + height = 0, + notches = 10, + notch_every = 0.1, + step_distance = nil, + font_size = nil, + init = function(this) + -- Fade out/in on timeline mouse enter/leave + elements.timeline:on('mouse_enter', function() + if not this.dragging then this:fadeout() end + end) + elements.timeline:on('mouse_leave', function() + if not this.dragging then this:fadein() end + end) - -- Flash on external changes - if options.speed_flash then - local initial_call = true - mp.observe_property('speed', 'number', function() - if not initial_call and not this.dragging then this:flash() end - initial_call = false - end) - end - end, - fadeout = function(this) - this:tween_property('forced_proximity', 1, 0, function(this) - this.forced_proximity = 0 - end) - end, - fadein = function(this) - local get_current_proximity = function() return this.proximity end - this:tween_property('forced_proximity', 0, get_current_proximity, function(this) - this.forced_proximity = nil - end) - end, - on_display_resize = function(this) - this.height = (state.fullscreen or state.maximized) and options.speed_size_fullscreen or options.speed_size - this.width = round(this.height * 3.6) - this.notch_spacing = this.width / this.notches - this.step_distance = this.notch_spacing * (options.speed_step / this.notch_every) - this.ax = (display.width - this.width) / 2 - this.by = display.height - elements.timeline.size_max - this.ay = this.by - this.height - this.bx = this.ax + this.width - this.font_size = round(this.height * 0.48 * options.speed_font_scale) - end, - set_from_cursor = function(this) - local volume_fraction = (this.by - cursor.y - options.volume_border) / (this.height - options.volume_border) - local new_volume = math.min(math.max(volume_fraction, 0), 1) * state.volume_max - new_volume = round(new_volume / options.volume_step) * options.volume_step - if state.volume ~= new_volume then mp.commandv('set', 'volume', new_volume) end - end, - on_mbtn_left_down = function(this) - this:tween_stop() -- Stop and cleanup possible ongoing animations - this.dragging = { - start_time = mp.get_time(), - start_x = cursor.x, - distance = 0, - start_speed = state.speed - } - end, - on_global_mouse_move = function(this) - if not this.dragging then return end + -- Flash on external changes + if options.speed_flash then + local initial_call = true + mp.observe_property('speed', 'number', function() + if not initial_call and not this.dragging then + this:flash() + end + initial_call = false + end) + end + end, + fadeout = function(this) + this:tween_property('forced_proximity', 1, 0, + function(this) + this.forced_proximity = 0 + end) + end, + fadein = function(this) + local get_current_proximity = + function() return this.proximity end + this:tween_property('forced_proximity', 0, get_current_proximity, + function(this) + this.forced_proximity = nil + end) + end, + on_display_resize = function(this) + this.height = (state.fullscreen or state.maximized) and + options.speed_size_fullscreen or + options.speed_size + this.width = round(this.height * 3.6) + this.notch_spacing = this.width / this.notches + this.step_distance = this.notch_spacing * + (options.speed_step / this.notch_every) + this.ax = (display.width - this.width) / 2 + this.by = display.height - elements.timeline.size_max + this.ay = this.by - this.height + this.bx = this.ax + this.width + this.font_size = + round(this.height * 0.48 * options.speed_font_scale) + end, + set_from_cursor = function(this) + local volume_fraction = + (this.by - cursor.y - options.volume_border) / + (this.height - options.volume_border) + local new_volume = math.min(math.max(volume_fraction, 0), 1) * + state.volume_max + new_volume = round(new_volume / options.volume_step) * + options.volume_step + if state.volume ~= new_volume then + mp.commandv('set', 'volume', new_volume) + end + end, + on_mbtn_left_down = function(this) + this:tween_stop() -- Stop and cleanup possible ongoing animations + this.dragging = { + start_time = mp.get_time(), + start_x = cursor.x, + distance = 0, + start_speed = state.speed + } + end, + on_global_mouse_move = function(this) + if not this.dragging then return end - this.dragging.distance = cursor.x - this.dragging.start_x - local steps_dragged = round(-this.dragging.distance / this.step_distance) - local new_speed = this.dragging.start_speed + (steps_dragged * options.speed_step) - mp.set_property_native('speed', round(new_speed * 100) / 100) - end, - on_mbtn_left_up = function(this) - -- Reset speed on short clicks - if this.dragging and math.abs(this.dragging.distance) < 6 and mp.get_time() - this.dragging.start_time < 0.15 then - mp.set_property_native('speed', 1) - end - end, - on_global_mbtn_left_up = function(this) - if this.dragging and elements.timeline.proximity_raw == 0 then - this:fadeout() - end - this.dragging = nil - request_render() - end, - on_global_mouse_leave = function(this) - this.dragging = nil - request_render() - end, - on_wheel_up = function(this) - mp.set_property_native('speed', state.speed - options.speed_step) - end, - on_wheel_down = function(this) - mp.set_property_native('speed', state.speed + options.speed_step) - end, - render = render_speed, - })) + this.dragging.distance = cursor.x - this.dragging.start_x + local steps_dragged = round(-this.dragging.distance / + this.step_distance) + local new_speed = this.dragging.start_speed + + (steps_dragged * options.speed_step) + mp.set_property_native('speed', round(new_speed * 100) / 100) + end, + on_mbtn_left_up = function(this) + -- Reset speed on short clicks + if this.dragging and math.abs(this.dragging.distance) < 6 and + mp.get_time() - this.dragging.start_time < 0.15 then + mp.set_property_native('speed', 1) + end + end, + on_global_mbtn_left_up = function(this) + if this.dragging and elements.timeline.proximity_raw == 0 then + this:fadeout() + end + this.dragging = nil + request_render() + end, + on_global_mouse_leave = function(this) + this.dragging = nil + request_render() + end, + on_wheel_up = function(this) + mp.set_property_native('speed', state.speed - options.speed_step) + end, + on_wheel_down = function(this) + mp.set_property_native('speed', state.speed + options.speed_step) + end, + render = render_speed + })) end elements:add('curtain', Element.new({ - opacity = 0, - fadeout = function(this) - this:tween_property('opacity', this.opacity, 0); - end, - fadein = function(this) - this:tween_property('opacity', this.opacity, 1); - end, - render = function(this) - if this.opacity > 0 then - local ass = assdraw.ass_new() - ass:new_event() - ass:append('{\\blur0\\bord0\\1c&H'..options.color_background..'}') - ass:append(ass_opacity(0.4, this.opacity)) - ass:pos(0, 0) - ass:draw_start() - ass:rect_cw(0, 0, display.width, display.height) - ass:draw_stop() - return ass - end - end + opacity = 0, + fadeout = function(this) this:tween_property('opacity', this.opacity, 0); end, + fadein = function(this) this:tween_property('opacity', this.opacity, 1); end, + render = function(this) + if this.opacity > 0 then + local ass = assdraw.ass_new() + ass:new_event() + ass:append('{\\blur0\\bord0\\1c&H' .. options.color_background .. + '}') + ass:append(ass_opacity(0.4, this.opacity)) + ass:pos(0, 0) + ass:draw_start() + ass:rect_cw(0, 0, display.width, display.height) + ass:draw_stop() + return ass + end + end })) -- CHAPTERS SERIALIZATION -- Parse `chapter_ranges` option into workable data structure for _, definition in ipairs(split(options.chapter_ranges, ' *,+ *')) do - local start_patterns, color, opacity, end_patterns = string.match(definition, '([^<]+)<(%x%x%x%x%x%x):(%d?%.?%d*)>([^>]+)') + local start_patterns, color, opacity, end_patterns = + string.match(definition, '([^<]+)<(%x%x%x%x%x%x):(%d?%.?%d*)>([^>]+)') - -- Invalid definition - if start_patterns == nil then goto continue end + -- Invalid definition + if start_patterns == nil then goto continue end - start_patterns = start_patterns:lower() - end_patterns = end_patterns:lower() - local uses_bof = start_patterns:find('{bof}') ~= nil - local uses_eof = end_patterns:find('{eof}') ~= nil - local chapter_range = { - start_patterns = split(start_patterns, '|'), - end_patterns = split(end_patterns, '|'), - color = color, - opacity = tonumber(opacity), - ranges = {} - } + start_patterns = start_patterns:lower() + end_patterns = end_patterns:lower() + local uses_bof = start_patterns:find('{bof}') ~= nil + local uses_eof = end_patterns:find('{eof}') ~= nil + local chapter_range = { + start_patterns = split(start_patterns, '|'), + end_patterns = split(end_patterns, '|'), + color = color, + opacity = tonumber(opacity), + ranges = {} + } - -- Filter out special keywords so we don't use them when matching titles - if uses_bof then - chapter_range.start_patterns = itable_remove(chapter_range.start_patterns, '{bof}') - end - if uses_eof and chapter_range.end_patterns then - chapter_range.end_patterns = itable_remove(chapter_range.end_patterns, '{eof}') - end + -- Filter out special keywords so we don't use them when matching titles + if uses_bof then + chapter_range.start_patterns = itable_remove( + chapter_range.start_patterns, '{bof}') + end + if uses_eof and chapter_range.end_patterns then + chapter_range.end_patterns = itable_remove(chapter_range.end_patterns, + '{eof}') + end - chapter_range['serialize'] = function (chapters) - chapter_range.ranges = {} - local current_range = nil - -- bof and eof should be used only once per timeline - -- eof is only used when last range is missing end - local bof_used = false + chapter_range['serialize'] = function(chapters) + chapter_range.ranges = {} + local current_range = nil + -- bof and eof should be used only once per timeline + -- eof is only used when last range is missing end + local bof_used = false - function start_range(chapter) - -- If there is already a range started, should we append or overwrite? - -- I chose overwrite here. - current_range = {['start'] = chapter} - end + function start_range(chapter) + -- If there is already a range started, should we append or overwrite? + -- I chose overwrite here. + current_range = {['start'] = chapter} + end - function end_range(chapter) - current_range['end'] = chapter - chapter_range.ranges[#chapter_range.ranges + 1] = current_range - -- Mark both chapter objects - current_range['start']._uosc_used_as_range_point = true - current_range['end']._uosc_used_as_range_point = true - -- Clear for next range - current_range = nil - end + function end_range(chapter) + current_range['end'] = chapter + chapter_range.ranges[#chapter_range.ranges + 1] = current_range + -- Mark both chapter objects + current_range['start']._uosc_used_as_range_point = true + current_range['end']._uosc_used_as_range_point = true + -- Clear for next range + current_range = nil + end - for _, chapter in ipairs(chapters) do - if type(chapter.title) == 'string' then - local lowercase_title = chapter.title:lower() - local is_end = false - local is_start = false + for _, chapter in ipairs(chapters) do + if type(chapter.title) == 'string' then + local lowercase_title = chapter.title:lower() + local is_end = false + local is_start = false - -- Is ending check and handling - if chapter_range.end_patterns then - for _, end_pattern in ipairs(chapter_range.end_patterns) do - is_end = is_end or lowercase_title:find(end_pattern) ~= nil - end + -- Is ending check and handling + if chapter_range.end_patterns then + for _, end_pattern in ipairs(chapter_range.end_patterns) do + is_end = is_end or lowercase_title:find(end_pattern) ~= + nil + end - if is_end then - if current_range == nil and uses_bof and not bof_used then - bof_used = true - start_range({time = 0}) - end - if current_range ~= nil then - end_range(chapter) - else - is_end = false - end - end - end + if is_end then + if current_range == nil and uses_bof and not bof_used then + bof_used = true + start_range({time = 0}) + end + if current_range ~= nil then + end_range(chapter) + else + is_end = false + end + end + end - -- Is start check and handling - for _, start_pattern in ipairs(chapter_range.start_patterns) do - is_start = is_start or lowercase_title:find(start_pattern) ~= nil - end + -- Is start check and handling + for _, start_pattern in ipairs(chapter_range.start_patterns) do + is_start = + is_start or lowercase_title:find(start_pattern) ~= nil + end - if is_start then start_range(chapter) end - end - end + if is_start then start_range(chapter) end + end + end - -- If there is an unfinished range and range type accepts eof, use it - if current_range ~= nil and uses_eof then - end_range({time = state.duration or infinity}) - end - end + -- If there is an unfinished range and range type accepts eof, use it + if current_range ~= nil and uses_eof then + end_range({time = state.duration or infinity}) + end + end - state.chapter_ranges = state.chapter_ranges or {} - state.chapter_ranges[#state.chapter_ranges + 1] = chapter_range + state.chapter_ranges = state.chapter_ranges or {} + state.chapter_ranges[#state.chapter_ranges + 1] = chapter_range - ::continue:: + ::continue:: end function parse_chapters() - -- Sometimes state.duration is not initialized yet for some reason - state.duration = mp.get_property_native('duration') + -- Sometimes state.duration is not initialized yet for some reason + state.duration = mp.get_property_native('duration') - local chapters = get_normalized_chapters() + local chapters = get_normalized_chapters() - if not chapters or not state.duration then return end + if not chapters or not state.duration then return end - -- Reset custom ranges - for _, chapter_range in ipairs(state.chapter_ranges or {}) do - chapter_range.serialize(chapters) - end + -- Reset custom ranges + for _, chapter_range in ipairs(state.chapter_ranges or {}) do + chapter_range.serialize(chapters) + end - -- Filter out chapters that were used as ranges - state.chapters = itable_remove(chapters, function(chapter) - return chapter._uosc_used_as_range_point == true - end) + -- Filter out chapters that were used as ranges + state.chapters = itable_remove(chapters, function(chapter) + return chapter._uosc_used_as_range_point == true + end) - request_render() + request_render() end -- CONTEXT MENU SERIALIZATION state.context_menu_items = (function() - local input_conf_path = mp.command_native({'expand-path', '~~/input.conf'}) - local input_conf_meta, meta_error = utils.file_info(input_conf_path) + local input_conf_path = mp.command_native({'expand-path', '~~/input.conf'}) + local input_conf_meta, meta_error = utils.file_info(input_conf_path) - -- File doesn't exist - if not input_conf_meta or not input_conf_meta.is_file then return end + -- File doesn't exist + if not input_conf_meta or not input_conf_meta.is_file then return end - local items = {} - local items_by_command = {} - local submenus_by_id = {} + local items = {} + local items_by_command = {} + local submenus_by_id = {} - for line in io.lines(input_conf_path) do - local key, command, title = string.match(line, ' *([%S]+) +(.*) #! *(.*)') - if key then - local is_dummy = key:sub(1, 1) == '#' - local submenu_id = '' - local target_menu = items - local title_parts = split(title or '', ' *> *') + for line in io.lines(input_conf_path) do + local key, command, title = string.match(line, + ' *([%S]+) +(.*) #! *(.*)') + if key then + local is_dummy = key:sub(1, 1) == '#' + local submenu_id = '' + local target_menu = items + local title_parts = split(title or '', ' *> *') - for index, title_part in ipairs(#title_parts > 0 and title_parts or {''}) do - if index < #title_parts then - submenu_id = submenu_id .. title_part + for index, title_part in ipairs( + #title_parts > 0 and title_parts or + {''}) do + if index < #title_parts then + submenu_id = submenu_id .. title_part - if not submenus_by_id[submenu_id] then - submenus_by_id[submenu_id] = {title = title_part, items = {}} - target_menu[#target_menu + 1] = submenus_by_id[submenu_id] - end + if not submenus_by_id[submenu_id] then + submenus_by_id[submenu_id] = + {title = title_part, items = {}} + target_menu[#target_menu + 1] = + submenus_by_id[submenu_id] + end - target_menu = submenus_by_id[submenu_id].items - else - -- If command is already in menu, just append the key to it - if items_by_command[command] then - items_by_command[command].hint = items_by_command[command].hint..', '..key - else - items_by_command[command] = { - title = title_part, - hint = not is_dummy and key or nil, - value = command - } - target_menu[#target_menu + 1] = items_by_command[command] - end - end - end - end - end + target_menu = submenus_by_id[submenu_id].items + else + -- If command is already in menu, just append the key to it + if items_by_command[command] then + items_by_command[command].hint = + items_by_command[command].hint .. ', ' .. key + else + items_by_command[command] = + { + title = title_part, + hint = not is_dummy and key or nil, + value = command + } + target_menu[#target_menu + 1] = + items_by_command[command] + end + end + end + end + end - if #items > 0 then return items end + if #items > 0 then return items end end)() -- EVENT HANDLERS function create_state_setter(name) - return function(_, value) - state[name] = value - dispatch_event_to_elements('prop_'..name, value) - request_render() - end + return function(_, value) + state[name] = value + dispatch_event_to_elements('prop_' .. name, value) + request_render() + end end function dispatch_event_to_elements(name, ...) - for _, element in pairs(elements) do - if element.proximity_raw == 0 then - element:maybe('on_'..name, ...) - end - element:maybe('on_global_'..name, ...) - end + for _, element in pairs(elements) do + if element.proximity_raw == 0 then + element:maybe('on_' .. name, ...) + end + element:maybe('on_global_' .. name, ...) + end end function create_event_to_elements_dispatcher(name, ...) - return function(...) dispatch_event_to_elements(name, ...) end + return function(...) dispatch_event_to_elements(name, ...) end end function handle_mouse_leave() - -- Slowly fadeout elements that are currently visible - for _, element_name in ipairs({'timeline', 'volume', 'top_bar'}) do - local element = elements[element_name] - if element and element.proximity > 0 then - element:tween_property('forced_proximity', element:get_effective_proximity(), 0, function() - element.forced_proximity = nil - end) - end - end + -- Slowly fadeout elements that are currently visible + for _, element_name in ipairs({'timeline', 'volume', 'top_bar'}) do + local element = elements[element_name] + if element and element.proximity > 0 then + element:tween_property('forced_proximity', + element:get_effective_proximity(), 0, + function() + element.forced_proximity = nil + end) + end + end - cursor.hidden = true - update_proximities() - dispatch_event_to_elements('mouse_leave') + cursor.hidden = true + update_proximities() + dispatch_event_to_elements('mouse_leave') end function handle_mouse_enter() - cursor.hidden = false - cursor.x, cursor.y = mp.get_mouse_pos() - tween_element_stop(state) - dispatch_event_to_elements('mouse_enter') + cursor.hidden = false + cursor.x, cursor.y = mp.get_mouse_pos() + tween_element_stop(state) + dispatch_event_to_elements('mouse_enter') end function handle_mouse_move() - -- Handle case when we are in cursor hidden state but not left the actual - -- window (i.e. when autohide simulates mouse_leave). - if cursor.hidden then - handle_mouse_enter() - return - end + -- Handle case when we are in cursor hidden state but not left the actual + -- window (i.e. when autohide simulates mouse_leave). + if cursor.hidden then + handle_mouse_enter() + return + end - cursor.x, cursor.y = mp.get_mouse_pos() - update_proximities() - dispatch_event_to_elements('mouse_move') - request_render() + cursor.x, cursor.y = mp.get_mouse_pos() + update_proximities() + dispatch_event_to_elements('mouse_move') + request_render() - -- Restart timer that hides UI when mouse is autohidden - if options.autohide then - state.cursor_autohide_timer:kill() - state.cursor_autohide_timer:resume() - end + -- Restart timer that hides UI when mouse is autohidden + if options.autohide then + state.cursor_autohide_timer:kill() + state.cursor_autohide_timer:resume() + end end function navigate_directory(direction) - local path = mp.get_property_native("path") + local path = mp.get_property_native("path") - if not path or is_protocol(path) then return end + if not path or is_protocol(path) then return end - local next_file = get_adjacent_file(path, direction, options.media_types) + local next_file = get_adjacent_file(path, direction, options.media_types) - if next_file then - mp.commandv("loadfile", utils.join_path(serialize_path(path).dirname, next_file)) - end + if next_file then + mp.commandv("loadfile", + utils.join_path(serialize_path(path).dirname, next_file)) + end end function load_file_in_current_directory(index) - local path = mp.get_property_native("path") + local path = mp.get_property_native("path") - if not path or is_protocol(path) then return end + if not path or is_protocol(path) then return end - local dirname = serialize_path(path).dirname - local files = get_files_in_directory(dirname, options.media_types) + local dirname = serialize_path(path).dirname + local files = get_files_in_directory(dirname, options.media_types) - if not files then return end - if index < 0 then index = #files + index + 1 end + if not files then return end + if index < 0 then index = #files + index + 1 end - if files[index] then - mp.commandv("loadfile", utils.join_path(dirname, files[index])) - end + if files[index] then + mp.commandv("loadfile", utils.join_path(dirname, files[index])) + end end -- MENUS -function create_select_tracklist_type_menu_opener(menu_title, track_type, track_prop) - return function() - if menu:is_open(track_type) then menu:close() return end +function create_select_tracklist_type_menu_opener(menu_title, track_type, + track_prop) + return function() + if menu:is_open(track_type) then + menu:close() + return + end - local items = {} - local active_item = nil + local items = {} + local active_item = nil - for index, track in ipairs(mp.get_property_native('track-list')) do - if track.type == track_type then - if track.selected then active_item = track.id end + for index, track in ipairs(mp.get_property_native('track-list')) do + if track.type == track_type then + if track.selected then active_item = track.id end - items[#items + 1] = { - title = (track.title and track.title or 'Track '..track.id), - hint = track.lang and track.lang:upper() or nil, - value = track.id - } - end - end + items[#items + 1] = { + title = (track.title and track.title or 'Track ' .. track.id), + hint = track.lang and track.lang:upper() or nil, + value = track.id + } + end + end - -- Add option to disable a subtitle track. This works for all tracks, - -- but why would anyone want to disable audio or video? Better to not - -- let people mistakenly select what is unwanted 99.999% of the time. - -- If I'm mistaken and there is an active need for this, feel free to - -- open an issue. - if track_type == 'sub' then - active_item = active_item and active_item + 1 or 1 - table.insert(items, 1, {hint = 'disabled', value = nil}) - end + -- Add option to disable a subtitle track. This works for all tracks, + -- but why would anyone want to disable audio or video? Better to not + -- let people mistakenly select what is unwanted 99.999% of the time. + -- If I'm mistaken and there is an active need for this, feel free to + -- open an issue. + if track_type == 'sub' then + active_item = active_item and active_item + 1 or 1 + table.insert(items, 1, {hint = 'disabled', value = nil}) + end - menu:open(items, function(id) - mp.commandv('set', track_prop, id and id or 'no') + menu:open(items, function(id) + mp.commandv('set', track_prop, id and id or 'no') - -- If subtitle track was selected, assume user also wants to see it - if id and track_type == 'sub' then - mp.commandv('set', 'sub-visibility', 'yes') - end + -- If subtitle track was selected, assume user also wants to see it + if id and track_type == 'sub' then + mp.commandv('set', 'sub-visibility', 'yes') + end - menu:close() - end, {type = track_type, title = menu_title, active_item = active_item}) - end + menu:close() + end, {type = track_type, title = menu_title, active_item = active_item}) + end end -- `menu_options`: @@ -2814,73 +3161,84 @@ end -- **active_path** - full path of a file to preselect -- Rest of the options are passed to `menu:open()` function open_file_navigation_menu(directory, handle_select, menu_options) - directory = serialize_path(directory) - local directories, error = utils.readdir(directory.path, 'dirs') - local files, error = get_files_in_directory(directory.path, menu_options.allowed_types) + directory = serialize_path(directory) + local directories, error = utils.readdir(directory.path, 'dirs') + local files, error = get_files_in_directory(directory.path, + menu_options.allowed_types) - if not files or not directories then - msg.error('Retrieving files from '..directory..' failed: '..(error or '')) - return - end + if not files or not directories then + msg.error('Retrieving files from ' .. directory .. ' failed: ' .. + (error or '')) + return + end - -- Files are already sorted - table.sort(directories, word_order_comparator) + -- Files are already sorted + table.sort(directories, word_order_comparator) - -- Pre-populate items with parent directory selector if not at root - local items = not directory.dirname and {} or { - {title = '..', hint = 'parent dir', value = directory.dirname} - } + -- Pre-populate items with parent directory selector if not at root + local items = not directory.dirname and {} or + { + {title = '..', hint = 'parent dir', value = directory.dirname} + } - for _, dir in ipairs(directories) do - local serialized = serialize_path(utils.join_path(directory.path, dir)) - items[#items + 1] = {title = serialized.basename, value = serialized.path, hint = '/'} - end + for _, dir in ipairs(directories) do + local serialized = serialize_path(utils.join_path(directory.path, dir)) + items[#items + 1] = { + title = serialized.basename, + value = serialized.path, + hint = '/' + } + end - menu_options.active_item = nil + menu_options.active_item = nil - for _, file in ipairs(files) do - local serialized = serialize_path(utils.join_path(directory.path, file)) - local item_index = #items + 1 + for _, file in ipairs(files) do + local serialized = serialize_path(utils.join_path(directory.path, file)) + local item_index = #items + 1 - items[item_index] = { - title = serialized.basename, - value = serialized.path, - } + items[item_index] = { + title = serialized.basename, + value = serialized.path + } - if menu_options.active_path == serialized.path then - menu_options.active_item = item_index - end - end + if menu_options.active_path == serialized.path then + menu_options.active_item = item_index + end + end - menu_options.title = directory.basename..'/' + menu_options.title = directory.basename .. '/' - menu:open(items, function(path) - local meta, error = utils.file_info(path) + menu:open(items, function(path) + local meta, error = utils.file_info(path) - if not meta then - msg.error('Retrieving file info for '..path..' failed: '..(error or '')) - return - end + if not meta then + msg.error('Retrieving file info for ' .. path .. ' failed: ' .. + (error or '')) + return + end - if meta.is_dir then - open_file_navigation_menu(path, handle_select, menu_options) - else - handle_select(path) - menu:close() - end - end, menu_options) + if meta.is_dir then + open_file_navigation_menu(path, handle_select, menu_options) + else + handle_select(path) + menu:close() + end + end, menu_options) end -- VALUE SERIALIZATION/NORMALIZATION -options.proximity_out = math.max(options.proximity_out, options.proximity_in + 1) -options.chapters = itable_find({'dots', 'lines', 'lines-top', 'lines-bottom'}, options.chapters) and options.chapters or 'none' +options.proximity_out = + math.max(options.proximity_out, options.proximity_in + 1) +options.chapters = itable_find({'dots', 'lines', 'lines-top', 'lines-bottom'}, + options.chapters) and options.chapters or 'none' options.media_types = split(options.media_types, ' *, *') options.subtitle_types = split(options.subtitle_types, ' *, *') options.timeline_cached_ranges = (function() - if options.timeline_cached_ranges == '' or options.timeline_cached_ranges == 'no' then return nil end - local parts = split(options.timeline_cached_ranges, ':') - return parts[1] and {color = parts[1], opacity = tonumber(parts[2])} or nil + if options.timeline_cached_ranges == '' or options.timeline_cached_ranges == + 'no' then return nil end + local parts = split(options.timeline_cached_ranges, ':') + return parts[1] and {color = parts[1], opacity = tonumber(parts[2])} or nil end)() -- HOOKS @@ -2897,60 +3255,61 @@ mp.observe_property('volume', 'number', create_state_setter('volume')) mp.observe_property('volume-max', 'number', create_state_setter('volume_max')) mp.observe_property('mute', 'bool', create_state_setter('mute')) mp.observe_property('playback-time', 'number', function(name, val) - -- Ignore the initial call with nil value - if val == nil then return end + -- Ignore the initial call with nil value + if val == nil then return end - state.position = val - state.elapsed_seconds = val - state.elapsed_time = state.elapsed_seconds and mp.format_time(state.elapsed_seconds) or nil - state.remaining_seconds = mp.get_property_native('playtime-remaining') - state.remaining_time = state.remaining_seconds and mp.format_time(state.remaining_seconds) or nil + state.position = val + state.elapsed_seconds = val + state.elapsed_time = state.elapsed_seconds and + mp.format_time(state.elapsed_seconds) or nil + state.remaining_seconds = mp.get_property_native('playtime-remaining') + state.remaining_time = state.remaining_seconds and + mp.format_time(state.remaining_seconds) or nil - request_render() + request_render() end) mp.observe_property('osd-dimensions', 'native', function(name, val) - update_display_dimensions() - request_render() + update_display_dimensions() + request_render() end) mp.observe_property('demuxer-cache-state', 'native', function(prop, cache_state) - if cache_state == nil then - state.cached_ranges = nil - return - end - local cache_ranges = cache_state['seekable-ranges'] - state.cached_ranges = #cache_ranges > 0 and cache_ranges or nil + if cache_state == nil then + state.cached_ranges = nil + return + end + local cache_ranges = cache_state['seekable-ranges'] + state.cached_ranges = #cache_ranges > 0 and cache_ranges or nil end) -- CONTROLS -- Mouse movement key binds local base_keybinds = { - {'mouse_move', handle_mouse_move}, - {'mouse_leave', handle_mouse_leave}, - {'mouse_enter', handle_mouse_enter}, + {'mouse_move', handle_mouse_move}, {'mouse_leave', handle_mouse_leave}, + {'mouse_enter', handle_mouse_enter} } if options.pause_on_click_shorter_than > 0 then - -- Cycles pause when click is shorter than `options.pause_on_click_shorter_than` - -- while filtering out double clicks. - local duration_seconds = options.pause_on_click_shorter_than / 1000 - local last_down_event; - local click_timer = mp.add_timeout(duration_seconds, function() - mp.command('cycle pause') - end); - click_timer:kill() - base_keybinds[#base_keybinds + 1] = {'mbtn_left', function() - if mp.get_time() - last_down_event < duration_seconds then - click_timer:resume() - end - end, function() - if click_timer:is_enabled() then - click_timer:kill() - last_down_event = 0 - else - last_down_event = mp.get_time() - end - end - } + -- Cycles pause when click is shorter than `options.pause_on_click_shorter_than` + -- while filtering out double clicks. + local duration_seconds = options.pause_on_click_shorter_than / 1000 + local last_down_event; + local click_timer = mp.add_timeout(duration_seconds, + function() mp.command('cycle pause') end); + click_timer:kill() + base_keybinds[#base_keybinds + 1] = { + 'mbtn_left', function() + if mp.get_time() - last_down_event < duration_seconds then + click_timer:resume() + end + end, function() + if click_timer:is_enabled() then + click_timer:kill() + last_down_event = 0 + else + last_down_event = mp.get_time() + end + end + } end mp.set_key_bindings(base_keybinds, 'mouse_movement', 'force') mp.enable_key_bindings('mouse_movement', 'allow-vo-dragging+allow-hide-cursor') @@ -2958,304 +3317,339 @@ mp.enable_key_bindings('mouse_movement', 'allow-vo-dragging+allow-hide-cursor') -- Context based key bind groups forced_key_bindings = (function() - mp.set_key_bindings({ - {'mbtn_left', create_event_to_elements_dispatcher('mbtn_left_up'), create_event_to_elements_dispatcher('mbtn_left_down')}, - {'mbtn_left_dbl', 'ignore'}, - }, 'mouse_buttons', 'force') - mp.set_key_bindings({ - {'wheel_up', create_event_to_elements_dispatcher('wheel_up')}, - {'wheel_down', create_event_to_elements_dispatcher('wheel_down')}, - }, 'wheel', 'force') + mp.set_key_bindings({ + { + 'mbtn_left', create_event_to_elements_dispatcher('mbtn_left_up'), + create_event_to_elements_dispatcher('mbtn_left_down') + }, {'mbtn_left_dbl', 'ignore'} + }, 'mouse_buttons', 'force') + mp.set_key_bindings({ + {'wheel_up', create_event_to_elements_dispatcher('wheel_up')}, + {'wheel_down', create_event_to_elements_dispatcher('wheel_down')} + }, 'wheel', 'force') - local groups = {} - for _, group in ipairs({'mouse_buttons', 'wheel'}) do - groups[group] = { - is_enabled = false, - enable = function(this) - if this.is_enabled then return end - this.is_enabled = true - mp.enable_key_bindings(group) - end, - disable = function(this) - if not this.is_enabled then return end - this.is_enabled = false - mp.disable_key_bindings(group) - end, - } - end - return groups + local groups = {} + for _, group in ipairs({'mouse_buttons', 'wheel'}) do + groups[group] = { + is_enabled = false, + enable = function(this) + if this.is_enabled then return end + this.is_enabled = true + mp.enable_key_bindings(group) + end, + disable = function(this) + if not this.is_enabled then return end + this.is_enabled = false + mp.disable_key_bindings(group) + end + } + end + return groups end)() -- KEY BINDABLE FEATURES mp.add_key_binding(nil, 'peek-timeline', function() - if elements.timeline.proximity > 0.5 then - elements.timeline:tween_property('proximity', elements.timeline.proximity, 0) - else - elements.timeline:tween_property('proximity', elements.timeline.proximity, 1) - end + if elements.timeline.proximity > 0.5 then + elements.timeline:tween_property('proximity', + elements.timeline.proximity, 0) + else + elements.timeline:tween_property('proximity', + elements.timeline.proximity, 1) + end end) mp.add_key_binding(nil, 'toggle-progress', function() - local timeline = elements.timeline - if timeline.size_min_override then - timeline:tween_property('size_min_override', timeline.size_min_override, timeline.size_min, function() - timeline.size_min_override = nil - end) - else - timeline:tween_property('size_min_override', timeline.size_min, 0) - end + local timeline = elements.timeline + if timeline.size_min_override then + timeline:tween_property('size_min_override', timeline.size_min_override, + timeline.size_min, + function() + timeline.size_min_override = nil + end) + else + timeline:tween_property('size_min_override', timeline.size_min, 0) + end end) mp.add_key_binding(nil, 'menu', function() - if menu:is_open('menu') then - menu:close() - elseif state.context_menu_items then - menu:open(state.context_menu_items, function(command) - mp.command(command) - end, {type = 'menu'}) - end + if menu:is_open('menu') then + menu:close() + elseif state.context_menu_items then + menu:open(state.context_menu_items, + function(command) mp.command(command) end, {type = 'menu'}) + end end) mp.add_key_binding(nil, 'load-subtitles', function() - if menu:is_open('load-subtitles') then menu:close() return end + if menu:is_open('load-subtitles') then + menu:close() + return + end - local path = mp.get_property_native('path') - if path and not is_protocol(path) then - open_file_navigation_menu( - serialize_path(path).dirname, - function(path) mp.commandv('sub-add', path) end, - { - type = 'load-subtitles', - allowed_types = options.subtitle_types - } - ) - end + local path = mp.get_property_native('path') + if path and not is_protocol(path) then + open_file_navigation_menu(serialize_path(path).dirname, function(path) + mp.commandv('sub-add', path) + end, {type = 'load-subtitles', allowed_types = options.subtitle_types}) + end end) -mp.add_key_binding(nil, 'subtitles', create_select_tracklist_type_menu_opener('Subtitles', 'sub', 'sid')) -mp.add_key_binding(nil, 'audio', create_select_tracklist_type_menu_opener('Audio', 'audio', 'aid')) -mp.add_key_binding(nil, 'video', create_select_tracklist_type_menu_opener('Video', 'video', 'vid')) +mp.add_key_binding(nil, 'subtitles', create_select_tracklist_type_menu_opener( + 'Subtitles', 'sub', 'sid')) +mp.add_key_binding(nil, 'audio', create_select_tracklist_type_menu_opener( + 'Audio', 'audio', 'aid')) +mp.add_key_binding(nil, 'video', create_select_tracklist_type_menu_opener( + 'Video', 'video', 'vid')) mp.add_key_binding(nil, 'playlist', function() - if menu:is_open('playlist') then menu:close() return end + if menu:is_open('playlist') then + menu:close() + return + end - function serialize_playlist() - local pos = mp.get_property_number('playlist-pos-1', 0) - local items = {} - local active_item - for index, item in ipairs(mp.get_property_native('playlist')) do - local is_url = item.filename:find('://') - items[index] = { - title = is_url and item.filename or serialize_path(item.filename).basename, - hint = tostring(index), - value = index - } + function serialize_playlist() + local pos = mp.get_property_number('playlist-pos-1', 0) + local items = {} + local active_item + for index, item in ipairs(mp.get_property_native('playlist')) do + local is_url = item.filename:find('://') + items[index] = { + title = is_url and item.filename or + serialize_path(item.filename).basename, + hint = tostring(index), + value = index + } - if index == pos then active_item = index end - end - return items, active_item - end + if index == pos then active_item = index end + end + return items, active_item + end - -- Update active index and playlist content on playlist changes - function handle_playlist_change() - if menu:is_open('playlist') then - local items, active_item = serialize_playlist() - elements.menu:set_items(items, { - active_item = active_item, - selected_item = active_item - }) - end - end + -- Update active index and playlist content on playlist changes + function handle_playlist_change() + if menu:is_open('playlist') then + local items, active_item = serialize_playlist() + elements.menu:set_items(items, { + active_item = active_item, + selected_item = active_item + }) + end + end - local items, active_item = serialize_playlist() + local items, active_item = serialize_playlist() - menu:open(items, function(index) - mp.commandv('set', 'playlist-pos-1', tostring(index)) - end, { - type = 'playlist', - title = 'Playlist', - active_item = active_item, - on_open = function() - mp.observe_property('playlist', 'native', handle_playlist_change) - mp.observe_property('playlist-pos-1', 'native', handle_playlist_change) - end, - on_close = function() - mp.unobserve_property(handle_playlist_change) - end, - }) + menu:open(items, function(index) + mp.commandv('set', 'playlist-pos-1', tostring(index)) + end, { + type = 'playlist', + title = 'Playlist', + active_item = active_item, + on_open = function() + mp.observe_property('playlist', 'native', handle_playlist_change) + mp.observe_property('playlist-pos-1', 'native', + handle_playlist_change) + end, + on_close = function() + mp.unobserve_property(handle_playlist_change) + end + }) end) mp.add_key_binding(nil, 'chapters', function() - if menu:is_open('chapters') then menu:close() return end + if menu:is_open('chapters') then + menu:close() + return + end - local items = {} - local chapters = get_normalized_chapters() + local items = {} + local chapters = get_normalized_chapters() - for index, chapter in ipairs(chapters) do - items[#items + 1] = { - title = chapter.title or '', - hint = mp.format_time(chapter.time), - value = chapter.time - } - end + for index, chapter in ipairs(chapters) do + items[#items + 1] = { + title = chapter.title or '', + hint = mp.format_time(chapter.time), + value = chapter.time + } + end - -- Select first chapter from the end with time lower - -- than current playing position (with 100ms leeway). - function get_selected_chapter_index() - local position = mp.get_property_native('playback-time') - if not position then return nil end - for index = #items, 1, -1 do - if position - 0.1 > items[index].value then return index end - end - end + -- Select first chapter from the end with time lower + -- than current playing position (with 100ms leeway). + function get_selected_chapter_index() + local position = mp.get_property_native('playback-time') + if not position then return nil end + for index = #items, 1, -1 do + if position - 0.1 > items[index].value then return index end + end + end - -- Update selected chapter in chapter navigation menu - function seek_handler() - if menu:is_open('chapters') then - elements.menu:activate_index(get_selected_chapter_index()) - end - end + -- Update selected chapter in chapter navigation menu + function seek_handler() + if menu:is_open('chapters') then + elements.menu:activate_index(get_selected_chapter_index()) + end + end - menu:open(items, function(time) - mp.commandv('seek', tostring(time), 'absolute') - end, { - type = 'chapters', - title = 'Chapters', - active_item = get_selected_chapter_index(), - on_open = function() mp.register_event('seek', seek_handler) end, - on_close = function() mp.unregister_event(seek_handler) end - }) + menu:open(items, function(time) + mp.commandv('seek', tostring(time), 'absolute') + end, { + type = 'chapters', + title = 'Chapters', + active_item = get_selected_chapter_index(), + on_open = function() mp.register_event('seek', seek_handler) end, + on_close = function() mp.unregister_event(seek_handler) end + }) end) mp.add_key_binding(nil, 'show-in-directory', function() - local path = mp.get_property_native('path') + local path = mp.get_property_native('path') - -- Ignore URLs - if not path or is_protocol(path) then return end + -- Ignore URLs + if not path or is_protocol(path) then return end - path = normalize_path(path) + path = normalize_path(path) - if state.os == 'windows' then - utils.subprocess_detached({args = {'explorer', '/select,', path}, cancellable = false}) - elseif state.os == 'macos' then - utils.subprocess_detached({args = {'open', '-R', path}, cancellable = false}) - elseif state.os == 'linux' then - local result = utils.subprocess({args = {'nautilus', path}, cancellable = false}) + if state.os == 'windows' then + utils.subprocess_detached({ + args = {'explorer', '/select,', path}, + cancellable = false + }) + elseif state.os == 'macos' then + utils.subprocess_detached({ + args = {'open', '-R', path}, + cancellable = false + }) + elseif state.os == 'linux' then + local result = utils.subprocess({ + args = {'nautilus', path}, + cancellable = false + }) - -- Fallback opens the folder with xdg-open instead - if result.status ~= 0 then - utils.subprocess({args = {'xdg-open', serialize_path(path).dirname}, cancellable = false}) - end - end + -- Fallback opens the folder with xdg-open instead + if result.status ~= 0 then + utils.subprocess({ + args = {'xdg-open', serialize_path(path).dirname}, + cancellable = false + }) + end + end end) mp.add_key_binding(nil, 'open-file', function() - if menu:is_open('open-file') then menu:close() return end + if menu:is_open('open-file') then + menu:close() + return + end - local path = mp.get_property_native('path') - local directory - local active_file + local path = mp.get_property_native('path') + local directory + local active_file - if path == nil or is_protocol(path) then - local path = serialize_path(mp.command_native({'expand-path', '~/'})) - directory = path.path - active_file = nil - else - local path = serialize_path(path) - directory = path.dirname - active_file = path.path - end + if path == nil or is_protocol(path) then + local path = serialize_path(mp.command_native({'expand-path', '~/'})) + directory = path.path + active_file = nil + else + local path = serialize_path(path) + directory = path.dirname + active_file = path.path + end - -- Update selected file in directory navigation menu - function handle_file_loaded() - if menu:is_open('open-file') then - local path = normalize_path(mp.get_property_native('path')) - elements.menu:activate_value(path) - elements.menu:select_value(path) - end - end + -- Update selected file in directory navigation menu + function handle_file_loaded() + if menu:is_open('open-file') then + local path = normalize_path(mp.get_property_native('path')) + elements.menu:activate_value(path) + elements.menu:select_value(path) + end + end - open_file_navigation_menu( - directory, - function(path) mp.commandv('loadfile', path) end, - { - type = 'open-file', - allowed_types = options.media_types, - active_path = active_file, - on_open = function() mp.register_event('file-loaded', handle_file_loaded) end, - on_close = function() mp.unregister_event(handle_file_loaded) end, - } - ) + open_file_navigation_menu(directory, + function(path) mp.commandv('loadfile', path) end, + { + type = 'open-file', + allowed_types = options.media_types, + active_path = active_file, + on_open = function() + mp.register_event('file-loaded', handle_file_loaded) + end, + on_close = function() mp.unregister_event(handle_file_loaded) end + }) end) mp.add_key_binding(nil, 'next', function() - if mp.get_property_native('playlist-count') > 1 then - mp.command('playlist-next') - else - navigate_directory('forward') - end + if mp.get_property_native('playlist-count') > 1 then + mp.command('playlist-next') + else + navigate_directory('forward') + end end) mp.add_key_binding(nil, 'prev', function() - if mp.get_property_native('playlist-count') > 1 then - mp.command('playlist-prev') - else - navigate_directory('backward') - end + if mp.get_property_native('playlist-count') > 1 then + mp.command('playlist-prev') + else + navigate_directory('backward') + end end) -mp.add_key_binding(nil, 'next-file', function() navigate_directory('forward') end) -mp.add_key_binding(nil, 'prev-file', function() navigate_directory('backward') end) +mp.add_key_binding(nil, 'next-file', + function() navigate_directory('forward') end) +mp.add_key_binding(nil, 'prev-file', + function() navigate_directory('backward') end) mp.add_key_binding(nil, 'first', function() - if mp.get_property_native('playlist-count') > 1 then - mp.commandv('set', 'playlist-pos-1', '1') - else - load_file_in_current_directory(1) - end + if mp.get_property_native('playlist-count') > 1 then + mp.commandv('set', 'playlist-pos-1', '1') + else + load_file_in_current_directory(1) + end end) mp.add_key_binding(nil, 'last', function() - local playlist_count = mp.get_property_native('playlist-count') - if playlist_count > 1 then - mp.commandv('set', 'playlist-pos-1', tostring(playlist_count)) - else - load_file_in_current_directory(-1) - end + local playlist_count = mp.get_property_native('playlist-count') + if playlist_count > 1 then + mp.commandv('set', 'playlist-pos-1', tostring(playlist_count)) + else + load_file_in_current_directory(-1) + end end) -mp.add_key_binding(nil, 'first-file', function() load_file_in_current_directory(1) end) -mp.add_key_binding(nil, 'last-file', function() load_file_in_current_directory(-1) end) +mp.add_key_binding(nil, 'first-file', + function() load_file_in_current_directory(1) end) +mp.add_key_binding(nil, 'last-file', + function() load_file_in_current_directory(-1) end) mp.add_key_binding(nil, 'delete-file-next', function() - local path = mp.get_property_native('path') + local path = mp.get_property_native('path') - if not path or is_protocol(path) then return end + if not path or is_protocol(path) then return end - path = normalize_path(path) - local playlist_count = mp.get_property_native('playlist-count') + path = normalize_path(path) + local playlist_count = mp.get_property_native('playlist-count') - if playlist_count > 1 then - mp.commandv('playlist-remove', 'current') - else - local next_file = get_adjacent_file(path, 'forward', options.media_types) + if playlist_count > 1 then + mp.commandv('playlist-remove', 'current') + else + local next_file = + get_adjacent_file(path, 'forward', options.media_types) - if menu:is_open('open-file') then - elements.menu:delete_value(path) - end + if menu:is_open('open-file') then + elements.menu:delete_value(path) + end - if next_file then - mp.commandv('loadfile', next_file) - else - mp.commandv('stop') - end - end + if next_file then + mp.commandv('loadfile', next_file) + else + mp.commandv('stop') + end + end - os.remove(path) + os.remove(path) end) mp.add_key_binding(nil, 'delete-file-quit', function() - local path = mp.get_property_native('path') - if not path or is_protocol(path) then return end - os.remove(normalize_path(path)) - mp.command('quit') + local path = mp.get_property_native('path') + if not path or is_protocol(path) then return end + os.remove(normalize_path(path)) + mp.command('quit') end) mp.add_key_binding(nil, 'open-config-directory', function() - local config = serialize_path(mp.command_native({'expand-path', '~~/mpv.conf'})) - local args + local config = serialize_path(mp.command_native( + {'expand-path', '~~/mpv.conf'})) + local args - if state.os == 'windows' then - args = {'explorer', '/select,', config.path} - elseif state.os == 'macos' then - args = {'open', '-R', config.path} - elseif state.os == 'linux' then - args = {'xdg-open', config.dirname} - end + if state.os == 'windows' then + args = {'explorer', '/select,', config.path} + elseif state.os == 'macos' then + args = {'open', '-R', config.path} + elseif state.os == 'linux' then + args = {'xdg-open', config.dirname} + end - utils.subprocess_detached({args = args, cancellable = false}) + utils.subprocess_detached({args = args, cancellable = false}) end) diff --git a/nvim/.config/nvim/lua/gitlens.lua b/nvim/.config/nvim/lua/gitlens.lua index bdd715d..873bc71 100644 --- a/nvim/.config/nvim/lua/gitlens.lua +++ b/nvim/.config/nvim/lua/gitlens.lua @@ -3,40 +3,42 @@ local api = vim.api function M.blameVirtText() - -- get the current file extension - local ft = vim.fn.expand('%:h:t') + -- get the current file extension + local ft = vim.fn.expand('%:h:t') - if ft == '' or ft == 'bin' then -- if we are in a scratch buffer, unknown filetype, or nvim's terminal window - return - end - - M.clearBlameVirtText() - - local currFile = vim.fn.expand('%') - local line = api.nvim_win_get_cursor(0) - local blame = vim.fn.system(string.format('git blame -c -L %d,%d %s', line[1], line[1], currFile)) - local hash = vim.split(blame, '%s')[1] - local cmd = string.format("git show %s ", hash).."--format='%an | %ar | %s'" - - if hash == '00000000' then - text = 'Not Committed Yet' - else - text = vim.fn.system(cmd) - text = vim.split(text, '\n')[1] - if text:find("fatal") then -- if the call to git show fails - text = 'Not Committed Yet' + if ft == '' or ft == 'bin' then -- if we are in a scratch buffer, unknown filetype, or nvim's terminal window + return end - end - -- set virtual text for namespace 2 with the content from git and assign it to the higlight group 'GitLens' - api.nvim_buf_set_virtual_text(0, 2, line[1] - 1, {{ text,'GitLens' }}, {}) + M.clearBlameVirtText() + + local currFile = vim.fn.expand('%') + local line = api.nvim_win_get_cursor(0) + local blame = vim.fn.system(string.format('git blame -c -L %d,%d %s', + line[1], line[1], currFile)) + local hash = vim.split(blame, '%s')[1] + local cmd = string.format("git show %s ", hash) .. + "--format='%an | %ar | %s'" + + if hash == '00000000' then + text = 'Not Committed Yet' + else + text = vim.fn.system(cmd) + text = vim.split(text, '\n')[1] + if text:find("fatal") then -- if the call to git show fails + text = 'Not Committed Yet' + end + end + + -- set virtual text for namespace 2 with the content from git and assign it to the higlight group 'GitLens' + api.nvim_buf_set_virtual_text(0, 2, line[1] - 1, {{text, 'GitLens'}}, {}) end -- important for clearing out the text when our cursor moves function M.clearBlameVirtText() - -- clear out virtual text from namespace 2 (the namespace we will set later) - api.nvim_buf_clear_namespace(0, 2, 0, -1) + -- clear out virtual text from namespace 2 (the namespace we will set later) + api.nvim_buf_clear_namespace(0, 2, 0, -1) end return M diff --git a/nvim/.config/nvim/lua/nvim-lspconfig/init.lua b/nvim/.config/nvim/lua/nvim-lspconfig/init.lua index 6980412..0bc82a1 100644 --- a/nvim/.config/nvim/lua/nvim-lspconfig/init.lua +++ b/nvim/.config/nvim/lua/nvim-lspconfig/init.lua @@ -1,31 +1,43 @@ - local on_attach = function(client, bufnr) - -- Keybindings for LSPs - -- Note these are in on_attach so that they don't override bindings in a non-LSP setting - vim.fn.nvim_set_keymap("n", "gd", "lua vim.lsp.buf.definition()", {noremap = true, silent = true}) - vim.fn.nvim_set_keymap("n", "gD", "lua vim.lsp.buf.implementation()", {noremap = true, silent = true}) - vim.fn.nvim_set_keymap("n", "K", "lua vim.lsp.buf.hover()", {noremap = true, silent = true}) - vim.fn.nvim_set_keymap("n", "gK", "lua vim.lsp.buf.signature_help()", {noremap = true, silent = true}) - vim.fn.nvim_set_keymap("n", "1gD", "lua vim.lsp.buf.type_definition()", {noremap = true, silent = true}) - vim.fn.nvim_set_keymap("n", "gr", "lua vim.lsp.buf.references()", {noremap = true, silent = true}) - vim.fn.nvim_set_keymap("n", "g0", "lua vim.lsp.buf.document_symbol()", {noremap = true, silent = true}) - vim.fn.nvim_set_keymap("n", "gW", "lua vim.lsp.buf.workspace_symbol()", {noremap = true, silent = true}) + -- Keybindings for LSPs + -- Note these are in on_attach so that they don't override bindings in a non-LSP setting + vim.fn.nvim_set_keymap("n", "gd", "lua vim.lsp.buf.definition()", + {noremap = true, silent = true}) + vim.fn.nvim_set_keymap("n", "gD", + "lua vim.lsp.buf.implementation()", + {noremap = true, silent = true}) + vim.fn.nvim_set_keymap("n", "K", "lua vim.lsp.buf.hover()", + {noremap = true, silent = true}) + vim.fn.nvim_set_keymap("n", "gK", + "lua vim.lsp.buf.signature_help()", + {noremap = true, silent = true}) + vim.fn.nvim_set_keymap("n", "1gD", + "lua vim.lsp.buf.type_definition()", + {noremap = true, silent = true}) + vim.fn.nvim_set_keymap("n", "gr", "lua vim.lsp.buf.references()", + {noremap = true, silent = true}) + vim.fn.nvim_set_keymap("n", "g0", + "lua vim.lsp.buf.document_symbol()", + {noremap = true, silent = true}) + vim.fn.nvim_set_keymap("n", "gW", + "lua vim.lsp.buf.workspace_symbol()", + {noremap = true, silent = true}) end -require'nvim_lsp'.pyls.setup{on_attach=on_attach} -require'nvim_lsp'.vimls.setup{on_attach=on_attach} -require'nvim_lsp'.bashls.setup{on_attach=on_attach} -require'nvim_lsp'.gopls.setup{on_attach=on_attach} -require'nvim_lsp'.texlab.setup{on_attach=on_attach} +require'nvim_lsp'.pyls.setup {on_attach = on_attach} +require'nvim_lsp'.vimls.setup {on_attach = on_attach} +require'nvim_lsp'.bashls.setup {on_attach = on_attach} +require'nvim_lsp'.gopls.setup {on_attach = on_attach} +require'nvim_lsp'.texlab.setup {on_attach = on_attach} -- To get builtin LSP running, do something like: -- NOTE: This replaces the calls where you would have before done `require('nvim_lsp').sumneko_lua.setup()` require('nlua.lsp.nvim').setup(require('nvim_lsp'), { - on_attach = on_attach, + on_attach = on_attach, - -- Include globals you want to tell the LSP are real :) - globals = { - -- Colorbuddy - "Color", "c", "Group", "g", "s", - } + -- Include globals you want to tell the LSP are real :) + globals = { + -- Colorbuddy + "Color", "c", "Group", "g", "s" + } }) diff --git a/nvim/.config/nvim/lua/pandoc_complete.lua b/nvim/.config/nvim/lua/pandoc_complete.lua index 77b96ac..689a9bc 100644 --- a/nvim/.config/nvim/lua/pandoc_complete.lua +++ b/nvim/.config/nvim/lua/pandoc_complete.lua @@ -1,13 +1,12 @@ local M = {} function M.getCompletionItems(prefix) - -- define your total completion items - local items = vim.api.nvim_call_function('pandoc#completion#Complete',{0, prefix}) - return items + -- define your total completion items + local items = vim.api.nvim_call_function('pandoc#completion#Complete', + {0, prefix}) + return items end -M.complete_item = { - item = M.getCompletionItems -} +M.complete_item = {item = M.getCompletionItems} return M diff --git a/nvim/.config/nvim/lua/scratchpad.lua b/nvim/.config/nvim/lua/scratchpad.lua index 6c7e3da..8390c16 100644 --- a/nvim/.config/nvim/lua/scratchpad.lua +++ b/nvim/.config/nvim/lua/scratchpad.lua @@ -9,32 +9,34 @@ window, otherwise opens a new split. The buffer, by default is set to the pandoc filetype. This can be changed by setting the `g:scratchpad_ft` variable or the `b:scratchpad_ft` variable to the intended filetype. -]]-- +]] -- local api = vim.api -local M= {} +local M = {} -local function isempty(s) - return s == nil or s == '' -end +local function isempty(s) return s == nil or s == '' end function M.create(split, ft) - -- should we create a new split or switch out the current buffer? - if isempty(split) then split = false else split = true end - -- which filetype to set for the scratchpad, defaults to pandoc - if isempty(ft) then ft = vim.b["scratchpad_ft"] or vim.g["scratchpad_ft"] or "pandoc" end + -- should we create a new split or switch out the current buffer? + if isempty(split) then + split = false + else + split = true + end + -- which filetype to set for the scratchpad, defaults to pandoc + if isempty(ft) then + ft = vim.b["scratchpad_ft"] or vim.g["scratchpad_ft"] or "pandoc" + end - local buf = api.nvim_create_buf(false, true) - if buf == 0 then - print("Error opening scratch buffer.") - end - api.nvim_buf_set_option(buf, "bufhidden", "hide") - api.nvim_buf_set_option(buf, "buftype", "nofile") - api.nvim_buf_set_option(buf, "swapfile",false) - api.nvim_buf_set_option(buf, "filetype",ft) + local buf = api.nvim_create_buf(false, true) + if buf == 0 then print("Error opening scratch buffer.") end + api.nvim_buf_set_option(buf, "bufhidden", "hide") + api.nvim_buf_set_option(buf, "buftype", "nofile") + api.nvim_buf_set_option(buf, "swapfile", false) + api.nvim_buf_set_option(buf, "filetype", ft) - if split then api.nvim_command('vsplit new') end -- i think this is the only way to interact with the buffers creating a new split - -- switch to scratchpad - api.nvim_win_set_buf(0, buf) + if split then api.nvim_command('vsplit new') end -- i think this is the only way to interact with the buffers creating a new split + -- switch to scratchpad + api.nvim_win_set_buf(0, buf) end return M diff --git a/nvim/.config/nvim/watch.lua b/nvim/.config/nvim/watch.lua index 4c82035..ff6ca01 100644 --- a/nvim/.config/nvim/watch.lua +++ b/nvim/.config/nvim/watch.lua @@ -1,17 +1,15 @@ local w = vim.loop.new_fs_event() local function on_change(err, fname, status) - -- Do work... - vim.api.nvim_command('checktime') - -- Debounce: stop/start. - w:stop() - watch_file(fname) + -- Do work... + vim.api.nvim_command('checktime') + -- Debounce: stop/start. + w:stop() + watch_file(fname) end function watch_file(fname) - local fullpath = vim.api.nvim_call_function( - 'fnamemodify', {fname, ':p'}) - w:start(fullpath, {}, vim.schedule_wrap(function(...) - on_change(...) end)) + local fullpath = vim.api.nvim_call_function('fnamemodify', {fname, ':p'}) + w:start(fullpath, {}, vim.schedule_wrap(function(...) on_change(...) end)) end vim.api.nvim_command( - "command! -nargs=1 Watch call luaeval('watch_file(_A)', expand(''))") + "command! -nargs=1 Watch call luaeval('watch_file(_A)', expand(''))")