1
0
mirror of https://github.com/mpv-player/mpv synced 2025-04-04 15:34:31 +00:00
mpv/TOOLS/lua/autoload.lua
Eva 3ba446d0b0 TOOLS/lua/autoload: avoid unnecessary playlist manipulation, performance
We used to sort the playlist with playlist-move after every loadfile.
Instead, append all files in order and call playlist-move once to move
the only entry we don't control the position of.
Don't fetch every playlist item separately, reuse native property.
We used to pick up on new files added to the directory, but only when
playing an entry at the edge of the playlist due to an early return.
New files are now added to the playlist on every file change.
This still works as expected and doesn't load duplicate files on
shuffled playlists or playlists with files manually added after autoload

33% faster on average for my test directory with 1371 files.
2023-07-03 02:41:58 +00:00

231 lines
6.6 KiB
Lua

-- This script automatically loads playlist entries before and after the
-- the currently played file. It does so by scanning the directory a file is
-- located in when starting playback. It sorts the directory entries
-- alphabetically, and adds entries before and after the current file to
-- the internal playlist. (It stops if it would add an already existing
-- playlist entry at the same position - this makes it "stable".)
-- Add at most 5000 * 2 files when starting a file (before + after).
--[[
To configure this script use file autoload.conf in directory script-opts (the "script-opts"
directory must be in the mpv configuration directory, typically ~/.config/mpv/).
Example configuration would be:
disabled=no
images=no
videos=yes
audio=yes
ignore_hidden=yes
--]]
MAXENTRIES = 5000
local msg = require 'mp.msg'
local options = require 'mp.options'
local utils = require 'mp.utils'
o = {
disabled = false,
images = true,
videos = true,
audio = true,
ignore_hidden = true
}
options.read_options(o)
function Set (t)
local set = {}
for _, v in pairs(t) do set[v] = true end
return set
end
function SetUnion (a,b)
local res = {}
for k in pairs(a) do res[k] = true end
for k in pairs(b) do res[k] = true end
return res
end
EXTENSIONS_VIDEO = Set {
'3g2', '3gp', 'avi', 'flv', 'm2ts', 'm4v', 'mj2', 'mkv', 'mov',
'mp4', 'mpeg', 'mpg', 'ogv', 'rmvb', 'webm', 'wmv', 'y4m'
}
EXTENSIONS_AUDIO = Set {
'aiff', 'ape', 'au', 'flac', 'm4a', 'mka', 'mp3', 'oga', 'ogg',
'ogm', 'opus', 'wav', 'wma'
}
EXTENSIONS_IMAGES = Set {
'avif', 'bmp', 'gif', 'j2k', 'jp2', 'jpeg', 'jpg', 'jxl', 'png',
'svg', 'tga', 'tif', 'tiff', 'webp'
}
EXTENSIONS = Set {}
if o.videos then EXTENSIONS = SetUnion(EXTENSIONS, EXTENSIONS_VIDEO) end
if o.audio then EXTENSIONS = SetUnion(EXTENSIONS, EXTENSIONS_AUDIO) end
if o.images then EXTENSIONS = SetUnion(EXTENSIONS, EXTENSIONS_IMAGES) end
function add_files(files)
local oldcount = mp.get_property_number("playlist-count", 1)
for i = 1, #files do
mp.commandv("loadfile", files[i][1], "append")
mp.commandv("playlist-move", oldcount + i - 1, files[i][2])
end
end
function get_extension(path)
match = string.match(path, "%.([^%.]+)$" )
if match == nil then
return "nomatch"
else
return match
end
end
table.filter = function(t, iter)
for i = #t, 1, -1 do
if not iter(t[i]) then
table.remove(t, i)
end
end
end
-- alphanum sorting for humans in Lua
-- http://notebook.kulchenko.com/algorithms/alphanumeric-natural-sorting-for-humans-in-lua
function alphanumsort(filenames)
local function padnum(n, d)
return #d > 0 and ("%03d%s%.12f"):format(#n, n, tonumber(d) / (10 ^ #d))
or ("%03d%s"):format(#n, n)
end
local tuples = {}
for i, f in ipairs(filenames) do
tuples[i] = {f:lower():gsub("0*(%d+)%.?(%d*)", padnum), f}
end
table.sort(tuples, function(a, b)
return a[1] == b[1] and #b[2] < #a[2] or a[1] < b[1]
end)
for i, tuple in ipairs(tuples) do filenames[i] = tuple[2] end
return filenames
end
local autoloaded = nil
function get_playlist_filenames(playlist)
local filenames = {}
for i = 1, #playlist do
local _, file = utils.split_path(playlist[i].filename)
filenames[file] = true
end
return filenames
end
function find_and_add_entries()
local path = mp.get_property("path", "")
local dir, filename = utils.split_path(path)
msg.trace(("dir: %s, filename: %s"):format(dir, filename))
if o.disabled then
msg.verbose("stopping: autoload disabled")
return
elseif #dir == 0 then
msg.verbose("stopping: not a local path")
return
end
pl_count = mp.get_property_number("playlist-count", 1)
-- check if this is a manually made playlist
if (pl_count > 1 and autoloaded == nil) or
(pl_count == 1 and EXTENSIONS[string.lower(get_extension(filename))] == nil) then
msg.verbose("stopping: manually made playlist")
return
else
autoloaded = true
end
local pl = mp.get_property_native("playlist", {})
local pl_current = mp.get_property_number("playlist-pos-1", 1)
msg.trace(("playlist-pos-1: %s, playlist: %s"):format(pl_current,
utils.to_string(pl)))
local files = utils.readdir(dir, "files")
if files == nil then
msg.verbose("no other files in directory")
return
end
table.filter(files, function (v, k)
-- The current file could be a hidden file, ignoring it doesn't load other
-- files from the current directory.
if (o.ignore_hidden and not (v == filename) and string.match(v, "^%.")) then
return false
end
local ext = get_extension(v)
if ext == nil then
return false
end
return EXTENSIONS[string.lower(ext)]
end)
alphanumsort(files)
if dir == "." then
dir = ""
end
-- Find the current pl entry (dir+"/"+filename) in the sorted dir list
local current
for i = 1, #files do
if files[i] == filename then
current = i
break
end
end
if current == nil then
return
end
msg.trace("current file position in files: "..current)
local append = {[-1] = {}, [1] = {}}
local filenames = get_playlist_filenames(pl)
for direction = -1, 1, 2 do -- 2 iterations, with direction = -1 and +1
for i = 1, MAXENTRIES do
local pos = current + i * direction
local file = files[pos]
if file == nil or file[1] == "." then
break
end
local filepath = dir .. file
-- skip files already in playlist
if not filenames[file] then
if direction == -1 then
msg.info("Prepending " .. file)
table.insert(append[-1], 1, {filepath, pos - 1})
else
msg.info("Adding " .. file)
if pl_count > 1 then
table.insert(append[1], {filepath, pos - 1})
else
mp.commandv("loadfile", filepath, "append")
end
end
end
end
if pl_count == 1 and direction == -1 and #append[-1] > 0 then
for i = 1, #append[-1] do
mp.commandv("loadfile", append[-1][i][1], "append")
end
mp.commandv("playlist-move", 0, current)
end
end
if pl_count > 1 then
add_files(append[1])
add_files(append[-1])
end
end
mp.register_event("start-file", find_and_add_entries)