1
0
mirror of https://github.com/mpv-player/mpv synced 2024-12-28 01:52:19 +00:00
mpv/TOOLS/lua/autoload.lua
Christoph Heinrich 7b09bf7ffc TOOLS/lua/autoload: improve alphanumeric sorting
Currently filenames like `EP.1.v0.1080p.mp4` do not get sorted correctly
(e.g. episode 11 right after episode 1). That is caused by the `.` in
front of the episode number, making it get sorted as if it were
decimals.

The solution is to match the whole real number or integer instead of
matching the integer part and the fractional part separately.

This will regress sorting of numbers with multiple commas where the
length of the individual segments differs between filenames.
Since those are rather uncommon, that is unlikely to be a problem (for
anyone ever).
2023-01-15 16:45:24 +00:00

222 lines
6.3 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_at(index, files)
index = index - 1
local oldcount = mp.get_property_number("playlist-count", 1)
for i = 1, #files do
mp.commandv("loadfile", files[i], "append")
mp.commandv("playlist-move", oldcount + i - 1, index + i - 1)
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()
local filenames = {}
for n = 0, pl_count - 1, 1 do
local filename = mp.get_property('playlist/'..n..'/filename')
local _, file = utils.split_path(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()
for direction = -1, 1, 2 do -- 2 iterations, with direction = -1 and +1
for i = 1, MAXENTRIES do
local file = files[current + i * direction]
if file == nil or file[1] == "." then
break
end
local filepath = dir .. file
-- skip files already in playlist
if filenames[file] then break end
if direction == -1 then
if pl_current == 1 then -- never add additional entries in the middle
msg.info("Prepending " .. file)
table.insert(append[-1], 1, filepath)
end
else
msg.info("Adding " .. file)
table.insert(append[1], filepath)
end
end
end
add_files_at(pl_current + 1, append[1])
add_files_at(pl_current, append[-1])
end
mp.register_event("start-file", find_and_add_entries)