Under the current file loading logic, a video file and an external audio track next to it will both be added to the playlist, which most users don't wish to happen. Having an option that tells the script to load only one type of files (video, audio, or image) can avoid this problem. It may also come in handy for people who have different types of files mixed in a folder but wish to consume only one type of media at a time.
262 lines
7.6 KiB
Lua
262 lines
7.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
|
|
additional_image_exts=list,of,ext
|
|
additional_video_exts=list,of,ext
|
|
additional_audio_exts=list,of,ext
|
|
ignore_hidden=yes
|
|
same_type=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,
|
|
additional_image_exts = "",
|
|
additional_video_exts = "",
|
|
additional_audio_exts = "",
|
|
ignore_hidden = true,
|
|
same_type = false
|
|
}
|
|
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
|
|
|
|
function Split (s)
|
|
local set = {}
|
|
for v in string.gmatch(s, '([^,]+)') do set[v] = true end
|
|
return set
|
|
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_VIDEO = SetUnion(EXTENSIONS_VIDEO, Split(o.additional_video_exts))
|
|
EXTENSIONS_AUDIO = SetUnion(EXTENSIONS_AUDIO, Split(o.additional_audio_exts))
|
|
EXTENSIONS_IMAGES = SetUnion(EXTENSIONS_IMAGES, Split(o.additional_image_exts))
|
|
|
|
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)
|
|
this_ext = get_extension(filename)
|
|
-- check if this is a manually made playlist
|
|
if (pl_count > 1 and autoloaded == nil) or
|
|
(pl_count == 1 and EXTENSIONS[string.lower(this_ext)] == nil) then
|
|
msg.verbose("stopping: manually made playlist")
|
|
return
|
|
else
|
|
autoloaded = true
|
|
end
|
|
|
|
if o.same_type then
|
|
if EXTENSIONS_VIDEO[string.lower(this_ext)] ~= nil then
|
|
EXTENSIONS_TARGET = EXTENSIONS_VIDEO
|
|
elseif EXTENSIONS_AUDIO[string.lower(this_ext)] ~= nil then
|
|
EXTENSIONS_TARGET = EXTENSIONS_AUDIO
|
|
else
|
|
EXTENSIONS_TARGET = EXTENSIONS_IMAGES
|
|
end
|
|
else
|
|
EXTENSIONS_TARGET = EXTENSIONS
|
|
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_TARGET[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)
|