-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathvlc_playlist_tracker.lua
More file actions
268 lines (214 loc) · 7.08 KB
/
Copy pathvlc_playlist_tracker.lua
File metadata and controls
268 lines (214 loc) · 7.08 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
-- Playlist Tracker – Restores the last played file inside a folder/playlist and restores the playback on extension activation.
-- Author: Sai / ChatGPT
-- State is stored in ~/vlc_playlist_state.json (Location for Windows: Documents folder and Linux/Mac: Home folder)
local config = {
state_file = nil
}
function descriptor()
return {
title = "Playlist Tracker",
version = "1.0",
author = "Sai, ChatGPT",
description = "Restores the last played file inside a folder/playlist and restores the playback on extension activation.",
capabilities = { "input-listener" }
}
end
-- Print playlist entries for debugging purposes
local function log_playlist()
vlc.msg.dbg("PTracker: Listing playlist...")
local root = vlc.playlist.get("playlist", false)
if not root or not root.children then
vlc.msg.dbg("PTracker: Playlist empty or unavailable")
return
end
for i, item in ipairs(root.children) do
vlc.msg.dbg(string.format(
"PTracker: [%d] name='%s' | path='%s'",
i, tostring(item.name), tostring(item.path)
))
end
end
-- Save JSON text to disk
local function save_state(json)
if not config.state_file then return end
local file = vlc.io.open(config.state_file, "wt")
if not file then
vlc.msg.err("PTracker: Could not write: " .. config.state_file)
return
end
file:write(json)
file:close()
vlc.msg.dbg("PTracker: State saved")
end
-- Split a path into its component directory names
local function split_path(path)
local parts = {}
for p in path:gmatch("[^/]+") do
table.insert(parts, p)
end
return parts
end
-- Compute the common folder prefix across a list of file paths
local function common_prefix(paths)
if #paths == 0 then return nil end
local split_paths = {}
for i, p in ipairs(paths) do
split_paths[i] = split_path(p)
end
local prefix, index = {}, 1
while true do
local token = split_paths[1][index]
if not token then break end
for j = 2, #split_paths do
if split_paths[j][index] ~= token then
return table.concat(prefix, "/") .. "/"
end
end
table.insert(prefix, token)
index = index + 1
end
return table.concat(prefix, "/") .. "/"
end
-- Extract paths of all playlist media items
local function collect_media_paths()
local paths = {}
local root = vlc.playlist.get("playlist", false)
if not root or not root.children then
return paths
end
for _, item in ipairs(root.children) do
if item.path then
table.insert(paths, item.path)
end
end
return paths
end
-- Identify the "true root" folder representing the playlist
local function determine_root_folder()
local paths = collect_media_paths()
if #paths == 0 then
vlc.msg.dbg("PTracker: No media paths found")
return nil
end
local parents = {}
for _, p in ipairs(paths) do
local folder = p:match("(.*/)")
if folder then
table.insert(parents, folder)
end
end
local root = common_prefix(parents)
vlc.msg.dbg("PTracker: Root folder resolved: " .. tostring(root))
return root
end
-- Read the state JSON file (raw string)
local function load_state()
if not config.state_file then return nil end
local file = vlc.io.open(config.state_file, "rt")
if not file then return nil end
local data = file:read("*a")
file:close()
return data
end
-- Given an index, return the corresponding playlist item ID
local function get_id_for_index(target_index)
local root = vlc.playlist.get("playlist", false)
if not root or not root.children then return nil end
local item = root.children[target_index]
if item then
return item.id
end
return nil
end
-- Given an item ID, find its current playlist index
local function get_index_for_id(target_id)
if not target_id then return nil end
local root = vlc.playlist.get("playlist", false)
if not root or not root.children then return nil end
for i, item in ipairs(root.children) do
if item.id == target_id then
return i
end
end
return nil
end
-- Save current playlist index to JSON
-- Note: VLC returns item IDs, so we map ID → playlist index
local function update_state()
local current_id = vlc.playlist.current()
if not current_id then
vlc.msg.dbg("PTracker: No active playlist item")
return
end
local idx = get_index_for_id(current_id)
if not idx then
vlc.msg.dbg("PTracker: Could not map current ID to index")
return
end
local folder = determine_root_folder()
if not folder then return end
local table_data = {}
local raw = load_state()
-- Parse existing values into table_data
if raw and #raw > 0 then
for k, v in raw:gmatch('"([^"]+)":%s*(%d+)') do
table_data[k] = tonumber(v)
end
end
table_data[folder] = idx
-- Build JSON manually (no JSON lib in VLC Lua)
local json = "{\n"
local first = true
for k, v in pairs(table_data) do
if not first then
json = json .. ",\n"
end
json = json .. string.format(' "%s": %d', k, v)
first = false
end
json = json .. "\n}\n"
save_state(json)
end
-- Called whenever VLC switches to a new playlist item
function input_changed()
vlc.msg.dbg("PTracker: Input changed")
update_state()
end
-- Called when metadata of the current item changes
function meta_changed()
vlc.msg.dbg("PTracker: meta_changed() triggered")
end
-- Called when the extension is loaded
function activate()
config.state_file = vlc.config.homedir() .. "/vlc_playlist_state.json"
vlc.msg.dbg("PTracker: Using state file: " .. config.state_file)
-- log_playlist() //for debugging
local folder = determine_root_folder()
if not folder then return end
local raw = load_state()
if not raw then
vlc.msg.dbg("PTracker: No saved state")
return
end
-- Escape Lua pattern characters for matching
local escaped = folder:gsub("([%-%.%+%*%?%[%]%^%$%(%)%%])", "%%%1")
local saved = raw:match('"' .. escaped .. '":%s*(%d+)')
if not saved then
vlc.msg.dbg("PTracker: No saved index for this folder")
return
end
saved = tonumber(saved)
vlc.msg.dbg("PTracker: Saved index: " .. saved)
local id = get_id_for_index(saved)
if id then
vlc.msg.dbg("PTracker: Restoring via item ID " .. tostring(id))
vlc.playlist.goto(id)
else
vlc.msg.dbg("PTracker: Index exists but corresponding ID is not yet available")
end
end
-- Called when the extension is unloaded
function deactivate()
vlc.msg.dbg("PTracker: Deactivated, saving state")
update_state()
end