-
Notifications
You must be signed in to change notification settings - Fork 17
/
Copy pathlyrics+.lua
3527 lines (3308 loc) · 159 KB
/
lyrics+.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 2020 amirchev/wzaggle
-- Licensed under the Apache License, Version 2.0 (the "License");
-- you may not use this file except in compliance with the License.
-- You may obtain a copy of the License at
-- http://www.apache.org/licenses/LICENSE-2.0
-- Unless required by applicable law or agreed to in writing, software
-- distributed under the License is distributed on an "AS IS" BASIS,
-- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-- See the License for the specific language governing permissions and
-- limitations under the License.
---------------------------------------------------------------------------------------------------------------------
-- Lyrics or Lyrics+ is a joint effort started by amirchev and joined by wzaggle (DC Strato)
-- The lua script breaks up text stored in files to be shown as pages in an OBS stream and is designed to be used
-- with songs, scripture, responsive reading, or any text that needs to have managed pages.
-- Songs or Text files can be created and edited within the script or with an external text editor
-- Files can be either A) Previewed to the scene, B) Pre-loaded into a "Prepared List" queue to be displayed in
-- the order of the list, or C) loaded on the fly when a scene loads, using a Prepare Lyric source within the scene.
-- The general function of the script is to modify the contents, visibility, and opacity levels for text sources
-- that have been created within OBS scenes. Currently only a single Global set of text sources are supported for
-- Text Source -------> The text source that will contain the Pages of song lyrics
-- Title Source ------> The text source that will contain the Title of the current song
-- Alternate Source --> The text source that will contain the Pages of Alternate song lyrics
-- Static Source -----> The text source that will contain any Static Text that will be shown along with lyrics
---------------------------------------------------------------------------------------------------------------------
-- NOTES ON INTERNAL DOCUMENTATION
-- Effort has been made to try and lay out the general function of the script for those wishing to contribute or just
-- follow its operation. The terms Text File, Song, or Lyric all refer to the text that is the content to be
-- paged by the script, and are used interchangeably within the internal documentation, whatever the purpose or
-- intent of that text might be to the user.
obs = obslua
bit = require("bit")
-- SOURCE DEFINITIONS USED WITH LOAD LYRIC SOURCES
source_def = {}
source_def.id = "Prepare_Lyrics"
source_def.type = OBS_SOURCE_TYPE_INPUT
source_def.output_flags = bit.bor(obs.OBS_SOURCE_CUSTOM_DRAW)
-- TEXT SOURCE NAMES USED BY LYRICS
source_name = ""
alternate_source_name = ""
static_source_name = ""
static_text = ""
title_source_name = ""
-- SETTINGS FOR WHAT OS IS BEING USED (FILE OPERATIONS DEPENDENT)
windows_os = false
display_lines = 0 -- lyric preparation option for default lines to display
ensure_lines = true -- page padding to ensure line count on/off
-- LYRICS/ALTERNATE LYRICS BY PAGE
lyrics = {}
alternate = {}
-- VERSE PAGE POINTERS (IF STARING POINTS MARKED IN TEXT WITH ##V)
verses = {}
-- MISC FLAGS AND TABLES
page_index = 0 -- current page of lyrics being displayed
prepared_index = 1 -- TODO: avoid setting prepared_index directly, use prepare_selected
song_directory = {} -- holds list of current songs from song directory TODO: Multiple Song Books (Directories)
prepared_songs = {} -- holds pre-prepared list of songs to use
extra_sources = {} -- holder for extra sources settings
max_opacity = {} -- record maximum opacity settings for sources
loadLyric_items = {}
link_text = false -- true if Title and Static should fade with text only during hide/show
link_extras = false -- extras fade with text always when true, only during hide/show when false
source_song_title = "" -- The song title from a source loaded song
using_source = false -- true when a lyric load song is being used instead of a pre-prepared song
using_preview = false -- true if song is not in prepared list nor from a source load
source_active = false -- true when a lyric load source is active in the current scene (song is loaded or available to load)
load_source = nil -- indicates source that loaded a lyric for use in monitor
load_scene = "" -- name of scene loading a lyric with a source
last_prepared_song = "" -- name of the last prepared song (prevents duplicate loading of already loaded song)
-- HOTKEY IDS USED IN SETTINGS FILE
hotkey_n_id = obs.OBS_INVALID_HOTKEY_ID
hotkey_p_id = obs.OBS_INVALID_HOTKEY_ID
hotkey_c_id = obs.OBS_INVALID_HOTKEY_ID
hotkey_n_p_id = obs.OBS_INVALID_HOTKEY_ID
hotkey_p_p_id = obs.OBS_INVALID_HOTKEY_ID
hotkey_home_id = obs.OBS_INVALID_HOTKEY_ID
hotkey_reset_id = obs.OBS_INVALID_HOTKEY_ID
-- KEYS USED FOR HOTKEYS IN SETTINGS FILE
hotkey_n_key = ""
hotkey_p_key = ""
hotkey_c_key = ""
hotkey_n_p_key = ""
hotkey_p_p_key = ""
hotkey_home_key = ""
hotkey_reset_key = ""
-- SCRIPT SETTINGS AND PROPERTY POINTERS
script_sets = nil
script_props = nil
source_sets = nil
source_props = nil
hotkey_props = nil
--MONITOR VARIABLES USED TO KEEP CURRENT STATISTICS SHOWED IN HTML FILE (TODO: ADD PREVIOUS LYRIC OPTION)
mon_song = ""
mon_lyric = ""
mon_verse = 0
mon_nextlyric = ""
mon_alt = ""
mon_nextalt = ""
mon_nextsong = ""
meta_tags = ""
-- META TAG VARIABLES
source_meta_tags = "" -- Current Filter Tags
use_meta_tags = false -- flag to use/not use filter
filtering = false -- set to keep user informed of background process
-- FLAGS USED IN USER INTERFACE
expandcollapse = true -- flag used in UI progressive disclosure
showhelp = false -- flag used to open and close markup HELP button text
-- TEXT STATUS & FADE
TEXT_VISIBLE = 0 -- text is visible
TEXT_HIDDEN = 1 -- text is hidden
TEXT_SHOWING = 3 -- going from hidden -> visible
TEXT_HIDING = 4 -- going from visible -> hidden
TEXT_TRANSITION_OUT = 5 -- fade out transition to next lyric
TEXT_TRANSITION_IN = 6 -- fade in transition after lyric change
TEXT_HIDE = 7 -- turn off the text and ignore fade if selected
TEXT_SHOW = 8 -- turn on the text and ignore fade if selected
-- GENERAL TEXT FADING
text_fade_enabled = false -- fading effect enabled (if false then source visibility is toggled rather than opacity changed)
all_sources_fade = false -- Title and Static fade when lyrics are changing or during show/hide
text_status = TEXT_VISIBLE -- current state of desired source visibility, one of above states VISIBLE thru SHOW
text_opacity = 100 -- used to fade text in/out
text_fade_speed = 5 -- speed used to fade text
-- FLAGS TO CONTROL BACKGROUND FADING
allow_back_fade = false -- overall fading is performed for text source background colors
use100percent = true -- Fading is 0-100% of opacity or 0 to Marked opacity
fade_text_back = false -- Text Background should fade
fade_title_back = false -- Title Background should fade
fade_alternate_back = false -- Alternate Lyric Background should fade
fade_static_back = false -- Static Text Background should fade
fade_extra_back = false -- Extra Linked Source Background should fade (if text source)
--[[ transitions are a work in progress to support duplicate source mode (not very stable)
in this mode OBS keeps separate sources in preview and active windows. Only pointers to the preview window are
accessable to the API. Changing the Active window requires both changing the Preview Window Text sources then
transitioning those changes to the Active Window. Fading is disabled because its effects are not visible in
the active window. --]]
transition_enabled = false
transition_completed = false
-------------------------------------------------------------------------------------------------------------------------
-- Help Button Text
-- Text shown when user selects toggles the Help Button to see valid Lyric Markup syntax
------------------------------------------------------------------------------------------------------------------------
help =
"▪▪▪▪▪ MARKUP SYNTAX HELP ▪▪▪▪▪▲- CLICK TO CLOSE -▲▪▪▪▪▪\n\n" ..
" Markup Syntax Markup Syntax \n" ..
"============ ========== ============ ==========\n" ..
" Display n Lines #L:n End Page after Line Line ###\n" ..
" Blank (Pad) Line ##B or ##P Blank(Pad) Lines #B:n or #P:n\n" ..
" External Refrain #r[ and #r] In-Line Refrain #R[ and #R]\n" ..
" Repeat Refrain ##r or ##R Duplicate Line n times #D:n Line\n" ..
" Static Lines #S[ and #s] Single Static Line #S: Line \n" ..
"Alternate Text #A[ and #A] Alt Line Repeat n Pages #A:n Line \n" ..
"Comment Line // Line Block Comments //[ and //] \n" ..
"Mark Verses ##V Override Title #T: text\n\n" ..
"Optional comma delimited meta tags follow '//meta ' on 1st line"
-- SIMPLE DEBUGGING/PRINT MECHANISM
--DEBUG = true -- on switch for entire debugging mechanism
--DEBUG_METHODS = true -- print method names
--DEBUG_INNER = true -- print inner method breakpoints
--DEBUG_CUSTOM = true -- print custom debugging messages
--DEBUG_BOOL = true -- print message with bool state true/false
--------
----------------
------------------------ PAGING FUNCTIONS
----------------
--------
--
---------------------------------------------------------------------------------------------------------------------
-- Function to move to Next page (lyric) of a song or text (HOTKEY ENABLED)
-- contents of text sources are only changed if they are showing to prevent accidental background paging
-- Manages adding a transition for duplicated sources mode if checked
---------------------------------------------------------------------------------------------------------------------
function next_lyric(pressed)
if not pressed then
return
end
dbg_method("next_lyric")
-- check if transition enabled
if transition_enabled and not transition_completed then
obs.obs_frontend_preview_program_trigger_transition()
transition_completed = true
return
end
dbg_inner("next page")
if (#lyrics > 0 or #alternate > 0) and sourceShowing() then -- only change if defined and showing
if page_index < #lyrics then
page_index = page_index + 1
dbg_inner("page_index: " .. page_index)
transition_lyric_text(false)
else
next_prepared(true)
end
end
end
---------------------------------------------------------------------------------------------------------------------
-- Function to move to Previous page (lyric) of a song or text (HOTKEY ENABLED)
-- contents of text sources are only changed if they are showing to prevent accidental background paging
-- Manages adding a transition for duplicated sources mode if checked
---------------------------------------------------------------------------------------------------------------------
function prev_lyric(pressed)
if not pressed then
return
end
dbg_method("prev_lyric")
if (#lyrics > 0 or #alternate > 0) and sourceShowing() then -- only change if defined and showing
if page_index > 1 then
page_index = page_index - 1
dbg_inner("page_index: " .. page_index)
transition_lyric_text(false)
else
prev_prepared(true)
end
end
end
---------------------------------------------------------------------------------------------------------------------
-- Function to move to the Previous Prepared song or text from the Prepared Songs List (HOTKEY ENABLED)
-- Songs rotate through the Prepared List starting back at the last prepared song, if paged backward from the 1st.
-- Songs prepared through an Active Prepare Lyric source are included in the rotation between the last and 1st song
-- Songs currently shown in Preview mode are discarded and the Preview Mode cancelled
---------------------------------------------------------------------------------------------------------------------
function prev_prepared(pressed)
if not pressed then
return
end
if #prepared_songs == 0 then
return
end
using_preview = false
if using_source then
using_source = false
prepare_selected(prepared_songs[prepared_index])
return
end
if prepared_index > 1 then
using_source = false
prepare_selected(prepared_songs[prepared_index - 1])
return
end
if not source_active or using_source then
using_source = false
prepare_selected(prepared_songs[#prepared_songs]) -- cycle through prepared
else
using_source = true
prepared_index = #prepared_songs -- wrap prepared index to end so ready if leaving load source
load_source_song(load_source, false)
end
end
---------------------------------------------------------------------------------------------------------------------
-- Function to move to the NEXT Prepared song or text from the Prepared Songs List (HOTKEY ENABLED)
-- Songs rotate through the Prepared List starting at the 1st prepared song, if paged forward from the last.
-- Songs prepared through an Active Prepare Lyric source are included in the rotation between the last and 1st song
-- Songs currently shown in Preview mode are discarded and the Preview Mode cancelled
---------------------------------------------------------------------------------------------------------------------
function next_prepared(pressed)
if not pressed then
return
end
if #prepared_songs == 0 then
return
end
using_preview = false
if using_source then
using_source = false
dbg_custom("do current prepared")
prepare_selected(prepared_songs[prepared_index]) -- if source load song showing then goto curren prepared song
return
end
if prepared_index < #prepared_songs then
using_source = false
dbg_custom("do next prepared")
prepare_selected(prepared_songs[prepared_index + 1]) -- if prepared then goto next prepared
return
end
if not source_active or using_source then
using_source = false
dbg_custom("do first prepared")
prepare_selected(prepared_songs[1]) -- at the end so go back to start if no source load available
else
using_source = true
dbg_custom("do source prepared")
prepared_index = 1 -- wrap prepared index to beginning so ready if leaving load source
load_source_song(load_source, false)
end
end
---------------------------------------------------------------------------------------------------------------------
-- Function to move to the FIRST Prepared song or text from the Prepared Songs List (HOTKEY ENABLED)
-- Songs currently shown in Preview mode are discarded and the Preview Mode cancelled
---------------------------------------------------------------------------------------------------------------------
function home_prepared(pressed)
if not pressed then
return false
end
dbg_method("home_prepared")
using_source = false
page_index = 0
using_preview = false
local prop_prep_list = obs.obs_properties_get(props, "prop_prepared_list")
if #prepared_songs > 0 then
obs.obs_data_set_string(script_sets, "prop_prepared_list", prepared_songs[1])
else
obs.obs_data_set_string(script_sets, "prop_prepared_list", "")
end
obs.obs_properties_apply_settings(props, script_sets)
prepared_index = 1
prepare_selected(prepared_songs[prepared_index])
return true
end
---------------------------------------------------------------------------------------------------------------------
-- Function to move to the first page of the current song or text (HOTKEY ENABLED)
---------------------------------------------------------------------------------------------------------------------
function home_song(pressed)
if not pressed then
return false
end
dbg_method("home_song")
page_index = 1
transition_lyric_text(false)
return true
end
---------------------------------------------------------------------------------------------------------------------
-- Function to hide/show the current text within sources (HOTKEY ENABLED)
-- option exists to include the song Title and any linked Extra Sources
---------------------------------------------------------------------------------------------------------------------
function toggle_lyrics_visibility(pressed)
dbg_method("toggle_lyrics_visibility")
if not pressed then
return
end
if link_text then
all_sources_fade = true
end
if text_status ~= TEXT_HIDDEN then
dbg_inner("hiding")
set_text_visibility(TEXT_HIDDEN)
else
dbg_inner("showing")
set_text_visibility(TEXT_VISIBLE)
end
end
---------------------------------------------------------------------------------------------------------------------
-- Functions used with paging Buttons on the scripts Properties User Interface
-- These buttons simply call the same functions used by the hotkeys
---------------------------------------------------------------------------------------------------------------------
function next_button_clicked(props, p)
next_lyric(true)
return true
end
function prev_button_clicked(props, p)
prev_lyric(true)
return true
end
function toggle_button_clicked(props, p)
toggle_lyrics_visibility(true)
return true
end
function home_button_clicked(props, p)
home_song(true)
return true
end
function reset_button_clicked(props, p)
home_prepared(true)
return true
end
function prev_prepared_clicked(props, p)
prev_prepared(true)
return true
end
function next_prepared_clicked(props, p)
next_prepared(true)
return true
end
--------
----------------
------------------------ PROGRAM FUNCTION BUTTONS AND CALLBACKS
----------------
--------
---------------------------------------------------------------------------------------------------------------------
-- Save is called when the users enters a new song title and lyric text to be saved, or modifies an existing song
-- If song is the one last prepared for viewing then update it (allows on-the-fly song edits)
---------------------------------------------------------------------------------------------------------------------
function save_song_clicked(props, p)
local name = obs.obs_data_get_string(script_sets, "prop_edit_song_title")
local text = obs.obs_data_get_string(script_sets, "prop_edit_song_text")
-- if this is a new song, add it to the directory
if save_song(name, text) then -- new song so add to directory table
local prop_dir_list = obs.obs_properties_get(props, "prop_directory_list")
obs.obs_property_list_add_string(prop_dir_list, name, name)
obs.obs_data_set_string(script_sets, "prop_directory_list", name)
obs.obs_properties_apply_settings(props, script_sets)
elseif name == last_prepared_song then
-- if this song is being displayed, then prepare it anew
prepare_song_by_name(name)
transition_lyric_text(false)
end
return true
end
---------------------------------------------------------------------------------------------------------------------
-- Called to delete the current song entirely
-- removed from prepared list if present
-- clears display if last song in directory
---------------------------------------------------------------------------------------------------------------------
function delete_song_clicked(props, p)
dbg_method("delete_song_clicked")
-- call delete song function
local name = obs.obs_data_get_string(script_sets, "prop_directory_list")
delete_song(name)
-- update
local prop_dir_list = obs.obs_properties_get(props, "prop_directory_list")
for i = 0, obs.obs_property_list_item_count(prop_dir_list) do
if obs.obs_property_list_item_string(prop_dir_list, i) == name then
obs.obs_property_list_item_remove(prop_dir_list, i)
if i > 1 then
i = i - 1
end
if #song_directory > 0 then
obs.obs_data_set_string(script_sets, "prop_directory_list", song_directory[i])
else
obs.obs_data_set_string(script_sets, "prop_directory_list", "")
obs.obs_data_set_string(script_sets, "prop_edit_song_title", "")
obs.obs_data_set_string(script_sets, "prop_edit_song_text", "")
end
local prop_prep_list = obs.obs_properties_get(props, "prop_prepared_list")
if get_index_in_list(prepared_songs, name) ~= nil then
if obs.obs_property_list_item_string(prop_prep_list, i) == name then
obs.obs_property_list_item_remove(prop_prep_list, i)
if i > 1 then
i = i - 1
end
if #prepared_songs > 0 then
obs.obs_data_set_string(script_sets, "prop_prepared_list", prepared_songs[i])
else
obs.obs_data_set_string(script_sets, "prop_prepared_list", "")
end
end
end
obs.obs_properties_apply_settings(props, script_sets)
return true
end
end
return true
end
-----------------------------------------------------------------------------------------------------------------------
-- PREPARE SONG
-- Adds the currently selected song from the directory to the prepared list. Sets index to first song.
-----------------------------------------------------------------------------------------------------------------------
function prepare_song_clicked(props, p)
dbg_method("prepare_song_clicked")
set_text_visibility(TEXT_HIDDEN)
prepared_songs[#prepared_songs + 1] = obs.obs_data_get_string(script_sets, "prop_directory_list")
prepared_index = 1
local prop_prep_list = obs.obs_properties_get(props, "prop_prepared_list")
obs.obs_property_list_add_string(prop_prep_list, prepared_songs[#prepared_songs], prepared_songs[#prepared_songs])
-- next line PREPARES the newly Added Song
-- obs.obs_data_set_string(script_sets, "prop_prepared_list", prepared_songs[#prepared_songs])
obs.obs_data_set_string(script_sets, "prop_prepared_list", "*** LIST OF PREPARED SONGS ***")
if #prepared_songs > 0 then
obs.obs_property_set_description(prop_prep_list, "<font color=#FFD966>Prepared (" .. #prepared_songs .. ")</font>")
else
obs.obs_property_set_description(prop_prep_list, "<font color=#FFD966>Prepared</font>")
end
obs.obs_properties_apply_settings(props, script_sets)
save_prepared(script_sets)
return true
end
-------------------------------------------------------------------------------------------------------------------------
-- PREVIEW SONG (Preveiw Mode)
-- Prepares the selected song into text sources without adding it to the prepared list
-------------------------------------------------------------------------------------------------------------------------
function preview_clicked(props, p)
dbg_method("preview_song_clicked")
if using_preview then
using_preview = false
if source_active then
load_source_song(load_source, false)
elseif #prepared_songs > 0 then
prepare_song_by_index(prepared_index)
end
transition_lyric_text()
return true
end
local song = obs.obs_data_get_string(script_sets, "prop_directory_list")
using_source = true
using_preview = true
all_sources_fade = true -- fade title and source the first time
set_text_visibility(TEXT_HIDE) -- if this is a transition turn it off so it can fade in
if song ~= last_prepared_song then -- skips prepare if song already prepared just to save some processing cycles
prepare_selected(song)
end
transition_lyric_text(true)
return true
end
-------------------------------------------------------------------------------------------------------------------------
-- LOAD SONG FROM DIRECTORY
-- Loads the text from the selected song into the Lyrics Edit window for editing within properties
-- Selects the current song for possible addition to prepared list
-------------------------------------------------------------------------------------------------------------------------
function load_song_from_directory(props, prop, settings)
local name = obs.obs_data_get_string(script_sets, "prop_directory_list")
if get_index_in_list(song_directory, name) == nil then
return false
end -- do nothing if invalid name
obs.obs_data_set_string(settings, "prop_edit_song_title", name)
local song_lines = get_song_text(name)
local combined_text = ""
for i, line in ipairs(song_lines) do
if (i < #song_lines) then
combined_text = combined_text .. line .. "\n"
else
combined_text = combined_text .. line
end
end
obs.obs_data_set_string(settings, "prop_edit_song_text", combined_text)
update_monitor()
return true
end
-------------------------------------------------------------------------------------------------------------------------
-- REFRESH SOURCES
-- New sources added to OBS are not automatically included (yet)
-- Also refreshes the Directory of Songs
-------------------------------------------------------------------------------------------------------------------------
function refresh_button_clicked(props, p)
dbg_method("Refresh Sources")
local source_prop = obs.obs_properties_get(props, "prop_source_list")
local alternate_source_prop = obs.obs_properties_get(props, "prop_alternate_list")
local static_source_prop = obs.obs_properties_get(props, "prop_static_list")
local title_source_prop = obs.obs_properties_get(props, "prop_title_list")
local extra_source_prop = obs.obs_properties_get(props, "extra_source_list")
obs.obs_property_list_clear(source_prop) -- clear current properties list
obs.obs_property_list_clear(alternate_source_prop) -- clear current properties list
obs.obs_property_list_clear(static_source_prop) -- clear current properties list
obs.obs_property_list_clear(title_source_prop) -- clear current properties list
obs.obs_property_list_clear(extra_source_prop) -- clear extra sources list
obs.obs_property_list_add_string(extra_source_prop, "", "")
local sources = obs.obs_enum_sources()
if sources ~= nil then
local n = {}
for _, source in ipairs(sources) do
local name = obs.obs_source_get_name(source)
if isValid(source) then
obs.obs_property_list_add_string(extra_source_prop, name, name) -- add source to extra list
end
source_id = obs.obs_source_get_unversioned_id(source)
if source_id == "text_gdiplus" or source_id == "text_ft2_source" then
n[#n + 1] = name
end
end
table.sort(n)
obs.obs_property_list_add_string(source_prop, "", "")
obs.obs_property_list_add_string(title_source_prop, "", "")
obs.obs_property_list_add_string(alternate_source_prop, "", "")
obs.obs_property_list_add_string(static_source_prop, "", "")
for _, name in ipairs(n) do
obs.obs_property_list_add_string(source_prop, name, name)
obs.obs_property_list_add_string(title_source_prop, name, name)
obs.obs_property_list_add_string(alternate_source_prop, name, name)
obs.obs_property_list_add_string(static_source_prop, name, name)
end
end
obs.source_list_release(sources)
refresh_directory()
return true
end
-------------------------------------------------------------------------------------------------------------------------
-- REFRESH DIRECTORY
-- Refreshes the Directory of Songs
-------------------------------------------------------------------------------------------------------------------------
function refresh_directory_button_clicked(props, p)
dbg_method("refresh directory")
refresh_directory()
return true
end
function refresh_directory()
local prop_dir_list = obs.obs_properties_get(script_props, "prop_directory_list")
local source_prop = obs.obs_properties_get(props, "prop_source_list")
load_source_song_directory(use_meta_tags)
table.sort(song_directory)
obs.obs_property_list_clear(prop_dir_list) -- clear directories
for _, name in ipairs(song_directory) do
dbg_inner(name)
obs.obs_property_list_add_string(prop_dir_list, name, name)
end
obs.obs_properties_apply_settings(script_props, script_sets)
end
-------------------------------------------------------------------------------------------------------------------------
-- PREPARE SELECTION
-- Selecting a song from the Prepared List will prepare it into sources
-- Called with ANY change to the prepared song list
-------------------------------------------------------------------------------------------------------------------------
function prepare_selection_made(props, prop, settings)
dbg_method("prepare_selection_made")
local name = obs.obs_data_get_string(settings, "prop_prepared_list")
using_source = false
using_preview = false
prepare_selected(name)
return true
end
-------------------------------------------------------------------------------------------------------------------------
-- CLEAR PREPARED SONGS LIST
-- Removes all songs from the prepared list
-------------------------------------------------------------------------------------------------------------------------
function clear_prepared_clicked(props, p)
dbg_method("clear_prepared_clicked")
prepared_songs = {} -- required for monitor page
page_index = 0 -- required for monitor page
prepared_index = 0 -- required for monitor page
save_prepared()
if source_active then
load_source_song(load_source, false)
end
update_monitor() -- required for monitor page
transition_lyric_text()
-- clear the list
local prep_prop = obs.obs_properties_get(props, "prop_prepared_list")
obs.obs_property_list_clear(prep_prop)
obs.obs_property_set_description(obs.obs_properties_get(props, "prop_prepared_list"), "<font color=#FFD966>Prepared</font>")
obs.obs_property_list_add_string(obs.obs_properties_get(props, "prop_prepared_list"), "*** LIST OF PREPARED SONGS ***", "")
obs.obs_data_set_string(script_sets, "prop_prepared_list", "*** LIST OF PREPARED SONGS ***")
local pp = obs.obs_properties_get(props, "edit_grp")
if obs.obs_property_visible(pp) then
obs.obs_property_set_visible(pp, false)
local mpb = obs.obs_properties_get(props, "prop_manage_button")
obs.obs_property_set_description(mpb, "Edit Prepared List")
end
return true
end
-------------------------------------------------------------------------------------------------------------------------
-- Open_Song_CLICKED
-- Tries to open the selected song in the default OS text editor
-------------------------------------------------------------------------------------------------------------------------
function open_song_clicked(props, p)
local name = obs.obs_data_get_string(script_sets, "prop_directory_list")
if testValid(name) then
path = get_song_file_path(name, ".txt")
else
path = get_song_file_path(enc(name), ".enc")
end
if windows_os then
os.execute('explorer "' .. path .. '"')
else
os.execute('xdg-open "' .. path .. '"')
end
return true
end
-------------------------------------------------------------------------------------------------------------------------
-- OPEN SONGS FOLDER
-- Opens the folder where song files are stored
-------------------------------------------------------------------------------------------------------------------------
function open_button_clicked(props, p)
local path = get_songs_folder_path()
if windows_os then
os.execute('explorer "' .. path .. '"')
else
os.execute('xdg-open "' .. path .. '"')
end
end
-------------------------------------------------------------------------------------------------------------------------
-- READ SOURCE OPACITIES
-- Reads the current source opacity levels
-------------------------------------------------------------------------------------------------------------------------
function read_source_opacity_clicked(props, p)
dbg_method("read_opacities_clicked")
read_source_opacity()
return true
end
--------------------------------------------------------------------------------------------------------------------------
-- Callback to add an extra linked source to the linked sources list.
-- Source must be text source, or have 'Color Correction' Filter applied
------------------------------------------------------------------------------------------------------------------------
function link_source_selected(props, prop, settings)
dbg_method("link_source_selected")
local extra_source = obs.obs_data_get_string(settings, "extra_source_list")
if extra_source ~= nil and extra_source ~= "" then
local extra_linked_list = obs.obs_properties_get(props, "extra_linked_list")
obs.obs_property_list_add_string(extra_linked_list, extra_source, extra_source)
obs.obs_data_set_string(script_sets, "extra_linked_list", extra_source)
obs.obs_data_set_string(script_sets, "extra_source_list", "")
obs.obs_property_set_description(
extra_linked_list,
"<font color=#FFD966>Linked Sources (" .. obs.obs_property_list_item_count(extra_linked_list) .. ")</font>"
)
end
return true
end
-----------------------------------------------------------------------------------------------------------------------
-- DO LINKED Option (Progressive disclosure)
-- Shows list to allow linking extra sources
------------------------------------------------------------------------------------------------------------------------
function do_linked_clicked(props, p)
dbg_method("do_link_clicked")
obs.obs_property_set_visible(obs.obs_properties_get(props, "xtr_grp"), true)
obs.obs_property_set_visible(obs.obs_properties_get(props, "do_link_button"), false)
obs.obs_properties_apply_settings(props, script_sets)
return true
end
-----------------------------------------------------------------------------------------------------------------------
-- CLEAR LINKED Option
-- Removes all additionally linked Show/Hide sources and hides Link Options in UI
------------------------------------------------------------------------------------------------------------------------
function clear_linked_clicked(props, p)
dbg_method("clear_linked_clicked")
local extra_linked_list = obs.obs_properties_get(props, "extra_linked_list")
obs.obs_property_list_clear(extra_linked_list)
obs.obs_property_set_visible(obs.obs_properties_get(props, "xtr_grp"), false)
obs.obs_property_set_visible(obs.obs_properties_get(props, "do_link_button"), true)
obs.obs_property_set_description(extra_linked_list, "<font color=#FFD966>Linked Sources</font>")
return true
end
---
--- Progressive Disclosure functions for UI
---
-----------------------------------------------------------------------------------------------------------------------
-- Working function to return UP or Down pointing arrows
------------------------------------------------------------------------------------------------------------------------
function vMode(vis)
return expandcollapse and "▲- HIDE " or "▼- SHOW ", expandcollapse and "-▲" or "-▼"
end
-----------------------------------------------------------------------------------------------------------------------
-- Function to Expand/Rollup all groups in the UI
------------------------------------------------------------------------------------------------------------------------
function expand_all_groups(props, prop, settings)
expandcollapse = not expandcollapse
obs.obs_property_set_visible(obs.obs_properties_get(script_props, "info_grp"), expandcollapse)
obs.obs_property_set_visible(obs.obs_properties_get(script_props, "mng_grp"), expandcollapse)
obs.obs_property_set_visible(obs.obs_properties_get(script_props, "disp_grp"), expandcollapse)
obs.obs_property_set_visible(obs.obs_properties_get(script_props, "src_grp"), expandcollapse)
obs.obs_property_set_visible(obs.obs_properties_get(script_props, "ctrl_grp"), expandcollapse)
local mode1, mode2 = vMode(expandecollapse)
obs.obs_property_set_description(obs.obs_properties_get(props, "expand_all_button"), mode1 .. "ALL GROUPS" .. mode2)
obs.obs_property_set_description(
obs.obs_properties_get(props, "info_showing"),
mode1 .. "SONG INFORMATION" .. mode2
)
obs.obs_property_set_description(
obs.obs_properties_get(props, "prepared_showing"),
mode1 .. "PREPARED SONGS" .. mode2
)
obs.obs_property_set_description(
obs.obs_properties_get(props, "options_showing"),
mode1 .. "DISPLAY OPTIONS" .. mode2
)
obs.obs_property_set_description(
obs.obs_properties_get(props, "src_showing"),
mode1 .. "SOURCE TEXT SELECTIONS" .. mode2
)
obs.obs_property_set_description(obs.obs_properties_get(props, "ctrl_showing"), mode1 .. "LYRIC CONTROLS" .. mode2)
return true
end
-----------------------------------------------------------------------------------------------------------------------
-- Function tests to see of all Groups are expanded or contracted
------------------------------------------------------------------------------------------------------------------------
function all_vis_equal(props)
if
(obs.obs_property_visible(obs.obs_properties_get(script_props, "info_grp")) and
obs.obs_property_visible(obs.obs_properties_get(script_props, "prep_grp")) and
obs.obs_property_visible(obs.obs_properties_get(script_props, "disp_grp")) and
obs.obs_property_visible(obs.obs_properties_get(script_props, "src_grp")) and
obs.obs_property_visible(obs.obs_properties_get(script_props, "ctrl_grp"))) or
not (obs.obs_property_visible(obs.obs_properties_get(script_props, "info_grp")) or
obs.obs_property_visible(obs.obs_properties_get(script_props, "mng_grp")) or
obs.obs_property_visible(obs.obs_properties_get(script_props, "disp_grp")) or
obs.obs_property_visible(obs.obs_properties_get(script_props, "src_grp")) or
obs.obs_property_visible(obs.obs_properties_get(script_props, "ctrl_grp")))
then
expandcollapse = not expandcollapse
local mode1, mode2 = vMode(expandecollapse)
obs.obs_property_set_description(
obs.obs_properties_get(props, "expand_all_button"),
mode1 .. "ALL GROUPS" .. mode2
)
end
end
-----------------------------------------------------------------------------------------------------------------------
-- Change state of the INFO Group
------------------------------------------------------------------------------------------------------------------------
function change_info_visible(props, prop, settings)
local pp = obs.obs_properties_get(script_props, "info_grp")
local vis = not obs.obs_property_visible(pp)
obs.obs_property_set_visible(pp, vis)
local mode1, mode2 = vMode(vis)
obs.obs_property_set_description(
obs.obs_properties_get(props, "info_showing"),
mode1 .. "SONG INFORMATION" .. mode2
)
all_vis_equal(props)
return true
end
-----------------------------------------------------------------------------------------------------------------------
-- Change state of the Prepared Group
------------------------------------------------------------------------------------------------------------------------
function change_prepared_visible(props, prop, settings)
local pp = obs.obs_properties_get(script_props, "mng_grp")
local vis = not obs.obs_property_visible(pp)
obs.obs_property_set_visible(pp, vis)
local mode1, mode2 = vMode(vis)
obs.obs_property_set_description(
obs.obs_properties_get(props, "prepared_showing"),
mode1 .. "PREPARED SONGS" .. mode2
)
all_vis_equal(props)
return true
end
-----------------------------------------------------------------------------------------------------------------------
-- Change state of the Options Group
------------------------------------------------------------------------------------------------------------------------
function change_options_visible(props, prop, settings)
local pp = obs.obs_properties_get(script_props, "disp_grp")
local vis = not obs.obs_property_visible(pp)
obs.obs_property_set_visible(pp, vis)
local mode1, mode2 = vMode(vis)
obs.obs_property_set_description(
obs.obs_properties_get(props, "options_showing"),
mode1 .. "DISPLAY OPTIONS" .. mode2
)
all_vis_equal(props)
return true
end
-----------------------------------------------------------------------------------------------------------------------
-- Change state of the Source Group
------------------------------------------------------------------------------------------------------------------------
function change_src_visible(props, prop, settings)
local pp = obs.obs_properties_get(script_props, "src_grp")
local vis = not obs.obs_property_visible(pp)
obs.obs_property_set_visible(pp, vis)
local mode1, mode2 = vMode(vis)
obs.obs_property_set_description(
obs.obs_properties_get(props, "src_showing"),
mode1 .. "SOURCE TEXT SELECTIONS" .. mode2
)
all_vis_equal(props)
return true
end
-----------------------------------------------------------------------------------------------------------------------
-- Change state of the Control Group
------------------------------------------------------------------------------------------------------------------------
function change_ctrl_visible(props, prop, settings)
local pp = obs.obs_properties_get(script_props, "ctrl_grp")
local vis = not obs.obs_property_visible(pp)
obs.obs_property_set_visible(pp, vis)
local mode1, mode2 = vMode(vis)
obs.obs_property_set_description(obs.obs_properties_get(props, "ctrl_showing"), mode1 .. "LYRIC CONTROLS" .. mode2)
all_vis_equal(props)
return true
end
-----------------------------------------------------------------------------------------------------------------------
-- Change state of the Fade Option
-- Fading enables or disables a number of other options like 0-100% and if backgrounds are faded
------------------------------------------------------------------------------------------------------------------------
function change_fade_property(props, prop, settings)
local text_fade_set = obs.obs_data_get_bool(settings, "text_fade_enabled")
obs.obs_property_set_visible(obs.obs_properties_get(props, "text_fade_speed"), text_fade_set)
obs.obs_property_set_visible(obs.obs_properties_get(props, "use100percent"), text_fade_set)
obs.obs_property_set_visible(obs.obs_properties_get(props, "allowBackFade"), text_fade_set)
obs.obs_property_set_visible(obs.obs_properties_get(props, "fade_text_back"), text_fade_set and allow_back_fade)
obs.obs_property_set_visible(obs.obs_properties_get(props, "fade_alternate_back"), text_fade_set and allow_back_fade)
obs.obs_property_set_visible(obs.obs_properties_get(props, "fade_title_back"), text_fade_set and allow_back_fade)
obs.obs_property_set_visible(obs.obs_properties_get(props, "fade_extra_back"), text_fade_set and allow_back_fade)
obs.obs_property_set_visible(obs.obs_properties_get(props, "fade_static_back"), text_fade_set and allow_back_fade)
obs.obs_property_set_visible(obs.obs_properties_get(props, "refreshOP"), text_fade_enabled and not use100percent)
local transition_set_prop = obs.obs_properties_get(props, "transition_enabled")
obs.obs_property_set_enabled(transition_set_prop, not text_fade_set)
return true
end
-----------------------------------------------------------------------------------------------------------------------
-- Change state of the 0-100% option
-----------------------------------------------------------------------------------------------------------------------
function change_100percent_property(props, prop, settings)
use100percent = obs.obs_data_get_bool(settings, "use100percent")
obs.obs_property_set_visible(obs.obs_properties_get(props, "refreshOP"), not use100percent)
return true
end
-----------------------------------------------------------------------------------------------------------------------
-- Change background fading options
-----------------------------------------------------------------------------------------------------------------------
function change_back_fade_property(props, prop, settings)
allow_back_fade = obs.obs_data_get_bool(settings, "allowBackFade")
if allow_back_fade then
obs.obs_property_set_visible(obs.obs_properties_get(props, "fade_text_back"), text_fade_enabled)
obs.obs_property_set_visible(obs.obs_properties_get(props, "fade_alternate_back"), text_fade_enabled)
obs.obs_property_set_visible(obs.obs_properties_get(props, "fade_title_back"), text_fade_enabled)
obs.obs_property_set_visible(obs.obs_properties_get(props, "fade_extra_back"), text_fade_enabled)
obs.obs_property_set_visible(obs.obs_properties_get(props, "fade_static_back"), text_fade_enabled)
else
obs.obs_property_set_visible(obs.obs_properties_get(props, "fade_text_back"), false)
obs.obs_property_set_visible(obs.obs_properties_get(props, "fade_alternate_back"), false)
obs.obs_property_set_visible(obs.obs_properties_get(props, "fade_title_back"), false)
obs.obs_property_set_visible(obs.obs_properties_get(props, "fade_extra_back"), false)
obs.obs_property_set_visible(obs.obs_properties_get(props, "fade_static_back"), false)
end
return true
end
-----------------------------------------------------------------------------------------------------------------------
-- Show/Hide the Help Button Text
-----------------------------------------------------------------------------------------------------------------------
function show_help_button(props, prop, settings)
dbg_method("show help")
local hb = obs.obs_properties_get(props, "show_help_button")
showhelp = not showhelp
if showhelp then
obs.obs_property_set_description(hb, help)
else
obs.obs_property_set_description(hb, "SHOW MARKUP SYNTAX HELP")
end
return true
end
-----------------------------------------------------------------------------------------------------------------------
-- Allow specifying meta tags for filtering songs
-----------------------------------------------------------------------------------------------------------------------
function filter_songs_clicked(props, p)
local pp = obs.obs_properties_get(props, "meta")
if not obs.obs_property_visible(pp) then
obs.obs_property_set_visible(pp, true)
local mpb = obs.obs_properties_get(props, "filter_songs_button")