-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathpersistent-playlist.lua
More file actions
274 lines (227 loc) · 8.56 KB
/
Copy pathpersistent-playlist.lua
File metadata and controls
274 lines (227 loc) · 8.56 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
--[[
persistent-playlist.lua
This script automatically saves and loads playlists across mpv sessions:
- When the playlist changes, it saves the playlist to a file
- When mpv exits, it saves the playlist to a file
- When mpv starts, it loads the saved playlist and appends to the current playlist
Version: 1.0.0
Author: tsukasa
License: MIT
]]
local mp = require('mp')
local utils = require('mp.utils')
local msg = require('mp.msg')
-- Configuration
local options = {
-- File path for storing the playlist
-- Will be stored in the mpv config directory by default
playlist_file = "~~/persistent_playlist.txt",
-- Whether to save the playlist when it changes
save_on_playlist_change = true,
-- Whether to save the playlist when mpv exits
save_on_exit = true,
-- Whether to load the saved playlist when mpv starts
load_on_start = true,
-- Whether to append or replace the playlist when loading
-- "append" will add the saved playlist entries to any existing playlist
-- "replace" will discard any existing playlist and load only the saved entries
load_mode = "append"
}
-- Read options from script configuration
-- (allows user to change settings by editing script-opts/persistent_playlist.conf)
mp.options = require('mp.options')
mp.options.read_options(options, "persistent_playlist")
-- Fully expand the playlist file path
options.playlist_file = mp.command_native({"expand-path", options.playlist_file})
-- Flag to track if playlist has been loaded
local load_done = false
-- Flag to temporarily disable saving during loading operations
local suppress_save = false
-- Function to convert a relative path to absolute path if needed
function ensure_absolute_path(path)
-- If path is already absolute or is a URL/protocol, return it as is
if path:match("^%a+://") or path:match("^/") or path:match("^%a:\\") then
return path
end
-- Get mpv's current working directory
local cwd = mp.get_property("working-directory", "")
if cwd == "" then
-- Fallback if working-directory is not available
cwd = utils.getcwd()
end
-- Combine current directory with the relative path
local abs_path
if package.config:sub(1,1) == '\\' then
-- Windows
abs_path = utils.join_path(cwd, path)
else
-- Unix-like systems
abs_path = cwd .. "/" .. path
end
return abs_path
end
-- Function to save the current playlist to a file
function save_playlist()
-- Don't save if saving is temporarily suppressed (during loading)
if suppress_save then
msg.debug("Save suppressed during loading operation")
return
end
-- Get the current playlist
local playlist = mp.get_property_native("playlist")
if not playlist or #playlist == 0 then
msg.info("Playlist is empty, nothing to save")
return
end
-- If this is a shutdown and we haven't loaded the playlist yet,
-- don't overwrite the existing playlist file...
if mp.get_script_name() == "shutdown" and not load_done then
msg.info("Skipping playlist save on shutdown because playlist wasn't loaded")
return
end
-- Check if at least one file in the playlist exists or is a URL.
-- This prevents saving playlists with only non-existent files...
local has_valid_entry = false
for _, item in ipairs(playlist) do
local path = item.filename
if file_exists(path) then
has_valid_entry = true
break
end
end
if not has_valid_entry then
msg.info("Playlist contains only non-existent files, skipping save")
return
end
msg.info("Saving playlist to " .. options.playlist_file)
-- Open the file for writing
local file, err = io.open(options.playlist_file, "w")
if not file then
msg.error("Failed to open playlist file for writing: " .. (err or "unknown error"))
return
end
-- Write each playlist entry to the file
for _, item in ipairs(playlist) do
-- Convert to absolute path if it's a local file
local path = item.filename
if not path:match("^%a+://") then
path = ensure_absolute_path(path)
end
file:write(path .. "\n")
end
file:close()
msg.info("Playlist saved with " .. #playlist .. " entries")
end
-- Function to normalize paths for comparison
function normalize_path(path)
-- Convert backslashes to forward slashes for consistent comparison
path = path:gsub("\\", "/")
-- Remove trailing slashes
path = path:gsub("/*$", "")
return path:lower() -- Case-insensitive comparison
end
-- Function to check if a file exists on disk
function file_exists(path)
-- If it's a URL or protocol, always return true
if path:match("^%a+://") then
return true
end
-- Convert to absolute path if needed
local file_path = ensure_absolute_path(path)
-- Check if the file exists on disk
local file_info = utils.file_info(file_path)
-- Return true if file exists, false otherwise
return file_info ~= nil
end
-- Function to check if a file exists in the current playlist
function file_exists_in_playlist(filename)
local normalized_filename = normalize_path(filename)
local playlist = mp.get_property_native("playlist")
for _, item in ipairs(playlist) do
local item_path = item.filename
if not item_path:match("^%a+://") then
item_path = ensure_absolute_path(item_path)
end
if normalize_path(item_path) == normalized_filename then
return true
end
end
return false
end
-- Function to load the playlist from a file
function load_playlist()
msg.info("Loading playlist from " .. options.playlist_file)
-- Check if the file exists
if not file_exists(options.playlist_file) then
msg.info("Playlist file does not exist, skipping load")
return
end
-- Open the file for reading
local file, err = io.open(options.playlist_file, "r")
if not file then
msg.error("Failed to open playlist file for reading: " .. (err or "unknown error"))
return
end
-- Temporarily suppress saving during loading
suppress_save = true
-- Get the current state before we start adding files
local initial_count = mp.get_property_native("playlist-count") or 0
local first_added = true
-- Read each line and add it to the playlist
local count = 0
for line in file:lines() do
if line and line ~= "" then
-- Skip duplicates to prevent the same item from being added multiple times
if not file_exists_in_playlist(line) then
-- Append or replace based on options
local mode = "append"
if options.load_mode == "replace" and first_added then
mode = "replace"
first_added = false
end
-- Workaround for when mpv gets passed an invalid file
-- Otherwise the player closes?
if file_exists(line) then
mp.commandv("loadfile", line, mode)
count = count + 1
else
msg.warn("File does not exist: " .. line)
end
else
msg.debug("Skipping duplicate playlist item: " .. line)
end
end
end
file:close()
-- Re-enable saving after a short delay to allow all playlist operations to complete
mp.add_timeout(1, function()
suppress_save = false
end)
msg.info("Loaded " .. count .. " entries from saved playlist")
end
-- Register event to save playlist when it changes
if options.save_on_playlist_change then
mp.observe_property("playlist", "native", function(_, playlist)
-- Skip if playlist is empty or unchanged
if not playlist or #playlist == 0 then
return
end
-- Small delay to avoid excessive writes when multiple changes happen quickly
mp.add_timeout(1, save_playlist)
end)
end
-- Register event to save playlist when mpv exits
if options.save_on_exit then
mp.register_event("shutdown", save_playlist)
end
-- Load playlist only once when mpv starts
if options.load_on_start then
-- Run once after a short delay to ensure mpv is fully initialized
mp.add_timeout(0.5, function()
if not load_done then
load_playlist()
load_done = true
end
end)
end
msg.info("Persistent playlist script loaded")