-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathsimplehistory.lua
2302 lines (2056 loc) · 96.8 KB
/
simplehistory.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
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
-- Copyright (c) 2022, Eisa AlAwadhi
-- License: BSD 2-Clause License
-- Creator: Eisa AlAwadhi
-- Project: SimpleHistory
-- Version: 1.1.6
local o = {
---------------------------USER CUSTOMIZATION SETTINGS---------------------------
--These settings are for users to manually change some options.
--Changes are recommended to be made in the script-opts directory.
-----Script Settings----
auto_run_list_idle = 'recents', --Auto run the list when opening mpv and there is no video / file loaded. 'none' for disabled. Or choose between: 'all', 'recents', 'distinct', 'protocols', 'fileonly', 'titleonly', 'timeonly', 'keywords'.
startup_idle_behavior = 'none', --The behavior when mpv launches and nothing is loaded. 'none' for disabled. 'resume' to automatically resume your last played item. 'resume-notime' to resume your last played item but starts from the beginning.
toggle_idlescreen = false, --hides OSC idle screen message when opening and closing menu (could cause unexpected behavior if multiple scripts are triggering osc-idlescreen off)
resume_offset = -0.65, --change to 0 so item resumes from the exact position, or decrease the value so that it gives you a little preview before loading the resume point
osd_messages = true, --true is for displaying osd messages when actions occur. Change to false will disable all osd messages generated from this script
resume_option = 'notification', --'none': for disabled. 'notification': a message to resume the previous reached time will be triggered. 'force': to forcefully resume last playback based on threshold
resume_option_threshold = 2, --0 to always trigger the resume option when the same video has been played previously, a value such as 5 will only trigger the resume option if the last played time starts after 5% of the video and ends before completion by 5%
mark_history_as_chapter = false, --true is for marking the time as a chapter. false disables mark as chapter behavior.
invert_history_blacklist = false, --true so that blacklist becomes a whitelist, resulting in stuff such as paths / websites that are added to history_blacklist to be saved into history
history_blacklist=[[
[""]
]], --Paths / URLs / Websites / Files / Protocols / Extensions, that wont be added to history automatically, e.g.: ["c:\\users\\eisa01\\desktop", "c:\\users\\eisa01\\desktop\\*", "c:\\temp\\naruto-01.mp4", "youtube.com", "https://dailymotion.com/", "avi", "https://www.youtube.com/watch?v=e8YBesRKq_U", ".jpeg", "magnet:", "https://", "ftp"]
history_resume_keybind=[[
["ctrl+r", "ctrl+R"]
]], --Keybind that will be used to immediately load and resume last item when no video is playing. If video is playing it will resume to the last found position
history_load_last_keybind=[[
["alt+r", "alt+R"]
]], --Keybind that will be used to immediately load the last item without resuming when no video is playing. If video is playing then it will add into playlist
open_list_keybind=[[
[ ["h", "all"], ["H", "all"], ["r", "recents"], ["R", "recents"] ]
]], --Keybind that will be used to open the list along with the specified filter. Available filters: 'all', 'recents', 'distinct', 'protocols', 'fileonly', 'titleonly', 'timeonly', 'keywords'.
list_filter_jump_keybind=[[
[ ["h", "all"], ["H", "all"], ["r", "recents"], ["R", "recents"], ["d", "distinct"], ["D", "distinct"], ["f", "fileonly"], ["F", "fileonly"] ]
]], --Keybind that is used while the list is open to jump to the specific filter (it also enables pressing a filter keybind twice to close list). Available fitlers: 'all', 'recents', 'distinct', 'protocols', 'fileonly', 'titleonly', 'timeonly', 'keywords'.
-----Incognito Settings----
auto_run_incognito_mode = false, --true to automatically start incognito mode when mpv launches, false disables this behavior
delete_incognito_entry = true, --true so that the file that had incognito mode triggered on gets removed from history automatically, false keeps the file in history that incognito mode triggered on
restore_incognito_entry = 'always', --'none' for disabled, 'deleted-restore' so that the the file that was removed when entering incognito automtically gets restored, 'always' so that exiting incognito_mode always immediately updates entry into history
history_incognito_mode_keybind=[[
["ctrl+H"]
]], --Triggers incognito mode. When enabled files played wont be added to history until this mode is disabled.
-----Logging Settings-----
log_path = '/:dir%mpvconf%', --Change to '/:dir%script%' for placing it in the same directory of script, OR change to '/:dir%mpvconf%' for mpv portable_config directory. OR write any variable using '/:var' then the variable '/:var%APPDATA%' you can use path also, such as: '/:var%APPDATA%\\mpv' OR '/:var%HOME%/mpv' OR specify the absolute path , e.g.: 'C:\\Users\\Eisa01\\Desktop\\'
log_file = 'mpvHistory.log', --name+extension of the file that will be used to store the log data
date_format = '%A/%B %d/%m/%Y %X', --Date format in the log (see lua date formatting), e.g.:'%d/%m/%y %X' or '%d/%b/%y %X'
file_title_logging = 'protocols', --Change between 'all', 'protocols', 'none'. This option will store the media title in log file, it is useful for websites / protocols because title cannot be parsed from links alone
logging_protocols=[[
["://", "magnet:"]
]], --add above (after a comma) any protocol you want its title to be stored in the log file. This is valid only for (file_title_logging = 'protocols' or file_title_logging = 'all')
prefer_filename_over_title = 'local', --Prefers to log filename over filetitle. Select between 'local', 'protocols', 'all', and 'none'. 'local' prefer filenames for videos that are not protocols. 'protocols' will prefer filenames for protocols only. 'all' will prefer filename over filetitle for both protocols and not protocols videos. 'none' will always use filetitle instead of filename
same_entry_limit = 2, --Limit saving entries with same path: -1 for unlimited, 0 will always update entries of same path, e.g. value of 3 will have the limit of 3 then it will start updating old values on the 4th entry.
-----List Settings-----
loop_through_list = false, --true is for going up on the first item loops towards the last item and vise-versa. false disables this behavior.
list_middle_loader = true, --false is for more items to show, then u must reach the end. true is for new items to show after reaching the middle of list.
show_paths = false, --Show file paths instead of media-title
show_item_number = true, --Show the number of each item before displaying its name and values.
slice_longfilenames = false, --Change to true or false. Slices long filenames per the amount specified below
slice_longfilenames_amount = 55, --Amount for slicing long filenames
list_show_amount = 10, --Change maximum number to show items at once
quickselect_0to9_keybind = true, --Keybind entries from 0 to 9 for quick selection when list is open (list_show_amount = 10 is maximum for this feature to work)
main_list_keybind_twice_exits = true, --Will exit the list when double tapping the main list, even if the list was accessed through a different filter.
search_not_typing_smartly = true, --To smartly set the search as not typing (when search box is open) without needing to press ctrl+enter.
search_behavior = 'any', --'specific' to find a match of either a date, title, path / url, time. 'any' to find any typed search based on combination of date, title, path / url, and time. 'any-notime' to find any typed search based on combination of date, title, and path / url, but without looking for time (this is to reduce unwanted results).
-----Filter Settings------
--available filters: "all" to display all the items. Or "recents" to display recently added items to log without duplicate. Or "distinct" to show recent saved entries for files in different paths. Or "fileonly" to display files saved without time. Or "timeonly" to display files that have time only. Or "keywords" to display files with matching keywords specified in the configuration. Or "playing" to show list of current playing file.
filters_and_sequence=[[
["all", "recents", "distinct", "protocols", "playing", "fileonly", "titleonly", "keywords"]
]], --Jump to the following filters and in the shown sequence when navigating via left and right keys. You can change the sequence and delete filters that are not needed.
next_filter_sequence_keybind=[[
["RIGHT", "MBTN_FORWARD"]
]], --Keybind that will be used to go to the next available filter based on the filters_and_sequence
previous_filter_sequence_keybind=[[
["LEFT", "MBTN_BACK"]
]], --Keybind that will be used to go to the previous available filter based on the filters_and_sequence
loop_through_filters = true, --true is for bypassing the last filter to go to first filter when navigating through filters using arrow keys, and vice-versa. false disables this behavior.
keywords_filter_list=[[
[""]
]], --Create a filter out of your desired 'keywords', e.g.: youtube.com will filter out the videos from youtube. You can also insert a portion of filename or title, or extension or a full path / portion of a path. e.g.: ["youtube.com", "mp4", "naruto", "c:\\users\\eisa01\\desktop"]
-----Sort Settings------
--available sort: 'added-asc' is for the newest added item to show first. Or 'added-desc' for the newest added to show last. Or 'alphanum-asc' is for A to Z approach with filename and episode number lower first. Or 'alphanum-desc' is for its Z to A approach. Or 'time-asc', 'time-desc' to sort the list based on time.
list_default_sort = 'added-asc', --the default sorting method for all the different filters in the list. select between 'added-asc', 'added-desc', 'time-asc', 'time-desc', 'alphanum-asc', 'alphanum-desc'
list_filters_sort=[[
[ ]
]], --Default sort for specific filters, e.g.: [ ["all", "alphanum-asc"], ["playing", "added-desc"] ]
list_cycle_sort_keybind=[[
["alt+s", "alt+S"]
]], --Keybind to cycle through the different available sorts when list is open
-----List Design Settings-----
list_alignment = 7, --The alignment for the list, uses numpad positions choose from 1-9 or 0 to disable. e,g.:7 top left alignment, 8 top middle alignment, 9 top right alignment.
text_time_type = 'duration', --The time type for items on the list. Select between 'duration', 'length', 'remaining'.
time_seperator = ' 🕒 ', --Time seperator that will be used before the time
list_sliced_prefix = '...\\h\\N\\N', --The text that indicates there are more items above. \\N is for new line. \\h is for hard space.
list_sliced_suffix = '...', --The text that indicates there are more items below.
quickselect_0to9_pre_text = false, --true enables pre text for showing quickselect keybinds before the list. false to disable
text_color = 'ffffff', --Text color for list in BGR hexadecimal
text_scale = 50, --Font size for the text of list
text_border = 0.7, --Black border size for the text of list
text_cursor_color = 'ffbf7f', --Text color of current cursor position in BGR hexadecimal
text_cursor_scale = 50, --Font size for text of current cursor position in list
text_cursor_border = 0.7, --Black border size for text of current cursor position in list
text_highlight_pre_text = '✅ ', --Pre text for highlighted multi-select item
search_color_typing = 'ffffaa', --Search color when in typing mode
search_color_not_typing = '00bfff', --Search color when not in typing mode and it is active
header_color = '00bfff', --Header color in BGR hexadecimal
header_scale = 55, --Header text size for the list
header_border = 0.8, --Black border size for the Header of list
header_text = '⌛ History [%cursor%/%total%]%prehighlight%%highlight%%afterhighlight%%prelistduration%%listduration%%afterlistduration%%prefilter%%filter%%afterfilter%%presort%%sort%%aftersort%%presearch%%search%%aftersearch%', --Text to be shown as header for the list
--Available header variables: %cursor%, %total%, %highlight%, %filter%, %search%, %listduration%, %listlength%, %listremaining%
--User defined text that only displays if a variable is triggered: %prefilter%, %afterfilter%, %prehighlight%, %afterhighlight% %presearch%, %aftersearch%, %prelistduration%, %afterlistduration%, %prelistlength%, %afterlistlength%, %prelistremaining%, %afterlistremaining%
--Variables explanation: %cursor: displays the number of cursor position in list. %total: total amount of items in current list. %highlight%: total number of highlighted items. %filter: shows the filter name, %search: shows the typed search. Example of user defined text that only displays if a variable is triggered of user: %prefilter: user defined text before showing filter, %afterfilter: user defined text after showing filter.
header_sort_hide_text = 'added-asc',--Sort method that is hidden from header when using %sort% variable
header_sort_pre_text = ' \\{',--Text to be shown before sort in the header, when using %presort%
header_sort_after_text = '}',--Text to be shown after sort in the header, when using %aftersort%
header_filter_pre_text = ' [Filter: ', --Text to be shown before filter in the header, when using %prefilter%
header_filter_after_text = ']', --Text to be shown after filter in the header, when using %afterfilter%
header_search_pre_text = '\\h\\N\\N[Search=', --Text to be shown before search in the header, when using %presearch%
header_search_after_text = '..]', --Text to be shown after search in the header, when using %aftersearch%
header_highlight_pre_text = '✅', --Text to be shown before total highlighted items of displayed list in the header
header_highlight_after_text = '', --Text to be shown after total highlighted items of displayed list in the header
header_list_duration_pre_text = ' 🕒 ', --Text to be shown before playback total duration of displayed list in the header
header_list_duration_after_text = '', --Text to be shown after playback total duration of displayed list in the header
header_list_length_pre_text = ' 🕒 ', --Text to be shown before playback total duration of displayed list in the header
header_list_length_after_text = '', --Text to be shown after playback total duration of displayed list in the header
header_list_remaining_pre_text = ' 🕒 ', --Text to be shown before playback total duration of displayed list in the header
header_list_remaining_after_text = '', --Text to be shown after playback total duration of displayed list in the header
-----Time Format Settings-----
--in the first parameter, you can define from the available styles: default, hms, hms-full, timestamp, timestamp-concise "default" to show in HH:MM:SS.sss format. "hms" to show in 1h 2m 3.4s format. "hms-full" is the same as hms but keeps the hours and minutes persistent when they are 0. "timestamp" to show the total time as timestamp 123456.700 format. "timestamp-concise" shows the total time in 123456.7 format (shows and hides decimals depending on availability).
--in the second parameter, you can define whether to show milliseconds, round them or truncate them. Available options: 'truncate' to remove the milliseconds and keep the seconds. 0 to remove the milliseconds and round the seconds. 1 or above is the amount of milliseconds to display. The default value is 3 milliseconds.
--in the third parameter you can define the seperator between hour:minute:second. "default" style is automatically set to ":", "hms", "hms-full" are automatically set to " ". You can define your own. Some examples: ["default", 3, "-"],["hms-full", 5, "."],["hms", "truncate", ":"],["timestamp-concise"],["timestamp", 0],["timestamp", "truncate"],["timestamp", 5]
osd_time_format=[[
["default", "truncate"]
]],
list_time_format=[[
["default", "truncate"]
]],
header_duration_time_format=[[
["hms", "truncate", ":"]
]],
header_length_time_format=[[
["hms", "truncate", ":"]
]],
header_remaining_time_format=[[
["hms", "truncate", ":"]
]],
-----List Keybind Settings-----
--Add below (after a comma) any additional keybind you want to bind. Or change the letter inside the quotes to change the keybind
--Example of changing and adding keybinds: --From ["b", "B"] To ["b"]. --From [""] to ["alt+b"]. --From [""] to ["a" "ctrl+a", "alt+a"]
list_move_up_keybind=[[
["UP", "WHEEL_UP"]
]], --Keybind that will be used to navigate up on the list
list_move_down_keybind=[[
["DOWN", "WHEEL_DOWN"]
]], --Keybind that will be used to navigate down on the list
list_page_up_keybind=[[
["PGUP"]
]], --Keybind that will be used to go to the first item for the page shown on the list
list_page_down_keybind=[[
["PGDWN"]
]], --Keybind that will be used to go to the last item for the page shown on the list
list_move_first_keybind=[[
["HOME"]
]], --Keybind that will be used to navigate to the first item on the list
list_move_last_keybind=[[
["END"]
]], --Keybind that will be used to navigate to the last item on the list
list_highlight_move_keybind=[[
["SHIFT"]
]], --Keybind that will be used to highlight while pressing a navigational keybind, keep holding shift and then press any navigation keybind, such as: up, down, home, pgdwn, etc..
list_highlight_all_keybind=[[
["ctrl+a", "ctrl+A"]
]], --Keybind that will be used to highlight all displayed items on the list
list_unhighlight_all_keybind=[[
["ctrl+d", "ctrl+D"]
]], --Keybind that will be used to remove all currently highlighted items from the list
list_select_keybind=[[
["ENTER", "MBTN_MID"]
]], --Keybind that will be used to load entry based on cursor position
list_add_playlist_keybind=[[
["CTRL+ENTER"]
]], --Keybind that will be used to add entry to playlist based on cursor position
list_add_playlist_highlighted_keybind=[[
["SHIFT+ENTER"]
]], --Keybind that will be used to add all highlighted entries to playlist
list_close_keybind=[[
["ESC", "MBTN_RIGHT"]
]], --Keybind that will be used to close the list (closes search first if it is open)
list_delete_keybind=[[
["DEL"]
]], --Keybind that will be used to delete the entry based on cursor position
list_delete_highlighted_keybind=[[
["SHIFT+DEL"]
]], --Keybind that will be used to delete all highlighted entries from the list
list_search_activate_keybind=[[
["ctrl+f", "ctrl+F"]
]], --Keybind that will be used to trigger search
list_search_not_typing_mode_keybind=[[
["ALT+ENTER"]
]], --Keybind that will be used to exit typing mode of search while keeping search open
list_ignored_keybind=[[
["B", "b", "k", "K", "c", "C"]
]], --Keybind thats are ignored when list is open
---------------------------END OF USER CUSTOMIZATION SETTINGS---------------------------
}
(require 'mp.options').read_options(o)
local utils = require 'mp.utils'
local msg = require 'mp.msg'
o.history_blacklist = utils.parse_json(o.history_blacklist)
o.history_incognito_mode_keybind = utils.parse_json(o.history_incognito_mode_keybind)
o.filters_and_sequence = utils.parse_json(o.filters_and_sequence)
o.keywords_filter_list = utils.parse_json(o.keywords_filter_list)
o.list_filters_sort = utils.parse_json(o.list_filters_sort)
o.logging_protocols = utils.parse_json(o.logging_protocols)
o.history_resume_keybind = utils.parse_json(o.history_resume_keybind)
o.history_load_last_keybind = utils.parse_json(o.history_load_last_keybind)
o.osd_time_format = utils.parse_json(o.osd_time_format)
o.list_time_format = utils.parse_json(o.list_time_format)
o.header_duration_time_format = utils.parse_json(o.header_duration_time_format)
o.header_length_time_format = utils.parse_json(o.header_length_time_format)
o.header_remaining_time_format = utils.parse_json(o.header_remaining_time_format)
o.list_move_up_keybind = utils.parse_json(o.list_move_up_keybind)
o.list_move_down_keybind = utils.parse_json(o.list_move_down_keybind)
o.list_page_up_keybind = utils.parse_json(o.list_page_up_keybind)
o.list_page_down_keybind = utils.parse_json(o.list_page_down_keybind)
o.list_move_first_keybind = utils.parse_json(o.list_move_first_keybind)
o.list_move_last_keybind = utils.parse_json(o.list_move_last_keybind)
o.list_highlight_move_keybind = utils.parse_json(o.list_highlight_move_keybind)
o.list_highlight_all_keybind = utils.parse_json(o.list_highlight_all_keybind)
o.list_unhighlight_all_keybind = utils.parse_json(o.list_unhighlight_all_keybind)
o.list_cycle_sort_keybind = utils.parse_json(o.list_cycle_sort_keybind)
o.list_select_keybind = utils.parse_json(o.list_select_keybind)
o.list_add_playlist_keybind = utils.parse_json(o.list_add_playlist_keybind)
o.list_add_playlist_highlighted_keybind = utils.parse_json(o.list_add_playlist_highlighted_keybind)
o.list_close_keybind = utils.parse_json(o.list_close_keybind)
o.list_delete_keybind = utils.parse_json(o.list_delete_keybind)
o.list_delete_highlighted_keybind = utils.parse_json(o.list_delete_highlighted_keybind)
o.list_search_activate_keybind = utils.parse_json(o.list_search_activate_keybind)
o.list_search_not_typing_mode_keybind = utils.parse_json(o.list_search_not_typing_mode_keybind)
o.next_filter_sequence_keybind = utils.parse_json(o.next_filter_sequence_keybind)
o.previous_filter_sequence_keybind = utils.parse_json(o.previous_filter_sequence_keybind)
o.open_list_keybind = utils.parse_json(o.open_list_keybind)
o.list_filter_jump_keybind = utils.parse_json(o.list_filter_jump_keybind)
o.list_ignored_keybind = utils.parse_json(o.list_ignored_keybind)
if utils.shared_script_property_set then
utils.shared_script_property_set('simplehistory-menu-open', 'no')
end
mp.set_property('user-data/simplehistory/menu-open', 'no')
if o.log_path:match('^/:dir%%mpvconf%%') then
o.log_path = o.log_path:gsub('/:dir%%mpvconf%%', mp.find_config_file('.'))
elseif o.log_path:match('^/:dir%%script%%') then
o.log_path = o.log_path:gsub('/:dir%%script%%', mp.find_config_file('scripts'))
elseif o.log_path:match('^/:var%%(.*)%%') then
local os_variable = o.log_path:match('/:var%%(.*)%%')
o.log_path = o.log_path:gsub('/:var%%(.*)%%', os.getenv(os_variable))
end
local log_fullpath = utils.join_path(o.log_path, o.log_file)
--create log_path if it doesn't exist
local log_path = utils.split_path(log_fullpath)
if utils.readdir(log_path) == nil then
local is_windows = package.config:sub(1, 1) == "\\"
local windows_args = { 'powershell', '-NoProfile', '-Command', 'mkdir', string.format("\"%s\"", log_path) }
local unix_args = { 'mkdir', '-p', log_path }
local args = is_windows and windows_args or unix_args
local res = mp.command_native({name = "subprocess", capture_stdout = true, playback_only = false, args = args})
if res.status ~= 0 then
msg.error("Failed to create log_path save directory "..log_path..". Error: "..(res.error or "unknown"))
return
end
end
local log_length_text = 'length='
local log_time_text = 'time='
local protocols = {'https?:', 'magnet:', 'rtmps?:', 'smb:', 'ftps?:', 'sftp:'}
local available_filters = {'all', 'recents', 'distinct', 'playing', 'protocols', 'fileonly', 'titleonly', 'timeonly', 'keywords'}
local available_sorts = {'added-asc', 'added-desc', 'time-asc', 'time-desc', 'alphanum-asc', 'alphanum-desc'}
local search_string = ''
local search_active = false
local incognito_mode = false
local autosaved_entry = false
local incognito_auto_run_triggered = false
local loadTriggered = false --1.1.5# to identify if load is triggered atleast once for idle option
local resume_selected = false
local list_contents = {}
local list_start = 0
local list_cursor = 1
local list_highlight_cursor = {}
local list_drawn = false
local list_pages = {}
local filePath, fileTitle, fileLength
local seekTime = 0
local logTime = 0 --1.3# use logTime since seekTime is used in multiple places
local filterName = 'all'
local sortName
function starts_protocol(tab, val)
for index, element in ipairs(tab) do
if string.find(val, element) then
return true
end
end
return false
end
function contain_value(tab, val)
if not tab then return msg.error('check value passed') end
if not val then return msg.error('check value passed') end
for index, value in ipairs(tab) do
if value.match(string.lower(val), string.lower(value)) then
return true
end
end
return false
end
function has_value(tab, val, array2d)
if not tab then return msg.error('check value passed') end
if not val then return msg.error('check value passed') end
if not array2d then
for index, value in ipairs(tab) do
if string.lower(value) == string.lower(val) then
return true
end
end
end
if array2d then
for i=1, #tab do
if tab[i] and string.lower(tab[i][array2d]) == string.lower(val) then
return true
end
end
end
return false
end
function file_exists(name)
local f = io.open(name, "r")
if f ~= nil then io.close(f) return true else return false end
end
function format_time(seconds, sep, decimals, style)
local function divmod (a, b)
return math.floor(a / b), a % b
end
decimals = decimals == nil and 3 or decimals
local s = seconds
local h, s = divmod(s, 60*60)
local m, s = divmod(s, 60)
if decimals == 'truncate' then
s = math.floor(s)
decimals = 0
if style == 'timestamp' then
seconds = math.floor(seconds)
end
end
if not style or style == '' or style == 'default' then
local second_format = string.format("%%0%d.%df", 2+(decimals > 0 and decimals+1 or 0), decimals)
sep = sep and sep or ":"
return string.format("%02d"..sep.."%02d"..sep..second_format, h, m, s)
elseif style == 'hms' or style == 'hms-full' then
sep = sep ~= nil and sep or " "
if style == 'hms-full' or h > 0 then
return string.format("%dh"..sep.."%dm"..sep.."%." .. tostring(decimals) .. "fs", h, m, s)
elseif m > 0 then
return string.format("%dm"..sep.."%." .. tostring(decimals) .. "fs", m, s)
else
return string.format("%." .. tostring(decimals) .. "fs", s)
end
elseif style == 'timestamp' then
return string.format("%." .. tostring(decimals) .. "f", seconds)
elseif style == 'timestamp-concise' then
return seconds
end
end
function get_file()
function hex_to_char(x)
return string.char(tonumber(x, 16))
end
local path = mp.get_property('path')
if not path then return end
if not path:match('^%a[%a%d-_]+://') then
path = utils.join_path(mp.get_property('working-directory'), path):gsub("\\", "/")
end
local length = (mp.get_property_number('duration') or 0)
local title = mp.get_property('media-title'):gsub("\"", "")
if starts_protocol(o.logging_protocols, path) and o.prefer_filename_over_title == 'protocols' then
title = mp.get_property('filename'):gsub("\"", "")
elseif not starts_protocol(o.logging_protocols, path) and o.prefer_filename_over_title == 'local' then
title = mp.get_property('filename'):gsub("\"", "")
elseif o.prefer_filename_over_title == 'all' then
title = mp.get_property('filename'):gsub("\"", "")
end
title = title:gsub('%%(%x%x)', hex_to_char)
return path, title, length
end
function bind_keys(keys, name, func, opts)
if not keys then
mp.add_forced_key_binding(keys, name, func, opts)
return
end
for i = 1, #keys do
if i == 1 then
mp.add_forced_key_binding(keys[i], name, func, opts)
else
mp.add_forced_key_binding(keys[i], name .. i, func, opts)
end
end
end
function unbind_keys(keys, name)
if not keys then
mp.remove_key_binding(name)
return
end
for i = 1, #keys do
if i == 1 then
mp.remove_key_binding(name)
else
mp.remove_key_binding(name .. i)
end
end
end
function esc_string(str)
return str:gsub("([%p])", "%%%1")
end
---------Start of LogManager---------
--LogManager (Read and Format the List from Log)--
function read_log(func)
local f = io.open(log_fullpath, "r")
if not f then return end
local contents = {}
for line in f:lines() do
table.insert(contents, (func(line)))
end
f:close()
return contents
end
function read_log_table()
local line_pos = 0
return read_log(function(line)
local tt, p, t, s, d, n, e, l, dt, ln, r
if line:match('^.-\"(.-)\"') then
tt = line:match('^.-\"(.-)\"')
n, p = line:match('^.-\"(.-)\" | (.*) | ' .. esc_string(log_length_text) .. '(.*)')
else
p = line:match('[(.*)%]]%s(.*) | ' .. esc_string(log_length_text) .. '(.*)')
d, n, e = p:match('^(.-)([^\\/]-)%.([^\\/%.]-)%.?$')
end
dt = line:match('%[(.-)%]')
t = line:match(' | ' .. esc_string(log_time_text) .. '(%d*%.?%d*)(.*)$')
ln = line:match(' | ' .. esc_string(log_length_text) .. '(%d*%.?%d*)(.*)$')
r = tonumber(ln) - tonumber(t)
l = line
line_pos = line_pos + 1
return {found_path = p, found_time = t, found_name = n, found_title = tt, found_line = l, found_sequence = line_pos, found_directory = d, found_datetime = dt, found_length = ln, found_remaining = r}
end)
end
function list_sort(tab, sort)
if sort == 'added-asc' then
table.sort(tab, function(a, b) return a['found_sequence'] < b['found_sequence'] end)
elseif sort == 'added-desc' then
table.sort(tab, function(a, b) return a['found_sequence'] > b['found_sequence'] end)
elseif sort == 'time-asc' then
table.sort(tab, function(a, b) return tonumber(a['found_time']) > tonumber(b['found_time']) end)
elseif sort == 'time-desc' then
table.sort(tab, function(a, b) return tonumber(a['found_time']) < tonumber(b['found_time']) end)
elseif sort == 'alphanum-asc' or sort == 'alphanum-desc' then
local function padnum(d) local dec, n = string.match(d, "(%.?)0*(.+)")
return #dec > 0 and ("%.12f"):format(d) or ("%s%03d%s"):format(dec, #n, n) end
if sort == 'alphanum-asc' then
table.sort(tab, function(a, b) return tostring(a['found_path']):lower():gsub("%.?%d+", padnum) .. ("%3d"):format(#b) > tostring(b['found_path']):lower():gsub("%.?%d+", padnum) .. ("%3d"):format(#a) end)
elseif sort == 'alphanum-desc' then
table.sort(tab, function(a, b) return tostring(a['found_path']):lower():gsub("%.?%d+", padnum) .. ("%3d"):format(#b) < tostring(b['found_path']):lower():gsub("%.?%d+", padnum) .. ("%3d"):format(#a) end)
end
end
return tab
end
function parse_header(string)
local osd_header_color = string.format("{\\1c&H%s}", o.header_color)
local osd_search_color = osd_header_color
if search_active == 'typing' then
osd_search_color = string.format("{\\1c&H%s}", o.search_color_typing)
elseif search_active == 'not_typing' then
osd_search_color = string.format("{\\1c&H%s}", o.search_color_not_typing)
end
local osd_msg_end = "{\\1c&HFFFFFF}"
string = string:gsub("%%total%%", #list_contents)
:gsub("%%cursor%%", list_cursor)
if filterName ~= 'all' then
string = string:gsub("%%filter%%", filterName)
:gsub("%%prefilter%%", o.header_filter_pre_text)
:gsub("%%afterfilter%%", o.header_filter_after_text)
else
string = string:gsub("%%filter%%", '')
:gsub("%%prefilter%%", '')
:gsub("%%afterfilter%%", '')
end
local list_total_duration = 0
if string:match('%listduration%%') then
list_total_duration = get_total_duration('found_time')
if list_total_duration > 0 then
string = string:gsub("%%listduration%%", format_time(list_total_duration, o.header_duration_time_format[3], o.header_duration_time_format[2], o.header_duration_time_format[1]))
else
string = string:gsub("%%listduration%%", '')
end
end
if list_total_duration > 0 then
string = string:gsub("%%prelistduration%%", o.header_list_duration_pre_text)
:gsub("%%afterlistduration%%", o.header_list_duration_after_text)
else
string = string:gsub("%%prelistduration%%", '')
:gsub("%%afterlistduration%%", '')
end
local list_total_length = 0
if string:match('%listlength%%') then
list_total_length = get_total_duration('found_length')
if list_total_length > 0 then
string = string:gsub("%%listlength%%", format_time(list_total_length, o.header_length_time_format[3], o.header_length_time_format[2], o.header_length_time_format[1]))
else
string = string:gsub("%%listlength%%", '')
end
end
if list_total_length > 0 then
string = string:gsub("%%prelistlength%%", o.header_list_length_pre_text)
:gsub("%%afterlistlength%%", o.header_list_length_after_text)
else
string = string:gsub("%%prelistlength%%", '')
:gsub("%%afterlistlength%%", '')
end
local list_total_remaining = 0
if string:match('%listremaining%%') then
list_total_remaining = get_total_duration('found_remaining')
if list_total_remaining > 0 then
string = string:gsub("%%listremaining%%", format_time(list_total_remaining, o.header_remaining_time_format[3], o.header_remaining_time_format[2], o.header_remaining_time_format[1]))
else
string = string:gsub("%%listremaining%%", '')
end
end
if list_total_remaining > 0 then
string = string:gsub("%%prelistremaining%%", o.header_list_remaining_pre_text)
:gsub("%%afterlistremaining%%", o.header_list_remaining_after_text)
else
string = string:gsub("%%prelistremaining%%", '')
:gsub("%%afterlistremaining%%", '')
end
if #list_highlight_cursor > 0 then
string = string:gsub("%%highlight%%", #list_highlight_cursor)
:gsub("%%prehighlight%%", o.header_highlight_pre_text)
:gsub("%%afterhighlight%%", o.header_highlight_after_text)
else
string = string:gsub("%%highlight%%", '')
:gsub("%%prehighlight%%", '')
:gsub("%%afterhighlight%%", '')
end
if sortName and sortName ~= o.header_sort_hide_text then
string = string:gsub("%%sort%%", sortName)
:gsub("%%presort%%", o.header_sort_pre_text)
:gsub("%%aftersort%%", o.header_sort_after_text)
else
string = string:gsub("%%sort%%", '')
:gsub("%%presort%%", '')
:gsub("%%aftersort%%", '')
end
if search_active then
local search_string_osd = search_string
if search_string_osd ~= '' then
search_string_osd = search_string:gsub('%%', '%%%%%%%%'):gsub('\\', '\\'):gsub('{', '\\{')
end
string = string:gsub("%%search%%", osd_search_color..search_string_osd..osd_header_color)
:gsub("%%presearch%%", o.header_search_pre_text)
:gsub("%%aftersearch%%", o.header_search_after_text)
else
string = string:gsub("%%search%%", '')
:gsub("%%presearch%%", '')
:gsub("%%aftersearch%%", '')
end
string = string:gsub("%%%%", "%%")
return string
end
function get_list_contents(filter, sort)
if not filter then filter = filterName end
if not sort then sort = get_list_sort(filter) end
local current_sort
local filtered_table = {}
local prev_list_contents
if list_contents ~= nil and list_contents[1] then
prev_list_contents = list_contents
else
prev_list_contents = read_log_table()
end
list_contents = read_log_table()
if not list_contents and not search_active or not list_contents[1] and not search_active then return end
current_sort = 'added-asc'
if filter == 'recents' then
table.sort(list_contents, function(a, b) return a['found_sequence'] < b['found_sequence'] end)
local unique_values = {}
local list_total = #list_contents
if filePath == list_contents[#list_contents].found_path and tonumber(list_contents[#list_contents].found_time) == 0 then
list_total = list_total -1
end
for i = list_total, 1, -1 do
if not has_value(unique_values, list_contents[i].found_path) then
table.insert(unique_values, list_contents[i].found_path)
table.insert(filtered_table, list_contents[i])
end
end
table.sort(filtered_table, function(a, b) return a['found_sequence'] < b['found_sequence'] end)
list_contents = filtered_table
end
if filter == 'distinct' then
table.sort(list_contents, function(a, b) return a['found_sequence'] < b['found_sequence'] end)
local unique_values = {}
local list_total = #list_contents
if filePath == list_contents[#list_contents].found_path and tonumber(list_contents[#list_contents].found_time) == 0 then
list_total = list_total -1
end
for i = list_total, 1, -1 do
if list_contents[i].found_directory and not has_value(unique_values, list_contents[i].found_directory) and not starts_protocol(protocols, list_contents[i].found_path) then
table.insert(unique_values, list_contents[i].found_directory)
table.insert(filtered_table, list_contents[i])
end
end
table.sort(filtered_table, function(a, b) return a['found_sequence'] < b['found_sequence'] end)
list_contents = filtered_table
end
if filter == 'fileonly' then
for i = 1, #list_contents do
if tonumber(list_contents[i].found_time) == 0 then
table.insert(filtered_table, list_contents[i])
end
end
list_contents = filtered_table
end
if filter == 'timeonly' then
for i = 1, #list_contents do
if tonumber(list_contents[i].found_time) > 0 then
table.insert(filtered_table, list_contents[i])
end
end
list_contents = filtered_table
end
if filter == 'titleonly' then
for i = 1, #list_contents do
if list_contents[i].found_title then
table.insert(filtered_table, list_contents[i])
end
end
list_contents = filtered_table
end
if filter == 'protocols' then
for i = 1, #list_contents do
if starts_protocol(o.logging_protocols, list_contents[i].found_path) then
table.insert(filtered_table, list_contents[i])
end
end
list_contents = filtered_table
end
if filter == 'keywords' then
for i = 1, #list_contents do
if contain_value(o.keywords_filter_list, list_contents[i].found_line) then
table.insert(filtered_table, list_contents[i])
end
end
list_contents = filtered_table
end
if filter == 'playing' then
for i = 1, #list_contents do
if list_contents[i].found_path == filePath then
table.insert(filtered_table, list_contents[i])
end
end
list_contents = filtered_table
end
if search_active and search_string ~= '' then
local filtered_table = {}
local search_query = ''
for search in search_string:gmatch("[^%s]+") do
search_query = search_query..'.-'..esc_string(search)
end
local contents_string = ''
for i = 1, #list_contents do
if o.search_behavior == 'specific' then
if string.lower(list_contents[i].found_path):match(string.lower(search_query)) then
table.insert(filtered_table, list_contents[i])
elseif list_contents[i].found_title and string.lower(list_contents[i].found_title):match(string.lower(search_query)) then
table.insert(filtered_table, list_contents[i])
elseif tonumber(list_contents[i].found_time) > 0 and format_time(list_contents[i].found_time, o.osd_time_format[3], o.osd_time_format[2], o.osd_time_format[1]):match(search_query) then
table.insert(filtered_table, list_contents[i])
elseif string.lower(list_contents[i].found_datetime):match(string.lower(search_query)) then
table.insert(filtered_table, list_contents[i])
end
elseif o.search_behavior == 'any' then
contents_string = list_contents[i].found_datetime..(list_contents[i].found_title or '')..list_contents[i].found_path
if tonumber(list_contents[i].found_time) > 0 then
contents_string = contents_string..format_time(list_contents[i].found_time, o.osd_time_format[3], o.osd_time_format[2], o.osd_time_format[1])
end
elseif o.search_behavior == 'any-notime' then
contents_string = list_contents[i].found_datetime..(list_contents[i].found_title or '')..list_contents[i].found_path
end
if string.lower(contents_string):match(string.lower(search_query)) then
table.insert(filtered_table, list_contents[i])
end
end
list_contents = filtered_table
end
if sort ~= current_sort then
list_sort(list_contents, sort)
end
if not list_contents and not search_active or not list_contents[1] and not search_active then return end
end
function get_list_sort(filter)
if not filter then filter = filterName end
local sort
for i=1, #o.list_filters_sort do
if o.list_filters_sort[i][1] == filter then
if has_value(available_sorts, o.list_filters_sort[i][2]) then sort = o.list_filters_sort[i][2] end
break
end
end
if not sort and has_value(available_sorts, o.list_default_sort) then sort = o.list_default_sort end
if not sort then sort = 'added-asc' end
return sort
end
function draw_list()
local osd_msg = ''
local osd_index = ''
local osd_key = ''
local osd_color = ''
local key = 0
local osd_text = string.format("{\\an%f{\\fscx%f}{\\fscy%f}{\\bord%f}{\\1c&H%s}", o.list_alignment, o.text_scale, o.text_scale, o.text_border, o.text_color)
local osd_cursor = string.format("{\\an%f}{\\fscx%f}{\\fscy%f}{\\bord%f}{\\1c&H%s}", o.list_alignment, o.text_cursor_scale, o.text_cursor_scale, o.text_cursor_border, o.text_cursor_color)
local osd_header = string.format("{\\an%f}{\\fscx%f}{\\fscy%f}{\\bord%f}{\\1c&H%s}", o.list_alignment, o.header_scale, o.header_scale, o.header_border, o.header_color)
local osd_msg_end = "{\\1c&HFFFFFF}"
local osd_time_type = 'found_time'
if o.text_time_type == 'length' then
osd_time_type = 'found_length'
elseif o.text_time_type == 'remaining' then
osd_time_type = 'found_remaining'
end
if o.header_text ~= '' then
osd_msg = osd_msg .. osd_header .. parse_header(o.header_text)
osd_msg = osd_msg .. "\\h\\N\\N" .. osd_msg_end
end
if search_active and not list_contents[1] then
osd_msg = osd_msg .. 'No search results found' .. osd_msg_end
end
if o.list_middle_loader then
list_start = list_cursor - math.floor(o.list_show_amount / 2)
else
list_start = list_cursor - o.list_show_amount
end
local showall = false
local showrest = false
if list_start < 0 then list_start = 0 end
if #list_contents <= o.list_show_amount then
list_start = 0
showall = true
end
if list_start > math.max(#list_contents - o.list_show_amount - 1, 0) then
list_start = #list_contents - o.list_show_amount
showrest = true
end
if list_start > 0 and not showall then
osd_msg = osd_msg .. o.list_sliced_prefix .. osd_msg_end
end
for i = list_start, list_start + o.list_show_amount - 1, 1 do
if i == #list_contents then break end
if o.show_paths then
p = list_contents[#list_contents - i].found_path or list_contents[#list_contents - i].found_name or ""
else
p = list_contents[#list_contents - i].found_name or list_contents[#list_contents - i].found_path or ""
end
if o.slice_longfilenames and p:len() > o.slice_longfilenames_amount then
p = p:sub(1, o.slice_longfilenames_amount) .. "..."
end
if o.quickselect_0to9_keybind and o.list_show_amount <= 10 and o.quickselect_0to9_pre_text then
key = 1 + key
if key == 10 then key = 0 end
osd_key = '(' .. key .. ') '
end
if o.show_item_number then
osd_index = (i + 1) .. '. '
end
if i + 1 == list_cursor then
osd_color = osd_cursor
else
osd_color = osd_text
end
for j = 1, #list_highlight_cursor do
if list_highlight_cursor[j] and list_highlight_cursor[j][1] == i+1 then
osd_msg = osd_msg..osd_color..esc_string(o.text_highlight_pre_text)
end
end
-- example in the mpv source suggests this escape method for set_osd_ass:
-- https://github.com/mpv-player/mpv/blob/94677723624fb84756e65c8f1377956667244bc9/player/lua/stats.lua#L145
p = p:gsub("\\", "/")
:gsub("{", "\\{")
:gsub("^ ", "\\h")
osd_msg = osd_msg .. osd_color .. osd_key .. osd_index .. p
if list_contents[#list_contents - i][osd_time_type] and tonumber(list_contents[#list_contents - i][osd_time_type]) > 0 then
osd_msg = osd_msg .. o.time_seperator .. format_time(list_contents[#list_contents - i][osd_time_type], o.list_time_format[3], o.list_time_format[2], o.list_time_format[1])
end
osd_msg = osd_msg .. '\\h\\N\\N' .. osd_msg_end
if i == list_start + o.list_show_amount - 1 and not showall and not showrest then
osd_msg = osd_msg .. o.list_sliced_suffix
end
end
mp.set_osd_ass(0, 0, osd_msg)
end
function list_empty_error_msg()
if list_contents ~= nil and list_contents[1] then return end
local msg_text
if filterName ~= 'all' then
msg_text = filterName .. " filter in History Empty"
else
msg_text = "History Empty"
end
msg.info(msg_text)
if o.osd_messages == true and not list_drawn then
mp.osd_message(msg_text)
end
end
function display_list(filter, sort, action)
if not filter or not has_value(available_filters, filter) then filter = 'all' end
if not sortName then sortName = get_list_sort(filter) end
local prev_sort = sortName
if not has_value(available_sorts, prev_sort) then prev_sort = get_list_sort() end
if not sort then sort = get_list_sort(filter) end
sortName = sort
local prev_filter = filterName
filterName = filter
get_list_contents(filter, sort)
if action ~= 'hide-osd' then
if not list_contents or not list_contents[1] then
list_empty_error_msg()
filterName = prev_filter
get_list_contents(filterName)
return
end
end
if not list_contents and not search_active or not list_contents[1] and not search_active then return end
if not has_value(o.filters_and_sequence, filter) then
table.insert(o.filters_and_sequence, filter)
end
local insert_new = false
local trigger_close_list = false
local trigger_initial_list = false
if not list_pages or not list_pages[1] then
table.insert(list_pages, {filter, 1, 1, {}, sort})
else
for i = 1, #list_pages do
if list_pages[i][1] == filter then
list_pages[i][3] = list_pages[i][3]+1
insert_new = false
break
else
insert_new = true
end
end
end
if insert_new then table.insert(list_pages, {filter, 1, 1, {}, sort}) end
for i = 1, #list_pages do
if not search_active and list_pages[i][1] == prev_filter then
list_pages[i][2] = list_cursor
list_pages[i][4] = list_highlight_cursor
list_pages[i][5] = prev_sort
end
if list_pages[i][1] ~= filter then
list_pages[i][3] = 0
end
if list_pages[i][3] == 2 and filter == 'all' and o.main_list_keybind_twice_exits then
trigger_close_list = true
elseif list_pages[i][3] == 2 and list_pages[1][1] == filter then
trigger_close_list = true
elseif list_pages[i][3] == 2 then
trigger_initial_list = true
end
end
if trigger_initial_list then
display_list(list_pages[1][1], nil, 'hide-osd')
return