forked from JacopoBucchioni/vlc-permanents-bookmarks
-
Notifications
You must be signed in to change notification settings - Fork 0
/
vlc_permanents_bookmarks.lua
564 lines (512 loc) · 17.9 KB
/
vlc_permanents_bookmarks.lua
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
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
-------------- Global variables ---------------------------------------
local mediaFile = {}
local input = nil
local Bookmarks = {}
local selectedBookmarkId = nil
local bookmarkFilePath = nil
-- UI
local dialog_UI = nil
local bookmarks_dialog = {}
local dialog_title = "VLC Permanents Bookmarks"
------------------------------------------------------------------------
-- VLC defined callback functions --------------------------------------
-- Script descriptor, called when the extensions are scanned
function descriptor()
return {
title = dialog_title,
version = "1.0.1",
author = "Bucchio",
url = 'https://github.com/JacopoBucchioni/vlc-permanents-bookmarks',
shortdesc = "Bookmarks",
description = "Save bookmarks for your media files and store them permanently.",
capabilities = {"menu", "input-listener"}
}
end
-- First function to be called when the extension is activated
function activate()
vlc.msg.dbg("[Activate extension] Welcome! Start saving your bookmarks!")
local ok, err = pcall(check_config)
if not ok then
vlc.msg.err(err)
return false
end
show_gui()
end
-- Called when the extension dialog is closed
function close()
vlc.deactivate()
end
-- Called when the extension is deactivated
function deactivate()
vlc.msg.dbg("[Deactivate extension] Bye bye!")
if dialog_UI then
dialog_UI:hide()
end
end
-- Called on mouseover on the extension in View menu
function menu()
return {"Show dialog"}
end
-- trigger function on menu() function call
function trigger_menu(dlg_id)
show_gui()
end
-- related to capabilities={"input-listener"} in descriptor()
-- triggered by Start/Stop media input event
function input_changed() -- ~ !important: deve essere qualcosa di veloce
vlc.msg.dbg("[Input changed]")
if dialog_UI then
dialog_UI:hide()
input = nil
mediaFile = nil
mediaFile = {}
Bookmarks = nil
Bookmarks = {}
selectedBookmarkId = nil
bookmarkFilePath = nil
end
end
-- triggered by available media input meta data?
function meta_changed()
-- return
end
-- End VLC defined callback functions ----------------------------------
-- // Bookmarks init function
function load_bookmarks()
-- mediaFile.metaTitle = vlc.input.item():name()
mediaFile.uri = vlc.input.item():uri()
if mediaFile.uri then
local filePath = vlc.strings.make_path(mediaFile.uri)
if not filePath then
filePath = vlc.strings.decode_uri(mediaFile.uri)
local match = string.match(filePath, "^.*[" .. slash .. "]([^" .. slash .. "]-).?[%a%d]*$")
if match then
filePath = match
end
else
mediaFile.dir, mediaFile.name = string.match(filePath,
"^(.*[" .. slash .. "])([^" .. slash .. "]-).?[%a%d]*$")
end
if not mediaFile.name then
mediaFile.name = filePath
end
-- vlc.msg.dbg("Video Meta Title: " .. mediaFile.metaTitle)
vlc.msg.dbg("Video URI: " .. mediaFile.uri)
vlc.msg.dbg("fileName: " .. mediaFile.name)
vlc.msg.dbg("fileDir: " .. tostring(mediaFile.dir))
getFileHash()
if mediaFile.hash then
bookmarkFilePath = bookmarksDir .. slash .. mediaFile.hash
Bookmarks = table_load(bookmarkFilePath)
input = vlc.object.input()
end
end
collectgarbage()
end
function getFileHash()
-- Calculate media hash
local data_start = ""
local data_end = ""
local size
local chunk_size = 65536
local ok
local err
-- Get data for hash calculation
vlc.msg.dbg("init read hash data from stream")
local stream = vlc.stream(mediaFile.uri)
data_start = stream:read(chunk_size)
ok, size = pcall(stream.getsize, stream)
if not size then
vlc.msg.warn("Failed to get stream size: " .. size)
return false
end
mediaFile.bytesize = size
vlc.msg.dbg("File bytesize: " .. mediaFile.bytesize)
-- size = math.floor(size / 2)
ok, err = pcall(stream.seek, stream, size - chunk_size)
if not ok then
vlc.msg.warn("Failed to seek the stream: " .. err)
return false
end
data_end = stream:read(chunk_size)
-- stream = nil
vlc.msg.dbg("finish Read hash data from stream")
-- Hash calculation
-- local lo = mediaFile.bytesize
local lo = size
local hi = 0
local o, a, b, c, d, e, f, g, h
local hash_data = data_start .. data_end
local max_size = 4294967296
local overflow
for i = 1, #hash_data, 8 do
a, b, c, d, e, f, g, h = hash_data:byte(i, i + 7)
lo = lo + a + b * 256 + c * 65536 + d * 16777216
hi = hi + e + f * 256 + g * 65536 + h * 16777216
if lo > max_size then
overflow = math.floor(lo / max_size)
lo = lo - (overflow * max_size)
hi = hi + overflow
end
if hi > max_size then
overflow = math.floor(hi / max_size)
hi = hi - (overflow * max_size)
end
end
mediaFile.hash = string.format("%08x%08x", hi, lo)
vlc.msg.dbg("File hash: " .. mediaFile.hash)
collectgarbage()
return true
end
-- // system check and extension config
function check_config()
slash = package.config:sub(1, 1)
bookmarksDir = vlc.config.userdatadir()
local res, err = vlc.io.mkdir(bookmarksDir, "0700")
if res ~= 0 and err ~= vlc.errno.EEXIST then
vlc.msg.warn("Failed to create " .. bookmarksDir)
return false
end
local subdirs = {"lua", "extensions", "userdata", "bookmarks"}
for _, dir in ipairs(subdirs) do
res, err = vlc.io.mkdir(bookmarksDir .. slash .. dir, "0700")
if res ~= 0 and err ~= vlc.errno.EEXIST then
vlc.msg.warn("Failed to create " .. bookmarksDir .. slash .. dir)
return false
end
bookmarksDir = bookmarksDir .. slash .. dir
end
if bookmarksDir then
vlc.msg.dbg("Bookmarks directory: " .. bookmarksDir)
end
collectgarbage()
return true
end
-- // The Save Function
function table_save(t, filePath)
local function exportstring(s)
return string.format("%q", s)
end
local charS, charE = " ", "\n"
local file, err = io.open(filePath, "wb")
if err then
return err
end
-- initiate variables for save procedure
local tables, lookup = {t}, {
[t] = 1
}
file:write("return {" .. charE)
for idx, t in ipairs(tables) do
file:write("-- Table: {" .. idx .. "}" .. charE)
file:write("{" .. charE)
local thandled = {}
for i, v in ipairs(t) do
thandled[i] = true
local stype = type(v)
-- only handle value
if stype == "table" then
if not lookup[v] then
table.insert(tables, v)
lookup[v] = #tables
end
file:write(charS .. "{" .. lookup[v] .. "}," .. charE)
elseif stype == "string" then
file:write(charS .. exportstring(v) .. "," .. charE)
elseif stype == "number" then
file:write(charS .. tostring(v) .. "," .. charE)
end
end
for i, v in pairs(t) do
-- escape handled values
if (not thandled[i]) then
local str = ""
local stype = type(i)
-- handle index
if stype == "table" then
if not lookup[i] then
table.insert(tables, i)
lookup[i] = #tables
end
str = charS .. "[{" .. lookup[i] .. "}]="
elseif stype == "string" then
str = charS .. "[" .. exportstring(i) .. "]="
elseif stype == "number" then
str = charS .. "[" .. tostring(i) .. "]="
end
if str ~= "" then
stype = type(v)
-- handle value
if stype == "table" then
if not lookup[v] then
table.insert(tables, v)
lookup[v] = #tables
end
file:write(str .. "{" .. lookup[v] .. "}," .. charE)
elseif stype == "string" then
file:write(str .. exportstring(v) .. "," .. charE)
elseif stype == "number" then
file:write(str .. tostring(v) .. "," .. charE)
end
end
end
end
file:write("}," .. charE)
end
file:write("}")
file:close()
end
-- // The Load Function
function table_load(filePath)
local ftables, err = loadfile(filePath)
if err then
return {}, err
end
local tables = ftables()
for idx = 1, #tables do
local tolinki = {}
for i, v in pairs(tables[idx]) do
if type(v) == "table" then
tables[idx][i] = tables[v[1]]
end
if type(i) == "table" and tables[i[1]] then
table.insert(tolinki, {i, tables[i[1]]})
end
end
-- link indices
for _, v in ipairs(tolinki) do
tables[idx][v[2]], tables[idx][v[1]] = tables[idx][v[1]], nil
end
end
return tables[1]
end
-- // The Binary Insert Function
function table_binInsert(t, value, fcomp)
local fcomp_default = function(a, b)
return a < b
end
-- Initialise compare function
local fcomp = fcomp or fcomp_default
-- Initialise numbers
local iStart, iEnd, iMid, iState = 1, #t, 1, 0
-- Get insert position
while iStart <= iEnd do
-- calculate middle
iMid = math.floor((iStart + iEnd) / 2)
-- compare
if fcomp(value, t[iMid]) then
iEnd, iState = iMid - 1, 0
else
iStart, iState = iMid + 1, 1
end
end
-- table.insert( t,(iMid+iState),value )
return (iMid + iState)
end
function table_length(t)
local count = 0
for _ in pairs(t) do
count = count + 1
end
return count
end
-- // get number rappresenting time in microseconds and return a string with formatted time hh:mm:ss.millis
function getFormattedTime(micros)
local millis = math.floor(micros / 1000)
local seconds = math.floor((millis / 1000) % 60)
local minutes = math.floor((millis / 60000) % 60)
local hours = math.floor((millis / 3600000) % 24)
millis = math.floor(millis % 1000)
return string.format("%02d:%02d:%02d.%03d", hours, minutes, seconds, millis)
end
-- GUI Setup and buttons callbacks ----------------------------------------
-- Create the main bookmarks dialog
function main_dialog()
vlc.msg.dbg("Creating main dialog")
-- Gui positional args: col, row, col_span, row_span, width, height
dialog_UI = vlc.dialog(dialog_title)
-- ~ !important: Add button must be the first item that is created
dialog_UI:add_button("Add", addBookmark, 1, 1, 1, 1)
-- bookmarks labels input box
bookmarks_dialog['text_input'] = dialog_UI:add_text_input('Bookmark (' .. (#Bookmarks + 1) .. ')', 2, 1, 1, 1)
-- buttons
dialog_UI:add_button("Go", goToBookmark, 1, 2, 1, 1)
dialog_UI:add_button("Rename", editBookmark, 1, 3, 1, 1)
dialog_UI:add_button("Remove", removeBookmark, 1, 4, 1, 1)
dialog_UI:add_button("Close", vlc.deactivate, 1, 5, 1, 1)
-- dialog_UI:add_button("Import", show_import_gui, 1, 10, 1, 1)
-- dialog_UI:add_button("Export", show_export_gui, 1, 11, 1, 1)
-- bookmarks list
-- ~ !important: invisible_label must be created before bookmarks_list
bookmarks_dialog['invisible_label'] = dialog_UI:add_label('', 2, 2, 0, 0)
bookmarks_dialog['bookmarks_list'] = dialog_UI:add_list(2, 2, 1, 14)
-- footer message_label
bookmarks_dialog['footer_message'] = dialog_UI:add_label('', 2, 16, 1, 1)
showBookmarks()
dialog_UI:show()
end
function showBookmarks()
if bookmarks_dialog['bookmarks_list'] then
bookmarks_dialog['bookmarks_list']:clear()
local maxText = ''
local count = 0
for idx, b in pairs(Bookmarks) do
local text = '#' .. idx .. ' - ' .. b.formattedTime .. 'ㅤ-ㅤ' .. b.label
bookmarks_dialog['bookmarks_list']:add_value(text, idx)
-- for dialog width autofit
if #text > #maxText then
maxText = text
end
text = idx .. b.label
if #text > count then
count = #text
end
end
-- max dialog width = 480px
count = math.floor(count * 8.5) + 140 -- TODO: ??
if count < 480 then
bookmarks_dialog['invisible_label']:set_text("<p style='font-size: 15px; margin-left: 24px;'>" .. maxText ..
"</p>")
else
bookmarks_dialog['invisible_label']:set_text("<p style='font-size: 15px; margin-left: 480px;'>.</p>")
end
end
end
-- Buttons callbacks -------------------------------------------------------------
function addBookmark()
dlt_footer()
if bookmarks_dialog['text_input'] then
local label = bookmarks_dialog['text_input']:get_text()
if string.len(label) > 0 then
if selectedBookmarkId ~= nil then
-- rename an existing bookmark
Bookmarks[selectedBookmarkId].label = label
selectedBookmarkId = nil
else
-- add a new bookmark
local b = {}
b.time = vlc.var.get(input, "time")
b.label = label
b.formattedTime = getFormattedTime(b.time)
local i = table_binInsert(Bookmarks, b, function(a, b)
return a.time <= b.time
end)
-- bookmark with same time already present
if Bookmarks[i] then
if Bookmarks[i].formattedTime == b.formattedTime then
bookmarks_dialog['footer_message']:set_text(setMessageStyle("Bookmark already added"))
return
end
end
table.insert(Bookmarks, i, b)
end
table_save(Bookmarks, bookmarkFilePath)
showBookmarks()
bookmarks_dialog['text_input']:set_text('Bookmark (' .. (#Bookmarks + 1) .. ')')
else
bookmarks_dialog['footer_message']:set_text(setMessageStyle("Please enter your bookmark title"))
end
end
end
function goToBookmark()
dlt_footer()
if bookmarks_dialog['bookmarks_list'] then
local selection = bookmarks_dialog['bookmarks_list']:get_selection()
selectedBookmarkId = nil
if next(selection) then
if table_length(selection) == 1 then
for idx, _ in pairs(selection) do
vlc.var.set(input, "time", Bookmarks[idx].time)
break
end
else
bookmarks_dialog['footer_message']:set_text(setMessageStyle("Please select only one item"))
end
else
bookmarks_dialog['footer_message']:set_text(setMessageStyle("Please select a item"))
end
end
end
function editBookmark()
dlt_footer()
if bookmarks_dialog['bookmarks_list'] then
local selection = bookmarks_dialog['bookmarks_list']:get_selection()
selectedBookmarkId = nil
if next(selection) then
if table_length(selection) == 1 then
for idx, _ in pairs(selection) do
bookmarks_dialog['text_input']:set_text(Bookmarks[idx].label)
selectedBookmarkId = idx
return
end
else
bookmarks_dialog['footer_message']:set_text(setMessageStyle("Please select only one item"))
end
else
bookmarks_dialog['footer_message']:set_text(setMessageStyle("Please select a item"))
end
end
end
function removeBookmark()
dlt_footer()
if bookmarks_dialog['bookmarks_list'] then
local selection = bookmarks_dialog['bookmarks_list']:get_selection()
selectedBookmarkId = nil
if next(selection) then
local count = 0
-- sort selection by ids
local selectionSorted = {}
for id in pairs(selection) do
table.insert(selectionSorted, id)
end
table.sort(selectionSorted)
for _, idx in pairs(selectionSorted) do
table.remove(Bookmarks, idx - count)
count = count + 1
end
table_save(Bookmarks, bookmarkFilePath)
showBookmarks()
else
bookmarks_dialog['footer_message']:set_text(setMessageStyle("Please select items you want remove"))
end
end
end
-- End buttons callbacks -------------------------------------------------
function setMessageStyle(str)
return "<p style='font-size: 12px; margin-left: 4px;'>" .. str .. "</p>"
end
function dlt_footer()
if bookmarks_dialog['footer_message'] then
bookmarks_dialog['footer_message']:set_text('')
end
end
function close_dlg()
vlc.msg.dbg("Closing dialog")
if dialog_UI ~= nil then
-- dialog_UI:delete() -- Throw an error
dialog_UI:hide()
end
dialog_UI = nil
bookmarks_dialog = nil
bookmarks_dialog = {}
collectgarbage() -- ~ !important
end
function show_gui()
close_dlg()
if vlc.input.item() then
if not input then
load_bookmarks()
end
main_dialog()
else
noinput_dialog()
end
collectgarbage() -- ~ !important
end
function noinput_dialog()
vlc.msg.dbg("Creating noinput dialog")
dialog_UI = vlc.dialog(dialog_title)
dialog_UI:add_label(
"<p style='font-size: 12px; text-align: center;'>Please open a media file before running this extension</p>")
-- dialog_UI:show()
end